Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make raw dialog settings saveable #444

Open
wants to merge 43 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
aa667fd
update master with 3D toolbar
paskino Jun 15, 2023
26517af
Automated autoyapf fixes
invalid-email-address Jun 15, 2023
0c71af7
update CHANGELOG
paskino Jun 15, 2023
1fe6440
works with vtk8
paskino Jun 16, 2023
7c5c184
Merge branch 'master' into toolbar3d
lauramurgatroyd Aug 2, 2023
a645517
Merge branch 'master' into toolbar3d
paskino Jan 23, 2024
428f19c
fixed relu to go between 0 and 1 and then remain at 1
paskino Jan 24, 2024
d18f089
added scale factor for more detailed slider
paskino Jan 24, 2024
da6d6f1
added logging
paskino Jan 24, 2024
d0f70b9
added logging and args parse in cilviewer exe
paskino Jan 24, 2024
322f5e3
Merge branch 'toolbar3d' of github.com:vais-ral/CILViewer into toolbar3d
paskino Jan 24, 2024
c969bb7
Automated autoyapf fixes
invalid-email-address Jan 24, 2024
787bc99
adds scale_factor and max_opacity to parameter at start of cilviewer
paskino Jan 24, 2024
9287248
Automated autoyapf fixes
invalid-email-address Jan 24, 2024
928c411
make the slider work for the slice
paskino Jan 24, 2024
03ea2a0
use sliderChanged
paskino Jan 24, 2024
bc2bf25
Merge branch 'master' into toolbar3d
lauramurgatroyd May 23, 2024
97ce493
merge with master
lauramurgatroyd May 23, 2024
8d6ec94
Automated autoyapf fixes
invalid-email-address May 23, 2024
c47477b
add createAnimation
paskino Jun 11, 2024
c40775f
Merge branch 'toolbar3d' of github.com:vais-ral/CILViewer into toolbar3d
paskino Jun 11, 2024
6df3f46
create an example to make volume renders and orbits programmatically
paskino Jun 26, 2024
88abdc2
fixed save and naming
paskino Jun 27, 2024
32d08e8
updates to script
paskino Jun 28, 2024
2c21767
updates
paskino Jul 1, 2024
10e0dcb
added argparser
paskino Jul 1, 2024
69d85ba
Merge branch 'master' into toolbar3d
paskino Sep 25, 2024
1865cdb
Merge branch 'toolbar3d_vol_render' into toolbar3d
paskino Sep 25, 2024
7cfe7bb
add max opacity control as spinbox
paskino Sep 25, 2024
7247ca8
Automated autoyapf fixes
invalid-email-address Sep 25, 2024
9bb3fa9
Make raw dialog settings saveable
lauramurgatroyd Dec 10, 2024
beaa24a
Move save name into dialog and add edit checkbox
lauramurgatroyd Dec 18, 2024
28c168d
Add unit tests
lauramurgatroyd Dec 18, 2024
6c503b9
reverting other files to master
lauramurgatroyd Dec 18, 2024
b509614
Automated autoyapf fixes
invalid-email-address Dec 18, 2024
33219ad
reverting other files to master
lauramurgatroyd Dec 18, 2024
58d3ae7
reverting other files to master
lauramurgatroyd Dec 18, 2024
2db3fcf
Merge branch 'master' into raw_dialog
lauramurgatroyd Dec 18, 2024
c2487e9
Update change log
lauramurgatroyd Dec 18, 2024
e348aa2
Rename checkbox
lauramurgatroyd Dec 18, 2024
0828306
Automated autoyapf fixes
invalid-email-address Dec 18, 2024
91cfc4c
Update conda_build_and_publish.yml
lauramurgatroyd Jan 8, 2025
1f27e25
Update docker_build_test_publish.yml
lauramurgatroyd Jan 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/conda_build_and_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:

jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docker_build_test_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:

jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3

Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## vx.x.x
Enhancements:
- Add `SaveableRawInputDialog` #444
- Standalone viewer uses `SaveableRawInputDialog` - this allows user to save and reload settings for loading raw data

## v24.1.0

