Skip to content

Commit

Permalink
Return DataArray from NdInfo
Browse files Browse the repository at this point in the history
  • Loading branch information
imagejan committed Oct 8, 2024
1 parent 8a4a295 commit 8d4f4ce
Show file tree
Hide file tree
Showing 15 changed files with 7,991 additions and 41 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# GitHub syntax highlighting
pixi.lock linguist-language=YAML linguist-generated=true
40 changes: 20 additions & 20 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: test

on:
push:
branches: [main, master]
branches: [main]
pull_request:
branches: [main, master]
branches: [main]

concurrency:
group: test-${{ github.head_ref }}
Expand All @@ -16,27 +16,27 @@ env:

jobs:
run:
name: Python ${{ matrix.python-version }} on ${{ startsWith(matrix.os, 'macos-') && 'macOS' || startsWith(matrix.os, 'windows-') && 'Windows' || 'Linux' }}
name: ${{ matrix.pixi-environment }} - ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ['3.10', '3.11', '3.12']

pixi-environment:
- py310-base
- py310-xarray
- py311-base
- py311-xarray
- py312-base
- py312-xarray
steps:
- uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Install Hatch
run: pip install --upgrade hatch

- name: Run static analysis
run: hatch fmt --check

- name: Run tests
run: hatch run cov
- uses: actions/checkout@v4

- name: Set up Pixi
uses: prefix-dev/[email protected]
with:
cache: true
environments: ${{ matrix.pixi-environment }}

- name: Run tests
run: pixi run --environment ${{ matrix.pixi-environment }} test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
dist
__pycache__
.coverage*
# pixi environments
.pixi
*.egg-info
7,804 changes: 7,804 additions & 0 deletions pixi.lock

Large diffs are not rendered by default.

71 changes: 50 additions & 21 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ dependencies = [
"pydantic",
]

[project.optional-dependencies]
xarray = [
"dask",
"numpy<2",
"tifffile",
"xarray",
"zarr",
]

[project.urls]
Documentation = "https://github.com/fmi-faim/metamorph-mda-parser#readme"
Issues = "https://github.com/fmi-faim/metamorph-mda-parser/issues"
Expand All @@ -41,27 +50,6 @@ dependencies = [
"coverage[toml]>=6.5",
"pytest",
]
[tool.hatch.envs.default.scripts]
test = "pytest {args:tests}"
test-cov = "coverage run -m pytest {args:tests}"
cov-report = [
"- coverage combine",
"coverage report",
]
cov = [
"test-cov",
"cov-report",
]

[[tool.hatch.envs.all.matrix]]
python = ["3.10", "3.11", "3.12"]

[tool.hatch.envs.types]
dependencies = [
"mypy>=1.0.0",
]
[tool.hatch.envs.types.scripts]
check = "mypy --install-types --non-interactive {args:src/metamorph_mda_parser tests}"

[tool.coverage.run]
source_pkgs = ["metamorph_mda_parser", "tests"]
Expand Down Expand Up @@ -93,3 +81,44 @@ runtime-evaluated-base-classes = ["pydantic.BaseModel"]
addopts = [
"--import-mode=importlib",
]

[tool.pixi.project]
channels = ["conda-forge"]
platforms = ["win-64", "linux-64", "osx-arm64"]

[tool.pixi.pypi-dependencies]
metamorph-mda-parser = { path = ".", editable = true }

[tool.pixi.environments]
default = { solve-group = "py312" }
xarray = { features = ["xarray"], solve-group = "py312" }
jupyter = { features = ["jupyter", "xarray"], solve-group = "py312" }
py310-base = { features = ["py310", "test"], solve-group = "py310" }
py310-xarray = { features = ["py310", "test", "xarray"], solve-group = "py310" }
py311-base = { features = ["py311", "test"], solve-group = "py311" }
py311-xarray = { features = ["py311", "test", "xarray"], solve-group = "py311" }
py312-base = { features = ["py312", "test"], solve-group = "py312" }
py312-xarray = { features = ["py312", "test", "xarray"], solve-group = "py312" }

[tool.pixi.tasks]

[tool.pixi.feature.jupyter.dependencies]
jupyter = "*"

[tool.pixi.feature.jupyter.tasks]
jupyter = "jupyter lab"

[tool.pixi.feature.test.dependencies]
pytest = "*"

[tool.pixi.feature.test.tasks]
test = "pytest"

[tool.pixi.feature.py310.dependencies]
python = "==3.10"

[tool.pixi.feature.py311.dependencies]
python = "==3.11"

[tool.pixi.feature.py312.dependencies]
python = "==3.12"
15 changes: 15 additions & 0 deletions src/metamorph_mda_parser/nd.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,18 @@ def get_files(self) -> pd.DataFrame:
"time",
],
)

