def test_multiple_tiles_of_different_kind(): with pytest.raises(TypeError): ImageStack.synthetic_stack( NUM_ROUND, NUM_CH, NUM_Z, HEIGHT, WIDTH, tile_fetcher=CornerDifferentDtype(np.uint32, np.float32), )
def test_fov_order(): data = SyntheticData() codebook = data.codebook() stack1 = ImageStack.synthetic_stack() stack2 = ImageStack.synthetic_stack() fovs = [ FieldOfView("stack2", {"primary": stack2}), FieldOfView("stack1", {"primary": stack1}) ] extras = {"synthetic": True} experiment = Experiment(fovs, codebook, extras) assert "stack1" == experiment.fov().name assert ["stack1", "stack2"] == [x.name for x in experiment.fovs()]
def test_coordinates(): """Set up an ImageStack with tiles that are offset based on round. Verify that the coordinates retrieved match. """ stack = ImageStack.synthetic_stack(NUM_ROUND, NUM_CH, NUM_Z, HEIGHT, WIDTH, tile_fetcher=tile_fetcher_factory( OffsettedTiles, True, )) for _round in range(NUM_ROUND): for ch in range(NUM_CH): for z in range(NUM_Z): indices = {Indices.ROUND: _round, Indices.CH: ch, Indices.Z: z} xmin, xmax = stack.tile_coordinates(indices, Coordinates.X) ymin, ymax = stack.tile_coordinates(indices, Coordinates.Y) zmin, zmax = stack.tile_coordinates(indices, Coordinates.Z) expected_xmin, expected_xmax = round_to_x(_round) expected_ymin, expected_ymax = round_to_y(_round) expected_zmin, expected_zmax = round_to_z(_round) assert np.isclose(xmin, expected_xmin) assert np.isclose(xmax, expected_xmax) assert np.isclose(ymin, expected_ymin) assert np.isclose(ymax, expected_ymax) assert np.isclose(zmin, expected_zmin) assert np.isclose(zmax, expected_zmax)
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( OffsettedScalarTiles, True, )) assert stack.tiles_aligned is False for selectors in stack._iter_axes({Axes.ROUND, Axes.CH, Axes.ZPLANE}): expected_x = round_to_x(selectors[Axes.ROUND])[0] expected_y = round_to_y(selectors[Axes.ROUND])[0] expected_z = round_to_z(selectors[Axes.ROUND])[0] verify_physical_coordinates( stack, selectors, (expected_x, expected_x), (expected_y, expected_y), (expected_z, expected_z), )
def test_missing_extras(): """ If the extras are not present on some of the tiles, it should still work. """ class OnesTilesWithExtrasMostly(OnesTile): def __init__(self, fov, r, ch, z, extras: dict) -> None: super().__init__((10, 10)) self.fov = fov self._extras = extras @property def extras(self): if self.fov == 0: return None return self._extras tile_fetcher = tile_fetcher_factory(OnesTilesWithExtrasMostly, True, {'random_key': { 'hello': "world", }}) stack = ImageStack.synthetic_stack( num_round=NUM_ROUND, num_ch=NUM_CH, num_z=NUM_Z, tile_fetcher=tile_fetcher, ) table = stack.tile_metadata assert len(table) == NUM_ROUND * NUM_CH * NUM_Z
def test_apply_3d(): """test that apply correctly applies a simple function across 3d volumes of a Stack""" stack = ImageStack.synthetic_stack() assert np.all(stack.xarray == 1) stack.apply(divide, in_place=True, value=4, group_by={Axes.ROUND, Axes.CH}) assert (stack.xarray == 0.25).all()
def test_unaligned_tiles(): """Test that imagestack error is thrown when constructed with unaligned tiles""" try: ImageStack.synthetic_stack(NUM_ROUND, NUM_CH, NUM_Z, HEIGHT, WIDTH, tile_fetcher=tile_fetcher_factory( OffsettedTiles, True, )) except ValueError as e: # Assert value error is thrown with right message assert e.args[0] == "Tiles must be aligned"
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( OffsettedScalarTiles, True, )) for _round in range(NUM_ROUND): for ch in range(NUM_CH): for z in range(NUM_Z): indices = {Indices.ROUND: _round, Indices.CH: ch, Indices.Z: z} xmin, xmax = stack.tile_coordinates(indices, Coordinates.X) ymin, ymax = stack.tile_coordinates(indices, Coordinates.Y) zmin, zmax = stack.tile_coordinates(indices, Coordinates.Z) expected_x = round_to_x(_round)[0] expected_y = round_to_y(_round)[0] expected_z = round_to_z(_round)[0] assert np.isclose(xmin, expected_x) assert np.isclose(xmax, expected_x) assert np.isclose(ymin, expected_y) assert np.isclose(ymax, expected_y) assert np.isclose(zmin, expected_z) assert np.isclose(zmax, expected_z)
def test_imagestack_export(tmpdir, format, count, recwarn): """ Save a synthetic stack to files and check the results """ stack = ImageStack.synthetic_stack() 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)]) assert ImageStack.from_path_or_url(str(stack_json)) assert count == len(files) with open(files[0], "rb") as fh: format.reader_func(fh)
def test_multiple_tiles_of_same_dtype(): stack = ImageStack.synthetic_stack( NUM_ROUND, NUM_CH, NUM_Z, HEIGHT, WIDTH, tile_fetcher=CornerDifferentDtype(np.uint32, np.uint32), ) expected = np.ones((NUM_ROUND, NUM_CH, NUM_Z, HEIGHT, WIDTH), dtype=np.uint32) assert np.array_equal(stack.xarray, img_as_float32(expected))
def test_aligned_coordinates(): """Set up an ImageStack where all the tiles are aligned (have the same physical coordinate values). Assert that the resulting Imagestack's tiles_aligned attribute is True """ stack = ImageStack.synthetic_stack(NUM_ROUND, NUM_CH, NUM_Z, HEIGHT, WIDTH, tile_fetcher=tile_fetcher_factory( AlignedTiles, True, )) assert stack.tiles_aligned is True
def test_float_type_demotion(): with warnings.catch_warnings(record=True) as warnings_: stack = ImageStack.synthetic_stack( NUM_ROUND, NUM_CH, NUM_Z, HEIGHT, WIDTH, tile_fetcher=CornerDifferentDtype(np.float64, np.float32), ) assert len(warnings_) == 2 assert issubclass(warnings_[0].category, DataFormatWarning) assert issubclass(warnings_[1].category, UserWarning) expected = np.ones((NUM_ROUND, NUM_CH, NUM_Z, HEIGHT, WIDTH), dtype=np.float64) assert np.array_equal(stack.xarray, expected)
def test_set_slice_range(): """ Sets a slice across a range of one of the axes. """ stack = ImageStack.synthetic_stack() zrange = slice(1, 3) y, x = stack.tile_shape expected = np.full((stack.shape[Axes.ROUND], stack.shape[Axes.CH], zrange.stop - zrange.start + 1, y, x), fill_value=0.5, dtype=np.float32) index = {Axes.ZPLANE: zrange} stack.set_slice(index, expected, [Axes.ROUND, Axes.CH, Axes.ZPLANE]) assert np.array_equal(stack.get_slice(index)[0], expected)
def test_conflict(): """ Tiles that have extras that conflict with indices should produce an error. """ tile_fetcher = tile_fetcher_factory(OnesTilesWithExtras, False, {Indices.ROUND: { 'hello': "world", }}) stack = ImageStack.synthetic_stack( num_round=NUM_ROUND, num_ch=NUM_CH, num_z=NUM_Z, tile_fetcher=tile_fetcher, ) with pytest.raises(ValueError): stack.tile_metadata
def test_set_slice_simple_index(): """ Sets a slice across one of the indices at the end. For instance, if the dimensions are (P, Q0,..., Qn-1, R), sets a slice across either P or R. """ stack = ImageStack.synthetic_stack() round_ = 1 y, x = stack.tile_shape expected = np.full((stack.shape[Indices.CH], stack.shape[Indices.Z], y, x), fill_value=0.5, dtype=np.float32) index = {Indices.ROUND: round_} stack.set_slice(index, expected) assert np.array_equal(stack.get_slice(index)[0], expected)
def test_metadata(): """ Normal situation where all the tiles have uniform keys for both indices and extras. """ tile_fetcher = tile_fetcher_factory(OnesTilesWithExtras, False, {'random_key': { 'hello': "world", }}) stack = ImageStack.synthetic_stack( num_round=NUM_ROUND, num_ch=NUM_CH, num_z=NUM_Z, tile_fetcher=tile_fetcher, ) table = stack.tile_metadata assert len(table) == NUM_ROUND * NUM_CH * NUM_Z
def test_set_slice_middle_index(): """ Sets a slice across one of the indices in the middle. For instance, if the dimensions are (P, Q0,..., Qn-1, R), slice across one of the Q axes. """ stack = ImageStack.synthetic_stack() ch = 1 y, x = stack.tile_shape expected = np.full( (stack.shape[Indices.ROUND], stack.shape[Indices.Z], y, x), fill_value=0.5, dtype=np.float32) index = {Indices.CH: ch} stack.set_slice(index, expected) assert np.array_equal(stack.get_slice(index)[0], expected)
def test_set_slice_simple_index(): """ Sets a slice across one of the axes at the end. For instance, if the axes are (P, Q0,..., Qn-1, R), sets a slice across either P or R. This test has expectations regarding the ordering of the axes in the ImageStack. """ stack = ImageStack.synthetic_stack() round_ = 1 y, x = stack.tile_shape expected = np.full((stack.shape[Axes.CH], stack.shape[Axes.ZPLANE], y, x), fill_value=0.5, dtype=np.float32) index = {Axes.ROUND: round_} stack.set_slice(index, expected, [Axes.CH, Axes.ZPLANE]) assert np.array_equal(stack.get_slice(index)[0], expected)
def test_get_slice_range(): """ Retrieve a slice across a range of one of the dimensions. """ stack = ImageStack.synthetic_stack() zrange = slice(1, 3) imageslice, axes = stack.get_slice({Axes.ZPLANE: zrange}) y, x = stack.tile_shape assert axes == [Axes.ROUND, Axes.CH, Axes.ZPLANE] for round_ in range(stack.shape[Axes.ROUND]): for ch in range(stack.shape[Axes.CH]): for z in range(zrange.stop - zrange.start): data = np.empty((y, x)) data.fill((round_ * stack.shape[Axes.CH] + ch) * stack.shape[Axes.ZPLANE] + (z + zrange.start)) assert data.all() == imageslice[round_, ch, z].all()
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_int_type_promotion(): with warnings.catch_warnings(record=True) as warnings_: stack = ImageStack.synthetic_stack( NUM_ROUND, NUM_CH, NUM_Z, HEIGHT, WIDTH, tile_fetcher=CornerDifferentDtype(np.int32, np.int8), ) assert len(warnings_) == 2 assert issubclass(warnings_[0].category, DataFormatWarning) assert issubclass(warnings_[1].category, UserWarning) expected = img_as_float32( np.ones((NUM_ROUND, NUM_CH, NUM_Z, HEIGHT, WIDTH), dtype=np.int32)) corner = img_as_float32(np.ones((HEIGHT, WIDTH), dtype=np.int8)) expected[0, 0, 0] = corner assert np.array_equal(stack.xarray, img_as_float32(expected))
def test_get_slice_middle_index(): """ Retrieve a slice across one of the indices in the middle. For instance, if the dimensions are (P, Q0,..., Qn-1, R), slice across one of the Q axes. """ stack = ImageStack.synthetic_stack() ch = 1 imageslice, axes = stack.get_slice({Indices.CH: ch}) assert axes == [Indices.ROUND, Indices.Z] y, x = stack.tile_shape for round_ in range(stack.shape[Indices.ROUND]): for z in range(stack.shape[Indices.Z]): data = np.empty((y, x)) data.fill((round_ * stack.shape[Indices.CH] + ch) * stack.shape[Indices.Z] + z) assert data.all() == imageslice[round_, z].all()
def test_set_slice_reorder(): """ Sets a slice across one of the axes. The source data is not in the same order as the axes in ImageStack, but set_slice should reorder the axes and write it correctly. """ stack = ImageStack.synthetic_stack() round_ = 1 y, x = stack.tile_shape index = {Axes.ROUND: round_} written = np.full((stack.shape[Axes.ZPLANE], stack.shape[Axes.CH], y, x), fill_value=0.5, dtype=np.float32) stack.set_slice(index, written, [Axes.ZPLANE, Axes.CH]) expected = np.full((stack.shape[Axes.CH], stack.shape[Axes.ZPLANE], y, x), fill_value=0.5, dtype=np.float32) assert np.array_equal(stack.get_slice(index)[0], expected)
def test_get_slice_simple_index(): """ Retrieve a slice across one of the indices at the end. For instance, if the dimensions are (P, Q0,..., Qn-1, R), slice across either P or R. """ stack = ImageStack.synthetic_stack() round_ = 1 imageslice, axes = stack.get_slice({Indices.ROUND: round_}) assert axes == [Indices.CH, Indices.Z] y, x = stack.tile_shape for ch in range(stack.shape[Indices.CH]): for z in range(stack.shape[Indices.Z]): data = np.empty((y, x)) data.fill((round_ * stack.shape[Indices.CH] + ch) * stack.shape[Indices.Z] + z) assert data.all() == imageslice[ch, z].all()
def test_get_slice_middle_index(): """ Retrieve a slice across one of the axes in the middle. For instance, if the axes are (P, Q0,..., Qn-1, R), slice across one of the Q axes. This test has expectations regarding the ordering of the axes in the ImageStack. """ stack = ImageStack.synthetic_stack() ch = 1 imageslice, axes = stack.get_slice({Axes.CH: ch}) assert axes == [Axes.ROUND, Axes.ZPLANE] y, x = stack.tile_shape for round_ in range(stack.shape[Axes.ROUND]): for z in range(stack.shape[Axes.ZPLANE]): data = np.empty((y, x)) data.fill((round_ * stack.shape[Axes.CH] + ch) * stack.shape[Axes.ZPLANE] + z) assert data.all() == imageslice[round_, z].all()
def test_coordinates(): """Set up an ImageStack with tiles that are offset based on round. Verify that the coordinates retrieved match. """ stack = ImageStack.synthetic_stack(NUM_ROUND, NUM_CH, NUM_Z, HEIGHT, WIDTH, tile_fetcher=tile_fetcher_factory( OffsettedTiles, True, )) assert stack.tiles_aligned is False for selectors in stack._iter_axes({Axes.ROUND, Axes.CH, Axes.ZPLANE}): verify_physical_coordinates( stack, selectors, round_to_x(selectors[Axes.ROUND]), round_to_y(selectors[Axes.ROUND]), round_to_z(selectors[Axes.ROUND]), )
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 test_imagestack_indexing(): """Tests indexing on an Imagestack with a shape (5, 5, 15, 200, 200) steps: 1.) stack.sel(indexers) 2.) assert new shape of stack is what we expect """ stack = ImageStack.synthetic_stack(num_round=5, num_ch=5, num_z=15, tile_height=200, tile_width=200) # index on range of rounds and single ch and Z indexed = stack.sel({ Indices.ROUND: (1, None), Indices.CH: 0, Indices.Z: 0 }) expected_shape = OrderedDict([(Indices.ROUND, 4), (Indices.CH, 1), (Indices.Z, 1), (Indices.Y, 200), (Indices.X, 200)]) assert indexed.shape == expected_shape # index on single round ch and z indexed = stack.sel({Indices.ROUND: 0, Indices.CH: 0, Indices.Z: 0}) expected_shape = OrderedDict([(Indices.ROUND, 1), (Indices.CH, 1), (Indices.Z, 1), (Indices.Y, 200), (Indices.X, 200)]) assert indexed.shape == expected_shape # index on single round and range of ch indexed = stack.sel({Indices.ROUND: 1, Indices.CH: (3, None)}) expected_shape = OrderedDict([(Indices.ROUND, 1), (Indices.CH, 2), (Indices.Z, 15), (Indices.Y, 200), (Indices.X, 200)]) assert indexed.shape == expected_shape # index on single round and range of ch and Z indexed = stack.sel({ Indices.ROUND: 1, Indices.CH: (None, 3), Indices.Z: (7, None) }) expected_shape = OrderedDict([(Indices.ROUND, 1), (Indices.CH, 3), (Indices.Z, 8), (Indices.Y, 200), (Indices.X, 200)]) assert indexed.shape == expected_shape # index on first half of X and single value of Y indexed_stack = stack.sel({ Indices.ROUND: 0, Indices.CH: 0, Indices.Z: 1, Indices.Y: 100, Indices.X: (None, 100) }) expected_shape = OrderedDict([(Indices.ROUND, 1), (Indices.CH, 1), (Indices.Z, 1), (Indices.Y, 1), (Indices.X, 100)]) assert indexed_stack.shape == expected_shape # index on first half of X and Y indexed_stack = stack.sel({Indices.Y: (None, 100), Indices.X: (None, 100)}) expected_shape = OrderedDict([(Indices.ROUND, 5), (Indices.CH, 5), (Indices.Z, 15), (Indices.Y, 100), (Indices.X, 100)]) assert indexed_stack.shape == expected_shape # index on single x and y indexed_stack = stack.sel({ Indices.ROUND: 0, Indices.CH: 0, Indices.Z: 1, Indices.Y: 100, Indices.X: 150 }) expected_shape = OrderedDict([(Indices.ROUND, 1), (Indices.CH, 1), (Indices.Z, 1), (Indices.Y, 1), (Indices.X, 1)]) assert indexed_stack.shape == expected_shape # Negative indexing indexed_stack = stack.sel({ Indices.ROUND: 0, Indices.CH: 0, Indices.Z: 1, Indices.Y: (None, -10), Indices.X: (None, -10) }) expected_shape = OrderedDict([(Indices.ROUND, 1), (Indices.CH, 1), (Indices.Z, 1), (Indices.Y, 190), (Indices.X, 190)]) assert indexed_stack.shape == expected_shape
def test_apply_single_process(): """test that apply correctly applies a simple function across 2d tiles of a Stack""" stack = ImageStack.synthetic_stack() assert (stack.xarray == 1).all() output = stack.apply(divide, value=2, n_processes=1) assert (output.xarray == 0.5).all()