Skip to content
This repository was archived by the owner on Nov 17, 2025. It is now read-only.

Commit ea06866

Browse files
authored
feature/convert-to-npe2 (#49)
* feat: convert to npe2 * add nbytes threshold * add test * lint
1 parent 728bf8f commit ea06866

8 files changed

Lines changed: 113 additions & 63 deletions

File tree

napari_aicsimageio/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
"""Top-level package for napari-aicsimageio."""
44

5-
__author__ = "Jackson Maxfield Brown"
6-
__email__ = "jacksonb@alleninstitute.org"
5+
__author__ = "Eva Maxfield Brown"
6+
__email__ = "evamaxfieldbrown@gmail.com"
77
# Do not edit this string manually, always use bumpversion
88
# Details in CONTRIBUTING.md
99
__version__ = "0.6.1"

napari_aicsimageio/core.py

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
3+
from __future__ import annotations
34

45
from functools import partial
6+
from logging import getLogger
57
from pathlib import Path
68
from typing import TYPE_CHECKING, Any, Dict, List, Optional
79

8-
import napari
9-
import xarray as xr
1010
from aicsimageio import AICSImage, exceptions
1111
from aicsimageio.dimensions import DimensionNames
1212
from qtpy.QtWidgets import (
@@ -18,8 +18,11 @@
1818
)
1919

2020
if TYPE_CHECKING:
21+
import xarray as xr
2122
from napari.types import LayerData, PathLike, ReaderFunction
2223

24+
logger = getLogger(__name__)
25+
2326
###############################################################################
2427

2528
AICSIMAGEIO_CHOICES = "AICSImageIO Scene Management"
@@ -28,6 +31,9 @@
2831

2932
SCENE_LABEL_DELIMITER = " :: "
3033

34+
# Threshold above which to use out-of-memory loading
35+
IN_MEM_THRESHOLD_PERCENT = 0.3
36+
IN_MEM_THRESHOLD_SIZE_BYTES = 4e9 # 4GB
3137
###############################################################################
3238

3339

@@ -44,7 +50,7 @@ def _get_full_image_data(
4450

4551
# Catch reader does not support tile stitching
4652
except NotImplementedError:
47-
print(
53+
logger.warning(
4854
"AICSImageIO: Mosaic tile stitching "
4955
"not yet supported for this file format reader."
5056
)
@@ -104,6 +110,8 @@ def _get_meta(data: xr.DataArray, img: AICSImage) -> Dict[str, Any]:
104110

105111

106112
def _widget_is_checked(widget_name: str) -> bool:
113+
import napari
114+
107115
# Get napari viewer from current process
108116
viewer = napari.current_viewer()
109117

@@ -119,6 +127,8 @@ def _widget_is_checked(widget_name: str) -> bool:
119127

120128
# Function to handle multi-scene files.
121129
def _get_scenes(path: "PathLike", img: AICSImage, in_memory: bool) -> None:
130+
import napari
131+
122132
# Get napari viewer from current process
123133
viewer = napari.current_viewer()
124134

@@ -186,47 +196,64 @@ def open_scene(item: QListWidgetItem) -> None:
186196
list_widget.currentItemChanged.connect(open_scene) # type: ignore
187197

188198

189-
def reader_function(path: "PathLike", in_memory: bool) -> Optional[List["LayerData"]]:
199+
def reader_function(
200+
path: "PathLike", in_memory: Optional[bool] = None
201+
) -> Optional[List["LayerData"]]:
190202
"""
191203
Given a single path return a list of LayerData tuples.
192204
"""
193-
# Alert console of how we are loading the image
194-
print(f"AICSImageIO: Reader will load image in-memory: {in_memory}")
195-
196205
# Only support single path
197206
if isinstance(path, list):
198-
print("AICSImageIO: Multi-file reading not yet supported.")
207+
logger.info("AICSImageIO: Multi-file reading not yet supported.")
199208
return None
200209

210+
if in_memory is None:
211+
from psutil import virtual_memory
212+
213+
imsize = Path(path).stat().st_size
214+
available_mem = virtual_memory().available
215+
_in_memory = (
216+
imsize <= IN_MEM_THRESHOLD_SIZE_BYTES
217+
and imsize / available_mem <= IN_MEM_THRESHOLD_PERCENT
218+
)
219+
else:
220+
_in_memory = in_memory
221+
222+
# Alert console of how we are loading the image
223+
logger.info(f"AICSImageIO: Reader will load image in-memory: {_in_memory}")
224+
201225
# Open file and get data
202226
img = AICSImage(path)
203227

204228
# Check for multiple scenes
205229
if len(img.scenes) > 1:
206-
print(
230+
logger.info(
207231
f"AICSImageIO: Image contains {len(img.scenes)} scenes. "
208232
f"Supporting more than the first scene is experimental. "
209233
f"Select a scene from the list widget. There may be dragons!"
210234
)
211235
# Launch the list widget
212-
_get_scenes(path=path, img=img, in_memory=in_memory)
236+
_get_scenes(path=path, img=img, in_memory=_in_memory)
213237

214238
# Return an empty LayerData list; ImgLayers will be handled via the widget.
215239
# HT Jonas Windhager
216240
return [(None,)]
217241
else:
218-
data = _get_full_image_data(img, in_memory=in_memory)
242+
data = _get_full_image_data(img, in_memory=_in_memory)
219243
meta = _get_meta(data, img)
220244
return [(data.data, meta, "image")]
221245

222246

223-
def get_reader(path: "PathLike", in_memory: bool) -> Optional["ReaderFunction"]:
247+
def get_reader(
248+
path: "PathLike", in_memory: Optional[bool] = None
249+
) -> Optional["ReaderFunction"]:
224250
"""
225251
Given a single path or list of paths, return the appropriate aicsimageio reader.
226252
"""
227253
# Only support single path
228254
if isinstance(path, list):
229-
print("AICSImageIO: Multi-file reading not yet supported.")
255+
logger.info("AICSImageIO: Multi-file reading not yet supported.")
256+
return None
230257

231258
# See if there is a supported reader for the file(s) provided
232259
try:
@@ -241,13 +268,13 @@ def get_reader(path: "PathLike", in_memory: bool) -> Optional["ReaderFunction"]:
241268

242269
# No supported reader, return None
243270
except exceptions.UnsupportedFileFormatError:
244-
print("AICSImageIO: Unsupported file format.")
271+
logger.warning("AICSImageIO: Unsupported file format.")
245272
return None
246273

247274
except Exception as e:
248-
print("AICSImageIO: exception occurred during reading...")
249-
print(e)
250-
print(
275+
logger.warning("AICSImageIO: exception occurred during reading...")
276+
logger.warning(e)
277+
logger.warning(
251278
"If this issue looks like a problem with AICSImageIO, "
252279
"please file a bug report: "
253280
"https://github.com/AllenCellModeling/napari-aicsimageio"

napari_aicsimageio/in_memory.py

Lines changed: 0 additions & 16 deletions
This file was deleted.

napari_aicsimageio/napari.yaml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: napari-aicsimageio
2+
schema_version: 0.1.0
3+
contributions:
4+
commands:
5+
- id: napari-aicsimageio.get_reader
6+
title: Get AICSImageIO Reader
7+
python_name: napari_aicsimageio.core:get_reader
8+
readers:
9+
- command: napari-aicsimageio.get_reader
10+
filename_patterns: [
11+
'*.1sc', '*.2fl', '*.3fr', '*.acff', '*.acqp', '*.afi', '*.afm', '*.aim', '*.al3d',
12+
'*.ali', '*.am', '*.amiramesh', '*.ano', '*.apl', '*.arf', '*.array-like', '*.arw',
13+
'*.avi', '*.bay', '*.bif', '*.bin', '*.bip', '*.bmp', '*.bmq', '*.bsdf', '*.bufr',
14+
'*.bw', '*.c01', '*.cap', '*.cat', '*.cfg', '*.ch5', '*.cif', '*.cine', '*.cr2',
15+
'*.crw', '*.cs1', '*.csv', '*.ct', '*.ct.img', '*.cur', '*.cut', '*.cxd', '*.czi',
16+
'*.dat', '*.db', '*.dc2', '*.dcm', '*.dcr', '*.dcx', '*.dds', '*.df3', '*.dicom',
17+
'*.dm2', '*.dm3', '*.dng', '*.drf', '*.dsc', '*.dti', '*.dv', '*.ecw', '*.emf',
18+
'*.eps', '*.epsi', '*.erf', '*.exp', '*.exr', '*.fake', '*.fdf', '*.fff', '*.ffr',
19+
'*.fid', '*.fit', '*.fits', '*.flc', '*.flex', '*.fli', '*.fpx', '*.frm', '*.ftc',
20+
'*.fts', '*.ftu', '*.fz', '*.g3', '*.gbr', '*.gdcm', '*.gel', '*.gif', '*.gipl',
21+
'*.grey', '*.grib', '*.h5', '*.hdf', '*.hdf5', '*.hdp', '*.hdr', '*.hed', '*.his',
22+
'*.htd', '*.htm', '*.html', '*.hx', '*.i2i', '*.ia', '*.icns', '*.ico', '*.ics',
23+
'*.ids', '*.iff', '*.iim', '*.iiq', '*.im', '*.im3', '*.img', '*.imggz', '*.ims',
24+
'*.inf', '*.inr', '*.ipl', '*.ipm', '*.ipw', '*.j2c', '*.j2k', '*.jfif', '*.jif',
25+
'*.jng', '*.jp2', '*.jpc', '*.jpe', '*.jpeg', '*.jpf', '*.jpg', '*.jpk', '*.jpx',
26+
'*.jxr', '*.k25', '*.kc2', '*.kdc', '*.klb', '*.koa', '*.l2d', '*.labels', '*.lbm',
27+
'*.lei', '*.lfp', '*.lfr', '*.lif', '*.liff', '*.lim', '*.lms', '*.lsm', '*.mdb',
28+
'*.mdc', '*.mef', '*.mgh', '*.mha', '*.mhd', '*.mic', '*.mkv', '*.mnc', '*.mnc2',
29+
'*.mng', '*.mod', '*.mos', '*.mov', '*.mp4', '*.mpeg', '*.mpg', '*.mpo', '*.mrc',
30+
'*.mri', '*.mrw', '*.msp', '*.msr', '*.mtb', '*.mvd2', '*.naf', '*.nd', '*.nd2',
31+
'*.ndpi', '*.ndpis', '*.nef', '*.nhdr', '*.nia', '*.nii', '*.nii.gz', '*.niigz',
32+
'*.npz', '*.nrrd', '*.nrw', '*.obf', '*.oib', '*.oif', '*.oir', '*.ome', '*.ome.tif',
33+
'*.ome.tiff', '*.orf', '*.par', '*.pbm', '*.pcd', '*.pcoraw', '*.pct', '*.pcx',
34+
'*.pef', '*.pfm', '*.pgm', '*.pic', '*.pict', '*.png', '*.pnl', '*.ppm', '*.pr3',
35+
'*.ps', '*.psd', '*.ptx', '*.pxn', '*.pxr', '*.qptiff', '*.qtk', '*.r3d', '*.raf',
36+
'*.ras', '*.raw', '*.rcpnl', '*.rdc', '*.rec', '*.rgb', '*.rgba', '*.rw2', '*.rwl',
37+
'*.rwz', '*.scan', '*.scn', '*.sdt', '*.seq', '*.sif', '*.sld', '*.sm2', '*.sm3',
38+
'*.spc', '*.spe', '*.spi', '*.sr2', '*.srf', '*.srw', '*.st', '*.sti', '*.stk',
39+
'*.stp', '*.svs', '*.swf', '*.sxm', '*.targa', '*.tfr', '*.tga', '*.thm', '*.tif',
40+
'*.tiff', '*.tim', '*.tnb', '*.top', '*.txt', '*.v', '*.vff', '*.vms', '*.vsi',
41+
'*.vtk', '*.vws', '*.wap', '*.wat', '*.wav', '*.wbm', '*.wbmp', '*.wdp', '*.webp',
42+
'*.wlz', '*.wmf', '*.wmv', '*.wpi', '*.xbm', '*.xdce', '*.xml', '*.xpm', '*.xqd',
43+
'*.xqf', '*.xv', '*.xys', '*.zfp', '*.zfr', '*.zip', '*.zpo', '*.zvi'
44+
]

napari_aicsimageio/out_of_memory.py

Lines changed: 0 additions & 16 deletions
This file was deleted.

napari_aicsimageio/tests/test_core.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@
22
# -*- coding: utf-8 -*-
33

44
from pathlib import Path
5-
from typing import Any, Callable, Dict, Tuple, Type
5+
from typing import TYPE_CHECKING, Any, Callable, Dict, Tuple, Type
66

77
import dask.array as da
8-
import napari
8+
import npe2
99
import numpy as np
1010
import pytest
11-
from napari.types import ArrayLike
1211

1312
from napari_aicsimageio import core
1413

14+
if TYPE_CHECKING:
15+
from napari import Viewer
16+
from napari.types import ArrayLike
17+
from npe2._pytest_plugin import TestPluginManager
18+
1519
###############################################################################
1620

1721
PNG_FILE = "example.png"
@@ -85,6 +89,7 @@ def test_reader(
8589
expected_dtype: type,
8690
expected_shape: Tuple[int, ...],
8791
expected_meta: Dict[str, Any],
92+
npe2pm: "TestPluginManager",
8893
) -> None:
8994
# Resolve filename to filepath
9095
if isinstance(filename, str):
@@ -111,6 +116,11 @@ def test_reader(
111116
meta.pop("metadata", None)
112117
assert meta == expected_meta # type: ignore
113118

119+
# confirm that this also works via npe2
120+
with npe2pm.tmp_plugin(package="napari-aicsimageio") as plugin:
121+
[via_npe2] = npe2.read([path], stack=False, plugin_name=plugin.name)
122+
assert via_npe2[0].shape == data.shape # type: ignore
123+
114124

115125
SINGLESCENE_FILE = "s_1_t_1_c_1_z_1.czi"
116126
MULTISCENE_FILE = "s_3_t_1_c_3_z_5.czi"
@@ -131,11 +141,11 @@ def test_reader(
131141
],
132142
)
133143
def test_for_multiscene_widget(
134-
make_napari_viewer: Callable[..., napari.Viewer],
144+
make_napari_viewer: Callable[..., "Viewer"],
135145
resources_dir: Path,
136146
filename: str,
137147
in_memory: bool,
138-
expected_dtype: Type[ArrayLike],
148+
expected_dtype: Type["ArrayLike"],
139149
expected_shape: Tuple[int, ...],
140150
) -> None:
141151
# Make a viewer

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ xfail_strict = true
2525
filterwarnings =
2626
ignore::UserWarning
2727
ignore::FutureWarning
28+
ignore:distutils Version classes are deprecated:
2829

2930
[flake8]
3031
exclude =

setup.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"aicsimageio[all]>=4.6.3",
4747
"fsspec[http]", # no version pin, we pull from aicsimageio
4848
"napari>=0.4.11",
49-
"napari_plugin_engine>=0.1.4",
49+
"psutil>=5.7.0",
5050
# Formats not included by default with aicsimageio
5151
"aicspylibczi>=3.0.5",
5252
"bioformats_jar",
@@ -60,8 +60,8 @@
6060
}
6161

6262
setup(
63-
author="Jackson Maxfield Brown",
64-
author_email="jmaxfieldbrown@gmail.com",
63+
author="Eva Maxfield Brown",
64+
author_email="evamaxfieldbrown@gmail.com",
6565
classifiers=[
6666
"Development Status :: 5 - Production/Stable",
6767
"Intended Audience :: Science/Research",
@@ -81,19 +81,19 @@
8181
"Multiple file format reading directly into napari using pure Python."
8282
),
8383
entry_points={
84-
"napari.plugin": [
85-
"aicsimageio-out-of-memory = napari_aicsimageio.out_of_memory",
86-
"aicsimageio-in-memory = napari_aicsimageio.in_memory",
84+
"napari.manifest": [
85+
"napari-aicsimageio = napari_aicsimageio:napari.yaml",
8786
],
8887
},
8988
install_requires=requirements,
90-
license="BSD-3-Clause",
89+
license="GPLv3",
9190
long_description=readme,
9291
long_description_content_type="text/markdown",
9392
include_package_data=True,
9493
keywords="napari, aicsimageio, TIFF, CZI, LIF, imageio, image reading, metadata",
9594
name="napari-aicsimageio",
9695
packages=find_packages(exclude=["tests", "*.tests", "*.tests.*"]),
96+
package_data={"napari_aicsimageio": ["napari.yaml"]},
9797
python_requires=">=3.8",
9898
setup_requires=setup_requirements,
9999
test_suite="napari_aicsimageio/tests",

0 commit comments

Comments
 (0)