def test_labeled_indices_sel_slice(): """Select a single tile across each index from an ImageStack with labeled indices. Verify that the data is correct and that the physical coordinates are correctly set.""" stack = setup_imagestack() selector = { Axes.ROUND: slice(None, 4), Axes.CH: slice(4, 6), Axes.ZPLANE: 4 } subselected = stack.sel(selector) # verify that the subselected stack has the correct index labels. for index_type, expected_results in ((Axes.ROUND, [1, 4]), (Axes.CH, [4, 6]), ( Axes.ZPLANE, [4], )): assert subselected.axis_labels(index_type) == expected_results for selectors in subselected._iter_axes({Axes.ROUND, Axes.CH, Axes.ZPLANE}): # verify that the subselected stack has the correct data. expected_fill_value = fill_value(selectors[Axes.ROUND], selectors[Axes.CH], selectors[Axes.ZPLANE]) verify_stack_fill(subselected, selectors, expected_fill_value) # verify that each tile in the subselected stack has the correct physical coordinates. verify_physical_coordinates( stack, X_COORDS, Y_COORDS, get_physical_coordinates_of_z_plane(Z_COORDS), )
def test_labeled_indices_sel_single_tile(): """Select a single tile across each index from an ImageStack with labeled indices. Verify that the data is correct and that the physical coordinates are correctly set.""" stack = setup_imagestack() for selector in stack._iter_axes({Axes.ROUND, Axes.CH, Axes.ZPLANE}): subselected = stack.sel(selector) # verify that the subselected stack has the correct index labels. for index_type in (Axes.ROUND, Axes.CH, Axes.ZPLANE): assert subselected.axis_labels(index_type) == [ selector[index_type] ] # verify that the subselected stack has the correct data. expected_fill_value = fill_value(selector[Axes.ROUND], selector[Axes.CH], selector[Axes.ZPLANE]) verify_stack_fill(stack, selector, expected_fill_value) # assert that the physical coordinate values are what we expect. verify_physical_coordinates( stack, X_COORDS, Y_COORDS, get_physical_coordinates_of_z_plane(Z_COORDS), )
def test_imagestack_export(tmpdir, format, count, recwarn): """ Save a synthetic stack to files and check the results """ stack_shape = OrderedDict([(Axes.ROUND, 3), (Axes.CH, 2), (Axes.ZPLANE, 1), (Axes.Y, 50), (Axes.X, 40)]) physical_coords = OrderedDict([ (PhysicalCoordinateTypes.X_MIN, X_COORDS[0]), (PhysicalCoordinateTypes.X_MAX, X_COORDS[1]), (PhysicalCoordinateTypes.Y_MIN, Y_COORDS[0]), (PhysicalCoordinateTypes.Y_MAX, Y_COORDS[1]), (PhysicalCoordinateTypes.Z_MIN, Z_COORDS[0]), (PhysicalCoordinateTypes.Z_MAX, Z_COORDS[1]) ]) stack = test_utils.imagestack_with_coords_factory(stack_shape, physical_coords) stack_json = tmpdir / "output.json" stack.export(str(stack_json), tile_format=format) files = list( [x for x in tmpdir.listdir() if str(x).endswith(format.file_ext)]) loaded_stack = ImageStack.from_path_or_url(str(stack_json)) verify_physical_coordinates( loaded_stack, X_COORDS, Y_COORDS, get_physical_coordinates_of_z_plane(Z_COORDS), ) assert count == len(files) with open(files[0], "rb") as fh: format.reader_func(fh)
def test_coordinates(): """Set up an ImageStack with tiles that are aligned. Verify that the coordinates retrieved match. """ stack = ImageStack.synthetic_stack(NUM_ROUND, NUM_CH, NUM_Z, HEIGHT, WIDTH, tile_fetcher=tile_fetcher_factory( AlignedTiles, True, )) for selectors in stack._iter_axes({Axes.ZPLANE}): verify_physical_coordinates( stack, X_COORDS, Y_COORDS, get_physical_coordinates_of_z_plane( zplane_to_z(selectors[Axes.ZPLANE])), selectors[Axes.ZPLANE])
def test_crop_rcz(): """Build an imagestack that contains a crop in r/c/z. Verify that the appropriate tiles are loaded. """ rounds = [1] chs = [2, 3] crop_parameters = CropParameters( permitted_rounds=rounds, permitted_chs=chs, ) stack = setup_imagestack(crop_parameters) assert stack.axis_labels(Axes.ROUND) == rounds assert stack.axis_labels(Axes.CH) == chs assert stack.axis_labels(Axes.ZPLANE) == Z_LABELS for round_ in stack.axis_labels(Axes.ROUND): for ch in stack.axis_labels(Axes.CH): for zplane in stack.axis_labels(Axes.ZPLANE): expected_data = data(round_, ch, zplane) verify_stack_data( stack, { Axes.ROUND: round_, Axes.CH: ch, Axes.ZPLANE: zplane }, expected_data, ) expected_z_coordinates = get_physical_coordinates_of_z_plane(Z_COORDS) verify_physical_coordinates( stack, X_COORDS, Y_COORDS, expected_z_coordinates, )
def test_scalar_coordinates(): """Set up an ImageStack where only a single scalar physical coordinate is provided per axis. Internally, this should be converted to a range where the two endpoints are identical to the physical coordinate provided. """ stack = ImageStack.synthetic_stack(NUM_ROUND, NUM_CH, NUM_Z, HEIGHT, WIDTH, tile_fetcher=tile_fetcher_factory( ScalarTiles, True, )) expected_x = X_COORDS[0] expected_y = Y_COORDS[0] for selectors in stack._iter_axes({Axes.ZPLANE}): expected_z = zplane_to_z(selectors[Axes.ZPLANE])[0] verify_physical_coordinates( stack, (expected_x, expected_x), (expected_y, expected_y), get_physical_coordinates_of_z_plane((expected_z, expected_z)), selectors[Axes.ZPLANE])
def __init__( self, tile_data: TileCollectionData, ) -> None: axes_sizes = { Axes.ROUND: len(set(tilekey.round for tilekey in tile_data.keys())), Axes.CH: len(set(tilekey.ch for tilekey in tile_data.keys())), Axes.ZPLANE: len(set(tilekey.z for tilekey in tile_data.keys())), } self._tile_data = tile_data # check for existing log info if STARFISH_EXTRAS_KEY in tile_data.extras and LOG in tile_data.extras[STARFISH_EXTRAS_KEY]: self._log = loads(tile_data.extras[STARFISH_EXTRAS_KEY])[LOG] else: self._log: List[dict] = list() data_shape: MutableSequence[int] = [] data_dimensions: MutableSequence[str] = [] data_tick_marks: MutableMapping[str, Sequence[int]] = dict() for ix in range(N_AXES): size_for_axis: Optional[int] = None dim_for_axis: Optional[Axes] = None for axis_name, axis_data in AXES_DATA.items(): if ix == axis_data.order: size_for_axis = axes_sizes[axis_name] dim_for_axis = axis_name break if size_for_axis is None or dim_for_axis is None: raise ValueError( f"Could not find entry for the {ix}th axis in AXES_DATA") data_shape.append(size_for_axis) data_dimensions.append(dim_for_axis.value) data_tick_marks[dim_for_axis.value] = list( sorted(set(tilekey[dim_for_axis] for tilekey in self._tile_data.keys()))) data_shape.extend([tile_data.tile_shape[Axes.Y], tile_data.tile_shape[Axes.X]]) data_dimensions.extend([Axes.Y.value, Axes.X.value]) # now that we know the tile data type (kind and size), we can allocate the data array. self._data = MPDataArray.from_shape_and_dtype( shape=data_shape, dtype=np.float32, initial_value=0, dims=data_dimensions, coords=data_tick_marks, ) all_selectors = list(self._iter_axes({Axes.ROUND, Axes.CH, Axes.ZPLANE})) first_selector = all_selectors[0] tile = tile_data.get_tile(r=first_selector[Axes.ROUND], ch=first_selector[Axes.CH], z=first_selector[Axes.ZPLANE]) # Set up coordinates self._data[Coordinates.X.value] = xr.DataArray( np.linspace(tile.coordinates[Coordinates.X][0], tile.coordinates[Coordinates.X][1], self.xarray.sizes[Axes.X.value]), dims=Axes.X.value) self._data[Coordinates.Y.value] = xr.DataArray( np.linspace(tile.coordinates[Coordinates.Y][0], tile.coordinates[Coordinates.Y][1], self.xarray.sizes[Axes.Y.value]), dims=Axes.Y.value) if Coordinates.Z in tile.coordinates: # Fill with zeros for now, then replace with calculated midpoints self._data[Coordinates.Z.value] = xr.DataArray(np.zeros( self.xarray.sizes[Axes.ZPLANE.value]), dims=Axes.ZPLANE.value) # Get coords on first tile, then verify all subsequent tiles are aligned starting_coords = [ tile.coordinates[Coordinates.X][0], tile.coordinates[Coordinates.X][1], tile.coordinates[Coordinates.Y][0], tile.coordinates[Coordinates.Y][1], ] for selector in tqdm(all_selectors): tile = tile_data.get_tile( r=selector[Axes.ROUND], ch=selector[Axes.CH], z=selector[Axes.ZPLANE]) data = img_as_float32(tile.numpy_array) self.set_slice(selector=selector, data=data) coordinates_values = [ tile.coordinates[Coordinates.X][0], tile.coordinates[Coordinates.X][1], tile.coordinates[Coordinates.Y][0], tile.coordinates[Coordinates.Y][1], ] if starting_coords != coordinates_values: raise ValueError( f"Tiles must be aligned") if Coordinates.Z in tile.coordinates: z_range = (tile.coordinates[Coordinates.Z][0], tile.coordinates[Coordinates.Z][1]) # Use mid-point of the z range for a tile for the z-coordinate self._data[Coordinates.Z.value].loc[selector[Axes.ZPLANE]] = \ physical_coordinate_calculator.get_physical_coordinates_of_z_plane(z_range)
def test_crop_xy(): """Build an imagestack that contains a crop in x/y. Verify that the data is sliced correctly. """ X_SLICE = (10, 30) Y_SLICE = (15, 40) crop_parameters = CropParameters( x_slice=slice(*X_SLICE), y_slice=slice(*Y_SLICE), ) stack = setup_imagestack(crop_parameters) assert stack.axis_labels(Axes.ROUND) == ROUND_LABELS assert stack.axis_labels(Axes.CH) == CH_LABELS assert stack.axis_labels(Axes.ZPLANE) == Z_LABELS assert stack.raw_shape[3] == Y_SLICE[1] - Y_SLICE[0] assert stack.raw_shape[4] == X_SLICE[1] - X_SLICE[0] for round_ in stack.axis_labels(Axes.ROUND): for ch in stack.axis_labels(Axes.CH): for zplane in stack.axis_labels(Axes.ZPLANE): expected_data = data(round_, ch, zplane) expected_data = expected_data[Y_SLICE[0]:Y_SLICE[1], X_SLICE[0]:X_SLICE[1]] verify_stack_data( stack, { Axes.ROUND: round_, Axes.CH: ch, Axes.ZPLANE: zplane }, expected_data, ) # the coordinates should be rescaled. verify that the coordinates on the ImageStack # are also rescaled. original_x_coordinates = X_COORDS expected_x_coordinates = recalculate_physical_coordinate_range( original_x_coordinates[0], original_x_coordinates[1], WIDTH, slice(*X_SLICE), ) original_y_coordinates = Y_COORDS expected_y_coordinates = recalculate_physical_coordinate_range( original_y_coordinates[0], original_y_coordinates[1], HEIGHT, slice(*Y_SLICE), ) expected_z_coordinates = get_physical_coordinates_of_z_plane(Z_COORDS) verify_physical_coordinates( stack, expected_x_coordinates, expected_y_coordinates, expected_z_coordinates, )