csi_images.csi_tiles

Contains the Tile class, which represents a collection of frames at the same position in a scan. The module comes with several helper functions that allow for gathering tiles based on their position in the scan.

  1"""
  2Contains the Tile class, which represents a collection of frames at the same position
  3in a scan. The module comes with several helper functions that allow for gathering tiles
  4based on their position in the scan.
  5"""
  6
  7from typing import Self, Iterable
  8
  9import numpy as np
 10
 11from csi_images.csi_scans import Scan
 12
 13
 14class Tile:
 15    """
 16    A class that represents a tile in a scan. This class encodes the position of a group
 17    of frames in a scan, based on the scan's metadata. The module comes with several
 18    helper functions that allow for gathering tiles based on their position in the scan.
 19    """
 20
 21    def __init__(self, scan: Scan, coordinates: int | tuple[int, int], n_roi: int = 0):
 22        self.scan = scan
 23
 24        # Check that the n_roi is valid
 25        if n_roi >= len(self.scan.roi):
 26            raise ValueError(f"n_roi {n_roi} is out of bounds for scan.")
 27        self.n_roi = int(n_roi)
 28
 29        # Check that the coordinates are valid
 30        tile_rows = scan.roi[n_roi].tile_rows
 31        tile_cols = scan.roi[n_roi].tile_cols
 32        total_tiles = tile_rows * tile_cols
 33        if np.issubdtype(type(coordinates), np.integer):
 34            # We received "n" as the coordinates
 35            if 0 > coordinates or coordinates > total_tiles:
 36                raise ValueError(
 37                    f"n ({coordinates}) must be between 0 and the "
 38                    f"number of tiles in ROI {self.n_roi} ({total_tiles})."
 39                )
 40            self.n = int(coordinates)
 41            self.x, self.y = self.n_to_position()
 42        elif (
 43            (isinstance(coordinates, tuple) or isinstance(coordinates, list))
 44            and len(coordinates) == 2
 45            and all([np.issubdtype(type(coord), np.integer) for coord in coordinates])
 46        ):
 47            # We received (x, y) as the coordinates
 48            if 0 > coordinates[0] or coordinates[0] >= tile_cols:
 49                raise ValueError(
 50                    f"x ({coordinates[0]}) must be between 0 and the "
 51                    f"number of columns in ROI {self.n_roi} ({tile_cols})."
 52                )
 53            if 0 > coordinates[1] or coordinates[1] >= tile_rows:
 54                raise ValueError(
 55                    f"y ({coordinates[1]}) must be between 0 and the "
 56                    f"number of rows in ROI {self.n_roi} ({tile_rows})."
 57                )
 58            self.x, self.y = int(coordinates[0]), int(coordinates[1])
 59            self.n = self.position_to_n()
 60        else:
 61            raise ValueError(
 62                "Coordinates must be an integer n or a tuple of (x, y) coordinates."
 63            )
 64
 65    def __key(self) -> tuple:
 66        return self.scan.slide_id, self.n_roi, self.n
 67
 68    def __hash__(self) -> int:
 69        return hash(self.__key())
 70
 71    def __repr__(self) -> str:
 72        return f"{self.scan.slide_id}-{self.n_roi}-{self.n}"
 73
 74    def __eq__(self, other) -> bool:
 75        return self.__repr__() == other.__repr__()
 76
 77    # Helper functions that convert ***indices***, which are 0-indexed
 78    def position_to_n(self, position: tuple[int, int] = (-1, -1)) -> int:
 79        """
 80        Convert the x, y coordinates to the n coordinate, based on this tile's scan
 81        metadata and ROI. Can be provided alternative x, y to convert for convenience.
 82        :param position: optional (x, y) coordinates to find the n for.
 83                         If none provided, this tile's (x, y) will be used.
 84        :return: the coordinate n, which depends on the scanner and scan layout.
 85        """
 86        if position == (-1, -1):
 87            position = self.x, self.y
 88        x, y = position
 89        if self.scan.scanner_id.startswith(self.scan.Type.AXIOSCAN7.value):
 90            n = y * self.scan.roi[self.n_roi].tile_cols + x
 91        elif self.scan.scanner_id.startswith(self.scan.Type.BZSCANNER.value):
 92            n = y * self.scan.roi[self.n_roi].tile_cols
 93            if y % 2 == 0:
 94                n += x
 95            else:
 96                n += self.scan.roi[0].tile_cols - x
 97        else:
 98            raise ValueError(f"Scanner type {self.scan.scanner_id} not supported.")
 99        return n
