From 64d5484b49ac4021950f77b3c03a47ef8e143ae5 Mon Sep 17 00:00:00 2001 From: Bing Li Date: Fri, 22 Nov 2024 17:19:26 -0500 Subject: [PATCH] added time zone info when converting to nexus --- src/tavi/data/fit.py | 102 +++++++++-------- src/tavi/data/nxdict.py | 14 ++- src/tavi/data/nxentry.py | 2 +- src/tavi/plotter.py | 19 +++- test_data/scan_to_nexus_test.h5 | Bin 18621 -> 18621 bytes test_data/spice_to_nxdict_test_scan0034.h5 | Bin 286416 -> 300844 bytes tests/test_fit.py | 122 +++++++++------------ tests/test_nxdict.py | 2 +- 8 files changed, 139 insertions(+), 122 deletions(-) diff --git a/src/tavi/data/fit.py b/src/tavi/data/fit.py index afad0aa..ab15842 100644 --- a/src/tavi/data/fit.py +++ b/src/tavi/data/fit.py @@ -2,6 +2,7 @@ import numpy as np from lmfit import Parameters, models +from lmfit.model import ModelResult from tavi.data.scan_data import ScanData1D @@ -20,7 +21,7 @@ def __init__( class Fit1D(object): - """Fit a 1d curve + """Fit a 1D curve Attributes: @@ -50,9 +51,13 @@ def __init__( self, data: ScanData1D, fit_range: Optional[tuple[float, float]] = None, + nan_policy: Literal["raise", "propagate", "omit"] = "propagate", + name="", ): """initialize a fit model, mask based on fit_range if given""" + self.name = name + self.x: np.ndarray = data.x self.y: np.ndarray = data.y self.err: Optional[np.ndarray] = data.err @@ -66,6 +71,7 @@ def __init__( self.fit_data: Optional[FitData1D] = None self.PLOT_SEPARATELY = False + self.nan_policy = nan_policy if fit_range is not None: self.set_range(fit_range) @@ -80,41 +86,68 @@ def set_range(self, fit_range: tuple[float, float]): self.err = self.err[mask] @staticmethod - def _add_model(model, prefix): + def _add_model(model, prefix, nan_policy): model = Fit1D.models[model] - return model(prefix=prefix, nan_policy="propagate") + return model(prefix=prefix, nan_policy=nan_policy) def add_signal( self, - model_name: Literal[ - "Gaussian", "Lorentzian", "Voigt", "PseudoVoigt", "DampedOscillator", "DampedHarmonicOscillator" + model: Literal[ + "Gaussian", + "Lorentzian", + "Voigt", + "PseudoVoigt", + "DampedOscillator", + "DampedHarmonicOscillator", ], ): self._num_signals += 1 prefix = f"s{self._num_signals}_" - self.signal_models.append(Fit1D._add_model(model_name, prefix)) + self.signal_models.append( + Fit1D._add_model( + model, + prefix, + nan_policy=self.nan_policy, + ) + ) def add_background( - self, model_name: Literal["Constant", "Linear", "Quadratic", "Polynomial", "Exponential", "PowerLaw"] + self, + model: Literal[ + "Constant", + "Linear", + "Quadratic", + "Polynomial", + "Exponential", + "PowerLaw", + ], ): self._num_backgrounds += 1 prefix = f"b{self._num_backgrounds}_" - self.background_models.append(Fit1D._add_model(model_name, prefix)) + self.background_models.append( + Fit1D._add_model( + model, + prefix, + nan_policy=self.nan_policy, + ) + ) @staticmethod - def _get_model_params(models): + def _get_model_params(models) -> list[list[str]]: params = [] for model in models: params.append(model.param_names) return params - def get_signal_params(self): + @property + def signal_params(self) -> list[list[str]]: return Fit1D._get_model_params(self.signal_models) - def get_background_params(self): + @property + def background_params(self) -> list[list[str]]: return Fit1D._get_model_params(self.background_models) - def guess(self): + def guess(self) -> Parameters: pars = Parameters() for signal in self.signal_models: pars += signal.guess(self.y, x=self.x) @@ -123,47 +156,28 @@ def guess(self): self.pars = pars return pars - @property - def x_to_plot(self): - return - - def eval(self, pars: Parameters, num_of_pts: Optional[int] = 100) -> FitData1D: - mod = self.signal_models[0] - if (sz := len(self.signal_models)) > 1: - for i in range(1, sz): - mod += self.signal_models[i] - - for bkg in self.background_models: - mod += bkg + def _build_composite_model(self): + compposite_model = np.sum(self.signal_models + self.background_models) + return compposite_model + def _get_x_to_plot(self, num_of_pts: Optional[int]): if num_of_pts is None: x_to_plot = self.x elif isinstance(num_of_pts, int): x_to_plot = np.linspace(self.x.min(), self.x.max(), num=num_of_pts) else: raise ValueError(f"num_of_points={num_of_pts} needs to be an integer.") - y_to_plot = mod.eval(pars, x=x_to_plot) - return FitData1D(x_to_plot, y_to_plot) + return x_to_plot - def fit(self, pars: Parameters, num_of_pts: Optional[int] = 100) -> FitData1D: - mod = self.signal_models[0] - if (sz := len(self.signal_models)) > 1: - for i in range(1, sz): - mod += self.signal_models[i] + def eval(self, pars: Parameters, num_of_pts: Optional[int] = 100) -> FitData1D: + model = self._build_composite_model() + x_to_plot = self._get_x_to_plot(num_of_pts) + y_to_plot = model.eval(pars, x=x_to_plot) - for bkg in self.background_models: - mod += bkg + return FitData1D(x_to_plot, y_to_plot) + def fit(self, pars: Parameters, num_of_pts: Optional[int] = 100) -> ModelResult: + mod = self._build_composite_model() result = mod.fit(self.y, pars, x=self.x, weights=self.err) self.result = result - - if num_of_pts is None: - x_to_plot = self.x - elif isinstance(num_of_pts, int): - x_to_plot = np.linspace(self.x.min(), self.x.max(), num=num_of_pts) - else: - raise ValueError(f"num_of_points={num_of_pts} needs to be an integer.") - - y_to_plot = mod.eval(result.params, x=x_to_plot) - - return FitData1D(x_to_plot, y_to_plot) + return result diff --git a/src/tavi/data/nxdict.py b/src/tavi/data/nxdict.py index 7051acd..56d90de 100644 --- a/src/tavi/data/nxdict.py +++ b/src/tavi/data/nxdict.py @@ -4,6 +4,7 @@ from datetime import datetime from pathlib import Path from typing import Optional +from zoneinfo import ZoneInfo import h5py import numpy as np @@ -75,7 +76,17 @@ def __init__(self, ds, **kwargs): case {"type": "NX_INT"} | {"type": "NX_FLOAT"}: dataset = _recast_type(ds, kwargs["type"]) case {"type": "NX_DATE_TIME"}: - dataset = datetime.strptime(ds, "%m/%d/%Y %I:%M:%S %p").isoformat() + dt = datetime.strptime(ds, "%m/%d/%Y %I:%M:%S %p") + date = datetime( + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second, + tzinfo=ZoneInfo("America/New_York"), + ) + dataset = date.isoformat() case _: dataset = ds @@ -464,7 +475,6 @@ def spice_scan_to_nxdict( nxdata = NXentry(NX_class="NXdata", EX_required="true", signal=def_y, axes=def_x) # ---------------------------------------- scan --------------------------------------------- - # TODO timezone start_date_time = "{} {}".format(metadata.get("date"), metadata.get("time")) # TODO what is last scan never finished? # if "end_time" in das_logs.attrs: diff --git a/src/tavi/data/nxentry.py b/src/tavi/data/nxentry.py index 92227a3..08980a7 100644 --- a/src/tavi/data/nxentry.py +++ b/src/tavi/data/nxentry.py @@ -256,7 +256,7 @@ def to_nexus(self, path_to_nexus: str, name="scan") -> None: # Create the ATTRIBUTES nexus_file.attrs["file_name"] = os.path.abspath(path_to_nexus) - nexus_file.attrs["file_time"] = datetime.now().isoformat() + nexus_file.attrs["file_time"] = datetime.now().astimezone().isoformat() nexus_file.attrs["h5py_version"] = h5py.version.version nexus_file.attrs["HDF5_Version"] = h5py.version.hdf5_version diff --git a/src/tavi/plotter.py b/src/tavi/plotter.py index 0c8e2ac..13222b5 100644 --- a/src/tavi/plotter.py +++ b/src/tavi/plotter.py @@ -1,8 +1,9 @@ # import matplotlib.colors as colors from functools import partial -from typing import Optional +from typing import Optional, Union import numpy as np +from lmfit.model import ModelResult from mpl_toolkits.axisartist.grid_finder import MaxNLocator from mpl_toolkits.axisartist.grid_helper_curvelinear import GridHelperCurveLinear @@ -54,10 +55,13 @@ def add_scan(self, scan_data: ScanData1D, **kwargs): for key, val in kwargs.items(): scan_data.fmt.update({key: val}) - def add_fit(self, fit_data: FitData1D, **kwargs): - self.fit_data.append(fit_data) - for key, val in kwargs.items(): - fit_data.fmt.update({key: val}) + def add_fit(self, fit_data: Union[FitData1D, ModelResult], PLOT_COMPONENTS=False, **kwargs): + if PLOT_COMPONENTS: + pass + else: + self.fit_data.append(fit_data) + for key, val in kwargs.items(): + fit_data.fmt.update({key: val}) def plot(self, ax): for data in self.scan_data: @@ -66,7 +70,10 @@ def plot(self, ax): else: ax.errorbar(x=data.x, y=data.y, yerr=data.err, **data.fmt) for fit in self.fit_data: - ax.plot(fit.x, fit.y, **fit.fmt) + if fit.PLOT_INDIVIDUALLY: + pass + else: + ax.plot(fit.x, fit.y, **fit.fmt) if self.xlim is not None: ax.set_xlim(left=self.xlim[0], right=self.xlim[1]) diff --git a/test_data/scan_to_nexus_test.h5 b/test_data/scan_to_nexus_test.h5 index bc1eb57e7f5bd502fc91cda3fcc54011928bf4c3..4c5952b495607a960636d0092494f57c3e3e6812 100644 GIT binary patch delta 61 zcmdlxk#X-t#t9;fj2lJYiwo@yU;qOaAju2FdjlqmN!Y=KMS()BVByVa65$2_ui*?! delta 61 ycmdlxk#X-t#t9;f3>!t?iwiMuFn|FIkmLoiI3|lp*ujNGfg-G6;mv6h;RXOiy9hP_ diff --git a/test_data/spice_to_nxdict_test_scan0034.h5 b/test_data/spice_to_nxdict_test_scan0034.h5 index ae0bb21e8d012c667bb0fac012f5563ca54e4297..85bc27cfb1ab3341d1545ac03be60209d3951bcd 100644 GIT binary patch delta 12432 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 delta 10592 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 L*nf+au7v&<-21Pq diff --git a/tests/test_fit.py b/tests/test_fit.py index 25031bb..f8c83b8 100644 --- a/tests/test_fit.py +++ b/tests/test_fit.py @@ -29,21 +29,12 @@ def test_fit_single_peak_external_model(): assert np.allclose(out.values["peak_fwhm"], 0.39, atol=0.01) assert np.allclose(out.redchi, 2.50, atol=0.01) - # p1 = Plot1D() - # p1.add_scan(s1_scan, fmt="o") - # fig, ax = plt.subplots() - # p1.plot(ax) - # ax.plot(f1.x, out.best_fit) - # plt.show() - - # f1.add_background(model="Constant", values=(0.7,)) - # f1.add_signal(values=(None, 3.5, None), vary=(True, True, True)) - # f1.perform_fit() - - # fig, ax = plt.subplots() - # plot1d.plot_curve(ax) - # f1.fit_plot.plot_curve(ax) - # plt.show() + p1 = Plot1D() + p1.add_scan(s1_scan, fmt="o") + fig, ax = plt.subplots() + p1.plot(ax) + ax.plot(f1.x, out.best_fit) + plt.show() def test_get_fitting_variables(): @@ -53,11 +44,11 @@ def test_get_fitting_variables(): s1_scan = scan42.get_data(norm_to=(30, "mcu")) f1 = Fit1D(s1_scan, fit_range=(0.5, 4.0)) - f1.add_signal(model_name="Gaussian") - f1.add_background(model_name="Constant") + f1.add_signal(model="Gaussian") + f1.add_background(model="Constant") - assert f1.get_signal_params() == [["s1_amplitude", "s1_center", "s1_sigma"]] - assert f1.get_background_params() == [["b1_c"]] + assert f1.signal_params == [["s1_amplitude", "s1_center", "s1_sigma"]] + assert f1.background_params == [["b1_c"]] def test_guess_initial(): @@ -65,18 +56,18 @@ def test_guess_initial(): scan42 = Scan.from_spice(path_to_spice_folder=path_to_spice_folder, scan_num=42) s1_scan = scan42.get_data(norm_to=(30, "mcu")) - f1 = Fit1D(s1_scan, fit_range=(0.5, 4.0)) + f1 = Fit1D(s1_scan, fit_range=(0.5, 4.0), name="scan42_fit") - f1.add_signal(model_name="Gaussian") - f1.add_background(model_name="Constant") + f1.add_signal(model="Gaussian") + f1.add_background(model="Constant") pars = f1.guess() inital = f1.eval(pars) - fit = f1.fit(pars) + # fit_result = f1.fit(pars) p1 = Plot1D() p1.add_scan(s1_scan, fmt="o", label="data") p1.add_fit(inital, label="guess") - p1.add_fit(fit, label="fit") + # p1.add_fit(fit_result, label="fit") fig, ax = plt.subplots() p1.plot(ax) @@ -91,38 +82,20 @@ def test_fit_single_peak_internal_model(): s1_scan = scan42.get_data(norm_to=(30, "mcu")) f1 = Fit1D(s1_scan, fit_range=(0.5, 4.0)) + f1.add_signal(model="Gaussian") f1.add_background(model="Constant") - f1.add_signal(model_name="Gaussian") - f1.eval() - f1.fit() - - bkg = ConstantModel(prefix="bkg_", nan_policy="propagate") - peak = GaussianModel(prefix="peak_", nan_policy="propagate") - model = peak + bkg - pars = peak.guess(f1.y, x=f1.x) - pars += bkg.make_params(c=0) - out = model.fit(f1.y, pars, x=f1.x, weight=f1.err) - - assert np.allclose(out.values["peak_center"], 3.54, atol=0.01) - assert np.allclose(out.values["peak_fwhm"], 0.39, atol=0.01) - assert np.allclose(out.redchi, 10.012, atol=0.01) + pars = f1.guess() + fit_result = f1.fit(pars) p1 = Plot1D() - p1.add_scan(s1_scan, fmt="o") + p1.add_scan(s1_scan, fmt="o", label="data") + p1.add_fit(fit_result, label="fit") + p1.add_fit(fit_result, label="fit", PLOT_COMPONENTS=True) + fig, ax = plt.subplots() p1.plot(ax) - ax.plot(f1.x, out.best_fit) plt.show() - # f1.add_background(model="Constant", values=(0.7,)) - # f1.add_signal(values=(None, 3.5, None), vary=(True, True, True)) - # f1.perform_fit() - - # fig, ax = plt.subplots() - # plot1d.plot_curve(ax) - # f1.fit_plot.plot_curve(ax) - # plt.show() - def test_fit_two_peak(): @@ -135,7 +108,7 @@ def test_fit_two_peak(): f1.add_background(values=(0.7,)) f1.add_signal(values=(None, 3.5, 0.29), vary=(True, True, True)) f1.add_signal( - model_name="Gaussian", + model="Gaussian", values=(None, 0, None), vary=(True, False, True), mins=(0, 0, 0.1), @@ -151,32 +124,45 @@ def test_fit_two_peak(): plt.show() -def test_plot_fit(): +def test_plot(): path_to_spice_folder = "./test_data/exp424" scan42 = Scan.from_spice(path_to_spice_folder=path_to_spice_folder, scan_num=42) - s1_scan = scan42.get_data(norm_channel="mcu", norm_val=30) + s1_scan = scan42.get_data(norm_to=(30, "mcu")) f1 = Fit1D(s1_scan, fit_range=(0.5, 4.0)) - bkg = ConstantModel(prefix="bkg_", nan_policy="propagate") - peak = GaussianModel(prefix="peak_", nan_policy="propagate") - model = peak + bkg - pars = peak.guess(f1.y, x=f1.x) - pars += bkg.make_params(c=0) - out = model.fit(f1.y, pars, x=f1.x, weight=f1.err) + f1.add_signal(model="Gaussian") + f1.add_background(model="Constant") + pars = f1.guess() + inital = f1.eval(pars) + fit_result = f1.fit(pars) - assert np.allclose(out.values["peak_center"], 3.54, atol=0.01) - assert np.allclose(out.values["peak_fwhm"], 0.39, atol=0.01) - assert np.allclose(out.redchi, 10.012, atol=0.01) + p1 = Plot1D() + p1.add_scan(s1_scan, fmt="o", label="data") + p1.add_fit(inital, label="guess") + p1.add_fit(fit_result, label="fit_result") - comps = out.eval_components(x=f1.x) + _, ax = plt.subplots() + p1.plot(ax) + plt.show() + + +def test_plot_indiviaully(): + path_to_spice_folder = "./test_data/exp424" + scan42 = Scan.from_spice(path_to_spice_folder=path_to_spice_folder, scan_num=42) + + s1_scan = scan42.get_data(norm_to=(30, "mcu")) + f1 = Fit1D(s1_scan, fit_range=(0.5, 4.0)) + + f1.add_signal(model="Gaussian") + f1.add_background(model="Constant") + pars = f1.guess() + fit_result = f1.fit(pars) p1 = Plot1D() - p1.add_scan(s1_scan, fmt="o") - fig, ax = plt.subplots() + p1.add_scan(s1_scan, fmt="o", label="data") + p1.add_fit(fit_result, label="fit_result", PLOT_INDIVIDUALLY=True) + + _, ax = plt.subplots() p1.plot(ax) - ax.plot(f1.x, out.best_fit, label="peak+bkg") - ax.plot(f1.x, comps["peak_"], label="peak") - ax.plot(f1.x, comps["bkg_"], label="bkg") - ax.legend() plt.show() diff --git a/tests/test_nxdict.py b/tests/test_nxdict.py index d261bb8..5422b20 100644 --- a/tests/test_nxdict.py +++ b/tests/test_nxdict.py @@ -74,7 +74,7 @@ def test_spice_scan_to_nxdict(): nxdict = spice_scan_to_nxdict(path_to_spice_data) assert nxdict["SPICElogs"]["attrs"]["scan"] == "34" - assert nxdict["start_time"]["dataset"] == "2024-07-03T01:44:46" + assert nxdict["start_time"]["dataset"] == "2024-07-03T01:44:46-04:00" assert np.allclose(nxdict["instrument"]["monochromator"]["ei"]["dataset"][0:3], [4.9, 5, 5.1]) entries = {"scan0034": nxdict}