Skip to content
This repository has been archived by the owner on Nov 26, 2023. It is now read-only.

Commit

Permalink
Merge pull request #72 from kushalkolar/inscopix-importer
Browse files Browse the repository at this point in the history
Inscopix importer
  • Loading branch information
kushalkolar committed Nov 12, 2021
2 parents c1a13fb + 86b95b1 commit 16fca7a
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 16 deletions.
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ https://www.youtube.com/playlist?list=PLgofWiw2s4RF_RkGRUfflcj5k5KUTG3o_
:caption: Viewer Modules

./user_guides/viewer/modules/tiff_file
./user_guides/viewer/modules/inscopix_importer
./user_guides/viewer/modules/batch_manager
./user_guides/viewer/modules/stimulus_mapping
./user_guides/viewer/modules/roi_manager
Expand Down
32 changes: 32 additions & 0 deletions docs/source/user_guides/viewer/modules/inscopix_importer.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.. _module_InscopixImporter:

Inscopix Importer
*****************

The Inscopix Importer module can be used to open ``.isxd`` movies created by Inscopix acquisition software

.. note:: You must have your own license for activating/running the Inscopix Data Processing Software (IDPS) and downloading the IDPS API and ``isx`` library. Mesmerize only provides an implementation of ``isx`` to read ``.isxd`` movies into the application.

In order to use the importer you will need to add the path to the parent dir containing the ``isx`` library to your ``PYTHONPATH`` environment variable.

For example if your ``isx`` dir is located at:

```
/home/user/Inscopix Data Processing 1.6.0/Inscopix Data Processing.linux/Contents/API/Python/isx``
```

Then you will need to add the path to the parent dir, for example:

```
export PYTHONPATH="/home/user/Inscopix Data Processing 1.6.0/Inscopix Data Processing.linux/Contents/API/Python:$PYTHONPATH"
```

**Usage:**

1. Enter the path to the ``.isxd`` or click the ``...`` and choose the file.

2. Click the button to load the file into the :ref:`Viewer Work Environment <ViewerWorkEnv>`.

The sampling rate (framerate) of the video is automatically imported from the ``isxd`` file.

.. note:: Memory usage is quite high when loading files, you will need at least twice as much RAM as the size of the file you're trying to open.
10 changes: 10 additions & 0 deletions mesmerize/common/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@
else:
HAS_CAIMAN = True

# try:
# print("Importing ISX")
# import isx
# print("Imported ISX")
# except ImportError:
# warn("Inscopix API not found, Inscopix importer will be disabled")
# HAS_ISX = False
# else:
# HAS_ISX = True

try:
import tensorflow
except ImportError:
Expand Down
34 changes: 24 additions & 10 deletions mesmerize/viewer/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,30 @@
_import_custom_modules = False


dock_widget_area = {'roi_manager': QtCore.Qt.LeftDockWidgetArea,
roi_manager.ModuleGUI: QtCore.Qt.LeftDockWidgetArea,
dock_widget_area = \
{
'roi_manager': QtCore.Qt.LeftDockWidgetArea,
roi_manager.ModuleGUI: QtCore.Qt.LeftDockWidgetArea,

'tiff_io': QtCore.Qt.TopDockWidgetArea,
tiff_io.ModuleGUI: QtCore.Qt.TopDockWidgetArea,
'tiff_io': QtCore.Qt.TopDockWidgetArea,
tiff_io.ModuleGUI: QtCore.Qt.TopDockWidgetArea,

'suite2p_importer': QtCore.Qt.TopDockWidgetArea,
suite2p.ModuleGUI: QtCore.Qt.TopDockWidgetArea,
'suite2p_importer': QtCore.Qt.TopDockWidgetArea,
suite2p.ModuleGUI: QtCore.Qt.TopDockWidgetArea,

'stimulus_mapping': QtCore.Qt.BottomDockWidgetArea,
stimulus_mapping.ModuleGUI: QtCore.Qt.BottomDockWidgetArea,
}

# inscopix importer
if sys.version_info[1] >= 8:
dock_widget_area.update(
{
'inscopix_importer': QtCore.Qt.TopDockWidgetArea,
inscopix_importer.ModuleGUI: QtCore.Qt.TopDockWidgetArea,
}
)

'stimulus_mapping': QtCore.Qt.BottomDockWidgetArea,
stimulus_mapping.ModuleGUI: QtCore.Qt.BottomDockWidgetArea
}

# caiman modules
if configuration.HAS_CAIMAN:
Expand Down Expand Up @@ -148,10 +160,12 @@ def __init__(self, parent=None):
self.ui.actionStimulus_Mapping.triggered.connect(lambda: self.run_module(stimulus_mapping.ModuleGUI))
self.ui.actionScript_Editor.triggered.connect(lambda: self.run_module(script_editor.ModuleGUI))

if sys.version_info[1] >= 8:
self.ui.actionInscopix_isxd.triggered.connect(lambda: self.run_module(inscopix_importer.ModuleGUI))

if configuration.HAS_TENSORFLOW:
self.ui.actionNuSeT_Segmentation.triggered.connect(lambda: self.run_module(nuset_segment.ModuleGUI))


