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

class Frame: View Source

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

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

    21    def __init__(self, scan: Scan, tile: Tile, channel: int | str):
    22        self.scan = scan
    23        self.tile = tile
    24        if isinstance(channel, int):
    25            self.channel = channel
    26            if self.channel < 0 or self.channel >= len(scan.channels):
    27                raise ValueError(
    28                    f"Channel index {self.channel} is out of bounds for scan."
    29                )
    30        elif isinstance(channel, str):
    31            self.channel = self.scan.get_channel_indices([channel])

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

    39    def get_file_path(
    40        self, input_path: str = None, file_extension: str = ".tif"
    41    ) -> str:
    42        """
    43        Get the file path for the frame, optionally changing
    44        the scan path and file extension.
    45        :param input_path: the path to the scan's directory. If None, defaults to
    46                           the path loaded in the frame's tile's scan object.
    47        :param file_extension: the image file extension. Defaults to .tif.
    48        :return: the file path.
    49        """
    50        if input_path is None:
    51            input_path = self.scan.path
    52            if len(self.scan.roi) > 1:
    53                input_path = os.path.join(input_path, f"roi_{self.tile.n_roi}")
    54        # Remove trailing slashes
    55        if input_path[-1] == os.sep:
    56            input_path = input_path[:-1]
    57        # Append proc if it's pointing to the base bzScanner directory
    58        if input_path.endswith("bzScanner"):
    59            input_path = os.path.join(input_path, "proc")
    60        # Should be a directory; append the file name
    61        if os.path.isdir(input_path):
    62            input_path = os.path.join(input_path, self.get_file_name())
    63        else:
    64            raise ValueError(f"Input path {input_path} is not a directory.")
    65        return input_path

Get the file path for the frame, optionally changing the scan path and file extension.

###### Parameters

- input_path: the path to the scan's directory. If None, defaults to the path loaded in the frame's tile's scan object.
- file_extension: the image file extension. Defaults to .tif.

###### Returns

> the file path.

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

    67    def get_file_name(self, file_extension: str = ".tif") -> str:
    68        """
    69        Get the file name for the frame, handling different name conventions by scanner.
    70        :param file_extension: the image file extension. Defaults to .tif.
    71        :return: the file name.
    72        """
    73        if self.scan.scanner_id.startswith(Scan.Type.AXIOSCAN7.value):
    74            channel_name = self.scan.channels[self.channel].name
    75            x = self.tile.x
    76            y = self.tile.y
    77            file_name = f"{channel_name}-X{x:03}-Y{y:03}{file_extension}"
    78        elif self.scan.scanner_id.startswith(Scan.Type.BZSCANNER.value):
    79            channel_name = self.scan.channels[self.channel].name
    80            real_channel_index = list(self.scan.BZSCANNER_CHANNEL_MAP.values()).index(
    81                channel_name
    82            )
    83            total_tiles = self.scan.roi[0].tile_rows * self.scan.roi[0].tile_cols
    84            tile_offset = (real_channel_index * total_tiles) + 1  # 1-indexed
    85            n_bzscanner = self.tile.n + tile_offset
    86            file_name = f"Tile{n_bzscanner:06}{file_extension}"
    87        else:
    88            raise ValueError(f"Scanner {self.scan.scanner_id} not supported.")
    89        return file_name

Get the file name for the frame, handling different name conventions by scanner.

###### Parameters

- file_extension: the image file extension. Defaults to .tif.

###### Returns

> the file name.

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

     91    def get_image(self, input_path: str = None) -> np.ndarray:
     92        """
     93        Loads the image for this frame. Handles .tif (will return 16-bit images) and
     94        .jpg/.jpeg (will return 8-bit images), based on the CSI convention for storing
     95        .jpg/.jpeg images (compressed, using .tags files).
     96        :param input_path: the path to the scan's directory. If None, defaults to
     97                           the path loaded in the frame's tile's scan object.
     98        :return: the array representing the image.
     99        """
    100
    101        file_path = self.get_file_path(input_path)
    102
    103        # Check for the file
    104        if not os.path.exists(file_path):
    105            # Alternative: could be a .jpg/.jpeg file, test both
    106            jpeg_path = os.path.splitext(file_path)[0] + ".jpg"
    107            if os.path.exists(jpeg_path):
    108                file_path = jpeg_path
    109            jpeg_path = os.path.splitext(file_path)[0] + ".jpeg"
    110            if os.path.exists(jpeg_path):
    111                file_path = jpeg_path
    112            # If we've found a .jpg/.jpeg, try loading it as compressed
    113            if file_path == jpeg_path:
    114                return self._get_jpeg_image(file_path)
    115            else:
    116                raise FileNotFoundError(f"Could not find image at {file_path}")
    117        else:
    118            # Load the image
    119            image = tifffile.imread(file_path)
    120            if image is None or image.size == 0:
    121                raise ValueError(f"Could not load image from {file_path}")
    122            return image

Loads the image for this frame. Handles .tif (will return 16-bit images) and .jpg/.jpeg (will return 8-bit images), based on the CSI convention for storing .jpg/.jpeg images (compressed, using .tags files).

###### Parameters

- input_path: the path to the scan's directory. If None, defaults to the path loaded in the frame's tile's scan object.

###### Returns

