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

Test range #13

Merged
merged 10 commits into from
Aug 7, 2024
4 changes: 3 additions & 1 deletion examples/ex_nogui_auto_decomposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ def auto_decomposition(verbosity=True, show_plots=False):
for fname in fnames:
spectrum = Spectrum()
spectrum.load_profile(fname)
spectrum.apply_range(range_min=55)
spectrum.auto_baseline()
spectrum.subtract_baseline()
spectrum.auto_peaks(model_name="LorentzianAsym")
spectrum.auto_peaks(model_name="Lorentzian")
# spectrum.fit() # fit is already performed during auto_peaks processing
spectra_list.append(spectrum)

Expand Down Expand Up @@ -54,6 +55,7 @@ def auto_decomposition(verbosity=True, show_plots=False):

# Fitted spectra
ax1.set_title('Flattened + Attractors + Fitted')
spectrum.preprocess()
spectrum.plot(ax=ax1)
ax1.legend()

Expand Down
49 changes: 29 additions & 20 deletions fitspy/app/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from fitspy import CMAP

from fitspy.app.utils import convert_dict_from_tk_variables
from fitspy.app.utils import is_convertible_to_float


class Callbacks:
Expand Down Expand Up @@ -737,34 +738,39 @@ def fit_all(self):

def set_spectrum_range(self, delete_tabview=True):
""" Set range to the current spectrum """
self.current_spectrum.range_min = float(self.range_min.get())
self.current_spectrum.range_max = float(self.range_max.get())
self.current_spectrum.load_profile(self.current_spectrum.fname)
self.set_range()
if is_convertible_to_float(self.range_min.get()):
self.current_spectrum.range_min = float(self.range_min.get())

if is_convertible_to_float(self.range_max.get()):
self.current_spectrum.range_max = float(self.range_max.get())

self.remove(delete_tabview=delete_tabview)
self.current_spectrum.preprocess()
self.plot()

def set_range(self):
""" Set range from the spectrum to the appli """
self.range_min.set(self.current_spectrum.x[0])
self.range_max.set(self.current_spectrum.x[-1])
self.current_spectrum.attractors_calculation()
self.range_min.set(self.current_spectrum.range_min)
self.range_max.set(self.current_spectrum.range_max)

def apply_range_to_all(self):
""" Apply the appli range to all the spectra """
range_min = float(self.range_min.get())
range_max = float(self.range_max.get())
self.range_min.set(range_min)
self.range_max.set(range_max)

self.show_plot = False
current_fname = self.current_spectrum.fname
for spectrum in self.spectra.all:
self.current_spectrum = spectrum
self.set_spectrum_range(delete_tabview=False)
self.show_plot = True
self.reassign_current_spectrum(current_fname)
if is_convertible_to_float(self.range_min.get()):
range_min = float(self.range_min.get())
for spectrum in self.spectra.all:
spectrum.range_min = range_min

if is_convertible_to_float(self.range_max.get()):
range_max = float(self.range_max.get())
for spectrum in self.spectra.all:
spectrum.range_max = range_max

self.current_spectrum.preprocess()
self.paramsview.delete()
self.statsview.delete()
self.ax.clear()
self.plot()

def normalize(self):
""" Normalize all spectra from maximum or attractor position """
Expand Down Expand Up @@ -829,7 +835,8 @@ def remove(self, delete_tabview=True):
""" Remove all the features (spectrum attributes, baseline, tabview) """
if self.current_spectrum is not None:
self.current_spectrum.remove_models()
self.delete_baseline()
self.current_spectrum.baseline.points = [[], []]
self.current_spectrum.attractors = []
if delete_tabview: # expensive operation when doing a lot of times
self.paramsview.delete()
self.statsview.delete()
Expand Down Expand Up @@ -933,8 +940,9 @@ def add_items(self, fnames=None):
fname_first_item = fname

spectrum = Spectrum()
spectrum.load_profile(fname)
spectrum.fname = fname
spectrum.attractors_params = attractors_params
spectrum.preprocess()
self.spectra.append(spectrum)

self.update(fname=fname_first_item or self.fileselector.filenames[0])
Expand Down Expand Up @@ -979,6 +987,7 @@ def update(self, fname=None):
self.update_markers(fname)

self.current_spectrum, _ = self.spectra.get_objects(fname)
self.current_spectrum.preprocess()

self.show_plot = False
self.set_range()
Expand Down
4 changes: 2 additions & 2 deletions fitspy/app/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ def __init__(self):
self.progressbar = ProgressBar(self.root)

# Spectrum parameters
self.range_min = DoubleVar(value=-1)
self.range_max = DoubleVar(value=99999)
self.range_min = StringVar(value="None")
self.range_max = StringVar(value="None")
self.attractors = BooleanVar(value=True)
self.outliers_coef = DoubleVar(value=1.5)

