From f07fbed58b0d8c464c1c2725f82b3472cafede87 Mon Sep 17 00:00:00 2001 From: rnfinnegan Date: Wed, 17 Nov 2021 21:27:19 +0000 Subject: [PATCH 1/9] fixed masd/hd when one label mask is all zero --- platipy/imaging/label/comparison.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/platipy/imaging/label/comparison.py b/platipy/imaging/label/comparison.py index bb5cceb7..3762d3fe 100644 --- a/platipy/imaging/label/comparison.py +++ b/platipy/imaging/label/comparison.py @@ -18,6 +18,7 @@ from platipy.imaging.utils.crop import label_to_roi, crop_to_roi + def compute_volume(label): """Computes the volume in cubic centimetres @@ -232,6 +233,12 @@ def compute_metric_masd(label_a, label_b, auto_crop=True): Returns: float: The mean absolute surface distance """ + if ( + sitk.GetArrayViewFromImage(label_a).sum() == 0 + or sitk.GetArrayViewFromImage(label_b).sum() == 0 + ): + return -1 + if auto_crop: largest_region = (label_a + label_b) > 0 crop_box_size, crop_box_index = label_to_roi(largest_region) @@ -267,6 +274,11 @@ def compute_metric_hd(label_a, label_b, auto_crop=True): Returns: float: The maximum Hausdorff distance """ + if ( + sitk.GetArrayViewFromImage(label_a).sum() == 0 + or sitk.GetArrayViewFromImage(label_b).sum() == 0 + ): + return -1 if auto_crop: largest_region = (label_a + label_b) > 0 crop_box_size, crop_box_index = label_to_roi(largest_region) From b31b18ab6e10e63d238b9252c67e021abca21828 Mon Sep 17 00:00:00 2001 From: rnfinnegan Date: Wed, 17 Nov 2021 21:27:37 +0000 Subject: [PATCH 2/9] fixed encoding of label maps --- platipy/imaging/label/utils.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/platipy/imaging/label/utils.py b/platipy/imaging/label/utils.py index 5b6b36f5..35f8d64a 100644 --- a/platipy/imaging/label/utils.py +++ b/platipy/imaging/label/utils.py @@ -20,7 +20,7 @@ from platipy.imaging.utils.math import gen_primes -def correct_volume_overlap(binary_label_dict): +def correct_volume_overlap(binary_label_dict, assign_overlap_to_largest=True): """ Label structures by primes Smallest prime = largest volume @@ -31,26 +31,29 @@ def correct_volume_overlap(binary_label_dict): volume_dict = {i: f_vol(binary_label_dict[i]) for i in binary_label_dict.keys()} keys, vals = zip(*volume_dict.items()) - volume_rank = np.argsort(vals)[::-1] + if assign_overlap_to_largest: + volume_rank = np.argsort(vals)[::-1] + else: + volume_rank = np.argsort(vals) # print(keys, volume_rank) ranked_names = np.array(keys)[volume_rank] - # Get overlap using prime factors - prime_labelled_image = sum(binary_label_dict.values()) > 0 + # Get overlap (this is used to reconstruct labels) combined_label = sum(binary_label_dict.values()) > 0 - for p, label in zip(gen_primes(), ranked_names): - prime_labelled_image = prime_labelled_image * ( - (p - 1) * binary_label_dict[label] + combined_label - ) + # Prime encode each binary label + prime_labelled_image = prime_encode_structure_list( + [binary_label_dict[i] for i in ranked_names] + ) + # Remove overlap (by assigning to binary volume) output_label_dict = {} for p, label in zip(gen_primes(), ranked_names): output_label_dict[label] = combined_label * (sitk.Modulus(prime_labelled_image, p) == 0) - combined_label = sitk.Mask(combined_label, output_label_dict[label] == 0) + combined_label = sitk.MaskNegated(combined_label, output_label_dict[label]) return output_label_dict @@ -155,6 +158,7 @@ def prime_encode_structure_list(structure_list): img_size = structure_list[0].GetSize() prime_encoded_image = sitk.GetImageFromArray(np.ones(img_size[::-1])) prime_encoded_image = sitk.Cast(prime_encoded_image, sitk.sitkUInt64) + prime_encoded_image.CopyInformation(structure_list[0]) prime_generator = generate_primes() @@ -162,7 +166,6 @@ def prime_encode_structure_list(structure_list): # Cast to int s_img_int = sitk.Cast(s_img > 0, sitk.sitkUInt64) - print(prime) # Multiply with the encoded image prime_encoded_image = ( sitk.MaskNegated(prime_encoded_image, s_img_int) From a7e6cdc432f0df90f38496d22d4bff8700583009 Mon Sep 17 00:00:00 2001 From: rnfinnegan Date: Tue, 11 Jan 2022 01:33:31 +0000 Subject: [PATCH 3/9] updated default params --- platipy/imaging/projects/cardiac/run.py | 9 ++++----- platipy/imaging/projects/multiatlas/run.py | 23 ++++++++++++---------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/platipy/imaging/projects/cardiac/run.py b/platipy/imaging/projects/cardiac/run.py index ff51b357..790c2921 100644 --- a/platipy/imaging/projects/cardiac/run.py +++ b/platipy/imaging/projects/cardiac/run.py @@ -129,12 +129,11 @@ "isotropic_resample": True, "resolution_staging": [ 6, - 4, - 2, - 1, + 3, + 1.5, ], # specify voxel size (mm) since isotropic_resample is set - "iteration_staging": [200, 150, 125, 100], - "smoothing_sigmas": [0, 0, 0, 0], + "iteration_staging": [200, 150, 100], + "smoothing_sigmas": [0, 0, 0], "ncores": 8, "default_value": 0, "verbose": False, diff --git a/platipy/imaging/projects/multiatlas/run.py b/platipy/imaging/projects/multiatlas/run.py index 71389c31..28bb0f55 100644 --- a/platipy/imaging/projects/multiatlas/run.py +++ b/platipy/imaging/projects/multiatlas/run.py @@ -19,7 +19,7 @@ from loguru import logger -from platipy.imaging.registration.utils import apply_transform, convert_mask_to_reg_structure +from platipy.imaging.registration.utils import apply_transform from platipy.imaging.registration.linear import ( linear_registration, @@ -37,7 +37,7 @@ from platipy.imaging.utils.crop import label_to_roi, crop_to_roi -from platipy.imaging.label.utils import binary_encode_structure_list, correct_volume_overlap +from platipy.imaging.label.utils import correct_volume_overlap ATLAS_PATH = "/atlas" if "ATLAS_PATH" in os.environ: @@ -65,7 +65,7 @@ "shrink_factors": [16, 8, 4], "smooth_sigmas": [0, 0, 0], "sampling_rate": 0.75, - "default_value": -1000, + "default_value": None, "number_of_iterations": 50, "metric": "mean_squares", "optimiser": "gradient_descent_line_search", @@ -75,14 +75,17 @@ "isotropic_resample": True, "resolution_staging": [ 6, - 4, - 2, - 1, + 3, + 1.5, ], # specify voxel size (mm) since isotropic_resample is set - "iteration_staging": [200, 150, 125, 100], - "smoothing_sigmas": [0, 0, 0, 0], + "iteration_staging": [150, 125, 100], + "smoothing_sigmas": [ + 0, + 0, + 0, + ], "ncores": 8, - "default_value": 0, + "default_value": None, "verbose": False, }, "label_fusion_settings": { @@ -315,7 +318,7 @@ def run_segmentation(img, settings=MUTLIATLAS_SETTINGS_DEFAULTS): atlas_reg_image = atlas_set[atlas_id]["RIR"]["CT Image"] target_reg_image = img_crop - deform_image, dir_tfm, _ = fast_symmetric_forces_demons_registration( + _, dir_tfm, _ = fast_symmetric_forces_demons_registration( target_reg_image, atlas_reg_image, **deformable_registration_settings, From 4cf6609950ddc8c519fe95faf8d6f0a1cef3fb5a Mon Sep 17 00:00:00 2001 From: rnfinnegan Date: Tue, 11 Jan 2022 01:33:50 +0000 Subject: [PATCH 4/9] determine default_value in registraion --- platipy/imaging/registration/deformable.py | 13 ++++++++++++- platipy/imaging/registration/linear.py | 13 +++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/platipy/imaging/registration/deformable.py b/platipy/imaging/registration/deformable.py index 5705a732..da842bc2 100644 --- a/platipy/imaging/registration/deformable.py +++ b/platipy/imaging/registration/deformable.py @@ -151,7 +151,7 @@ def fast_symmetric_forces_demons_registration( initial_displacement_field=None, smoothing_sigma_factor=1, smoothing_sigmas=False, - default_value=-1000, + default_value=None, ncores=1, interp_order=2, verbose=False, @@ -176,6 +176,8 @@ def fast_symmetric_forces_demons_registration( 2 = Bi-linear splines 3 = B-Spline (cubic) + default_value (float) : Default voxel value. Defaults to 0 unless image is CT-like. + Returns registered_image (sitk.Image) : the registered moving image output_transform : the displacement field transform @@ -224,6 +226,15 @@ def fast_symmetric_forces_demons_registration( resampler = sitk.ResampleImageFilter() resampler.SetReferenceImage(fixed_image) resampler.SetInterpolator(interp_order) + + # Try to find default value + if default_value is None: + default_value = 0 + + # Test if image is CT-like + if sitk.GetArrayViewFromImage(moving_image).min() <= -1000: + default_value = -1000 + resampler.SetDefaultPixelValue(default_value) resampler.SetTransform(output_transform) diff --git a/platipy/imaging/registration/linear.py b/platipy/imaging/registration/linear.py index 27857be9..24738170 100644 --- a/platipy/imaging/registration/linear.py +++ b/platipy/imaging/registration/linear.py @@ -60,7 +60,7 @@ def linear_registration( sampling_rate=0.25, final_interp=2, number_of_iterations=50, - default_value=-1000, + default_value=None, verbose=False, ): """ @@ -110,7 +110,7 @@ def linear_registration( final_interp (int, optional): The final interpolation order. Defaults to 2 (linear). number_of_iterations (int, optional): Number of iterations in each multi-resolution step. Defaults to 50. - default_value (int, optional): Default voxel value. Defaults to -1000. + default_value (int, optional): Default voxel value. Defaults to 0 unless image is CT-like. verbose (bool, optional): Print image registration process information. Defaults to False. Returns: @@ -236,6 +236,15 @@ def linear_registration( # Combine initial and optimised transform combined_transform = sitk.CompositeTransform([initial_transform, output_transform]) + + # Try to find default value + if default_value is None: + default_value = 0 + + # Test if image is CT-like + if sitk.GetArrayViewFromImage(moving_image).min() <= -1000: + default_value = -1000 + registered_image = apply_transform( input_image=moving_image, reference_image=fixed_image, From d35e1da1327ae39534d73fb172771534948b9e4d Mon Sep 17 00:00:00 2001 From: rnfinnegan Date: Thu, 27 Jan 2022 06:32:00 +0000 Subject: [PATCH 5/9] added projection functionality to scalar overlay --- platipy/imaging/visualisation/visualiser.py | 245 +++++++++++--------- 1 file changed, 139 insertions(+), 106 deletions(-) diff --git a/platipy/imaging/visualisation/visualiser.py b/platipy/imaging/visualisation/visualiser.py index e736c78c..613b438d 100644 --- a/platipy/imaging/visualisation/visualiser.py +++ b/platipy/imaging/visualisation/visualiser.py @@ -219,6 +219,7 @@ def add_scalar_overlay( mid_ticks=False, show_colorbar=True, norm=None, + projection=False, ): """Overlay a scalar image on to the existing image @@ -256,6 +257,7 @@ def add_scalar_overlay( mid_ticks=mid_ticks, show_colorbar=show_colorbar, norm=norm, + projection=projection, ) self.__scalar_overlays.append(visualise_scalar) @@ -277,6 +279,7 @@ def add_scalar_overlay( mid_ticks=mid_ticks, show_colorbar=show_colorbar, norm=norm, + projection=projection, ) self.__scalar_overlays.append(visualise_scalar) else: @@ -1107,9 +1110,6 @@ def _overlay_contours(self): def _overlay_scalar_field(self): """Overlay the scalar image onto the existing figure""" - if self.__projection and len(self.__scalar_overlays) > 0: - raise Warning("Scalar overlay is not implemented in projection mode.") - for scalar_index, scalar in enumerate(self.__scalar_overlays): scalar_image = scalar.image @@ -1139,86 +1139,62 @@ def _overlay_scalar_field(self): else: norm = None - nda = np.ma.masked_less_equal(nda, s_min) - sp_plane, _, sp_slice = scalar_image.GetSpacing() asp = (1.0 * sp_slice) / sp_plane - # Test types of axes - axes = self.__figure.axes[:4] - if len(axes) < 4: - ax = axes[0] - s = return_slice(self.__axis, self.__cut) - if self.__axis == "z": - org = {"normal": "upper", "reversed": "lower"}[self.__origin] - else: - org = "lower" - sp = ax_indiv = ax.imshow( - nda.__getitem__(s), - interpolation="none", - cmap=colormap, - clim=(s_min, s_max), - aspect={"z": 1, "y": asp, "x": asp}[self.__axis], - origin=org, - vmin=s_min, - vmax=s_max, - alpha=alpha, - norm=norm, - ) + # projection organisation + if scalar.projection: + projection = scalar.projection + else: + projection = self.__projection - if scalar.show_colorbar: - divider = make_axes_locatable(ax) - cax = divider.append_axes("right", size="5%", pad=0.05) - cbar = self.__figure.colorbar(sp, cax=cax, orientation="vertical") - cbar.set_label(scalar.name) - cbar.solids.set_alpha(1) - if scalar.discrete_levels: - cbar.set_ticks(np.linspace(s_min, s_max, scalar.discrete_levels + 1)) - - if scalar.mid_ticks: - - delta_tick = (s_max - s_min) / scalar.discrete_levels - cbar.set_ticks( - np.linspace( - s_min + delta_tick / 2, - s_max - delta_tick / 2, - scalar.discrete_levels, - ) - ) - cbar.set_ticklabels(np.linspace(s_min, s_max, scalar.discrete_levels)) - - else: - cbar.set_ticks( - np.linspace( - s_min, - s_max, - scalar.discrete_levels + 1, - ) - ) + if self.__axis == "ortho": - f_x, f_y = self.__figure.get_size_inches() - self.__figure.set_size_inches(f_x * 1.15, f_y) - self.__figure.subplots_adjust(left=0, right=0.88, bottom=0, top=1) + ax_ax, _, ax_cor, ax_sag = self.__figure.axes + ax = ax_ax - if self.__axis == "z": - axis_view_name_consistent = "ax_view" - if self.__axis == "y": - axis_view_name_consistent = "cor_view" - if self.__axis == "x": - axis_view_name_consistent = "sag_view" + if not projection: + s_ax = return_slice("z", self.__cut[0]) + s_cor = return_slice("y", self.__cut[1]) + s_sag = return_slice("x", self.__cut[2]) - self.__scalar_view = {axis_view_name_consistent: ax_indiv} + ax_img = nda.__getitem__(s_ax) + cor_img = nda.__getitem__(s_cor) + sag_img = nda.__getitem__(s_sag) - elif len(axes) == 4: - ax_ax, _, ax_cor, ax_sag = axes + else: + ax_img_proj = project_onto_arbitrary_plane( + scalar_image, + projection_axis=2, + projection_name=projection, + default_value=int(nda.min()), + ) + ax_img = sitk.GetArrayFromImage(ax_img_proj) - s_ax = return_slice("z", self.__cut[0]) - s_cor = return_slice("y", self.__cut[1]) - s_sag = return_slice("x", self.__cut[2]) + cor_img_proj = project_onto_arbitrary_plane( + scalar_image, + projection_axis=1, + projection_name=projection, + default_value=int(nda.min()), + ) + cor_img = sitk.GetArrayFromImage(cor_img_proj) + + sag_img_proj = project_onto_arbitrary_plane( + scalar_image, + projection_axis=0, + projection_name=projection, + default_value=int(nda.min()), + ) + sag_img = sitk.GetArrayFromImage(sag_img_proj) + + # mask images to enforce transparency + ax_img = np.ma.masked_less_equal(ax_img, s_min) + cor_img = np.ma.masked_less_equal(cor_img, s_min) + sag_img = np.ma.masked_less_equal(sag_img, s_min) ax_view = ax_ax.imshow( - nda.__getitem__(s_ax), + ax_img, interpolation="none", cmap=colormap, clim=(s_min, s_max), @@ -1231,7 +1207,7 @@ def _overlay_scalar_field(self): ) cor_view = ax_cor.imshow( - nda.__getitem__(s_cor), + cor_img, interpolation="none", cmap=colormap, clim=(s_min, s_max), @@ -1244,7 +1220,7 @@ def _overlay_scalar_field(self): ) sag_view = ax_sag.imshow( - nda.__getitem__(s_sag), + sag_img, interpolation="none", cmap=colormap, clim=(s_min, s_max), @@ -1256,14 +1232,57 @@ def _overlay_scalar_field(self): norm=norm, ) - if scalar.show_colorbar: + # this is for (work-in-progress) dynamic visualisation + self.__scalar_view = { + "ax_view": ax_view, + "cor_view": cor_view, + "sag_view": sag_view, + } - # divider = make_axes_locatable(ax_view) - # cax = divider.append_axes("right", size="5%", pad=0.05) + else: - ax_box = ax_ax.get_position(original=False) - cbar_width = ax_box.width * 0.05 # 5% of axis width + ax = self.__figure.axes[0] + + if not projection: + s = return_slice(self.__axis, self.__cut) + disp_img = nda.__getitem__(s) + else: + disp_img_proj = project_onto_arbitrary_plane( + scalar_image, + projection_axis={"x": 0, "y": 1, "z": 2}[self.__axis], + projection_name=projection, + default_value=int(nda.min()), + ) + disp_img = sitk.GetArrayFromImage(disp_img_proj) + # disp_img = (disp_img - disp_img.min()) / (disp_img.max() - disp_img.min()) + + disp_img = np.ma.masked_less_equal(disp_img, s_min) + asp = {"x": asp, "y": asp, "z": 1}[self.__axis] + + s = return_slice(self.__axis, self.__cut) + ax_view = ax.imshow( + disp_img, + interpolation="none", + cmap=colormap, + clim=(s_min, s_max), + origin="lower", + aspect=asp, + vmin=s_min, + vmax=s_max, + alpha=alpha, + norm=norm, + ) + + if scalar.show_colorbar: + + # divider = make_axes_locatable(ax_view) + # cax = divider.append_axes("right", size="5%", pad=0.05) + + ax_box = ax.get_position(original=False) + cbar_width = ax_box.width * 0.05 # 5% of axis width + + if self.__axis == "ortho": cax = self.__figure.add_axes( ( ax_box.x1 + 0.02 + (cbar_width + 0.1) * scalar_index, @@ -1273,41 +1292,55 @@ def _overlay_scalar_field(self): ) ) - cbar = self.__figure.colorbar(ax_view, cax=cax, orientation="vertical") + cbar_color = "black" + else: + cax = self.__figure.add_axes( + ( + ax_box.x1 - 0.02 - (cbar_width + 0.1) * (scalar_index + 1), + 0.025, + cbar_width, + ax_box.height - ax_box.y1 * 0.05, + ) + ) + + # check background values + if np.linalg.norm(colormap(0)[:3]) < 0.1: + # background is dark + cbar_color = "white" - if scalar.show_colorbar: + cbar = self.__figure.colorbar(ax_view, cax=cax, orientation="vertical") - cbar.set_label(scalar.name) - cbar.solids.set_alpha(1) + # set color + cbar.outline.set_edgecolor(color=cbar_color) + cbar.ax.tick_params(color=cbar_color) + cax.tick_params(axis="x", colors=cbar_color) + cax.tick_params(axis="y", colors=cbar_color) - if scalar.discrete_levels: + cbar.set_label(scalar.name, color=cbar_color) + cbar.solids.set_alpha(1) - if scalar.mid_ticks: + if scalar.discrete_levels: - delta_tick = (s_max - s_min) / scalar.discrete_levels - cbar.set_ticks( - np.linspace( - s_min + delta_tick / 2, - s_max - delta_tick / 2, - scalar.discrete_levels, - ) - ) - cbar.set_ticklabels(np.linspace(s_min, s_max, scalar.discrete_levels)) - - else: - cbar.set_ticks( - np.linspace( - s_min, - s_max, - scalar.discrete_levels + 1, - ) + if scalar.mid_ticks: + + delta_tick = (s_max - s_min) / scalar.discrete_levels + cbar.set_ticks( + np.linspace( + s_min + delta_tick / 2, + s_max - delta_tick / 2, + scalar.discrete_levels, ) + ) + cbar.set_ticklabels(np.linspace(s_min, s_max, scalar.discrete_levels)) - self.__scalar_view = { - "ax_view": ax_view, - "cor_view": cor_view, - "sag_view": sag_view, - } + else: + cbar.set_ticks( + np.linspace( + s_min, + s_max, + scalar.discrete_levels + 1, + ) + ) def _overlay_vector_field(self): """Overlay vector field onto existing figure""" From 48b57c58843439f757c246c3b5cfe34168087bb4 Mon Sep 17 00:00:00 2001 From: rnfinnegan Date: Thu, 27 Jan 2022 06:32:24 +0000 Subject: [PATCH 6/9] check for divide by zero in DVH normalisation --- platipy/imaging/dose/dvh.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/platipy/imaging/dose/dvh.py b/platipy/imaging/dose/dvh.py index 31c9a302..dfd733e3 100644 --- a/platipy/imaging/dose/dvh.py +++ b/platipy/imaging/dose/dvh.py @@ -47,7 +47,10 @@ def calculate_dvh(dose_grid, label, bins=1001): # Calculate the actual DVH values values = np.cumsum(counts[::-1])[::-1] - values = values / values.max() + if np.all(values == 0): + return bins, values + else: + values = values / values.max() return bins, values @@ -142,7 +145,7 @@ def calculate_d_x(dvh, x, label=None): def calculate_v_x(dvh, x, label=None): """Get the volume (in cc) which receives x dose - + Args: dvh (pandas.DataFrame): DVH DataFrame as produced by calculate_dvh_for_labels x (float): The dose to get the volume for. From 5245697dadf84d53c9590c6ca0308f570b8e5ec4 Mon Sep 17 00:00:00 2001 From: rnfinnegan Date: Thu, 27 Jan 2022 06:32:39 +0000 Subject: [PATCH 7/9] update projection functionality for scalar overlay --- platipy/imaging/visualisation/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/platipy/imaging/visualisation/utils.py b/platipy/imaging/visualisation/utils.py index 278c4e17..34cc6ad4 100644 --- a/platipy/imaging/visualisation/utils.py +++ b/platipy/imaging/visualisation/utils.py @@ -60,6 +60,7 @@ def __init__( mid_ticks=False, show_colorbar=True, norm=None, + projection=False, ): self.image = image self.name = name @@ -71,6 +72,7 @@ def __init__( self.mid_ticks = mid_ticks self.show_colorbar = show_colorbar self.norm = norm + self.projection = projection class VisualiseVectorOverlay: From 0b5eba762afff900693d379e85c9bc96d75a5161 Mon Sep 17 00:00:00 2001 From: rnfinnegan Date: Mon, 31 Jan 2022 23:55:39 +0000 Subject: [PATCH 8/9] colorbar bug fix --- platipy/imaging/visualisation/visualiser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platipy/imaging/visualisation/visualiser.py b/platipy/imaging/visualisation/visualiser.py index 613b438d..c6cde0e7 100644 --- a/platipy/imaging/visualisation/visualiser.py +++ b/platipy/imaging/visualisation/visualiser.py @@ -1281,6 +1281,7 @@ def _overlay_scalar_field(self): ax_box = ax.get_position(original=False) cbar_width = ax_box.width * 0.05 # 5% of axis width + cbar_color = "black" if self.__axis == "ortho": cax = self.__figure.add_axes( @@ -1292,7 +1293,6 @@ def _overlay_scalar_field(self): ) ) - cbar_color = "black" else: cax = self.__figure.add_axes( ( From a32080b4e12461101e7334efbdd34c6a09c3154f Mon Sep 17 00:00:00 2001 From: rnfinnegan Date: Tue, 1 Feb 2022 02:24:08 +0000 Subject: [PATCH 9/9] return nan in surface metrics for empty labels --- platipy/imaging/label/comparison.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platipy/imaging/label/comparison.py b/platipy/imaging/label/comparison.py index 3762d3fe..76a6240f 100644 --- a/platipy/imaging/label/comparison.py +++ b/platipy/imaging/label/comparison.py @@ -237,7 +237,7 @@ def compute_metric_masd(label_a, label_b, auto_crop=True): sitk.GetArrayViewFromImage(label_a).sum() == 0 or sitk.GetArrayViewFromImage(label_b).sum() == 0 ): - return -1 + return np.nan if auto_crop: largest_region = (label_a + label_b) > 0 @@ -278,7 +278,7 @@ def compute_metric_hd(label_a, label_b, auto_crop=True): sitk.GetArrayViewFromImage(label_a).sum() == 0 or sitk.GetArrayViewFromImage(label_b).sum() == 0 ): - return -1 + return np.nan if auto_crop: largest_region = (label_a + label_b) > 0 crop_box_size, crop_box_index = label_to_roi(largest_region)