# TODO: refactor the actions trigger connections so they're automated based on module name
if configuration.HAS_CAIMAN:
self.ui.actionCNMF.triggered.connect(lambda: self.run_module(cnmf.ModuleGUI))
Expand Down
12 changes: 9 additions & 3 deletions mesmerize/viewer/main_window_pytemplate.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'main_window_pytemplate.ui'
# Form implementation generated from reading ui file './main_window_pytemplate.ui'
#
# Created by: PyQt5 UI code generator 5.9.2
# Created by: PyQt5 UI code generator 5.12
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
Expand All @@ -16,7 +17,7 @@ def setupUi(self, MainWindow):
self.centralwidget.setObjectName("centralwidget")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 32))
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 30))
self.menubar.setObjectName("menubar")
self.menuFile = QtWidgets.QMenu(self.menubar)
self.menuFile.setObjectName("menuFile")
Expand Down Expand Up @@ -126,6 +127,8 @@ def setupUi(self, MainWindow):
self.action_mes_importer.setObjectName("action_mes_importer")
self.actionNuSeT_Segmentation = QtWidgets.QAction(MainWindow)
self.actionNuSeT_Segmentation.setObjectName("actionNuSeT_Segmentation")
self.actionInscopix_isxd = QtWidgets.QAction(MainWindow)
self.actionInscopix_isxd.setObjectName("actionInscopix_isxd")
self.menuFile.addAction(self.actionAdd_to_project)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actionOpen_work_environment)
Expand All @@ -136,6 +139,7 @@ def setupUi(self, MainWindow):
self.menuFemtonics.addAction(self.action_mes_importer)
self.menuLoad_images.addAction(self.actionTiff_file)
self.menuLoad_images.addAction(self.menuFemtonics.menuAction())
self.menuLoad_images.addAction(self.actionInscopix_isxd)
self.menuCaImAn_toolbox.addAction(self.actionMotion_Correction)
self.menuCaImAn_toolbox.addAction(self.actionCNMF)
self.menuCaImAn_toolbox.addAction(self.actionCNMF_E)
Expand Down Expand Up @@ -231,4 +235,6 @@ def retranslateUi(self, MainWindow):
self.action_mesc_importer.setText(_translate("MainWindow", ".&mesc importer"))
self.action_mes_importer.setText(_translate("MainWindow", ".mes &importer"))
self.actionNuSeT_Segmentation.setText(_translate("MainWindow", "&NuSeT Segmentation"))
self.actionInscopix_isxd.setText(_translate("MainWindow", "Inscopix isxd"))


8 changes: 7 additions & 1 deletion mesmerize/viewer/main_window_pytemplate.ui
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<x>0</x>
<y>0</y>
<width>800</width>
<height>32</height>
<height>30</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
Expand Down Expand Up @@ -51,6 +51,7 @@
</widget>
<addaction name="actionTiff_file"/>
<addaction name="menuFemtonics"/>
<addaction name="actionInscopix_isxd"/>
</widget>
<widget class="QMenu" name="menuCaImAn_toolbox">
<property name="title">
Expand Down Expand Up @@ -332,6 +333,11 @@
<string>&amp;NuSeT Segmentation</string>
</property>
</action>
<action name="actionInscopix_isxd">
<property name="text">
<string>Inscopix isxd</string>
</property>
</action>
</widget>
<resources/>
<connections>
Expand Down
14 changes: 12 additions & 2 deletions mesmerize/viewer/modules/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from ...common.configuration import HAS_CAIMAN, HAS_TENSORFLOW
import sys
from ...common.configuration import HAS_CAIMAN, HAS_TENSORFLOW#, HAS_ISX

__all__ = [
'mesfile_io',
Expand All @@ -8,7 +9,7 @@
'script_editor',
'suite2p',
'femtonics_mesc',
'exporter'
'exporter',
]

if HAS_CAIMAN:
Expand All @@ -27,3 +28,12 @@
[
'nuset_segment',
]

if sys.version_info[1] >= 8:
__all__ += ['inscopix_importer']

#if HAS_ISX:
# __all__ += \
# [
# 'inscopix_importer'
# ]
74 changes: 74 additions & 0 deletions mesmerize/viewer/modules/_inscopix_importer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import isx
import numpy as np
import click
from multiprocessing import shared_memory
from tqdm import tqdm


@click.command()
@click.option('--isx-path', type=str)
@click.option('--shm-meta-array-name', type=str)
def load_file(isx_path, shm_meta_array_name):#shm_name, shm-size, shm-dtype):
"""
isx_path: full path to the isx file to open
shm_meta_array_name: same of the shared memory array that is used to pass
array metadata (shape, dtype, name) to effectively communication with the
parent mesmerize instance
shm-name: name of the parent shared array that contains the
"""
movie = isx.Movie.read(isx_path)

meta = movie.get_acquisition_info()

d = \
{
'fps': 1000 / movie.timing.period.to_msecs(),
'origin': meta['Microscope Type'],
'date': '00000000_000000',
'orig_meta': meta
}

