8 Commits
Author SHA1 Message Date
markusro b46debad69 Fixed #13 and #14
Build Debian Packages / build (trixie, debian13) (push) Successful in 14m2s
Build Debian Packages / build (bookworm, debian12) (push) Successful in 14m11s
Build Debian Packages / build (bullseye, debian11) (push) Has been cancelled
2026-07-05 14:07:57 +02:00
markusro 4250039b3c can now set ADC_Result bit depth in the config window (Bug #16)
Build Debian Packages / build (trixie, debian13) (push) Successful in 14m3s
Build Debian Packages / build (bookworm, debian12) (push) Successful in 14m4s
Build Debian Packages / build (bullseye, debian11) (push) Has been cancelled
2026-07-05 13:59:09 +02:00
markusro d3b1a9e3fd Show message if ADC_Data is clipped (solves #16)
Build Debian Packages / build (bookworm, debian12) (push) Successful in 14m38s
Build Debian Packages / build (trixie, debian13) (push) Successful in 14m40s
Build Debian Packages / build (bullseye, debian11) (push) Has been cancelled
2026-07-05 13:44:20 +02:00
markusro 06867ec17b Missing search input field fixed (Fixes #25)
Build Debian Packages / build (bookworm, debian12) (push) Successful in 14m5s
Build Debian Packages / build (trixie, debian13) (push) Successful in 14m7s
Build Debian Packages / build (bullseye, debian11) (push) Has been cancelled
2026-07-05 13:24:20 +02:00
markusro 80d8f1e78d fixed stop button, no can interrupt running experiment
Build Debian Packages / build (trixie, debian13) (push) Successful in 15m23s
Build Debian Packages / build (bookworm, debian12) (push) Successful in 15m46s
Build Debian Packages / build (bullseye, debian11) (push) Has been cancelled
2026-07-04 21:03:23 +02:00
markusro 58e7846bbe description is plotted better now 2026-07-04 20:50:57 +02:00
markusro 1c4f8f5344 Improved interruptable sleep and warn user when using time.sleep.
Build Debian Packages / build (trixie, debian13) (push) Successful in 13m4s
Build Debian Packages / build (bookworm, debian12) (push) Successful in 13m9s
Build Debian Packages / build (bullseye, debian11) (push) Has been cancelled
2026-07-04 20:26:14 +02:00
markusro a3738025b7 fixed autocomplete in the editor windows
Build Debian Packages / build (trixie, debian13) (push) Successful in 12m59s
Build Debian Packages / build (bookworm, debian12) (push) Successful in 13m9s
Build Debian Packages / build (bullseye, debian11) (push) Has been cancelled
2026-07-04 20:01:34 +02:00
10 changed files with 256 additions and 34 deletions

No files matched your search

+6
View File
@@ -1,3 +1,9 @@
python3-damaris (0.21) UNRELEASED; urgency=medium
* fixed autocomplete in the editor tabs
-- Markus Rosenstihl <markus.rosenstihl@pkm.tu-darmstadt.de> Sat, 04 Jul 2026 20:00:13 +0200
python3-damaris (0.20) stable; urgency=medium python3-damaris (0.20) stable; urgency=medium
* Unified version management across Python app, GUI and documentation * Unified version management across Python app, GUI and documentation
+1 -1
View File
@@ -15,5 +15,5 @@
## module contents ## module contents
# #
__version__ = "0.20" __version__ = "0.21"
__all__=["experiments", "data", "gui"] __all__=["experiments", "data", "gui"]
+44 -1
View File
@@ -19,6 +19,7 @@ import tables
############################################################################# #############################################################################
class ADC_Result(Resultable, Drawable, DamarisFFT, Signalpath): class ADC_Result(Resultable, Drawable, DamarisFFT, Signalpath):
default_bit_depth = 14
""" """
Represents the result of an ADC, encapsulating data and metadata Represents the result of an ADC, encapsulating data and metadata
for processing, visualization, and export. for processing, visualization, and export.
@@ -50,6 +51,7 @@ class ADC_Result(Resultable, Drawable, DamarisFFT, Signalpath):
self.ylabel = "Samples [Digits]" self.ylabel = "Samples [Digits]"
self.lock=threading.RLock() self.lock=threading.RLock()
self.nChannels = 0 self.nChannels = 0
self.is_clipped = False
# using no argument for initialization # 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): 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 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): 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\"" "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()): for i in range(self.get_number_of_channels()):
tmp_y.append(self.y[i][start:end+1].copy()) 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() self.lock.release()
return r return r
@@ -536,6 +576,8 @@ def read_from_hdf(hdf_node):
# job id and x,y titles are missing # job id and x,y titles are missing
adc=ADC_Result() adc=ADC_Result()
if hdf_node._v_title:
adc.set_title(hdf_node._v_title)
# populate description dictionary # populate description dictionary
adc.description={} adc.description={}
for attrname in hdf_node._v_attrs._v_attrnamesuser: 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]): for ch in range(adc_data.shape[1]):
adc.y.append(adc_data[:,ch]) adc.y.append(adc_data[:,ch])
adc.check_clipping()
return adc return adc
+34 -3
View File
@@ -61,6 +61,7 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
self.xlabel = "Time / s" self.xlabel = "Time / s"
self.ylabel = "Avg. Samples [Digits]" self.ylabel = "Avg. Samples [Digits]"
self.lock=threading.RLock() self.lock=threading.RLock()
self.is_clipped = False
self.common_descriptions=None self.common_descriptions=None
self.time_period=[] self.time_period=[]
@@ -117,9 +118,23 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
tmp_y.append(self.y[i][start:end+1]) 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) 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() self.lock.release()
return r 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): def get_ysquare(self, channel):
if self.uses_statistics(): if self.uses_statistics():
try: 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) 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: 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 = 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_id = self.job_id
r.job_ids = self.job_ids.copy() r.job_ids = self.job_ids.copy()
r.time_period = self.time_period[:] if self.time_period is not None else None 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) 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: 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) 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.time_period=[other.job_date,other.job_date]
r.job_id = other.job_id r.job_id = other.job_id
r.common_descriptions=other.description.copy() 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) 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: 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 = 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), r.time_period=[min(self.time_period[0],other.job_date),
max(self.time_period[1],other.job_date)] max(self.time_period[1],other.job_date)]
r.job_id = other.job_id 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()): for i in range(self.get_number_of_channels()):
tmp_y.append(self.y[i] + other.y[i]) tmp_y.append(self.y[i] + other.y[i])
if self.uses_statistics():
tmp_ysquare.append(self.y_square[i] + other.y_square[i]) tmp_ysquare.append(self.y_square[i] + other.y_square[i])
if self.uses_statistics(): 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) 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: 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 = 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]), r.time_period=[min(self.time_period[0],other.time_period[0]),
max(self.time_period[1],other.time_period[1])] max(self.time_period[1],other.time_period[1])]
r.job_id = other.job_id 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.set_title(self.__title_pattern % self.n)
self.lock.release() self.lock.release()
if hasattr(other, "is_clipped"):
self.is_clipped = other.is_clipped
self.time_period=[other.job_date,other.job_date] self.time_period=[other.job_date,other.job_date]
self.job_id = other.job_id # added by Oleg Petrov self.job_id = other.job_id # added by Oleg Petrov
self.job_ids[other.job_id] = other.get_job_date() 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.y_square[i] += numpy.array(other.y[i], dtype="float32") ** 2
self.n += 1 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), self.time_period=[min(self.time_period[0],other.job_date),
max(self.time_period[1],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.y_square.append(self.y[i] ** 2)
self.set_title(self.__title_pattern % self.n) 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.time_period=other.time_period[:]
self.job_id = other.job_id # added by Oleg Petrov 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() self.lock.release()
return self return self
@@ -1049,9 +1075,12 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
self.y_square[i] += other.y_square[i] self.y_square[i] += other.y_square[i]
self.n += other.n 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]), self.time_period=[min(self.time_period[0],other.time_period[0]),
max(self.time_period[1],other.time_period[1])] max(self.time_period[1],other.time_period[1])]
self.job_id = other.job_id self.job_id = other.job_id
self.job_ids.update(other.job_ids)
# Removes mismatched common description keys # Removes mismatched common description keys
if self.common_descriptions is not None and other.common_descriptions is not None: if self.common_descriptions is not None and other.common_descriptions is not None:
# Get all keys that exist in both dictionaries # Get all keys that exist in both dictionaries
@@ -1168,6 +1197,8 @@ def read_from_hdf(hdf_node):
accu.n=r["number"] accu.n=r["number"]
accu.sampling_rate=1.0/r["dwelltime"] accu.sampling_rate=1.0/r["dwelltime"]
accu.set_title("Accumulation: n = %d" % accu.n)
# now really belief there are no data # now really belief there are no data
if len(accu.index)==0 or accu.n==0: if len(accu.index)==0 or accu.n==0:
accu.cont_data=False accu.cont_data=False
+106 -28
View File
@@ -471,9 +471,10 @@ class DamarisGUI:
self.id = self.lockfile.add_experiment() self.id = self.lockfile.add_experiment()
while not self.lockfile.am_i_next(): while not self.lockfile.am_i_next():
self.stop_experiment_flag.wait(interval)
while gtk.events_pending(): while gtk.events_pending():
gtk.main_iteration_do(False) gtk.main_iteration_do(False)
if self.stop_experiment_flag.wait(interval):
break
if loop_run > 1: if loop_run > 1:
self.experiment_script_statusbar_label.set_text("Waiting for other experiment to finish") self.experiment_script_statusbar_label.set_text("Waiting for other experiment to finish")
loop_run = 0 loop_run = 0
@@ -524,7 +525,10 @@ class DamarisGUI:
print("waiting for threads stoping...", end=' ') 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( )] 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: 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") print("done")
# cleanup # cleanup
@@ -1186,7 +1190,6 @@ get_ylabel set_ylabel"""
compexp.add_provider(self.compwexp) compexp.add_provider(self.compwexp)
compres.add_provider(self.compwres) compres.add_provider(self.compwres)
# script fonts are atlered by configuration
# clipboard # clipboard
self.main_clipboard = gtk.Clipboard( ) self.main_clipboard = gtk.Clipboard( )
@@ -1194,13 +1197,12 @@ get_ylabel set_ylabel"""
self.experiment_script_statusbar_label = self.xml_gui.get_object( "statusbar_experiment_script_label" ) 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" ) 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_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.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_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" ) self.data_handling_column_indicator = self.xml_gui.get_object( "data_handling_column_textfield" )
# some event handlers # some event handlers
self.experiment_script_textbuffer.connect( "modified-changed", self.textviews_modified ) self.experiment_script_textbuffer.connect( "modified-changed", self.textviews_modified )
self.experiment_script_textview.connect_after( "move-cursor", self.textviews_moved ) self.experiment_script_textview.connect_after( "move-cursor", self.textviews_moved )
@@ -1211,8 +1213,7 @@ get_ylabel set_ylabel"""
self.data_handling_textview.connect( "key-press-event", self.textviews_keypress ) self.data_handling_textview.connect( "key-press-event", self.textviews_keypress )
self.data_handling_textview.connect( "button-release-event", self.textviews_clicked ) self.data_handling_textview.connect( "button-release-event", self.textviews_clicked )
# and the editing toolbar part # and the editing toolbar buttons
# buttons
self.toolbar_new_button = self.xml_gui.get_object( "toolbar_new_button" ) 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_open_button = self.xml_gui.get_object( "toolbar_open_file_button" )
self.toolbar_save_button = self.xml_gui.get_object( "toolbar_save_file_button" ) self.toolbar_save_button = self.xml_gui.get_object( "toolbar_save_file_button" )
@@ -1223,8 +1224,9 @@ get_ylabel set_ylabel"""
self.toolbar_redo_button = self.xml_gui.get_object( "toolbar_redo_button" ) self.toolbar_redo_button = self.xml_gui.get_object( "toolbar_redo_button" )
self.toolbar_search_button = self.xml_gui.get_object("toolbar_search_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" ) self.main_notebook = self.xml_gui.get_object( "main_notebook" )
# config toolbar # config toolbar
self.main_notebook.connect_after( "switch_page", self.notebook_page_switched ) self.main_notebook.connect_after( "switch_page", self.notebook_page_switched )
@@ -1798,9 +1800,9 @@ get_ylabel set_ylabel"""
try: try:
if response_id == -1: 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: 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: except:
pass pass
@@ -1857,7 +1859,7 @@ get_ylabel set_ylabel"""
dialog.connect("response", response, self, textbox) dialog.connect("response", response, self, textbox)
dialog.set_resizable(False) dialog.set_resizable(False)
dialog.show() dialog.show_all()
if startit is not None and endit is not None: if startit is not None and endit is not None:
if current_page == 0: if current_page == 0:
@@ -1951,6 +1953,20 @@ class ConfigTab:
self.config_info_textview = self.xml_gui.get_object( "info_textview" ) self.config_info_textview = self.xml_gui.get_object( "info_textview" )
self.config_script_font_button = self.xml_gui.get_object( "script_fontbutton" ) 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 # insert version informations
components_text = """ components_text = """
operating system %(os)s operating system %(os)s
@@ -2044,7 +2060,7 @@ pygobject version %(pygobject)s
""" """
complib_iter = self.config_data_pool_complib.get_active_iter( ) complib_iter = self.config_data_pool_complib.get_active_iter( )
complib = self.config_data_pool_complib.get_model( ).get_value( complib_iter, 0 ) 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_backend": self.config_start_backend_checkbutton.get_active( ),
"start_result_script": self.config_start_result_script_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( ), "start_experiment_script": self.config_start_experiment_script_checkbutton.get_active( ),
@@ -2056,8 +2072,11 @@ pygobject version %(pygobject)s
"data_pool_write_interval": self.config_data_pool_write_interval_entry.get_text( ), "data_pool_write_interval": self.config_data_pool_write_interval_entry.get_text( ),
"data_pool_complib": complib, "data_pool_complib": complib,
"data_pool_comprate": self.config_data_pool_comprate.get_value_as_int( ), "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 ): def set( self, config ):
if "start_backend" in config: if "start_backend" in config:
@@ -2083,6 +2102,12 @@ pygobject version %(pygobject)s
if "script_font" in config: if "script_font" in config:
self.config_script_font_button.set_font_name( config[ "script_font" ] ) self.config_script_font_button.set_font_name( config[ "script_font" ] )
self.set_script_font_handler( None ) 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: if "data_pool_complib" in config:
# find combo-box entry and make it active... # find combo-box entry and make it active...
model = self.config_data_pool_complib.get_model( ) model = self.config_data_pool_complib.get_model( )
@@ -2096,6 +2121,9 @@ pygobject version %(pygobject)s
if iter is None: if iter is None:
print("compression method %s is not supported" % config[ "data_pool_complib" ]) 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 ): def load_config_handler( self, widget ):
if self.system_default_filename: if self.system_default_filename:
self.load_config( self.system_default_filename ) self.load_config( self.system_default_filename )
@@ -2270,6 +2298,13 @@ class MonitorWidgets:
self.display_autoscaling_checkbutton = self.xml_gui.get_object( "display_autoscaling_checkbutton" ) 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_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) -------------------------------------------------------- # Matplot (Display_Table, 1st Row) --------------------------------------------------------
# create new plot # create new plot
@@ -2363,6 +2398,8 @@ class MonitorWidgets:
self.__rescale = True self.__rescale = True
self.update_counter = 0 self.update_counter = 0
self.update_counter_lock = threading.Lock( ) self.update_counter_lock = threading.Lock( )
self.description_text = None
self.clipping_marker = None
def source_list_reset( self ): def source_list_reset( self ):
self.display_source_treestore.clear( ) self.display_source_treestore.clear( )
@@ -2685,6 +2722,10 @@ class MonitorWidgets:
if self.displayed_data[ 0 ] is not None: if self.displayed_data[ 0 ] is not None:
self.update_display( self.displayed_data[ 0 ][ : ] ) 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 ): def save_display_data_as_text( self, widget, data=None ):
""" """
copy data to tmp file and show save dialog copy data to tmp file and show save dialog
@@ -2935,6 +2976,18 @@ class MonitorWidgets:
self.graphen = [ ] self.graphen = [ ]
self.matplot_axes.clear( ) self.matplot_axes.clear( )
self.matplot_axes.grid( True ) 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( ) self.matplot_canvas.draw_idle( )
def update_display( self, subject=None ): def update_display( self, subject=None ):
@@ -3093,25 +3146,48 @@ class MonitorWidgets:
self.matplot_axes.set_ylabel( "" ) self.matplot_axes.set_ylabel( "" )
# Any variables to be drawn on plot # Any variables to be drawn on plot
if False: description_lines = []
descriptions = None if self.display_descriptions_checkbutton.get_active():
if isinstance(in_result, Accumulation): 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): elif isinstance(in_result, ADC_Result):
descriptions = in_result.description if hasattr(in_result, "job_id") and in_result.job_id is not None:
else: description_lines.append("job_id = %s" % str(in_result.job_id))
pass if hasattr(in_result, "description") and in_result.description is not None:
#actual_config = self.config.get() description_lines.extend(["%s = %s" % (key, in_result.description[key]) for key in in_result.description.keys()])
if (descriptions is not None):
#print(actual_config['pretty_descriptions']) if description_lines:
description_string = "" description_string = "\n".join(description_lines)
for key in descriptions.keys(): if self.description_text is None:
description_string += "%s = %s\n" % (key,descriptions[key]) self.description_text = self.matplot_axes.text(0.98, 0.95, description_string,
self.matplot_axes.text(0.8,0.95, description_string[:-1],
size=10, size=10,
transform=self.matplot_axes.transAxes, transform=self.matplot_axes.transAxes,
va='top', va='top', ha='right', ma='left',
backgroundcolor='white') bbox=dict(facecolor='lightgray', alpha=0.5, boxstyle='round,pad=0.2'))
else:
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! # Draw it!
self.matplot_canvas.draw_idle( ) self.matplot_canvas.draw_idle( )
@@ -3287,6 +3363,8 @@ class ScriptInterface:
self.back_driver.start( ) self.back_driver.start( )
while (not self.back_driver.quit_flag.isSet( ) and \ while (not self.back_driver.quit_flag.isSet( ) and \
(self.back_driver.core_pid is None or self.back_driver.core_pid <= 0)): (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 ) self.back_driver.quit_flag.wait( 0.1 )
if self.exp_handling: if self.exp_handling:
self.exp_handling.start( ) self.exp_handling.start( )
+27
View File
@@ -3,6 +3,7 @@ import io
import traceback import traceback
import sys import sys
import time import time
import ast
from damaris.experiments.Experiment import Quit from damaris.experiments.Experiment import Quit
from damaris.experiments import Experiment from damaris.experiments import Experiment
@@ -48,8 +49,32 @@ class ExperimentHandling(threading.Thread):
self.raised_exception = None self.raised_exception = None
self.location = None self.location = None
exp_iterator=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: try:
exec(self.script, dataspace) exec(self.script, dataspace)
except StopExperiment:
return
except Exception as e: except Exception as e:
self.raised_exception=e self.raised_exception=e
self.location=traceback.extract_tb(sys.exc_info()[2])[-1][1:3] self.location=traceback.extract_tb(sys.exc_info()[2])[-1][1:3]
@@ -61,6 +86,8 @@ class ExperimentHandling(threading.Thread):
if "experiment" in dataspace: if "experiment" in dataspace:
try: try:
exp_iterator=dataspace["experiment"]() exp_iterator=dataspace["experiment"]()
except StopExperiment:
return
except Exception as e: except Exception as e:
self.raised_exception=e self.raised_exception=e
self.location=traceback.extract_tb(sys.exc_info()[2])[-1][1:3] self.location=traceback.extract_tb(sys.exc_info()[2])[-1][1:3]
+34
View File
@@ -4,7 +4,9 @@ import sys
import os import os
import os.path import os.path
import traceback import traceback
import ast
from damaris.data import Resultable from damaris.data import Resultable
from damaris.gui.ExperimentHandling import StopExperiment
class ResultHandling(threading.Thread): class ResultHandling(threading.Thread):
""" """
@@ -30,10 +32,35 @@ class ResultHandling(threading.Thread):
del data_classes del data_classes
dataspace["results"]=self dataspace["results"]=self
dataspace["data"]=self.data_space dataspace["data"]=self.data_space
dataspace["sleep"]=self.sleep
self.raised_exception=None self.raised_exception=None
self.location = 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: try:
exec(self.script, dataspace) exec(self.script, dataspace)
except StopExperiment:
return
except Exception as e: except Exception as e:
self.raised_exception=e self.raised_exception=e
self.location=traceback.extract_tb(sys.exc_info()[2])[-1][1:3] self.location=traceback.extract_tb(sys.exc_info()[2])[-1][1:3]
@@ -47,6 +74,8 @@ class ResultHandling(threading.Thread):
return return
try: try:
dataspace["result"]() dataspace["result"]()
except StopExperiment:
pass
except Exception as e: except Exception as e:
self.raised_exception=e self.raised_exception=e
self.location=traceback.extract_tb(sys.exc_info()[2])[-1][1:3] self.location=traceback.extract_tb(sys.exc_info()[2])[-1][1:3]
@@ -56,6 +85,11 @@ class ResultHandling(threading.Thread):
traceback_file=None traceback_file=None
dataspace=None dataspace=None
def sleep(self, seconds):
self.quit_flag.wait(seconds)
if self.quit_flag.isSet():
raise StopExperiment
def __iter__(self): def __iter__(self):
if self.quit_flag.isSet(): if self.quit_flag.isSet():
self.results=None self.results=None
+2
View File
@@ -239,6 +239,7 @@ class ResultReader:
self.result.index = [(0,tmp_size-1)] self.result.index = [(0,tmp_size-1)]
tmp_index += tmp_size tmp_index += tmp_size
self.result.cont_data=True self.result.cont_data=True
self.result.check_clipping()
tmp_part = None tmp_part = None
def __parseFile_expat(self, in_file): 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)) self.result.index.append((tmp_sample_counter,tmp_sample_counter+tmp_size-1))
tmp_sample_counter+=tmp_size tmp_sample_counter+=tmp_size
self.result.cont_data=True self.result.cont_data=True
self.result.check_clipping()
# Callback when a xml start tag is found # Callback when a xml start tag is found
def __xmlStartTagFound(self, in_name, in_attribute): def __xmlStartTagFound(self, in_name, in_attribute):
+1
View File
@@ -1,4 +1,5 @@
import tables import tables
import numpy
r = MeasurementResult("test") r = MeasurementResult("test")
+1 -1
View File
@@ -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[0], np.array([20, 30])))
self.assertTrue(np.array_equal(sub_result.y[1], np.array([25, 35]))) self.assertTrue(np.array_equal(sub_result.y[1], np.array([25, 35])))
self.assertEqual(sub_result.index, [(0, 1)]) 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): def test_write_to_csv_without_error(self):
"""Test the functionality of writing to CSV.""" """Test the functionality of writing to CSV."""