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

Implement peak finding in Azimuthal Integration View. #240

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion extra_foam/algorithms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,8 @@

from .computer_vision import (
edge_detect, fourier_transform_2d
)
)

from .peak_finding import (
find_peaks_1d
)
14 changes: 14 additions & 0 deletions extra_foam/algorithms/peak_finding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""
Distributed under the terms of the BSD 3-Clause License.

The full license is in the file LICENSE, distributed with this software.

Author: Jun Zhu <jun.zhu@xfel.eu>
Copyright (C) European X-Ray Free-Electron Laser Facility GmbH.
All rights reserved.
"""
from scipy.signal import find_peaks


def find_peaks_1d(a, *args, **kwargs):
return find_peaks(a, *args, **kwargs)
120 changes: 86 additions & 34 deletions extra_foam/gui/ctrl_widgets/azimuthal_integ_ctrl_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,21 @@

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QDoubleValidator, QIntValidator
from PyQt5.QtWidgets import QComboBox, QGridLayout, QLabel
from PyQt5.QtWidgets import (
QCheckBox, QComboBox, QFrame, QGridLayout, QHBoxLayout, QLabel
)

from .base_ctrl_widgets import _AbstractCtrlWidget
from .smart_widgets import SmartBoundaryLineEdit, SmartLineEdit
from .smart_widgets import (
SmartBoundaryLineEdit, SmartSliceLineEdit, SmartLineEdit
)
from ..gui_helpers import invert_dict
from ...algorithms import compute_q
from ...config import config, list_azimuthal_integ_methods, Normalizer
from ...database import Metadata as mt

_DEFAULT_AZIMUTHAL_INTEG_POINTS = 512
_DEFAULT_PEAK_PROMINENCE = 100


def _estimate_q_range():
Expand Down Expand Up @@ -90,56 +95,85 @@ def __init__(self, *args, **kwargs):
self._auc_range_le = SmartBoundaryLineEdit("0, Inf")
self._fom_integ_range_le = SmartBoundaryLineEdit("0, Inf")

self._peak_finding_cb = QCheckBox("Peak finding")
self._peak_finding_cb.setChecked(True)
self._peak_prominence_le = SmartLineEdit(str(_DEFAULT_PEAK_PROMINENCE))
self._peak_prominence_le.setValidator(QIntValidator())
self._peak_slicer_le = SmartSliceLineEdit(":")

self._non_reconfigurable_widgets = [
]

self.initUI()
self.initConnections()

self.setFixedHeight(self.minimumSizeHint().height())

def initUI(self):
"""Override."""
layout = QGridLayout()
layout = QHBoxLayout()
AR = Qt.AlignRight

param_widget = QFrame()
param_layout = QGridLayout()
row = 0
layout.addWidget(QLabel("Cx (pixel): "), row, 0, AR)
layout.addWidget(self._cx_le, row, 1)
layout.addWidget(QLabel("Cy (pixel): "), row, 2, AR)
layout.addWidget(self._cy_le, row, 3)
layout.addWidget(QLabel("Pixel x (m): "), row, 4, AR)
layout.addWidget(self._px_le, row, 5)
layout.addWidget(QLabel("Pixel y (m): "), row, 6, AR)
layout.addWidget(self._py_le, row, 7)
param_layout.addWidget(QLabel("Cx (pixel): "), row, 0, AR)
param_layout.addWidget(self._cx_le, row, 1)
param_layout.addWidget(QLabel("Cy (pixel): "), row, 2, AR)
param_layout.addWidget(self._cy_le, row, 3)
param_layout.addWidget(QLabel("Pixel x (m): "), row, 4, AR)
param_layout.addWidget(self._px_le, row, 5)
param_layout.addWidget(QLabel("Pixel y (m): "), row, 6, AR)
param_layout.addWidget(self._py_le, row, 7)

row += 1
layout.addWidget(QLabel("Sample distance (m): "), row, 0, AR)
layout.addWidget(self._sample_dist_le, row, 1)
layout.addWidget(QLabel("Rotation x (rad): "), row, 2, AR)
layout.addWidget(self._rx_le, row, 3)
layout.addWidget(QLabel("Rotation y (rad): "), row, 4, AR)
layout.addWidget(self._ry_le, row, 5)
layout.addWidget(QLabel("Rotation z (rad): "), row, 6, AR)
layout.addWidget(self._rz_le, row, 7)
param_layout.addWidget(QLabel("Sample distance (m): "), row, 0, AR)
param_layout.addWidget(self._sample_dist_le, row, 1)
param_layout.addWidget(QLabel("Rotation x (rad): "), row, 2, AR)
param_layout.addWidget(self._rx_le, row, 3)
param_layout.addWidget(QLabel("Rotation y (rad): "), row, 4, AR)
param_layout.addWidget(self._ry_le, row, 5)
param_layout.addWidget(QLabel("Rotation z (rad): "), row, 6, AR)
param_layout.addWidget(self._rz_le, row, 7)

row += 1
layout.addWidget(QLabel("Photon energy (keV): "), row, 0, AR)
layout.addWidget(self._photon_energy_le, row, 1)
layout.addWidget(QLabel("Integ method: "), row, 2, AR)
layout.addWidget(self._integ_method_cb, row, 3)
layout.addWidget(QLabel("Integ points: "), row, 4, AR)
layout.addWidget(self._integ_pts_le, row, 5)
layout.addWidget(QLabel("Integ range (1/A): "), row, 6, AR)
layout.addWidget(self._integ_range_le, row, 7)
param_layout.addWidget(QLabel("Photon energy (keV): "), row, 0, AR)
param_layout.addWidget(self._photon_energy_le, row, 1)
param_layout.addWidget(QLabel("Integ method: "), row, 2, AR)
param_layout.addWidget(self._integ_method_cb, row, 3)
param_layout.addWidget(QLabel("Integ points: "), row, 4, AR)
param_layout.addWidget(self._integ_pts_le, row, 5)
param_layout.addWidget(QLabel("Integ range (1/A): "), row, 6, AR)
param_layout.addWidget(self._integ_range_le, row, 7)

row += 1
layout.addWidget(QLabel("Norm: "), row, 0, AR)
layout.addWidget(self._norm_cb, row, 1)
layout.addWidget(QLabel("AUC range (1/A): "), row, 2, AR)
layout.addWidget(self._auc_range_le, row, 3)
layout.addWidget(QLabel("FOM range (1/A): "), row, 4, AR)
layout.addWidget(self._fom_integ_range_le, row, 5)

param_layout.addWidget(QLabel("Norm: "), row, 0, AR)
param_layout.addWidget(self._norm_cb, row, 1)
param_layout.addWidget(QLabel("AUC range (1/A): "), row, 2, AR)
param_layout.addWidget(self._auc_range_le, row, 3)
param_layout.addWidget(QLabel("FOM range (1/A): "), row, 4, AR)
param_layout.addWidget(self._fom_integ_range_le, row, 5)

param_widget.setLayout(param_layout)

algo_widget = QFrame()
algo_layout = QGridLayout()
algo_layout.addWidget(self._peak_finding_cb, 0, 0, 1, 2)
algo_layout.addWidget(QLabel("Peak prominence: "), 1, 0, AR)
algo_layout.addWidget(self._peak_prominence_le, 1, 1)
algo_layout.addWidget(QLabel("Peak slicer: "), 2, 0, AR)
algo_layout.addWidget(self._peak_slicer_le, 2, 1)
algo_widget.setLayout(algo_layout)

layout.addWidget(param_widget)
layout.addWidget(algo_widget)
layout.setContentsMargins(1, 1, 1, 1)
self.setLayout(layout)

self.setFrameStyle(QFrame.NoFrame)
param_widget.setFrameStyle(QFrame.StyledPanel)
algo_widget.setFrameStyle(QFrame.StyledPanel)

def initConnections(self):
"""Override."""
mediator = self._mediator
Expand Down Expand Up @@ -178,6 +212,15 @@ def initConnections(self):
self._fom_integ_range_le.value_changed_sgn.connect(
mediator.onAiFomIntegRangeChange)

self._peak_finding_cb.toggled.connect(
mediator.onAiPeakFindingChange)

self._peak_prominence_le.value_changed_sgn.connect(
mediator.onAiPeakProminenceChange)

self._peak_slicer_le.value_changed_sgn.connect(
mediator.onAiPeakSlicerChange)

def updateMetaData(self):
"""Override."""
self._photon_energy_le.returnPressed.emit()
Expand All @@ -203,6 +246,10 @@ def updateMetaData(self):

self._fom_integ_range_le.returnPressed.emit()

self._peak_finding_cb.toggled.emit(self._peak_finding_cb.isChecked())
self._peak_prominence_le.returnPressed.emit()
self._peak_slicer_le.returnPressed.emit()

return True

def loadMetaData(self):
Expand All @@ -223,3 +270,8 @@ def loadMetaData(self):
self._available_norms_inv[int(cfg['normalizer'])])
self._auc_range_le.setText(cfg['auc_range'][1:-1])
self._fom_integ_range_le.setText(cfg['fom_integ_range'][1:-1])

self._updateWidgetValue(self._peak_finding_cb, cfg, "peak_finding")
self._updateWidgetValue(
self._peak_prominence_le, cfg, "peak_prominence")
self._updateWidgetValue(self._peak_slicer_le, cfg, "peak_slicer")
43 changes: 42 additions & 1 deletion extra_foam/gui/ctrl_widgets/base_ctrl_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@
import abc

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QFrame, QGroupBox
from PyQt5.QtWidgets import (
QCheckBox, QComboBox, QFrame, QGroupBox, QLineEdit, QAbstractSpinBox
)

from .smart_widgets import SmartBoundaryLineEdit, SmartSliceLineEdit
from ..gui_helpers import parse_slice_inv
from ..mediator import Mediator
from ...database import MetaProxy
from ...logger import logger


class _AbstractCtrlWidgetMixin:
Expand Down Expand Up @@ -50,6 +55,42 @@ def onStart(self):
def onStop(self):
raise NotImplementedError

def _updateWidgetValue(self, widget, config, key, *, cast=None):
"""Update widget value from meta data."""
value = self._getMetaData(config, key)
if value is None:
return

if cast is not None:
value = cast(value)

if isinstance(widget, QCheckBox):
widget.setChecked(value == 'True')
elif isinstance(widget, SmartBoundaryLineEdit):
widget.setText(value[1:-1])
elif isinstance(widget, SmartSliceLineEdit):
widget.setText(parse_slice_inv(value))
elif isinstance(widget, QLineEdit):
widget.setText(value)
elif isinstance(widget, QAbstractSpinBox):
widget.setValue(value)
else:
logger.error(f"Unknown widget type: {type(widget)}")

@staticmethod
def _getMetaData(config, key):
"""Convienient function to get metadata and capture key error.