# total number of frames in the recording
nframes = movie.timing.num_samples

# shape of each frame
frame0 = movie.get_frame_data(0)

# image shape
shape = (*frame0.shape, nframes)

# calculate number of bytes required for the shared memory
nbytes = np.prod(shape) * np.dtype(movie.data_type).itemsize

# created a shared memory buffer
shm = shared_memory.SharedMemory(create=True, size=nbytes)

# create shared numpy array backed by the shared memory buffer
imgseq = np.ndarray(shape, dtype=np.dtype(movie.data_type), buffer=shm.buf)

# fill the array with the inscopix data
for i in tqdm(range(nframes)):
imgseq[..., i] = movie.get_frame_data(i)

# communicate the array metadata to access the shared array from within mesmerize
shm_metadata = shared_memory.SharedMemory(name=shm_meta_array_name)
array_metadata = np.ndarray((5,), dtype=np.dtype('<U64'), buffer=shm_metadata.buf)

array_metadata[0] = shm.name
array_metadata[1] = np.dtype(movie.data_type).name
array_metadata[2] = ','.join(str(s) for s in shape)
array_metadata[3] = str(d['fps'])
array_metadata[4] = str(d['origin'])

print(f'imgdata address: {shm.name}')
print(array_metadata)

shm.close()


if __name__ == '__main__':
load_file()


103 changes: 103 additions & 0 deletions mesmerize/viewer/modules/inscopix_importer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import numpy as np
from ...common.qdialogs import *
from ..core.common import ViewerUtils
from PyQt5 import QtWidgets
from ..core import ViewerWorkEnv
from ..core.data_types import ImgData
from multiprocessing.shared_memory import SharedMemory
import os
from subprocess import Popen


class Widget(QtWidgets.QWidget):
def __init__(self, parent):
QtWidgets.QWidget.__init__(self, parent=parent)
self.vboxlayout = QtWidgets.QVBoxLayout(self)
self.hboxlayout = QtWidgets.QHBoxLayout(self)

self.label_choose_file = QtWidgets.QLabel()
self.label_choose_file.setText("Choose `.isxd` file")
self.vboxlayout.addWidget(self.label_choose_file)

self.line_edit_path = QtWidgets.QLineEdit()
self.line_edit_path.setPlaceholderText("Path to `.isxd` file")
self.line_edit_path.returnPressed.connect(self.parent().load_file)
self.hboxlayout.addWidget(self.line_edit_path)

self.btn_open_file_dialog = QtWidgets.QPushButton()
self.btn_open_file_dialog.setText("...")
self.btn_open_file_dialog.clicked.connect(self.parent().file_dialog)
self.hboxlayout.addWidget(self.btn_open_file_dialog)

self.vboxlayout.addLayout(self.hboxlayout)

self.btn_load_file = QtWidgets.QPushButton()
self.btn_load_file.setText("Load File")
self.btn_load_file.clicked.connect(self.parent().load_file)
self.vboxlayout.addWidget(self.btn_load_file)


class ModuleGUI(QtWidgets.QDockWidget):
def __init__(self, parent, viewer_reference):
self.vi = ViewerUtils(viewer_reference)
QtWidgets.QDockWidget.__init__(self, parent)
self.setFloating(True)
self.setWindowTitle('Inscopix Importer')

self.widget = Widget(parent=self)

self.setWidget(self.widget)

# self.setLayout(self.vboxlayout)
@use_open_file_dialog("Choose .isxd file", "", exts=["*.isxd"])
def file_dialog(self, path, *args, **kwargs):
self.widget.line_edit_path.setText(path)

@present_exceptions("File open error", "The following error occurred when opening the file")
def load_file(self, *args, **kwargs):
shm = SharedMemory(create=True, size=5 * np.dtype('<U64').itemsize)

# get array metadata, shared memory buffer name, dtype, and shape
array_metadata = np.ndarray(shape=(5,), dtype=np.dtype('<U64'), buffer=shm.buf)

importer_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '_inscopix_importer.py')

file_path = self.widget.line_edit_path.text()

cmd = ['python', importer_path, '--isx-path', file_path, '--shm-meta-array-name', shm.name]
print(cmd)
proc = Popen(
cmd,
env=os.environ.copy()
)
proc.wait()

# read the metadata
name = array_metadata[0]
dtype = array_metadata[1]
shape = array_metadata[2]

# open the shared buffer
existing_shm = SharedMemory(name=array_metadata[0])

shape = tuple(map(int, shape.split(',')))

_imgseq = np.ndarray(shape, dtype=np.dtype(dtype), buffer=existing_shm.buf)

imgseq = np.zeros(shape=shape, dtype=np.dtype(dtype))
imgseq[:] = _imgseq[:]

d = \
{
'fps': float(array_metadata[3]),
'origin': array_metadata[4],
'date': '00000000_000000'
}

imgdata = ImgData(imgseq, d)

self.vi.viewer.workEnv = ViewerWorkEnv(imgdata)
self.vi.update_workEnv()

existing_shm.close()

0 comments on commit 16fca7a

Please sign in to comment.