CosmoIknosLab / NuLite

NuLite - Lightweight and Fast Model for Nuclei Instance Segmentation and Classification
Apache License 2.0
13 stars 0 forks source link

Wrong Coordinates Offset Cause by rescaling_factor and downsample #4

Open name-used opened 1 month ago

name-used commented 1 month ago

There discovers some related issues.

  1. Naming mix-ups. nuclei_detection.inference.nuclei_detection: line 231 -- wsi_scaling_factor = wsi.metadata["downsampling"] The variable 'downsampling' comes from 'self.config.downsample' in preprocessing at preprocessing.patch_extraction.src.patch_extraction: line628-638 -- target_*_to_downsample In the same lines, there is another variable names 'self.rescaling_factor'. It seems like that rescaling_factor is a relative zoom between level-scale and target-scale, while downsample is the defination of level-scale. Actually, downsample is defined as a int, and rescaling_factor is defined as a float. So the code rescaling_factor = downsample in inference-process causes confusion .

  2. Asynchronous scaling. nuclei_detection.inference.nuclei_detection: line 233-240 -- xy_global = int(patch_size * n * wsi_scaling_factor - overlap * n) Both patch_size and overlap are used to calculate global position, but only one of them multiplied by wsi_scaling_factor(actually downsample). This will cause the prediction results to lose their meaning - they neither represent the original prediction results nor the results corresponding to the original image.

  3. Mistake of np.isclose preprocessing.patch_extraction.src.utils.patch_util: 167 -- is_close = np.isclose(exponent_fraction, nearest_integer, rtol=tolerance) The method np.isclose takes two params: rtol && atol. rtol means relative difference which multiplied by the target number like this: np.isclose(501, 500, rtot=0.1) # True --- 501 is 10% close to 500 atol means absolute difference like this: np.isclose(0.01, 0, atot=0.1) # True --- 0.01 is 0.1 close to 0 In this code block, the relative difference of target_mpp and base_mpp has been calculated by math.log operation, which may transform multiplication relationships into addition relationships. Observe this:

    target_mpp = 0.54
    base_mpp = 0.5
    # target_mpp / base_mpp = 1.08
    exponent_fraction = log2(target_mpp / base_mpp) # = 0.111
    nearest_integer = round(exponent_fraction) # = 0
    # guess what? 
    # 0 times anything is 0
    is_close = np.isclose(exponent_fraction, nearest_integer, rtol=tolerance)
    # is_close will always be False

    It actually should be atol instead.

  4. Missing preprocess info in metadata preprocessing.patch_extraction.src.utils.patch_util: 86 -- patch_to_tile_size When rescaling_factor is not 1.0 (why use '==' && '!=' on float, even the float is in fact a integer? And if rescaling_factor e.q. 1.0, nothing will be different when doing the following calculations), the patch_size will be scaled by rescaling_factor and rounded up with math.ceil. But the calculation here are not reliable. I tried to fix the positions with wrong outputed and found that the correct fix process should be in different logics here: Maybe np.ceil -> np.round; Maybe rescaling -> round(rescaling, 3); Maybe base_mpp -> round(base_mpp, 3) I have no idea what happened indeed. What I know is that some unknown small errors exhibit significant problems on a full image size of 100_000 pixels. THE TRUELY PROBLEM is that the values used in the preprocessing process, such as tile_size and rescaling_factor, are not recorded in the metadata. That means I can not restore the output-coordinates to the original image.