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