- base & view model for imread

- transforms bugfix
- implement (nan)var and (nan)std
- add tests
This commit is contained in:
Wim Pomp
2023-08-18 18:10:42 +02:00
parent bdd7a5399c
commit ff6c1aa8a8
13 changed files with 929 additions and 779 deletions

22
.github/workflows/pytest.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: PyTest
on: [workflow_call, push, pull_request]
jobs:
pytest:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
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 dependencies
run: |
pip install pytest
- name: Test with pytest
run: pytest

View File

@@ -1,3 +1,5 @@
[![Pytest](https://github.com/wimpomp/ndbioimage/actions/workflows/pytest.yml/badge.svg)](https://github.com/wimpomp/ndbioimage/actions/workflows/pytest.yml)
# ndbioimage - Work in progress # ndbioimage - Work in progress
Exposes (bio) images as a numpy ndarray-like-object, but without loading the whole Exposes (bio) images as a numpy ndarray-like-object, but without loading the whole
@@ -55,9 +57,9 @@ with Imread('image_file.tif', axes='cztxy') as im:
``` ```
## Adding more formats ## Adding more formats
Readers for image formats subclass Imread. When an image reader is imported, Imread will Readers for image formats subclass AbstractReader. When an image reader is imported, Imread will
automatically recognize it and use it to open the appropriate file format. Image readers automatically recognize it and use it to open the appropriate file format. Image readers
subclass Imread and are required to implement the following methods: are required to implement the following methods:
- staticmethod _can_open(path): return True if path can be opened by this reader - staticmethod _can_open(path): return True if path can be opened by this reader
- property ome: reads metadata from file and adds them to an OME object imported - property ome: reads metadata from file and adds them to an OME object imported
@@ -65,7 +67,7 @@ from the ome-types library
- \_\_frame__(self, c, z, t): return the frame at channel=c, z-slice=z, time=t from the file - \_\_frame__(self, c, z, t): return the frame at channel=c, z-slice=z, time=t from the file
Optional methods: Optional methods:
- open(self): maybe open some file - open(self): maybe open some file handle
- close(self): close any file handles - close(self): close any file handles
Optional fields: Optional fields:

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ import numpy as np
from abc import ABC from abc import ABC
from multiprocessing import queues from multiprocessing import queues
from traceback import print_exc from traceback import print_exc
from .. import Imread, JVM from .. import AbstractReader, JVM
jars = {'bioformats_package.jar': jars = {'bioformats_package.jar':
@@ -174,7 +174,7 @@ def can_open(path):
jvm.kill_vm() jvm.kill_vm()
class Reader(Imread, ABC): class Reader(AbstractReader, ABC):
""" This class is used as a last resort, when we don't have another way to open the file. We don't like it """ This class is used as a last resort, when we don't have another way to open the file. We don't like it
because it requires the java vm. because it requires the java vm.
""" """

View File

@@ -7,10 +7,10 @@ from abc import ABC
from functools import cached_property from functools import cached_property
from itertools import product from itertools import product
from pathlib import Path from pathlib import Path
from .. import Imread from .. import AbstractReader
class Reader(Imread, ABC): class Reader(AbstractReader, ABC):
priority = 0 priority = 0
do_not_pickle = 'reader', 'filedict' do_not_pickle = 'reader', 'filedict'
@@ -69,7 +69,7 @@ class Reader(Imread, ABC):
instrument = information.find("Instrument") instrument = information.find("Instrument")
for _ in instrument.find("Microscopes"): for _ in instrument.find("Microscopes"):
ome.instruments.append(model.Instrument()) ome.instruments.append(model.Instrument(id='Instrument:0'))
for detector in instrument.find("Detectors"): for detector in instrument.find("Detectors"):
try: try:
@@ -414,7 +414,7 @@ class Reader(Imread, ABC):
return ome return ome
def __frame__(self, c=0, z=0, t=0): def __frame__(self, c=0, z=0, t=0):
f = np.zeros(self.file_shape[:2], self.dtype) f = np.zeros(self.base.shape['xy'], self.dtype)
directory_entries = self.filedict[c, z, t] directory_entries = self.filedict[c, z, t]
x_min = min([f.start[f.axes.index('X')] for f in directory_entries]) x_min = min([f.start[f.axes.index('X')] for f in directory_entries])
y_min = min([f.start[f.axes.index('Y')] for f in directory_entries]) y_min = min([f.start[f.axes.index('Y')] for f in directory_entries])

View File

@@ -7,10 +7,10 @@ from pathlib import Path
from struct import unpack from struct import unpack
from warnings import warn from warnings import warn
import numpy as np import numpy as np
from .. import Imread from .. import AbstractReader
class Reader(Imread, ABC): class Reader(AbstractReader, ABC):
""" Can read some tif files written with Fiji which are broken because Fiji didn't finish writing. """ """ Can read some tif files written with Fiji which are broken because Fiji didn't finish writing. """
priority = 90 priority = 90
do_not_pickle = 'reader' do_not_pickle = 'reader'

View File

@@ -2,11 +2,11 @@ import numpy as np
from ome_types import model from ome_types import model
from functools import cached_property from functools import cached_property
from abc import ABC from abc import ABC
from .. import Imread from .. import AbstractReader
from itertools import product from itertools import product
class Reader(Imread, ABC): class Reader(AbstractReader, ABC):
priority = 20 priority = 20
@staticmethod @staticmethod

View File

@@ -8,7 +8,7 @@ from ome_types._base_type import quantity_property
from itertools import product from itertools import product
from datetime import datetime from datetime import datetime
from abc import ABC from abc import ABC
from .. import Imread from .. import AbstractReader
def lazy_property(function, field, *arg_fields): def lazy_property(function, field, *arg_fields):
@@ -36,7 +36,7 @@ class Plane(model.Plane):
return float((datetime.strptime(info["Time"], "%Y-%m-%d %H:%M:%S %z") - t0).seconds) return float((datetime.strptime(info["Time"], "%Y-%m-%d %H:%M:%S %z") - t0).seconds)
class Reader(Imread, ABC): class Reader(AbstractReader, ABC):
priority = 10 priority = 10
@staticmethod @staticmethod

View File

@@ -6,10 +6,10 @@ from functools import cached_property
from ome_types import model from ome_types import model
from pathlib import Path from pathlib import Path
from itertools import product from itertools import product
from .. import Imread from .. import AbstractReader
class Reader(Imread, ABC): class Reader(AbstractReader, ABC):
priority = 0 priority = 0
do_not_pickle = 'reader' do_not_pickle = 'reader'
@@ -67,6 +67,6 @@ class Reader(Imread, ABC):
def __frame__(self, c, z, t): def __frame__(self, c, z, t):
if self.p_ndim == 3: if self.p_ndim == 3:
return np.transpose(self.reader.asarray(z + t * self.file_shape[3]), self.p_transpose)[c] return np.transpose(self.reader.asarray(z + t * self.base.shape['z']), self.p_transpose)[c]
else: else:
return self.reader.asarray(c + z * self.file_shape[2] + t * self.file_shape[2] * self.file_shape[3]) return self.reader.asarray(c + z * self.base.shape['c'] + t * self.base.shape['c'] * self.base.shape['z'])

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "ndbioimage" name = "ndbioimage"
version = "2023.7.4" version = "2023.8.0"
description = "Bio image reading, metadata and some affine registration." description = "Bio image reading, metadata and some affine registration."
authors = ["W. Pomp <w.pomp@nki.nl>"] authors = ["W. Pomp <w.pomp@nki.nl>"]
license = "GPLv3" license = "GPLv3"
@@ -22,7 +22,7 @@ pint = "*"
tqdm = "*" tqdm = "*"
lxml = "*" lxml = "*"
pyyaml = "*" pyyaml = "*"
parfor = "*" parfor = ">=2023.8.2"
JPype1 = "*" JPype1 = "*"
SimpleITK-SimpleElastix = "*" SimpleITK-SimpleElastix = "*"
pytest = { version = "*", optional = true } pytest = { version = "*", optional = true }

View File

@@ -1,12 +1,25 @@
import pickle
import pytest import pytest
from pathlib import Path from pathlib import Path
from multiprocessing import active_children
from ndbioimage import Imread, ReaderNotFoundError from ndbioimage import Imread, ReaderNotFoundError
@pytest.mark.parametrize("file", (Path(__file__).parent / 'files').iterdir()) @pytest.mark.parametrize('file', (Path(__file__).parent / 'files').iterdir())
def test_open(file): def test_open(file):
try: try:
with Imread(file) as im: with Imread(file) as im:
print(im[dict(c=0, z=0, t=0)].mean()) mean = im[dict(c=0, z=0, t=0)].mean()
b = pickle.dumps(im)
jm = pickle.loads(b)
assert jm[dict(c=0, z=0, t=0)].mean() == mean
v = im.view()
assert v[dict(c=0, z=0, t=0)].mean() == mean
b = pickle.dumps(v)
w = pickle.loads(b)
assert w[dict(c=0, z=0, t=0)].mean() == mean
except ReaderNotFoundError: except ReaderNotFoundError:
assert len(Imread.__subclasses__()), "No subclasses for Imread found." assert len(Imread.__subclasses__()), 'No subclasses for Imread found.'
for child in active_children():
child.kill()

18
tests/test_ufuncs.py Normal file
View File

@@ -0,0 +1,18 @@
import pytest
import numpy as np
from ndbioimage import Imread
from itertools import product
r = np.random.randint(0, 255, (64, 64, 2, 3, 4))
im = Imread(r)
a = np.array(im)
@pytest.mark.parametrize('fun_and_axis', product(
(np.sum, np.nansum, np.min, np.nanmin, np.max, np.nanmax, np.argmin, np.argmax,
np.mean, np.nanmean, np.var, np.nanvar, np.std, np.nanstd), (None, 0, 1, 2, 3, 4)))
def test_ufuncs(fun_and_axis):
fun, axis = fun_and_axis
assert np.all(np.isclose(fun(im, axis), fun(a, axis))), \
f'function {fun.__name__} over axis {axis} does not give the correct result'