feat: init
This commit is contained in:
185
test/model/metadata_sources/test_bdv_metadata.py
Normal file
185
test/model/metadata_sources/test_bdv_metadata.py
Normal file
@@ -0,0 +1,185 @@
|
||||
import os
|
||||
import numpy as np
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ext", ["h5", "n5", "tiff"])
|
||||
def test_bdv_metadata(ext):
|
||||
from navigate.model.metadata_sources.bdv_metadata import BigDataViewerMetadata
|
||||
|
||||
md = BigDataViewerMetadata()
|
||||
|
||||
views = []
|
||||
for _ in range(10):
|
||||
views.append(
|
||||
{
|
||||
"x": np.random.randint(-1000, 1000),
|
||||
"y": np.random.randint(-1000, 1000),
|
||||
"z": np.random.randint(-1000, 1000),
|
||||
"theta": np.random.randint(-1000, 1000),
|
||||
"f": np.random.randint(-1000, 1000),
|
||||
}
|
||||
)
|
||||
|
||||
for view in views:
|
||||
arr = md.stage_positions_to_affine_matrix(**view)
|
||||
assert arr[0, 3] == view["y"] / md.dy
|
||||
assert arr[1, 3] == view["x"] / md.dx
|
||||
assert arr[2, 3] == view["z"] / md.dz
|
||||
|
||||
md.write_xml(f"test_bdv.{ext}", views)
|
||||
|
||||
os.remove("test_bdv.xml")
|
||||
|
||||
# Test defaults for shear transform.
|
||||
assert md.rotate_data is False
|
||||
assert md.shear_data is False
|
||||
assert np.shape(md.rotate_transform) == (3, 4)
|
||||
assert np.shape(md.shear_transform) == (3, 4)
|
||||
|
||||
# Confirm that the shear/rotation transforms are identity matrices by default
|
||||
assert np.all(md.shear_transform == np.eye(3, 4))
|
||||
assert np.all(md.rotate_transform == np.eye(3, 4))
|
||||
|
||||
# Confirm that the shear/rotation transforms are identity matrices by default
|
||||
# even after calling calculate_shear_transform and calculate_rotate_transform
|
||||
md.bdv_shear_transform()
|
||||
md.bdv_rotate_transform()
|
||||
assert np.all(md.shear_transform == np.eye(3, 4))
|
||||
assert np.all(md.rotate_transform == np.eye(3, 4))
|
||||
|
||||
# Test that the shear/rotation transforms are correctly calculated.
|
||||
md.shear_data = True
|
||||
md.shear_dimension = "XZ"
|
||||
md.shear_angle = 15
|
||||
md.dx, md.dy, md.dz = 1, 1, 1
|
||||
md.bdv_shear_transform()
|
||||
assert md.shear_transform[0, 2] == np.tan(np.deg2rad(15))
|
||||
|
||||
md.rotate_data = True
|
||||
md.rotate_angle_x = 15
|
||||
md.rotate_angle_y = 0
|
||||
md.rotate_angle_z = 0
|
||||
md.bdv_rotate_transform()
|
||||
assert md.rotate_transform[1, 1] == np.cos(np.deg2rad(15))
|
||||
assert md.rotate_transform[1, 2] == -np.sin(np.deg2rad(15))
|
||||
assert md.rotate_transform[2, 1] == np.sin(np.deg2rad(15))
|
||||
assert md.rotate_transform[2, 2] == np.cos(np.deg2rad(15))
|
||||
|
||||
# Make sure we can still write the data.
|
||||
md.write_xml(f"test_bdv.{ext}", views)
|
||||
os.remove("test_bdv.xml")
|
||||
|
||||
@pytest.mark.parametrize("stack_cycling_mode", ["per_stack", "per_z"])
|
||||
def test_bdv_xml_dict(dummy_model, stack_cycling_mode):
|
||||
from navigate.model.metadata_sources.bdv_metadata import BigDataViewerMetadata
|
||||
|
||||
md = BigDataViewerMetadata()
|
||||
|
||||
md.configuration = dummy_model.configuration.copy()
|
||||
|
||||
# set shape from configuration and experiment
|
||||
md.configuration["experiment"]["MicroscopeState"]["image_mode"] = "z-stack"
|
||||
|
||||
# timepoints, channels, z-slices, positions
|
||||
for tp in [1]: #, 2, 3, 5]:
|
||||
for pos in [1, 3, 5]:
|
||||
for ch in [1, 2, 3]:
|
||||
for z in [1, 5, 10, 20]:
|
||||
md.configuration["experiment"]["MicroscopeState"]["timepoints"] = tp
|
||||
# channel settings
|
||||
channel_dict = md.configuration["experiment"]["MicroscopeState"]["channels"]
|
||||
for i in range(1, ch+1):
|
||||
channel_name = f"channel_{i}"
|
||||
if channel_name not in channel_dict:
|
||||
channel_dict[channel_name] = {
|
||||
"is_selected": True,
|
||||
"laser": "488nm",
|
||||
"laser_index": 0,
|
||||
"camera_exposure_time": 200.0,
|
||||
"laser_power": 20.0,
|
||||
"interval_time": 1.0,
|
||||
"defocus": 104.0,
|
||||
"filter_wheel_0": "Empty-Alignment",
|
||||
"filter_position_0": 6,
|
||||
"filter_wheel_1": "Empty-Alignment",
|
||||
"filter_position_1": 6
|
||||
}
|
||||
else:
|
||||
channel_dict[channel_name]["is_selected"] = True
|
||||
for i in range(ch+1, 5):
|
||||
channel_name = f"channel_{i}"
|
||||
if channel_name in channel_dict:
|
||||
channel_dict[channel_name]["is_selected"] = False
|
||||
# z-stack settings
|
||||
start_z_position = md.configuration["experiment"]["MicroscopeState"]["start_position"]
|
||||
md.configuration["experiment"]["MicroscopeState"]["number_z_steps"] = z
|
||||
md.configuration["experiment"]["MicroscopeState"]["step_size"] = 0.2
|
||||
md.configuration["experiment"]["MicroscopeState"]["end_position"] = z * 0.2 + start_z_position
|
||||
# multiposition settings
|
||||
md.configuration["experiment"]["MicroscopeState"]["is_multiposition"] = (
|
||||
True if pos > 1 else False
|
||||
)
|
||||
multipositions = [
|
||||
["X", "Y", "Z", "THETA", "F"]
|
||||
]
|
||||
for p in range(pos):
|
||||
position = [
|
||||
np.random.uniform(0, 100), # X
|
||||
np.random.uniform(0, 100), # Y
|
||||
np.random.uniform(0, 100), # Z
|
||||
np.random.uniform(0, 360), # THETA
|
||||
np.random.uniform(0, 10), # F
|
||||
]
|
||||
multipositions.append(position)
|
||||
|
||||
md.configuration["multi_positions"] = multipositions
|
||||
|
||||
md.set_from_configuration_experiment()
|
||||
|
||||
assert md.shape_t == tp
|
||||
assert md.shape_c == ch
|
||||
assert md.shape_z == z
|
||||
assert md.positions == pos
|
||||
|
||||
if pos > 1:
|
||||
assert md._multiposition is True
|
||||
else:
|
||||
assert md._multiposition is False
|
||||
|
||||
# view
|
||||
views = []
|
||||
for p in range(1, pos):
|
||||
for c in range(ch):
|
||||
position = md.configuration["multi_positions"][p + 1]
|
||||
for _z in range(z):
|
||||
views.append(
|
||||
{
|
||||
"x": position[0],
|
||||
"y": position[1],
|
||||
"z": position[2] + start_z_position + _z * 0.2,
|
||||
"theta": position[3],
|
||||
"f": position[4],
|
||||
}
|
||||
)
|
||||
|
||||
xml_dict = md.bdv_xml_dict("test_file.h5", views)
|
||||
|
||||
assert "ImageLoader" in xml_dict["SequenceDescription"]
|
||||
assert "ViewSetups" in xml_dict["SequenceDescription"]
|
||||
assert "Timepoints" in xml_dict["SequenceDescription"]
|
||||
assert "ViewRegistrations" in xml_dict
|
||||
# verify affine values are the same for a position
|
||||
view_registrations = xml_dict["ViewRegistrations"]["ViewRegistration"]
|
||||
for i in range(len(view_registrations)):
|
||||
# assert view id
|
||||
view_id = view_registrations[i]["setup"]
|
||||
if ch > 1:
|
||||
assert view_id == ((i % ch) * pos + (i // ch))
|
||||
# assert affine position consistency between channels
|
||||
if i % ch == 0:
|
||||
affine = view_registrations[i]["ViewTransform"][0]["affine"]["text"]
|
||||
else:
|
||||
affine_c = view_registrations[i]["ViewTransform"][0]["affine"]["text"]
|
||||
assert affine == affine_c
|
||||
154
test/model/metadata_sources/test_metadata.py
Normal file
154
test/model/metadata_sources/test_metadata.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import pytest
|
||||
import random
|
||||
|
||||
|
||||
def test_metadata_voxel_size(dummy_model):
|
||||
from navigate.model.metadata_sources.metadata import Metadata
|
||||
|
||||
md = Metadata()
|
||||
|
||||
md.configuration = dummy_model.configuration
|
||||
|
||||
zoom = dummy_model.configuration["experiment"]["MicroscopeState"]["zoom"]
|
||||
active_microscope = dummy_model.configuration["experiment"]["MicroscopeState"][
|
||||
"microscope_name"
|
||||
]
|
||||
pixel_size = float(
|
||||
dummy_model.configuration["configuration"]["microscopes"][active_microscope][
|
||||
"zoom"
|
||||
]["pixel_size"][zoom]
|
||||
)
|
||||
|
||||
dx, dy, dz = md.voxel_size
|
||||
|
||||
assert (
|
||||
(dx == pixel_size)
|
||||
and (dy == pixel_size)
|
||||
and (
|
||||
dz
|
||||
== float(
|
||||
dummy_model.configuration["experiment"]["MicroscopeState"]["step_size"]
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_metadata_shape(dummy_model):
|
||||
from navigate.model.metadata_sources.metadata import Metadata
|
||||
|
||||
dummy_model.configuration["experiment"]["MicroscopeState"]["image_mode"] = "z-stack"
|
||||
|
||||
md = Metadata()
|
||||
|
||||
md.configuration = dummy_model.configuration
|
||||
|
||||
microscope_name = dummy_model.configuration["experiment"]["MicroscopeState"][
|
||||
"microscope_name"
|
||||
]
|
||||
txs = dummy_model.configuration["experiment"]["CameraParameters"][microscope_name][
|
||||
"img_x_pixels"
|
||||
]
|
||||
tys = dummy_model.configuration["experiment"]["CameraParameters"][microscope_name][
|
||||
"img_y_pixels"
|
||||
]
|
||||
tzs = dummy_model.configuration["experiment"]["MicroscopeState"]["number_z_steps"]
|
||||
tts = dummy_model.configuration["experiment"]["MicroscopeState"]["timepoints"]
|
||||
tcs = sum(
|
||||
[
|
||||
v["is_selected"] is True
|
||||
for k, v in dummy_model.configuration["experiment"]["MicroscopeState"][
|
||||
"channels"
|
||||
].items()
|
||||
]
|
||||
)
|
||||
|
||||
xs, ys, cs, zs, ts = md.shape
|
||||
|
||||
assert (xs == txs) and (ys == tys) and (zs == tzs) and (ts == tts) and (cs == tcs)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"image_mode",
|
||||
[
|
||||
"single",
|
||||
"Confocal Projection",
|
||||
"z-stack",
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("stack_cycling_mode", ["per_stack", "per_z"])
|
||||
@pytest.mark.parametrize("conpro_cycling_mode", ["per_stack", "per_plane"])
|
||||
def test_metadata_set_stack_order_from_configuration_experiment(
|
||||
dummy_model, image_mode, stack_cycling_mode, conpro_cycling_mode
|
||||
):
|
||||
from navigate.model.metadata_sources.metadata import Metadata
|
||||
|
||||
dummy_model.configuration["experiment"]["MicroscopeState"][
|
||||
"image_mode"
|
||||
] = image_mode
|
||||
dummy_model.configuration["experiment"]["MicroscopeState"][
|
||||
"stack_cycling_mode"
|
||||
] = stack_cycling_mode
|
||||
dummy_model.configuration["experiment"]["MicroscopeState"][
|
||||
"conpro_cycling_mode"
|
||||
] = conpro_cycling_mode
|
||||
|
||||
md = Metadata()
|
||||
|
||||
md.configuration = dummy_model.configuration
|
||||
|
||||
if image_mode == "z-stack" and stack_cycling_mode == "per_stack":
|
||||
assert md._per_stack is True
|
||||
elif image_mode == "Confocal Projection" and stack_cycling_mode == "per_stack":
|
||||
assert md._per_stack is True
|
||||
else:
|
||||
assert md._per_stack is False
|
||||
|
||||
def set_shape_from_configuration_experiment(dummy_model):
|
||||
from navigate.model.metadata_sources.metadata import Metadata
|
||||
|
||||
md = Metadata()
|
||||
|
||||
md.configuration = dummy_model.configuration.copy()
|
||||
|
||||
|
||||
# set up experiment with multiposition
|
||||
# no position
|
||||
md.configuration["experiment"]["MicroscopeState"]["image_mode"] = "z-stack"
|
||||
md.configuration["multi_positions"] = [
|
||||
["X", "Y", "Z", "THETA", "F"]
|
||||
]
|
||||
md.configuration["expriment"]["MicroscopeState"]["is_multiposition"] = False
|
||||
|
||||
md._set_shape_from_configuration_experiment()
|
||||
|
||||
assert md._multiposition is False
|
||||
assert md.positions == 1
|
||||
|
||||
# customized mode
|
||||
md.configuration["experiment"]["MicroscopeState"]["image_mode"] = "customized"
|
||||
assert md._multiposition is True
|
||||
assert md.positions == 1
|
||||
|
||||
# random multiposition
|
||||
md.configuration["experiment"]["MicroscopeState"]["image_mode"] = "z-stack"
|
||||
for i in range(5):
|
||||
num_positions = random.randint(2, 10)
|
||||
md.configuration["multi_positions"] = [["X", "Y", "Z", "THETA", "F"]]
|
||||
for p in range(num_positions):
|
||||
pos = [
|
||||
random.uniform(0, 100), # X
|
||||
random.uniform(0, 100), # Y
|
||||
random.uniform(0, 100), # Z
|
||||
random.uniform(0, 360), # THETA
|
||||
random.uniform(0, 10), # F
|
||||
]
|
||||
md.configuration["multi_positions"].append(pos)
|
||||
|
||||
md.configuration["experiment"]["MicroscopeState"]["is_multiposition"] = True
|
||||
|
||||
md._set_shape_from_configuration_experiment()
|
||||
|
||||
assert md._multiposition is True
|
||||
assert md.positions == num_positions
|
||||
|
||||
|
||||
46
test/model/metadata_sources/test_ome_tiff_metadata.py
Normal file
46
test/model/metadata_sources/test_ome_tiff_metadata.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import urllib.request
|
||||
import os
|
||||
import platform
|
||||
|
||||
from navigate.tools.file_functions import delete_folder
|
||||
|
||||
|
||||
def test_ome_metadata_valid(dummy_model):
|
||||
from navigate.model.metadata_sources.ome_tiff_metadata import OMETIFFMetadata
|
||||
|
||||
# First, download OME-XML validation tools
|
||||
# new_path = https://downloads.openmicroscopy.org/bio-formats/8.1.0/artifacts/bftools.zip
|
||||
# old_path = https://downloads.openmicroscopy.org/bio-formats/6.0.1/artifacts/bftools.zip
|
||||
|
||||
urllib.request.urlretrieve(
|
||||
"https://downloads.openmicroscopy.org/bio-formats/8.1.0/artifacts/bftools.zip",
|
||||
"bftools.zip",
|
||||
)
|
||||
|
||||
# Unzip
|
||||
_ = os.popen("tar -xzvf bftools.zip").read()
|
||||
|
||||
# Create metadata
|
||||
md = OMETIFFMetadata()
|
||||
|
||||
md.configuration = dummy_model.configuration
|
||||
|
||||
# Write metadata to file
|
||||
md.write_xml("test.xml")
|
||||
|
||||
# Validate the XML
|
||||
if platform.system() == "Windows":
|
||||
output = os.popen("bftools\\xmlvalid.bat test.xml").read()
|
||||
else:
|
||||
output = os.popen("./bftools/xmlvalid test.xml").read()
|
||||
|
||||
print(output)
|
||||
|
||||
# Delete bftools
|
||||
delete_folder("./bftools")
|
||||
os.remove("bftools.zip")
|
||||
|
||||
# Delete XML
|
||||
os.remove("test.xml")
|
||||
|
||||
assert "No validation errors found." in output
|
||||
189
test/model/metadata_sources/test_zarr_metadata.py
Normal file
189
test/model/metadata_sources/test_zarr_metadata.py
Normal file
@@ -0,0 +1,189 @@
|
||||
import pytest
|
||||
|
||||
SPACE_UNITS = [
|
||||
"angstrom",
|
||||
"attometer",
|
||||
"centimeter",
|
||||
"decimeter",
|
||||
"exameter",
|
||||
"femtometer",
|
||||
"foot",
|
||||
"gigameter",
|
||||
"hectometer",
|
||||
"inch",
|
||||
"kilometer",
|
||||
"megameter",
|
||||
"meter",
|
||||
"micrometer",
|
||||
"mile",
|
||||
"millimeter",
|
||||
"nanometer",
|
||||
"parsec",
|
||||
"petameter",
|
||||
"picometer",
|
||||
"terameter",
|
||||
"yard",
|
||||
"yoctometer",
|
||||
"yottameter",
|
||||
"zeptometer",
|
||||
"zettameter",
|
||||
]
|
||||
|
||||
TIME_UNITS = [
|
||||
"attosecond",
|
||||
"centisecond",
|
||||
"day",
|
||||
"decisecond",
|
||||
"exasecond",
|
||||
"femtosecond",
|
||||
"gigasecond",
|
||||
"hectosecond",
|
||||
"hour",
|
||||
"kilosecond",
|
||||
"megasecond",
|
||||
"microsecond",
|
||||
"millisecond",
|
||||
"minute",
|
||||
"nanosecond",
|
||||
"petasecond",
|
||||
"picosecond",
|
||||
"second",
|
||||
"terasecond",
|
||||
"yoctosecond",
|
||||
"yottasecond",
|
||||
"zeptosecond",
|
||||
"zettasecond",
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dummy_metadata(dummy_model):
|
||||
from navigate.model.metadata_sources.zarr_metadata import OMEZarrMetadata
|
||||
|
||||
# Create metadata
|
||||
md = OMEZarrMetadata()
|
||||
|
||||
md.configuration = dummy_model.configuration
|
||||
|
||||
return md
|
||||
|
||||
|
||||
def test_axes(dummy_metadata):
|
||||
|
||||
axes = dummy_metadata._axes
|
||||
|
||||
# Check length
|
||||
assert (len(axes) > 1) and (len(axes) < 6)
|
||||
|
||||
# Check list types and count
|
||||
time_count = 0
|
||||
space_count = 0
|
||||
channel_count = 0
|
||||
custom_count = 0
|
||||
for d in axes:
|
||||
if d["type"] == "time":
|
||||
assert d["unit"] in TIME_UNITS
|
||||
time_count += 1
|
||||
elif d["type"] == "space":
|
||||
assert d["unit"] in SPACE_UNITS
|
||||
space_count += 1
|
||||
elif d["type"] == "channel":
|
||||
channel_count += 1
|
||||
else:
|
||||
custom_count += 1
|
||||
|
||||
assert (space_count > 1) and (space_count < 4)
|
||||
assert time_count < 2
|
||||
assert ((channel_count < 2) and (custom_count == 0)) or (
|
||||
(channel_count == 0) and (custom_count < 2)
|
||||
)
|
||||
|
||||
# Check order
|
||||
order_type = [x["type"] for x in axes]
|
||||
if "time" in order_type:
|
||||
# Time must be first, if present
|
||||
assert order_type.index("time") == 0
|
||||
if "channel" in order_type:
|
||||
# Channel must be before all the space axes, if present
|
||||
ci = order_type.index("channel")
|
||||
for i, el in enumerate(order_type):
|
||||
if el == "space":
|
||||
assert i > ci
|
||||
|
||||
# Skip zyx order spec as the naming of axes is not enforcable.
|
||||
|
||||
|
||||
def test_stage_positions_to_translation_transform(dummy_metadata):
|
||||
import random
|
||||
|
||||
pos = [random.random() for _ in range(5)]
|
||||
|
||||
translation = dummy_metadata._stage_positions_to_translation_transform(*pos)
|
||||
|
||||
axes = dummy_metadata._axes
|
||||
|
||||
assert len(translation) == len(axes)
|
||||
|
||||
|
||||
def test_scale_transform(dummy_metadata):
|
||||
scale = dummy_metadata._scale_transform()
|
||||
|
||||
axes = dummy_metadata._axes
|
||||
|
||||
assert len(scale) == len(axes)
|
||||
|
||||
|
||||
def test_coordinate_transformations(dummy_metadata):
|
||||
import random
|
||||
|
||||
pos = [random.random() for _ in range(5)]
|
||||
|
||||
translation = dummy_metadata._stage_positions_to_translation_transform(*pos)
|
||||
scale = dummy_metadata._scale_transform()
|
||||
|
||||
assert len(dummy_metadata._coordinate_transformations(scale)) == 1
|
||||
|
||||
combo = dummy_metadata._coordinate_transformations(scale, translation)
|
||||
assert len(combo) == 2
|
||||
assert combo[0]["type"] == "scale" and combo[1]["type"] == "translation"
|
||||
|
||||
with pytest.raises(UserWarning):
|
||||
dummy_metadata._coordinate_transformations(translation=translation)
|
||||
|
||||
|
||||
def test_multiscale_metadata(dummy_metadata):
|
||||
"""https://ngff.openmicroscopy.org/0.4/#multiscale-md"""
|
||||
import numpy as np
|
||||
import random
|
||||
|
||||
resolutions = np.array([[1, 1, 1], [2, 2, 1], [4, 4, 1], [8, 8, 1]], dtype=int)
|
||||
paths = [f"path{i}" for i in range(resolutions.shape[0])]
|
||||
view = {k: random.random() for k in ["x", "y", "z", "theta", "f"]}
|
||||
|
||||
msd = dummy_metadata.multiscales_dict("test", paths, resolutions, view)
|
||||
|
||||
# Each "multiscales" dictionary MUST contain the field "axes"
|
||||
assert "axes" in msd.keys()
|
||||
|
||||
# Each "multiscales" dictionary MUST contain the field "datasets"
|
||||
assert "datasets" in msd.keys()
|
||||
# Each dictionary in "datasets" MUST contain the field "path",
|
||||
# whose value contains the path to the array for this resolution
|
||||
# relative to the current zarr group. The "path"s MUST be ordered
|
||||
# from largest (i.e. highest resolution) to smallest.
|
||||
# Each "datasets" dictionary MUST have the same number of dimensions
|
||||
# and MUST NOT have more than 5 dimensions.
|
||||
|
||||
# Each "multiscales" dictionary SHOULD contain the field "name"
|
||||
assert "name" in msd.keys()
|
||||
|
||||
# Each "multiscales" dictionary MAY contain the field "coordinateTransformations"
|
||||
assert "coordinateTransformations" in msd.keys()
|
||||
|
||||
# It SHOULD contain the field "version"
|
||||
assert "version" in msd.keys()
|
||||
|
||||
# Each "multiscales" dictionary SHOULD contain the field "type", which gives
|
||||
# the type of downscaling method used to generate the multiscale image pyramid.
|
||||
# It SHOULD contain the field "metadata", which contains a dictionary with
|
||||
# additional information about the downscaling method.
|
||||
Reference in New Issue
Block a user