Skip to content

Commit

Permalink
Merge pull request #90 from fmi-faim/imagexpress-single-channel
Browse files Browse the repository at this point in the history
ImageXpress: support single-channel acquisitions
  • Loading branch information
tibuch authored Feb 16, 2024
2 parents f94232a + 7932365 commit c3e48a1
Show file tree
Hide file tree
Showing 29 changed files with 59 additions and 9 deletions.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
4 changes: 3 additions & 1 deletion src/faim_hcs/hcs/imagexpress/ImageXpressPlateAcquisition.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def _list_and_match_files(
root_dir: Union[Path, str],
root_re: re.Pattern,
filename_re: re.Pattern,
) -> list[[dict[dict, str]]]:
) -> list[list[dict[dict, str]]]:
files = []
for root, _, filenames in os.walk(root_dir):
m_root = root_re.fullmatch(root)
Expand All @@ -66,6 +66,8 @@ def _list_and_match_files(
if m_filename:
row = m_root.groupdict()
row |= m_filename.groupdict()
if "channel" not in row or row["channel"] is None:
row["channel"] = "w1"
row["path"] = str(Path(root).joinpath(f))
files.append(row)
return files
Expand Down
2 changes: 1 addition & 1 deletion src/faim_hcs/hcs/imagexpress/MixedAcquisition.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,5 @@ def _get_root_re(self) -> re.Pattern:

def _get_filename_re(self) -> re.Pattern:
return re.compile(
r"(?P<name>.*)_(?P<well>[A-Z]+\d{2})_(?P<field>s\d+)_(?P<channel>w[1-9]{1})(?!_thumb)(?P<md_id>.*)(?P<ext>.tif)"
r"(?P<name>.*)_(?P<well>[A-Z]+\d{2})_?(?P<field>s\d+)?_?(?P<channel>w[1-9]{1})?(?!_thumb)(?P<md_id>[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})(?P<ext>.tif)"
)
2 changes: 1 addition & 1 deletion src/faim_hcs/hcs/imagexpress/SinglePlaneAcquisition.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def _get_root_re(self) -> re.Pattern:

def _get_filename_re(self) -> re.Pattern:
return re.compile(
r"(?P<name>.*)_(?P<well>[A-Z]+\d{2})_(?P<field>s\d+)_(?P<channel>w[1-9]{1})(?!_thumb)(?P<md_id>.*)(?P<ext>.tif)"
r"(?P<name>.*)_(?P<well>[A-Z]+\d{2})_?(?P<field>s\d+)?_?(?P<channel>w[1-9]{1})?(?!_thumb)(?P<md_id>[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})(?P<ext>.tif)"
)

def _get_z_spacing(self) -> Optional[float]:
Expand Down
6 changes: 3 additions & 3 deletions src/faim_hcs/hcs/imagexpress/StackAcquisition.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,15 @@ def _get_root_re(self) -> re.Pattern:

def _get_filename_re(self) -> re.Pattern:
return re.compile(
r"(?P<name>.*)_(?P<well>[A-Z]+\d{2})_(?P<field>s\d+)_(?P<channel>w[1-9]{1})(?!_thumb)(?P<md_id>.*)(?P<ext>.tif)"
r"(?P<name>.*)_(?P<well>[A-Z]+\d{2})_?(?P<field>s\d+)?_?(?P<channel>w[1-9]{1})?(?!_thumb)(?P<md_id>[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})(?P<ext>.tif)"
)

def _get_z_spacing(self) -> Optional[float]:
return self._z_spacing

def _compute_z_spacing(self, files: pd.DataFrame) -> Optional[float]:
assert "z" in files.columns, "No z column in files DataFrame."
channel_with_stack = files[files["z"] == "2"]["channel"].unique()[0]
channel_with_stack = np.sort(files[files["z"] == "2"]["channel"].unique())[0]
subset = files[files["channel"] == channel_with_stack]
subset = subset[subset["well"] == subset["well"].unique()[0]]
subset = subset[subset["field"] == subset["field"].unique()[0]]
Expand All @@ -87,7 +87,7 @@ def _compute_z_spacing(self, files: pd.DataFrame) -> Optional[float]:
z_position = metadata["stage-position-z"]
plane_positions.append(z_position)

plane_positions = sorted(plane_positions)
plane_positions = np.array(sorted(plane_positions), dtype=np.float32)

precision = -Decimal(str(plane_positions[0])).as_tuple().exponent
z_step = np.round(np.mean(np.diff(plane_positions)), decimals=precision)
Expand Down
54 changes: 51 additions & 3 deletions tests/hcs/imagexpress/test_ImageXpress.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def test_stack_acquistion(stack_acquisition: PlateAcquisition):
assert ch.spatial_calibration_x == 1.3668
assert ch.spatial_calibration_y == 1.3668
assert ch.wavelength == 536
assert_almost_equal(ch.z_spacing, 5.0, decimal=4)
assert_almost_equal(ch.z_spacing, 5.0, decimal=2)

ch = channels[1]
assert ch.channel_index == 1
Expand All @@ -132,7 +132,7 @@ def test_stack_acquistion(stack_acquisition: PlateAcquisition):
assert ch.spatial_calibration_x == 1.3668
assert ch.spatial_calibration_y == 1.3668
assert ch.wavelength == 536
assert_almost_equal(ch.z_spacing, 5.0, decimal=4)
assert_almost_equal(ch.z_spacing, 5.0, decimal=2)

ch = channels[3]
assert ch.channel_index == 3
Expand All @@ -145,7 +145,7 @@ def test_stack_acquistion(stack_acquisition: PlateAcquisition):
assert ch.spatial_calibration_x == 1.3668
assert ch.spatial_calibration_y == 1.3668
assert ch.wavelength == 536
assert_almost_equal(ch.z_spacing, 5.0, decimal=4)
assert_almost_equal(ch.z_spacing, 5.0, decimal=2)

for well in stack_acquisition.get_well_acquisitions():
assert isinstance(well, WellAcquisition)
Expand Down Expand Up @@ -266,3 +266,51 @@ def test_raise_not_implemented_error(dummy_plate):

with pytest.raises(NotImplementedError):
dummy_plate._get_z_spacing()


@pytest.fixture
def acquisition_dir_single_channel():
return Path(__file__).parent.parent.parent.parent / "resources" / "SingleChannel"


@pytest.fixture
def single_channel_acquisition(acquisition_dir_single_channel):
return SinglePlaneAcquisition(
acquisition_dir_single_channel, alignment=TileAlignmentOptions.GRID
)


def test_single_channel_acquistion(single_channel_acquisition: PlateAcquisition):
wells = single_channel_acquisition.get_well_acquisitions()

assert wells is not None
assert len(wells) == 2
# MIPs: 1 well has 2 fields * 1 channels = 2 files
assert len(wells[0]._files) == 2
assert len(wells[0]._files) == 2

channels = single_channel_acquisition.get_channel_metadata()
assert len(channels) == 1
ch = channels[0]
assert ch.channel_index == 0
assert ch.channel_name == "Maximum-Projection_FITC_05"
assert ch.display_color == "73ff00"
assert ch.exposure_time == 15.0
assert ch.exposure_time_unit == "ms"
assert ch.objective == "20X Plan Apo Lambda"
assert ch.spatial_calibration_units == "um"
assert ch.spatial_calibration_x == 1.3668
assert ch.spatial_calibration_y == 1.3668
assert ch.wavelength == 536
assert ch.z_spacing is None

for well in single_channel_acquisition.get_well_acquisitions():
assert isinstance(well, WellAcquisition)
assert len(well.get_tiles()) == 2
for tile in well.get_tiles():
assert tile.position.time == 0
assert tile.position.channel in [0]
assert tile.position.z == 0
assert tile.position.y in [0]
assert tile.position.x in [0, 512]
assert tile.shape == (512, 512)

0 comments on commit c3e48a1

Please sign in to comment.