-
Notifications
You must be signed in to change notification settings - Fork 26
Add Fuzzy Segmentation Based Volume Creation #400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,12 +21,22 @@ class SegmentationBasedAdapter(VolumeCreationAdapterBase): | |
| def create_simulation_volume(self) -> dict: | ||
| volumes, x_dim_px, y_dim_px, z_dim_px = self.create_empty_volumes() | ||
| wavelength = self.global_settings[Tags.WAVELENGTH] | ||
| for key in volumes.keys(): | ||
| volumes[key] = volumes[key].to('cpu') | ||
|
|
||
| segmentation_volume = self.component_settings[Tags.INPUT_SEGMENTATION_VOLUME] | ||
| segmentation_classes = np.unique(segmentation_volume, return_counts=False) | ||
| x_dim_seg_px, y_dim_seg_px, z_dim_seg_px = np.shape(segmentation_volume) | ||
| segmentation_volume = torch.tensor(self.component_settings[Tags.INPUT_SEGMENTATION_VOLUME], device=self.torch_device) | ||
| class_mapping = self.component_settings[Tags.SEGMENTATION_CLASS_MAPPING] | ||
|
|
||
| if torch.is_floating_point(segmentation_volume): | ||
| assert len(segmentation_volume.shape) == 4 and segmentation_volume.shape[0] == len(class_mapping), \ | ||
| "Fuzzy segmentation must be a 4D array with the first dimension being the number of classes." | ||
| fuzzy = True | ||
| segmentation_classes = np.arange(segmentation_volume.shape[0]) | ||
|
|
||
| else: | ||
| assert len(segmentation_volume.shape) == 3, "Hard segmentations must be a 3D array." | ||
| fuzzy = False | ||
| segmentation_classes = torch.unique(segmentation_volume, return_counts=False).cpu().numpy() | ||
|
|
||
| x_dim_seg_px, y_dim_seg_px, z_dim_seg_px = np.shape(segmentation_volume)[-3:] | ||
|
|
||
| if x_dim_px != x_dim_seg_px: | ||
| raise ValueError("x_dim of volumes and segmentation must perfectly match but was {} and {}" | ||
|
|
@@ -38,16 +48,17 @@ def create_simulation_volume(self) -> dict: | |
| raise ValueError("z_dim of volumes and segmentation must perfectly match but was {} and {}" | ||
| .format(z_dim_px, z_dim_seg_px)) | ||
|
|
||
| class_mapping = self.component_settings[Tags.SEGMENTATION_CLASS_MAPPING] | ||
|
|
||
| for seg_class in segmentation_classes: | ||
| class_properties = class_mapping[seg_class].get_properties_for_wavelength(self.global_settings, wavelength) | ||
| for volume_key in volumes.keys(): | ||
| if isinstance(class_properties[volume_key], (int, float)) or class_properties[volume_key] == None: # scalar | ||
| assigned_prop = class_properties[volume_key] | ||
| if assigned_prop is None: | ||
| assigned_prop = torch.nan | ||
| volumes[volume_key][segmentation_volume == seg_class] = assigned_prop | ||
| if fuzzy: | ||
| volumes[volume_key] += segmentation_volume[seg_class] * assigned_prop | ||
|
||
| else: | ||
| volumes[volume_key][segmentation_volume == seg_class] = assigned_prop | ||
| elif len(torch.Tensor.size(class_properties[volume_key])) == 3: # 3D map | ||
| assigned_prop = class_properties[volume_key][torch.tensor(segmentation_volume == seg_class)] | ||
| assigned_prop[assigned_prop is None] = torch.nan | ||
|
|
@@ -57,6 +68,6 @@ def create_simulation_volume(self) -> dict: | |
|
|
||
| # convert volumes back to CPU | ||
| for key in volumes.keys(): | ||
| volumes[key] = volumes[key].numpy().astype(np.float64, copy=False) | ||
| volumes[key] = volumes[key].cpu().numpy().astype(np.float64, copy=False) | ||
|
|
||
| return volumes | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -6,7 +6,7 @@ | |||||
| import simpa as sp | ||||||
| import numpy as np | ||||||
| from skimage.data import shepp_logan_phantom | ||||||
| from scipy.ndimage import zoom | ||||||
| from scipy.ndimage import zoom, gaussian_filter | ||||||
| from skimage.transform import resize | ||||||
|
|
||||||
| # FIXME temporary workaround for newest Intel architectures | ||||||
|
|
@@ -20,8 +20,8 @@ | |||||
|
|
||||||
|
|
||||||
| @profile | ||||||
| def run_segmentation_loader(spacing: float | int = 1.0, input_spacing: float | int = 0.2, path_manager=None, | ||||||
| visualise: bool = True): | ||||||
| def run_segmentation_loader(spacing: float | int = 1.0, input_spacing: float | int = 0.2, fuzzy: bool = False, | ||||||
| path_manager=None, visualise: bool = True): | ||||||
| """ | ||||||
|
|
||||||
| :param spacing: The simulation spacing between voxels in mm | ||||||
|
|
@@ -30,19 +30,28 @@ def run_segmentation_loader(spacing: float | int = 1.0, input_spacing: float | i | |||||
| :param visualise: If VISUALIZE is set to True, the reconstruction result will be plotted | ||||||
| :return: a run through of the example | ||||||
| """ | ||||||
|
|
||||||
| if path_manager is None: | ||||||
| path_manager = sp.PathManager() | ||||||
|
|
||||||
| C = 11 # number of classes | ||||||
| label_mask = shepp_logan_phantom() | ||||||
|
|
||||||
| label_mask = np.digitize(label_mask, bins=np.linspace(0.0, 1.0, 11), right=True) | ||||||
| label_mask = np.digitize(label_mask, bins=np.linspace(0.0, 1.0, C), right=True) | ||||||
| label_mask = label_mask[100:300, 100:300] | ||||||
| label_mask = np.reshape(label_mask, (label_mask.shape[0], 1, label_mask.shape[1])) | ||||||
|
|
||||||
| segmentation_volume_tiled = np.tile(label_mask, (1, 128, 1)) | ||||||
| segmentation_volume_mask = sp.round_x5_away_from_zero(zoom(segmentation_volume_tiled, input_spacing/spacing, | ||||||
| order=0)).astype(int) | ||||||
|
|
||||||
| if fuzzy: | ||||||
| segmentation_volume_mask = np.eye(C)[segmentation_volume_mask] | ||||||
| segmentation_volume_mask = np.moveaxis(segmentation_volume_mask, -1, 0) | ||||||
| segmentation_volume_mask = gaussian_filter(segmentation_volume_mask, sigma=1e-5, axes=(1, 2, 3)) # smooth the segmentation | ||||||
|
||||||
| segmentation_volume_mask = gaussian_filter(segmentation_volume_mask, sigma=1e-5, axes=(1, 2, 3)) # smooth the segmentation | |
| segmentation_volume_mask = gaussian_filter(segmentation_volume_mask, sigma=1.0, axes=(1, 2, 3)) # smooth the segmentation |
Copilot
AI
Nov 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using type=bool with argparse doesn't work as expected. Any non-empty string will be converted to True, so --fuzzy False will actually set fuzzy to True. Use action='store_true' instead, or implement a proper boolean parser like lambda x: x.lower() == 'true'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fuzzy segmentation detection relies on dtype, which could be fragile. A hard segmentation converted to float would be incorrectly detected as fuzzy. Consider adding an explicit parameter or checking both dtype and shape (e.g.,
len(shape) == 4) to make the detection more robust.