100
101    def n_to_position(self, n: int = -1) -> tuple[int, int]:
102        """
103        Convert the n coordinate to x, y coordinates, based on this tile's scan
104        metadata and ROI. Can be provided alternative n to convert for convenience.
105        :param n: an optional n coordinate to find the position for.
106                  If none provided, this tile's n will be used.
107        :return: x, y coordinates of the tile in the scan's coordinate system.
108        """
109        if n == -1:
110            n = self.n
111        if n < 0:
112            raise ValueError(f"n ({n}) must be non-negative.")
113        if self.scan.scanner_id.startswith(self.scan.Type.AXIOSCAN7.value):
114            x = n % self.scan.roi[0].tile_cols
115            y = n // self.scan.roi[0].tile_cols
116            return x, y
117        elif self.scan.scanner_id.startswith(self.scan.Type.BZSCANNER.value):
118            y = n // self.scan.roi[0].tile_cols
119            if y % 2 == 0:
120                x = n % self.scan.roi[0].tile_cols
121            else:
122                x = (self.scan.roi[0].tile_cols - 1) - (n % self.scan.roi[0].tile_cols)
123        else:
124            raise ValueError(f"Scanner type {self.scan.scanner_id} not supported.")
125        return x, y
126
127    @classmethod
128    def get_tiles(
129        cls,
130        scan: Scan,
131        coordinates: Iterable[int] | Iterable[tuple[int, int]] = None,
132        n_roi: int = 0,
133        as_flat: bool = True,
134    ) -> list[Self] | list[list[Self]]:
135        """
136        The simplest way to gather a list of Tile objects. By default, it will gather all
137        tiles in the scan. To gather specific tiles, provide a list of coordinates.
138        :param scan: the scan metadata.
139        :param coordinates: a list of n-based indices or (x, y) coordinates.
140                            Leave as None to include all tiles.
141        :param n_roi: the region of interest to use. Defaults to 0.
142        :param as_flat: whether to return a flat list of Tile objects or a list of lists.
143        :return: if as_flat: a list of Tile objects in the same order as the coordinates;
144                 if not as_flat: a list of lists of Tile objects in their relative coordinates.
145        """
146        if as_flat:
147            if coordinates is None:
148                # Populate coordinates with all n's.
149                coordinates = list(
150                    range(scan.roi[n_roi].tile_rows * scan.roi[n_roi].tile_cols)
151                )
152            tiles = []
153            for coordinate in coordinates:
154                tiles.append(cls(scan, coordinate, n_roi))
155        else:
156            if coordinates is None:
157                # Populate coordinates with all (x, y) pairs in row-major order
158                coordinates = []
159                for y in range(scan.roi[n_roi].tile_rows):
160                    for x in range(scan.roi[n_roi].tile_cols):
161                        coordinates.append((x, y))
162            # Check that the coordinates are contiguous, otherwise we can't make a grid
163            # Find the min and max x, y values
164            x_min = scan.roi[n_roi].tile_cols
165            x_max = 0
166            y_min = scan.roi[n_roi].tile_rows
167            y_max = 0
168            for x, y in coordinates:
169                x_min = min(x_min, x)
170                x_max = max(x_max, x)
171                y_min = min(y_min, y)
172                y_max = max(y_max, y)
173
174            # Check that the coordinates are contiguous
175            if (x_max - x_min + 1) * (y_max - y_min + 1) != len(coordinates):
176                raise ValueError(
177                    "Coordinates must be a contiguous square to form "
178                    "a grid; number of coordinates does not match."
179                )
180
181            tiles = [[None] * (x_max - x_min + 1)] * (y_max - y_min + 1)
182            for coordinate in coordinates:
183                x, y = coordinate
184                tiles[y][x] = cls(scan, coordinate, n_roi)
185
186        return tiles
187
188    @classmethod
189    def get_tiles_by_row_col(
190        cls,
191        scan: Scan,
192        rows: Iterable[int] = None,
193        cols: Iterable[int] = None,
194        n_roi: int = 0,
195        as_flat: bool = True,
196    ) -> list[Self] | list[list[Self]]:
197        """
198        Gather a list of Tile objects based on the row and column indices provided.
199        If left as None, it will gather all rows and/or columns.
200        :param scan: the scan metadata.
201        :param rows: a list of 0-indexed rows (y-positions) in the scan axes.
202                     Leave as None to include all rows.
203        :param cols: a list of 0-indexed columns (x-positions) in the scan axes.
204                     Leave as None to include all columns.
205        :param n_roi: the region of interest to use. Defaults to 0.
206        :param as_flat: whether to return a flat list of Tile objects or a list of lists.
207        :return: if as_flat: a list of Tile objects in row-major order;
208                 if not as_flat: a list of lists of Tile objects in their relative coordinates
209        """
210        if rows is None:
211            rows = range(scan.roi[n_roi].tile_rows)
212        if cols is None:
213            cols = range(scan.roi[n_roi].tile_cols)
214
215        # Populate coordinates
216        coordinates = []
217        for row in rows:
218            for col in cols:
219                coordinates.append((col, row))
220
221        return cls.get_tiles(scan, coordinates, n_roi, as_flat)
222
223    @classmethod
224    def get_tiles_by_xy_bounds(
225        cls,
226        scan: Scan,
227        bounds: tuple[int, int, int, int],
228        n_roi: int = 0,
229        as_flat: bool = True,
230    ) -> list[Self] | list[list[Self]]:
231        """
232        Gather a list of Tile objects based on the x, y bounds provided. The bounds are
233        exclusive, like indices, so the tiles at the corners are NOT included in the list.
234        :param scan: the scan metadata.
235        :param bounds: a tuple of (x_0, y_0, x_1, y_1) in the scan axes.
236        :param n_roi: the region of interest to use. Defaults to 0.
237        :param as_flat: whether to return a flat list of Tile objects or a list of lists.
238        :return: if as_flat: a list of Tile objects in row-major order;
239                 if not as_flat: a list of lists of Tile objects in their relative coordinates
240        """
241        x_0, y_0, x_1, y_1 = bounds
242        coordinates = []
243        for y in range(y_0, y_1):
244            for x in range(x_0, x_1):
245                coordinates.append((x, y))
246        return cls.get_tiles(scan, coordinates, n_roi, as_flat)
class Tile:
 15class Tile:
 16    """
 17    A class that represents a tile in a scan. This class encodes the position of a group
 18    of frames in a scan, based on the scan's metadata. The module comes with several
 19    helper functions that allow for gathering tiles based on their position in the scan.
 20    """
 21
 22    def __init__(self, scan: Scan, coordinates: int | tuple[int, int], n_roi: int = 0):
 23        self.scan = scan
 24
 25        # Check that the n_roi is valid
 26        if n_roi >= len(self.scan.roi):
 27            raise ValueError(f"n_roi {n_roi} is out of bounds for scan.")
 28        self.n_roi = int(n_roi)
 29
 30        # Check that the coordinates are valid
 31        tile_rows = scan.roi[n_roi].tile_rows
 32        tile_cols = scan.roi[n_roi].tile_cols
 33        total_tiles = tile_rows * tile_cols
 34        if np.issubdtype(type(coordinates), np.integer):
 35            # We received "n" as the coordinates
 36            if 0 > coordinates or coordinates > total_tiles:
 37                raise ValueError(
 38                    f"n ({coordinates}) must be between 0 and the "
 39                    f"number of tiles in ROI {self.n_roi} ({total_tiles})."
 40                )
 41            self.n = int(coordinates)
 42            self.x, self.y = self.n_to_position()
 43        elif (
 44            (isinstance(coordinates, tuple) or isinstance(coordinates, list))
 45            and len(coordinates) == 2
 46            and all([np.issubdtype(type(coord), np.integer) for coord in coordinates])
 47        ):
 48            # We received (x, y) as the coordinates
 49            if 0 > coordinates[0] or coordinates[0] >= tile_cols:
 50                raise ValueError(
 51                    f"x ({coordinates[0]}) must be between 0 and the "
 52                    f"number of columns in ROI {self.n_roi} ({tile_cols})."
 53                )
 54            if 0 > coordinates[1] or coordinates[1] >= tile_rows:
 55                raise ValueError(
 56                    f"y ({coordinates[1]}) must be between 0 and the "
 57                    f"number of rows in ROI {self.n_roi} ({tile_rows})."
 58                )
 59            self.x, self.y = int(coordinates[0]), int(coordinates[1])
 60            self.n = self.position_to_n()
 61        else:
 62            raise ValueError(
 63                "Coordinates must be an integer n or a tuple of (x, y) coordinates."
 64            )
 65
 66    def __key(self) -> tuple:
 67        return self.scan.slide_id, self.n_roi, self.n
 68
 69    def __hash__(self) -> int:
 70        return hash(self.__key())
 71
 72    def __repr__(self) -> str:
 73        return f"{self.scan.slide_id}-{self.n_roi}-{self.n}"
 74
 75    def __eq__(self, other) -> bool:
 76        return self.__repr__() == other.__repr__()
 77
 78    # Helper functions that convert ***indices***, which are 0-indexed
 79    def position_to_n(self, position: tuple[int, int] = (-1, -1)) -> int:
 80        """
 81        Convert the x, y coordinates to the n coordinate, based on this tile's scan
 82        metadata and ROI. Can be provided alternative x, y to convert for convenience.
 83        :param position: optional (x, y) coordinates to find the n for.
 84                         If none provided, this tile's (x, y) will be used.
 85        :return: the coordinate n, which depends on the scanner and scan layout.
 86        """
 87        if position == (-1, -1):
 88            position = self.x, self.y
 89        x, y = position
 90        if self.scan.scanner_id.startswith(self.scan.Type.AXIOSCAN7.value):
 91            n = y * self.scan.roi[self.n_roi].tile_cols + x
 92        elif self.scan.scanner_id.startswith(self.scan.Type.BZSCANNER.value):
 93            n = y * self.scan.roi[self.n_roi].tile_cols
 94            if y % 2 == 0:
 95                n += x
 96            else:
 97                n += self.scan.roi[0].tile_cols - x
 98        else:
 99            raise ValueError(f"Scanner type {self.scan.scanner_id} not supported.")
