synapse_net.imod.export
1import shutil 2import tempfile 3from subprocess import run 4from typing import Dict, List, Optional, Tuple 5 6import imageio.v3 as imageio 7import numpy as np 8from elf.io import open_file 9from scipy.ndimage import maximum_filter1d 10from skimage.morphology import ball 11 12from tqdm import tqdm 13 14 15def get_label_names( 16 imod_path: str, 17 return_types: bool = False, 18 return_counts: bool = False, 19 filter_empty_labels: bool = True, 20) -> Dict[str, str]: 21 """Get the ids and names of annotations in an imod file. 22 23 Uses the imodinfo command to parse the contents of the imod file. 24 25 Args: 26 imod_path: The filepath to the imod annotation file. 27 return_types: Whether to also return the annotation types. 28 return_counts: Whether to also return the number of annotations per object. 29 filter_empty_labels: Whether to filter out empty objects from the object list. 30 31 Returns: 32 Dictionary that maps the object ids to their names. 33 """ 34 cmd = "imodinfo" 35 cmd_path = shutil.which(cmd) 36 assert cmd_path is not None, f"Could not find the {cmd} imod command." 37 38 label_names, label_types, label_counts = {}, {}, {} 39 40 with tempfile.NamedTemporaryFile() as f: 41 tmp_path = f.name 42 run([cmd, "-f", tmp_path, imod_path]) 43 44 object_id = None 45 with open(tmp_path) as f: 46 lines = f.readlines() 47 for line in lines: 48 49 if line.startswith("OBJECT"): 50 object_id = int(line.rstrip("\n").strip().split()[-1]) 51 52 if line.startswith("NAME"): 53 name = line.rstrip("\n").strip().split()[-1] 54 assert object_id is not None 55 label_names[object_id] = name 56 57 if "object uses" in line: 58 type_ = " ".join(line.rstrip("\n").strip().split()[2:]).rstrip(".") 59 label_types[object_id] = type_ 60 61 # Parse the number of annotions for this object. 62 if "contours" in line: 63 try: 64 count = int(line.rstrip("\n").strip().split()[0]) 65 label_counts[object_id] = count 66 # The condition also hits for 'closed countours' / 'open contours'. 67 # We just catch the exception thrown for these cases and continue. 68 except ValueError: 69 pass 70 71 if "CONTOUR" in line and label_types[object_id] == "scattered points": 72 count = int(line.rstrip("\n").strip().split()[2]) 73 label_counts[object_id] = count 74 75 if filter_empty_labels: 76 empty_labels = [k for k, v in label_counts.items() if v == 0] 77 label_names = {k: v for k, v in label_names.items() if k not in empty_labels} 78 label_types = {k: v for k, v in label_types.items() if k not in empty_labels} 79 label_counts = {k: v for k, v in label_counts.items() if k not in empty_labels} 80 81 if return_types and return_counts: 82 return label_names, label_types, label_counts 83 elif return_types: 84 return label_names, label_types 85 elif return_counts: 86 return label_names, label_counts 87 88 return label_names 89 90 91def export_segmentation( 92 imod_path: str, 93 mrc_path: str, 94 object_id: Optional[int] = None, 95 output_path: Optional[int] = None, 96 require_object: bool = True, 97 depth_interpolation: Optional[int] = None, 98) -> Optional[np.ndarray]: 99 """Export a segmentation mask from IMOD annotations. 100 101 This function uses the imodmop command to extract masks. 102 103 Args: 104 imod_path: The filepath to the imod annotation file. 105 mrc_path: The filepath to the mrc file with the corresponding tomogram data. 106 object_id: The id of the object to be extracted. 107 If none is given all objects in the mod file will be extracted. 108 output_path: Optional path to a file where to save the extracted mask as tif. 109 If not given the segmentation mask will be returned as a numpy array. 110 require_object: Whether to throw an error if the object could not be extractd. 111 depth_interpolation: Window for interpolating the extracted mask across the depth 112 axis. This can be used to close masks which are only annotated in a subset of slices. 113 114 Returns: 115 The extracted mask. Will only be returned if `output_path` is not given. 116 """ 117 cmd = "imodmop" 118 cmd_path = shutil.which(cmd) 119 assert cmd_path is not None, f"Could not find the {cmd} imod command." 120 121 with tempfile.NamedTemporaryFile() as f: 122 tmp_path = f.name 123 124 if object_id is None: 125 cmd = [cmd, "-ma", "1", imod_path, mrc_path, tmp_path] 126 else: 127 cmd = [cmd, "-ma", "1", "-o", str(object_id), imod_path, mrc_path, tmp_path] 128 129 run(cmd) 130 with open_file(tmp_path, ext=".mrc", mode="r") as f: 131 data = f["data"][:] 132 133 segmentation = data == 1 134 if require_object and segmentation.sum() == 0: 135 id_str = "" if object_id is None else f"for object {object_id}" 136 raise RuntimeError(f"Segmentation extracted from {imod_path} {id_str} is empty.") 137 138 if depth_interpolation is not None: 139 segmentation = maximum_filter1d(segmentation, size=depth_interpolation, axis=0, mode="constant") 140 141 if output_path is None: 142 return segmentation 143 144 imageio.imwrite(output_path, segmentation.astype("uint8"), compression="zlib") 145 146 147def draw_spheres( 148 coordinates: np.ndarray, 149 radii: np.ndarray, 150 shape: Tuple[int, int, int], 151 verbose: bool = True, 152) -> np.ndarray: 153 """Create a volumetric segmentation by painting spheres around the given coordinates. 154 155 Args: 156 coordinates: The center coordinates of the spheres. 157 radii: The radii of the spheres. 158 shape: The shape of the volume. 159 verbose: Whether to print the progress bar. 160 161 Returns: 162 The segmentation volume with painted spheres. 163 """ 164 labels = np.zeros(shape, dtype="uint32") 165 for label_id, (coord, radius) in tqdm( 166 enumerate(zip(coordinates, radii), start=1), total=len(coordinates), disable=not verbose 167 ): 168 radius = int(radius) 169 mask = ball(radius) 170 full_mask = np.zeros(shape, dtype="bool") 171 full_slice = tuple( 172 slice(max(co - radius, 0), min(co + radius, sh)) for co, sh in zip(coord, shape) 173 ) 174 radius_clipped_left = [co - max(co - radius, 0) for co in coord] 175 radius_clipped_right = [min(co + radius, sh) - co for co, sh in zip(coord, shape)] 176 mask_slice = tuple( 177 slice(radius + 1 - rl, radius + 1 + rr) for rl, rr in zip(radius_clipped_left, radius_clipped_right) 178 ) 179 full_mask[full_slice] = mask[mask_slice] 180 labels[full_mask] = label_id 181 return labels 182 183 184def load_points_from_imodinfo( 185 imod_path: str, 186 full_shape: Tuple[int, int, int], 187 bb: Optional[Tuple[slice, slice, slice]] = None, 188 exclude_labels: Optional[List[int]] = None, 189 exclude_label_patterns: Optional[List[str]] = None, 190 resolution: Optional[float] = None, 191) -> Tuple[np.ndarray, np.ndarray, np.ndarray, Dict[int, str]]: 192 """Load point coordinates, radii and label information from a .mod file. 193 194 The coordinates and sizes returned will be scaled so that they are in 195 the voxel coordinate space if the 'resolution' parameter is passed. 196 If it is not passed then the radius will be returned in the physical resolution. 197 198 Args: 199 imod_path: The filepath to the .mod file. 200 full_shape: The voxel shape of the volume. 201 bb: Optional bounding box to limit the extracted points to. 202 exclude_labels: Label ids to exclude from the export. 203 This can be used to exclude specific labels / classes, specifying them by their id. 204 exclude_label_patterns: Label names to exclude from the export. 205 This can be used to exclude specific labels / classes, specifying them by their name. 206 resolution: The resolution / voxel size of the data. Will be used to scale the radii. 207 208 Returns: 209 The center coordinates of the sphere annotations. 210 The radii of the spheres. 211 The ids of the semantic labels. 212 The names of the semantic labels. 213 """ 214 coordinates, sizes, labels = [], [], [] 215 label_names = {} 216 217 if bb is not None: 218 start = [b.start for b in bb] 219 shape = [b.stop - sta for b, sta in zip(bb, start)] 220 221 # first round: load the point sizes and labels 222 with tempfile.NamedTemporaryFile() as f: 223 tmp_path = f.name 224 run(["imodinfo", "-p", "-f", tmp_path, imod_path]) 225 226 label_id = None 227 label_name = None 228 with open(tmp_path) as f: 229 lines = f.readlines() 230 for line in lines: 231 232 if line.startswith("#Object"): 233 line = line.rstrip("\n") 234 label_id = int(line.split(" ")[1]) 235 label_name = line.split(",")[1] 236 label_names[label_id] = label_name 237 continue 238 239 if label_id is None: 240 continue 241 if exclude_labels is not None and label_id in exclude_labels: 242 continue 243 if exclude_label_patterns is not None and any( 244 pattern.lower() in label_name.lower() for pattern in exclude_label_patterns 245 ): 246 continue 247 248 try: 249 size = float(line.rstrip("\n")) 250 sizes.append(size) 251 labels.append(label_id) 252 except ValueError: 253 continue 254 255 label_names = {lid: label_name for lid, label_name in label_names.items() if lid in labels} 256 257 in_bounds = [] 258 with tempfile.NamedTemporaryFile() as f: 259 tmp_path = f.name 260 run(["imodinfo", "-vv", "-f", tmp_path, imod_path]) 261 262 label_id = None 263 with open(tmp_path) as f: 264 lines = f.readlines() 265 for line in lines: 266 if line.startswith("OBJECT"): 267 label_id = int(line.rstrip("\n").split(" ")[-1]) 268 continue 269 if label_id not in label_names: 270 continue 271 272 values = line.strip().rstrip("\n").split("\t") 273 if len(values) != 3: 274 continue 275 276 try: 277 x, y, z = float(values[0]), float(values[1]), float(values[2]) 278 coords = [x, y, z] 279 coords = [int(np.round(float(coord))) for coord in coords] 280 # IMOD uses a very weird coordinate system: 281 coords = [coords[2], full_shape[1] - coords[1], coords[0]] 282 283 in_bound = True 284 if bb is not None: 285 coords = [co - sta for co, sta in zip(coords, start)] 286 if any(co < 0 or co > sh for co, sh in zip(coords, shape)): 287 in_bound = False 288 289 in_bounds.append(in_bound) 290 coordinates.append(coords) 291 except ValueError: 292 continue 293 294 # labelx = {seg_id: int(label_id) for seg_id, label_id in enumerate(labels, 1)} 295 # print("Extracted the following labels:", label_names) 296 # print("With counts:", {k: v for k, v in zip(*np.unique(list(labelx.values()), return_counts=True))}) 297 # breakpoint() 298 assert len(coordinates) == len(sizes) == len(labels) == len(in_bounds), \ 299 f"{len(coordinates)}, {len(sizes)}, {len(labels)}, {len(in_bounds)}" 300 301 coordinates, sizes, labels = np.array(coordinates), np.array(sizes), np.array(labels) 302 in_bounds = np.array(in_bounds) 303 304 # get rid of empty annotations 305 in_bounds = np.logical_and(in_bounds, sizes > 0) 306 307 coordinates, sizes, labels = coordinates[in_bounds], sizes[in_bounds], labels[in_bounds] 308 309 if resolution is not None: 310 sizes /= resolution 311 312 if len(coordinates) == 0: 313 raise RuntimeError(f"Empty annotations: {imod_path}") 314 return coordinates, sizes, labels, label_names 315 316 317def export_point_annotations( 318 imod_path: str, 319 shape: Tuple[int, int, int], 320 bb: Optional[Tuple[slice, slice, slice]] = None, 321 exclude_labels: Optional[List[int]] = None, 322 exclude_label_patterns: Optional[List[str]] = None, 323 return_coords_and_radii: bool = False, 324 resolution: Optional[float] = None, 325) -> Tuple[np.ndarray, np.ndarray, Dict[int, str]]: 326 """Create a segmentation by drawing spheres corresponding to objects from a .mod file. 327 328 Args: 329 imod_path: The filepath to the .mod file. 330 shape: The voxel shape of the volume. 331 bb: Optional bounding box to limit the extracted points to. 332 exclude_labels: Label ids to exclude from the segmentation. 333 This can be used to exclude specific labels / classes, specifying them by their id. 334 exclude_label_patterns: Label names to exclude from the segmentation. 335 This can be used to exclude specific labels / classes, specifying them by their name. 336 return_coords_and_radii: Whether to also return the underlying coordinates 337 and radii of the exported spheres. 338 resolution: The resolution / voxel size of the data. Will be used to scale the radii. 339 340 Returns: 341 The exported segmentation. 342 The label ids for the instance ids in the segmentation. 343 The map of label ids to corresponding obejct names. 344 """ 345 coordinates, radii, labels, label_names = load_points_from_imodinfo( 346 imod_path, shape, bb=bb, 347 exclude_labels=exclude_labels, 348 exclude_label_patterns=exclude_label_patterns, 349 resolution=resolution, 350 ) 351 labels = {seg_id: int(label_id) for seg_id, label_id in enumerate(labels, 1)} 352 segmentation = draw_spheres(coordinates, radii, shape) 353 if return_coords_and_radii: 354 return segmentation, labels, label_names, coordinates, radii 355 return segmentation, labels, label_names
16def get_label_names( 17 imod_path: str, 18 return_types: bool = False, 19 return_counts: bool = False, 20 filter_empty_labels: bool = True, 21) -> Dict[str, str]: 22 """Get the ids and names of annotations in an imod file. 23 24 Uses the imodinfo command to parse the contents of the imod file. 25 26 Args: 27 imod_path: The filepath to the imod annotation file. 28 return_types: Whether to also return the annotation types. 29 return_counts: Whether to also return the number of annotations per object. 30 filter_empty_labels: Whether to filter out empty objects from the object list. 31 32 Returns: 33 Dictionary that maps the object ids to their names. 34 """ 35 cmd = "imodinfo" 36 cmd_path = shutil.which(cmd) 37 assert cmd_path is not None, f"Could not find the {cmd} imod command." 38 39 label_names, label_types, label_counts = {}, {}, {} 40 41 with tempfile.NamedTemporaryFile() as f: 42 tmp_path = f.name 43 run([cmd, "-f", tmp_path, imod_path]) 44 45 object_id = None 46 with open(tmp_path) as f: 47 lines = f.readlines() 48 for line in lines: 49 50 if line.startswith("OBJECT"): 51 object_id = int(line.rstrip("\n").strip().split()[-1]) 52 53 if line.startswith("NAME"): 54 name = line.rstrip("\n").strip().split()[-1] 55 assert object_id is not None 56 label_names[object_id] = name 57 58 if "object uses" in line: 59 type_ = " ".join(line.rstrip("\n").strip().split()[2:]).rstrip(".") 60 label_types[object_id] = type_ 61 62 # Parse the number of annotions for this object. 63 if "contours" in line: 64 try: 65 count = int(line.rstrip("\n").strip().split()[0]) 66 label_counts[object_id] = count 67 # The condition also hits for 'closed countours' / 'open contours'. 68 # We just catch the exception thrown for these cases and continue. 69 except ValueError: 70 pass 71 72 if "CONTOUR" in line and label_types[object_id] == "scattered points": 73 count = int(line.rstrip("\n").strip().split()[2]) 74 label_counts[object_id] = count 75 76 if filter_empty_labels: 77 empty_labels = [k for k, v in label_counts.items() if v == 0] 78 label_names = {k: v for k, v in label_names.items() if k not in empty_labels} 79 label_types = {k: v for k, v in label_types.items() if k not in empty_labels} 80 label_counts = {k: v for k, v in label_counts.items() if k not in empty_labels} 81 82 if return_types and return_counts: 83 return label_names, label_types, label_counts 84 elif return_types: 85 return label_names, label_types 86 elif return_counts: 87 return label_names, label_counts 88 89 return label_names
Get the ids and names of annotations in an imod file.
Uses the imodinfo command to parse the contents of the imod file.
Arguments:
- imod_path: The filepath to the imod annotation file.
- return_types: Whether to also return the annotation types.
- return_counts: Whether to also return the number of annotations per object.
- filter_empty_labels: Whether to filter out empty objects from the object list.
Returns:
Dictionary that maps the object ids to their names.
92def export_segmentation( 93 imod_path: str, 94 mrc_path: str, 95 object_id: Optional[int] = None, 96 output_path: Optional[int] = None, 97 require_object: bool = True, 98 depth_interpolation: Optional[int] = None, 99) -> Optional[np.ndarray]: 100 """Export a segmentation mask from IMOD annotations. 101 102 This function uses the imodmop command to extract masks. 103 104 Args: 105 imod_path: The filepath to the imod annotation file. 106 mrc_path: The filepath to the mrc file with the corresponding tomogram data. 107 object_id: The id of the object to be extracted. 108 If none is given all objects in the mod file will be extracted. 109 output_path: Optional path to a file where to save the extracted mask as tif. 110 If not given the segmentation mask will be returned as a numpy array. 111 require_object: Whether to throw an error if the object could not be extractd. 112 depth_interpolation: Window for interpolating the extracted mask across the depth 113 axis. This can be used to close masks which are only annotated in a subset of slices. 114 115 Returns: 116 The extracted mask. Will only be returned if `output_path` is not given. 117 """ 118 cmd = "imodmop" 119 cmd_path = shutil.which(cmd) 120 assert cmd_path is not None, f"Could not find the {cmd} imod command." 121 122 with tempfile.NamedTemporaryFile() as f: 123 tmp_path = f.name 124 125 if object_id is None: 126 cmd = [cmd, "-ma", "1", imod_path, mrc_path, tmp_path] 127 else: 128 cmd = [cmd, "-ma", "1", "-o", str(object_id), imod_path, mrc_path, tmp_path] 129 130 run(cmd) 131 with open_file(tmp_path, ext=".mrc", mode="r") as f: 132 data = f["data"][:] 133 134 segmentation = data == 1 135 if require_object and segmentation.sum() == 0: 136 id_str = "" if object_id is None else f"for object {object_id}" 137 raise RuntimeError(f"Segmentation extracted from {imod_path} {id_str} is empty.") 138 139 if depth_interpolation is not None: 140 segmentation = maximum_filter1d(segmentation, size=depth_interpolation, axis=0, mode="constant") 141 142 if output_path is None: 143 return segmentation 144 145 imageio.imwrite(output_path, segmentation.astype("uint8"), compression="zlib")
Export a segmentation mask from IMOD annotations.
This function uses the imodmop command to extract masks.
Arguments:
- imod_path: The filepath to the imod annotation file.
- mrc_path: The filepath to the mrc file with the corresponding tomogram data.
- object_id: The id of the object to be extracted. If none is given all objects in the mod file will be extracted.
- output_path: Optional path to a file where to save the extracted mask as tif. If not given the segmentation mask will be returned as a numpy array.
- require_object: Whether to throw an error if the object could not be extractd.
- depth_interpolation: Window for interpolating the extracted mask across the depth axis. This can be used to close masks which are only annotated in a subset of slices.
Returns:
The extracted mask. Will only be returned if
output_path
is not given.
148def draw_spheres( 149 coordinates: np.ndarray, 150 radii: np.ndarray, 151 shape: Tuple[int, int, int], 152 verbose: bool = True, 153) -> np.ndarray: 154 """Create a volumetric segmentation by painting spheres around the given coordinates. 155 156 Args: 157 coordinates: The center coordinates of the spheres. 158 radii: The radii of the spheres. 159 shape: The shape of the volume. 160 verbose: Whether to print the progress bar. 161 162 Returns: 163 The segmentation volume with painted spheres. 164 """ 165 labels = np.zeros(shape, dtype="uint32") 166 for label_id, (coord, radius) in tqdm( 167 enumerate(zip(coordinates, radii), start=1), total=len(coordinates), disable=not verbose 168 ): 169 radius = int(radius) 170 mask = ball(radius) 171 full_mask = np.zeros(shape, dtype="bool") 172 full_slice = tuple( 173 slice(max(co - radius, 0), min(co + radius, sh)) for co, sh in zip(coord, shape) 174 ) 175 radius_clipped_left = [co - max(co - radius, 0) for co in coord] 176 radius_clipped_right = [min(co + radius, sh) - co for co, sh in zip(coord, shape)] 177 mask_slice = tuple( 178 slice(radius + 1 - rl, radius + 1 + rr) for rl, rr in zip(radius_clipped_left, radius_clipped_right) 179 ) 180 full_mask[full_slice] = mask[mask_slice] 181 labels[full_mask] = label_id 182 return labels
Create a volumetric segmentation by painting spheres around the given coordinates.
Arguments:
- coordinates: The center coordinates of the spheres.
- radii: The radii of the spheres.
- shape: The shape of the volume.
- verbose: Whether to print the progress bar.
Returns:
The segmentation volume with painted spheres.
185def load_points_from_imodinfo( 186 imod_path: str, 187 full_shape: Tuple[int, int, int], 188 bb: Optional[Tuple[slice, slice, slice]] = None, 189 exclude_labels: Optional[List[int]] = None, 190 exclude_label_patterns: Optional[List[str]] = None, 191 resolution: Optional[float] = None, 192) -> Tuple[np.ndarray, np.ndarray, np.ndarray, Dict[int, str]]: 193 """Load point coordinates, radii and label information from a .mod file. 194 195 The coordinates and sizes returned will be scaled so that they are in 196 the voxel coordinate space if the 'resolution' parameter is passed. 197 If it is not passed then the radius will be returned in the physical resolution. 198 199 Args: 200 imod_path: The filepath to the .mod file. 201 full_shape: The voxel shape of the volume. 202 bb: Optional bounding box to limit the extracted points to. 203 exclude_labels: Label ids to exclude from the export. 204 This can be used to exclude specific labels / classes, specifying them by their id. 205 exclude_label_patterns: Label names to exclude from the export. 206 This can be used to exclude specific labels / classes, specifying them by their name. 207 resolution: The resolution / voxel size of the data. Will be used to scale the radii. 208 209 Returns: 210 The center coordinates of the sphere annotations. 211 The radii of the spheres. 212 The ids of the semantic labels. 213 The names of the semantic labels. 214 """ 215 coordinates, sizes, labels = [], [], [] 216 label_names = {} 217 218 if bb is not None: 219 start = [b.start for b in bb] 220 shape = [b.stop - sta for b, sta in zip(bb, start)] 221 222 # first round: load the point sizes and labels 223 with tempfile.NamedTemporaryFile() as f: 224 tmp_path = f.name 225 run(["imodinfo", "-p", "-f", tmp_path, imod_path]) 226 227 label_id = None 228 label_name = None 229 with open(tmp_path) as f: 230 lines = f.readlines() 231 for line in lines: 232 233 if line.startswith("#Object"): 234 line = line.rstrip("\n") 235 label_id = int(line.split(" ")[1]) 236 label_name = line.split(",")[1] 237 label_names[label_id] = label_name 238 continue 239 240 if label_id is None: 241 continue 242 if exclude_labels is not None and label_id in exclude_labels: 243 continue 244 if exclude_label_patterns is not None and any( 245 pattern.lower() in label_name.lower() for pattern in exclude_label_patterns 246 ): 247 continue 248 249 try: 250 size = float(line.rstrip("\n")) 251 sizes.append(size) 252 labels.append(label_id) 253 except ValueError: 254 continue 255 256 label_names = {lid: label_name for lid, label_name in label_names.items() if lid in labels} 257 258 in_bounds = [] 259 with tempfile.NamedTemporaryFile() as f: 260 tmp_path = f.name 261 run(["imodinfo", "-vv", "-f", tmp_path, imod_path]) 262 263 label_id = None 264 with open(tmp_path) as f: 265 lines = f.readlines() 266 for line in lines: 267 if line.startswith("OBJECT"): 268 label_id = int(line.rstrip("\n").split(" ")[-1]) 269 continue 270 if label_id not in label_names: 271 continue 272 273 values = line.strip().rstrip("\n").split("\t") 274 if len(values) != 3: 275 continue 276 277 try: 278 x, y, z = float(values[0]), float(values[1]), float(values[2]) 279 coords = [x, y, z] 280 coords = [int(np.round(float(coord))) for coord in coords] 281 # IMOD uses a very weird coordinate system: 282 coords = [coords[2], full_shape[1] - coords[1], coords[0]] 283 284 in_bound = True 285 if bb is not None: 286 coords = [co - sta for co, sta in zip(coords, start)] 287 if any(co < 0 or co > sh for co, sh in zip(coords, shape)): 288 in_bound = False 289 290 in_bounds.append(in_bound) 291 coordinates.append(coords) 292 except ValueError: 293 continue 294 295 # labelx = {seg_id: int(label_id) for seg_id, label_id in enumerate(labels, 1)} 296 # print("Extracted the following labels:", label_names) 297 # print("With counts:", {k: v for k, v in zip(*np.unique(list(labelx.values()), return_counts=True))}) 298 # breakpoint() 299 assert len(coordinates) == len(sizes) == len(labels) == len(in_bounds), \ 300 f"{len(coordinates)}, {len(sizes)}, {len(labels)}, {len(in_bounds)}" 301 302 coordinates, sizes, labels = np.array(coordinates), np.array(sizes), np.array(labels) 303 in_bounds = np.array(in_bounds) 304 305 # get rid of empty annotations 306 in_bounds = np.logical_and(in_bounds, sizes > 0) 307 308 coordinates, sizes, labels = coordinates[in_bounds], sizes[in_bounds], labels[in_bounds] 309 310 if resolution is not None: 311 sizes /= resolution 312 313 if len(coordinates) == 0: 314 raise RuntimeError(f"Empty annotations: {imod_path}") 315 return coordinates, sizes, labels, label_names
Load point coordinates, radii and label information from a .mod file.
The coordinates and sizes returned will be scaled so that they are in the voxel coordinate space if the 'resolution' parameter is passed. If it is not passed then the radius will be returned in the physical resolution.
Arguments:
- imod_path: The filepath to the .mod file.
- full_shape: The voxel shape of the volume.
- bb: Optional bounding box to limit the extracted points to.
- exclude_labels: Label ids to exclude from the export. This can be used to exclude specific labels / classes, specifying them by their id.
- exclude_label_patterns: Label names to exclude from the export. This can be used to exclude specific labels / classes, specifying them by their name.
- resolution: The resolution / voxel size of the data. Will be used to scale the radii.
Returns:
The center coordinates of the sphere annotations. The radii of the spheres. The ids of the semantic labels. The names of the semantic labels.
318def export_point_annotations( 319 imod_path: str, 320 shape: Tuple[int, int, int], 321 bb: Optional[Tuple[slice, slice, slice]] = None, 322 exclude_labels: Optional[List[int]] = None, 323 exclude_label_patterns: Optional[List[str]] = None, 324 return_coords_and_radii: bool = False, 325 resolution: Optional[float] = None, 326) -> Tuple[np.ndarray, np.ndarray, Dict[int, str]]: 327 """Create a segmentation by drawing spheres corresponding to objects from a .mod file. 328 329 Args: 330 imod_path: The filepath to the .mod file. 331 shape: The voxel shape of the volume. 332 bb: Optional bounding box to limit the extracted points to. 333 exclude_labels: Label ids to exclude from the segmentation. 334 This can be used to exclude specific labels / classes, specifying them by their id. 335 exclude_label_patterns: Label names to exclude from the segmentation. 336 This can be used to exclude specific labels / classes, specifying them by their name. 337 return_coords_and_radii: Whether to also return the underlying coordinates 338 and radii of the exported spheres. 339 resolution: The resolution / voxel size of the data. Will be used to scale the radii. 340 341 Returns: 342 The exported segmentation. 343 The label ids for the instance ids in the segmentation. 344 The map of label ids to corresponding obejct names. 345 """ 346 coordinates, radii, labels, label_names = load_points_from_imodinfo( 347 imod_path, shape, bb=bb, 348 exclude_labels=exclude_labels, 349 exclude_label_patterns=exclude_label_patterns, 350 resolution=resolution, 351 ) 352 labels = {seg_id: int(label_id) for seg_id, label_id in enumerate(labels, 1)} 353 segmentation = draw_spheres(coordinates, radii, shape) 354 if return_coords_and_radii: 355 return segmentation, labels, label_names, coordinates, radii 356 return segmentation, labels, label_names
Create a segmentation by drawing spheres corresponding to objects from a .mod file.
Arguments:
- imod_path: The filepath to the .mod file.
- shape: The voxel shape of the volume.
- bb: Optional bounding box to limit the extracted points to.
- exclude_labels: Label ids to exclude from the segmentation. This can be used to exclude specific labels / classes, specifying them by their id.
- exclude_label_patterns: Label names to exclude from the segmentation. This can be used to exclude specific labels / classes, specifying them by their name.
- return_coords_and_radii: Whether to also return the underlying coordinates and radii of the exported spheres.
- resolution: The resolution / voxel size of the data. Will be used to scale the radii.
Returns:
The exported segmentation. The label ids for the instance ids in the segmentation. The map of label ids to corresponding obejct names.