COMPASS  5.4.4
End-to-end AO simulation tool using GPU acceleration
widget_base.py
1 
37 
38 import os
39 import sys
40 import threading
41 import warnings
42 from typing import Any, Callable, Dict
43 
44 import numpy as np
45 import pyqtgraph as pg
46 
47 try:
48  from PyQt5 import QtWidgets
49  from PyQt5.QtCore import QThread, QTimer
50  from PyQt5.uic import loadUiType
51 except ModuleNotFoundError as e:
52  try:
53  from PySide2 import QtWidgets
54  from PySide2.QtCore import QThread, QTimer
55  from PySide2.QtUiTools import loadUiType
56  except ModuleNotFoundError as e:
57  raise ModuleNotFoundError("No module named 'PyQt5' or PySide2', please install one of them\nException raised: "+e.msg)
58 
59 from pyqtgraph.dockarea import Dock, DockArea
60 
61 from shesha.util.matplotlibwidget import MatplotlibWidget
62 
63 
64 def uiLoader(moduleName):
65  return loadUiType(os.environ["SHESHA_ROOT"] +
66  "/shesha/widgets/%s.ui" % moduleName) # type: type, type
67 
68 
69 BaseWidgetTemplate, BaseClassTemplate = uiLoader('widget_base')
70 
71 
72 class PupilBoxes(QtWidgets.QGraphicsPathItem):
73 
74  def __init__(self, x, y):
75  """x and y are 2D arrays of shape (Nplots, Nsamples)"""
76  connect = np.ones(x.shape, dtype=bool)
77  connect[:, -1] = 0 # don't draw the segment between each trace
78  self.pathpath = pg.arrayToQPath(x.flatten(), y.flatten(), connect.flatten())
79  pg.QtGui.QGraphicsPathItem.__init__(self, self.pathpath)
80  self.setPen(pg.mkPen('r'))
81 
82  def shape(self): # override because QGraphicsPathItem.shape is too expensive.
83  return pg.QtGui.QGraphicsItem.shape(self)
84 
85  def boundingRect(self):
86  return self.pathpath.boundingRect()
87 
88 
89 class WidgetBase(BaseClassTemplate):
90 
91  def __init__(self, parent=None, hide_histograms=False) -> None:
92  BaseClassTemplate.__init__(self, parent=parent)
93 
94  self.uiBaseuiBase = BaseWidgetTemplate()
95  self.uiBaseuiBase.setupUi(self)
96 
97 
100 
101  self.gui_timergui_timer = QTimer() # type: QTimer
102  self.gui_timergui_timer.timeout.connect(self.updateDisplayupdateDisplay)
103  if self.uiBaseuiBase.wao_Display.isChecked():
104  self.gui_timergui_timer.start(1000. / self.uiBaseuiBase.wao_frameRate.value())
105  self.loopLockloopLock = threading.Lock(
106  ) # type: Threading.Lock # Asynchronous loop / display safe-threading
107  self.hide_histogramshide_histograms = hide_histograms
108 
111 
112  self.areaarea = DockArea()
113  self.uiBaseuiBase.wao_DisplayDock.setWidget(self.areaarea)
114  self.gridSHgridSH = []
115 
116 
120  self.defaultParPathdefaultParPath = "."
121  self.defaultAreaPathdefaultAreaPath = "."
122  self.uiBaseuiBase.wao_load_config.clicked.connect(self.load_configload_config)
123  self.uiBaseuiBase.wao_loadArea.clicked.connect(self.loadArealoadArea)
124  self.uiBaseuiBase.wao_saveArea.clicked.connect(self.saveAreasaveArea)
125  self.uiBaseuiBase.wao_init.clicked.connect(self.init_configinit_config)
126  self.uiBaseuiBase.wao_configFromFile.clicked.connect(self.addConfigFromFileaddConfigFromFile)
127 
128  self.uiBaseuiBase.wao_Display.stateChanged.connect(self.gui_timer_configgui_timer_config)
129  self.uiBaseuiBase.wao_frameRate.setValue(2)
130 
131  self.uiBaseuiBase.wao_load_config.setDisabled(False)
132  self.uiBaseuiBase.wao_init.setDisabled(True)
133 
134  self.disp_checkboxesdisp_checkboxes = []
135  self.docksdocks = {} # type: Dict[str, pg.dockarea.Dock]
136  self.viewboxesviewboxes = {} # type: Dict[str, pg.ViewBox]
137  self.imgsimgs = {} # type: Dict[str, pg.ImageItem]
138  self.histshists = {} # type: Dict[str, pg.HistogramLUTItem]
139 
140  self.PupilLinesPupilLines = None
141  self.adjustSize()
142 
143  def gui_timer_config(self, state) -> None:
144  self.uiBaseuiBase.wao_frameRate.setDisabled(state)
145  if state:
146  self.gui_timergui_timer.start(1000. / self.uiBaseuiBase.wao_frameRate.value())
147  else:
148  self.gui_timergui_timer.stop()
149 
150  def closeEvent(self, event: Any) -> None:
151  self.quitGUIquitGUI(event)
152 
153  def quitGUI(self, event: Any = None) -> None:
154  reply = QtWidgets.QMessageBox.question(
155  self, 'Message', "Are you sure to quit?", QtWidgets.QMessageBox.Yes |
156  QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
157 
158  if reply == QtWidgets.QMessageBox.Yes:
159  if event:
160  event.accept()
161  quit()
162  else:
163  if event:
164  event.ignore()
165 
166 
169 
170  def saveArea(self, widget, filename=None):
171  '''
172  Callback when a area layout file is double clicked in the file browser
173  Place the selected file name in the browsing drop-down menu,
174  the call the self.load_config callback of the load button.
175  '''
176  if filename is None:
177  filepath = QtWidgets.QFileDialog(
178  directory=self.defaultAreaPathdefaultAreaPath).getSaveFileName(
179  self, "Select area layout file", "",
180  "area layout file (*.area);;all files (*)")
181  filename = filepath[0]
182 
183  try:
184  with open(filename, "w+") as f:
185  st = self.areaarea.saveState()
186  f.write(str(st))
187  except FileNotFoundError as err:
188  warnings.warn(filename + " not loaded: " + err)
189 
190  def showDock(self, name):
191  for disp_checkbox in self.disp_checkboxesdisp_checkboxes:
192  if disp_checkbox.text() == name:
193  disp_checkbox.setChecked(True)
194  break
195  if name in self.docksdocks.keys():
196  self.areaarea.addDock(self.docksdocks[name])
197 
198  def restoreMyState(self, state):
199  typ, contents, _ = state
200 
201  if typ == 'dock':
202  self.showDockshowDock(contents)
203  else:
204  for o in contents:
205  self.restoreMyStaterestoreMyState(o)
206 
207  def loadArea(self, widget=None, filename=None):
208  # close all docks
209  for disp_checkbox in self.disp_checkboxesdisp_checkboxes:
210  disp_checkbox.setChecked(False)
211  for dock in self.docksdocks.values():
212  if dock.isVisible():
213  dock.close()
214 
215  if filename is None:
216  filepath = QtWidgets.QFileDialog(
217  directory=self.defaultAreaPathdefaultAreaPath).getOpenFileName(
218  self, "Select area layout file", "",
219  "area layout file (*.area);;all files (*)")
220  filename = filepath[0]
221 
222  try:
223  with open(filename, "r") as f:
224  st = eval(f.readline())
225 
226  # restore docks from main area
227  if st['main'] is not None:
228  self.restoreMyStaterestoreMyState(st["main"])
229 
230  # restore docks from floating area
231  for win in st["float"]:
232  self.restoreMyStaterestoreMyState(win[0]['main'])
233 
234  # rearange dock s as in stored state
235  self.areaarea.restoreState(st)
236  except FileNotFoundError as err:
237  warnings.warn(filename + "not loaded: " + err)
238 
239  def addConfigFromFile(self) -> None:
240  '''
241  Callback when a config file is double clicked in the file browser
242  Place the selected file name in the browsing drop-down menu,
243  the call the self.load_config callback of the load button.
244  '''
245  filepath = QtWidgets.QFileDialog(directory=self.defaultParPathdefaultParPath).getOpenFileName(
246  self, "Select parameter file", "",
247  "parameters file (*.py);;hdf5 file (*.h5);;all files (*)")
248 
249  self.uiBaseuiBase.wao_selectConfig.clear()
250  self.uiBaseuiBase.wao_selectConfig.addItem(str(filepath[0]))
251 
252  self.load_configload_config(config_file=self.uiBaseuiBase.wao_selectConfig.currentText())
253 
255  guilty_guy = self.sender().text()
256  state = self.sender().isChecked()
257  if state:
258  self.areaarea.addDock(self.docksdocks[guilty_guy])
259  elif self.docksdocks[guilty_guy].isVisible():
260  self.docksdocks[guilty_guy].close()
261 
262  def add_dispDock(self, name: str, parent, type: str = "pg_image") -> Dock:
263  checkBox = QtWidgets.QCheckBox(name, parent)
264  checkBox.clicked.connect(self.update_displayDockupdate_displayDock)
265  checkableAction = QtWidgets.QWidgetAction(parent)
266  checkableAction.setDefaultWidget(checkBox)
267  parent.addAction(checkableAction)
268  self.disp_checkboxesdisp_checkboxes.append(checkBox)
269 
270  d = Dock(name) # , closable=True)
271  self.docksdocks[name] = d
272  if type == "pg_image":
273  img = pg.ImageItem(border='w', image=np.zeros((2,2)))
274  self.imgsimgs[name] = img
275 
276  viewbox = pg.ViewBox()
277 
278  viewbox.setAspectLocked(True)
279  viewbox.addItem(img) # Put image in plot area
280  self.viewboxesviewboxes[name] = viewbox
281  viewbox.invertY(False)
282 
283  iv = pg.ImageView(view=viewbox, imageItem=img)
284  try:
285  cmap = pg.colormap.get('viridis') # prepare a viridis color map
286  iv.setColorMap(cmap)
287  except:
288  pass
289  if (self.hide_histogramshide_histograms):
290  iv.ui.histogram.hide()
291  iv.ui.histogram.autoHistogramRange() # init levels
292  iv.ui.histogram.setMaximumWidth(100)
293  iv.ui.menuBtn.hide()
294  iv.ui.roiBtn.hide()
295  d.addWidget(iv)
296  elif type == "pg_plot":
297  img = pg.PlotItem(border='w')
298  self.imgsimgs[name] = img
299  d.addWidget(img)
300  elif type == "MPL":
301  img = MatplotlibWidget()
302  self.imgsimgs[name] = img
303  d.addWidget(img)
304  # elif type == "SR":
305  # d.addWidget(self.uiBase.wao_Strehl)
306  return d
307 
308  def load_config(self, *args, **kwargs) -> None:
309  '''
310  Callback when 'LOAD' button is hit
311  '''
312  for groupbox in [
313  self.uiBaseuiBase.wao_phasesgroup_tb, self.uiBaseuiBase.wao_imagesgroup_tb,
314  self.uiBaseuiBase.wao_graphgroup_tb
315  ]:
316  layout = groupbox.menu()
317  while layout and not layout.isEmpty():
318  w = layout.children()[0]
319  layout.removeAction(w)
320  w.setParent(None)
321  self.disp_checkboxesdisp_checkboxes.clear()
322 
323  # TODO: remove self.imgs, self.viewboxes and self.docks children
324  for _, dock in self.docksdocks.items():
325  if dock.isVisible():
326  dock.close()
327 
328  self.docksdocks.clear()
329  self.imgsimgs.clear()
330  self.viewboxesviewboxes.clear()
331 
332  self.wao_phasesgroup_cbwao_phasesgroup_cb = QtWidgets.QMenu(self)
333  self.uiBaseuiBase.wao_phasesgroup_tb.setMenu(self.wao_phasesgroup_cbwao_phasesgroup_cb)
334  self.uiBaseuiBase.wao_phasesgroup_tb.setText('Select')
335  self.uiBaseuiBase.wao_phasesgroup_tb.setPopupMode(QtWidgets.QToolButton.InstantPopup)
336 
337  self.wao_graphgroup_cbwao_graphgroup_cb = QtWidgets.QMenu(self)
338  self.uiBaseuiBase.wao_graphgroup_tb.setMenu(self.wao_graphgroup_cbwao_graphgroup_cb)
339  self.uiBaseuiBase.wao_graphgroup_tb.setText('Select')
340  self.uiBaseuiBase.wao_graphgroup_tb.setPopupMode(QtWidgets.QToolButton.InstantPopup)
341 
342  self.uiBaseuiBase.wao_imagesgroup_tb.setText('Select')
343  self.wao_imagesgroup_cbwao_imagesgroup_cb = QtWidgets.QMenu(self)
344  self.uiBaseuiBase.wao_imagesgroup_tb.setMenu(self.wao_imagesgroup_cbwao_imagesgroup_cb)
345  self.uiBaseuiBase.wao_imagesgroup_tb.setPopupMode(QtWidgets.QToolButton.InstantPopup)
346 
347  # self.uiBase.wao_init.setDisabled(False)
348  #
349  # if (hasattr(self.sim.config, "layout")):
350  # area_filename = self.defaultAreaPath + "/" + self.sim.config.layout + ".area"
351  # self.loadArea(filename=area_filename)
352  #
353  # self.adjustSize()
354 
355  def loadDefaultConfig(self) -> None:
356  import glob
357  parlist = sorted(glob.glob(self.defaultParPathdefaultParPath + "/*.py"))
358  self.uiBaseuiBase.wao_selectConfig.clear()
359  self.uiBaseuiBase.wao_selectConfig.addItems([
360  parlist[i].split('/')[-1] for i in range(len(parlist))
361  ])
362 
363  def init_config(self) -> None:
364  self.loopLockloopLock.acquire(True)
365  self.uiBaseuiBase.wao_load_config.setDisabled(True)
366  self.uiBaseuiBase.wao_init.setDisabled(True)
367  self.threadthread = WorkerThread(self.init_configThreadinit_configThread)
368  self.threadthread.finished.connect(self.init_configFinishedinit_configFinished)
369  self.threadthread.start()
370 
371  def init_configThread(self) -> None:
372  pass
373 
374  def init_configFinished(self) -> None:
375  self.uiBase.wao_load_config.setDisabled(False)
376  self.uiBase.wao_init.setDisabled(False)
377  self.loopLock.release()
378 
379  def updateDisplay(self) -> None:
380  if not self.loopLockloopLock.acquire(False):
381  return
382  else:
383  try:
384  pass
385  finally:
386  self.loopLockloopLock.release()
387 
388  def addSHGrid(self, pg_image, valid_sub, sspsize, pitch):
389  # First remove the old grid, if any
390  if self.PupilLinesPupilLines is not None:
391  pg_image.removeItem(self.PupilLinesPupilLines)
392 
393  nssp_tot = valid_sub[0].size
394  connect = np.ones((nssp_tot, 5), dtype=bool)
395  connect[:, -1] = 0 # don't draw the segment between each trace
396  roi_x = np.ones((nssp_tot, 5), dtype=int)
397  roi_y = np.ones((nssp_tot, 5), dtype=int)
398  for idx_ssp in range(nssp_tot):
399  (x, y) = (valid_sub[0][idx_ssp], valid_sub[1][idx_ssp])
400  roi_x[idx_ssp, :] = [x, x, x + sspsize, x + sspsize, x]
401  roi_y[idx_ssp, :] = [y, y + sspsize, y + sspsize, y, y]
402  self.PupilLinesPupilLines = PupilBoxes(roi_x, roi_y)
403  pg_image.addItem(self.PupilLinesPupilLines)
404 
405  def printInPlace(self, text: str) -> None:
406  # This seems to trigger the GUI and keep it responsive
407  print(text, end='\r', flush=True)
408 
409  def run(self):
410  self.loop_once()
411  if not self.stop:
412  QTimer.singleShot(0, self.runrun) # Update loop
413 
414 
415 class WorkerThread(QThread):
416 
417  def __init__(self, loopFunc: Callable) -> None:
418  QThread.__init__(self)
419  self.loopFuncloopFunc = loopFunc
420 
421  def run(self) -> None:
422  self.loopFuncloopFunc()
423 
424  def stop(self) -> None:
425  pass
426 
427  def cleanUp(self) -> None:
428  pass
def __init__(self, x, y)
x and y are 2D arrays of shape (Nplots, Nsamples)
Definition: widget_base.py:75
None __init__(self, parent=None, hide_histograms=False)
Definition: widget_base.py:91
None closeEvent(self, Any event)
Definition: widget_base.py:150
def saveArea(self, widget, filename=None)
METHODS #.
Definition: widget_base.py:175
def loadArea(self, widget=None, filename=None)
Definition: widget_base.py:207
None addConfigFromFile(self)
Callback when a config file is double clicked in the file browser Place the selected file name in the...
Definition: widget_base.py:244
None quitGUI(self, Any event=None)
Definition: widget_base.py:153
None load_config(self, *args, **kwargs)
Callback when 'LOAD' button is hit.
Definition: widget_base.py:311
def addSHGrid(self, pg_image, valid_sub, sspsize, pitch)
Definition: widget_base.py:388
Dock add_dispDock(self, str name, parent, str type="pg_image")
Definition: widget_base.py:262
area
PYQTGRAPH DockArea INIT #.
Definition: widget_base.py:112
None __init__(self, Callable loopFunc)
Definition: widget_base.py:417
void split(std::vector< std::string > &tokens, const std::string &text, char sep)
Definition: carma_utils.h:71
def uiLoader(moduleName)
Definition: widget_base.py:64