> the array representing the image.

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

    127    def check_image(self, input_path: str = None) -> bool:
    128        """
    129        Check if the image for this frame exists.
    130        :param input_path: the path to the scan's directory. If None, defaults to
    131                           the path loaded in the frame's tile's scan object.
    132        :return: whether the image exists.
    133        """
    134        file_path = self.get_file_path(input_path)
    135        # 72 is the minimum size for a valid TIFF file
    136        if os.path.exists(file_path) and os.path.getsize(file_path) > 72:
    137            return True
    138        else:
    139            # Alternative: could be a .jpg/.jpeg file, test both
    140            jpeg_path = os.path.splitext(file_path)[0] + ".jpg"
    141            if os.path.exists(jpeg_path) and os.path.getsize(jpeg_path) > 107:
    142                file_path = jpeg_path
    143            jpeg_path = os.path.splitext(file_path)[0] + ".jpeg"
    144            if os.path.exists(jpeg_path) and os.path.getsize(jpeg_path) > 107:
    145                file_path = jpeg_path
    146            # If we've found a .jpg/.jpeg, it must have a .tags file with it
    147            if file_path == jpeg_path:
    148                tags_path = os.path.splitext(file_path)[0] + ".tags"
    149                # Tags are text files that should include at least a few bytes
    150                if os.path.exists(tags_path) and os.path.getsize(tags_path) > 20:
    151                    return True
    152        # Didn't hit any of those, return false
    153        return False

Check if the image for this frame exists.

###### Parameters

- input_path: the path to the scan's directory. If None, defaults to the path loaded in the frame's tile's scan object.

###### Returns

> whether the image exists.

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

    155    @classmethod
    156    def check_all_images(cls, scan: Scan):
    157        """
    158        Check if all images for a scan exist, either in .tif or .jpg form.
    159        :param scan:
    160        :return:
    161        """
    162        for n in range(len(scan.roi)):
    163            for frames in cls.get_all_frames(scan, n_roi=n):
    164                for frame in frames:
    165                    if not frame.check_image():
    166                        return False
    167        return True

Check if all images for a scan exist, either in .tif or .jpg form.

###### Parameters

- scan:

###### Returns

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

    169    @classmethod
    170    def get_frames(
    171        cls, tile: Tile, channels: tuple[int | str] = None
    172    ) -> list[typing.Self]:
    173        """
    174        Get the frames for a tile and a set of channels. By default, gets all channels.
    175        :param tile: the tile.
    176        :param channels: the channels, as indices or names. Defaults to all channels.
    177        :return: the frames, in order of the channels.
    178        """
    179        if channels is None:
    180            channels = range(len(tile.scan.channels))
    181        frames = []
    182        for channel in channels:
    183            frames.append(Frame(tile.scan, tile, channel))
    184        return frames

Get the frames for a tile and a set of channels. By default, gets all channels.

###### Parameters

- tile: the tile.
- channels: the channels, as indices or names. Defaults to all channels.

###### Returns

> the frames, in order of the channels.

@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

    186    @classmethod
    187    def get_all_frames(
    188        cls,
    189        scan: Scan,
    190        channels: tuple[int | str] = None,
    191        n_roi: int = 0,
    192        as_flat: bool = True,
    193    ) -> list[list[typing.Self]] | list[list[list[typing.Self]]]:
    194        """
    195        Get all frames for a scan and a set of channels.
    196        :param scan: the scan metadata.
    197        :param channels: the channels, as indices or names. Defaults to all channels.
    198        :param n_roi: the region of interest to use. Defaults to 0.
    199        :param as_flat: whether to flatten the frames into a 2D list.
    200        :return: if as_flat: 2D list of frames, organized as [n][channel];
    201                 if not as_flat: 3D list of frames organized as [row][col][channel] a.k.a. [y][x][channel].
    202        """
    203        if as_flat:
    204            frames = []
    205            for n in range(scan.roi[n_roi].tile_rows * scan.roi[n_roi].tile_cols):
    206                tile = Tile(scan, n, n_roi)
    207                frames.append(cls.get_frames(tile, channels))
    208        else:
    209            frames = [[None] * scan.roi[n_roi].tile_cols] * scan.roi[n_roi].tile_rows
    210            for x in range(scan.roi[n_roi].tile_cols):
    211                for y in range(scan.roi[n_roi].tile_rows):
    212                    tile = Tile(scan, (x, y), n_roi)
    213                    frames[y][x] = cls.get_frames(tile, channels)
    214        return frames

Get all frames for a scan and a set of channels.

###### Parameters

- scan: the scan metadata.
- channels: the channels, as indices or names. Defaults to all channels.
- n_roi: the region of interest to use. Defaults to 0.
- as_flat: whether to flatten the frames into a 2D list.

###### Returns

> if as_flat: 2D list of frames, organized as [n][channel]; if not as_flat: 3D list of frames organized as [row][col][channel] a.k.a. [y][x][channel].

@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

    216    @classmethod
    217    def make_rgb_image(
    218        cls,
    219        tile: Tile,
    220        channels: dict[int, tuple[float, float, float]],
    221        input_path=None,
    222    ) -> np.ndarray:
    223        """
    224        Convenience method for creating an RGB image from a tile and a set of channels
    225        without manually extracting any frames.
    226        :param tile: the tile for which the image should be made.
    227        :param channels: a dictionary of scan channel indices and RGB gains.
    228        :param input_path: the path to the input images. Will use metadata if not provided.
    229        :return: the image as a numpy array.
    230        """
    231        images = []
    232        colors = []
    233        for channel_index, color in channels.items():
    234            if channel_index == -1:
    235                continue
    236            image = Frame(tile.scan, tile, channel_index).get_image(input_path)
    237            images.append(image)
    238            colors.append(color)
    239        return csi_images.make_rgb(images, colors)

Convenience method for creating an RGB image from a tile and a set of channels without manually extracting any frames.

###### Parameters

- tile: the tile for which the image should be made.
- channels: a dictionary of scan channel indices and RGB gains.
- input_path: the path to the input images. Will use metadata if not provided.

###### Returns

> the image as a numpy array.