Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
NickiShaw committed Jun 4, 2023
2 parents 7058320 + eaf0876 commit 80fe3d3
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 22 deletions.
62 changes: 56 additions & 6 deletions src/napari_emd/_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
"""
import numpy as np
import h5py
import time
import ujson
from collections.abc import Iterable


class navigate:

Expand Down Expand Up @@ -55,6 +59,20 @@ def getMemberName(group, path):
def parseFileName(file):
return str(file).split("/")[-1].split(".")[0]


# class metadata:
#
# def __init__(self, h5pyfile):
# metalocation = navigate.getMemberName(h5pyfile, '/Data/Image/') # CAUTION, may break.
# self.meta = h5pyfile['/Data/Image/' + str(metalocation) + '/Metadata']
# self.nframes = self.meta.shape[1]
# self.transposed_meta = [list(i) for i in zip(*(self.meta[:]))]
#

# def getMetaAllFrames(self, query):
# for i in range(self.nframes):
# meta = self.convertASCII(i)

class EMDreader:
"""
Unpacks emd data files with the h5py package, by navigating subdirectories.
Expand All @@ -64,19 +82,50 @@ class EMDreader:
path : str
Path to file, NOT a list of paths.
"""

def __init__(self, singlePath: str):
self.singleH5pyObject = h5py.File(singlePath, 'r')
self.path = singlePath
self.singleH5pyObject = h5py.File(singlePath, 'r', driver='core')

@staticmethod
def convertASCII(transposed_meta, frame):
ascii_meta = transposed_meta[frame]
metadata_text = ''.join(chr(i) for i in ascii_meta)
ASCii = metadata_text.replace("\0", '')
return ujson.loads(ASCii)

def unpackMetadata(self):

# TODO add implementation to search subfolders if the format is not the Velox default.

try:
metadata = self.singleH5pyObject[
'Data/Image/' + navigate.getMemberName(self.singleH5pyObject, '/Data/Image/') + '/Metadata']
transposed_meta = [list(i) for i in zip(*(metadata[:]))]
except:
raise ValueError("Metadata was not able to be read, see unpackMetadata function.")

# Unpack metadata dictionaries.
meta = {}
nframes = metadata.shape[-1]
for i in range(nframes):
meta[i] = self.convertASCII(transposed_meta, i)

return meta

def unpackData(self):

# TODO add implementation to search subfolders if the format is not the Velox default.

try:
data = self.singleH5pyObject['Data/Image/' + navigate.getMemberName(self.singleH5pyObject, '/Data/Image/') + '/Data']
return np.array(data)
data = self.singleH5pyObject[
'Data/Image/' + navigate.getMemberName(self.singleH5pyObject, '/Data/Image/') + '/Data']
data = np.array(data)
return data

except:
raise ValueError("File was not able to be read, see unpackData function.")

def parseEMDdata(self):
"""
Returns
Expand All @@ -94,14 +143,15 @@ def parseEMDdata(self):
data = data.reshape(data.shape[0], data.shape[1])
# Shape multiple frame data with transpose.
else:
data = np.transpose(data)
data = data[...].transpose()

add_kwargs = {}
add_kwargs = {'metadata': {'tag': 'emdfile', 'frames_metadata': self.unpackMetadata()}}

layer_type = "image" # optional, default is "image"

return (data, add_kwargs, layer_type)


