from __future__ import division, print_function, absolute_import
from ScopeFoundry import BaseApp
from ScopeFoundry.helper_funcs import load_qt_ui_file, sibling_path
from collections import OrderedDict
import os
from qtpy import QtCore, QtWidgets
import pyqtgraph as pg
import pyqtgraph.dockarea as dockarea
import numpy as np
from ScopeFoundry.logged_quantity import LQCollection
[docs]class DataBrowser(BaseApp):
name = "DataBrowser"
def __init__(self, argv):
BaseApp.__init__(self, argv)
self.setup()
[docs] def setup(self):
self.ui = load_qt_ui_file(sibling_path(__file__, "data_browser.ui"))
self.ui.show()
self.ui.raise_()
self.views = OrderedDict()
self.current_view = None
self.settings.New('data_filename', dtype='file')
self.settings.New('browse_dir', dtype='file', is_dir=True, initial='/')
self.settings.New('file_filter', dtype=str, initial='*.*,')
self.settings.data_filename.add_listener(self.on_change_data_filename)
self.settings.New('auto_select_view',dtype=bool, initial=True)
self.settings.New('view_name', dtype=str, initial='0', choices=('0',))
# UI Connections
self.settings.data_filename.connect_to_browse_widgets(self.ui.data_filename_lineEdit,
self.ui.data_filename_browse_pushButton)
self.settings.browse_dir.connect_to_browse_widgets(self.ui.browse_dir_lineEdit,
self.ui.browse_dir_browse_pushButton)
self.settings.view_name.connect_bidir_to_widget(self.ui.view_name_comboBox)
self.settings.file_filter.connect_bidir_to_widget(self.ui.file_filter_lineEdit)
# file system tree
self.fs_model = QtWidgets.QFileSystemModel()
self.fs_model.setRootPath(QtCore.QDir.currentPath())
self.ui.treeView.setModel(self.fs_model)
self.ui.treeView.setIconSize(QtCore.QSize(16,16))
self.ui.treeView.setSortingEnabled(True)
#for i in (1,2,3):
# self.ui.treeView.hideColumn(i)
#print("="*80, self.ui.treeView.selectionModel())
self.tree_selectionModel = self.ui.treeView.selectionModel()
self.tree_selectionModel.selectionChanged.connect(self.on_treeview_selection_change)
self.settings.browse_dir.add_listener(self.on_change_browse_dir)
self.settings['browse_dir'] = os.getcwd()
# set views
self.load_view(FileInfoView(self))
self.load_view(NPZView(self))
self.settings.view_name.add_listener(self.on_change_view_name)
self.settings['view_name'] = "file_info"
self.settings.file_filter.add_listener(self.on_change_file_filter)
#self.console_widget.show()
self.ui.console_pushButton.clicked.connect(self.console_widget.show)
self.ui.log_pushButton.clicked.connect(self.logging_widget.show)
self.ui.show()
[docs] def load_view(self, new_view):
#instantiate view
#new_view = ViewClass(self)
self.log.debug('load_view called {}'.format(new_view))
# add to views dict
self.views[new_view.name] = new_view
self.ui.dataview_groupBox.layout().addWidget(new_view.ui)
new_view.ui.hide()
# update choices for view_name
self.settings.view_name.change_choice_list(list(self.views.keys()))
self.log.debug('load_view done {}'.format(new_view))
return new_view
[docs] def on_change_data_filename(self):
fname = self.settings.data_filename.val
if not self.settings['auto_select_view']:
self.current_view.on_change_data_filename(fname)
else:
view_name = self.auto_select_view(fname)
if self.current_view is None or view_name != self.current_view.name:
# update view (automatically calls on_change_data_filename)
self.settings['view_name'] = view_name
else:
# force update
if os.path.isfile(fname):
self.current_view.on_change_data_filename(fname)
[docs] @QtCore.Slot()
def on_change_browse_dir(self):
self.log.debug("on_change_browse_dir")
self.ui.treeView.setRootIndex(self.fs_model.index(self.settings['browse_dir']))
self.fs_model.setRootPath(self.settings['browse_dir'])
[docs] def on_change_file_filter(self):
self.log.debug("on_change_file_filter")
filter_str = self.settings['file_filter']
if filter_str == "":
filter_str = "*"
self.settings['file_filter'] = "*"
filter_str_list = [x.strip() for x in filter_str.split(',')]
self.log.debug(filter_str_list)
self.fs_model.setNameFilters(filter_str_list)
[docs] def on_change_view_name(self):
#print('on_change_view_name')
previous_view = self.current_view
self.current_view = self.views[self.settings['view_name']]
# hide current view
# (handle the initial case where previous_view is None )
if previous_view:
previous_view.ui.hide()
else:
self.ui.dataview_placeholder.hide()
# show new view
self.current_view.ui.show()
# set datafile for new (current) view
fname = self.settings['data_filename']
if os.path.isfile(fname):
self.current_view.on_change_data_filename(self.settings['data_filename'])
[docs] def on_treeview_selection_change(self, sel, desel):
fname = self.fs_model.filePath(self.tree_selectionModel.currentIndex())
self.settings['data_filename'] = fname
# print( 'on_treeview_selection_change' , fname, sel, desel)
[docs] def auto_select_view(self, fname):
"return the name of the last supported view for the given fname"
for view_name, view in list(self.views.items())[::-1]:
if view.is_file_supported(fname):
return view_name
# return default file_info view if no others work
return 'file_info'
[docs]class DataBrowserView(QtCore.QObject):
""" Abstract class for DataBrowser Views"""
def __init__(self, databrowser):
QtCore.QObject.__init__(self)
self.databrowser = databrowser
self.settings = LQCollection()
self.setup()
# create view with no data file
[docs] def on_change_data_filename(self, fname=None):
pass
# load data file
# update display
[docs] def is_file_supported(self, fname):
# returns whether view can handle file, should return False early to avoid
# too much computation when selecting a file
return False
[docs]class FileInfoView(DataBrowserView):
name = 'file_info'
[docs] def setup(self):
self.ui = QtWidgets.QTextEdit("file_info")
[docs] def on_change_data_filename(self, fname=None):
if fname is None:
fname = self.databrowser.settings['data_filename']
_, ext = os.path.splitext(fname)
if ext in ('.py', '.ini', '.txt'):
with open(fname, 'r') as f:
self.ui.setText(f.read())
else:
self.ui.setText(fname)
[docs] def is_file_supported(self, fname):
return True
[docs]class NPZView(DataBrowserView):
name = 'npz_view'
[docs] def setup(self):
#self.ui = QtGui.QScrollArea()
#self.display_label = QtGui.QLabel("TestNPZView")
self.ui = self.display_textEdit = QtWidgets.QTextEdit()
#self.ui.setLayout(QtGui.QVBoxLayout())
#self.ui.layout().addWidget(self.display_label)
#self.ui.setWidget(self.display_label)
[docs] def on_change_data_filename(self, fname=None):
import numpy as np
try:
self.dat = np.load(fname)
self.display_txt = "File: {}\n".format(fname)
sorted_keys = sorted(self.dat.keys())
for key in sorted_keys:
val = self.dat[key]
if val.shape == ():
self.display_txt += " --> {}: {}\n".format(key, val)
else:
self.display_txt += " --D {}: Array of {} {}\n".format(key, val.dtype, val.shape)
#self.display_label.setText(self.display_txt)
self.display_textEdit.setText(self.display_txt)
except Exception as err:
self.display_textEdit.setText("failed to load %s:\n%s" %(fname, err))
raise(err)
[docs] def is_file_supported(self, fname):
return os.path.splitext(fname)[1] == ".npz"
[docs]class HyperSpectralBaseView(DataBrowserView):
name = 'HyperSpectralBaseView'
[docs] def setup(self):
#self.ui = self.splitter = QtWidgets.QSplitter()
#self.ui.setLayout(QtWidgets.QVBoxLayout())
self.ui = self.dockarea = dockarea.DockArea()
self.imview = pg.ImageView()
self.imview.getView().invertY(False) # lower left origin
#self.splitter.addWidget(self.imview)
self.dockarea.addDock(name='Image', widget=self.imview)
self.graph_layout = pg.GraphicsLayoutWidget()
#self.splitter.addWidget(self.graph_layout)
self.dockarea.addDock(name='Spec Plot', widget=self.graph_layout)
self.spec_plot = self.graph_layout.addPlot()
self.rect_plotdata = self.spec_plot.plot()
self.point_plotdata = self.spec_plot.plot(pen=(0,9))
# Rectangle ROI
self.rect_roi = pg.RectROI([20, 20], [20, 20], pen=(0,9))
self.rect_roi.addTranslateHandle((0.5,0.5))
self.imview.getView().addItem(self.rect_roi)
self.rect_roi.sigRegionChanged[object].connect(self.on_change_rect_roi)
# Point ROI
self.circ_roi = pg.CircleROI( (0,0), (2,2) , movable=True, pen=(0,9))
#self.circ_roi.removeHandle(self.circ_roi.getHandles()[0])
h = self.circ_roi.addTranslateHandle((0.5,.5))
h.pen = pg.mkPen('r')
h.update()
self.imview.getView().addItem(self.circ_roi)
self.circ_roi.removeHandle(0)
self.circ_roi_plotline = pg.PlotCurveItem([0], pen=(0,9))
self.imview.getView().addItem(self.circ_roi_plotline)
self.circ_roi.sigRegionChanged[object].connect(self.on_update_circ_roi)
self.hyperspec_data = None
self.display_image = None
self.spec_x_array = None
self.scan_specific_setup()
[docs] def scan_specific_setup(self):
#override this!
pass
[docs] def is_file_supported(self, fname):
# override this!
return False
[docs] def on_change_data_filename(self, fname):
try:
self.load_data(fname)
self.databrowser.ui.statusbar.clearMessage()
except Exception as err:
#self.imview.setImage(np.zeros((10,10)))
HyperSpectralBaseView.load_data(self, fname) # load default dummy data
self.databrowser.ui.statusbar.showMessage("failed to load %s:\n%s" %(fname, err))
raise(err)
finally:
self.update_display()
[docs] def update_display(self):
# pyqtgraph axes are x,y, but data is stored in (frame, y,x, time), so we need to transpose
self.imview.setImage(self.display_image.T)
self.on_change_rect_roi()
self.on_update_circ_roi()
[docs] def load_data(self, fname):
"""
override to set hyperspectral dataset and the display image
need to define:
* self.hyperspec_data (shape Ny, Nx, Nspec)
* self.display_image (shape Ny, Nx)
* self.spec_x_array (shape Nspec)
"""
self.hyperspec_data = np.zeros((10,10,34))#np.random.rand(10,10,34)
self.display_image =np.zeros((10,10,34))# np.random.rand(10,10)
self.spec_x_array = 3*np.arange(34)
[docs] @QtCore.Slot(object)
def on_change_rect_roi(self, roi=None):
# pyqtgraph axes are x,y, but data is stored in (frame, y,x, time)
# NOTE: If data is indeed stored as (frame, y, x, time) in self.hyperspec_data, then axis argument should be axes = (2,1)
roi_slice, roi_tr = self.rect_roi.getArraySlice(self.hyperspec_data, self.imview.getImageItem(), axes=(1,0))
#print("roi_slice", roi_slice)
self.rect_plotdata.setData(self.spec_x_array, self.hyperspec_data[roi_slice].mean(axis=(0,1))+1)
[docs] @QtCore.Slot(object)
def on_update_circ_roi(self, roi=None):
if roi is None:
roi = self.circ_roi
roi_state = roi.saveState()
#print roi_state
#xc, y
x0, y0 = roi_state['pos']
xc = x0 + 1
yc = y0 + 1
#Nframe, Ny, Nx, Nt = self.time_trace_map.shape
#print 'Nframe, Ny, Nx, Nt', Nframe, Ny, Nx, Nt,
Ny, Nx, Nspec = self.hyperspec_data.shape
i = max(0, min(int(xc), Nx-1))
j = max(0, min(int(yc), Ny-1))
#print "xc,yc,i,j", xc,yc, i,j
self.circ_roi_plotline.setData([xc, i+0.5], [yc, j + 0.5])
self.circ_roi_ji = (j,i)
self.point_plotdata.setData(self.spec_x_array, self.hyperspec_data[j,i,:])
if __name__ == '__main__':
import sys
app = DataBrowser(sys.argv)
sys.exit(app.exec_())