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