Enhancements:
Expand Down
Empty file.
175 changes: 132 additions & 43 deletions Wrappers/Python/ccpi/viewer/ui/dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from eqt.ui import FormDialog
from eqt.ui.SessionDialogs import AppSettingsDialog, ErrorDialog
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtWidgets import QCheckBox, QDoubleSpinBox, QLabel, QLineEdit, QComboBox
from PySide2.QtWidgets import QCheckBox, QDoubleSpinBox, QLabel, QLineEdit, QComboBox, QPushButton
import numpy as np
from ccpi.viewer.utils import Converter
from ccpi.viewer.utils.conversion import cilRawCroppedReader
Expand Down Expand Up @@ -260,48 +260,6 @@ def preview(self):
reader2.SetTypeCodeName(dt.name)
reader2.SetStoredArrayShape(shape)
reader2.Update()
# image = reader2.GetOutput()

# rawfname = os.path.join(tempfile.gettempdir(),"test.raw")

# offset = offset * bytes_per_element
# slices_to_read = 1
# if shape[2] > 1:
# slices_to_read = 2
# with open(self.fname, 'br') as f:
# f.seek(offset)
# raw_data = f.read(slice_size*bytes_per_element* slices_to_read)
# with open(rawfname, 'wb') as f2:
# f2.write(raw_data)

# reader2 = vtk.vtkImageReader2()
# reader2.SetFileName(rawfname)

# vtktype = Converter.dtype_name_to_vtkType[dt.name]
# reader2.SetDataScalarType(vtktype)

# if isBigEndian:
# reader2.SetDataByteOrderToBigEndian()
# else:
# reader2.SetDataByteOrderToLittleEndian()

# reader2.SetFileDimensionality(len(shape))
# vtkshape = shape[:]
# if not isFortran:
# # need to reverse the shape (again)
# vtkshape = shape[::-1]
# # vtkshape = shape[:]
# slice_idx = 0
# if dimensionality == 3:
# slice_idx = vtkshape[2]//2
# reader2.SetDataExtent(0, vtkshape[0]-1, 0, vtkshape[1]-1, slice_idx, slice_idx+slices_to_read-1)
# # DataSpacing and DataOrigin should be added to the interface
# reader2.SetDataSpacing(1, 1, 1)
# reader2.SetDataOrigin(0, 0, 0)

# print("reading")
# reader2.Update()
# read one slice in the middle and display it in a viewer in a modal dialog

diag = QtWidgets.QDialog(parent=self)
diag.setModal(True)
Expand Down Expand Up @@ -338,6 +296,137 @@ def preview(self):
diag.open()


class SaveableRawInputDialog(RawInputDialog):
'''
This is a dialog window which allows the user to set information
for a raw file, including:
- dimensionality
- size of dimensions
- data type
- endianness
- fortran ordering

The dialog can let the user preview the data and verify that it is correct.

The dialog allows you to save load settings under a memorable name.
You can reload settings you have saved previously, by selecting their associated name from a dropdown.
'''

def __init__(self, parent, fname, qsettings):
super(SaveableRawInputDialog, self).__init__(parent, fname)

self.settings = qsettings
Comment on lines +315 to +318
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we create a qsettings in here if one isn't passed in?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test this case


self.formWidget.addSpanningWidget(QCheckBox("Edit Parameters"), 'enable_edit')
self.getWidget('enable_edit').setChecked(True)
self.getWidget('enable_edit').stateChanged.connect(self._change_edit_state)

self.formWidget.addTitle(QLabel('Load Settings'), 'load_settings_title')
load_label = QLabel('Settings Name: ')
load_drop_down = QComboBox()
load_drop_down.addItems(self._get_settings_names_for_dialog())
self.formWidget.addWidget(load_drop_down, load_label, 'load_name')

load_button = QPushButton('Load Settings')
load_button.clicked.connect(self._load_settings)
self.formWidget.addSpanningWidget(load_button, 'load')

self.buttonBox.addButton(QtWidgets.QDialogButtonBox.Save)
self.buttonBox.button(QtWidgets.QDialogButtonBox.Save).clicked.connect(self._open_save_dialog)