100        return n
101
102    def n_to_position(self, n: int = -1) -> tuple[int, int]:
103        """
104        Convert the n coordinate to x, y coordinates, based on this tile's scan
105        metadata and ROI. Can be provided alternative n to convert for convenience.
106        :param n: an optional n coordinate to find the position for.
107                  If none provided, this tile's n will be used.
108        :return: x, y coordinates of the tile in the scan's coordinate system.
109        """
110        if n == -1:
111            n = self.n
112        if n < 0:
113            raise ValueError(f"n ({n}) must be non-negative.")
114        if self.scan.scanner_id.startswith(self.scan.Type.AXIOSCAN7.value):
115            x = n % self.scan.roi[0].tile_cols
116            y = n // self.scan.roi[0].tile_cols
117            return x, y
118        elif self.scan.scanner_id.startswith(self.scan.Type.BZSCANNER.value):
119            y = n // self.scan.roi[0].tile_cols
120            if y % 2 == 0:
121                x = n % self.scan.roi[0].tile_cols
122            else:
123                x = (self.scan.roi[0].tile_cols - 1) - (n % self.scan.roi[0].tile_cols)
124        else:
125            raise ValueError(f"Scanner type {self.scan.scanner_id} not supported.")
126        return x, y
127
128    @classmethod
129    def get_tiles(
130        cls,
131        scan: Scan,
132        coordinates: Iterable[int] | Iterable[tuple[int, int]] = None,
133        n_roi: int = 0,
134        as_flat: bool = True,
135    ) -> list[Self] | list[list[Self]]:
136        """
137        The simplest way to gather a list of Tile objects. By default, it will gather all
138        tiles in the scan. To gather specific tiles, provide a list of coordinates.
139        :param scan: the scan metadata.
140        :param coordinates: a list of n-based indices or (x, y) coordinates.
141                            Leave as None to include all tiles.
142        :param n_roi: the region of interest to use. Defaults to 0.
143        :param as_flat: whether to return a flat list of Tile objects or a list of lists.
144        :return: if as_flat: a list of Tile objects in the same order as the coordinates;
145                 if not as_flat: a list of lists of Tile objects in their relative coordinates.
146        """
147        if as_flat:
148            if coordinates is None:
149                # Populate coordinates with all n's.
150                coordinates = list(
151                    range(scan.roi[n_roi].tile_rows * scan.roi[n_roi].tile_cols)
152                )
153            tiles = []
154            for coordinate in coordinates:
155                tiles.append(cls(scan, coordinate, n_roi))
156        else:
157            if coordinates is None:
158                # Populate coordinates with all (x, y) pairs in row-major order
159                coordinates = []
160                for y in range(scan.roi[n_roi].tile_rows):
161                    for x in range(scan.roi[n_roi].tile_cols):
162                        coordinates.append((x, y))
163            # Check that the coordinates are contiguous, otherwise we can't make a grid
164            # Find the min and max x, y values
165            x_min = scan.roi[n_roi].tile_cols
166            x_max = 0
167            y_min = scan.roi[n_roi].tile_rows
168            y_max = 0
169            for x, y in coordinates:
170                x_min = min(x_min, x)
171                x_max = max(x_max, x)
172                y_min = min(y_min, y)
173                y_max = max(y_max, y)
174
175            # Check that the coordinates are contiguous
176            if (x_max - x_min + 1) * (y_max - y_min + 1) != len(coordinates):
177                raise ValueError(
178                    "Coordinates must be a contiguous square to form "
179                    "a grid; number of coordinates does not match."
180                )
181
182            tiles = [[None] * (x_max - x_min + 1)] * (y_max - y_min + 1)
183            for coordinate in coordinates:
184                x, y = coordinate
185                tiles[y][x] = cls(scan, coordinate, n_roi)
186
187        return tiles
188
189    @classmethod
190    def get_tiles_by_row_col(
191        cls,
192        scan: Scan,
193        rows: Iterable[int] = None,
194        cols: Iterable[int] = None,
195        n_roi: int = 0,
196        as_flat: bool = True,
197    ) -> list[Self] | list[list[Self]]:
198        """
199        Gather a list of Tile objects based on the row and column indices provided.
200        If left as None, it will gather all rows and/or columns.
201        :param scan: the scan metadata.
202        :param rows: a list of 0-indexed rows (y-positions) in the scan axes.
203                     Leave as None to include all rows.
204        :param cols: a list of 0-indexed columns (x-positions) in the scan axes.
205                     Leave as None to include all columns.
206        :param n_roi: the region of interest to use. Defaults to 0.
207        :param as_flat: whether to return a flat list of Tile objects or a list of lists.
208        :return: if as_flat: a list of Tile objects in row-major order;
209                 if not as_flat: a list of lists of Tile objects in their relative coordinates
210        """
211        if rows is None:
212            rows = range(scan.roi[n_roi].tile_rows)
213        if cols is None:
214            cols = range(scan.roi[n_roi].tile_cols)
215
216        # Populate coordinates
217        coordinates = []
218        for row in rows:
219            for col in cols:
220                coordinates.append((col, row))
221
222        return cls.get_tiles(scan, coordinates, n_roi, as_flat)
223
224    @classmethod
225    def get_tiles_by_xy_bounds(
226        cls,
227        scan: Scan,
228        bounds: tuple[int, int, int, int],
229        n_roi: int = 0,
230        as_flat: bool = True,
231    ) -> list[Self] | list[list[Self]]:
232        """
233        Gather a list of Tile objects based on the x, y bounds provided. The bounds are
234        exclusive, like indices, so the tiles at the corners are NOT included in the list.
235        :param scan: the scan metadata.
236        :param bounds: a tuple of (x_0, y_0, x_1, y_1) in the scan axes.
237        :param n_roi: the region of interest to use. Defaults to 0.
238        :param as_flat: whether to return a flat list of Tile objects or a list of lists.
239        :return: if as_flat: a list of Tile objects in row-major order;
240                 if not as_flat: a list of lists of Tile objects in their relative coordinates
241        """
242        x_0, y_0, x_1, y_1 = bounds
243        coordinates = []
244        for y in range(y_0, y_1):
245            for x in range(x_0, x_1):
246                coordinates.append((x, y))
247        return cls.get_tiles(scan, coordinates, n_roi, as_flat)

