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

Multi run select #885

Merged
merged 10 commits into from
Oct 7, 2024
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ jobs:
- name: Run PyRS tests
run: |
conda activate PyRS
xvfb-run --server-args="-screen 0 640x480x24" -a pytest --cov=pyrs --cov-report=xml --cov-report=term tests
conda install conda-forge::pytest-xvfb
pytest tests

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
Expand Down
4 changes: 2 additions & 2 deletions environment.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
name: PyRS
channels:
- conda-forge
- mantid/label/nightly
- mantid
dependencies:
- python=3.10
- anaconda-client
- boa
- mantidworkbench>=6.8.20231213
- mantidworkbench>=6.10
- qtpy
- pip
- pyqt=5.15,<6
Expand Down
57 changes: 19 additions & 38 deletions pyrs/core/peak_profile_utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@


# Effective peak and background parameters
EFFECTIVE_PEAK_PARAMETERS = ['Center', 'Height', 'FWHM', 'Mixing', 'A0', 'A1', 'Intensity']
EFFECTIVE_PEAK_PARAMETERS = ['Center', 'Height', 'FWHM', 'Mixing', 'Intensity', 'A0', 'A1', 'A2']


class PeakShape(Enum):
GAUSSIAN = 'Gaussian'
PSEUDOVOIGT = 'PseudoVoigt'
VOIGT = 'Voigt'

def __str__(self):
return self.value
Expand All @@ -35,14 +34,14 @@ def getShape(shape):
def native_parameters(self):
# Native peak parameters in Mantid naming convention
NATIVE_PEAK_PARAMETERS = {'Gaussian': ['Height', 'PeakCentre', 'Sigma'],
'PseudoVoigt': ['Mixing', 'Intensity', 'PeakCentre', 'FWHM'],
'Voigt': ['LorentzAmp', 'LorentzPos', 'LorentzFWHM', 'GaussianFWHM']}
'PseudoVoigt': ['Mixing', 'Intensity', 'PeakCentre', 'FWHM']}

return NATIVE_PEAK_PARAMETERS[self.value][:]


class BackgroundFunction(Enum):
LINEAR = 'Linear' # so far, one and only supported
QUADRATIC = 'Quadratic' # so far, one and only supported

def __str__(self):
return self.value
Expand All @@ -64,7 +63,8 @@ def getFunction(function):
@property
def native_parameters(self):
# Native background parameters in Mantid naming convention
NATIVE_BACKGROUND_PARAMETERS = {'Linear': ['A0', 'A1']}
NATIVE_BACKGROUND_PARAMETERS = {'Linear': ['A0', 'A1'],
'Quadratic': ['A0', 'A1', 'A2']}

return NATIVE_BACKGROUND_PARAMETERS[self.value][:]

Expand Down Expand Up @@ -106,8 +106,6 @@ def get_effective_parameters_converter(peak_profile):
converter = Gaussian()
elif peak_profile == PeakShape.PSEUDOVOIGT:
converter = PseudoVoigt()
elif peak_profile == PeakShape.VOIGT:
converter = Voigt()
else:
raise RuntimeError('if/else tree is incomplete')

Expand Down Expand Up @@ -223,6 +221,13 @@ def calculate_effective_parameters(self, param_value_array, param_error_array):
eff_error_array['A1'] = param_error_array['A1'] # A1
eff_error_array['Intensity'] = intensity_error_array[:] # intensity

try:
eff_value_array['A2'] = param_value_array['A2'] # A2
eff_error_array['A2'] = param_error_array['A2'] # A2
except ValueError:
eff_value_array['A2'] = np.zeros_like(param_value_array['A1']) # A2
eff_error_array['A2'] = np.zeros_like(param_value_array['A1']) + 0.01 # A2

return eff_value_array, eff_error_array

@staticmethod
Expand Down Expand Up @@ -396,6 +401,13 @@ def calculate_effective_parameters(self, param_value_array, param_error_array):
eff_error_array['A1'] = param_error_array['A1'] # A1
eff_error_array['Intensity'] = param_error_array['Intensity'] # intensity

try:
eff_value_array['A2'] = param_value_array['A2'] # A2
eff_error_array['A2'] = param_error_array['A2'] # A2
except ValueError:
eff_value_array['A2'] = np.zeros_like(param_value_array['A1']) # A2
eff_error_array['A2'] = np.zeros_like(param_value_array['A1']) + 0.01 # A2

