Skip to content

Commit

Permalink
Bugfix/slow tile retrieval (#486)
Browse files Browse the repository at this point in the history
* Stitch tiles together properly regardless of initial order given

* Use mosaic position index as M

* Shave pixels off in consistent direction

* Swap reversed Y and X dimensions

* Move xy origin to SE

* Provide options for flipping or swapping x/y coords

* Shave pixel after swapping x and y

* Allow passing additional args to tile retrieval

* Adjust kwarg typing for additional dimensions

* Avoid C and T dimension default when not present in dims

* Add method for retrieving multiple tile positions

* Adjust M index by kwarg

* Sorted bboxes by m_index. Remove IndexError expectation

* Reverse list after assignment

* Adjust expected error from high M index

* Adjust expected number of positions

* Avoid touching reference to mosaic position list directly

* Use constant for dimension names
  • Loading branch information
SeanLeRoy authored Apr 18, 2023
1 parent c30ed9e commit bed6166
Show file tree
Hide file tree
Showing 6 changed files with 337 additions and 16 deletions.
68 changes: 64 additions & 4 deletions aicsimageio/aics_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -878,25 +878,85 @@ def physical_pixel_sizes(self) -> PhysicalPixelSizes:
return self.reader.physical_pixel_sizes

def get_mosaic_tile_position(
self, mosaic_tile_index: int
) -> Optional[Tuple[int, int]]:
self, mosaic_tile_index: int, **kwargs: int
) -> Tuple[int, int]:
"""
Get the absolute position of the top left point for a single mosaic tile.
Returns None if the image is not a mosaic.
Parameters
----------
mosaic_tile_index: int
The index for the mosaic tile to retrieve position information for.
kwargs: int
The keywords below allow you to specify the dimensions that you wish
to match. If you under-specify the constraints you can easily
end up with a massive image stack.
Z = 1 # The Z-dimension.
C = 2 # The C-dimension ("channel").
T = 3 # The T-dimension ("time").
Returns
-------
top: int
The Y coordinate for the tile position.
left: int
The X coordinate for the tile position.
Raises
------
UnexpectedShapeError
The image has no mosaic dimension available.
"""
return self.reader.get_mosaic_tile_position(mosaic_tile_index)
return self.reader.get_mosaic_tile_position(mosaic_tile_index, **kwargs)

def get_mosaic_tile_positions(self, **kwargs: int) -> List[Tuple[int, int]]:
"""
Get the absolute positions of the top left points for each mosaic tile
matching the specified dimensions and current scene.
Parameters
----------
kwargs: int
The keywords below allow you to specify the dimensions that you wish
to match. If you under-specify the constraints you can easily
end up with a massive image stack.
Z = 1 # The Z-dimension.
C = 2 # The C-dimension ("channel").
T = 3 # The T-dimension ("time").
M = 4 # The mosaic tile index
Returns
-------
mosaic_tile_positions: List[Tuple[int, int]]
List of the Y and X coordinate for the tile positions.
Raises
------
UnexpectedShapeError
The image has no mosaic dimension available.
NotImplementedError
Unable to combine M dimension with other dimensions when finding
tiles matching kwargs
"""
if dimensions.DimensionNames.MosaicTile in kwargs:
# Don't support getting positions by M + another dim
if len(kwargs) != 1:
other_keys = {
key for key in kwargs if key != dimensions.DimensionNames.MosaicTile
}
raise NotImplementedError(
"Unable to determine appropriate position using mosaic tile "
+ "index (M) combined with other dimensions "
+ f"(including {other_keys})"
)

return [
self.get_mosaic_tile_position(
kwargs[dimensions.DimensionNames.MosaicTile]
)
]

return self.reader.get_mosaic_tile_positions(**kwargs)

@property
def mosaic_tile_dims(self) -> Optional[dimensions.Dimensions]:
Expand Down
80 changes: 74 additions & 6 deletions aicsimageio/readers/czi_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -917,14 +917,25 @@ def physical_pixel_sizes(self) -> types.PhysicalPixelSizes:

return self._px_sizes

