diff --git a/CMakeLists.txt b/CMakeLists.txt index daffa4b67..042b28905 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -202,7 +202,6 @@ option(SLS_USE_SANITIZER "Sanitizers for debugging" OFF) option(SLS_USE_SANITIZER_IN_SERVER "Sanitizers for debugging" OFF) option(SLS_USE_PYTHON "Python bindings" OFF) option(SLS_INSTALL_PYTHONEXT "Install the python extension in the install tree under CMAKE_INSTALL_PREFIX/python/" OFF) -option(SLS_USE_CTBGUI "ctb GUI" OFF) option(SLS_BUILD_DOCS "docs" OFF) option(SLS_BUILD_EXAMPLES "examples" OFF) option(SLS_TUNE_LOCAL "tune to local machine" OFF) @@ -431,9 +430,6 @@ if (SLS_USE_PYTHON) add_subdirectory(python) endif(SLS_USE_PYTHON) -if (SLS_USE_CTBGUI) - add_subdirectory(pyctbgui) -endif(SLS_USE_CTBGUI) # Workaround for file note being copied to build directory # when issuing a python -m build diff --git a/pyctbgui/Makefile b/pyctbgui/Makefile deleted file mode 100644 index f1b26ec09..000000000 --- a/pyctbgui/Makefile +++ /dev/null @@ -1,39 +0,0 @@ -# TODO! Add support for making the pkg? -# Which tests should we have? - -default: ext - -ext: ## [DEFAULT] build c extension in place - rm -rf build/ pyctbgui/_decoder.cpython* - python setup.py build_ext --inplace - -clean: ## Remove the build folder and the shared library - rm -rf build/ pyctbgui/_decoder.cpython* - -test: ## Run unit tests using pytest - python -m pytest -v tests/unit - -test_gui: ## Run E2E tests using pytest - python -m pytest -v tests/gui - -setup_gui_test: ## Setup the environment for the E2E tests - ctbDetectorServer_virtual > /tmp/simulator.log 2>&1 & - slsReceiver > /tmp/slsReceiver.log 2>&1 & - sleep 3 - sls_detector_put config tests/gui/data/simulator.config - -killall: ## Kill all the processes started by setup_gui_test - killall slsReceiver ctbDetectorServer_virtual - - -lint: ## run ruff linter to check formatting errors - @ruff check tests pyctbgui *.py && echo "Ruff checks passed ✅" - -format: ## format code inplace using style in pyproject.toml - yapf --style pyproject.toml -m -r -i tests pyctbgui *.py - -check_format: ## Check if source is formatted properly - yapf --style pyproject.toml -r -d tests pyctbgui *.py - -help: # from compiler explorer - @grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' diff --git a/pyctbgui/README.md b/pyctbgui/README.md index d38bdfb40..a382e37fc 100644 --- a/pyctbgui/README.md +++ b/pyctbgui/README.md @@ -4,28 +4,49 @@ Prototype for a new python based GUI for the Chip Test Board ## Getting started +To install all dependencies build the package with python package ``build``. This requires that you have ``build`` installed. +We recommend using a virtual conda environment. + ```bash git clone https://github.com/slsdetectorgroup/pyctbgui.git cd pyctbgui -make #compiles the c extension inplace +python -m build +pip install dist/pyctbgui-0.0.0-cp313-cp313-linux_x86_64.whl ./CtbGui ``` +Note that the above package installs ``slsdet`` and ``aare``. If you want to work on your local branch add the locally build python modules first to your ``PYTHONPATH``. -## Display help for the Makefile +## Format Code -``` -$ make help -check_format Check if source is formatted properly -clean Remove the build folder and the shared library -ext [DEFAULT] build c extension in place -format format code inplace using style in pyproject.toml -lint run ruff linter to check formatting errors -test Run unit tests using pytest +To check python code is formatted correctly according to style guides in pyproject.toml run (requires ``yapf`` to be installed.): + +```bash +yapf --style pyproject.toml -r -d tests pyctbgui *.py ``` +To format inplace using style in pyproject.toml run: + +```bash +yapf --style pyproject.toml -m -r -i tests pyctbgui *.py +``` + +## Run Tests: + +The following command runs the unit tests (Requires ``pytest``to be installed): + +```bash + python -m pytest -v tests/unit +``` + +The following command runs end to end tests: + +```bash + python -m pytest -v tests/gui +``` ## setup pre-commit hooks + ``` pre-commit install ``` diff --git a/pyctbgui/pyctbgui/services/ADC.py b/pyctbgui/pyctbgui/services/ADC.py index 462ed7de9..e33e0d183 100644 --- a/pyctbgui/pyctbgui/services/ADC.py +++ b/pyctbgui/pyctbgui/services/ADC.py @@ -8,10 +8,8 @@ from PyQt5 import QtWidgets, uic import pyqtgraph as pg from pyqtgraph import LegendItem -from pyctbgui.utils import decoder from pyctbgui.utils.bit_utils import bit_is_set, manipulate_bit from pyctbgui.utils.defines import Defines -import pyctbgui.utils.pixelmap as pm from pyctbgui.utils.recordOrApplyPedestal import recordOrApplyPedestal from slsdet import detectorType @@ -207,7 +205,7 @@ class AdcTab(QtWidgets.QWidget): @recordOrApplyPedestal def _processImageData(self, data, aSamples, nADCEnabled): analog_array = np.array(np.frombuffer(data, dtype=np.uint16, count=nADCEnabled * aSamples)) - return decoder.decode(analog_array, pm.moench04_analog()) + return self.mainWindow.decoder(analog_array) def getADCEnableReg(self): if self.det.type == detectorType.CHIPTESTBOARD: diff --git a/pyctbgui/pyctbgui/services/Plot.py b/pyctbgui/pyctbgui/services/Plot.py index 318a00317..a4bfa0669 100644 --- a/pyctbgui/pyctbgui/services/Plot.py +++ b/pyctbgui/pyctbgui/services/Plot.py @@ -1,14 +1,16 @@ import logging from functools import partial -import pyctbgui.utils.pixelmap as pm import random from pathlib import Path import numpy as np from PyQt5 import QtWidgets, QtGui, uic +from aare import transform, ReadoutMode +from aare._aare import Matterhorn10, Matterhorn02, Moench04 + import pyqtgraph as pg -from pyctbgui.utils import recordOrApplyPedestal +from pyctbgui.utils import recordOrApplyPedestal from pyqtgraph import PlotWidget from pyctbgui.utils.defines import Defines @@ -59,7 +61,7 @@ class PlotTab(QtWidgets.QWidget): self.view.radioButtonWaveform.clicked.connect(self.plotOptions) self.view.radioButtonDistribution.clicked.connect(self.plotOptions) self.view.radioButtonImage.clicked.connect(self.plotOptions) - self.view.comboBoxPlot.currentIndexChanged.connect(self.setPixelMap) + self.view.comboBoxPlot.currentIndexChanged.connect(self.setDecoder) self.view.comboBoxColorMap.currentIndexChanged.connect(self.setColorMap) self.view.comboBoxZMQHWM.currentIndexChanged.connect(self.setZMQHWM) self.view.spinBoxSerialOffset.editingFinished.connect(self.setSerialOffset) @@ -404,7 +406,7 @@ class PlotTab(QtWidgets.QWidget): elif self.view.radioButtonImage.isChecked(): self.view.stackedWidgetPlotType.setCurrentIndex(2) - self.setPixelMap() + self.setDecoder() if self.view.radioButtonNoPlot.isChecked(): self.view.labelPlotOptions.hide() @@ -415,26 +417,55 @@ class PlotTab(QtWidgets.QWidget): self.view.stackedWidgetPlotType.show() self.mainWindow.read_timer.start(Defines.Time_Plot_Refresh_ms) - def setPixelMap(self): + def setDecoder(self): if self.view.comboBoxPlot.currentText() == "Matterhorn02": - print("Setting pixel map for Matterhorn02") - self.mainWindow.nTransceiverRows = Defines.Matterhorn02.nRows - self.mainWindow.nTransceiverCols = Defines.Matterhorn02.nCols - self.mainWindow.pixel_map = pm.matterhorn_transceiver() + print("Initializing decoder for Matterhorn02") + self.mainWindow.nTransceiverRows = Matterhorn02.nRows + self.mainWindow.nTransceiverCols = Matterhorn02.nCols + self.mainWindow.decoder = transform.Matterhorn02TransceiverTransform() elif self.view.comboBoxPlot.currentText() == "Matterhorn1_16bit_1_counter": - print("Setting pixel map for Matterhorn1") - self.mainWindow.nTransceiverRows = Defines.Matterhorn1.nRows - self.mainWindow.nTransceiverCols = Defines.Matterhorn1.nCols - self.mainWindow.pixel_map = pm.matterhorn1_transceiver_16bit_1_counter() + print("Initializing decoder for Matterhorn1 with 1 counter 16 bit dynamic range") + self.mainWindow.nTransceiverRows = Matterhorn10.nRows + self.mainWindow.nTransceiverCols = Matterhorn10.nCols + self.mainWindow.decoder = transform.Matterhorn10Transform(16, 1) elif self.view.comboBoxPlot.currentText() == "Matterhorn1_16bit_4_counters": - print("Setting pixel map for Matterhorn1 with 4 counters") - self.mainWindow.nTransceiverRows = Defines.Matterhorn1.nRows*4 - self.mainWindow.nTransceiverCols = Defines.Matterhorn1.nCols - self.mainWindow.pixel_map = pm.matterhorn1_transceiver_16bit_4_counters() + print("Initializing decoder for Matterhorn1 with 4 counters 16 bit dynamic range") + self.mainWindow.nTransceiverRows = Matterhorn10.nRows*4 + self.mainWindow.nTransceiverCols = Matterhorn10.nCols + self.mainWindow.decoder = transform.Matterhorn10Transform(16, 4) + elif self.view.comboBoxPlot.currentText() == "Matterhorn1_8bit_1_counter": + print("Initializing decoder for Matterhorn1 with 1 counter 8 bit dynamic range") + self.mainWindow.nTransceiverRows = Matterhorn10.nRows + self.mainWindow.nTransceiverCols = Matterhorn10.nCols + self.mainWindow.decoder = transform.Matterhorn10Transform(8, 1) + elif self.view.comboBoxPlot.currentText() == "Matterhorn1_8bit_4_counters": + print("Initializing decoder for Matterhorn1 with 4 counters 8 bit dynamic range") + self.mainWindow.nTransceiverRows = Matterhorn10.nRows*4 + self.mainWindow.nTransceiverCols = Matterhorn10.nCols + self.mainWindow.decoder = transform.Matterhorn10Transform(8, 4) + elif self.view.comboBoxPlot.currentText() == "Matterhorn1_4bit_4_counters": + print("Initializing decoder for Matterhorn1 with 4 counters 4 bit dynamic range") + self.mainWindow.nTransceiverRows = Matterhorn10.nRows*4 + self.mainWindow.nTransceiverCols = Matterhorn10.nCols + self.mainWindow.decoder = transform.Matterhorn10Transform(4, 4) + elif self.view.comboBoxPlot.currentText() == "Matterhorn1_4bit_1_counter": + print("Initializing decoder for Matterhorn1 with 1 counter 4 bit dynamic range") + self.mainWindow.nTransceiverRows = Matterhorn10.nRows + self.mainWindow.nTransceiverCols = Matterhorn10.nCols + self.mainWindow.decoder = transform.Matterhorn10Transform(4, 1) elif self.view.comboBoxPlot.currentText() == "Moench04": - self.mainWindow.nAnalogRows = Defines.Moench04.nRows - self.mainWindow.nAnalogCols = Defines.Moench04.nCols + self.mainWindow.nAnalogRows = Moench04.nRows + self.mainWindow.nAnalogCols = Moench04.nCols + self.mainWindow.decoder = transform.Moench04AnalogTransform() + try: + if hasattr(self.mainWindow.decoder, "compatibility") and callable(getattr(self.mainWindow.decoder, "compatibility")): + self.mainWindow.decoder.compatibility(ReadoutMode(self.mainWindow.romode.value)) + except Exception as e: + self.mainWindow.statusbar.setStyleSheet("color:red") + self.mainWindow.statusbar.showMessage(str(e)) + print("Error: ", str(e)) + def showPatternViewer(self, enable): if enable: diff --git a/pyctbgui/pyctbgui/services/Transceiver.py b/pyctbgui/pyctbgui/services/Transceiver.py index ecaf64282..5dd28753a 100644 --- a/pyctbgui/pyctbgui/services/Transceiver.py +++ b/pyctbgui/pyctbgui/services/Transceiver.py @@ -6,11 +6,9 @@ from PyQt5 import QtWidgets, uic import pyqtgraph as pg from pyqtgraph import LegendItem -from pyctbgui.utils import decoder from pyctbgui.utils.defines import Defines from pyctbgui.utils.bit_utils import bit_is_set, manipulate_bit -import pyctbgui.utils.pixelmap as pm from pyctbgui.utils.recordOrApplyPedestal import recordOrApplyPedestal @@ -132,8 +130,10 @@ class TransceiverTab(QtWidgets.QWidget): if dSamples % 8 != 0: nbitsPerDBit += (8 - (dSamples % 8)) transceiverOffset += nDBitEnabled * (nbitsPerDBit // 8) - trans_array = np.array(np.frombuffer(data, offset=transceiverOffset, dtype=np.uint16)) - tmp = np.take(trans_array, self.mainWindow.pixel_map) + trans_array = np.array(np.frombuffer(data, offset=transceiverOffset, dtype=np.uint8)) + + tmp = self.mainWindow.decoder(trans_array) + return tmp def processImageData(self, data, dSamples): @@ -151,14 +151,11 @@ class TransceiverTab(QtWidgets.QWidget): self.mainWindow.nDBitEnabled) self.plotTab.ignoreHistogramSignal = True self.mainWindow.plotTransceiverImage.setImage(self.mainWindow.transceiver_frame) - except Exception: + except Exception as e: self.mainWindow.statusbar.setStyleSheet("color:red") - message = f'Warning: Invalid size for Transceiver Image. Expected' \ - f' {self.mainWindow.nTransceiverRows * self.mainWindow.nTransceiverCols} size,' \ - f' got {self.mainWindow.transceiver_frame.size} instead.' self.acquisitionTab.updateCurrentFrame('Invalid Image') - self.mainWindow.statusbar.showMessage(message) - print(message) + self.mainWindow.statusbar.showMessage(str(e)) + print("Error: ", str(e)) self.plotTab.setFrameLimits(self.mainWindow.transceiver_frame) diff --git a/pyctbgui/pyctbgui/ui/plot.ui b/pyctbgui/pyctbgui/ui/plot.ui index edebb4548..848d48f16 100644 --- a/pyctbgui/pyctbgui/ui/plot.ui +++ b/pyctbgui/pyctbgui/ui/plot.ui @@ -178,6 +178,26 @@ Matterhorn1_16bit_1_counter + + + Matterhorn1_8bit_4_counters + + + + + Matterhorn1_8bit_1_counter + + + + + Matterhorn1_4bit_1_counter + + + + + Matterhorn1_4bit_4_counters + + Moench04 diff --git a/pyctbgui/pyctbgui/utils/decoder.py b/pyctbgui/pyctbgui/utils/decoder.py deleted file mode 100644 index ca254b175..000000000 --- a/pyctbgui/pyctbgui/utils/decoder.py +++ /dev/null @@ -1,52 +0,0 @@ -from pyctbgui.utils.defines import Defines -from pyctbgui._decoder import * #bring in the function from the compiled extension -import numpy as np -""" -Python implementation, keep as a reference. Change name and replace -with C version to swap it out in the GUI -""" - - -def moench04(analog_buffer): - nAnalogCols = Defines.Moench04.nCols - nAnalogRows = Defines.Moench04.nRows - adcNumbers = Defines.Moench04.adcNumbers - nPixelsPerSC = Defines.Moench04.nPixelsPerSuperColumn - scWidth = Defines.Moench04.superColumnWidth - - analog_frame = np.zeros((nAnalogCols, nAnalogRows), dtype=analog_buffer.dtype) - - for iPixel in range(nPixelsPerSC): - for iSC, iAdc in enumerate(adcNumbers): - col = ((iAdc % 16) * scWidth) + (iPixel % scWidth) - if iSC < 16: - row = 199 - int(iPixel / scWidth) - else: - row = 200 + int(iPixel / scWidth) - index_min = iPixel * 32 + iSC - pixel_value = analog_buffer[index_min] - analog_frame[row, col] = pixel_value - - return analog_frame - - -def matterhorn(trans_buffer): - nTransceiverRows = Defines.Matterhorn.nRows - nTransceiverCols = Defines.Matterhorn.nCols - - transceiver_frame = np.zeros((nTransceiverCols, nTransceiverRows), dtype=trans_buffer.dtype) - - offset = 0 - nSamples = Defines.Matterhorn.nPixelsPerTransceiver - for row in range(Defines.Matterhorn.nRows): - for col in range(Defines.Matterhorn.nHalfCols): - #print(f'row:{row} col:{col} offset: {offset}') - for iTrans in range(Defines.Matterhorn.nTransceivers): - transceiver_frame[iTrans * Defines.Matterhorn.nHalfCols + col, - row] = trans_buffer[offset + nSamples * iTrans] - offset += 1 - if (col + 1) % nSamples == 0: - offset += nSamples - - return transceiver_frame - diff --git a/pyctbgui/pyctbgui/utils/defines.py b/pyctbgui/pyctbgui/utils/defines.py index 413285185..2226b5c33 100644 --- a/pyctbgui/pyctbgui/utils/defines.py +++ b/pyctbgui/pyctbgui/utils/defines.py @@ -50,32 +50,6 @@ class Defines: Matterhorn = 0 Moench04 = 1 - class Matterhorn02: - nRows = 48 - nHalfCols = 24 - nCols = 48 - nTransceivers = 2 - tranceiverEnable = 0x3 - nPixelsPerTransceiver = 4 - - class Matterhorn1: - nRows = 256 - nHalfCols = 24 - nCols = 256 - nTransceivers = 2 - tranceiverEnable = 0x3 - nPixelsPerTransceiver = 4 - - class Moench04: - nRows = 400 - nCols = 400 - adcNumbers = [ - 9, 8, 11, 10, 13, 12, 15, 14, 1, 0, 3, 2, 5, 4, 7, 6, 23, 22, 21, 20, 19, 18, 17, 16, 31, 30, 29, 28, 27, - 26, 25, 24 - ] - nPixelsPerSuperColumn = 5000 - superColumnWidth = 25 - Color_map = [ 'viridis', 'plasma', 'inferno', 'magma', 'cividis', 'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink', 'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia', 'hot', 'afmhot', 'gist_heat', 'copper', diff --git a/pyctbgui/pyctbgui/utils/pixelmap.py b/pyctbgui/pyctbgui/utils/pixelmap.py deleted file mode 100644 index 72fbcc385..000000000 --- a/pyctbgui/pyctbgui/utils/pixelmap.py +++ /dev/null @@ -1,92 +0,0 @@ -import numpy as np -# generate pixelmaps for various CTB detectors - - -def moench03(): - out = np.zeros((400, 400), dtype=np.uint32) - adc_numbers = np.array( - (12, 13, 14, 15, 12, 13, 14, 15, 8, 9, 10, 11, 8, 9, 10, 11, 4, 5, 6, 7, 4, 5, 6, 7, 0, 1, 2, 3, 0, 1, 2, 3), - dtype=np.int_) - for n_pixel in range(5000): - for i_sc in range(32): - adc_nr = adc_numbers[i_sc] - col = ((adc_nr * 25) + (n_pixel % 25)) - row = 0 - if (i_sc // 4 % 2 == 0): - row = 199 - (n_pixel // 25) - else: - row = 200 + (n_pixel // 25) - - i_analog = n_pixel * 32 + i_sc - out[row, col] = i_analog - - return out - - -def moench04_analog(): - out = np.zeros((400, 400), dtype=np.uint32) - adc_numbers = np.array((9, 8, 11, 10, 13, 12, 15, 14, 1, 0, 3, 2, 5, 4, 7, 6, 23, 22, 21, 20, 19, 18, 17, 16, 31, - 30, 29, 28, 27, 26, 25, 24), - dtype=np.int_) - - for n_pixel in range(5000): - for i_sc in range(32): - adc_nr = adc_numbers[i_sc] - col = ((adc_nr % 16) * 25) + (n_pixel % 25) - row = 0 - if i_sc < 16: - row = 199 - (n_pixel // 25) - else: - row = 200 + (n_pixel // 25) - - i_analog = n_pixel * 32 + i_sc - out[row, col] = i_analog - - return out - - -def matterhorn_transceiver(): - out = np.zeros((48, 48), dtype=np.uint32) - - offset = 0 - nSamples = 4 - for row in range(48): - for col in range(24): - for iTrans in range(2): - out[iTrans * 24 + col, row] = offset + nSamples * iTrans - offset += 1 - if (col + 1) % nSamples == 0: - offset += nSamples - - return out - - -def matterhorn1_transceiver_16bit_1_counter(): - pixel_map = np.zeros((256,256), np.uint32) - n_cols = 256 - n_rows = 256 - for row in range(n_rows): - col = 0 - for offset in range(0,64,4): - for pkg in range(offset,256,64): - for pixel in range(4): - pixel_map[row, col] = pixel+pkg+row*n_cols - col += 1 - - return pixel_map - -def matterhorn1_transceiver_16bit_4_counters(): - n_counters = 4 - n_cols = 256 - n_rows = 256 - pixel_map = np.zeros((n_rows*n_counters,n_cols), np.uint32) - - for row in range(n_rows): - for counter in range(n_counters): - col = 0 - for offset in range(0,64,4): - for pkg in range(offset,256,64): - for pixel in range(4): - pixel_map[row+n_rows*counter, col] = pixel+pkg+row*n_cols*n_counters+n_cols*counter - col += 1 - return pixel_map \ No newline at end of file diff --git a/pyctbgui/pyproject.toml b/pyctbgui/pyproject.toml index 08de6be1c..40222d1b4 100644 --- a/pyctbgui/pyproject.toml +++ b/pyctbgui/pyproject.toml @@ -2,6 +2,41 @@ requires = ["setuptools", "numpy"] build-backend = "setuptools.build_meta" +[project] +name = "pyctbgui" +description = "Experimental GUI for the chip test board" +version = "0.0.0" +requires-python = ">=3.10" +dependencies = [ + "numpy", + "pyzmq", + "pillow", + "PyQt5", + "pyqtgraph", + "matplotlib", + "aare", + "slsdet", +] + +[project.optional-dependencies] +dev = [ + "pytest==7.4.0", + "ruff==0.0.285", + "yapf==0.40.1", + "pytest-ruff==0.1.1", + "pre-commit", + "pytest-qt", +] + +[tool.setuptools] +package-dir = {"" = "pyctbgui"} + +[tool.setuptools.packages.find] +where = ["pyctbgui"] + +[project.scripts] +CtbGui = "pyctbgui.main:main" + [tool.ruff] line-length = 119 select = ["E","F","UP","PT","RET","SLF","TID","PTH","NPY"] @@ -38,9 +73,7 @@ exclude = [ target-version = "py311" [tool.ruff.per-file-ignores] -"__init__.py" = ["F401"] -"pyctbgui/utils/decoder.py" = ["F403"] - +"__init__.py" = ["F401"] # ignore unused import warnings [tool.yapf] based_on_style = "pep8" @@ -53,3 +86,4 @@ branch = false include = [ "pyctbgui/*" ] + diff --git a/pyctbgui/setup.py b/pyctbgui/setup.py deleted file mode 100644 index 1bb1e3848..000000000 --- a/pyctbgui/setup.py +++ /dev/null @@ -1,44 +0,0 @@ -import setuptools -import numpy as np - -c_ext = setuptools.Extension("pyctbgui._decoder", - sources=["src/decoder.c", "src/pm_decode.c"], - include_dirs=[ - np.get_include(), - "src/", - ], - extra_compile_args=['-std=c99', '-Wall', '-Wextra']) - -c_ext.language = 'c' -setuptools.setup( - name='pyctbgui', - version='2023.8.17', - description='Experimental GUI for the chip test board', - packages=setuptools.find_packages(exclude=[ - 'tests', - ]), - include_package_data=True, - ext_modules=[c_ext], - scripts=[ - 'CtbGui', - ], - python_requires='>=3.10', # using match statement - install_requires=[ - 'numpy', - 'pyzmq', - 'pillow', - 'PyQt5', - 'pyqtgraph', - 'matplotlib', - # 'slsdet', not yet available on pypi, maybe v8 - ], - extras_require={ - 'dev': [ - 'pytest==7.4.0', - 'ruff==0.0.285', - 'yapf==0.40.1', - 'pytest-ruff==0.1.1', - 'pre-commit', - 'pytest-qt', - ], - }) diff --git a/pyctbgui/src/decoder.c b/pyctbgui/src/decoder.c deleted file mode 100644 index 2cbb6909b..000000000 --- a/pyctbgui/src/decoder.c +++ /dev/null @@ -1,177 +0,0 @@ -#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION -#define PY_SSIZE_T_CLEAN -#include -#include - -#include -#include - -#include "pm_decode.h" -#include "thread_utils.h" - -/*Decode various types of CTB data using a pixel map. Works on single frames and -on stacks of frames*/ -static PyObject *decode(PyObject *Py_UNUSED(self), PyObject *args, - PyObject *kwds) { - // Function arguments to be parsed - PyObject *raw_data_obj = NULL; - PyObject *data_obj = NULL; - PyObject *pm_obj = NULL; - size_t n_threads = 1; - - static char *kwlist[] = {"raw_data", "pixel_map", "out", "n_threads", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|On", kwlist, &raw_data_obj, - &pm_obj, &data_obj, &n_threads)) { - return NULL; - } - - // Create a handle to the numpy array from the generic python object - PyObject *raw_data = PyArray_FROM_OTF( - raw_data_obj, NPY_UINT16, - NPY_ARRAY_C_CONTIGUOUS | NPY_ARRAY_ENSUREARRAY | NPY_ARRAY_ALIGNED); - if (!raw_data) { - return NULL; - } - - // Handle to the pixel map - PyObject *pixel_map = PyArray_FROM_OTF( - pm_obj, NPY_UINT32, NPY_ARRAY_C_CONTIGUOUS); // Make 64bit? - if (!pixel_map) { - return NULL; - } - if (PyArray_NDIM((PyArrayObject *)pixel_map) != 2) { - PyErr_SetString(PyExc_TypeError, "The pixel map needs to be 2D"); - return NULL; - } - npy_intp n_rows = PyArray_DIM((PyArrayObject *)pixel_map, 0); - npy_intp n_cols = PyArray_DIM((PyArrayObject *)pixel_map, 1); - - // If called with an output array get an handle to it, otherwise allocate - // the output array - PyObject *data_out = NULL; - if (data_obj) { - data_out = - PyArray_FROM_OTF(data_obj, NPY_UINT16, NPY_ARRAY_C_CONTIGUOUS); - } else { - int ndim = PyArray_NDIM((PyArrayObject *)raw_data) + 1; - npy_intp dims_arr[3] = {PyArray_DIM((PyArrayObject *)raw_data, 0), - n_rows, n_cols}; - npy_intp *dims = NULL; - if (ndim == 2) - dims = &dims_arr[1]; - else - dims = &dims_arr[0]; - // Allocate output array - data_out = PyArray_SimpleNew(ndim, dims, NPY_UINT16); - } - - // Check that raw_data has one less dimension than frame - // eg raw_data[n_frames, pixels] frame[nframes, nrows, ncols] - // raw data is an array of values and data_out is 2/3D - int rd_dim = PyArray_NDIM((PyArrayObject *)raw_data); - int f_dim = PyArray_NDIM((PyArrayObject *)data_out); - - if (rd_dim != (f_dim - 1)) { - PyErr_SetString( - PyExc_TypeError, - "Raw data and data needs to have the one less dim"); // eg -1 - return NULL; - } - - uint16_t *src = (uint16_t *)PyArray_DATA((PyArrayObject *)raw_data); - uint16_t *dst = (uint16_t *)PyArray_DATA((PyArrayObject *)data_out); - uint32_t *pm = (uint32_t *)PyArray_DATA((PyArrayObject *)pixel_map); - - // Check sizes - npy_intp rd_size = PyArray_SIZE((PyArrayObject *)raw_data); - npy_intp fr_size = PyArray_SIZE((PyArrayObject *)data_out); - npy_intp pm_size = PyArray_SIZE((PyArrayObject *)pixel_map); - - // TODO! Add exceptions - if (rd_size != fr_size) { - PyErr_SetString(PyExc_TypeError, - "Raw data size and data size needs to match"); - return NULL; - } - - int64_t n_frames = 1; - if (rd_dim == 2) - n_frames = PyArray_DIM((PyArrayObject *)raw_data, 0); - // printf("n_frames: %lu\n", n_frames); - - // do the correct size check - if (rd_size / n_frames != pm_size) { - PyErr_SetString(PyExc_TypeError, - "Pixel map size needs to match with frame size"); - return NULL; - } - - if (n_threads == 1) { - pm_decode(src, dst, pm, n_frames, n_rows * n_cols); - } else { - // Multithreaded processing - pthread_t *threads = malloc(sizeof(pthread_t *) * n_threads); - thread_args *arguments = malloc(sizeof(thread_args) * n_threads); - - size_t frames_per_thread = n_frames / n_threads; - size_t assigned_frames = 0; - for (size_t i = 0; i < n_threads; i++) { - arguments[i].src = src + (i * frames_per_thread * pm_size); - arguments[i].dst = dst + (i * frames_per_thread * pm_size); - arguments[i].pm = pm; - arguments[i].n_frames = - frames_per_thread; // TODO! not matching frames. - arguments[i].n_pixels = n_rows * n_cols; - assigned_frames += frames_per_thread; - } - arguments[n_threads - 1].n_frames += n_frames - assigned_frames; - - for (size_t i = 0; i < n_threads; i++) { - pthread_create(&threads[i], NULL, (void *)thread_pmdecode, - &arguments[i]); - } - for (size_t i = 0; i < n_threads; i++) { - pthread_join(threads[i], NULL); - } - free(threads); - free(arguments); - } - - Py_DECREF(raw_data); - Py_DECREF(pixel_map); - - return data_out; -} - -// Module docstring, shown as a part of help(creader) -static char module_docstring[] = "C functions decode CTB data"; - -// Module methods -static PyMethodDef creader_methods[] = { - {"decode", (PyCFunction)(void (*)(void))decode, - METH_VARARGS | METH_KEYWORDS, "Decode analog data using a pixel map"}, - {NULL, NULL, 0, NULL} /* Sentinel */ -}; - -// Module defenition -static struct PyModuleDef decoder_def = { - PyModuleDef_HEAD_INIT, - "_decoder", - module_docstring, - -1, - creader_methods, // m_methods - NULL, // m_slots - NULL, // m_traverse - NULL, // m_clear - NULL // m_free -}; - -// Initialize module and add classes -PyMODINIT_FUNC PyInit__decoder(void) { - - PyObject *m = PyModule_Create(&decoder_def); - if (m == NULL) - return NULL; - import_array(); // Needed for numpy - return m; -} diff --git a/pyctbgui/src/pm_decode.c b/pyctbgui/src/pm_decode.c deleted file mode 100644 index fc702b22b..000000000 --- a/pyctbgui/src/pm_decode.c +++ /dev/null @@ -1,18 +0,0 @@ -#include "pm_decode.h" -#include "thread_utils.h" - -void thread_pmdecode(void *args) { - thread_args *a; - a = (thread_args *)args; - pm_decode(a->src, a->dst, a->pm, a->n_frames, a->n_pixels); -} - -void pm_decode(uint16_t *src, uint16_t *dst, uint32_t *pm, size_t n_frames, - size_t n_pixels) { - for (size_t i = 0; i < n_frames; i++) { - for (size_t j = 0; j < n_pixels; j++) { - *dst++ = src[pm[j]]; - } - src += n_pixels; - } -} \ No newline at end of file diff --git a/pyctbgui/src/pm_decode.h b/pyctbgui/src/pm_decode.h deleted file mode 100644 index 325e4d46b..000000000 --- a/pyctbgui/src/pm_decode.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include -#include -// Wrapper to be used with pthreads -void thread_pmdecode(void *args); - -void pm_decode(uint16_t *src, uint16_t *dst, uint32_t *pm, size_t n_frames, - size_t n_pixels); \ No newline at end of file diff --git a/pyctbgui/src/thread_utils.h b/pyctbgui/src/thread_utils.h deleted file mode 100644 index 6cc3c2381..000000000 --- a/pyctbgui/src/thread_utils.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once -#include -#include -typedef struct { - uint16_t *src; - uint16_t *dst; - uint32_t *pm; - size_t n_frames; - size_t n_pixels; -} thread_args; \ No newline at end of file diff --git a/pyctbgui/tests/unit/test_decoder.py b/pyctbgui/tests/unit/test_decoder.py deleted file mode 100644 index 21c8b69bf..000000000 --- a/pyctbgui/tests/unit/test_decoder.py +++ /dev/null @@ -1,42 +0,0 @@ -import numpy as np -import sys - -from pyctbgui.utils import decoder -from pyctbgui.utils.pixelmap import moench04_analog, matterhorn_transceiver - - -def test_simple_decode(): - pixel_map = np.zeros((2, 2), dtype=np.uint32) - pixel_map.flat = [0, 1, 2, 3] - - raw_data = np.zeros(4, dtype=np.uint16) - raw_data.flat = [8, 9, 10, 11] - data = decoder.decode(raw_data, pixel_map) - - assert data[0, 0] == 8 - assert data[0, 1] == 9 - assert data[1, 0] == 10 - assert data[1, 1] == 11 - - # Make sure we didn't mess up the reference counts - assert sys.getrefcount(data) == 2 - assert sys.getrefcount(raw_data) == 2 - assert sys.getrefcount(pixel_map) == 2 - - -def test_compare_python_and_c_decoding(): - """ - Check that the python and C implementation give the same results - provides a regression test in case we change the C implementation - """ - pixel_map = moench04_analog() - raw_data = np.arange(400 * 400, dtype=np.uint16) - c_data = decoder.decode(raw_data, pixel_map) - py_data = decoder.moench04(raw_data) - assert (c_data == py_data).all() - - pixel_map = matterhorn_transceiver() - raw_data = np.arange(48 * 48, dtype=np.uint16) - c_data = decoder.decode(raw_data, pixel_map) - py_data = decoder.matterhorn(raw_data) - assert (c_data == py_data).all()