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