mahmoodlab / CLAM

Data-efficient and weakly supervised computational pathology on whole slide images - Nature Biomedical Engineering
http://clam.mahmoodlab.org
GNU General Public License v3.0
975 stars 329 forks source link

`custom_downsample` specification flipped for heatmaps? #219

Closed vipulnj closed 4 months ago

vipulnj commented 7 months ago

I am training a CLAM model which uses 256x256 patches at 20x magnification. My slides have max magnification of 40x. To obtain the 20x patches, I am patch them at 512x512 at 40x (patch_level=0) and then apply downsampling.

I am supplying these params to CLAM/extract_features_fp.py file

$pyExec -u $pyScript --data_slide_dir $DATA_DIR --data_h5_dir $RESULTS_DIR --custom_downsample 2 \
    --csv_path ${RESULTS_DIR}/process_list.csv --feat_dir $FEATURES_DIR --batch_size 64 --slide_ext .svs

The code where it downsamples happens is in the file CLAM/datasets/dataset_h5.py

with h5py.File(self.file_path, "r") as f:
    dset = f['coords']
    self.patch_level = f['coords'].attrs['patch_level']
    self.patch_size = f['coords'].attrs['patch_size']
    self.length = len(dset)
    if target_patch_size > 0:
        self.target_patch_size = (target_patch_size, ) * 2
    elif custom_downsample > 1:
        self.target_patch_size = (self.patch_size // custom_downsample, ) * 2
    else:
        self.target_patch_size = None

which suggests custom_downsample should be > 1, which in my case is, =2.

But, in CLAM/create_heatmaps.py, I see

wsi_ref_downsample = wsi_object.level_downsamples[patch_args.patch_level]
vis_patch_size = \
    tuple((np.array(patch_size) * np.array(wsi_ref_downsample) * patch_args.custom_downsample).astype(int))

which suggests that I need to specify patch_args.custom_downsample = 0.5, not 2 (like before).

I was expecting to pass the same value as before but the code seems to suggest otherwise. Am I correct in reading this?

fedshyvana commented 6 months ago

Hey @vipulnj, vis_patch_size is passed to the patch_size argument of visHeatmap function to generate the heatmap, if you read the doc strings here: https://github.com/mahmoodlab/CLAM/blob/26e0b6c4873e112f1ccd74cd834894c4ab7a2934/wsi_core/WholeSlideImage.py#L487 It actually refers to the patch size at level_0 (i.e. the highest level of mag).

So as an example, if your slide is 40x (level 0), you patch at 40x (level 0) at 512 x 512 ---> your patch size is 512, wsi_ref_downsample = 1. You then also use custom_downsample = 2 to end up with patches of size 256 x 256. So each one of them, should translate to a patch of size 256 1 2 = 512 at level 0, which is correct. This calculation is necessary so that we have a fixed frame of reference to work with and can therefore mix and match the patch size/magnification of patching/computation with the magnification at which to visualize the heatmap.

vipulnj commented 6 months ago

Thank you for responding!

So, I should just pass the target patch size i.e. the patchsize the model was trained, the patch_level to extract the patch from (enabled thru vis_level variable) and custom_downsample (a multiplier for target_patch_size to extract the patch at vis_level)?

So, current specification

patching_arguments:
    custom_downsample: 0.5
    overlap: 0.5
    patch_level: 0
    patch_size: 512

should be changed to

patching_arguments:
    custom_downsample: 2   # changed
    overlap: 0.5
    patch_level: 0
    patch_size: 256  # changed

Can you confirm if I am understanding you correctly?

As mentioned before, I have extracted 256x256 patches at 20x magnification from 40x slides by extracting 512x512 patches first and then specifying my target_patch_size as 256 while running the script CLAM/extract_features_fp.py.

fedshyvana commented 6 months ago

patching_arguments should match exactly the setting you used to train the model. So in this case, it would be set to custom_downsample: 2, patch_level: 0 and patch_size: 512 ---> this will mirror your workflow of extracting 512 x 512 patches at level 0 and then resizing each patch to 256 x 256.