Dev/use aare in pyctbgui (#1379)
Build on RHEL9 / build (push) Successful in 3m57s
Build on RHEL8 / build (push) Successful in 4m43s
Run Simulator Tests on local RHEL9 / build (push) Failing after 3m50s
Build on local RHEL9 / build (push) Failing after 1m24s
Run Simulator Tests on local RHEL8 / build (push) Failing after 5m28s
Build on local RHEL8 / build (push) Failing after 3m32s

* added CMakeLists.txt for pyctbgui

* using aare decoders

* removed c code

* showing proper error message

* dvjgj

---------

Co-authored-by: Erik Fröjdh <erik.frojdh@gmail.com>
This commit is contained in:
2026-02-23 12:33:10 +01:00
committed by GitHub
parent 65c8f2c7d8
commit f8723eb0d8
17 changed files with 146 additions and 557 deletions
-4
View File
@@ -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
-39
View File
@@ -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}'
+31 -10
View File
@@ -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
```
+1 -3
View File
@@ -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:
+50 -19
View File
@@ -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:
+7 -10
View File
@@ -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)
+20
View File
@@ -178,6 +178,26 @@
<string>Matterhorn1_16bit_1_counter</string>
</property>
</item>
<item>
<property name="text">
<string>Matterhorn1_8bit_4_counters</string>
</property>
</item>
<item>
<property name="text">
<string>Matterhorn1_8bit_1_counter</string>
</property>
</item>
<item>
<property name="text">
<string>Matterhorn1_4bit_1_counter</string>
</property>
</item>
<item>
<property name="text">
<string>Matterhorn1_4bit_4_counters</string>
</property>
</item>
<item>
<property name="text">
<string>Moench04</string>
-52
View File
@@ -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
-26
View File
@@ -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',
-92
View File
@@ -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
+37 -3
View File
@@ -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/*"
]
-44
View File
@@ -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',
],
})
-177
View File
@@ -1,177 +0,0 @@
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <numpy/arrayobject.h>
#include <pthread.h>
#include <stdbool.h>
#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;
}
-18
View File
@@ -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;
}
}
-8
View File
@@ -1,8 +0,0 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
// 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);
-10
View File
@@ -1,10 +0,0 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
typedef struct {
uint16_t *src;
uint16_t *dst;
uint32_t *pm;
size_t n_frames;
size_t n_pixels;
} thread_args;
-42
View File
@@ -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()