mirror of
https://github.com/slsdetectorgroup/slsDetectorPackage.git
synced 2026-06-04 14:48:41 +02:00
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
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:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
```
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
@@ -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/*"
|
||||
]
|
||||
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
})
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user