A class that represents a tile in a scan. This class encodes the position of a group of frames in a scan, based on the scan's metadata. The module comes with several helper functions that allow for gathering tiles based on their position in the scan.

Tile( scan: csi_images.csi_scans.Scan, coordinates: int | tuple[int, int], n_roi: int = 0)
22    def __init__(self, scan: Scan, coordinates: int | tuple[int, int], n_roi: int = 0):
23        self.scan = scan
24
25        # Check that the n_roi is valid
26        if n_roi >= len(self.scan.roi):
27            raise ValueError(f"n_roi {n_roi} is out of bounds for scan.")
28        self.n_roi = int(n_roi)
29
30        # Check that the coordinates are valid
31        tile_rows = scan.roi[n_roi].tile_rows
32        tile_cols = scan.roi[n_roi].tile_cols
33        total_tiles = tile_rows * tile_cols
34        if np.issubdtype(type(coordinates), np.integer):
35            # We received "n" as the coordinates
36            if 0 > coordinates or coordinates > total_tiles:
37                raise ValueError(
38                    f"n ({coordinates}) must be between 0 and the "
39                    f"number of tiles in ROI {self.n_roi} ({total_tiles})."
40                )
41            self.n = int(coordinates)
42            self.x, self.y = self.n_to_position()
43        elif (
44            (isinstance(coordinates, tuple) or isinstance(coordinates, list))
45            and len(coordinates) == 2
46            and all([np.issubdtype(type(coord), np.integer) for coord in coordinates])
47        ):
48            # We received (x, y) as the coordinates
49            if 0 > coordinates[0] or coordinates[0] >= tile_cols:
50                raise ValueError(
51                    f"x ({coordinates[0]}) must be between 0 and the "
52                    f"number of columns in ROI {self.n_roi} ({tile_cols})."
53                )
54            if 0 > coordinates[1] or coordinates[1] >= tile_rows:
55                raise ValueError(
56                    f"y ({coordinates[1]}) must be between 0 and the "
57                    f"number of rows in ROI {self.n_roi} ({tile_rows})."
58                )
59            self.x, self.y = int(coordinates[0]), int(coordinates[1])
60            self.n = self.position_to_n()
61        else:
62            raise ValueError(
63                "Coordinates must be an integer n or a tuple of (x, y) coordinates."
64            )
scan
n_roi
def position_to_n(self, position: tuple[int, int] = (-1, -1)) -> int:
 79    def position_to_n(self, position: tuple[int, int] = (-1, -1)) -> int:
 80        """
 81        Convert the x, y coordinates to the n coordinate, based on this tile's scan
 82        metadata and ROI. Can be provided alternative x, y to convert for convenience.
 83        :param position: optional (x, y) coordinates to find the n for.
 84                         If none provided, this tile's (x, y) will be used.
 85        :return: the coordinate n, which depends on the scanner and scan layout.
 86        """
 87        if position == (-1, -1):
 88            position = self.x, self.y
 89        x, y = position
 90        if self.scan.scanner_id.startswith(self.scan.Type.AXIOSCAN7.value):
 91            n = y * self.scan.roi[self.n_roi].tile_cols + x
 92        elif self.scan.scanner_id.startswith(self.scan.Type.BZSCANNER.value):
 93            n = y * self.scan.roi[self.n_roi].tile_cols
 94            if y % 2 == 0:
 95                n += x
 96            else:
 97                n += self.scan.roi[0].tile_cols - x
 98        else:
 99            raise ValueError(f"Scanner type {self.scan.scanner_id} not supported.")
