From b6ca2cbba3abd4e45f1d549796ec2ca855f061fa Mon Sep 17 00:00:00 2001 From: Markus Rosenstihl Date: Tue, 14 Jul 2026 20:47:06 +0200 Subject: [PATCH 1/3] add more documentation: API doc --- doc/api_reference.rst | 91 +++++++++++++++++++++++++++++++++++++++++++ doc/conf.py | 4 ++ 2 files changed, 95 insertions(+) create mode 100644 doc/api_reference.rst diff --git a/doc/api_reference.rst b/doc/api_reference.rst new file mode 100644 index 0000000..fae80bb --- /dev/null +++ b/doc/api_reference.rst @@ -0,0 +1,91 @@ +############# +API Reference +############# + +This page provides an auto-generated reference of all DAMARIS classes, functions, and modules. +It is built from docstrings using Sphinx autodoc and autosummary. + +.. toctree:: + :maxdepth: 2 + :caption: Modules: + + damaris/data + damaris/experiments + damaris/gui + damaris/tools + + +Data Module +=========== + +The data module contains result types, data handling, and signal processing classes. + +.. autosummary:: + :toctree: _autosummary + :recursive: + + damaris.data.ADC_Result + damaris.data.Accumulation + damaris.data.DamarisFFT + damaris.data.DataPool + damaris.data.Drawable + damaris.data.Errorable + damaris.data.Error_Result + damaris.data.MeasurementResult + damaris.data.Persistance + damaris.data.Resultable + damaris.data.Signalpath + damaris.data.Temperature + damaris.data.autophase + + +Experiments Module +================== + +The experiments module contains the Experiment class and related utilities for defining pulse sequences. + +.. autosummary:: + :toctree: _autosummary + :recursive: + + damaris.experiments.Experiment + damaris.experiments.DurationEstimator + damaris.experiments.dac + + +GUI Module +========== + +The GUI module contains the graphical interface, backend driver, and experiment/result handling. + +.. autosummary:: + :toctree: _autosummary + :recursive: + + damaris.gui.BackendDriver + damaris.gui.DamarisGUI + damaris.gui.ExperimentHandling + damaris.gui.ExperimentWriter + damaris.gui.ResultHandling + damaris.gui.ResultReader + damaris.gui.TemperatureMonitor + damaris.gui.logging_utils + + +Tools Module +============ + +The tools module contains hardware driver interfaces and utility functions. + +.. autosummary:: + :toctree: _autosummary + :recursive: + + damaris.tools.eurotherm + damaris.tools.flow_control + damaris.tools.goniometer + damaris.tools.lakeshore + damaris.tools.ranges + damaris.tools.signal + damaris.tools.temperature + damaris.tools.temperature_simulator diff --git a/doc/conf.py b/doc/conf.py index 704d8b8..c66ff43 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -21,9 +21,13 @@ release = damaris.__version__ extensions = [ 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', 'sphinx.ext.napoleon', + 'sphinx.ext.viewcode', 'sphinxcontrib.bibtex', ] + +autosummary_generate = True bibtex_bibfiles = ['bibdesk_db.bib'] bibtex_bibstyles = ['numeric'] bibtex_citestyle = 'numeric' From ddb3f00abb9c09c5674d3d846d9cf9ceb9d74a03 Mon Sep 17 00:00:00 2001 From: Markus Rosenstihl Date: Thu, 1 Oct 2026 21:10:49 +0200 Subject: [PATCH 2/3] fixed inconsistencies in returned objects: missing channels, is_clipped --- src/damaris/data/ADC_Result.py | 5 +++++ src/damaris/data/Accumulation.py | 9 +++++---- src/damaris/gui/ResultReader.py | 1 - tests/test_ADC_Result.py | 28 ++++++++++++++++++++++++++++ tests/test_Accumulation.py | 29 +++++++++++++++++++++++++++++ 5 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/damaris/data/ADC_Result.py b/src/damaris/data/ADC_Result.py index 97bbd50..a709729 100644 --- a/src/damaris/data/ADC_Result.py +++ b/src/damaris/data/ADC_Result.py @@ -98,6 +98,7 @@ class ADC_Result(Resultable, Drawable, DamarisFFT, Signalpath): self.y.append(numpy.zeros((samples,), dtype="int16")) self.x = numpy.zeros((samples,), dtype="float32") + self.nChannels = channels self.index.append((0, samples-1)) self.cont_data = True @@ -231,6 +232,8 @@ class ADC_Result(Resultable, Drawable, DamarisFFT, Signalpath): job_id=self.job_id, job_date=self.job_date, ) + r.nChannels = self.nChannels + r.is_clipped = self.is_clipped finally: self.lock.release() @@ -274,6 +277,8 @@ class ADC_Result(Resultable, Drawable, DamarisFFT, Signalpath): 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.copy() if self.description is not None else None, job_id = self.job_id, job_date = self.job_date) + r.nChannels = self.nChannels + r.is_clipped = self.is_clipped self.lock.release() return r diff --git a/src/damaris/data/Accumulation.py b/src/damaris/data/Accumulation.py index 15a7787..f9ee5f0 100644 --- a/src/damaris/data/Accumulation.py +++ b/src/damaris/data/Accumulation.py @@ -111,21 +111,22 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath): self.lock.release() raise - tmp_x = self.x[start:end+1] + tmp_x = self.x[start:end+1].copy() tmp_y = [] for i in range(self.get_number_of_channels()): - tmp_y.append(self.y[i][start:end+1]) + tmp_y.append(self.y[i][start:end+1].copy()) - 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]) + r.y_square.append(self.y_square[i][start:end+1].copy()) 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() + r.is_clipped = self.is_clipped self.lock.release() return r diff --git a/src/damaris/gui/ResultReader.py b/src/damaris/gui/ResultReader.py index 4f2d884..3bed988 100644 --- a/src/damaris/gui/ResultReader.py +++ b/src/damaris/gui/ResultReader.py @@ -231,7 +231,6 @@ class ResultReader: for i in range(nChannels): # split interleaved data self.result.y[i][tmp_index:tmp_index+tmp_size] = tmp_part[i::nChannels] - self.result.y[i][tmp_index:tmp_index+tmp_size] = tmp_part[i::nChannels] if self.result.index != []: self.result.index.append((tmp_index, tmp_index+tmp_size-1)) diff --git a/tests/test_ADC_Result.py b/tests/test_ADC_Result.py index d6b54e3..ead41e3 100644 --- a/tests/test_ADC_Result.py +++ b/tests/test_ADC_Result.py @@ -64,6 +64,7 @@ class TestADCResult(unittest.TestCase): self.assertTrue(np.array_equal(adc.x, np.zeros(3, dtype="float32"))) self.assertTrue(np.array_equal(adc.y[0], np.zeros(3, dtype="int16"))) self.assertEqual(adc.index, [(0, 2)]) + self.assertEqual(adc.nChannels, 2) def test_add_sample_space(self): """Test adding sample space.""" @@ -90,6 +91,33 @@ class TestADCResult(unittest.TestCase): self.assertEqual(sub_result.description, {"key": "value"}) self.assertEqual(sub_result.job_id, 42) + def test_get_result_by_index_copies_nChannels_and_is_clipped(self): + """Test that get_result_by_index properly copies nChannels and is_clipped.""" + adc = self.create_adc_result() + adc.index = [(0, 1), (1, 2)] + adc.set_nChannels(2) + adc.is_clipped = True + + sub_result = adc.get_result_by_index(1) + + self.assertEqual(sub_result.nChannels, 2) + self.assertEqual(sub_result.is_clipped, True) + + def test_get_result_by_index_data_independence(self): + """Test that get_result_by_index returns independent data (not shared views).""" + adc = self.create_adc_result() + adc.index = [(0, 1), (1, 2)] + original_x = adc.x.copy() + original_data = adc.y[0].copy() + + sub_result = adc.get_result_by_index(1) + sub_result.y[0][0] = 9999 + sub_result.x[0] = 999.0 + + self.assertEqual(sub_result.y[0][0], 9999) + self.assertTrue(np.array_equal(adc.y[0], original_data)) + self.assertTrue(np.array_equal(adc.x, original_x)) + def test_set_sampling_rate(self): """Test updating the sampling rate.""" adc = ADC_Result() diff --git a/tests/test_Accumulation.py b/tests/test_Accumulation.py index 84d6927..e0b3330 100644 --- a/tests/test_Accumulation.py +++ b/tests/test_Accumulation.py @@ -107,6 +107,35 @@ class TestAccumulation(unittest.TestCase): self.assertEqual(sub_result.index, [(0, 1)]) self.assertEqual(sub_result.common_descriptions, {"key": "value"}) + def test_get_accu_by_index_copies_is_clipped(self): + """Test that get_accu_by_index properly copies is_clipped.""" + adc = self.create_adc_result() + adc.index = [(0, 1), (1, 2)] + accu = Accumulation() + accu += adc + accu.is_clipped = True + + sub_result = accu.get_accu_by_index(1) + + self.assertEqual(sub_result.is_clipped, True) + + def test_get_accu_by_index_data_independence(self): + """Test that get_accu_by_index returns independent data (not shared views).""" + adc = self.create_adc_result() + adc.index = [(0, 1), (1, 2)] + accu = Accumulation() + accu += adc + original_y0 = accu.y[0].copy() + + sub_result = accu.get_accu_by_index(1) + sub_result.y[0][0] = 9999 + sub_result.x[0] = 999.0 + + self.assertEqual(sub_result.y[0][0], 9999) + self.assertTrue(np.array_equal(accu.y[0], original_y0)) + # x array should also be independent + self.assertEqual(accu.x[1], 1.0) + def test_write_to_csv_without_error(self): """Test the functionality of writing to CSV.""" from io import StringIO From d8d7b9853a39765a20fa30bfce91d06e67cd92de Mon Sep 17 00:00:00 2001 From: Markus Rosenstihl Date: Thu, 1 Oct 2026 21:54:29 +0200 Subject: [PATCH 3/3] Added AGENTS.md --- .gitignore | 3 --- AGENTS.md | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 AGENTS.md diff --git a/.gitignore b/.gitignore index 15e9f45..9be833f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,3 @@ debian/*.debhelper.log debian/*.debhelper debian/*.substvars debian/python3-damaris/ - -# Agent metadata -AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2c7f68f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,19 @@ +# python3-damaris + +## GUI Purpose and Operation +This is the front end part of GUI for controlling NMR spectrometers. On "Start", the experiment script executes and creates XML job files. A backend, not in this code, executes these jobs sequentially adn writes the results as XML files. The front end consumes these result files using the result script. + +## Guidelines +1. Use the .venv python3 environment +5. Old code was ported from gtk2 and python2.7. This code has to work on python 3.11 and above, as well as GTK3 +2. Be aware the targeted Debian versions are bookworm, trixie, and forky. +3. Only use packages and libraries available in Debian bookworm, trixie, and forky. Make sure that the code is compatible with all of these versions. +3. Do not commit changes (with git) or write commit messages without asking the user. +4. Try to minimize code changes, do not use elaborate checks for defensive programming. Ask the user if something should be tested with unit tests. +6. Use the python unittest module for unit tests, not pytest +7. Functions for the experiment script belong to the src/damaris/experiments/Experiment.py file +8. Functions for the result script belong in the files in the src/damaris/data/ folder +9. The gitea workflow can be found at .gitea/workflows/debian-build.yml +10. to not edit anything in "build", it is regenerated +11. Use sphinx for documentation, autodoc and autosummary is used for references of all classes, functions and modules. +12. If a new function/method is created document a short usage the doc/api_reference.rst and a longer explanation in doc/manual.rst