def get_data_array(self, channels=None, positions=None, timepoints=None):
from metamorph_mda_parser.xarray import HAS_XARRAY
if HAS_XARRAY:
from metamorph_mda_parser.xarray import dataarray_from_dataframe
else:
raise ValueError("Dependencies for data array creation not found.")
files = self.get_files()
if channels:
files = files[files["channel"].isin(channels)]
if positions:
files = files[files["position"].isin(positions)]
if timepoints:
files = files[files["time"].isin(timepoints)]
return dataarray_from_dataframe(files, self.wave_do_z)
51 changes: 51 additions & 0 deletions src/metamorph_mda_parser/xarray.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
try:
import dask.array as da
import xarray as xr
from tifffile.tifffile import imread
from pandas import DataFrame
HAS_XARRAY = True
except ImportError:
HAS_XARRAY = False


def dataarray_from_dataframe(df: "DataFrame", channels_3d: list[bool]):
required_columns = ["path", "channel", "position", "time"]
missing_columns = [col for col in required_columns if col not in df.columns]
if missing_columns:
msg = f"Missing columns: {missing_columns}"
raise ValueError(msg)
if df["channel"].max() > len(channels_3d):
msg = f"No dimension information available for certain channels."
raise ValueError(msg)
data_arrays = [_load_file(row, channels_3d) for _, row in df.iterrows()]
return xr.combine_by_coords(data_arrays)["intensity"]


def _load_file(row, channels_3d: list[bool]):
path = row['path']
position = row['position']
time = row['time']
channel = row['channel']

chunks = (-1,) * (3 if channels_3d[channel] else 2)
with imread(path, aszarr=True) as store:
data = da.from_zarr(store, chunks=chunks)

# Determine if the array is 2D or 3D
if data.ndim == 2:
data = data[None, ...] # Add a dummy Z dimension

data_array = xr.DataArray(
data[None, None, None, ...], # Add singleton dimensions for position, time, and channel
dims=['position', 'time', 'channel', 'z', 'y', 'x'],
coords={
'position': [int(position)],
'time': [int(time)],
'channel': [int(channel)],
'z': range(data.shape[0]),
'y': range(data.shape[1]),
'x': range(data.shape[2])
}
)

return xr.Dataset({'intensity': data_array})
21 changes: 21 additions & 0 deletions tests/resources/data/sample_3ch_2pos_mixed-z.nd
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"NDInfoFile", Version 1.0
"Description", File recreated from images.
"StartTime1", 20241001 12:00:00.000
"DoTimelapse", FALSE
"DoStage", TRUE
"NStagePositions", 2
"Stage1", "Position1"
"Stage2", "Position2"
"DoWave", TRUE
"NWavelengths", 3
"WaveName1", "Conf640"
"WaveDoZ1", FALSE
"WaveName2", "Conf561"
"WaveDoZ2", TRUE
"WaveName3", "Conf488"
"WaveDoZ3", TRUE
"DoZSeries", TRUE
"NZSteps", 42
"ZStepSize", 3
"WaveInFileName", TRUE
"EndFile"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
25 changes: 25 additions & 0 deletions tests/test_xarray.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import pytest
from pathlib import Path

from metamorph_mda_parser.nd import NdInfo

pytest.importorskip("xarray")

@pytest.fixture
def sample_3ch_2pos_mixed_z():
return Path("tests/resources/data/sample_3ch_2pos_mixed-z.nd")

def test_data_array_from_nd(sample_3ch_2pos_mixed_z):
ndinfo = NdInfo.from_path(sample_3ch_2pos_mixed_z)
dataarray2d = ndinfo.get_data_array(channels=[0])
dataarray3d = ndinfo.get_data_array(channels=[1, 2])

assert dataarray2d.dims == ("position", "time", "channel", "z", "y", "x")
assert dataarray2d[0, 0, 0, 0, :, :].data.compute().tolist() == [[0,0], [0,128], [0,0]]
assert dataarray2d[1, 0, 0, 0, :, :].data.compute().tolist() == [[0,1], [0,128], [0,0]]

assert dataarray3d.dims == ("position", "time", "channel", "z", "y", "x")
assert dataarray3d[0, 0, 0, 0, :, :].data.compute().tolist() == [[1,0], [0,128], [0,0]]
assert dataarray3d[0, 0, 1, 0, :, :].data.compute().tolist() == [[2,0], [0,128], [0,0]]
assert dataarray3d[1, 0, 0, 0, :, :].data.compute().tolist() == [[1,1], [0,128], [0,0]]
assert dataarray3d[1, 0, 1, 0, :, :].data.compute().tolist() == [[2,1], [0,128], [0,0]]

0 comments on commit 8d4f4ce

Please sign in to comment.