100        return n

Convert the x, y coordinates to the n coordinate, based on this tile's scan metadata and ROI. Can be provided alternative x, y to convert for convenience.

Parameters
  • position: optional (x, y) coordinates to find the n for. If none provided, this tile's (x, y) will be used.
Returns

the coordinate n, which depends on the scanner and scan layout.

def n_to_position(self, n: int = -1) -> tuple[int, int]:
102    def n_to_position(self, n: int = -1) -> tuple[int, int]:
103        """
104        Convert the n coordinate to x, y coordinates, based on this tile's scan
105        metadata and ROI. Can be provided alternative n to convert for convenience.
106        :param n: an optional n coordinate to find the position for.
107                  If none provided, this tile's n will be used.
108        :return: x, y coordinates of the tile in the scan's coordinate system.
109        """
110        if n == -1:
111            n = self.n
112        if n < 0:
113            raise ValueError(f"n ({n}) must be non-negative.")
114        if self.scan.scanner_id.startswith(self.scan.Type.AXIOSCAN7.value):
115            x = n % self.scan.roi[0].tile_cols
116            y = n // self.scan.roi[0].tile_cols
117            return x, y
118        elif self.scan.scanner_id.startswith(self.scan.Type.BZSCANNER.value):
119            y = n // self.scan.roi[0].tile_cols
120            if y % 2 == 0:
121                x = n % self.scan.roi[0].tile_cols
122            else:
123                x = (self.scan.roi[0].tile_cols - 1) - (n % self.scan.roi[0].tile_cols)
124        else:
125            raise ValueError(f"Scanner type {self.scan.scanner_id} not supported.")
126        return x, y

