Skip to content

Commit 5ab1d76

Browse files
Make raw dialog settings saveable (#444)
1 parent 4cf1960 commit 5ab1d76

File tree

5 files changed

+233
-51
lines changed

5 files changed

+233
-51
lines changed

CHANGELOG.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@
22

33
## vx.x.x
44

5+
Enhancements:
6+
- Add `SaveableRawInputDialog` #444
7+
- Standalone viewer uses `SaveableRawInputDialog` - this allows user to save and reload settings for loading raw data #444
8+
59
Requirements:
610
- Reduced the requirements to the basic packages, add extra requirements in `ui_env.yml` file #451
711

812
Documentation:
913
- Update readme, contributing, documentation #451
1014

1115
Build and CI:
12-
- Renamed mambaforge to miniforge to fix docker action #446
13-
- Use ubuntu v22.04 in the actions #446
14-
- Add setuptools as a build requirment #446
16+
- Renamed mambaforge to miniforge to fix docker action #446
17+
- Use ubuntu v22.04 in the actions #446
18+
- Add setuptools as a build requirement #446
19+
1520

1621
## v24.1.0
1722

Wrappers/Python/ccpi/viewer/ui/__init__.py

Whitespace-only changes.

Wrappers/Python/ccpi/viewer/ui/dialogs.py

Lines changed: 143 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from eqt.ui import FormDialog
44
from eqt.ui.SessionDialogs import AppSettingsDialog, ErrorDialog
55
from PySide2 import QtCore, QtGui, QtWidgets
6-
from PySide2.QtWidgets import QCheckBox, QDoubleSpinBox, QLabel, QLineEdit, QComboBox
6+
from PySide2.QtWidgets import QCheckBox, QDoubleSpinBox, QLabel, QLineEdit, QComboBox, QPushButton
77
import numpy as np
88
from ccpi.viewer.utils import Converter
99
from ccpi.viewer.utils.conversion import cilRawCroppedReader
@@ -260,48 +260,6 @@ def preview(self):
260260
reader2.SetTypeCodeName(dt.name)
261261
reader2.SetStoredArrayShape(shape)
262262
reader2.Update()
263-
# image = reader2.GetOutput()
264-
265-
# rawfname = os.path.join(tempfile.gettempdir(),"test.raw")
266-
267-
# offset = offset * bytes_per_element
268-
# slices_to_read = 1
269-
# if shape[2] > 1:
270-
# slices_to_read = 2
271-
# with open(self.fname, 'br') as f:
272-
# f.seek(offset)
273-
# raw_data = f.read(slice_size*bytes_per_element* slices_to_read)
274-
# with open(rawfname, 'wb') as f2:
275-
# f2.write(raw_data)
276-
277-
# reader2 = vtk.vtkImageReader2()
278-
# reader2.SetFileName(rawfname)
279-
280-
# vtktype = Converter.dtype_name_to_vtkType[dt.name]
281-
# reader2.SetDataScalarType(vtktype)
282-
283-
# if isBigEndian:
284-
# reader2.SetDataByteOrderToBigEndian()
285-
# else:
286-
# reader2.SetDataByteOrderToLittleEndian()
287-
288-
# reader2.SetFileDimensionality(len(shape))
289-
# vtkshape = shape[:]
290-
# if not isFortran:
291-
# # need to reverse the shape (again)
292-
# vtkshape = shape[::-1]
293-
# # vtkshape = shape[:]
294-
# slice_idx = 0
295-
# if dimensionality == 3:
296-
# slice_idx = vtkshape[2]//2
297-
# reader2.SetDataExtent(0, vtkshape[0]-1, 0, vtkshape[1]-1, slice_idx, slice_idx+slices_to_read-1)
298-
# # DataSpacing and DataOrigin should be added to the interface
299-
# reader2.SetDataSpacing(1, 1, 1)
300-
# reader2.SetDataOrigin(0, 0, 0)
301-
302-
# print("reading")
303-
# reader2.Update()
304-
# read one slice in the middle and display it in a viewer in a modal dialog
305263

306264
diag = QtWidgets.QDialog(parent=self)
307265
diag.setModal(True)
@@ -338,6 +296,148 @@ def preview(self):
338296
diag.open()
339297

340298

299+
class SaveableRawInputDialog(RawInputDialog):
300+
'''
301+
This is a dialog window which allows the user to set information
302+
for a raw file, including:
303+
- dimensionality
304+
- size of dimensions
305+
- data type
306+
- endianness
307+
- fortran ordering
308+
309+
The dialog can let the user preview the data and verify that it is correct.
310+
311+
The dialog allows you to save load settings under a memorable name.
312+
You can reload settings you have saved previously, by selecting their associated name from a dropdown.
313+
'''
314+
315+
def __init__(self, parent, fname, qsettings=None):
316+
super(SaveableRawInputDialog, self).__init__(parent, fname)
317+
318+
if qsettings is None:
319+
qsettings = QtCore.QSettings("CCPi", "CILViewer Raw Dialog")
320+
self.settings = qsettings
321+
322+
self.formWidget.addSpanningWidget(QCheckBox("Edit Parameters"), 'enable_edit')
323+
self.getWidget('enable_edit').setChecked(True)
324+
self.getWidget('enable_edit').stateChanged.connect(self._change_edit_state)
325+
326+
self.formWidget.addTitle(QLabel('Load Settings'), 'load_settings_title')
327+
load_label = QLabel('Settings Name: ')
328+
load_drop_down = QComboBox()
329+
self.formWidget.addWidget(load_drop_down, load_label, 'load_name')
330+
self._update_load_combobox()
331+
332+
load_button = QPushButton('Load Settings')
333+
load_button.clicked.connect(self._load_settings)
334+
self.formWidget.addSpanningWidget(load_button, 'load')
335+
336+
self.buttonBox.addButton(QtWidgets.QDialogButtonBox.Save)
337+
self.buttonBox.button(QtWidgets.QDialogButtonBox.Save).clicked.connect(self._open_save_dialog)
338+
339+
def _update_load_combobox(self):
340+
load_drop_down = self.getWidget('load_name')
341+
load_drop_down.clear()
342+
load_drop_down.addItems(self._get_settings_names_for_dialog())
343+
344+
def _change_edit_state(self, editable=True):
345+
'''Changes the edit state of the form'''
346+
347+
widgets = self.getWidgets()
348+
for widget in widgets.values():
349+
widget.setEnabled(editable)
350+
351+
if not editable:
352+
widgets_to_preserve = [
353+
'load_name', 'load', 'enable_edit', 'load_name', 'load_settings_title', 'preview_slice',
354+
'preview_button'
355+
]
356+
for widget in widgets_to_preserve:
357+
self.getWidget(widget, 'field').setEnabled(True)
358+
try:
359+
self.getWidget(widget, 'label').setEnabled(True)
360+
except:
361+
pass
362+
363+
def _open_save_dialog(self):
364+
'''Opens dialog for specifiying name to save settings under'''
365+
dialog = FormDialog(self)
366+
dialog.formWidget.addTitle(QLabel('Save Settings'), 'save_settings_title')
367+
save_label = QLabel('Settings Name: ')
368+
save_box = QLineEdit()
369+
dialog.formWidget.addWidget(save_box, save_label, 'save_name')
370+
dialog.Cancel.clicked.connect(dialog.close)
371+
dialog.Ok.clicked.connect(self._save_settings)
372+
dialog.Ok.clicked.connect(dialog.close)
373+
dialog.open()
374+
self.save_dialog = dialog
375+
376+
def _get_settings_save_name(self):
377+
return self.save_dialog.getWidget('save_name').text()
378+
379+
def _save_settings(self):
380+
'''
381+
Adds a dictionary to the qsettings 'raw_dialog'
382+
dictionary :
383+
key: name entered by the user
384+
value: the status of all widgets on the form
385+
'''
386+
settings_dict = self.settings.value('raw_dialog', {})
387+
388+
settings_name = self._get_settings_save_name()
389+
390+
self.saveAllWidgetStates()
391+
current_widget_status = self.getSavedWidgetStates()
392+
393+
settings_dict[settings_name] = current_widget_status
394+
395+
self.settings.setValue('raw_dialog', settings_dict)
396+
397+
self._update_load_combobox()
398+
399+
def _get_settings_names_for_dialog(self):
400+
'''
401+
Retrive from self.settings the names of all settings previously saved in the
402+
'raw_dialog' entry
403+
'''
404+
settings_dict = self.settings.value('raw_dialog', {})
405+
return settings_dict.keys()
406+
407+
def _get_name_of_state_to_load(self):
408+
return self.formWidget.getWidget('load_name').currentText()
409+
410+
def _load_settings(self):
411+
'''
412+
Load all of the widget states saved in the 'raw_dialog' entry of
413+
self.settings under the name selected by the user from the load_name combobox
414+
415+
Disable editing of parameters.
416+
'''
417+
settings_found = False
418+
if self.settings.value('raw_dialog'):
419+
settings_dict = self.settings.value('raw_dialog', {})
420+
421+
name_of_state = self._get_name_of_state_to_load()
422+
state = settings_dict.get(name_of_state)
423+
424+
if state is not None:
425+
426+
# save current stae
427+
428+
self.applyWidgetStates(state)
429+
self.getWidget('enable_edit').setChecked(False)
430+
431+
# load current state of dropdown
432+
settings_found = True
433+
434+
self.getWidget('load_name').setCurrentText(name_of_state)
435+
436+
if not settings_found:
437+
# create error dialog:
438+
print("Settings not found")
439+
440+
341441
class HDF5InputDialog(FormDialog):
342442
'''
343443
This is a dialog window which allows the user to set:

Wrappers/Python/ccpi/viewer/ui/main_windows.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from ccpi.viewer.CILViewer2D import CILViewer2D
99
from ccpi.viewer.CILViewer import CILViewer
1010
from ccpi.viewer.QCILViewerWidget import QCILDockableWidget
11-
from ccpi.viewer.ui.dialogs import HDF5InputDialog, RawInputDialog, ViewerSettingsDialog
11+
from ccpi.viewer.ui.dialogs import HDF5InputDialog, RawInputDialog, ViewerSettingsDialog, SaveableRawInputDialog
1212
from ccpi.viewer.ui.qt_widgets import ViewerCoordsDockWidget
1313
from ccpi.viewer.utils import cilPlaneClipper
1414
from ccpi.viewer.utils.io import ImageReader
@@ -178,12 +178,12 @@ def selectImage(self, label=None):
178178
if hasattr(self, 'raw_dialog'):
179179
self.raw_dialog.restoreAllSavedWidgetStates()
180180
else:
181-
raw_dialog = RawInputDialog(self, file)
181+
raw_dialog = SaveableRawInputDialog(self, file, self.settings)
182182
raw_dialog.Ok.clicked.connect(lambda: self.getRawAttrsFromDialog(raw_dialog))
183183
self.raw_dialog = raw_dialog
184184
# See https://doc.qt.io/qt-6/qdialog.html#exec
185185
# Shows a modal dialog, blocking until the user closes it.
186-
raw_dialog.exec()
186+
self.raw_dialog.exec()
187187
if self.raw_attrs == {}:
188188
return None
189189
elif file_extension in ['.nxs', '.h5', '.hdf5']:
@@ -210,7 +210,7 @@ def getRawAttrsFromDialog(self, dialog):
210210
211211
Parameters
212212
----------
213-
dialog : RawInputDialog
213+
dialog : SaveableRawInputDialog
214214
The dialog to get the attributes from.
215215
'''
216216
dialog.saveAllWidgetStates()

Wrappers/Python/test/test_ui_dialogs.py

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import unittest
22
from unittest import mock
3-
from ccpi.viewer.ui.dialogs import ViewerSettingsDialog, HDF5InputDialog, RawInputDialog
3+
from ccpi.viewer.ui.dialogs import ViewerSettingsDialog, HDF5InputDialog, RawInputDialog, SaveableRawInputDialog
44
from eqt.ui.SessionDialogs import AppSettingsDialog
55

66
from PySide2.QtWidgets import QMainWindow
77
import os
88

99
from unittest import mock
10+
from unittest.mock import patch
1011
from PySide2.QtWidgets import QApplication, QLabel, QFrame, QDoubleSpinBox, QCheckBox, QPushButton, QLineEdit, QComboBox, QWidget
12+
from PySide2.QtCore import QSettings
13+
from eqt.ui import FormDialog
14+
from functools import partial
1115

1216
import sys
1317

@@ -204,5 +208,78 @@ def test_getRawAttrs(self):
204208
}
205209