def get_mosaic_tile_position(self, mosaic_tile_index: int) -> Tuple[int, int]:
def get_mosaic_tile_position(
self,
mosaic_tile_index: int,
**kwargs: int,
) -> Tuple[int, int]:
"""
Get the absolute position of the top left point for a single mosaic tile.
Parameters
----------
mosaic_tile_index: int
The index for the mosaic tile to retrieve position information for.
kwargs: int
The keywords below allow you to specify the dimensions that you wish
to match. If you under-specify the constraints you can easily
end up with a massive image stack.
Z = 1 # The Z-dimension.
C = 2 # The C-dimension ("channel").
T = 3 # The T-dimension ("time").
Returns
-------
Expand All @@ -937,16 +948,73 @@ def get_mosaic_tile_position(self, mosaic_tile_index: int) -> Tuple[int, int]:
------
UnexpectedShapeError
The image has no mosaic dimension available.
IndexError
No matching mosaic tile index found.
Notes
-----
Defaults T and C dimensions to 0 if present as dimensions in image
to avoid reading in massive image stack for large files.
"""
if DimensionNames.MosaicTile not in self.dims.order:
raise exceptions.UnexpectedShapeError("No mosaic dimension in image.")

# Get max of mosaic positions from lif
with self._fs.open(self._path) as open_resource:
czi = CziFile(open_resource.f)

bboxes = czi.get_all_mosaic_tile_bounding_boxes(S=self.current_scene_index)
bbox = list(bboxes.values())[mosaic_tile_index]
# Default Channel and Time dimensions to 0 to improve
# worst case read time for large files **only**
# when those dimensions are present on the image.
for dimension_name in [DimensionNames.Channel, DimensionNames.Time]:
if dimension_name not in kwargs and dimension_name in self.dims.order:
kwargs[dimension_name] = 0

bbox = czi.get_mosaic_tile_bounding_box(
M=mosaic_tile_index, S=self.current_scene_index, **kwargs
)
return bbox.y, bbox.x

def get_mosaic_tile_positions(self, **kwargs: int) -> List[Tuple[int, int]]:
"""
Get the absolute positions of the top left points for each mosaic tile
matching the specified dimensions and current scene.
Parameters
----------
kwargs: int
The keywords below allow you to specify the dimensions that you wish
to match. If you under-specify the constraints you can easily
end up with a massive image stack.
Z = 1 # The Z-dimension.
C = 2 # The C-dimension ("channel").
T = 3 # The T-dimension ("time").
Returns
-------
mosaic_tile_positions: List[Tuple[int, int]]
List of the Y and X coordinate for the tile positions.
Raises
------
UnexpectedShapeError
The image has no mosaic dimension available.
"""
if DimensionNames.MosaicTile not in self.dims.order:
raise exceptions.UnexpectedShapeError("No mosaic dimension in image.")

with self._fs.open(self._path) as open_resource:
czi = CziFile(open_resource.f)

tile_info_to_bboxes = czi.get_all_mosaic_tile_bounding_boxes(
S=self.current_scene_index, **kwargs
)

# Convert dictionary of tile info mappings to
# a list of bounding boxes sorted according to their
# respective M indexes
m_indexes_to_mosaic_positions = {
tile_info.m_index: (bbox.y, bbox.x)
for tile_info, bbox in tile_info_to_bboxes.items()
}
return [
m_indexes_to_mosaic_positions[m_index]
for m_index in sorted(m_indexes_to_mosaic_positions.keys())
]
83 changes: 82 additions & 1 deletion aicsimageio/readers/lif_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -750,7 +750,9 @@ def physical_pixel_sizes(self) -> types.PhysicalPixelSizes:

return self._px_sizes

