Add transforms for Digital Pathology#100
Conversation
@shekhardw Please specify the absolute path to load the test data. (pytest is run in root of the repository so that relative paths are not available). How to reproduce |
gigony
left a comment
There was a problem hiding this comment.
Please check/auto-format using black/isort.
pip install black
python -m black python/cucim/src/cucim/core/ --check # check
python -m black python/cucim/src/cucim/core/ # autofix
pip install isort
python -m isort python/cucim/src/cucim/core/ --check # check
python -m isort python/cucim/src/cucim/core/ # autofix
python/cucim/src/cucim/core/operations/color/tests/test_color_jitter.py
Outdated
Show resolved
Hide resolved
grlee77
left a comment
There was a problem hiding this comment.
Thanks @shekhardw,
In general the kernels look nice and performance is great, but input validations, docstrings and test cases are very minimal.
For example, it is not always clear from the docstrings:
1.) which dtypes are supported
2.) whether RGB, RGBA and/or arbitrary number of "channels" are supported
3.) whether an additional "batch" dimension is supported.
I have made a number of suggestions, but it could probably use another pass after these are addressed.
I did not review the C++ code for all individual CUDA kernels, but the ones I looked at seemed good to me.
| from typing import Any, List, Optional, Tuple | ||
|
|
||
| import numpy as np | ||
| import scipy.ndimage as ndimage |
There was a problem hiding this comment.
Nitpick, but to be consistent with the cucim.skimage and upstream scikit-image, I would use
import scipy.ndimage as ndi| diff_im = np_output - arr | ||
| diff_total_value = np.abs(np.sum(diff_im)) | ||
|
|
||
| assert diff_total_value > 0 |
There was a problem hiding this comment.
This very minimal test coverage that only verifies that the kernels run without crashing and that some change to the image has occurred.
Why not place this in a named test function, though, so pytest would report it as a test case?
You could then add additional tests in separate functions. Additional simple tests would be:
- a test case with all factors set to 0, verifying that the image is unmodified in that case.
- a test with a batch image, e.g. using
cupy_arr_batch = cupy.stack((cupy_arr, ) * 8, axis=0). In this case since all elements in the batch are identical we could also verify that for the output. - a test verifying that providing
numpy.ndarrayinput results in anumpy.ndarrayoutput - same output with C or Fortran-ordered inputs
There was a problem hiding this comment.
Test cases included for all factors 0, batch image, cupy input, numpy input. Color jitter picks a random sequence to apply 4 operations. Test functions included in all test files.
python/cucim/src/cucim/core/operations/spatial/rotate_and_flip.py
Outdated
Show resolved
Hide resolved
python/cucim/src/cucim/core/operations/spatial/rotate_and_flip.py
Outdated
Show resolved
Hide resolved
python/cucim/src/cucim/core/operations/spatial/rotate_and_flip.py
Outdated
Show resolved
Hide resolved
| R = np.random.RandomState() | ||
|
|
||
| if R.rand() < prob: | ||
| return image_rotate_90(img, spatial_axis) |
There was a problem hiding this comment.
| return image_rotate_90(img, spatial_axis) | |
| return image_flip(img, spatial_axis) |
wrong function!
|
|
||
| import cucim.core.operations.spatial as spt | ||
|
|
||
| img = Image.open(os.path.join(os.path.abspath(os.path.dirname(__file__)), "1.png")) |
There was a problem hiding this comment.
What is the source of this dice image? (i.e. is it public domain?)
There was a problem hiding this comment.
If there is any rights issue it should be easy enough to just use one of the images bundled with scikit-image.
e.g. for an RGB image:
skimage.data.astronaut() (will need to transpose dimensions to get the color axis from last to first)
There was a problem hiding this comment.
test image updated to use astronaut image from scikit-image. Additional test cases included.
gigony
left a comment
There was a problem hiding this comment.
Thanks @grlee77 for the review! @shekhardw would address your comments.
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| from cucim.core.operations.color import color_jitter as color_jitter |
There was a problem hiding this comment.
Thanks @grlee77 . Let me chime in this question.
We didn't decide what/how to expose APIs from cucim.core.operations and it looks like MONAI's transforms require APIs in a specific form and prone to change.
For this reason, Shekhar and I decided to use cucim.core.operations.expose.transform for exposing APIs needed by MONAI transforms, without messing up cucim.core.operations.xxx namespaces.
We need to discuss how to use cucim.core.operations namespace in detail, as a group, in the next cuCIM bi-weekly meeting.
| return arr_o | ||
|
|
||
| def test_rand_flip_numpy_input(): | ||
|
|
| assert np.allclose(output,flip_arr) | ||
|
|
||
| def test_rand_flip_zero_prob(): | ||
|
|
There was a problem hiding this comment.
nit: blank line
please check other parts of test methods that start with a blank line.
There was a problem hiding this comment.
corrected and all test functions checked.
|
rerun tests |
grlee77
left a comment
There was a problem hiding this comment.
Thanks for addressing the comments @shekhardw. The tests are looking a lot better now and can always be expanded further as needed if issues crop up.
Thanks for also adding the dimension info to the docstrings.
Out of curiosity, what is the source of the 1.png image used in the color tests? (it appears as somewhat random colors). Is there a particular reason for testing with this image?
| # limitations under the License. | ||
|
|
||
| from cucim.core.operations.color import color_jitter as color_jitter | ||
| from cucim.core.operations.intensity import rand_zoom as rand_zoom |
There was a problem hiding this comment.
| from cucim.core.operations.intensity import rand_zoom as rand_zoom | |
| from cucim.core.operations.intensity import rand_zoom |
There was a problem hiding this comment.
@grlee77 1.png was created out of random array. I will change it with astronaut image (to be consistent).
grlee77
left a comment
There was a problem hiding this comment.
Please also change import skimage to import skimage.data in the tests as suggested in the recently added comments. I was surprised the former works and I think it may not work across all older scikit-image versions!
It happens to work because there is a from .data import data_dir in skimage.__init__ which apparently loads data into the skimage namespace (see here). It is safer not to rely on this incidental import of data and just import it explicitly.
python/cucim/src/cucim/core/operations/color/tests/test_color_jitter.py
Outdated
Show resolved
Hide resolved
python/cucim/src/cucim/core/operations/intensity/tests/test_rand_zoom.py
Outdated
Show resolved
Hide resolved
python/cucim/src/cucim/core/operations/intensity/tests/test_scaling.py
Outdated
Show resolved
Hide resolved
python/cucim/src/cucim/core/operations/intensity/tests/test_zoom.py
Outdated
Show resolved
Hide resolved
python/cucim/src/cucim/core/operations/spatial/tests/test_flip.py
Outdated
Show resolved
Hide resolved
python/cucim/src/cucim/core/operations/spatial/tests/test_random_flip.py
Outdated
Show resolved
Hide resolved
|
Thanks @grlee77 test cases updated. |
jakirkham
left a comment
There was a problem hiding this comment.
Thanks Shekhar! 😄
Had a couple questions/suggestion below. For simplicity and clearer discussion focused only on one case of the pattern for discussion, but the same could be applied in other places where that pattern is used
| def get_params(brightness: Optional[List[float]], | ||
| contrast: Optional[List[float]], | ||
| saturation: Optional[List[float]], | ||
| hue: Optional[List[float]] | ||
| ) -> Tuple[np.ndarray, Optional[float], | ||
| Optional[float], Optional[float], | ||
| Optional[float]]: |
There was a problem hiding this comment.
Is it necessary for these to be closures? Or could we define module level functions for these tasks?
There was a problem hiding this comment.
I propose to fix this in a separate MR.
| _logger.error("[cucim] " + str(e), exc_info=True) | ||
| _logger.info("Error executing color jitter on GPU") | ||
| raise |
There was a problem hiding this comment.
Do we want to handle logging within cuCIM? Typically this is the sort of thing that would be configured in a user application (unless for some reason the exception would never be seen)
There was a problem hiding this comment.
I propose to fix this in a separate MR.
| extern "C" { | ||
| __global__ void brightnessjitter_kernel(unsigned char *input_rgb, \ |
There was a problem hiding this comment.
Would it make sense to put these in C file? Might make this is easier to read/lint/test/etc. and also use directly if desired. From the Python side we could always load the file into a str for use as needed
There was a problem hiding this comment.
I propose to fix this in a separate MR.
python/cucim/src/cucim/core/operations/color/tests/test_color_jitter.py
Outdated
Show resolved
Hide resolved
python/cucim/src/cucim/core/operations/expose/tests/test_expose.py
Outdated
Show resolved
Hide resolved
| def test_exposed_transforms(): | ||
| assert color_jitter is not None | ||
| assert image_flip is not None | ||
| assert image_rotate_90 is not None | ||
| assert scale_intensity is not None | ||
| assert zoom is not None | ||
| assert rand_zoom is not None | ||
| assert rand_image_flip is not None | ||
| assert rand_image_rotate_90 is not None |
There was a problem hiding this comment.
Is the goal just to test that these just exist or use them in some way?
There was a problem hiding this comment.
just the existence for exposing. Each of the transforms has its own set of test cases.
|
@jakirkham Thanks for your valuable feedback. I have updated expose related changes. |
|
Thanks Shekhar! 😄 There's a couple comments above that may have been missed. Bumped them above. Please let me know your thoughts on those |
|
Hi @jakirkham, I propose to address the 3 remaining commments on closure, logging and cuda kernel in a separate MR. Are you fine with that? |
|
In that case could you please raise an issue tracking these? |
Co-authored-by: Gregory R. Lee <grlee77@gmail.com>
…aling.py Co-authored-by: Gregory R. Lee <grlee77@gmail.com>
Co-authored-by: Gregory R. Lee <grlee77@gmail.com>
…om_flip.py Co-authored-by: Gregory R. Lee <grlee77@gmail.com>
…om.py Co-authored-by: Gregory R. Lee <grlee77@gmail.com>
…nd_zoom.py Co-authored-by: Gregory R. Lee <grlee77@gmail.com>
…jitter.py Co-authored-by: Gregory R. Lee <grlee77@gmail.com>
…e.py Co-authored-by: jakirkham <jakirkham@gmail.com>
…jitter.py Co-authored-by: jakirkham <jakirkham@gmail.com>
bdfd569 to
c77b8f8
Compare
./src/cucim/core/operations/expose/transform.py:18:1: F40 'cucim.core.operations.spatial.rand_image_rotate_90' imported but unused
|
@gpucibot merge |
|
Thanks Shekhar for the PR and Gigon and Greg for the reviews! 😄 |
closes #114 Removes the try/except wrappers with logging of errors from the digital pathology transforms added in #100. Also renames a variable `to_cupy` to `to_numpy` instead to reflect its actual function. **For reviewers**: There appear to be many lines changed here, but it is mostly a change in indentation due to removal of `try/except` blocks with `_logger` calls. Also some nested function definitions within `color_jitter` were moved outside of that function. Authors: - Gregory Lee (https://github.com/grlee77) Approvers: - Gigon Bae (https://github.com/gigony) - https://github.com/jakirkham
Implement transforms for color jitter, image scale intensity, image flip and rotate included for batched RGB images.
This will be used exclusively for digital pathology training pipelines.
closes #105