csi_images.csi_frames API documentation

# 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.

View Source

      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)

class Frame: View Source

     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)

Frame( scan: csi_images.csi_scans.Scan, tile: csi_images.csi_tiles.Tile, channel: int | str) View Source

    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])

scan
tile
def get_file_path(self, input_path: str = None, file_extension: str = '.tif') -> str: View Source

    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.

def get_file_name(self, file_extension: str = '.tif') -> str: View Source

    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.

def get_image(self, input_path: str = None) -> numpy.ndarray: View Source

     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.

def check_image(self, input_path: str = None) -> bool: View Source

    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.

@classmethod
def check_all_images(cls, scan: csi_images.csi_scans.Scan): View Source

    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

@classmethod
def get_frames( cls, tile: csi_images.csi_tiles.Tile, channels: tuple[int | str] = None) -> list[typing.Self]: View Source

    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.

@classmethod
def get_all_frames( cls, scan: csi_images.csi_scans.Scan, channels: tuple[int | str] = None, n_roi: int = 0, as_flat: bool = True) -> list[list[typing.Self]] | list[list[list[typing.Self]]]: View Source

    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].

@classmethod
def make_rgb_image( cls, tile: csi_images.csi_tiles.Tile, channels: dict[int, tuple[float, float, float]], input_path=None) -> numpy.ndarray: View Source

    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.