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