return eff_value_array, eff_error_array

@staticmethod
Expand Down Expand Up @@ -496,37 +508,6 @@ def cal_intensity(height, fwhm, mixing):
return intensity


class Voigt(PeakParametersConverter):
"""
class for handling peak profile parameters' conversion
"""
def __init__(self):
super(Voigt, self).__init__(PeakShape.VOIGT)

def calculate_effective_parameters(self, param_value_array, param_error_array):
"""Calculate effective peak parameter values

If input parameter values include fitting error, then this method will calculate
the propagation of error

Native PseudoVoigt: ['Mixing', 'Intensity', 'PeakCentre', 'FWHM']

Parameters
----------
native_param_names: list or None
param_value_array : numpy.ndarray
(p, n, 1) or (p, n, 2) vector for parameter values and optionally fitting error
p = number of native parameters , n = number of sub runs
param_error_array : numpy.ndarray
Returns
-------
np.ndarray
(p', n, 1) or (p', n, 2) array for parameter values and optionally fitting error
p' = number of effective parameters , n = number of sub runs
"""
raise NotImplementedError('Somebody should write this')


"""
From here are a list of static method of peak profiles
"""
Expand Down
45 changes: 45 additions & 0 deletions pyrs/interface/combine_runs/combine_runs_crtl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import numpy as np
from pyrs.utilities import get_input_project_file # type: ignore


class CombineRunsCrtl:
def __init__(self, _model):
self._model = _model

def parse_file_path(self, runs):
filepaths = []
for run in runs:
try:
filepaths.append(get_input_project_file(int(run)))
except FileNotFoundError:
pass

return filepaths

def parse_entry_list(self, entry_list):
entry_list = entry_list.replace(' ', '')
split_text = entry_list.split(',')
ranges = [entry for entry in split_text if (':' in entry) or (';' in entry)]
entries = [entry for entry in split_text if (':' not in entry) and (';' not in entry)]

runs = np.array([np.int16(entry) for entry in entries])
for entry in ranges:
split_entry = None
if ':' in entry:
split_entry = np.array(entry.split(':'), dtype=np.int16)
elif ';' in entry:
split_entry = np.array(entry.split(';'), dtype=np.int16)

if split_entry is not None:
split_entry = np.sort(split_entry)
runs = np.append(runs, np.arange(split_entry[0],
split_entry[1]))

return self.parse_file_path(runs)

def load_combine_projects(self, project_files):
if len(project_files) > 1:
self._model.combine_project_files(project_files)
return 1
else:
return 0
31 changes: 31 additions & 0 deletions pyrs/interface/combine_runs/combine_runs_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from qtpy.QtCore import Signal, QObject # type:ignore
from pyrs.core.workspaces import HidraWorkspace
from pyrs.projectfile import HidraProjectFile # type: ignore


class CombineRunsModel(QObject):
propertyUpdated = Signal(str)
failureMsg = Signal(str, str, str)

def __init__(self):
super().__init__()
self._hidra_ws = None

def combine_project_files(self, project_files):
self._hidra_ws = HidraWorkspace('Combined Project Files')
_project = HidraProjectFile(project_files[0])
self._hidra_ws.load_hidra_project(_project, load_raw_counts=False, load_reduced_diffraction=True)
_project.close()

for project in project_files[1:]:
_project = HidraProjectFile(project)
self._hidra_ws.append_hidra_project(_project)
_project.close()

def export_project_files(self, fileout):
export_project = HidraProjectFile(fileout, 'w')
self._hidra_ws.save_experimental_data(export_project,
sub_runs=self._hidra_ws._sample_logs.subruns,
ignore_raw_counts=True)
self._hidra_ws.save_reduced_diffraction_data(export_project)
export_project.save()
130 changes: 130 additions & 0 deletions pyrs/interface/combine_runs/combine_runs_viewer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
from qtpy.QtWidgets import QHBoxLayout, QLabel, QWidget # type:ignore
from qtpy.QtWidgets import QLineEdit, QPushButton # type:ignore
from qtpy.QtWidgets import QFileDialog, QGroupBox # type:ignore

from qtpy.QtWidgets import QGridLayout # type:ignore
from qtpy.QtWidgets import QMainWindow # type:ignore
from qtpy.QtCore import Qt # type: ignore