def napari_get_reader(path):
"""A basic implementation of a Reader contribution.
Expand Down Expand Up @@ -157,4 +207,4 @@ def reader_function(path):
return LayerDataList
else:
# Return a single LayerData tuple.
return [EMDreader(path).parseEMDdata()]
return [EMDreader(path).parseEMDdata()]
3 changes: 3 additions & 0 deletions src/napari_emd/_tests/test_widget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import numpy as np

from napari_emd._widget import *
170 changes: 154 additions & 16 deletions src/napari_emd/_widget.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@


from napari.utils.notifications import show_info
import napari
from napari_plugin_engine import napari_hook_implementation

from qtpy.QtWidgets import QPushButton, QHBoxLayout, QVBoxLayout, QSpinBox, QLabel, QFrame, QFileDialog, QWidget, QApplication


from qtpy.QtWidgets import QVBoxLayout, QTabWidget, QWidget, QLabel, QTreeWidget, QTreeWidgetItem
from typing import TYPE_CHECKING

from magicgui import magic_factory
from qtpy.QtWidgets import QHBoxLayout, QPushButton, QWidget, QLabel

if TYPE_CHECKING:
import napari

Expand All @@ -20,16 +13,161 @@ class EMDWidget(QWidget):
def __init__(self, napari_viewer):
super().__init__()
self.viewer = napari_viewer
self.current_metadata_widget = None

btn = QPushButton("Click me!")
btn.clicked.connect(self._on_click)
layout = QVBoxLayout()
self.setLayout(layout)
self.current_layout = layout

lb = QLabel("some text")
# Active layer label (top layer in list view).
self.nm = QLabel()
self.current_layout.addWidget(self.nm)
# Update layer label when file is changed.
self.viewer.layers.events.reordered.connect(self.update_frame_name_and_metadata)

self.setLayout(QHBoxLayout())
self.layout().addWidget(btn)
self.layout().addWidget(lb)
# Set frame label.
self.lb = QLabel()
self.current_layout.addWidget(self.lb)
# Update frame label when frame is changed.
self.viewer.dims.events.current_step.connect(self.update_frame_name_and_metadata)

def _on_click(self):
print("napari has", len(self.viewer.layers), "layers")
self.update_frame_name_and_metadata()

# Button to perform action _on_click.
# btn = QPushButton("Export metadata")
# btn.clicked.connect(self._on_click)
# self.current_layout.addWidget(btn)

def get_topmost_layer_name(self):
if len(self.viewer.layers) == 0:
return "No image open"

# Get name of topmost layer in list to display in metadata view.
return 'Image ' + str(self.viewer.layers[-1])

def get_current_frame_number(self):
# Return number of frames.
if len(self.viewer.layers) > 0 and len(self.viewer.layers[-1].data.shape) > 2:
return self.viewer.dims.current_step[0]
else:
return 0 # single frame

def get_frame_number_label(self):
if len(self.viewer.layers) == 0:
return ""

# Return frame number for stack images, or 'Single frame' for single images.
if len(self.viewer.layers[-1].data.shape) > 2:
return 'Frame #' + str(self.viewer.dims.current_step[0])
else:
return 'Single frame'

def get_empty_metadata_view(self):
tabs = QTabWidget()
empty_tab = QTreeWidget()
empty_tab.setHeaderLabels([''])
tabs.addTab(empty_tab, 'Metadata')
return tabs

def get_metadata_view(self, frame_num: int):
assert (self.is_current_layer_emd())

# Get metadata from kwargs of topmost layer.
data_dict = self.viewer.layers[-1].metadata

# Get frame of metadata to unpack.
frame_meta = data_dict['frames_metadata'][frame_num]

tabs = QTabWidget()

tab_list = self.create_tabs_ui(frame_meta)
for tab_widget, tab_name in tab_list:
tabs.addTab(tab_widget, tab_name)

return tabs

def create_tabs_ui(self, tab_items):
tabs_by_name = {}
items_by_tab_name = {}

# Go over each top-level item and create a tab representing each non-trivial group (i.e. where the value is a
# dictionary).
for key, val in tab_items.items():
if isinstance(val, dict):
tab = QTreeWidget()
tab.setColumnCount(2)
tab.setHeaderLabels(['Name', 'Value'])
tabs_by_name[key] = tab

# Add an extra tab for any top-level items that are just plain strings.
extra_tab = QTreeWidget()
extra_tab.setColumnCount(2)
extra_tab.setHeaderLabels(['Name', 'Value'])
tabs_by_name['Extra Items'] = extra_tab
items_by_tab_name['Extra Items'] = []

for key, val in tab_items.items():
if isinstance(val, str):
items_by_tab_name['Extra Items'].append(QTreeWidgetItem([key, val]))
else:
items_by_tab_name[key] = self.collectInnerWidgetItems(val)

for tab_name, tab_items in items_by_tab_name.items():
tabs_by_name[tab_name].insertTopLevelItems(0, tab_items)

tabs = []
for tab_name, tab_widget in tabs_by_name.items():
tabs.append((tab_widget, tab_name))

return tabs

def collectInnerWidgetItems(self, dict_items):
widget_items = []
for key, val in dict_items.items():
if isinstance(val, str):
widget_items.append(QTreeWidgetItem([key, val]))
elif isinstance(val, dict):
# Collect a group of items.
group_item = QTreeWidgetItem([key])
group_children = self.collectInnerWidgetItems(val)
group_item.addChildren(group_children)
widget_items.append(group_item)

return widget_items

def update_frame_name_and_metadata(self):
self.nm.setText(self.get_topmost_layer_name())

# Get the frame number for the image if it is a stack.
self.lb.setText(self.get_frame_number_label())

self.update_metadata()

def is_current_layer_emd(self):
if len(self.viewer.layers) == 0:
return False

# Get metadata for topmost layer.
# See _reader.py for the format of the metadata dictionary.
data_dict = self.viewer.layers[-1].metadata
return data_dict and 'tag' in data_dict and data_dict['tag'] == 'emdfile'

def update_metadata(self):
# Remove old metadata view.
if self.current_metadata_widget:
self.current_layout.removeWidget(self.current_metadata_widget)
self.current_metadata_widget = None

# Create new metadata view.
# Add an empty placeholder if the current layer does not have EMD metadata.
if self.is_current_layer_emd():
frame_num = self.get_current_frame_number()
self.current_metadata_widget = self.get_metadata_view(frame_num)
else:
self.current_metadata_widget = self.get_empty_metadata_view()

self.current_layout.addWidget(self.current_metadata_widget)

def _on_click(self):
# TODO: export metadata as .csv.
pass

0 comments on commit 80fe3d3

Please sign in to comment.