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/add-scene-selection-widget #25

Closed
Changes from 6 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2bd22cc
Open QListWidget if len(img.scenes) > 1
psobolewskiPhD Sep 12, 2021
2d71dfe
Refactor meta into a function
psobolewskiPhD Sep 12, 2021
6a619e6
Initial try at getting LayerData from list
psobolewskiPhD Sep 12, 2021
a7ecb91
Return [(None,)] and call add_image
psobolewskiPhD Sep 12, 2021
45041db
Ensure meta["scale"] is used
psobolewskiPhD Sep 12, 2021
8e4b238
Add comments
psobolewskiPhD Sep 12, 2021
7681037
Try Flake8
psobolewskiPhD Sep 15, 2021
78edab7
Use napari.current_viewer()
psobolewskiPhD Sep 15, 2021
a69c9a6
Add & use scene indexes
psobolewskiPhD Sep 15, 2021
cd8911c
Fix ImgLayer name
psobolewskiPhD Sep 15, 2021
aaa4b77
Fix lines and return none
psobolewskiPhD Sep 15, 2021
1f7a28f
Clean-up & comments
psobolewskiPhD Sep 15, 2021
1b3fa28
change to napari[all] and v4.11
psobolewskiPhD Sep 15, 2021
196888b
Fix scale in LIF test
psobolewskiPhD Sep 15, 2021
308fc3a
Add pytest-qt to test_req
psobolewskiPhD Sep 15, 2021
a22b1cd
Test for multi-scene widget
psobolewskiPhD Sep 15, 2021
f9d5d6c
Fix nr of widgets check
psobolewskiPhD Sep 15, 2021
99ea946
Fix flake8 import error?
psobolewskiPhD Sep 15, 2021
1c21e5d
Fix flake8 imports take 2 (isort)
psobolewskiPhD Sep 15, 2021
a241491
AICSImageIO~4.1.0
psobolewskiPhD Sep 15, 2021
75ae94e
Don't add a list of widgets!!
psobolewskiPhD Sep 15, 2021
ed026a0
Test img layer from widget
psobolewskiPhD Sep 15, 2021
e285842
Fix (some) linting
psobolewskiPhD Sep 16, 2021
63f564d
Fix linting
psobolewskiPhD Sep 16, 2021
ff81a0c
Linting—for real this time?
psobolewskiPhD Sep 16, 2021
dacc272
Linting!
psobolewskiPhD Sep 16, 2021
d194b8f
Fix typing to use napari.types and TYPE_CHECKING
psobolewskiPhD Sep 17, 2021
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
155 changes: 111 additions & 44 deletions napari_aicsimageio/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,35 @@
import xarray as xr
from aicsimageio import AICSImage, exceptions, types
from aicsimageio.dimensions import DimensionNames
from qtpy.QtWidgets import QListWidget
from qtpy.QtCore import Qt
from napari import Viewer

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

LayerData = Union[Tuple[types.ArrayLike, Dict[str, Any], str]]
PathLike = Union[str, List[str]]
ReaderFunction = Callable[[PathLike], List[LayerData]]

###############################################################################
# _get_viewer() function from https://github.com/napari/napari/issues/2202
# To provide access to the napari viewer to make the dock widget
# Copyright (c) 2021 Jonas Windhager
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
psobolewskiPhD marked this conversation as resolved.
Show resolved Hide resolved
def _get_viewer() -> Optional[Viewer]:
import inspect

frame = inspect.currentframe().f_back
while frame:
instance = frame.f_locals.get("self")
if instance is not None and isinstance(instance, Viewer):
return instance
frame = frame.f_back
return None
psobolewskiPhD marked this conversation as resolved.
Show resolved Hide resolved


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


