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

Read/Save dead-time options to global state #122

Merged
merged 11 commits into from
May 7, 2024
8 changes: 2 additions & 6 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,8 @@
<!-- description here. -->

# Manual test for the reviewer
Before running the manual tests, install the conda environment and install the source in editable mode
```bash
> conda env create --solver libmamba --name refred-dev --file ./environment.yml
> conda activate refred-dev
(refred-dev)> pip install -e .
```
([instructions to set up the environment](https://github.com/neutrons/RefRed/blob/next/dev-docs/testing.rst#running-manual-tests-in-a-pull-request))

Start RefRed GUI
```bash
(refred-dev)> PYTHONPATH=$(pwd):$PYTHONPATH ./scripts/start_refred.py
Expand Down
2 changes: 1 addition & 1 deletion RefRed/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def _create_proxy():
add them to the ConfigProxy object that stores
all information in .ini files. The usage in other
modules is the same for both cases when no parameter
is imported dirctly from the submodule.
is imported directly from the submodule.
'''
global proxy, __all__
proxy = _ConfigProxy(_config_path)
Expand Down
61 changes: 15 additions & 46 deletions RefRed/configuration/export_xml_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,14 @@
# package imports
import RefRed
from RefRed.calculations.lr_data import LRData
from RefRed.gui_handling.gui_utility import GuiUtility
from RefRed.reduction.global_reduction_settings_handler import GlobalReductionSettingsHandler


class ExportXMLConfig(object):

parent = None
filename = ''
str_array = []

def __init__(self, parent=None, filename=''):
self.init_variables()

def __init__(self, parent=None):
self.parent = parent
self.filename = filename

self.prepare_big_table_data()
self.header_part()
self.main_part()
self.save_xml()

def init_variables(self):
self.filename = ''
self.str_array = []

def prepare_big_table_data(self):
"""
all data files used last data clocking values
"""

big_table_data = self.parent.big_table_data
o_gui_utility = GuiUtility(parent=self.parent)
row_highest_q = o_gui_utility.get_row_with_highest_q()

for row in range(row_highest_q):
_lrdata = big_table_data[row, 0]
big_table_data[row, 0] = _lrdata

self.parent.big_table_data = big_table_data

def header_part(self):
str_array = self.str_array
str_array.append('<Reduction>\n')
Expand All @@ -76,14 +44,11 @@ def main_part(self):

_data: LRData = _big_table_data[row, 0]
if _data is None:
break
break # we found the first empty row in the reduction table. No more runs available

str_array.append(' <RefLData>\n')
str_array.append(' <peak_selection_type>narrow</peak_selection_type>\n')

# data_full_file_name = _data.full_file_name
# if type(data_full_file_name) == type([]):
# data_full_file_name = ','.join(data_full_file_name)
data_peak = _data.peak
data_back = _data.back
data_back2 = _data.back2
Expand Down Expand Up @@ -210,24 +175,28 @@ def main_part(self):

str_array.append(' <slits_width_flag>True</slits_width_flag>\n')

# Dead time correction
dead_time = o_general_settings.dead_time
str_array.append(' <dead_time_correction>' + str(dead_time) + '</dead_time_correction>\n')
str_array.append(o_general_settings.dead_time.to_xml(indent=" ") + "\n") # dead time settings

str_array.append(' </RefLData>\n')

str_array.append(' </DataSeries>\n')
str_array.append('</Reduction>\n')
self.str_array = str_array

def save_xml(self):
filename = self.filename
str_array = self.str_array
def save(self, filename: str):
r"""Save the current configuration for each run

Parameters
----------
filename: str
The name of the file to which the configuration will be saved.
"""
self.header_part()
self.main_part()

# write out XML file
if os.path.isfile(filename):
os.remove(filename)

with open(filename, 'w') as outfile:
outfile.writelines(str_array)
outfile.writelines(self.str_array)

logging.info(f"Config is saved to {filename}.")
10 changes: 10 additions & 0 deletions RefRed/configuration/global_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# third party imports
from pydantic import BaseModel


class GlobalSettings(BaseModel):
r"""Base class for a specific global settings class.

See `DeadTimeSettingsModel` for an example
"""
pass
6 changes: 4 additions & 2 deletions RefRed/configuration/loading_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def populate_main_gui_general_settings(self):
logging.warning("No RefLData found in config, skipping.")
return

# Assumption: all <RefLData> entries have the same global settings, so the first will suffice
node_0 = RefLData[0]

q_step = self.getNodeValue(node_0, 'q_step')
Expand Down Expand Up @@ -179,8 +180,9 @@ def populate_main_gui_general_settings(self):
o_scaling_factor_widget.checkbox(status=scaling_factor_flag)
o_scaling_factor_widget.set_enabled(status=scaling_factor_flag)

use_dead_time = str2bool(self.getNodeValue(node_0, 'dead_time_correction'))
self.parent.ui.deadtime_entry.applyCheckBox.setChecked(use_dead_time)
# initialize the dead time settings
self.parent.deadtime_settings.from_xml(node_0)
self.parent.ui.deadtime_entry.applyCheckBox.setChecked(self.parent.deadtime_settings.apply_deadtime)

def getMetadataObject(self, node) -> LConfigDataset:
r"""Populate an instance of type LConfigDataset using the information contained in one of the
Expand Down
2 changes: 1 addition & 1 deletion RefRed/configuration/saving_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def run(self):

self.parent.path_config = os.path.dirname(self.filename)
self.filename = makeSureFileHasExtension(self.filename)
ExportXMLConfig(parent=self.parent, filename=self.filename)
ExportXMLConfig(parent=self.parent).save(self.filename)

StatusMessageHandler(parent=self.parent, message='Done!', is_threaded=True)

Expand Down
119 changes: 111 additions & 8 deletions RefRed/interfaces/deadtime_settings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,106 @@
# third-party imports
from qtpy.QtWidgets import QDialog, QWidget
from xml.dom.minidom import Document, Element
from typing import Any, Callable, Dict

# RefRed imports
from RefRed.configuration.global_settings import GlobalSettings
from RefRed.interfaces import load_ui
from RefRed.utilities import str2bool


class DeadTimeSettingsModel(GlobalSettings):
r"""Stores all options for the dead time correction. These are global options"""

# pydantic fields
apply_deadtime: bool = True
paralyzable: bool = True
dead_time: float = 4.2
tof_step: float = 150.0

# class variable, translates fields to XML tag names, same names as the lr_reduction package
_to_xmltag = {
"apply_deadtime": "dead_time_correction",
"paralyzable": "dead_time_paralyzable",
"dead_time": "dead_time_value",
"tof_step": "dead_time_tof_step",
}

def to_xml(self, indent: str = "") -> str:
r"""Serialize the settings to XML format.

XML tags names are those expected by lr_reduction.reduction_template_reader.from_xml()

Parameters
----------
indent: str
Prefix to prepend each line of XML. Typically, a certain number of spaces

Example
-------
One example of the expected output by this method would look like:
<dead_time_correction>True</dead_time_correction>
<dead_time_paralyzable>True</dead_time_paralyzable>
<dead_time_value>4.2</dead_time_value>
<dead_time_tof_step>150</dead_time_tof_step>
"""
doc = Document() # Create a new XML document

def _to_xml(field: str) -> str:
r"""Serialize one of the dead time settings to XML format"""
element = doc.createElement(self._to_xmltag[field])
element_text = doc.createTextNode(str(getattr(self, field)))
element.appendChild(element_text)
return element.toxml()

return "\n".join([indent + _to_xml(field) for field in self.dict()])

def from_xml(self, node: Element):
r"""Update the settings from the contents of an XML element

If the XMl element is missing one (or more) setting, the setting(s) are not updated.
XML tag names name are same as those produced by lr_reduction.reduction_template_reader.to_xml()

Example
-------
A valid input XML element would look like:
<RefRed>
<some_entry1>value1</some_entry1>
<some_entry2>value2</some_entry2>
<dead_time_correction>True</dead_time_correction>
<dead_time_paralyzable>True</dead_time_paralyzable>
<dead_time_value>4.2</dead_time_value>
<dead_time_tof_step>150</dead_time_tof_step>
<some_entry3>value3</some_entry3>
</RefRed>
"""
# cast each value (of type `str`) to the type appropriate to the corresponding pydantic field
converters: Dict[str, Callable[[str], Any]] = {
"apply_deadtime": str2bool,
"paralyzable": str2bool,
"dead_time": float,
"tof_step": float,
}
for field, converter in converters.items():
tmp: list = node.getElementsByTagName(self._to_xmltag[field])
if len(tmp):
value = tmp[0].childNodes[0].nodeValue
setattr(self, field, converter(value))
return self

def as_template_reader_dict(self) -> Dict[str, Any]:
r"""Save the settings as a dictionary that can be understood by
lr_reduction.reduction_template_reader.from_dict()
"""
# The values in this dictionary are attribute names of instances of
# class lr_reduction.reduction_template_reader.ReductionParameters
_to_reader_key = {
"apply_deadtime": "dead_time",
"paralyzable": "paralyzable",
"dead_time": "dead_time_value",
"tof_step": "dead_time_tof_step",
}
return {_to_reader_key[field]: value for field, value in self.dict().items()}


class DeadTimeSettingsView(QDialog):
Expand All @@ -26,15 +126,18 @@ def set_state(self, paralyzable, dead_time, tof_step):
self.ui.dead_time_tof.setValue(tof_step)
self.options = self.get_state_from_form()

def get_state_from_form(self):
"""
Read the options from the form.
def get_state_from_form(self) -> dict:
r"""Read the options from the form.

Returns
-------
Dictionary whose keys must match fields of class `DeadTimeSettingsModel`
"""
options = {}
options['paralyzable'] = self.ui.use_paralyzable.isChecked()
options['dead_time'] = self.ui.dead_time_value.value()
options['tof_step'] = self.ui.dead_time_tof.value()
return options
return {
'paralyzable': self.ui.use_paralyzable.isChecked(),
'dead_time': self.ui.dead_time_value.value(),
'tof_step': self.ui.dead_time_tof.value(),
}

def accept(self):
"""
Expand Down
21 changes: 11 additions & 10 deletions RefRed/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from RefRed.initialization.gui import Gui as InitializeGui
from RefRed.initialization.gui_connections import GuiConnections as MakeGuiConnections
from RefRed.interfaces import load_ui
from RefRed.interfaces.deadtime_settings import DeadTimeSettingsView
from RefRed.interfaces.deadtime_settings import DeadTimeSettingsModel, DeadTimeSettingsView
from RefRed.load_reduced_data_set.load_reduced_data_set_handler import LoadReducedDataSetHandler
from RefRed.load_reduced_data_set.reduced_ascii_data_right_click import ReducedAsciiDataRightClick
from RefRed.metadata.metadata_finder import MetadataFinder
Expand Down Expand Up @@ -149,7 +149,7 @@ def __init__(self, argv=[], parent=None):
backgrounds_settings["norm"].signal_second_background.connect(self.norm_back_checkbox)

# deadtime_entry connections
self.deadtime_options = {"apply_deadtime": True, "paralyzable": True, "dead_time": 4.2, "tof_step": 150}
self.deadtime_settings = DeadTimeSettingsModel()
self.ui.deadtime_entry.applyCheckBox.stateChanged.connect(self.apply_deadtime_update)
self.ui.deadtime_entry.settingsButton.clicked.connect(self.show_deadtime_settings)

Expand Down Expand Up @@ -659,21 +659,22 @@ def settings_editor(self):
o_settings_editor.show()

def apply_deadtime_update(self):
r"""Update option apply_deadtime of attribute deadtime_options when the associated checkbox
r"""Update option apply_deadtime of field deadtime_settings when the associated checkbox
changes its state"""
apply_deadtime = self.ui.deadtime_entry.applyCheckBox.isChecked()
if self.deadtime_options["apply_deadtime"] != apply_deadtime:
self.deadtime_options["apply_deadtime"] = apply_deadtime
if self.deadtime_settings.apply_deadtime != apply_deadtime:
self.deadtime_settings.apply_deadtime = apply_deadtime

def show_deadtime_settings(self):
r"""Show the dialog for dead-time options. Update attribue deadtime options upon closing the dialog."""
dt_settings = DeadTimeSettingsView(parent=self)
dt_settings.set_state(
self.deadtime_options["paralyzable"], self.deadtime_options["dead_time"], self.deadtime_options["tof_step"]
view = DeadTimeSettingsView(parent=self)
view.set_state(
self.deadtime_settings.paralyzable, self.deadtime_settings.dead_time, self.deadtime_settings.tof_step
)
dt_settings.exec_()
view.exec_()
# update the dead time settings of the Main GUI after user has closed the dialog
for option in ["paralyzable", "dead_time", "tof_step"]:
self.deadtime_options[option] = dt_settings.options[option]
setattr(self.deadtime_settings, option, view.options[option])

def closeEvent(self, event=None):
SaveUserConfiguration(parent=self)
Loading
Loading