Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c7fc56d
Initial plan
Copilot Sep 9, 2025
e4ce70e
Replace aicsimageio with bioio in imports and class references
Copilot Sep 9, 2025
be54c3b
Add default physical pixel sizes to BioImage obj
jni Nov 14, 2025
ebfc88c
typo: filw -> file
jni Nov 14, 2025
df1990a
Add physical pixel size to BioImage in create_data
jni Nov 14, 2025
d31ab66
Add bioio plugin dependencies
jni Nov 14, 2025
8998f42
Use separate resolutionunit for tifffile write
jni Nov 14, 2025
337a699
Use bioio-tifffile to read raw.tif
jni Nov 15, 2025
8a616e5
Fix patch to patch bioio instead of aicsimageio
jni Nov 15, 2025
692e429
fix dependency issues
pr4deepr Nov 26, 2025
f0c6669
use bioio for reading psf with czi extension
pr4deepr Nov 26, 2025
dc3e6ee
update dependencies
pr4deepr Nov 26, 2025
f453a3e
update fawltydeps
pr4deepr Nov 26, 2025
077e847
Update test matrix to 3.10 3.13
jni Dec 2, 2025
7aad4d1
Remove white space
jni Dec 2, 2025
d865bd9
fix issues with workflow_path when running tests locally in windows
pr4deepr Dec 2, 2025
7920379
remove bioio_bioformats
pr4deepr Dec 3, 2025
f0d6bfa
testing if modifying install order fixes tiff reading issues
pr4deepr Dec 3, 2025
d7d77cc
enable bioformats in fawltydeps
pr4deepr Dec 3, 2025
cd01a8d
tifffile to fawltydeps
pr4deepr Dec 3, 2025
4179fb8
fix tifffile in pyproject
pr4deepr Dec 3, 2025
76c9ed0
remove bioformats due to instability
pr4deepr Dec 3, 2025
b825398
add omet-tiff for tif writing
pr4deepr Dec 3, 2025
154b592
add tifffile constraints back
pr4deepr Dec 3, 2025
e82bbb6
specify min bioio-ome tif version
pr4deepr Dec 3, 2025
f03f86f
add to fawltydeps
pr4deepr Dec 3, 2025
21f17f5
loosen tifffile version restriction
pr4deepr Dec 3, 2025
556ad20
add testing for 3.11 3.12
pr4deepr Dec 3, 2025
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
2 changes: 1 addition & 1 deletion .github/lint/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ runs:
using: "composite"
steps:
- run: |
pip install fawltydeps==0.12.0
pip install fawltydeps
fawltydeps --check --detailed --verbose
shell: bash -l {0}
working-directory: ${{ inputs.directory }}
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/test_and_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8","3.9","3.10"] #not compatible with py 3.10 and 3.12 yet
python-version: ["3.10", "3.11", "3.12"]

env:
DISPLAY: ":99.0"
Expand All @@ -46,7 +46,7 @@ jobs:
with:
# Install a specific version of uv.
version: "0.6.17"

- name: Install core
timeout-minutes: 10
run: |
Expand Down Expand Up @@ -94,9 +94,9 @@ jobs:
# github secrets (see readme for details)
needs: [test]
runs-on: ubuntu-latest
environment:
environment:
name: github-pages
permissions:
permissions:
id-token: write
pages: write
contents: write
Expand Down Expand Up @@ -136,7 +136,7 @@ jobs:
uses: actions/deploy-pages@v4

