Skip to content

Commit

Permalink
Merge branch 'master' into kk/control_randomness
Browse files Browse the repository at this point in the history
  • Loading branch information
kanishk16 committed Jul 18, 2022
2 parents ec4d4f8 + 9e5b0bf commit 2671e8d
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 19 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ jobs:
steps:

- name: Checkout repo
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Setup Python
uses: actions/setup-python@v2
uses: actions/setup-python@v3
with:
python-version: 3.8

- name: Cache pip dependencies
id: cache-pip-dependencies
uses: actions/cache@v2
uses: actions/cache@v3
with:
# Ubuntu-specific, see
# https://github.com/actions/cache/blob/main/examples.md#python---pip
Expand All @@ -36,7 +36,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .[dev]
pip install 'pre-commit>=2.10.1'
shell: bash

- name: Run pre-commit large file check
Expand Down
7 changes: 7 additions & 0 deletions docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,10 @@ Note that both path CLI flags are optional and can be specified instead via the
If not set via CLI, then you MUST specify this field in the configuration file.

Please see section ``TUTORIALS`` to run this command on an example dataset.

Additional optional flags with ``--segment`` command for models trained with 2D patches:

``--no-patch``: 2D patches are not used while segmenting with models trained with patches. The ``--no-patch`` flag supersedes the
``--overlap-2d`` flag. This option may not be suitable with large images depending on computer RAM capacity.