Expand Down Expand Up @@ -41,6 +63,77 @@ def _get_full_image_data(img: AICSImage, in_memory: bool) -> Optional[xr.DataArr
return None


# Function to handle multi-scene files.
def _get_scenes(img: AICSImage, in_memory: bool) -> Optional[xr.DataArray]:
psobolewskiPhD marked this conversation as resolved.
Show resolved Hide resolved
# Create the list widget and populate with the scenes in the file
list_widget = QListWidget()
for scene in img.scenes:
list_widget.addItem(scene)
viewer = _get_viewer()
viewer.window.add_dock_widget([list_widget], area="right", name="Scene Selector")

# Function to create image layer from a scene selected in the list widget
def open_scene(item):
scene = item.text()
img.set_scene(scene)
psobolewskiPhD marked this conversation as resolved.
Show resolved Hide resolved
if DimensionNames.MosaicTile in img.reader.dims.order:
try:
if in_memory:
data = img.reader.mosaic_xarray_data
else:
data = img.reader.mosaic_xarray_dask_data

# Catch reader does not support tile stitching
except NotImplementedError:
print(
"AICSImageIO: Mosaic tile stitching "
"not yet supported for this file format reader."
)
else:
if in_memory:
data = img.reader.xarray_data
else:
data = img.reader.xarray_dask_data
meta = _get_meta(data, img)
viewer.add_image(data, name=scene, metadata=meta, scale=meta["scale"])

list_widget.currentItemChanged.connect(open_scene)
return None
psobolewskiPhD marked this conversation as resolved.
Show resolved Hide resolved


# Function to get Metadata to provide with data
def _get_meta(data, img):
meta = {}
if DimensionNames.Channel in data.dims:
# Construct basic metadata
meta["name"] = data.coords[DimensionNames.Channel].data.tolist()
meta["channel_axis"] = data.dims.index(DimensionNames.Channel)
# Not multi-channel, use current scene as image name
else:
meta["name"] = img.reader.current_scene
# Handle samples / RGB
if DimensionNames.Samples in img.reader.dims.order:
meta["rgb"] = True
# Handle scales
scale: List[float] = []
for dim in img.reader.dims.order:
if dim in [
DimensionNames.SpatialX,
DimensionNames.SpatialY,
DimensionNames.SpatialZ,
]:
scale_val = getattr(img.physical_pixel_sizes, dim)
if scale_val is not None:
scale.append(scale_val)
# Apply scales
if len(scale) > 0:
meta["scale"] = tuple(scale)
# Apply all other metadata
meta["metadata"] = {"ome_types": img.metadata}
psobolewskiPhD marked this conversation as resolved.
Show resolved Hide resolved

return meta


def reader_function(
path: PathLike, in_memory: bool, scene_name: Optional[str] = None
) -> Optional[List[LayerData]]:
Expand All @@ -57,53 +150,27 @@ def reader_function(

# Open file and get data
img = AICSImage(path)
print(
f"AICSImageIO: Image contains {len(img.scenes)} scenes. "
f"napari-aicsimageio currently only supports loading first scene, "
f"will load scene: '{img.current_scene}'."
)

data = _get_full_image_data(img, in_memory=in_memory)

# Catch None data
if data is None:
return None
# Check for multiple scenes
if len(img.scenes) > 1:
print(
f"AICSImageIO: Image contains {len(img.scenes)} scenes. "
f"Supporting more than the first scene is a work in progress. "
f"Select a scene from the list widget. There may be dragons!"
)
psobolewskiPhD marked this conversation as resolved.
Show resolved Hide resolved
# Launch the list widget
_get_scenes(img, in_memory=in_memory)
# Return an empty LayerData list, because Layers will be handled via the widget. HT Jonas Windhager
return [(None,)]
psobolewskiPhD marked this conversation as resolved.
Show resolved Hide resolved
else:
# Metadata to provide with data
meta = {}
if DimensionNames.Channel in data.dims:
# Construct basic metadata
meta["name"] = data.coords[DimensionNames.Channel].data.tolist()
meta["channel_axis"] = data.dims.index(DimensionNames.Channel)

# Not multi-channel, use current scene as image name
data = _get_full_image_data(img, in_memory=in_memory)

# Catch None data
if data is None:
return None
else:
meta["name"] = img.reader.current_scene

# Handle samples / RGB
if DimensionNames.Samples in img.reader.dims.order:
meta["rgb"] = True

# Handle scales
scale: List[float] = []
for dim in img.reader.dims.order:
if dim in [
DimensionNames.SpatialX,
DimensionNames.SpatialY,
DimensionNames.SpatialZ,
]:
scale_val = getattr(img.physical_pixel_sizes, dim)
if scale_val is not None:
scale.append(scale_val)

# Apply scales
if len(scale) > 0:
meta["scale"] = tuple(scale)

# Apply all other metadata
meta["metadata"] = {"ome_types": img.metadata}

return [(data.data, meta, "image")]
meta = _get_meta(data, img)
return [(data.data, meta, "image")]


def get_reader(path: PathLike, in_memory: bool) -> Optional[ReaderFunction]:
Expand Down