merge upstream
Build Debian Packages / build (bookworm, debian12) (push) Failing after 7s
Build Debian Packages / build (trixie, debian13) (push) Canceled after 7s

Merge branch 'main' of gitea.pkm.physik.tu-darmstadt.de:IPKM/python3-damaris
This commit is contained in:
markusro committed 2026-10-02 11:27:38 +02:00
commit 227ddda684
9 files changed
+181 -8

No files matched your search

-3
View File
@@ -20,6 +20,3 @@ debian/*.debhelper.log
debian/*.debhelper
debian/*.substvars
debian/python3-damaris/
# Agent metadata
AGENTS.md
+19
View File
@@ -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
+91
View File
@@ -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
+4
View File
@@ -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'
+5
View File
@@ -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
+5 -4
View File
@@ -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
-1
View File
@@ -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))
+28
View File
@@ -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()
+29
View File
@@ -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