``--overlap-2d``: Custom overlap for 2D patches while segmenting. Example: ``--overlap-2d 48 48`` for an overlap of 48 pixels between patches in X and Y respectively. Default model overlap is used otherwise.
41 changes: 32 additions & 9 deletions ivadomed/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,9 @@ def segment_volume(folder_model: str, fname_images: list, gpu_id: int = 0, optio
Length equals 2 [PixelSizeX, PixelSizeY] for 2D or 3 [PixelSizeX, PixelSizeY, PixelSizeZ] for 3D, \
where X is the width, Y the height and Z the depth of the image.
* 'pixel_size_units': (str) Units of pixel size (Must be either "mm", "um" or "nm")
* 'no_patch': (bool) 2D patches are not used while segmenting with models trained with patches. \
The "no_patch" option supersedes the "overlap_2D" option. \
This option may not be suitable with large images depending on computer RAM capacity.
* 'overlap_2D': (list of int) List of overlaps in pixels for 2D patching. Length equals 2 [OverlapX, OverlapY], \
where X is the width and Y the height of the image.
* 'metadata': (str) Film metadata.
Expand Down Expand Up @@ -411,15 +414,35 @@ def segment_volume(folder_model: str, fname_images: list, gpu_id: int = 0, optio
ModelParamsKW.LENGTH_2D in context[ConfigKW.DEFAULT_MODEL] else []
stride_2D = context[ConfigKW.DEFAULT_MODEL][ModelParamsKW.STRIDE_2D] if \
ModelParamsKW.STRIDE_2D in context[ConfigKW.DEFAULT_MODEL] else []
is_2d_patch = bool(length_2D)

if is_2d_patch and (options is not None) and (OptionKW.OVERLAP_2D in options):
overlap_2D = options.get(OptionKW.OVERLAP_2D)
# Swap OverlapX and OverlapY resulting in an array in order [OverlapY, OverlapX]
# to match length_2D and stride_2D in [Height, Width] orientation.
overlap_2D[1], overlap_2D[0] = overlap_2D[0], overlap_2D[1]
# Adjust stride_2D with overlap_2D
stride_2D = [x1 - x2 for (x1, x2) in zip(length_2D, overlap_2D)]
is_2d_patch = bool(length_2D)
if (options is not None) and (OptionKW.NO_PATCH in options):
if is_2d_patch:
is_2d_patch = not options.get(OptionKW.NO_PATCH)
length_2D = []
stride_2D = []
else:
logger.warning(f"The 'no_patch' option is provided but the model has no 'length_2D' and "
f"'stride_2D' parameters in its configuration file "
f"'{fname_model_metadata.split('/')[-1]}'. 2D patching is ignored, the segmentation "
f"'is done on the entire image without patches.")
if OptionKW.OVERLAP_2D in options:
logger.warning(f"The 'no_patch' option is provided along with the 'overlap_2D' option. "
f"2D patching is ignored, the segmentation is done on the entire image without patches.")
else:
if (options is not None) and (OptionKW.OVERLAP_2D in options):
if (length_2D and stride_2D):
overlap_2D = options.get(OptionKW.OVERLAP_2D)
# Swap OverlapX and OverlapY resulting in an array in order [OverlapY, OverlapX]
# to match length_2D and stride_2D in [Height, Width] orientation.
overlap_2D[1], overlap_2D[0] = overlap_2D[0], overlap_2D[1]
# Adjust stride_2D with overlap_2D
stride_2D = [x1 - x2 for (x1, x2) in zip(length_2D, overlap_2D)]
else:
logger.warning(f"The 'overlap_2D' option is provided but the model has no 'length_2D' and "
f"'stride_2D' parameters in its configuration file "
f"'{fname_model_metadata.split('/')[-1]}'. 2D patching is ignored, the segmentation "
f"is done on the entire image without patches.")

# Add microscopy pixel size and pixel size units from options to metadata for filenames_pairs
if (options is not None) and (OptionKW.PIXEL_SIZE in options):
Expand Down Expand Up @@ -516,7 +539,7 @@ def reconstruct_3d_object(context: dict, batch: dict, undo_transforms: UndoCompo
preds (tensor): Subvolume predictions
preds_list (list of tensor): list of subvolume predictions.
kernel_3D (bool): true when using 3D kernel.
is_2d_patch (bool): True if length in default model params.
is_2d_patch (bool): Indicates if 2d patching is used.
slice_axis (int): Indicates the axis used for the 2D slice extraction: Sagittal: 0, Coronal: 1, Axial: 2.
slice_idx_list (list of int): list of indices for the axis slices.
data_loader (DataLoader): DataLoader object containing batches using in object construction.
Expand Down
1 change: 1 addition & 0 deletions ivadomed/keywords.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ class OptionKW:
OVERLAP_2D: str = "overlap_2D"
PIXEL_SIZE: str = "pixel_size"
PIXEL_SIZE_UNITS: str = "pixel_size_units"
NO_PATCH: str = "no_patch"


@dataclass
Expand Down
34 changes: 28 additions & 6 deletions ivadomed/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ def get_parser():
optional_args.add_argument('--resume-training', dest="resume_training", required=False, action='store_true',
help='Load a saved model ("checkpoint.pth.tar" in the output directory specified either with flag "--path-output" or via the config file "output_path" argument) '
'for resume training. This training state is saved everytime a new best model is saved in the output directory specified with flag "--path-output"')
optional_args.add_argument('--no-patch', dest="no_patch", action='store_true', required=False,
help='2D patches are not used while segmenting with models trained with patches '
'(command "--segment" only). The "--no-patch" argument supersedes the "--overlap-2D" argument. '
' This option may not be suitable with large images depending on computer RAM capacity.')
optional_args.add_argument('--overlap-2d', dest="overlap_2d", required=False, type=int, nargs="+",
help='Custom overlap for 2D patches while segmenting (command "--segment" only). '
'Example: "--overlap-2d 48 48" for an overlap of 48 pixels between patches in X and Y respectively. '
'Default model overlap is used otherwise.')
optional_args.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
help='Shows function documentation.')

Expand Down Expand Up @@ -221,7 +229,7 @@ def update_film_model_params(context, ds_test, model_params, path_output):
return ds_test, model_params


def run_segment_command(context, model_params):
def run_segment_command(context, model_params, no_patch, overlap_2d):
# BIDSDataframe of all image files
# Indexing of derivatives is False for command segment
# split_method is unused for command segment
Expand Down Expand Up @@ -289,6 +297,12 @@ def run_segment_command(context, model_params):
options[OptionKW.PIXEL_SIZE_UNITS] = \
bids_df.df.loc[bids_df.df['filename'] == subject][MetadataKW.PIXEL_SIZE_UNITS].values[0]

# Add 'no_patch' and 'overlap-2d' argument to options
if no_patch:
options[OptionKW.NO_PATCH] = no_patch
if overlap_2d:
options[OptionKW.OVERLAP_2D] = overlap_2d

if fname_img:
pred_list, target_list = imed_inference.segment_volume(str(path_model),
fname_images=fname_img,
Expand All @@ -314,7 +328,7 @@ def run_segment_command(context, model_params):
suffix="_pred.png")


def run_command(context, n_gif=0, thr_increment=None, resume_training=False):
def run_command(context, n_gif=0, thr_increment=None, resume_training=False, no_patch=False, overlap_2d=None):
"""Run main command.
This function is central in the ivadomed project as training / testing / evaluation commands
Expand All @@ -329,8 +343,14 @@ def run_command(context, n_gif=0, thr_increment=None, resume_training=False):
thr_increment (float): A threshold analysis is performed at the end of the training using the trained model and
the training + validation sub-dataset to find the optimal binarization threshold. The specified value
indicates the increment between 0 and 1 used during the ROC analysis (e.g. 0.1).
resume_training (bool): Load a saved model ("checkpoint.pth.tar" in the output directory specified with flag "--path-output" or via the config file "output_path" ' This training state is saved everytime a new best model is saved in the log
argument) for resume training directory.
resume_training (bool): Load a saved model ("checkpoint.pth.tar" in the output directory specified with flag
"--path-output" or via the config file "output_path". This training state is saved everytime a new best
model is saved in the log argument) for resume training directory.
no_patch (bool): If True, 2D patches are not used while segmenting with models trained with patches
(command "--segment" only). Default: False (i.e. segment with patches). The "no_patch" option supersedes
the "overlap_2D" option.
overlap_2d (list of int): Custom overlap for 2D patches while segmenting (command "--segment" only).
Default model overlap is used otherwise.
Returns:
float or pandas.DataFrame or None:
Expand Down Expand Up @@ -369,7 +389,7 @@ def run_command(context, n_gif=0, thr_increment=None, resume_training=False):
model_params, loader_params = set_model_params(context, loader_params)

if command == 'segment':
run_segment_command(context, model_params)
run_segment_command(context, model_params, no_patch, overlap_2d)
return

# BIDSDataframe of all image files
Expand Down Expand Up @@ -602,7 +622,9 @@ def run_main():
run_command(context=context,
n_gif=args.gif if args.gif is not None else 0,
thr_increment=args.thr_increment if args.thr_increment else None,
resume_training=bool(args.resume_training))
resume_training=bool(args.resume_training),
no_patch=bool(args.no_patch),
overlap_2d=args.overlap_2d if args.overlap_2d else None)


if __name__ == "__main__":
Expand Down
64 changes: 64 additions & 0 deletions testing/functional_tests/test_segment_volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,70 @@ def test_segment_volume_2d_with_patches(download_functional_test_files, center_c
shutil.rmtree(PATH_MODEL)


@pytest.mark.parametrize("center_crop", [[200, 200]])
def test_segment_volume_2d_without_patches(download_functional_test_files, center_crop):
model = imed_models.Unet(in_channel=1,
out_channel=1,
depth=2,
dropout_rate=DROPOUT,
bn_momentum=BN)

if not PATH_MODEL.exists():
PATH_MODEL.mkdir(parents=True, exist_ok=True)

torch.save(model, Path(PATH_MODEL, "model_test.pt"))
config = {
"loader_parameters": {
"slice_filter_params": {
"filter_empty_mask": False,
"filter_empty_input": False
},
"patch_filter_params": {
"filter_empty_mask": False,
"filter_empty_input": False
},
"roi_params": {
"suffix": None,
"slice_filter_roi": None
},
"slice_axis": "axial"
},
"default_model": {
"length_2D": LENGTH_2D,
"stride_2D": LENGTH_2D
},
"transformation": {
"Resample": {"wspace": 0.75, "hspace": 0.75},
"CenterCrop": {"size": center_crop},
"RandomTranslation": {
"translate": [0.03, 0.03],
"applied_to": ["im", "gt"],
"dataset_type": ["training"]
},
"NormalizeInstance": {"applied_to": ["im"]}
},
"postprocessing": {},
"training_parameters": {
"batch_size": BATCH_SIZE
}
}

PATH_CONFIG = Path(PATH_MODEL, 'model_test.json')
with PATH_CONFIG.open(mode='w') as fp:
json.dump(config, fp)

options = {}
options['no_patch'] = True

nib_lst, _ = imed_inference.segment_volume(str(PATH_MODEL), [str(PATH_IMAGE)], options=options)
nib_img = nib_lst[0]
assert np.squeeze(nib_img.get_fdata()).shape == nib.load(PATH_IMAGE).shape
assert (nib_img.dataobj.max() <= 1.0) and (nib_img.dataobj.min() >= 0.0)
assert nib_img.dataobj.dtype == 'float32'

shutil.rmtree(PATH_MODEL)


@pytest.mark.parametrize("center_crop", [[192, 192, 16]])
def test_segment_volume_3d(download_functional_test_files, center_crop):
model = imed_models.Modified3DUNet(in_channel=1,
Expand Down

0 comments on commit 2671e8d

Please sign in to comment.