def get_mosaic_tile_position(self, mosaic_tile_index: int) -> Tuple[int, int]:
def get_mosaic_tile_position(
self, mosaic_tile_index: int, **kwargs: int
) -> Tuple[int, int]:
"""
Get the absolute position of the top left point for a single mosaic tile.
Not equivalent to readlif's notion of mosaic_position.
Expand All @@ -759,6 +761,13 @@ def get_mosaic_tile_position(self, mosaic_tile_index: int) -> Tuple[int, int]:
----------
mosaic_tile_index: int
The index for the mosaic tile to retrieve position information for.
kwargs: int
The keywords below allow you to specify the dimensions that you wish
to match. If you under-specify the constraints you can easily
end up with a massive image stack.
Z = 1 # The Z-dimension.
C = 2 # The C-dimension ("channel").
T = 3 # The T-dimension ("time").
Returns
-------
Expand All @@ -777,6 +786,13 @@ def get_mosaic_tile_position(self, mosaic_tile_index: int) -> Tuple[int, int]:
if DimensionNames.MosaicTile not in self.dims.order:
raise exceptions.UnexpectedShapeError("No mosaic dimension in image.")

if kwargs:
raise NotImplementedError(
"Selecting mosaic positions by dimensions is not supporting "
+ "by LifReader. Retrieve a specific mosaic position via the "
+ "mosaic tile index (M) by using .get_mosaic_tile_position() instead."
)

# LIFs are packed from bottom right to top left
# To counter: subtract 1 + M from list index to get from back of list
index_x, index_y, _, _ = self._scene_short_info["mosaic_position"][
Expand All @@ -798,3 +814,68 @@ def get_mosaic_tile_position(self, mosaic_tile_index: int) -> Tuple[int, int]:
(index_y * self.dims.Y) - index_y,
(index_x * self.dims.X) - index_x,
)

def get_mosaic_tile_positions(self, **kwargs: int) -> List[Tuple[int, int]]:
"""
Get the absolute positions of the top left points for each mosaic tile
matching the specified dimensions and current scene.
Parameters
----------
kwargs: int
The keywords below allow you to specify the dimensions that you wish
to match. If you under-specify the constraints you can easily
end up with a massive image stack.
Z = 1 # The Z-dimension.
C = 2 # The C-dimension ("channel").
T = 3 # The T-dimension ("time").
Returns
-------
mosaic_tile_positions: List[Tuple[int, int]]
List of the Y and X coordinate for the tile positions.
Raises
------
UnexpectedShapeError
The image has no mosaic dimension available.
NotImplementedError
This reader does not support indexing tiles by dimensions other than M
"""
if DimensionNames.MosaicTile not in self.dims.order:
raise exceptions.UnexpectedShapeError("No mosaic dimension in image.")

if kwargs:
raise NotImplementedError(
"Selecting mosaic positions by dimensions is not supporting "
+ "by LifReader. Retrieve a specific mosaic position via the "
+ "mosaic tile index (M) by using .get_mosaic_tile_position() instead."
)

mosaic_positions: List[Tuple[int, int, float, float]] = self._scene_short_info[
"mosaic_position"
]

# LIFs are packed from bottom right to top left
# To counter: read the positions in reverse
adjusted_mosaic_positions: List[Tuple[int, int]] = []
y_dim_length, x_dim_length = self._get_yx_tile_count()
for x, y, *_ in reversed(mosaic_positions):
if self.is_x_and_y_swapped:
x, y = y, x
if self.is_x_flipped:
x = x_dim_length - x
if self.is_y_flipped:
y = y_dim_length - y

# Formula: (Dim position * Tile dim length) - Dim position
# where the "- Dim position" is to account for shaving a pixel off
# of each tile to account for overlap
adjusted_mosaic_positions.append(
(
(y * self.dims.Y) - y,
(x * self.dims.X) - x,
)
)

return adjusted_mosaic_positions
39 changes: 36 additions & 3 deletions aicsimageio/readers/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -794,16 +794,22 @@ def physical_pixel_sizes(self) -> PhysicalPixelSizes:
return PhysicalPixelSizes(None, None, None)

def get_mosaic_tile_position(
self, mosaic_tile_index: int
) -> Optional[Tuple[int, int]]:
self, mosaic_tile_index: int, **kwargs: int
) -> Tuple[int, int]:
"""
Get the absolute position of the top left point for a single mosaic tile.
Returns None if the image is not a mosaic.
Parameters
----------
mosaic_tile_index: int
The index for the mosaic tile to retrieve position information for.
kwargs: int
The keywords below allow you to specify the dimensions that you wish
to match. If you under-specify the constraints you can easily
end up with a massive image stack.
Z = 1 # The Z-dimension.
C = 2 # The C-dimension ("channel").
T = 3 # The T-dimension ("time").
Returns
-------
Expand All @@ -821,6 +827,33 @@ def get_mosaic_tile_position(
"""
raise NotImplementedError()

def get_mosaic_tile_positions(self, **kwargs: int) -> List[Tuple[int, int]]:
"""
Get the absolute positions of the top left points for each mosaic tile
matching the specified dimensions and current scene.
Parameters
----------
kwargs: int
The keywords below allow you to specify the dimensions that you wish
to match. If you under-specify the constraints you can easily
end up with a massive image stack.
Z = 1 # The Z-dimension.
C = 2 # The C-dimension ("channel").
T = 3 # The T-dimension ("time").
Returns
-------
mosaic_tile_positions: List[Tuple[int, int]]
List of the Y and X coordinate for the tile positions.
Raises
------
UnexpectedShapeError
The image has no mosaic dimension available.
"""
raise NotImplementedError()

@property
def mosaic_tile_dims(self) -> Optional[Dimensions]:
"""
Expand Down
Loading

0 comments on commit bed6166

Please sign in to comment.