from pyrs.interface.gui_helper import pop_message


class FileLoad(QWidget):
def __init__(self, name=None, fileType="HidraProjectFile (*.h5);;All Files (*)", parent=None):
self._parent = parent
super().__init__(parent)
self.name = name
self.fileType = fileType
layout = QHBoxLayout()
if name == "Run Numbers:":
label = QLabel(name)
label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
layout.addWidget(label)
self.lineEdit = QLineEdit()
self.lineEdit.setReadOnly(False)
self.lineEdit.setFixedWidth(300)

layout.addWidget(self.lineEdit)

self.browse_button = QPushButton("Load")
self.browse_button.clicked.connect(self.loadRunNumbers)
else:
if name is None:
self.browse_button = QPushButton("Browse Exp Data")
else:
self.browse_button = QPushButton("Browse")

self.browse_button.clicked.connect(self.openFileDialog)

layout.addWidget(self.browse_button)
self.setLayout(layout)

def _reset_fit_data(self):
self._parent.fit_summary.fit_table_operator.fits = None
self._parent.fit_summary.fit_table_operator.fit_result = None

def openFileDialog(self):
self._parent._project_files, _ = QFileDialog.getOpenFileNames(self,
self.name,
"",
self.fileType,
options=QFileDialog.DontUseNativeDialog)

if self._parent._project_files:
self._parent.load_project_files = self._parent._project_files

self.load_project_files()

def saveFileDialog(self, combined_files):
if combined_files != 0:
self._parent._project_files, _ = QFileDialog.getSaveFileName(self,
'Save Combined Proeject File',
"",
self.fileType,
options=QFileDialog.DontUseNativeDialog)

def loadRunNumbers(self):
self._parent._project_files = self._parent.controller.parse_entry_list(self.lineEdit.text())
combined_files = self._parent.controller.load_combine_projects(self._parent._project_files)
self.saveFileDialog(combined_files)

def load_project_files(self):
try:
combined_files = self._parent.controller.load_combine_projects(self._parent._project_files)
self.saveFileDialog(combined_files)

except (FileNotFoundError, RuntimeError, ValueError) as run_err:
pop_message(self, f'Failed to find run {self._parent._project_files}',
str(run_err), 'error')

self._parent.load_project_files = None

def setFilenamesText(self, filenames):
self.lineEdit.setText(filenames)


class FileLoading(QGroupBox):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("Load Project Files")
layout = QHBoxLayout()

self.file_load_run_number = FileLoad(name="Run Numbers:", parent=parent)
self.file_load_dilg = FileLoad(name=None, parent=parent)

layout.addWidget(self.file_load_run_number)
layout.addWidget(self.file_load_dilg)

self.setLayout(layout)

def set_text_values(self, direction, text):
getattr(self, f"file_load_e{direction}").setFilenamesText(text)


class CombineRunsViewer(QMainWindow):
def __init__(self, combine_runs_model, combine_runs_ctrl, parent=None):

self._model = combine_runs_model
self._ctrl = combine_runs_ctrl
self._nexus_file = None
self._run_number = None
self._calibration_input = None

super().__init__(parent)

self.setWindowTitle("PyRS Combine Projectfiles Window")

self.fileLoading = FileLoading(self)

self.window = QWidget()
self.layout = QGridLayout()
self.setCentralWidget(self.fileLoading)
self.window.setLayout(self.layout)

@property
def controller(self):
return self._ctrl

@property
def model(self):
return self._model
12 changes: 1 addition & 11 deletions pyrs/interface/designer/peakfitwindow.ui
Original file line number Diff line number Diff line change
Expand Up @@ -398,11 +398,6 @@
<string>Gaussian</string>
</property>
</item>
<item>
<property name="text">
<string>Voigt</string>
</property>
</item>
</widget>
</item>
<item>
Expand All @@ -412,11 +407,6 @@
<string>Linear</string>
</property>
</item>
<item>
<property name="text">
<string>Flat</string>
</property>
</item>
<item>
<property name="text">
<string>Quadratic</string>
Expand Down Expand Up @@ -942,7 +932,7 @@
<x>0</x>
<y>0</y>
<width>1536</width>
<height>23</height>
<height>22</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
Expand Down
Loading
Loading