def _change_edit_state(self, editable=True):
'''Changes the edit state of the form'''

widgets = self.getWidgets()
for widget in widgets.values():
widget.setEnabled(editable)

if not editable:
widgets_to_preserve = [
'load_name', 'load', 'enable_edit', 'load_name', 'load_settings_title', 'preview_slice',
'preview_button'
]
for widget in widgets_to_preserve:
self.getWidget(widget, 'field').setEnabled(True)
try:
self.getWidget(widget, 'label').setEnabled(True)
except:
pass

def _open_save_dialog(self):
'''Opens dialog for specifiying name to save settings under'''
dialog = FormDialog(self)
dialog.formWidget.addTitle(QLabel('Save Settings'), 'save_settings_title')
save_label = QLabel('Settings Name: ')
save_box = QLineEdit()
dialog.formWidget.addWidget(save_box, save_label, 'save_name')
dialog.Cancel.clicked.connect(dialog.close)
dialog.Ok.clicked.connect(self._save_settings)
dialog.Ok.clicked.connect(dialog.close)
dialog.open()
self.save_dialog = dialog

def _get_settings_save_name(self):
return self.save_dialog.getWidget('save_name').text()

def _save_settings(self):
'''
Adds a dictionary to the qsettings 'raw_dialog'
dictionary :
key: name entered by the user
value: the status of all widgets on the form
'''
settings_dict = self.settings.value('raw_dialog', {})

settings_name = self._get_settings_save_name()

self.saveAllWidgetStates()
current_widget_status = self.getSavedWidgetStates()

settings_dict[settings_name] = current_widget_status

self.settings.setValue('raw_dialog', settings_dict)

def _get_settings_names_for_dialog(self):
'''
Retrive from self.settings the names of all settings previously saved in the
'raw_dialog' entry
'''
settings_dict = self.settings.value('raw_dialog', {})
return settings_dict.keys()

def _get_name_of_state_to_load(self):
return self.formWidget.getWidget('load_name').currentText()

def _load_settings(self):
'''
Load all of the widget states saved in the 'raw_dialog' entry of
self.settings under the name selected by the user from the load_name comobox

Disable editing of parameters.
'''
settings_found = False
if self.settings.value('raw_dialog'):
settings_dict = self.settings.value('raw_dialog', {})

name_of_state = self._get_name_of_state_to_load()
state = settings_dict.get(name_of_state)

if state is not None:

# save current stae

self.applyWidgetStates(state)
self.getWidget('enable_edit').setChecked(False)

# load current state of dropdown
settings_found = True

if not settings_found:
# create error dialog:
print("Settings not found")


class HDF5InputDialog(FormDialog):
'''
This is a dialog window which allows the user to set:
Expand Down
6 changes: 3 additions & 3 deletions Wrappers/Python/ccpi/viewer/ui/main_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from ccpi.viewer.CILViewer2D import CILViewer2D
from ccpi.viewer.CILViewer import CILViewer
from ccpi.viewer.QCILViewerWidget import QCILDockableWidget
from ccpi.viewer.ui.dialogs import HDF5InputDialog, RawInputDialog, ViewerSettingsDialog
from ccpi.viewer.ui.dialogs import HDF5InputDialog, RawInputDialog, ViewerSettingsDialog, SaveableRawInputDialog
from ccpi.viewer.ui.qt_widgets import ViewerCoordsDockWidget
from ccpi.viewer.utils import cilPlaneClipper
from ccpi.viewer.utils.io import ImageReader
Expand Down Expand Up @@ -178,7 +178,7 @@ def selectImage(self, label=None):
if hasattr(self, 'raw_dialog'):
self.raw_dialog.restoreAllSavedWidgetStates()
else:
raw_dialog = RawInputDialog(self, file)
raw_dialog = SaveableRawInputDialog(self, file, self.settings)
raw_dialog.Ok.clicked.connect(lambda: self.getRawAttrsFromDialog(raw_dialog))
self.raw_dialog = raw_dialog
# See https://doc.qt.io/qt-6/qdialog.html#exec
Expand Down Expand Up @@ -210,7 +210,7 @@ def getRawAttrsFromDialog(self, dialog):

Parameters
----------
dialog : RawInputDialog
dialog : SaveableRawInputDialog
The dialog to get the attributes from.
'''
dialog.saveAllWidgetStates()
Expand Down
66 changes: 65 additions & 1 deletion Wrappers/Python/test/test_ui_dialogs.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import unittest
from unittest import mock
from ccpi.viewer.ui.dialogs import ViewerSettingsDialog, HDF5InputDialog, RawInputDialog
from ccpi.viewer.ui.dialogs import ViewerSettingsDialog, HDF5InputDialog, RawInputDialog, SaveableRawInputDialog
from eqt.ui.SessionDialogs import AppSettingsDialog

