Public Access
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ba5c7d56a | ||
|
|
c181e15aa8 | ||
|
|
3bfb2670eb | ||
|
|
83a01f1d83 | ||
|
|
b46debad69 | ||
|
|
4250039b3c | ||
|
|
d3b1a9e3fd | ||
|
|
06867ec17b | ||
|
|
80d8f1e78d | ||
|
|
58e7846bbe | ||
|
|
1c4f8f5344 | ||
|
|
a3738025b7 |
No files matched your search
Vendored
+9
@@ -1,3 +1,12 @@
|
||||
python3-damaris (0.22) stable; urgency=medium
|
||||
|
||||
* fixed autocomplete in the editor tabs
|
||||
* added clipping warning on ADC overload, ADC bit-depth settable in config
|
||||
* Accumulation object keeps track of job_ids
|
||||
* get_result_by_index keeps common description
|
||||
|
||||
-- Markus Rosenstihl <markus.rosenstihl@pkm.tu-darmstadt.de> Sun, 05 Jul 2026 14:09:34 +0200
|
||||
|
||||
python3-damaris (0.20) stable; urgency=medium
|
||||
|
||||
* Unified version management across Python app, GUI and documentation
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
|
||||
## module contents
|
||||
#
|
||||
__version__ = "0.20"
|
||||
__version__ = "0.22"
|
||||
__all__=["experiments", "data", "gui"]
|
||||
@@ -19,6 +19,7 @@ import tables
|
||||
#############################################################################
|
||||
|
||||
class ADC_Result(Resultable, Drawable, DamarisFFT, Signalpath):
|
||||
default_bit_depth = 14
|
||||
"""
|
||||
Represents the result of an ADC, encapsulating data and metadata
|
||||
for processing, visualization, and export.
|
||||
@@ -50,6 +51,7 @@ class ADC_Result(Resultable, Drawable, DamarisFFT, Signalpath):
|
||||
self.ylabel = "Samples [Digits]"
|
||||
self.lock=threading.RLock()
|
||||
self.nChannels = 0
|
||||
self.is_clipped = False
|
||||
|
||||
# using no argument for initialization
|
||||
if (x is None) and (y is None) and (index is None) and (sampl_freq is None) and (desc is None) and (job_id is None) and (job_date is None):
|
||||
@@ -107,6 +109,44 @@ class ADC_Result(Resultable, Drawable, DamarisFFT, Signalpath):
|
||||
return self.cont_data
|
||||
|
||||
|
||||
def check_clipping(self, bit_depth=None):
|
||||
"""
|
||||
Check if any data in y is clipped.
|
||||
For a 14-bit ADC, clipping occurs at 2**13 (8192) or 2**13-1 (8191).
|
||||
In general, for a bit_depth-bit signed ADC, limits are 2**(bit_depth-1) and -(2**(bit_depth-1)).
|
||||
"""
|
||||
if not self.contains_data():
|
||||
return False
|
||||
|
||||
if bit_depth is None:
|
||||
# try to get bit_depth from description
|
||||
if hasattr(self, "description") and self.description is not None:
|
||||
if "adc_bit_depth" in self.description:
|
||||
try:
|
||||
bit_depth = int(self.description["adc_bit_depth"])
|
||||
except (ValueError, TypeError):
|
||||
bit_depth = ADC_Result.default_bit_depth
|
||||
else:
|
||||
bit_depth = ADC_Result.default_bit_depth
|
||||
else:
|
||||
bit_depth = ADC_Result.default_bit_depth
|
||||
|
||||
limit_pos = 2**(bit_depth-1) - 1
|
||||
limit_neg = -2**(bit_depth-1)
|
||||
|
||||
self.lock.acquire()
|
||||
try:
|
||||
self.is_clipped = False
|
||||
for ch_data in self.y:
|
||||
if numpy.any(ch_data >= limit_pos) or numpy.any(ch_data <= limit_neg):
|
||||
self.is_clipped = True
|
||||
break
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
return self.is_clipped
|
||||
|
||||
|
||||
def add_sample_space(self, samples):
|
||||
"Adds space for n samples, where n can also be negative (deletes space). New space is filled up with \"0\""
|
||||
|
||||
@@ -143,7 +183,7 @@ class ADC_Result(Resultable, Drawable, DamarisFFT, Signalpath):
|
||||
for i in range(self.get_number_of_channels()):
|
||||
tmp_y.append(self.y[i][start:end+1].copy())
|
||||
|
||||
r = ADC_Result(x = tmp_x, y = tmp_y, index = [(0,len(tmp_y[0])-1)], sampl_freq = self.sampling_rate, desc = self.description, job_id = self.job_id, job_date = self.job_date)
|
||||
r = ADC_Result(x = tmp_x, y = tmp_y, index = [(0,len(tmp_y[0])-1)], sampl_freq = self.sampling_rate, desc = self.description.copy() if self.description is not None else None, job_id = self.job_id, job_date = self.job_date)
|
||||
self.lock.release()
|
||||
return r
|
||||
|
||||
@@ -536,6 +576,8 @@ def read_from_hdf(hdf_node):
|
||||
|
||||
# job id and x,y titles are missing
|
||||
adc=ADC_Result()
|
||||
if hdf_node._v_title:
|
||||
adc.set_title(hdf_node._v_title)
|
||||
# populate description dictionary
|
||||
adc.description={}
|
||||
for attrname in hdf_node._v_attrs._v_attrnamesuser:
|
||||
@@ -573,4 +615,5 @@ def read_from_hdf(hdf_node):
|
||||
for ch in range(adc_data.shape[1]):
|
||||
adc.y.append(adc_data[:,ch])
|
||||
|
||||
adc.check_clipping()
|
||||
return adc
|
||||
@@ -61,6 +61,7 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
|
||||
self.xlabel = "Time / s"
|
||||
self.ylabel = "Avg. Samples [Digits]"
|
||||
self.lock=threading.RLock()
|
||||
self.is_clipped = False
|
||||
|
||||
self.common_descriptions=None
|
||||
self.time_period=[]
|
||||
@@ -117,9 +118,23 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
|
||||
tmp_y.append(self.y[i][start:end+1])
|
||||
|
||||
r = Accumulation(x = tmp_x, y = tmp_y, n = self.n, index = [(0,len(tmp_y[0])-1)], sampl_freq = self.sampling_rate, error = self.use_error)
|
||||
if self.uses_statistics():
|
||||
r.y_square = []
|
||||
for i in range(self.get_number_of_channels()):
|
||||
r.y_square.append(self.y_square[i][start:end+1])
|
||||
if self.common_descriptions is not None:
|
||||
r.common_descriptions = self.common_descriptions.copy()
|
||||
r.time_period = self.time_period[:]
|
||||
r.job_ids = self.job_ids.copy()
|
||||
self.lock.release()
|
||||
return r
|
||||
|
||||
def get_result_by_index(self, index):
|
||||
return self.get_accu_by_index(index)
|
||||
|
||||
def get_Result_by_index(self, index):
|
||||
return self.get_accu_by_index(index)
|
||||
|
||||
def get_ysquare(self, channel):
|
||||
if self.uses_statistics():
|
||||
try:
|
||||
@@ -616,6 +631,7 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
|
||||
r = Accumulation(x = numpy.array(self.x, dtype="float32"), y = tmp_y, y_2 = tmp_ysquare, n = self.n, index = self.index, sampl_freq = self.sampling_rate, error = self.use_error)
|
||||
else:
|
||||
r = Accumulation(x = numpy.array(self.x, dtype="float32"), y = tmp_y, n = self.n, index = self.index, sampl_freq = self.sampling_rate, error = self.use_error)
|
||||
r.is_clipped = self.is_clipped
|
||||
r.job_id = self.job_id
|
||||
r.job_ids = self.job_ids.copy()
|
||||
r.time_period = self.time_period[:] if self.time_period is not None else None
|
||||
@@ -645,6 +661,8 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
|
||||
r = Accumulation(x = numpy.array(other.x, dtype="float32"), y = tmp_y, y_2 = tmp_ysquare, n = 1, index = other.index, sampl_freq = other.sampling_rate, error = True)
|
||||
else:
|
||||
r = Accumulation(x = numpy.array(other.x, dtype="float32"), y = tmp_y, index = other.index, sampl_freq = other.sampling_rate, n = 1, error = False)
|
||||
if hasattr(other, "is_clipped"):
|
||||
r.is_clipped = other.is_clipped
|
||||
r.time_period=[other.job_date,other.job_date]
|
||||
r.job_id = other.job_id
|
||||
r.common_descriptions=other.description.copy()
|
||||
@@ -678,6 +696,7 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
|
||||
r = Accumulation(x = numpy.array(self.x, dtype="float32"), y = tmp_y, y_2 = tmp_ysquare, n = self.n + 1, index = self.index, sampl_freq = self.sampling_rate, error = True)
|
||||
else:
|
||||
r = Accumulation(x = numpy.array(self.x, dtype="float32"), y = tmp_y, n = self.n + 1, index = self.index, sampl_freq = self.sampling_rate, error = False)
|
||||
r.is_clipped = self.is_clipped or (hasattr(other, "is_clipped") and other.is_clipped)
|
||||
r.time_period=[min(self.time_period[0],other.job_date),
|
||||
max(self.time_period[1],other.job_date)]
|
||||
r.job_id = other.job_id
|
||||
@@ -744,13 +763,14 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
|
||||
|
||||
for i in range(self.get_number_of_channels()):
|
||||
tmp_y.append(self.y[i] + other.y[i])
|
||||
tmp_ysquare.append(self.y_square[i] + other.y_square[i])
|
||||
if self.uses_statistics():
|
||||
tmp_ysquare.append(self.y_square[i] + other.y_square[i])
|
||||
|
||||
if self.uses_statistics():
|
||||
r = Accumulation(x = numpy.array(self.x, dtype="float32"), y = tmp_y, y_2 = tmp_ysquare, n = other.n + self.n, index = self.index, sampl_freq = self.sampling_rate, error = True)
|
||||
else:
|
||||
r = Accumulation(x = numpy.array(self.x, dtype="float32"), y = tmp_y, n = other.n + self.n, index = self.index, sampl_freq = self.sampling_rate, error = False)
|
||||
|
||||
r.is_clipped = self.is_clipped or (hasattr(other, "is_clipped") and other.is_clipped)
|
||||
r.time_period=[min(self.time_period[0],other.time_period[0]),
|
||||
max(self.time_period[1],other.time_period[1])]
|
||||
r.job_id = other.job_id
|
||||
@@ -954,6 +974,8 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
|
||||
self.set_title(self.__title_pattern % self.n)
|
||||
self.lock.release()
|
||||
|
||||
if hasattr(other, "is_clipped"):
|
||||
self.is_clipped = other.is_clipped
|
||||
self.time_period=[other.job_date,other.job_date]
|
||||
self.job_id = other.job_id # added by Oleg Petrov
|
||||
self.job_ids[other.job_id] = other.get_job_date()
|
||||
@@ -982,6 +1004,8 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
|
||||
self.y_square[i] += numpy.array(other.y[i], dtype="float32") ** 2
|
||||
|
||||
self.n += 1
|
||||
if hasattr(other, "is_clipped"):
|
||||
self.is_clipped = self.is_clipped or other.is_clipped
|
||||
self.time_period=[min(self.time_period[0],other.job_date),
|
||||
max(self.time_period[1],other.job_date)]
|
||||
|
||||
@@ -1020,10 +1044,12 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
|
||||
self.y_square.append(self.y[i] ** 2)
|
||||
|
||||
self.set_title(self.__title_pattern % self.n)
|
||||
self.common_descriptions=other.common_desriptions.copy()
|
||||
if hasattr(other, "is_clipped"):
|
||||
self.is_clipped = other.is_clipped
|
||||
self.common_descriptions=other.common_descriptions.copy()
|
||||
self.time_period=other.time_period[:]
|
||||
self.job_id = other.job_id # added by Oleg Petrov
|
||||
self.job_ids += other.job_ids
|
||||
self.job_ids.update(other.job_ids)
|
||||
self.lock.release()
|
||||
|
||||
return self
|
||||
@@ -1049,9 +1075,12 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
|
||||
self.y_square[i] += other.y_square[i]
|
||||
|
||||
self.n += other.n
|
||||
if hasattr(other, "is_clipped"):
|
||||
self.is_clipped = self.is_clipped or other.is_clipped
|
||||
self.time_period=[min(self.time_period[0],other.time_period[0]),
|
||||
max(self.time_period[1],other.time_period[1])]
|
||||
self.job_id = other.job_id
|
||||
self.job_ids.update(other.job_ids)
|
||||
# Removes mismatched common description keys
|
||||
if self.common_descriptions is not None and other.common_descriptions is not None:
|
||||
# Get all keys that exist in both dictionaries
|
||||
@@ -1131,26 +1160,66 @@ def read_from_hdf(hdf_node):
|
||||
earliest_time=None
|
||||
if "earliest_time" in dir(hdf_node._v_attrs):
|
||||
timestring=hdf_node._v_attrs.__getattr__("earliest_time")
|
||||
earliest_time=datetime.datetime(int(timestring[:4]), # year
|
||||
int(timestring[4:6]), # month
|
||||
int(timestring[6:8]), # day
|
||||
int(timestring[9:11]), # hour
|
||||
int(timestring[12:14]), # minute
|
||||
int(timestring[15:17]), # second
|
||||
int(timestring[18:21])*1000 # microsecond
|
||||
)
|
||||
# Handle both formats: "20260705 19:13:59.162" and "2026-07-05 19:13:59.162"
|
||||
if '-' in timestring:
|
||||
# New format: "2026-07-05 19:13:59.162"
|
||||
try:
|
||||
earliest_time=datetime.datetime.strptime(str(timestring), '%Y-%m-%d %H:%M:%S.%f')
|
||||
except ValueError:
|
||||
# Fallback to manual parsing
|
||||
parts = timestring.split()
|
||||
if len(parts) >= 2:
|
||||
date_parts = parts[0].split('-')
|
||||
time_parts = parts[1].split(':')
|
||||
if len(date_parts) == 3 and len(time_parts) >= 2:
|
||||
year, month, day = int(date_parts[0]), int(date_parts[1]), int(date_parts[2])
|
||||
hour, minute = int(time_parts[0]), int(time_parts[1])
|
||||
second_parts = time_parts[2].split('.') if len(time_parts) > 2 else ['0']
|
||||
second = int(second_parts[0])
|
||||
microsecond = int(second_parts[1].ljust(6, '0')[:6]) if len(second_parts) > 1 else 0
|
||||
earliest_time=datetime.datetime(year, month, day, hour, minute, second, microsecond)
|
||||
else:
|
||||
# Old format: "20260705 19:13:59.162"
|
||||
earliest_time=datetime.datetime(int(timestring[:4]), # year
|
||||
int(timestring[4:6]), # month
|
||||
int(timestring[6:8]), # day
|
||||
int(timestring[9:11]), # hour
|
||||
int(timestring[12:14]), # minute
|
||||
int(timestring[15:17]), # second
|
||||
int(timestring[18:21])*1000 # microsecond
|
||||
)
|
||||
|
||||
oldest_time=None
|
||||
if "oldest_time" in dir(hdf_node._v_attrs):
|
||||
timestring=hdf_node._v_attrs.__getattr__("oldest_time")
|
||||
oldest_time=datetime.datetime(int(timestring[:4]), # year
|
||||
int(timestring[4:6]), # month
|
||||
int(timestring[6:8]), # day
|
||||
int(timestring[9:11]), # hour
|
||||
int(timestring[12:14]), # minute
|
||||
int(timestring[15:17]), # second
|
||||
int(timestring[18:21])*1000 # microsecond
|
||||
)
|
||||
# Handle both formats: "20260705 19:13:59.162" and "2026-07-05 19:13:59.162"
|
||||
if '-' in timestring:
|
||||
# New format: "2026-07-05 19:13:59.162"
|
||||
try:
|
||||
oldest_time=datetime.datetime.strptime(str(timestring), '%Y-%m-%d %H:%M:%S.%f')
|
||||
except ValueError:
|
||||
# Fallback to manual parsing
|
||||
parts = timestring.split()
|
||||
if len(parts) >= 2:
|
||||
date_parts = parts[0].split('-')
|
||||
time_parts = parts[1].split(':')
|
||||
if len(date_parts) == 3 and len(time_parts) >= 2:
|
||||
year, month, day = int(date_parts[0]), int(date_parts[1]), int(date_parts[2])
|
||||
hour, minute = int(time_parts[0]), int(time_parts[1])
|
||||
second_parts = time_parts[2].split('.') if len(time_parts) > 2 else ['0']
|
||||
second = int(second_parts[0])
|
||||
microsecond = int(second_parts[1].ljust(6, '0')[:6]) if len(second_parts) > 1 else 0
|
||||
oldest_time=datetime.datetime(year, month, day, hour, minute, second, microsecond)
|
||||
else:
|
||||
# Old format: "20260705 19:13:59.162"
|
||||
oldest_time=datetime.datetime(int(timestring[:4]), # year
|
||||
int(timestring[4:6]), # month
|
||||
int(timestring[6:8]), # day
|
||||
int(timestring[9:11]), # hour
|
||||
int(timestring[12:14]), # minute
|
||||
int(timestring[15:17]), # second
|
||||
int(timestring[18:21])*1000 # microsecond
|
||||
)
|
||||
|
||||
if oldest_time is None or earliest_time is None:
|
||||
accu.time_period=None
|
||||
@@ -1167,6 +1236,8 @@ def read_from_hdf(hdf_node):
|
||||
accu.index.append((r["start"],r["start"]+r["length"]-1))
|
||||
accu.n=r["number"]
|
||||
accu.sampling_rate=1.0/r["dwelltime"]
|
||||
|
||||
accu.set_title("Accumulation: n = %d" % accu.n)
|
||||
|
||||
# now really belief there are no data
|
||||
if len(accu.index)==0 or accu.n==0:
|
||||
@@ -1181,7 +1252,7 @@ def read_from_hdf(hdf_node):
|
||||
accu.y_square=[]
|
||||
accu.use_error=False
|
||||
|
||||
for ch in range(accu_data.shape[1]/2):
|
||||
for ch in range(accu_data.shape[1] // 2):
|
||||
accu.y.append(accu_data[:,ch*2]*accu.n)
|
||||
if accu.n<2 or numpy.all(accu_data[:,ch*2+1]==0.0):
|
||||
accu.y_square.append(numpy.zeros((accu_data.shape[0]) ,dtype="float32"))
|
||||
|
||||
@@ -7,6 +7,7 @@ import collections
|
||||
import threading
|
||||
import traceback
|
||||
import io
|
||||
import numpy
|
||||
|
||||
class DataPool(collections.abc.MutableMapping):
|
||||
"""
|
||||
@@ -107,7 +108,7 @@ class DataPool(collections.abc.MutableMapping):
|
||||
self.__registered_listeners=None
|
||||
|
||||
def write_hdf5(self,hdffile,where="/",name="data_pool", complib=None, complevel=None):
|
||||
if type(hdffile) is bytes:
|
||||
if isinstance(hdffile, (bytes, str)):
|
||||
dump_file=tables.open_file(hdffile, mode="a")
|
||||
elif isinstance(hdffile,tables.File):
|
||||
dump_file=hdffile
|
||||
@@ -156,7 +157,7 @@ class DataPool(collections.abc.MutableMapping):
|
||||
value=self.__mydict[key]
|
||||
self.__dictlock.release()
|
||||
# now write data, assuming, the object is constant during write operation
|
||||
if "write_to_hdf" in dir(value):
|
||||
if hasattr(value, "write_to_hdf"):
|
||||
try:
|
||||
value.write_to_hdf(hdffile=dump_file,
|
||||
where=dump_dir,
|
||||
@@ -170,16 +171,255 @@ class DataPool(collections.abc.MutableMapping):
|
||||
traceback.print_tb(sys.exc_info()[2], None, traceback_file)
|
||||
print("detailed traceback: %s\n"%str(e)+traceback_file.getvalue())
|
||||
traceback_file=None
|
||||
elif isinstance(value, (numpy.ndarray, int, float, str, bytes)):
|
||||
try:
|
||||
obj_to_save = value
|
||||
if isinstance(value, str):
|
||||
obj_to_save = value.encode('utf-8')
|
||||
dump_file.create_array(dump_dir,
|
||||
name=group_keyname,
|
||||
obj=obj_to_save,
|
||||
title=key)
|
||||
except Exception as e:
|
||||
print("failed to write data_pool[\"%s\"]: %s"%(key,str(e)))
|
||||
else:
|
||||
print("don't know how to store data_pool[\"%s\"]"%key)
|
||||
print("don't know how to store data_pool[\"%s\"] (type: %s)"%(key, type(value)))
|
||||
value=None
|
||||
|
||||
finally:
|
||||
dump_group=None
|
||||
if type(hdffile) is bytes:
|
||||
if isinstance(hdffile, (bytes, str)):
|
||||
dump_file.close()
|
||||
dump_file=None
|
||||
|
||||
def read_from_hdf(self, hdffile, where="/data_pool"):
|
||||
"""
|
||||
Read data from HDF5 file and populate the DataPool.
|
||||
|
||||
Parameters:
|
||||
- hdffile: HDF5 file object or filename (bytes/str)
|
||||
- where: Location within HDF5 file to read from (default: "/data_pool")
|
||||
"""
|
||||
# Handle both filename and file object
|
||||
if type(hdffile) is bytes or isinstance(hdffile, str):
|
||||
hdf_file = tables.open_file(hdffile, mode="r")
|
||||
close_file = True
|
||||
elif isinstance(hdffile, tables.File):
|
||||
hdf_file = hdffile
|
||||
close_file = False
|
||||
else:
|
||||
raise Exception("expecting hdffile or string")
|
||||
|
||||
try:
|
||||
# Navigate to the data pool group
|
||||
if where not in hdf_file:
|
||||
raise Exception(f"HDF5 location '{where}' not found")
|
||||
|
||||
data_pool_group = hdf_file.get_node(where)
|
||||
|
||||
# Recursively process the group structure
|
||||
self._read_hdf_group(data_pool_group, "")
|
||||
|
||||
finally:
|
||||
if close_file:
|
||||
hdf_file.close()
|
||||
|
||||
def _read_hdf_group(self, group, path_prefix):
|
||||
"""
|
||||
Recursively read HDF5 group and populate DataPool.
|
||||
|
||||
Parameters:
|
||||
- group: HDF5 group object
|
||||
- path_prefix: Current path in DataPool hierarchy
|
||||
"""
|
||||
# Process all nodes in this group
|
||||
for node in group:
|
||||
# Get the node name (last part of the path)
|
||||
node_name = node._v_name
|
||||
|
||||
# Try to get the original name from title
|
||||
title = node._v_title
|
||||
|
||||
if title:
|
||||
# For DAMARIS objects, the title is usually the full path
|
||||
# For intermediate groups, it's just the part name
|
||||
if "/" in title:
|
||||
full_path = title
|
||||
else:
|
||||
if path_prefix:
|
||||
full_path = f"{path_prefix}/{title}"
|
||||
else:
|
||||
full_path = title
|
||||
else:
|
||||
# Strip dir_ and dict_ prefixes from node name
|
||||
clean_name = self._clean_hdf_name(node_name)
|
||||
|
||||
# Build the full path in DataPool hierarchy
|
||||
if path_prefix:
|
||||
full_path = f"{path_prefix}/{clean_name}"
|
||||
else:
|
||||
full_path = clean_name
|
||||
|
||||
full_path = full_path.replace("//", "/")
|
||||
|
||||
if isinstance(node, (tables.Group, tables.Table)):
|
||||
# Check if this is a known DAMARIS object type
|
||||
if hasattr(node._v_attrs, 'damaris_type'):
|
||||
damaris_type = node._v_attrs.damaris_type
|
||||
|
||||
# Handle different DAMARIS object types
|
||||
if damaris_type == "Accumulation":
|
||||
from damaris.data.Accumulation import read_from_hdf
|
||||
obj = read_from_hdf(node)
|
||||
if obj is not None:
|
||||
self[full_path] = obj
|
||||
|
||||
elif damaris_type == "ADC_Result":
|
||||
from damaris.data.ADC_Result import read_from_hdf
|
||||
obj = read_from_hdf(node)
|
||||
if obj is not None:
|
||||
self[full_path] = obj
|
||||
|
||||
elif damaris_type == "MeasurementResult":
|
||||
from damaris.data.MeasurementResult import read_from_hdf
|
||||
obj = read_from_hdf(node)
|
||||
if obj is not None:
|
||||
self[full_path] = obj
|
||||
|
||||
# Skip further processing of this group since we handled it
|
||||
continue
|
||||
|
||||
# Recursively process sub-groups
|
||||
if isinstance(node, tables.Group):
|
||||
self._read_hdf_group(node, full_path)
|
||||
|
||||
elif isinstance(node, (tables.Array, tables.CArray, tables.EArray, tables.Table)):
|
||||
# Handle simple array data
|
||||
try:
|
||||
data = node.read()
|
||||
# If it's a scalar array of bytes, decode it to string
|
||||
if hasattr(data, 'decode'):
|
||||
try:
|
||||
data = data.decode('utf-8')
|
||||
except:
|
||||
pass
|
||||
elif isinstance(data, numpy.ndarray) and data.dtype.kind in ('S', 'V'):
|
||||
# For numpy byte arrays, try to decode if they are scalar or small
|
||||
if data.size == 1:
|
||||
try:
|
||||
decoded = data.item().decode('utf-8')
|
||||
data = decoded
|
||||
except:
|
||||
pass
|
||||
self[full_path] = data
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not read array {full_path}: {e}")
|
||||
|
||||
# Note: Other node types (Table, etc.) would be handled here as needed
|
||||
|
||||
def _clean_hdf_name(self, name):
|
||||
"""
|
||||
Clean HDF5 node name by removing dir_ and dict_ prefixes.
|
||||
|
||||
Parameters:
|
||||
- name: HDF5 node name (may have dir_ or dict_ prefix)
|
||||
|
||||
Returns:
|
||||
- Cleaned name with prefixes removed
|
||||
"""
|
||||
if name.startswith("dir_"):
|
||||
return name[4:] # Remove "dir_" prefix
|
||||
elif name.startswith("dict_"):
|
||||
return name[5:] # Remove "dict_" prefix
|
||||
else:
|
||||
return name
|
||||
|
||||
@classmethod
|
||||
def load_hdf5(cls, filename):
|
||||
"""
|
||||
Load a DAMARIS HDF5 file and return DataPool and metadata.
|
||||
|
||||
Parameters:
|
||||
- filename: Path to HDF5 file
|
||||
|
||||
Returns:
|
||||
- tuple: (DataPool instance, metadata dict)
|
||||
|
||||
metadata dict contains:
|
||||
- 'scripts': dict with experiment scripts and spool info
|
||||
- 'log': log data if available
|
||||
- 'timeline': timeline data if available
|
||||
"""
|
||||
# Validate file
|
||||
if not tables.is_pytables_file(filename):
|
||||
raise Exception(f"File {filename} is not a valid PyTables HDF5 file")
|
||||
|
||||
# Create DataPool and load data
|
||||
data_pool = cls()
|
||||
|
||||
try:
|
||||
hdf_file = tables.open_file(filename, mode="r")
|
||||
|
||||
# Load main data pool
|
||||
if "/data_pool" in hdf_file:
|
||||
data_pool.read_from_hdf(hdf_file, where="/data_pool")
|
||||
|
||||
# Extract metadata
|
||||
metadata = {}
|
||||
|
||||
# Load scripts
|
||||
if "/scripts" in hdf_file:
|
||||
scripts_group = hdf_file.get_node("/scripts")
|
||||
scripts_data = {}
|
||||
# Use _v_children to get node names safely
|
||||
if hasattr(scripts_group, '_v_children'):
|
||||
for node_name, node in scripts_group._v_children.items():
|
||||
try:
|
||||
if hasattr(node, 'read'):
|
||||
data = node.read()
|
||||
# Handle numpy string arrays and other types
|
||||
if hasattr(data, 'decode'):
|
||||
# bytes or numpy bytes
|
||||
scripts_data[node_name] = data.decode('utf-8', errors='replace')
|
||||
elif hasattr(data, 'tolist'):
|
||||
# numpy array
|
||||
scripts_data[node_name] = str(data.tolist())
|
||||
elif isinstance(data, (str, bytes)):
|
||||
# string or bytes
|
||||
if isinstance(data, bytes):
|
||||
scripts_data[node_name] = data.decode('utf-8', errors='replace')
|
||||
else:
|
||||
scripts_data[node_name] = data
|
||||
else:
|
||||
scripts_data[node_name] = str(data)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not read script {node_name}: {e}")
|
||||
metadata['scripts'] = scripts_data
|
||||
|
||||
# Load log
|
||||
if "/log" in hdf_file:
|
||||
try:
|
||||
log_node = hdf_file.get_node("/log")
|
||||
metadata['log'] = log_node.read()
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not read log: {e}")
|
||||
|
||||
# Load timeline
|
||||
if "/timeline" in hdf_file:
|
||||
try:
|
||||
timeline_node = hdf_file.get_node("/timeline")
|
||||
metadata['timeline'] = timeline_node.read()
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not read timeline: {e}")
|
||||
|
||||
return data_pool, metadata
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"Failed to load HDF5 file {filename}: {e}")
|
||||
finally:
|
||||
if 'hdf_file' in locals():
|
||||
hdf_file.close()
|
||||
|
||||
def register_listener(self, listening_function):
|
||||
self.__registered_listeners.append(listening_function)
|
||||
|
||||
|
||||
@@ -96,12 +96,10 @@ class Experiment:
|
||||
|
||||
#for tracking the experiment length:
|
||||
#because loops are possible we need to track the length for each loop level
|
||||
self.total_time=[]
|
||||
self.total_time.append(0.0)
|
||||
self.total_time=[0.0]
|
||||
|
||||
#and we need to know the number of iterations of the loops to multiply the state.
|
||||
self.loop_iterations=[]
|
||||
self.loop_iterations.append(1)
|
||||
self.loop_iterations=[1]
|
||||
|
||||
# Commands -------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
+267
-57
@@ -169,7 +169,7 @@ class DamarisGUI:
|
||||
|
||||
self.log = LogWindow( self.xml_gui , self)
|
||||
|
||||
self.sw = ScriptWidgets( self.xml_gui )
|
||||
self.sw = ScriptWidgets( self.xml_gui, self )
|
||||
|
||||
self.toolbar_init( )
|
||||
|
||||
@@ -218,6 +218,7 @@ class DamarisGUI:
|
||||
"on_experiment_script_line_textfield_value_changed": self.sw.column_line_widgets_changed_event,
|
||||
"on_experiment_script_column_textfield_value_changed": self.sw.column_line_widgets_changed_event,
|
||||
"on_toolbar_open_file_button_clicked": self.sw.open_file,
|
||||
"on_toolbar_open_hdf5_button_clicked": self.sw.open_hdf5,
|
||||
"on_toolbar_new_button_clicked": self.sw.new_file,
|
||||
"on_toolbar_save_as_button_clicked": self.sw.save_file_as,
|
||||
"on_toolbar_save_file_button_clicked": self.sw.save_file,
|
||||
@@ -297,6 +298,14 @@ class DamarisGUI:
|
||||
self.config = None
|
||||
self.xml_gui = None
|
||||
|
||||
def observe_data_pool( self, data_pool ):
|
||||
"""
|
||||
Delegate to monitor widgets
|
||||
"""
|
||||
self.data = data_pool
|
||||
if hasattr(self, "monitor"):
|
||||
self.monitor.observe_data_pool(data_pool)
|
||||
|
||||
# event handling: the real acitons in gui programming
|
||||
|
||||
# first global events
|
||||
@@ -471,9 +480,10 @@ class DamarisGUI:
|
||||
|
||||
self.id = self.lockfile.add_experiment()
|
||||
while not self.lockfile.am_i_next():
|
||||
self.stop_experiment_flag.wait(interval)
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration_do(False)
|
||||
if self.stop_experiment_flag.wait(interval):
|
||||
break
|
||||
if loop_run > 1:
|
||||
self.experiment_script_statusbar_label.set_text("Waiting for other experiment to finish")
|
||||
loop_run = 0
|
||||
@@ -524,7 +534,10 @@ class DamarisGUI:
|
||||
print("waiting for threads stoping...", end=' ')
|
||||
still_running = [x for x in [ self.si.exp_handling, self.si.res_handling, self.si.back_driver ] if x is not None and x.is_alive( )]
|
||||
for t in still_running:
|
||||
t.join( )
|
||||
while t.is_alive():
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration_do(False)
|
||||
t.join( 0.05 )
|
||||
print("done")
|
||||
|
||||
# cleanup
|
||||
@@ -1099,11 +1112,12 @@ class LogWindow:
|
||||
|
||||
|
||||
class ScriptWidgets:
|
||||
def __init__( self, xml_gui ):
|
||||
def __init__( self, xml_gui, outer_space ):
|
||||
"""
|
||||
initialize text widgets with text
|
||||
"""
|
||||
self.xml_gui = xml_gui
|
||||
self.outer_space = outer_space
|
||||
|
||||
# my states
|
||||
# editing enabled/disabled
|
||||
@@ -1186,7 +1200,6 @@ get_ylabel set_ylabel"""
|
||||
compexp.add_provider(self.compwexp)
|
||||
compres.add_provider(self.compwres)
|
||||
|
||||
# script fonts are atlered by configuration
|
||||
# clipboard
|
||||
self.main_clipboard = gtk.Clipboard( )
|
||||
|
||||
@@ -1194,13 +1207,12 @@ get_ylabel set_ylabel"""
|
||||
self.experiment_script_statusbar_label = self.xml_gui.get_object( "statusbar_experiment_script_label" )
|
||||
self.data_handling_statusbar_label = self.xml_gui.get_object( "statusbar_data_handling_label" )
|
||||
|
||||
# footline with line and col indicators
|
||||
# footer with line and col indicators
|
||||
self.experiment_script_line_indicator = self.xml_gui.get_object( "experiment_script_line_textfield" )
|
||||
self.experiment_script_column_indicator = self.xml_gui.get_object( "experiment_script_column_textfield" )
|
||||
self.data_handling_line_indicator = self.xml_gui.get_object( "data_handling_line_textfield" )
|
||||
self.data_handling_column_indicator = self.xml_gui.get_object( "data_handling_column_textfield" )
|
||||
|
||||
|
||||
# some event handlers
|
||||
self.experiment_script_textbuffer.connect( "modified-changed", self.textviews_modified )
|
||||
self.experiment_script_textview.connect_after( "move-cursor", self.textviews_moved )
|
||||
@@ -1211,8 +1223,7 @@ get_ylabel set_ylabel"""
|
||||
self.data_handling_textview.connect( "key-press-event", self.textviews_keypress )
|
||||
self.data_handling_textview.connect( "button-release-event", self.textviews_clicked )
|
||||
|
||||
# and the editing toolbar part
|
||||
# buttons
|
||||
# and the editing toolbar buttons
|
||||
self.toolbar_new_button = self.xml_gui.get_object( "toolbar_new_button" )
|
||||
self.toolbar_open_button = self.xml_gui.get_object( "toolbar_open_file_button" )
|
||||
self.toolbar_save_button = self.xml_gui.get_object( "toolbar_save_file_button" )
|
||||
@@ -1223,8 +1234,9 @@ get_ylabel set_ylabel"""
|
||||
self.toolbar_redo_button = self.xml_gui.get_object( "toolbar_redo_button" )
|
||||
self.toolbar_search_button = self.xml_gui.get_object("toolbar_search_button")
|
||||
|
||||
# my notebook
|
||||
# main notebook
|
||||
self.main_notebook = self.xml_gui.get_object( "main_notebook" )
|
||||
|
||||
# config toolbar
|
||||
self.main_notebook.connect_after( "switch_page", self.notebook_page_switched )
|
||||
|
||||
@@ -1570,7 +1582,7 @@ get_ylabel set_ylabel"""
|
||||
elif response == gtk.ResponseType.CANCEL:
|
||||
return 0
|
||||
|
||||
def response( self, response_id, script_widget ):
|
||||
def response( dialog, response_id, script_widget ):
|
||||
if response_id == gtk.ResponseType.OK:
|
||||
file_name = dialog.get_filename( )
|
||||
if file_name is None:
|
||||
@@ -1578,7 +1590,13 @@ get_ylabel set_ylabel"""
|
||||
|
||||
script_filename = os.path.abspath( file_name )
|
||||
if not os.access( script_filename, os.R_OK ):
|
||||
self.outer_space.show_error_dialog( "File I/O Error", "Cannot read from file %s" % script_filename )
|
||||
error_dialog = gtk.MessageDialog(parent=script_widget.xml_gui.get_object( "main_window"),
|
||||
type=gtk.MessageType.ERROR,
|
||||
flags=gtk.DialogFlags.MODAL,
|
||||
buttons=("_OK", gtk.ResponseType.OK))
|
||||
error_dialog.set_markup("Cannot read from file %s" % script_filename)
|
||||
error_dialog.run()
|
||||
error_dialog.destroy()
|
||||
return True
|
||||
|
||||
script_string = script_widget.load_file_as_unicode( script_filename )
|
||||
@@ -1611,6 +1629,103 @@ get_ylabel set_ylabel"""
|
||||
|
||||
return True
|
||||
|
||||
def open_hdf5( self, widget, Data=None ):
|
||||
"""
|
||||
Open and load an HDF5 file written by DAMARIS.
|
||||
"""
|
||||
# ignore
|
||||
if not self.editing_state:
|
||||
return 0
|
||||
|
||||
parent_window = self.xml_gui.get_object( "main_window" )
|
||||
dialog = gtk.FileChooserDialog( title="Open HDF5 File...",
|
||||
parent=parent_window,
|
||||
action=gtk.FileChooserAction.OPEN,
|
||||
buttons=("_Open", gtk.ResponseType.OK, "_Cancel", gtk.ResponseType.CANCEL)
|
||||
)
|
||||
dialog.set_default_response( gtk.ResponseType.OK )
|
||||
dialog.set_select_multiple( False )
|
||||
|
||||
# Add filter for HDF5 files
|
||||
filter_hdf5 = gtk.FileFilter()
|
||||
filter_hdf5.set_name("HDF5 files")
|
||||
filter_hdf5.add_pattern("*.h5")
|
||||
filter_hdf5.add_pattern("*.hdf5")
|
||||
filter_hdf5.add_pattern("*.HDF5")
|
||||
filter_hdf5.add_pattern("*.H5")
|
||||
dialog.add_filter(filter_hdf5)
|
||||
|
||||
# Also allow all files
|
||||
filter_all = gtk.FileFilter()
|
||||
filter_all.set_name("All files")
|
||||
filter_all.add_pattern("*")
|
||||
dialog.add_filter(filter_all)
|
||||
|
||||
def response( dialog, response_id, script_widget ):
|
||||
if response_id == gtk.ResponseType.OK:
|
||||
file_name = dialog.get_filename( )
|
||||
if file_name is None:
|
||||
return
|
||||
|
||||
file_name = os.path.abspath( file_name )
|
||||
if not os.access( file_name, os.R_OK ):
|
||||
error_dialog = gtk.MessageDialog(parent=script_widget.xml_gui.get_object( "main_window"),
|
||||
type=gtk.MessageType.ERROR,
|
||||
flags=gtk.DialogFlags.MODAL,
|
||||
buttons=("_OK", gtk.ResponseType.OK))
|
||||
error_dialog.set_markup("Cannot read from file %s" % file_name)
|
||||
error_dialog.run()
|
||||
error_dialog.destroy()
|
||||
return True
|
||||
|
||||
try:
|
||||
# Load DataPool and metadata from HDF5
|
||||
data_pool, metadata = DataPool.load_hdf5(file_name)
|
||||
|
||||
# Set the loaded data pool in the outer space (main application)
|
||||
if hasattr(script_widget.outer_space, 'observe_data_pool'):
|
||||
script_widget.outer_space.observe_data_pool(data_pool)
|
||||
elif hasattr(script_widget.outer_space, 'monitor'):
|
||||
# Fallback if delegate not present
|
||||
script_widget.outer_space.data = data_pool
|
||||
script_widget.outer_space.monitor.observe_data_pool(data_pool)
|
||||
|
||||
# If there are scripts in metadata, load them
|
||||
if 'scripts' in metadata:
|
||||
scripts = metadata['scripts']
|
||||
exp_script = scripts.get('experiment_script', None)
|
||||
res_script = scripts.get('result_script', None)
|
||||
if exp_script or res_script:
|
||||
script_widget.set_scripts(exp_script, res_script)
|
||||
|
||||
# Show success message
|
||||
success_dialog = gtk.MessageDialog(parent=script_widget.xml_gui.get_object( "main_window"),
|
||||
type=gtk.MessageType.INFO,
|
||||
flags=gtk.DialogFlags.MODAL,
|
||||
buttons=("_OK", gtk.ResponseType.OK))
|
||||
success_dialog.set_markup("Successfully loaded data from %s" % os.path.basename(file_name))
|
||||
success_dialog.run()
|
||||
success_dialog.destroy()
|
||||
|
||||
except Exception as e:
|
||||
error_dialog = gtk.MessageDialog(parent=script_widget.xml_gui.get_object( "main_window"),
|
||||
type=gtk.MessageType.ERROR,
|
||||
flags=gtk.DialogFlags.MODAL,
|
||||
buttons=("_OK", gtk.ResponseType.OK))
|
||||
error_dialog.set_markup("Failed to load HDF5 file: %s" % str(e))
|
||||
error_dialog.run()
|
||||
error_dialog.destroy()
|
||||
|
||||
return True
|
||||
|
||||
# Event-Handler for response-signal (when one of the button is pressed)
|
||||
dialog.connect( "response", response, self )
|
||||
|
||||
dialog.run( )
|
||||
dialog.destroy( )
|
||||
|
||||
return True
|
||||
|
||||
def save_file( self, widget=None, Data=None ):
|
||||
"""
|
||||
save file to associated filename
|
||||
@@ -1798,9 +1913,9 @@ get_ylabel set_ylabel"""
|
||||
|
||||
try:
|
||||
if response_id == -1:
|
||||
match_start, match_end = startiter.backward_search(searchstring, gtk.TEXT_SEARCH_TEXT_ONLY)
|
||||
match_start, match_end = startiter.backward_search(searchstring, gtk.TextSearchFlags.TEXT_ONLY)
|
||||
elif response_id == 1:
|
||||
match_start, match_end = enditer.forward_search(searchstring, gtk.TEXT_SEARCH_TEXT_ONLY)
|
||||
match_start, match_end = enditer.forward_search(searchstring, gtk.TextSearchFlags.TEXT_ONLY)
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -1857,7 +1972,7 @@ get_ylabel set_ylabel"""
|
||||
dialog.connect("response", response, self, textbox)
|
||||
dialog.set_resizable(False)
|
||||
|
||||
dialog.show()
|
||||
dialog.show_all()
|
||||
|
||||
if startit is not None and endit is not None:
|
||||
if current_page == 0:
|
||||
@@ -1951,6 +2066,20 @@ class ConfigTab:
|
||||
self.config_info_textview = self.xml_gui.get_object( "info_textview" )
|
||||
self.config_script_font_button = self.xml_gui.get_object( "script_fontbutton" )
|
||||
|
||||
# ADC bit depth setting
|
||||
self.config_adc_bit_depth_hbox = gtk.HBox(homogeneous=False, spacing=6)
|
||||
self.config_adc_bit_depth_label = gtk.Label(label="ADC bit depth:")
|
||||
self.config_adc_bit_depth_spinbutton = gtk.SpinButton.new_with_range(8, 32, 1)
|
||||
self.config_adc_bit_depth_spinbutton.set_value(ADC_Result.default_bit_depth)
|
||||
self.config_adc_bit_depth_spinbutton.set_tooltip_text("Set the default bit depth for clipping detection (e.g., 14 for 14-bit ADCs).")
|
||||
self.config_adc_bit_depth_spinbutton.connect("value-changed", self.on_adc_bit_depth_changed)
|
||||
self.config_adc_bit_depth_hbox.pack_start(self.config_adc_bit_depth_label, False, False, 0)
|
||||
self.config_adc_bit_depth_hbox.pack_start(self.config_adc_bit_depth_spinbutton, False, False, 0)
|
||||
backend_vbox = self.xml_gui.get_object("vbox4")
|
||||
if backend_vbox:
|
||||
backend_vbox.pack_start(self.config_adc_bit_depth_hbox, False, False, 0)
|
||||
self.config_adc_bit_depth_hbox.show_all()
|
||||
|
||||
# insert version informations
|
||||
components_text = """
|
||||
operating system %(os)s
|
||||
@@ -2044,7 +2173,7 @@ pygobject version %(pygobject)s
|
||||
"""
|
||||
complib_iter = self.config_data_pool_complib.get_active_iter( )
|
||||
complib = self.config_data_pool_complib.get_model( ).get_value( complib_iter, 0 )
|
||||
return {
|
||||
actual_config = {
|
||||
"start_backend": self.config_start_backend_checkbutton.get_active( ),
|
||||
"start_result_script": self.config_start_result_script_checkbutton.get_active( ),
|
||||
"start_experiment_script": self.config_start_experiment_script_checkbutton.get_active( ),
|
||||
@@ -2056,8 +2185,11 @@ pygobject version %(pygobject)s
|
||||
"data_pool_write_interval": self.config_data_pool_write_interval_entry.get_text( ),
|
||||
"data_pool_complib": complib,
|
||||
"data_pool_comprate": self.config_data_pool_comprate.get_value_as_int( ),
|
||||
"script_font": self.config_script_font_button.get_font_name( )
|
||||
"script_font": self.config_script_font_button.get_font_name( ),
|
||||
"adc_bit_depth": self.config_adc_bit_depth_spinbutton.get_value_as_int()
|
||||
}
|
||||
ADC_Result.default_bit_depth = actual_config["adc_bit_depth"]
|
||||
return actual_config
|
||||
|
||||
def set( self, config ):
|
||||
if "start_backend" in config:
|
||||
@@ -2083,6 +2215,12 @@ pygobject version %(pygobject)s
|
||||
if "script_font" in config:
|
||||
self.config_script_font_button.set_font_name( config[ "script_font" ] )
|
||||
self.set_script_font_handler( None )
|
||||
if "adc_bit_depth" in config:
|
||||
try:
|
||||
self.config_adc_bit_depth_spinbutton.set_value(float(config["adc_bit_depth"]))
|
||||
ADC_Result.default_bit_depth = int(config["adc_bit_depth"])
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
if "data_pool_complib" in config:
|
||||
# find combo-box entry and make it active...
|
||||
model = self.config_data_pool_complib.get_model( )
|
||||
@@ -2096,6 +2234,9 @@ pygobject version %(pygobject)s
|
||||
if iter is None:
|
||||
print("compression method %s is not supported" % config[ "data_pool_complib" ])
|
||||
|
||||
def on_adc_bit_depth_changed(self, widget):
|
||||
ADC_Result.default_bit_depth = widget.get_value_as_int()
|
||||
|
||||
def load_config_handler( self, widget ):
|
||||
if self.system_default_filename:
|
||||
self.load_config( self.system_default_filename )
|
||||
@@ -2270,6 +2411,13 @@ class MonitorWidgets:
|
||||
self.display_autoscaling_checkbutton = self.xml_gui.get_object( "display_autoscaling_checkbutton" )
|
||||
self.display_statistics_checkbutton = self.xml_gui.get_object( "display_statistics_checkbutton" )
|
||||
|
||||
self.display_descriptions_checkbutton = gtk.CheckButton(label="Show Desc.")
|
||||
self.display_descriptions_checkbutton.set_active(True)
|
||||
self.display_descriptions_checkbutton.show()
|
||||
self.display_settings_frame.set_property("n-columns", 5)
|
||||
self.display_settings_frame.attach(self.display_descriptions_checkbutton, 4, 5, 0, 1, gtk.AttachOptions.FILL, 0, 0, 0)
|
||||
self.display_descriptions_checkbutton.connect("toggled", self.display_descriptions_toggled)
|
||||
|
||||
# Matplot (Display_Table, 1st Row) --------------------------------------------------------
|
||||
|
||||
# create new plot
|
||||
@@ -2363,6 +2511,8 @@ class MonitorWidgets:
|
||||
self.__rescale = True
|
||||
self.update_counter = 0
|
||||
self.update_counter_lock = threading.Lock( )
|
||||
self.description_text = None
|
||||
self.clipping_marker = None
|
||||
|
||||
def source_list_reset( self ):
|
||||
self.display_source_treestore.clear( )
|
||||
@@ -2374,7 +2524,7 @@ class MonitorWidgets:
|
||||
def source_list_find_one( self, model, iter, what ):
|
||||
"""find node in subcategory"""
|
||||
while iter is not None:
|
||||
if model.get( iter, 0 )[ 0 ] == what:
|
||||
if str( model.get( iter, 0 )[ 0 ] ) == what:
|
||||
return iter
|
||||
iter = model.iter_next( iter )
|
||||
return iter
|
||||
@@ -2445,31 +2595,50 @@ class MonitorWidgets:
|
||||
register a listener and save reference to data
|
||||
assume to be in gtk/gdk lock
|
||||
"""
|
||||
if self.data_pool is not None:
|
||||
# maybe some extra cleanup needed
|
||||
print("ToDo: cleanup widgets")
|
||||
if self.displayed_data[ 1 ] is not None and hasattr( self.displayed_data[ 1 ], "unregister_listener" ):
|
||||
self.displayed_data[ 1 ].unregister_listener( self.datastructures_listener )
|
||||
self.displayed_data[ 1 ] = None
|
||||
self.displayed_data = [ None, None ]
|
||||
self.data_pool.unregister_listener( self.datapool_listener )
|
||||
self.data_pool = None
|
||||
gdk.threads_enter( )
|
||||
try:
|
||||
if self.data_pool is not None:
|
||||
# maybe some extra cleanup needed
|
||||
print("ToDo: cleanup widgets")
|
||||
if self.displayed_data[ 1 ] is not None and hasattr( self.displayed_data[ 1 ], "unregister_listener" ):
|
||||
self.displayed_data[ 1 ].unregister_listener( self.datastructures_listener )
|
||||
self.displayed_data[ 1 ] = None
|
||||
self.displayed_data = [ None, None ]
|
||||
self.data_pool.unregister_listener( self.datapool_listener )
|
||||
self.data_pool = None
|
||||
|
||||
self.update_counter_lock.acquire( )
|
||||
self.update_counter = 0
|
||||
self.update_counter_lock.release( )
|
||||
|
||||
# display states
|
||||
self.source_list_reset( )
|
||||
self.update_counter_lock.acquire( )
|
||||
self.update_counter = 0
|
||||
self.update_counter_lock.release( )
|
||||
self.__rescale = True
|
||||
self.displayed_data = [ None, None ]
|
||||
self.display_source_path_label.set_label( "" )
|
||||
self.clear_display( )
|
||||
|
||||
# display states
|
||||
self.__rescale = True
|
||||
self.displayed_data = [ None, None ]
|
||||
self.display_source_path_label.set_label( "" )
|
||||
self.clear_display( )
|
||||
|
||||
if data_pool is not None:
|
||||
# keep track of data
|
||||
self.data_pool = data_pool
|
||||
self.data_pool.register_listener( self.datapool_listener )
|
||||
if data_pool is not None:
|
||||
# keep track of data
|
||||
self.data_pool = data_pool
|
||||
self.data_pool.register_listener( self.datapool_listener )
|
||||
|
||||
# Populate source list with existing data since the listener
|
||||
# was registered after the data was loaded
|
||||
for key in sorted(data_pool.keys()):
|
||||
if key != "__builtins__": # Skip internal keys
|
||||
self.source_list_add(key)
|
||||
|
||||
# Select first data item if available
|
||||
keys = [k for k in data_pool.keys() if not k.startswith("__")]
|
||||
if keys:
|
||||
keys.sort()
|
||||
first_key = keys[0]
|
||||
first_iter = self.source_list_find(first_key.split("/"))
|
||||
if first_iter:
|
||||
self.display_source_combobox.set_active_iter(first_iter)
|
||||
finally:
|
||||
gdk.threads_leave( )
|
||||
|
||||
#################### observing data structures and produce idle events
|
||||
|
||||
@@ -2606,7 +2775,7 @@ class MonitorWidgets:
|
||||
gdk.threads_leave( )
|
||||
elif event.what == DataPool.Event.destroy:
|
||||
gdk.threads_enter( )
|
||||
self.source_list_reset( 'None' )
|
||||
self.source_list_reset( )
|
||||
self.displayed_data = [ None, None ]
|
||||
self.clear_display( )
|
||||
gdk.threads_leave( )
|
||||
@@ -2685,6 +2854,10 @@ class MonitorWidgets:
|
||||
if self.displayed_data[ 0 ] is not None:
|
||||
self.update_display( self.displayed_data[ 0 ][ : ] )
|
||||
|
||||
def display_descriptions_toggled( self, widget, data=None ):
|
||||
if self.displayed_data[ 0 ] is not None:
|
||||
self.update_display( self.displayed_data[ 0 ][ : ] )
|
||||
|
||||
def save_display_data_as_text( self, widget, data=None ):
|
||||
"""
|
||||
copy data to tmp file and show save dialog
|
||||
@@ -2935,6 +3108,18 @@ class MonitorWidgets:
|
||||
self.graphen = [ ]
|
||||
self.matplot_axes.clear( )
|
||||
self.matplot_axes.grid( True )
|
||||
if self.description_text is not None:
|
||||
try:
|
||||
self.description_text.remove()
|
||||
except:
|
||||
pass
|
||||
self.description_text = None
|
||||
if self.clipping_marker is not None:
|
||||
try:
|
||||
self.clipping_marker.remove()
|
||||
except:
|
||||
pass
|
||||
self.clipping_marker = None
|
||||
self.matplot_canvas.draw_idle( )
|
||||
|
||||
def update_display( self, subject=None ):
|
||||
@@ -3093,25 +3278,48 @@ class MonitorWidgets:
|
||||
self.matplot_axes.set_ylabel( "" )
|
||||
|
||||
# Any variables to be drawn on plot
|
||||
if False:
|
||||
descriptions = None
|
||||
description_lines = []
|
||||
if self.display_descriptions_checkbutton.get_active():
|
||||
if isinstance(in_result, Accumulation):
|
||||
descriptions = in_result.common_descriptions
|
||||
description_lines.append("n = %d" % in_result.n)
|
||||
if in_result.common_descriptions is not None:
|
||||
description_lines.extend(["%s = %s" % (key, in_result.common_descriptions[key]) for key in in_result.common_descriptions.keys()])
|
||||
elif isinstance(in_result, ADC_Result):
|
||||
descriptions = in_result.description
|
||||
if hasattr(in_result, "job_id") and in_result.job_id is not None:
|
||||
description_lines.append("job_id = %s" % str(in_result.job_id))
|
||||
if hasattr(in_result, "description") and in_result.description is not None:
|
||||
description_lines.extend(["%s = %s" % (key, in_result.description[key]) for key in in_result.description.keys()])
|
||||
|
||||
if description_lines:
|
||||
description_string = "\n".join(description_lines)
|
||||
if self.description_text is None:
|
||||
self.description_text = self.matplot_axes.text(0.98, 0.95, description_string,
|
||||
size=10,
|
||||
transform=self.matplot_axes.transAxes,
|
||||
va='top', ha='right', ma='left',
|
||||
bbox=dict(facecolor='lightgray', alpha=0.5, boxstyle='round,pad=0.2'))
|
||||
else:
|
||||
pass
|
||||
#actual_config = self.config.get()
|
||||
if (descriptions is not None):
|
||||
#print(actual_config['pretty_descriptions'])
|
||||
description_string = ""
|
||||
for key in descriptions.keys():
|
||||
description_string += "%s = %s\n" % (key,descriptions[key])
|
||||
self.matplot_axes.text(0.8,0.95, description_string[:-1],
|
||||
size=10,
|
||||
transform=self.matplot_axes.transAxes,
|
||||
va='top',
|
||||
backgroundcolor='white')
|
||||
self.description_text.set_text(description_string)
|
||||
elif self.description_text is not None:
|
||||
try:
|
||||
self.description_text.remove()
|
||||
except:
|
||||
pass
|
||||
self.description_text = None
|
||||
|
||||
if getattr(in_result, "is_clipped", False):
|
||||
if self.clipping_marker is None:
|
||||
self.clipping_marker = self.matplot_axes.text(0.5, 0.95, "ADC OVERFLOW",
|
||||
transform=self.matplot_axes.transAxes,
|
||||
color="red", fontsize=20, fontweight="bold",
|
||||
ha="center", va="top",
|
||||
bbox=dict(facecolor='white', alpha=0.7, edgecolor='red'))
|
||||
elif self.clipping_marker is not None:
|
||||
try:
|
||||
self.clipping_marker.remove()
|
||||
except:
|
||||
pass
|
||||
self.clipping_marker = None
|
||||
|
||||
# Draw it!
|
||||
self.matplot_canvas.draw_idle( )
|
||||
@@ -3287,6 +3495,8 @@ class ScriptInterface:
|
||||
self.back_driver.start( )
|
||||
while (not self.back_driver.quit_flag.isSet( ) and \
|
||||
(self.back_driver.core_pid is None or self.back_driver.core_pid <= 0)):
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration_do(False)
|
||||
self.back_driver.quit_flag.wait( 0.1 )
|
||||
if self.exp_handling:
|
||||
self.exp_handling.start( )
|
||||
|
||||
@@ -3,6 +3,7 @@ import io
|
||||
import traceback
|
||||
import sys
|
||||
import time
|
||||
import ast
|
||||
from damaris.experiments.Experiment import Quit
|
||||
from damaris.experiments import Experiment
|
||||
|
||||
@@ -48,8 +49,32 @@ class ExperimentHandling(threading.Thread):
|
||||
self.raised_exception = None
|
||||
self.location = None
|
||||
exp_iterator=None
|
||||
|
||||
# check for time.sleep()
|
||||
try:
|
||||
tree = ast.parse(self.script)
|
||||
for node in ast.walk(tree):
|
||||
found_blocking_sleep = False
|
||||
if isinstance(node, ast.Call):
|
||||
func = node.func
|
||||
if isinstance(func, ast.Attribute) and func.attr == 'sleep':
|
||||
if isinstance(func.value, ast.Name) and func.value.id == 'time':
|
||||
found_blocking_sleep = True
|
||||
elif isinstance(node, ast.ImportFrom) and node.module == 'time':
|
||||
for alias in node.names:
|
||||
if alias.name == 'sleep':
|
||||
found_blocking_sleep = True
|
||||
|
||||
if found_blocking_sleep:
|
||||
print("Warning: time.sleep() detected in experiment script. This is blocking and not interruptible. Please use sleep() instead.")
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
exec(self.script, dataspace)
|
||||
except StopExperiment:
|
||||
return
|
||||
except Exception as e:
|
||||
self.raised_exception=e
|
||||
self.location=traceback.extract_tb(sys.exc_info()[2])[-1][1:3]
|
||||
@@ -61,6 +86,8 @@ class ExperimentHandling(threading.Thread):
|
||||
if "experiment" in dataspace:
|
||||
try:
|
||||
exp_iterator=dataspace["experiment"]()
|
||||
except StopExperiment:
|
||||
return
|
||||
except Exception as e:
|
||||
self.raised_exception=e
|
||||
self.location=traceback.extract_tb(sys.exc_info()[2])[-1][1:3]
|
||||
|
||||
@@ -4,7 +4,9 @@ import sys
|
||||
import os
|
||||
import os.path
|
||||
import traceback
|
||||
import ast
|
||||
from damaris.data import Resultable
|
||||
from damaris.gui.ExperimentHandling import StopExperiment
|
||||
|
||||
class ResultHandling(threading.Thread):
|
||||
"""
|
||||
@@ -30,10 +32,35 @@ class ResultHandling(threading.Thread):
|
||||
del data_classes
|
||||
dataspace["results"]=self
|
||||
dataspace["data"]=self.data_space
|
||||
dataspace["sleep"]=self.sleep
|
||||
self.raised_exception=None
|
||||
self.location = None
|
||||
|
||||
# check for time.sleep()
|
||||
try:
|
||||
tree = ast.parse(self.script)
|
||||
for node in ast.walk(tree):
|
||||
found_blocking_sleep = False
|
||||
if isinstance(node, ast.Call):
|
||||
func = node.func
|
||||
if isinstance(func, ast.Attribute) and func.attr == 'sleep':
|
||||
if isinstance(func.value, ast.Name) and func.value.id == 'time':
|
||||
found_blocking_sleep = True
|
||||
elif isinstance(node, ast.ImportFrom) and node.module == 'time':
|
||||
for alias in node.names:
|
||||
if alias.name == 'sleep':
|
||||
found_blocking_sleep = True
|
||||
|
||||
if found_blocking_sleep:
|
||||
print("Warning: time.sleep() detected in result script. This is blocking and not interruptible. Please use sleep() instead.")
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
exec(self.script, dataspace)
|
||||
except StopExperiment:
|
||||
return
|
||||
except Exception as e:
|
||||
self.raised_exception=e
|
||||
self.location=traceback.extract_tb(sys.exc_info()[2])[-1][1:3]
|
||||
@@ -47,6 +74,8 @@ class ResultHandling(threading.Thread):
|
||||
return
|
||||
try:
|
||||
dataspace["result"]()
|
||||
except StopExperiment:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.raised_exception=e
|
||||
self.location=traceback.extract_tb(sys.exc_info()[2])[-1][1:3]
|
||||
@@ -56,6 +85,11 @@ class ResultHandling(threading.Thread):
|
||||
traceback_file=None
|
||||
dataspace=None
|
||||
|
||||
def sleep(self, seconds):
|
||||
self.quit_flag.wait(seconds)
|
||||
if self.quit_flag.isSet():
|
||||
raise StopExperiment
|
||||
|
||||
def __iter__(self):
|
||||
if self.quit_flag.isSet():
|
||||
self.results=None
|
||||
|
||||
@@ -239,6 +239,7 @@ class ResultReader:
|
||||
self.result.index = [(0,tmp_size-1)]
|
||||
tmp_index += tmp_size
|
||||
self.result.cont_data=True
|
||||
self.result.check_clipping()
|
||||
tmp_part = None
|
||||
|
||||
def __parseFile_expat(self, in_file):
|
||||
@@ -310,6 +311,7 @@ class ResultReader:
|
||||
self.result.index.append((tmp_sample_counter,tmp_sample_counter+tmp_size-1))
|
||||
tmp_sample_counter+=tmp_size
|
||||
self.result.cont_data=True
|
||||
self.result.check_clipping()
|
||||
|
||||
# Callback when a xml start tag is found
|
||||
def __xmlStartTagFound(self, in_name, in_attribute):
|
||||
|
||||
@@ -491,6 +491,21 @@ Public License instead of this License.
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="toolbar_open_hdf5_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Open DAMARIS HDF5 file</property>
|
||||
<property name="label">Open HDF5</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="icon-name">document-open</property>
|
||||
<signal name="clicked" handler="on_toolbar_open_hdf5_button_clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="toolbar_save_file_button">
|
||||
<property name="visible">True</property>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import tables
|
||||
import numpy
|
||||
|
||||
r = MeasurementResult("test")
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ class TestAccumulation(unittest.TestCase):
|
||||
self.assertTrue(np.array_equal(sub_result.y[0], np.array([20, 30])))
|
||||
self.assertTrue(np.array_equal(sub_result.y[1], np.array([25, 35])))
|
||||
self.assertEqual(sub_result.index, [(0, 1)])
|
||||
# TODO Fix #13: self.assertEqual(sub_result.common_descriptions, {"key": "value"})
|
||||
self.assertEqual(sub_result.common_descriptions, {"key": "value"})
|
||||
|
||||
def test_write_to_csv_without_error(self):
|
||||
"""Test the functionality of writing to CSV."""
|
||||
|
||||
Reference in new issue
Block a user