Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Tests

on:
push:
branches: [main, v2.0]
paths-ignore:
- "docs/**"
- "*.md"
- "LICENSE"
pull_request:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
test:
name: locked py${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12", "3.13", "3.14"]

steps:
- uses: actions/checkout@v7

- name: Install uv
uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0
with:
version: "0.12.4"
python-version: ${{ matrix.python-version }}

- name: Check lockfile
run: uv lock --check

- name: Install test environment
run: uv sync --locked --extra test --extra dev

- name: Verify Python version
run: >
uv run --no-sync python -c
"import sys; v = sys.version_info;
expected = tuple(map(int, '${{ matrix.python-version }}'.split('.')));
assert (v.major, v.minor) == expected, sys.version;
assert v.releaselevel == 'final', sys.version;
print(sys.version)"

- name: Run tests
run: uv run --no-sync pytest -q -p no:warnings --cov --cov-branch --cov-report=term-missing tests
8 changes: 8 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ Issues = "https://github.com/KyleHarrington/copick-utils/issues"
Documentation = "https://github.com/KyleHarrington/copick-utils#readme"

[project.optional-dependencies]
test = [
"pytest>=8.4.1",
"pytest-cov>=6.2.1",
]
dev = [
"black>=25.1.0",
"hatchling>=1.25.0",
Expand Down Expand Up @@ -165,3 +169,7 @@ exclude_lines = [
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]

[tool.pytest.ini_options]
minversion = "8.0"
testpaths = ["tests"]
97 changes: 97 additions & 0 deletions tests/test_zarr_migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""Executable baseline for the copick 2 / Zarr 3 migration.

This module records the observable storage behavior present before the
migration so the migrated implementation can be checked against it.
"""

import hashlib

import numpy as np
import pytest
import zarr
from copick_utils.features.skimage import compute_skimage_features


def _memory_store():
return zarr.storage.MemoryStore()


def _tomogram_store(path="0"):
store = _memory_store()
group = zarr.group(store=store)
data = ((np.indices((5, 6, 7)) * np.array([11, 5, 2])[:, None, None, None]).sum(0) % 17).astype(np.float32)
group.create_dataset(path, data=data, chunks=(3, 4, 5))
group.attrs["multiscales"] = [{"datasets": [{"path": path}]}]
return store, data


class _Features:
def __init__(self):
self.store = _memory_store()

def zarr(self):
return self.store


class _Tomogram:
def __init__(self, store):
self.store = store
self.features = None

def zarr(self):
return self.store

def new_features(self, feature_type):
assert feature_type == "golden"
self.features = _Features()
return self.features


@pytest.mark.parametrize("path", ["0", "s0"])
def test_local_ome_zarr_fixture_declares_its_level_path(path):
store, expected = _tomogram_store(path)
group = zarr.open_group(store=store, mode="r")

declared_path = group.attrs["multiscales"][0]["datasets"][0]["path"]
np.testing.assert_array_equal(group[declared_path][:], expected)


def test_pre_migration_feature_result_is_frozen():
"""Protect the existing chunk subdivision and boundary behavior."""
store, _ = _tomogram_store()
features = compute_skimage_features(
_Tomogram(store),
"golden",
None,
sigma_min=0.5,
sigma_max=0.5,
feature_chunk_size=(3, 4, 5),
)
result = zarr.open(features.zarr(), mode="r")[:]

assert result.shape == (5, 5, 6, 7)
assert result.dtype == np.float32
rounded_digest = hashlib.sha256(np.round(result, 5).tobytes()).hexdigest()
assert rounded_digest == "8364181d58811d79fe86847872316a97370ed2737aeee6411a38753124305312"


def test_pre_migration_feature_store_documents_reader_incompatibility():
store, _ = _tomogram_store()
features = compute_skimage_features(
_Tomogram(store),
"golden",
None,
intensity=True,
edges=False,
texture=False,
sigma_min=0.5,
sigma_max=0.5,
feature_chunk_size=(3, 4, 5),
)

# The old implementation writes an array at the store root. CopickFeatures
# expects an OME group and therefore cannot resolve a metadata-defined level.
root = zarr.open(features.zarr(), mode="r")
assert isinstance(root, zarr.Array)
with pytest.raises((AttributeError, TypeError, zarr.errors.ContainsArrayError)):
zarr.open_group(store=features.zarr(), mode="r")
Loading
Loading