Expand Down
9 changes: 9 additions & 0 deletions fitspy/app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ def convert_dict_from_tk_variables(parent_dict, excluded_keys=None):
return new_dict


def is_convertible_to_float(s):
""" Check a string can be convert to float """
try:
float(s)
return True
except ValueError:
return False


class ToggleFrame(LabelFrame):
""" Class dedicated to Enable/Disable LabelFrame and its related children"""

Expand Down
5 changes: 4 additions & 1 deletion fitspy/baseline.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,10 @@ def eval(self, x, y=None):
# use the original points or the attached points to an y-profile
points = self.points if y is None else self.attach_points(x, y)

if len(points[1]) == 1:
if len(points[1]) == 0:
self.y_eval = None

elif len(points[1]) == 1:
self.y_eval = points[1] * np.ones_like(x)

elif self.mode == 'Linear':
Expand Down
6 changes: 3 additions & 3 deletions fitspy/spectra.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,6 @@ def apply_model(self, model_dict, fnames=None, ncpus=1, fit_only=False,
spectrum, _ = self.get_objects(fname)
spectrum.set_attributes(model_dict)
spectrum.fname = fname # reassign the correct fname
if not fit_only:
spectrum.preprocess()
spectra.append(spectrum)

queue_incr = Queue()
Expand All @@ -222,10 +220,12 @@ def apply_model(self, model_dict, fnames=None, ncpus=1, fit_only=False,

if ncpus == 1:
for spectrum in spectra:
if not fit_only:
spectrum.preprocess()
spectrum.fit()
queue_incr.put(1)
else:
fit_mp(spectra, ncpus, queue_incr)
fit_mp(spectra, ncpus, queue_incr, fit_only)

self.pbar_index = 0 # reinitialize pbar_index after the calculation
thread.join()
Expand Down
60 changes: 31 additions & 29 deletions fitspy/spectrum.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import csv
import itertools
from copy import deepcopy
import warnings
import numpy as np
from scipy.signal import find_peaks
from scipy.interpolate import interp1d
Expand Down Expand Up @@ -227,23 +226,18 @@ def set_attributes(self, model_dict):

def preprocess(self):
""" Preprocess the spectrum: call successively load_profile(),
subtract_baseline() and normalize() """

apply_range(), attractors_calculation(), subtract_baseline() and
normalize() """
self.load_profile(self.fname)
if self.baseline.is_subtracted:
self.baseline.is_subtracted = False
self.subtract_baseline()
self.apply_range()
self.attractors_calculation()
self.baseline.is_subtracted = False
self.subtract_baseline()
self.normalize()

def load_profile(self, fname, xmin=None, xmax=None):
def load_profile(self, fname):
""" Load profile from 'fname' with 1 header line and 2 (x,y) columns"""

if xmin is not None:
self.range_min = xmin
if xmax is not None:
self.range_max = xmax

# raw profile loading
if self.x0 is None:
x0, y0 = get_1d_profile(fname)

Expand All @@ -254,17 +248,26 @@ def load_profile(self, fname, xmin=None, xmax=None):

self.fname = fname

# (re)initialization or cropping
if self.range_min is None:
self.range_min = self.x0.min()
self.range_max = self.x0.max()
self.x = self.x0.copy()
self.y = self.y0.copy()
else:
ind_min = closest_index(self.x0, self.range_min)
ind_max = closest_index(self.x0, self.range_max)
self.x = self.x0[ind_min:ind_max + 1].copy()
self.y = self.y0[ind_min:ind_max + 1].copy()
self.x = self.x0.copy()
self.y = self.y0.copy()

def apply_range(self, range_min=None, range_max=None):
""" Apply range to the raw spectrum (and the baseline.y_eval) """

self.range_min = range_min or self.range_min
self.range_max = range_max or self.range_max

if self.range_min is not None or self.range_max is not None:
ind_min, ind_max = 0, len(self.x)
if self.range_min is not None:
ind_min = closest_index(self.x, self.range_min)
if self.range_max is not None:
ind_max = closest_index(self.x, self.range_max)

self.x = self.x[ind_min:ind_max + 1]
self.y = self.y[ind_min:ind_max + 1]
if self.baseline.y_eval is not None:
self.baseline.y_eval = self.baseline.y_eval[ind_min:ind_max + 1]

def calculate_outliers(self):
""" Return outliers points (x,y) coordinates """
Expand Down Expand Up @@ -620,9 +623,6 @@ def fit(self, fit_method=None, fit_negative=None, fit_outliers=None,

def auto_baseline(self):
""" set baseline.mode to 'Semi-Auto """
msg = "Method auto_baseline() will be deprecated.\n"
msg += "Set spectrum.baseline.mode attribute to 'Semi-Auto' instead."
warnings.warn(msg, DeprecationWarning, stacklevel=2)
self.baseline.mode = 'Semi-Auto'

def subtract_baseline(self):
Expand All @@ -632,8 +632,10 @@ def subtract_baseline(self):
x, y = self.x, None
if self.baseline.attached or self.baseline.mode == 'Semi-Auto':
y = self.y_no_outliers
self.y -= self.baseline.eval(x=x, y=y)
self.baseline.is_subtracted = True
self.baseline.eval(x=x, y=y)
if self.baseline.y_eval is not None:
self.y -= self.baseline.y_eval
self.baseline.is_subtracted = True

def auto_peaks(self, model_name):
""" Create automatically 'model_name' peak-models in the limit of
Expand Down
33 changes: 25 additions & 8 deletions fitspy/utils_mp.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

def fit(params):
""" Fitting function used in multiprocessing """
x0, y0, x, y, peak_models_, bkg_models_, fit_params, outliers_limit = params
(x0, y0, x, y, peak_models_, bkg_models_,
fit_params, baseline_params, outliers_limit, fit_only) = params

spectrum = Spectrum()
spectrum.x0 = x0
Expand All @@ -25,10 +26,20 @@ def fit(params):
spectrum.bkg_model = dill.loads(bkg_models_)
spectrum.fit_params = fit_params
spectrum.outliers_limit = outliers_limit

if not fit_only:
for attr, value in baseline_params.items():
setattr(spectrum.baseline, attr, value)
spectrum.preprocess()
spectrum.fit()

res = (dill.dumps(spectrum.result_fit),)
if not fit_only:
res += (spectrum.x, spectrum.y, spectrum.baseline.y_eval)

shared_queue.put(1)

return dill.dumps(spectrum.result_fit)
return res


def initializer(queue_incr):
Expand All @@ -37,28 +48,34 @@ def initializer(queue_incr):
shared_queue = queue_incr


def fit_mp(spectra, ncpus, queue_incr):
def fit_mp(spectra, ncpus, queue_incr, fit_only):
""" Multiprocessing fit function applied to spectra """

# models and fit_params are assumed to be consistent across all spectra ,
# models and fit_params are assumed to be consistent across all spectra,
# the 2 dill.dumps are performed once to limit the CPU cost
peak_models_ = dill.dumps(spectra[0].peak_models)
bkg_models_ = dill.dumps(spectra[0].bkg_model)
fit_params = spectra[0].fit_params
baseline_params = vars(spectra[0].baseline)
outliers_limit = spectra[0].outliers_limit

args = []
for spectrum in spectra:
args.append((spectrum.x0, spectrum.y0, spectrum.x, spectrum.y,
peak_models_, bkg_models_, fit_params, outliers_limit))
peak_models_, bkg_models_, fit_params,
baseline_params, outliers_limit, fit_only))

with ProcessPoolExecutor(initializer=initializer,
initargs=(queue_incr,),
max_workers=ncpus) as executor:
results = tuple(executor.map(fit, args))
results = executor.map(fit, args)

for result_fit_, spectrum in zip(results, spectra):
spectrum.result_fit = dill.loads(result_fit_)
for res, spectrum in zip(results, spectra):
spectrum.result_fit = dill.loads(res[0])
if not fit_only:
spectrum.x = res[1]
spectrum.y = res[2]
spectrum.baseline.y_eval = res[3]
spectrum.reassign_params()

# import os
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dependencies = [
"lmfit",
"parse",
"dill",
"rosettasciio",
"pywin32; platform_system == 'Windows'",
]

Expand Down
11 changes: 6 additions & 5 deletions tests/test_gui_auto_decomposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
from examples.ex_gui_auto_decomposition import gui_auto_decomposition
from utils import extract_results, display_is_ok

# REFS shared with test_nogui_auto_decomposition
REFS = [[142.2017338937751, 28029.56087034003, 8.277805414840545],
[137.67515324567708, 18759.15930233963, 14.979941168937462],
[366.6105573370797, 8114.486816783022, 43.73282414562729]]


@pytest.mark.skipif(not display_is_ok(), reason="DISPLAY problem")
def test_gui_auto_decomposition(tmp_path):
Expand All @@ -12,9 +17,5 @@ def test_gui_auto_decomposition(tmp_path):
results = extract_results(dirname_res=tmp_path)
# print(results)

refs = [[142.2017338937751, 28029.56087034003, 8.277805414840545],
[137.67515324567708, 18759.15930233963, 14.979941168937462],
[366.6105573370797, 8114.486816783022, 43.73282414562729]]

for result, reference in zip(results, refs):
for result, reference in zip(results, REFS):
assert result == approx(reference, rel=2e-2)
Loading
Loading