Skip to content

Commit 247f35f

Browse files
authored
Merge pull request #240 from zhujun98/peak_finding_for_azimuthal_integration
Implement peak finding in Azimuthal Integration View.
2 parents f453822 + 265b1bb commit 247f35f

21 files changed

+430
-90
lines changed

extra_foam/algorithms/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,8 @@
4242

4343
from .computer_vision import (
4444
edge_detect, fourier_transform_2d
45-
)
45+
)
46+
47+
from .peak_finding import (
48+
find_peaks_1d
49+
)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""
2+
Distributed under the terms of the BSD 3-Clause License.
3+
4+
The full license is in the file LICENSE, distributed with this software.
5+
6+
Author: Jun Zhu <[email protected]>
7+
Copyright (C) European X-Ray Free-Electron Laser Facility GmbH.
8+
All rights reserved.
9+
"""
10+
from scipy.signal import find_peaks
11+
12+
13+
def find_peaks_1d(a, *args, **kwargs):
14+
return find_peaks(a, *args, **kwargs)

extra_foam/gui/ctrl_widgets/azimuthal_integ_ctrl_widget.py

Lines changed: 86 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,21 @@
1111

1212
from PyQt5.QtCore import Qt
1313
from PyQt5.QtGui import QDoubleValidator, QIntValidator
14-
from PyQt5.QtWidgets import QComboBox, QGridLayout, QLabel
14+
from PyQt5.QtWidgets import (
15+
QCheckBox, QComboBox, QFrame, QGridLayout, QHBoxLayout, QLabel
16+
)
1517

1618
from .base_ctrl_widgets import _AbstractCtrlWidget
17-
from .smart_widgets import SmartBoundaryLineEdit, SmartLineEdit
19+
from .smart_widgets import (
20+
SmartBoundaryLineEdit, SmartSliceLineEdit, SmartLineEdit
21+
)
1822
from ..gui_helpers import invert_dict
1923
from ...algorithms import compute_q
2024
from ...config import config, list_azimuthal_integ_methods, Normalizer
2125
from ...database import Metadata as mt
2226

2327
_DEFAULT_AZIMUTHAL_INTEG_POINTS = 512
28+
_DEFAULT_PEAK_PROMINENCE = 100
2429

2530

2631
def _estimate_q_range():
@@ -90,56 +95,85 @@ def __init__(self, *args, **kwargs):
9095
self._auc_range_le = SmartBoundaryLineEdit("0, Inf")
9196
self._fom_integ_range_le = SmartBoundaryLineEdit("0, Inf")
9297

98+
self._peak_finding_cb = QCheckBox("Peak finding")
99+
self._peak_finding_cb.setChecked(True)
100+
self._peak_prominence_le = SmartLineEdit(str(_DEFAULT_PEAK_PROMINENCE))
101+
self._peak_prominence_le.setValidator(QIntValidator())
102+
self._peak_slicer_le = SmartSliceLineEdit(":")
103+
104+
self._non_reconfigurable_widgets = [
105+
]
106+
93107
self.initUI()
94108
self.initConnections()
95109

96110
self.setFixedHeight(self.minimumSizeHint().height())
97111

98112
def initUI(self):
99113
"""Override."""
100-
layout = QGridLayout()
114+
layout = QHBoxLayout()
101115
AR = Qt.AlignRight
102116

117+
param_widget = QFrame()
118+
param_layout = QGridLayout()
103119
row = 0
104-
layout.addWidget(QLabel("Cx (pixel): "), row, 0, AR)
105-
layout.addWidget(self._cx_le, row, 1)
106-
layout.addWidget(QLabel("Cy (pixel): "), row, 2, AR)
107-
layout.addWidget(self._cy_le, row, 3)
108-
layout.addWidget(QLabel("Pixel x (m): "), row, 4, AR)
109-
layout.addWidget(self._px_le, row, 5)
110-
layout.addWidget(QLabel("Pixel y (m): "), row, 6, AR)
111-
layout.addWidget(self._py_le, row, 7)
120+
param_layout.addWidget(QLabel("Cx (pixel): "), row, 0, AR)
121+
param_layout.addWidget(self._cx_le, row, 1)
122+
param_layout.addWidget(QLabel("Cy (pixel): "), row, 2, AR)
123+
param_layout.addWidget(self._cy_le, row, 3)
124+
param_layout.addWidget(QLabel("Pixel x (m): "), row, 4, AR)
125+
param_layout.addWidget(self._px_le, row, 5)
126+
param_layout.addWidget(QLabel("Pixel y (m): "), row, 6, AR)
127+
param_layout.addWidget(self._py_le, row, 7)
112128

113129
row += 1
114-
layout.addWidget(QLabel("Sample distance (m): "), row, 0, AR)
115-
layout.addWidget(self._sample_dist_le, row, 1)
116-
layout.addWidget(QLabel("Rotation x (rad): "), row, 2, AR)
117-
layout.addWidget(self._rx_le, row, 3)
118-
layout.addWidget(QLabel("Rotation y (rad): "), row, 4, AR)
119-
layout.addWidget(self._ry_le, row, 5)
120-
layout.addWidget(QLabel("Rotation z (rad): "), row, 6, AR)
121-
layout.addWidget(self._rz_le, row, 7)
130+
param_layout.addWidget(QLabel("Sample distance (m): "), row, 0, AR)
131+
param_layout.addWidget(self._sample_dist_le, row, 1)
132+
param_layout.addWidget(QLabel("Rotation x (rad): "), row, 2, AR)
133+
param_layout.addWidget(self._rx_le, row, 3)
134+
param_layout.addWidget(QLabel("Rotation y (rad): "), row, 4, AR)
135+
param_layout.addWidget(self._ry_le, row, 5)
136+
param_layout.addWidget(QLabel("Rotation z (rad): "), row, 6, AR)
137+
param_layout.addWidget(self._rz_le, row, 7)
122138

123139
row += 1
124-
layout.addWidget(QLabel("Photon energy (keV): "), row, 0, AR)
125-
layout.addWidget(self._photon_energy_le, row, 1)
126-
layout.addWidget(QLabel("Integ method: "), row, 2, AR)
127-
layout.addWidget(self._integ_method_cb, row, 3)
128-
layout.addWidget(QLabel("Integ points: "), row, 4, AR)
129-
layout.addWidget(self._integ_pts_le, row, 5)
130-
layout.addWidget(QLabel("Integ range (1/A): "), row, 6, AR)
131-
layout.addWidget(self._integ_range_le, row, 7)
140+
param_layout.addWidget(QLabel("Photon energy (keV): "), row, 0, AR)
141+
param_layout.addWidget(self._photon_energy_le, row, 1)
142+
param_layout.addWidget(QLabel("Integ method: "), row, 2, AR)
143+
param_layout.addWidget(self._integ_method_cb, row, 3)
144+
param_layout.addWidget(QLabel("Integ points: "), row, 4, AR)
145+
param_layout.addWidget(self._integ_pts_le, row, 5)
146+
param_layout.addWidget(QLabel("Integ range (1/A): "), row, 6, AR)
147+
param_layout.addWidget(self._integ_range_le, row, 7)
132148

133149
row += 1
134-
layout.addWidget(QLabel("Norm: "), row, 0, AR)
135-
layout.addWidget(self._norm_cb, row, 1)
136-
layout.addWidget(QLabel("AUC range (1/A): "), row, 2, AR)
137-
layout.addWidget(self._auc_range_le, row, 3)
138-
layout.addWidget(QLabel("FOM range (1/A): "), row, 4, AR)
139-
layout.addWidget(self._fom_integ_range_le, row, 5)
140-
150+
param_layout.addWidget(QLabel("Norm: "), row, 0, AR)
151+
param_layout.addWidget(self._norm_cb, row, 1)
152+
param_layout.addWidget(QLabel("AUC range (1/A): "), row, 2, AR)
153+
param_layout.addWidget(self._auc_range_le, row, 3)
154+
param_layout.addWidget(QLabel("FOM range (1/A): "), row, 4, AR)
155+
param_layout.addWidget(self._fom_integ_range_le, row, 5)
156+
157+
param_widget.setLayout(param_layout)
158+
159+
algo_widget = QFrame()
160+
algo_layout = QGridLayout()
161+
algo_layout.addWidget(self._peak_finding_cb, 0, 0, 1, 2)
162+
algo_layout.addWidget(QLabel("Peak prominence: "), 1, 0, AR)
163+
algo_layout.addWidget(self._peak_prominence_le, 1, 1)
164+
algo_layout.addWidget(QLabel("Peak slicer: "), 2, 0, AR)
165+
algo_layout.addWidget(self._peak_slicer_le, 2, 1)
166+
algo_widget.setLayout(algo_layout)
167+
168+
layout.addWidget(param_widget)
169+
layout.addWidget(algo_widget)
170+
layout.setContentsMargins(1, 1, 1, 1)
141171
self.setLayout(layout)
142172

173+
self.setFrameStyle(QFrame.NoFrame)
174+
param_widget.setFrameStyle(QFrame.StyledPanel)
175+
algo_widget.setFrameStyle(QFrame.StyledPanel)
176+
143177
def initConnections(self):
144178
"""Override."""
145179
mediator = self._mediator
@@ -178,6 +212,15 @@ def initConnections(self):
178212
self._fom_integ_range_le.value_changed_sgn.connect(
179213
mediator.onAiFomIntegRangeChange)
180214

215+
self._peak_finding_cb.toggled.connect(
216+
mediator.onAiPeakFindingChange)
217+
218+
self._peak_prominence_le.value_changed_sgn.connect(
219+
mediator.onAiPeakProminenceChange)
220+
221+
self._peak_slicer_le.value_changed_sgn.connect(
222+
mediator.onAiPeakSlicerChange)
223+
181224
def updateMetaData(self):
182225
"""Override."""
183226
self._photon_energy_le.returnPressed.emit()
@@ -203,6 +246,10 @@ def updateMetaData(self):
203246

204247
self._fom_integ_range_le.returnPressed.emit()
205248

249+
self._peak_finding_cb.toggled.emit(self._peak_finding_cb.isChecked())
250+
self._peak_prominence_le.returnPressed.emit()
251+
self._peak_slicer_le.returnPressed.emit()
252+
206253
return True
207254

208255
def loadMetaData(self):
@@ -223,3 +270,8 @@ def loadMetaData(self):
223270
self._available_norms_inv[int(cfg['normalizer'])])
224271
self._auc_range_le.setText(cfg['auc_range'][1:-1])
225272
self._fom_integ_range_le.setText(cfg['fom_integ_range'][1:-1])
273+
274+
self._updateWidgetValue(self._peak_finding_cb, cfg, "peak_finding")
275+
self._updateWidgetValue(
276+
self._peak_prominence_le, cfg, "peak_prominence")
277+
self._updateWidgetValue(self._peak_slicer_le, cfg, "peak_slicer")

extra_foam/gui/ctrl_widgets/base_ctrl_widgets.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@
1010
import abc
1111

1212
from PyQt5.QtCore import Qt
13-
from PyQt5.QtWidgets import QFrame, QGroupBox
13+
from PyQt5.QtWidgets import (
14+
QCheckBox, QComboBox, QFrame, QGroupBox, QLineEdit, QAbstractSpinBox
15+
)
1416

17+
from .smart_widgets import SmartBoundaryLineEdit, SmartSliceLineEdit
18+
from ..gui_helpers import parse_slice_inv
1519
from ..mediator import Mediator
1620
from ...database import MetaProxy
21+
from ...logger import logger
1722

1823

1924
class _AbstractCtrlWidgetMixin:
@@ -50,6 +55,42 @@ def onStart(self):
5055
def onStop(self):
5156
raise NotImplementedError
5257

58+
def _updateWidgetValue(self, widget, config, key, *, cast=None):
59+
"""Update widget value from meta data."""
60+
value = self._getMetaData(config, key)
61+
if value is None:
62+
return
63+
64+
if cast is not None:
65+
value = cast(value)
66+
67+
if isinstance(widget, QCheckBox):
68+
widget.setChecked(value == 'True')
69+
elif isinstance(widget, SmartBoundaryLineEdit):
70+
widget.setText(value[1:-1])
71+
elif isinstance(widget, SmartSliceLineEdit):
72+
widget.setText(parse_slice_inv(value))
73+
elif isinstance(widget, QLineEdit):
74+
widget.setText(value)
75+
elif isinstance(widget, QAbstractSpinBox):
76+
widget.setValue(value)
77+
else:
78+
logger.error(f"Unknown widget type: {type(widget)}")
79+
80+
@staticmethod
81+
def _getMetaData(config, key):
82+
"""Convienient function to get metadata and capture key error.
83+
84+
:param dict config: config dictionary.
85+
:param str key: meta data key.
86+
"""
87+
try:
88+
return config[key]
89+
except KeyError:
90+
# This happens when loading metadata in a new version with
91+
# a config file in the old version.
92+
logger.warning(f"Meta data key not found: {key}")
93+
5394

5495
class _AbstractCtrlWidget(QFrame, _AbstractCtrlWidgetMixin):
5596

extra_foam/gui/ctrl_widgets/calibration_ctrl_widget.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from .base_ctrl_widgets import _AbstractCtrlWidget
1616
from .smart_widgets import SmartSliceLineEdit
17-
from ..gui_helpers import create_icon_button, parse_slice_inv
17+
from ..gui_helpers import create_icon_button
1818
from ...database import Metadata as mt
1919

2020

@@ -134,7 +134,5 @@ def loadMetaData(self):
134134
self._dark_as_offset_cb.setChecked(cfg["dark_as_offset"] == 'True')
135135

136136
if self._pulse_resolved:
137-
self._gain_cells_le.setText(
138-
parse_slice_inv(cfg["gain_cells"]))
139-
self._offset_cells_le.setText(
140-
parse_slice_inv(cfg["offset_cells"]))
137+
self._updateWidgetValue(self._gain_cells_le, cfg, "gain_cells")
138+
self._updateWidgetValue(self._offset_cells_le, cfg, "offset_cells")

extra_foam/gui/ctrl_widgets/geometry_ctrl_widget.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@
1919
)
2020

2121
from .base_ctrl_widgets import _AbstractCtrlWidget
22-
from .smart_widgets import SmartLineEdit, SmartStringLineEdit
22+
from .smart_widgets import SmartLineEdit
2323
from ..gui_helpers import invert_dict
24+
from ..items import GeometryItem
2425
from ...config import config, GeomAssembler
2526
from ...database import Metadata as mt
2627
from ...geometries import module_indices
27-
from ..items import GeometryItem
28+
from ...logger import logger
2829

2930

3031
def _parse_table_widget(widget):
@@ -213,16 +214,21 @@ def loadMetaData(self):
213214

214215
cfg = self._meta.hget_all(mt.GEOMETRY_PROC)
215216

216-
self._assembler_cb.setCurrentText(
217-
self._assemblers_inv[int(cfg["assembler"])])
218-
self._stack_only_cb.setChecked(cfg["stack_only"] == 'True')
219-
self._geom_file_le.setText(cfg["geometry_file"])
217+
assembler = self._getMetaData(cfg, "assembler")
218+
if assembler is not None:
219+
self._assembler_cb.setCurrentText(
220+
self._assemblers_inv[int(cfg["assembler"])])
221+
222+
self._updateWidgetValue(self._stack_only_cb, cfg, "stack_only")
223+
self._updateWidgetValue(self._geom_file_le, cfg, "geometry_file")
220224

221225
# TODO: check number of modules for JungFrau
222-
coordinates = json.loads(cfg["coordinates"], encoding='utf8')
223-
table = self._coordinates_tb
224-
n_rows = table.rowCount()
225-
n_cols = table.columnCount()
226-
for j in range(n_cols):
227-
for i in range(n_rows):
228-
table.cellWidget(i, j).setText(str(coordinates[j][i]))
226+
coordinates = self._getMetaData(cfg, "coordinates")
227+
if coordinates is not None:
228+
coordinates = json.loads(coordinates, encoding='utf8')
229+
table = self._coordinates_tb
230+
n_rows = table.rowCount()
231+
n_cols = table.columnCount()
232+
for j in range(n_cols):
233+
for i in range(n_rows):
234+
table.cellWidget(i, j).setText(str(coordinates[j][i]))

extra_foam/gui/ctrl_widgets/image_transform_ctrl_widget.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -162,17 +162,18 @@ def loadMetaData(self):
162162
"""Override."""
163163
cfg = self._meta.hget_all(mt.IMAGE_TRANSFORM_PROC)
164164

165-
self._ma_window_le.setText(str(cfg["ma_window"]))
165+
self._updateWidgetValue(self._ma_window_le, cfg, "ma_window")
166166

167167
# do not load transform type since it is not an "input"
168168

169169
fft = self._fourier_transform
170-
fft.logrithmic_cb.setChecked(cfg["fft:logrithmic"] == 'True')
170+
self._updateWidgetValue(fft.logrithmic_cb, cfg, "fft:logrithmic")
171171

172172
ed = self._edge_detection
173-
ed.kernel_size_sp.setValue(int(cfg["ed:kernel_size"]))
174-
ed.sigma_sp.setValue(float(cfg["ed:sigma"]))
175-
ed.threshold_le.setText(cfg['ed:threshold'][1:-1])
173+
self._updateWidgetValue(
174+
ed.kernel_size_sp, cfg, "ed:kernel_size", cast=int)
175+
self._updateWidgetValue(ed.sigma_sp, cfg, "ed:sigma", cast=float)
176+
self._updateWidgetValue(ed.threshold_le, cfg, "ed:threshold")
176177

177178
def registerTransformType(self):
178179
self._mediator.onItTransformTypeChange(

extra_foam/gui/ctrl_widgets/pump_probe_ctrl_widget.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from .base_ctrl_widgets import _AbstractGroupBoxCtrlWidget
1919
from .smart_widgets import SmartSliceLineEdit
20-
from ..gui_helpers import invert_dict, parse_slice_inv
20+
from ..gui_helpers import invert_dict
2121
from ...config import PumpProbeMode, AnalysisType
2222
from ...database import Metadata as mt
2323

@@ -140,10 +140,10 @@ def loadMetaData(self):
140140
self._abs_difference_cb.setChecked(cfg["abs_difference"] == 'True')
141141

142142
if self._pulse_resolved:
143-
self._on_pulse_le.setText(
144-
parse_slice_inv(cfg["on_pulse_slicer"]))
145-
self._off_pulse_le.setText(
146-
parse_slice_inv(cfg["off_pulse_slicer"]))
143+
self._updateWidgetValue(
144+
self._on_pulse_le, cfg, "on_pulse_slicer")
145+
self._updateWidgetValue(
146+
self._off_pulse_le, cfg, "off_pulse_slicer")
147147

148148
def onPpModeChange(self, pp_mode):
149149
if not self._pulse_resolved:

0 commit comments

Comments
 (0)