Convert the n coordinate to x, y coordinates, based on this tile's scan metadata and ROI. Can be provided alternative n to convert for convenience.

Parameters
  • n: an optional n coordinate to find the position for. If none provided, this tile's n will be used.
Returns

x, y coordinates of the tile in the scan's coordinate system.

@classmethod
def get_tiles( cls, scan: csi_images.csi_scans.Scan, coordinates: Union[Iterable[int], Iterable[tuple[int, int]]] = None, n_roi: int = 0, as_flat: bool = True) -> list[typing.Self] | list[list[typing.Self]]:
128    @classmethod
129    def get_tiles(
130        cls,
131        scan: Scan,
132        coordinates: Iterable[int] | Iterable[tuple[int, int]] = None,
133        n_roi: int = 0,
134        as_flat: bool = True,
135    ) -> list[Self] | list[list[Self]]:
136        """
137        The simplest way to gather a list of Tile objects. By default, it will gather all
138        tiles in the scan. To gather specific tiles, provide a list of coordinates.
139        :param scan: the scan metadata.
140        :param coordinates: a list of n-based indices or (x, y) coordinates.
141                            Leave as None to include all tiles.
142        :param n_roi: the region of interest to use. Defaults to 0.
143        :param as_flat: whether to return a flat list of Tile objects or a list of lists.
144        :return: if as_flat: a list of Tile objects in the same order as the coordinates;
145                 if not as_flat: a list of lists of Tile objects in their relative coordinates.
146        """
147        if as_flat:
148            if coordinates is None:
149                # Populate coordinates with all n's.
150                coordinates = list(
151                    range(scan.roi[n_roi].tile_rows * scan.roi[n_roi].tile_cols)
152                )
153            tiles = []
154            for coordinate in coordinates:
155                tiles.append(cls(scan, coordinate, n_roi))
156        else:
157            if coordinates is None:
158                # Populate coordinates with all (x, y) pairs in row-major order
159                coordinates = []
160                for y in range(scan.roi[n_roi].tile_rows):
161                    for x in range(scan.roi[n_roi].tile_cols):
162                        coordinates.append((x, y))
163            # Check that the coordinates are contiguous, otherwise we can't make a grid
164            # Find the min and max x, y values
165            x_min = scan.roi[n_roi].tile_cols
166            x_max = 0
167            y_min = scan.roi[n_roi].tile_rows
168            y_max = 0
169            for x, y in coordinates:
170                x_min = min(x_min, x)
171                x_max = max(x_max, x)
172                y_min = min(y_min, y)
173                y_max = max(y_max, y)
174
175            # Check that the coordinates are contiguous
176            if (x_max - x_min + 1) * (y_max - y_min + 1) != len(coordinates):
177                raise ValueError(
178                    "Coordinates must be a contiguous square to form "
179                    "a grid; number of coordinates does not match."
180                )
181
182            tiles = [[None] * (x_max - x_min + 1)] * (y_max - y_min + 1)
183            for coordinate in coordinates:
184                x, y = coordinate
185                tiles[y][x] = cls(scan, coordinate, n_roi)
186
187        return tiles