:param dict config: config dictionary.
:param str key: meta data key.
"""
try:
return config[key]
except KeyError:
# This happens when loading metadata in a new version with
# a config file in the old version.
logger.warning(f"Meta data key not found: {key}")


class _AbstractCtrlWidget(QFrame, _AbstractCtrlWidgetMixin):

Expand Down
8 changes: 3 additions & 5 deletions extra_foam/gui/ctrl_widgets/calibration_ctrl_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from .base_ctrl_widgets import _AbstractCtrlWidget
from .smart_widgets import SmartSliceLineEdit
from ..gui_helpers import create_icon_button, parse_slice_inv
from ..gui_helpers import create_icon_button
from ...database import Metadata as mt


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

if self._pulse_resolved:
self._gain_cells_le.setText(
parse_slice_inv(cfg["gain_cells"]))
self._offset_cells_le.setText(
parse_slice_inv(cfg["offset_cells"]))
self._updateWidgetValue(self._gain_cells_le, cfg, "gain_cells")
self._updateWidgetValue(self._offset_cells_le, cfg, "offset_cells")
32 changes: 19 additions & 13 deletions extra_foam/gui/ctrl_widgets/geometry_ctrl_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@
)

from .base_ctrl_widgets import _AbstractCtrlWidget
from .smart_widgets import SmartLineEdit, SmartStringLineEdit
from .smart_widgets import SmartLineEdit
from ..gui_helpers import invert_dict
from ..items import GeometryItem
from ...config import config, GeomAssembler
from ...database import Metadata as mt
from ...geometries import module_indices
from ..items import GeometryItem
from ...logger import logger


def _parse_table_widget(widget):
Expand Down Expand Up @@ -213,16 +214,21 @@ def loadMetaData(self):

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

self._assembler_cb.setCurrentText(
self._assemblers_inv[int(cfg["assembler"])])
self._stack_only_cb.setChecked(cfg["stack_only"] == 'True')
self._geom_file_le.setText(cfg["geometry_file"])
assembler = self._getMetaData(cfg, "assembler")
if assembler is not None:
self._assembler_cb.setCurrentText(
self._assemblers_inv[int(cfg["assembler"])])

self._updateWidgetValue(self._stack_only_cb, cfg, "stack_only")
self._updateWidgetValue(self._geom_file_le, cfg, "geometry_file")

# TODO: check number of modules for JungFrau
coordinates = json.loads(cfg["coordinates"], encoding='utf8')
table = self._coordinates_tb
n_rows = table.rowCount()
n_cols = table.columnCount()
for j in range(n_cols):
for i in range(n_rows):
table.cellWidget(i, j).setText(str(coordinates[j][i]))
coordinates = self._getMetaData(cfg, "coordinates")
if coordinates is not None:
coordinates = json.loads(coordinates, encoding='utf8')
table = self._coordinates_tb
n_rows = table.rowCount()
n_cols = table.columnCount()
for j in range(n_cols):
for i in range(n_rows):
table.cellWidget(i, j).setText(str(coordinates[j][i]))
11 changes: 6 additions & 5 deletions extra_foam/gui/ctrl_widgets/image_transform_ctrl_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,17 +162,18 @@ def loadMetaData(self):
"""Override."""
cfg = self._meta.hget_all(mt.IMAGE_TRANSFORM_PROC)

self._ma_window_le.setText(str(cfg["ma_window"]))
self._updateWidgetValue(self._ma_window_le, cfg, "ma_window")

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

fft = self._fourier_transform
fft.logrithmic_cb.setChecked(cfg["fft:logrithmic"] == 'True')
self._updateWidgetValue(fft.logrithmic_cb, cfg, "fft:logrithmic")

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

def registerTransformType(self):
self._mediator.onItTransformTypeChange(
Expand Down
10 changes: 5 additions & 5 deletions extra_foam/gui/ctrl_widgets/pump_probe_ctrl_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from .base_ctrl_widgets import _AbstractGroupBoxCtrlWidget
from .smart_widgets import SmartSliceLineEdit
from ..gui_helpers import invert_dict, parse_slice_inv
from ..gui_helpers import invert_dict
from ...config import PumpProbeMode, AnalysisType
from ...database import Metadata as mt

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

if self._pulse_resolved:
self._on_pulse_le.setText(
parse_slice_inv(cfg["on_pulse_slicer"]))
self._off_pulse_le.setText(
parse_slice_inv(cfg["off_pulse_slicer"]))
self._updateWidgetValue(
self._on_pulse_le, cfg, "on_pulse_slicer")
self._updateWidgetValue(
self._off_pulse_le, cfg, "off_pulse_slicer")

def onPpModeChange(self, pp_mode):
if not self._pulse_resolved:
Expand Down