csi_images.csi_frames

Contains the Frame class, which represents a single frame of an image. The Frame class does not hold the image data, but allows for easy loading of the image data from the appropriate file. This module also contains functions for creating RGB and RGBW composite images from a tile and a set of channels.

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