Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/convert-to-npe2 #49

Merged
merged 4 commits into from
Jul 21, 2022
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
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: [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is rough but understandable. Should become easier to generate in aicsimageio v5 though because plugins report which filetypes they support.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not strictly necessary... we could also use filename_patterns: ["*"] ... but this is preferable, as it will allow napari to do things like this make suggestions to a user that doesn't have aicsimageio installed that "there's a plugin that can help with this"

'*.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