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()