add FitResult fit overlays and close leaked HDF5 files after experiment stop
Build Debian Packages / build (bookworm, debian12) (push) Successful in 8m50s
Build Debian Packages / build (trixie, debian13) (push) Successful in 8m55s

FitResult is a new Drawable in damaris.data with x-values independent of
the measured points, so fits can be sampled on a dense grid. It is stored
as its own data pool entry (e.g. "measurement/fit"), written to and read
from the HDF5 data pool (damaris_type "FitResult", params as attributes),
and drawn by the GUI as overlay on its measurement, including a fit
parameter text box. Fits get no entry in the display tree, so measurement
rows stay directly selectable. Result scripts create them via
FitResult(x, y, title, source, params).

Additionally, HDF5 files that experiment or result scripts left open
(e.g. after a script failure) are closed when the experiment stops.
pytables keeps such handles registered process-wide, which blocked
reopening the dump file and the scripts' own files on the next run.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
markusro committed 2026-10-18 17:11:05 +02:00
1 parent 947c4c7f8d
commit ab943c30ae
7 files changed
+296 -5

No files matched your search

+127
View File
@@ -0,0 +1,127 @@
# fit curve with x-values independent of the measurement data points
import numpy
import tables
from . import Drawable
class FitResult(Drawable.Drawable):
"""
plottable fit curve belonging to a MeasurementResult
In contrast to MeasurementResult, the x-values are a plain array
and are independent of the measured data points, so a fit can be
sampled on a much denser x-grid for a smooth plot. The fit is
stored in the data pool under its own key, e.g. as a child of the
measurement ("Magnetization(tau)/fit"), and is drawn by the GUI
as an overlay on the measurement it belongs to.
"""
def __init__(self, x, y, title=None, source=None, y_err=None, params=None):
"""
x: array-like of x-values (independent of the measurement)
y: array-like of y-values, same length as x
title: plot title of the fit
source: data pool key of the MeasurementResult this fit belongs to
y_err: optional array-like of y errors, same length as x
params: optional dictionary of fit parameters, shown in the plot
"""
Drawable.Drawable.__init__(self)
self.x = numpy.array(x, dtype="float64").reshape(-1)
self.y = [numpy.array(y, dtype="float64").reshape(-1)]
if y_err is not None:
self.y_err = numpy.array(y_err, dtype="float64").reshape(-1)
else:
self.y_err = None
self.title = title
self.source = source
self.params = dict(params) if params is not None else {}
def get_lineplotdata(self):
"""
x and y data for plotting the fit curve
"""
return [self.x, self.y[0]]
def uses_statistics(self):
"""
drawable interface method returns False
"""
return False
def write_to_hdf(self, hdffile, where, name, title, complib=None, complevel=None):
h5_table_format = {
"x": tables.Float64Col(),
"y": tables.Float64Col()
}
if self.y_err is not None:
h5_table_format["y_err"] = tables.Float64Col()
filter = None
if complib is not None:
if complevel is None:
complevel = 9
filter = tables.Filters(complevel=complevel, complib=complib, shuffle=1)
fit_table = hdffile.create_table(where=where, name=name,
description=h5_table_format,
title=title,
filters=filter,
expectedrows=self.x.shape[0])
fit_table.flavor = "numpy"
fit_table.attrs.damaris_type = "FitResult"
fit_table.attrs.quantity_name = self.get_title()
if self.xlabel is not None:
fit_table.attrs.xlabel = self.xlabel
if self.ylabel is not None:
fit_table.attrs.ylabel = self.ylabel
if self.source is not None:
fit_table.attrs.source = self.source
for (key, value) in self.params.items():
if isinstance(value, (int, float)) and not isinstance(value, bool):
fit_table.attrs.__setattr__("param_" + str(key), float(value))
else:
fit_table.attrs.__setattr__("param_" + str(key), str(value))
row = fit_table.row
has_y_err = self.y_err is not None
for i in range(self.x.shape[0]):
row["x"] = self.x[i]
row["y"] = self.y[0][i]
if has_y_err:
row["y_err"] = self.y_err[i]
row.append()
fit_table.flush()
def read_from_hdf(hdf_node):
"""
reads a FitResult object from the hdf_node
or None if the node is not suitable
"""
if not isinstance(hdf_node, tables.Table):
return None
if hdf_node._v_attrs.damaris_type != "FitResult":
return None
xdata = hdf_node.col("x")
ydata = hdf_node.col("y")
if "y_err" in hdf_node.colnames:
y_err = hdf_node.col("y_err")
else:
y_err = None
fr = FitResult(xdata, ydata,
title=getattr(hdf_node._v_attrs, "quantity_name", None),
source=getattr(hdf_node._v_attrs, "source", None),
y_err=y_err)
fr.xlabel = getattr(hdf_node._v_attrs, "xlabel", None)
fr.ylabel = getattr(hdf_node._v_attrs, "ylabel", None)
params = {}
for attr_name in hdf_node._v_attrs._f_list("all"):
if attr_name.startswith("param_"):
params[attr_name[6:]] = getattr(hdf_node._v_attrs, attr_name)
fr.params = params
return fr