From 0606860b6b3bdd61a0135528d9f7fc36828cde60 Mon Sep 17 00:00:00 2001 From: bendichter Date: Tue, 9 Nov 2021 17:01:19 -0600 Subject: [PATCH 01/49] move tests outside the nwbwidgets package --- nwbwidgets/ophys.py | 1 - {nwbwidgets/test => test}/__init__.py | 0 {nwbwidgets/test => test}/test_base.py | 13 +++++++------ {nwbwidgets/test => test}/test_behavior.py | 0 {nwbwidgets/test => test}/test_controllers.py | 3 ++- {nwbwidgets/test => test}/test_ecephys.py | 0 {nwbwidgets/test => test}/test_file.py | 0 {nwbwidgets/test => test}/test_icephys.py | 3 ++- {nwbwidgets/test => test}/test_image.py | 0 {nwbwidgets/test => test}/test_misc.py | 0 {nwbwidgets/test => test}/test_ophys.py | 0 {nwbwidgets/test => test}/test_timeseries.py | 0 {nwbwidgets/test => test}/test_utils_cmaps.py | 0 .../test => test}/test_utils_dynamictable.py | 0 {nwbwidgets/test => test}/test_utils_mpl.py | 0 {nwbwidgets/test => test}/test_utils_timeseries.py | 0 {nwbwidgets/test => test}/test_utils_units.py | 4 ++-- 17 files changed, 13 insertions(+), 11 deletions(-) rename {nwbwidgets/test => test}/__init__.py (100%) rename {nwbwidgets/test => test}/test_base.py (99%) rename {nwbwidgets/test => test}/test_behavior.py (100%) rename {nwbwidgets/test => test}/test_controllers.py (99%) rename {nwbwidgets/test => test}/test_ecephys.py (100%) rename {nwbwidgets/test => test}/test_file.py (100%) rename {nwbwidgets/test => test}/test_icephys.py (99%) rename {nwbwidgets/test => test}/test_image.py (100%) rename {nwbwidgets/test => test}/test_misc.py (100%) rename {nwbwidgets/test => test}/test_ophys.py (100%) rename {nwbwidgets/test => test}/test_timeseries.py (100%) rename {nwbwidgets/test => test}/test_utils_cmaps.py (100%) rename {nwbwidgets/test => test}/test_utils_dynamictable.py (100%) rename {nwbwidgets/test => test}/test_utils_mpl.py (100%) rename {nwbwidgets/test => test}/test_utils_timeseries.py (100%) rename {nwbwidgets/test => test}/test_utils_units.py (98%) diff --git a/nwbwidgets/ophys.py b/nwbwidgets/ophys.py index a063bd0b..8ca02a54 100644 --- a/nwbwidgets/ophys.py +++ b/nwbwidgets/ophys.py @@ -1,7 +1,6 @@ from functools import lru_cache import ipywidgets as widgets -import matplotlib.pyplot as plt import numpy as np import plotly.graph_objects as go import plotly.express as px diff --git a/nwbwidgets/test/__init__.py b/test/__init__.py similarity index 100% rename from nwbwidgets/test/__init__.py rename to test/__init__.py diff --git a/nwbwidgets/test/test_base.py b/test/test_base.py similarity index 99% rename from nwbwidgets/test/test_base.py rename to test/test_base.py index 388fce17..ef513960 100644 --- a/nwbwidgets/test/test_base.py +++ b/test/test_base.py @@ -7,6 +7,13 @@ import pytest from dateutil.tz import tzlocal from ipywidgets import widgets +from pynwb import NWBFile +from pynwb import ProcessingModule +from pynwb import TimeSeries +from pynwb.behavior import Position, SpatialSeries +from pynwb.core import DynamicTable +from pynwb.file import Subject + from nwbwidgets.base import ( show_neurodata_base, processing_module, @@ -20,12 +27,6 @@ ) from nwbwidgets.view import default_neurodata_vis_spec from nwbwidgets.view import show_dynamic_table -from pynwb import NWBFile -from pynwb import ProcessingModule -from pynwb import TimeSeries -from pynwb.behavior import Position, SpatialSeries -from pynwb.core import DynamicTable -from pynwb.file import Subject def test_show_neurodata_base(): diff --git a/nwbwidgets/test/test_behavior.py b/test/test_behavior.py similarity index 100% rename from nwbwidgets/test/test_behavior.py rename to test/test_behavior.py diff --git a/nwbwidgets/test/test_controllers.py b/test/test_controllers.py similarity index 99% rename from nwbwidgets/test/test_controllers.py rename to test/test_controllers.py index 195816aa..76514efc 100644 --- a/nwbwidgets/test/test_controllers.py +++ b/test/test_controllers.py @@ -1,9 +1,10 @@ import unittest import numpy as np -from nwbwidgets.controllers import RangeController, GroupAndSortController from hdmf.common import DynamicTable, VectorData from pynwb.ecephys import ElectrodeGroup, Device +from nwbwidgets.controllers import RangeController, GroupAndSortController + class FloatRangeControllerTestCase(unittest.TestCase): def setUp(self): diff --git a/nwbwidgets/test/test_ecephys.py b/test/test_ecephys.py similarity index 100% rename from nwbwidgets/test/test_ecephys.py rename to test/test_ecephys.py diff --git a/nwbwidgets/test/test_file.py b/test/test_file.py similarity index 100% rename from nwbwidgets/test/test_file.py rename to test/test_file.py diff --git a/nwbwidgets/test/test_icephys.py b/test/test_icephys.py similarity index 99% rename from nwbwidgets/test/test_icephys.py rename to test/test_icephys.py index 626a0764..faa412c4 100644 --- a/nwbwidgets/test/test_icephys.py +++ b/test/test_icephys.py @@ -1,11 +1,12 @@ import matplotlib.pyplot as plt import numpy as np from ndx_icephys_meta.icephys import Sweeps, IntracellularRecordings -from nwbwidgets.icephys import show_single_sweep_sequence from pynwb.base import TimeSeries from pynwb.device import Device from pynwb.icephys import IntracellularElectrode +from nwbwidgets.icephys import show_single_sweep_sequence + def test_show_single_sweep_sequence(): device = Device(name="Axon Patch-Clamp") diff --git a/nwbwidgets/test/test_image.py b/test/test_image.py similarity index 100% rename from nwbwidgets/test/test_image.py rename to test/test_image.py diff --git a/nwbwidgets/test/test_misc.py b/test/test_misc.py similarity index 100% rename from nwbwidgets/test/test_misc.py rename to test/test_misc.py diff --git a/nwbwidgets/test/test_ophys.py b/test/test_ophys.py similarity index 100% rename from nwbwidgets/test/test_ophys.py rename to test/test_ophys.py diff --git a/nwbwidgets/test/test_timeseries.py b/test/test_timeseries.py similarity index 100% rename from nwbwidgets/test/test_timeseries.py rename to test/test_timeseries.py diff --git a/nwbwidgets/test/test_utils_cmaps.py b/test/test_utils_cmaps.py similarity index 100% rename from nwbwidgets/test/test_utils_cmaps.py rename to test/test_utils_cmaps.py diff --git a/nwbwidgets/test/test_utils_dynamictable.py b/test/test_utils_dynamictable.py similarity index 100% rename from nwbwidgets/test/test_utils_dynamictable.py rename to test/test_utils_dynamictable.py diff --git a/nwbwidgets/test/test_utils_mpl.py b/test/test_utils_mpl.py similarity index 100% rename from nwbwidgets/test/test_utils_mpl.py rename to test/test_utils_mpl.py diff --git a/nwbwidgets/test/test_utils_timeseries.py b/test/test_utils_timeseries.py similarity index 100% rename from nwbwidgets/test/test_utils_timeseries.py rename to test/test_utils_timeseries.py diff --git a/nwbwidgets/test/test_utils_units.py b/test/test_utils_units.py similarity index 98% rename from nwbwidgets/test/test_utils_units.py rename to test/test_utils_units.py index c84f1c76..7c962a15 100644 --- a/nwbwidgets/test/test_utils_units.py +++ b/test/test_utils_units.py @@ -12,8 +12,8 @@ from pynwb import NWBFile from pynwb.epoch import TimeIntervals -from ..base import TimeIntervalsSelector -from ..misc import TuningCurveWidget, TuningCurveExtendedWidget +from nwbwidgets.base import TimeIntervalsSelector +from nwbwidgets.misc import TuningCurveWidget, TuningCurveExtendedWidget class UnitsTrialsTestCase(unittest.TestCase): From 8e385923d718c76a1925101366608c345c97bb62 Mon Sep 17 00:00:00 2001 From: bendichter Date: Tue, 9 Nov 2021 17:23:20 -0600 Subject: [PATCH 02/49] improve plane segmentation coverage --- test/test_ophys.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/test/test_ophys.py b/test/test_ophys.py index 5970bc61..d5fe117a 100644 --- a/test/test_ophys.py +++ b/test/test_ophys.py @@ -73,28 +73,30 @@ def setUpClass(self): "2d_plane_seg", self.image_series, ) + self.ps2.add_column("type", "desc") + self.ps2.add_column("type2", "desc") w, h = 3, 3 img_mask1 = np.zeros((w, h)) img_mask1[0, 0] = 1.1 img_mask1[1, 1] = 1.2 img_mask1[2, 2] = 1.3 - self.ps2.add_roi(image_mask=img_mask1) + self.ps2.add_roi(image_mask=img_mask1, type=1, type2=0) img_mask2 = np.zeros((w, h)) img_mask2[0, 0] = 2.1 img_mask2[1, 1] = 2.2 - self.ps2.add_roi(image_mask=img_mask2) + self.ps2.add_roi(image_mask=img_mask2, type=1, type2=1) img_mask2 = np.zeros((w, h)) img_mask2[0, 0] = 9.1 img_mask2[1, 1] = 10.2 - self.ps2.add_roi(image_mask=img_mask2) + self.ps2.add_roi(image_mask=img_mask2, type=2, type2=0) img_mask2 = np.zeros((w, h)) img_mask2[0, 0] = 3.5 img_mask2[1, 1] = 5.6 - self.ps2.add_roi(image_mask=img_mask2) + self.ps2.add_roi(image_mask=img_mask2, type=2, type2=1) fl = Fluorescence() rt_region = self.ps2.create_roi_table_region( @@ -130,12 +132,13 @@ def test_show_3d_two_photon_series(self): def test_show_df_over_f(self): dff = show_df_over_f(self.df_over_f, default_neurodata_vis_spec) assert isinstance(dff, widgets.Widget) - dff.controls['gas'].window = [1,2] + dff.controls['gas'].window = [1, 2] def test_plane_segmentation_2d_widget(self): wid = PlaneSegmentation2DWidget(self.ps2) assert isinstance(wid, widgets.Widget) wid.button.click() + wid.cat_controller.value = "type" def test_show_plane_segmentation_3d_mask(self): ps3 = PlaneSegmentation( From 3603e17285e1c8ed350079ad7da0c8b66e181281 Mon Sep 17 00:00:00 2001 From: bendichter Date: Tue, 9 Nov 2021 17:31:45 -0600 Subject: [PATCH 03/49] improve plane segmentation coverage --- test/test_ophys.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_ophys.py b/test/test_ophys.py index d5fe117a..b4ec3608 100644 --- a/test/test_ophys.py +++ b/test/test_ophys.py @@ -139,6 +139,7 @@ def test_plane_segmentation_2d_widget(self): assert isinstance(wid, widgets.Widget) wid.button.click() wid.cat_controller.value = "type" + wid.cat_controller.value = "type2" def test_show_plane_segmentation_3d_mask(self): ps3 = PlaneSegmentation( From 4940ee7776ac54fa125577c525e9f11a92580f10 Mon Sep 17 00:00:00 2001 From: bendichter Date: Tue, 9 Nov 2021 17:35:44 -0600 Subject: [PATCH 04/49] test show_timeseries_mpl --- test/test_timeseries.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/test_timeseries.py b/test/test_timeseries.py index 98aaf282..852becc7 100644 --- a/test/test_timeseries.py +++ b/test/test_timeseries.py @@ -15,6 +15,7 @@ SeparateTracesPlotlyWidget, get_timeseries_tt, show_indexed_timeseries_plotly, + show_timeseries_mpl, ) from pynwb import TimeSeries from pynwb.epoch import TimeIntervals @@ -33,6 +34,20 @@ def test_timeseries_widget(): BaseGroupedTraceWidget(ts) +def test_show_timeseries_mpl(): + + ts = TimeSeries( + name="name", + description="no description", + data=np.array([[1.0, 2.0, 3.0, 4.0], [11.0, 12.0, 13.0, 14.0]]), + rate=100.0, + unit="m", + ) + + show_timeseries_mpl(ts) + show_timeseries_mpl(ts, time_window=(0.0, 1.0)) + + class TestTracesPlotlyWidget(unittest.TestCase): def setUp(self): data = np.random.rand(160, 3) From b438b7c4c2b673f2355ff2b999602157acd1a726 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Tue, 4 Jan 2022 13:31:30 +0530 Subject: [PATCH 05/49] extenral files existance check fix --- nwbwidgets/ophys.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/nwbwidgets/ophys.py b/nwbwidgets/ophys.py index 8ca02a54..d6a62720 100644 --- a/nwbwidgets/ophys.py +++ b/nwbwidgets/ophys.py @@ -37,26 +37,24 @@ def _add_fig_trace(img_fig: go.Figure, index): else: self.figure.for_each_trace(lambda trace: trace.update(img_fig.data[0])) self.figure.layout.title = f"Frame no: {index}" - - if indexed_timeseries.data is None: - if indexed_timeseries.external_file is not None: - path_ext_file = indexed_timeseries.external_file[0] - # Get Frames dimensions - tif = TiffFile(path_ext_file) - n_samples = len(tif.pages) - page = tif.pages[0] - n_y, n_x = page.shape - - def update_figure(index=0): - # Read first frame - img_fig = px.imshow( - imread(path_ext_file, key=int(index)), binary_string=True - ) - _add_fig_trace(img_fig, index) - - slider = widgets.IntSlider( - value=0, min=0, max=n_samples - 1, orientation="horizontal" + if indexed_timeseries.external_file is not None: + path_ext_file = indexed_timeseries.external_file[0] + # Get Frames dimensions + tif = TiffFile(path_ext_file) + n_samples = len(tif.pages) + page = tif.pages[0] + n_y, n_x = page.shape + + def update_figure(index=0): + # Read first frame + img_fig = px.imshow( + imread(path_ext_file, key=int(index)), binary_string=True ) + _add_fig_trace(img_fig, index) + + slider = widgets.IntSlider( + value=0, min=0, max=n_samples - 1, orientation="horizontal" + ) else: if len(indexed_timeseries.data.shape) == 3: From 234f37d7554f524c6ef9f587059faef64d28c1ba Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Tue, 4 Jan 2022 17:12:17 +0530 Subject: [PATCH 06/49] merging with 2photonseries widget implementation to make parent class --- nwbwidgets/image.py | 179 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 138 insertions(+), 41 deletions(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index bb6e599a..f1114c2c 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -2,10 +2,10 @@ import matplotlib.pyplot as plt import plotly.graph_objects as go +import plotly.express as px import pynwb from ipywidgets import widgets, fixed, Layout from pynwb.image import GrayscaleImage, ImageSeries, RGBImage -from tifffile import imread, TiffFile from .base import fig2widget from .controllers import StartAndDurationController @@ -14,6 +14,22 @@ get_timeseries_mint, timeseries_time_to_ind, ) +from .utils.cmaps import linear_transfer_function +from typing import Union + +try: + import cv2 + HAVE_OPENCV = True +except ImportError: + HAVE_OPENCV = False + +try: + from tifffile import imread, TiffFile + HAVE_TIF = True +except ImportError: + HAVE_TIF = False + +PathType = Union[str, Path] class ImageSeriesWidget(widgets.VBox): @@ -28,25 +44,77 @@ def __init__( super().__init__() self.imageseries = imageseries self.controls = {} - self.out_fig = None - - # Set controller - if foreign_time_window_controller is None: - tmin = get_timeseries_mint(imageseries) - if imageseries.external_file and imageseries.rate: - tif = TiffFile(imageseries.external_file[0]) - tmax = imageseries.starting_time + len(tif.pages) / imageseries.rate + + tmin = get_timeseries_mint(imageseries) + + # Make widget figure -------- + def _add_fig_trace(img_fig: go.Figure, index): + if self.figure is None: + self.figure = go.FigureWidget(img_fig) + else: + self.figure.for_each_trace(lambda trace: trace.update(img_fig.data[0])) + self.figure.layout.title = f"Frame no: {index}" + + if imageseries.external_file is not None: + file_selector = widgets.Dropdown(options=imageseries.external_file) + path_ext_file = file_selector.value + tmax = imageseries.starting_time + get_frame_count(path_ext_file)/imageseries.rate + # Get Frames dimensions + def update_figure(index=0): + # Read first frame + img_fig = px.imshow(get_frame(path_ext_file, index), binary_string=True) + _add_fig_trace(img_fig, index) + + n_samples = get_frame_count(path_ext_file) + if foreign_time_window_controller is None: + self.time_window_controller = StartAndDurationController(tmax, tmin) else: - tmax = get_timeseries_maxt(imageseries) - self.time_window_controller = StartAndDurationController(tmax, tmin) + self.time_window_controller = foreign_time_window_controller + self.set_children(file_selector) else: - self.time_window_controller = foreign_time_window_controller + if len(imageseries.data.shape) == 3: + + def update_figure(index=0): + img_fig = px.imshow( + imageseries.data[index].T, binary_string=True + ) + _add_fig_trace(img_fig, index) + + elif len(imageseries.data.shape) == 4: + import ipyvolume.pylab as p3 + + output = widgets.Output() + + def update_figure(index=0): + p3.figure() + p3.volshow( + imageseries.data[index].transpose([1, 0, 2]), + tf=linear_transfer_function([0, 0, 0], max_opacity=0.3), + ) + output.clear_output(wait=True) + self.figure = output + with output: + p3.show() + + else: + raise NotImplementedError + tmax = get_timeseries_maxt(imageseries) + if foreign_time_window_controller is None: + self.time_window_controller = StartAndDurationController(tmax, tmin) + else: + self.time_window_controller = foreign_time_window_controller + self.set_children() + self.set_controls(**kwargs) + self.figure = None - # Make widget figure - self.set_out_fig() + def on_change(change): + # Read frame + frame_number = self.time_to_index(change["new"][0]) + update_figure(frame_number) - self.children = [self.out_fig, self.time_window_controller] + update_figure() + self.controls["time_window"].observe(on_change) def time_to_index(self, time): if self.imageseries.external_file and self.imageseries.rate: @@ -60,35 +128,16 @@ def set_controls(self, **kwargs): ) self.controls.update({key: widgets.fixed(val) for key, val in kwargs.items()}) + def set_children(self, *args): + self.children = [self.out_fig, + self.time_window_controller, + *args] + def get_frame(self, idx): if self.imageseries.external_file is not None: - return imread(self.imageseries.external_file, key=idx) + return get_frame(self.imageseries.external_file[0]) else: - return self.image_series.data[idx].T - - def set_out_fig(self): - - self.out_fig = go.FigureWidget( - data=go.Heatmap( - z=self.get_frame(0), - colorscale="gray", - showscale=False, - ) - ) - self.out_fig.update_layout( - xaxis=go.layout.XAxis(showticklabels=False, ticks=""), - yaxis=go.layout.YAxis( - showticklabels=False, ticks="", scaleanchor="x", scaleratio=1 - ), - ) - - def on_change(change): - # Read frame - frame_number = self.time_to_index(change["new"][0]) - image = self.get_frame(frame_number) - self.out_fig.data[0].z = image - - self.controls["time_window"].observe(on_change) + return self.imageseries.data[idx].T def show_image_series(image_series: ImageSeries, neurodata_vis_spec: dict): @@ -167,3 +216,51 @@ def show_rbga_image(rgb_image: RGBImage, neurodata_vis_spec=None): plt.axis("off") return fig + +def get_frame_shape(external_path_file: PathType): + external_path_file = Path(external_path_file) + if external_path_file.suffix in ['tif','tiff']: + assert HAVE_TIF, 'pip install tifffile' + tif = TiffFile(external_path_file) + page = tif.pages[0] + return page.shape + else: + assert HAVE_OPENCV, 'pip install opencv-python' + cap = cv2.VideoCapture(str(external_path_file)) + success, frame = cap.read() + cap.release() + return frame.shape + +def get_frame_count(external_path_file: PathType): + external_path_file = Path(external_path_file) + if external_path_file.suffix in ['tif', 'tiff']: + assert HAVE_TIF, 'pip install tifffile' + tif = TiffFile(external_path_file) + return len(tif.pages) + else: + assert HAVE_OPENCV, 'pip install opencv-python' + cap = cv2.VideoCapture(str(external_path_file)) + if int(cv2.__version__.split(".")[0]) < 3: + frame_count_arg = cv2.cv.CV_CAP_PROP_FRAME_COUNT + else: + frame_count_arg = cv2.CAP_PROP_FRAME_COUNT + frame_count = cap.get(frame_count_arg) + cap.release() + return frame_count + +def get_frame(external_path_file: PathType, index): + external_path_file = Path(external_path_file) + if external_path_file.suffix in ['tif','tiff']: + assert HAVE_TIF, 'pip install tifffile' + return imread(str(external_path_file), key=int(index)) + else: + assert HAVE_OPENCV, 'pip install opencv-python' + cap = cv2.VideoCapture(str(external_path_file)) + if int(cv2.__version__.split(".")[0]) < 3: + set_arg = cv2.cv.CV_CAP_PROP_POS_FRAMES + else: + set_arg = cv2.CAP_PROP_POS_FRAMES + set_value = cap.set(set_arg, index) + success, frame = cap.read() + cap.release() + return frame \ No newline at end of file From e5024adf131fafebd11450e1fda8eb30657bc097 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Tue, 4 Jan 2022 17:13:04 +0530 Subject: [PATCH 07/49] fixes --- nwbwidgets/image.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index f1114c2c..1b4a323a 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -65,7 +65,6 @@ def update_figure(index=0): img_fig = px.imshow(get_frame(path_ext_file, index), binary_string=True) _add_fig_trace(img_fig, index) - n_samples = get_frame_count(path_ext_file) if foreign_time_window_controller is None: self.time_window_controller = StartAndDurationController(tmax, tmin) else: From 6a45abd1be6581ad04b8d4b309ecdda4d49d8e18 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Wed, 5 Jan 2022 09:42:26 +0530 Subject: [PATCH 08/49] fixes --- nwbwidgets/image.py | 102 +++++++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 45 deletions(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index 1b4a323a..26a21614 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -43,10 +43,9 @@ def __init__( ): super().__init__() self.imageseries = imageseries - self.controls = {} - + self.figure = None + self.time_window_controller = foreign_time_window_controller tmin = get_timeseries_mint(imageseries) - # Make widget figure -------- def _add_fig_trace(img_fig: go.Figure, index): if self.figure is None: @@ -55,65 +54,82 @@ def _add_fig_trace(img_fig: go.Figure, index): self.figure.for_each_trace(lambda trace: trace.update(img_fig.data[0])) self.figure.layout.title = f"Frame no: {index}" + def _add_time_window_controller(min, max): + if self.time_window_controller is None: + self.time_window_controller = StartAndDurationController(max, min) + else: + self.time_window_controller.slider.max = max + self.time_window_controller.slider.min = min + self.time_window_controller.duration.max = max-min + + if imageseries.external_file is not None: file_selector = widgets.Dropdown(options=imageseries.external_file) - path_ext_file = file_selector.value - tmax = imageseries.starting_time + get_frame_count(path_ext_file)/imageseries.rate # Get Frames dimensions - def update_figure(index=0): - # Read first frame - img_fig = px.imshow(get_frame(path_ext_file, index), binary_string=True) + def set_figure(index, ext_file_path): + frame_number = self.time_to_index(index) + img_fig = px.imshow(get_frame(ext_file_path, frame_number), binary_string=True) _add_fig_trace(img_fig, index) - if foreign_time_window_controller is None: - self.time_window_controller = StartAndDurationController(tmax, tmin) - else: - self.time_window_controller = foreign_time_window_controller + + def update_figure(value): + path_ext_file = value["new"][0] + # Read first frame + tmax = imageseries.starting_time + get_frame_count(path_ext_file)/imageseries.rate + _add_time_window_controller(0, tmax) + + def change_fig(change): + time = change["new"][0] + set_figure(time, path_ext_file) + + self.time_window_controller.observe(change_fig, names='value') + + file_selector.observe(update_figure, names='value') + # set default figure: + set_figure(0, imageseries.external_file[0]) + # set default time window contorller + tmax = imageseries.starting_time + \ + get_frame_count(imageseries.external_file[0])/imageseries.rate + print(get_frame_count(imageseries.external_file[0])) + _add_time_window_controller(0, tmax) self.set_children(file_selector) else: if len(imageseries.data.shape) == 3: - - def update_figure(index=0): + def set_figure(frame_number): img_fig = px.imshow( - imageseries.data[index].T, binary_string=True + imageseries.data[frame_number].T, binary_string=True ) - _add_fig_trace(img_fig, index) - + _add_fig_trace(img_fig, frame_number) + set_figure(0) elif len(imageseries.data.shape) == 4: import ipyvolume.pylab as p3 output = widgets.Output() - - def update_figure(index=0): + def set_figure(frame_number): p3.figure() p3.volshow( - imageseries.data[index].transpose([1, 0, 2]), + imageseries.data[frame_number].transpose([1, 0, 2]), tf=linear_transfer_function([0, 0, 0], max_opacity=0.3), ) output.clear_output(wait=True) self.figure = output with output: p3.show() - + set_figure(0) else: raise NotImplementedError - tmax = get_timeseries_maxt(imageseries) - if foreign_time_window_controller is None: - self.time_window_controller = StartAndDurationController(tmax, tmin) - else: - self.time_window_controller = foreign_time_window_controller - self.set_children() - self.set_controls(**kwargs) - self.figure = None + # create callback: + def update_figure(change): + frame_number = self.time_to_index(change["new"][0]) + set_figure(frame_number) - def on_change(change): - # Read frame - frame_number = self.time_to_index(change["new"][0]) - update_figure(frame_number) + # creat time window controller: + tmax = get_timeseries_maxt(imageseries) + _add_time_window_controller(tmin, tmax) + self.time_window_controller.observe(update_figure, names='value') + self.set_children() - update_figure() - self.controls["time_window"].observe(on_change) def time_to_index(self, time): if self.imageseries.external_file and self.imageseries.rate: @@ -121,14 +137,8 @@ def time_to_index(self, time): else: return timeseries_time_to_ind(self.imageseries, time) - def set_controls(self, **kwargs): - self.controls.update( - timeseries=fixed(self.imageseries), time_window=self.time_window_controller - ) - self.controls.update({key: widgets.fixed(val) for key, val in kwargs.items()}) - def set_children(self, *args): - self.children = [self.out_fig, + self.children = [self.figure, self.time_window_controller, *args] @@ -218,7 +228,7 @@ def show_rbga_image(rgb_image: RGBImage, neurodata_vis_spec=None): def get_frame_shape(external_path_file: PathType): external_path_file = Path(external_path_file) - if external_path_file.suffix in ['tif','tiff']: + if external_path_file.suffix in ['.tif','.tiff']: assert HAVE_TIF, 'pip install tifffile' tif = TiffFile(external_path_file) page = tif.pages[0] @@ -232,9 +242,11 @@ def get_frame_shape(external_path_file: PathType): def get_frame_count(external_path_file: PathType): external_path_file = Path(external_path_file) - if external_path_file.suffix in ['tif', 'tiff']: + print(external_path_file.suffix) + if external_path_file.suffix in ['.tif', '.tiff']: assert HAVE_TIF, 'pip install tifffile' tif = TiffFile(external_path_file) + print('tif opened') return len(tif.pages) else: assert HAVE_OPENCV, 'pip install opencv-python' @@ -249,7 +261,7 @@ def get_frame_count(external_path_file: PathType): def get_frame(external_path_file: PathType, index): external_path_file = Path(external_path_file) - if external_path_file.suffix in ['tif','tiff']: + if external_path_file.suffix in ['.tif','.tiff']: assert HAVE_TIF, 'pip install tifffile' return imread(str(external_path_file), key=int(index)) else: From 45b2098664ecdfdd80ea4bc0f8dfde65631145bf Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Wed, 5 Jan 2022 13:52:54 +0530 Subject: [PATCH 09/49] remove time window controller --- nwbwidgets/image.py | 89 ++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 41 deletions(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index 26a21614..068c177d 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -1,30 +1,31 @@ -from pathlib import Path, PureWindowsPath +from pathlib import Path +from typing import Union import matplotlib.pyplot as plt -import plotly.graph_objects as go import plotly.express as px +import plotly.graph_objects as go import pynwb -from ipywidgets import widgets, fixed, Layout +from ipywidgets import widgets, Layout from pynwb.image import GrayscaleImage, ImageSeries, RGBImage from .base import fig2widget -from .controllers import StartAndDurationController +from .utils.cmaps import linear_transfer_function from .utils.timeseries import ( get_timeseries_maxt, get_timeseries_mint, timeseries_time_to_ind, ) -from .utils.cmaps import linear_transfer_function -from typing import Union try: import cv2 + HAVE_OPENCV = True except ImportError: HAVE_OPENCV = False try: from tifffile import imread, TiffFile + HAVE_TIF = True except ImportError: HAVE_TIF = False @@ -36,16 +37,15 @@ class ImageSeriesWidget(widgets.VBox): """Widget showing ImageSeries.""" def __init__( - self, - imageseries: ImageSeries, - foreign_time_window_controller: StartAndDurationController = None, - **kwargs + self, + imageseries: ImageSeries, ): super().__init__() self.imageseries = imageseries self.figure = None - self.time_window_controller = foreign_time_window_controller + self.time_slider = None tmin = get_timeseries_mint(imageseries) + # Make widget figure -------- def _add_fig_trace(img_fig: go.Figure, index): if self.figure is None: @@ -54,44 +54,48 @@ def _add_fig_trace(img_fig: go.Figure, index): self.figure.for_each_trace(lambda trace: trace.update(img_fig.data[0])) self.figure.layout.title = f"Frame no: {index}" - def _add_time_window_controller(min, max): - if self.time_window_controller is None: - self.time_window_controller = StartAndDurationController(max, min) + def _add_time_slider_controller(min, max): + if self.time_slider is None: + self.time_slider = widgets.FloatSlider(value=0, + min=min, + max=max, + orientation="horizontal", + description="time(s)") else: - self.time_window_controller.slider.max = max - self.time_window_controller.slider.min = min - self.time_window_controller.duration.max = max-min - + self.time_slider.max = max + self.time_slider.min = min + self.time_slider.value = min if imageseries.external_file is not None: file_selector = widgets.Dropdown(options=imageseries.external_file) + # Get Frames dimensions - def set_figure(index, ext_file_path): - frame_number = self.time_to_index(index) + def set_figure(time, ext_file_path): + frame_number = self.time_to_index(time) img_fig = px.imshow(get_frame(ext_file_path, frame_number), binary_string=True) - _add_fig_trace(img_fig, index) - + _add_fig_trace(img_fig, frame_number) def update_figure(value): - path_ext_file = value["new"][0] + path_ext_file = value["new"] # Read first frame tmax = imageseries.starting_time + get_frame_count(path_ext_file)/imageseries.rate - _add_time_window_controller(0, tmax) - + _add_time_slider_controller(0, tmax) def change_fig(change): - time = change["new"][0] + time = change["new"] set_figure(time, path_ext_file) - self.time_window_controller.observe(change_fig, names='value') + self.time_slider.observe(change_fig, names='value') file_selector.observe(update_figure, names='value') # set default figure: - set_figure(0, imageseries.external_file[0]) + set_figure(self.imageseries.starting_time, + imageseries.external_file[0]) # set default time window contorller tmax = imageseries.starting_time + \ get_frame_count(imageseries.external_file[0])/imageseries.rate - print(get_frame_count(imageseries.external_file[0])) - _add_time_window_controller(0, tmax) + _add_time_slider_controller(0, tmax) + self.time_slider.observe(lambda x: set_figure(x["new"],imageseries.external_file[0]), + names='value') self.set_children(file_selector) else: if len(imageseries.data.shape) == 3: @@ -100,11 +104,13 @@ def set_figure(frame_number): imageseries.data[frame_number].T, binary_string=True ) _add_fig_trace(img_fig, frame_number) + set_figure(0) elif len(imageseries.data.shape) == 4: import ipyvolume.pylab as p3 output = widgets.Output() + def set_figure(frame_number): p3.figure() p3.volshow( @@ -115,31 +121,31 @@ def set_figure(frame_number): self.figure = output with output: p3.show() + set_figure(0) else: raise NotImplementedError # create callback: def update_figure(change): - frame_number = self.time_to_index(change["new"][0]) + frame_number = self.time_to_index(change["new"]) set_figure(frame_number) # creat time window controller: tmax = get_timeseries_maxt(imageseries) - _add_time_window_controller(tmin, tmax) - self.time_window_controller.observe(update_figure, names='value') + _add_time_slider_controller(tmin, tmax) + self.time_slider.observe(update_figure, names='value') self.set_children() - def time_to_index(self, time): if self.imageseries.external_file and self.imageseries.rate: - return int((time - self.imageseries.starting_time) * self.imageseries.rate) + return int((time - self.imageseries.starting_time)*self.imageseries.rate) else: return timeseries_time_to_ind(self.imageseries, time) def set_children(self, *args): self.children = [self.figure, - self.time_window_controller, + self.time_slider, *args] def get_frame(self, idx): @@ -226,9 +232,10 @@ def show_rbga_image(rgb_image: RGBImage, neurodata_vis_spec=None): return fig + def get_frame_shape(external_path_file: PathType): external_path_file = Path(external_path_file) - if external_path_file.suffix in ['.tif','.tiff']: + if external_path_file.suffix in ['.tif', '.tiff']: assert HAVE_TIF, 'pip install tifffile' tif = TiffFile(external_path_file) page = tif.pages[0] @@ -240,13 +247,12 @@ def get_frame_shape(external_path_file: PathType): cap.release() return frame.shape + def get_frame_count(external_path_file: PathType): external_path_file = Path(external_path_file) - print(external_path_file.suffix) if external_path_file.suffix in ['.tif', '.tiff']: assert HAVE_TIF, 'pip install tifffile' tif = TiffFile(external_path_file) - print('tif opened') return len(tif.pages) else: assert HAVE_OPENCV, 'pip install opencv-python' @@ -259,9 +265,10 @@ def get_frame_count(external_path_file: PathType): cap.release() return frame_count + def get_frame(external_path_file: PathType, index): external_path_file = Path(external_path_file) - if external_path_file.suffix in ['.tif','.tiff']: + if external_path_file.suffix in ['.tif', '.tiff']: assert HAVE_TIF, 'pip install tifffile' return imread(str(external_path_file), key=int(index)) else: @@ -274,4 +281,4 @@ def get_frame(external_path_file: PathType, index): set_value = cap.set(set_arg, index) success, frame = cap.read() cap.release() - return frame \ No newline at end of file + return frame From b91e39ab14fd2ec2de649e2d21e798c915743623 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Wed, 5 Jan 2022 13:53:04 +0530 Subject: [PATCH 10/49] inherit from ImageSeries --- nwbwidgets/ophys.py | 72 +++------------------------------------------ 1 file changed, 4 insertions(+), 68 deletions(-) diff --git a/nwbwidgets/ophys.py b/nwbwidgets/ophys.py index d6a62720..f8ddd181 100644 --- a/nwbwidgets/ophys.py +++ b/nwbwidgets/ophys.py @@ -21,80 +21,16 @@ from .utils.cmaps import linear_transfer_function from .utils.dynamictable import infer_categorical_columns from .controllers import ProgressBar +from .image import ImageSeriesWidget color_wheel = ["red", "blue", "green", "black", "magenta", "yellow"] -class TwoPhotonSeriesWidget(widgets.VBox): +class TwoPhotonSeriesWidget(ImageSeriesWidget): """Widget showing Image stack recorded over time from 2-photon microscope.""" - def __init__(self, indexed_timeseries: TwoPhotonSeries, neurodata_vis_spec: dict): - super().__init__() - - def _add_fig_trace(img_fig: go.Figure, index): - if self.figure is None: - self.figure = go.FigureWidget(img_fig) - else: - self.figure.for_each_trace(lambda trace: trace.update(img_fig.data[0])) - self.figure.layout.title = f"Frame no: {index}" - if indexed_timeseries.external_file is not None: - path_ext_file = indexed_timeseries.external_file[0] - # Get Frames dimensions - tif = TiffFile(path_ext_file) - n_samples = len(tif.pages) - page = tif.pages[0] - n_y, n_x = page.shape - - def update_figure(index=0): - # Read first frame - img_fig = px.imshow( - imread(path_ext_file, key=int(index)), binary_string=True - ) - _add_fig_trace(img_fig, index) - - slider = widgets.IntSlider( - value=0, min=0, max=n_samples - 1, orientation="horizontal" - ) - else: - if len(indexed_timeseries.data.shape) == 3: - - def update_figure(index=0): - img_fig = px.imshow( - indexed_timeseries.data[index].T, binary_string=True - ) - _add_fig_trace(img_fig, index) - - elif len(indexed_timeseries.data.shape) == 4: - import ipyvolume.pylab as p3 - - output = widgets.Output() - - def update_figure(index=0): - p3.figure() - p3.volshow( - indexed_timeseries.data[index].transpose([1, 0, 2]), - tf=linear_transfer_function([0, 0, 0], max_opacity=0.3), - ) - output.clear_output(wait=True) - self.figure = output - with output: - p3.show() - - else: - raise NotImplementedError - - slider = widgets.IntSlider( - value=0, - min=0, - max=indexed_timeseries.data.shape[0] - 1, - orientation="horizontal", - ) - - slider.observe(lambda change: update_figure(change.new), names="value") - self.figure = None - self.controls = dict(slider=slider) - update_figure() - self.children = [self.figure, slider] + def __init__(self, indexed_timeseries: TwoPhotonSeries): + super(TwoPhotonSeriesWidget, self).__init__(indexed_timeseries) def show_df_over_f(df_over_f: DfOverF, neurodata_vis_spec: dict): From 52110891b746b7abb50ebae5f2395874069b575c Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Wed, 5 Jan 2022 13:56:50 +0530 Subject: [PATCH 11/49] reformat --- nwbwidgets/ophys.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/nwbwidgets/ophys.py b/nwbwidgets/ophys.py index f8ddd181..6e368238 100644 --- a/nwbwidgets/ophys.py +++ b/nwbwidgets/ophys.py @@ -3,7 +3,6 @@ import ipywidgets as widgets import numpy as np import plotly.graph_objects as go -import plotly.express as px from ndx_grayscalevolume import GrayscaleVolume from pynwb.base import NWBDataInterface from pynwb.ophys import ( @@ -14,14 +13,13 @@ ImageSegmentation, ) from skimage import measure -from tifffile import imread, TiffFile from .base import df_to_hover_text +from .controllers import ProgressBar +from .image import ImageSeriesWidget from .timeseries import BaseGroupedTraceWidget from .utils.cmaps import linear_transfer_function from .utils.dynamictable import infer_categorical_columns -from .controllers import ProgressBar -from .image import ImageSeriesWidget color_wheel = ["red", "blue", "green", "black", "magenta", "yellow"] @@ -157,13 +155,13 @@ def update_fig(self, color_by): data.showlegend = False def show_plane_segmentation_2d( - self, - color_wheel: list = color_wheel, - color_by: str = None, - threshold: float = 0.01, - fig: go.Figure = None, - width: int = 600, - ref_image=None, + self, + color_wheel: list = color_wheel, + color_by: str = None, + threshold: float = 0.01, + fig: go.Figure = None, + width: int = 600, + ref_image=None, ): """ @@ -294,6 +292,6 @@ def show_grayscale_volume(vol: GrayscaleVolume, neurodata_vis_spec: dict): class RoiResponseSeriesWidget(BaseGroupedTraceWidget): def __init__( - self, roi_response_series: RoiResponseSeries, neurodata_vis_spec=None, **kwargs + self, roi_response_series: RoiResponseSeries, neurodata_vis_spec=None, **kwargs ): super().__init__(roi_response_series, "rois", **kwargs) From 45b1f7c27811cda93f03b2626dabd2508b6ff565 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Wed, 5 Jan 2022 14:16:38 +0530 Subject: [PATCH 12/49] fix 2pseries tests init arg --- test/test_ophys.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_ophys.py b/test/test_ophys.py index b4ec3608..b286a01a 100644 --- a/test/test_ophys.py +++ b/test/test_ophys.py @@ -112,7 +112,7 @@ def setUpClass(self): self.df_over_f = DfOverF(rrs) def test_show_two_photon_series(self): - wid = TwoPhotonSeriesWidget(self.image_series, default_neurodata_vis_spec) + wid = TwoPhotonSeriesWidget(self.image_series) assert isinstance(wid, widgets.Widget) wid.controls['slider'].value = 50 @@ -125,7 +125,7 @@ def test_show_3d_two_photon_series(self): rate=1.0, unit="n.a", ) - wid = TwoPhotonSeriesWidget(image_series3, default_neurodata_vis_spec) + wid = TwoPhotonSeriesWidget(image_series3) assert isinstance(wid, widgets.Widget) wid.controls['slider'].value = 50 From b6bcaadfde180d8484e4a3a0e2d4b14bcb2cfb37 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Wed, 5 Jan 2022 14:23:17 +0530 Subject: [PATCH 13/49] bug fix --- test/test_ophys.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_ophys.py b/test/test_ophys.py index b286a01a..325a9b12 100644 --- a/test/test_ophys.py +++ b/test/test_ophys.py @@ -114,7 +114,7 @@ def setUpClass(self): def test_show_two_photon_series(self): wid = TwoPhotonSeriesWidget(self.image_series) assert isinstance(wid, widgets.Widget) - wid.controls['slider'].value = 50 + wid.time_slider.value = 50.0 def test_show_3d_two_photon_series(self): image_series3 = TwoPhotonSeries( @@ -127,7 +127,7 @@ def test_show_3d_two_photon_series(self): ) wid = TwoPhotonSeriesWidget(image_series3) assert isinstance(wid, widgets.Widget) - wid.controls['slider'].value = 50 + wid.time_slider.value = 50.0 def test_show_df_over_f(self): dff = show_df_over_f(self.df_over_f, default_neurodata_vis_spec) From 10957590e9db843c2d456b5fe23539156011a271 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Wed, 5 Jan 2022 14:29:03 +0530 Subject: [PATCH 14/49] propagate neurodata_vis_spec --- nwbwidgets/image.py | 1 + nwbwidgets/ophys.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index 068c177d..d524e4f0 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -39,6 +39,7 @@ class ImageSeriesWidget(widgets.VBox): def __init__( self, imageseries: ImageSeries, + neurodata_vis_spec: dict ): super().__init__() self.imageseries = imageseries diff --git a/nwbwidgets/ophys.py b/nwbwidgets/ophys.py index 6e368238..54add897 100644 --- a/nwbwidgets/ophys.py +++ b/nwbwidgets/ophys.py @@ -27,7 +27,7 @@ class TwoPhotonSeriesWidget(ImageSeriesWidget): """Widget showing Image stack recorded over time from 2-photon microscope.""" - def __init__(self, indexed_timeseries: TwoPhotonSeries): + def __init__(self, indexed_timeseries: TwoPhotonSeries, neurodata_vis_spec: dict): super(TwoPhotonSeriesWidget, self).__init__(indexed_timeseries) From bf5fa4d0912b254db89bde71f895d80814721eca Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Wed, 5 Jan 2022 16:09:54 +0530 Subject: [PATCH 15/49] propagate neurodata_vis_spec --- nwbwidgets/ophys.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nwbwidgets/ophys.py b/nwbwidgets/ophys.py index 54add897..76009f01 100644 --- a/nwbwidgets/ophys.py +++ b/nwbwidgets/ophys.py @@ -28,7 +28,8 @@ class TwoPhotonSeriesWidget(ImageSeriesWidget): """Widget showing Image stack recorded over time from 2-photon microscope.""" def __init__(self, indexed_timeseries: TwoPhotonSeries, neurodata_vis_spec: dict): - super(TwoPhotonSeriesWidget, self).__init__(indexed_timeseries) + super(TwoPhotonSeriesWidget, self).__init__(indexed_timeseries, + neurodata_vis_spec) def show_df_over_f(df_over_f: DfOverF, neurodata_vis_spec: dict): From 1136fac52b17aeb00aea7ca5001f9cc4f087318e Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Wed, 5 Jan 2022 16:15:44 +0530 Subject: [PATCH 16/49] Revert "fix 2pseries tests init arg" This reverts commit 45b1f7c27811cda93f03b2626dabd2508b6ff565. --- test/test_ophys.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_ophys.py b/test/test_ophys.py index 325a9b12..fd91e6a4 100644 --- a/test/test_ophys.py +++ b/test/test_ophys.py @@ -112,7 +112,7 @@ def setUpClass(self): self.df_over_f = DfOverF(rrs) def test_show_two_photon_series(self): - wid = TwoPhotonSeriesWidget(self.image_series) + wid = TwoPhotonSeriesWidget(self.image_series, default_neurodata_vis_spec) assert isinstance(wid, widgets.Widget) wid.time_slider.value = 50.0 @@ -125,7 +125,7 @@ def test_show_3d_two_photon_series(self): rate=1.0, unit="n.a", ) - wid = TwoPhotonSeriesWidget(image_series3) + wid = TwoPhotonSeriesWidget(image_series3, default_neurodata_vis_spec) assert isinstance(wid, widgets.Widget) wid.time_slider.value = 50.0 From 2d02901c99f9835d1cb0850730c466b03c94ff7e Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Thu, 6 Jan 2022 18:03:01 +0530 Subject: [PATCH 17/49] restructure --- nwbwidgets/image.py | 190 +++++++++++++++++++++++--------------------- 1 file changed, 101 insertions(+), 89 deletions(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index d524e4f0..07af75af 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -44,110 +44,122 @@ def __init__( super().__init__() self.imageseries = imageseries self.figure = None - self.time_slider = None - tmin = get_timeseries_mint(imageseries) - - # Make widget figure -------- - def _add_fig_trace(img_fig: go.Figure, index): - if self.figure is None: - self.figure = go.FigureWidget(img_fig) - else: - self.figure.for_each_trace(lambda trace: trace.update(img_fig.data[0])) - self.figure.layout.title = f"Frame no: {index}" - - def _add_time_slider_controller(min, max): - if self.time_slider is None: - self.time_slider = widgets.FloatSlider(value=0, - min=min, - max=max, - orientation="horizontal", - description="time(s)") - else: - self.time_slider.max = max - self.time_slider.min = min - self.time_slider.value = min if imageseries.external_file is not None: - file_selector = widgets.Dropdown(options=imageseries.external_file) - - # Get Frames dimensions - def set_figure(time, ext_file_path): - frame_number = self.time_to_index(time) - img_fig = px.imshow(get_frame(ext_file_path, frame_number), binary_string=True) - _add_fig_trace(img_fig, frame_number) - - def update_figure(value): - path_ext_file = value["new"] - # Read first frame - tmax = imageseries.starting_time + get_frame_count(path_ext_file)/imageseries.rate - _add_time_slider_controller(0, tmax) - def change_fig(change): - time = change["new"] - set_figure(time, path_ext_file) - - self.time_slider.observe(change_fig, names='value') - - file_selector.observe(update_figure, names='value') - # set default figure: - set_figure(self.imageseries.starting_time, - imageseries.external_file[0]) - # set default time window contorller - tmax = imageseries.starting_time + \ - get_frame_count(imageseries.external_file[0])/imageseries.rate - _add_time_slider_controller(0, tmax) - self.time_slider.observe(lambda x: set_figure(x["new"],imageseries.external_file[0]), - names='value') - self.set_children(file_selector) + + # set time slider: + tmax = imageseries.starting_time + get_frame_count(imageseries.external_file[0])/imageseries.rate + self.time_slider = widgets.FloatSlider(min=imageseries.starting_time, + max=tmax, + orientation="horizontal", + description="time(s)") + external_file = imageseries.external_file[0] + self.file_selector = None + # set file selector: + if len(imageseries.external_file) > 1: + self.file_selector = widgets.Dropdown(options=imageseries.external_file) + external_file = self.file_selector.value + + def update_time_slider(value): + path_ext_file = value["new"] + # Read first frame + nonlocal external_file + external_file = path_ext_file + tmax = imageseries.starting_time + get_frame_count(path_ext_file)/imageseries.rate + tmin = 0 + self.time_slider.max = tmax + self.time_slider.min = tmin + # self.time_slider.value = tmin + self._set_figure_external(tmin, external_file, tmin) + print('update time slider callback', self.time_slider.value) + + self.file_selector.observe(update_time_slider, names='value') + + # set time slider callbacks: + def change_fig(change): + time = change["new"] + starting_time = change["owner"].min + print('time slider callback', time, starting_time) + self._set_figure_external(time, external_file, starting_time) + + self.time_slider.observe(change_fig, names='value') + self._set_figure_external(imageseries.starting_time, + external_file, + imageseries.starting_time) + + # set children: + self.set_children(self.file_selector) + + else: if len(imageseries.data.shape) == 3: - def set_figure(frame_number): - img_fig = px.imshow( - imageseries.data[frame_number].T, binary_string=True - ) - _add_fig_trace(img_fig, frame_number) - - set_figure(0) + self._set_figure_2d(0) + def time_slider_callback(change): + frame_number = self.time_to_index(change["new"]) + self._set_figure_2d(frame_number) elif len(imageseries.data.shape) == 4: - import ipyvolume.pylab as p3 - - output = widgets.Output() - - def set_figure(frame_number): - p3.figure() - p3.volshow( - imageseries.data[frame_number].transpose([1, 0, 2]), - tf=linear_transfer_function([0, 0, 0], max_opacity=0.3), - ) - output.clear_output(wait=True) - self.figure = output - with output: - p3.show() - - set_figure(0) + self._set_figure_3d(0) + def time_slider_callback(change): + frame_number = self.time_to_index(change["new"]) + self._set_figure_3d(frame_number) else: raise NotImplementedError - # create callback: - def update_figure(change): - frame_number = self.time_to_index(change["new"]) - set_figure(frame_number) - # creat time window controller: + tmin = get_timeseries_mint(imageseries) tmax = get_timeseries_maxt(imageseries) - _add_time_slider_controller(tmin, tmax) - self.time_slider.observe(update_figure, names='value') + self.time_slider = widgets.FloatSlider(value=tmin, + min=tmin, + max=tmax, + orientation="horizontal", + description="time(s)") + self.time_slider.observe(time_slider_callback, names='value') self.set_children() - def time_to_index(self, time): + def _set_figure_3d(self, frame_number): + import ipyvolume.pylab as p3 + output = widgets.Output() + p3.figure() + p3.volshow( + self.imageseries.data[frame_number].transpose([1, 0, 2]), + tf=linear_transfer_function([0, 0, 0], max_opacity=0.3), + ) + output.clear_output(wait=True) + self.figure = output + with output: + p3.show() + + def _set_figure_2d(self, frame_number): + img_fig = px.imshow( + self.imageseries.data[frame_number].T, binary_string=True + ) + self._add_fig_trace(img_fig, frame_number) + + def _set_figure_external(self, time, ext_file_path, starting_time): + frame_number = self.time_to_index(time, starting_time) + img_fig = px.imshow(get_frame(ext_file_path, frame_number), binary_string=True) # TODO: use go.image + self._add_fig_trace(img_fig, frame_number) # TODO: create figure at this level. + + def _add_fig_trace(self, img_fig: go.Figure, index): + if self.figure is None: + self.figure = go.FigureWidget(img_fig) # TODO: remove this, make scatter from go directly + else: + self.figure.for_each_trace(lambda trace: trace.update(img_fig.data[0])) + self.figure.layout.title = f"Frame no: {index}" + + def time_to_index(self, time, starting_time=None): + starting_time = starting_time if starting_time is not None \ + else self.imageseries.starting_time if self.imageseries.external_file and self.imageseries.rate: - return int((time - self.imageseries.starting_time)*self.imageseries.rate) + return int((time - starting_time)*self.imageseries.rate) else: return timeseries_time_to_ind(self.imageseries, time) - def set_children(self, *args): + def set_children(self, *widgets): + set_widgets = [wid for wid in widgets if wid is not None] self.children = [self.figure, self.time_slider, - *args] + *set_widgets] def get_frame(self, idx): if self.imageseries.external_file is not None: @@ -267,9 +279,9 @@ def get_frame_count(external_path_file: PathType): return frame_count -def get_frame(external_path_file: PathType, index): +def get_frame(external_path_file: PathType, index):# TODO: make this as a routing method external_path_file = Path(external_path_file) - if external_path_file.suffix in ['.tif', '.tiff']: + if external_path_file.suffix in ['.tif', '.tiff']: # TODO: keep separate and support jpeg assert HAVE_TIF, 'pip install tifffile' return imread(str(external_path_file), key=int(index)) else: @@ -282,4 +294,4 @@ def get_frame(external_path_file: PathType, index): set_value = cap.set(set_arg, index) success, frame = cap.read() cap.release() - return frame + return frame \ No newline at end of file From a7c06e777b21db11d5275c0f3921516d5d7a3688 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Thu, 6 Jan 2022 19:01:41 +0530 Subject: [PATCH 18/49] using go.image --- nwbwidgets/image.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index 07af75af..966b6bb9 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -1,6 +1,6 @@ from pathlib import Path from typing import Union - +import numpy as np import matplotlib.pyplot as plt import plotly.express as px import plotly.graph_objects as go @@ -69,9 +69,7 @@ def update_time_slider(value): tmin = 0 self.time_slider.max = tmax self.time_slider.min = tmin - # self.time_slider.value = tmin self._set_figure_external(tmin, external_file, tmin) - print('update time slider callback', self.time_slider.value) self.file_selector.observe(update_time_slider, names='value') @@ -79,7 +77,6 @@ def update_time_slider(value): def change_fig(change): time = change["new"] starting_time = change["owner"].min - print('time slider callback', time, starting_time) self._set_figure_external(time, external_file, starting_time) self.time_slider.observe(change_fig, names='value') @@ -130,21 +127,17 @@ def _set_figure_3d(self, frame_number): p3.show() def _set_figure_2d(self, frame_number): - img_fig = px.imshow( - self.imageseries.data[frame_number].T, binary_string=True - ) - self._add_fig_trace(img_fig, frame_number) + self._add_fig_trace(self.imageseries.data[frame_number].T, frame_number) def _set_figure_external(self, time, ext_file_path, starting_time): frame_number = self.time_to_index(time, starting_time) - img_fig = px.imshow(get_frame(ext_file_path, frame_number), binary_string=True) # TODO: use go.image - self._add_fig_trace(img_fig, frame_number) # TODO: create figure at this level. + self._add_fig_trace(get_frame(ext_file_path, frame_number), frame_number) - def _add_fig_trace(self, img_fig: go.Figure, index): + def _add_fig_trace(self, img_data: np.ndarray, index): if self.figure is None: - self.figure = go.FigureWidget(img_fig) # TODO: remove this, make scatter from go directly + self.figure = go.FigureWidget(data=dict(type='image', z=img_data)) else: - self.figure.for_each_trace(lambda trace: trace.update(img_fig.data[0])) + self.figure.data[0]["z"] = img_data self.figure.layout.title = f"Frame no: {index}" def time_to_index(self, time, starting_time=None): From 34a053cce1ca1b0f4963a057800d99f0c884c370 Mon Sep 17 00:00:00 2001 From: Saksham Sharda <11567561+Saksham20@users.noreply.github.com> Date: Thu, 6 Jan 2022 19:05:06 +0530 Subject: [PATCH 19/49] Update nwbwidgets/ophys.py Co-authored-by: Ben Dichter --- nwbwidgets/ophys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nwbwidgets/ophys.py b/nwbwidgets/ophys.py index 76009f01..6ec2c97b 100644 --- a/nwbwidgets/ophys.py +++ b/nwbwidgets/ophys.py @@ -28,7 +28,7 @@ class TwoPhotonSeriesWidget(ImageSeriesWidget): """Widget showing Image stack recorded over time from 2-photon microscope.""" def __init__(self, indexed_timeseries: TwoPhotonSeries, neurodata_vis_spec: dict): - super(TwoPhotonSeriesWidget, self).__init__(indexed_timeseries, + super().__init__(indexed_timeseries, neurodata_vis_spec) From 8906b88cdadfc70791a673c3312922188e0d05b3 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Fri, 7 Jan 2022 18:10:32 +0530 Subject: [PATCH 20/49] black --- nwbwidgets/image.py | 173 +++++++++++++++++--------------------------- 1 file changed, 66 insertions(+), 107 deletions(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index 966b6bb9..0e901686 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -1,8 +1,8 @@ from pathlib import Path from typing import Union -import numpy as np + import matplotlib.pyplot as plt -import plotly.express as px +import numpy as np import plotly.graph_objects as go import pynwb from ipywidgets import widgets, Layout @@ -10,26 +10,13 @@ from .base import fig2widget from .utils.cmaps import linear_transfer_function +from .utils.imageseries import get_frame_count, get_frame from .utils.timeseries import ( get_timeseries_maxt, get_timeseries_mint, timeseries_time_to_ind, ) -try: - import cv2 - - HAVE_OPENCV = True -except ImportError: - HAVE_OPENCV = False - -try: - from tifffile import imread, TiffFile - - HAVE_TIF = True -except ImportError: - HAVE_TIF = False - PathType = Union[str, Path] @@ -37,22 +24,30 @@ class ImageSeriesWidget(widgets.VBox): """Widget showing ImageSeries.""" def __init__( - self, - imageseries: ImageSeries, - neurodata_vis_spec: dict + self, + imageseries: ImageSeries, + foreign_time_slider: widgets.FloatSlider = None, + neurodata_vis_spec: dict = None, ): super().__init__() self.imageseries = imageseries self.figure = None + self.time_slider = foreign_time_slider if imageseries.external_file is not None: # set time slider: - tmax = imageseries.starting_time + get_frame_count(imageseries.external_file[0])/imageseries.rate - self.time_slider = widgets.FloatSlider(min=imageseries.starting_time, - max=tmax, - orientation="horizontal", - description="time(s)") + tmax = ( + imageseries.starting_time + + get_frame_count(imageseries.external_file[0]) / imageseries.rate + ) + if self.time_slider is None: + self.time_slider = widgets.FloatSlider( + min=imageseries.starting_time, + max=tmax, + orientation="horizontal", + description="time(s)", + ) external_file = imageseries.external_file[0] self.file_selector = None # set file selector: @@ -65,13 +60,16 @@ def update_time_slider(value): # Read first frame nonlocal external_file external_file = path_ext_file - tmax = imageseries.starting_time + get_frame_count(path_ext_file)/imageseries.rate + tmax = ( + imageseries.starting_time + + get_frame_count(path_ext_file) / imageseries.rate + ) tmin = 0 self.time_slider.max = tmax self.time_slider.min = tmin self._set_figure_external(tmin, external_file, tmin) - self.file_selector.observe(update_time_slider, names='value') + self.file_selector.observe(update_time_slider, names="value") # set time slider callbacks: def change_fig(change): @@ -79,42 +77,48 @@ def change_fig(change): starting_time = change["owner"].min self._set_figure_external(time, external_file, starting_time) - self.time_slider.observe(change_fig, names='value') - self._set_figure_external(imageseries.starting_time, - external_file, - imageseries.starting_time) - + print(self.time_slider) + self.time_slider.observe(change_fig, names="value") + self._set_figure_external( + imageseries.starting_time, external_file, imageseries.starting_time + ) # set children: - self.set_children(self.file_selector) - - + self.children = self.get_children(self.file_selector) else: if len(imageseries.data.shape) == 3: self._set_figure_2d(0) + def time_slider_callback(change): frame_number = self.time_to_index(change["new"]) self._set_figure_2d(frame_number) + elif len(imageseries.data.shape) == 4: self._set_figure_3d(0) + def time_slider_callback(change): frame_number = self.time_to_index(change["new"]) self._set_figure_3d(frame_number) + else: raise NotImplementedError # creat time window controller: tmin = get_timeseries_mint(imageseries) tmax = get_timeseries_maxt(imageseries) - self.time_slider = widgets.FloatSlider(value=tmin, - min=tmin, - max=tmax, - orientation="horizontal", - description="time(s)") - self.time_slider.observe(time_slider_callback, names='value') - self.set_children() + if self.time_slider is None: + self.time_slider = widgets.FloatSlider( + value=tmin, + min=tmin, + max=tmax, + orientation="horizontal", + description="time(s)", + ) + self.time_slider.observe(time_slider_callback, names="value") + self.children = self.get_children() def _set_figure_3d(self, frame_number): import ipyvolume.pylab as p3 + output = widgets.Output() p3.figure() p3.volshow( @@ -125,34 +129,40 @@ def _set_figure_3d(self, frame_number): self.figure = output with output: p3.show() - + def _set_figure_2d(self, frame_number): - self._add_fig_trace(self.imageseries.data[frame_number].T, frame_number) - + data = self.imageseries.data[frame_number].T + if self.figure is None: + self.figure = go.FigureWidget(data=dict(type="image", z=data)) + else: + self._add_fig_trace(data, frame_number) + def _set_figure_external(self, time, ext_file_path, starting_time): frame_number = self.time_to_index(time, starting_time) - self._add_fig_trace(get_frame(ext_file_path, frame_number), frame_number) - - def _add_fig_trace(self, img_data: np.ndarray, index): + data = get_frame(ext_file_path, frame_number) if self.figure is None: - self.figure = go.FigureWidget(data=dict(type='image', z=img_data)) + self.figure = go.FigureWidget(data=dict(type="image", z=data)) else: - self.figure.data[0]["z"] = img_data + self._add_fig_trace(data, frame_number) + + def _add_fig_trace(self, img_data: np.ndarray, index): + self.figure.data[0]["z"] = img_data self.figure.layout.title = f"Frame no: {index}" - + def time_to_index(self, time, starting_time=None): - starting_time = starting_time if starting_time is not None \ + starting_time = ( + starting_time + if starting_time is not None else self.imageseries.starting_time + ) if self.imageseries.external_file and self.imageseries.rate: - return int((time - starting_time)*self.imageseries.rate) + return int((time - starting_time) * self.imageseries.rate) else: return timeseries_time_to_ind(self.imageseries, time) - def set_children(self, *widgets): + def get_children(self, *widgets): set_widgets = [wid for wid in widgets if wid is not None] - self.children = [self.figure, - self.time_slider, - *set_widgets] + return [self.figure, self.time_slider, *set_widgets] def get_frame(self, idx): if self.imageseries.external_file is not None: @@ -237,54 +247,3 @@ def show_rbga_image(rgb_image: RGBImage, neurodata_vis_spec=None): plt.axis("off") return fig - - -def get_frame_shape(external_path_file: PathType): - external_path_file = Path(external_path_file) - if external_path_file.suffix in ['.tif', '.tiff']: - assert HAVE_TIF, 'pip install tifffile' - tif = TiffFile(external_path_file) - page = tif.pages[0] - return page.shape - else: - assert HAVE_OPENCV, 'pip install opencv-python' - cap = cv2.VideoCapture(str(external_path_file)) - success, frame = cap.read() - cap.release() - return frame.shape - - -def get_frame_count(external_path_file: PathType): - external_path_file = Path(external_path_file) - if external_path_file.suffix in ['.tif', '.tiff']: - assert HAVE_TIF, 'pip install tifffile' - tif = TiffFile(external_path_file) - return len(tif.pages) - else: - assert HAVE_OPENCV, 'pip install opencv-python' - cap = cv2.VideoCapture(str(external_path_file)) - if int(cv2.__version__.split(".")[0]) < 3: - frame_count_arg = cv2.cv.CV_CAP_PROP_FRAME_COUNT - else: - frame_count_arg = cv2.CAP_PROP_FRAME_COUNT - frame_count = cap.get(frame_count_arg) - cap.release() - return frame_count - - -def get_frame(external_path_file: PathType, index):# TODO: make this as a routing method - external_path_file = Path(external_path_file) - if external_path_file.suffix in ['.tif', '.tiff']: # TODO: keep separate and support jpeg - assert HAVE_TIF, 'pip install tifffile' - return imread(str(external_path_file), key=int(index)) - else: - assert HAVE_OPENCV, 'pip install opencv-python' - cap = cv2.VideoCapture(str(external_path_file)) - if int(cv2.__version__.split(".")[0]) < 3: - set_arg = cv2.cv.CV_CAP_PROP_POS_FRAMES - else: - set_arg = cv2.CAP_PROP_POS_FRAMES - set_value = cap.set(set_arg, index) - success, frame = cap.read() - cap.release() - return frame \ No newline at end of file From 978270990a46c45e4bb579ea7f6833d568c8acdf Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Fri, 7 Jan 2022 18:10:43 +0530 Subject: [PATCH 21/49] add imageseries.py --- nwbwidgets/utils/imageseries.py | 161 ++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 nwbwidgets/utils/imageseries.py diff --git a/nwbwidgets/utils/imageseries.py b/nwbwidgets/utils/imageseries.py new file mode 100644 index 00000000..7f8e7f12 --- /dev/null +++ b/nwbwidgets/utils/imageseries.py @@ -0,0 +1,161 @@ +from pathlib import Path +from typing import Union + +try: + import cv2 + + HAVE_OPENCV = True +except ImportError: + HAVE_OPENCV = False + +try: + from tifffile import imread, TiffFile + + HAVE_TIF = True +except ImportError: + HAVE_TIF = False + +PathType = Union[str, Path] + +VIDEO_EXTENSIONS = [".mp4", ".avi", ".wmv", ".mov", ".flv"] + + +class VideoCaptureContext(cv2.VideoCapture): + """ + Context manager for opening videos using opencv + """ + def __enter__(self): + return self + + def __exit__(self, *args): + self.release() + + +def get_frame_shape(external_path_file: PathType): + """ + Get frame shape + Parameters + ---------- + external_path_file: PathType + path of external file from the external_file argument of ImageSeries + """ + external_path_file = Path(external_path_file) + if external_path_file.suffix in [".tif", ".tiff"]: + return get_frame_shape_tif(external_path_file) + elif external_path_file.suffix in VIDEO_EXTENSIONS: + return get_frame_shape_video(external_path_file) + else: + raise NotImplementedError + + +def get_frame_count(external_path_file: PathType): + """ + Get number of frames in the video or tif stack. + Parameters + ---------- + external_path_file: PathType + path of external file from the external_file argument of ImageSeries + """ + external_path_file = Path(external_path_file) + if external_path_file.suffix in [".tif", ".tiff"]: + return get_frame_count_tif(external_path_file) + elif external_path_file.suffix in VIDEO_EXTENSIONS: + return get_frame_count_video(external_path_file) + else: + raise NotImplementedError + + +def get_frame(external_path_file: PathType, index): + """ + Get frame + Parameters + ---------- + external_path_file: PathType + path of external file from the external_file argument of ImageSeries + index: int + the frame number to retrieve from the video/tif file + """ + external_path_file = Path(external_path_file) + if external_path_file.suffix in [".tif", ".tiff"]: + return get_frame_tif(external_path_file, index) + elif external_path_file.suffix in VIDEO_EXTENSIONS: + return get_frame_video(external_path_file, index) + else: + raise NotImplementedError + + +def get_frame_tif(external_path_file: PathType, index): + external_path_file = Path(external_path_file) + assert external_path_file.suffix in [".tif", ".tiff"], f"supply a tif file" + assert HAVE_TIF, "pip install tifffile" + return imread(str(external_path_file), key=int(index)) + + +def get_frame_shape_tif(external_path_file: PathType): + external_path_file = Path(external_path_file) + assert external_path_file.suffix in [".tif", ".tiff"], f"supply a tif file" + assert HAVE_TIF, "pip install tifffile" + tif = TiffFile(external_path_file) + page = tif.pages[0] + return page.shape + + +def get_frame_count_tif(external_path_file: PathType): + external_path_file = Path(external_path_file) + assert external_path_file.suffix in [".tif", ".tiff"], f"supply a tif file" + assert HAVE_TIF, "pip install tifffile" + tif = TiffFile(external_path_file) + return len(tif.pages) + + +def get_frame_video(external_path_file: PathType, index): + external_path_file = Path(external_path_file) + assert ( + external_path_file.suffix in VIDEO_EXTENSIONS + ), f"supply any of {VIDEO_EXTENSIONS} files" + assert HAVE_OPENCV, "pip install opencv-python" + no_frames = get_frame_count(external_path_file) + assert index < no_frames, f"enter index < {no_frames}" + if int(cv2.__version__.split(".")[0]) < 3: + set_arg = cv2.cv.CV_CAP_PROP_POS_FRAMES + else: + set_arg = cv2.CAP_PROP_POS_FRAMES + with VideoCaptureContext(str(external_path_file)) as cap: + cap = VideoCaptureContext(str(external_path_file)) + set_value = cap.set(set_arg, index) + success, frame = cap.read() + if success: + return frame + else: + raise Exception("could not open video file") + + +def get_frame_count_video(external_path_file: PathType): + external_path_file = Path(external_path_file) + assert ( + external_path_file.suffix in VIDEO_EXTENSIONS + ), f"supply any of {VIDEO_EXTENSIONS} files" + assert HAVE_OPENCV, "pip install opencv-python" + if int(cv2.__version__.split(".")[0]) < 3: + frame_count_arg = cv2.cv.CV_CAP_PROP_FRAME_COUNT + else: + frame_count_arg = cv2.CAP_PROP_FRAME_COUNT + with VideoCaptureContext(str(external_path_file)) as cap: + cap = VideoCaptureContext(str(external_path_file)) + frame_count = cap.get(frame_count_arg) + return frame_count + + +def get_frame_shape_video(external_path_file: PathType): + external_path_file = Path(external_path_file) + assert ( + external_path_file.suffix in VIDEO_EXTENSIONS + ), f"supply any of {VIDEO_EXTENSIONS} files" + assert HAVE_OPENCV, "pip install opencv-python" + with VideoCaptureContext(str(external_path_file)) as cap: + cap = VideoCaptureContext(str(external_path_file)) + success, frame = cap.read() + if success: + return frame.shape + else: + raise Exception("could not open video file") From 17d94951938ac3682a5f586dfd93441206fcf81d Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Fri, 7 Jan 2022 19:18:52 +0530 Subject: [PATCH 22/49] using custom context manager without inheritance --- nwbwidgets/utils/imageseries.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/nwbwidgets/utils/imageseries.py b/nwbwidgets/utils/imageseries.py index 7f8e7f12..de02ab39 100644 --- a/nwbwidgets/utils/imageseries.py +++ b/nwbwidgets/utils/imageseries.py @@ -20,15 +20,18 @@ VIDEO_EXTENSIONS = [".mp4", ".avi", ".wmv", ".mov", ".flv"] -class VideoCaptureContext(cv2.VideoCapture): +class VideoCaptureContext: """ Context manager for opening videos using opencv """ + def __init__(self, video_path): + self.vc = cv2.VideoCapture(video_path) + def __enter__(self): return self def __exit__(self, *args): - self.release() + self.vc.release() def get_frame_shape(external_path_file: PathType): @@ -121,9 +124,8 @@ def get_frame_video(external_path_file: PathType, index): else: set_arg = cv2.CAP_PROP_POS_FRAMES with VideoCaptureContext(str(external_path_file)) as cap: - cap = VideoCaptureContext(str(external_path_file)) - set_value = cap.set(set_arg, index) - success, frame = cap.read() + set_value = cap.vc.set(set_arg, index) + success, frame = cap.vc.read() if success: return frame else: @@ -141,8 +143,7 @@ def get_frame_count_video(external_path_file: PathType): else: frame_count_arg = cv2.CAP_PROP_FRAME_COUNT with VideoCaptureContext(str(external_path_file)) as cap: - cap = VideoCaptureContext(str(external_path_file)) - frame_count = cap.get(frame_count_arg) + frame_count = cap.vc.get(frame_count_arg) return frame_count @@ -153,8 +154,7 @@ def get_frame_shape_video(external_path_file: PathType): ), f"supply any of {VIDEO_EXTENSIONS} files" assert HAVE_OPENCV, "pip install opencv-python" with VideoCaptureContext(str(external_path_file)) as cap: - cap = VideoCaptureContext(str(external_path_file)) - success, frame = cap.read() + success, frame = cap.vc.read() if success: return frame.shape else: From d2b7d59a08969564be6d5c721ecdc3130eec0407 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Fri, 7 Jan 2022 19:19:49 +0530 Subject: [PATCH 23/49] remove print st --- nwbwidgets/image.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index 0e901686..c4d2e38b 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -77,7 +77,6 @@ def change_fig(change): starting_time = change["owner"].min self._set_figure_external(time, external_file, starting_time) - print(self.time_slider) self.time_slider.observe(change_fig, names="value") self._set_figure_external( imageseries.starting_time, external_file, imageseries.starting_time From 22a34ff21fbb721f25e343769cf134d2c8a01487 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Sat, 8 Jan 2022 18:31:54 +0530 Subject: [PATCH 24/49] tests setup --- test/fixtures.py | 63 ++++++++++++++++++++++++++++++++++ test/test_utils_imageseries.py | 31 +++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 test/fixtures.py create mode 100644 test/test_utils_imageseries.py diff --git a/test/fixtures.py b/test/fixtures.py new file mode 100644 index 00000000..90329d01 --- /dev/null +++ b/test/fixtures.py @@ -0,0 +1,63 @@ +import cv2 +import numpy as np +import pytest +from tifffile import imwrite + + +@pytest.fixture(scope="session") +def movie_fps(): + return 10 + + +@pytest.fixture(scope="session") +def movie_shape(): + return (300, 400, 3) + + +@pytest.fixture(scope="session") +def movie_no_frames(): + return 30 + + +@pytest.fixture(scope="session") +def create_frames(movie_no_frames, movie_shape): + mov_ar1 = np.random.randint(0, 255, size=[*movie_shape, movie_no_frames], dtype="uint8") + mov_ar2 = np.random.randint(0, 255, size=[*movie_shape, movie_no_frames], dtype="uint8") + return mov_ar1, mov_ar2 + + +@pytest.fixture(scope="session") +def create_movie_files(tmp_path_factory, create_frames, movie_fps): + base_path = tmp_path_factory.mktemp('moviefiles') + print(base_path) + mov_ar1_path = base_path/'movie1.avi' + mov_ar2_path = base_path/'movie.avi' + mov_array1, mov_array2 = create_frames + movie_shape = mov_array1.shape[:2] + no_frames = mov_array1.shape[-1] + cap_mp4 = cv2.VideoWriter(str(mov_ar1_path), + cv2.VideoWriter_fourcc(*'mp4v'), + movie_fps, movie_shape) + cap_avi = cv2.VideoWriter(str(mov_ar2_path), + cv2.VideoWriter_fourcc(*'DIVX'), + movie_fps, movie_shape) + for frame_no in range(no_frames): + cap_mp4.write(mov_array1[:,:,:,frame_no]) + cap_avi.write(mov_array2[:,:,:,frame_no]) + + cap_mp4.release() + cap_avi.release() + return mov_ar2_path, mov_ar2_path + + +@pytest.fixture(scope="session") +def create_tif_files(tmp_path_factory, create_frames): + base_path = tmp_path_factory.mktemp('tiffiles') + print(base_path) + frame1 = create_frames[0][:, :, :, 0] + frame2 = create_frames[1][:, :, :, 0] + tif_path1 = base_path/'tif_image1.tif' + tif_path2 = base_path/'tif_image2.tif' + imwrite(str(tif_path1), frame1, photometric='rgb') + imwrite(str(tif_path2), frame2, photometric='rgb') + return tif_path1, tif_path2 diff --git a/test/test_utils_imageseries.py b/test/test_utils_imageseries.py new file mode 100644 index 00000000..151bf99e --- /dev/null +++ b/test/test_utils_imageseries.py @@ -0,0 +1,31 @@ +from nwbwidgets.utils.imageseries import get_frame_count, get_frame, get_frame_shape +from .fixtures import * + +def test_movie_frame(create_movie_files, movie_shape): + frame = get_frame(create_movie_files[0], 0) + assert frame.shape == movie_shape + + +def test_tif_frame(create_tif_files, movie_shape): + frame = get_frame(str(create_tif_files[0]), 0) + assert frame.shape == movie_shape + + +def test_movie_no_frames(create_movie_files, movie_no_frames): + count = get_frame_count(create_movie_files[0]) + assert count == movie_no_frames + + +def test_tif_no_frames(create_tif_files, movie_no_frames): + count = get_frame_count(create_tif_files[0]) + assert count == movie_no_frames + + +def test_movie_frame_shape(create_movie_files, movie_shape): + shape = get_frame_shape(create_movie_files[0]) + assert shape == movie_shape + + +def test_tif_frame_shape(create_tif_files, movie_shape): + shape = get_frame_shape(create_tif_files[0]) + assert shape == movie_shape From 0288883bce2fba3e5e4d0ae5171f1056408842f9 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Sat, 8 Jan 2022 18:53:31 +0530 Subject: [PATCH 25/49] cv2 dependency --- requirements-dev.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 55fcfcfb..2d602513 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,4 +8,5 @@ ndx_grayscalevolume plotly tqdm>=4.36.0 ndx-spectrum -aiohttp \ No newline at end of file +aiohttp +cv2 \ No newline at end of file From 4d785c236d511bf61d10f0b6beff063425fd0805 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Sat, 8 Jan 2022 18:55:23 +0530 Subject: [PATCH 26/49] cv2 dependency --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 2d602513..131a63a1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,4 +9,4 @@ plotly tqdm>=4.36.0 ndx-spectrum aiohttp -cv2 \ No newline at end of file +opencv-python \ No newline at end of file From 7dda8db118d16d2d69b5b947108a6b4301c6bfb1 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Sat, 8 Jan 2022 19:01:52 +0530 Subject: [PATCH 27/49] neurodataviz spec not required --- test/test_ophys.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_ophys.py b/test/test_ophys.py index fd91e6a4..325a9b12 100644 --- a/test/test_ophys.py +++ b/test/test_ophys.py @@ -112,7 +112,7 @@ def setUpClass(self): self.df_over_f = DfOverF(rrs) def test_show_two_photon_series(self): - wid = TwoPhotonSeriesWidget(self.image_series, default_neurodata_vis_spec) + wid = TwoPhotonSeriesWidget(self.image_series) assert isinstance(wid, widgets.Widget) wid.time_slider.value = 50.0 @@ -125,7 +125,7 @@ def test_show_3d_two_photon_series(self): rate=1.0, unit="n.a", ) - wid = TwoPhotonSeriesWidget(image_series3, default_neurodata_vis_spec) + wid = TwoPhotonSeriesWidget(image_series3) assert isinstance(wid, widgets.Widget) wid.time_slider.value = 50.0 From f31e312a6085452355fa6b377c50b645871d4c35 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Sat, 8 Jan 2022 19:05:45 +0530 Subject: [PATCH 28/49] neurodataviz spec not required --- nwbwidgets/ophys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nwbwidgets/ophys.py b/nwbwidgets/ophys.py index 6ec2c97b..1894234c 100644 --- a/nwbwidgets/ophys.py +++ b/nwbwidgets/ophys.py @@ -27,7 +27,7 @@ class TwoPhotonSeriesWidget(ImageSeriesWidget): """Widget showing Image stack recorded over time from 2-photon microscope.""" - def __init__(self, indexed_timeseries: TwoPhotonSeries, neurodata_vis_spec: dict): + def __init__(self, indexed_timeseries: TwoPhotonSeries, neurodata_vis_spec: dict = None): super().__init__(indexed_timeseries, neurodata_vis_spec) From f73fde31412e86ccec6f202f21444021de2754b8 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Sun, 9 Jan 2022 18:31:04 +0530 Subject: [PATCH 29/49] return variable novie frames number --- test/fixtures.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/fixtures.py b/test/fixtures.py index 90329d01..585186fd 100644 --- a/test/fixtures.py +++ b/test/fixtures.py @@ -11,18 +11,18 @@ def movie_fps(): @pytest.fixture(scope="session") def movie_shape(): - return (300, 400, 3) + return (30, 40, 3) @pytest.fixture(scope="session") def movie_no_frames(): - return 30 + return 10, 15 @pytest.fixture(scope="session") def create_frames(movie_no_frames, movie_shape): - mov_ar1 = np.random.randint(0, 255, size=[*movie_shape, movie_no_frames], dtype="uint8") - mov_ar2 = np.random.randint(0, 255, size=[*movie_shape, movie_no_frames], dtype="uint8") + mov_ar1 = np.random.randint(0, 255, size=[*movie_shape, movie_no_frames[0]], dtype="uint8") + mov_ar2 = np.random.randint(0, 255, size=[*movie_shape, movie_no_frames[1]], dtype="uint8") return mov_ar1, mov_ar2 From 798a528820b854a210c6136d5415e9e4175bee6a Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Sun, 9 Jan 2022 18:31:28 +0530 Subject: [PATCH 30/49] indentation --- nwbwidgets/ophys.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nwbwidgets/ophys.py b/nwbwidgets/ophys.py index 1894234c..1a5bf829 100644 --- a/nwbwidgets/ophys.py +++ b/nwbwidgets/ophys.py @@ -28,8 +28,7 @@ class TwoPhotonSeriesWidget(ImageSeriesWidget): """Widget showing Image stack recorded over time from 2-photon microscope.""" def __init__(self, indexed_timeseries: TwoPhotonSeries, neurodata_vis_spec: dict = None): - super().__init__(indexed_timeseries, - neurodata_vis_spec) + super().__init__(indexed_timeseries,neurodata_vis_spec) def show_df_over_f(df_over_f: DfOverF, neurodata_vis_spec: dict): From c8a622f6539b03ebc32114da0d8d766db05cfd4b Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Sun, 9 Jan 2022 18:31:43 +0530 Subject: [PATCH 31/49] add tests for ImageSeries --- test/test_image.py | 55 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/test/test_image.py b/test/test_image.py index 65315cc2..41b9c9cb 100644 --- a/test/test_image.py +++ b/test/test_image.py @@ -6,10 +6,12 @@ show_grayscale_image, show_index_series, show_image_series, + ImageSeriesWidget ) from nwbwidgets.view import default_neurodata_vis_spec from pynwb.base import TimeSeries from pynwb.image import RGBImage, GrayscaleImage, IndexSeries, ImageSeries +import plotly.graph_objects as go def test_show_rbg_image(): @@ -54,3 +56,56 @@ def test_show_image_series(): assert isinstance( show_image_series(image_series, default_neurodata_vis_spec), widgets.Widget ) + + +def test_image_series_widget_data_2d(): + data = np.random.randint(0,255,size=[10,30,40]) + image_series = ImageSeries(name="Image Series", data=data, rate=1.0, unit='n.a.') + wd = ImageSeriesWidget(image_series) + assert isinstance(wd.figure, go.FigureWidget) + assert wd.time_slider.min == 0.0 + assert wd.time_slider.max == 9.0 + + +def test_image_series_widget_data_3d(): + data = np.random.randint(0,255,size=[10,30,40,5]) + image_series = ImageSeries(name="Image Series", data=data, rate=1.0, unit='n.a.') + wd = ImageSeriesWidget(image_series) + assert isinstance(wd.figure, widgets.Output) + assert wd.time_slider.min == 0.0 + assert wd.time_slider.max == 9.0 + + +def test_image_series_widget_external_file_tif(create_tif_files, movie_no_frames): + image_series = ImageSeries(name="Image Series", external_file=create_tif_files, + rate=1.0, unit='n.a.') + wd = ImageSeriesWidget(image_series) + assert isinstance(wd.figure, go.FigureWidget) + assert wd.time_slider.max == movie_no_frames[0] + assert wd.time_slider.min == 0.0 + assert wd.file_selector.value == create_tif_files[0] + wd.file_selector.value = create_tif_files[1] + assert wd.time_slider.min == 0.0 + assert wd.time_slider.max == movie_no_frames[1] + +def test_image_series_widget_external_file_single(create_tif_files, movie_no_frames): + image_series = ImageSeries(name="Image Series", external_file=create_tif_files[:1], + rate=1.0, unit='n.a.') + wd = ImageSeriesWidget(image_series) + assert isinstance(wd.figure, go.FigureWidget) + assert wd.time_slider.max == movie_no_frames[0] + assert wd.time_slider.min == 0.0 + assert wd.file_selector is None + + +def test_image_series_widget_external_file_video(create_movie_files, movie_no_frames): + image_series = ImageSeries(name="Image Series", external_file=create_movie_files, + rate=1.0, unit='n.a.') + wd = ImageSeriesWidget(image_series) + assert isinstance(wd.figure, go.FigureWidget) + assert wd.time_slider.max == movie_no_frames[0] + assert wd.time_slider.min == 0.0 + assert wd.file_selector.value == create_movie_files[0] + wd.file_selector.value = create_movie_files[1] + assert wd.time_slider.min == 0.0 + assert wd.time_slider.max == movie_no_frames[1] From aa04d9f0de64b178b335a017306c9b2127b531a4 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Sun, 9 Jan 2022 18:32:01 +0530 Subject: [PATCH 32/49] varible movie frames --- test/test_utils_imageseries.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/test_utils_imageseries.py b/test/test_utils_imageseries.py index 151bf99e..0df3fb20 100644 --- a/test/test_utils_imageseries.py +++ b/test/test_utils_imageseries.py @@ -1,5 +1,4 @@ from nwbwidgets.utils.imageseries import get_frame_count, get_frame, get_frame_shape -from .fixtures import * def test_movie_frame(create_movie_files, movie_shape): frame = get_frame(create_movie_files[0], 0) @@ -13,12 +12,12 @@ def test_tif_frame(create_tif_files, movie_shape): def test_movie_no_frames(create_movie_files, movie_no_frames): count = get_frame_count(create_movie_files[0]) - assert count == movie_no_frames + assert count == movie_no_frames[0] def test_tif_no_frames(create_tif_files, movie_no_frames): count = get_frame_count(create_tif_files[0]) - assert count == movie_no_frames + assert count == movie_no_frames[0] def test_movie_frame_shape(create_movie_files, movie_shape): From d29ea9b53f709a362d9943272ef2ba6f980e1f52 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Sun, 9 Jan 2022 18:41:21 +0530 Subject: [PATCH 33/49] import specific fixtures --- test/test_image.py | 1 + test/test_utils_imageseries.py | 1 + 2 files changed, 2 insertions(+) diff --git a/test/test_image.py b/test/test_image.py index 41b9c9cb..fa9422fc 100644 --- a/test/test_image.py +++ b/test/test_image.py @@ -12,6 +12,7 @@ from pynwb.base import TimeSeries from pynwb.image import RGBImage, GrayscaleImage, IndexSeries, ImageSeries import plotly.graph_objects as go +from .fixtures import create_movie_files, create_tif_files, movie_no_frames def test_show_rbg_image(): diff --git a/test/test_utils_imageseries.py b/test/test_utils_imageseries.py index 0df3fb20..05d82f13 100644 --- a/test/test_utils_imageseries.py +++ b/test/test_utils_imageseries.py @@ -1,4 +1,5 @@ from nwbwidgets.utils.imageseries import get_frame_count, get_frame, get_frame_shape +from .fixtures import create_movie_files, movie_shape, movie_no_frames, create_tif_files def test_movie_frame(create_movie_files, movie_shape): frame = get_frame(create_movie_files[0], 0) From 5509487d18dc5db6da81871362320f8076866d08 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Mon, 10 Jan 2022 13:08:56 +0530 Subject: [PATCH 34/49] kwargs update for videowriter --- test/fixtures.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/fixtures.py b/test/fixtures.py index 585186fd..c2fffea5 100644 --- a/test/fixtures.py +++ b/test/fixtures.py @@ -35,12 +35,18 @@ def create_movie_files(tmp_path_factory, create_frames, movie_fps): mov_array1, mov_array2 = create_frames movie_shape = mov_array1.shape[:2] no_frames = mov_array1.shape[-1] - cap_mp4 = cv2.VideoWriter(str(mov_ar1_path), - cv2.VideoWriter_fourcc(*'mp4v'), - movie_fps, movie_shape) - cap_avi = cv2.VideoWriter(str(mov_ar2_path), - cv2.VideoWriter_fourcc(*'DIVX'), - movie_fps, movie_shape) + cap_mp4 = cv2.VideoWriter(filename=str(mov_ar1_path), + apiPreference=None, + fourcc=cv2.VideoWriter_fourcc(*'mp4v'), + fps=movie_fps, + frameSize=movie_shape, + params=None) + cap_avi = cv2.VideoWriter(filename=str(mov_ar2_path), + apiPreference=None, + fourcc=cv2.VideoWriter_fourcc(*'DIVX'), + fps=movie_fps, + frameSize=movie_shape, + params=None) for frame_no in range(no_frames): cap_mp4.write(mov_array1[:,:,:,frame_no]) cap_avi.write(mov_array2[:,:,:,frame_no]) From b5af95cb05280c0b3579eaa04ba3ebc6cedb9a6f Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Mon, 10 Jan 2022 13:45:10 +0530 Subject: [PATCH 35/49] fix video writer --- test/fixtures.py | 14 +++++++------- test/test_utils_imageseries.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/fixtures.py b/test/fixtures.py index c2fffea5..d70d0c91 100644 --- a/test/fixtures.py +++ b/test/fixtures.py @@ -33,23 +33,23 @@ def create_movie_files(tmp_path_factory, create_frames, movie_fps): mov_ar1_path = base_path/'movie1.avi' mov_ar2_path = base_path/'movie.avi' mov_array1, mov_array2 = create_frames - movie_shape = mov_array1.shape[:2] + movie_shape = mov_array1.shape[1::-1] no_frames = mov_array1.shape[-1] cap_mp4 = cv2.VideoWriter(filename=str(mov_ar1_path), apiPreference=None, - fourcc=cv2.VideoWriter_fourcc(*'mp4v'), + fourcc=cv2.VideoWriter_fourcc("M", "J", "P", "G"), fps=movie_fps, frameSize=movie_shape, params=None) cap_avi = cv2.VideoWriter(filename=str(mov_ar2_path), apiPreference=None, - fourcc=cv2.VideoWriter_fourcc(*'DIVX'), + fourcc=cv2.VideoWriter_fourcc("M", "J", "P", "G"), fps=movie_fps, frameSize=movie_shape, params=None) for frame_no in range(no_frames): - cap_mp4.write(mov_array1[:,:,:,frame_no]) - cap_avi.write(mov_array2[:,:,:,frame_no]) + cap_mp4.write(mov_array1[:,:,:,frame_no].squeeze()) + cap_avi.write(mov_array2[:,:,:,frame_no].squeeze()) cap_mp4.release() cap_avi.release() @@ -60,8 +60,8 @@ def create_movie_files(tmp_path_factory, create_frames, movie_fps): def create_tif_files(tmp_path_factory, create_frames): base_path = tmp_path_factory.mktemp('tiffiles') print(base_path) - frame1 = create_frames[0][:, :, :, 0] - frame2 = create_frames[1][:, :, :, 0] + frame1 = create_frames[0].transpose([3,0,1,2]) + frame2 = create_frames[1].transpose([3,0,1,2]) tif_path1 = base_path/'tif_image1.tif' tif_path2 = base_path/'tif_image2.tif' imwrite(str(tif_path1), frame1, photometric='rgb') diff --git a/test/test_utils_imageseries.py b/test/test_utils_imageseries.py index 05d82f13..045c053b 100644 --- a/test/test_utils_imageseries.py +++ b/test/test_utils_imageseries.py @@ -1,5 +1,5 @@ from nwbwidgets.utils.imageseries import get_frame_count, get_frame, get_frame_shape -from .fixtures import create_movie_files, movie_shape, movie_no_frames, create_tif_files +from .fixtures import * def test_movie_frame(create_movie_files, movie_shape): frame = get_frame(create_movie_files[0], 0) From 0213ccc54697992aaf2f320f3320e822d04cb9ed Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Mon, 10 Jan 2022 13:52:01 +0530 Subject: [PATCH 36/49] movie frames loop bug fix --- test/fixtures.py | 10 +++++----- test/test_image.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/fixtures.py b/test/fixtures.py index d70d0c91..a603d45f 100644 --- a/test/fixtures.py +++ b/test/fixtures.py @@ -34,7 +34,6 @@ def create_movie_files(tmp_path_factory, create_frames, movie_fps): mov_ar2_path = base_path/'movie.avi' mov_array1, mov_array2 = create_frames movie_shape = mov_array1.shape[1::-1] - no_frames = mov_array1.shape[-1] cap_mp4 = cv2.VideoWriter(filename=str(mov_ar1_path), apiPreference=None, fourcc=cv2.VideoWriter_fourcc("M", "J", "P", "G"), @@ -47,13 +46,14 @@ def create_movie_files(tmp_path_factory, create_frames, movie_fps): fps=movie_fps, frameSize=movie_shape, params=None) - for frame_no in range(no_frames): - cap_mp4.write(mov_array1[:,:,:,frame_no].squeeze()) - cap_avi.write(mov_array2[:,:,:,frame_no].squeeze()) + for frame_no in range(mov_array1.shape[-1]): + cap_mp4.write(mov_array1[:,:,:,frame_no]) + for frame_no in range(mov_array2.shape[-1]): + cap_avi.write(mov_array2[:,:,:,frame_no]) cap_mp4.release() cap_avi.release() - return mov_ar2_path, mov_ar2_path + return mov_ar1_path, mov_ar2_path @pytest.fixture(scope="session") diff --git a/test/test_image.py b/test/test_image.py index fa9422fc..f2f20da6 100644 --- a/test/test_image.py +++ b/test/test_image.py @@ -12,7 +12,7 @@ from pynwb.base import TimeSeries from pynwb.image import RGBImage, GrayscaleImage, IndexSeries, ImageSeries import plotly.graph_objects as go -from .fixtures import create_movie_files, create_tif_files, movie_no_frames +from .fixtures import * def test_show_rbg_image(): From 973704742f6351a9e7a0b4ecb9b2ca4abd715618 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Mon, 10 Jan 2022 13:52:47 +0530 Subject: [PATCH 37/49] rename fixtures.py to fixtures.py --- test/{fixtures.py => fixtures_imageseries.py} | 0 test/test_image.py | 2 +- test/test_utils_imageseries.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename test/{fixtures.py => fixtures_imageseries.py} (100%) diff --git a/test/fixtures.py b/test/fixtures_imageseries.py similarity index 100% rename from test/fixtures.py rename to test/fixtures_imageseries.py diff --git a/test/test_image.py b/test/test_image.py index f2f20da6..f4e1dab4 100644 --- a/test/test_image.py +++ b/test/test_image.py @@ -12,7 +12,7 @@ from pynwb.base import TimeSeries from pynwb.image import RGBImage, GrayscaleImage, IndexSeries, ImageSeries import plotly.graph_objects as go -from .fixtures import * +from .fixtures_imageseries import * def test_show_rbg_image(): diff --git a/test/test_utils_imageseries.py b/test/test_utils_imageseries.py index 045c053b..1ede6f4f 100644 --- a/test/test_utils_imageseries.py +++ b/test/test_utils_imageseries.py @@ -1,5 +1,5 @@ from nwbwidgets.utils.imageseries import get_frame_count, get_frame, get_frame_shape -from .fixtures import * +from .fixtures_imageseries import * def test_movie_frame(create_movie_files, movie_shape): frame = get_frame(create_movie_files[0], 0) From 8e72ae96033c16009e718c501f7c62666f6ed2c8 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Tue, 11 Jan 2022 19:39:00 +0530 Subject: [PATCH 38/49] add all callbacks as class methods --- nwbwidgets/image.py | 92 +++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 49 deletions(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index c4d2e38b..0d0fe641 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -33,10 +33,10 @@ def __init__( self.imageseries = imageseries self.figure = None self.time_slider = foreign_time_slider + self.external_file = None + self.file_selector = None if imageseries.external_file is not None: - - # set time slider: tmax = ( imageseries.starting_time + get_frame_count(imageseries.external_file[0]) / imageseries.rate @@ -48,60 +48,20 @@ def __init__( orientation="horizontal", description="time(s)", ) - external_file = imageseries.external_file[0] - self.file_selector = None + self.external_file = imageseries.external_file[0] # set file selector: if len(imageseries.external_file) > 1: self.file_selector = widgets.Dropdown(options=imageseries.external_file) - external_file = self.file_selector.value - - def update_time_slider(value): - path_ext_file = value["new"] - # Read first frame - nonlocal external_file - external_file = path_ext_file - tmax = ( - imageseries.starting_time - + get_frame_count(path_ext_file) / imageseries.rate - ) - tmin = 0 - self.time_slider.max = tmax - self.time_slider.min = tmin - self._set_figure_external(tmin, external_file, tmin) - - self.file_selector.observe(update_time_slider, names="value") - - # set time slider callbacks: - def change_fig(change): - time = change["new"] - starting_time = change["owner"].min - self._set_figure_external(time, external_file, starting_time) - - self.time_slider.observe(change_fig, names="value") + self.external_file = self.file_selector.value + self.file_selector.observe(self._update_time_slider, names="value") + + self.time_slider.observe(self._time_slider_callback_external, names="value") self._set_figure_external( - imageseries.starting_time, external_file, imageseries.starting_time + imageseries.starting_time, self.external_file, imageseries.starting_time ) # set children: self.children = self.get_children(self.file_selector) else: - if len(imageseries.data.shape) == 3: - self._set_figure_2d(0) - - def time_slider_callback(change): - frame_number = self.time_to_index(change["new"]) - self._set_figure_2d(frame_number) - - elif len(imageseries.data.shape) == 4: - self._set_figure_3d(0) - - def time_slider_callback(change): - frame_number = self.time_to_index(change["new"]) - self._set_figure_3d(frame_number) - - else: - raise NotImplementedError - - # creat time window controller: tmin = get_timeseries_mint(imageseries) tmax = get_timeseries_maxt(imageseries) if self.time_slider is None: @@ -112,9 +72,43 @@ def time_slider_callback(change): orientation="horizontal", description="time(s)", ) - self.time_slider.observe(time_slider_callback, names="value") + if len(imageseries.data.shape) == 3: + self._set_figure_2d(0) + self.time_slider.observe(self.time_slider_callback_2d, names="value") + + elif len(imageseries.data.shape) == 4: + self._set_figure_3d(0) + self.time_slider.observe(self._time_slider_callback_3d, names="value") + else: + raise NotImplementedError self.children = self.get_children() + def _time_slider_callback_2d(self, change): + frame_number = self.time_to_index(change["new"]) + self._set_figure_2d(frame_number) + + def _time_slider_callback_3d(self, change): + frame_number = self.time_to_index(change["new"]) + self._set_figure_3d(frame_number) + + def _time_slider_callback_external(self, change): + time = change["new"] + starting_time = change["owner"].min + self._set_figure_external(time, self.external_file, starting_time) + + def _update_time_slider(self, value): + path_ext_file = value["new"] + # Read first frame + self.external_file = path_ext_file + tmax = ( + self.imageseries.starting_time + + get_frame_count(path_ext_file)/self.imageseries.rate + ) + tmin = 0 + self.time_slider.max = tmax + self.time_slider.min = tmin + self._set_figure_external(tmin, self.external_file, tmin) + def _set_figure_3d(self, frame_number): import ipyvolume.pylab as p3 From 9bb4cf01e67b2f82b32b70c0fa2cdf1da7e41c1c Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Tue, 11 Jan 2022 21:33:26 +0530 Subject: [PATCH 39/49] time slider spans frames if video file selected is other than default --- nwbwidgets/image.py | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index 0d0fe641..12567f3c 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -24,10 +24,10 @@ class ImageSeriesWidget(widgets.VBox): """Widget showing ImageSeries.""" def __init__( - self, - imageseries: ImageSeries, - foreign_time_slider: widgets.FloatSlider = None, - neurodata_vis_spec: dict = None, + self, + imageseries: ImageSeries, + foreign_time_slider: widgets.FloatSlider = None, + neurodata_vis_spec: dict = None, ): super().__init__() self.imageseries = imageseries @@ -38,8 +38,8 @@ def __init__( if imageseries.external_file is not None: tmax = ( - imageseries.starting_time - + get_frame_count(imageseries.external_file[0]) / imageseries.rate + imageseries.starting_time + + get_frame_count(imageseries.external_file[0])/imageseries.rate ) if self.time_slider is None: self.time_slider = widgets.FloatSlider( @@ -100,14 +100,26 @@ def _update_time_slider(self, value): path_ext_file = value["new"] # Read first frame self.external_file = path_ext_file - tmax = ( - self.imageseries.starting_time - + get_frame_count(path_ext_file)/self.imageseries.rate - ) - tmin = 0 - self.time_slider.max = tmax - self.time_slider.min = tmin - self._set_figure_external(tmin, self.external_file, tmin) + max_frame_count = get_frame_count(path_ext_file) + # if first video file selected, keep time slider else make frame slider out of time slider: + if self.file_selector.options.index(path_ext_file) == 0: + tmax = ( + self.imageseries.starting_time + + get_frame_count(path_ext_file)/self.imageseries.rate + ) + self.time_slider.max = tmax + self.time_slider.min = self.imageseries.starting_time + self.time_slider.description = "time (s)" + self.time_slider.observe(self._time_slider_callback_external, names="value") + else: + # set time slider to span the frames instead of time: + self.time_slider.max = max_frame_count + self.time_slider.min = 0 + self.time_slider.description = "frame no" + self.time_slider.observe( + lambda change: self._set_figure_from_frame(change["new"], path_ext_file), + names="value") + self._set_figure_from_frame(max_frame_count, self.external_file) def _set_figure_3d(self, frame_number): import ipyvolume.pylab as p3 @@ -132,6 +144,9 @@ def _set_figure_2d(self, frame_number): def _set_figure_external(self, time, ext_file_path, starting_time): frame_number = self.time_to_index(time, starting_time) + self._set_figure_from_frame(frame_number, ext_file_path) + + def _set_figure_from_frame(self, frame_number, ext_file_path): data = get_frame(ext_file_path, frame_number) if self.figure is None: self.figure = go.FigureWidget(data=dict(type="image", z=data)) From 3add69ade68d62a7790abeac45f538e11c7ea7dc Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Tue, 11 Jan 2022 21:38:24 +0530 Subject: [PATCH 40/49] bug fix --- nwbwidgets/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index 12567f3c..146d115b 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -74,7 +74,7 @@ def __init__( ) if len(imageseries.data.shape) == 3: self._set_figure_2d(0) - self.time_slider.observe(self.time_slider_callback_2d, names="value") + self.time_slider.observe(self._time_slider_callback_2d, names="value") elif len(imageseries.data.shape) == 4: self._set_figure_3d(0) @@ -119,7 +119,7 @@ def _update_time_slider(self, value): self.time_slider.observe( lambda change: self._set_figure_from_frame(change["new"], path_ext_file), names="value") - self._set_figure_from_frame(max_frame_count, self.external_file) + self._set_figure_from_frame(0, self.external_file) def _set_figure_3d(self, frame_number): import ipyvolume.pylab as p3 From ae8e9cc42181bc6a21ea2d7786180cfd27b4eb7c Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Wed, 12 Jan 2022 11:41:11 +0530 Subject: [PATCH 41/49] linking foreign time slider to timeslider --- nwbwidgets/image.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index 146d115b..70d68541 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -32,7 +32,7 @@ def __init__( super().__init__() self.imageseries = imageseries self.figure = None - self.time_slider = foreign_time_slider + self.foreign_time_slider = foreign_time_slider self.external_file = None self.file_selector = None @@ -41,13 +41,13 @@ def __init__( imageseries.starting_time + get_frame_count(imageseries.external_file[0])/imageseries.rate ) - if self.time_slider is None: - self.time_slider = widgets.FloatSlider( - min=imageseries.starting_time, - max=tmax, - orientation="horizontal", - description="time(s)", - ) + self.time_slider = widgets.FloatSlider( + min=imageseries.starting_time, + max=tmax, + orientation="horizontal", + description="time(s)", + continuous_update=False, + ) self.external_file = imageseries.external_file[0] # set file selector: if len(imageseries.external_file) > 1: @@ -64,14 +64,14 @@ def __init__( else: tmin = get_timeseries_mint(imageseries) tmax = get_timeseries_maxt(imageseries) - if self.time_slider is None: - self.time_slider = widgets.FloatSlider( - value=tmin, - min=tmin, - max=tmax, - orientation="horizontal", - description="time(s)", - ) + self.time_slider = widgets.FloatSlider( + value=tmin, + min=tmin, + max=tmax, + orientation="horizontal", + description="time(s)", + continuous_update=False, + ) if len(imageseries.data.shape) == 3: self._set_figure_2d(0) self.time_slider.observe(self._time_slider_callback_2d, names="value") @@ -82,6 +82,9 @@ def __init__( else: raise NotImplementedError self.children = self.get_children() + if self.foreign_time_slider is not None: + _ = widgets.jslink((self.foreign_time_slider, "value"), + (self.time_slider, "value")) def _time_slider_callback_2d(self, change): frame_number = self.time_to_index(change["new"]) From c2e2be016b9452c0087401c39c96f1ff61a0e17f Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Wed, 12 Jan 2022 18:26:58 +0530 Subject: [PATCH 42/49] using px for faster plotting --- nwbwidgets/image.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index 70d68541..5e2a4066 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -4,6 +4,7 @@ import matplotlib.pyplot as plt import numpy as np import plotly.graph_objects as go +import plotly.express as px import pynwb from ipywidgets import widgets, Layout from pynwb.image import GrayscaleImage, ImageSeries, RGBImage @@ -152,12 +153,14 @@ def _set_figure_external(self, time, ext_file_path, starting_time): def _set_figure_from_frame(self, frame_number, ext_file_path): data = get_frame(ext_file_path, frame_number) if self.figure is None: - self.figure = go.FigureWidget(data=dict(type="image", z=data)) + img = px.imshow(data, binary_string=True) + self.figure = go.FigureWidget(img) else: self._add_fig_trace(data, frame_number) def _add_fig_trace(self, img_data: np.ndarray, index): - self.figure.data[0]["z"] = img_data + img = px.imshow(img_data, binary_string=True) + self.figure.for_each_trace(lambda trace: trace.update(img.data[0])) self.figure.layout.title = f"Frame no: {index}" def time_to_index(self, time, starting_time=None): From fe7d1821e74abe11c45df6d128346d8166e193dd Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Wed, 12 Jan 2022 18:30:18 +0530 Subject: [PATCH 43/49] black --- nwbwidgets/image.py | 28 ++++++++++++++++------------ nwbwidgets/ophys.py | 22 ++++++++++++---------- test/test_image.py | 32 ++++++++++++++++++-------------- 3 files changed, 46 insertions(+), 36 deletions(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index 5e2a4066..7e80150b 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -25,10 +25,10 @@ class ImageSeriesWidget(widgets.VBox): """Widget showing ImageSeries.""" def __init__( - self, - imageseries: ImageSeries, - foreign_time_slider: widgets.FloatSlider = None, - neurodata_vis_spec: dict = None, + self, + imageseries: ImageSeries, + foreign_time_slider: widgets.FloatSlider = None, + neurodata_vis_spec: dict = None, ): super().__init__() self.imageseries = imageseries @@ -39,8 +39,8 @@ def __init__( if imageseries.external_file is not None: tmax = ( - imageseries.starting_time - + get_frame_count(imageseries.external_file[0])/imageseries.rate + imageseries.starting_time + + get_frame_count(imageseries.external_file[0]) / imageseries.rate ) self.time_slider = widgets.FloatSlider( min=imageseries.starting_time, @@ -84,8 +84,9 @@ def __init__( raise NotImplementedError self.children = self.get_children() if self.foreign_time_slider is not None: - _ = widgets.jslink((self.foreign_time_slider, "value"), - (self.time_slider, "value")) + _ = widgets.jslink( + (self.foreign_time_slider, "value"), (self.time_slider, "value") + ) def _time_slider_callback_2d(self, change): frame_number = self.time_to_index(change["new"]) @@ -108,8 +109,8 @@ def _update_time_slider(self, value): # if first video file selected, keep time slider else make frame slider out of time slider: if self.file_selector.options.index(path_ext_file) == 0: tmax = ( - self.imageseries.starting_time - + get_frame_count(path_ext_file)/self.imageseries.rate + self.imageseries.starting_time + + get_frame_count(path_ext_file) / self.imageseries.rate ) self.time_slider.max = tmax self.time_slider.min = self.imageseries.starting_time @@ -121,8 +122,11 @@ def _update_time_slider(self, value): self.time_slider.min = 0 self.time_slider.description = "frame no" self.time_slider.observe( - lambda change: self._set_figure_from_frame(change["new"], path_ext_file), - names="value") + lambda change: self._set_figure_from_frame( + change["new"], path_ext_file + ), + names="value", + ) self._set_figure_from_frame(0, self.external_file) def _set_figure_3d(self, frame_number): diff --git a/nwbwidgets/ophys.py b/nwbwidgets/ophys.py index 1a5bf829..2d575c55 100644 --- a/nwbwidgets/ophys.py +++ b/nwbwidgets/ophys.py @@ -27,8 +27,10 @@ class TwoPhotonSeriesWidget(ImageSeriesWidget): """Widget showing Image stack recorded over time from 2-photon microscope.""" - def __init__(self, indexed_timeseries: TwoPhotonSeries, neurodata_vis_spec: dict = None): - super().__init__(indexed_timeseries,neurodata_vis_spec) + def __init__( + self, indexed_timeseries: TwoPhotonSeries, neurodata_vis_spec: dict = None + ): + super().__init__(indexed_timeseries, neurodata_vis_spec) def show_df_over_f(df_over_f: DfOverF, neurodata_vis_spec: dict): @@ -155,13 +157,13 @@ def update_fig(self, color_by): data.showlegend = False def show_plane_segmentation_2d( - self, - color_wheel: list = color_wheel, - color_by: str = None, - threshold: float = 0.01, - fig: go.Figure = None, - width: int = 600, - ref_image=None, + self, + color_wheel: list = color_wheel, + color_by: str = None, + threshold: float = 0.01, + fig: go.Figure = None, + width: int = 600, + ref_image=None, ): """ @@ -292,6 +294,6 @@ def show_grayscale_volume(vol: GrayscaleVolume, neurodata_vis_spec: dict): class RoiResponseSeriesWidget(BaseGroupedTraceWidget): def __init__( - self, roi_response_series: RoiResponseSeries, neurodata_vis_spec=None, **kwargs + self, roi_response_series: RoiResponseSeries, neurodata_vis_spec=None, **kwargs ): super().__init__(roi_response_series, "rois", **kwargs) diff --git a/test/test_image.py b/test/test_image.py index f4e1dab4..d21fb25b 100644 --- a/test/test_image.py +++ b/test/test_image.py @@ -6,7 +6,7 @@ show_grayscale_image, show_index_series, show_image_series, - ImageSeriesWidget + ImageSeriesWidget, ) from nwbwidgets.view import default_neurodata_vis_spec from pynwb.base import TimeSeries @@ -35,14 +35,14 @@ def test_show_index_series(): name="Index Series time data", data=np.random.rand(800).reshape((8, 10, 10)), rate=1.0, - unit='na', + unit="na", ) index_series = IndexSeries( name="Sample Index Series", data=data, indexed_timeseries=indexed_timeseries, rate=1.0, - unit='n.a.', + unit="n.a.", ) assert isinstance( @@ -52,7 +52,7 @@ def test_show_index_series(): def test_show_image_series(): data = np.random.rand(800).reshape((8, 10, 10)) - image_series = ImageSeries(name="Image Series", data=data, rate=1.0, unit='n.a.') + image_series = ImageSeries(name="Image Series", data=data, rate=1.0, unit="n.a.") assert isinstance( show_image_series(image_series, default_neurodata_vis_spec), widgets.Widget @@ -60,8 +60,8 @@ def test_show_image_series(): def test_image_series_widget_data_2d(): - data = np.random.randint(0,255,size=[10,30,40]) - image_series = ImageSeries(name="Image Series", data=data, rate=1.0, unit='n.a.') + data = np.random.randint(0, 255, size=[10, 30, 40]) + image_series = ImageSeries(name="Image Series", data=data, rate=1.0, unit="n.a.") wd = ImageSeriesWidget(image_series) assert isinstance(wd.figure, go.FigureWidget) assert wd.time_slider.min == 0.0 @@ -69,8 +69,8 @@ def test_image_series_widget_data_2d(): def test_image_series_widget_data_3d(): - data = np.random.randint(0,255,size=[10,30,40,5]) - image_series = ImageSeries(name="Image Series", data=data, rate=1.0, unit='n.a.') + data = np.random.randint(0, 255, size=[10, 30, 40, 5]) + image_series = ImageSeries(name="Image Series", data=data, rate=1.0, unit="n.a.") wd = ImageSeriesWidget(image_series) assert isinstance(wd.figure, widgets.Output) assert wd.time_slider.min == 0.0 @@ -78,8 +78,9 @@ def test_image_series_widget_data_3d(): def test_image_series_widget_external_file_tif(create_tif_files, movie_no_frames): - image_series = ImageSeries(name="Image Series", external_file=create_tif_files, - rate=1.0, unit='n.a.') + image_series = ImageSeries( + name="Image Series", external_file=create_tif_files, rate=1.0, unit="n.a." + ) wd = ImageSeriesWidget(image_series) assert isinstance(wd.figure, go.FigureWidget) assert wd.time_slider.max == movie_no_frames[0] @@ -89,9 +90,11 @@ def test_image_series_widget_external_file_tif(create_tif_files, movie_no_frames assert wd.time_slider.min == 0.0 assert wd.time_slider.max == movie_no_frames[1] + def test_image_series_widget_external_file_single(create_tif_files, movie_no_frames): - image_series = ImageSeries(name="Image Series", external_file=create_tif_files[:1], - rate=1.0, unit='n.a.') + image_series = ImageSeries( + name="Image Series", external_file=create_tif_files[:1], rate=1.0, unit="n.a." + ) wd = ImageSeriesWidget(image_series) assert isinstance(wd.figure, go.FigureWidget) assert wd.time_slider.max == movie_no_frames[0] @@ -100,8 +103,9 @@ def test_image_series_widget_external_file_single(create_tif_files, movie_no_fra def test_image_series_widget_external_file_video(create_movie_files, movie_no_frames): - image_series = ImageSeries(name="Image Series", external_file=create_movie_files, - rate=1.0, unit='n.a.') + image_series = ImageSeries( + name="Image Series", external_file=create_movie_files, rate=1.0, unit="n.a." + ) wd = ImageSeriesWidget(image_series) assert isinstance(wd.figure, go.FigureWidget) assert wd.time_slider.max == movie_no_frames[0] From 85a0d0ea884d1b89f6d0878b21ab19c2f78e184f Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Thu, 13 Jan 2022 14:19:46 +0530 Subject: [PATCH 44/49] use self.get_frame --- nwbwidgets/image.py | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index 7e80150b..8138905e 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -89,8 +89,7 @@ def __init__( ) def _time_slider_callback_2d(self, change): - frame_number = self.time_to_index(change["new"]) - self._set_figure_2d(frame_number) + self._set_figure_from_time(change["new"][0]) def _time_slider_callback_3d(self, change): frame_number = self.time_to_index(change["new"]) @@ -99,7 +98,7 @@ def _time_slider_callback_3d(self, change): def _time_slider_callback_external(self, change): time = change["new"] starting_time = change["owner"].min - self._set_figure_external(time, self.external_file, starting_time) + self._set_figure_from_time(time, starting_time, self.external_file) def _update_time_slider(self, value): path_ext_file = value["new"] @@ -143,29 +142,19 @@ def _set_figure_3d(self, frame_number): with output: p3.show() - def _set_figure_2d(self, frame_number): - data = self.imageseries.data[frame_number].T - if self.figure is None: - self.figure = go.FigureWidget(data=dict(type="image", z=data)) - else: - self._add_fig_trace(data, frame_number) - - def _set_figure_external(self, time, ext_file_path, starting_time): + def _set_figure_from_time(self, time, starting_time, ext_file_path=None): frame_number = self.time_to_index(time, starting_time) self._set_figure_from_frame(frame_number, ext_file_path) - def _set_figure_from_frame(self, frame_number, ext_file_path): - data = get_frame(ext_file_path, frame_number) + def _set_figure_from_frame(self, frame_number, ext_file_path=None): + data = self.get_frame(frame_number, ext_file_path) if self.figure is None: img = px.imshow(data, binary_string=True) self.figure = go.FigureWidget(img) else: - self._add_fig_trace(data, frame_number) - - def _add_fig_trace(self, img_data: np.ndarray, index): - img = px.imshow(img_data, binary_string=True) - self.figure.for_each_trace(lambda trace: trace.update(img.data[0])) - self.figure.layout.title = f"Frame no: {index}" + img = px.imshow(data, binary_string=True) + self.figure.for_each_trace(lambda trace: trace.update(img.data[0])) + self.figure.layout.title = f"Frame no: {frame_number}" def time_to_index(self, time, starting_time=None): starting_time = ( @@ -182,9 +171,9 @@ def get_children(self, *widgets): set_widgets = [wid for wid in widgets if wid is not None] return [self.figure, self.time_slider, *set_widgets] - def get_frame(self, idx): - if self.imageseries.external_file is not None: - return get_frame(self.imageseries.external_file[0]) + def get_frame(self, idx, ext_file_path=None): + if ext_file_path is not None: + return get_frame(ext_file_path) else: return self.imageseries.data[idx].T From 3ea7f5878b39c3a9d2d01823fe299d57a8d5546c Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Thu, 13 Jan 2022 15:01:12 +0530 Subject: [PATCH 45/49] use video start_times, visible time slider --- nwbwidgets/image.py | 85 ++++++++++++++++----------------- nwbwidgets/utils/imageseries.py | 28 +++++++++++ 2 files changed, 68 insertions(+), 45 deletions(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index 8138905e..4fa080b9 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -1,6 +1,6 @@ from pathlib import Path from typing import Union - +from tqdm import tqdm import matplotlib.pyplot as plt import numpy as np import plotly.graph_objects as go @@ -11,13 +11,13 @@ from .base import fig2widget from .utils.cmaps import linear_transfer_function -from .utils.imageseries import get_frame_count, get_frame +from .utils.imageseries import get_frame_count, get_frame, get_fps from .utils.timeseries import ( get_timeseries_maxt, get_timeseries_mint, timeseries_time_to_ind, ) - +from .controllers.time_window_controllers import StartAndDurationController PathType = Union[str, Path] @@ -27,24 +27,22 @@ class ImageSeriesWidget(widgets.VBox): def __init__( self, imageseries: ImageSeries, - foreign_time_slider: widgets.FloatSlider = None, + foreign_start_duration_controller: StartAndDurationController = None, neurodata_vis_spec: dict = None, ): super().__init__() self.imageseries = imageseries self.figure = None - self.foreign_time_slider = foreign_time_slider + self.time_slider = None self.external_file = None self.file_selector = None + self.video_start_times = [] if imageseries.external_file is not None: - tmax = ( - imageseries.starting_time - + get_frame_count(imageseries.external_file[0]) / imageseries.rate - ) + self.video_start_times = self._get_video_start_times() self.time_slider = widgets.FloatSlider( - min=imageseries.starting_time, - max=tmax, + min=self.video_start_times[0], + max=self.video_start_times[1], orientation="horizontal", description="time(s)", continuous_update=False, @@ -57,11 +55,9 @@ def __init__( self.file_selector.observe(self._update_time_slider, names="value") self.time_slider.observe(self._time_slider_callback_external, names="value") - self._set_figure_external( - imageseries.starting_time, self.external_file, imageseries.starting_time + self._set_figure_from_time( + imageseries.starting_time, imageseries.starting_time, self.external_file, ) - # set children: - self.children = self.get_children(self.file_selector) else: tmin = get_timeseries_mint(imageseries) tmax = get_timeseries_maxt(imageseries) @@ -82,17 +78,34 @@ def __init__( self.time_slider.observe(self._time_slider_callback_3d, names="value") else: raise NotImplementedError - self.children = self.get_children() - if self.foreign_time_slider is not None: - _ = widgets.jslink( - (self.foreign_time_slider, "value"), (self.time_slider, "value") - ) + + # set visible time slider: + if foreign_start_duration_controller is None: + self.visible_time_slider = self.time_slider + else: + self.visible_time_slider = foreign_start_duration_controller + # link the value[0] to time_slider value + def _link_time_slider(change): + self.time_slider.value = change["new"][0] + self.visible_time_slider.observe(_link_time_slider, names="value") + self.children = self.get_children(self.file_selector) + + def _get_video_start_times(self): + if self.external_file is not None: + start_times=[self.imageseries.starting_time] + for file in tqdm(self.imageseries.external_file, + desc="retrieving video start times"): + fps = get_fps(file) + fps = fps if fps is not None else self.imageseries.rate + file_time_duration = get_frame_count(file) / fps + start_times.append(file_time_duration) + return np.cumsum(start_times) def _time_slider_callback_2d(self, change): self._set_figure_from_time(change["new"][0]) def _time_slider_callback_3d(self, change): - frame_number = self.time_to_index(change["new"]) + frame_number = self.time_to_index(change["new"][0]) self._set_figure_3d(frame_number) def _time_slider_callback_external(self, change): @@ -102,30 +115,12 @@ def _time_slider_callback_external(self, change): def _update_time_slider(self, value): path_ext_file = value["new"] - # Read first frame self.external_file = path_ext_file - max_frame_count = get_frame_count(path_ext_file) - # if first video file selected, keep time slider else make frame slider out of time slider: - if self.file_selector.options.index(path_ext_file) == 0: - tmax = ( - self.imageseries.starting_time - + get_frame_count(path_ext_file) / self.imageseries.rate - ) - self.time_slider.max = tmax - self.time_slider.min = self.imageseries.starting_time - self.time_slider.description = "time (s)" - self.time_slider.observe(self._time_slider_callback_external, names="value") - else: - # set time slider to span the frames instead of time: - self.time_slider.max = max_frame_count - self.time_slider.min = 0 - self.time_slider.description = "frame no" - self.time_slider.observe( - lambda change: self._set_figure_from_frame( - change["new"], path_ext_file - ), - names="value", - ) + idx = self.imageseries.external_file.index(self.external_file) + tmin = self.video_start_times[idx] + tmax = self.video_start_times[idx+1] + self.time_slider.min = tmin + self.time_slider.max = tmax self._set_figure_from_frame(0, self.external_file) def _set_figure_3d(self, frame_number): @@ -169,7 +164,7 @@ def time_to_index(self, time, starting_time=None): def get_children(self, *widgets): set_widgets = [wid for wid in widgets if wid is not None] - return [self.figure, self.time_slider, *set_widgets] + return [self.figure, self.visible_time_slider, *set_widgets] def get_frame(self, idx, ext_file_path=None): if ext_file_path is not None: diff --git a/nwbwidgets/utils/imageseries.py b/nwbwidgets/utils/imageseries.py index de02ab39..94396413 100644 --- a/nwbwidgets/utils/imageseries.py +++ b/nwbwidgets/utils/imageseries.py @@ -87,6 +87,15 @@ def get_frame(external_path_file: PathType, index): raise NotImplementedError +def get_fps(external_path_file: PathType): + external_path_file = Path(external_path_file) + if external_path_file.suffix in [".tif", ".tiff"]: + return get_fps_tif(external_path_file) + elif external_path_file.suffix in VIDEO_EXTENSIONS: + return get_fps_video(external_path_file) + else: + raise NotImplementedError + def get_frame_tif(external_path_file: PathType, index): external_path_file = Path(external_path_file) assert external_path_file.suffix in [".tif", ".tiff"], f"supply a tif file" @@ -111,6 +120,10 @@ def get_frame_count_tif(external_path_file: PathType): return len(tif.pages) +def get_fps_tif(external_path_file: PathType): + return + + def get_frame_video(external_path_file: PathType, index): external_path_file = Path(external_path_file) assert ( @@ -159,3 +172,18 @@ def get_frame_shape_video(external_path_file: PathType): return frame.shape else: raise Exception("could not open video file") + + +def get_fps_video(external_path_file: PathType): + external_path_file = Path(external_path_file) + assert ( + external_path_file.suffix in VIDEO_EXTENSIONS + ), f"supply any of {VIDEO_EXTENSIONS} files" + assert HAVE_OPENCV, "pip install opencv-python" + if int(cv2.__version__.split(".")[0]) < 3: + fps_arg = cv2.cv.CV_CAP_PROP_FPS + else: + fps_arg = cv2.CAP_PROP_FPS + with VideoCaptureContext(str(external_path_file)) as cap: + fps = cap.vc.get(fps_arg) + return fps \ No newline at end of file From 329d70230b63bf4402b765a3d3e93317d4001a0c Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Thu, 13 Jan 2022 15:53:09 +0530 Subject: [PATCH 46/49] get fps of video and construct external file frame index --- nwbwidgets/image.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index 4fa080b9..81e0f740 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -34,9 +34,10 @@ def __init__( self.imageseries = imageseries self.figure = None self.time_slider = None - self.external_file = None + self.external_file = imageseries.external_file self.file_selector = None self.video_start_times = [] + self.fps = None if imageseries.external_file is not None: self.video_start_times = self._get_video_start_times() @@ -48,6 +49,7 @@ def __init__( continuous_update=False, ) self.external_file = imageseries.external_file[0] + self.fps = get_fps(self.external_file) # set file selector: if len(imageseries.external_file) > 1: self.file_selector = widgets.Dropdown(options=imageseries.external_file) @@ -61,6 +63,10 @@ def __init__( else: tmin = get_timeseries_mint(imageseries) tmax = get_timeseries_maxt(imageseries) + if imageseries.rate is not None: + self.fps = imageseries.rate + else: + self.fps = imageseries.timestamps[1]-imageseries.timestamps[0] self.time_slider = widgets.FloatSlider( value=tmin, min=tmin, @@ -116,6 +122,7 @@ def _time_slider_callback_external(self, change): def _update_time_slider(self, value): path_ext_file = value["new"] self.external_file = path_ext_file + self.fps = get_fps(self.external_file) idx = self.imageseries.external_file.index(self.external_file) tmin = self.video_start_times[idx] tmax = self.video_start_times[idx+1] @@ -157,8 +164,8 @@ def time_to_index(self, time, starting_time=None): if starting_time is not None else self.imageseries.starting_time ) - if self.imageseries.external_file and self.imageseries.rate: - return int((time - starting_time) * self.imageseries.rate) + if self.imageseries.external_file: + return int((time - starting_time) * self.fps) else: return timeseries_time_to_ind(self.imageseries, time) @@ -168,7 +175,7 @@ def get_children(self, *widgets): def get_frame(self, idx, ext_file_path=None): if ext_file_path is not None: - return get_frame(ext_file_path) + return get_frame(ext_file_path, idx) else: return self.imageseries.data[idx].T From 65419934e184ddc3672fedf641cf9423b25ca1ee Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Thu, 13 Jan 2022 16:18:06 +0530 Subject: [PATCH 47/49] fps constant across external video files --- nwbwidgets/image.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index 81e0f740..5bc489c3 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -37,7 +37,7 @@ def __init__( self.external_file = imageseries.external_file self.file_selector = None self.video_start_times = [] - self.fps = None + self.fps = self.get_fps() if imageseries.external_file is not None: self.video_start_times = self._get_video_start_times() @@ -49,7 +49,6 @@ def __init__( continuous_update=False, ) self.external_file = imageseries.external_file[0] - self.fps = get_fps(self.external_file) # set file selector: if len(imageseries.external_file) > 1: self.file_selector = widgets.Dropdown(options=imageseries.external_file) @@ -63,10 +62,6 @@ def __init__( else: tmin = get_timeseries_mint(imageseries) tmax = get_timeseries_maxt(imageseries) - if imageseries.rate is not None: - self.fps = imageseries.rate - else: - self.fps = imageseries.timestamps[1]-imageseries.timestamps[0] self.time_slider = widgets.FloatSlider( value=tmin, min=tmin, @@ -76,7 +71,7 @@ def __init__( continuous_update=False, ) if len(imageseries.data.shape) == 3: - self._set_figure_2d(0) + self._set_figure_from_frame(0) self.time_slider.observe(self._time_slider_callback_2d, names="value") elif len(imageseries.data.shape) == 4: @@ -101,9 +96,7 @@ def _get_video_start_times(self): start_times=[self.imageseries.starting_time] for file in tqdm(self.imageseries.external_file, desc="retrieving video start times"): - fps = get_fps(file) - fps = fps if fps is not None else self.imageseries.rate - file_time_duration = get_frame_count(file) / fps + file_time_duration = get_frame_count(file) / self.fps start_times.append(file_time_duration) return np.cumsum(start_times) @@ -122,7 +115,6 @@ def _time_slider_callback_external(self, change): def _update_time_slider(self, value): path_ext_file = value["new"] self.external_file = path_ext_file - self.fps = get_fps(self.external_file) idx = self.imageseries.external_file.index(self.external_file) tmin = self.video_start_times[idx] tmax = self.video_start_times[idx+1] @@ -158,6 +150,13 @@ def _set_figure_from_frame(self, frame_number, ext_file_path=None): self.figure.for_each_trace(lambda trace: trace.update(img.data[0])) self.figure.layout.title = f"Frame no: {frame_number}" + def get_fps(self): + if self.imageseries.rate is None: + fps = self.imageseries.timestamps[1]-self.imageseries.timestamps[0] + else: + fps = self.imageseries.rate + return fps + def time_to_index(self, time, starting_time=None): starting_time = ( starting_time From 089ed608950d9151fb7f757d0e31fa6bd673af42 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Thu, 13 Jan 2022 16:26:34 +0530 Subject: [PATCH 48/49] movie fps fixes --- test/fixtures_imageseries.py | 2 +- test/test_image.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/fixtures_imageseries.py b/test/fixtures_imageseries.py index a603d45f..f4bc1ce1 100644 --- a/test/fixtures_imageseries.py +++ b/test/fixtures_imageseries.py @@ -6,7 +6,7 @@ @pytest.fixture(scope="session") def movie_fps(): - return 10 + return 10.0 @pytest.fixture(scope="session") diff --git a/test/test_image.py b/test/test_image.py index d21fb25b..ee986b42 100644 --- a/test/test_image.py +++ b/test/test_image.py @@ -91,7 +91,7 @@ def test_image_series_widget_external_file_tif(create_tif_files, movie_no_frames assert wd.time_slider.max == movie_no_frames[1] -def test_image_series_widget_external_file_single(create_tif_files, movie_no_frames): +def test_image_series_widget_external_file_single(create_tif_files, movie_no_frames, movie_fps): image_series = ImageSeries( name="Image Series", external_file=create_tif_files[:1], rate=1.0, unit="n.a." ) @@ -102,15 +102,15 @@ def test_image_series_widget_external_file_single(create_tif_files, movie_no_fra assert wd.file_selector is None -def test_image_series_widget_external_file_video(create_movie_files, movie_no_frames): +def test_image_series_widget_external_file_video(create_movie_files, movie_no_frames, movie_fps): image_series = ImageSeries( - name="Image Series", external_file=create_movie_files, rate=1.0, unit="n.a." + name="Image Series", external_file=create_movie_files, unit="n.a.", rate=movie_fps ) wd = ImageSeriesWidget(image_series) assert isinstance(wd.figure, go.FigureWidget) - assert wd.time_slider.max == movie_no_frames[0] + assert wd.time_slider.max == movie_no_frames[0]/movie_fps assert wd.time_slider.min == 0.0 assert wd.file_selector.value == create_movie_files[0] wd.file_selector.value = create_movie_files[1] - assert wd.time_slider.min == 0.0 - assert wd.time_slider.max == movie_no_frames[1] + assert wd.time_slider.min == movie_no_frames[0]/movie_fps + assert wd.time_slider.max == sum([movie_no_frames[i]/movie_fps for i in range(len(movie_no_frames))]) From 1d131bfdca90f448032ebbdb3a39314bea428d16 Mon Sep 17 00:00:00 2001 From: Saksham Sharda Date: Thu, 13 Jan 2022 17:20:54 +0530 Subject: [PATCH 49/49] external files list, indexing fixes --- nwbwidgets/image.py | 16 ++++++++++++---- test/test_image.py | 36 ++++++++++++++++++++++++++---------- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/nwbwidgets/image.py b/nwbwidgets/image.py index 5bc489c3..1c891ed1 100644 --- a/nwbwidgets/image.py +++ b/nwbwidgets/image.py @@ -38,12 +38,14 @@ def __init__( self.file_selector = None self.video_start_times = [] self.fps = self.get_fps() + self.external_files = [] if imageseries.external_file is not None: + self.external_files = [i for i in self.imageseries.external_file] self.video_start_times = self._get_video_start_times() self.time_slider = widgets.FloatSlider( min=self.video_start_times[0], - max=self.video_start_times[1], + max=self.video_start_times[1]-1/self.fps, orientation="horizontal", description="time(s)", continuous_update=False, @@ -115,11 +117,17 @@ def _time_slider_callback_external(self, change): def _update_time_slider(self, value): path_ext_file = value["new"] self.external_file = path_ext_file - idx = self.imageseries.external_file.index(self.external_file) + idx = self.external_files.index(self.external_file) tmin = self.video_start_times[idx] tmax = self.video_start_times[idx+1] - self.time_slider.min = tmin - self.time_slider.max = tmax + tmax_ = tmax - 1/self.fps + tmax = tmax_ if tmax_>tmin else tmax + if tmax < self.time_slider.min: # order of setting min/max depends + self.time_slider.min = tmin + self.time_slider.max = tmax + else: + self.time_slider.max = tmax + self.time_slider.min = tmin self._set_figure_from_frame(0, self.external_file) def _set_figure_3d(self, frame_number): diff --git a/test/test_image.py b/test/test_image.py index ee986b42..f896dea8 100644 --- a/test/test_image.py +++ b/test/test_image.py @@ -9,6 +9,7 @@ ImageSeriesWidget, ) from nwbwidgets.view import default_neurodata_vis_spec +from nwbwidgets.controllers.time_window_controllers import StartAndDurationController from pynwb.base import TimeSeries from pynwb.image import RGBImage, GrayscaleImage, IndexSeries, ImageSeries import plotly.graph_objects as go @@ -78,26 +79,28 @@ def test_image_series_widget_data_3d(): def test_image_series_widget_external_file_tif(create_tif_files, movie_no_frames): + rate = 1.0 image_series = ImageSeries( - name="Image Series", external_file=create_tif_files, rate=1.0, unit="n.a." + name="Image Series", external_file=create_tif_files, rate=rate, unit="n.a." ) wd = ImageSeriesWidget(image_series) assert isinstance(wd.figure, go.FigureWidget) - assert wd.time_slider.max == movie_no_frames[0] + assert wd.time_slider.max == movie_no_frames[0]/rate - 1/rate assert wd.time_slider.min == 0.0 assert wd.file_selector.value == create_tif_files[0] wd.file_selector.value = create_tif_files[1] - assert wd.time_slider.min == 0.0 - assert wd.time_slider.max == movie_no_frames[1] + assert wd.time_slider.min == movie_no_frames[0]/rate + assert wd.time_slider.max == sum([movie_no_frames[i]/rate for i in range(len(movie_no_frames))]) - 1/rate -def test_image_series_widget_external_file_single(create_tif_files, movie_no_frames, movie_fps): +def test_image_series_widget_external_file_single(create_tif_files, movie_no_frames): + rate = 1.0 image_series = ImageSeries( - name="Image Series", external_file=create_tif_files[:1], rate=1.0, unit="n.a." + name="Image Series", external_file=create_tif_files[:1], rate=rate, unit="n.a." ) wd = ImageSeriesWidget(image_series) assert isinstance(wd.figure, go.FigureWidget) - assert wd.time_slider.max == movie_no_frames[0] + assert wd.time_slider.max == movie_no_frames[0]/rate - 1/rate assert wd.time_slider.min == 0.0 assert wd.file_selector is None @@ -108,9 +111,22 @@ def test_image_series_widget_external_file_video(create_movie_files, movie_no_fr ) wd = ImageSeriesWidget(image_series) assert isinstance(wd.figure, go.FigureWidget) - assert wd.time_slider.max == movie_no_frames[0]/movie_fps + assert wd.time_slider.max == movie_no_frames[0]/movie_fps - 1/movie_fps assert wd.time_slider.min == 0.0 assert wd.file_selector.value == create_movie_files[0] wd.file_selector.value = create_movie_files[1] - assert wd.time_slider.min == movie_no_frames[0]/movie_fps - assert wd.time_slider.max == sum([movie_no_frames[i]/movie_fps for i in range(len(movie_no_frames))]) + assert wd.time_slider.max == sum([movie_no_frames[i]/movie_fps for i in range(len(movie_no_frames))]) - 1/movie_fps + wd.file_selector.value = create_movie_files[0] + assert wd.time_slider.max == movie_no_frames[0]/movie_fps - 1/movie_fps + assert wd.time_slider.min == 0.0 + + +def test_image_series_foreign_time_controller(create_movie_files, movie_no_frames, movie_fps): + st_controller = StartAndDurationController(tmax=20.0, tmin=0) + image_series = ImageSeries( + name="Image Series", external_file=create_movie_files, unit="n.a.", rate=movie_fps + ) + wd = ImageSeriesWidget(image_series,st_controller) + assert wd.time_slider.max == movie_no_frames[0]/movie_fps - 1/movie_fps + st_controller.value = (5.0, 20.0) + assert wd.time_slider.value == movie_no_frames[0]/movie_fps - 1/movie_fps \ No newline at end of file