csi_images.csi_frames

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

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

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

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

the file path.

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

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

Parameters
  • file_extension: the image file extension. Defaults to .tif.
Returns

the file name.

def get_image(self, input_path: str = None) -> numpy.ndarray:
100    def get_image(self, input_path: str = None) -> np.ndarray:
101        """
102        Loads the image for this frame. Handles .tif (will return 16-bit images) and
103        .jpg/.jpeg (will return 8-bit images), based on the CSI convention for storing
104        .jpg/.jpeg images (compressed, using .tags files).
105        :param input_path: the path to the scan's directory. If None, defaults to
106                           the path loaded in the frame's tile's scan object.
107        :return: the array representing the image.
108        """
109        if tifffile is None:
110            raise ModuleNotFoundError(
111                "tifffile library not installed. "
112                "Install csi-images with [imageio] option to resolve."
113            )
114
115        file_path = self.get_file_path(input_path)
116
117        # Check for the file
118        if not os.path.exists(file_path):
119            # Alternative: could be a .jpg/.jpeg file, test both
120            jpeg_path = os.path.splitext(file_path)[0] + ".jpg"
121            if os.path.exists(jpeg_path):
122                file_path = jpeg_path
123            jpeg_path = os.path.splitext(file_path)[0] + ".jpeg"
124            if os.path.exists(jpeg_path):
125                file_path = jpeg_path
126            # If we've found a .jpg/.jpeg, try loading it as compressed
127            if file_path == jpeg_path:
128                return self._get_jpeg_image(file_path)
129            else:
130                raise FileNotFoundError(f"Could not find image at {file_path}")
131        else:
132            # Load the image
133            image = tifffile.imread(file_path)
134            if image is None or image.size == 0:
135                raise ValueError(f"Could not load image from {file_path}")
136            return image

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

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

the array representing the image.

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

Check if the image for this frame exists.

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

whether the image exists.

@classmethod
def check_all_images(cls, scan: csi_images.csi_scans.Scan):
169    @classmethod
170    def check_all_images(cls, scan: Scan):
171        """
172        Check if all images for a scan exist, either in .tif or .jpg form.
173        :param scan:
174        :return:
175        """
176        for n in range(len(scan.roi)):
177            for frames in cls.get_all_frames(scan, n_roi=n):
178                for frame in frames:
179                    if not frame.check_image():
180                        return False
181        return True

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

Parameters
  • scan:
Returns
@classmethod
def get_frames( cls, tile: csi_images.csi_tiles.Tile, channels: tuple[int | str] = None) -> list[typing.Self]:
183    @classmethod
184    def get_frames(
185        cls, tile: Tile, channels: tuple[int | str] = None
186    ) -> list[typing.Self]:
187        """
188        Get the frames for a tile and a set of channels. By default, gets all channels.
189        :param tile: the tile.
190        :param channels: the channels, as indices or names. Defaults to all channels.
191        :return: the frames, in order of the channels.
192        """
193        if channels is None:
194            channels = range(len(tile.scan.channels))
195        frames = []
196        for channel in channels:
197            frames.append(Frame(tile.scan, tile, channel))
198        return frames

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

Parameters
  • tile: the tile.
  • channels: the channels, as indices or names. Defaults to all channels.
Returns

the frames, in order of the channels.

@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]]]:
200    @classmethod
201    def get_all_frames(
202        cls,
203        scan: Scan,
204        channels: tuple[int | str] = None,
205        n_roi: int = 0,
206        as_flat: bool = True,
207    ) -> list[list[typing.Self]] | list[list[list[typing.Self]]]:
208        """
209        Get all frames for a scan and a set of channels.
210        :param scan: the scan metadata.
211        :param channels: the channels, as indices or names. Defaults to all channels.
212        :param n_roi: the region of interest to use. Defaults to 0.
213        :param as_flat: whether to flatten the frames into a 2D list.
214        :return: if as_flat: 2D list of frames, organized as [n][channel];
215                 if not as_flat: 3D list of frames organized as [row][col][channel] a.k.a. [y][x][channel].
216        """
217        if as_flat:
218            frames = []
219            for n in range(scan.roi[n_roi].tile_rows * scan.roi[n_roi].tile_cols):
220                tile = Tile(scan, n, n_roi)
221                frames.append(cls.get_frames(tile, channels))
222        else:
223            frames = [[None] * scan.roi[n_roi].tile_cols] * scan.roi[n_roi].tile_rows
224            for x in range(scan.roi[n_roi].tile_cols):
225                for y in range(scan.roi[n_roi].tile_rows):
226                    tile = Tile(scan, (x, y), n_roi)
227                    frames[y][x] = cls.get_frames(tile, channels)
228        return frames

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

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

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

@classmethod
def make_rgb_image( cls, tile: csi_images.csi_tiles.Tile, channels: dict[int, tuple[float, float, float]], input_path=None) -> numpy.ndarray:
230    @classmethod
231    def make_rgb_image(
232        cls,
233        tile: Tile,
234        channels: dict[int, tuple[float, float, float]],
235        input_path=None,
236    ) -> np.ndarray:
237        """
238        Convenience method for creating an RGB image from a tile and a set of channels
239        without manually extracting any frames.
240        :param tile: the tile for which the image should be made.
241        :param channels: a dictionary of scan channel indices and RGB gains.
242        :param input_path: the path to the input images. Will use metadata if not provided.
243        :return: the image as a numpy array.
244        """
245        if csi_images is None:
246            raise ModuleNotFoundError(
247                "csi-images library not installed. "
248                "Install csi-images with [imageio] option to resolve."
249            )
250        images = []
251        colors = []
252        for channel_index, color in channels.items():
253            if channel_index == -1:
254                continue
255            image = Frame(tile.scan, tile, channel_index).get_image(input_path)
256            images.append(image)
257            colors.append(color)
258        return csi_images.make_rgb(images, colors)

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

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

the image as a numpy array.