- uses: ncipollo/release-action@v1.18.0
name: Create GitHub release
name: Create GitHub release
if: contains(github.ref, 'tags')
with:
skipIfReleaseExists: true
Expand Down
16 changes: 7 additions & 9 deletions core/lls_core/deconvolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
import logging
import importlib.util
from typing import Collection, Iterable,Union,Literal, Optional, TYPE_CHECKING
from aicsimageio.aics_image import AICSImage
from bioio import BioImage
import bioio_czi
from skimage.io import imread
from aicspylibczi import CziFile
from numpy.typing import NDArray
import os
import numpy as np
Expand Down Expand Up @@ -62,12 +62,10 @@ def read_psf(psf_paths: Collection[Path],
for psf in psf_paths:
if psf.exists() and psf.is_file():
if psf.suffix == ".czi":
psf_czi = CziFile(psf.__str__())
psf_aics = psf_czi.read_image()
psf_czi = BioImage(psf.__str__(), reader=bioio_czi.Reader)
psf_aics = psf_czi.data
# make sure shape is 3D
psf_aics = psf_aics[0][0] # np.expand_dims(psf_aics[0],axis=0)
# if len(psf_aics[0])>=1:
#psf_channels = len(psf_aics[0])
psf_aics = psf_aics[0][0]
assert len(
psf_aics.shape) == 3, f"PSF should be a 3D image (shape of 3), but got {psf_aics.shape}"
# pad psf to multiple of 16 for decon
Expand All @@ -79,8 +77,8 @@ def read_psf(psf_paths: Collection[Path],
if len(psf_aics_data.shape) != 3:
raise ValueError(f"PSF should be a 3D image (shape of 3), but got {psf_aics.shape}")
else:
#Use AICSImageIO
psf_aics = AICSImage(str(psf))
#Use BioIO
psf_aics = BioImage(str(psf))
psf_aics_data = psf_aics.data[0][0]
psf_aics_data = pad_image_nearest_multiple(
img=psf_aics_data, nearest_multiple=16)
Expand Down
16 changes: 8 additions & 8 deletions core/lls_core/models/deskew.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import numpy as np

if TYPE_CHECKING:
from aicsimageio.types import PhysicalPixelSizes
from bioio import PhysicalPixelSizes

class DefinedPixelSizes(FieldAccessModel):
"""
Expand Down Expand Up @@ -161,7 +161,7 @@ def convert_skew(cls, v: Any):

@validator("physical_pixel_sizes", pre=True, always=True)
def convert_pixels(cls, v: Any, values: dict[Any, Any]):
from aicsimageio.types import PhysicalPixelSizes
from bioio import PhysicalPixelSizes
if isinstance(v, PhysicalPixelSizes):
v = DefinedPixelSizes.from_physical(v)
elif isinstance(v, tuple) and len(v) == 3:
Expand All @@ -176,15 +176,15 @@ def convert_pixels(cls, v: Any, values: dict[Any, Any]):

@root_validator(pre=True)
def read_image(cls, values: dict):
from aicsimageio import AICSImage
from bioio import BioImage
from os import fspath

img = values["input_image"]

aics: AICSImage | None = None
aics: BioImage | None = None
if is_pathlike(img):
aics = AICSImage(fspath(img))
elif isinstance(img, AICSImage):
aics = BioImage(fspath(img))
elif isinstance(img, BioImage):
aics = img
elif isinstance(img, DataArray):
if set(img.dims) >= {"Z", "Y", "X"}:
Expand All @@ -198,9 +198,9 @@ def read_image(cls, values: dict):
else:
raise ValueError("Only 3D numpy arrays are currently supported. If you have a different shape, please use a DataArray and name your dimensions C, T, Z, Y and/or Z.")
else:
raise ValueError("Value of input_image was neither a path, an AICSImage, or array-like.")
raise ValueError("Value of input_image was neither a path, a BioImage, or array-like.")

# If the image was convertible to AICSImage, we should use the metadata from there
# If the image was convertible to BioImage, we should use the metadata from there
if aics:
values["input_image"] = aics.xarray_dask_data
# Take pixel sizes from the image metadata, but only if they're defined
Expand Down
34 changes: 17 additions & 17 deletions core/lls_core/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import numpy as np
from numpy.typing import NDArray
from xarray import DataArray
from aicsimageio import AICSImage
from bioio import BioImage
from os import fspath, PathLike as OriginalPathLike

# This is a superset of os.PathLike
Expand All @@ -19,20 +19,20 @@ def is_pathlike(x: Any) -> TypeGuard[PathLike]:
def is_arraylike(arr: Any) -> TypeGuard[ArrayLike]:
return isinstance(arr, (DaskArray, np.ndarray, OCLArray, DataArray))

ImageLike: TypeAlias = Union[PathLike, AICSImage, ArrayLike]
def image_like_to_image(img: ImageLike) -> DataArray:
"""
Converts an image in one of many formats to a DataArray
"""
# First try treating it as a path
try:
img = AICSImage(fspath(img))
except TypeError:
pass
if isinstance(img, AICSImage):
return img.xarray_dask_data
else:
for required_key in ("shape", "dtype", "ndim", "__array__", "__array_ufunc__"):
if not hasattr(img, required_key):
raise ValueError(f"The provided object {img} is not array like!")
ImageLike: TypeAlias = Union[PathLike, BioImage, ArrayLike]
def image_like_to_image(img: ImageLike) -> DataArray:
"""
Converts an image in one of many formats to a DataArray
"""
# First try treating it as a path
try:
img = BioImage(fspath(img))
except TypeError:
pass
if isinstance(img, BioImage):
return img.xarray_dask_data
else:
for required_key in ("shape", "dtype", "ndim", "__array__", "__array_ufunc__"):
if not hasattr(img, required_key):
raise ValueError(f"The provided object {img} is not array like!")
return DataArray(img)
3 changes: 2 additions & 1 deletion core/lls_core/writers.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ def flush(self):
str(path),
data = images_array,
bigtiff=True,
resolution=(1./self.lattice.dx, 1./self.lattice.dy, "MICROMETER"),
resolution=(1./self.lattice.dx, 1./self.lattice.dy),
resolutionunit="MICROMETER",
metadata={'spacing': self.lattice.new_dz, 'unit': 'um', 'axes': 'TZCYX'},
imagej=True
)
Expand Down
16 changes: 10 additions & 6 deletions core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ classifiers = [
]
requires-python = ">=3.8"
dependencies = [
"aicsimageio>=4.6.3",
# Earlier versions don't have Python 3.11 binaries, and the sdist
# is misconfigured: https://github.com/AllenCellModeling/aicspylibczi/issues/90
"aicspylibczi>=3.1.1",
"bioio>=3.0.0",
"bioio-czi>=2.4.0",
"bioio-tifffile==1.3.0",
"bioio-ome-tiff>=1.4.0",
"click",
"dask",
"dask[distributed]",
Expand All @@ -61,7 +61,7 @@ dependencies = [
"resource-backed-dask-array>=0.1.0",
"scikit-image",
"StrEnum",
"tifffile>=2023.3.15,<2025.2.18", #>=2022.8.12
"tifffile>=2023.3.15",#<2025.2.18", #>=2022.8.12
"toolz",
"tqdm",
"typer<0.17.0",
Expand Down Expand Up @@ -158,7 +158,11 @@ ignore_unused = [

# Used for the deployment, but never imported
"build",
"twine"
"twine",

#bioio packages are needed but not directly imported
"bioio-czi",
"bioio-ome-tiff",
]
output_format = "human_detailed"

Expand Down
14 changes: 8 additions & 6 deletions core/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import Callable, List
import pytest
from aicsimageio.aics_image import AICSImage
from bioio import BioImage
from npy2bdv import BdvEditor
import numpy as np
from pathlib import Path
Expand All @@ -14,8 +14,9 @@ def create_image(path: Path):
# Create a zero array of shape 5x5x5 with a value of 10 at (2,4,2)
raw = np.zeros((5, 5, 5))
raw[2, 4, 2] = 10
# Save image as a tif filw in home directory
AICSImage(raw).save(path)
# Save image as a tif file in home directory
b = BioImage(raw, physical_pixel_sizes={ax: 1. for ax in 'ZYX'})
b.save(path)
assert path.exists()


Expand All @@ -27,8 +28,9 @@ def create_data(dir: Path) -> Path:
# Create a zero array of shape 5x5x5 with a value of 10 at (2,4,2)
raw = np.zeros((5, 5, 5))
raw[2, 4, 2] = 10
# Save image as a tif filw in home directory
AICSImage(raw).save(input_file)
# Save image as a tif file in home directory
b = BioImage(raw, physical_pixel_sizes={ax: 1. for ax in 'ZYX'})
b.save(input_file)
assert input_file.exists()

config: dict[str, str] = {
Expand All @@ -47,7 +49,7 @@ def assert_tiff(output_dir: Path):
results = list(output_dir.glob("*.tif"))
assert len(results) > 0
for result in results:
AICSImage(result).get_image_data()
BioImage(result).get_image_data()

def assert_h5(output_dir: Path):
"""Checks that a valid H5 was generated"""
Expand Down
42 changes: 26 additions & 16 deletions core/tests/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from pathlib import Path
from napari_workflows import Workflow
from pytest import FixtureRequest
from bioio_tifffile import reader as tiffreader
from bioio import BioImage


from .params import parameterized
Expand Down Expand Up @@ -51,22 +53,30 @@ def test_save(minimal_image_path: str, args: dict):


def test_process_deconv_crop():
for slice in (
LatticeData.parse_obj(
{
"input_image": root / "raw.tif",
"deconvolution": {
"psf": [root / "psf.tif"],
},
"crop": CropParams(
roi_list=[[[0, 0], [0, 110], [95, 0], [95, 110]]]
),
}
)
.process()
.slices
):
assert slice.data.ndim == 3
with tempfile.TemporaryDirectory() as tempdir:
for slice in (
LatticeData.parse_obj(
{
# use BioImage rather than just Path to ensure tifffile
# is used instead of bioformats, which prevents dask use
# due to a bug:
# https://github.com/bioio-devs/bioio-bioformats/issues/40
"input_image": BioImage(
root / "raw.tif", reader=tiffreader.Reader
),
"deconvolution": {
"psf": [root / "psf.tif"],
},
"crop": CropParams(
roi_list=[[[0, 0], [0, 110], [95, 0], [95, 110]]]
),
"save_dir": tempdir,
}
)
.process()
.slices
):
assert slice.data.ndim == 3


def test_process_time_range(multi_channel_time: Path):
Expand Down
4 changes: 2 additions & 2 deletions core/tests/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def test_allow_trailing_slash():

def test_infer_czi_pixel_sizes(rbc_tiny: Path):
mock = PropertyMock()
with patch("aicsimageio.AICSImage.physical_pixel_sizes", new=mock):
with patch("bioio.BioImage.physical_pixel_sizes", new=mock):
DeskewParams(input_image=rbc_tiny)
# The AICSImage should be queried for the pixel sizes
# The BioImage should be queried for the pixel sizes
assert mock.called
11 changes: 8 additions & 3 deletions core/tests/test_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,11 @@ def test_table_workflow(lls7_t1_ch1: Path, table_workflow: Workflow):
def test_argument_order(rbc_tiny: Path):
# Tests that only the first unfilled argument is passed an array
with tempfile.TemporaryDirectory() as tmpdir:
# Get the path relative to this test file
workflow_path = Path(__file__).parent / "workflows" / "argument_order" / "test_workflow.yml"
params = LatticeData(
input_image = rbc_tiny,
workflow = "core/tests/workflows/argument_order/test_workflow.yml",
workflow = str(workflow_path),
save_dir = tmpdir
)
for output in params.process_workflow().process():
Expand All @@ -109,9 +111,11 @@ def test_sum_preview(rbc_tiny: Path):
import numpy as np
# Tests that we can sum the preview result. This is required for the plugin
with tempfile.TemporaryDirectory() as tmpdir:
# Get the path relative to this test file
workflow_path = Path(__file__).parent / "workflows" / "binarisation" / "workflow.yml"
params = LatticeData(
input_image = rbc_tiny,
workflow = "core/tests/workflows/binarisation/workflow.yml",
workflow = str(workflow_path),
save_dir = tmpdir
)
previews = list(params.process_workflow().roi_previews())
Expand All @@ -122,9 +126,10 @@ def test_crop_workflow(lls7_t1_ch1: Path):
# Tests that crop workflows only process each ROI lazily

with tempfile.TemporaryDirectory() as tmpdir:
workflow_path = Path(__file__).parent / "workflows" / "binarisation" / "workflow.yml"
params = LatticeData(
input_image = lls7_t1_ch1,
workflow = "core/tests/workflows/binarisation/workflow.yml",
workflow = str(workflow_path),
save_dir = tmpdir,
crop=CropParams(
roi_list=[
Expand Down
4 changes: 2 additions & 2 deletions core/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typer.testing import CliRunner
from lls_core.cmds.__main__ import app
import npy2bdv
from aicsimageio import AICSImage
from bioio import BioImage

def invoke(args: Sequence[str]):
CliRunner().invoke(app, args, catch_exceptions=False)
Expand All @@ -13,5 +13,5 @@ def valid_image_path(path: Path) -> bool:
npy2bdv.npy2bdv.BdvEditor(str(path)).read_view()
return True
else:
AICSImage(path).get_image_data()
BioImage(path).get_image_data()
return True
Loading
Loading