Skip to content

Commit

Permalink
feature/convert-to-npe2 (#49)
Browse files Browse the repository at this point in the history
* feat: convert to npe2

* add nbytes threshold

* add test

* lint
  • Loading branch information
tlambert03 authored Jul 21, 2022
1 parent 728bf8f commit ea06866
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 63 deletions.
4 changes: 2 additions & 2 deletions napari_aicsimageio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

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

__author__ = "Jackson Maxfield Brown"
__email__ = "[email protected]"
__author__ = "Eva Maxfield Brown"
__email__ = "[email protected]"
# Do not edit this string manually, always use bumpversion
# Details in CONTRIBUTING.md
__version__ = "0.6.1"
Expand Down
61 changes: 44 additions & 17 deletions napari_aicsimageio/core.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import annotations

from functools import partial
from logging import getLogger
from pathlib import Path
from typing import TYPE_CHECKING, Any, Dict, List, Optional

import napari
import xarray as xr
from aicsimageio import AICSImage, exceptions
from aicsimageio.dimensions import DimensionNames
from qtpy.QtWidgets import (
Expand All @@ -18,8 +18,11 @@
)

if TYPE_CHECKING:
import xarray as xr
from napari.types import LayerData, PathLike, ReaderFunction

logger = getLogger(__name__)

###############################################################################

AICSIMAGEIO_CHOICES = "AICSImageIO Scene Management"
Expand All @@ -28,6 +31,9 @@

SCENE_LABEL_DELIMITER = " :: "

# Threshold above which to use out-of-memory loading
IN_MEM_THRESHOLD_PERCENT = 0.3
IN_MEM_THRESHOLD_SIZE_BYTES = 4e9 # 4GB
###############################################################################


Expand All @@ -44,7 +50,7 @@ def _get_full_image_data(

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


def _widget_is_checked(widget_name: str) -> bool:
import napari

# Get napari viewer from current process
viewer = napari.current_viewer()

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

# Function to handle multi-scene files.
def _get_scenes(path: "PathLike", img: AICSImage, in_memory: bool) -> None:
import napari

# Get napari viewer from current process
viewer = napari.current_viewer()

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


def reader_function(path: "PathLike", in_memory: bool) -> Optional[List["LayerData"]]:
def reader_function(
path: "PathLike", in_memory: Optional[bool] = None
) -> Optional[List["LayerData"]]:
"""
Given a single path return a list of LayerData tuples.
"""
# Alert console of how we are loading the image
print(f"AICSImageIO: Reader will load image in-memory: {in_memory}")

# Only support single path
if isinstance(path, list):
print("AICSImageIO: Multi-file reading not yet supported.")
logger.info("AICSImageIO: Multi-file reading not yet supported.")
return None

if in_memory is None:
from psutil import virtual_memory

imsize = Path(path).stat().st_size
available_mem = virtual_memory().available
_in_memory = (
imsize <= IN_MEM_THRESHOLD_SIZE_BYTES
and imsize / available_mem <= IN_MEM_THRESHOLD_PERCENT
)
else:
_in_memory = in_memory

# Alert console of how we are loading the image
logger.info(f"AICSImageIO: Reader will load image in-memory: {_in_memory}")

# Open file and get data
img = AICSImage(path)

# Check for multiple scenes
if len(img.scenes) > 1:
print(
logger.info(
f"AICSImageIO: Image contains {len(img.scenes)} scenes. "
f"Supporting more than the first scene is experimental. "
f"Select a scene from the list widget. There may be dragons!"
)
# Launch the list widget
_get_scenes(path=path, img=img, in_memory=in_memory)
_get_scenes(path=path, img=img, in_memory=_in_memory)

# Return an empty LayerData list; ImgLayers will be handled via the widget.
# HT Jonas Windhager
return [(None,)]
else:
data = _get_full_image_data(img, in_memory=in_memory)
data = _get_full_image_data(img, in_memory=_in_memory)
meta = _get_meta(data, img)
return [(data.data, meta, "image")]


def get_reader(path: "PathLike", in_memory: bool) -> Optional["ReaderFunction"]:
def get_reader(
path: "PathLike", in_memory: Optional[bool] = None
) -> Optional["ReaderFunction"]:
"""
Given a single path or list of paths, return the appropriate aicsimageio reader.
"""
# Only support single path
if isinstance(path, list):
print("AICSImageIO: Multi-file reading not yet supported.")
logger.info("AICSImageIO: Multi-file reading not yet supported.")
return None

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

# No supported reader, return None
except exceptions.UnsupportedFileFormatError:
print("AICSImageIO: Unsupported file format.")
logger.warning("AICSImageIO: Unsupported file format.")
return None

except Exception as e:
print("AICSImageIO: exception occurred during reading...")
print(e)
print(
logger.warning("AICSImageIO: exception occurred during reading...")
logger.warning(e)
logger.warning(
"If this issue looks like a problem with AICSImageIO, "
"please file a bug report: "
"https://github.com/AllenCellModeling/napari-aicsimageio"
Expand Down
16 changes: 0 additions & 16 deletions napari_aicsimageio/in_memory.py

This file was deleted.

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

This file was deleted.

20 changes: 15 additions & 5 deletions napari_aicsimageio/tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@
# -*- coding: utf-8 -*-

from pathlib import Path
from typing import Any, Callable, Dict, Tuple, Type
from typing import TYPE_CHECKING, Any, Callable, Dict, Tuple, Type

import dask.array as da
import napari
import npe2
import numpy as np
import pytest
from napari.types import ArrayLike

from napari_aicsimageio import core

if TYPE_CHECKING:
from napari import Viewer
from napari.types import ArrayLike
from npe2._pytest_plugin import TestPluginManager

###############################################################################

PNG_FILE = "example.png"
Expand Down Expand Up @@ -85,6 +89,7 @@ def test_reader(
expected_dtype: type,
expected_shape: Tuple[int, ...],
expected_meta: Dict[str, Any],
npe2pm: "TestPluginManager",
) -> None:
# Resolve filename to filepath
if isinstance(filename, str):
Expand All @@ -111,6 +116,11 @@ def test_reader(
meta.pop("metadata", None)
assert meta == expected_meta # type: ignore

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


SINGLESCENE_FILE = "s_1_t_1_c_1_z_1.czi"
MULTISCENE_FILE = "s_3_t_1_c_3_z_5.czi"
Expand All @@ -131,11 +141,11 @@ def test_reader(
],
)
def test_for_multiscene_widget(
make_napari_viewer: Callable[..., napari.Viewer],
make_napari_viewer: Callable[..., "Viewer"],
resources_dir: Path,
filename: str,
in_memory: bool,
expected_dtype: Type[ArrayLike],
expected_dtype: Type["ArrayLike"],
expected_shape: Tuple[int, ...],
) -> None:
# Make a viewer
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ xfail_strict = true
filterwarnings =
ignore::UserWarning
ignore::FutureWarning
ignore:distutils Version classes are deprecated:

[flake8]
exclude =
Expand Down
14 changes: 7 additions & 7 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"aicsimageio[all]>=4.6.3",
"fsspec[http]", # no version pin, we pull from aicsimageio
"napari>=0.4.11",
"napari_plugin_engine>=0.1.4",
"psutil>=5.7.0",
# Formats not included by default with aicsimageio
"aicspylibczi>=3.0.5",
"bioformats_jar",
Expand All @@ -60,8 +60,8 @@
}

setup(
author="Jackson Maxfield Brown",
author_email="jmaxfieldbrown@gmail.com",
author="Eva Maxfield Brown",
author_email="evamaxfieldbrown@gmail.com",
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Science/Research",
Expand All @@ -81,19 +81,19 @@
"Multiple file format reading directly into napari using pure Python."
),
entry_points={
"napari.plugin": [
"aicsimageio-out-of-memory = napari_aicsimageio.out_of_memory",
"aicsimageio-in-memory = napari_aicsimageio.in_memory",
"napari.manifest": [
"napari-aicsimageio = napari_aicsimageio:napari.yaml",
],
},
install_requires=requirements,
license="BSD-3-Clause",
license="GPLv3",
long_description=readme,
long_description_content_type="text/markdown",
include_package_data=True,
keywords="napari, aicsimageio, TIFF, CZI, LIF, imageio, image reading, metadata",
name="napari-aicsimageio",
packages=find_packages(exclude=["tests", "*.tests", "*.tests.*"]),
package_data={"napari_aicsimageio": ["napari.yaml"]},
python_requires=">=3.8",
setup_requires=setup_requirements,
test_suite="napari_aicsimageio/tests",
Expand Down

0 comments on commit ea06866

Please sign in to comment.