Public Access
merge upstream
Merge branch 'main' of gitea.pkm.physik.tu-darmstadt.de:IPKM/python3-damaris
This commit is contained in:
commit
227ddda684
9 files changed
+180
-7
No files matched your search
@@ -20,6 +20,3 @@ debian/*.debhelper.log
|
|||||||
debian/*.debhelper
|
debian/*.debhelper
|
||||||
debian/*.substvars
|
debian/*.substvars
|
||||||
debian/python3-damaris/
|
debian/python3-damaris/
|
||||||
|
|
||||||
# Agent metadata
|
|
||||||
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
|
||||||
@@ -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
|
||||||
@@ -21,9 +21,13 @@ release = damaris.__version__
|
|||||||
|
|
||||||
extensions = [
|
extensions = [
|
||||||
'sphinx.ext.autodoc',
|
'sphinx.ext.autodoc',
|
||||||
|
'sphinx.ext.autosummary',
|
||||||
'sphinx.ext.napoleon',
|
'sphinx.ext.napoleon',
|
||||||
|
'sphinx.ext.viewcode',
|
||||||
'sphinxcontrib.bibtex',
|
'sphinxcontrib.bibtex',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
autosummary_generate = True
|
||||||
bibtex_bibfiles = ['bibdesk_db.bib']
|
bibtex_bibfiles = ['bibdesk_db.bib']
|
||||||
bibtex_bibstyles = ['numeric']
|
bibtex_bibstyles = ['numeric']
|
||||||
bibtex_citestyle = 'numeric'
|
bibtex_citestyle = 'numeric'
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ class ADC_Result(Resultable, Drawable, DamarisFFT, Signalpath):
|
|||||||
self.y.append(numpy.zeros((samples,), dtype="int16"))
|
self.y.append(numpy.zeros((samples,), dtype="int16"))
|
||||||
|
|
||||||
self.x = numpy.zeros((samples,), dtype="float32")
|
self.x = numpy.zeros((samples,), dtype="float32")
|
||||||
|
self.nChannels = channels
|
||||||
|
|
||||||
self.index.append((0, samples-1))
|
self.index.append((0, samples-1))
|
||||||
self.cont_data = True
|
self.cont_data = True
|
||||||
@@ -231,6 +232,8 @@ class ADC_Result(Resultable, Drawable, DamarisFFT, Signalpath):
|
|||||||
job_id=self.job_id,
|
job_id=self.job_id,
|
||||||
job_date=self.job_date,
|
job_date=self.job_date,
|
||||||
)
|
)
|
||||||
|
r.nChannels = self.nChannels
|
||||||
|
r.is_clipped = self.is_clipped
|
||||||
finally:
|
finally:
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
|
||||||
@@ -274,6 +277,8 @@ class ADC_Result(Resultable, Drawable, DamarisFFT, Signalpath):
|
|||||||
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.copy() if self.description is not None else None, 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)
|
||||||
|
r.nChannels = self.nChannels
|
||||||
|
r.is_clipped = self.is_clipped
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|||||||
@@ -111,21 +111,22 @@ class Accumulation(Errorable, Drawable, DamarisFFT, Signalpath):
|
|||||||
self.lock.release()
|
self.lock.release()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
tmp_x = self.x[start:end+1]
|
tmp_x = self.x[start:end+1].copy()
|
||||||
tmp_y = []
|
tmp_y = []
|
||||||
|
|
||||||
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])
|
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():
|
if self.uses_statistics():
|
||||||
r.y_square = []
|
r.y_square = []
|
||||||
for i in range(self.get_number_of_channels()):
|
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:
|
if self.common_descriptions is not None:
|
||||||
r.common_descriptions = self.common_descriptions.copy()
|
r.common_descriptions = self.common_descriptions.copy()
|
||||||
r.time_period = self.time_period[:]
|
r.time_period = self.time_period[:]
|
||||||
r.job_ids = self.job_ids.copy()
|
r.job_ids = self.job_ids.copy()
|
||||||
|
r.is_clipped = self.is_clipped
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|||||||
@@ -231,7 +231,6 @@ class ResultReader:
|
|||||||
for i in range(nChannels):
|
for i in range(nChannels):
|
||||||
# split interleaved data
|
# 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]
|
||||||
self.result.y[i][tmp_index:tmp_index+tmp_size] = tmp_part[i::nChannels]
|
|
||||||
|
|
||||||
if self.result.index != []:
|
if self.result.index != []:
|
||||||
self.result.index.append((tmp_index, tmp_index+tmp_size-1))
|
self.result.index.append((tmp_index, tmp_index+tmp_size-1))
|
||||||
|
|||||||
@@ -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.x, np.zeros(3, dtype="float32")))
|
||||||
self.assertTrue(np.array_equal(adc.y[0], np.zeros(3, dtype="int16")))
|
self.assertTrue(np.array_equal(adc.y[0], np.zeros(3, dtype="int16")))
|
||||||
self.assertEqual(adc.index, [(0, 2)])
|
self.assertEqual(adc.index, [(0, 2)])
|
||||||
|
self.assertEqual(adc.nChannels, 2)
|
||||||
|
|
||||||
def test_add_sample_space(self):
|
def test_add_sample_space(self):
|
||||||
"""Test adding sample space."""
|
"""Test adding sample space."""
|
||||||
@@ -90,6 +91,33 @@ class TestADCResult(unittest.TestCase):
|
|||||||
self.assertEqual(sub_result.description, {"key": "value"})
|
self.assertEqual(sub_result.description, {"key": "value"})
|
||||||
self.assertEqual(sub_result.job_id, 42)
|
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):
|
def test_set_sampling_rate(self):
|
||||||
"""Test updating the sampling rate."""
|
"""Test updating the sampling rate."""
|
||||||
adc = ADC_Result()
|
adc = ADC_Result()
|
||||||
|
|||||||
@@ -107,6 +107,35 @@ class TestAccumulation(unittest.TestCase):
|
|||||||
self.assertEqual(sub_result.index, [(0, 1)])
|
self.assertEqual(sub_result.index, [(0, 1)])
|
||||||
self.assertEqual(sub_result.common_descriptions, {"key": "value"})
|
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):
|
def test_write_to_csv_without_error(self):
|
||||||
"""Test the functionality of writing to CSV."""
|
"""Test the functionality of writing to CSV."""
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|||||||
Reference in new issue
Block a user