Public Access
normalize list/array handling in data classes
Establish one invariant across the Drawable hierarchy: x is always a numpy ndarray, y is always a list of per-channel ndarrays, and empty data is the same types with zero length. - Drawable: x starts as empty ndarray; min/max helpers return 0 on empty data instead of raising; drop contradictory isinstance(self.y, list) branches - ADC_Result/Accumulation: empty constructors build ndarray x; full constructors coerce x/y/y_square with numpy.asarray; Accumulation get_ydata/get_yerr return empty ndarrays and no longer crash on empty statistics-less accumulations - MeasurementResult: fix get_lineplotdata to return the accumulated means (previously the never-populated self.y, so the GUI line plot could never draw); remove dead float/int branches in CSV and HDF writers - Error_Result: get_xdata/get_ydata return ndarrays Add unittest coverage for the invariant, empty-state accessors, constructor coercion, line plot data, CSV output and HDF round-trip. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
1 parent
35d040426b
commit
947c4c7f8d
10 files changed
+245
-58
No files matched your search
@@ -67,7 +67,7 @@ class ADC_Result(Resultable, Drawable, DamarisFFT, Signalpath):
|
||||
self.cont_data = False
|
||||
self.sampling_rate = 0
|
||||
self.index = []
|
||||
self.x = []
|
||||
self.x = numpy.array([], dtype="float32")
|
||||
self.y = []
|
||||
# using all arguments for initialization
|
||||
elif (x is not None) and (y is not None) and (index is not None) and (sampl_freq is not None) and (desc is not None) and (job_id is not None) and (job_date is not None):
|
||||
@@ -75,8 +75,8 @@ class ADC_Result(Resultable, Drawable, DamarisFFT, Signalpath):
|
||||
#for ch in y:
|
||||
# if not numpy.issubdtype(ch.dtype, numpy.integer):
|
||||
# raise TypeError("TypeError: ADC_Result y data must be a list with integer type channels")
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.x = numpy.asarray(x)
|
||||
self.y = [numpy.asarray(ch) for ch in y]
|
||||
self.index = index
|
||||
self.sampling_rate = sampl_freq
|
||||
self.cont_data = True
|
||||
|
||||
@@ -78,7 +78,7 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
|
||||
if self.uses_statistics():
|
||||
if (y_2 is not None):
|
||||
# y_square is always kept as a list of per-channel arrays
|
||||
self.y_square: _YChannels = y_2 if isinstance(y_2, list) else list(y_2)
|
||||
self.y_square: _YChannels = [numpy.asarray(ch) for ch in y_2]
|
||||
elif (y_2 is None) :
|
||||
self.y_square = []
|
||||
else:
|
||||
@@ -93,12 +93,12 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
|
||||
|
||||
self.cont_data = False
|
||||
self.index = []
|
||||
self.x = []
|
||||
self.x = numpy.array([], dtype="float32")
|
||||
self.y = []
|
||||
|
||||
elif (x is not None) and (y is not None) and (index is not None) and (sampl_freq is not None) and (n is not None):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.x = numpy.asarray(x)
|
||||
self.y = [numpy.asarray(ch) for ch in y]
|
||||
self.sampling_rate = sampl_freq
|
||||
|
||||
self.n = n
|
||||
@@ -179,15 +179,15 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
|
||||
|
||||
# external interface --------------------------------------------------------------------
|
||||
|
||||
def get_yerr(self, channel: int) -> numpy.ndarray | list:
|
||||
def get_yerr(self, channel: int) -> numpy.ndarray:
|
||||
"""
|
||||
return error (std.dev/sqrt(n)) of mean
|
||||
"""
|
||||
|
||||
if not self.contains_data():
|
||||
return numpy.array([], dtype="float32")
|
||||
if not self.uses_statistics():
|
||||
return numpy.zeros((len(self.y[0]),),dtype="float32")
|
||||
if not self.contains_data():
|
||||
return []
|
||||
|
||||
self.lock.acquire()
|
||||
if self.n < 2:
|
||||
@@ -203,13 +203,13 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
|
||||
# sample standard deviation / sqrt(n)
|
||||
return numpy.nan_to_num(numpy.sqrt(variance_over_n))
|
||||
|
||||
def get_ydata(self, channel: int = 0) -> numpy.ndarray | list:
|
||||
def get_ydata(self, channel: int = 0) -> numpy.ndarray:
|
||||
"""
|
||||
return mean data
|
||||
"""
|
||||
|
||||
if not self.contains_data():
|
||||
return []
|
||||
return numpy.array([], dtype="float32")
|
||||
self.lock.acquire()
|
||||
try:
|
||||
tmp_y = self.y[channel] / self.n
|
||||
|
||||
@@ -17,8 +17,11 @@ class Drawable:
|
||||
def __init__(self):
|
||||
|
||||
# Will be set correctly in one of the subclasses
|
||||
self.x: numpy.ndarray | list = []
|
||||
self.y: list = []
|
||||
# x is always a numpy.ndarray, y is always a list of numpy.ndarray
|
||||
# (one array per channel); empty data is represented by zero-length
|
||||
# arrays of the same types
|
||||
self.x: numpy.ndarray = numpy.array([], dtype="float64")
|
||||
self.y: list[numpy.ndarray] = []
|
||||
|
||||
self.styles: dict = { }
|
||||
|
||||
@@ -37,7 +40,7 @@ class Drawable:
|
||||
self.ymax = 0
|
||||
|
||||
|
||||
def get_xdata(self) -> numpy.ndarray | list:
|
||||
def get_xdata(self) -> numpy.ndarray:
|
||||
"Returns a reference to the x-Plotdata (array)"
|
||||
return self.x
|
||||
|
||||
@@ -50,7 +53,7 @@ class Drawable:
|
||||
raise
|
||||
|
||||
|
||||
def get_ydata(self, channel: int = 0) -> numpy.ndarray | list:
|
||||
def get_ydata(self, channel: int = 0) -> numpy.ndarray:
|
||||
"Returns the y-Plotdata of channel n (array)"
|
||||
try:
|
||||
return self.y[channel]
|
||||
@@ -140,12 +143,16 @@ class Drawable:
|
||||
|
||||
def get_xmin(self) -> float:
|
||||
"Returns minimun of x"
|
||||
if self.x.size == 0:
|
||||
return 0
|
||||
return self.x.min()
|
||||
|
||||
|
||||
def get_xminpos(self) -> float:
|
||||
"Returns smallest positive value of x"
|
||||
mask = self.x > 0
|
||||
if not numpy.any(mask):
|
||||
return 0
|
||||
return self.x[mask].min()
|
||||
|
||||
|
||||
@@ -156,6 +163,8 @@ class Drawable:
|
||||
|
||||
def get_xmax(self) -> float:
|
||||
"Returns maximum of x"
|
||||
if self.x.size == 0:
|
||||
return 0
|
||||
return self.x.max()
|
||||
|
||||
|
||||
@@ -166,24 +175,21 @@ class Drawable:
|
||||
|
||||
def get_ymin(self) -> float | int:
|
||||
"Returns minimum of y"
|
||||
if isinstance(self.y, list):
|
||||
return min([yarr.min() for yarr in self.y])
|
||||
else:
|
||||
return self.y.min()
|
||||
if len(self.y) == 0:
|
||||
return 0
|
||||
return min([yarr.min() for yarr in self.y])
|
||||
|
||||
|
||||
def get_yminpos(self) -> float | int:
|
||||
"Returns smallest positive value of y"
|
||||
if isinstance(self.y, list):
|
||||
ymins = []
|
||||
for ys in self.y:
|
||||
mask = ys > 0
|
||||
ymins = []
|
||||
for ys in self.y:
|
||||
mask = ys > 0
|
||||
if numpy.any(mask):
|
||||
ymins.append(ys[mask].min())
|
||||
ymin = min(ymins)
|
||||
else:
|
||||
mask = self.y > 0
|
||||
ymin = self.y[mask].min()
|
||||
return ymin
|
||||
if len(ymins) == 0:
|
||||
return 0
|
||||
return min(ymins)
|
||||
|
||||
|
||||
def set_ymin(self, ymin: float) -> None:
|
||||
@@ -193,10 +199,9 @@ class Drawable:
|
||||
|
||||
def get_ymax(self) -> float | int:
|
||||
"Returns maximimum of y"
|
||||
if isinstance(self.y, list):
|
||||
return max([yarr.max() for yarr in self.y])
|
||||
else:
|
||||
return self.y.max()
|
||||
if len(self.y) == 0:
|
||||
return 0
|
||||
return max([yarr.max() for yarr in self.y])
|
||||
|
||||
|
||||
def set_ymax(self, ymax: float) -> None:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from .Resultable import Resultable
|
||||
from .Drawable import Drawable
|
||||
import numpy
|
||||
|
||||
#############################################################################
|
||||
# #
|
||||
@@ -47,11 +48,11 @@ class Error_Result(Resultable, Drawable):
|
||||
|
||||
# Nothing to plot
|
||||
def get_ydata(self, channel=0):
|
||||
return [0.0]
|
||||
return numpy.array([0.0])
|
||||
|
||||
# Nothing to plot
|
||||
def get_xdata(self):
|
||||
return [0.0]
|
||||
return numpy.array([0.0])
|
||||
|
||||
#overload of operators und built-ins -------------------------------------------------------
|
||||
|
||||
|
||||
@@ -195,9 +195,7 @@ class MeasurementResult(Drawable.Drawable, collections.UserDict):
|
||||
return [k,v,e]
|
||||
|
||||
def get_lineplotdata(self) -> list[numpy.ndarray]:
|
||||
k=self.get_xdata()
|
||||
v=numpy.array(self.y, dtype="float64")
|
||||
return [k, v]
|
||||
return self.get_xydata()
|
||||
|
||||
def uses_statistics(self) -> bool:
|
||||
"""
|
||||
@@ -222,16 +220,13 @@ class MeasurementResult(Drawable.Drawable, collections.UserDict):
|
||||
dest.write("# x y ysigma n\n")
|
||||
for x in self.get_xdata():
|
||||
y = self.data[x]
|
||||
if isinstance(y, (float, int)):
|
||||
dest.write("%e%s%e%s0%s1\n" % (x, delimiter, y, delimiter, delimiter))
|
||||
else:
|
||||
dest.write("%e%s%e%s%e%s%d\n" % (x,
|
||||
delimiter,
|
||||
y.mean(),
|
||||
delimiter,
|
||||
y.mean_error(),
|
||||
delimiter,
|
||||
y.n))
|
||||
dest.write("%e%s%e%s%e%s%d\n" % (x,
|
||||
delimiter,
|
||||
y.mean(),
|
||||
delimiter,
|
||||
y.mean_error(),
|
||||
delimiter,
|
||||
y.n))
|
||||
|
||||
|
||||
def write_to_hdf(self, hdffile: tables.File, where: str, name: str, title: str, complib: str | None = None, complevel: int | None = None) -> None:
|
||||
@@ -260,17 +255,12 @@ class MeasurementResult(Drawable.Drawable, collections.UserDict):
|
||||
row=mr_table.row
|
||||
xdata=self.get_xdata()
|
||||
if xdata.shape[0]!=0:
|
||||
for x in self.get_xdata():
|
||||
for x in xdata:
|
||||
y=self.data[x]
|
||||
row["x"]=x
|
||||
if type(y) in [float, int, int]:
|
||||
row["y"]=y
|
||||
row["y_err"]=0.0
|
||||
row["n"]=1
|
||||
else:
|
||||
row["y"]=y.mean()
|
||||
row["y_err"]=y.mean_error()
|
||||
row["n"]=y.n
|
||||
row["y"]=y.mean()
|
||||
row["y_err"]=y.mean_error()
|
||||
row["n"]=y.n
|
||||
row.append()
|
||||
|
||||
finally:
|
||||
|
||||
Reference in new issue
Block a user