206210

211+
@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces")
212+
class TestSaveableRawInputDialog(unittest.TestCase):
213+
214+
def setUp(self):
215+
global _instance
216+
if _instance is None:
217+
_instance = QApplication(sys.argv)
218+
self.parent = QMainWindow()
219+
self.settings = QSettings()
220+
self.fname = "test.raw"
221+
222+
@patch("ccpi.viewer.ui.dialogs.RawInputDialog.__init__")
223+
def test_init_calls_raw_input_dialog_init(self, mock_init_call):
224+
mock_init_call.return_value = partial(FormDialog.__init__)
225+
# expect attribute error after init call:
226+
with self.assertRaises(AttributeError):
227+
rdi = SaveableRawInputDialog(self.parent, self.fname, self.settings)
228+
mock_init_call.assert_called_once()
229+
230+
def test_init(self):
231+
rdi = SaveableRawInputDialog(self.parent, self.fname, self.settings)
232+
assert rdi is not None
233+
234+
def test_init_no_settings(self):
235+
rdi = SaveableRawInputDialog(self.parent, self.fname)
236+
assert rdi is not None
237+
238+
@patch("ccpi.viewer.ui.dialogs.SaveableRawInputDialog._get_settings_save_name")
239+
def test_save_settings_when_nothing_in_qsettings(self, mock_get_name):
240+
mock_get_name.return_value = "my_name"
241+
empty_settings = QSettings('A', 'A')
242+
rdi = SaveableRawInputDialog(self.parent, self.fname, empty_settings)
243+
rdi._save_settings()
244+
the_dict = empty_settings.value('raw_dialog')
245+
self.assertEqual(empty_settings.allKeys(), ['raw_dialog'])
246+
self.assertEqual(the_dict, {'my_name': rdi.getAllWidgetStates()})
247+
248+
@patch("ccpi.viewer.ui.dialogs.SaveableRawInputDialog._get_settings_save_name")
249+
def test_save_settings_when_no_qsettings(self, mock_get_name):
250+
mock_get_name.return_value = "my_name"
251+
rdi = SaveableRawInputDialog(self.parent, self.fname)
252+
rdi._save_settings()
253+
the_dict = rdi.settings.value('raw_dialog')
254+
self.assertEqual(rdi.settings.allKeys(), ['raw_dialog'])
255+
self.assertEqual(the_dict, {'my_name': rdi.getAllWidgetStates()})
256+
257+
@patch("ccpi.viewer.ui.dialogs.SaveableRawInputDialog._get_settings_save_name")
258+
def test_save_settings_when_soemthing_in_qsettings(self, mock_get_name):
259+
mock_get_name.return_value = "my_name_2"
260+
pop_settings = QSettings('C', 'D')
261+
pop_settings.setValue('raw_dialog', {'hi': "I'm not empty"})
262+
rdi = SaveableRawInputDialog(self.parent, self.fname, pop_settings)
263+
rdi._save_settings()
264+
the_dict = pop_settings.value('raw_dialog')
265+
self.assertEqual(the_dict, {'hi': "I'm not empty", 'my_name_2': rdi.getAllWidgetStates()})
266+
267+
@patch("ccpi.viewer.ui.dialogs.SaveableRawInputDialog._get_name_of_state_to_load")
268+
def test_load_settings(self, mock_get_name):
269+
mock_get_name.return_value = "state"
270+
example_qsettings = QSettings('B', 'B')
271+
rdi = SaveableRawInputDialog(self.parent, self.fname, example_qsettings)
272+
rdi.getWidget('dim_Images').setText('10')
273+
example_settings = rdi.getAllWidgetStates()
274+
275+
example_qsettings = QSettings('B', 'B')
276+
277+
example_qsettings.setValue('raw_dialog', {'state': example_settings})
278+
279+
rdi2 = SaveableRawInputDialog(self.parent, self.fname, example_qsettings)
280+
rdi2._load_settings()
281+
self.assertEqual(rdi2.getWidget('dim_Images').text(), "10")
282+
283+
207284
if __name__ == '__main__':
208285
unittest.main()

0 commit comments

Comments
 (0)