The simplest way to gather a list of Tile objects. By default, it will gather all tiles in the scan. To gather specific tiles, provide a list of coordinates.

Parameters
  • scan: the scan metadata.
  • coordinates: a list of n-based indices or (x, y) coordinates. Leave as None to include all tiles.
  • n_roi: the region of interest to use. Defaults to 0.
  • as_flat: whether to return a flat list of Tile objects or a list of lists.
Returns

if as_flat: a list of Tile objects in the same order as the coordinates; if not as_flat: a list of lists of Tile objects in their relative coordinates.

@classmethod
def get_tiles_by_row_col( cls, scan: csi_images.csi_scans.Scan, rows: Iterable[int] = None, cols: Iterable[int] = None, n_roi: int = 0, as_flat: bool = True) -> list[typing.Self] | list[list[typing.Self]]:
189    @classmethod
190    def get_tiles_by_row_col(
191        cls,
192        scan: Scan,
193        rows: Iterable[int] = None,
194        cols: Iterable[int] = None,
195        n_roi: int = 0,
196        as_flat: bool = True,
197    ) -> list[Self] | list[list[Self]]:
198        """
199        Gather a list of Tile objects based on the row and column indices provided.
200        If left as None, it will gather all rows and/or columns.
201        :param scan: the scan metadata.
202        :param rows: a list of 0-indexed rows (y-positions) in the scan axes.
203                     Leave as None to include all rows.
204        :param cols: a list of 0-indexed columns (x-positions) in the scan axes.
205                     Leave as None to include all columns.
206        :param n_roi: the region of interest to use. Defaults to 0.
207        :param as_flat: whether to return a flat list of Tile objects or a list of lists.
208        :return: if as_flat: a list of Tile objects in row-major order;
209                 if not as_flat: a list of lists of Tile objects in their relative coordinates
210        """
211        if rows is None:
212            rows = range(scan.roi[n_roi].tile_rows)
213        if cols is None:
214            cols = range(scan.roi[n_roi].tile_cols)
215
216        # Populate coordinates
217        coordinates = []
218        for row in rows:
219            for col in cols:
220                coordinates.append((col, row))
221
222        return cls.get_tiles(scan, coordinates, n_roi, as_flat)

