Skip to content

Commit

Permalink
Merge pull request #2 from OpenCOMPES/specsscan-crop
Browse files Browse the repository at this point in the history
Specsscan crop
  • Loading branch information
rettigl authored Feb 27, 2024
2 parents 66d995e + 032e3ab commit 2699335
Show file tree
Hide file tree
Showing 7 changed files with 549 additions and 75 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ license = "MIT"
python = ">=3.8, <3.12"
h5py = ">=3.6.0"
ipympl = ">=0.9.1"
ipywidgets = ">=7.7.1"
ipywidgets = ">=8.1.1"
matplotlib = ">=3.5.1"
numpy = ">=1.21.6"
pynxtools = ">=0.0.9"
Expand Down
319 changes: 296 additions & 23 deletions specsanalyzer/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
from typing import Tuple
from typing import Union

import ipywidgets as ipw
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import xarray as xr
from IPython.display import display

from specsanalyzer import io
from specsanalyzer.config import parse_config
Expand Down Expand Up @@ -45,7 +49,8 @@ def __init__(
**kwds,
)
self._attributes = MetaHandler(meta=metadata)

self._data_array = None
self.print_msg = True
try:
self._config["calib2d_dict"] = io.parse_calib2d_to_dict(
self._config["calib2d_file"],
Expand Down Expand Up @@ -95,13 +100,10 @@ def convert_image(
) -> xr.DataArray:
"""Converts an imagin in physical unit data, angle vs energy
Args:
raw_img (np.ndarray): Raw image data, numpy 2d matrix
lens_mode (str):
analzser lens mode, check calib2d for a list
of modes Camelback naming convention e.g. "WideAngleMode"
lens_mode (str): analzser lens mode, check calib2d for a list
of modes Camelcase naming convention e.g. "WideAngleMode"
kinetic_energy (float): set analyser kinetic energy
pass_energy (float): set analyser pass energy
work_function (float): set analyser work function
Expand Down Expand Up @@ -228,29 +230,300 @@ def convert_image(
dims=["Position", "Ekin"],
)

# TODO discuss how to handle cropping. Can he store one set of cropping
# parameters in the config, or should we store one set per pass energy/
# lens mode/ kinetic energy in the dict?

# Handle cropping based on parameters stored in correction dictionary
crop = kwds.pop("crop", self._config.get("crop", False))
if crop:
try:
ek_min = kwds.pop("ek_min", self._config["ek_min"])
ek_max = kwds.pop("ek_max", self._config["ek_max"])
ang_min = kwds.pop("ang_min", self._config["ang_min"])
ang_max = kwds.pop("ang_max", self._config["ang_max"])
data_array = crop_xarray(
data_array,
ang_min,
ang_max,
ek_min,
ek_max,
)
range_dict: dict = self._correction_matrix_dict[lens_mode][kinetic_energy][
pass_energy
][work_function]["crop_params"]
ang_min = range_dict["ang_min"]
ang_max = range_dict["ang_max"]
ek_min = range_dict["ek_min"]
ek_max = range_dict["ek_max"]
if self.print_msg:
print("Using saved crop parameters...")
data_array = crop_xarray(data_array, ang_min, ang_max, ek_min, ek_max)
except KeyError:
pass
try:
ang_range_min = (
kwds["ang_range_min"]
if "ang_range_min" in kwds
else self._config["ang_range_min"]
)
ang_range_max = (
kwds["ang_range_max"]
if "ang_range_max" in kwds
else self._config["ang_range_max"]
)
ek_range_min = (
kwds["ek_range_min"]
if "ek_range_min" in kwds
else self._config["ek_range_min"]
)
ek_range_max = (
kwds["ek_range_max"]
if "ek_range_max" in kwds
else self._config["ek_range_max"]
)
ang_min = (
ang_range_min
* (
data_array.coords[data_array.dims[0]][-1]
- data_array.coords[data_array.dims[0]][0]
)
+ data_array.coords[data_array.dims[0]][0]
)
ang_max = (
ang_range_max
* (
data_array.coords[data_array.dims[0]][-1]
- data_array.coords[data_array.dims[0]][0]
)
+ data_array.coords[data_array.dims[0]][0]
)
ek_min = (
ek_range_min
* (
data_array.coords[data_array.dims[1]][-1]
- data_array.coords[data_array.dims[1]][0]
)
+ data_array.coords[data_array.dims[1]][0]
)
ek_max = (
ek_range_max
* (
data_array.coords[data_array.dims[1]][-1]
- data_array.coords[data_array.dims[1]][0]
)
+ data_array.coords[data_array.dims[1]][0]
)
if self.print_msg:
print("Cropping parameters not found, using cropping ranges from config...")
data_array = crop_xarray(data_array, ang_min, ang_max, ek_min, ek_max)
except KeyError:
if self.print_msg:
print(
"Warning: Cropping parameters not found, "
"use method crop_tool() after loading.",
)

return data_array

def crop_tool(
self,
raw_img: np.ndarray,
lens_mode: str,
kinetic_energy: float,
pass_energy: float,
work_function: float,
apply: bool = False,
**kwds,
):
"""Crop tool for selecting cropping parameters
Args:
raw_img (np.ndarray): Raw image data, numpy 2d matrix
lens_mode (str): analzser lens mode, check calib2d for a list
of modes Camelcase naming convention e.g. "WideAngleMode"
kinetic_energy (float): set analyser kinetic energy
pass_energy (float): set analyser pass energy
work_function (float): set analyser work function
apply (bool, optional): Option to directly apply the pre-selected cropping parameters.
Defaults to False.
**kwds: Keyword parameters for the crop tool:
-ek_range_min
-ek_range_max
-ang_range_min
-ang_range_max
"""

data_array = self.convert_image(
raw_img=raw_img,
lens_mode=lens_mode,
kinetic_energy=kinetic_energy,
pass_energy=pass_energy,
work_function=work_function,
crop=False,
)

matplotlib.use("module://ipympl.backend_nbagg")
fig = plt.figure()
ax = fig.add_subplot(111)
try:
mesh_obj = data_array.plot(ax=ax)
except AttributeError:
print("Load the scan first!")
raise

lineh1 = ax.axhline(y=data_array.Angle[0])
lineh2 = ax.axhline(y=data_array.Angle[-1])
linev1 = ax.axvline(x=data_array.Ekin[0])
linev2 = ax.axvline(x=data_array.Ekin[-1])

try:
range_dict = self._correction_matrix_dict[lens_mode][kinetic_energy][pass_energy][
work_function
]["crop_params"]

ek_min = range_dict["ek_min"]
ek_max = range_dict["ek_max"]
ang_min = range_dict["ang_min"]
ang_max = range_dict["ang_max"]
except KeyError:
try:
ang_range_min = (
kwds["ang_range_min"]
if "ang_range_min" in kwds
else self._config["ang_range_min"]
)
ang_range_max = (
kwds["ang_range_max"]
if "ang_range_max" in kwds
else self._config["ang_range_max"]
)
ek_range_min = (
kwds["ek_range_min"] if "ek_range_min" in kwds else self._config["ek_range_min"]
)
ek_range_max = (
kwds["ek_range_max"] if "ek_range_max" in kwds else self._config["ek_range_max"]
)
ang_min = (
ang_range_min
* (
data_array.coords[data_array.dims[0]][-1]
- data_array.coords[data_array.dims[0]][0]
)
+ data_array.coords[data_array.dims[0]][0]
)
ang_max = (
ang_range_max
* (
data_array.coords[data_array.dims[0]][-1]
- data_array.coords[data_array.dims[0]][0]
)
+ data_array.coords[data_array.dims[0]][0]
)
ek_min = (
ek_range_min
* (
data_array.coords[data_array.dims[1]][-1]
- data_array.coords[data_array.dims[1]][0]
)
+ data_array.coords[data_array.dims[1]][0]
)
ek_max = (
ek_range_max
* (
data_array.coords[data_array.dims[1]][-1]
- data_array.coords[data_array.dims[1]][0]
)
+ data_array.coords[data_array.dims[1]][0]
)
except KeyError:
ek_min = data_array.coords[data_array.dims[1]][0]
ek_max = data_array.coords[data_array.dims[1]][-1]
ang_min = data_array.coords[data_array.dims[0]][0]
ang_max = data_array.coords[data_array.dims[0]][-1]

vline_range = [ek_min, ek_max]
hline_range = [ang_min, ang_max]

vline_slider = ipw.FloatRangeSlider(
description="Ekin",
value=vline_range,
min=data_array.Ekin[0],
max=data_array.Ekin[-1],
step=0.1,
)
hline_slider = ipw.FloatRangeSlider(
description="Angle",
value=hline_range,
min=data_array.Angle[0],
max=data_array.Angle[-1],
step=0.1,
)
clim_slider = ipw.FloatRangeSlider(
description="colorbar limits",
value=[data_array.data.min(), data_array.data.max()],
min=data_array.data.min(),
max=data_array.data.max(),
)

def update(hline, vline, v_vals):
lineh1.set_ydata(hline[0])
lineh2.set_ydata(hline[1])
linev1.set_xdata(vline[0])
linev2.set_xdata(vline[1])
mesh_obj.set_clim(vmin=v_vals[0], vmax=v_vals[1])
fig.canvas.draw_idle()

ipw.interact(
update,
hline=hline_slider,
vline=vline_slider,
v_vals=clim_slider,
)

def cropit(val): # pylint: disable=unused-argument
ang_min = min(hline_slider.value)
ang_max = max(hline_slider.value)
ek_min = min(vline_slider.value)
ek_max = max(vline_slider.value)
self._data_array = crop_xarray(data_array, ang_min, ang_max, ek_min, ek_max)
self._correction_matrix_dict[lens_mode][kinetic_energy][pass_energy][work_function] = {
"crop_params": {
"ek_min": ek_min,
"ek_max": ek_max,
"ang_min": ang_min,
"ang_max": ang_max,
},
}
self._config["ek_range_min"] = (
(ek_min - data_array.coords[data_array.dims[1]][0])
/ (
data_array.coords[data_array.dims[1]][-1]
- data_array.coords[data_array.dims[1]][0]
)
).item()
self._config["ek_range_max"] = (
(ek_max - data_array.coords[data_array.dims[1]][0])
/ (
data_array.coords[data_array.dims[1]][-1]
- data_array.coords[data_array.dims[1]][0]
)
).item()
self._config["ang_range_min"] = (
(ang_min - data_array.coords[data_array.dims[0]][0])
/ (
data_array.coords[data_array.dims[0]][-1]
- data_array.coords[data_array.dims[0]][0]
)
).item()
self._config["ang_range_max"] = (
(ang_max - data_array.coords[data_array.dims[0]][0])
/ (
data_array.coords[data_array.dims[0]][-1]
- data_array.coords[data_array.dims[0]][0]
)
).item()

ax.cla()
self._data_array.plot(ax=ax, add_colorbar=False)
fig.canvas.draw_idle()

vline_slider.close()
hline_slider.close()
clim_slider.close()
apply_button.close()

apply_button = ipw.Button(description="Crop")
display(apply_button)
apply_button.on_click(cropit)
plt.show()
if apply:
cropit("")


def mergedicts(
dict1: dict,
Expand All @@ -261,7 +534,7 @@ def mergedicts(
Args:
dict1 (dict): dictionary 1
dict2 (dict): dictiontary 2
dict2 (dict): dictionary 2
Yields:
dict: merged dictionary generator
Expand Down
1 change: 1 addition & 0 deletions specsscan/config/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ units:
azimuth: "degree"
polar: "degree"
voltage: "V"
temperature: "K"
Loading

0 comments on commit 2699335

Please sign in to comment.