csi_images.csi_frames
Contains the Frame class, which represents a single frame of an image. The Frame class does not hold the image data, but allows for easy loading of the image data from the appropriate file. This module also contains functions for creating RGB and RGBW composite images from a tile and a set of channels.
1""" 2Contains the Frame class, which represents a single frame of an image. The Frame class 3does not hold the image data, but allows for easy loading of the image data from the 4appropriate file. This module also contains functions for creating RGB and RGBW 5composite images from a tile and a set of channels. 6""" 7 8import os 9import typing 10 11import tifffile 12import numpy as np 13 14from .csi_scans import Scan 15from .csi_tiles import Tile 16from . import csi_images 17 18 19class Frame: 20 def __init__(self, scan: Scan, tile: Tile, channel: int | str): 21 self.scan = scan 22 self.tile = tile 23 if isinstance(channel, int): 24 self.channel = channel 25 if self.channel < 0 or self.channel >= len(scan.channels): 26 raise ValueError( 27 f"Channel index {self.channel} is out of bounds for scan." 28 ) 29 elif isinstance(channel, str): 30 self.channel = self.scan.get_channel_indices([channel]) 31 32 def __repr__(self) -> str: 33 return f"{self.scan.slide_id}-{self.tile.n}-{self.scan.channels[self.channel].name}" 34 35 def __eq__(self, other) -> bool: 36 return self.__repr__() == other.__repr__() 37 38 def get_file_path( 39 self, input_path: str = None, file_extension: str = ".tif" 40 ) -> str: 41 """ 42 Get the file path for the frame, optionally changing 43 the scan path and file extension. 44 :param input_path: the path to the scan's directory. If None, defaults to 45 the path loaded in the frame's tile's scan object. 46 :param file_extension: the image file extension. Defaults to .tif. 47 :return: the file path. 48 """ 49 if input_path is None: 50 input_path = self.scan.path 51 if len(self.scan.roi) > 1: 52 input_path = os.path.join(input_path, f"roi_{self.tile.n_roi}") 53 # Remove trailing slashes 54 if input_path[-1] == os.sep: 55 input_path = input_path[:-1] 56 # Append proc if it's pointing to the base bzScanner directory 57 if input_path.endswith("bzScanner"): 58 input_path = os.path.join(input_path, "proc") 59 # Should be a directory; append the file name 60 if os.path.isdir(input_path): 61 input_path = os.path.join(input_path, self.get_file_name()) 62 else: 63 raise ValueError(f"Input path {input_path} is not a directory.") 64 return input_path 65 66 def get_file_name(self, file_extension: str = ".tif") -> str: 67 """ 68 Get the file name for the frame, handling different name conventions by scanner. 69 :param file_extension: the image file extension. Defaults to .tif. 70 :return: the file name. 71 """ 72 if self.scan.scanner_id.startswith(Scan.Type.AXIOSCAN7.value): 73 channel_name = self.scan.channels[self.channel].name 74 x = self.tile.x 75 y = self.tile.y 76 file_name = f"{channel_name}-X{x:03}-Y{y:03}{file_extension}" 77 elif self.scan.scanner_id.startswith(Scan.Type.BZSCANNER.value): 78 channel_name = self.scan.channels[self.channel].name 79 real_channel_index = list(self.scan.BZSCANNER_CHANNEL_MAP.values()).index( 80 channel_name 81 ) 82 total_tiles = self.scan.roi[0].tile_rows * self.scan.roi[0].tile_cols 83 tile_offset = (real_channel_index * total_tiles) + 1 # 1-indexed 84 n_bzscanner = self.tile.n + tile_offset 85 file_name = f"Tile{n_bzscanner:06}{file_extension}" 86 else: 87 raise ValueError(f"Scanner {self.scan.scanner_id} not supported.") 88 return file_name 89 90 def get_image(self, input_path: str = None) -> np.ndarray: 91 """ 92 Loads the image for this frame. Handles .tif (will return 16-bit images) and 93 .jpg/.jpeg (will return 8-bit images), based on the CSI convention for storing 94 .jpg/.jpeg images (compressed, using .tags files). 95 :param input_path: the path to the scan's directory. If None, defaults to 96 the path loaded in the frame's tile's scan object. 97 :return: the array representing the image. 98 """ 99 100 file_path = self.get_file_path(input_path) 101 102 # Check for the file 103 if not os.path.exists(file_path): 104 # Alternative: could be a .jpg/.jpeg file, test both 105 jpeg_path = os.path.splitext(file_path)[0] + ".jpg" 106 if os.path.exists(jpeg_path): 107 file_path = jpeg_path 108 jpeg_path = os.path.splitext(file_path)[0] + ".jpeg" 109 if os.path.exists(jpeg_path): 110 file_path = jpeg_path 111 # If we've found a .jpg/.jpeg, try loading it as compressed 112 if file_path == jpeg_path: 113 return self._get_jpeg_image(file_path) 114 else: 115 raise FileNotFoundError(f"Could not find image at {file_path}") 116 else: 117 # Load the image 118 image = tifffile.imread(file_path) 119 if image is None or image.size == 0: 120 raise ValueError(f"Could not load image from {file_path}") 121 return image 122 123 def _get_jpeg_image(self, input_path: str) -> np.ndarray: 124 raise NotImplementedError("JPEG image loading not yet implemented.") 125 126 def check_image(self, input_path: str = None) -> bool: 127 """ 128 Check if the image for this frame exists. 129 :param input_path: the path to the scan's directory. If None, defaults to 130 the path loaded in the frame's tile's scan object. 131 :return: whether the image exists. 132 """ 133 file_path = self.get_file_path(input_path) 134 # 72 is the minimum size for a valid TIFF file 135 if os.path.exists(file_path) and os.path.getsize(file_path) > 72: 136 return True 137 else: 138 # Alternative: could be a .jpg/.jpeg file, test both 139 jpeg_path = os.path.splitext(file_path)[0] + ".jpg" 140 if os.path.exists(jpeg_path) and os.path.getsize(jpeg_path) > 107: 141 file_path = jpeg_path 142 jpeg_path = os.path.splitext(file_path)[0] + ".jpeg" 143 if os.path.exists(jpeg_path) and os.path.getsize(jpeg_path) > 107: 144 file_path = jpeg_path 145 # If we've found a .jpg/.jpeg, it must have a .tags file with it 146 if file_path == jpeg_path: 147 tags_path = os.path.splitext(file_path)[0] + ".tags" 148 # Tags are text files that should include at least a few bytes 149 if os.path.exists(tags_path) and os.path.getsize(tags_path) > 20: 150 return True 151 # Didn't hit any of those, return false 152 return False 153 154 @classmethod 155 def check_all_images(cls, scan: Scan): 156 """ 157 Check if all images for a scan exist, either in .tif or .jpg form. 158 :param scan: 159 :return: 160 """ 161 for n in range(len(scan.roi)): 162 for frames in cls.get_all_frames(scan, n_roi=n): 163 for frame in frames: 164 if not frame.check_image(): 165 return False 166 return True 167 168 @classmethod 169 def get_frames( 170 cls, tile: Tile, channels: tuple[int | str] = None 171 ) -> list[typing.Self]: 172 """ 173 Get the frames for a tile and a set of channels. By default, gets all channels. 174 :param tile: the tile. 175 :param channels: the channels, as indices or names. Defaults to all channels. 176 :return: the frames, in order of the channels. 177 """ 178 if channels is None: 179 channels = range(len(tile.scan.channels)) 180 frames = [] 181 for channel in channels: 182 frames.append(Frame(tile.scan, tile, channel)) 183 return frames 184 185 @classmethod 186 def get_all_frames( 187 cls, 188 scan: Scan, 189 channels: tuple[int | str] = None, 190 n_roi: int = 0, 191 as_flat: bool = True, 192 ) -> list[list[typing.Self]] | list[list[list[typing.Self]]]: 193 """ 194 Get all frames for a scan and a set of channels. 195 :param scan: the scan metadata. 196 :param channels: the channels, as indices or names. Defaults to all channels. 197 :param n_roi: the region of interest to use. Defaults to 0. 198 :param as_flat: whether to flatten the frames into a 2D list. 199 :return: if as_flat: 2D list of frames, organized as [n][channel]; 200 if not as_flat: 3D list of frames organized as [row][col][channel] a.k.a. [y][x][channel]. 201 """ 202 if as_flat: 203 frames = [] 204 for n in range(scan.roi[n_roi].tile_rows * scan.roi[n_roi].tile_cols): 205 tile = Tile(scan, n, n_roi) 206 frames.append(cls.get_frames(tile, channels)) 207 else: 208 frames = [[None] * scan.roi[n_roi].tile_cols] * scan.roi[n_roi].tile_rows 209 for x in range(scan.roi[n_roi].tile_cols): 210 for y in range(scan.roi[n_roi].tile_rows): 211 tile = Tile(scan, (x, y), n_roi) 212 frames[y][x] = cls.get_frames(tile, channels) 213 return frames 214 215 @classmethod 216 def make_rgb_image( 217 cls, 218 tile: Tile, 219 channels: dict[int, tuple[float, float, float]], 220 input_path=None, 221 ) -> np.ndarray: 222 """ 223 Convenience method for creating an RGB image from a tile and a set of channels 224 without manually extracting any frames. 225 :param tile: the tile for which the image should be made. 226 :param channels: a dictionary of scan channel indices and RGB gains. 227 :param input_path: the path to the input images. Will use metadata if not provided. 228 :return: the image as a numpy array. 229 """ 230 images = [] 231 colors = [] 232 for channel_index, color in channels.items(): 233 if channel_index == -1: 234 continue 235 image = Frame(tile.scan, tile, channel_index).get_image(input_path) 236 images.append(image) 237 colors.append(color) 238 return csi_images.make_rgb(images, colors)
20class Frame: 21 def __init__(self, scan: Scan, tile: Tile, channel: int | str): 22 self.scan = scan 23 self.tile = tile 24 if isinstance(channel, int): 25 self.channel = channel 26 if self.channel < 0 or self.channel >= len(scan.channels): 27 raise ValueError( 28 f"Channel index {self.channel} is out of bounds for scan." 29 ) 30 elif isinstance(channel, str): 31 self.channel = self.scan.get_channel_indices([channel]) 32 33 def __repr__(self) -> str: 34 return f"{self.scan.slide_id}-{self.tile.n}-{self.scan.channels[self.channel].name}" 35 36 def __eq__(self, other) -> bool: 37 return self.__repr__() == other.__repr__() 38 39 def get_file_path( 40 self, input_path: str = None, file_extension: str = ".tif" 41 ) -> str: 42 """ 43 Get the file path for the frame, optionally changing 44 the scan path and file extension. 45 :param input_path: the path to the scan's directory. If None, defaults to 46 the path loaded in the frame's tile's scan object. 47 :param file_extension: the image file extension. Defaults to .tif. 48 :return: the file path. 49 """ 50 if input_path is None: 51 input_path = self.scan.path 52 if len(self.scan.roi) > 1: 53 input_path = os.path.join(input_path, f"roi_{self.tile.n_roi}") 54 # Remove trailing slashes 55 if input_path[-1] == os.sep: 56 input_path = input_path[:-1] 57 # Append proc if it's pointing to the base bzScanner directory 58 if input_path.endswith("bzScanner"): 59 input_path = os.path.join(input_path, "proc") 60 # Should be a directory; append the file name 61 if os.path.isdir(input_path): 62 input_path = os.path.join(input_path, self.get_file_name()) 63 else: 64 raise ValueError(f"Input path {input_path} is not a directory.") 65 return input_path 66 67 def get_file_name(self, file_extension: str = ".tif") -> str: 68 """ 69 Get the file name for the frame, handling different name conventions by scanner. 70 :param file_extension: the image file extension. Defaults to .tif. 71 :return: the file name. 72 """ 73 if self.scan.scanner_id.startswith(Scan.Type.AXIOSCAN7.value): 74 channel_name = self.scan.channels[self.channel].name 75 x = self.tile.x 76 y = self.tile.y 77 file_name = f"{channel_name}-X{x:03}-Y{y:03}{file_extension}" 78 elif self.scan.scanner_id.startswith(Scan.Type.BZSCANNER.value): 79 channel_name = self.scan.channels[self.channel].name 80 real_channel_index = list(self.scan.BZSCANNER_CHANNEL_MAP.values()).index( 81 channel_name 82 ) 83 total_tiles = self.scan.roi[0].tile_rows * self.scan.roi[0].tile_cols 84 tile_offset = (real_channel_index * total_tiles) + 1 # 1-indexed 85 n_bzscanner = self.tile.n + tile_offset 86 file_name = f"Tile{n_bzscanner:06}{file_extension}" 87 else: 88 raise ValueError(f"Scanner {self.scan.scanner_id} not supported.") 89 return file_name 90 91 def get_image(self, input_path: str = None) -> np.ndarray: 92 """ 93 Loads the image for this frame. Handles .tif (will return 16-bit images) and 94 .jpg/.jpeg (will return 8-bit images), based on the CSI convention for storing 95 .jpg/.jpeg images (compressed, using .tags files). 96 :param input_path: the path to the scan's directory. If None, defaults to 97 the path loaded in the frame's tile's scan object. 98 :return: the array representing the image. 99 """ 100 101 file_path = self.get_file_path(input_path) 102 103 # Check for the file 104 if not os.path.exists(file_path): 105 # Alternative: could be a .jpg/.jpeg file, test both 106 jpeg_path = os.path.splitext(file_path)[0] + ".jpg" 107 if os.path.exists(jpeg_path): 108 file_path = jpeg_path 109 jpeg_path = os.path.splitext(file_path)[0] + ".jpeg" 110 if os.path.exists(jpeg_path): 111 file_path = jpeg_path 112 # If we've found a .jpg/.jpeg, try loading it as compressed 113 if file_path == jpeg_path: 114 return self._get_jpeg_image(file_path) 115 else: 116 raise FileNotFoundError(f"Could not find image at {file_path}") 117 else: 118 # Load the image 119 image = tifffile.imread(file_path) 120 if image is None or image.size == 0: 121 raise ValueError(f"Could not load image from {file_path}") 122 return image 123 124 def _get_jpeg_image(self, input_path: str) -> np.ndarray: 125 raise NotImplementedError("JPEG image loading not yet implemented.") 126 127 def check_image(self, input_path: str = None) -> bool: 128 """ 129 Check if the image for this frame exists. 130 :param input_path: the path to the scan's directory. If None, defaults to 131 the path loaded in the frame's tile's scan object. 132 :return: whether the image exists. 133 """ 134 file_path = self.get_file_path(input_path) 135 # 72 is the minimum size for a valid TIFF file 136 if os.path.exists(file_path) and os.path.getsize(file_path) > 72: 137 return True 138 else: 139 # Alternative: could be a .jpg/.jpeg file, test both 140 jpeg_path = os.path.splitext(file_path)[0] + ".jpg" 141 if os.path.exists(jpeg_path) and os.path.getsize(jpeg_path) > 107: 142 file_path = jpeg_path 143 jpeg_path = os.path.splitext(file_path)[0] + ".jpeg" 144 if os.path.exists(jpeg_path) and os.path.getsize(jpeg_path) > 107: 145 file_path = jpeg_path 146 # If we've found a .jpg/.jpeg, it must have a .tags file with it 147 if file_path == jpeg_path: 148 tags_path = os.path.splitext(file_path)[0] + ".tags" 149 # Tags are text files that should include at least a few bytes 150 if os.path.exists(tags_path) and os.path.getsize(tags_path) > 20: 151 return True 152 # Didn't hit any of those, return false 153 return False 154 155 @classmethod 156 def check_all_images(cls, scan: Scan): 157 """ 158 Check if all images for a scan exist, either in .tif or .jpg form. 159 :param scan: 160 :return: 161 """ 162 for n in range(len(scan.roi)): 163 for frames in cls.get_all_frames(scan, n_roi=n): 164 for frame in frames: 165 if not frame.check_image(): 166 return False 167 return True 168 169 @classmethod 170 def get_frames( 171 cls, tile: Tile, channels: tuple[int | str] = None 172 ) -> list[typing.Self]: 173 """ 174 Get the frames for a tile and a set of channels. By default, gets all channels. 175 :param tile: the tile. 176 :param channels: the channels, as indices or names. Defaults to all channels. 177 :return: the frames, in order of the channels. 178 """ 179 if channels is None: 180 channels = range(len(tile.scan.channels)) 181 frames = [] 182 for channel in channels: 183 frames.append(Frame(tile.scan, tile, channel)) 184 return frames 185 186 @classmethod 187 def get_all_frames( 188 cls, 189 scan: Scan, 190 channels: tuple[int | str] = None, 191 n_roi: int = 0, 192 as_flat: bool = True, 193 ) -> list[list[typing.Self]] | list[list[list[typing.Self]]]: 194 """ 195 Get all frames for a scan and a set of channels. 196 :param scan: the scan metadata. 197 :param channels: the channels, as indices or names. Defaults to all channels. 198 :param n_roi: the region of interest to use. Defaults to 0. 199 :param as_flat: whether to flatten the frames into a 2D list. 200 :return: if as_flat: 2D list of frames, organized as [n][channel]; 201 if not as_flat: 3D list of frames organized as [row][col][channel] a.k.a. [y][x][channel]. 202 """ 203 if as_flat: 204 frames = [] 205 for n in range(scan.roi[n_roi].tile_rows * scan.roi[n_roi].tile_cols): 206 tile = Tile(scan, n, n_roi) 207 frames.append(cls.get_frames(tile, channels)) 208 else: 209 frames = [[None] * scan.roi[n_roi].tile_cols] * scan.roi[n_roi].tile_rows 210 for x in range(scan.roi[n_roi].tile_cols): 211 for y in range(scan.roi[n_roi].tile_rows): 212 tile = Tile(scan, (x, y), n_roi) 213 frames[y][x] = cls.get_frames(tile, channels) 214 return frames 215 216 @classmethod 217 def make_rgb_image( 218 cls, 219 tile: Tile, 220 channels: dict[int, tuple[float, float, float]], 221 input_path=None, 222 ) -> np.ndarray: 223 """ 224 Convenience method for creating an RGB image from a tile and a set of channels 225 without manually extracting any frames. 226 :param tile: the tile for which the image should be made. 227 :param channels: a dictionary of scan channel indices and RGB gains. 228 :param input_path: the path to the input images. Will use metadata if not provided. 229 :return: the image as a numpy array. 230 """ 231 images = [] 232 colors = [] 233 for channel_index, color in channels.items(): 234 if channel_index == -1: 235 continue 236 image = Frame(tile.scan, tile, channel_index).get_image(input_path) 237 images.append(image) 238 colors.append(color) 239 return csi_images.make_rgb(images, colors)
21 def __init__(self, scan: Scan, tile: Tile, channel: int | str): 22 self.scan = scan 23 self.tile = tile 24 if isinstance(channel, int): 25 self.channel = channel 26 if self.channel < 0 or self.channel >= len(scan.channels): 27 raise ValueError( 28 f"Channel index {self.channel} is out of bounds for scan." 29 ) 30 elif isinstance(channel, str): 31 self.channel = self.scan.get_channel_indices([channel])
39 def get_file_path( 40 self, input_path: str = None, file_extension: str = ".tif" 41 ) -> str: 42 """ 43 Get the file path for the frame, optionally changing 44 the scan path and file extension. 45 :param input_path: the path to the scan's directory. If None, defaults to 46 the path loaded in the frame's tile's scan object. 47 :param file_extension: the image file extension. Defaults to .tif. 48 :return: the file path. 49 """ 50 if input_path is None: 51 input_path = self.scan.path 52 if len(self.scan.roi) > 1: 53 input_path = os.path.join(input_path, f"roi_{self.tile.n_roi}") 54 # Remove trailing slashes 55 if input_path[-1] == os.sep: 56 input_path = input_path[:-1] 57 # Append proc if it's pointing to the base bzScanner directory 58 if input_path.endswith("bzScanner"): 59 input_path = os.path.join(input_path, "proc") 60 # Should be a directory; append the file name 61 if os.path.isdir(input_path): 62 input_path = os.path.join(input_path, self.get_file_name()) 63 else: 64 raise ValueError(f"Input path {input_path} is not a directory.") 65 return input_path
Get the file path for the frame, optionally changing the scan path and file extension.
Parameters
- input_path: the path to the scan's directory. If None, defaults to the path loaded in the frame's tile's scan object.
- file_extension: the image file extension. Defaults to .tif.
Returns
the file path.
67 def get_file_name(self, file_extension: str = ".tif") -> str: 68 """ 69 Get the file name for the frame, handling different name conventions by scanner. 70 :param file_extension: the image file extension. Defaults to .tif. 71 :return: the file name. 72 """ 73 if self.scan.scanner_id.startswith(Scan.Type.AXIOSCAN7.value): 74 channel_name = self.scan.channels[self.channel].name 75 x = self.tile.x 76 y = self.tile.y 77 file_name = f"{channel_name}-X{x:03}-Y{y:03}{file_extension}" 78 elif self.scan.scanner_id.startswith(Scan.Type.BZSCANNER.value): 79 channel_name = self.scan.channels[self.channel].name 80 real_channel_index = list(self.scan.BZSCANNER_CHANNEL_MAP.values()).index( 81 channel_name 82 ) 83 total_tiles = self.scan.roi[0].tile_rows * self.scan.roi[0].tile_cols 84 tile_offset = (real_channel_index * total_tiles) + 1 # 1-indexed 85 n_bzscanner = self.tile.n + tile_offset 86 file_name = f"Tile{n_bzscanner:06}{file_extension}" 87 else: 88 raise ValueError(f"Scanner {self.scan.scanner_id} not supported.") 89 return file_name
Get the file name for the frame, handling different name conventions by scanner.
Parameters
- file_extension: the image file extension. Defaults to .tif.
Returns
the file name.
91 def get_image(self, input_path: str = None) -> np.ndarray: 92 """ 93 Loads the image for this frame. Handles .tif (will return 16-bit images) and 94 .jpg/.jpeg (will return 8-bit images), based on the CSI convention for storing 95 .jpg/.jpeg images (compressed, using .tags files). 96 :param input_path: the path to the scan's directory. If None, defaults to 97 the path loaded in the frame's tile's scan object. 98 :return: the array representing the image. 99 """ 100 101 file_path = self.get_file_path(input_path) 102 103 # Check for the file 104 if not os.path.exists(file_path): 105 # Alternative: could be a .jpg/.jpeg file, test both 106 jpeg_path = os.path.splitext(file_path)[0] + ".jpg" 107 if os.path.exists(jpeg_path): 108 file_path = jpeg_path 109 jpeg_path = os.path.splitext(file_path)[0] + ".jpeg" 110 if os.path.exists(jpeg_path): 111 file_path = jpeg_path 112 # If we've found a .jpg/.jpeg, try loading it as compressed 113 if file_path == jpeg_path: 114 return self._get_jpeg_image(file_path) 115 else: 116 raise FileNotFoundError(f"Could not find image at {file_path}") 117 else: 118 # Load the image 119 image = tifffile.imread(file_path) 120 if image is None or image.size == 0: 121 raise ValueError(f"Could not load image from {file_path}") 122 return image
Loads the image for this frame. Handles .tif (will return 16-bit images) and .jpg/.jpeg (will return 8-bit images), based on the CSI convention for storing .jpg/.jpeg images (compressed, using .tags files).
Parameters
- input_path: the path to the scan's directory. If None, defaults to the path loaded in the frame's tile's scan object.
Returns
the array representing the image.
127 def check_image(self, input_path: str = None) -> bool: 128 """ 129 Check if the image for this frame exists. 130 :param input_path: the path to the scan's directory. If None, defaults to 131 the path loaded in the frame's tile's scan object. 132 :return: whether the image exists. 133 """ 134 file_path = self.get_file_path(input_path) 135 # 72 is the minimum size for a valid TIFF file 136 if os.path.exists(file_path) and os.path.getsize(file_path) > 72: 137 return True 138 else: 139 # Alternative: could be a .jpg/.jpeg file, test both 140 jpeg_path = os.path.splitext(file_path)[0] + ".jpg" 141 if os.path.exists(jpeg_path) and os.path.getsize(jpeg_path) > 107: 142 file_path = jpeg_path 143 jpeg_path = os.path.splitext(file_path)[0] + ".jpeg" 144 if os.path.exists(jpeg_path) and os.path.getsize(jpeg_path) > 107: 145 file_path = jpeg_path 146 # If we've found a .jpg/.jpeg, it must have a .tags file with it 147 if file_path == jpeg_path: 148 tags_path = os.path.splitext(file_path)[0] + ".tags" 149 # Tags are text files that should include at least a few bytes 150 if os.path.exists(tags_path) and os.path.getsize(tags_path) > 20: 151 return True 152 # Didn't hit any of those, return false 153 return False
Check if the image for this frame exists.
Parameters
- input_path: the path to the scan's directory. If None, defaults to the path loaded in the frame's tile's scan object.
Returns
whether the image exists.
155 @classmethod 156 def check_all_images(cls, scan: Scan): 157 """ 158 Check if all images for a scan exist, either in .tif or .jpg form. 159 :param scan: 160 :return: 161 """ 162 for n in range(len(scan.roi)): 163 for frames in cls.get_all_frames(scan, n_roi=n): 164 for frame in frames: 165 if not frame.check_image(): 166 return False 167 return True
Check if all images for a scan exist, either in .tif or .jpg form.
Parameters
- scan:
Returns
169 @classmethod 170 def get_frames( 171 cls, tile: Tile, channels: tuple[int | str] = None 172 ) -> list[typing.Self]: 173 """ 174 Get the frames for a tile and a set of channels. By default, gets all channels. 175 :param tile: the tile. 176 :param channels: the channels, as indices or names. Defaults to all channels. 177 :return: the frames, in order of the channels. 178 """ 179 if channels is None: 180 channels = range(len(tile.scan.channels)) 181 frames = [] 182 for channel in channels: 183 frames.append(Frame(tile.scan, tile, channel)) 184 return frames
Get the frames for a tile and a set of channels. By default, gets all channels.
Parameters
- tile: the tile.
- channels: the channels, as indices or names. Defaults to all channels.
Returns
the frames, in order of the channels.
186 @classmethod 187 def get_all_frames( 188 cls, 189 scan: Scan, 190 channels: tuple[int | str] = None, 191 n_roi: int = 0, 192 as_flat: bool = True, 193 ) -> list[list[typing.Self]] | list[list[list[typing.Self]]]: 194 """ 195 Get all frames for a scan and a set of channels. 196 :param scan: the scan metadata. 197 :param channels: the channels, as indices or names. Defaults to all channels. 198 :param n_roi: the region of interest to use. Defaults to 0. 199 :param as_flat: whether to flatten the frames into a 2D list. 200 :return: if as_flat: 2D list of frames, organized as [n][channel]; 201 if not as_flat: 3D list of frames organized as [row][col][channel] a.k.a. [y][x][channel]. 202 """ 203 if as_flat: 204 frames = [] 205 for n in range(scan.roi[n_roi].tile_rows * scan.roi[n_roi].tile_cols): 206 tile = Tile(scan, n, n_roi) 207 frames.append(cls.get_frames(tile, channels)) 208 else: 209 frames = [[None] * scan.roi[n_roi].tile_cols] * scan.roi[n_roi].tile_rows 210 for x in range(scan.roi[n_roi].tile_cols): 211 for y in range(scan.roi[n_roi].tile_rows): 212 tile = Tile(scan, (x, y), n_roi) 213 frames[y][x] = cls.get_frames(tile, channels) 214 return frames
Get all frames for a scan and a set of channels.
Parameters
- scan: the scan metadata.
- channels: the channels, as indices or names. Defaults to all channels.
- n_roi: the region of interest to use. Defaults to 0.
- as_flat: whether to flatten the frames into a 2D list.
Returns
if as_flat: 2D list of frames, organized as [n][channel]; if not as_flat: 3D list of frames organized as [row][col][channel] a.k.a. [y][x][channel].
216 @classmethod 217 def make_rgb_image( 218 cls, 219 tile: Tile, 220 channels: dict[int, tuple[float, float, float]], 221 input_path=None, 222 ) -> np.ndarray: 223 """ 224 Convenience method for creating an RGB image from a tile and a set of channels 225 without manually extracting any frames. 226 :param tile: the tile for which the image should be made. 227 :param channels: a dictionary of scan channel indices and RGB gains. 228 :param input_path: the path to the input images. Will use metadata if not provided. 229 :return: the image as a numpy array. 230 """ 231 images = [] 232 colors = [] 233 for channel_index, color in channels.items(): 234 if channel_index == -1: 235 continue 236 image = Frame(tile.scan, tile, channel_index).get_image(input_path) 237 images.append(image) 238 colors.append(color) 239 return csi_images.make_rgb(images, colors)
Convenience method for creating an RGB image from a tile and a set of channels without manually extracting any frames.
Parameters
- tile: the tile for which the image should be made.
- channels: a dictionary of scan channel indices and RGB gains.
- input_path: the path to the input images. Will use metadata if not provided.
Returns
the image as a numpy array.