Gather a list of Tile objects based on the row and column indices provided. If left as None, it will gather all rows and/or columns.

Parameters
  • scan: the scan metadata.
  • rows: a list of 0-indexed rows (y-positions) in the scan axes. Leave as None to include all rows.
  • cols: a list of 0-indexed columns (x-positions) in the scan axes. Leave as None to include all columns.
  • n_roi: the region of interest to use. Defaults to 0.
  • as_flat: whether to return a flat list of Tile objects or a list of lists.
Returns

if as_flat: a list of Tile objects in row-major order; if not as_flat: a list of lists of Tile objects in their relative coordinates

@classmethod
def get_tiles_by_xy_bounds( cls, scan: csi_images.csi_scans.Scan, bounds: tuple[int, int, int, int], n_roi: int = 0, as_flat: bool = True) -> list[typing.Self] | list[list[typing.Self]]:
224    @classmethod
225    def get_tiles_by_xy_bounds(
226        cls,
227        scan: Scan,
228        bounds: tuple[int, int, int, int],
229        n_roi: int = 0,
230        as_flat: bool = True,
231    ) -> list[Self] | list[list[Self]]:
232        """
233        Gather a list of Tile objects based on the x, y bounds provided. The bounds are
234        exclusive, like indices, so the tiles at the corners are NOT included in the list.
235        :param scan: the scan metadata.
236        :param bounds: a tuple of (x_0, y_0, x_1, y_1) in the scan axes.
237        :param n_roi: the region of interest to use. Defaults to 0.
238        :param as_flat: whether to return a flat list of Tile objects or a list of lists.
239        :return: if as_flat: a list of Tile objects in row-major order;
240                 if not as_flat: a list of lists of Tile objects in their relative coordinates
241        """
242        x_0, y_0, x_1, y_1 = bounds
243        coordinates = []
244        for y in range(y_0, y_1):
245            for x in range(x_0, x_1):
246                coordinates.append((x, y))
247        return cls.get_tiles(scan, coordinates, n_roi, as_flat)

Gather a list of Tile objects based on the x, y bounds provided. The bounds are exclusive, like indices, so the tiles at the corners are NOT included in the list.

Parameters
  • scan: the scan metadata.
  • bounds: a tuple of (x_0, y_0, x_1, y_1) in the scan axes.
  • n_roi: the region of interest to use. Defaults to 0.
  • as_flat: whether to return a flat list of Tile objects or a list of lists.
Returns

if as_flat: a list of Tile objects in row-major order; if not as_flat: a list of lists of Tile objects in their relative coordinates