from PySide2.QtWidgets import QMainWindow
import os

from unittest import mock
from unittest.mock import patch
from PySide2.QtWidgets import QApplication, QLabel, QFrame, QDoubleSpinBox, QCheckBox, QPushButton, QLineEdit, QComboBox, QWidget
from PySide2.QtCore import QSettings
from eqt.ui import FormDialog
from functools import partial

import sys

Expand Down Expand Up @@ -204,5 +208,65 @@ def test_getRawAttrs(self):
}


@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces")
class TestSaveableRawInputDialog(unittest.TestCase):

def setUp(self):
global _instance
if _instance is None:
_instance = QApplication(sys.argv)
self.parent = QMainWindow()
self.settings = QSettings()
self.fname = "test.raw"

@patch("ccpi.viewer.ui.dialogs.RawInputDialog.__init__")
def test_init_calls_raw_input_dialog_init(self, mock_init_call):
mock_init_call.return_value = partial(FormDialog.__init__)
# expect attribute error after init call:
with self.assertRaises(AttributeError):
rdi = SaveableRawInputDialog(self.parent, self.fname, self.settings)
mock_init_call.assert_called_once()

def test_init(self):
rdi = SaveableRawInputDialog(self.parent, self.fname, self.settings)
assert rdi is not None

@patch("ccpi.viewer.ui.dialogs.SaveableRawInputDialog._get_settings_save_name")
def test_save_settings_when_nothing_in_qsettings(self, mock_get_name):
mock_get_name.return_value = "my_name"
empty_settings = QSettings('A', 'A')
rdi = SaveableRawInputDialog(self.parent, self.fname, empty_settings)
rdi._save_settings()
the_dict = empty_settings.value('raw_dialog')
self.assertEqual(empty_settings.allKeys(), ['raw_dialog'])
self.assertEqual(the_dict, {'my_name': rdi.getAllWidgetStates()})

@patch("ccpi.viewer.ui.dialogs.SaveableRawInputDialog._get_settings_save_name")
def test_save_settings_when_soemthing_in_qsettings(self, mock_get_name):
mock_get_name.return_value = "my_name_2"
pop_settings = QSettings('C', 'D')
pop_settings.setValue('raw_dialog', {'hi': "I'm not empty"})
rdi = SaveableRawInputDialog(self.parent, self.fname, pop_settings)
rdi._save_settings()
the_dict = pop_settings.value('raw_dialog')
self.assertEqual(the_dict, {'hi': "I'm not empty", 'my_name_2': rdi.getAllWidgetStates()})

@patch("ccpi.viewer.ui.dialogs.SaveableRawInputDialog._get_name_of_state_to_load")
def test_load_settings(self, mock_get_name):
mock_get_name.return_value = "state"
example_qsettings = QSettings('B', 'B')
rdi = SaveableRawInputDialog(self.parent, self.fname, example_qsettings)
rdi.getWidget('dim_Images').setText('10')
example_settings = rdi.getAllWidgetStates()

example_qsettings = QSettings('B', 'B')

example_qsettings.setValue('raw_dialog', {'state': example_settings})

rdi2 = SaveableRawInputDialog(self.parent, self.fname, example_qsettings)
rdi2._load_settings()
self.assertEqual(rdi2.getWidget('dim_Images').text(), "10")


if __name__ == '__main__':
unittest.main()
Loading