From 5b61ff24bbfd4be6416b99b33c28b2ead7b58875 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Tue, 10 Sep 2024 16:00:04 +0200 Subject: [PATCH] Dev/pyctbgui merge (#960) * added empty c extension * added rotation to the decoding * added color map, options and findex * minor * move checks to before acquisition * added pixel map based decoder * cleanup * no thread creation for single thread processing * added rotation and test to compare * allow high and low water mark for zmq (also buffer size) for fast readouts * removed roatation during decoding * added Transpose to image and invert Y False to invert it * retains the zoomed state after the first image of gui, catch and display exception if no detector connected * moved start frame to dockable widget, removed extra frame number label, moved current measurement also to dockable widget, hide frame plot entirely when showing patternviewer * first image dependin on which plot * remember settings of main window size and position, dockewidget if docked, its size and posisiotn as well, then update it next time the gui is opened * change in comment * using c decoder for moench 04 and matterhorn * catch exception from invalid image from decoder * clean up * update row and col when choosing image type, neeeded to show values * fix for previous PR * disable resetting colormap values keep the range selected for every new acquisition * fix typos + tested on virtual matterhorn * minor print * refactored Slow ADCs Tab * refactored DAC tab * refactored power supplies * refactored signals tab * refactored transceiver tab * fix typo * fix typo2 * remove commented code * delete commented code * delete commented code * delete commented signals code * remove commented code for transceiver tab * refactor adc tab * refactor Pattern Tab * Refactor transceivers tab (PR#5) (#118) * refactored transceiver tab * remove commented code for transceiver tab --------- Co-authored-by: Erik Frojdh * refactor adc tab (PR#6) (#119) * refactor adc tab * refactored Plot and Acquisition Tabs * fix the regression issue * restructure project files * applying singleton and renaming tabs to services * working install using pip * applies singleton to tab classes and remove CI erros * added pyzmq and pillow * remove the singleton implementation and keep changes * fix merge errors in mainWindow * moved misplaced init file * rename service to tab * reorganize imports * iterate over tabs * reorder tabs * add slowadc to the list * saving changes (buggy) power supply ui not showing in the gui * split power supply tab * fixed tests * add hardcoded values to defines file * fix error * separate power supply * fix errors for powerSuppliesTab * split dacs * split slow adcs * split signals tab * added tests for bit_utils * add slowAdc class to defines * split transceiver ui file * split adc.ui * split pattern ui file * split plot and acquisition ui file * added basic test for parsing bit names * removed redundant code in read_alias_file * fix dacs ui position * testing for correct exception * cmd and args at split * group radio buttons * fix comments from PR#1 * show legend * added python version and dev requirements to setup.py * fix dac issue * moved _decoder into pkg * added inplace build * removed clear * fixed dependencies * make tests run without slsdet * updated name of action * define colcount * fixed loading of alias file * add yapf and ruff * apply formatting * fix E and F rules * add more ruff rules * change variable name * squashing gh debugging commits and add pre-commit * update label values to mv units * add hook for yapf * reconfigure yapf precommit hook * add format and check_format to makefile * change gh actions * update readme * added check_format * WIP * added linting in github action * updated readme] * add more control for color choice * remove useless file * fix un-updated line after refactoring Defines BIT0_31_MASK is not found in Defines.signals * visually improve the interface * fix last commit * add only selected plots for legend * add hide legend button * change hide legend to show legend checkbox show legend is checked by default * add support for saving in numpy * solve conversations * fix acq index offset * fix browse button in pattern error * fix other browse button errors * finish tests and add usage.md * remove buffer * add file,numpy-like interface and tests * remove useless .npy files * subscriptible npz files * remove useless files * remove repetetive tests * save changes * add mode r+, add with support,remove logging * remove offset of acqIndex between raw and numpy saving * fix only saving last frame * save signals of multiple devices * add comments and move condition for clearer code * fix bug when vieweing pattern file * iterate over enabled and plotted plots * add padestal substraction to transceiver and analog data * init pedestal frames to detector.frames * restore old exception * add pedestal substraction for digital signals * remove frames spinbox from plotTab * remove comments and use str instead of Path * avoid saving all frames * correct exception and log error's trace * add gui tests * add waveform test * add pedestal test * refactor by using fixtures * add tests for moench analog and pattern * add pytest-qt to dependencies * add save and load gui parameters * remove nohup file * fix old bug IndexError * save plot type * a * handle canceling load, loading matterhorn pedestal for moench * remove comparing .png files for pattern test * save plot type * red error on status bar when shape mismatch for loaded pedestal * fix makefile and docstrings * fix PRs conversation * move code into different function * fix wrong function names for power supply * removed old ctbgui * removed unnecessary files --------- Co-authored-by: Erik Frojdh Co-authored-by: Braham Bechir Co-authored-by: Bechir Co-authored-by: Bechir --- CMakeLists.txt | 3 +- ctbGui/CMakeLists.txt | 90 - ctbGui/Makefile.root5 | 46 - ctbGui/Makefile.root6 | 48 - ctbGui/ctbAcquisition.cpp | 2277 ------ ctbGui/ctbAcquisition.h | 248 - ctbGui/ctbAdcs.cpp | 616 -- ctbGui/ctbAdcs.h | 157 - ctbGui/ctbDacs.cpp | 235 - ctbGui/ctbDacs.h | 77 - ctbGui/ctbDefs.h | 86 - ctbGui/ctbGui.cpp | 159 - ctbGui/ctbLinkDef.h | 17 - ctbGui/ctbMain.cpp | 588 -- ctbGui/ctbMain.h | 130 - ctbGui/ctbPattern.cpp | 1111 --- ctbGui/ctbPattern.h | 181 - ctbGui/ctbPowers.cpp | 225 - ctbGui/ctbPowers.h | 69 - ctbGui/ctbSignals.cpp | 543 -- ctbGui/ctbSignals.h | 123 - ctbGui/ctbSlowAdcs.cpp | 184 - ctbGui/ctbSlowAdcs.h | 80 - .../workflows/build-and-test-inplace.yml | 35 + pyctbgui/.gitignore | 133 + pyctbgui/.pre-commit-config.yaml | 18 + pyctbgui/CtbGui | 11 + pyctbgui/MANIFEST.in | 2 + pyctbgui/Makefile | 39 + pyctbgui/README.md | 31 + pyctbgui/client.py | 73 + pyctbgui/pyctbgui/__init__.py | 2 + pyctbgui/pyctbgui/services/ADC.py | 401 ++ pyctbgui/pyctbgui/services/Acquisition.py | 719 ++ pyctbgui/pyctbgui/services/DACs.py | 170 + pyctbgui/pyctbgui/services/Pattern.py | 456 ++ pyctbgui/pyctbgui/services/Plot.py | 519 ++ pyctbgui/pyctbgui/services/PowerSupplies.py | 123 + pyctbgui/pyctbgui/services/Signals.py | 373 + pyctbgui/pyctbgui/services/SlowADCs.py | 45 + pyctbgui/pyctbgui/services/Transceiver.py | 273 + pyctbgui/pyctbgui/services/__init__.py | 9 + pyctbgui/pyctbgui/ui/CtbGui.ui | 779 +++ pyctbgui/pyctbgui/ui/Dacs.ui | 1198 ++++ pyctbgui/pyctbgui/ui/MainWindow.py | 369 + pyctbgui/pyctbgui/ui/__init__.py | 1 + pyctbgui/pyctbgui/ui/acquisition.ui | 927 +++ pyctbgui/pyctbgui/ui/adc.ui | 4212 +++++++++++ pyctbgui/pyctbgui/ui/pattern.ui | 2733 ++++++++ pyctbgui/pyctbgui/ui/plot.ui | 1055 +++ pyctbgui/pyctbgui/ui/powerSupplies.ui | 483 ++ pyctbgui/pyctbgui/ui/signals.ui | 6189 +++++++++++++++++ pyctbgui/pyctbgui/ui/slowAdcs.ui | 472 ++ pyctbgui/pyctbgui/ui/transceiver.ui | 542 ++ pyctbgui/pyctbgui/utils/__init__.py | 0 pyctbgui/pyctbgui/utils/alias_utility.py | 101 + pyctbgui/pyctbgui/utils/bit_utils.py | 16 + pyctbgui/pyctbgui/utils/decoder.py | 51 + pyctbgui/pyctbgui/utils/defines.py | 111 + .../pyctbgui/utils/numpyWriter/__init__.py | 0 .../pyctbgui/utils/numpyWriter/npy_writer.py | 224 + .../pyctbgui/utils/numpyWriter/npz_writer.py | 91 + pyctbgui/pyctbgui/utils/numpyWriter/usage.md | 71 + pyctbgui/pyctbgui/utils/pixelmap.py | 61 + pyctbgui/pyctbgui/utils/plotPattern.py | 726 ++ .../pyctbgui/utils/recordOrApplyPedestal.py | 100 + pyctbgui/pyproject.toml | 55 + pyctbgui/setup.py | 44 + pyctbgui/src/decoder.c | 179 + pyctbgui/src/pm_decode.c | 17 + pyctbgui/src/pm_decode.h | 8 + pyctbgui/src/thread_utils.h | 10 + pyctbgui/tests/gui/__init__.py | 0 pyctbgui/tests/gui/conftest.py | 11 + .../gui/data/matterhorm_image_transceiver.npy | Bin 0 -> 4736 bytes .../matterhorn_waveform_transceiver1and2.npz | Bin 0 -> 5106 bytes .../tests/gui/data/moench04_image_analog.npy | Bin 0 -> 1280128 bytes .../tests/gui/data/moench04_waveform_adc.npz | Bin 0 -> 327106 bytes pyctbgui/tests/gui/data/pattern.pat | 232 + pyctbgui/tests/gui/data/pattern.png | Bin 0 -> 47247 bytes pyctbgui/tests/gui/data/simulator.config | 9 + pyctbgui/tests/gui/test_analog_moench.py | 57 + pyctbgui/tests/gui/test_pattern.py | 27 + .../tests/gui/test_pedestal_matterhorn.py | 34 + pyctbgui/tests/gui/test_signal_matterhorn.py | 28 + .../tests/gui/test_transceiver_matterhorn.py | 50 + pyctbgui/tests/gui/utils.py | 78 + .../unit/data/moench04_start_trigger.alias | 129 + pyctbgui/tests/unit/test_alias_file.py | 63 + pyctbgui/tests/unit/test_bit_utils.py | 27 + pyctbgui/tests/unit/test_decoder.py | 42 + pyctbgui/tests/unit/test_npy_writer.py | 160 + pyctbgui/tests/unit/test_npz_writer.py | 194 + 93 files changed, 25399 insertions(+), 7292 deletions(-) delete mode 100644 ctbGui/CMakeLists.txt delete mode 100644 ctbGui/Makefile.root5 delete mode 100644 ctbGui/Makefile.root6 delete mode 100644 ctbGui/ctbAcquisition.cpp delete mode 100644 ctbGui/ctbAcquisition.h delete mode 100644 ctbGui/ctbAdcs.cpp delete mode 100644 ctbGui/ctbAdcs.h delete mode 100644 ctbGui/ctbDacs.cpp delete mode 100644 ctbGui/ctbDacs.h delete mode 100644 ctbGui/ctbDefs.h delete mode 100644 ctbGui/ctbGui.cpp delete mode 100644 ctbGui/ctbLinkDef.h delete mode 100644 ctbGui/ctbMain.cpp delete mode 100644 ctbGui/ctbMain.h delete mode 100644 ctbGui/ctbPattern.cpp delete mode 100644 ctbGui/ctbPattern.h delete mode 100644 ctbGui/ctbPowers.cpp delete mode 100644 ctbGui/ctbPowers.h delete mode 100644 ctbGui/ctbSignals.cpp delete mode 100644 ctbGui/ctbSignals.h delete mode 100644 ctbGui/ctbSlowAdcs.cpp delete mode 100644 ctbGui/ctbSlowAdcs.h create mode 100644 pyctbgui/.github/workflows/build-and-test-inplace.yml create mode 100644 pyctbgui/.gitignore create mode 100644 pyctbgui/.pre-commit-config.yaml create mode 100755 pyctbgui/CtbGui create mode 100644 pyctbgui/MANIFEST.in create mode 100644 pyctbgui/Makefile create mode 100644 pyctbgui/README.md create mode 100644 pyctbgui/client.py create mode 100644 pyctbgui/pyctbgui/__init__.py create mode 100644 pyctbgui/pyctbgui/services/ADC.py create mode 100644 pyctbgui/pyctbgui/services/Acquisition.py create mode 100644 pyctbgui/pyctbgui/services/DACs.py create mode 100644 pyctbgui/pyctbgui/services/Pattern.py create mode 100644 pyctbgui/pyctbgui/services/Plot.py create mode 100644 pyctbgui/pyctbgui/services/PowerSupplies.py create mode 100644 pyctbgui/pyctbgui/services/Signals.py create mode 100644 pyctbgui/pyctbgui/services/SlowADCs.py create mode 100644 pyctbgui/pyctbgui/services/Transceiver.py create mode 100644 pyctbgui/pyctbgui/services/__init__.py create mode 100644 pyctbgui/pyctbgui/ui/CtbGui.ui create mode 100644 pyctbgui/pyctbgui/ui/Dacs.ui create mode 100644 pyctbgui/pyctbgui/ui/MainWindow.py create mode 100644 pyctbgui/pyctbgui/ui/__init__.py create mode 100644 pyctbgui/pyctbgui/ui/acquisition.ui create mode 100644 pyctbgui/pyctbgui/ui/adc.ui create mode 100644 pyctbgui/pyctbgui/ui/pattern.ui create mode 100644 pyctbgui/pyctbgui/ui/plot.ui create mode 100644 pyctbgui/pyctbgui/ui/powerSupplies.ui create mode 100644 pyctbgui/pyctbgui/ui/signals.ui create mode 100644 pyctbgui/pyctbgui/ui/slowAdcs.ui create mode 100644 pyctbgui/pyctbgui/ui/transceiver.ui create mode 100644 pyctbgui/pyctbgui/utils/__init__.py create mode 100644 pyctbgui/pyctbgui/utils/alias_utility.py create mode 100644 pyctbgui/pyctbgui/utils/bit_utils.py create mode 100644 pyctbgui/pyctbgui/utils/decoder.py create mode 100644 pyctbgui/pyctbgui/utils/defines.py create mode 100644 pyctbgui/pyctbgui/utils/numpyWriter/__init__.py create mode 100644 pyctbgui/pyctbgui/utils/numpyWriter/npy_writer.py create mode 100644 pyctbgui/pyctbgui/utils/numpyWriter/npz_writer.py create mode 100644 pyctbgui/pyctbgui/utils/numpyWriter/usage.md create mode 100644 pyctbgui/pyctbgui/utils/pixelmap.py create mode 100755 pyctbgui/pyctbgui/utils/plotPattern.py create mode 100644 pyctbgui/pyctbgui/utils/recordOrApplyPedestal.py create mode 100644 pyctbgui/pyproject.toml create mode 100644 pyctbgui/setup.py create mode 100644 pyctbgui/src/decoder.c create mode 100644 pyctbgui/src/pm_decode.c create mode 100644 pyctbgui/src/pm_decode.h create mode 100644 pyctbgui/src/thread_utils.h create mode 100644 pyctbgui/tests/gui/__init__.py create mode 100644 pyctbgui/tests/gui/conftest.py create mode 100644 pyctbgui/tests/gui/data/matterhorm_image_transceiver.npy create mode 100644 pyctbgui/tests/gui/data/matterhorn_waveform_transceiver1and2.npz create mode 100644 pyctbgui/tests/gui/data/moench04_image_analog.npy create mode 100644 pyctbgui/tests/gui/data/moench04_waveform_adc.npz create mode 100644 pyctbgui/tests/gui/data/pattern.pat create mode 100644 pyctbgui/tests/gui/data/pattern.png create mode 100644 pyctbgui/tests/gui/data/simulator.config create mode 100644 pyctbgui/tests/gui/test_analog_moench.py create mode 100644 pyctbgui/tests/gui/test_pattern.py create mode 100644 pyctbgui/tests/gui/test_pedestal_matterhorn.py create mode 100644 pyctbgui/tests/gui/test_signal_matterhorn.py create mode 100644 pyctbgui/tests/gui/test_transceiver_matterhorn.py create mode 100644 pyctbgui/tests/gui/utils.py create mode 100755 pyctbgui/tests/unit/data/moench04_start_trigger.alias create mode 100644 pyctbgui/tests/unit/test_alias_file.py create mode 100644 pyctbgui/tests/unit/test_bit_utils.py create mode 100644 pyctbgui/tests/unit/test_decoder.py create mode 100644 pyctbgui/tests/unit/test_npy_writer.py create mode 100644 pyctbgui/tests/unit/test_npz_writer.py diff --git a/CMakeLists.txt b/CMakeLists.txt index d7d325436..6124fd40f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,7 +148,6 @@ set(SLS_INTERNAL_QWT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libs/qwt-6.1.5) set(ClangFormat_EXCLUDE_PATTERNS "build/" "libs/" "slsDetectorCalibration/" - "ctbGui/" "manual/" "python/" "sample/" @@ -318,7 +317,7 @@ if (SLS_USE_PYTHON) endif(SLS_USE_PYTHON) if (SLS_USE_CTBGUI) - add_subdirectory(ctbGui) + add_subdirectory(pyctbgui) endif(SLS_USE_CTBGUI) configure_file( .clang-tidy diff --git a/ctbGui/CMakeLists.txt b/ctbGui/CMakeLists.txt deleted file mode 100644 index 1799bddc5..000000000 --- a/ctbGui/CMakeLists.txt +++ /dev/null @@ -1,90 +0,0 @@ -# SPDX-License-Identifier: LGPL-3.0-or-other -# Copyright (C) 2021 Contributors to the SLS Detector Package - - -find_package(ROOT CONFIG REQUIRED COMPONENTS Core Gui) -find_package(TIFF REQUIRED) - -target_include_directories(ROOT::Core INTERFACE "${ROOT_INCLUDE_DIRS}") -add_library(ROOT::Flags_CXX IMPORTED INTERFACE) -separate_arguments(ROOT_CXX_FLAGS) -target_compile_options(ROOT::Flags_CXX INTERFACE ${ROOT_CXX_FLAGS}) -separate_arguments(ROOT_DEFINITIONS) -target_compile_definitions(ROOT::Flags_CXX INTERFACE ${ROOT_DEFINITIONS}) - -# This fixes a bug in the linker flags -string(REPLACE "-L " "-L" ROOT_EXE_LINKER_FLAGS "${ROOT_EXE_LINKER_FLAGS}") -separate_arguments(ROOT_EXE_LINKER_FLAGS) - -# Stuck into using old property method due to separate -L and -l arguments -# (A full path to -l is better!) -set_property(TARGET ROOT::Flags_CXX PROPERTY - INTERFACE_LINK_LIBRARIES ${ROOT_EXE_LINKER_FLAGS}) -set_property(TARGET ROOT::Core PROPERTY - INTERFACE_INCLUDE_DIRECTORIES "${ROOT_INCLUDE_DIRS}") - - -add_executable(ctbGui - ctbGui.cpp - ctbMain.cpp - ctbDacs.cpp - ctbPowers.cpp - ctbSlowAdcs.cpp - ctbSignals.cpp - ctbAdcs.cpp - ctbPattern.cpp - ctbAcquisition.cpp - ${CMAKE_SOURCE_DIR}/slsDetectorCalibration/tiffio/src/tiffIO.cpp -) - - -#TODO! Replace with target -target_include_directories(ctbGui PRIVATE - ${CMAKE_SOURCE_DIR}/slsDetectorCalibration/dataStructures - ${CMAKE_SOURCE_DIR}/slsDetectorCalibration/interpolations - ${CMAKE_SOURCE_DIR}/slsDetectorCalibration/ - ${CMAKE_SOURCE_DIR}/slsDetectorCalibration/tiffio/include/ -) - -# Headders needed for ROOT dictionary generation -set( HEADERS - ctbDefs.h - ctbMain.h - ctbDacs.h - ctbPattern.h - ctbSignals.h - ctbAdcs.h - ctbAcquisition.h - ctbPowers.h - ctbSlowAdcs.h -) - -#set(ROOT_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) - -# ROOT dictionary generation -root_generate_dictionary(ctbDict ${HEADERS} LINKDEF ctbLinkDef.h) -add_library(ctbRootLib SHARED ctbDict.cxx) -target_include_directories(ctbRootLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(ctbRootLib PUBLIC - ROOT::Core - slsDetectorShared - ${ROOT_LIBRARIES} - ${ROOT_EXE_LINKER_FLAGS} -) - -set_target_properties( - ctbRootLib PROPERTIES - LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin -) - -target_link_libraries(ctbGui PUBLIC - slsDetectorShared - ctbRootLib - ${TIFF_LIBRARIES} -) - -set_target_properties(ctbGui PROPERTIES - RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin - -) - diff --git a/ctbGui/Makefile.root5 b/ctbGui/Makefile.root5 deleted file mode 100644 index 050acbc17..000000000 --- a/ctbGui/Makefile.root5 +++ /dev/null @@ -1,46 +0,0 @@ -# SPDX-License-Identifier: LGPL-3.0-or-other -# Copyright (C) 2021 Contributors to the SLS Detector Package - - -INCS=ctbMain.h ctbDacs.h ctbPattern.h ctbSignals.h ctbAdcs.h ctbAcquisition.h ctbPowers.h ctbSlowAdcs.h -SRC= $(INCS:.h=.cpp) ctbDict.cpp -LINKDEF=ctbLinkDef.h -ZMQLIB=../slsReceiverSoftware/include -LIBRARYCBF=$(CBFLIBDIR)/lib/*.o - -INCDIR=-I../slsReceiverSoftware/include/ -I../slsDetectorSoftware/include/ -I../slsSupportLib/include/ -I../slsDetectorCalibration -I../slsDetectorCalibration/dataStructures -I$(CBFLIBDIR)/include -I../slsDetectorCalibration/interpolations -LDFLAG=-L../build/bin -lSlsDetector -lSlsSupport -L/usr/lib64/ -lpthread -lm -lstdc++ -lzmq -pthread -lrt -ltiff -L$(ZMQLIB) -L$(CBFLIBDIR)/lib/ -std=c++11 -# -MAIN=ctbGui.cpp - -DESTDIR?=../build/bin - - -OBJS = $(SRC:.cpp=.o) $(MAIN:.cpp=.o) - -all: $(DESTDIR)/ctbGui - - -doc: - cd manual && make DESTDIR=$(DESTDIR) - -htmldoc: - cd manual && make html DESTDIR=$(DESTDIR) - -ctbDict.cpp: $(INCS) $(LINKDEF) - rootcint -f ctbDict.cpp -c $(INCS) $(LINKDEF) - -%.o : %.cpp - echo $@ - g++ -DMYROOT `root-config --cflags --glibs` -lMinuit -DCTB $(LDFLAG) -o $@ -c $< $(INCDIR) -#$(CXX) -o $@ -c $< $(INCLUDES) $(DFLAGS) -fPIC $(EPICSFLAGS) -lpthread #$(FLAGS) - - - -$(DESTDIR)/ctbGui: $(OBJS) $(LINKDEF) - g++ -DMYROOT `root-config --cflags --glibs` -lMinuit -DCTB $(LDFLAG) -o ctbGui $(INCDIR) $(OBJS) ../slsDetectorCalibration/tiffIO.cpp - mv ctbGui $(DESTDIR) - -clean: - rm -f $(DESTDIR)/ctbGui *.o ctbDict.* $(OBJS) - diff --git a/ctbGui/Makefile.root6 b/ctbGui/Makefile.root6 deleted file mode 100644 index 338949617..000000000 --- a/ctbGui/Makefile.root6 +++ /dev/null @@ -1,48 +0,0 @@ -# SPDX-License-Identifier: LGPL-3.0-or-other -# Copyright (C) 2021 Contributors to the SLS Detector Package - - -INCS=ctbMain.h ctbDacs.h ctbPattern.h ctbSignals.h ctbAdcs.h ctbAcquisition.h ctbPowers.h ctbSlowAdcs.h -SRC= $(INCS:.h=.cpp) ctbDict.cpp -LINKDEF=ctbLinkDef.h -#ctbActions.h -ZMQLIB=../slsReceiverSoftware/include -LIBRARYCBF=$(CBFLIBDIR)/lib/*.o - -INCDIR=-I../slsReceiverSoftware/include/ -I../slsDetectorSoftware/include/ -I../slsSupportLib/include/ -I../slsDetectorCalibration -I../slsDetectorCalibration/dataStructures -I$(CBFLIBDIR)/include -I../slsDetectorCalibration/interpolations - -LDFLAG=-L../build/bin -lSlsDetector -lSlsSupport -L/usr/lib64/ -lpthread -lm -lstdc++ -lzmq -pthread -lrt -ltiff -L$(ZMQLIB) -L$(CBFLIBDIR)/lib/ -std=c++11 -# -MAIN=ctbGui.cpp - -DESTDIR?=../build/bin - - -OBJS = $(SRC:.cpp=.o) $(MAIN:.cpp=.o) - -all: $(DESTDIR)/ctbGui - - -doc: - cd manual && make DESTDIR=$(DESTDIR) - -htmldoc: - cd manual && make html DESTDIR=$(DESTDIR) - -ctbDict.cpp: $(INCS) $(LINKDEF) - rootcling -f ctbDict.cpp -c $(INCS) $(LINKDEF) - -%.o : %.cpp - echo $@ - g++ -DMYROOT `source root-config --cflags --glibs` -lMinuit -DCTB $(LDFLAG) -o $@ -c $< $(INCDIR) -#$(CXX) -o $@ -c $< $(INCLUDES) $(DFLAGS) -fPIC $(EPICSFLAGS) -lpthread #$(FLAGS) - - - -$(DESTDIR)/ctbGui: $(OBJS) $(LINKDEF) - g++ -DMYROOT `source root-config --cflags --glibs` -lMinuit -DCTB $(LDFLAG) -o ctbGui $(INCDIR) $(OBJS) ../slsDetectorCalibration/tiffIO.cpp - mv ctbGui $(DESTDIR) - -clean: - rm -f $(DESTDIR)/ctbGui *.o ctbDict.* $(OBJS) - diff --git a/ctbGui/ctbAcquisition.cpp b/ctbGui/ctbAcquisition.cpp deleted file mode 100644 index 8de365271..000000000 --- a/ctbGui/ctbAcquisition.cpp +++ /dev/null @@ -1,2277 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package -//#define TESTADC - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -#include -#include -#include -#include - -#include "ctbAcquisition.h" -#include "ctbDefs.h" -#include "sls/Detector.h" -#include "sls/sls_detector_defs.h" -#include "ctbMain.h" -#include "moench03CtbData.h" -//#include "moench03TCtbData.h" -//#include "moench03T1CtbData.h" -#include "moench03CommonMode.h" -#include "moench03T1ZmqDataNew.h" -#include "moench02CtbData.h" -//#include "jungfrau10ModuleData.h" -#include "moenchCommonMode.h" -#include "singlePhotonDetector.h" -#include "Mythen3_01_jctbData.h" -#include "Mythen3_02_jctbData.h" -#include "adcSar2_jctbData.h" -#include "moench04CtbZmqData.h" -#include "moench04CtbZmq10GbData.h" -#include "deserializer.h" -#include "sls/detectorData.h" -#include "imageZmq16bit.h" -#include "imageZmq32bit.h" - - -using namespace std; - - - - -ctbAcquisition::ctbAcquisition(TGVerticalFrame *page, sls::Detector *det) : TGGroupFrame(page,"Acquisition",kVerticalFrame), myDet(det), myCanvas(NULL), globalPlot(0), tenG(0), nAnalogSamples(1), nDigitalSamples(1), dataStructure(NULL), photonFinder(NULL), cmSub(0), dBitMask(0xffffffffffffffff), deserializer(0) { - - adcFit=NULL; - bitPlot=NULL; - countsFit=NULL; - - page->AddFrame(this,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 10,10,10,10)); - MapWindow(); - - TGHorizontalFrame *hframe=new TGHorizontalFrame(this, 800,50); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 10,10,10,10)); - hframe->MapWindow(); - - char tit[100]; - - cout << "outfile "<< endl; - - cFileSave= new TGCheckButton(hframe, "Output file: "); - hframe->AddFrame(cFileSave,new TGLayoutHints(kLHintsTop | kLHintsLeft | kLHintsExpandX, 5, 5, 5, 5)); - cFileSave->MapWindow(); - cFileSave->SetTextJustify(kTextRight); - cFileSave->Connect("Toggled(Bool_t)","ctbAcquisition",this,"setFsave(Bool_t)"); - - std::string temp = "run"; - try { - temp = myDet->getFileNamePrefix().tsquash("Different values"); - } CATCH_DISPLAY ("Could not get file name prefix.", "ctbAcquisition::ctbAcquisition") - eFname = new TGTextEntry(hframe, temp.c_str()); - - hframe->AddFrame(eFname,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 5, 5, 5, 5)); - eFname->MapWindow(); - eFname->Resize(150,30); - - eFname->Connect("ReturnPressed()","ctbAcquisition",this,"setFname()"); - - - TGLabel *label=new TGLabel(hframe,"index: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextRight); - - - eFindex = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELNoLimits); - hframe->AddFrame( eFindex,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eFindex->MapWindow(); - eFindex->Resize(150,30); - TGTextEntry *e= eFindex->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbAcquisition",this,"setFindex()"); - - - - - cout << "outdir "<< endl; - - - hframe=new TGHorizontalFrame(this, 800,50); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 10,10,10,10)); - hframe->MapWindow(); - - label=new TGLabel(hframe,"Output directory: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextRight); - - - - temp = "/tmp/"; - try { - temp = myDet->getFilePath().tsquash("Different values"); - } CATCH_DISPLAY ("Could not get file path.", "ctbAcquisition::ctbAcquisition") - eOutdir = new TGTextEntry(hframe, temp.c_str()); - - hframe->AddFrame(eOutdir,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 5, 5, 5, 5)); - eOutdir->MapWindow(); - eOutdir->Resize(150,30); - - - eOutdir->Connect("ReturnPressed()","ctbAcquisition",this,"setOutdir()"); - - hframe=new TGHorizontalFrame(this, 800,50); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 10,10,10,10)); - hframe->MapWindow(); - - - cout << "meas "<< endl; -label=new TGLabel(hframe,"Number of Measurements (fake): "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextRight); - - - eMeasurements = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELNoLimits); - hframe->AddFrame( eMeasurements,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eMeasurements->MapWindow(); - eMeasurements->Resize(150,30); - eMeasurements->SetNumber(1); - e= eMeasurements->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbAcquisition",this,"setMeasurements()"); - - - - -hframe=new TGHorizontalFrame(this, 800,50); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 10,10,10,10)); - hframe->MapWindow(); - - - - - cout << "pattern "<< endl; - - - - - cCompile= new TGCheckButton(hframe, "Compile"); - hframe->AddFrame(cCompile,new TGLayoutHints(kLHintsTop | kLHintsLeft | kLHintsExpandX, 5, 5, 5, 5)); - cCompile->MapWindow(); - cCompile->SetOn(); - // cCompile->Connect("Toggled(Bool_t)","ctbAcquisition",this,"setFsave(Bool_t)"); - - - cLoad= new TGTextButton(hframe, "Load"); - hframe->AddFrame(cLoad,new TGLayoutHints(kLHintsTop | kLHintsLeft | kLHintsExpandX, 5, 5, 5, 5)); - cLoad->MapWindow(); - cLoad->Connect("Clicked()","ctbAcquisition",this,"loadPattern()"); - - -// cRun= new TGCheckButton(hframe, "Run"); -// hframe->AddFrame(cRun,new TGLayoutHints(kLHintsTop | kLHintsLeft | kLHintsExpandX, 5, 5, 5, 5)); -// cRun->MapWindow(); -// // cCompile->Connect("Toggled(Bool_t)","ctbAcquisition",this,"setFsave(Bool_t)"); - - - - - - - bStatus=new TGTextButton(hframe, "Start"); - hframe->AddFrame(bStatus,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 5, 5, 5, 5)); - bStatus->MapWindow(); - bStatus->Connect("Clicked()","ctbAcquisition",this,"toggleAcquisition()"); - - - - - - cout << "plot "<< endl; - - hframe=new TGHorizontalFrame(this, 800,50); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 10,10,10,10)); - hframe->MapWindow(); - - - - - label=new TGLabel(hframe,"Plot: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextRight); - - - - - - - TGButtonGroup *bgPlot = new TGButtonGroup(hframe); - // horizontal->SetTitlePos(TGGroupFrame::kCenter); - rbPlotOff=new TGRadioButton(hframe, "No plot"); - rbWaveform=new TGRadioButton(hframe, "Waveform"); - rbDistribution=new TGRadioButton(hframe, "Distribution"); - rb2D=new TGRadioButton(hframe, "Image"); - // rbScan=new TGRadioButton(hframe, "Scan"); - - cbDetType=new TGComboBox(hframe); - // enum {DESERIALIZER, MOENCH04, MOENCH02, ADCSAR2, MYTHEN301, MYTHEN302, MAXDET}; - cbDetType->AddEntry("Deserializer", DESERIALIZER); - cbDetType->AddEntry("MOENCH02", MOENCH02); - cbDetType->AddEntry("MOENCH04", MOENCH04); - // cbDetType->AddEntry("JUNGFRAU1.0", 2); - cbDetType->AddEntry("MOENCH03",MOENCH03); - cbDetType->AddEntry("IMAGE32BIT",IMAGE32B); - cbDetType->AddEntry("IMAGE16BIT",IMAGE16B); - - //cbDetType->AddEntry("MOENCH03", iiii++); - // cbDetType->AddEntry("MYTHEN3 0.1", MYTHEN301); - // cbDetType->AddEntry("ADCSAR2", ADCSAR2); - // cbDetType->AddEntry("MYTHEN3 0.2", MYTHEN302); - - cbDetType->SetHeight(20); - cbDetType->Select(0); - - bgPlot->Insert(rbPlotOff,0); - bgPlot->Insert(rbWaveform,1); - bgPlot->Insert(rbDistribution,2); - bgPlot->Insert(rb2D,3); - // bgPlot->Insert(rbScan,4); - - bgPlot->Connect("Clicked(Int_t)", "ctbAcquisition", this, "changePlot(Int_t)"); - // hframe->AddFrame(bgPlot, new TGLayoutHints(kLHintsExpandX)); - - cbDetType->Connect("Selected(Int_t)", "ctbAcquisition",this, "changeDetector(Int_t)"); - hframe->AddFrame(rbPlotOff, new TGLayoutHints(kLHintsTop | kLHintsExpandX)); - hframe->AddFrame(rbWaveform, new TGLayoutHints(kLHintsTop | kLHintsExpandX)); - hframe->AddFrame(rbDistribution, new TGLayoutHints(kLHintsTop | kLHintsExpandX)); - hframe->AddFrame(rb2D, new TGLayoutHints(kLHintsTop | kLHintsExpandX)); - // hframe->AddFrame(rbScan, new TGLayoutHints(kLHintsTop | kLHintsExpandX)); - hframe->AddFrame(cbDetType, new TGLayoutHints(kLHintsTop | kLHintsExpandX| kLHintsExpandY)); - - - bgPlot->SetExclusive(kTRUE); - rbWaveform->SetOn(); - rbPlotOff->MapWindow(); - rbWaveform->MapWindow(); - rbDistribution->MapWindow(); - rb2D->MapWindow(); - // rbScan->MapWindow(); - cbDetType->MapWindow(); - - - - - // cout << "off "<< endl; - - - hframe=new TGHorizontalFrame(this, 800,50); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 10,10,10,10)); - hframe->MapWindow(); - - - - label=new TGLabel(hframe,"Serial offset:"); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextRight); - - - eSerOff=new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax,0,16535); - hframe->AddFrame(eSerOff,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eSerOff->MapWindow(); - eSerOff->SetNumber(0); - e= eSerOff->TGNumberEntry::GetNumberEntry(); - eSerOff->Connect("ValueSet(Long_t)","ctbAcquisition",this,"ChangeSerialOffset(Long_t)"); - e->Connect("ReturnPressed()","ctbAcquisition",this,"ChangeSerialOffset()"); - - - label=new TGLabel(hframe,"N counters:"); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextRight); - - - eNumCount=new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax,0,16535); - hframe->AddFrame(eNumCount,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eNumCount->MapWindow();; - eNumCount->SetNumber(128*3); - e= eNumCount->TGNumberEntry::GetNumberEntry(); - eNumCount->Connect("ValueSet(Long_t)","ctbAcquisition",this,"ChangeNumberOfChannels(Long_t)"); - e->Connect("ReturnPressed()","ctbAcquisition",this,"ChangeNumberOfChannels()"); - - - - cout << "dr "<< endl; - - label=new TGLabel(hframe,"Dynamic Range:"); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextRight); - - - eDynRange=new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax,0,16535); - hframe->AddFrame(eDynRange,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eDynRange->MapWindow();; - eDynRange->SetNumber(24); - e= eDynRange->TGNumberEntry::GetNumberEntry(); - eDynRange->Connect("ValueSet(Long_t)","ctbAcquisition",this,"ChangeDynamicRange(Long_t)"); - e->Connect("ReturnPressed()","ctbAcquisition",this,"ChangeDynamicRange()"); - - - - - - hframe=new TGHorizontalFrame(this, 800,50); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 10,10,10,10)); - hframe->MapWindow(); - - - - label=new TGLabel(hframe,"Image Pixels"); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - label=new TGLabel(hframe,"X: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextRight); - - - ePixX=new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax,0,16535); - hframe->AddFrame(ePixX,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - ePixX->MapWindow(); - ePixX->SetNumber(400); - e= ePixX->TGNumberEntry::GetNumberEntry(); - ePixX->Connect("ValueSet(Long_t)","ctbAcquisition",this,"ChangeImagePixels(Long_t)"); - e->Connect("ReturnPressed()","ctbAcquisition",this,"ChangeImagePixels()"); - - - - label=new TGLabel(hframe,"Y: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextRight); - - - ePixY=new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax,0,16535); - hframe->AddFrame(ePixY,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - ePixY->MapWindow(); - ePixY->SetNumber(400); - e= ePixY->TGNumberEntry::GetNumberEntry(); - ePixY->Connect("ValueSet(Long_t)","ctbAcquisition",this,"ChangeImagePixels(Long_t)"); - e->Connect("ReturnPressed()","ctbAcquisition",this,"ChangeImagePixels()"); - - - - - hframe=new TGHorizontalFrame(this, 800,50); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 10,10,10,10)); - hframe->MapWindow(); - - - - label=new TGLabel(hframe,"Pedestal "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextRight); - - - - cbGetPedestal= new TGCheckButton(hframe, "Acquire"); - hframe->AddFrame(cbGetPedestal,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - cbGetPedestal->MapWindow(); - - cbSubtractPedestal= new TGCheckButton(hframe, "Subtract"); - hframe->AddFrame(cbSubtractPedestal,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - cbSubtractPedestal->MapWindow(); - - - cbSubtractPedestal->Connect("Toggled(Bool_t)","ctbAcquisition",this,"TogglePedSub(Bool_t)"); - - cbCommonMode= new TGCheckButton(hframe, "Common Mode"); - hframe->AddFrame(cbCommonMode,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - cbCommonMode->MapWindow(); - - - cbCommonMode->Connect("Toggled(Bool_t)","ctbAcquisition",this,"ToggleCommonMode(Bool_t)"); - - - bResetPedestal= new TGTextButton(hframe, "Reset"); - hframe->AddFrame(bResetPedestal,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - bResetPedestal->MapWindow(); - - - bResetPedestal->Connect("Clicked()","ctbAcquisition",this,"resetPedestal()"); - - - - - - hframe=new TGHorizontalFrame(this, 800,50); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 10,10,10,10)); - hframe->MapWindow(); - - - cMinMaxRaw=new TGCheckButton(hframe,"Raw data "); - hframe->AddFrame(cMinMaxRaw,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - cMinMaxRaw->MapWindow(); - cMinMaxRaw->Connect("Toggled(Bool_t)","ctbAcquisition",this,"ChangeHistoLimitsRaw(Bool_t)"); - - - - - label=new TGLabel(hframe,"Min: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextRight); - - - eMinRaw=new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax,0,16535); - hframe->AddFrame(eMinRaw,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eMinRaw->MapWindow();; - eMinRaw->SetNumber(0); - e= eMinRaw->TGNumberEntry::GetNumberEntry(); - eMinRaw->Connect("ValueSet(Long_t)","ctbAcquisition",this,"ChangeHistoLimitsRaw(Long_t)"); - e->Connect("ReturnPressed()","ctbAcquisition",this,"ChangeHistoLimitsRaw()"); - - - label=new TGLabel(hframe,"Max: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextRight); - - - - - eMaxRaw=new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax,0,16535); - hframe->AddFrame(eMaxRaw,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eMaxRaw->MapWindow();; - eMaxRaw->SetNumber(16535); - - e= eMaxRaw->TGNumberEntry::GetNumberEntry(); - eMaxRaw->Connect("ValueSet(Long_t)","ctbAcquisition",this,"ChangeHistoLimitsRaw(Long_t)"); - e->Connect("ReturnPressed()","ctbAcquisition",this,"ChangeHistoLimitsRaw()"); - - - hframe=new TGHorizontalFrame(this, 800,50); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 10,10,10,10)); - hframe->MapWindow(); - - - - cMinMaxPedSub=new TGCheckButton(hframe,"Pedestal Subtracted "); - hframe->AddFrame(cMinMaxPedSub,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - cMinMaxPedSub->MapWindow(); - cMinMaxPedSub->Connect("Toggled(Bool_t)","ctbAcquisition",this,"ChangeHistoLimitsPedSub(Bool_t)"); - - - label=new TGLabel(hframe,"Min: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextRight); - - - eMinPedSub=new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEAAnyNumber, - TGNumberFormat::kNELLimitMinMax,-16535,16535); - hframe->AddFrame(eMinPedSub,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eMinPedSub->MapWindow();; - eMinPedSub->SetNumber(-100); - - e= eMinPedSub->TGNumberEntry::GetNumberEntry(); - - eMinPedSub->Connect("ValueSet(Long_t)","ctbAcquisition",this,"ChangeHistoLimitsPedSub(Long_t)"); - e->Connect("ReturnPressed()","ctbAcquisition",this,"ChangeHistoLimitsPedSub()"); - - - label=new TGLabel(hframe,"Max: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextRight); - - - eMaxPedSub=new TGNumberEntry(hframe,0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEAAnyNumber, - TGNumberFormat::kNELLimitMinMax,-16535,16535); - hframe->AddFrame(eMaxPedSub,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eMaxPedSub->MapWindow();; - eMaxPedSub->SetNumber(100); - - - e= eMaxPedSub->TGNumberEntry::GetNumberEntry(); - eMaxPedSub->Connect("ValueSet(Long_t)","ctbAcquisition",this,"ChangeHistoLimitsPedSub(Long_t)"); - e->Connect("ReturnPressed()","ctbAcquisition",this,"ChangeHistoLimitsPedSub()"); - - - hframe=new TGHorizontalFrame(this, 800,50); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 10,10,10,10)); - hframe->MapWindow(); - - - - - - TGTextButton *b= new TGTextButton(hframe, "Fit Panel ADC:"); - hframe->AddFrame(b,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - b->MapWindow(); - - - b->Connect("Clicked()","ctbAcquisition",this,"FitADC()"); - - - eFitADC=new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax,0,31); - hframe->AddFrame( eFitADC,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eFitADC->MapWindow();; - - - - hframe=new TGHorizontalFrame(this, 800,50); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 10,10,10,10)); - hframe->MapWindow(); - - - - - - b= new TGTextButton(hframe, "Plot bit:"); - hframe->AddFrame(b,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - b->MapWindow(); - - - b->Connect("Clicked()","ctbAcquisition",this,"plotBit()"); - - - eBitPlot=new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax,0,64); - hframe->AddFrame( eBitPlot,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eBitPlot->MapWindow();; - - - - - - hframe=new TGHorizontalFrame(this, 800,50); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 10,10,10,10)); - hframe->MapWindow(); - - - - label=new TGLabel(hframe,"X "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextRight); - - - label=new TGLabel(hframe," "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - lClickX=label; - - - - label=new TGLabel(hframe,"Y "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextRight); - - - - label=new TGLabel(hframe," "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - lClickY=label; - - - label=new TGLabel(hframe,"Value "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextRight); - - - - - - label=new TGLabel(hframe," "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - lClickValue=label; - - - - - - - - b= new TGTextButton(hframe, "Refresh"); - hframe->AddFrame(b,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - b->MapWindow(); - - - b->Connect("Clicked()","ctbAcquisition",this,"Draw()"); - - - - - - - - - - - acqThread = new TThread("acqThread", - ctbAcquisition::ThreadHandle,(void*)this); - - cout <<"Registering data callback" << endl; - try { - myDet->registerDataCallback(&dataCallback, (void*)this); - } CATCH_DISPLAY ("Could not get register call back.", "ctbAcquisition::ctbAcquisition") - try { - myDet->setRxZmqDataStream(true); - } CATCH_DISPLAY ("Could not get set RxZmqDataStream.", "ctbAcquisition::ctbAcquisition") - cout <<"Done" << endl; - - // mgAdcs=new TMultiGraph(); - adcStack=new THStack(); - TH1F *h; - int nSamples=nAnalogSamples; - - for (int i=0; iSetLineColor(i+1); - h->SetLineWidth(2); - adcStack->Add(h); - adcHisto[i]=h; - plotFlag[i]=0; - // h->SetMinimum(-1); - // h->SetMaximum(16385); - } - - - // mgAdcs=new TMultiGraph(); - bitStack=new THStack(); - // bitStack=adcStack; - TH1F *hb; - for (int i=0; iSetLineColor(i+1); - hb->SetLineWidth(2); - bitStack->Add(hb); - bitHisto[i]=hb; - bitOffset[i]=0; - bitPlotFlag[i]=0; - // h->SetMinimum(-1); - // h->SetMaximum(16385); - } - - - countsStack=new THStack(); - TH1F *h1; - for (int i=0; iSetLineColor(i+1); - h1->SetFillColor(i+1); - h1->SetLineWidth(2); - countsStack->Add(h1); - countsHisto[i]=h1; - } - - dataStructure=NULL; - commonMode=NULL; - photonFinder=NULL; - h2DMapAn=NULL; - h2DMapDig=NULL; - //h2Scan=NULL; - h1DMap=NULL; - - changeDetector(cbDetType->GetSelected()); - - - - // display could be updated with TTimer instead of with histogramfillthread: - // plotTimer= new TTimer("ctbAcquisition::Draw()",100); - - - // plotTimer->Connect("TurnOff()", "ctbAcquisition", this, "Draw()"); -} - - - - -void ctbAcquisition::canvasClicked() { - int event = gPad->GetEvent(); - if (event != 11) return; - TObject *select = gPad->GetSelected(); - if (!select) return; - - if (select->InheritsFrom("TH2")) { - TH2* hh=(TH2*)select; - - - - - int px = gPad->GetEventX(); - int py = gPad->GetEventY(); - Float_t upy = gPad->AbsPixeltoY(py); - Float_t y = gPad->PadtoY(upy); - Float_t upx = gPad->AbsPixeltoX(px); - Float_t x = gPad->PadtoY(upx); - - - // cout << "x: " << x << " y: " << y << " " << hh->GetBinContent(hh->GetXaxis()->FindBin(x), hh->GetYaxis()->FindBin(y)) << endl; - - - lClickX->SetText(hh->GetXaxis()->FindBin(x)-1); - lClickY->SetText( hh->GetYaxis()->FindBin(y)-1); - lClickValue->SetText(hh->GetBinContent(hh->GetXaxis()->FindBin(x), hh->GetYaxis()->FindBin(y))); - - - - - - - } else if (select->InheritsFrom("TH1")) { - - TH1* h1=(TH1*)select; - int px = gPad->GetEventX(); - Float_t upx = gPad->AbsPixeltoX(px); - Float_t x = gPad->PadtoY(upx); - - - // cout << "x: " << x << " y: " << y << " " << hh->GetBinContent(hh->GetXaxis()->FindBin(x), hh->GetYaxis()->FindBin(y)) << endl; - - - lClickX->SetText(h1->GetXaxis()->FindBin(x)-1); - lClickY->SetText(" "); - lClickValue->SetText(h1->GetBinContent(h1->GetXaxis()->FindBin(x))); - - - - - - } else// if ((select->ClassName())=="THStack") { - { - - - int px = gPad->GetEventX(); - int py = gPad->GetEventY(); - Float_t upy = gPad->AbsPixeltoY(py); - Float_t y = gPad->PadtoY(upy); - Float_t upx = gPad->AbsPixeltoX(px); - Float_t x = gPad->PadtoY(upx); - - - lClickX->SetText(x); - lClickY->SetText(y); - lClickValue->SetText(""); - - - - } - -} - - - - - - -void ctbAcquisition::setCanvas(TCanvas* c) { - myCanvas=c; - myCanvas->cd(); - myCanvas->AddExec("dynamic",Form("((ctbAcquisition*)%p)->canvasClicked()",this)); - // myCanvas->AddExec("ex","canvasClicked()"); -} -void ctbAcquisition::dataCallback(sls::detectorData *data, long unsigned int index, unsigned int dum, void* pArgs) { - - // return - ((ctbAcquisition*)pArgs)->plotData(data,index); -} - - -int ctbAcquisition::plotData(sls::detectorData *data, int index) { - - /* -****************************************************************** -When selecting dbit -amount of data is nadc * nasamples * 16 bit + ndbitlist * ndsamples (bits) -order of data -analog: -sample0 (adc0 + adc1 +...) -sample1 (adc0 + adc1 +...) -digital: -dbit0 (sample0 + sample1 ...) -dbit1 (sample0 + sample1..) - -when setting dbit to all -amount of data: nadc * nasamples * 16 bit + 8 * ndsamples * 64 bit -what you had before.. -except analog first, then digital -analog: -sample0 (adc0 + adc1 +...) -sample1 (adc0 + adc1 +...) -digital: -sample0 (dbit0 + dbit1 +...) -sample1 (dbit0 + dbit1 +...)if (cmd == "rx_dbitlist") { - - if (action == PUT_ACTION) { - std::vector dbitlist; - - // if not all digital bits enabled - if (std::string(args[1]) != "all") { - for (int i = 1; i < narg; ++i) { - int temp = 0; - if (!sscanf(args[i], "%d", &temp)) - return std::string("Could not scan dbitlist value ") + - std::string(args[i]); - if (temp < 0 || temp > 63) - return std::string("dbitlist value should be between 0 and 63 ") + - std::string(args[i]); - dbitlist.push_back(temp); - } - if (dbitlist.size() > 64) { - return std::string("Max number of values for dbitlist is 64 "); - } - } - - myDet->setReceiverDbitList(dbitlist, detPos); - } - -****************************************************************** -*/ - - // cout << "plot data" << endl; - - - // cout <<"global plot is " << globalPlot << endl; - // cout << "*******************************************" <progressIndex << " nx:" << data->nx << " ny: " << data->ny << " " << data->fileName << " bytes: " << data->databytes << " dr:"<< data->dynamicRange << " fi: " << data ->fileIndex << endl; - if (globalPlot || cbGetPedestal->IsOn()) { - //#ifdef TESTADC - // cout <<"------"<< index << " " << ip << " " << data->npoints << endl; - //#endif - int ig=0; - int i, ii, ib; - // TList *l= adcStack->GetHists(); - // TList *l1= countsStack->GetHists(); - TH1F *h; - TH1F *h1; - TH1F *hb; - int x; - double ped=0; - int dsize=-1; - int *val=NULL; - int nx=1, ny=1; - - if (dataStructure) { - dataStructure->getDetectorSize(nx,ny); - cout << "Data structure: " << dataStructure << " size " << nx << " " << ny << endl; - } - int dr=24, soff=2; - if (deserializer) { - nx=eNumCount->GetIntNumber(); - dr=eDynRange->GetIntNumber(); - soff=eSerOff->GetIntNumber(); - // cout <<"deserializer: " << endl; - // cout << "Number of chans:\t" << nx << endl; - // cout << "Serial Offset:\t" << soff << endl; - // cout << "Dynamic range:\t" << dr << endl; - - } - - i=0; - int nadc; - int ndbit; - - tenG = 0; - - - - - if (adclist.empty()) - nadc=32; - else - nadc=adclist.size(); - - std::vector plotlist; - if (dbitlist.empty()) { - ndbit=64; - dBitOffset=0; - for (ib=0; ib<64; ib++){ - if (bitPlotFlag[ib]) { - plotlist.push_back(ib); - } - } - } else - ndbit=dbitlist.size(); - if (tenG){ - - if (nDigitalSamples && nAnalogSamples){ - if (nDigitalSamples>nAnalogSamples) - dsize=nDigitalSamples*(32*2+8); - else - dsize=nAnalogSamples*(32*2+8); - } else - dsize=32*2*nAnalogSamples+8*nDigitalSamples; - - } else - dsize=nadc*2*nAnalogSamples+ndbit*(nDigitalSamples-dBitOffset/8)/8; - - cout << "dataBytes is " << data->databytes << " expected " << dsize << endl; - - cout << "*******************************************" <data; - else - d_data = data->data+2*nadc*nAnalogSamples; - char dval; - - - if (dataStructure) { - - - for (int x=0; xgetValue(data->data,x,y); - //aval=dataStructure->getChannel(data->data,x,y); - // cout << x << " " <IsOn()) { - if (photonFinder) { - photonFinder->addToPedestal(aval,x,y); - } - } - - if (cbSubtractPedestal->IsOn()) { - if (photonFinder) { - ped=photonFinder->getPedestal(x,y,cmSub); - } - } - if (h2DMapAn) - h2DMapAn->SetBinContent(x+1,y+1,aval-ped); - - - - - if (h2DMapDig) - h2DMapDig->SetBinContent(x+1,y+1,dataStructure->getGain(data->data,x,y)); - - - } - } - } else - if (deserializer) { - cout << "deserializer"<< endl; - if (dbitlist.empty()) - val=deserializer::deserializeAll(d_data,plotlist,dr,nx,soff);//dataStructure->getData(d_data); - else - val=deserializer::deserializeList(d_data,dbitlist,dr,nx,soff);//dataStructure->getData(d_data); - - - if (val) { - if (h1DMap){ - for (x=0; xSetBinContent(x+1,val[x]); - } - } - delete [] val; - } else - cout << "get val did not succeed"<getChannel(i); - // else - aval=data->getChannel(i);//*((uint16_t*)(data->cvalues+i*2));// - - if (plotFlag[ig]) { - - //if (enableFlag[ig]) { - h=adcHisto[ig]; - h1=countsHisto[ig]; - //} - - // cout << data->getChannel(i) << endl; - h->SetBinContent(ip+1,aval); - h1->Fill(aval); - } - - i++; - } - if (tenG) i+=4; - - } - - - cout << "bit histo"<< endl; - - if (dbitlist.empty()) { - for (ip=0; ipSetBinContent(ip+1,1+bitOffset[ib]); - else - hb->SetBinContent(ip+1,bitOffset[ib]); - } - } - - } - } - } else { - ii=0; - int iii=0; - for (const auto &value : dbitlist) { - ib=value; - hb=bitHisto[ib]; - // cout << dec <SetBinContent(iii,1+bitOffset[ib]); - else - hb->SetBinContent(iii,bitOffset[ib]); - iii++; - } - } - } - ii++; - } - } - } - Draw(); - // iScanStep++; - if (photonFinder) - photonFinder->newFrame(); - } - return 0; - -} - - - -void ctbAcquisition::Draw(){ - if (globalPlot) { - // TThread::Lock(); - cout << "Draw" << endl; - if (myCanvas) { - if (adcPlot && dbitPlot) { - - myCanvas->cd(1); - // myCanvas->Modified(); - // myCanvas->Update(); - gPad->Modified(); - gPad->Update(); - - myCanvas->cd(2); - // myCanvas->Modified(); - // myCanvas->Update(); - gPad->Modified(); - gPad->Update(); - - } else { - - myCanvas->cd(); - myCanvas->Modified(); - myCanvas->Update(); - - } - } - // TThread::UnLock(); - } - -} - - -//here!! -void ctbAcquisition::changePlot(){ - if (rbPlotOff->IsOn()) { - adcPlot=0; - dbitPlot=0; - try { - myDet->registerDataCallback(nullptr, this); - } CATCH_DISPLAY ("Could not get unregister call back.", "ctbAcquisition::ctbAcquisition") - try { - myDet->setRxZmqDataStream(false); - } CATCH_DISPLAY ("Could not get unset RxZmqDataStream.", "ctbAcquisition::ctbAcquisition") - } else { - try { - myDet->registerDataCallback(&dataCallback, (void*)this); - } CATCH_DISPLAY ("Could not get register call back.", "ctbAcquisition::ctbAcquisition") - try { - myDet->setRxZmqDataStream(true); - } CATCH_DISPLAY ("Could not get set RxZmqDataStream.", "ctbAcquisition::ctbAcquisition") - adcPlot=0; - dbitPlot=0; - for (int ii=0; iiGetPad(1)==NULL || myCanvas->GetPad(2)==NULL) { - myCanvas->Clear(); - myCanvas->Divide(1,2); - } else - cout << "Pad already there" << endl; - myCanvas->cd(1); - } else { - myCanvas->Clear(); - // myCanvas->Divide(1,1); - myCanvas->cd(); - } - - if (adcPlot) { - if (rbWaveform->IsOn()) - if (adcStack) - adcStack->Draw("NOSTACK"); - else - cout << "adcStack is NULL" << endl; - else if (rbDistribution->IsOn()) - if (countsStack) - countsStack->Draw("NOSTACK"); - else - cout << "countsStack is NULL" << endl; - else if (rb2D->IsOn()) { - if (h2DMapAn) - h2DMapAn->Draw("colz"); - else if (h1DMap) - h1DMap->Draw(); - else - cout << "h2DMap and h1DMap are NULL" << endl; - } - } - - if (dbitPlot) { - if (adcPlot) - myCanvas->cd(2); - if (rb2D->IsOn()) { - if (h2DMapDig) - h2DMapDig->Draw("colz"); - else if (h1DMap) - h1DMap->Draw(); - } else if (bitStack) - bitStack->Draw("NOSTACK"); - else - cout << "bitStack is NULL" << endl; - } - - - // else if (rbScan->IsOn()) { - // if (h2Scan) - // h2Scan->Draw("colz"); - // else - // cout << "h2Scan is NULL" << endl; - // } - - Draw(); - - } -} - - - - - -void ctbAcquisition::changeDetector(){ - // cout << "change detector " << i << " old " << cbDetType->GetSelected() << endl; - - if (dataStructure) delete dataStructure; - if (commonMode) delete commonMode; - if (photonFinder) delete photonFinder; - if (h2DMapAn) delete h2DMapAn; - if (h2DMapDig) delete h2DMapDig; - if (h1DMap) delete h1DMap; - // if (h2Scan) delete h2Scan; - h2DMapAn=NULL; - h2DMapDig=NULL; - h1DMap=NULL; - // h2Scan=NULL; - photonFinder=NULL; - dataStructure=NULL; - commonMode=NULL; - - // TH2F *h2ScanOld=h2Scan; - - - int nx,ny; - int csize=3; - int nsigma=5; - commonModeSubtraction* cm=0; - eNumCount->SetState(kFALSE); - eDynRange->SetState(kFALSE); - eSerOff->SetState(kFALSE); - ePixX->SetState(kFALSE); - ePixY->SetState(kFALSE); - - deserializer=0; - if (rb2D->IsOn() ) {//|| rbScan->IsOn() - switch (cbDetType->GetSelected()) { - case DESERIALIZER: - deserializer=1; - cout << "DESERIALIZER!" << endl; - // dataStructure=new moench03T1CtbData(); - // commonMode=new moench03CommonMode(); - break; - case MOENCH04: - try { - auto retval = myDet->getTenGiga().tsquash("Different values"); - if (retval) { - dataStructure=new moench04CtbZmq10GbData(nAnalogSamples, nDigitalSamples); - } else { - dataStructure=new moench04CtbZmqData(nAnalogSamples, nDigitalSamples); - } - } CATCH_DISPLAY ("Could not get ten giga enable.", "ctbAcquisition::changeDetector") - - cout << "MOENCH 0.4!" << endl; - commonMode=new moench03CommonMode(); - break; - case MOENCH03: - //try { - // auto retval = myDet->getTenGiga().tsquash("Different values"); - // if (retval) { - dataStructure=new moench03T1ZmqDataNew(nAnalogSamples); - // } else { - // dataStructure=new moench04CtbZmqData(nAnalogSamples, nDigitalSamples); - // } - //} CATCH_DISPLAY ("Could not get ten giga enable.", "ctbAcquisition::changeDetector") - - cout << "MOENCH 0.3! USE JUNGFRAU MODULE!" << endl; - commonMode=new moench03CommonMode(); - break; - case IMAGE32B: - //try { - // auto retval = myDet->getTenGiga().tsquash("Different values"); - // if (retval) { - // if (deserializer) { - ePixX->SetState(kTRUE); - ePixY->SetState(kTRUE); - // } - dataStructure=new imageZmq32bit(ePixX->GetIntNumber(),ePixY->GetIntNumber()); - // } else { - // dataStructure=new moench04CtbZmqData(nAnalogSamples, nDigitalSamples); - // } - //} CATCH_DISPLAY ("Could not get ten giga enable.", "ctbAcquisition::changeDetector") - - cout << "Image 32bit, no channel shuffling" << endl; - commonMode=NULL; - break; - - case IMAGE16B: - //try { - // auto retval = myDet->getTenGiga().tsquash("Different values"); - // if (retval) { - // if (deserializer) { - ePixX->SetState(kTRUE); - ePixY->SetState(kTRUE); - // } - dataStructure=new imageZmq16bit(ePixX->GetIntNumber(),ePixY->GetIntNumber()); - // } else { - // dataStructure=new moench04CtbZmqData(nAnalogSamples, nDigitalSamples); - // } - //} CATCH_DISPLAY ("Could not get ten giga enable.", "ctbAcquisition::changeDetector") - - cout << "Image 16bit, no channel shuffling" << endl; - commonMode=NULL; - break; - - // case 1: - // cout << "************** T!!!!!!!!!!" << endl; - // dataStructure=new moench03TCtbData(); - // commonMode=new moench03CommonMode(); - // break; - case MOENCH02: - cout << "MOENCH 0.2" << endl; - dataStructure=new moench02CtbData(); - commonMode=new moenchCommonMode(); - break; - // case 2: - // dataStructure=new jungfrau10ModuleData(); - // commonMode=new commonModeSubtraction(); - // break; - // case 3: - // cout << "************** Flat!!!!!!!!!!" << endl; - // dataStructure=new moench03CtbData(); - // commonMode=new moench03CommonMode(); - // break; - // case MYTHEN301: - // deserializer=1; - // cout << "MYTHEN 3 0.1" << endl; - // dataStructure=new mythen3_01_jctbData(eNumCount->GetIntNumber(),eDynRange->GetIntNumber(),eSerOff->GetIntNumber()); - // //( int nch=64*3,int dr=24, int off=5) - // eNumCount->SetState(kTRUE); - // eDynRange->SetState(kTRUE); - // eSerOff->SetState(kTRUE); - // commonMode=NULL; - // dim=1; - // break; - // case ADCSAR2: - // deserializer=1; - // //adcsar2 - // dataStructure=new adcSar2_jctbData(); - // //need to use configurable number of counters, offset or dynamic range? - // commonMode=NULL; - // dim=1; - // break; - - // case MYTHEN302: - // deserializer=1; - // cout << "MYTHEN 3 0.2" << endl; - // dataStructure=new mythen3_02_jctbData(eNumCount->GetIntNumber(),eDynRange->GetIntNumber(),eSerOff->GetIntNumber()); - // //( int nch=64*3,int dr=24, int off=5) - // eNumCount->SetState(kTRUE); - // eDynRange->SetState(kTRUE); - // eSerOff->SetState(kTRUE); - // commonMode=NULL; - // dim=1; - // break; - default: - dataStructure=NULL; - commonMode=NULL; - } - if (cbCommonMode->IsOn()) cm=commonMode; - } - - if (dataStructure || deserializer) { - if (dataStructure) { - photonFinder=new singlePhotonDetector(dataStructure,csize,nsigma,1,cm); //sign is positive - should correct with ADC mask, no common mode - //photonFinder=new singlePhotonDetector(dataStructure,csize,nsigma,1,cm); //sign is positive - should correct with ADC mask, no common mode - dataStructure->getDetectorSize(nx,ny); - - } - if (deserializer) { - ny=1; - nx=eNumCount->GetIntNumber(); - eNumCount->SetState(kTRUE); - eDynRange->SetState(kTRUE); - eSerOff->SetState(kTRUE); - } - // cout << "h size is " << nx << " " << ny << endl; - int ymax=ny, xmax=nx; - // if (ny>500) {ny=ny/2;} - // if (nx>500) {nx=nx/2;} - cout << "*** " << nx << " " << ny << endl; - if (rb2D->IsOn()) { - if (ny>1) { - h2DMapAn=new TH2F("h2dmapAn","",nx,0,xmax,ny,0,ymax); - h2DMapAn->SetStats(kFALSE); - cout << "Created h2DMapAn"<< endl; - if (dbitPlot && adcPlot){ - h2DMapDig=new TH2F("h2dmapDig","",nx,0,xmax,ny,0,ymax); - h2DMapDig->SetStats(kFALSE); - cout << "Created h2DMapDig"<< endl; - } - } else { - h1DMap=new TH1F("h1dmap","",nx,0,xmax); - h1DMap->SetStats(kFALSE); - cout << "Created h1DMap"<< endl; - } - } // else if (rbScan->IsOn()) { - // int nsteps=0;//myDet->getScanSteps(0); - // double stepmin=0, stepmax=1; - // if (nsteps>0) { - // stepmin=myDet->getScanStep(0,0); - // stepmax=myDet->getScanStep(0,nsteps-1); - // } - // cout << "************ creating scan histogram " << nx*ny << " " << nsteps << " " << stepmin << " " << stepmax << endl; - // if (nsteps<1) nsteps=1; - // double hmin=stepmin, hmax=stepmax; - // if (stepmin>stepmax) { - // hmin=stepmax; - // hmax=stepmin; - // } - // h2Scan=new TH2F("h2scan","",nx*ny,0,nx*ny,nsteps,hmin,hmax); - // } - - } - - - cout << "done " << endl; -} - - - -void ctbAcquisition::changeDetector(int i){ - changePlot(); - changeDetector(); -} - -void ctbAcquisition::changePlot(int i){ - changePlot(); - changeDetector(); -} - - - -void ctbAcquisition::setGraph(int i ,int en, Pixel_t col) { - char name[100]; - // TList *l= mgAdcs->GetListOfGraphs(); - sprintf(name,"adc%d",i); - - // TList *l= adcStack->GetHists(); - TH1F *h=adcHisto[i];//(TH1F*)l->At(i);; - TH1F *h1=countsHisto[i];//(TH1F*)(countsStack->GetHists()->At(i)); - if (en) { - plotFlag[i]=1; - h->SetLineColor(TColor::GetColor(col)); - h1->SetLineColor(TColor::GetColor(col)); - h1->SetFillColor(TColor::GetColor(col)); - - if (adcStack->GetHists()) - // if (adcStack->GetHists()->GetEntries()) - if (adcStack->GetHists()->Contains(h)==0) - adcStack->Add(h); - - if (countsStack->GetHists()) - if (countsStack->GetHists()->Contains(h1)==0) - countsStack->Add(h1); - - cout << "Enable plot " << i << " color " << col << endl; - } else { - cout << "Disable plot " << i << endl; - plotFlag[i]=0; - if (adcStack->GetHists()) - // if (adcStack->GetHists()->GetEntries()) - if (adcStack->GetHists()->Contains(h)) - adcStack->RecursiveRemove(h); - if (countsStack->GetHists()) - if (countsStack->GetHists()->Contains(h1)) - countsStack->RecursiveRemove(h1); - } - cout << countsStack->GetHists()->GetEntries() << endl; - - cout << "Number of histos " << adcStack->GetHists()->GetEntries() << endl; - - changePlot(); - - // globalPlot=0; - // for (int ii=0; iiGetListOfGraphs(); - sprintf(name,"bit%d",i); - // TList *l= adcStack->GetHists(); - TH1F *h=bitHisto[i];//(TH1F*)l->At(i);; - if (en) { - //cout<< "enabling plot of bit "<SetLineColor(TColor::GetColor(col)); - if (bitStack->GetHists()) - //if (bitStack->GetHists()->GetEntries()) - if (bitStack->GetHists()->Contains(h)==0) - bitStack->Add(h); - - - cout << "Enable bit plot " << i << " color " << col << endl; - } else { - cout << "Disable bit plot " << i << endl; - bitPlotFlag[i]=0; - if (bitStack->GetHists()) - // if (bitStack->GetHists()->GetEntries()) - if (bitStack->GetHists()->Contains(h)) - bitStack->RecursiveRemove(h); - } - cout << "Number of histos " << bitStack->GetHists()->GetEntries() << endl; - - changePlot(); - - float off=0; - for (int ii=0; ii(1.5); - cout << "bit " << ii << " offset " << bitOffset[ii] << endl; - } - } - - // globalPlot=0; - // for (int ii=0; iisetFilePath(eOutdir->GetText()); - } CATCH_DISPLAY ("Could not set file path", "ctbAcquisition::setOutdir") -} - -void ctbAcquisition::setFname() { - try { - myDet->setFileNamePrefix(eFname->GetText()); - } CATCH_DISPLAY ("Could not set file name prefix", "ctbAcquisition::setFname") -} - -void ctbAcquisition::setFindex() { - try { - myDet->setAcquisitionIndex(eFindex->GetNumber()); - } CATCH_DISPLAY ("Could not set acquisition index", "ctbAcquisition::setFindex") -} - -void ctbAcquisition::setFsave(Bool_t b) { - try { - myDet->setFileWrite(b); - eFname->SetState(b); - eOutdir->SetState(b); - } CATCH_DISPLAY ("Could not set file write", "ctbAcquisition::setFsave") -} - -void ctbAcquisition::update() { - try { - auto retval = myDet->getFileNamePrefix().tsquash("Different values"); - eFname->SetText(retval.c_str()); - } CATCH_DISPLAY ("Could not get file name prefix", "ctbAcquisition::update") - - try { - auto retval = myDet->getAcquisitionIndex().tsquash("Different values"); - eFindex->SetNumber(retval); - } CATCH_DISPLAY ("Could not get acquisition index", "ctbAcquisition::update") - - try { - auto retval = myDet->getFileWrite().tsquash("Different values"); - cFileSave->SetOn(retval); - } CATCH_DISPLAY ("Could not get file write", "ctbAcquisition::update") - - eFname->SetState(cFileSave->IsOn()); - eOutdir->SetState(cFileSave->IsOn()); - eFindex->SetState(cFileSave->IsOn()); - - try { - auto retval = myDet->getNumberOfAnalogSamples().tsquash("Different values"); - setAnalogSamples(retval); - } CATCH_DISPLAY ("Could not get number of analog samples", "ctbAcquisition::update") - - try { - auto retval = myDet->getNumberOfDigitalSamples().tsquash("Different values"); - setDigitalSamples(retval); - } CATCH_DISPLAY ("Could not get number of digital samples", "ctbAcquisition::update") - - try { - roMode = static_cast(myDet->getReadoutMode().tsquash("Different values")); - setReadoutMode(roMode); - } CATCH_DISPLAY ("Could not get readout mode", "ctbAcquisition::update") - - updateChans(); - - if (dataStructure) { - cout << cbDetType->GetSelected()<< endl; - // if (cbDetType->GetSelected()==MYTHEN301 || cbDetType->GetSelected()==MYTHEN302){ - // cout << "settings deserialiation parameters for MYTHEN" << endl; - // mythen3_01_jctbData* ms=(mythen3_01_jctbData*)dataStructure; - // eSerOff->SetNumber( ms->setSerialOffset(-1)); - // eDynRange->SetNumber( ms->setDynamicRange(-1)); - // eNumCount->SetNumber( ms->setNumberOfCounters(-1)); - // } - - } - - if (myDet->getDetectorType().squash() == slsDetectorDefs::MOENCH) { - dBitOffset = 0; - } else { - try { - dBitOffset = myDet->getRxDbitOffset().tsquash("Different values"); - } CATCH_DISPLAY ("Could not get receiver dbit offset", "ctbAcquisition::update") - } - try { - tenG = myDet->getTenGiga().tsquash("Different values"); - } CATCH_DISPLAY ("Could not get ten giga enable", "ctbAcquisition::update") - - // char aargs[10][100]; - // char *args[10]; - // for (int i=0; i<10; i++) - // args[i]=aargs[i]; - - // string retval; - // sprintf(args[0],"adcdisable"); - // slsDetectorCommand *cmd=new slsDetectorCommand(myDet); - // retval=cmd->executeLine(1,args,slsDetectorDefs::GET_ACTION); - // delete cmd; - // int mask; - // sscanf(retval.c_str(),"adcdisable %d",&mask); - // for (int i=0; iGetState()==1 || acqThread->GetState()==6) { - - - if (cCompile->IsOn()) { - sprintf(fname,"%s %s",patternCompiler,patternFile); - cout << "Compile: " << fname << endl; - strcpy(currdir,gSystem->pwd()); - - std::size_t found = string(patternCompiler).rfind('/'); - if (found!=std::string::npos) - gSystem->cd(string(patternCompiler).substr(0,found).c_str()); - - gSystem->cd(cdir); - system(fname); - gSystem->cd(currdir); - } - - if (string(patternCompiler).rfind(".pat")!=std::string::npos) - strcpy(fname,patternFile); - else if (string(patternCompiler).rfind(".npat")!=std::string::npos) - strcpy(fname,patternFile); - else - sprintf(fname,"%sat",patternFile); - - cout << "Load: " << fname << endl; - try { - myDet->loadParameters(fname); - } CATCH_DISPLAY ("Could not load parameters", "ctbAcquisition::loadPattern") - } -} - - -void ctbAcquisition::toggleAcquisition() { - - - if (acqThread->GetState()==1 || acqThread->GetState()==6) { - /** update all infos useful for the acquisition! */ - - try { - auto retval = myDet->getNumberOfAnalogSamples().tsquash("Different values"); - setAnalogSamples(retval); - } CATCH_DISPLAY ("Could not get number of analog samples", "ctbAcquisition::toggleAcquisition") - - try { - auto retval = myDet->getNumberOfDigitalSamples().tsquash("Different values"); - setDigitalSamples(retval); - } CATCH_DISPLAY ("Could not get number of digital samples", "ctbAcquisition::toggleAcquisition") - - if (myDet->getDetectorType().squash() == slsDetectorDefs::MOENCH) { - dBitOffset = 0; - } else { - try { - dBitOffset = myDet->getRxDbitOffset().tsquash("Different values"); - } CATCH_DISPLAY ("Could not get receiver dbit offset", "ctbAcquisition::toggleAcquisition") - } - - try { - roMode = static_cast(myDet->getReadoutMode().tsquash("Different values")); - setReadoutMode(roMode); - } CATCH_DISPLAY ("Could not get readout mode", "ctbAcquisition::toggleAcquisition") - - - cout << "Run" << endl; - bStatus->SetText("Stop"); - ip=0; - for (int i=0; iGetListOfFunctions()) - adcHisto[i]->GetListOfFunctions()->Delete(); - - adcHisto[i]->Reset(); - - if (countsHisto[i]->GetListOfFunctions()) - countsHisto[i]->GetListOfFunctions()->Delete(); - countsHisto[i]->Reset(); - // ((TH1F*)adcStack->GetHists()->At(i))->Reset(); - // ((TH1F*)countsStack->GetHists()->At(i))->Reset(); - } - for (int i=0; iReset(); - } - cout << "reset 2d an" << endl;; - if (h2DMapAn) h2DMapAn->Reset(); - cout << "reset 2d dig" << endl;; - if (h2DMapDig) h2DMapDig->Reset(); - cout << "reset 1d" << endl;; - if (h1DMap) h1DMap->Reset(); - cout << "done" << endl;; - // if (h2Scan) h2Scan->Reset(); - // cout << "reset 1d" << endl;; - // if (rbWaveform->IsOn()) -// adcStack->Draw("NOSTACK"); -// else if (rbDistribution->IsOn()) -// countsStack->Draw("NOSTACK"); -// else if (rb2D->IsOn()) -// h2DMap->Draw("colz"); - - // cout << "timer" << endl; - changePlot(); - - // plotTimer->TurnOn(); - // cout << "thread" << endl; - acqThread->Run(); - StopFlag=0; - - - - - - } else { - StopFlag=1; - try{ - myDet->stopDetector(); - } CATCH_DISPLAY ("Could not stop acquisition", "ctbAcquisition::toggleAcquisition") - stop=1; - bStatus->SetText("Start"); - // acqThread->Kill(); - } -} - -void ctbAcquisition::acquisitionFinished() { - bStatus->SetText("Start"); - cout << "finished " << endl; - // plotTimer->TurnOff(); - Draw(); -} - -void ctbAcquisition::startAcquisition(){ - cout << "Detector started " <GetNumber()<< endl; - stop=0; - - try { - tenG = myDet->getTenGiga().tsquash("Different values"); - } CATCH_DISPLAY ("Could not get ten giga enable", "ctbAcquisition::startAcquisition") - - for (int im=0; imGetNumber(); im++) { - try { - myDet->acquire(); - } CATCH_DISPLAY ("Could not acquire", "ctbAcquisition::startAcquisition") - - cout << im << endl; - if (stop) - break; - } -} - -void* ctbAcquisition::ThreadHandle(void *arg) -{ - ctbAcquisition *acq = static_cast(arg); - - acq->startAcquisition(); - acq->acquisitionFinished(); - - return nullptr; -} - - void ctbAcquisition::progressCallback(double f,void* arg) { - - - // ctbAcquisition *acq = static_cast(arg); - - - cout << "*********" << f << "*******" << endl; - - - - - } - -void ctbAcquisition::setPatternFile(const char* t) { - - - cout << "New pattern is " << t << endl; - - strcpy(patternFile,t); - } - -void ctbAcquisition::setPatternCompiler(const char* t) { - - - cout << "New compiler is " << t << endl; - strcpy(patternCompiler,t); - - } -void ctbAcquisition::setMeasurements() { - -} - -void ctbAcquisition::setAnalogSamples(int n) { - - - cout<< "Set number of analog samples to " << dec<< n << endl; - if (n>0 && n<8192) - nAnalogSamples=n; - - // TList *l= adcStack->GetHists(); - TH1 *h; - // if (l) { - for (int i=0; iAt(i); - if (h) { - - h->SetBins(nAnalogSamples,0,nAnalogSamples); - } - } - - h=adcStack->GetHistogram(); - if (h) - h->SetBins(nAnalogSamples,0,nAnalogSamples); -} - - - -void ctbAcquisition::setDigitalSamples(int n) { - - - cout<< "Set number of digital samples to " << dec<< n << endl; - if (n>0 && n<8192) - nDigitalSamples=n; - - TH1 *h; - for (int i=0; iAt(i); - if (h) { - - h->SetBins(nDigitalSamples,0,nDigitalSamples); - } - - } - // cout<< "histos resized " << dec<< h->GetNbinsX() << endl; - - h=bitStack->GetHistogram(); - if (h) - h->SetBins(nDigitalSamples,0,nDigitalSamples); - -} - -void ctbAcquisition::setReadoutMode(int f) { - - roMode=f; - slsDetectorDefs::readoutMode flag=(slsDetectorDefs::readoutMode)f; - if (flag == slsDetectorDefs::DIGITAL_ONLY) { - nAnalogSamples=0; - adclist.clear(); - } else if (flag ==slsDetectorDefs::ANALOG_AND_DIGITAL) { - ; - } - else { - nDigitalSamples=0; - dbitlist.clear(); - } - - // for (int i=0; igetDetectorType().squash() == slsDetectorDefs::MOENCH) { - dbitlist.clear(); - } else { - try { - auto retval = myDet->getRxDbitList().tsquash("Different values"); - dbitlist.clear(); - if (!retval.empty()) { - for (const auto &value : retval) - dbitlist.push_back(value); - } - } CATCH_DISPLAY ("Could not get receiver dbit list.", "ctbAcquisition::updateChans") - } - - // adc mask - try { - auto retval = myDet->getADCEnableMask().tsquash("Different values"); - adclist.clear(); - if (retval!=0xffffffff) { - for (int i=0; inewDataSet(); - }; - -} - -void ctbAcquisition::ToggleCommonMode(Bool_t b) { - if (photonFinder) { - if (b) { - photonFinder->setCommonModeSubtraction(commonMode); - cmSub=1; - cout << "Enable common mode" << endl; - } else { - photonFinder->setCommonModeSubtraction(NULL); - cmSub=0; - cout << "Disable common mode" << endl; - } - } - -} - - -void ctbAcquisition::TogglePedSub(Bool_t b) { - if (b) { - ChangeHistoLimitsPedSub(); - } else { - ChangeHistoLimitsRaw(); - } - -} - - -void ctbAcquisition::FitADC() { - int iadc=eFitADC->GetNumber(); - if (iadc<0 || iadc>=NADCS) return; - cout << "fit panel for adc " << eFitADC->GetNumber() << endl; - if (rbWaveform->IsOn()) { - if (adcHisto[iadc]==NULL) return; - new TCanvas("Cadcfit"); - if (adcFit) { - delete adcFit; - adcFit=NULL; - } - adcFit=(TH1F*)(adcHisto[iadc]->Clone("adcfit")); - adcFit->Draw(); - adcFit->FitPanel(); - - } else if (rbDistribution->IsOn()) { - if (countsHisto[iadc]==NULL) return; - new TCanvas("Ccountsfit"); - - if (countsFit) { - delete countsFit; - countsFit=NULL; - } - - countsFit=(TH1F*)(countsHisto[iadc]->Clone("countsfit")); - countsFit->Draw(); - countsFit->FitPanel(); - } -} - - -void ctbAcquisition::plotBit() { - int iadc=eBitPlot->GetNumber(); - if (iadc<0 || iadc>=NSIGNALS) return; - cout << "plot panel for bit " << eBitPlot->GetNumber() << endl; - if (bitHisto[iadc]==NULL) return; - new TCanvas("Cbitplot"); - if (bitPlot) { - delete bitPlot; - bitPlot=NULL; - } - bitPlot=(TH1F*)(bitHisto[iadc]->Clone("bitplot")); - bitPlot->Draw(); -} - - - - - - - - - - -void ctbAcquisition::ChangeSerialOffset(Long_t a){ - ChangeSerialOffset(); -}; - - -void ctbAcquisition::ChangeDynamicRange(Long_t a){ - ChangeDynamicRange(); -}; - -void ctbAcquisition::ChangeNumberOfChannels(Long_t a){ - ChangeNumberOfChannels(); -}; - - - -void ctbAcquisition::ChangeSerialOffset(){ - changeDetector(); - // if (dataStructure) { - - // cout << cbDetType->GetSelected()<< endl; - // if (cbDetType->GetSelected()==MYTHEN301 || cbDetType->GetSelected()==MYTHEN302 ){ - // cout << "settings offsets for MYTHEN" << endl; - // mythen3_01_jctbData* ms=(mythen3_01_jctbData*)dataStructure; - // ms->setSerialOffset(eSerOff->GetIntNumber()); - - // } - // } -}; - - -void ctbAcquisition::ChangeDynamicRange(){ - changeDetector(); - // if (dataStructure) { - - // cout << cbDetType->GetSelected()<< endl; - // if (cbDetType->GetSelected()==MYTHEN301 || cbDetType->GetSelected()==MYTHEN302){ - // cout << "settings dynamic range for MYTHEN" << endl; - // mythen3_01_jctbData* ms=(mythen3_01_jctbData*)dataStructure; - // ms->setDynamicRange(eDynRange->GetIntNumber()); - - // } - // } -}; - -void ctbAcquisition::ChangeNumberOfChannels(){ - changeDetector(); - // if (dataStructure) { - // cout << cbDetType->GetSelected()<< endl; - // if (cbDetType->GetSelected()==MYTHEN301 || cbDetType->GetSelected()==MYTHEN302){ - // cout << "settings number of channels for MYTHEN" << endl; - // mythen3_01_jctbData* ms=(mythen3_01_jctbData*)dataStructure; - // ms->setNumberOfCounters(eNumCount->GetIntNumber()); - - // } - // } - if (deserializer) - changePlot(); -}; - -void ctbAcquisition::ChangeImagePixels(Long_t a){ - ChangeImagePixels(); -}; - -void ctbAcquisition::ChangeImagePixels(){ - changeDetector(); - // if (dataStructure) { - // cout << cbDetType->GetSelected()<< endl; - // if (cbDetType->GetSelected()==MYTHEN301 || cbDetType->GetSelected()==MYTHEN302){ - // cout << "settings number of channels for MYTHEN" << endl; - // mythen3_01_jctbData* ms=(mythen3_01_jctbData*)dataStructure; - // ms->setNumberOfCounters(eNumCount->GetIntNumber()); - - // } - // } - // if (deserializer) - // changePlot(); -}; - - -void ctbAcquisition::ChangeHistoLimitsPedSub(Long_t a){ - ChangeHistoLimitsPedSub(); -}; - - -void ctbAcquisition::ChangeHistoLimitsRaw(Long_t a){ - ChangeHistoLimitsRaw(); -} - -void ctbAcquisition::ChangeHistoLimitsPedSub(Bool_t a){ - ChangeHistoLimitsPedSub(); -}; - - -void ctbAcquisition::ChangeHistoLimitsRaw(Bool_t a){ - ChangeHistoLimitsRaw(); -} - - -void ctbAcquisition::ChangeHistoLimitsPedSub(){ - - cout << "set Limits ped sub hist " << eMinPedSub->GetNumber() << " " << eMaxPedSub->GetNumber() << endl; - - if (eMinPedSub->GetNumber()>eMaxPedSub->GetNumber()) - return; - - if (cbSubtractPedestal->IsOn()) { - if (cMinMaxPedSub->IsOn()) { - adcStack->SetMaximum( eMaxPedSub->GetNumber()); - adcStack->SetMinimum( eMinPedSub->GetNumber()); - if (h2DMapAn) { - h2DMapAn->SetMaximum( eMaxPedSub->GetNumber()); - h2DMapAn->SetMinimum( eMinPedSub->GetNumber()); - } - if (h1DMap) { - h1DMap->SetMaximum( eMaxPedSub->GetNumber()); - h1DMap->SetMinimum( eMinPedSub->GetNumber()); - } - if (countsStack->GetHistogram()) - countsStack->GetHistogram()->GetXaxis()->SetRangeUser(eMinPedSub->GetNumber(), eMaxPedSub->GetNumber()); - } else { - if (adcStack->GetHistogram()) - adcStack->GetHistogram()->GetYaxis()->UnZoom(); - if (h2DMapAn) { - h2DMapAn->GetZaxis()->UnZoom(); - } - if (h1DMap) { - h1DMap->GetYaxis()->UnZoom(); - } - if (countsStack->GetHistogram()) - countsStack->GetHistogram()->GetXaxis()->UnZoom(); - } - } - - -}; - - -void ctbAcquisition::ChangeHistoLimitsRaw(){ - - cout << "set Limits raw hist " << eMinRaw->GetNumber() << " " << eMaxRaw->GetNumber() << endl; - if (eMinRaw->GetNumber()>eMaxRaw->GetNumber()) - return; - - if (cbSubtractPedestal->IsOn()==0) { - if (cMinMaxRaw->IsOn()) { - adcStack->SetMaximum( eMaxRaw->GetNumber()); - adcStack->SetMinimum( eMinRaw->GetNumber()); - if (h2DMapAn) { - h2DMapAn->SetMaximum( eMaxRaw->GetNumber()); - h2DMapAn->SetMinimum( eMinRaw->GetNumber()); - } - if (h1DMap) { - h1DMap->SetMaximum( eMaxRaw->GetNumber()); - h1DMap->SetMinimum( eMinRaw->GetNumber()); - } - if (countsStack->GetHistogram()) - countsStack->GetHistogram()->GetXaxis()->SetRangeUser(eMinRaw->GetNumber(), eMaxRaw->GetNumber()); - } else { - - if (adcStack->GetHistogram()) - adcStack->GetHistogram()->GetYaxis()->UnZoom(); - if (h2DMapAn) { - h2DMapAn->GetZaxis()->UnZoom(); - } - - if (h1DMap) { - h1DMap->GetYaxis()->UnZoom(); - } - if (countsStack->GetHistogram()) - countsStack->GetHistogram()->GetXaxis()->UnZoom(); - - } - } - -} diff --git a/ctbGui/ctbAcquisition.h b/ctbGui/ctbAcquisition.h deleted file mode 100644 index 3d5524eda..000000000 --- a/ctbGui/ctbAcquisition.h +++ /dev/null @@ -1,248 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package -#ifndef CTBACQUISITION_H -#define CTBACQUISITION_H -#include - -#include "ctbAdcs.h" -#include "ctbSignals.h" -#include "ctbPattern.h" -class TGTextEntry; -class TGLabel; -class TGNumberEntry; -class TGCheckButton; -class TThread; -class TGraph; -class TMultiGraph; -class THStack; -class TGButtonGroup; -class TGRadioButton; -class TGComboBox; -class TTimer; -class TCanvas; -class TH2F; -class TH1F; -class TGLabel; -class TGTextButton; - -namespace sls -{ - class Detector; - class detectorData; -}; - -template class slsDetectorData; - -class singlePhotonDetector; -//class singlePhotonDetector; -class commonModeSubtraction; - -#include -#include -using namespace std; - -class ctbAcquisition : public TGGroupFrame { - - - enum {DESERIALIZER, MOENCH04, MOENCH02, MOENCH03, IMAGE32B, IMAGE16B, ADCSAR2, MYTHEN301, MYTHEN302}; - - - private: - TGTextEntry *eOutdir; - TGTextEntry *eFname; - TGNumberEntry *eFindex; - TGCheckButton *cFileSave; - - - TGNumberEntry *eSerOff; - TGNumberEntry *eDynRange; - TGNumberEntry *eNumCount; - - - TGNumberEntry *ePixX; - TGNumberEntry *ePixY; - - TGNumberEntry *eFitADC; - TGNumberEntry *eBitPlot; - TGNumberEntry *eMinRaw; - TGNumberEntry *eMaxRaw; - TGNumberEntry *eMinPedSub; - TGNumberEntry *eMaxPedSub; - TGCheckButton *cMinMaxRaw; - TGCheckButton *cMinMaxPedSub; - - - - - TGNumberEntry *eMeasurements; - - - - TGTextButton *bStatus; - // TGTextButton - TGCheckButton *cCompile; - TGTextButton *cLoad; - // TGCheckButton *cRun; - - TThread *acqThread; - - - THStack *adcStack; - THStack *bitStack; - THStack *countsStack; - - - TH1F *adcHisto[NADCS]; - TH1F *countsHisto[NADCS]; - - TH1F *bitHisto[NSIGNALS]; - float bitOffset[NSIGNALS]; - - // int enableFlag[NADCS+4]; - int roMode; - - int dBitOffset; - - - - TH1F *adcFit; - TH1F *bitPlot; - TH1F *countsFit; - - - - TH2F *h2DMapAn; // for 2D detectors - TH2F *h2DMapDig; // for 2D detectors - TH1F *h1DMap; //for 1D detectors - - // TH2F *h2Scan; // for 2D detectors - // TMultiGraph *mgAdcs; - // TH1I *plotAdc[NADCS]; - - - sls::Detector* myDet; - - int plotFlag[NADCS]; - int bitPlotFlag[NSIGNALS]; - - int ip; - // int nChannels; - // int chanEnable; - //int nADCs; - - std::vector dbitlist; - std::vector adclist; - - TGButtonGroup *bgPlot;// = new TGVButtonGroup(main_frame); - TGRadioButton *rbPlotOff; - TGRadioButton *rbWaveform; - TGRadioButton *rbDistribution; - TGRadioButton *rb2D; - // TGRadioButton *rbScan; - TGComboBox *cbDetType; - TGCheckButton *cbGetPedestal; - TGCheckButton *cbSubtractPedestal; - TGCheckButton *cbCommonMode; - TGTextButton *bResetPedestal; - - TGLabel *lClickX; - TGLabel *lClickY; - TGLabel *lClickValue; - - - TCanvas *myCanvas; - TTimer *plotTimer; - - char patternFile[10000]; - char patternCompiler[10000]; - - int globalPlot; - int adcPlot; - int dbitPlot; - int tenG; - - int nAnalogSamples, nDigitalSamples; - // int iScanStep; - - slsDetectorData *dataStructure; - singlePhotonDetector *photonFinder; - //singlePhotonDetector *photonFinder; - commonModeSubtraction *commonMode; - int cmSub; - - int stop; - - uint64_t dBitMask; - - int deserializer; - - public: - ctbAcquisition(TGVerticalFrame*, sls::Detector*); - void setOutdir(); - void setFname(); - void setMeasurements(); - void setFsave(Bool_t); - void changePlot(Int_t); - void changeDetector(Int_t); - void changePlot(); - void changeDetector(); - void setFindex(); - void Draw(); - void setCanvas(TCanvas*); - - void toggleAcquisition(); - void loadPattern(); - static void* ThreadHandle(void *arg); - void update(); - void acquisitionFinished(); - // string getParameters(); - - void setGraph (int i ,int en, Pixel_t col); - void setBitGraph (int i ,int en, Pixel_t col); - void startAcquisition(); - static void progressCallback(double,void*); - static void dataCallback(sls::detectorData*, long unsigned int, unsigned int, void*); - int StopFlag; - - int plotData(sls::detectorData*, int); - - void setPatternFile(const char* t); - - void setPatternCompiler(const char* t); - - void setAnalogSamples(int); - void setDigitalSamples(int); - - void setADCEnable(Int_t); - void setDbitEnable(Int_t); - void setReadoutMode(int); - void updateChans(); - - void resetPedestal(); - - void ToggleCommonMode(Bool_t); - void TogglePedSub(Bool_t); - void ChangeHistoLimitsPedSub(Long_t ); - void ChangeHistoLimitsRaw(Long_t); - void ChangeHistoLimitsPedSub( ); - void ChangeHistoLimitsRaw(); - void ChangeHistoLimitsPedSub(Bool_t ); - void ChangeHistoLimitsRaw(Bool_t); - - - void ChangeSerialOffset(); - void ChangeSerialOffset(Long_t); - void ChangeNumberOfChannels(); - void ChangeNumberOfChannels(Long_t); - void ChangeDynamicRange(); - void ChangeDynamicRange(Long_t); - void ChangeImagePixels(); - void ChangeImagePixels(Long_t); - - void canvasClicked(); - void FitADC(); - void plotBit(); - ClassDef(ctbAcquisition,0) -}; - -#endif diff --git a/ctbGui/ctbAdcs.cpp b/ctbGui/ctbAdcs.cpp deleted file mode 100644 index 694f5ff13..000000000 --- a/ctbGui/ctbAdcs.cpp +++ /dev/null @@ -1,616 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "ctbAdcs.h" -#include "ctbDefs.h" -#include "sls/Detector.h" - -using namespace std; - - - -ctbAdc::ctbAdc(TGVerticalFrame *page, int i, sls::Detector *det) - : TGHorizontalFrame(page, 800,800), id(i), myDet(det) { - - TGHorizontalFrame *hframe=this; - char tit[100]; - - page->AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - - - - sprintf(tit, "ADC%d", id); - - sAdcLabel= new TGLabel(hframe, tit); - hframe->AddFrame(sAdcLabel,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - sAdcLabel->MapWindow(); - sAdcLabel->SetTextJustify(kTextLeft); - - - - - - - sAdcInvert= new TGCheckButton(hframe, "Inv"); - hframe->AddFrame( sAdcInvert,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - sAdcInvert->MapWindow(); - sAdcInvert->Connect("Toggled(Bool_t)","ctbAdc",this,"ToggledInvert(Bool_t)"); - - - sAdcEnable= new TGCheckButton(hframe, "En"); - hframe->AddFrame( sAdcEnable,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - sAdcEnable->MapWindow(); - // sAdcEnable->SetOn(kTRUE); - // sAdcEnable->SetEnabled(kFALSE); - sAdcEnable->Connect("Toggled(Bool_t)","ctbAdc",this,"ToggledEnable(Bool_t)"); - - - - sAdcPlot= new TGCheckButton(hframe, "Plot"); - hframe->AddFrame( sAdcPlot,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - sAdcPlot->MapWindow(); - - - sAdcPlot->Connect("Toggled(Bool_t)","ctbAdc",this,"ToggledPlot(Bool_t)"); - - - - fColorSel = new TGColorSelect(hframe, id+1, 0); - - fColorSel->Connect("ColorSelected(Pixel_t)","ctbAdc",this,"ColorChanged(Pixel_t)"); - hframe->AddFrame(fColorSel, new TGLayoutHints(kLHintsTop | - kLHintsLeft, 2, 0, 2, 2)); - - - fColorSel->SetColor(TColor::Number2Pixel(id+1)); - // sprintf(tit,"adc%d",id); -// gADC=new TGraph(); -// gADC->SetName(tit); -// gADC->SetLineColor(id+1); -// gADC->SetMarkerColor(id+1); - - - -}; -Pixel_t ctbAdc::getColor(){ - return fColorSel->GetColor(); -} -Bool_t ctbAdc::getEnabled(){ - return getPlot(); -} -Bool_t ctbAdc::getPlot(){ - return sAdcPlot->IsOn(); -} -Bool_t ctbAdc::getInverted(){ - return sAdcInvert->IsOn(); -} - -Bool_t ctbAdc::getEnable(){ - return sAdcEnable->IsOn(); -} - - - -void ctbAdc::setInverted(Bool_t b){ - // cout << id << "set enabled " << b << endl; - if (b) - sAdcInvert->SetOn(kTRUE,kTRUE); - else - sAdcInvert->SetOn(kFALSE,kTRUE); - -} - - -void ctbAdc::setEnable(Bool_t b){ - // cout << id << "set enabled " << b << endl; - if (b) - sAdcEnable->SetOn(kTRUE,kFALSE); - else - sAdcEnable->SetOn(kFALSE,kFALSE); - -} - - - - - -void ctbAdc::setAdcAlias(char *tit, int plot, int color) { - if (tit) - sAdcLabel->SetText(tit); - if (plot>0) - sAdcPlot->SetOn(kTRUE,kTRUE); - else if (plot==0) - sAdcPlot->SetOn(kFALSE,kTRUE); - if (color>=0) - fColorSel->SetColor(color); - fColorSel->SetEnabled(sAdcPlot->IsOn()); -} - - -string ctbAdc::getAdcAlias() { - - char line[1000]; - sprintf(line,"ADC%d %s %d %lx\n",id,sAdcLabel->GetText()->Data(),sAdcPlot->IsOn(),fColorSel->GetColor()); - return string(line); -} - -void ctbAdc::update() { - - - //Emit("ToggledAdcEnable(Int_t)", id); - -} - - -void ctbAdc::ToggledPlot(Bool_t b){ - - // Long_t mask=b<SetEnabled(kTRUE); - else - fColorSel->SetEnabled(kFALSE); - - // fColorSel->SetEnabled(sAdcPlot->IsOn()); - Emit("ToggledAdcPlot(Int_t)", id); - -} - - - -void ctbAdc::ToggledInvert(Bool_t b){ - - - // fColorSel->SetEnabled(sAdcPlot->IsOn()); - Emit("ToggledAdcInvert(Int_t)", id); - -} - - - -void ctbAdc::ToggledEnable(Bool_t b){ - - - fColorSel->SetEnabled(sAdcPlot->IsOn()); - Emit("ToggledAdcEnable(Int_t)", id); - -} - - - - - - -void ctbAdc::ColorChanged(Pixel_t) { - - Emit("ToggledAdcPlot(Int_t)", id); - -} - -void ctbAdc::ToggledAdcPlot(Int_t b){ - - - Emit("ToggledAdcPlot(Int_t)", id); - -} - -void ctbAdc::ToggledAdcInvert(Int_t b){ - - - Emit("ToggledAdcInvert(Int_t)", id); - -} - -void ctbAdc::ToggledAdcEnable(Int_t b){ - - - Emit("ToggledAdcEnable(Int_t)", id); - -} - - - - -void ctbAdc::setEnabled(Bool_t b){ - // cout << id << "set enabled " << b << endl; - if (b) - sAdcPlot->SetOn(kTRUE,kFALSE); - else - sAdcPlot->SetOn(kFALSE,kFALSE); - -} - - - -void ctbAdc::setPlot(Bool_t b){ - // cout << id << "set enabled " << b << endl; - if (b) - sAdcPlot->SetOn(kTRUE,kTRUE); - else - sAdcPlot->SetOn(kFALSE,kTRUE); - - -} - - - - - - - -ctbAdcs::ctbAdcs(TGVerticalFrame *page, sls::Detector *det) - : TGGroupFrame(page,"Adcs",kVerticalFrame), myDet(det) { - - - SetTitlePos(TGGroupFrame::kLeft); - page->AddFrame(this,new TGLayoutHints( kLHintsTop | kLHintsExpandX , 10,10,10,10)); - MapWindow(); - - - - TGHorizontalFrame* hframe=new TGHorizontalFrame(this, 800,800); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - - - int idac=0; - - - - - - TGHorizontalFrame* hhframe=new TGHorizontalFrame(this, 800,800); - AddFrame(hhframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hhframe->MapWindow(); - - TGVerticalFrame *vframe; - - - - - for (idac=0; idacAddFrame(vframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - vframe->MapWindow(); - - - - } - - sAdc[idac]=new ctbAdc(vframe,idac,myDet); - - - sAdc[idac]->Connect("ToggledAdcPlot(Int_t)","ctbAdcs",this,"ToggledAdcPlot(Int_t)"); - sAdc[idac]->Connect("ToggledAdcInvert(Int_t)","ctbAdcs",this,"ToggledAdcInvert(Int_t)"); - sAdc[idac]->Connect("ToggledAdcEnable(Int_t)","ctbAdcs",this,"ToggledAdcEnable(Int_t)"); - - } - - hframe=new TGHorizontalFrame(this, 800,800); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - bCheckHalf[0]=new TGTextButton(hframe, "All 0-15"); - hframe->AddFrame(bCheckHalf[0],new TGLayoutHints(kLHintsTop | kLHintsExpandX, 5, 5, 5, 5)); - bCheckHalf[0]->MapWindow(); - bCheckHalf[0]->Connect("Clicked()","ctbAdcs",this,"CheckHalf0()"); - - - bRemoveHalf[0]=new TGTextButton(hframe, "None 0-15"); - hframe->AddFrame(bRemoveHalf[0],new TGLayoutHints(kLHintsBottom | kLHintsExpandX, 5, 5, 5, 5)); - bRemoveHalf[0]->MapWindow(); - bRemoveHalf[0]->Connect("Clicked()","ctbAdcs",this,"RemoveHalf0()"); - - - bCheckHalf[1]=new TGTextButton(hframe, "All 16-23"); - hframe->AddFrame(bCheckHalf[1],new TGLayoutHints(kLHintsTop | kLHintsExpandX, 5, 5, 5, 5)); - bCheckHalf[1]->MapWindow(); - bCheckHalf[1]->Connect("Clicked()","ctbAdcs",this,"CheckHalf1()"); - // bCheckAll->Connect("Clicked()","ctbAdcs",this,"CheckAll()"); - - - bRemoveHalf[1]=new TGTextButton(hframe, "None 16-23"); - hframe->AddFrame(bRemoveHalf[1],new TGLayoutHints(kLHintsBottom | kLHintsExpandX, 5, 5, 5, 5)); - bRemoveHalf[1]->MapWindow(); - bRemoveHalf[1]->Connect("Clicked()","ctbAdcs",this,"RemoveHalf1()"); - // bRemoveAll->Connect("Clicked()","ctbAdcs",this,"RemoveAll()"); - - - - - hframe=new TGHorizontalFrame(this, 800,800); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - bCheckAll=new TGTextButton(hframe, "All"); - hframe->AddFrame(bCheckAll,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 5, 5, 5, 5)); - bCheckAll->MapWindow(); - bCheckAll->Connect("Clicked()","ctbAdcs",this,"CheckAll()"); - - - bRemoveAll=new TGTextButton(hframe, "None"); - hframe->AddFrame(bRemoveAll,new TGLayoutHints(kLHintsBottom | kLHintsExpandX, 5, 5, 5, 5)); - bRemoveAll->MapWindow(); - bRemoveAll->Connect("Clicked()","ctbAdcs",this,"RemoveAll()"); - - - - hframe=new TGHorizontalFrame(this, 800,50); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - TGLabel *label= new TGLabel(hframe, "Inversion mask: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - eInversionMask = new TGNumberEntry(hframe, 0, 16,999, TGNumberFormat::kNESHex, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELNoLimits); - - hframe->AddFrame(eInversionMask,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eInversionMask->MapWindow(); - eInversionMask->Resize(150,30); - eInversionMask->SetState(kFALSE); - - - hframe=new TGHorizontalFrame(this, 800,50); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - label= new TGLabel(hframe, "Enable mask: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - eEnableMask = new TGNumberEntry(hframe, 0, 16,999, TGNumberFormat::kNESHex, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELNoLimits); - - hframe->AddFrame(eEnableMask,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eEnableMask->MapWindow(); - eEnableMask->Resize(150,30); - eEnableMask->SetState(kFALSE); - -} - - -int ctbAdcs::setEnable(int reg) { - - try { - if (reg > -1) { - myDet->setADCEnableMask(reg); - } - auto retval = myDet->getADCEnableMask().tsquash("Different values"); - eEnableMask->SetHexNumber(retval); - return retval; - } CATCH_DISPLAY ("Could not set/get adc enablemask.", "ctbAdcs::setEnable") - - return -1; -} - -int ctbAdcs::setInvert(int reg) { - - try { - if (reg > -1) { - myDet->setADCInvert(reg); - } - auto retval = myDet->getADCInvert().tsquash("Different values"); - eInversionMask->SetHexNumber(retval); - return retval; - } CATCH_DISPLAY ("Could not set/get adc enablemask.", "ctbAdcs::setEnable") - - return -1; -} - - - -void ctbAdcs::update() { - Int_t invreg; - Int_t disreg; - - disreg=setEnable(); - invreg=setInvert(); - - for (int is=0; issetAdcAlias(NULL,-1,-1); - if (invreg & (1<setInverted(kTRUE); - else - sAdc[is]->setInverted(kFALSE); - - if (disreg & (1<setEnable(kTRUE); - else - sAdc[is]->setEnable(kFALSE); - } - - Emit("AdcEnable(Int_t)", disreg); - -} -string ctbAdcs::getAdcParameters() { - ostringstream line; - line << "reg "<< hex << setInvert() << "# ADC invert reg" << dec << endl; - line << "reg "<< hex << setEnable() << " # ADC enable reg"<< dec << endl; - return line.str(); - -} - - -void ctbAdcs::CheckAll() { - for (int is=0; issetPlot(kTRUE); - } -} - - -void ctbAdcs::RemoveAll() { - for (int is=0; issetPlot(kFALSE); - } -} - - - -void ctbAdcs::CheckHalf0() { - for (int is=0; issetPlot(kTRUE); - } -} - - -void ctbAdcs::RemoveHalf0() { - for (int is=0; issetPlot(kFALSE); - } -} - -void ctbAdcs::CheckHalf1() { - for (int is=NADCS/2; issetPlot(kTRUE); - } -} - - -void ctbAdcs::RemoveHalf1() { - for (int is=NADCS/2; issetPlot(kFALSE); - } -} - - -int ctbAdcs::setAdcAlias(string line) { - - int is=-1, plot=0, color=-1; - char tit[100]; - int narg=sscanf(line.c_str(),"ADC%d %s %d %x",&is,tit,&plot, &color); - if (narg<2) - return -1; - if (narg!=3) - color=-1; - if (is>=0 && issetAdcAlias(tit,plot,color); - } - return is; - -} - -string ctbAdcs::getAdcAlias() { - - ostringstream line; - - for (int is=0; isgetAdcAlias(); - - return line.str(); -} - - -void ctbAdcs::ToggledAdcPlot(Int_t b){ - - - Emit("ToggledAdcPlot(Int_t)", b); - -} - -void ctbAdcs::AdcEnable(Int_t b){ - Emit("AdcEnable(Int_t)", b); -} - - -void ctbAdcs::ToggledAdcEnable(Int_t b){ - - Int_t oreg=setEnable(); - Int_t m=1<getEnable()) - oreg|=m; - else - oreg&=~m; - - setEnable(oreg); - - Emit("AdcEnable(Int_t)", oreg); -} - - -void ctbAdcs::ToggledAdcInvert(Int_t b){ - - Int_t oreg=setInvert(); - Int_t m=1<getInverted()) - oreg|=m; - else - oreg&=~m; - - setInvert(oreg); -} - - - - - -Pixel_t ctbAdcs::getColor(int i){ - if (i>=0 && igetColor(); - return static_cast(-1); -} - -Bool_t ctbAdcs::getEnabled(int i){ - if (i>=0 && igetEnabled(); - return static_cast(-1); -} - -Bool_t ctbAdcs::getEnable(int i){ - if (i>=0 && igetEnable(); - return static_cast(-1); -} - -Bool_t ctbAdcs::getPlot(int i){ - if (i>=0 && igetPlot(); - return static_cast(-1); -} diff --git a/ctbGui/ctbAdcs.h b/ctbGui/ctbAdcs.h deleted file mode 100644 index 3a4cf35da..000000000 --- a/ctbGui/ctbAdcs.h +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package - - - -#ifndef CTBADCS_H -#define CTBADCS_H -#include - - -#define NADCS 32 - -class TRootEmbeddedCanvas; -class TGButtonGroup; -class TGVerticalFrame; -class TGHorizontalFrame; -class TGTextEntry; -class TGLabel; -class TGNumberEntry; -class TH2F; -class TGComboBox; -class TGCheckButton; -class TGColorSelect; -class TColor; - -class THStack; -class TGraphErrors; -class TGTextButton; -class TGTab; - -class TGraph; - -namespace sls -{ - class Detector; -}; - -#include -using namespace std; - -class ctbAdc : public TGHorizontalFrame { - - - private: - - - TGLabel *sAdcLabel; - TGCheckButton *sAdcEnable; - TGCheckButton *sAdcPlot; - TGCheckButton *sAdcInvert; - - TGColorSelect *fColorSel; - - // TGraph *gADC; - - int id; - sls::Detector *myDet; - - public: - ctbAdc(TGVerticalFrame *page, int i, sls::Detector *det); - - - void setAdcAlias(char *tit, int plot, int color); - string getAdcAlias(); - void ToggledAdcPlot(Int_t b); - void ToggledAdcEnable(Int_t b); - void ToggledAdcInvert(Int_t b); - - - void ToggledPlot(Bool_t b); - void ToggledEnable(Bool_t b); - void ToggledInvert(Bool_t b); - void ColorChanged(Pixel_t); - void setEnabled(Bool_t b); - Bool_t getEnabled(); - // TGraph *getGraph(); - void update(); - - Pixel_t getColor(); - - Bool_t getEnable(); - void setEnable(Bool_t); - void setPlot(Bool_t); - Bool_t getInverted(); - Bool_t getPlot(); - void setInverted(Bool_t); - - ClassDef(ctbAdc,0) - }; - - - -class ctbAdcs : public TGGroupFrame { -private: - - ctbAdc *sAdc[NADCS]; - sls::Detector *myDet; - - - TGTextButton *bCheckAll; - TGTextButton *bRemoveAll; - TGTextButton *bCheckHalf[2]; - TGTextButton *bRemoveHalf[2]; - TGNumberEntry *eInversionMask; - TGNumberEntry *eEnableMask; - - -/* TGTextButton *bPlotSelected; */ -/* TGNumberEntry *eMinX; */ -/* TGNumberEntry *eMaxX; */ -/* TGNumberEntry *eMinY; */ -/* TGNumberEntry *eMaxY; */ - - - -/* TGTextButton *bGetPixel; */ -/* TGNumberEntry *ePixelX; */ -/* TGNumberEntry *ePixelY; */ -/* TGLabel *lPixelValue; */ - -public: - - ctbAdcs(TGVerticalFrame *page, sls::Detector *det); - int setAdcAlias(string line); - string getAdcAlias(); - string getAdcParameters(); - void ToggledAdcPlot(Int_t); - void ToggledAdcInvert(Int_t); - void ToggledAdcEnable(Int_t); - void AdcEnable(Int_t b); - // TGraph *getGraph(int i); - void CheckAll(); - void RemoveAll(); - void update(); - - int setInvert(int reg=-1); - int setEnable(int reg=-1); - - - Pixel_t getColor(int i); - Bool_t getEnabled(int i); - Bool_t getPlot(int i); - Bool_t getEnable(int i); - - void CheckHalf0(); - void RemoveHalf0(); - - void CheckHalf1(); - void RemoveHalf1(); - - - ClassDef(ctbAdcs,0) -}; - -#endif - - diff --git a/ctbGui/ctbDacs.cpp b/ctbGui/ctbDacs.cpp deleted file mode 100644 index dd2763fdf..000000000 --- a/ctbGui/ctbDacs.cpp +++ /dev/null @@ -1,235 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package - -#include -#include -#include - -#include -#include -#include -#include - -#include "ctbDacs.h" -#include "ctbDefs.h" -#include "sls/Detector.h" -#include "sls/sls_detector_defs.h" - -using namespace std; - - -ctbDac::ctbDac(TGGroupFrame *page, int idac, sls::Detector *det) : TGHorizontalFrame(page, 800,50) , id(idac), myDet(det) { - - TGHorizontalFrame *hframe=this; - - page->AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - MapWindow(); - - char tit[100]; - - - sprintf(tit, "DAC %d:",idac); - - dacsLabel= new TGCheckButton(hframe, tit);// new TGLabel(hframe, tit); - dacsLabel->SetOn(kTRUE, kTRUE); - - dacsLabel->Connect("Toggled(Bool_t)","ctbDac",this,"setOn(Bool_t)"); - - - hframe->AddFrame(dacsLabel,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - dacsLabel->MapWindow(); - dacsLabel->SetTextJustify(kTextLeft); - - - dacsEntry = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax, - 0, 65535); - - hframe->AddFrame(dacsEntry,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 5, 5, 5, 5)); - dacsEntry->MapWindow(); - dacsEntry->Resize(150,30); - - - dacsUnit= new TGCheckButton(hframe, "mV"); - // if (idac!=slsDetectorDefs::ADC_VPP) { - hframe->AddFrame( dacsUnit,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - dacsUnit->MapWindow(); - if (idac==slsDetectorDefs::ADC_VPP) { - dacsUnit->SetEnabled(kFALSE); - hframe->HideFrame(dacsUnit); - dacsUnit->MapWindow(); - cout << "hiding!" << endl; - } - if (idac==slsDetectorDefs::HIGH_VOLTAGE) { - dacsUnit->SetText("V"); - dacsUnit->SetOn(kTRUE,kTRUE); - dacsUnit->SetEnabled(kFALSE); - } - //} - - - - sprintf(tit, "xxx"); - dacsValue= new TGLabel(hframe, tit); - hframe->AddFrame( dacsValue,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - dacsValue->MapWindow(); - dacsValue->SetTextJustify(kTextLeft); - - TGTextEntry *e=dacsEntry->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbDac",this,"setValue()"); - // e->Connect("ValueSet(Long_t)","ctbDac",this,"setValue(Long_t)"); - dacsEntry->Connect("ValueSet(Long_t)","ctbDac",this,"setValue(Long_t)"); - // cout << "(((((((((((((((((((((((((((((((" << dacsEntry->GetListOfSignals()->At(0)->IsA() << endl; - - -} - - - -int ctbDac::setLabel(char *tit, int mv) { - if(tit) - dacsLabel->SetText(tit); - if (mv==1) - dacsUnit->SetOn(kTRUE,kTRUE); - else if (mv==0) - dacsUnit->SetOn(kFALSE,kTRUE); - // else if (mv==2) { - // ;} - // else if (mv==3) - // ; - return id; - -} - -string ctbDac::getLabel() { - ostringstream line; - line << dacsLabel->GetText() << " " << dacsUnit->IsOn() << endl; - // line << "DAC" << dec << id << " " << dacsUnit->IsOn() << endl; - return line.str(); - -} - -int ctbDac::getMoenchDacId() { - slsDetectorDefs::dacIndex moenchDacIndices[8] = {slsDetectorDefs::VBP_COLBUF, slsDetectorDefs::VIPRE, slsDetectorDefs::VIN_CM, slsDetectorDefs::VB_SDA, slsDetectorDefs::VCASC_SFP, slsDetectorDefs::VOUT_CM, slsDetectorDefs::VIPRE_CDS, slsDetectorDefs::IBIAS_SFP}; - - if (id >= 8) { - return id; - } - return static_cast(moenchDacIndices[id]); -} - -void ctbDac::setValue(Long_t a) {setValue();} - -void ctbDac::setValue() { - cout << "setting dac! "<< id << " value " << dacsEntry->GetIntNumber() << " units " << dacsUnit->IsOn() << endl; - - try { - int sid = id; - if (myDet->getDetectorType().squash() == slsDetectorDefs::MOENCH) { - sid = getMoenchDacId(); - } - myDet->setDAC(static_cast(sid), dacsEntry->GetIntNumber(), dacsUnit->IsOn()); - } CATCH_DISPLAY ("Could not set dac " + to_string(id) + ".", "ctbDac::setValue") - - getValue(); -} - -void ctbDac::setOn(Bool_t b) { - // cout << "setting dac! "<< id << endl; - if ( dacsLabel->IsOn()) { - setValue(); - } else { - try { - int sid = id; - if (myDet->getDetectorType().squash() == slsDetectorDefs::MOENCH) { - sid = getMoenchDacId(); - } - myDet->setDAC(static_cast(sid), -100, false); - } CATCH_DISPLAY ("Could not power off dac " + to_string(id) + ".", "ctbDac::setOn") - } - getValue(); -} - -int ctbDac::getValue() { - try { - int sid = id; - if (myDet->getDetectorType().squash() == slsDetectorDefs::MOENCH) { - sid = getMoenchDacId(); - } - int val = myDet->getDAC(static_cast(sid), dacsUnit->IsOn()).tsquash("Different values"); - cout << "dac " << id << " " << val << endl; - dacsValue->SetText(to_string(val).c_str()); - if (val >= 0) { - dacsLabel->SetOn(kTRUE); - } else { - dacsLabel->SetOn(kFALSE); - } - return val; - } CATCH_DISPLAY ("Could not get dac " + to_string(id) + ".", "ctbDac::getValue") - - return -1; -} - - -ctbDacs::ctbDacs(TGVerticalFrame *page, sls::Detector *det) : TGGroupFrame(page,"DACs",kVerticalFrame) , myDet(det){ - - SetTitlePos(TGGroupFrame::kLeft); - page->AddFrame(this,new TGLayoutHints( kLHintsTop | kLHintsExpandX , 10,10,10,10)); - MapWindow(); - - // cout << "window mapped " << endl; - - for (int idac=0; idacsetLabel((char*)"ADC Vpp",2); - dacs[NDACS+1]->setLabel((char*)"High Voltage",3); -} - - -int ctbDacs::setDacAlias(string line) { - int is=-1, mv=0; - char tit[100]; - int narg=sscanf(line.c_str(),"DAC%d %s %d",&is,tit,&mv); - if (narg<2) - return -1; - if (is>=0 && issetLabel(tit,mv); - return is; - -} - -string ctbDacs::getDacAlias() { - ostringstream line; - - for (int i=0; igetLabel() << endl; - return line.str(); -} - - - - - -string ctbDacs::getDacParameters() { - ostringstream line; - - for (int i=0; igetValue << endl; - line << "dac:" << i << " " << dacs[i]->getValue() << endl; - } - return line.str(); -} - - - -void ctbDacs::update() { - for (int idac=0; idacgetValue(); - } -} diff --git a/ctbGui/ctbDacs.h b/ctbGui/ctbDacs.h deleted file mode 100644 index 818c0c42d..000000000 --- a/ctbGui/ctbDacs.h +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package - - -#ifndef CTBDACS_H -#define CTBDACS_H -#include - - -#define NDACS 18 -//#define NDACS 16 - - -class TGTextEntry; -class TGLabel; -class TGNumberEntry; -class TGCheckButton; - - -namespace sls -{ - class Detector; -}; - -#include -using namespace std; - - -class ctbDac : public TGHorizontalFrame { - - - protected: - // TGLabel *dacsLabel; - TGNumberEntry *dacsEntry; - TGCheckButton *dacsUnit; - TGCheckButton *dacsLabel; - TGLabel *dacsValue; - int id; - - sls::Detector* myDet; - public: - ctbDac(TGGroupFrame*, int , sls::Detector*); - void setValue(); - void setValue(Long_t); - int getValue(); - void setOn(Bool_t); - - int setLabel(char *tit, int mv); - string getLabel(); -int getMoenchDacId(); - - - ClassDef(ctbDac,0) -}; - -class ctbDacs : public TGGroupFrame { -private: - - ctbDac *dacs[NDACS+2]; - - sls::Detector* myDet; - -public: - ctbDacs(TGVerticalFrame *page, sls::Detector*); - - int setDacAlias(string line); - // int setDacAlias(string line); - string getDacAlias(); - string getDacParameters(); - - void update(); - - ClassDef(ctbDacs,0) -}; - -#endif - diff --git a/ctbGui/ctbDefs.h b/ctbGui/ctbDefs.h deleted file mode 100644 index 85d7e1b8a..000000000 --- a/ctbGui/ctbDefs.h +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package -#pragma once - -#include -#include -#include - -//#include "sls/sls_detector_exceptions.h" -//#include "sls/ansi.h" -#define RED "\x1b[31m" -#define RESET "\x1b[0m" -#define BOLD "\x1b[1m" -#define cprintf(code, format, ...) printf(code format RESET, ##__VA_ARGS__) - - -#define CATCH_DISPLAY(m, s) catch(...) { ctbDefs::DisplayExceptions(m, s); } -#define CATCH_HANDLE(...) catch(...) { ctbDefs::HandleExceptions(__VA_ARGS__); } - -class ctbDefs { - public: - /** - * Empty Constructor - */ - ctbDefs(){}; - - // convert double seconds to chrono ns - static std::chrono::nanoseconds ConvertDoubleStoChronoNS(double timeS) { - using std::chrono::duration; - using std::chrono::duration_cast; - using std::chrono::nanoseconds; - return duration_cast(duration(timeS)); - } - - // convert chrono ns to doubel s - static double ConvertChronoNStoDoubleS(std::chrono::nanoseconds timeNs) { - using std::chrono::duration; - using std::chrono::duration_cast; - return duration_cast>(timeNs).count(); - } - - static void DisplayExceptions(std::string emsg, std::string src) { - try { - throw; - } /* catch (const sls::SocketError &e) { - throw; - } catch (const sls::SharedMemoryError &e) { - throw; - } */catch (const std::exception &e) { - ExceptionMessage(emsg, e.what(), src); - } - }; - - template struct NonDeduced { using type = CT; }; - template - static void HandleExceptions(const std::string emsg, const std::string src, S* s, - RT (S::*somefunc)(CT...), - typename NonDeduced::type... Args) { - try { - throw; - } /*catch (const sls::SocketError &e) { - throw; - } catch (const sls::SharedMemoryError &e) { - throw; - } */catch (const std::exception &e) { - - ExceptionMessage(emsg, e.what(), src); - (s->*somefunc)(Args...); - } - }; - - static void ExceptionMessage(std::string message, - std::string exceptionMessage, - std::string source) { - // because sls_detector_exceptions cannot be included - if (exceptionMessage.find("hared memory") != std::string::npos) { - throw; - } - if (exceptionMessage.find("annot connect") != std::string::npos) { - throw; - } - cprintf(RED, "Warning (%s): %s [Caught Exception: %s]\n", source.c_str(), message.c_str(), exceptionMessage.c_str()); - //return Message(qDefs::WARNING, message + std::string("\nCaught exception:\n") + exceptionMessage, source); - }; - -}; diff --git a/ctbGui/ctbGui.cpp b/ctbGui/ctbGui.cpp deleted file mode 100644 index 5c34d8ee0..000000000 --- a/ctbGui/ctbGui.cpp +++ /dev/null @@ -1,159 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "sls/Detector.h" -#include "sls/sls_detector_defs.h" -//#include "sls_receiver_defs.h" -#include "ctbMain.h" -#include "ctbDefs.h" -using namespace std; - - -int main(int argc, char **argv) { - - - string afname, cfname, pfname; - int id=0; - - int af=0, cf=0, pf=0; - - - cout << " *** " << argc << endl; - for (int ia=0; ialoadConfig(cfname); - cout << "Config file loaded successfully" << endl; - } else { - cout << "No config file specified" << endl; - } - cout << "hostname " << myDet->getHostname() << endl; - - if (pf) { - myDet->loadParameters(pfname); - cout << "Loaded parameter file successfully" << endl; - } else{ - cout << "No parameter file specified" << endl; - } - } CATCH_DISPLAY ("Could not create detector/ load config/parameters.", "ctbGui::main") - - /***********Create GUI stuff *******************/ - TApplication theApp("App",&argc,argv); - - - gStyle->SetDrawBorder(0); - gStyle->SetCanvasColor(kWhite); - gStyle->SetCanvasDefH(800); - gStyle->SetCanvasDefW(800); - gStyle->SetCanvasBorderMode(0); - gStyle->SetPadBorderMode(0); - gStyle->SetPaintTextFormat("5.2f"); - gStyle->SetLineWidth(2); - gStyle->SetTextSize(1.1); - gStyle->SetLabelSize(0.04,"xy"); - gStyle->SetTitleSize(0.05,"xy"); - gStyle->SetTitleOffset(1.0,"x"); - gStyle->SetTitleOffset(1.1,"y"); - gStyle->SetPadTopMargin(0.15); - gStyle->SetPadRightMargin(0.15); - gStyle->SetPadBottomMargin(0.15); - gStyle->SetPadLeftMargin(0.15); - gStyle->SetLegendBorderSize(1); - gStyle->SetFrameBorderMode(0); - gStyle->SetFrameFillColor(kWhite); - // gStyle->SetLegendFillColor(kWhite); - gStyle->SetTitleFillColor(kWhite); - gStyle->SetFillColor(kWhite); - gStyle->SetStatFontSize(0.03); - gStyle->SetStatBorderSize(1); - gStyle->SetStatFormat("6.4g"); - gStyle->SetStatX(0.95); - gStyle->SetStatY(0.95); - gStyle->SetStatW(0.2); - gStyle->SetStatH(0.2); - gStyle->SetTitleX(0.1); - gStyle->SetTitleY(0.95); - gStyle->SetTitleBorderSize(0); - gStyle->SetTitleFontSize(0.05); - gROOT->SetStyle("Default"); - - - TColor::InitializeColors(); - const Int_t NRGBs = 5; - const Int_t NCont = 90; - - Double_t stops[NRGBs] = { 0.00, 0.34, 0.61, 0.84, 1.00 }; - Double_t red[NRGBs] = { 0.00, 0.00, 0.87, 1.00, 0.51 }; - Double_t green[NRGBs] = { 0.00, 0.81, 1.00, 0.20, 0.00 }; - Double_t blue[NRGBs] = { 0.51, 1.00, 0.12, 0.00, 0.00 }; - TColor::CreateGradientColorTable(NRGBs, stops, red, green, blue, NCont); - gStyle->SetNumberContours(NCont); - - - gROOT->ForceStyle(); - ctbMain *mf=new ctbMain(gClient->GetRoot(), myDet); - - cout << " *** " << argc << endl; - for (int ia=0; ialoadAlias(afname); - else - cout << "no alias specified" << endl; - - theApp.Run(); - - return 0; -} diff --git a/ctbGui/ctbLinkDef.h b/ctbGui/ctbLinkDef.h deleted file mode 100644 index f041a01fd..000000000 --- a/ctbGui/ctbLinkDef.h +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package -#pragma link C++ class ctbMain; -#pragma link C++ class ctbDacs; -#pragma link C++ class ctbDac; -#pragma link C++ class ctbSignals; -#pragma link C++ class ctbSignal; -#pragma link C++ class ctbAdc; -#pragma link C++ class ctbAdcs; -#pragma link C++ class ctbLoop; -#pragma link C++ class ctbWait; -#pragma link C++ class ctbPattern; -#pragma link C++ class ctbAcquisition; -#pragma link C++ class ctbPower; -#pragma link C++ class ctbPowers; -#pragma link C++ class ctbSlowAdc; -#pragma link C++ class ctbSlowAdcs; diff --git a/ctbGui/ctbMain.cpp b/ctbGui/ctbMain.cpp deleted file mode 100644 index 0308589bb..000000000 --- a/ctbGui/ctbMain.cpp +++ /dev/null @@ -1,588 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -//#include -//#include - - - - -#include -#include -#include -#include - -#include "sls/Detector.h" -#include "ctbDefs.h" -#include "ctbMain.h" -#include "ctbDacs.h" -#include "ctbSlowAdcs.h" -#include "ctbPowers.h" -#include "ctbSignals.h" -#include "ctbPattern.h" -#include "ctbAdcs.h" -#include "ctbAcquisition.h" -//#include "ctbActions.h" - -using namespace std; - - - -ctbMain::ctbMain(const TGWindow *p, sls::Detector *det) - : TGMainFrame(p,800,800), pwrs(NULL), senses(NULL) { - - myDet=det; - - Connect("CloseWindow()", "ctbMain", this, "CloseWindow()"); - - - - - -// fMenuDock = new TGDockableFrame(this); -// AddFrame(fMenuDock, new TGLayoutHints(kLHintsExpandX, 0, 0, 1, 0)); -// fMenuDock->SetWindowName("GuiTest Menu"); - - fMenuBarLayout = new TGLayoutHints(kLHintsTop | kLHintsExpandX); - fMenuBarItemLayout = new TGLayoutHints(kLHintsTop | kLHintsLeft, 0, 4, 0, 0); - fMenuBarHelpLayout = new TGLayoutHints(kLHintsTop | kLHintsRight); - - fMenuFile = new TGPopupMenu(gClient->GetRoot()); - int im=0; - - fMenuFile->AddEntry("Open Alias", im++); - fMenuFile->AddEntry("Save Alias", im++); - fMenuFile->AddSeparator(); - fMenuFile->AddEntry("Open Parameters", im++); - fMenuFile->AddEntry("Save Parameters", im++); - fMenuFile->AddSeparator(); - fMenuFile->AddEntry("Open Configuration", im++); - fMenuFile->AddEntry("Save Configuration", im++); - fMenuFile->AddSeparator(); - fMenuFile->AddEntry("Open Pattern", im++); - fMenuFile->AddEntry("Save Pattern", im++); - fMenuFile->AddSeparator(); - fMenuFile->AddEntry("Exit", im++); - - fMenuFile->Connect("Activated(Int_t)", "ctbMain", this, - "HandleMenu(Int_t)"); - - - i_dacs=-1; - i_pwrs=-1; - i_senses=-1; - i_sig=-1; - i_adcs=-1; - i_pat=-1; - i_acq=-1; - - int i_page=0; - - - - - - - - - - - - - - - - - - TGVerticalFrame *vframe=new TGVerticalFrame(this, 800,1200); //main frame - - - - fMenuBar = new TGMenuBar(vframe, 1, 1, kHorizontalFrame); - fMenuBar->AddPopup("&File", fMenuFile, fMenuBarItemLayout); -// fMenuBar->AddPopup("&Test", fMenuTest, fMenuBarItemLayout); -// fMenuBar->AddPopup("&View", fMenuView, fMenuBarItemLayout); -// fMenuBar->AddPopup("&Help", fMenuHelp, fMenuBarHelpLayout); - - vframe->AddFrame(fMenuBar, fMenuBarLayout); - - TGHorizontalFrame* hpage=new TGHorizontalFrame(vframe, 800,1200); //horizontal frame. Inside there should be the tab and the canvas - mtab=new TGTab(hpage, 1500, 1200); //tab! - // page=new TGVerticalFrame(mtab, 1500,1200); - - cout << "DACS" << endl; - - TGCompositeFrame *tf = mtab->AddTab("DACs"); - TGVerticalFrame *page=new TGVerticalFrame(tf, 1500,1200); - tf->AddFrame(page, new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 10,10,10,1)); - dacs=new ctbDacs(page, myDet); - i_dacs=i_page++; - - - cout << "power " << endl; - tf = mtab->AddTab("Power Supplies"); - page=new TGVerticalFrame(tf, 1500,1200); - tf->AddFrame(page, new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 10,10,10,1)); - pwrs=new ctbPowers(page, myDet); - - i_pwrs=i_page++; - - cout << "sense " << endl; - tf = mtab->AddTab("Sense"); - page=new TGVerticalFrame(tf, 1500,1200); - tf->AddFrame(page, new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 10,10,10,1)); - senses=new ctbSlowAdcs(page, myDet); - - i_senses=i_page++; - - - - cout << "signals " << endl; - tf = mtab->AddTab("Signals"); - page=new TGVerticalFrame(tf, 1500,1200); - tf->AddFrame(page, new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 10,10,10,1)); - sig=new ctbSignals(page, myDet); - sig->Connect("ToggledSignalPlot(Int_t)","ctbMain",this,"setSignalPlot(Int_t)"); - - i_sig=i_page++; - - cout << "adcs " << endl; - tf = mtab->AddTab("ADCs"); - page=new TGVerticalFrame(tf, 1500,1200); - tf->AddFrame(page, new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 10,10,10,1)); - adcs=new ctbAdcs(page, myDet); - adcs->Connect("ToggledAdcPlot(Int_t)","ctbMain",this,"setADCPlot(Int_t)"); - adcs->Connect("AdcEnable(Int_t)","ctbMain",this,"setADCEnable(Int_t)"); - i_adcs=i_page++; - - - cout << "pattern" << endl; - - tf = mtab->AddTab("Pattern"); - page=new TGVerticalFrame(tf, 1500,1200); - tf->AddFrame(page, new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 10,10,10,1)); - pat=new ctbPattern(page, myDet); - pat->Connect("patternFileChanged(const char*)","ctbMain",this,"setPatternFile(const char*)"); - pat->Connect("patternCompilerChanged(const char*)","ctbMain",this,"setPatternCompiler(const char*)"); - pat->Connect("analogSamplesChanged(const int)","ctbMain",this,"setAnalogSamples(int)"); - pat->Connect("digitalSamplesChanged(const int)","ctbMain",this,"setDigitalSamples(int)"); - pat->Connect("readoutModeChanged(int)","ctbMain",this,"setReadoutMode(int)"); - - i_pat=i_page++; - - cout << "acquisition" << endl; - - tf = mtab->AddTab("Acquisition"); - page=new TGVerticalFrame(tf, 1500,1200); - tf->AddFrame(page, new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 10,10,10,1)); - acq=new ctbAcquisition(page, myDet); - - - i_acq=i_page++; - - - // cout << "actions" << endl; - // tf = mtab->AddTab("Actions"); - // page=new TGVerticalFrame(tf, 1500,1200); - // tf->AddFrame(page, new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 10,10,10,1)); - // actions=new ctbActions(page, myDet); - - - // i_actions=i_page++; - - - - cout << "tabs finished" << endl; - - hpage->AddFrame(mtab,new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 10,10,10,1)); - - vframe->AddFrame(hpage,new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 10,10,10,1)); - - AddFrame(vframe,new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 10,10,10,1)); - vframe->MapWindow(); - hpage->MapWindow(); - mtab->MapWindow(); - page->MapWindow(); - - // Sets window name and shows the main frame - cout << "dockabel" << endl; - TGDockableFrame *fdock=new TGDockableFrame(hpage); - hpage->AddFrame(fdock, new TGLayoutHints(kLHintsBottom | kLHintsCenterX | kLHintsExpandX | kLHintsExpandY, 10,10,10,10)); - fdock->MapWindow(); - - cout << "canvas" << endl; -// // Creates widgets of the example - - - fEcanvas = new TRootEmbeddedCanvas ("Ecanvas",fdock,800,800);//hpage,800,800); - //fEcanvas = new TRootEmbeddedCanvas ("Ecanvas",this,800,800);//hpage,800,800); - // fEcanvas->Resize(); - // fEcanvas->GetCanvas()->Update(); - //AddFrame(fEcanvas, new TGLayoutHints(kLHintsBottom | kLHintsCenterX | kLHintsExpandX | kLHintsExpandY, 10,10,10,10)); - - // // hpage-> - fdock->AddFrame(fEcanvas, new TGLayoutHints(kLHintsBottom | kLHintsCenterX | kLHintsExpandX | kLHintsExpandY, 10,10,10,10)); - - - fEcanvas->MapWindow(); - - acq->setCanvas(getCanvas()); - - - - hpage->MapSubwindows(); - mtab->Connect("Selected(Int_t)","ctbMain",this,"tabSelected(Int_t)"); - - - - cout << "connect mtab" << endl; - - try{ - setReadoutMode(pat->getReadoutMode()); - } CATCH_DISPLAY ("Could not get readout flags", "ctbPattern::getReadoutMode") - - setADCEnable(adcs->setEnable()); - setAnalogSamples(pat->getAnalogSamples()); - setDigitalSamples(pat->getDigitalSamples()); - - tabSelected(0); - - SetWindowName("CTB Gui"); - MapSubwindows(); - Resize(1500,1200); - - MapWindow(); -} - -void ctbMain::CloseWindow() { - gApplication->Terminate(); -} - -TCanvas* ctbMain::getCanvas() { - return fEcanvas->GetCanvas(); -} - - - -void ctbMain::HandleMenu(Int_t id) -{ - // Handle menu items. - - - - - switch (id) { - - case 0: // fMenuFile->AddEntry("Open Alias", im++); - cout << "Open Alias" << endl; - { - static TString dir("."); - TGFileInfo fi; - //fi.fFileTypes = filetypes; - fi.fIniDir = StrDup(dir); - printf("fIniDir = %s\n", fi.fIniDir); - new TGFileDialog(gClient->GetRoot(), this, kFDOpen, &fi); - printf("Open file: %s (dir: %s)\n", fi.fFilename, fi.fIniDir); - // dir = fi.fIniDir; - if (fi.fFilename) - loadAlias(fi.fFilename); - } - break; - - case 1: // fMenuFile->AddEntry("Save Alias", im++); - cout << "Save Alias" << endl; - { - static TString dir("."); - TGFileInfo fi; - //fi.fFileTypes = filetypes; - fi.fIniDir = StrDup(dir); - printf("fIniDir = %s\n", fi.fIniDir); - new TGFileDialog(gClient->GetRoot(), this, kFDSave, &fi); - printf("Save file: %s (dir: %s)\n", fi.fFilename, fi.fIniDir); - // dir = fi.fIniDir; - if (fi.fFilename) - saveAlias(fi.fFilename); - } - break; - case 2: //fMenuFile->AddEntry("Open Parameters", im++); - cout << "Open Parameters" << endl; - { - static TString dir("."); - TGFileInfo fi; - //fi.fFileTypes = filetypes; - fi.fIniDir = StrDup(dir); - printf("fIniDir = %s\n", fi.fIniDir); - new TGFileDialog(gClient->GetRoot(), this, kFDOpen, &fi); - printf("Open file: %s (dir: %s)\n", fi.fFilename, fi.fIniDir); - // dir = fi.fIniDir; - if (fi.fFilename) - loadParameters(fi.fFilename); - } - break; - - case 3: // fMenuFile->AddEntry("Open Configuration", im++); - cout << "Open configuration" << endl; - { - static TString dir("."); - TGFileInfo fi; - //fi.fFileTypes = filetypes; - fi.fIniDir = StrDup(dir); - printf("fIniDir = %s\n", fi.fIniDir); - new TGFileDialog(gClient->GetRoot(), this, kFDOpen, &fi); - printf("Open file: %s (dir: %s)\n", fi.fFilename, fi.fIniDir); - // dir = fi.fIniDir; - if (fi.fFilename) - loadConfiguration(fi.fFilename); - } - break; - - case 4: //fMenuFile->AddEntry("Open Pattern", im++); - cout << "Open pattern" << endl; - { - static TString dir("."); - TGFileInfo fi; - //fi.fFileTypes = filetypes; - fi.fIniDir = StrDup(dir); - printf("fIniDir = %s\n", fi.fIniDir); - new TGFileDialog(gClient->GetRoot(), this, kFDOpen, &fi); - printf("Open file: %s (dir: %s)\n", fi.fFilename, fi.fIniDir); - // dir = fi.fIniDir; - if (fi.fFilename) - loadParameters(fi.fFilename); - } - break; - - case 5: //fMenuFile->AddEntry("Save Pattern", im++); - cout << "Save pattern" << endl; - { - static TString dir("."); - TGFileInfo fi; - //fi.fFileTypes = filetypes; - fi.fIniDir = StrDup(dir); - printf("fIniDir = %s\n", fi.fIniDir); - new TGFileDialog(gClient->GetRoot(), this, kFDSave, &fi); - printf("Open file: %s (dir: %s)\n", fi.fFilename, fi.fIniDir); - // dir = fi.fIniDir; - if (fi.fFilename) - savePattern(fi.fFilename); - } - break; - - case 6: // fMenuFile->AddEntry("Exit", im++); - CloseWindow(); - - default: - printf("Menu item %d selected\n", id); - break; - } -} - - - -int ctbMain::setADCPlot(Int_t i) { - - // cout << "ADC " << i << " plot or color toggled" << endl; - // acq->setGraph(i,adcs->getGraph(i)); - acq->setGraph(i,adcs->getEnabled(i),adcs->getColor(i)); - return -1; -} - - -int ctbMain::setSignalPlot(Int_t i) { - - // cout << "ADC " << i << " plot or color toggled" << endl; - // acq->setGraph(i,adcs->getGraph(i)); - acq->setBitGraph(i,sig->getPlot(i),sig->getColor(i)); - return -1; -} - - - -void ctbMain::loadConfiguration(string fname) { - try{ - myDet->loadConfig(fname); - } CATCH_DISPLAY ("Could not load config.", "ctbMain::loadConfiguration") -} - -void ctbMain::loadParameters(string fname) { - try{ - myDet->loadParameters(fname); - } CATCH_DISPLAY ("Could not load parameters.", "ctbMain::loadParameters") -} - -void ctbMain::savePattern(string fname) { - try{ - myDet->savePattern(fname); - } CATCH_DISPLAY ("Could not save pattern.", "ctbMain::savePattern") -} - - - -int ctbMain::loadAlias(string fname) { - - - string line; - char aaaa[1000]; - int i; - ifstream myfile (fname.c_str()); - if (myfile.is_open()) - { - while ( getline (myfile,line) ) - { - // cout << line ; - if (sscanf(line.c_str(),"BIT%d",&i)>0) { - //cout << "*******" << line<< endl; - sig->setSignalAlias(line); - // cout << line ; - } else if (sscanf(line.c_str(),"DAC%d",&i)>0) { - dacs->setDacAlias(line); - // cout << "+++++++++" << line<< endl; - } else if (sscanf(line.c_str(),"ADC%d",&i)>0) { - adcs->setAdcAlias(line); - // cout << "---------" << line<< endl; - } // else - // cout << "<<<<<<<" << line << endl; - else if (sscanf(line.c_str(),"PAT%s",aaaa)>0) { - pat->setPatternAlias(line); - // cout << "---------" << line<< endl; - } else if (sscanf(line.c_str(),"V%s",aaaa)>0) { - if (pwrs) pwrs->setPwrAlias(line); - // cout << "+++++++++" << line<< endl; - } else if (sscanf(line.c_str(),"SENSE%d",&i)>0) { - if (senses) senses->setSlowAdcAlias(line); - // cout << "+++++++++" << line<< endl; - } - - } - myfile.close(); - } - - else cout << "Unable to open file"; - - return 0; - -} - - - - - -int ctbMain::saveAlias(string fname) { - - - string line; - ofstream myfile (fname.c_str()); - if (myfile.is_open()) - { - //while ( getline (myfile,line) ) - // { - // cout << line ; - //if (sscanf(line.c_str(),"BIT%d",&i)>0) { - //cout << "*******" << line<< endl; - myfile << sig->getSignalAlias(); - // cout << line ; - // } else if (sscanf(line.c_str(),"DAC%d",&i)>0) { - myfile << dacs->getDacAlias(); - if (pwrs) myfile << pwrs->getPwrAlias(); - if (senses) myfile << senses->getSlowAdcAlias(); - // cout << "+++++++++" << line<< endl; - // } else if (sscanf(line.c_str(),"ADC%d",&i)>0) { - myfile << adcs->getAdcAlias(); - // cout << "---------" << line<< endl; - // } // else - // cout << "<<<<<<<" << line << endl; - myfile << pat->getPatternAlias(); - - //} - myfile.close(); - } - - else cout << "Unable to open file"; - - return 0; - -} - - - - - - - - - - - -void ctbMain::tabSelected(Int_t i) { - - // cout << "Selected tab " << i << endl; - // cout << "Current tab is " << mtab->GetCurrent() << endl; - - if (i==i_dacs) dacs->update(); - else if (i==i_pwrs) pwrs->update(); - else if (i==i_senses) ;//senses->update(); - else if (i==i_sig) sig->update(); - else if (i==i_adcs) adcs->update(); - else if (i==i_pat) pat->update(); - else if (i==i_acq) acq->update(); - else if (i==i_acq) acq->update(); - // else if (i==i_actions) actions->update(); - else cout << "Unknown tab " << i << endl; - - -} - -void ctbMain::setPatternFile(const char* t) { - acq->setPatternFile(t); - -} - -void ctbMain::setPatternCompiler(const char* t) { - acq->setPatternCompiler(t); - - -} - -void ctbMain::setAnalogSamples(const int n) { - acq->setAnalogSamples(n); - - -} - -void ctbMain::setDigitalSamples(const int n) { - acq->setDigitalSamples(n); - - -} - -void ctbMain::setReadoutMode(int flags) { - acq->setReadoutMode(flags); -} - -void ctbMain::setADCEnable(Int_t reg){ - acq->setADCEnable(reg); -} diff --git a/ctbGui/ctbMain.h b/ctbGui/ctbMain.h deleted file mode 100644 index 34ff8d429..000000000 --- a/ctbGui/ctbMain.h +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package -#ifndef CTBMAIN_H -#define CTBMAIN_H -#include - - -class TRootEmbeddedCanvas; -class TGButtonGroup; -class TGVerticalFrame; -class TGHorizontalFrame; -class TGTextEntry; -class TGLabel; -class TGNumberEntry; -class TH2F; -class TGComboBox; -class TGCheckButton; - -class THStack; -class TGraphErrors; -class TGTextButton; -class TGTab; - -class TGMenuBar; -class TGPopupMenu; -class TGDockableFrame; -class TGLayoutHints; -class TGCanvas; -class TCanvas; - -class ctbDacs; -class ctbSlowAdcs; -class ctbPowers; - - -class ctbSignals; - -namespace sls -{ - class Detector; -}; - -class ctbPattern; -class ctbAdcs; -class ctbAcquisition; -//class ctbActions; - -#include -using namespace std; - -class ctbMain : public TGMainFrame { -private: - - - sls::Detector *myDet; - - - - TRootEmbeddedCanvas *fEcanvas; - TRootEmbeddedCanvas *fModulecanvas; - TGButtonGroup *br; - - TGTab *mtab; - - - ctbDacs *dacs; - int i_dacs; - - ctbPowers *pwrs; - int i_pwrs; - - ctbSlowAdcs *senses; - int i_senses; - - - ctbSignals *sig; - int i_sig; - - - ctbAdcs *adcs; - int i_adcs; - - - ctbPattern *pat; - int i_pat; - - ctbAcquisition *acq; - int i_acq; - - // ctbActions *actions; - int i_actions; - - TGDockableFrame *fMenuDock; - - TGMenuBar *fMenuBar; - TGPopupMenu *fMenuFile, *fMenuTest, *fMenuView, *fMenuHelp; - TGPopupMenu *fCascadeMenu, *fCascade1Menu, *fCascade2Menu; - TGPopupMenu *fMenuNew1, *fMenuNew2; - TGLayoutHints *fMenuBarLayout, *fMenuBarItemLayout, *fMenuBarHelpLayout; - TGCanvas *myCanvas; - - -public: - ctbMain(const TGWindow *p, sls::Detector *det); - - - int loadAlias(string fname); - int saveAlias(string fname); - void loadParameters(string fname); - void savePattern(string fname); - void loadConfiguration(string fname); - void tabSelected(Int_t); - int setADCPlot(Int_t); - int setSignalPlot(Int_t); - void CloseWindow(); - - void setPatternFile(const char* t); - - void setPatternCompiler(const char* t); - void setAnalogSamples(const int); - void setDigitalSamples(const int); - void setReadoutMode(int); - void setADCEnable(Int_t); - - void HandleMenu(Int_t); - TCanvas* getCanvas(); - ClassDef(ctbMain,0) -}; - -#endif diff --git a/ctbGui/ctbPattern.cpp b/ctbGui/ctbPattern.cpp deleted file mode 100644 index 5d3d49a5e..000000000 --- a/ctbGui/ctbPattern.cpp +++ /dev/null @@ -1,1111 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "ctbPattern.h" -#include "ctbDefs.h" -#include "sls/Detector.h" -#include -using namespace std; - - - - - -ctbLoop::ctbLoop(TGGroupFrame *page, int i, sls::Detector *det) : TGHorizontalFrame(page, 800,800), id(i), myDet(det) { - - TGHorizontalFrame *hframe=this; - - char tit[100]; - - page->AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - MapWindow(); - - - - - - sprintf(tit, "Loop %d Repetitions: ", id); - - TGLabel *label= new TGLabel(hframe, tit); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - eLoopNumber = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELNoLimits); - hframe->AddFrame( eLoopNumber,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eLoopNumber->MapWindow(); - eLoopNumber->Resize(150,30); - TGTextEntry *e= eLoopNumber->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbLoop",this,"setNLoops()"); - - - - - sprintf(tit, "Start Address: "); - - label= new TGLabel(hframe, tit); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - eLoopStartAddr = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESHex, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax, - 0, 1024); - hframe->AddFrame( eLoopStartAddr,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eLoopStartAddr->MapWindow(); - eLoopStartAddr->Resize(150,30); - - // eLoopStartAddr->SetState(kFALSE); - - label= new TGLabel(hframe, "Stop Address: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - - eLoopStopAddr = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESHex, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax, - 0, 1024); - hframe->AddFrame( eLoopStopAddr,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eLoopStopAddr->MapWindow(); - eLoopStopAddr->Resize(150,30); - - - - // eLoopStopAddr->SetState(kFALSE); - - - - - - -} - -void ctbLoop::setNLoops() { - try{ - myDet->setPatternLoopCycles(id, eLoopNumber->GetNumber()); - } CATCH_DISPLAY ("Could not set number of pattern loops for level " + to_string(id) + ".", "ctbLoop::setNLoops") -} - - - -void ctbLoop::update() { - try{ - - auto loop = myDet->getPatternLoopCycles(id).tsquash("Different values"); - eLoopNumber->SetNumber(loop); - auto loopaddr = myDet->getPatternLoopAddresses(id).tsquash("Different values"); - eLoopStartAddr->SetHexNumber(loopaddr[0]); - eLoopStopAddr->SetHexNumber(loopaddr[1]); - - - } CATCH_DISPLAY ("Could not get pattern loops for level " + to_string(id) + ".", "ctbLoop::update") -} - - - -ctbWait::ctbWait(TGGroupFrame *page, int i, sls::Detector *det) : TGHorizontalFrame(page, 800,800), id(i), myDet(det) { - - char tit[100]; - TGHorizontalFrame *hframe=this; - page->AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - MapWindow(); - - - - sprintf(tit, "Wait %d (run clk): ", id); - - TGLabel *label= new TGLabel(hframe, tit); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - eWaitTime = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELNoLimits); - hframe->AddFrame( eWaitTime,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eWaitTime->MapWindow(); - eWaitTime->Resize(150,30); - TGTextEntry *e= eWaitTime->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbWait",this,"setWaitTime()"); - - - - sprintf(tit, "Wait Address: "); - - label= new TGLabel(hframe, tit); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - eWaitAddr = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESHex, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax, - 0, 1024); - hframe->AddFrame( eWaitAddr,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eWaitAddr->MapWindow(); - eWaitAddr->Resize(150,30); - - // eWaitAddr->SetState(kFALSE); - -} - - - -void ctbWait::setWaitTime() { - try{ - - myDet->setPatternWaitTime(id, eWaitTime->GetNumber()); - - } CATCH_DISPLAY ("Could not set pattern wait time for level " + to_string(id) + ".", "ctbWait::setWaitTime") -} - - - -void ctbWait::update() { - try{ - - auto time = myDet->getPatternWaitTime(id).tsquash("Different values"); - auto addr = myDet->getPatternWaitAddr(id).tsquash("Different values"); - - eWaitAddr->SetHexNumber(addr); - eWaitTime->SetNumber(time); - - } CATCH_DISPLAY ("Could not get pattern loops for level " + to_string(id) + ".", "ctbWait::update") -} - - - - - - - - - -ctbPattern::ctbPattern(TGVerticalFrame *page, sls::Detector *det) - : TGGroupFrame(page,"Pattern",kVerticalFrame), myDet(det) { - - - SetTitlePos(TGGroupFrame::kLeft); - page->AddFrame(this,new TGLayoutHints( kLHintsTop | kLHintsExpandX , 10,10,10,10)); - MapWindow(); - - - char tit[100]; - - - TGHorizontalFrame* hframe=new TGHorizontalFrame(this, 800,800); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - sprintf(tit, "Run Clock Frequency (MHz): "); - - TGLabel *label= new TGLabel(hframe, tit); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - eRunClkFreq = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax, - 0, 400); - hframe->AddFrame( eRunClkFreq,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eRunClkFreq->MapWindow(); - eRunClkFreq->Resize(150,30); - TGTextEntry *e= eRunClkFreq->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbPattern",this,"setRunFreq()"); - - - - - - hframe=new TGHorizontalFrame(this, 800,800); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - - - sprintf(tit, "ADC Clock Frequency (MHz): "); - - label= new TGLabel(hframe, tit); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - eAdcClkFreq = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax, - 0, 40); - hframe->AddFrame( eAdcClkFreq,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eAdcClkFreq->MapWindow(); - eAdcClkFreq->Resize(150,30); - e= eAdcClkFreq->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbPattern",this,"setAdcFreq()"); - - - - - sprintf(tit, "DBIT Clock Frequency (MHz): "); - - label= new TGLabel(hframe, tit); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - eDBitClkFreq = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax, - 0, 400); - hframe->AddFrame( eDBitClkFreq,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eDBitClkFreq->MapWindow(); - eDBitClkFreq->Resize(150,30); - e= eDBitClkFreq->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbPattern",this,"setDBitFreq()"); - - - - - - - - - - - hframe=new TGHorizontalFrame(this, 800,800); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - - - label= new TGLabel(hframe, "ADC Clock Phase (a.u.): "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - - eAdcClkPhase = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEAAnyNumber, - TGNumberFormat::kNELLimitMinMax, - -255, 255); - hframe->AddFrame( eAdcClkPhase,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eAdcClkPhase->MapWindow(); - eAdcClkPhase->Resize(150,30); - e= eAdcClkPhase->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbPattern",this,"setAdcPhase()"); - - - - - label= new TGLabel(hframe, "DBit Clock Phase (a.u.): "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - - eDBitClkPhase = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEAAnyNumber, - TGNumberFormat::kNELLimitMinMax, - -255, 255); - hframe->AddFrame( eDBitClkPhase,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eDBitClkPhase->MapWindow(); - eDBitClkPhase->Resize(150,30); - e= eDBitClkPhase->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbPattern",this,"setDBitPhase()"); - - -// label= new TGLabel(hframe, " Phase (0.15ns step): "); -// hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); -// label->MapWindow(); -// label->SetTextJustify(kTextLeft); - - - - - -// eRunClkPhase = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, -// TGNumberFormat::kNEANonNegative, -// TGNumberFormat::kNELLimitMinMax, -// 0, 200); -// hframe->AddFrame( eRunClkPhase,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); -// eRunClkPhase->MapWindow(); -// eRunClkPhase->Resize(150,30); -// e= eRunClkPhase->TGNumberEntry::GetNumberEntry(); -// e->Connect("ReturnPressed()","ctbPattern",this,"setRunPhase()"); - - - - - - hframe=new TGHorizontalFrame(this, 800,800); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - - - label= new TGLabel(hframe, "Adc pipeline: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - - eAdcPipeline = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax, - 0, 64); - hframe->AddFrame( eAdcPipeline,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eAdcPipeline->MapWindow(); - eAdcPipeline->Resize(150,30); - e= eAdcPipeline->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbPattern",this,"setAdcPipeline()"); - - - - - - - label= new TGLabel(hframe, "DBIT pipeline: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - - eDBitPipeline = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax, - 0, 64); - hframe->AddFrame( eDBitPipeline,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eDBitPipeline->MapWindow(); - eDBitPipeline->Resize(150,30); - e= eDBitPipeline->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbPattern",this,"setDBitPipeline()"); - - - - - - - - - hframe=new TGHorizontalFrame(this, 800,800); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - sprintf(tit, "Number of triggers: "); - - label= new TGLabel(hframe, tit); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - eTriggers = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELNoLimits); - hframe->AddFrame( eTriggers,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eTriggers->MapWindow(); - eTriggers->Resize(150,30); - e= eTriggers->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbPattern",this,"setTriggers()"); - - - // sprintf(tit, "Number of measurements: "); - - // label= new TGLabel(hframe, tit); - // hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - // label->MapWindow(); - // label->SetTextJustify(kTextLeft); - - - - - // eMeasurements = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - // TGNumberFormat::kNEANonNegative, - // TGNumberFormat::kNELNoLimits); - // hframe->AddFrame( eMeasurements,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - // eMeasurements->MapWindow(); - // eMeasurements->Resize(150,30); - // e= eMeasurements->TGNumberEntry::GetNumberEntry(); - // e->Connect("ReturnPressed()","ctbPattern",this,"setMeasurements()"); - - - hframe=new TGHorizontalFrame(this, 800,800); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - sprintf(tit, "Number of frames: "); - - label= new TGLabel(hframe, tit); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - eFrames = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELNoLimits); - hframe->AddFrame( eFrames,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eFrames->MapWindow(); - eFrames->Resize(150,30); - e= eFrames->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbPattern",this,"setFrames()"); - - - label= new TGLabel(hframe, " Period (s): "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - - ePeriod = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESReal, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELNoLimits); - hframe->AddFrame( ePeriod,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - ePeriod->MapWindow(); - ePeriod->Resize(150,30); - e= ePeriod->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbPattern",this,"setPeriod()"); - - - - - - - hframe=new TGHorizontalFrame(this, 800,800); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - sprintf(tit, "Start Address: "); - - label= new TGLabel(hframe, tit); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - eStartAddr = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESHex, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax, - 0, 1024); - hframe->AddFrame( eStartAddr,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eStartAddr->MapWindow(); - eStartAddr->Resize(150,30); - - eStartAddr->SetState(kFALSE); - - label= new TGLabel(hframe, "Stop Address: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - - eStopAddr = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESHex, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax, - 0, 1024); - hframe->AddFrame( eStopAddr,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eStopAddr->MapWindow(); - eStopAddr->Resize(150,30); - - - - - eStopAddr->SetState(kFALSE); - - - - - - - int idac=0; - for (idac=0; idacMapWindow(); - - - - label= new TGLabel(hframe, "Compiler: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - - patternCompiler=new TGTextEntry(hframe,"generate.sh"); - hframe->AddFrame(patternCompiler,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - patternCompiler->MapWindow(); - // patternCompiler->SetTextJustify(kTextLeft); - patternCompiler->Connect("ReturnPressed()","ctbPattern",this,"setCompiler()"); - - - browseCompiler=new TGTextButton(hframe,"Browse"); - hframe->AddFrame(browseCompiler,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - browseCompiler->MapWindow(); - // patternCompiler->SetTextJustify(kTextLeft); - browseCompiler->Connect("Clicked()","ctbPattern",this,"chooseCompiler()"); - - - - - hframe=new TGHorizontalFrame(this, 800,800); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - label= new TGLabel(hframe, "Pattern: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - patternFile=new TGTextEntry(hframe,"file.p"); - hframe->AddFrame(patternFile,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - patternFile->MapWindow(); - patternFile->Connect("ReturnPressed()","ctbPattern",this,"setFile()"); - // patternFile->SetTextJustify(kTextLeft); - - - - browseFile=new TGTextButton(hframe,"Browse"); - hframe->AddFrame(browseFile,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - browseFile->MapWindow(); - // patternCompiler->SetTextJustify(kTextLeft); - browseFile->Connect("Clicked()","ctbPattern",this,"choosePattern()"); - - - - - hframe=new TGHorizontalFrame(this, 800,800); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - sprintf(tit, "Samples per frame - "); - - label= new TGLabel(hframe, tit); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - sprintf(tit, "Analog: "); - - label= new TGLabel(hframe, tit); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - eAnalogSamples = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax, - 1, 8192); - hframe->AddFrame( eAnalogSamples,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eAnalogSamples->MapWindow(); - eAnalogSamples->Resize(150,30); - e= eAnalogSamples->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbPattern",this,"setAnalogSamples()"); - - sprintf(tit, "Digital: "); - - label= new TGLabel(hframe, tit); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - - eDigitalSamples = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELLimitMinMax, - 1, 8192); - hframe->AddFrame( eDigitalSamples,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eDigitalSamples->MapWindow(); - eDigitalSamples->Resize(150,30); - e= eDigitalSamples->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbPattern",this,"setDigitalSamples()"); - - - hframe=new TGHorizontalFrame(this, 800,800); - AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - sprintf(tit, "Read Out Mode: "); - - label= new TGLabel(hframe, tit); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - cbAnalog= new TGCheckButton(hframe, "Analog"); - hframe->AddFrame(cbAnalog,new TGLayoutHints(kLHintsTop | kLHintsLeft | kLHintsExpandX, 5, 5, 5, 5)); - cbAnalog->MapWindow(); - cbAnalog->SetTextJustify(kTextRight); - cbAnalog->Connect("Toggled(Bool_t)","ctbPattern",this,"setReadoutMode(Bool_t)"); - - cbDigital= new TGCheckButton(hframe, "Digital"); - hframe->AddFrame(cbDigital,new TGLayoutHints(kLHintsTop | kLHintsLeft | kLHintsExpandX, 5, 5, 5, 5)); - cbDigital->MapWindow(); - cbDigital->SetTextJustify(kTextRight); - cbDigital->Connect("Toggled(Bool_t)","ctbPattern",this,"setReadoutMode(Bool_t)"); - - - - -} - -void ctbPattern::update() { - try{ - auto retval = myDet->getRUNClock().tsquash("Different values"); - eRunClkFreq->SetNumber(retval); - } CATCH_DISPLAY ("Could not get run clock.", "ctbPattern::update") - - try{ - auto retval = myDet->getADCClock().tsquash("Different values"); - eAdcClkFreq->SetNumber(retval); - } CATCH_DISPLAY ("Could not get adc clock.", "ctbPattern::update") - - try{ - auto retval = myDet->getADCPhase().tsquash("Different values"); - eAdcClkPhase->SetNumber(retval); - } CATCH_DISPLAY ("Could not get adc phase shift.", "ctbPattern::update") - - try{ - auto retval = myDet->getADCPipeline().tsquash("Different values"); - eAdcPipeline->SetNumber(retval); - } CATCH_DISPLAY ("Could not get adc pipeline.", "ctbPattern::update") - - try{ - auto retval = myDet->getDBITClock().tsquash("Different values"); - eDBitClkFreq->SetNumber(retval); - } CATCH_DISPLAY ("Could not get dbit clock.", "ctbPattern::update") - - try{ - auto retval = myDet->getDBITPhase().tsquash("Different values"); - eDBitClkPhase->SetNumber(retval); - } CATCH_DISPLAY ("Could not get dbit phase shift.", "ctbPattern::update") - - try{ - auto retval = myDet->getDBITPipeline().tsquash("Different values"); - eDBitPipeline->SetNumber(retval); - } CATCH_DISPLAY ("Could not get dbit pipeline.", "ctbPattern::update") - - try{ - auto retval = myDet->getNumberOfFrames().tsquash("Different values"); - eFrames->SetNumber(retval); - } CATCH_DISPLAY ("Could not get number of frames.", "ctbPattern::update") - - try{ - auto timeNs = myDet->getPeriod().tsquash("Different values"); - ePeriod->SetNumber(ctbDefs::ConvertChronoNStoDoubleS(timeNs)); - } CATCH_DISPLAY ("Could not get period.", "ctbPattern::update") - - try{ - auto retval = myDet->getNumberOfTriggers().tsquash("Different values"); - eTriggers->SetNumber(retval); - } CATCH_DISPLAY ("Could not get number of triggers.", "ctbPattern::update") - - try{ - auto retval = myDet->getPatternLoopAddresses(-1).tsquash("Different values"); - eStartAddr->SetHexNumber(retval[0]); - eStopAddr->SetHexNumber(retval[1]); - } CATCH_DISPLAY ("Could not get dbit phase shift.", "ctbPattern::update") - - for (int iloop=0; iloopupdate(); - } - - for (int iwait=0; iwaitupdate(); - } - - getAnalogSamples(); - getDigitalSamples(); - getReadoutMode(); -} - - -void ctbPattern::setFile() { - patternFileChanged(patternFile->GetText()); - -} - - -void ctbPattern::setCompiler() { - patternCompilerChanged(patternCompiler->GetText()); -} - - -void ctbPattern::patternFileChanged(const char* t){ - Emit("patternFileChanged(const char*)", t); -} - - -void ctbPattern::patternCompilerChanged(const char* t){ - Emit("patternCompilerChanged(const char*)", t); - -} - - -void ctbPattern::setPatternAlias(string line){ - char fname[10000]; - if (sscanf(line.c_str(),"PATCOMPILER %s",fname)) { - patternCompiler->SetText(fname); - patternCompilerChanged(patternCompiler->GetText()); - } else if (sscanf(line.c_str(),"PATFILE %s",fname)) { - patternFile->SetText(fname); - patternFileChanged(patternFile->GetText()); - } -} - - -string ctbPattern::getPatternAlias() { - char line[100000]; - sprintf(line, "PATCOMPILER %s\nPATFILE %s\n",patternCompiler->GetText(),patternFile->GetText()); - return line; -} - - -void ctbPattern::chooseCompiler() { - static TString dir("."); - TGFileInfo fi; - //fi.fFileTypes = filetypes; - fi.fIniDir = StrDup(dir); - printf("fIniDir = %s\n", fi.fIniDir); - new TGFileDialog(gClient->GetRoot(), this, kFDOpen, &fi); - printf("Open file: %s (dir: %s)\n", fi.fFilename, fi.fIniDir); - // dir = fi.fIniDir; - if (fi.fFilename) { - patternCompiler->SetText(fi.fFilename); - patternCompilerChanged(patternCompiler->GetText()); - } -} - - -void ctbPattern::choosePattern() { - static TString dir("."); - TGFileInfo fi; - //fi.fFileTypes = filetypes; - fi.fIniDir = StrDup(dir); - printf("fIniDir = %s\n", fi.fIniDir); - new TGFileDialog(gClient->GetRoot(), this, kFDOpen, &fi); - printf("Open file: %s (dir: %s)\n", fi.fFilename, fi.fIniDir); - // dir = fi.fIniDir; - if (fi.fFilename) { - patternFile->SetText(fi.fFilename); - patternFileChanged(patternFile->GetText()); - } - -} - - -string ctbPattern::getCompiler() { - return string(patternCompiler->GetText()); - -} - -string ctbPattern::getPatternFile() { - return string(patternFile->GetText()); - -} - -void ctbPattern::setFrames() { - try{ - myDet->setNumberOfFrames(eFrames->GetNumber()); - } CATCH_DISPLAY ("Could not set number of frames", "ctbPattern::setFrames") -} - -void ctbPattern::setTriggers() { - try{ - myDet->setNumberOfTriggers(eTriggers->GetNumber()); - } CATCH_DISPLAY ("Could not set number of triggers", "ctbPattern::setTriggers") -} - -void ctbPattern::setPeriod() { - using std::chrono::duration; - using std::chrono::duration_cast; - using std::chrono::nanoseconds; - try{ - auto timeNs = ctbDefs::ConvertDoubleStoChronoNS(ePeriod->GetNumber()); - myDet->setPeriod(timeNs); - } CATCH_DISPLAY ("Could not set period", "ctbPattern::setPeriod") -} - -void ctbPattern::setAdcFreq() { - try{ - myDet->setADCClock(eAdcClkFreq->GetNumber()); - } CATCH_DISPLAY ("Could not set adc clock", "ctbPattern::setAdcFreq") -} - -void ctbPattern::setRunFreq() { - try{ - myDet->setRUNClock(eRunClkFreq->GetNumber()); - } CATCH_DISPLAY ("Could not set run clock", "ctbPattern::setRunFreq") -} - -void ctbPattern::setDBitFreq() { - try{ - myDet->setDBITClock(eDBitClkFreq->GetNumber()); - } CATCH_DISPLAY ("Could not set dbit clock", "ctbPattern::setDBitFreq") -} - -void ctbPattern::setAdcPhase() { - try{ - myDet->setADCPhase(eAdcClkPhase->GetNumber()); - } CATCH_DISPLAY ("Could not set adc phase shift", "ctbPattern::setAdcPhase") -} - -void ctbPattern::setDBitPhase() { - try{ - myDet->setDBITPhase(eDBitClkPhase->GetNumber()); - } CATCH_DISPLAY ("Could not set dbit phase shift", "ctbPattern::setDBitPhase") -} - -void ctbPattern::setAdcPipeline() { - try{ - myDet->setADCPipeline(eAdcPipeline->GetNumber()); - } CATCH_DISPLAY ("Could not set adc pipeline", "ctbPattern::setAdcPipeline") -} - -void ctbPattern::setDBitPipeline() { - try{ - myDet->setDBITPipeline(eDBitPipeline->GetNumber()); - } CATCH_DISPLAY ("Could not set dbit pipeline", "ctbPattern::setDBitPipeline") -} - - -void ctbPattern::setAnalogSamples() { - try{ - myDet->setNumberOfAnalogSamples(eAnalogSamples->GetNumber()); - } CATCH_DISPLAY ("Could not set number of analog sampels", "ctbPattern::setAnalogSamples") - - analogSamplesChanged(eAnalogSamples->GetNumber()); -} - -void ctbPattern::setDigitalSamples() { - try{ - myDet->setNumberOfDigitalSamples(eDigitalSamples->GetNumber()); - } CATCH_DISPLAY ("Could not set number of digital samples", "ctbPattern::setDigitalSamples") - - digitalSamplesChanged(eDigitalSamples->GetNumber()); -} - -void ctbPattern::setReadoutMode(Bool_t) { - try { - slsDetectorDefs::readoutMode flag = slsDetectorDefs::ANALOG_ONLY; - if (cbAnalog->IsOn() && cbDigital->IsOn()) - flag=slsDetectorDefs::ANALOG_AND_DIGITAL; - else if (~cbAnalog->IsOn() && cbDigital->IsOn()) - flag=slsDetectorDefs::DIGITAL_ONLY; - else if (cbAnalog->IsOn() && ~cbDigital->IsOn()) - flag=slsDetectorDefs::ANALOG_ONLY; - else { - throw runtime_error("unkown readout flag"); - } - myDet->setReadoutMode(flag); - cout << "Set readout flag: " << flag << endl; - } CATCH_DISPLAY ("Could not set readout flag", "ctbPattern::setReadoutMode") - - getReadoutMode(); -} - -void ctbPattern::readoutModeChanged(int flags) { - Emit("readoutModeChanged(Int_t)",(int)flags); - -} - -int ctbPattern::getReadoutMode() { - int retval=slsDetectorDefs::ANALOG_ONLY; - - if (myDet->getDetectorType().squash() == slsDetectorDefs::CHIPTESTBOARD) { - try{ - retval = myDet->getReadoutMode().tsquash("Different values"); - } CATCH_DISPLAY ("Could not get readout flags", "ctbPattern::getReadoutMode") - - switch(retval) { - case slsDetectorDefs::ANALOG_AND_DIGITAL: - cout << "analog and digital" << endl; - cbAnalog->SetOn(kTRUE); - cbDigital->SetOn(kTRUE); - break; - case slsDetectorDefs::DIGITAL_ONLY: - cout << "digital only" << endl; - cbAnalog->SetOn(kFALSE); - cbDigital->SetOn(kTRUE); - break; - case slsDetectorDefs::ANALOG_ONLY: - cout << "analog only" << endl; - cbAnalog->SetOn(kTRUE); - cbDigital->SetOn(kFALSE); - break; - default: - throw("unknown readout flag"); - } - } else { - cbAnalog->SetOn(kTRUE); - cbDigital->SetOn(kFALSE); - } - - Emit("readoutModeChanged(int)",static_cast(retval)); - return retval; - - -} - -int ctbPattern::getAnalogSamples() { - try{ - auto retval = myDet->getNumberOfAnalogSamples().tsquash("Different values"); - eAnalogSamples->SetNumber((Double_t)retval); - Emit("analogSamplesChanged(const int)", eAnalogSamples->GetNumber()); - return eAnalogSamples->GetNumber(); - } CATCH_DISPLAY ("Could not get number of triggers.", "ctbPattern::update") - - return -1; -} - -int ctbPattern::getDigitalSamples() { - int retval=0; - if (myDet->getDetectorType().squash() == slsDetectorDefs::CHIPTESTBOARD) { - try{ - auto retval = myDet->getNumberOfDigitalSamples().tsquash("Different values"); - } CATCH_DISPLAY ("Could not get number of digital samples.", "ctbPattern::getDigitalSamples") - } - eDigitalSamples->SetNumber((Double_t)retval); - Emit("digitalSamplesChanged(const int)", eDigitalSamples->GetNumber()); - return eDigitalSamples->GetNumber(); - - - return -1; -} - - -void ctbPattern::analogSamplesChanged(const int t){ - Emit("analogSamplesChanged(const int)", t); -} - -void ctbPattern::digitalSamplesChanged(const int t){ - Emit("digitalSamplesChanged(const int)", t); -} diff --git a/ctbGui/ctbPattern.h b/ctbGui/ctbPattern.h deleted file mode 100644 index 6f85974a1..000000000 --- a/ctbGui/ctbPattern.h +++ /dev/null @@ -1,181 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package -#ifndef CTBPATTERN_H -#define CTBPATTERN_H -#include - - -#define NLOOPS 3 -#define NWAITS 3 -#define NADCS 32 -#define PATLEN 1024 - -class TRootEmbeddedCanvas; -class TGButtonGroup; -class TGVerticalFrame; -class TGHorizontalFrame; -class TGTextEntry; -class TGLabel; -class TGNumberEntry; -class TH2F; -class TGComboBox; -class TGCheckButton; -class TGTextEntry; -class TGCheckButton; - -class THStack; -class TGraphErrors; -class energyCalibration; -class TGTextButton; -class TGTab; - -namespace sls -{ - class Detector; -}; - - -#include -using namespace std; - - - -class ctbLoop : public TGHorizontalFrame { - - - private: - - TGNumberEntry *eLoopStartAddr; - TGNumberEntry *eLoopStopAddr; - TGNumberEntry *eLoopNumber; - - int id; - - sls::Detector *myDet; - - public: - ctbLoop(TGGroupFrame *page, int i,sls::Detector *det); - - void setNLoops(); - void update(); - - ClassDef(ctbLoop,0) - }; - -class ctbWait : public TGHorizontalFrame { - - - private: - - TGNumberEntry *eWaitAddr; - TGNumberEntry *eWaitTime; - - int id; - - sls::Detector *myDet; - - public: - ctbWait(TGGroupFrame *page, int i,sls::Detector *det); - - void setWaitTime(); - void update(); - - ClassDef(ctbWait,0) - }; - - - - - - - -class ctbPattern : public TGGroupFrame { -private: - - - TGNumberEntry *eAdcClkFreq; - TGNumberEntry *eRunClkFreq; - TGNumberEntry *eDBitClkFreq; - TGNumberEntry *eAdcClkPhase; - TGNumberEntry *eDBitClkPhase; - //TGNumberEntry *eRunClkPhase; - - TGNumberEntry *eStartAddr; - TGNumberEntry *eStopAddr; - TGNumberEntry *eFrames; - TGNumberEntry *ePeriod; - TGNumberEntry *eTriggers; - // TGNumberEntry *eMeasurements; - TGNumberEntry *eAdcPipeline; - TGNumberEntry *eDBitPipeline; - - ctbLoop *eLoop[NLOOPS]; - ctbWait *eWait[NWAITS]; - - TGTextEntry *patternCompiler; - TGTextEntry *patternFile; - - TGTextButton *browseCompiler; - TGTextButton *browseFile; - - - TGNumberEntry *eAnalogSamples; - TGNumberEntry *eDigitalSamples; - - TGCheckButton *cbAnalog; - TGCheckButton *cbDigital; - - char pat[PATLEN*8]; - - sls::Detector *myDet; - -public: - - ctbPattern(TGVerticalFrame *page, sls::Detector *det); - - void update(); - void setAdcFreq(); - void setRunFreq(); - void setDBitFreq(); - void setAdcPhase(); - void setDBitPhase(); - // void setRunPhase(); - void setAdcPipeline(); - void setDBitPipeline(); - void setFrames(); - void setTriggers(); - // void setMeasurements(); - void setPeriod(); - - - void chooseCompiler(); - void choosePattern(); - - string getCompiler(); - string getPatternFile(); - - void setPatternAlias(string); - string getPatternAlias(); - - - int getAnalogSamples(); - void setAnalogSamples(); - int getDigitalSamples(); - void setDigitalSamples(); - void setReadoutMode(Bool_t); - int getReadoutMode(); - - - void setFile(); - void setCompiler(); - void patternFileChanged(const char*); - void patternCompilerChanged(const char*); - void analogSamplesChanged(const int t); - void digitalSamplesChanged(const int t); - void readoutModeChanged(int); - - - ClassDef(ctbPattern,0) -}; - -#endif diff --git a/ctbGui/ctbPowers.cpp b/ctbGui/ctbPowers.cpp deleted file mode 100644 index 498ec2c13..000000000 --- a/ctbGui/ctbPowers.cpp +++ /dev/null @@ -1,225 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package -#include - - -#include -#include -#include -#include - -#include -#include -#include - -#include "ctbDefs.h" -#include "ctbDacs.h" -#include "ctbPowers.h" -#include "sls/Detector.h" -#include "sls/sls_detector_defs.h" - -using namespace std; - - - -ctbPower::ctbPower(TGGroupFrame* f, int i, sls::Detector* d) - : ctbDac(f, i, d) -{ - cout << "****************************************************************power " << i << endl; - dacsUnit->SetOn(kTRUE); - dacsUnit->SetEnabled(kFALSE); - - switch(i) { - case slsDetectorDefs::V_POWER_IO: - dacsLabel->SetText("VIO"); - break; - case slsDetectorDefs::V_POWER_A: - dacsLabel->SetText("VA"); - break; - case slsDetectorDefs::V_POWER_B: - dacsLabel->SetText("VB"); - break; - case slsDetectorDefs::V_POWER_C: - dacsLabel->SetText("VC"); - break; - case slsDetectorDefs::V_POWER_D: - dacsLabel->SetText("VD"); - break; - case slsDetectorDefs::V_POWER_CHIP: - dacsLabel->SetText("VCHIP"); - dacsLabel->SetEnabled(kFALSE); - break; - default: - dacsLabel->SetText("Bad index"); - break; - }; - - - TGTextEntry *e=dacsEntry->TGNumberEntry::GetNumberEntry(); - e->Disconnect ("ReturnPressed()"); - e->Disconnect ("ValueSet(Long_t)"); - - e->Connect("ReturnPressed()","ctbPower",this,"setValue()"); - dacsEntry->Connect("ValueSet(Long_t)","ctbPower",this,"setValue(Long_t)"); - }; - - -string ctbPower::getLabel() { - - ostringstream line; - switch (id) { - case slsDetectorDefs::V_POWER_IO: - line << "VIO"; - break; - case slsDetectorDefs::V_POWER_A: - line << "VA"; - break; - case slsDetectorDefs::V_POWER_B: - line << "VB"; - break; - case slsDetectorDefs::V_POWER_C: - line << "VC"; - break; - case slsDetectorDefs::V_POWER_D: - line << "VD"; - break; - case slsDetectorDefs::V_POWER_CHIP: - line << "VCHIP"; - break; - default: - line << "VBAD"; - break; - - } - line << " " << dacsLabel->GetText() << endl; - return line.str(); -} - -void ctbPower::setValue(Long_t a) {ctbPower::setValue();} - -void ctbPower::setValue() { - cout << "***************************Setting power " << dacsEntry->GetIntNumber() << " " << id << " " << 1 << endl; - - try { - myDet->setVoltage(static_cast(id), dacsEntry->GetIntNumber()); - } CATCH_DISPLAY ("Could not set power " + to_string(id) + ".", "ctbPower::setValue") - - getValue(); -} - - -int ctbPower::getValue() { - try { - - int val = myDet->getVoltage(static_cast(id)).tsquash("Different values"); - cout << "****************************Getting power " << val << " " << id << " " << 1 << endl; - - dacsValue->SetText(to_string(val).c_str()); - if (val > 0) { - if (id != static_cast(slsDetectorDefs::V_POWER_CHIP)) - dacsLabel->SetOn(kTRUE); - } else { - dacsLabel->SetOn(kFALSE); - } - - return val; - - } CATCH_DISPLAY ("Could not get power " + to_string(id) + ".", "ctbPower::getValue") - - return -1; -} - - - -ctbPowers::ctbPowers(TGVerticalFrame* page, sls::Detector* det) : TGGroupFrame(page,"Power Supplies",kVerticalFrame) , myDet(det){ - - - SetTitlePos(TGGroupFrame::kLeft); - page->AddFrame(this,new TGLayoutHints( kLHintsTop | kLHintsExpandX , 10,10,10,10)); - MapWindow(); - - // cout << "window mapped " << endl; - - for (int idac=0; idacsetLabel(tit,1); - is=0; - } - - if (sscanf(line.c_str(),"VB %s",tit)) { - dacs[1]->setLabel(tit,1); - is=1; - } - - if (sscanf(line.c_str(),"VC %s",tit)) { - dacs[2]->setLabel(tit,1); - is=2; - } - - if (sscanf(line.c_str(),"VD %s",tit)) { - dacs[3]->setLabel(tit,1); - is=3; - } - - if (sscanf(line.c_str(),"VIO %s",tit)) { - dacs[4]->setLabel(tit,1); - is=4; - } - - if (sscanf(line.c_str(),"VCHIP %s",tit)) { - dacs[5]->setLabel(tit,1); - is=5; - } - - return is; - -} - -string ctbPowers::getPwrAlias() { - - ostringstream line; - - for (int i=0; igetLabel() << endl; - return line.str(); - -} - - - - -string ctbPowers::getPwrParameters() { - - ostringstream line; - line << "v_a" << " " << dacs[0]->getValue() << " mv" << endl; - line << "v_b" << " " << dacs[1]->getValue() << " mv" << endl; - line << "v_c" << " " << dacs[2]->getValue() << " mv" << endl; - line << "v_d" << " " << dacs[3]->getValue() << " mv" << endl; - line << "v_io" << " " << dacs[4]->getValue() << " mv" << endl; - line << "v_chip" << " " << dacs[5]->getValue() << " mv" << endl; - // for (int i=0; igetValue << endl; - // line << "dac:" << i << " " << dacs[i]->getValue() << endl; - // } - return line.str(); -} - - - -void ctbPowers::update() { - for (int idac=0; idacgetValue(); - - } -} diff --git a/ctbGui/ctbPowers.h b/ctbGui/ctbPowers.h deleted file mode 100644 index d0c39f1c7..000000000 --- a/ctbGui/ctbPowers.h +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package -#ifndef CTBPOWERS_H -#define CTBPOWERS_H - -#include - -#define NPOWERS 6 - - - -class TGTextEntry; -class TGLabel; -class TGNumberEntry; -class TGCheckButton; - - - - -namespace sls -{ - class Detector; -}; - -#include -using namespace std; - - -class ctbPower : public ctbDac { - - - - - public: - - ctbPower(TGGroupFrame* f, int i, sls::Detector* d); - - string getLabel(); - - int getValue(); - void setValue(); - void setValue(Long_t); - - ClassDef(ctbPower,0) -}; - - -class ctbPowers : public TGGroupFrame -{ - private: - - ctbPower *dacs[NPOWERS]; - - sls::Detector* myDet; - -public: - //ctbPowers(); - ctbPowers(TGVerticalFrame*, sls::Detector*); - - int setPwrAlias(string); - string getPwrAlias(); - string getPwrParameters(); - - void update(); - - ClassDef(ctbPowers,0) -}; - -#endif diff --git a/ctbGui/ctbSignals.cpp b/ctbGui/ctbSignals.cpp deleted file mode 100644 index f1e0c840f..000000000 --- a/ctbGui/ctbSignals.cpp +++ /dev/null @@ -1,543 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -#include "ctbSignals.h" -#include "ctbDefs.h" -#include "sls/Detector.h" - -using namespace std; - - - -//#define DEFAULTFN "run_0.encal" - - -ctbSignal::ctbSignal(TGFrame *page, int i, sls::Detector *det) - : TGHorizontalFrame(page, 800,50), myDet(det), id(i), hsig(NULL) { - - - TGHorizontalFrame *hframe=this; - char tit[100]; - - - - sprintf(tit, "BIT%d ",id); - - sLabel= new TGLabel(hframe, tit); - hframe->AddFrame( sLabel,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - sLabel->MapWindow(); - sLabel->SetTextJustify(kTextLeft); - - - - sOutput= new TGCheckButton(hframe, "Out"); - hframe->AddFrame( sOutput,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - sOutput->MapWindow(); - - - sOutput->Connect("Toggled(Bool_t)","ctbSignal",this,"ToggledOutput(Bool_t)"); - - sDbitList= new TGCheckButton(hframe, "DB List"); - hframe->AddFrame( sDbitList,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - sDbitList->MapWindow(); - - sDbitList->Connect("Toggled(Bool_t)","ctbSignal",this,"ToggledDbitList(Bool_t)"); - - - sPlot= new TGCheckButton(hframe, "Plot"); - hframe->AddFrame( sPlot,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - sPlot->MapWindow(); - - sPlot->Connect("Toggled(Bool_t)","ctbSignal",this,"ToggledPlot(Bool_t)"); - - fColorSel = new TGColorSelect(hframe, id+1, 0); - fColorSel->Connect("ColorSelected(Pixel_t)","ctbSignal",this,"ColorChanged(Pixel_t)"); - hframe->AddFrame(fColorSel, new TGLayoutHints(kLHintsTop | - kLHintsLeft, 2, 0, 2, 2)); - - - fColorSel->SetColor(TColor::Number2Pixel(id+1)); - - - ToggledOutput(kFALSE); - - - ToggledPlot(kFALSE); - - // if (id==63) { -// sOutput->SetOn(kTRUE); -// sOutput->SetEnabled(kFALSE); -// } -// #ifdef CTB -// if (id==62) { -// sOutput->SetOn(kTRUE); -// sOutput->SetEnabled(kFALSE); -// } - -// // if (id>=32 && id<48) -// // fixOutput(1); -// // else if (id>=48 && id<64) -// // fixOutput(0); - -// #endif -} -int ctbSignal::setSignalAlias(char *tit, int plot, int col) { - - if (tit) - sLabel->SetText(tit); - - if (plot>0) { - sPlot->SetOn(kTRUE,kTRUE); - } else if (plot==0) - sPlot->SetOn(kFALSE,kTRUE); - - if (col>=0) - fColorSel->SetColor(col);//TColor::Number2Pixel(col+1)); - - fColorSel->SetEnabled(sPlot->IsOn()); - return 0; - -} - -string ctbSignal::getSignalAlias() { - - - ostringstream oss; - oss << "BIT" << dec << id << " " << sLabel->GetText()->Data() << " " << sPlot->IsOn() << hex << " " << fColorSel->GetColor() << endl; - return oss.str(); - - - - -} -int ctbSignal::setOutput(Long64_t r) { - - - // cout << hex << r << dec <SetOn(kTRUE,kTRUE); - else - sOutput->SetOn(kFALSE,kTRUE); - - return sOutput->IsOn(); - -} - -int ctbSignal::fixOutput(int i) { - - if (i) { - sPlot->SetOn(kFALSE); - //sClock->SetOn(kFALSE,kTRUE); - sOutput->SetOn(kTRUE); - // sPlot->SetEnabled(kFALSE); - // sClock->SetEnabled(kTRUE); - } else { - sOutput->SetOn(kFALSE,kTRUE); - // sClock->SetOn(kFALSE); - // sClock->SetEnabled(kFALSE); - sPlot->SetEnabled(kTRUE); - } - sOutput->SetEnabled(kFALSE); - return 0; - -} - -int ctbSignal::setDbitList(Long64_t r) { - - if (r) - sDbitList->SetOn(kTRUE,kFALSE); - else - sDbitList->SetOn(kFALSE,kFALSE); - - return sDbitList->IsOn(); - -} - -int ctbSignal::isDbitList() { return sDbitList->IsOn();} -int ctbSignal::isOutput() { return sOutput->IsOn();} -int ctbSignal::isPlot() { return sPlot->IsOn();} -Pixel_t ctbSignal::getColor(){return fColorSel->GetColor();} - -void ctbSignal::ToggledOutput(Bool_t b) { - ToggledSignalOutput(id); - if (b) { - // sClock->SetEnabled(kTRUE); - sPlot->SetOn(kFALSE); - // sPlot->SetEnabled(kFALSE); - fColorSel->SetEnabled(kFALSE); - } else { - // sClock->SetEnabled(kFALSE); - // sClock->SetOn(kFALSE); - sPlot->SetEnabled(kTRUE); - if ( sPlot->IsOn()) - fColorSel->SetEnabled(kFALSE); - else - fColorSel->SetEnabled(kTRUE); - } - - -} - -void ctbSignal::ToggledDbitList(Bool_t b){ - Long_t mask=id; - ToggledSignalDbitList(mask); -} - - -void ctbSignal::ToggledPlot(Bool_t b){ - Long_t mask=b<SetEnabled(b); -} - -void ctbSignal::ColorChanged(Pixel_t p){ - ToggledSignalPlot(id); -} - - -void ctbSignal::ToggledSignalOutput(Int_t b) { - cout << "Toggle signal " << id << " " << b << " " << sOutput->IsOn() <AddFrame(this,new TGLayoutHints( kLHintsTop | kLHintsExpandX , 10,10,10,10)); - MapWindow(); - - - TGHorizontalFrame *hframe; - - TGHorizontalFrame* hhframe=new TGHorizontalFrame(this, 800,800); - AddFrame(hhframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hhframe->MapWindow(); - - TGVerticalFrame *vframe; - - - - - int idac=0; - for (idac=0; idacAddFrame(vframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - vframe->MapWindow(); - } - - - signals[idac]=new ctbSignal(vframe,idac,myDet); - signals[idac]->Connect("ToggledSignalOutput(Int_t)","ctbSignals",this,"ToggledOutReg(Int_t)"); - signals[idac]->Connect("ToggledSignalDbitList(Int_t)","ctbSignals",this,"ToggledDbitList(Int_t)"); - signals[idac]->Connect("ToggledSignalPlot(Int_t)","ctbSignals",this,"ToggledPlot(Int_t)"); - - vframe->AddFrame(signals[idac],new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - signals[idac]->MapWindow(); - - - } - - hframe=new TGHorizontalFrame(vframe, 800,50); - vframe->AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - TGLabel *label= new TGLabel(hframe, "IO Control Register: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - eIOCntrlRegister = new TGNumberEntry(hframe, 0, 16,999, TGNumberFormat::kNESHex, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELNoLimits); - - hframe->AddFrame(eIOCntrlRegister,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eIOCntrlRegister->MapWindow(); - eIOCntrlRegister->Resize(150,30); - - - - - hframe=new TGHorizontalFrame(vframe, 800,50); - vframe->AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - hframe->MapWindow(); - - - label= new TGLabel(hframe, "DBit Offset: "); - hframe->AddFrame(label,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 1, 1, 1, 1)); - label->MapWindow(); - label->SetTextJustify(kTextLeft); - - - eDbitOffset = new TGNumberEntry(hframe, 0, 9,999, TGNumberFormat::kNESInteger, - TGNumberFormat::kNEANonNegative, - TGNumberFormat::kNELNoLimits); - - hframe->AddFrame(eDbitOffset,new TGLayoutHints(kLHintsTop | kLHintsExpandX, 1, 1, 1, 1)); - eDbitOffset->MapWindow(); - eDbitOffset->Resize(150,30); - - - TGTextEntry *e= eDbitOffset->TGNumberEntry::GetNumberEntry(); - e->Connect("ReturnPressed()","ctbSignals",this,"setDbitOffset()"); - - e->Connect("ValueSet(Long_t)","ctbSignals",this,"setDbitOffset(Long_t)"); -} - - - - -int ctbSignals::setSignalAlias(string line) { - - int is=-1, plot=0, col=-1; - char tit[100]; - int narg=sscanf(line.c_str(),"BIT%d %s %d %d",&is,tit,&plot,&col); - if (narg<2) - return -1; - if (is>=0 && issetSignalAlias(tit,plot,col); - } - return is; - -} - -string ctbSignals::getSignalAlias() { - - ostringstream oss; - for (int is=0; isgetSignalAlias() << endl; - - - return oss.str(); - -} - - -void ctbSignals::update() { - try { - - Long64_t oreg = static_cast(myDet->getPatternIOControl().tsquash("Different values")); - cout << hex << oreg << dec << endl; - - for (int idac=0; idacsetOutput(oreg); - } - - } CATCH_DISPLAY ("Could not get patternIOcontrol.", "ctbSignals::update") - - if (myDet->getDetectorType().squash() == slsDetectorDefs::MOENCH) { - // enable all - for (int is=0; is<64; is++) { - signals[is]->setDbitList(1); - } - eDbitOffset->SetNumber(0); - } - - // ctb - else { - try { - - auto dbitlist = myDet->getRxDbitList().tsquash("Different values"); - // enable all - if (dbitlist.empty()) { - for (int is=0; is<64; is++) { - signals[is]->setDbitList(1); - } - } - else { - // disable all - for (int is=0; is<64; is++) { - signals[is]->setDbitList(0); - } - // enable selected - for (const auto &value : dbitlist) { - signals[value]->setDbitList(1); - } - } - - } CATCH_DISPLAY ("Could not get receiver dbit list.", "ctbSignals::update") - - try { - auto val = myDet->getRxDbitOffset().tsquash("Different values"); - eDbitOffset->SetNumber(val); - } CATCH_DISPLAY ("Could not get receiver dbit offset.", "ctbSignals::update") - } -} - - -string ctbSignals::getSignalParameters() { - - try { - - auto val = myDet->getPatternIOControl().tsquash("Different values"); - ostringstream line; - line << "patioctrl " << hex << val << dec << endl; - return line.str(); - - } CATCH_DISPLAY ("Could not get patternIOcontrol.", "ctbSignals::getSignalParameters") - - return (""); -} - -void ctbSignals::ToggledOutReg(Int_t mask) { - try { - - Long64_t oreg = static_cast(myDet->getPatternIOControl().tsquash("Different values")); - Long64_t m=((Long64_t)1)<isOutput()) { - cout << " or " << m ; - oreg|=m; - } else { - cout << " not " << ~m ; - oreg&=~m; - } - cout << " after " << oreg << endl; - - myDet->setPatternIOControl(static_cast(oreg)); - oreg = static_cast(myDet->getPatternIOControl().tsquash("Different values")); - cout << dec << sizeof(Long64_t) << " " << mask << " " << hex << m << " ioreg " << oreg << endl; - - eIOCntrlRegister->SetText(to_string(oreg).c_str()); - - } CATCH_DISPLAY ("Could not get/set patternIOcontrol.", "ctbSignals::ToggledOutReg") - -} - - - -void ctbSignals::ToggledDbitList(Int_t mask){ - try { - - auto dbitlist = myDet->getRxDbitList().tsquash("Different values"); - - // anyway all enabled - if ((dbitlist.empty()) && (signals[mask]->isDbitList())) { - ; - } - // set the dbitlist - else { - std::vector new_dbitlist; - for (int is=0; is<64; is++) { - if (signals[is]->isDbitList()){ - new_dbitlist.push_back(is); - cout << is << " " << new_dbitlist.size() - 1 << endl; - } - } - if (new_dbitlist.size() > 64) - new_dbitlist.clear(); - myDet->setRxDbitList(new_dbitlist); - // get list again - dbitlist = myDet->getRxDbitList().tsquash("Different values"); - } - - // enable all - if (dbitlist.empty()) { - for (int is=0; is<64; is++) { - signals[is]->setDbitList(1); - } - } - else { - // disable all - for (int is=0; is<64; is++) { - signals[is]->setDbitList(0); - } - // enable selected - for (const auto &value : dbitlist) { - signals[value]->setDbitList(1); - } - } - - } CATCH_DISPLAY ("Could not get/set receiver dbit list.", "ctbSignals::ToggledDbitList") -} - - - - -void ctbSignals::ToggledPlot(Int_t b) { - - Emit("ToggledSignalPlot(Int_t)", b); - -} - - -void ctbSignals::ToggledSignalPlot(Int_t b) { - - Emit("ToggledSignalPlot(Int_t)", b); - -} - - -Pixel_t ctbSignals::getColor(int i){ - if (i>=0 && igetColor(); - return static_cast(-1); -} - -int ctbSignals::getPlot(int i){ - if (i>=0 && iisPlot(); - return -1; -}; - -void ctbSignals::setDbitOffset(Long_t) { - setDbitOffset(); -} - -void ctbSignals::setDbitOffset(){ - try { - myDet->setRxDbitOffset(eDbitOffset->GetNumber()); - } CATCH_DISPLAY ("Could not set receiver dbit offset.", "ctbSignals::setDbitOffset") -} diff --git a/ctbGui/ctbSignals.h b/ctbGui/ctbSignals.h deleted file mode 100644 index eb3deb221..000000000 --- a/ctbGui/ctbSignals.h +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package -#ifndef CTBSIGNALS_H -#define CTBSIGNALS_H -#include - - -#define NSIGNALS 64 - -#define NIOSIGNALS 64 //for moench board was 52 - - -#define ADCLATCH 63 -#define DIGSIGLATCH 62 - - - - -class TGTextEntry; -class TGLabel; -class TGNumberEntry; -class TGCheckButton; -class TH1I; -class TGTextButton; -class TGColorSelect; - - - -class TGNumberEntry; -namespace sls -{ - class Detector; -}; -class ctbSignal; - -#include -using namespace std; - -class ctbSignal : public TGHorizontalFrame { - - // RQ_OBJECT("ctbSignal") - -private: - - TGLabel *sLabel; - TGCheckButton *sOutput; - TGCheckButton *sDbitList; - TGCheckButton *sPlot; - TGLabel *sValue; - TGNumberEntry *sEntry; - TGColorSelect *fColorSel; - - sls::Detector *myDet; - Int_t id; - - TH1I *hsig; - -public: - - ctbSignal(TGFrame *page, int i, sls::Detector *det); - int setSignalAlias(char *tit, int plot, int col); - string getSignalAlias(); - - TH1I *getPlot() {return hsig;}; - int setOutput(Long64_t); - int fixOutput(int); - int setDbitList(Long64_t); - - void ToggledOutput(Bool_t); - void ToggledDbitList(Bool_t); - void ToggledPlot(Bool_t); - void ColorChanged(Pixel_t); - - int isDbitList(); - int isOutput(); - int isPlot(); - Pixel_t getColor(); - - - void ToggledSignalOutput(Int_t); //*SIGNAL* - void ToggledSignalDbitList(Int_t); //*SIGNAL* - void ToggledSignalPlot(Int_t); //*SIGNAL* - - - - ClassDef(ctbSignal,0) -}; - -class ctbSignals : public TGGroupFrame { -private: - - ctbSignal *signals[NSIGNALS]; - - TGNumberEntry *eIOCntrlRegister; - TGNumberEntry *eDbitOffset; - - sls::Detector *myDet; - -public: - ctbSignals(TGVerticalFrame *page, sls::Detector *det); - int setSignalAlias(string line); - string getSignalAlias(); - - int getPlot(int); - Pixel_t getColor(int); - - void update(); - // void saveParameters(); - string getSignalParameters(); - - //void setDbitList(Int_t); - void setDbitOffset(Long_t); - void setDbitOffset(); - - void ToggledOutReg(Int_t); - void ToggledDbitList(Int_t); - void ToggledPlot(Int_t); - void ToggledSignalPlot(Int_t); //*SIGNAL* - - ClassDef(ctbSignals,0) -}; - -#endif diff --git a/ctbGui/ctbSlowAdcs.cpp b/ctbGui/ctbSlowAdcs.cpp deleted file mode 100644 index 6507e0d17..000000000 --- a/ctbGui/ctbSlowAdcs.cpp +++ /dev/null @@ -1,184 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package - -#include -#include -#include - -#include -#include -#include -#include - -#include "ctbSlowAdcs.h" -#include "ctbDefs.h" -#include "sls/Detector.h" -#include "sls/sls_detector_defs.h" - -using namespace std; - - - - -ctbSlowAdc::ctbSlowAdc(TGGroupFrame *page, int idac, sls::Detector *det) : TGHorizontalFrame(page, 800,50) , id(idac), myDet(det) { - - - TGHorizontalFrame *hframe=this; - - page->AddFrame(hframe,new TGLayoutHints(kLHintsTop | kLHintsExpandX , 1,1,1,1)); - MapWindow(); - - char tit[100]; - - - sprintf(tit, "SENSE %d:",idac-1000); - - dacsLabel= new TGLabel(hframe, tit);// new TGLabel(hframe, tit); - - - - hframe->AddFrame(dacsLabel,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - dacsLabel->MapWindow(); - dacsLabel->SetTextJustify(kTextLeft); - - - - - sprintf(tit, "xxx"); - dacsValue= new TGLabel(hframe, tit); - hframe->AddFrame( dacsValue,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - dacsValue->MapWindow(); - dacsValue->SetTextJustify(kTextLeft); - - - - TGTextButton *b= new TGTextButton(hframe, "Update"); - hframe->AddFrame( b,new TGLayoutHints(kLHintsTop | kLHintsLeft| kLHintsExpandX, 5, 5, 5, 5)); - b->MapWindow(); - b->SetTextJustify(kTextLeft); - - b->Connect("Clicked()","ctbSlowAdc",this,"getValue()"); -} - - - -int ctbSlowAdc::setLabel(char *tit) { - if(tit) - dacsLabel->SetText(tit); - - return id; - -} - -string ctbSlowAdc::getLabel() { - ostringstream line; - line << dacsLabel->GetText() << endl; - - // line << "DAC" << dec << id << " " << dacsUnit->IsOn() << endl; - - return line.str(); -} - - - -int ctbSlowAdc::getValue() { - try { - std::string s; - - // temp - if (id == static_cast(slsDetectorDefs::SLOW_ADC_TEMP)) { - - int val = myDet->getTemperature(static_cast(id)).tsquash("Different values"); - cout << "slow adc temp" << " " << val << endl; - - s = to_string(val) + " " + to_string(0x00b0) + "C";//�C - dacsValue->SetText(s.c_str()); - return val; - } - - // mv - else { - - int val = myDet->getSlowADC(static_cast(id)).tsquash("Different values"); - cout << "slow adc " << id << " " << val << endl; - - s = to_string(val) + " mV"; - dacsValue->SetText(s.c_str()); - return val; - } - - } CATCH_DISPLAY ("Could not get slow dac " + to_string(id) + ".", "ctbSlowAdc::getValue") - - return -1; -} - - - - - - - -ctbSlowAdcs::ctbSlowAdcs(TGVerticalFrame *page, sls::Detector *det) : TGGroupFrame(page,"Sense",kVerticalFrame) , myDet(det){ - - SetTitlePos(TGGroupFrame::kLeft); - page->AddFrame(this,new TGLayoutHints( kLHintsTop | kLHintsExpandX , 10,10,10,10)); - MapWindow(); - - // cout << "window mapped " << endl; - - - for (int idac=0; idacsetLabel((char*)"Temperature"); -} - - - - -int ctbSlowAdcs::setSlowAdcAlias(string line) { - - int is=-1, mv=0; - char tit[100]; - int narg=sscanf(line.c_str(),"SENSE%d %s %d",&is,tit,&mv); - if (narg<2) - return -1; - if (is>=0 && issetLabel(tit); - return is; - -} - -string ctbSlowAdcs::getSlowAdcAlias() { - - ostringstream line; - - for (int i=0; igetLabel() << endl; - return line.str(); -} - - - - -string ctbSlowAdcs::getAdcParameters() { - - ostringstream line; - - for (int i=0; igetValue << endl; - line << "adc:" << i << " " << adcs[i]->getValue() << endl; - } - line << "adc:-1" << adcs[NSLOWADCS]->getValue() << endl; - return line.str(); -} - - - -void ctbSlowAdcs::update() { - for (int idac=0; idacgetValue(); - } -} diff --git a/ctbGui/ctbSlowAdcs.h b/ctbGui/ctbSlowAdcs.h deleted file mode 100644 index a25bbe5b1..000000000 --- a/ctbGui/ctbSlowAdcs.h +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-other -// Copyright (C) 2021 Contributors to the SLS Detector Package - - -#ifndef CTBSLOWADCS_H -#define CTBSLOWADCS_H -#include - - -//#define NDACS 16 -#define NSLOWADCS 8 - - - - -class TGTextEntry; -class TGLabel; -class TGNumberEntry; -class TGCheckButton; -class TGTextButton; - - - -namespace sls -{ - class Detector; -}; - -#include -using namespace std; - -class ctbSlowAdc : public TGHorizontalFrame { - - - protected: - // TGLabel *dacsLabel; - // TGNumberEntry *dacsEntry; - // TGCheckButton *dacsUnit; - TGLabel *dacsLabel; - TGLabel *dacsValue; - int id; - - sls::Detector* myDet; - public: - ctbSlowAdc(TGGroupFrame*, int , sls::Detector*); - int getValue(); - - int setLabel(char *tit); - string getLabel(); - - - - ClassDef(ctbSlowAdc,0) -}; - - -class ctbSlowAdcs : public TGGroupFrame { -private: - - - - ctbSlowAdc *adcs[NSLOWADCS+1]; - - sls::Detector* myDet; - -public: - ctbSlowAdcs(TGVerticalFrame *page, sls::Detector*); - - int setSlowAdcAlias(string line); - // int setDacAlias(string line); - string getSlowAdcAlias(); - string getAdcParameters(); - - void update(); - - ClassDef(ctbSlowAdcs,0) -}; - -#endif - diff --git a/pyctbgui/.github/workflows/build-and-test-inplace.yml b/pyctbgui/.github/workflows/build-and-test-inplace.yml new file mode 100644 index 000000000..465bfcd13 --- /dev/null +++ b/pyctbgui/.github/workflows/build-and-test-inplace.yml @@ -0,0 +1,35 @@ +name: Build inplace, run tests and linting + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [ "3.10", "3.11"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install ".[dev]" + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Build C extension + run: | + make + - name: Run tests using pytest + run: | + make test + - name: check code formatting + run: | + make check_format + - name: lint the code with ruff + run: | + make lint \ No newline at end of file diff --git a/pyctbgui/.gitignore b/pyctbgui/.gitignore new file mode 100644 index 000000000..53456bd67 --- /dev/null +++ b/pyctbgui/.gitignore @@ -0,0 +1,133 @@ +nohup.out +.idea/ +tests/.tmp +run_* +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/pyctbgui/.pre-commit-config.yaml b/pyctbgui/.pre-commit-config.yaml new file mode 100644 index 000000000..f45532776 --- /dev/null +++ b/pyctbgui/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/google/yapf + rev: v0.40.1 + hooks: + - id: yapf + name: yapf + language: python + entry: yapf + args: [-i,--style,pyproject.toml] + types: [python] + +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.0.285 + hooks: + - id: ruff diff --git a/pyctbgui/CtbGui b/pyctbgui/CtbGui new file mode 100755 index 000000000..0312b6d61 --- /dev/null +++ b/pyctbgui/CtbGui @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +from PyQt5 import QtWidgets +import sys + +from pyctbgui.ui import MainWindow + +if __name__ == "__main__": + app = QtWidgets.QApplication(sys.argv) + main = MainWindow() + main.show() + app.exec_() diff --git a/pyctbgui/MANIFEST.in b/pyctbgui/MANIFEST.in new file mode 100644 index 000000000..f0aa8ba82 --- /dev/null +++ b/pyctbgui/MANIFEST.in @@ -0,0 +1,2 @@ +include pyctbgui/ui/*.ui +recursive-include src *.c *.h \ No newline at end of file diff --git a/pyctbgui/Makefile b/pyctbgui/Makefile new file mode 100644 index 000000000..f1b26ec09 --- /dev/null +++ b/pyctbgui/Makefile @@ -0,0 +1,39 @@ +# 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 new file mode 100644 index 000000000..d38bdfb40 --- /dev/null +++ b/pyctbgui/README.md @@ -0,0 +1,31 @@ +# New Chip Test Board Gui using Python +Prototype for a new python based GUI for the Chip Test Board + + +## Getting started + +```bash +git clone https://github.com/slsdetectorgroup/pyctbgui.git +cd pyctbgui +make #compiles the c extension inplace +./CtbGui +``` + + +## Display help for the Makefile + +``` +$ 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 +``` + + +## setup pre-commit hooks +``` +pre-commit install +``` diff --git a/pyctbgui/client.py b/pyctbgui/client.py new file mode 100644 index 000000000..36e92f937 --- /dev/null +++ b/pyctbgui/client.py @@ -0,0 +1,73 @@ +import json +import zmq +import numpy as np + +from slsdet import Detector +import matplotlib.pyplot as plt + +det = Detector() + +zmqIp = det.rx_zmqip +zmqport = det.rx_zmqport +zmq_stream = det.rx_zmqstream + + +def zmq_receiver(): + context = zmq.Context() + socket = context.socket(zmq.SUB) + socket.connect(f"tcp://{zmqIp}:{zmqport}") + socket.subscribe("") + + while True: + msg = socket.recv_multipart() + if len(msg) == 2: + header, data = msg + jsonHeader = json.loads(header) + print(jsonHeader) + print(f'Data size: {len(data)}') + data_array = np.array(np.frombuffer(data, dtype=np.uint16)) + break + return data_array + + +def analog(data_array): + adc_numbers = [ + 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 + ] + + n_pixels_per_sc = 5000 + + sc_width = 25 + analog_frame = np.zeros((400, 400)) + order_sc = np.zeros((400, 400)) + + for n_pixel in range(n_pixels_per_sc): + #these_dbits = int(digital_data[n_pixel]) + + for i_sc, adc_nr in enumerate(adc_numbers): + # ANALOG + col = ((adc_nr % 16) * sc_width) + (n_pixel % sc_width) + if i_sc < 16: + row = 199 - int(n_pixel / sc_width) + else: + row = 200 + int(n_pixel / sc_width) + + index_min = n_pixel * 32 + i_sc + + pixel_value = data_array[index_min] + analog_frame[row, col] = pixel_value + order_sc[row, col] = i_sc + return analog_frame + + +fig, ax = plt.subplots() +data = analog(data_array=zmq_receiver()) +im = ax.imshow(data) +ax.invert_yaxis() +fig.colorbar(im) +plt.show() +# pg.image(data, title="test") + +# if __name__ == '__main__': +# pg.QtWidgets.QApplication.exec_() diff --git a/pyctbgui/pyctbgui/__init__.py b/pyctbgui/pyctbgui/__init__.py new file mode 100644 index 000000000..561a39e95 --- /dev/null +++ b/pyctbgui/pyctbgui/__init__.py @@ -0,0 +1,2 @@ +from .utils.defines import Defines as defs +from .utils import bit_utils, alias_utility diff --git a/pyctbgui/pyctbgui/services/ADC.py b/pyctbgui/pyctbgui/services/ADC.py new file mode 100644 index 000000000..f470fc917 --- /dev/null +++ b/pyctbgui/pyctbgui/services/ADC.py @@ -0,0 +1,401 @@ +import logging +import typing +from functools import partial +from pathlib import Path + +import numpy as np +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 + +if typing.TYPE_CHECKING: + from pyctbgui.services import AcquisitionTab, PlotTab + + +class AdcTab(QtWidgets.QWidget): + + def __init__(self, parent, *args, **kwargs): + super().__init__(parent, *args, **kwargs) + uic.loadUi(Path(__file__).parent.parent / 'ui' / "adc.ui", parent) + self.view = parent + self.mainWindow = None + self.det = None + self.plotTab: PlotTab | None = None + self.acquisitionTab: AcquisitionTab | None = None + self.legend: LegendItem | None = None + self.logger = logging.getLogger('AdcTab') + + def setup_ui(self): + self.plotTab = self.mainWindow.plotTab + self.acquisitionTab = self.mainWindow.acquisitionTab + for i in range(Defines.adc.count): + self.setADCButtonColor(i, self.plotTab.getRandomColor()) + self.initializeAllAnalogPlots() + + self.legend = self.mainWindow.plotAnalogWaveform.getPlotItem().legend + self.legend.clear() + # subscribe to toggle legend + self.plotTab.subscribeToggleLegend(self.updateLegend) + + def initializeAllAnalogPlots(self): + self.mainWindow.plotAnalogWaveform = pg.plot() + self.mainWindow.plotAnalogWaveform.addLegend(colCount=Defines.colCount) + self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotAnalogWaveform, 1) + self.mainWindow.analogPlots = {} + waveform = np.zeros(1000) + for i in range(Defines.adc.count): + pen = pg.mkPen(color=self.getADCButtonColor(i), width=1) + legendName = getattr(self.view, f"labelADC{i}").text() + self.mainWindow.analogPlots[i] = self.mainWindow.plotAnalogWaveform.plot(waveform, + pen=pen, + name=legendName) + self.mainWindow.analogPlots[i].hide() + + self.mainWindow.plotAnalogImage = pg.ImageView() + self.mainWindow.nAnalogRows = 0 + self.mainWindow.nAnalogCols = 0 + self.mainWindow.analog_frame = np.zeros((self.mainWindow.nAnalogRows, self.mainWindow.nAnalogCols)) + self.mainWindow.plotAnalogImage.getView().invertY(False) + self.mainWindow.plotAnalogImage.setImage(self.mainWindow.analog_frame) + self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotAnalogImage, 2) + + def connect_ui(self): + for i in range(Defines.adc.count): + getattr(self.view, f"checkBoxADC{i}Inv").stateChanged.connect(partial(self.setADCInv, i)) + getattr(self.view, f"checkBoxADC{i}En").stateChanged.connect(partial(self.setADCEnable, i)) + getattr(self.view, f"checkBoxADC{i}Plot").stateChanged.connect(partial(self.setADCEnablePlot, i)) + getattr(self.view, f"pushButtonADC{i}").clicked.connect(partial(self.selectADCColor, i)) + self.view.checkBoxADC0_15En.stateChanged.connect(partial(self.setADCEnableRange, 0, Defines.adc.half)) + self.view.checkBoxADC16_31En.stateChanged.connect( + partial(self.setADCEnableRange, Defines.adc.half, Defines.adc.count)) + self.view.checkBoxADC0_15Plot.stateChanged.connect(partial(self.setADCEnablePlotRange, 0, Defines.adc.half)) + self.view.checkBoxADC16_31Plot.stateChanged.connect( + partial(self.setADCEnablePlotRange, Defines.adc.half, Defines.adc.count)) + self.view.checkBoxADC0_15Inv.stateChanged.connect(partial(self.setADCInvRange, 0, Defines.adc.half)) + self.view.checkBoxADC16_31Inv.stateChanged.connect( + partial(self.setADCInvRange, Defines.adc.half, Defines.adc.count)) + self.view.lineEditADCInversion.editingFinished.connect(self.setADCInvReg) + self.view.lineEditADCEnable.editingFinished.connect(self.setADCEnableReg) + + def refresh(self): + self.updateADCNames() + self.updateADCInv() + self.updateADCEnable() + + # ADCs Tab functions + + def getEnabledPlots(self): + """ + return plots that are shown (checkBoxADC#Plot is checked) + """ + enabledPlots = [] + self.legend.clear() + for i in range(Defines.adc.count): + if getattr(self.view, f'checkBoxADC{i}Plot').isChecked(): + plotName = getattr(self.view, f"labelADC{i}").text() + enabledPlots.append((self.mainWindow.analogPlots[i], plotName)) + return enabledPlots + + def updateLegend(self): + """ + update the legend for the ADC waveform plot + should be called after checking or unchecking plot checkbox + """ + if not self.mainWindow.showLegend: + self.legend.clear() + else: + for plot, name in self.getEnabledPlots(): + self.legend.addItem(plot, name) + + def updateADCNames(self): + """ + get adc names from detector and update them in the UI + """ + for i, adc_name in enumerate(self.det.getAdcNames()): + getattr(self.view, f"labelADC{i}").setText(adc_name) + + def processWaveformData(self, data: bytes, aSamples: int) -> dict[str, np.ndarray]: + """ + view function + plots processed waveform data + @param data: raw waveform data + @param aSamples: analog samples + @return: waveform dict returned to handle it for saving the output + """ + + waveforms = {} + analog_array = self._processWaveformData(data, aSamples, self.mainWindow.nADCEnabled) + idx = 0 + for i in range(Defines.adc.count): + checkBoxPlot = getattr(self.view, f"checkBoxADC{i}Plot") + checkBoxEn = getattr(self.view, f"checkBoxADC{i}En") + + if checkBoxEn.isChecked() and checkBoxPlot.isChecked(): + waveform = analog_array[:, idx] + idx += 1 + self.mainWindow.analogPlots[i].setData(waveform) + plotName = getattr(self.view, f"labelADC{i}").text() + waveforms[plotName] = waveform + return waveforms + + @recordOrApplyPedestal + def _processWaveformData(self, data: bytes, aSamples: int, nADCEnabled: int) -> np.ndarray: + """ + model function + processes raw waveform data + @param data: raw waveform data + @param aSamples: analog samples + @param nADCEnabled: number of enabled ADCs + @return: processed waveform data + """ + analog_array = np.array(np.frombuffer(data, dtype=np.uint16, count=nADCEnabled * aSamples)) + return analog_array.reshape(-1, nADCEnabled) + + def processImageData(self, data, aSamples): + """ + process the raw receiver data for analog image + data: raw analog image + aSamples: analog samples + """ + # get zoom state + viewBox = self.mainWindow.plotAnalogImage.getView() + state = viewBox.getState() + try: + self.mainWindow.analog_frame = self._processImageData(data, aSamples, self.mainWindow.nADCEnabled) + self.plotTab.ignoreHistogramSignal = True + self.mainWindow.plotAnalogImage.setImage(self.mainWindow.analog_frame.T) + except Exception: + self.logger.exception('Exception Caught') + self.mainWindow.statusbar.setStyleSheet("color:red") + message = f'Warning: Invalid size for Analog Image. Expected' \ + f' {self.mainWindow.nAnalogRows * self.mainWindow.nAnalogCols} ' \ + f'size, got {self.mainWindow.analog_frame.size} instead.' + self.acquisitionTab.updateCurrentFrame('Invalid Image') + + self.mainWindow.statusbar.showMessage(message) + print(message) + + self.plotTab.setFrameLimits(self.mainWindow.analog_frame) + + # keep the zoomed in state (not 1st image) + if self.mainWindow.firstAnalogImage: + self.mainWindow.firstAnalogImage = False + else: + viewBox.setState(state) + return self.mainWindow.analog_frame.T + + @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()) + + def getADCEnableReg(self): + retval = self.det.adcenable + if self.det.tengiga: + retval = self.det.adcenable10g + self.view.lineEditADCEnable.editingFinished.disconnect() + self.view.lineEditADCEnable.setText("0x{:08x}".format(retval)) + self.view.lineEditADCEnable.editingFinished.connect(self.setADCEnableReg) + return retval + + def setADCEnableReg(self): + self.view.lineEditADCEnable.editingFinished.disconnect() + try: + mask = int(self.mainWindow.lineEditADCEnable.text(), 16) + if self.det.tengiga: + self.det.adcenable10g = mask + else: + self.det.adcenable = mask + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "ADC Enable Fail", str(e), QtWidgets.QMessageBox.Ok) + pass + # TODO: handling double event exceptions + self.view.lineEditADCEnable.editingFinished.connect(self.setADCEnableReg) + self.updateADCEnable() + + def getADCEnable(self, i, mask): + checkBox = getattr(self.view, f"checkBoxADC{i}En") + checkBox.stateChanged.disconnect() + checkBox.setChecked(bit_is_set(mask, i)) + checkBox.stateChanged.connect(partial(self.setADCEnable, i)) + + def updateADCEnable(self): + retval = self.getADCEnableReg() + self.mainWindow.nADCEnabled = bin(retval).count('1') + for i in range(Defines.adc.count): + self.getADCEnable(i, retval) + self.getADCEnablePlot(i) + self.getADCEnableColor(i) + self.plotTab.addSelectedAnalogPlots(i) + self.getADCEnableRange(retval) + self.getADCEnablePlotRange() + + def setADCEnable(self, i): + checkBox = getattr(self.view, f"checkBoxADC{i}En") + try: + if self.det.tengiga: + enableMask = manipulate_bit(checkBox.isChecked(), self.det.adcenable10g, i) + self.det.adcenable10g = enableMask + else: + enableMask = manipulate_bit(checkBox.isChecked(), self.det.adcenable, i) + self.det.adcenable = enableMask + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "ADC Enable Fail", str(e), QtWidgets.QMessageBox.Ok) + + self.updateADCEnable() + + def getADCEnableRange(self, mask): + self.view.checkBoxADC0_15En.stateChanged.disconnect() + self.view.checkBoxADC16_31En.stateChanged.disconnect() + self.view.checkBoxADC0_15En.setChecked((mask & Defines.adc.BIT0_15_MASK) == Defines.adc.BIT0_15_MASK) + self.view.checkBoxADC16_31En.setChecked((mask & Defines.adc.BIT16_31_MASK) == Defines.adc.BIT16_31_MASK) + self.view.checkBoxADC0_15En.stateChanged.connect(partial(self.setADCEnableRange, 0, Defines.adc.half)) + self.view.checkBoxADC16_31En.stateChanged.connect( + partial(self.setADCEnableRange, Defines.adc.half, Defines.adc.count)) + + def setADCEnableRange(self, start_nr, end_nr): + mask = self.getADCEnableReg() + checkBox = getattr(self.view, f"checkBoxADC{start_nr}_{end_nr - 1}En") + for i in range(start_nr, end_nr): + mask = manipulate_bit(checkBox.isChecked(), mask, i) + try: + if self.det.tengiga: + self.det.adcenable10g = mask + else: + self.det.adcenable = mask + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "ADC Enable Fail", str(e), QtWidgets.QMessageBox.Ok) + pass + self.updateADCEnable() + + def getADCEnablePlot(self, i): + checkBox = getattr(self.view, f"checkBoxADC{i}En") + checkBoxPlot = getattr(self.view, f"checkBoxADC{i}Plot") + checkBoxPlot.setEnabled(checkBox.isChecked()) + + def setADCEnablePlot(self, i): + pushButton = getattr(self.view, f"pushButtonADC{i}") + checkBox = getattr(self.view, f"checkBoxADC{i}Plot") + pushButton.setEnabled(checkBox.isChecked()) + + self.getADCEnablePlotRange() + self.plotTab.addSelectedAnalogPlots(i) + self.updateLegend() + + def getADCEnablePlotRange(self): + self.view.checkBoxADC0_15Plot.stateChanged.disconnect() + self.view.checkBoxADC16_31Plot.stateChanged.disconnect() + self.view.checkBoxADC0_15Plot.setEnabled( + all(getattr(self.view, f"checkBoxADC{i}Plot").isEnabled() for i in range(Defines.adc.half))) + self.view.checkBoxADC16_31Plot.setEnabled( + all( + getattr(self.view, f"checkBoxADC{i}Plot").isEnabled() + for i in range(Defines.adc.half, Defines.adc.count))) + self.view.checkBoxADC0_15Plot.setChecked( + all(getattr(self.view, f"checkBoxADC{i}Plot").isChecked() for i in range(Defines.adc.half))) + self.view.checkBoxADC16_31Plot.setChecked( + all( + getattr(self.view, f"checkBoxADC{i}Plot").isChecked() + for i in range(Defines.adc.half, Defines.adc.count))) + self.view.checkBoxADC0_15Plot.stateChanged.connect(partial(self.setADCEnablePlotRange, 0, Defines.adc.half)) + self.view.checkBoxADC16_31Plot.stateChanged.connect( + partial(self.setADCEnablePlotRange, Defines.adc.half, Defines.adc.count)) + + def setADCEnablePlotRange(self, start_nr, end_nr): + checkBox = getattr(self.view, f"checkBoxADC{start_nr}_{end_nr - 1}Plot") + enable = checkBox.isChecked() + for i in range(start_nr, end_nr): + checkBox = getattr(self.view, f"checkBoxADC{i}Plot") + checkBox.setChecked(enable) + self.plotTab.addAllSelectedAnalogPlots() + + def getADCEnableColor(self, i): + checkBox = getattr(self.view, f"checkBoxADC{i}Plot") + pushButton = getattr(self.view, f"pushButtonADC{i}") + pushButton.setEnabled(checkBox.isEnabled() and checkBox.isChecked()) + + def selectADCColor(self, i): + pushButton = getattr(self.view, f"pushButtonADC{i}") + self.plotTab.showPalette(pushButton) + pen = pg.mkPen(color=self.getADCButtonColor(i), width=1) + self.mainWindow.analogPlots[i].setPen(pen) + + def getADCButtonColor(self, i): + pushButton = getattr(self.view, f"pushButtonADC{i}") + return self.plotTab.getActiveColor(pushButton) + + def setADCButtonColor(self, i, color): + pushButton = getattr(self.view, f"pushButtonADC{i}") + return self.plotTab.setActiveColor(pushButton, color) + + def getADCInvReg(self): + retval = self.det.adcinvert + self.view.lineEditADCInversion.editingFinished.disconnect() + self.view.lineEditADCInversion.setText("0x{:08x}".format(retval)) + self.view.lineEditADCInversion.editingFinished.connect(self.setADCInvReg) + return retval + + def setADCInvReg(self): + self.view.lineEditADCInversion.editingFinished.disconnect() + try: + self.det.adcinvert = int(self.mainWindow.lineEditADCInversion.text(), 16) + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "ADC Inversion Fail", str(e), QtWidgets.QMessageBox.Ok) + pass + # TODO: handling double event exceptions + self.view.lineEditADCInversion.editingFinished.connect(self.setADCInvReg) + self.updateADCInv() + + def getADCInv(self, i, inv): + checkBox = getattr(self.view, f"checkBoxADC{i}Inv") + checkBox.stateChanged.disconnect() + checkBox.setChecked(bit_is_set(inv, i)) + checkBox.stateChanged.connect(partial(self.setADCInv, i)) + + def updateADCInv(self): + retval = self.getADCInvReg() + for i in range(Defines.adc.count): + self.getADCInv(i, retval) + self.getADCInvRange(retval) + + def setADCInv(self, i): + out = self.det.adcinvert + checkBox = getattr(self.view, f"checkBoxADC{i}Inv") + mask = manipulate_bit(checkBox.isChecked(), out, i) + self.det.adcinvert = mask + + retval = self.getADCInvReg() + self.getADCInv(i, retval) + self.getADCInvRange(retval) + + def getADCInvRange(self, inv): + self.view.checkBoxADC0_15Inv.stateChanged.disconnect() + self.view.checkBoxADC16_31Inv.stateChanged.disconnect() + self.view.checkBoxADC0_15Inv.setChecked((inv & Defines.adc.BIT0_15_MASK) == Defines.adc.BIT0_15_MASK) + self.view.checkBoxADC16_31Inv.setChecked((inv & Defines.adc.BIT16_31_MASK) == Defines.adc.BIT16_31_MASK) + self.view.checkBoxADC0_15Inv.stateChanged.connect(partial(self.setADCInvRange, 0, Defines.adc.half)) + self.view.checkBoxADC16_31Inv.stateChanged.connect( + partial(self.setADCInvRange, Defines.adc.half, Defines.adc.count)) + + def setADCInvRange(self, start_nr, end_nr): + out = self.det.adcinvert + checkBox = getattr(self.view, f"checkBoxADC{start_nr}_{end_nr - 1}Inv") + mask = getattr(Defines.adc, f"BIT{start_nr}_{end_nr - 1}_MASK") + if checkBox.isChecked(): + self.det.adcinvert = out | mask + else: + self.det.adcinvert = out & ~mask + + self.updateADCInv() + + def saveParameters(self) -> list[str]: + return [ + f"adcenable {self.view.lineEditADCEnable.text()}", + f"adcinvert {self.view.lineEditADCInversion.text()}", + ] diff --git a/pyctbgui/pyctbgui/services/Acquisition.py b/pyctbgui/pyctbgui/services/Acquisition.py new file mode 100644 index 000000000..9ececaf82 --- /dev/null +++ b/pyctbgui/pyctbgui/services/Acquisition.py @@ -0,0 +1,719 @@ +import json +import typing +from pathlib import Path +import numpy as np +import time +import zmq +from PyQt5 import QtWidgets, uic +import logging + +from slsdet import readoutMode, runStatus +from pyctbgui.utils.defines import Defines +from pyctbgui.utils.numpyWriter.npy_writer import NumpyFileManager +from pyctbgui.utils.numpyWriter.npz_writer import NpzFileWriter + +if typing.TYPE_CHECKING: + # only used for type hinting. To avoid circular dependencies these + # won't be imported in runtime + from pyctbgui.services import SignalsTab, TransceiverTab, AdcTab, PlotTab + + +class AcquisitionTab(QtWidgets.QWidget): + + def __init__(self, parent): + self.__isWaveform = None + super().__init__(parent) + self.currentMeasurement = None + self.dsamples = None + self.stoppedFlag = None + self.asamples = None + self.tsamples = None + uic.loadUi(Path(__file__).parent.parent / 'ui' / "acquisition.ui", parent) + self.view = parent + self.mainWindow = None + self.det = None + self.signalsTab: SignalsTab = None + self.transceiverTab: TransceiverTab = None + self.adcTab: AdcTab = None + self.plotTab: PlotTab = None + self.writeNumpy: bool = False + self.outputDir: Path = Path('/') + self.outputFileNamePrefix: str = '' + self.numpyFileManagers: dict[str, NumpyFileManager] = {} + + self.logger = logging.getLogger('AcquisitionTab') + + def setup_ui(self): + self.signalsTab = self.mainWindow.signalsTab + self.transceiverTab = self.mainWindow.transceiverTab + self.adcTab = self.mainWindow.adcTab + self.plotTab = self.mainWindow.plotTab + self.toggleStartButton(False) + + def connect_ui(self): + # For Acquistions Tab + self.view.comboBoxROMode.currentIndexChanged.connect(self.setReadOut) + self.view.spinBoxRunF.editingFinished.connect(self.setRunFrequency) + self.view.spinBoxTransceiver.editingFinished.connect(self.setTransceiver) + self.view.spinBoxAnalog.editingFinished.connect(self.setAnalog) + self.view.spinBoxDigital.editingFinished.connect(self.setDigital) + self.view.spinBoxADCF.editingFinished.connect(self.setADCFrequency) + self.view.spinBoxADCPhase.editingFinished.connect(self.setADCPhase) + self.view.spinBoxADCPipeline.editingFinished.connect(self.setADCPipeline) + self.view.spinBoxDBITF.editingFinished.connect(self.setDBITFrequency) + self.view.spinBoxDBITPhase.editingFinished.connect(self.setDBITPhase) + self.view.spinBoxDBITPipeline.editingFinished.connect(self.setDBITPipeline) + + self.view.checkBoxFileWriteRaw.stateChanged.connect(self.setFileWrite) + self.view.checkBoxFileWriteNumpy.stateChanged.connect(self.setFileWriteNumpy) + self.view.lineEditFileName.editingFinished.connect(self.setFileName) + self.view.lineEditFilePath.editingFinished.connect(self.setFilePath) + self.view.pushButtonFilePath.clicked.connect(self.browseFilePath) + self.view.spinBoxAcquisitionIndex.editingFinished.connect(self.setAccquisitionIndex) + self.view.spinBoxFrames.editingFinished.connect(self.setFrames) + self.view.spinBoxPeriod.editingFinished.connect(self.setPeriod) + self.view.comboBoxPeriod.currentIndexChanged.connect(self.setPeriod) + self.view.spinBoxTriggers.editingFinished.connect(self.setTriggers) + + def refresh(self): + self.getReadout() + self.getRunFrequency() + self.getTransceiver() + self.getAnalog() + self.getDigital() + self.getADCFrequency() + self.getADCPhase() + self.getADCPipeline() + self.getDBITFrequency() + self.getDBITPhase() + self.getDBITPipeline() + self.getFileWrite() + self.getFileName() + self.getFilePath() + self.getAccquisitionIndex() + self.getFrames() + self.getTriggers() + self.getPeriod() + self.updateDetectorStatus(self.det.status) + + # Acquisition Tab functions + + def getReadout(self): + self.view.comboBoxROMode.currentIndexChanged.disconnect() + self.view.spinBoxAnalog.editingFinished.disconnect() + self.view.spinBoxDigital.editingFinished.disconnect() + self.view.spinBoxTransceiver.editingFinished.disconnect() + + self.mainWindow.romode = self.det.romode + self.view.comboBoxROMode.setCurrentIndex(self.mainWindow.romode.value) + match self.mainWindow.romode: + case readoutMode.ANALOG_ONLY: + self.view.spinBoxAnalog.setEnabled(True) + self.view.labelAnalog.setEnabled(True) + self.view.spinBoxDigital.setDisabled(True) + self.view.labelDigital.setDisabled(True) + self.view.labelTransceiver.setDisabled(True) + self.view.spinBoxTransceiver.setDisabled(True) + case readoutMode.DIGITAL_ONLY: + self.view.spinBoxAnalog.setDisabled(True) + self.view.labelAnalog.setDisabled(True) + self.view.spinBoxDigital.setEnabled(True) + self.view.labelDigital.setEnabled(True) + self.view.labelTransceiver.setDisabled(True) + self.view.spinBoxTransceiver.setDisabled(True) + case readoutMode.ANALOG_AND_DIGITAL: + self.view.spinBoxAnalog.setEnabled(True) + self.view.labelAnalog.setEnabled(True) + self.view.spinBoxDigital.setEnabled(True) + self.view.labelDigital.setEnabled(True) + self.view.labelTransceiver.setDisabled(True) + self.view.spinBoxTransceiver.setDisabled(True) + case readoutMode.TRANSCEIVER_ONLY: + self.view.spinBoxAnalog.setDisabled(True) + self.view.labelAnalog.setDisabled(True) + self.view.spinBoxDigital.setDisabled(True) + self.view.labelDigital.setDisabled(True) + self.view.labelTransceiver.setEnabled(True) + self.view.spinBoxTransceiver.setEnabled(True) + case _: + self.view.spinBoxAnalog.setDisabled(True) + self.view.labelAnalog.setDisabled(True) + self.view.spinBoxDigital.setEnabled(True) + self.view.labelDigital.setEnabled(True) + self.view.labelTransceiver.setEnabled(True) + self.view.spinBoxTransceiver.setEnabled(True) + + self.view.comboBoxROMode.currentIndexChanged.connect(self.setReadOut) + self.view.spinBoxAnalog.editingFinished.connect(self.setAnalog) + self.view.spinBoxDigital.editingFinished.connect(self.setDigital) + self.view.spinBoxTransceiver.editingFinished.connect(self.setTransceiver) + self.getAnalog() + self.getDigital() + self.plotTab.showPlot() + + def plotReferesh(self): + self.read_zmq() + + def setReadOut(self): + self.view.comboBoxROMode.currentIndexChanged.disconnect() + try: + if self.view.comboBoxROMode.currentIndex() == 0: + self.det.romode = readoutMode.ANALOG_ONLY + elif self.view.comboBoxROMode.currentIndex() == 1: + self.det.romode = readoutMode.DIGITAL_ONLY + elif self.view.comboBoxROMode.currentIndex() == 2: + self.det.romode = readoutMode.ANALOG_AND_DIGITAL + elif self.view.comboBoxROMode.currentIndex() == 3: + self.det.romode = readoutMode.TRANSCEIVER_ONLY + else: + self.det.romode = readoutMode.DIGITAL_AND_TRANSCEIVER + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "Readout Mode Fail", str(e), QtWidgets.QMessageBox.Ok) + # TODO: handling double event exceptions + self.view.comboBoxROMode.currentIndexChanged.connect(self.setReadOut) + self.getReadout() + + def getRunFrequency(self): + self.view.spinBoxRunF.editingFinished.disconnect() + self.view.spinBoxRunF.setValue(self.det.runclk) + self.view.spinBoxRunF.editingFinished.connect(self.setRunFrequency) + + def setRunFrequency(self): + self.view.spinBoxRunF.editingFinished.disconnect() + try: + self.det.runclk = self.view.spinBoxRunF.value() + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "Run Frequency Fail", str(e), QtWidgets.QMessageBox.Ok) + # TODO: handling double event exceptions + self.view.spinBoxRunF.editingFinished.connect(self.setRunFrequency) + self.getRunFrequency() + + def getTransceiver(self): + self.view.spinBoxTransceiver.editingFinished.disconnect() + self.tsamples = self.det.tsamples + self.view.spinBoxTransceiver.setValue(self.tsamples) + self.view.spinBoxTransceiver.editingFinished.connect(self.setTransceiver) + + def setTransceiver(self): + self.view.spinBoxTransceiver.editingFinished.disconnect() + try: + self.det.tsamples = self.view.spinBoxTransceiver.value() + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "Transceiver Samples Fail", str(e), + QtWidgets.QMessageBox.Ok) + # TODO: handling double event exceptions + self.view.spinBoxTransceiver.editingFinished.connect(self.setTransceiver) + self.getTransceiver() + + def getAnalog(self): + self.view.spinBoxAnalog.editingFinished.disconnect() + self.asamples = self.det.asamples + self.view.spinBoxAnalog.setValue(self.asamples) + self.view.spinBoxAnalog.editingFinished.connect(self.setAnalog) + + def setAnalog(self): + self.view.spinBoxAnalog.editingFinished.disconnect() + try: + self.det.asamples = self.view.spinBoxAnalog.value() + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "Digital Samples Fail", str(e), QtWidgets.QMessageBox.Ok) + # TODO: handling double event exceptions + self.view.spinBoxAnalog.editingFinished.connect(self.setAnalog) + self.getAnalog() + + def getDigital(self): + self.view.spinBoxDigital.editingFinished.disconnect() + self.dsamples = self.det.dsamples + self.view.spinBoxDigital.setValue(self.dsamples) + self.view.spinBoxDigital.editingFinished.connect(self.setDigital) + + def setDigital(self): + self.view.spinBoxDigital.editingFinished.disconnect() + try: + self.det.dsamples = self.view.spinBoxDigital.value() + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "Digital Samples Fail", str(e), QtWidgets.QMessageBox.Ok) + # TODO: handling double event exceptions + self.view.spinBoxDigital.editingFinished.connect(self.setDigital) + self.getDigital() + + def getADCFrequency(self): + self.view.spinBoxADCF.editingFinished.disconnect() + self.view.spinBoxADCF.setValue(self.det.adcclk) + self.view.spinBoxADCF.editingFinished.connect(self.setADCFrequency) + + def setADCFrequency(self): + self.view.spinBoxADCF.editingFinished.disconnect() + try: + self.det.adcclk = self.view.spinBoxADCF.value() + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "ADC Frequency Fail", str(e), QtWidgets.QMessageBox.Ok) + # TODO: handling double event exceptions + self.view.spinBoxADCF.editingFinished.connect(self.setADCFrequency) + self.getADCFrequency() + + def getADCPhase(self): + self.view.spinBoxADCPhase.editingFinished.disconnect() + self.view.spinBoxADCPhase.setValue(self.det.adcphase) + self.view.spinBoxADCPhase.editingFinished.connect(self.setADCPhase) + + def setADCPhase(self): + self.view.spinBoxADCPhase.editingFinished.disconnect() + try: + self.det.adcphase = self.view.spinBoxADCPhase.value() + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "ADC Phase Fail", str(e), QtWidgets.QMessageBox.Ok) + # TODO: handling double event exceptions + self.view.spinBoxADCPhase.editingFinished.connect(self.setADCPhase) + self.getADCPhase() + + def getADCPipeline(self): + self.view.spinBoxADCPipeline.editingFinished.disconnect() + self.view.spinBoxADCPipeline.setValue(self.det.adcpipeline) + self.view.spinBoxADCPipeline.editingFinished.connect(self.setADCPipeline) + + def setADCPipeline(self): + self.view.spinBoxADCPipeline.editingFinished.disconnect() + try: + self.det.adcpipeline = self.view.spinBoxADCPipeline.value() + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "ADC Pipeline Fail", str(e), QtWidgets.QMessageBox.Ok) + # TODO: handling double event exceptions + self.view.spinBoxADCPipeline.editingFinished.connect(self.setADCPipeline) + self.getADCPipeline() + + def getDBITFrequency(self): + self.view.spinBoxDBITF.editingFinished.disconnect() + self.view.spinBoxDBITF.setValue(self.det.dbitclk) + self.view.spinBoxDBITF.editingFinished.connect(self.setDBITFrequency) + + def setDBITFrequency(self): + self.view.spinBoxDBITF.editingFinished.disconnect() + try: + self.det.dbitclk = self.view.spinBoxDBITF.value() + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "DBit Frequency Fail", str(e), QtWidgets.QMessageBox.Ok) + # TODO: handling double event exceptions + self.view.spinBoxDBITF.editingFinished.connect(self.setDBITFrequency) + self.getDBITFrequency() + + def getDBITPhase(self): + self.view.spinBoxDBITPhase.editingFinished.disconnect() + self.view.spinBoxDBITPhase.setValue(self.det.dbitphase) + self.view.spinBoxDBITPhase.editingFinished.connect(self.setDBITPhase) + + def setDBITPhase(self): + self.view.spinBoxDBITPhase.editingFinished.disconnect() + try: + self.det.dbitphase = self.view.spinBoxDBITPhase.value() + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "DBit Phase Fail", str(e), QtWidgets.QMessageBox.Ok) + # TODO: handling double event exceptions + self.view.spinBoxDBITPhase.editingFinished.connect(self.setDBITPhase) + self.getDBITPhase() + + def getDBITPipeline(self): + self.view.spinBoxDBITPipeline.editingFinished.disconnect() + self.view.spinBoxDBITPipeline.setValue(self.det.dbitpipeline) + self.view.spinBoxDBITPipeline.editingFinished.connect(self.setDBITPipeline) + + def setDBITPipeline(self): + self.view.spinBoxDBITPipeline.editingFinished.disconnect() + try: + self.det.dbitpipeline = self.view.spinBoxDBITPipeline.value() + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "DBit Pipeline Fail", str(e), QtWidgets.QMessageBox.Ok) + # TODO: handling double event exceptions + self.view.spinBoxDBITPipeline.editingFinished.connect(self.setDBITPipeline) + self.getDBITPipeline() + + def getFileWrite(self): + self.view.checkBoxFileWriteRaw.stateChanged.disconnect() + self.view.checkBoxFileWriteRaw.setChecked(self.det.fwrite) + self.view.checkBoxFileWriteRaw.stateChanged.connect(self.setFileWrite) + + def setFileWrite(self): + self.det.fwrite = self.view.checkBoxFileWriteRaw.isChecked() + self.getFileWrite() + + def setFileWriteNumpy(self): + """ + slot for saving the data in numpy (.npy) format + """ + self.writeNumpy = not self.writeNumpy + + def getFileName(self): + """ + set the lineEditFilePath input widget to the filename value from the detector + """ + + self.view.lineEditFileName.editingFinished.disconnect() + fileName = self.det.fname + self.view.lineEditFileName.setText(fileName) + self.outputFileNamePrefix = fileName + self.view.lineEditFileName.editingFinished.connect(self.setFileName) + + def setFileName(self): + """ + slot for setting the filename from the widget to the detector + """ + self.det.fname = self.view.lineEditFileName.text() + self.getFileName() + + def getFilePath(self): + """ + set the lineEditFilePath input widget to the path value from the detector + """ + self.view.lineEditFilePath.editingFinished.disconnect() + path = self.det.fpath + self.view.lineEditFilePath.setText(str(path)) + self.view.lineEditFilePath.editingFinished.connect(self.setFilePath) + self.outputDir = path + + def setFilePath(self): + """ + slot to set the directory of the output for the detector + """ + self.det.fpath = Path(self.view.lineEditFilePath.text()) + self.getFilePath() + + def saveNumpyFile(self, data: np.ndarray | dict[str, np.ndarray], jsonHeader): + """ + save the acquisition data (waveform or image) in the specified path + save waveform in multiple .npy files + save image as npy file format + @note: frame number can be up to 100,000 so the data arrays cannot be fully loaded to memory + """ + if not self.writeNumpy: + return + if self.outputDir == Path('/'): + self.outputDir = Path('./') + if self.outputFileNamePrefix == '': + self.outputFileNamePrefix = 'run' + + for device in data: + if device not in self.numpyFileManagers: + tmpPath = self.outputDir / f'{self.outputFileNamePrefix}_{device}_{jsonHeader["fileIndex"]}.npy' + self.numpyFileManagers[device] = NumpyFileManager(tmpPath, 'w', data[device].shape, data[device].dtype) + self.numpyFileManagers[device].writeOneFrame(data[device]) + + if 'progress' in jsonHeader and jsonHeader['progress'] >= 100: + # close opened files after saving the last frame + self.closeOpenedNumpyFiles(jsonHeader) + + def closeOpenedNumpyFiles(self, jsonHeader): + """ + create npz file for waveform plots and close opened numpy files to persist their data + """ + if not self.writeNumpy: + return + if len(self.numpyFileManagers) == 0: + return + oneFile: bool = len(self.numpyFileManagers) == 1 + + filepaths = [npw.file.name for device, npw in self.numpyFileManagers.items()] + filenames = list(self.numpyFileManagers.keys()) + ext = 'npy' if oneFile else 'npz' + newPath = self.outputDir / f'{self.outputFileNamePrefix}_{jsonHeader["fileIndex"]}.{ext}' + + if not oneFile: + # if there is multiple .npy files group them in an .npz file + self.numpyFileManagers.clear() + NpzFileWriter.zipNpyFiles(newPath, filepaths, filenames, deleteOriginals=True, compressed=False) + else: + # rename files from "run_ADC0_0.npy" to "run_0.npy" if it is a single .npy file + oldPath = self.outputDir / f'{self.outputFileNamePrefix}_' \ + f'{self.numpyFileManagers.popitem()[0]}_{jsonHeader["fileIndex"]}.{ext}' + Path.rename(oldPath, newPath) + + self.logger.info(f'Saving numpy data in {newPath} Finished') + + def browseFilePath(self): + response = QtWidgets.QFileDialog.getExistingDirectory(parent=self.mainWindow, + caption="Select Path to Save Output File", + directory=str(Path.cwd()), + options=(QtWidgets.QFileDialog.ShowDirsOnly + | QtWidgets.QFileDialog.DontResolveSymlinks) + # filter='README (*.md *.ui)' + ) + if response: + self.view.lineEditFilePath.setText(response) + self.setFilePath() + + def getAccquisitionIndex(self): + self.view.spinBoxAcquisitionIndex.editingFinished.disconnect() + self.view.spinBoxAcquisitionIndex.setValue(self.det.findex) + self.view.spinBoxAcquisitionIndex.editingFinished.connect(self.setAccquisitionIndex) + + def setAccquisitionIndex(self): + self.det.findex = self.view.spinBoxAcquisitionIndex.value() + self.getAccquisitionIndex() + + def getFrames(self): + self.view.spinBoxFrames.editingFinished.disconnect() + self.view.spinBoxFrames.setValue(self.det.frames) + self.view.spinBoxFrames.editingFinished.connect(self.setFrames) + + def setFrames(self): + self.det.frames = self.view.spinBoxFrames.value() + self.getFrames() + + def getPeriod(self): + self.view.spinBoxPeriod.editingFinished.disconnect() + self.view.comboBoxPeriod.currentIndexChanged.disconnect() + + # Converting to right time unit for period + tPeriod = self.det.period + if tPeriod < 100e-9: + self.view.comboBoxPeriod.setCurrentIndex(3) + self.view.spinBoxPeriod.setValue(tPeriod / 1e-9) + elif tPeriod < 100e-6: + self.view.comboBoxPeriod.setCurrentIndex(2) + self.view.spinBoxPeriod.setValue(tPeriod / 1e-6) + elif tPeriod < 100e-3: + self.view.comboBoxPeriod.setCurrentIndex(1) + self.view.spinBoxPeriod.setValue(tPeriod / 1e-3) + else: + self.view.comboBoxPeriod.setCurrentIndex(0) + self.view.spinBoxPeriod.setValue(tPeriod) + + self.view.spinBoxPeriod.editingFinished.connect(self.setPeriod) + self.view.comboBoxPeriod.currentIndexChanged.connect(self.setPeriod) + + def setPeriod(self): + if self.view.comboBoxPeriod.currentIndex() == 0: + self.det.period = self.view.spinBoxPeriod.value() + elif self.view.comboBoxPeriod.currentIndex() == 1: + self.det.period = self.view.spinBoxPeriod.value() * (1e-3) + elif self.view.comboBoxPeriod.currentIndex() == 2: + self.det.period = self.view.spinBoxPeriod.value() * (1e-6) + else: + self.det.period = self.view.spinBoxPeriod.value() * (1e-9) + + self.getPeriod() + + def getTriggers(self): + self.view.spinBoxTriggers.editingFinished.disconnect() + self.view.spinBoxTriggers.setValue(self.det.triggers) + self.view.spinBoxTriggers.editingFinished.connect(self.setTriggers) + + def setTriggers(self): + self.det.triggers = self.view.spinBoxTriggers.value() + self.getTriggers() + + def updateDetectorStatus(self, status): + self.mainWindow.labelDetectorStatus.setText(status.name) + + def updateCurrentMeasurement(self): + self.mainWindow.labelCurrentMeasurement.setText(str(self.currentMeasurement)) + # print(f"Meausrement {self.currentMeasurement}") + + def updateCurrentFrame(self, val): + self.mainWindow.labelFrameNumber.setText(str(val)) + + def updateAcquiredFrames(self, val): + self.mainWindow.labelAcquiredFrames.setText(str(val)) + + def toggleAcquire(self): + if self.mainWindow.pushButtonStart.isChecked(): + self.plotTab.showPatternViewer(False) + self.acquire() + else: + self.stopAcquisition() + + def toggleStartButton(self, started): + if started: + self.mainWindow.pushButtonStart.setChecked(True) + self.mainWindow.pushButtonStart.setText('Stop') + else: + self.mainWindow.pushButtonStart.setChecked(False) + self.mainWindow.pushButtonStart.setText('Start') + + def stopAcquisition(self): + self.det.stop() + self.stoppedFlag = True + + def checkBeforeAcquire(self): + if self.plotTab.view.radioButtonImage.isChecked(): + # matterhorn image + if self.plotTab.view.comboBoxPlot.currentText() == "Matterhorn": + if self.mainWindow.romode not in [readoutMode.TRANSCEIVER_ONLY, readoutMode.DIGITAL_AND_TRANSCEIVER]: + QtWidgets.QMessageBox.warning(self.mainWindow, "Plot type", + "To read Matterhorn image, please enable transceiver readout mode", + QtWidgets.QMessageBox.Ok) + return False + if self.transceiverTab.getTransceiverEnableReg() != Defines.Matterhorn.tranceiverEnable: + QtWidgets.QMessageBox.warning( + self.mainWindow, "Plot type", "To read Matterhorn image, please set transceiver enable to " + + str(Defines.Matterhorn.tranceiverEnable), QtWidgets.QMessageBox.Ok) + return False + # moench04 image + elif self.plotTab.view.comboBoxPlot.currentText() == "Moench04": + if self.mainWindow.romode not in [readoutMode.ANALOG_ONLY, readoutMode.ANALOG_AND_DIGITAL]: + QtWidgets.QMessageBox.warning(self.mainWindow, "Plot type", + "To read Moench 04 image, please enable analog readout mode", + QtWidgets.QMessageBox.Ok) + return False + if self.mainWindow.nADCEnabled != 32: + QtWidgets.QMessageBox.warning(self.mainWindow, "Plot type", + "To read Moench 04 image, please enable all 32 adcs", + QtWidgets.QMessageBox.Ok) + return False + return True + + def acquire(self): + if not self.checkBeforeAcquire(): + self.toggleStartButton(False) + return + + self.stoppedFlag = False + self.toggleStartButton(True) + self.currentMeasurement = 0 + + # ensure zmq streaming is enabled + if self.det.rx_zmqstream == 0: + self.det.rx_zmqstream = 1 + + # some functions that must be updated for local values + self.getTransceiver() + self.getAnalog() + self.getDigital() + self.getReadout() + self.signalsTab.getDBitOffset() + self.adcTab.getADCEnableReg() + self.signalsTab.updateDigitalBitEnable() + self.transceiverTab.getTransceiverEnableReg() + + self.startMeasurement() + + def startMeasurement(self): + try: + self.updateCurrentMeasurement() + self.updateCurrentFrame(0) + self.updateAcquiredFrames(0) + self.mainWindow.progressBar.setValue(0) + + self.det.rx_start() + self.det.start() + time.sleep(Defines.Time_Wait_For_Packets_ms) + self.checkEndofAcquisition() + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "Acquire Fail", str(e), QtWidgets.QMessageBox.Ok) + self.checkEndofAcquisition() + + def checkEndofAcquisition(self): + caught = self.det.rx_framescaught[0] + self.updateAcquiredFrames(caught) + status = self.det.getDetectorStatus()[0] + self.updateDetectorStatus(status) + measurementDone = False + # print(f'status:{status}') + match status: + case runStatus.RUNNING: + pass + case runStatus.WAITING: + pass + case runStatus.TRANSMITTING: + pass + case _: + measurementDone = True + + # check for 500ms for no packets + # needs more time for 1g streaming out done + if measurementDone: + time.sleep(Defines.Time_Wait_For_Packets_ms) + if self.det.rx_framescaught[0] != caught: + measurementDone = False + + numMeasurments = self.view.spinBoxMeasurements.value() + if measurementDone: + + if self.det.rx_status == runStatus.RUNNING: + self.det.rx_stop() + if self.view.checkBoxFileWriteRaw.isChecked() or self.view.checkBoxFileWriteNumpy.isChecked(): + self.view.spinBoxAcquisitionIndex.stepUp() + self.setAccquisitionIndex() + # next measurement + self.currentMeasurement += 1 + if self.currentMeasurement < numMeasurments and not self.stoppedFlag: + self.startMeasurement() + else: + self.mainWindow.statusTimer.stop() + self.toggleStartButton(False) + else: + self.mainWindow.statusTimer.start(Defines.Time_Status_Refresh_ms) + + # For other functios + # Reading data from zmq and decoding it + def read_zmq(self): + # print("in readzmq") + try: + msg = self.socket.recv_multipart(flags=zmq.NOBLOCK) + if len(msg) != 2: + if len(msg) != 1: + print(f'len(msg) = {len(msg)}') + return + header, data = msg + jsonHeader = json.loads(header) + self.mainWindow.progressBar.setValue(int(jsonHeader['progress'])) + self.updateCurrentFrame(jsonHeader['frameIndex']) + + # waveform + waveforms = {} + if self.plotTab.view.radioButtonWaveform.isChecked(): + # analog + if self.mainWindow.romode.value in [0, 2]: + waveforms |= self.adcTab.processWaveformData(data, self.asamples) + # digital + if self.mainWindow.romode.value in [1, 2, 4]: + waveforms |= self.signalsTab.processWaveformData(data, self.asamples, self.dsamples) + # transceiver + if self.mainWindow.romode.value in [3, 4]: + waveforms |= self.transceiverTab.processWaveformData(data, self.dsamples) + # image + else: + # analog + if self.mainWindow.romode.value in [0, 2]: + waveforms['analog_image'] = self.adcTab.processImageData(data, self.asamples) + # transceiver + if self.mainWindow.romode.value in [3, 4]: + waveforms['tx_image'] = self.transceiverTab.processImageData(data, self.dsamples) + + self.saveNumpyFile(waveforms, jsonHeader) + except zmq.ZMQError: + pass + except Exception: + self.logger.exception("Exception caught") + + def setup_zmq(self): + self.det.rx_zmqstream = 1 + self.zmqIp = self.det.rx_zmqip + self.zmqport = self.det.rx_zmqport + self.zmq_stream = self.det.rx_zmqstream + + self.context = zmq.Context() + self.socket = self.context.socket(zmq.SUB) + self.socket.connect(f"tcp://{self.zmqIp}:{self.zmqport}") + self.socket.subscribe("") + + def saveParameters(self) -> list[str]: + return [ + f'romode {self.view.comboBoxROMode.currentText().lower()}', + f'runclk {self.view.spinBoxRunF.value()}', + f'adcclk {self.view.spinBoxADCF.value()}', + f'adcphase {self.view.spinBoxADCPhase.value()}', + f'adcpipeline {self.view.spinBoxADCPipeline.value()}', + f'dbitclk {self.view.spinBoxDBITF.value()}', + f'dbitphase {self.view.spinBoxDBITPhase.value()}', + f'dbitpipeline {self.view.spinBoxDBITPipeline.value()}', + f'fwrite {int(self.view.checkBoxFileWriteRaw.isChecked())}', + f'fname {self.view.lineEditFileName.text()}', + f'fpath {self.view.lineEditFilePath.text()}', + f'findex {self.view.spinBoxAcquisitionIndex.value()}', + f'frames {self.view.spinBoxFrames.value()}', + f'triggers {self.view.spinBoxTriggers.value()}', + f'period {self.view.spinBoxPeriod.value()} {self.view.comboBoxPeriod.currentText().lower()}', + f'asamples {self.view.spinBoxAnalog.value()}', + f'dsamples {self.view.spinBoxDigital.value()}', + f'tsamples {self.view.spinBoxTransceiver.value()}', + ] diff --git a/pyctbgui/pyctbgui/services/DACs.py b/pyctbgui/pyctbgui/services/DACs.py new file mode 100644 index 000000000..1bd7ab721 --- /dev/null +++ b/pyctbgui/pyctbgui/services/DACs.py @@ -0,0 +1,170 @@ +from functools import partial +from pathlib import Path + +from PyQt5 import QtWidgets, uic +from pyctbgui.utils.defines import Defines + +from slsdet import dacIndex + + +class DacTab(QtWidgets.QWidget): + + def __init__(self, parent): + super().__init__(parent) + uic.loadUi(Path(__file__).parent.parent / 'ui' / "Dacs.ui", parent) + self.view = parent + + def setup_ui(self): + for i in range(Defines.dac.count): + dac = getattr(dacIndex, f"DAC_{i}") + getattr(self.view, f"spinBoxDAC{i}").setValue(self.det.getDAC(dac)[0]) + + if self.det.highvoltage == 0: + self.view.spinBoxHighVoltage.setDisabled(True) + self.view.checkBoxHighVoltage.setChecked(False) + + def connect_ui(self): + n_dacs = len(self.det.daclist) + for i in range(n_dacs): + getattr(self.view, f"spinBoxDAC{i}").editingFinished.connect(partial(self.setDAC, i)) + getattr(self.view, f"checkBoxDAC{i}").stateChanged.connect(partial(self.setDACTristate, i)) + getattr(self.view, f"checkBoxDAC{i}mV").stateChanged.connect(partial(self.getDAC, i)) + + self.view.comboBoxADCVpp.currentIndexChanged.connect(self.setADCVpp) + self.view.spinBoxHighVoltage.editingFinished.connect(self.setHighVoltage) + self.view.checkBoxHighVoltage.stateChanged.connect(self.setHighVoltage) + + def refresh(self): + self.updateDACNames() + for i in range(Defines.dac.count): + self.getDACTristate(i) + self.getDAC(i) + + self.getADCVpp() + self.getHighVoltage() + + def updateDACNames(self): + for i, name in enumerate(self.det.getDacNames()): + getattr(self.view, f"checkBoxDAC{i}").setText(name) + + def getDACTristate(self, i): + checkBox = getattr(self.view, f"checkBoxDAC{i}") + dac = getattr(dacIndex, f"DAC_{i}") + checkBox.stateChanged.disconnect() + if (self.det.getDAC(dac)[0]) == -100: + checkBox.setChecked(False) + else: + checkBox.setChecked(True) + checkBox.stateChanged.connect(partial(self.setDACTristate, i)) + + def setDACTristate(self, i): + checkBox = getattr(self.view, f"checkBoxDAC{i}") + if not checkBox.isChecked(): + self.setDAC(i) + self.getDAC(i) + + def getDAC(self, i): + checkBox = getattr(self.view, f"checkBoxDAC{i}") + checkBoxmV = getattr(self.view, f"checkBoxDAC{i}mV") + spinBox = getattr(self.view, f"spinBoxDAC{i}") + label = getattr(self.view, f"labelDAC{i}") + dac = getattr(dacIndex, f"DAC_{i}") + + checkBox.stateChanged.disconnect() + checkBoxmV.stateChanged.disconnect() + spinBox.editingFinished.disconnect() + + # do not uncheck automatically + if self.det.getDAC(dac)[0] != -100: + checkBox.setChecked(True) + + if checkBox.isChecked(): + spinBox.setEnabled(True) + checkBoxmV.setEnabled(True) + else: + spinBox.setDisabled(True) + checkBoxmV.setDisabled(True) + + in_mv = checkBoxmV.isChecked() and checkBox.isChecked() + dacValue = self.det.getDAC(dac, in_mv)[0] + unit = "mV" if in_mv else "" + label.setText(f"{dacValue} {unit}") + spinBox.setValue(dacValue) + + checkBox.stateChanged.connect(partial(self.setDACTristate, i)) + checkBoxmV.stateChanged.connect(partial(self.getDAC, i)) + spinBox.editingFinished.connect(partial(self.setDAC, i)) + + def setDAC(self, i): + checkBoxDac = getattr(self.view, f"checkBoxDAC{i}") + checkBoxmV = getattr(self.view, f"checkBoxDAC{i}mV") + spinBox = getattr(self.view, f"spinBoxDAC{i}") + dac = getattr(dacIndex, f"DAC_{i}") + + value = -100 + if checkBoxDac.isChecked(): + value = spinBox.value() + in_mV = checkBoxDac.isChecked() and checkBoxmV.isChecked() + self.det.setDAC(dac, value, in_mV) + self.getDAC(i) + + def getADCVpp(self): + retval = self.det.adcvpp + self.view.labelADCVpp.setText(f'Mode: {str(retval)}') + + self.view.comboBoxADCVpp.currentIndexChanged.disconnect() + self.view.comboBoxADCVpp.setCurrentIndex(retval) + self.view.comboBoxADCVpp.currentIndexChanged.connect(self.setADCVpp) + + def setADCVpp(self): + self.det.adcvpp = self.view.comboBoxADCVpp.currentIndex() + self.getADCVpp() + + def getHighVoltage(self): + retval = self.det.highvoltage + self.view.labelHighVoltage.setText(str(retval)) + + self.view.spinBoxHighVoltage.editingFinished.disconnect() + self.view.checkBoxHighVoltage.stateChanged.disconnect() + + self.view.spinBoxHighVoltage.setValue(retval) + if retval: + self.view.checkBoxHighVoltage.setChecked(True) + if self.view.checkBoxHighVoltage.isChecked(): + self.view.spinBoxHighVoltage.setEnabled(True) + + self.view.spinBoxHighVoltage.editingFinished.connect(self.setHighVoltage) + self.view.checkBoxHighVoltage.stateChanged.connect(self.setHighVoltage) + + def setHighVoltage(self): + value = 0 + if self.view.checkBoxHighVoltage.isChecked(): + value = self.view.spinBoxHighVoltage.value() + try: + self.det.highvoltage = value + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "High Voltage Fail", str(e), QtWidgets.QMessageBox.Ok) + pass + self.getHighVoltage() + + def saveParameters(self) -> list: + """ + save parameters for the current tab + @return: list of commands + """ + commands = [] + for i in range(Defines.dac.count): + # if checkbox disabled put -100 in dac units + enabled = getattr(self.view, f"checkBoxDAC{i}").isChecked() + if not enabled: + commands.append(f"dac {i} -100") + # else put the value in dac or mV units + else: + value = getattr(self.view, f"spinBoxDAC{i}").value() + inMV = getattr(self.view, f"checkBoxDAC{i}mV").isChecked() + unit = " mV" if inMV else "" + commands.append(f"dac {i} {value}{unit}") + + commands.append(f"adcvpp {self.view.comboBoxADCVpp.currentText()} mV") + commands.append(f"highvoltage {self.view.spinBoxHighVoltage.value()}") + return commands diff --git a/pyctbgui/pyctbgui/services/Pattern.py b/pyctbgui/pyctbgui/services/Pattern.py new file mode 100644 index 000000000..e7e274ff9 --- /dev/null +++ b/pyctbgui/pyctbgui/services/Pattern.py @@ -0,0 +1,456 @@ +import os +from functools import partial +from pathlib import Path + +from PyQt5 import QtWidgets, uic +import matplotlib.pyplot as plt +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar + +from pyctbgui.utils.defines import Defines +from pyctbgui.utils.plotPattern import PlotPattern + + +class PatternTab(QtWidgets.QWidget): + + def __init__(self, parent): + super().__init__(parent) + uic.loadUi(Path(__file__).parent.parent / 'ui' / "pattern.ui", parent) + self.view = parent + self.mainWindow = None + self.det = None + self.plotTab = None + + def setup_ui(self): + # Pattern Tab + self.plotTab = self.mainWindow.plotTab + + for i in range(len(Defines.Colors)): + self.view.comboBoxPatColor.addItem(Defines.Colors[i]) + self.view.comboBoxPatWaitColor.addItem(Defines.Colors[i]) + self.view.comboBoxPatLoopColor.addItem(Defines.Colors[i]) + for i in range(len(Defines.LineStyles)): + self.view.comboBoxPatWaitLineStyle.addItem(Defines.LineStyles[i]) + self.view.comboBoxPatLoopLineStyle.addItem(Defines.LineStyles[i]) + self.updateDefaultPatViewerParameters() + self.view.comboBoxPatColorSelect.setCurrentIndex(0) + self.view.comboBoxPatWait.setCurrentIndex(0) + self.view.comboBoxPatLoop.setCurrentIndex(0) + self.view.spinBoxPatClockSpacing.setValue(self.clock_vertical_lines_spacing) + self.view.checkBoxPatShowClockNumber.setChecked(self.show_clocks_number) + self.view.doubleSpinBoxLineWidth.setValue(self.line_width) + self.view.lineEditPatternFile.setText(self.det.patfname[0]) + # rest gets updated after connecting to slots + # pattern viewer plot area + self.figure, self.ax = plt.subplots() + self.canvas = FigureCanvas(self.figure) + self.toolbar = NavigationToolbar(self.canvas, self.view) + self.mainWindow.gridLayoutPatternViewer.addWidget(self.toolbar) + self.mainWindow.gridLayoutPatternViewer.addWidget(self.canvas) + self.figure.clear() + + def connect_ui(self): + # For Pattern Tab + self.view.lineEditStartAddress.editingFinished.connect(self.setPatLimitAddress) + self.view.lineEditStopAddress.editingFinished.connect(self.setPatLimitAddress) + for i in range(Defines.pattern.loops_count): + getattr(self.view, + f"lineEditLoop{i}Start").editingFinished.connect(partial(self.setPatLoopStartStopAddress, i)) + getattr(self.view, + f"lineEditLoop{i}Stop").editingFinished.connect(partial(self.setPatLoopStartStopAddress, i)) + getattr(self.view, f"lineEditLoop{i}Wait").editingFinished.connect(partial(self.setPatLoopWaitAddress, i)) + getattr(self.view, + f"spinBoxLoop{i}Repetition").editingFinished.connect(partial(self.setPatLoopRepetition, i)) + getattr(self.view, f"spinBoxLoop{i}WaitTime").editingFinished.connect(partial(self.setPatLoopWaitTime, i)) + self.view.pushButtonCompiler.clicked.connect(self.setCompiler) + self.view.pushButtonUncompiled.clicked.connect(self.setUncompiledPatternFile) + self.view.pushButtonPatternFile.clicked.connect(self.setPatternFile) + self.view.pushButtonLoadPattern.clicked.connect(self.loadPattern) + + self.view.comboBoxPatColorSelect.currentIndexChanged.connect(self.getPatViewerColors) + self.view.comboBoxPatWait.currentIndexChanged.connect(self.getPatViewerWaitParameters) + self.view.comboBoxPatLoop.currentIndexChanged.connect(self.getPatViewerLoopParameters) + + self.view.comboBoxPatColor.currentIndexChanged.connect(self.updatePatViewerParameters) + self.view.comboBoxPatWaitColor.currentIndexChanged.connect(self.updatePatViewerParameters) + self.view.comboBoxPatLoopColor.currentIndexChanged.connect(self.updatePatViewerParameters) + self.view.comboBoxPatWaitLineStyle.currentIndexChanged.connect(self.updatePatViewerParameters) + self.view.comboBoxPatLoopLineStyle.currentIndexChanged.connect(self.updatePatViewerParameters) + self.view.doubleSpinBoxWaitAlpha.editingFinished.connect(self.updatePatViewerParameters) + self.view.doubleSpinBoxLoopAlpha.editingFinished.connect(self.updatePatViewerParameters) + self.view.doubleSpinBoxWaitAlphaRect.editingFinished.connect(self.updatePatViewerParameters) + self.view.doubleSpinBoxLoopAlphaRect.editingFinished.connect(self.updatePatViewerParameters) + self.view.spinBoxPatClockSpacing.editingFinished.connect(self.updatePatViewerParameters) + self.view.checkBoxPatShowClockNumber.stateChanged.connect(self.updatePatViewerParameters) + self.view.doubleSpinBoxLineWidth.editingFinished.connect(self.updatePatViewerParameters) + self.view.pushButtonViewPattern.clicked.connect(self.viewPattern) + + def refresh(self): + self.getPatLimitAddress() + for i in range(Defines.pattern.loops_count): + self.getPatLoopStartStopAddress(i) + self.getPatLoopWaitAddress(i) + self.getPatLoopRepetition(i) + self.getPatLoopWaitTime(i) + + # Pattern Tab functions + + def getPatLimitAddress(self): + retval = self.det.patlimits + self.view.lineEditStartAddress.editingFinished.disconnect() + self.view.lineEditStopAddress.editingFinished.disconnect() + self.view.lineEditStartAddress.setText("0x{:04x}".format(retval[0])) + self.view.lineEditStopAddress.setText("0x{:04x}".format(retval[1])) + self.view.lineEditStartAddress.editingFinished.connect(self.setPatLimitAddress) + self.view.lineEditStopAddress.editingFinished.connect(self.setPatLimitAddress) + + def setPatLimitAddress(self): + self.view.lineEditStartAddress.editingFinished.disconnect() + self.view.lineEditStopAddress.editingFinished.disconnect() + try: + start = int(self.view.lineEditStartAddress.text(), 16) + stop = int(self.view.lineEditStopAddress.text(), 16) + self.det.patlimits = [start, stop] + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "Pattern Limit Address Fail", str(e), + QtWidgets.QMessageBox.Ok) + pass + # TODO: handling double event exceptions + self.view.lineEditStartAddress.editingFinished.connect(self.setPatLimitAddress) + self.view.lineEditStopAddress.editingFinished.connect(self.setPatLimitAddress) + self.getPatLimitAddress() + + def getPatLoopStartStopAddress(self, level): + retval = self.det.patloop[level] + lineEditStart = getattr(self.view, f"lineEditLoop{level}Start") + lineEditStop = getattr(self.view, f"lineEditLoop{level}Stop") + lineEditStart.editingFinished.disconnect() + lineEditStop.editingFinished.disconnect() + lineEditStart.setText("0x{:04x}".format(retval[0])) + lineEditStop.setText("0x{:04x}".format(retval[1])) + lineEditStart.editingFinished.connect(partial(self.setPatLoopStartStopAddress, level)) + lineEditStop.editingFinished.connect(partial(self.setPatLoopStartStopAddress, level)) + + def setPatLoopStartStopAddress(self, level): + lineEditStart = getattr(self.view, f"lineEditLoop{level}Start") + lineEditStop = getattr(self.view, f"lineEditLoop{level}Stop") + lineEditStart.editingFinished.disconnect() + lineEditStop.editingFinished.disconnect() + try: + start = int(lineEditStart.text(), 16) + stop = int(lineEditStop.text(), 16) + self.det.patloop[level] = [start, stop] + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "Pattern Loop Start Stop Address Fail", str(e), + QtWidgets.QMessageBox.Ok) + pass + # TODO: handling double event exceptions + lineEditStart.editingFinished.connect(partial(self.setPatLoopStartStopAddress, level)) + lineEditStop.editingFinished.connect(partial(self.setPatLoopStartStopAddress, level)) + self.getPatLoopStartStopAddress(level) + + def getPatLoopWaitAddress(self, level): + retval = self.det.patwait[level] + lineEdit = getattr(self.view, f"lineEditLoop{level}Wait") + lineEdit.editingFinished.disconnect() + lineEdit.setText("0x{:04x}".format(retval)) + lineEdit.editingFinished.connect(partial(self.setPatLoopWaitAddress, level)) + + def setPatLoopWaitAddress(self, level): + lineEdit = getattr(self.view, f"lineEditLoop{level}Wait") + lineEdit.editingFinished.disconnect() + try: + addr = int(lineEdit.text(), 16) + self.det.patwait[level] = addr + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "Pattern Wait Address Fail", str(e), + QtWidgets.QMessageBox.Ok) + pass + # TODO: handling double event exceptions + lineEdit.editingFinished.connect(partial(self.setPatLoopWaitAddress, level)) + self.getPatLoopWaitAddress(level) + + def getPatLoopRepetition(self, level): + retval = self.det.patnloop[level] + spinBox = getattr(self.view, f"spinBoxLoop{level}Repetition") + spinBox.editingFinished.disconnect() + spinBox.setValue(retval) + spinBox.editingFinished.connect(partial(self.setPatLoopRepetition, level)) + + def setPatLoopRepetition(self, level): + spinBox = getattr(self.view, f"spinBoxLoop{level}Repetition") + self.det.patnloop[level] = spinBox.value() + self.getPatLoopRepetition(level) + + def getPatLoopWaitTime(self, level): + retval = self.det.patwaittime[level] + spinBox = getattr(self.view, f"spinBoxLoop{level}WaitTime") + spinBox.editingFinished.disconnect() + spinBox.setValue(retval) + spinBox.editingFinished.connect(partial(self.setPatLoopWaitTime, level)) + + def setPatLoopWaitTime(self, level): + spinBox = getattr(self.view, f"spinBoxLoop{level}WaitTime") + self.det.patwaittime[level] = spinBox.value() + self.getPatLoopWaitTime(level) + + def setCompiler(self): + response = QtWidgets.QFileDialog.getOpenFileName( + parent=self.mainWindow, + caption="Select a compiler file", + directory=str(Path.cwd()), + # filter='README (*.md *.ui)' + ) + if response[0]: + self.view.lineEditCompiler.setText(response[0]) + + def setUncompiledPatternFile(self): + filt = 'Pattern code(*.py *.c)' + folder = Path(self.det.patfname[0]).parent + if not folder: + folder = Path.cwd() + response = QtWidgets.QFileDialog.getOpenFileName(parent=self.mainWindow, + caption="Select an uncompiled pattern file", + directory=str(folder), + filter=filt) + if response[0]: + self.view.lineEditUncompiled.setText(response[0]) + + def setPatternFile(self): + filt = 'Pattern file(*.pyat *.pat)' + folder = Path(self.det.patfname[0]).parent + if not folder: + folder = Path.cwd() + response = QtWidgets.QFileDialog.getOpenFileName(parent=self.mainWindow, + caption="Select a compiled pattern file", + directory=str(folder), + filter=filt) + if response[0]: + self.view.lineEditPatternFile.setText(response[0]) + + def compilePattern(self): + compilerFile = self.view.lineEditCompiler.text() + if not compilerFile: + QtWidgets.QMessageBox.warning(self.mainWindow, "Compile Fail", "No compiler selected. Please select one.", + QtWidgets.QMessageBox.Ok) + return "" + + pattern_file = self.view.lineEditUncompiled.text() + + # if old compile file exists, backup and remove to ensure old copy not loaded + oldFile = Path(pattern_file + 'at') + if oldFile.is_file(): + print("Moving old compiled pattern file to _bck") + exit_status = os.system('mv ' + str(oldFile) + ' ' + str(oldFile) + '_bkup') + if exit_status != 0: + retval = QtWidgets.QMessageBox.question( + self.mainWindow, "Backup Fail", + "Could not make a backup of old compiled code. Proceed anyway to compile and overwrite?", + QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) + if retval == QtWidgets.QMessageBox.No: + return "" + + compileCommand = compilerFile + ' ' + pattern_file + print(compileCommand) + print("Compiling pattern code to .pat file") + exit_status = os.system(compileCommand) + if exit_status != 0: + QtWidgets.QMessageBox.warning(self.mainWindow, "Compile Fail", "Could not compile pattern.", + QtWidgets.QMessageBox.Ok) + return "" + pattern_file += 'at' + + return pattern_file + + def getCompiledPatFname(self): + if self.view.checkBoxCompile.isChecked(): + pattern_file = self.compilePattern() + # pat name from pattern field + else: + pattern_file = self.view.lineEditPatternFile.text() + if not pattern_file: + QtWidgets.QMessageBox.warning(self.mainWindow, "Pattern Fail", + "No pattern file selected. Please select one.", QtWidgets.QMessageBox.Ok) + return "" + return pattern_file + + def loadPattern(self): + pattern_file = self.getCompiledPatFname() + if not pattern_file: + return + # load pattern + self.det.pattern = pattern_file + self.view.lineEditPatternFile.setText(self.det.patfname[0]) + + def getPatViewerColors(self): + colorLevel = self.view.comboBoxPatColorSelect.currentIndex() + color = self.colors_plot[colorLevel] + self.view.comboBoxPatColor.currentIndexChanged.disconnect() + self.view.comboBoxPatColor.setCurrentIndex(Defines.Colors.index(color)) + self.view.comboBoxPatColor.currentIndexChanged.connect(self.updatePatViewerParameters) + + def getPatViewerWaitParameters(self): + waitLevel = self.view.comboBoxPatWait.currentIndex() + color = self.colors_wait[waitLevel] + line_style = self.linestyles_wait[waitLevel] + alpha = self.alpha_wait[waitLevel] + alpha_rect = self.alpha_wait_rect[waitLevel] + + self.view.comboBoxPatWaitColor.currentIndexChanged.disconnect() + self.view.comboBoxPatWaitLineStyle.currentIndexChanged.disconnect() + self.view.doubleSpinBoxWaitAlpha.editingFinished.disconnect() + self.view.doubleSpinBoxWaitAlphaRect.editingFinished.disconnect() + + self.view.comboBoxPatWaitColor.setCurrentIndex(Defines.Colors.index(color)) + self.view.comboBoxPatWaitLineStyle.setCurrentIndex(Defines.LineStyles.index(line_style)) + self.view.doubleSpinBoxWaitAlpha.setValue(alpha) + self.view.doubleSpinBoxWaitAlphaRect.setValue(alpha_rect) + + self.view.comboBoxPatWaitColor.currentIndexChanged.connect(self.updatePatViewerParameters) + self.view.comboBoxPatWaitLineStyle.currentIndexChanged.connect(self.updatePatViewerParameters) + self.view.doubleSpinBoxWaitAlpha.editingFinished.connect(self.updatePatViewerParameters) + self.view.doubleSpinBoxWaitAlphaRect.editingFinished.connect(self.updatePatViewerParameters) + + def getPatViewerLoopParameters(self): + loopLevel = self.view.comboBoxPatLoop.currentIndex() + color = self.colors_loop[loopLevel] + line_style = self.linestyles_loop[loopLevel] + alpha = self.alpha_loop[loopLevel] + alpha_rect = self.alpha_loop_rect[loopLevel] + + self.view.comboBoxPatLoopColor.currentIndexChanged.disconnect() + self.view.comboBoxPatLoopLineStyle.currentIndexChanged.disconnect() + self.view.doubleSpinBoxLoopAlpha.editingFinished.disconnect() + self.view.doubleSpinBoxLoopAlphaRect.editingFinished.disconnect() + + self.view.comboBoxPatLoopColor.setCurrentIndex(Defines.Colors.index(color)) + self.view.comboBoxPatLoopLineStyle.setCurrentIndex(Defines.LineStyles.index(line_style)) + self.view.doubleSpinBoxLoopAlpha.setValue(alpha) + self.view.doubleSpinBoxLoopAlphaRect.setValue(alpha_rect) + + self.view.comboBoxPatLoopColor.currentIndexChanged.connect(self.updatePatViewerParameters) + self.view.comboBoxPatLoopLineStyle.currentIndexChanged.connect(self.updatePatViewerParameters) + self.view.doubleSpinBoxLoopAlpha.editingFinished.connect(self.updatePatViewerParameters) + self.view.doubleSpinBoxLoopAlphaRect.editingFinished.connect(self.updatePatViewerParameters) + + # only at start up + def updateDefaultPatViewerParameters(self): + self.colors_plot = Defines.Colors_plot.copy() + self.colors_wait = Defines.Colors_wait.copy() + self.linestyles_wait = Defines.Linestyles_wait.copy() + self.alpha_wait = Defines.Alpha_wait.copy() + self.alpha_wait_rect = Defines.Alpha_wait_rect.copy() + self.colors_loop = Defines.Colors_loop.copy() + self.linestyles_loop = Defines.Linestyles_loop.copy() + self.alpha_loop = Defines.Alpha_loop.copy() + self.alpha_loop_rect = Defines.Alpha_loop_rect.copy() + self.clock_vertical_lines_spacing = Defines.Clock_vertical_lines_spacing + self.show_clocks_number = Defines.Show_clocks_number + self.line_width = Defines.Line_width + + # print('default') + # self.printPatViewerParameters() + + def updatePatViewerParameters(self): + colorLevel = self.view.comboBoxPatColorSelect.currentIndex() + color = self.view.comboBoxPatColor.currentIndex() + # self.colors_plot[colorLevel] = f'tab:{Defines.Colors[color].lower()}' + self.colors_plot[colorLevel] = Defines.Colors[color] + + waitLevel = self.view.comboBoxPatWait.currentIndex() + color = self.view.comboBoxPatWaitColor.currentIndex() + line_style = self.view.comboBoxPatWaitLineStyle.currentIndex() + alpha = self.view.doubleSpinBoxWaitAlpha.value() + alpha_rect = self.view.doubleSpinBoxWaitAlphaRect.value() + + self.colors_wait[waitLevel] = Defines.Colors[color] + self.linestyles_wait[waitLevel] = Defines.LineStyles[line_style] + self.alpha_wait[waitLevel] = alpha + self.alpha_wait_rect[waitLevel] = alpha_rect + + loopLevel = self.view.comboBoxPatLoop.currentIndex() + color = self.view.comboBoxPatLoopColor.currentIndex() + line_style = self.view.comboBoxPatLoopLineStyle.currentIndex() + alpha = self.view.doubleSpinBoxLoopAlpha.value() + alpha_rect = self.view.doubleSpinBoxLoopAlphaRect.value() + + self.colors_loop[loopLevel] = Defines.Colors[color] + self.linestyles_loop[loopLevel] = Defines.LineStyles[line_style] + self.alpha_loop[loopLevel] = alpha + self.alpha_loop_rect[loopLevel] = alpha_rect + + self.clock_vertical_lines_spacing = self.view.spinBoxPatClockSpacing.value() + self.show_clocks_number = self.view.checkBoxPatShowClockNumber.isChecked() + self.line_width = self.view.doubleSpinBoxLineWidth.value() + + # for debugging + # self.printPatViewerParameters() + + def printPatViewerParameters(self): + print('Pattern Viewer Parameters:') + print(f'\tcolor1: {self.colors_plot[0]}, color2: {self.colors_plot[1]}') + print(f"\twait color: {self.colors_wait}") + print(f"\twait linestyles: {self.linestyles_wait}") + print(f"\twait alpha: {self.alpha_wait}") + print(f"\twait alpha rect: {self.alpha_wait_rect}") + print(f"\tloop color: {self.colors_loop}") + print(f"\tloop linestyles: {self.linestyles_loop}") + print(f"\tloop alpha: {self.alpha_loop}") + print(f"\tloop alpha rect: {self.alpha_loop_rect}") + print(f'\tclock vertical lines spacing: {self.clock_vertical_lines_spacing}') + print(f'\tshow clocks number: {self.show_clocks_number}') + print(f'\tline width: {self.line_width}') + print('\n') + + def viewPattern(self): + self.plotTab.showPatternViewer(True) + pattern_file = self.getCompiledPatFname() + if not pattern_file: + return + + signalNames = self.det.getSignalNames() + p = PlotPattern( + pattern_file, + signalNames, + self.colors_plot, + self.colors_wait, + self.linestyles_wait, + self.alpha_wait, + self.alpha_wait_rect, + self.colors_loop, + self.linestyles_loop, + self.alpha_loop, + self.alpha_loop_rect, + self.clock_vertical_lines_spacing, + self.show_clocks_number, + self.line_width, + ) + + plt.close(self.figure) + self.mainWindow.gridLayoutPatternViewer.removeWidget(self.canvas) + self.canvas.close() + self.mainWindow.gridLayoutPatternViewer.removeWidget(self.toolbar) + self.toolbar.close() + + try: + self.figure = p.patternPlot() + self.canvas = FigureCanvas(self.figure) + self.toolbar = NavigationToolbar(self.canvas, self.view) + self.mainWindow.gridLayoutPatternViewer.addWidget(self.toolbar) + self.mainWindow.gridLayoutPatternViewer.addWidget(self.canvas) + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "Pattern Viewer Fail", str(e), QtWidgets.QMessageBox.Ok) + pass + + def saveParameters(self) -> list[str]: + commands = [] + for i in range(Defines.pattern.loops_count): + commands.append(f"patnloop {i} {getattr(self.view, f'spinBoxLoop{i}Repetition').text()}") + commands.append(f"patloop {i} {getattr(self.view, f'lineEditLoop{i}Start').text()}, " + f"{getattr(self.view, f'lineEditLoop{i}Stop').text()}") + + commands.append(f"patwait {i} {getattr(self.view, f'lineEditLoop{i}Wait').text()}") + commands.append(f"patwaittime {i} {getattr(self.view, f'spinBoxLoop{i}WaitTime').text()}") + commands.append(f"patlimits {self.view.lineEditStartAddress.text()}, {self.view.lineEditStopAddress.text()}") + # commands.append(f"patfname {self.view.lineEditPatternFile.text()}") + return commands diff --git a/pyctbgui/pyctbgui/services/Plot.py b/pyctbgui/pyctbgui/services/Plot.py new file mode 100644 index 000000000..0dfedd205 --- /dev/null +++ b/pyctbgui/pyctbgui/services/Plot.py @@ -0,0 +1,519 @@ +import logging +from functools import partial +import random +from pathlib import Path + +import numpy as np +from PyQt5 import QtWidgets, QtGui, uic + +import pyqtgraph as pg +from pyctbgui.utils import recordOrApplyPedestal +from pyqtgraph import PlotWidget + +from pyctbgui.utils.defines import Defines + + +class PlotTab(QtWidgets.QWidget): + + def __init__(self, parent): + super().__init__(parent) + self.frame_min: float = 0.0 + self.frame_max: float = 0.0 + uic.loadUi(Path(__file__).parent.parent / 'ui' / "plot.ui", parent) + self.view = parent + self.mainWindow = None + self.det = None + self.signalsTab = None + self.transceiverTab = None + self.acquisitionTab = None + self.adcTab = None + self.cmin: float = 0.0 + self.cmax: float = 0.0 + self.colorRangeMode: Defines.colorRange = Defines.colorRange.all + self.ignoreHistogramSignal: bool = False + self.imagePlots: list[PlotWidget] = [] + # list of callback functions to notify tabs when we should hide their legend + # follows the observer design pattern + self.hideLegendObservers = [] + self.pedestalRecord: bool = False + self.pedestalApply: bool = True + self.__acqFrames = None + self.logger = logging.getLogger('PlotTab') + + def setup_ui(self): + self.signalsTab = self.mainWindow.signalsTab + self.transceiverTab = self.mainWindow.transceiverTab + self.acquisitionTab = self.mainWindow.acquisitionTab + self.adcTab = self.mainWindow.adcTab + self.initializeColorMaps() + + self.imagePlots = ( + self.mainWindow.plotAnalogImage, + self.mainWindow.plotDigitalImage, + self.mainWindow.plotTransceiverImage, + ) + + def connect_ui(self): + self.view.radioButtonNoPlot.clicked.connect(self.plotOptions) + 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.comboBoxColorMap.currentIndexChanged.connect(self.setColorMap) + self.view.comboBoxZMQHWM.currentIndexChanged.connect(self.setZMQHWM) + self.view.spinBoxSerialOffset.editingFinished.connect(self.setSerialOffset) + self.view.spinBoxNCount.editingFinished.connect(self.setNCounter) + self.view.spinBoxDynamicRange.editingFinished.connect(self.setDynamicRange) + self.view.spinBoxImageX.editingFinished.connect(self.setImageX) + self.view.spinBoxImageY.editingFinished.connect(self.setImageY) + + self.view.radioButtonPedestalRecord.toggled.connect(self.togglePedestalRecord) + self.view.radioButtonPedestalApply.toggled.connect(self.togglePedestalApply) + self.view.pushButtonPedestalReset.clicked.connect(self.resetPedestal) + self.view.pushButtonSavePedestal.clicked.connect(self.savePedestal) + self.view.pushButtonLoadPedestal.clicked.connect(self.loadPedestal) + + self.view.checkBoxRaw.stateChanged.connect(self.setRawData) + self.view.spinBoxRawMin.editingFinished.connect(self.setRawData) + self.view.spinBoxRawMax.editingFinished.connect(self.setRawData) + self.view.checkBoxPedestal.stateChanged.connect(self.setPedestalSubtract) + self.view.spinBoxPedestalMin.editingFinished.connect(self.setPedestalSubtract) + self.view.spinBoxPedestalMax.editingFinished.connect(self.setPedestalSubtract) + self.view.spinBoxFit.editingFinished.connect(self.setFitADC) + self.view.spinBoxPlot.editingFinished.connect(self.setPlotBit) + self.view.pushButtonReferesh.clicked.connect(self.acquisitionTab.refresh) + # color range widgets + self.view.cminSpinBox.editingFinished.connect(self.setCmin) + self.view.cmaxSpinBox.editingFinished.connect(self.setCmax) + self.view.radioButtonAutomatic.clicked.connect(partial(self.setColorRangeMode, Defines.colorRange.all)) + self.view.radioButtonFixed.clicked.connect(partial(self.setColorRangeMode, Defines.colorRange.fixed)) + self.view.radioButtonCenter.clicked.connect(partial(self.setColorRangeMode, Defines.colorRange.center)) + + for plot in self.imagePlots: + plot.scene.sigMouseMoved.connect(partial(self.showPlotValues, plot)) + plot.getHistogramWidget().item.sigLevelChangeFinished.connect(partial(self.handleHistogramChange, plot)) + + self.view.checkBoxShowLegend.stateChanged.connect(self.toggleLegend) + + def refresh(self): + self.getZMQHWM() + + def initializeColorMaps(self): + self.view.comboBoxColorMap.addItems(Defines.Color_map) + self.view.comboBoxColorMap.setCurrentIndex(Defines.Color_map.index(Defines.Default_Color_Map)) + self.setColorMap() + + def savePedestal(self): + """ + slot function to save pedestal values + """ + response = QtWidgets.QFileDialog.getSaveFileName(self.view, "Save Pedestal", str(self.det.fpath)) + recordOrApplyPedestal.savePedestal(Path(response[0])) + self.logger.info(f'saved Pedestal in {response[0]}') + + def loadPedestal(self): + """ + slot function to load pedestal values + """ + response = QtWidgets.QFileDialog.getOpenFileName(self.view, "Load Pedestal", str(self.det.fpath)) + if response[0] == '': + return + recordOrApplyPedestal.reset(self) + try: + recordOrApplyPedestal.loadPedestal(Path(response[0])) + except (IsADirectoryError, ValueError, EOFError): + QtWidgets.QMessageBox.warning( + self.view, + "Loading Pedestal Failed", + "Loading Pedestal failed make sure the file is in the valid .npy format", + QtWidgets.QMessageBox.Ok, + ) + self.logger.exception("Exception when loading pedestal") + else: + self.logger.info(f'loaded Pedestal from {response[0]}') + self.updateLabelPedestalFrames(True) + + def togglePedestalRecord(self): + """ + slot function for pedestal record radio button + toggle pedestal record variable and disables the frames spinboxes in acquisition tab or plot tab depenging on + the mode + """ + self.pedestalRecord = not self.pedestalRecord + + def togglePedestalApply(self): + """ + slot function for pedestal apply radio button + """ + self.pedestalApply = not self.pedestalApply + + def resetPedestal(self): + """ + slot function for resetting the pedestal + """ + recordOrApplyPedestal.reset(self) + + def updateLabelPedestalFrames(self, loadedPedestal=False): + """ + updates labelPedestalFrames to the length of savedFrames + """ + if loadedPedestal: + self.view.labelPedestalFrames.setText('using loaded pedestal file') + else: + self.view.labelPedestalFrames.setText(f'recorded frames: {recordOrApplyPedestal.getFramesCount()}') + + def subscribeToggleLegend(self, fn_cbk): + """ + subscribe to the event of toggling the hide legend checkbox by subscribing + with a callback function + """ + self.hideLegendObservers.append(fn_cbk) + + def toggleLegend(self): + """ + notify subscribers for the showLegend checkbox event by executing their callbacks + """ + self.mainWindow.showLegend = not self.mainWindow.showLegend + for notify_function in self.hideLegendObservers: + notify_function() + + def setCmin(self, value=None): + """ + slot for setting cmin from cminSpinBox + also used as a normal function + """ + if value is None: + self.cmin = self.view.cminSpinBox.value() + else: + self.cmin = value + self.updateColorRangeUI() + + def setCmax(self, value=None): + """ + slot for setting cmax from cmaxSpinBox + also used as a normal function + """ + if value is None: + self.cmax = self.view.cmaxSpinBox.value() + else: + self.cmax = value + self.updateColorRangeUI() + + def setColorRangeMode(self, mode): + """ + slot for setting the color range mode (all,fixed,3-97%) + """ + self.colorRangeMode = mode + + # disable or enable cmin/cmax spinboxes + enableSpinBoxes = mode == Defines.colorRange.fixed + self.view.cminSpinBox.setEnabled(enableSpinBoxes) + self.view.cmaxSpinBox.setEnabled(enableSpinBoxes) + self.updateColorRange() + self.updateColorRangeUI() + + def handleHistogramChange(self, plot): + """ + slot called after changing the color bar + This is called even when pyqtgraph sets the image without any user intervention + the class attribute ignore_histogram_signal is set to False before setting the image + so that we can distinguish between the signal originates from pyqt or from the user + """ + if not self.ignoreHistogramSignal: + self.cmin, self.cmax = plot.getHistogramWidget().item.getLevels() + self.setCmin(self.cmin) + self.setCmax(self.cmax) + + self.ignoreHistogramSignal = False + # self.setColorRangeMode(Defines.colorRange.fixed) + + def setFrameLimits(self, frame): + """ + function called from AcquisitionTab::read_zmq to get the maximum and minimum + values of the decoded frame and save them in frame_min/frame_max + """ + self.frame_min = np.min(frame) + self.frame_max = np.max(frame) + self.updateColorRange() + + def updateColorRange(self): + """ + for mode: + - all: sets cmin and cmax to the maximums/minimum values of the frame + - 3-97%: limits cmax and cmin so that we ignore 3% from each end of the whole range + - fixed: this function does not change cmin and cmax + """ + + if self.colorRangeMode == Defines.colorRange.all: + self.cmin = self.frame_min + self.cmax = self.frame_max + elif self.colorRangeMode == Defines.colorRange.center: + self.cmin = self.frame_min + 0.03 * (self.frame_max - self.frame_min) + self.cmax = self.frame_max - 0.03 * (self.frame_max - self.frame_min) + + self.updateColorRangeUI() + + def updateColorRangeUI(self): + """ + updates UI views should be called after every change to cmin or cmax + """ + for plot in self.imagePlots: + plot.getHistogramWidget().item.setLevels(min=self.cmin, max=self.cmax) + self.view.cminSpinBox.setValue(self.cmin) + self.view.cmaxSpinBox.setValue(self.cmax) + + def setColorMap(self): + cm = pg.colormap.getFromMatplotlib(self.view.comboBoxColorMap.currentText()) + # print(f'color map:{self.comboBoxColorMap.currentText()}') + self.mainWindow.plotAnalogImage.setColorMap(cm) + self.mainWindow.plotDigitalImage.setColorMap(cm) + self.mainWindow.plotTransceiverImage.setColorMap(cm) + + def getZMQHWM(self): + + self.view.comboBoxZMQHWM.currentIndexChanged.disconnect() + + rx_zmq_hwm = self.det.getRxZmqHwm()[0] + # ensure same value in client zmq + self.det.setClientZmqHwm(rx_zmq_hwm) + + # high readout, low HWM + if -1 < rx_zmq_hwm < 25: + self.view.comboBoxZMQHWM.setCurrentIndex(1) + # low readout, high HWM + else: + self.view.comboBoxZMQHWM.setCurrentIndex(0) + self.view.comboBoxZMQHWM.currentIndexChanged.connect(self.setZMQHWM) + + def setZMQHWM(self): + val = self.view.comboBoxZMQHWM.currentIndex() + # low readout, high HWM + if val == 0: + self.det.setRxZmqHwm(Defines.Zmq_hwm_low_speed) + self.det.setClientZmqHwm(Defines.Zmq_hwm_low_speed) + # high readout, low HWM + else: + self.det.setRxZmqHwm(Defines.Zmq_hwm_high_speed) + self.det.setClientZmqHwm(Defines.Zmq_hwm_high_speed) + + self.getZMQHWM() + + def addSelectedAnalogPlots(self, i): + enable = getattr(self.adcTab.view, f"checkBoxADC{i}Plot").isChecked() + if enable: + self.mainWindow.analogPlots[i].show() + if not enable: + self.mainWindow.analogPlots[i].hide() + + def addAllSelectedAnalogPlots(self): + for i in range(Defines.adc.count): + self.addSelectedAnalogPlots(i) + + def removeAllAnalogPlots(self): + for i in range(Defines.adc.count): + self.mainWindow.analogPlots[i].hide() + + cm = pg.colormap.get('CET-L9') # prepare a linear color map + self.mainWindow.plotDigitalImage.setColorMap(cm) + + def addSelectedDigitalPlots(self, i): + enable = getattr(self.signalsTab.view, f"checkBoxBIT{i}Plot").isChecked() + if enable: + self.mainWindow.digitalPlots[i].show() + if not enable: + self.mainWindow.digitalPlots[i].hide() + + def addAllSelectedDigitalPlots(self): + for i in range(Defines.signals.count): + self.addSelectedDigitalPlots(i) + + def removeAllDigitalPlots(self): + for i in range(Defines.signals.count): + self.mainWindow.digitalPlots[i].hide() + + def addSelectedTransceiverPlots(self, i): + enable = getattr(self.transceiverTab.view, f"checkBoxTransceiver{i}Plot").isChecked() + if enable: + self.mainWindow.transceiverPlots[i].show() + if not enable: + self.mainWindow.transceiverPlots[i].hide() + + def addAllSelectedTransceiverPlots(self): + for i in range(Defines.transceiver.count): + self.addSelectedTransceiverPlots(i) + + def removeAllTransceiverPlots(self): + for i in range(Defines.transceiver.count): + self.mainWindow.transceiverPlots[i].hide() + + def showPlot(self): + self.mainWindow.plotAnalogWaveform.hide() + self.mainWindow.plotDigitalWaveform.hide() + self.mainWindow.plotTransceiverWaveform.hide() + self.mainWindow.plotAnalogImage.hide() + self.mainWindow.plotDigitalImage.hide() + self.mainWindow.plotTransceiverImage.hide() + self.view.labelDigitalWaveformOption.setDisabled(True) + self.view.radioButtonOverlay.setDisabled(True) + self.view.radioButtonStripe.setDisabled(True) + + if self.mainWindow.romode.value in [0, 2]: + if self.view.radioButtonWaveform.isChecked(): + self.mainWindow.plotAnalogWaveform.show() + elif self.view.radioButtonImage.isChecked(): + self.mainWindow.plotAnalogImage.show() + if self.mainWindow.romode.value in [1, 2, 4]: + if self.view.radioButtonWaveform.isChecked(): + self.mainWindow.plotDigitalWaveform.show() + elif self.view.radioButtonImage.isChecked(): + self.mainWindow.plotDigitalImage.show() + self.view.labelDigitalWaveformOption.setEnabled(True) + self.view.radioButtonOverlay.setEnabled(True) + self.view.radioButtonStripe.setEnabled(True) + + if self.mainWindow.romode.value in [3, 4]: + if self.view.radioButtonWaveform.isChecked(): + self.mainWindow.plotTransceiverWaveform.show() + elif self.view.radioButtonImage.isChecked(): + self.mainWindow.plotTransceiverImage.show() + + def plotOptions(self): + + self.mainWindow.framePatternViewer.hide() + self.showPlot() + + # disable plotting + self.mainWindow.read_timer.stop() + + if self.view.radioButtonWaveform.isChecked(): + self.mainWindow.plotAnalogWaveform.setLabel( + 'left', "Output [ADC]") + self.mainWindow.plotAnalogWaveform.setLabel( + 'bottom', "Analog Sample [#]") + self.mainWindow.plotDigitalWaveform.setLabel( + 'left', "Digital Bit") + self.mainWindow.plotDigitalWaveform.setLabel( + 'bottom', "Digital Sample [#]") + self.mainWindow.plotTransceiverWaveform.setLabel( + 'left', "Transceiver Bit") + self.mainWindow.plotTransceiverWaveform.setLabel( + 'bottom', "Transceiver Sample [#]") + + self.view.stackedWidgetPlotType.setCurrentIndex(0) + + elif self.view.radioButtonImage.isChecked(): + self.view.stackedWidgetPlotType.setCurrentIndex(2) + self.setPixelMap() + + if self.view.radioButtonNoPlot.isChecked(): + self.view.labelPlotOptions.hide() + self.view.stackedWidgetPlotType.hide() + # enable plotting + else: + self.view.labelPlotOptions.show() + self.view.stackedWidgetPlotType.show() + self.mainWindow.read_timer.start(Defines.Time_Plot_Refresh_ms) + + def setPixelMap(self): + if self.view.comboBoxPlot.currentText() == "Matterhorn": + self.mainWindow.nTransceiverRows = Defines.Matterhorn.nRows + self.mainWindow.nTransceiverCols = Defines.Matterhorn.nCols + elif self.view.comboBoxPlot.currentText() == "Moench04": + self.mainWindow.nAnalogRows = Defines.Moench04.nRows + self.mainWindow.nAnalogCols = Defines.Moench04.nCols + + def showPatternViewer(self, enable): + if enable: + self.mainWindow.framePatternViewer.show() + self.mainWindow.framePlot.hide() + elif self.mainWindow.framePatternViewer.isVisible(): + self.mainWindow.framePatternViewer.hide() + self.mainWindow.framePlot.show() + self.showPlot() + + def setSerialOffset(self): + print("plot options - Not implemented yet") + # TODO: + + def setNCounter(self): + print("plot options - Not implemented yet") + # TODO: + + def setDynamicRange(self): + print("plot options - Not implemented yet") + # TODO: + + def setImageX(self): + print("plot options - Not implemented yet") + # TODO: + + def setImageY(self): + print("plot options - Not implemented yet") + # TODO: + + def setRawData(self): + print("plot options - Not implemented yet") + # TODO: raw data, min, max + + def setPedestalSubtract(self): + print("plot options - Not implemented yet") + # TODO: pedestal, min, max + + def setFitADC(self): + print("plot options - Not implemented yet") + # TODO: + + def setPlotBit(self): + print("plot options - Not implemented yet") + # TODO: + + def getRandomColor(self): + ''' + Returns a random color range (except white) in format string eg. "#aabbcc" + ''' + randomColor = random.randrange(0, 0xffffaa, 0xaa) + return "#{:06x}".format(randomColor) + + def getActiveColor(self, button): + return button.palette().color(QtGui.QPalette.Window) + + def setActiveColor(self, button, str_color): + button.setStyleSheet(":enabled {background-color: %s" % str_color + "} :disabled {background-color: grey}") + + def showPalette(self, button): + color = QtWidgets.QColorDialog.getColor() + if color.isValid(): + self.setActiveColor(button, color.name()) + # get the RGB Values + # print(color.getRgb()) + + def showPlotValues(self, sender, pos): + x = sender.getImageItem().mapFromScene(pos).x() + y = sender.getImageItem().mapFromScene(pos).y() + val = 0 + nMaxY = self.mainWindow.nAnalogRows + nMaxX = self.mainWindow.nAnalogCols + frame = self.mainWindow.analog_frame + if sender == self.mainWindow.plotDigitalImage: + nMaxY = self.mainWindow.nDigitalRows + nMaxX = self.mainWindow.nDigitalCols + frame = self.mainWindow.digital_frame + elif sender == self.mainWindow.plotTransceiverImage: + nMaxY = self.mainWindow.nTransceiverRows + nMaxX = self.mainWindow.nTransceiverCols + frame = self.mainWindow.transceiver_frame + if 0 <= x < nMaxX and 0 <= y < nMaxY and not np.array_equal(frame, []): + val = frame[int(x), int(y)] + message = f'[{x:.2f}, {y:.2f}] = {val:.2f}' + sender.setToolTip(message) + # print(message) + else: + sender.setToolTip('') + + def saveParameters(self): + commands = [] + if self.view.comboBoxZMQHWM.currentIndex() == 0: + commands.append(f"zmqhwm {Defines.Zmq_hwm_low_speed}") + else: + commands.append(f"zmqhwm {Defines.Zmq_hwm_high_speed}") + return commands diff --git a/pyctbgui/pyctbgui/services/PowerSupplies.py b/pyctbgui/pyctbgui/services/PowerSupplies.py new file mode 100644 index 000000000..ec8d2f5dd --- /dev/null +++ b/pyctbgui/pyctbgui/services/PowerSupplies.py @@ -0,0 +1,123 @@ +from functools import partial +from pathlib import Path + +from PyQt5 import QtWidgets, uic +from pyctbgui.utils.defines import Defines + +from slsdet import dacIndex + + +class PowerSuppliesTab(QtWidgets.QWidget): + + def __init__(self, parent): + super().__init__(parent) + uic.loadUi(Path(__file__).parent.parent / 'ui' / "powerSupplies.ui", parent) + self.view = parent + + def refresh(self): + self.updateVoltageNames() + for i in Defines.powerSupplies: + self.getVoltage(i) + self.getCurrent(i) + + def connect_ui(self): + for i in Defines.powerSupplies: + spinBox = getattr(self.view, f"spinBoxV{i}") + checkBox = getattr(self.view, f"checkBoxV{i}") + spinBox.editingFinished.connect(partial(self.setVoltage, i)) + checkBox.stateChanged.connect(partial(self.setVoltage, i)) + self.view.pushButtonPowerOff.clicked.connect(self.powerOff) + + def setup_ui(self): + for i in Defines.powerSupplies: + dac = getattr(dacIndex, f"V_POWER_{i}") + spinBox = getattr(self.view, f"spinBoxV{i}") + checkBox = getattr(self.view, f"checkBoxV{i}") + retval = self.det.getPower(dac)[0] + spinBox.setValue(retval) + if retval == 0: + checkBox.setChecked(False) + spinBox.setDisabled(True) + + def updateVoltageNames(self): + retval = self.det.getPowerNames() + getattr(self.view, "checkBoxVA").setText(retval[0]) + getattr(self.view, "checkBoxVB").setText(retval[1]) + getattr(self.view, "checkBoxVC").setText(retval[2]) + getattr(self.view, "checkBoxVD").setText(retval[3]) + getattr(self.view, "checkBoxVIO").setText(retval[4]) + + def getVoltage(self, i): + spinBox = getattr(self.view, f"spinBoxV{i}") + checkBox = getattr(self.view, f"checkBoxV{i}") + voltageIndex = getattr(dacIndex, f"V_POWER_{i}") + label = getattr(self.view, f"labelV{i}") + + spinBox.editingFinished.disconnect() + checkBox.stateChanged.disconnect() + + retval = self.det.getMeasuredPower(voltageIndex)[0] + # spinBox.setValue(retval) + if retval > 1: + checkBox.setChecked(True) + if checkBox.isChecked(): + spinBox.setEnabled(True) + else: + spinBox.setDisabled(True) + label.setText(f'{str(retval)} mV') + + spinBox.editingFinished.connect(partial(self.setVoltage, i)) + checkBox.stateChanged.connect(partial(self.setVoltage, i)) + + self.getVChip() + + # TODO: handle multiple events when pressing enter (twice) + + def setVoltage(self, i): + checkBox = getattr(self.view, f"checkBoxV{i}") + spinBox = getattr(self.view, f"spinBoxV{i}") + voltageIndex = getattr(dacIndex, f"V_POWER_{i}") + spinBox.editingFinished.disconnect() + + value = 0 + if checkBox.isChecked(): + value = spinBox.value() + try: + self.det.setPower(voltageIndex, value) + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "Voltage Fail", str(e), QtWidgets.QMessageBox.Ok) + pass + + # TODO: (properly) disconnecting and connecting to handle multiple events (out of focus and pressing enter). + spinBox.editingFinished.connect(partial(self.setVoltage, i)) + self.getVoltage(i) + self.getCurrent(i) + + def getCurrent(self, i): + label = getattr(self.view, f"labelI{i}") + currentIndex = getattr(dacIndex, f"I_POWER_{i}") + retval = self.det.getMeasuredCurrent(currentIndex)[0] + label.setText(f'{str(retval)} mA') + + def getVChip(self): + self.view.spinBoxVChip.setValue(self.det.getPower(dacIndex.V_POWER_CHIP)[0]) + + def powerOff(self): + for i in Defines.powerSupplies: + # set all voltages to 0 + checkBox = getattr(self.view, f"checkBoxV{i}") + checkBox.stateChanged.disconnect() + checkBox.setChecked(False) + checkBox.stateChanged.connect(partial(self.setVoltage, i)) + self.setVoltage(i) + + def saveParameters(self) -> list: + commands = [] + for i in Defines.powerSupplies: + enabled = getattr(self.view, f"checkBoxV{i}").isChecked() + if enabled: + value = getattr(self.view, f"spinBoxV{i}").value() + commands.append(f"v_{i.lower()} {value}") + else: + commands.append(f"v_{i.lower()} 0") + return commands diff --git a/pyctbgui/pyctbgui/services/Signals.py b/pyctbgui/pyctbgui/services/Signals.py new file mode 100644 index 000000000..a06b9a412 --- /dev/null +++ b/pyctbgui/pyctbgui/services/Signals.py @@ -0,0 +1,373 @@ +from functools import partial +from pathlib import Path + +import numpy as np +from PyQt5 import QtWidgets, uic +import pyqtgraph as pg +from pyqtgraph import LegendItem + +from pyctbgui.utils.bit_utils import bit_is_set, manipulate_bit +from pyctbgui.utils.defines import Defines +from pyctbgui.utils.recordOrApplyPedestal import recordOrApplyPedestal + + +class SignalsTab(QtWidgets.QWidget): + + def __init__(self, parent): + super().__init__(parent) + uic.loadUi(Path(__file__).parent.parent / 'ui' / "signals.ui", parent) + self.view = parent + self.mainWindow = None + self.det = None + self.plotTab = None + self.legend: LegendItem | None = None + self.rx_dbitoffset = None + self.rx_dbitlist = None + + def refresh(self): + self.updateSignalNames() + self.updateDigitalBitEnable() + self.updateIOOut() + self.getDBitOffset() + + def connect_ui(self): + for i in range(Defines.signals.count): + getattr(self.view, f"checkBoxBIT{i}DB").stateChanged.connect(partial(self.setDigitalBitEnable, i)) + getattr(self.view, f"checkBoxBIT{i}Out").stateChanged.connect(partial(self.setIOOut, i)) + getattr(self.view, f"checkBoxBIT{i}Plot").stateChanged.connect(partial(self.setEnableBitPlot, i)) + getattr(self.view, f"pushButtonBIT{i}").clicked.connect(partial(self.selectBitColor, i)) + self.view.checkBoxBIT0_31DB.stateChanged.connect( + partial(self.setDigitalBitEnableRange, 0, Defines.signals.half)) + self.view.checkBoxBIT32_63DB.stateChanged.connect( + partial(self.setDigitalBitEnableRange, Defines.signals.half, Defines.signals.count)) + self.view.checkBoxBIT0_31Plot.stateChanged.connect(partial(self.setEnableBitPlotRange, 0, + Defines.signals.half)) + self.view.checkBoxBIT32_63Plot.stateChanged.connect( + partial(self.setEnableBitPlotRange, Defines.signals.half, Defines.signals.count)) + self.view.checkBoxBIT0_31Out.stateChanged.connect(partial(self.setIOOutRange, 0, Defines.signals.half)) + self.view.checkBoxBIT32_63Out.stateChanged.connect( + partial(self.setIOOutRange, Defines.signals.half, Defines.signals.count)) + self.view.lineEditPatIOCtrl.editingFinished.connect(self.setIOOutReg) + self.view.spinBoxDBitOffset.editingFinished.connect(self.setDbitOffset) + + def setup_ui(self): + self.plotTab = self.mainWindow.plotTab + + for i in range(Defines.signals.count): + self.setDBitButtonColor(i, self.plotTab.getRandomColor()) + + self.initializeAllDigitalPlots() + + self.legend = self.mainWindow.plotDigitalWaveform.getPlotItem().legend + self.legend.clear() + # subscribe to toggle legend + self.plotTab.subscribeToggleLegend(self.updateLegend) + + def getEnabledPlots(self): + """ + return plots that are shown (checkBoxTransceiver{i}Plot is checked) + """ + enabledPlots = [] + self.legend.clear() + for i in range(Defines.signals.count): + if getattr(self.view, f'checkBoxBIT{i}Plot').isChecked(): + plotName = getattr(self.view, f"labelBIT{i}").text() + enabledPlots.append((self.mainWindow.digitalPlots[i], plotName)) + return enabledPlots + + def updateLegend(self): + """ + update the legend for the signals waveform plot + should be called after checking or unchecking plot checkbox + """ + if not self.mainWindow.showLegend: + self.legend.clear() + else: + for plot, name in self.getEnabledPlots(): + self.legend.addItem(plot, name) + + @recordOrApplyPedestal + def _processWaveformData(self, data, aSamples, dSamples, rx_dbitlist, isPlottedArray, rx_dbitoffset, romode, + nADCEnabled): + """ + transform raw waveform data into a processed numpy array + @param data: raw waveform data + """ + dbitoffset = rx_dbitoffset + if romode == 2: + dbitoffset += nADCEnabled * 2 * aSamples + digital_array = np.array(np.frombuffer(data, offset=dbitoffset, dtype=np.uint8)) + nbitsPerDBit = dSamples + if nbitsPerDBit % 8 != 0: + nbitsPerDBit += (8 - (dSamples % 8)) + offset = 0 + arr = [] + for i in rx_dbitlist: + # where numbits * numsamples is not a multiple of 8 + if offset % 8 != 0: + offset += (8 - (offset % 8)) + if not isPlottedArray[i]: + offset += nbitsPerDBit + return None + waveform = np.zeros(dSamples) + for iSample in range(dSamples): + # all samples for digital bit together from slsReceiver + index = int(offset / 8) + iBit = offset % 8 + bit = (digital_array[index] >> iBit) & 1 + waveform[iSample] = bit + offset += 1 + arr.append(waveform) + + return np.array(arr) + + def processWaveformData(self, data, aSamples, dSamples): + """ + view function + plots processed waveform data + data: raw waveform data + dsamples: digital samples + asamples: analog samples + """ + waveforms = {} + isPlottedArray = {i: getattr(self.view, f"checkBoxBIT{i}Plot").isChecked() for i in self.rx_dbitlist} + + digital_array = self._processWaveformData(data, aSamples, dSamples, self.rx_dbitlist, isPlottedArray, + self.rx_dbitoffset, self.mainWindow.romode.value, + self.mainWindow.nADCEnabled) + + irow = 0 + for idx, i in enumerate(self.rx_dbitlist): + # bits enabled but not plotting + waveform = digital_array[idx] + if waveform is None: + continue + self.mainWindow.digitalPlots[i].setData(waveform) + plotName = getattr(self.view, f"labelBIT{i}").text() + waveforms[plotName] = waveform + # TODO: left axis does not show 0 to 1, but keeps increasing + if self.plotTab.view.radioButtonStripe.isChecked(): + self.mainWindow.digitalPlots[i].setY(irow * 2) + irow += 1 + else: + self.mainWindow.digitalPlots[i].setY(0) + return waveforms + + def initializeAllDigitalPlots(self): + self.mainWindow.plotDigitalWaveform = pg.plot() + self.mainWindow.plotDigitalWaveform.addLegend(colCount=Defines.colCount) + self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotDigitalWaveform, 3) + self.mainWindow.digitalPlots = {} + waveform = np.zeros(1000) + for i in range(Defines.signals.count): + pen = pg.mkPen(color=self.getDBitButtonColor(i), width=1) + legendName = getattr(self.view, f"labelBIT{i}").text() + self.mainWindow.digitalPlots[i] = self.mainWindow.plotDigitalWaveform.plot(waveform, + pen=pen, + name=legendName, + stepMode="left") + self.mainWindow.digitalPlots[i].hide() + + self.mainWindow.plotDigitalImage = pg.ImageView() + self.mainWindow.nDigitalRows = 0 + self.mainWindow.nDigitalCols = 0 + self.mainWindow.digital_frame = np.zeros((self.mainWindow.nDigitalRows, self.mainWindow.nDigitalCols)) + self.mainWindow.plotDigitalImage.setImage(self.mainWindow.digital_frame) + self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotDigitalImage, 4) + + def updateSignalNames(self): + for i, name in enumerate(self.det.getSignalNames()): + getattr(self.view, f"labelBIT{i}").setText(name) + + def getDigitalBitEnable(self, i, dbitList): + checkBox = getattr(self.view, f"checkBoxBIT{i}DB") + checkBox.stateChanged.disconnect() + checkBox.setChecked(i in list(dbitList)) + checkBox.stateChanged.connect(partial(self.setDigitalBitEnable, i)) + + def updateDigitalBitEnable(self): + retval = self.det.rx_dbitlist + self.rx_dbitlist = list(retval) + self.mainWindow.nDBitEnabled = len(list(retval)) + for i in range(Defines.signals.count): + self.getDigitalBitEnable(i, retval) + self.getEnableBitPlot(i) + self.getEnableBitColor(i) + self.plotTab.addSelectedDigitalPlots(i) + self.getDigitalBitEnableRange(retval) + self.getEnableBitPlotRange() + + def setDigitalBitEnable(self, i): + bitList = self.det.rx_dbitlist + checkBox = getattr(self.view, f"checkBoxBIT{i}DB") + if checkBox.isChecked(): + bitList.append(i) + else: + bitList.remove(i) + self.det.rx_dbitlist = bitList + + self.updateDigitalBitEnable() + + def getDigitalBitEnableRange(self, dbitList): + self.view.checkBoxBIT0_31DB.stateChanged.disconnect() + self.view.checkBoxBIT32_63DB.stateChanged.disconnect() + self.view.checkBoxBIT0_31DB.setChecked(all(x in list(dbitList) for x in range(Defines.signals.half))) + self.view.checkBoxBIT32_63DB.setChecked( + all(x in list(dbitList) for x in range(Defines.signals.half, Defines.signals.count))) + self.view.checkBoxBIT0_31DB.stateChanged.connect( + partial(self.setDigitalBitEnableRange, 0, Defines.signals.half)) + self.view.checkBoxBIT32_63DB.stateChanged.connect( + partial(self.setDigitalBitEnableRange, Defines.signals.half, Defines.signals.count)) + + def setDigitalBitEnableRange(self, start_nr, end_nr): + bitList = self.det.rx_dbitlist + checkBox = getattr(self.view, f"checkBoxBIT{start_nr}_{end_nr - 1}DB") + for i in range(start_nr, end_nr): + if checkBox.isChecked(): + if i not in list(bitList): + bitList.append(i) + else: + if i in list(bitList): + bitList.remove(i) + self.det.rx_dbitlist = bitList + + self.updateDigitalBitEnable() + + def getEnableBitPlot(self, i): + checkBox = getattr(self.view, f"checkBoxBIT{i}DB") + checkBoxPlot = getattr(self.view, f"checkBoxBIT{i}Plot") + checkBoxPlot.setEnabled(checkBox.isChecked()) + + def setEnableBitPlot(self, i): + pushButton = getattr(self.view, f"pushButtonBIT{i}") + checkBox = getattr(self.view, f"checkBoxBIT{i}Plot") + pushButton.setEnabled(checkBox.isChecked()) + + self.getEnableBitPlotRange() + self.plotTab.addSelectedDigitalPlots(i) + self.updateLegend() + + def getEnableBitPlotRange(self): + self.view.checkBoxBIT0_31Plot.stateChanged.disconnect() + self.view.checkBoxBIT32_63Plot.stateChanged.disconnect() + self.view.checkBoxBIT0_31Plot.setEnabled( + all(getattr(self.view, f"checkBoxBIT{i}Plot").isEnabled() for i in range(Defines.signals.half))) + self.view.checkBoxBIT32_63Plot.setEnabled( + all( + getattr(self.view, f"checkBoxBIT{i}Plot").isEnabled() + for i in range(Defines.signals.half, Defines.signals.count))) + self.view.checkBoxBIT0_31Plot.setChecked( + all(getattr(self.view, f"checkBoxBIT{i}Plot").isChecked() for i in range(Defines.signals.half))) + self.view.checkBoxBIT32_63Plot.setChecked( + all( + getattr(self.view, f"checkBoxBIT{i}Plot").isChecked() + for i in range(Defines.signals.half, Defines.signals.count))) + self.view.checkBoxBIT0_31Plot.stateChanged.connect(partial(self.setEnableBitPlotRange, 0, + Defines.signals.half)) + self.view.checkBoxBIT32_63Plot.stateChanged.connect( + partial(self.setEnableBitPlotRange, Defines.signals.half, Defines.signals.count)) + + def setEnableBitPlotRange(self, start_nr, end_nr): + checkBox = getattr(self.view, f"checkBoxBIT{start_nr}_{end_nr - 1}Plot") + enable = checkBox.isChecked() + for i in range(start_nr, end_nr): + checkBox = getattr(self.view, f"checkBoxBIT{i}Plot") + checkBox.setChecked(enable) + self.plotTab.addAllSelectedDigitalPlots() + + def getEnableBitColor(self, i): + checkBox = getattr(self.view, f"checkBoxBIT{i}Plot") + pushButton = getattr(self.view, f"pushButtonBIT{i}") + pushButton.setEnabled(checkBox.isEnabled() and checkBox.isChecked()) + + def selectBitColor(self, i): + pushButton = getattr(self.view, f"pushButtonBIT{i}") + self.plotTab.showPalette(pushButton) + pen = pg.mkPen(color=self.getDBitButtonColor(i), width=1) + self.mainWindow.digitalPlots[i].setPen(pen) + + def getDBitButtonColor(self, i): + pushButton = getattr(self.view, f"pushButtonBIT{i}") + return self.plotTab.getActiveColor(pushButton) + + def setDBitButtonColor(self, i, color): + pushButton = getattr(self.view, f"pushButtonBIT{i}") + return self.plotTab.setActiveColor(pushButton, color) + + def getIOOutReg(self): + retval = self.det.patioctrl + self.view.lineEditPatIOCtrl.editingFinished.disconnect() + self.view.lineEditPatIOCtrl.setText("0x{:016x}".format(retval)) + self.view.lineEditPatIOCtrl.editingFinished.connect(self.setIOOutReg) + return retval + + def setIOOutReg(self): + self.view.lineEditPatIOCtrl.editingFinished.disconnect() + try: + self.det.patioctrl = int(self.view.lineEditPatIOCtrl.text(), 16) + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "IO Out Fail", str(e), QtWidgets.QMessageBox.Ok) + pass + # TODO: handling double event exceptions + self.view.lineEditPatIOCtrl.editingFinished.connect(self.setIOOutReg) + self.updateIOOut() + + def updateCheckBoxIOOut(self, i, out): + checkBox = getattr(self.view, f"checkBoxBIT{i}Out") + checkBox.stateChanged.disconnect() + checkBox.setChecked(bit_is_set(out, i)) + checkBox.stateChanged.connect(partial(self.setIOOut, i)) + + def updateIOOut(self): + retval = self.getIOOutReg() + for i in range(Defines.signals.count): + self.updateCheckBoxIOOut(i, retval) + self.getIOoutRange(retval) + + def setIOOut(self, i): + out = self.det.patioctrl + checkBox = getattr(self.view, f"checkBoxBIT{i}Out") + mask = manipulate_bit(checkBox.isChecked(), out, i) + self.det.patioctrl = mask + + retval = self.getIOOutReg() + self.updateCheckBoxIOOut(i, retval) + self.getIOoutRange(retval) + + def getIOoutRange(self, out): + self.view.checkBoxBIT0_31Out.stateChanged.disconnect() + self.view.checkBoxBIT32_63Out.stateChanged.disconnect() + self.view.checkBoxBIT0_31Out.setChecked((out & Defines.signals.BIT0_31_MASK) == Defines.signals.BIT0_31_MASK) + self.view.checkBoxBIT32_63Out.setChecked((out + & Defines.signals.BIT32_63_MASK) == Defines.signals.BIT32_63_MASK) + self.view.checkBoxBIT0_31Out.stateChanged.connect(partial(self.setIOOutRange, 0, Defines.signals.half)) + self.view.checkBoxBIT32_63Out.stateChanged.connect( + partial(self.setIOOutRange, Defines.signals.half, Defines.signals.count)) + + def setIOOutRange(self, start_nr, end_nr): + out = self.det.patioctrl + checkBox = getattr(self.view, f"checkBoxBIT{start_nr}_{end_nr - 1}Out") + mask = getattr(Defines.signals, f"BIT{start_nr}_{end_nr - 1}_MASK") + if checkBox.isChecked(): + self.det.patioctrl = out | mask + else: + self.det.patioctrl = out & ~mask + self.updateIOOut() + + def getDBitOffset(self): + self.view.spinBoxDBitOffset.editingFinished.disconnect() + self.rx_dbitoffset = self.det.rx_dbitoffset + self.view.spinBoxDBitOffset.setValue(self.rx_dbitoffset) + self.view.spinBoxDBitOffset.editingFinished.connect(self.setDbitOffset) + + def setDbitOffset(self): + self.det.rx_dbitoffset = self.view.spinBoxDBitOffset.value() + + def saveParameters(self) -> list: + commands = [] + dblist = [str(i) for i in range(Defines.signals.count) if getattr(self.view, f"checkBoxBIT{i}DB").isChecked()] + if len(dblist) > 0: + commands.append(f"rx_dbitlist {', '.join(dblist)}") + commands.append(f"rx_dbitoffset {self.view.spinBoxDBitOffset.value()}") + commands.append(f"patioctrl {self.view.lineEditPatIOCtrl.text()}") + return commands diff --git a/pyctbgui/pyctbgui/services/SlowADCs.py b/pyctbgui/pyctbgui/services/SlowADCs.py new file mode 100644 index 000000000..4b73b18f0 --- /dev/null +++ b/pyctbgui/pyctbgui/services/SlowADCs.py @@ -0,0 +1,45 @@ +from functools import partial +from pathlib import Path + +from PyQt5 import uic, QtWidgets + +from pyctbgui.utils.defines import Defines +from slsdet import dacIndex + + +class SlowAdcTab(QtWidgets.QWidget): + + def __init__(self, parent): + super().__init__(parent) + uic.loadUi(Path(__file__).parent.parent / 'ui' / "slowAdcs.ui", parent) + self.view = parent + self.mainWindow = None + self.det = None + + def setup_ui(self): + pass + + def connect_ui(self): + for i in range(Defines.slowAdc.count): + getattr(self.view, f"pushButtonSlowAdc{i}").clicked.connect(partial(self.updateSlowAdc, i)) + self.view.pushButtonTemp.clicked.connect(self.updateTemperature) + + def refresh(self): + self.updateSlowAdcNames() + for i in range(Defines.slowAdc.count): + self.updateSlowAdc(i) + self.updateTemperature() + + def updateSlowAdcNames(self): + for i, name in enumerate(self.mainWindow.det.getSlowADCNames()): + getattr(self.view, f"labelSlowAdc{i}").setText(name) + + def updateSlowAdc(self, i): + slowADCIndex = getattr(dacIndex, f"SLOW_ADC{i}") + label = getattr(self.view, f"labelSlowAdcValue{i}") + slowadc = (self.det.getSlowADC(slowADCIndex))[0] / 1000 + label.setText(f'{slowadc:.2f} mV') + + def updateTemperature(self): + slowadc = self.det.getTemperature(dacIndex.SLOW_ADC_TEMP) + self.view.labelTempValue.setText(f'{str(slowadc[0])} °C') diff --git a/pyctbgui/pyctbgui/services/Transceiver.py b/pyctbgui/pyctbgui/services/Transceiver.py new file mode 100644 index 000000000..98c8fc541 --- /dev/null +++ b/pyctbgui/pyctbgui/services/Transceiver.py @@ -0,0 +1,273 @@ +from functools import partial +from pathlib import Path + +import numpy as np +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 + + +class TransceiverTab(QtWidgets.QWidget): + + def __init__(self, parent): + super().__init__(parent) + uic.loadUi(Path(__file__).parent.parent / 'ui' / "transceiver.ui", parent) + self.view = parent + self.mainWindow = None + self.det = None + self.plotTab = None + self.legend: LegendItem | None = None + self.acquisitionTab = None + + def setup_ui(self): + self.plotTab = self.mainWindow.plotTab + self.acquisitionTab = self.mainWindow.acquisitionTab + for i in range(Defines.transceiver.count): + self.setTransceiverButtonColor(i, self.plotTab.getRandomColor()) + self.initializeAllTransceiverPlots() + + self.legend = self.mainWindow.plotTransceiverWaveform.getPlotItem().legend + self.legend.clear() + + # subscribe to toggle legend + self.plotTab.subscribeToggleLegend(self.updateLegend) + + def connect_ui(self): + for i in range(Defines.transceiver.count): + getattr(self.view, f"checkBoxTransceiver{i}").stateChanged.connect(partial(self.setTransceiverEnable, i)) + getattr(self.view, + f"checkBoxTransceiver{i}Plot").stateChanged.connect(partial(self.setTransceiverEnablePlot, i)) + getattr(self.view, f"pushButtonTransceiver{i}").clicked.connect(partial(self.selectTransceiverColor, i)) + self.view.lineEditTransceiverMask.editingFinished.connect(self.setTransceiverEnableReg) + + def refresh(self): + self.updateTransceiverEnable() + + def getEnabledPlots(self): + """ + return plots that are shown (checkBoxTransceiver{i}Plot is checked) + """ + enabledPlots = [] + self.legend.clear() + for i in range(Defines.transceiver.count): + if getattr(self.view, f'checkBoxTransceiver{i}Plot').isChecked(): + plotName = getattr(self.view, f"labelTransceiver{i}").text() + enabledPlots.append((self.mainWindow.transceiverPlots[i], plotName)) + return enabledPlots + + def updateLegend(self): + """ + update the legend for the transceiver waveform plot + should be called after checking or unchecking plot checkbox + """ + if not self.mainWindow.showLegend: + self.legend.clear() + else: + for plot, name in self.getEnabledPlots(): + self.legend.addItem(plot, name) + + @recordOrApplyPedestal + def _processWaveformData(self, data, dSamples, romode, nDBitEnabled, nTransceiverEnabled): + """ + model function + processes raw receiver waveform data + @param data: raw receiver waveform data + @param dSamples: digital samples + @param romode: readout mode value + @param nDBitEnabled: number of digital bits enabled + @param nTransceiverEnabled: number of transceivers enabled + @return: processed transceiver data + """ + transceiverOffset = 0 + if romode == 4: + nbitsPerDBit = dSamples + if dSamples % 8 != 0: + nbitsPerDBit += (8 - (dSamples % 8)) + transceiverOffset += nDBitEnabled * (nbitsPerDBit // 8) + trans_array = np.array(np.frombuffer(data, offset=transceiverOffset, dtype=np.uint16)) + return trans_array.reshape(-1, nTransceiverEnabled) + + def processWaveformData(self, data, dSamples): + """ + plots raw waveform data + data: raw waveform data + dsamples: digital samples + tsamples: transceiver samples + """ + waveforms = {} + trans_array = self._processWaveformData(data, dSamples, self.mainWindow.romode.value, + self.mainWindow.nDBitEnabled, self.nTransceiverEnabled) + idx = 0 + for i in range(Defines.transceiver.count): + checkBoxPlot = getattr(self.view, f"checkBoxTransceiver{i}Plot") + checkBoxEn = getattr(self.view, f"checkBoxTransceiver{i}") + if checkBoxEn.isChecked() and checkBoxPlot.isChecked(): + waveform = trans_array[:, idx] + idx += 1 + self.mainWindow.transceiverPlots[i].setData(waveform) + plotName = getattr(self.view, f"labelTransceiver{i}").text() + waveforms[plotName] = waveform + return waveforms + + @recordOrApplyPedestal + def _processImageData(self, data, dSamples, romode, nDBitEnabled): + """ + processes raw image data + @param data: + @param dSamples: + @param romode: + @param nDBitEnabled: + @return: + """ + transceiverOffset = 0 + if romode == 4: + nbitsPerDBit = dSamples + if dSamples % 8 != 0: + nbitsPerDBit += (8 - (dSamples % 8)) + transceiverOffset += nDBitEnabled * (nbitsPerDBit // 8) + trans_array = np.array(np.frombuffer(data, offset=transceiverOffset, dtype=np.uint16)) + return decoder.decode(trans_array, pm.matterhorn_transceiver()) + + def processImageData(self, data, dSamples): + """ + view function + plots transceiver image + dSamples: digital samples + data: raw image data + """ + # get zoom state + viewBox = self.mainWindow.plotTransceiverImage.getView() + state = viewBox.getState() + try: + self.mainWindow.transceiver_frame = self._processImageData(data, dSamples, self.mainWindow.romode.value, + self.mainWindow.nDBitEnabled) + self.plotTab.ignoreHistogramSignal = True + self.mainWindow.plotTransceiverImage.setImage(self.mainWindow.transceiver_frame) + except Exception: + 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.plotTab.setFrameLimits(self.mainWindow.transceiver_frame) + + # keep the zoomed in state (not 1st image) + if self.mainWindow.firstTransceiverImage: + self.mainWindow.firstTransceiverImage = False + else: + viewBox.setState(state) + return self.mainWindow.transceiver_frame + + def initializeAllTransceiverPlots(self): + self.mainWindow.plotTransceiverWaveform = pg.plot() + self.mainWindow.plotTransceiverWaveform.addLegend(colCount=Defines.colCount) + self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotTransceiverWaveform, 5) + self.mainWindow.transceiverPlots = {} + waveform = np.zeros(1000) + for i in range(Defines.transceiver.count): + pen = pg.mkPen(color=self.getTransceiverButtonColor(i), width=1) + legendName = getattr(self.view, f"labelTransceiver{i}").text() + self.mainWindow.transceiverPlots[i] = self.mainWindow.plotTransceiverWaveform.plot(waveform, + pen=pen, + name=legendName) + self.mainWindow.transceiverPlots[i].hide() + + self.mainWindow.plotTransceiverImage = pg.ImageView() + self.mainWindow.nTransceiverRows = 0 + self.mainWindow.nTransceiverCols = 0 + self.mainWindow.transceiver_frame = np.zeros( + (self.mainWindow.nTransceiverRows, self.mainWindow.nTransceiverCols)) + self.mainWindow.plotTransceiverImage.setImage(self.mainWindow.transceiver_frame) + self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotTransceiverImage, 6) + + cm = pg.colormap.get('CET-L9') # prepare a linear color map + self.mainWindow.plotTransceiverImage.setColorMap(cm) + + def getTransceiverEnableReg(self): + retval = self.det.transceiverenable + self.view.lineEditTransceiverMask.editingFinished.disconnect() + self.view.lineEditTransceiverMask.setText("0x{:08x}".format(retval)) + self.view.lineEditTransceiverMask.editingFinished.connect(self.setTransceiverEnableReg) + return retval + + def setTransceiverEnableReg(self): + self.view.lineEditTransceiverMask.editingFinished.disconnect() + try: + mask = int(self.view.lineEditTransceiverMask.text(), 16) + self.det.transceiverenable = mask + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "Transceiver Enable Fail", str(e), QtWidgets.QMessageBox.Ok) + pass + # TODO: handling double event exceptions + self.view.lineEditTransceiverMask.editingFinished.connect(self.setTransceiverEnableReg) + self.updateTransceiverEnable() + + def getTransceiverEnable(self, i, mask): + checkBox = getattr(self.view, f"checkBoxTransceiver{i}") + checkBox.stateChanged.disconnect() + checkBox.setChecked(bit_is_set(mask, i)) + checkBox.stateChanged.connect(partial(self.setTransceiverEnable, i)) + + def updateTransceiverEnable(self): + retval = self.getTransceiverEnableReg() + self.nTransceiverEnabled = bin(retval).count('1') + for i in range(4): + self.getTransceiverEnable(i, retval) + self.getTransceiverEnablePlot(i) + self.getTransceiverEnableColor(i) + self.plotTab.addSelectedTransceiverPlots(i) + + def setTransceiverEnable(self, i): + checkBox = getattr(self.view, f"checkBoxTransceiver{i}") + try: + enableMask = manipulate_bit(checkBox.isChecked(), self.det.transceiverenable, i) + self.det.transceiverenable = enableMask + except Exception as e: + QtWidgets.QMessageBox.warning(self.mainWindow, "Transceiver Enable Fail", str(e), QtWidgets.QMessageBox.Ok) + pass + + self.updateTransceiverEnable() + + def getTransceiverEnablePlot(self, i): + checkBox = getattr(self.view, f"checkBoxTransceiver{i}") + checkBoxPlot = getattr(self.view, f"checkBoxTransceiver{i}Plot") + checkBoxPlot.setEnabled(checkBox.isChecked()) + + def setTransceiverEnablePlot(self, i): + pushButton = getattr(self.view, f"pushButtonTransceiver{i}") + checkBox = getattr(self.view, f"checkBoxTransceiver{i}Plot") + pushButton.setEnabled(checkBox.isChecked()) + self.plotTab.addSelectedTransceiverPlots(i) + self.updateLegend() + + def getTransceiverEnableColor(self, i): + checkBox = getattr(self.view, f"checkBoxTransceiver{i}Plot") + pushButton = getattr(self.view, f"pushButtonTransceiver{i}") + pushButton.setEnabled(checkBox.isEnabled() and checkBox.isChecked()) + + def selectTransceiverColor(self, i): + pushButton = getattr(self.view, f"pushButtonTransceiver{i}") + self.plotTab.showPalette(pushButton) + pen = pg.mkPen(color=self.getTransceiverButtonColor(i), width=1) + self.mainWindow.transceiverPlots[i].setPen(pen) + + def getTransceiverButtonColor(self, i): + pushButton = getattr(self.view, f"pushButtonTransceiver{i}") + return self.plotTab.getActiveColor(pushButton) + + def setTransceiverButtonColor(self, i, color): + pushButton = getattr(self.view, f"pushButtonTransceiver{i}") + return self.plotTab.setActiveColor(pushButton, color) + + def saveParameters(self): + return ["transceiverenable {}".format(self.view.lineEditTransceiverMask.text())] diff --git a/pyctbgui/pyctbgui/services/__init__.py b/pyctbgui/pyctbgui/services/__init__.py new file mode 100644 index 000000000..9f3e43434 --- /dev/null +++ b/pyctbgui/pyctbgui/services/__init__.py @@ -0,0 +1,9 @@ +from .ADC import AdcTab +from .Acquisition import AcquisitionTab +from .DACs import DacTab +from .Pattern import PatternTab +from .Plot import PlotTab +from .PowerSupplies import PowerSuppliesTab +from .Signals import SignalsTab +from .SlowADCs import SlowAdcTab +from .Transceiver import TransceiverTab diff --git a/pyctbgui/pyctbgui/ui/CtbGui.ui b/pyctbgui/pyctbgui/ui/CtbGui.ui new file mode 100644 index 000000000..3d96407aa --- /dev/null +++ b/pyctbgui/pyctbgui/ui/CtbGui.ui @@ -0,0 +1,779 @@ + + + MainWindow + + + + 0 + 0 + 1444 + 943 + + + + Chip Test Board + + + false + + + + + 870 + 890 + + + + + 870 + 890 + + + + + 9 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 50 + 0 + + + + QFrame::Plain + + + true + + + + + 0 + 0 + 870 + 879 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + + 0 + + + 9 + + + 0 + + + 0 + + + + + + 870 + 870 + + + + + 870 + 870 + + + + 0 + + + + DACs + + + + + 0 + 0 + 851 + 841 + + + + + 711 + 791 + + + + + + + Power Supplies + + + + + -1 + -1 + 841 + 821 + + + + + + + Slow ADCs + + + + + 0 + 0 + 871 + 821 + + + + + 871 + 571 + + + + + + + Signals + + + + + 0 + 0 + 831 + 821 + + + + + 831 + 821 + + + + + + + Transceivers + + + + + 9 + 19 + 841 + 341 + + + + + 841 + 181 + + + + + + + ADCs + + + + + -1 + -1 + 841 + 821 + + + + + + + Pattern + + + + + 0 + 0 + 851 + 831 + + + + + + + Acquisition + + + + + -10 + 0 + 860 + 800 + + + + + 860 + 800 + + + + + + + Plot + + + + + 0 + 0 + 860 + 800 + + + + + 860 + 800 + + + + + + + + + + + + + + + + + + + 0 + 0 + 1444 + 25 + + + + + File + + + + + + + + Help + + + + + + + + + + + + 0 + 0 + + + + + 568 + 214 + + + + + 524287 + 524287 + + + + QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable + + + Qt::RightDockWidgetArea + + + 2 + + + + + + + + 16777215 + 100 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 70 + 0 + + + + + 70 + 16777215 + + + + 0 + + + + + + + + 100 + 0 + + + + 24 + + + + + + + + 0 + 36 + + + + + 100 + 16777215 + + + + + + + + + 85 + 170 + 127 + + + + + + + 85 + 170 + 127 + + + + + + + 85 + 170 + 127 + + + + + + + + + 85 + 170 + 127 + + + + + + + 85 + 170 + 127 + + + + + + + 85 + 170 + 127 + + + + + + + + + 85 + 170 + 127 + + + + + + + 85 + 170 + 127 + + + + + + + 85 + 170 + 127 + + + + + + + + Shift + Enter is the keyboard shortcut to start/stop acquisition from any tab + + + QPushButton {background-color: rgb(85, 170, 127);} +QPushButton:checked{background-color: red;} + + + Start + + + true + + + + + + + + 110 + 0 + + + + IDLE + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 70 + 16777215 + + + + Acquired: + + + + + + + + 70 + 0 + + + + + 70 + 16777215 + + + + 0 + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + Measurement: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + # + + + + + + + + + + + + + + Load Parameters + + + + + Save + + + + + Exit + + + + + Info + + + + + Keyboard Shortcuts + + + + + Save Parameters + + + + + + PowerSuppliesTab + QWidget +
pyctbgui.services.PowerSupplies
+ 1 +
+ + DacTab + QWidget +
pyctbgui.services.DACs
+ 1 +
+ + SlowAdcTab + QWidget +
pyctbgui.services.SlowADCs
+ 1 +
+ + SignalsTab + QWidget +
pyctbgui.services.Signals
+ 1 +
+ + TransceiverTab + QWidget +
pyctbgui.services.Transceiver
+ 1 +
+ + AdcTab + QWidget +
pyctbgui.services.ADC
+ 1 +
+ + PatternTab + QWidget +
pyctbgui.services.Pattern
+ 1 +
+ + AcquisitionTab + QWidget +
pyctbgui.services.Acquisition
+ 1 +
+ + PlotTab + QWidget +
pyctbgui.services.Plot
+ 1 +
+
+ + + + actionExit + triggered() + MainWindow + close() + + + -1 + -1 + + + 753 + 496 + + + + +
diff --git a/pyctbgui/pyctbgui/ui/Dacs.ui b/pyctbgui/pyctbgui/ui/Dacs.ui new file mode 100644 index 000000000..940f93c7d --- /dev/null +++ b/pyctbgui/pyctbgui/ui/Dacs.ui @@ -0,0 +1,1198 @@ + + + Form + + + + 0 + 0 + 870 + 791 + + + + + 870 + 791 + + + + + 2000 + 2000 + + + + + 711 + 791 + + + + Form + + + + + 20 + 10 + 711 + 791 + + + + + 711 + 791 + + + + + 1000 + 1000 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + 11 + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 1 + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 7 + + + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 8 + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 15 + + + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 2 + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + 0 + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4096 + + + + + + + 0 + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4096 + + + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 14 + + + + + + + 0 + + + + + + + 0 + + + + + + + 0 + + + + + + + 0 + + + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 16 + + + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 0 + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + 0 + + + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 6 + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0 + + + 4096 + + + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 9 + + + + + + + 0 + + + + + + + mV + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + 0 + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4096 + + + + + + + V + + + + + + + 0 + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 5 + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4096 + + + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 13 + + + + + + + 0 + + + + + + + 0 + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4096 + + + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 17 + + + + + + + 0 + + + + + + + High Voltage + + + + + + + Qt::LeftToRight + + + + 1000 + + + + + 1140 + + + + + 1330 + + + + + 1600 + + + + + 2000 + + + + + + + + 0 + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4096 + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4096 + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4096 + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4096 + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4096 + + + + + + + ADC_VPP + + + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 11 + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4096 + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4096 + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4096 + + + + + + + 0 + + + + + + + 0 + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4096 + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + 0 + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only enter values between 60-200 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 200 + + + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 3 + + + + + + + 0 + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4096 + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 10 + + + + + + + 0 + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4096 + + + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 4 + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + Only accepts value range (0 - 4096). Only modifying this or pressing enter will set DAC. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4096 + + + + + + + Checking or unchecking this will only get DAC values on the right (with this condition).<br> +Only pressing enter on spinbox will set DAC (with this condition). + + + mV + + + + + + + false + + + Unchecking will set DAC in tristate (sends -100).<br> +Checking sends nothing to DAC + + + DAC 12 + + + + + + + 0 + + + + + + + + + diff --git a/pyctbgui/pyctbgui/ui/MainWindow.py b/pyctbgui/pyctbgui/ui/MainWindow.py new file mode 100644 index 000000000..acd07c6f7 --- /dev/null +++ b/pyctbgui/pyctbgui/ui/MainWindow.py @@ -0,0 +1,369 @@ +import logging + +from PyQt5 import QtWidgets, QtCore, uic +import argparse +import signal +import pyqtgraph as pg +from pathlib import Path +from functools import partial + +from slsdet import Detector, dacIndex + +from pyctbgui.services import TransceiverTab, DacTab, AdcTab, AcquisitionTab, SignalsTab, PatternTab, \ + SlowAdcTab, PlotTab, PowerSuppliesTab +from pyctbgui.utils import alias_utility +from pyctbgui.utils.defines import Defines + + +class MainWindow(QtWidgets.QMainWindow): + signalShortcutAcquire = QtCore.pyqtSignal() + signalShortcutTabUp = QtCore.pyqtSignal() + signalShortcutTabDown = QtCore.pyqtSignal() + + def __init__(self, *args, **kwargs): + parser = argparse.ArgumentParser() + parser.add_argument('-a', '--alias', help="Alias file complete path") + arglist, __ = parser.parse_known_args() + self.alias_file = arglist.alias + + pg.setConfigOption("background", (247, 247, 247)) + pg.setConfigOption("foreground", "k") + pg.setConfigOption('leftButtonPan', False) + + super().__init__(*args, **kwargs) + + uic.loadUi(Path(__file__).parent / "CtbGui.ui", self) + logging.basicConfig(encoding='utf-8', level=logging.INFO) + + self.logger = logging.getLogger(__name__) + self.det = None + self.showLegend = True + self.settings = None + try: + self.det = Detector() + # ensure detector is up + self.det.detectorserverversion[0] + except Exception as e: + QtWidgets.QMessageBox.critical(self, "Connect Fail", str(e) + "Exiting Gui...", QtWidgets.QMessageBox.Ok) + raise + + # get Tab Classes + self.plotTab: PlotTab = self.widgetPlot + self.slowAdcTab: SlowAdcTab = self.widgetSlowAdcs + self.dacTab: DacTab = self.widgetDacs + self.powerSuppliesTab: PowerSuppliesTab = self.widgetPowerSupplies + self.signalsTab: SignalsTab = self.widgetSignals + self.transceiverTab: TransceiverTab = self.widgetTransceiver + self.adcTab: AdcTab = self.widgetAdc + self.patternTab: PatternTab = self.widgetPattern + self.acquisitionTab: AcquisitionTab = self.widgetAcquisition + + self.tabs_list = [ + self.dacTab, self.powerSuppliesTab, self.slowAdcTab, self.signalsTab, self.transceiverTab, self.adcTab, + self.patternTab, self.acquisitionTab, self.plotTab + ] + + self.setup_ui() + self.acquisitionTab.setup_zmq() + self.tabWidget.setCurrentIndex(Defines.Acquisition_Tab_Index) + self.tabWidget.currentChanged.connect(self.refresh_tab) + self.connect_ui() + + for tab in self.tabs_list: + tab.refresh() + + # also refreshes timer to start plotting + self.plotTab.plotOptions() + self.plotTab.showPlot() + + self.patternTab.getPatViewerColors() + self.patternTab.getPatViewerWaitParameters() + self.patternTab.getPatViewerLoopParameters() + self.patternTab.updatePatViewerParameters() + self.plotTab.showPatternViewer(False) + + if self.alias_file is not None: + self.loadAliasFile() + + self.signalShortcutAcquire.connect(self.pushButtonStart.click) + self.signalShortcutTabUp.connect(partial(self.changeTabIndex, True)) + self.signalShortcutTabDown.connect(partial(self.changeTabIndex, False)) + # to catch the ctrl + c to abort + signal.signal(signal.SIGINT, signal.SIG_DFL) + self.firstAnalogImage = True + self.firstDigitalImage = True + self.firstTransceiverImage = True + + self.updateSettingValues() + + def updateSettingMainWindow(self): + self.settings.beginGroup("mainwindow") + # window size + width = self.settings.value('window_width') + height = self.settings.value('window_height') + if width is not None and height is not None: + self.resize(int(width), int(height)) + # print(f'Main window resized to {width}x{height}') + + # window position + pos = self.settings.value('window_pos') + if type(pos) is QtCore.QPoint: + # print(f'Moved main window to {pos}') + self.move(pos) + self.settings.endGroup() + + def saveSettingMainWindow(self): + self.settings.beginGroup("mainwindow") + self.settings.setValue('window_width', self.rect().width()) + self.settings.setValue('window_height', self.rect().height()) + self.settings.setValue('window_pos', self.pos()) + self.settings.endGroup() + + def updateSettingDockWidget(self): + self.settings.beginGroup("dockwidget") + + # is docked + if self.settings.contains('window_width') and self.settings.contains('window_height'): + # window size + width = self.settings.value('window_width') + height = self.settings.value('window_height') + if width is not None and height is not None: + # print(f'Plot window - Floating ({width}x{height})') + self.dockWidget.setFloating(True) + self.dockWidget.resize(int(width), int(height)) + # window position + pos = self.settings.value('window_pos') + if type(pos) is QtCore.QPoint: + # print(f'Moved plot window to {pos}') + self.dockWidget.move(pos) + self.settings.endGroup() + + def saveSettingDockWidget(self): + self.settings.beginGroup("dockwidget") + if self.dockWidget.isFloating(): + self.settings.setValue('window_width', self.dockWidget.rect().width()) + self.settings.setValue('window_height', self.dockWidget.rect().height()) + self.settings.setValue('window_pos', self.dockWidget.pos()) + else: + self.settings.remove('window_width') + self.settings.remove('window_height') + self.settings.remove('window_pos') + self.settings.endGroup() + + def savePlotTypeAndDetector(self): + self.settings.setValue('isImage', self.plotTab.view.radioButtonImage.isChecked()) + self.settings.setValue('detector', self.plotTab.view.comboBoxPlot.currentText()) + + def updatePlotTypeAndDetector(self): + # load plot type from qsettings + isImage = self.settings.value('isImage', True, type=bool) + self.plotTab.view.radioButtonImage.setChecked(isImage) + self.plotTab.plotOptions() + # load detector from qsettings + if isImage: + self.plotTab.view.comboBoxPlot.setCurrentText(self.settings.value('detector', 'Matterhorn')) + + def updateSettingValues(self): + self.settings = QtCore.QSettings('slsdetectorgroup', 'pyctbgui') + self.updateSettingMainWindow() + self.updateSettingDockWidget() + self.updatePlotTypeAndDetector() + + def saveSettings(self): + # store in ~/.config/slsdetectorgroup/pyctbgui.conf + self.saveSettingMainWindow() + self.saveSettingDockWidget() + self.savePlotTypeAndDetector() + + def closeEvent(self, event): + self.saveSettings() + + def loadAliasFile(self): + print(f'Loading Alias file: {self.alias_file}') + try: + bit_names, bit_plots, bit_colors, adc_names, adc_plots, adc_colors, dac_names, slowadc_names, \ + voltage_names, pat_file_name = alias_utility.read_alias_file(self.alias_file) + except Exception as e: + QtWidgets.QMessageBox.warning(self, "Alias File Fail", + str(e) + "
" + self.alias_file, QtWidgets.QMessageBox.Ok) + return + + for i in range(Defines.signals.count): + if bit_names[i]: + self.det.setSignalName(i, bit_names[i]) + if bit_plots[i]: + getattr(self.signalsTab.view, f"checkBoxBIT{i}DB").setChecked(bit_plots[i]) + getattr(self.signalsTab.view, f"checkBoxBIT{i}Plot").setChecked(bit_plots[i]) + if bit_colors[i]: + self.signalsTab.setDBitButtonColor(i, bit_colors[i]) + + for i in range(Defines.adc.count): + if adc_names[i]: + self.det.setAdcName(i, adc_names[i]) + if adc_plots[i]: + getattr(self.adcTab.view, f"checkBoxADC{i}En").setChecked(adc_plots[i]) + getattr(self.adcTab.view, f"checkBoxADC{i}Plot").setChecked(adc_plots[i]) + if adc_colors[i]: + self.adcTab.setADCButtonColor(i, adc_colors[i]) + + for i in range(Defines.dac.count): + if dac_names[i]: + iDac = getattr(dacIndex, f"DAC_{i}") + self.det.setDacName(iDac, dac_names[i]) + + for i in range(Defines.slowAdc.count): + slowadc_index = self.det.getSlowADCList() + if slowadc_names[i]: + self.det.setSlowADCName(slowadc_index[i], slowadc_names[i]) + + for i in range(len(Defines.powerSupplies)): + voltage_index = self.det.getVoltageList() + if voltage_names[i]: + self.det.setVoltageName(voltage_index[i], voltage_names[i]) + + if pat_file_name: + self.lineEditPatternFile.setText(pat_file_name) + + self.signalsTab.updateSignalNames() + self.adcTab.updateADCNames() + self.slowAdcTab.updateSlowAdcNames() + self.dacTab.updateDACNames() + self.powerSuppliesTab.updateVoltageNames() + + # For Action options function + # TODO Only add the components of action option+ functions + # Function to show info + def showInfo(self): + msg = QtWidgets.QMessageBox() + msg.setWindowTitle("About") + msg.setText("This Gui is for Chip Test Boards.\n Current Phase: Development") + msg.exec_() + + def showKeyBoardShortcuts(self): + msg = QtWidgets.QMessageBox() + msg.setWindowTitle("Keyboard Shortcuts") + msg.setText( + "Start Acquisition (from any tab): Shift + Return
Move Tab Right : Ctrl + '+'
Move Tab Left :" + " Ctrl + '-'
") + msg.exec_() + + def loadParameters(self): + response = QtWidgets.QFileDialog.getOpenFileName( + parent=self, + caption="Select a parameter file to open", + directory=str(Path.cwd()), + # filter='README (*.md *.ui)' + ) + if response[0] == '': + return + try: + self.det.parameters = (response[0]) + for tab in self.tabs_list: + tab.refresh() + QtWidgets.QMessageBox.information(self, "Load Parameter Success", "Parameters loaded successfully", + QtWidgets.QMessageBox.Ok) + except (RuntimeError, FileNotFoundError) as e: + self.logger.exception(e) + QtWidgets.QMessageBox.warning(self, "Load Parameter Fail", str(e), QtWidgets.QMessageBox.Ok) + + def refresh_tab(self, tab_index): + match tab_index: + case 0: + self.dacTab.refresh() + case 1: + self.powerSuppliesTab.refresh() + case 2: + self.slowAdcTab.refresh() + case 3: + self.transceiverTab.refresh() + case 4: + self.signalsTab.refresh() + case 5: + self.adcTab.refresh() + case 6: + self.patternTab.refresh() + case 7: + self.acquisitionTab.refresh() + case 8: + self.plotTab.refresh() + + def setup_ui(self): + # To check detector status + self.statusTimer = QtCore.QTimer() + self.statusTimer.timeout.connect(self.acquisitionTab.checkEndofAcquisition) + + # To auto trigger the read + self.read_timer = QtCore.QTimer() + self.read_timer.timeout.connect(self.acquisitionTab.read_zmq) + + for tab in self.tabs_list: + tab.mainWindow = self + tab.det = self.det + + for tab in self.tabs_list: + tab.setup_ui() + + def keyPressEvent(self, event): + if event.modifiers() & QtCore.Qt.ShiftModifier: + if event.key() == QtCore.Qt.Key_Return: + self.signalShortcutAcquire.emit() + if event.modifiers() & QtCore.Qt.ControlModifier: + if event.key() == QtCore.Qt.Key_Plus: + self.signalShortcutTabUp.emit() + if event.key() == QtCore.Qt.Key_Minus: + self.signalShortcutTabDown.emit() + + def changeTabIndex(self, up): + ind = self.tabWidget.currentIndex() + if up: + ind += 1 + if ind == Defines.Max_Tabs: + ind = 0 + else: + ind -= 1 + if ind == -1: + ind = Defines.Max_Tabs - 1 + self.tabWidget.setCurrentIndex(ind) + + def connect_ui(self): + # Show info + self.actionInfo.triggered.connect(self.showInfo) + self.actionKeyboardShortcuts.triggered.connect(self.showKeyBoardShortcuts) + self.actionLoadParameters.triggered.connect(self.loadParameters) + self.pushButtonStart.clicked.connect(self.acquisitionTab.toggleAcquire) + self.actionSaveParameters.triggered.connect(self.saveParameters) + + for tab in self.tabs_list: + tab.connect_ui() + + def saveParameters(self): + response = QtWidgets.QFileDialog.getSaveFileName(self, "Save Parameters", str(self.det.fpath)) + if response[0] == '': + return + + # save DACs + commands = self.dacTab.saveParameters() + # save signals + commands.extend(self.signalsTab.saveParameters()) + # save transceiver + commands.extend(self.transceiverTab.saveParameters()) + # save ADCs + commands.extend(self.adcTab.saveParameters()) + # save pattern + commands.extend(self.patternTab.saveParameters()) + # save acquisition + commands.extend(self.acquisitionTab.saveParameters()) + # save power supplies + commands.extend(self.powerSuppliesTab.saveParameters()) + # save plot + commands.extend(self.plotTab.saveParameters()) + + try: + with open(response[0], 'w') as fp: + fp.write('\n'.join(commands)) + except Exception as e: + self.logger.exception(e) + QtWidgets.QMessageBox.warning(self, "Save Parameter Fail", str(e), QtWidgets.QMessageBox.Ok) + + QtWidgets.QMessageBox.information(self, "Save Parameter Success", "Parameters saved successfully", + QtWidgets.QMessageBox.Ok) diff --git a/pyctbgui/pyctbgui/ui/__init__.py b/pyctbgui/pyctbgui/ui/__init__.py new file mode 100644 index 000000000..0a3f5ca61 --- /dev/null +++ b/pyctbgui/pyctbgui/ui/__init__.py @@ -0,0 +1 @@ +from .MainWindow import MainWindow \ No newline at end of file diff --git a/pyctbgui/pyctbgui/ui/acquisition.ui b/pyctbgui/pyctbgui/ui/acquisition.ui new file mode 100644 index 000000000..5b9fa58fd --- /dev/null +++ b/pyctbgui/pyctbgui/ui/acquisition.ui @@ -0,0 +1,927 @@ + + + Form + + + + 0 + 0 + 870 + 823 + + + + + 870 + 800 + + + + Form + + + + + 10 + 10 + 841 + 71 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + 0 + 0 + + + + + 125 + 31 + + + + + 125 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + QAbstractSpinBox::UpDownArrows + + + 1000 + + + 0 + + + + + + + Read Out Mode: + + + + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + + Analog + + + + + Digital + + + + + Analog and Digital + + + + + Transceiver + + + + + Digital and Transceiver + + + + + + + + Run Clock Frequency (MHz): + + + + + + + + + 10 + 90 + 841 + 51 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 0 + + + + + 125 + 32 + + + + + 125 + 32 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1 + + + 100000 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 200 + 0 + + + + Transceiver Samples: + + + + + + + + + 10 + 150 + 841 + 201 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + 200 + 0 + + + + DBIT Clock Frequency (MHz): + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + ADC Clock Frequency (MHz): + + + + + + + + 0 + 0 + + + + + 125 + 31 + + + + + 125 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 9999 + + + + + + + DBIT Pipeline: + + + + + + + + 0 + 0 + + + + + 125 + 31 + + + + + 125 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1000 + + + + + + + + 0 + 0 + + + + + 125 + 31 + + + + + 125 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ADC Pipeline: + + + + + + + ADC Clock Phase (a.u.): + + + + + + + DBIT Clock Phase (a.u.): + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 10 + 20 + + + + + + + + + 0 + 0 + + + + + 125 + 31 + + + + + 125 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1000 + + + + + + + + 0 + 0 + + + + + 125 + 31 + + + + + 125 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 125 + 31 + + + + + 125 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 9999 + + + + + + + Analog Samples: + + + + + + + + 0 + 0 + + + + + 125 + 32 + + + + + 125 + 32 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1 + + + 100000 + + + + + + + Digital Samples: + + + + + + + + 0 + 0 + + + + + 125 + 32 + + + + + 125 + 32 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1 + + + 100000 + + + 1 + + + + + + + + + 10 + 370 + 841 + 181 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + 11 + + + + QFrame::Sunken + + + 1 + + + Output Settings + + + + + + + File name: + + + + + + + + 0 + 31 + + + + + 16777215 + 31 + + + + background-color: rgb(200, 219, 230); + + + File Name + + + + + + + Index: + + + + + + + + 0 + 31 + + + + + 200 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999999999 + + + + + + + Raw + + + + + + + + 0 + 36 + + + + background-color: rgb(199, 213, 207); + + + Browse + + + + + + + + 0 + 31 + + + + + 16777215 + 31 + + + + background-color: rgb(200, 219, 230); + + + File Path + + + + + + + File path: + + + + + + + Numpy + + + + + + + Save format: + + + + + + + + + 10 + 570 + 841 + 141 + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + Number of frames: + + + + + + + + 0 + 0 + + + + + 120 + 32 + + + + + 16777215 + 32 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1 + + + 999999999 + + + 999999999 + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Period (s): + + + + + + + + 0 + 0 + + + + + 120 + 32 + + + + + 150 + 32 + + + + 4 + + + 99999.990000000005239 + + + + + + + + 0 + 31 + + + + + s + + + + + ms + + + + + μs + + + + + ns + + + + + + + + + 0 + 0 + + + + Number of triggers: + + + + + + + + 0 + 0 + + + + + 120 + 32 + + + + + 16777215 + 32 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1 + + + 999999999 + + + + + + + Number of measurements: + + + + + + + + 120 + 32 + + + + + 16777215 + 32 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1 + + + 999999999 + + + + + + + + + diff --git a/pyctbgui/pyctbgui/ui/adc.ui b/pyctbgui/pyctbgui/ui/adc.ui new file mode 100644 index 000000000..c1fabe266 --- /dev/null +++ b/pyctbgui/pyctbgui/ui/adc.ui @@ -0,0 +1,4212 @@ + + + Form + + + + 0 + 0 + 841 + 470 + + + + + 841 + 461 + + + + Form + + + + + 0 + 10 + 841 + 461 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC17 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC29 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC22 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC31 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC30 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC2 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC5 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC26 + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC28 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC18 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 80 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC 16-31 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC4 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 80 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC 0-15 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC11 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC21 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC12 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC16 + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC9 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC19 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC3 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC13 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC20 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC10 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC6 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC23 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC24 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC25 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC1 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC7 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC8 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 10 + + + + Inversion mask: + + + + + + + + 150 + 32 + + + + + 150 + 32 + + + + + Monospace + 10 + + + + background-color: rgb(255, 255, 255); + + + 0xFFFFFFFF + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 10 + + + + Enable mask: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 150 + 32 + + + + + 150 + 32 + + + + + Monospace + 10 + + + + background-color: rgb(255, 255, 255); + + + 0xFFFFFFFF + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC0 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC14 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC27 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Inv + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + ADC15 + + + + + + + + + diff --git a/pyctbgui/pyctbgui/ui/pattern.ui b/pyctbgui/pyctbgui/ui/pattern.ui new file mode 100644 index 000000000..0c642a83a --- /dev/null +++ b/pyctbgui/pyctbgui/ui/pattern.ui @@ -0,0 +1,2733 @@ + + + Form + + + + 0 + 0 + 870 + 800 + + + + + 870 + 800 + + + + Form + + + + + 0 + 10 + 471 + 311 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Loop 3 + + + + + + + + 110 + 31 + + + + + 110 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999999999 + + + 0 + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 110 + 31 + + + + + 110 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999999999 + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 110 + 31 + + + + + 110 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999999999 + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Loop 4 + + + + + + + + 110 + 31 + + + + + 110 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999999999 + + + + + + + Stop Address + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Loop 5 + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Loop 2 + + + + + + + Repetitions + + + Qt::AlignCenter + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + Start Address + + + Qt::AlignCenter + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Loop 1 + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 110 + 31 + + + + + 110 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999999999 + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Loop 0 + + + + + + + + 110 + 31 + + + + + 110 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999999999 + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Limits: + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + + 490 + 10 + 361 + 271 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + 125 + 31 + + + + + 125 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999999999 + + + + + + + + 125 + 31 + + + + + 125 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999999999 + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Wait 0 + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Wait 5 + + + + + + + + 125 + 31 + + + + + 125 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999999999 + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Wait 3 + + + + + + + Time + + + Qt::AlignCenter + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 125 + 31 + + + + + 125 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999999999 + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Wait 4 + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Wait 1 + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + Address + + + Qt::AlignCenter + + + + + + + + 125 + 31 + + + + + 125 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999999999 + + + + + + + + 80 + 31 + + + + + 80 + 31 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 36 + 31 + 49 + + + + + + + + + 146 + 149 + 149 + + + + + + + + + Monospace + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 125 + 31 + + + + + 125 + 31 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999999999 + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Wait 2 + + + + + + + + + 10 + 330 + 841 + 141 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + 0 + 36 + + + + + 16777215 + 31 + + + + background-color: rgb(199, 213, 207); + + + Browse + + + + + + + background-color: rgb(200, 219, 230); + + + python + + + Compiler + + + + + + + + 0 + 36 + + + + + 16777215 + 31 + + + + background-color: rgb(199, 213, 207); + + + Browse + + + + + + + Pattern: + + + + + + + background-color: rgb(200, 219, 230); + + + Pattern File + + + + + + + background-color: rgb(200, 219, 230); + + + Uncompiled Pattern File + + + + + + + + 0 + 36 + + + + + 16777215 + 31 + + + + background-color: rgb(199, 213, 207); + + + Browse + + + + + + + Uncompiled Pattern: + + + + + + + Compiler: + + + + + + + + + 10 + 480 + 841 + 211 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 0 + + + + + 30 + 16777215 + + + + : + + + + + + + + 100 + 20 + + + + + 9 + + + + Alpha + + + Qt::AlignCenter + + + + + + + + 100 + 20 + + + + + 9 + + + + Alpha rect + + + Qt::AlignCenter + + + + + + + + 80 + 20 + + + + + 9 + + + + Color + + + Qt::AlignCenter + + + + + + + + 100 + 20 + + + + Viewer + + + + + + + + 0 + 31 + + + + + 120 + 32 + + + + Alpha + + + 1.000000000000000 + + + 0.010000000000000 + + + + + + + + 100 + 32 + + + + + 120 + 32 + + + + + Loop 0 + + + + + Loop 1 + + + + + Loop 2 + + + + + Loop 3 + + + + + Loop 4 + + + + + Loop 5 + + + + + + + + + 0 + 0 + + + + + 30 + 16777215 + + + + : + + + + + + + + 0 + 31 + + + + + 120 + 32 + + + + Alpha + + + 1.000000000000000 + + + 0.010000000000000 + + + + + + + + 100 + 32 + + + + + 120 + 32 + + + + + Wait 0 + + + + + Wait 1 + + + + + Wait 2 + + + + + Wait 3 + + + + + Wait 4 + + + + + Wait 5 + + + + + + + + + 70 + 32 + + + + + 120 + 32 + + + + Line Style + + + + + + + + 120 + 32 + + + + Alpha Rect + + + 1.000000000000000 + + + 0.010000000000000 + + + + + + + + 80 + 32 + + + + + 120 + 32 + + + + + + + + + 70 + 32 + + + + + 120 + 32 + + + + Line Style + + + + + + + + 120 + 32 + + + + Alpha Rect + + + 1.000000000000000 + + + 0.010000000000000 + + + + + + + + 70 + 20 + + + + + 9 + + + + Line style + + + Qt::AlignCenter + + + + + + + + 80 + 32 + + + + + 120 + 32 + + + + + + + + + 100 + 32 + + + + + 120 + 32 + + + + + Plot color 1 + + + + + Plot color 2 + + + + + + + + + 80 + 32 + + + + + 120 + 32 + + + + + + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + Clock spacing: + + + + + + + + 70 + 32 + + + + + 16777215 + 32 + + + + 999 + + + 999 + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 0 + + + + + + + + Clocks Number + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 70 + 0 + + + + + 70 + 16777215 + + + + Line width + + + + + + + + 0 + 32 + + + + + 120 + 32 + + + + Alpha Rect + + + + + + + + + + + 10 + 720 + 841 + 51 + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 200 + 0 + + + + + + + + Compile + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 15 + 20 + + + + + + + + + 150 + 36 + + + + + 16777215 + 36 + + + + background-color: rgb(199, 213, 207); + + + Load Pattern + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 15 + 20 + + + + + + + + + 150 + 36 + + + + + 16777215 + 36 + + + + background-color: rgb(199, 213, 207); + + + View Pattern + + + + + + + + + diff --git a/pyctbgui/pyctbgui/ui/plot.ui b/pyctbgui/pyctbgui/ui/plot.ui new file mode 100644 index 000000000..b01bc3e71 --- /dev/null +++ b/pyctbgui/pyctbgui/ui/plot.ui @@ -0,0 +1,1055 @@ + + + Form + + + + 0 + 0 + 860 + 800 + + + + + 860 + 800 + + + + Form + + + + + 10 + 50 + 841 + 141 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + true + + + Type: + + + + + + + false + + + Distribution + + + buttonGroup + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 2 + + + + + + + Digital plot: + + + + + + + Overlay + + + buttonGroup_2 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + show legend + + + true + + + + + + + Stripe + + + true + + + buttonGroup_2 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + true + + + + 0 + 31 + + + + Image decoder + + + + Matterhorn + + + + + Moench04 + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + + 120 + 31 + + + + Color map of the image + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + + + + + Decoder: + + + + + + + Color Map: + + + + + + + + + + + Options: + + + + + + + true + + + No Plot + + + false + + + buttonGroup + + + + + + + true + + + + 0 + 31 + + + + <html><head/><body><p>If set to high readout, zmq HWM is set to 2 and buffer size to 1MB to drop zmq packets to catch up.</p><p>If set to low readout (default), zmq HWM is set to zmq default (1000) and buffer size to os default to not drop any zmq packets.</p></body></html> + + + + Low - drop no zmq packet + + + + + High - drop zmq packets to catch up + + + + + + + + <html><head/><body><p>If set to high readout, zmq HWM is set to 2 and buffer size to 1MB to drop zmq packets to catch up.</p><p>If set to low readout (default), zmq HWM is set to zmq default (1000) and buffer size to os default to not drop any zmq packets.</p></body></html> + + + Readout speed: + + + + + + + true + + + Image + + + buttonGroup + + + + + + + true + + + Waveform + + + true + + + buttonGroup + + + + + + + + + 0 + 480 + 841 + 311 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + false + + + Fit Panel ADC: + + + + + + + false + + + Max: + + + + + + + false + + + Values + + + + + + + false + + + Dynamic Range: + + + + + + + false + + + Y: + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + Min: + + + + + + + false + + + X: + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + Image Pixels: + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + Y + + + + + + + false + + + Min: + + + + + + + false + + + X + + + + + + + false + + + Max: + + + + + + + false + + + Serial offset: + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + Plot Bit: + + + + + + + false + + + Raw Data + + + + + + + + 0 + 31 + + + + background-color: rgb(199, 213, 207); + + + Referesh + + + + + + + false + + + Pedestal Subtract + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + N Counters: + + + + + + + + + 10 + 219 + 841 + 81 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + 0 + -7 + 841 + 111 + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + + + + + Color Range: + + + + + + + + + 0 + 0 + 121 + 91 + + + + + + + + 16777215 + 15 + + + + All + + + true + + + buttonGroup_4 + + + + + + + true + + + + 16777215 + 15 + + + + 3-97% + + + buttonGroup_4 + + + + + + + + 16777215 + 15 + + + + Fixed + + + buttonGroup_4 + + + + + + + + + + + + 50 + 16777215 + + + + min: + + + + + + + false + + + -1000.000000000000000 + + + 100000.000000000000000 + + + + + + + + 50 + 16777215 + + + + max: + + + + + + + false + + + 100000.000000000000000 + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + + + + + + + + 10 + 330 + 841 + 101 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + 0 + 0 + 841 + 51 + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + + + + + true + + + Pedestal: + + + + + + + + 250 + 0 + + + + recorded frames: 0 + + + + + + + true + + + Record + + + true + + + false + + + buttonGroup_3 + + + + + + + true + + + Apply + + + true + + + buttonGroup_3 + + + + + + + true + + + + 0 + 31 + + + + background-color: rgb(199, 213, 207); + + + Reset + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + + + + + + + -1 + 49 + 841 + 51 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 120 + 31 + + + + background-color: rgb(199, 213, 207); + + + Load Pedestal + + + + + + + + 120 + 31 + + + + background-color: rgb(199, 213, 207); + + + Save Pedestal + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 40 + 20 + + + + + + + + + + + + + + + + + diff --git a/pyctbgui/pyctbgui/ui/powerSupplies.ui b/pyctbgui/pyctbgui/ui/powerSupplies.ui new file mode 100644 index 000000000..d8739957c --- /dev/null +++ b/pyctbgui/pyctbgui/ui/powerSupplies.ui @@ -0,0 +1,483 @@ + + + Form + + + + 0 + 0 + 827 + 375 + + + + Form + + + + + 0 + 0 + 841 + 381 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + 0 + 32 + + + + + 150 + 32 + + + + Only accepts value range (636 - 2468) + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + mV + + + 2468 + + + + + + + + 100 + 16777215 + + + + 0 + + + + + + + + 0 + 32 + + + + + 150 + 32 + + + + Only accepts value range (636 - 2468) + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + mV + + + 0 + + + 2468 + + + 0 + + + + + + + + 100 + 16777215 + + + + 0 + + + + + + + true + + + + 0 + 36 + + + + + 150 + 16777215 + + + + QPushButton{background-color: red;} +QPushButton:disabled{background-color: grey;} + + + Power off + + + false + + + + + + + + 100 + 16777215 + + + + 0 + + + + + + + + 100 + 16777215 + + + + 0 + + + + + + + + 100 + 16777215 + + + + VC + + + + + + + + 0 + 32 + + + + + 150 + 32 + + + + Only accepts value range (1200 - 2468) + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + mV + + + 2468 + + + + + + + + 0 + 32 + + + + + 150 + 32 + + + + Only accepts value range (1200 - 2468) + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + mV + + + 2468 + + + + + + + + 100 + 16777215 + + + + VB + + + + + + + + 100 + 16777215 + + + + 0 + + + + + + + + 100 + 16777215 + + + + 0 + + + + + + + + 100 + 16777215 + + + + VA + + + + + + + + 100 + 16777215 + + + + 0 + + + + + + + + 100 + 16777215 + + + + VD + + + + + + + + 100 + 16777215 + + + + VCHIP + + + + + + + + 100 + 16777215 + + + + 0 + + + + + + + + 0 + 32 + + + + + 150 + 32 + + + + Only accepts value range (636 - 2468) + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + mV + + + 2468 + + + + + + + + 100 + 16777215 + + + + VIO + + + + + + + + 100 + 16777215 + + + + 0 + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 80 + 20 + + + + + + + + + 100 + 16777215 + + + + 0 + + + + + + + + 0 + 32 + + + + + 150 + 32 + + + + Only accepts value range (636 - 2468) + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + mV + + + 2468 + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 80 + 20 + + + + + + + + + + diff --git a/pyctbgui/pyctbgui/ui/signals.ui b/pyctbgui/pyctbgui/ui/signals.ui new file mode 100644 index 000000000..231c0b92f --- /dev/null +++ b/pyctbgui/pyctbgui/ui/signals.ui @@ -0,0 +1,6189 @@ + + + Form + + + + 0 + 0 + 831 + 821 + + + + + 831 + 821 + + + + Form + + + + + 0 + 0 + 831 + 821 + + + + + 831 + 821 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 28 + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + true + + + + 16777215 + 16 + + + + + + + + + + false + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 26 + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 2 + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 20 + + + + + + + + 0 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 23 + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 8 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 43 + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 22 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 44 + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 19 + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 60 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 7 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 18 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 16 + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 59 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 29 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 41 + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 4 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 58 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 9 + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 17 + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 6 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 1 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 35 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 51 + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 21 + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 13 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 0 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 47 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 24 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 57 + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 63 + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 80 + 0 + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 32-63 + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + true + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 36 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 48 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 3 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 37 + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 5 + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 34 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 46 + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT3 1 + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 32 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 50 + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 14 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 40 + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + true + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 42 + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 25 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 39 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 0 + 0 + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 55 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 10 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 53 + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 49 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 30 + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 52 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 62 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 27 + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 80 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 0-31 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 56 + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 11 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 61 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 10 + + + + Out + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 15 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 50 + 0 + + + + + 16777215 + 16 + + + + + 10 + + + + BIT 12 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 38 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Plot + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 54 + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 45 + + + + + + + + 16777215 + 16 + + + + + + + false + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + Out + + + + + + + + 16777215 + 16 + + + + + 9 + + + + DB List + + + + + + + + 16777215 + 16 + + + + + 9 + + + + BIT 33 + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 10 + + + + DBit Offset: + + + + + + + + 0 + 0 + + + + + 200 + 32 + + + + + 200 + 32 + + + + + Monospace + 10 + + + + 0xFFFFFFFFFFFFFFFF + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 150 + 32 + + + + + 150 + 32 + + + + + 10 + + + + background-color: rgb(255, 255, 255); + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 50 + 0 + + + + + 10 + + + + IO Control Register: + + + + + + + + + + + + diff --git a/pyctbgui/pyctbgui/ui/slowAdcs.ui b/pyctbgui/pyctbgui/ui/slowAdcs.ui new file mode 100644 index 000000000..546a30e53 --- /dev/null +++ b/pyctbgui/pyctbgui/ui/slowAdcs.ui @@ -0,0 +1,472 @@ + + + Form + + + + 0 + 0 + 841 + 571 + + + + + 841 + 571 + + + + Form + + + + + 0 + 0 + 841 + 571 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + SENSE 0: + + + + + + + *** + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 150 + 36 + + + + + 16777215 + 36 + + + + + Cantarell + + + + background-color: rgb(199, 213, 207); + + + Update + + + + ../../../../.designer../../../../.designer + + + false + + + + + + + *** + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + *** + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 150 + 36 + + + + + 16777215 + 36 + + + + background-color: rgb(199, 213, 207); + + + Update + + + false + + + + + + + SENSE 4: + + + + + + + *** + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 150 + 36 + + + + + 16777215 + 36 + + + + background-color: rgb(199, 213, 207); + + + Update + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + SENSE 5: + + + + + + + *** + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 150 + 36 + + + + + 16777215 + 36 + + + + background-color: rgb(199, 213, 207); + + + Update + + + false + + + + + + + SENSE 6: + + + + + + + *** + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + SENSE 1: + + + + + + + + 150 + 36 + + + + + 16777215 + 36 + + + + background-color: rgb(199, 213, 207); + + + Update + + + false + + + + + + + SENSE 2: + + + + + + + *** + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + *** + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 150 + 36 + + + + + 16777215 + 36 + + + + background-color: rgb(199, 213, 207); + + + Update + + + false + + + + + + + SENSE 3: + + + + + + + + 150 + 36 + + + + + 16777215 + 36 + + + + background-color: rgb(199, 213, 207); + + + Update + + + false + + + + + + + SENSE 7: + + + + + + + *** + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 150 + 36 + + + + + 16777215 + 36 + + + + background-color: rgb(199, 213, 207); + + + Update + + + false + + + + + + + Temperature + + + + + + + + 150 + 36 + + + + + 16777215 + 36 + + + + background-color: rgb(199, 213, 207); + + + Update + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + + + + + + + diff --git a/pyctbgui/pyctbgui/ui/transceiver.ui b/pyctbgui/pyctbgui/ui/transceiver.ui new file mode 100644 index 000000000..7d1548028 --- /dev/null +++ b/pyctbgui/pyctbgui/ui/transceiver.ui @@ -0,0 +1,542 @@ + + + Form + + + + 0 + 0 + 841 + 239 + + + + + 841 + 181 + + + + Form + + + + + 10 + 20 + 841 + 181 + + + + + 841 + 181 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 150 + 32 + + + + + 150 + 32 + + + + + Monospace + 10 + + + + background-color: rgb(255, 255, 255); + + + 0xFFFF + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + + + + + + 10 + + + + Enable mask: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 80 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Transceiver 0 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Transceiver 2 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Transceiver 1 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Transceiver 3 + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + Plot + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + En + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + + 10 + + + + + + + false + + + + + + + + + diff --git a/pyctbgui/pyctbgui/utils/__init__.py b/pyctbgui/pyctbgui/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pyctbgui/pyctbgui/utils/alias_utility.py b/pyctbgui/pyctbgui/utils/alias_utility.py new file mode 100644 index 000000000..af5cbbe36 --- /dev/null +++ b/pyctbgui/pyctbgui/utils/alias_utility.py @@ -0,0 +1,101 @@ +from pathlib import Path + + +def read_alias_file(alias_file): + with open(alias_file) as fp: + lines_alias = fp.readlines() + return parse_alias_lines(lines_alias) + + +def parse_alias_lines(lines_alias): + bit_names = [None] * 64 + bit_plots = [None] * 64 + bit_colors = [None] * 64 + adc_names = [None] * 32 + adc_plots = [None] * 32 + adc_colors = [None] * 32 + dac_names = [None] * 18 + sense_names = [None] * 8 + power_names = [None] * 5 + pat_file_name = None + + for line_nr, line in enumerate(lines_alias): + ignore_list = ['PATCOMPILER'] + + # skip empty lines + if line == '\n' or len(line) == 0: + continue + # skip comments + if line.startswith('#'): + continue + + cmd, *args = line.split() + + if not args: + raise Exception( + f"Alias file parsing failed: Require atleast one argument in addition to command. ({line_nr}:{line})") + + if cmd.startswith("BIT"): + process_alias_bit_or_adc(cmd, args, bit_names, bit_plots, bit_colors) + + elif cmd.startswith("ADC"): + process_alias_bit_or_adc(cmd, args, adc_names, adc_plots, adc_colors) + + elif cmd.startswith("DAC"): + if len(args) > 1: + raise Exception(f"Too many arguments {len(args)} (expected max: 1) for this type. ({line_nr}:{line})") + i = int(cmd[3:]) + dac_names[i] = args[0] + + elif cmd.startswith("SENSE"): + if len(args) > 1: + raise Exception(f"Too many arguments {len(args)} (expected max: 1) for this type. ({line_nr}:{line})") + i = int(cmd[5:]) + sense_names[i] = args[0] + + elif cmd in ["VA", "VB", "VC", "VD", "VIO"]: + if len(args) > 1: + raise Exception(f"Too many arguments {len(args)} (expected max: 1) for this type. ({line_nr}:{line})") + + match cmd: + case "VA": + i = 0 + case "VB": + i = 1 + case "VC": + i = 2 + case "VD": + i = 3 + case "VIO": + i = 4 + power_names[i] = args[0] + + elif cmd == "PATFILE": + if len(args) > 1: + raise Exception(f"Too many arguments {len(args)} (expected max: 1) for this type. ({line_nr}:{line})") + + pat_file_name = args[0] + path = Path(pat_file_name) + if not path.is_file(): + raise Exception("Pattern file provided in alias file does not exist.

Pattern file:" + + pat_file_name) + elif cmd in ignore_list: + pass + + else: + raise Exception(f"Command: {cmd} not supported. Line {line_nr}:{line}") + + return bit_names, bit_plots, bit_colors, adc_names, adc_plots, adc_colors, dac_names, sense_names, power_names,\ + pat_file_name + + +def process_alias_bit_or_adc(cmd, args, names, plots, colors): + n_args = len(args) + i = int(cmd[3:]) + names[i] = args[0] + if n_args > 1: + plots[i] = bool(int(args[1])) + if n_args > 2: + colors[i] = args[2] + if n_args > 3: + raise Exception(f"Too many arguments {args} (expected max: 3) for this type in line.") diff --git a/pyctbgui/pyctbgui/utils/bit_utils.py b/pyctbgui/pyctbgui/utils/bit_utils.py new file mode 100644 index 000000000..335c9d3e9 --- /dev/null +++ b/pyctbgui/pyctbgui/utils/bit_utils.py @@ -0,0 +1,16 @@ +def set_bit(value, bit_nr): + return value | 1 << bit_nr + + +def remove_bit(value, bit_nr): + return value & ~(1 << bit_nr) + + +def bit_is_set(value, bit_nr): + return (value >> bit_nr) & 1 == 1 + + +def manipulate_bit(is_set, value, bit_nr): + if is_set: + return set_bit(value, bit_nr) + return remove_bit(value, bit_nr) diff --git a/pyctbgui/pyctbgui/utils/decoder.py b/pyctbgui/pyctbgui/utils/decoder.py new file mode 100644 index 000000000..6197f811c --- /dev/null +++ b/pyctbgui/pyctbgui/utils/decoder.py @@ -0,0 +1,51 @@ +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 new file mode 100644 index 000000000..51c6ac1cb --- /dev/null +++ b/pyctbgui/pyctbgui/utils/defines.py @@ -0,0 +1,111 @@ +from enum import Enum + + +class Defines: + Time_Wait_For_Packets_ms = 0.5 + Time_Status_Refresh_ms = 100 + Time_Plot_Refresh_ms = 20 + + Zmq_hwm_high_speed = 2 + Zmq_hwm_low_speed = -1 + + Acquisition_Tab_Index = 7 + Max_Tabs = 9 + + class adc: + tabIndex = 5 + count = 32 + half = 16 + BIT0_15_MASK = 0x0000FFFF + BIT16_31_MASK = 0xFFFF0000 + + class dac: + tabIndex = 0 + count = 18 + + class signals: + tabIndex = 3 + count = 64 + half = 32 + BIT0_31_MASK = 0x00000000FFFFFFFF + BIT32_63_MASK = 0xFFFFFFFF00000000 + + class pattern: + tabIndex = 6 + loops_count = 6 + + class transceiver: + count = 4 + tabIndex = 4 + + class slowAdc: + tabIndex = 2 + count = 8 + + colCount = 4 + + powerSupplies = ('A', 'B', 'C', 'D', 'IO') + + class ImageIndex(Enum): + Matterhorn = 0 + Moench04 = 1 + + class Matterhorn: + nRows = 48 + nHalfCols = 24 + nCols = 48 + 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', + 'gist_rainbow', 'rainbow', 'jet', 'turbo' + ] + Default_Color_Map = 'viridis' + + # pattern viewer defines + + # pattern plot + Colors_plot = ['Blue', 'Orange'] + + # Wait colors and line styles (6 needed from 0 to 5) + # Colors_wait = ['b', 'g', 'r', 'c', 'm', 'y'] + Colors_wait = ['Blue', 'Green', 'Red', 'Cyan', 'Magenta', 'Yellow'] + Linestyles_wait = ['--', '--', '--', '--', '--', '--'] + Alpha_wait = [0.5, 0.5, 0.5, 0.5, 0.5, 0.5] + Alpha_wait_rect = [0.2, 0.2, 0.2, 0.2, 0.2, 0.2] + + # Loop colors and line styles (6 needed from 0 to 5) + Colors_loop = ['Green', 'Red', 'Purple', 'Brown', 'Pink', 'Grey'] + Linestyles_loop = ['-.', '-.', '-.', '-.', '-.', '-.'] + Alpha_loop = [0.5, 0.5, 0.5, 0.5, 0.5, 0.5] + Alpha_loop_rect = [0.2, 0.2, 0.2, 0.2, 0.2, 0.2] + + # Display the count of clocks + Clock_vertical_lines_spacing = 50 + Show_clocks_number = True + Line_width = 2.0 + + Colors = [ + 'Blue', 'Orange', 'Green', 'Red', 'Purple', 'Brown', 'Pink', 'Gray', 'Olive', 'Cyan', 'Magenta', 'Yellow', + 'Black', 'White' + ] + + LineStyles = ['-', '--', '-.', ':'] + + class colorRange(Enum): + all = 0 + center = 1 + fixed = 2 diff --git a/pyctbgui/pyctbgui/utils/numpyWriter/__init__.py b/pyctbgui/pyctbgui/utils/numpyWriter/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pyctbgui/pyctbgui/utils/numpyWriter/npy_writer.py b/pyctbgui/pyctbgui/utils/numpyWriter/npy_writer.py new file mode 100644 index 000000000..7eea8ec67 --- /dev/null +++ b/pyctbgui/pyctbgui/utils/numpyWriter/npy_writer.py @@ -0,0 +1,224 @@ +""" +Wrapper to be able to append frames to a numpy file + +numpy header v1 + +- 6bytes \x93NUMPY +- 1 byte major version number \x01 +- 1 byte minor version number \x00 +- 2 bytes (unsigned short) HEADER_LEN length of header to follow +- Header as an ASCII dict terminated by \n padded with space \x20 to make sure +we get len(magic string) + 2 + len(length) + HEADER_LEN divisible with 64 +Allocate enough space to allow for the data to grow +""" + +import ast +import os +import zipfile +from pathlib import Path + +import numpy as np + + +class NumpyFileManager: + """ + class used to read and write into .npy files that can't be loaded completely into memory + + for read mode implements numpy like interface and file-like object function + """ + magic_str = np.lib.format.magic(1, 0) + headerLength = np.uint16(128) + FSEEK_FILE_END = 2 + BUFFER_MAX = 500 + + def __init__( + self, + file: str | Path | zipfile.ZipExtFile, + mode: str = 'r', + frameShape: tuple = None, + dtype=None, + ): + """ + initiates a NumpyFileManager class for reading or writing bytes directly to/from a .npy file + @param file: path to the file to open or create + @param frameShape: shape of the frame ex: (5000,) for waveforms or (400,400) for image + @param dtype: type of the numpy array's header + @param mode: file open mode must be in 'rwx' + """ + if mode not in ['r', 'w', 'x', 'r+']: + raise ValueError('file mode should be either r,w,x,r+') + + if isinstance(file, zipfile.ZipExtFile): + if mode != 'r': + raise ValueError('NumpyFileManager only supports read mode for zipfiles') + else: + if mode == 'x' and Path.is_file(Path(file)): + raise FileExistsError(f'file {file} exists while given mode is x') + + self.dtype = np.dtype(dtype) # in case we pass a type like np.float32 + self.frameShape = frameShape + self.frameCount = 0 + self.cursorPosition = self.headerLength + self.mode = mode + + # if newFile frameShape and dtype should be present + if mode == 'w' or mode == 'x': + assert frameShape is not None + assert dtype is not None + # create/clear the file with mode wb+ + self.file = open(file, 'wb+') + self.updateHeader() + + else: + # opens file for read and check if the header of the file corresponds to the given function + # arguments + if isinstance(file, zipfile.ZipExtFile): + self.file = file + else: + mode = 'rb' if self.mode == 'r' else 'rb+' + self.file = open(file, mode) + self.file.seek(10) + headerStr = self.file.read(np.uint16(self.headerLength - 10)).decode("UTF-8") + header_dict = ast.literal_eval(headerStr) + self.frameShape = header_dict['shape'][1:] + if frameShape is not None: + assert frameShape == self.frameShape, \ + f"shape in arguments ({frameShape}) is not the same as the shape of the stored " \ + f"file ({self.frameShape})" + + self.dtype = np.lib.format.descr_to_dtype(header_dict['descr']) + if dtype is not None: + assert dtype == self.dtype, \ + f"dtype in argument ({dtype}) is not the same as the dtype of the stored file ({self.dtype})" + + self.frameCount = header_dict['shape'][0] + + assert not header_dict['fortran_order'], "fortran_order in the stored file is not False" + + self.__frameSize = np.dtype(self.dtype).itemsize * np.prod(self.frameShape) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + + def restoreCursorPosition(func): + """ + decorator function used to restore the file descriptors + cursor position after using read or write functions + """ + + def wrapper(self, *args, **kwargs): + tmp = self.cursorPosition + result = func(self, *args, **kwargs) + self.cursorPosition = tmp + self.file.seek(tmp) + return result + + return wrapper + + @restoreCursorPosition + def updateHeader(self): + """ + updates the header of the .npy file with the class attributes + @note: fortran_order is always set to False + """ + if self.mode == 'r': + return + self.file.seek(0) + header_dict = { + 'descr': np.lib.format.dtype_to_descr(self.dtype), + 'fortran_order': False, + 'shape': (self.frameCount, *self.frameShape) + } + np.lib.format.write_array_header_1_0(self.file, header_dict) + self.flush() + + @restoreCursorPosition + def writeOneFrame(self, frame: np.ndarray): + """ + write one frame without buffering + @param frame: numpy array for a frame + """ + if frame.shape != self.frameShape: + raise ValueError(f"frame shape given {frame.shape} is not the same as the file's shape {self.frameShape}") + if frame.dtype != self.dtype: + raise ValueError(f"frame dtype given {frame.dtype} is not the same as the file's dtype {self.dtype}") + + self.file.seek(0, self.FSEEK_FILE_END) + self.frameCount += 1 + self.file.write(frame.tobytes()) + + def flush(self): + """ + persist data into disk + """ + self.file.flush() + os.fsync(self.file) + + @restoreCursorPosition + def readFrames(self, frameStart: int, frameEnd: int) -> np.ndarray: + """ + read frames from .npy file without loading the whole file to memory with np.load + @param frameStart: number of the frame to start reading from + @param frameEnd: index of the last frame (not inclusive) + @return: np.ndarray of frames of the shape [frameEnd-frameStart,*self.frameShape] + """ + frameCount = frameEnd - frameStart + + if frameStart < 0: + raise NotImplementedError("frameStart must be bigger than 0") + if frameCount < 0: + if frameStart <= 0: + raise NotImplementedError("frameEnd must be bigger than frameStart") + frameCount = 0 + self.file.seek(self.headerLength + frameStart * self.__frameSize) + data = self.file.read(frameCount * self.__frameSize) + return np.frombuffer(data, self.dtype).reshape([-1, *self.frameShape]) + + def read(self, frameCount): + """ + file like interface to read frameCount frames from the already stored position + @param frameCount: number of frames to read + @return: numpy array containing frameCount frames + """ + assert frameCount > 0 + data = self.file.read(frameCount * self.__frameSize) + self.cursorPosition += frameCount * self.__frameSize + return np.frombuffer(data, self.dtype).reshape([-1, *self.frameShape]) + + def seek(self, frameNumber): + """ + file-like interface to move the file's cursor position to the frameNumber + """ + assert frameNumber >= 0 + self.cursorPosition = self.headerLength + frameNumber * self.__frameSize + self.file.seek(self.cursorPosition) + + def close(self): + self.updateHeader() + self.file.close() + + def __getitem__(self, item): + isSlice = False + if isinstance(item, slice): + isSlice = True + if item.step is not None: + raise NotImplementedError("step parameter is not implemented yet") + if isSlice: + return self.readFrames(item.start, item.stop) + frame = self.readFrames(item, item + 1) + if frame.size != 0: + frame = frame.squeeze(0) + return frame + + def __del__(self): + """ + in case the user forgot to close the file + """ + if hasattr(self, 'file') and not self.file.closed: + try: + self.close() + except ImportError: + self.file.close() diff --git a/pyctbgui/pyctbgui/utils/numpyWriter/npz_writer.py b/pyctbgui/pyctbgui/utils/numpyWriter/npz_writer.py new file mode 100644 index 000000000..4f0cf881d --- /dev/null +++ b/pyctbgui/pyctbgui/utils/numpyWriter/npz_writer.py @@ -0,0 +1,91 @@ +from pathlib import Path +import shutil +import zipfile +import io + +import numpy as np +from pyctbgui.utils.numpyWriter.npy_writer import NumpyFileManager + + +class NpzFileWriter: + """ + Write data to npz file incrementally rather than compute all and write + once, as in ``np.save``. This class can be used with ``contextlib.closing`` + to ensure closed after usage. + """ + + def __init__(self, tofile: str, mode='w', compress_file=False): + """ + :param tofile: the ``npz`` file to write + :param mode: must be one of {'x', 'w', 'a'}. See + https://docs.python.org/3/library/zipfile.html for detail + """ + self.__openedFiles = {} + self.compression = zipfile.ZIP_DEFLATED if compress_file else zipfile.ZIP_STORED + self.tofile = tofile + self.mode = mode + self.file = zipfile.ZipFile(self.tofile, mode=self.mode, compression=self.compression) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + + def writeArray(self, key: str, data: np.ndarray | bytes) -> None: + """ + overwrite existing data of name ``key``. + + :param key: the name of data to write + :param data: the data + """ + key += '.npy' + with io.BytesIO() as cbuf: + np.save(cbuf, data) + cbuf.seek(0) + with self.file.open(key, mode='w', force_zip64=True) as outfile: + shutil.copyfileobj(cbuf, outfile) + + def readFrames(self, file: str, frameStart: int, frameEnd: int): + file += '.npy' + with self.file.open(file, mode='r') as outfile: + npw = NumpyFileManager(outfile) + return npw.readFrames(frameStart, frameEnd) + + @staticmethod + def zipNpyFiles(filename: str, + files: list[str | Path], + fileKeys: list[str], + deleteOriginals=False, + compressed=False): + compression = zipfile.ZIP_DEFLATED if compressed else zipfile.ZIP_STORED + + with zipfile.ZipFile(filename, mode='w', compression=compression, allowZip64=True) as zipf: + for idx, file in enumerate(files): + zipf.write(file, arcname=fileKeys[idx] + '.npy') + if deleteOriginals: + for file in files: + Path.unlink(file) + + def __getitem__(self, item: str) -> NumpyFileManager: + """ + returns NumpyFileManager file handling the .npy file under the key item inside of the .npz file + @param item: + @return: + """ + if not isinstance(item, str): + raise TypeError('given item is not of type str') + if item not in self.__openedFiles: + outfile = self.file.open(item + '.npy', mode='r') + self.__openedFiles[item] = NumpyFileManager(outfile) + return self.__openedFiles[item] + + def namelist(self): + return sorted([key[:-4] for key in self.file.namelist()]) + + def close(self): + if hasattr(self, 'file') and self.file is not None: + self.file.close() + + def __del__(self): + self.close() diff --git a/pyctbgui/pyctbgui/utils/numpyWriter/usage.md b/pyctbgui/pyctbgui/utils/numpyWriter/usage.md new file mode 100644 index 000000000..b47bb742c --- /dev/null +++ b/pyctbgui/pyctbgui/utils/numpyWriter/usage.md @@ -0,0 +1,71 @@ +# Using numpyWriter module +## concept +numpyWriter is used to write and load huge numpy arrays that can't be fully loaded in RAM. +It is designed to write frames of a constant shape (defined by user) and incrementally add to .npy and .npz files without accessing all of its contents + +### NumpyFileManager +class to handle writing in .npy files frame by frame. +its positional parameter `file` can be of type: str,pathlib.Path, zipfile.ZipExtFile. This way we can use NumpyFileManager to open files by getting their path or +**in read mode** it can receiver file-like objects to read their data. + +the complexity of initializing from file-like objects is added to be able to read from .npz files which are simply a zip of .npy files. Furthermore now we can save our files .npz files and read from them (even when compressed (⊙_⊙) ) without loading the whole .npy or .npz in memory. + +### NpzFileWriter +class used to handle .npz file functionalities. it can zip existing .npy files, write a whole array in an .npz file without loading the whole .npz in memory, +and read frames from .npy files inside the .npz file + +## Usage + +```python +# create .npy file +npw = NumpyFileManager('file.npy', 'w', (400, 400), np.int32) +npw.addFrame(np.ones([400, 400], dtype=np.int32)) +npw.close() + +# read frames from existing .npy file +npw = NumpyFileManager('file.npy') +# if arr is stored in the .npy file this statement will return arr[50:100] +npw.readFrames(50, 100) + +# Numpy like interface +# NumpyFileManager is also subscriptable +npw[50:100] # returns the array arr[50:100] +npw[0] # returns the array arr[0] + +# File like interface +# the npw class's cursors is initialized on the first frame +npw.read(5) # reads five frames and updates the cursor +npw.seek(99) # updates the cursor to point it to the 99-th frame + +# to ensure that files are written to disk +npw.flush() + +# zip existing .npy files (stored on disk) +# filePaths: the paths to .npy files +# keys: name of the arrays inside of the .npz file +NpzFileWriter.zipNpyFiles('file.npz', filePaths, keys, compressed=True) + +# add numpy arrays incrementally to a .npz file +with NpzFileWriter('tmp.npz', 'w', compress_file=True) as npz: + npz.writeArray('adc', arr1) + npz.writeArray('tx', arr2) + +# read frames from adc.npy inside of tmp.npz +with NpzFileWriter('tmp.npz', 'r') as npz: + frames = npz.readFrames('adc', 5, 8) + +# NpzFileWriter is also subscriptable and returns a NumpyFileManager initialized +# to open the the file with the given key inside the .npz file +npz = NpzFileWriter('tmp.npz', 'r') +npz.writeArray('adc', arr1) + + +npz['adc'] # returns a NumpyFileManager +npz['adc'][50:100] # returns the array from 50 to 100 +# note once a NumpyFileManager instance is created internally NpzFileWriter stores it +# this is done to avoid opening and closing the same file +# also file-like interface can be used +npz['adc'].read(5) # returns arr[:5] +npz['adc'].seek(100) # updates the cursor +npz['adc'].read(2) # returns arr[100:2] +``` \ No newline at end of file diff --git a/pyctbgui/pyctbgui/utils/pixelmap.py b/pyctbgui/pyctbgui/utils/pixelmap.py new file mode 100644 index 000000000..6cfa28a14 --- /dev/null +++ b/pyctbgui/pyctbgui/utils/pixelmap.py @@ -0,0 +1,61 @@ +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 diff --git a/pyctbgui/pyctbgui/utils/plotPattern.py b/pyctbgui/pyctbgui/utils/plotPattern.py new file mode 100755 index 000000000..443b766e9 --- /dev/null +++ b/pyctbgui/pyctbgui/utils/plotPattern.py @@ -0,0 +1,726 @@ +#!/usr/bin/env python3 +""" +Created on Wed May 24 09:44:53 2017 + +Plot the pattern for New Chip Test Box (.pat) + +Changes: + - 2017-11-21 Adapt it to python-3 + - 2017-09-25 All can be plotted + - 2017-09-22 Can be plotted but the loop and wait not work yet + +@author: Jiaguo Zhang and Julian Heymes +""" + +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.patches import Rectangle + + +class PlotPattern: + + def __init__(self, pattern, signalNames, colors_plot, colors_wait, linestyles_wait, alpha_wait, alpha_wait_rect, + colors_loop, linestyles_loop, alpha_loop, alpha_loop_rect, clock_vertical_lines_spacing, + show_clocks_number, line_width): + self.pattern = pattern + self.signalNames = signalNames + self.verbose = False + # TODO: send alias + + self.colors_plot = colors_plot.copy() + self.colors_wait = colors_wait.copy() + self.linestyles_wait = linestyles_wait.copy() + self.alpha_wait = alpha_wait.copy() + self.alpha_wait_rect = alpha_wait_rect.copy() + self.colors_loop = colors_loop.copy() + self.linestyles_loop = linestyles_loop.copy() + self.alpha_loop = alpha_loop.copy() + self.alpha_loop_rect = alpha_loop_rect.copy() + self.clock_vertical_lines_spacing = clock_vertical_lines_spacing + self.show_clocks_number = show_clocks_number + self.line_width = line_width + + self.colors_plot[0] = f'xkcd:{colors_plot[0].lower()}' + self.colors_plot[1] = f'xkcd:{colors_plot[1].lower()}' + + for i in range(6): + self.colors_wait[i] = f'xkcd:{colors_wait[i].lower()}' + self.colors_loop[i] = f'xkcd:{colors_loop[i].lower()}' + + if self.verbose: + self.printPatViewerParameters() + + def printPatViewerParameters(self): + print('Pattern Viewer Parameters:') + print(f'\tcolor1: {self.colors_plot[0]}, color2: {self.colors_plot[1]}') + print(f"\twait color: {self.colors_wait}") + print(f"\twait linestyles: {self.linestyles_wait}") + print(f"\twait alpha: {self.alpha_wait}") + print(f"\twait alpha rect: {self.alpha_wait_rect}") + print(f"\tloop color: {self.colors_loop}") + print(f"\tloop linestyles: {self.linestyles_loop}") + print(f"\tloop alpha: {self.alpha_loop}") + print(f"\tloop alpha rect: {self.alpha_loop_rect}") + print(f'\tclock vertical lines spacing: {self.clock_vertical_lines_spacing}') + print(f'\tshow clocks number: {self.show_clocks_number}') + print(f'\tline width: {self.line_width}') + print('\n') + + def dec2binary(self, dec_num, width=None): + return np.binary_repr(int(dec_num), width=width) + + def hex2dec(self, string_num): + return str(int(string_num.upper(), 16)) + + def hex2binary(self, string_num, width=None): + return self.dec2binary(self.hex2dec(string_num.upper()), width=width) + + def patternPlot(self): + # Define a hex to binary function + # global definition + # base = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F] + self.base = [str(x) for x in range(10)] + [chr(x) for x in range(ord('A'), ord('A') + 6)] + + # Load the pattern and get all lines + # Loop all lines + # with open(Folder + "/" + File_pat + ".pat") as f_pat: + with open(self.pattern) as f_pat: + lines_pat = f_pat.readlines() + + # number of lines for pattern file + nlines_pat = len(lines_pat) + # a counter + cnt = 0 + if self.verbose: + print("The total number of lines of pattern:", nlines_pat) + + # Loop all lines of pattern + waittime0 = None + waittime1 = None + waittime2 = None + waittime3 = None + waittime4 = None + waittime5 = None + + nloop0 = None + nloop1 = None + nloop2 = None + nloop3 = None + nloop4 = None + nloop5 = None + + for k in range(nlines_pat): + # content of line + words_line = lines_pat[k].split() + if len(words_line) < 2: + continue + if words_line[0] == "patword": + # print words_line from b0 to b63 + bits = self.hex2binary(words_line[-1], 64)[::-1] + if self.verbose: + print("The bits for line-", k + 1, "is:", bits) + # convert string bits to decimal array + num_bits = np.array(list(map(str, bits)), dtype="uint16") + if cnt == 0: + mat_pat = num_bits + else: + # add bits to matrix + mat_pat = np.concatenate((mat_pat, num_bits), axis=0) + cnt = cnt + 1 + # print("The matrix of pattern:", mat_pat.reshape(int(cnt), int(len(num_bits)))) + + # Look at the io: 0 for sending to ASIC, 1 for reading from ASIC + if words_line[0] == "patioctrl": + # print words_line + if self.verbose: + print(words_line[-1]) + bits = self.hex2binary(words_line[-1], 64)[::-1] + if self.verbose: + print(bits) + # convert string bits to decimal array + self.out_bits = np.array(list(map(str, bits)), dtype="uint16") + + if self.verbose: + print(words_line) + # Deal with waiting point + + # ====== WAIT ====== + if words_line[0] == "patwait" and words_line[1] == "0": + wait0 = int(self.hex2dec(words_line[2])) + if self.verbose: + print("wait 0 at:", wait0) + if words_line[0] == "patwaittime" and words_line[1] == "0": + waittime0 = int(words_line[2]) + if self.verbose: + print("wait 0 for:", waittime0) + + if words_line[0] == "patwait" and words_line[1] == "1": + wait1 = int(self.hex2dec(words_line[2])) + if self.verbose: + print("wait 1 at:", wait1) + if words_line[0] == "patwaittime" and words_line[1] == "1": + waittime1 = int(words_line[2]) + if self.verbose: + print("wait 1 for:", waittime1) + + if words_line[0] == "patwait" and words_line[1] == "2": + wait2 = int(self.hex2dec(words_line[2])) + if self.verbose: + print("wait 2 at:", wait2) + if words_line[0] == "patwaittime" and words_line[1] == "2": + waittime2 = int(words_line[2]) + if self.verbose: + print("wait 2 for:", waittime2) + + if words_line[0] == "patwait" and words_line[1] == "3": + wait3 = int(self.hex2dec(words_line[2])) + if self.verbose: + print("wait 0 at:", wait3) + if words_line[0] == "patwaittime" and words_line[1] == "3": + waittime3 = int(words_line[2]) + if self.verbose: + print("wait 0 for:", waittime3) + + if words_line[0] == "patwait" and words_line[1] == "4": + wait4 = int(self.hex2dec(words_line[2])) + if self.verbose: + print("wait 1 at:", wait4) + if words_line[0] == "patwaittime" and words_line[1] == "4": + waittime4 = int(words_line[2]) + if self.verbose: + print("wait 1 for:", waittime4) + + if words_line[0] == "patwait" and words_line[1] == "5": + wait5 = int(self.hex2dec(words_line[2])) + if self.verbose: + print("wait 2 at:", wait5) + if words_line[0] == "patwaittime" and words_line[1] == "5": + waittime5 = int(words_line[2]) + if self.verbose: + print("wait 2 for:", waittime5) + + # ====== LOOPS ====== + if words_line[0] == "patloop" and words_line[1] == "0": + loop0_start = int(self.hex2dec(words_line[2])) + loop0_end = int(self.hex2dec(words_line[3])) + if self.verbose: + print("loop 0 start:", loop0_start, ", end:", loop0_end) + if words_line[0] == "patnloop" and words_line[1] == "0": + nloop0 = int(words_line[2]) + if self.verbose: + print("loop 0 times:", nloop0) + + if words_line[0] == "patloop" and words_line[1] == "1": + loop1_start = int(self.hex2dec(words_line[2])) + loop1_end = int(self.hex2dec(words_line[3])) + if self.verbose: + print("loop 1 start:", loop1_start, ", end:", loop1_end) + if words_line[0] == "patnloop" and words_line[1] == "1": + nloop1 = int(words_line[2]) + if self.verbose: + print("loop 1 times:", nloop1) + + if words_line[0] == "patloop" and words_line[1] == "2": + loop2_start = int(self.hex2dec(words_line[2])) + loop2_end = int(self.hex2dec(words_line[3])) + if self.verbose: + print("loop 2 start:", loop2_start, ", end:", loop2_end) + if words_line[0] == "patnloop" and words_line[1] == "2": + nloop2 = int(words_line[2]) + if self.verbose: + print("loop 2 times:", nloop2) + + if words_line[0] == "patloop" and words_line[1] == "3": + loop3_start = int(self.hex2dec(words_line[2])) + loop3_end = int(self.hex2dec(words_line[3])) + if self.verbose: + print("loop 3 start:", loop3_start, ", end:", loop3_end) + if words_line[0] == "patnloop" and words_line[1] == "3": + nloop3 = int(words_line[2]) + if self.verbose: + print("loop 3 times:", nloop3) + + if words_line[0] == "patloop" and words_line[1] == "4": + loop4_start = int(self.hex2dec(words_line[2])) + loop4_end = int(self.hex2dec(words_line[3])) + if self.verbose: + print("loop 4 start:", loop4_start, ", end:", loop4_end) + if words_line[0] == "patnloop" and words_line[1] == "4": + nloop4 = int(words_line[2]) + if self.verbose: + print("loop 4 times:", nloop4) + + if words_line[0] == "patloop" and words_line[1] == "5": + loop5_start = int(self.hex2dec(words_line[2])) + loop5_end = int(self.hex2dec(words_line[3])) + if self.verbose: + print("loop 5 start:", loop5_start, ", end:", loop5_end) + if words_line[0] == "patnloop" and words_line[1] == "5": + nloop5 = int(words_line[2]) + if self.verbose: + print("loop 5 times:", nloop5) + + # no patioctrl commands read + if not hasattr(self, 'out_bits'): + raise Exception("No patioctrl command found in pattern file") + # print(self.out_bits) + + # internal counter + avail_index = [] + avail_name = [] + # Remove non-used bits + for i in range(64): + # if self.out_bits[0][i] == 1: + if self.out_bits[i] == 1: + avail_index.append(i) + avail_name.append(self.signalNames[i]) + if self.verbose: + print(avail_index) + print(avail_name) + + # number of effective used bits + nbiteff = len(avail_name) + + # subMat = mat_ext[:,index] + # print(mat_pat.shape) + subMat = mat_pat.reshape(int(cnt), int(len(num_bits)))[0:, avail_index] + # subMat = mat_pat[avail_index] + # timing = np.linspace(0, subMat.shape[0] - 1, subMat.shape[0]) + plt.rcParams['figure.figsize'] = 15, 5 + + # ============= PLOTTING ============= + + plt.rcParams["font.weight"] = "bold" + plt.rcParams["axes.labelweight"] = "bold" + fig, axs = plt.subplots(nbiteff, sharex='all') + plt.subplots_adjust(wspace=0, hspace=0) + # axs[nbiteff - 1].set(xlabel='Timing [clk]') + for idx, i in enumerate(range(nbiteff)): + axs[idx].tick_params(axis='x', labelsize=6) + + axs[idx].plot(subMat.T[i], + "-", + drawstyle="steps-post", + linewidth=self.line_width, + color=self.colors_plot[idx % 2]) + x_additional = range(len(subMat.T[i]) - 1, len(subMat.T[i]) + 2) + additional_stuff = [subMat.T[i][-1]] * 3 + + axs[idx].plot(x_additional, + additional_stuff, + "--", + drawstyle="steps-post", + linewidth=self.line_width, + color=self.colors_plot[idx % 2], + alpha=0.5) + axs[idx].yaxis.set_ticks([0.5], minor=False) + axs[idx].xaxis.set_ticks(np.arange(0, len(subMat.T[i]) + 10, self.clock_vertical_lines_spacing)) + + axs[idx].yaxis.set_ticklabels([avail_name[i]]) + axs[idx].get_yticklabels()[0].set_color(self.colors_plot[idx % 2]) + + axs[idx].grid(1, 'both', 'both', alpha=0.5) + axs[idx].yaxis.grid(which="both", color=self.colors_plot[idx % 2], alpha=0.2) + if idx != nbiteff - 1: + if not self.show_clocks_number: + axs[idx].xaxis.set_ticklabels([]) + axs[idx].set(xlabel=' ', ylim=(-0.2, 1.2)) + else: + axs[idx].set(xlabel='Timing [clk]', ylim=(-0.2, 1.2)) + # axs[idx].set_xlim(left=0) + axs[idx].set_xlim(left=0, right=len(subMat.T[i]) + 1) + axs[idx].spines['top'].set_visible(False) + axs[idx].spines['right'].set_alpha(0.2) + axs[idx].spines['right'].set_visible(True) + axs[idx].spines['bottom'].set_visible(False) + axs[idx].spines['left'].set_visible(False) + + # ===================================================================================================== + # Plot the wait lines + # Wait 0 + if waittime0 is not None: + if waittime0 == 0: + axs[idx].plot([wait0, wait0], [-10, 10], + linestyle=self.linestyles_wait[0], + color=self.colors_wait[0], + alpha=self.alpha_wait[0], + linewidth=self.line_width) + axs[idx].plot([wait0 + 1, wait0 + 1], [-10, 10], + linestyle=self.linestyles_wait[0], + color=self.colors_wait[0], + linewidth=self.line_width, + alpha=self.alpha_wait[0]) + axs[idx].add_patch( + Rectangle((wait0, -10), + 1, + 20, + label="wait 0: skipped" if idx == 0 else "", + facecolor=self.colors_wait[0], + alpha=self.alpha_wait_rect[0], + hatch='\\\\')) + else: + axs[idx].plot([wait0, wait0], [-10, 10], + linestyle=self.linestyles_wait[0], + color=self.colors_wait[0], + label="wait 0: " + str(waittime0) + " clk" if idx == 0 else "", + linewidth=self.line_width, + alpha=self.alpha_wait[0]) + + # Wait 1 + if waittime1 is not None: + if waittime1 == 0: + axs[idx].plot([wait1, wait1], [-10, 10], + linestyle=self.linestyles_wait[1], + color=self.colors_wait[1], + alpha=self.alpha_wait[1], + linewidth=self.line_width) + axs[idx].plot([wait1 + 1, wait1 + 1], [-10, 10], + linestyle=self.linestyles_wait[1], + color=self.colors_wait[1], + linewidth=self.line_width, + alpha=self.alpha_wait[1]) + axs[idx].add_patch( + Rectangle((wait1, -10), + 1, + 20, + label="wait 1: skipped" if idx == 0 else "", + facecolor=self.colors_wait[1], + alpha=self.alpha_wait_rect[1], + hatch='\\\\')) + else: + axs[idx].plot([wait1, wait1], [-10, 10], + linestyle=self.linestyles_wait[1], + color=self.colors_wait[1], + label="wait 1: " + str(waittime1) + " clk" if idx == 0 else "", + linewidth=self.line_width, + alpha=self.alpha_wait[1]) + + # Wait 2 + if waittime2 is not None: + if waittime2 == 0: + axs[idx].plot([wait2, wait2], [-10, 10], + linestyle=self.linestyles_wait[2], + color=self.colors_wait[2], + alpha=self.alpha_wait[2], + linewidth=self.line_width) + axs[idx].plot([wait2 + 1, wait2 + 1], [-10, 10], + linestyle=self.linestyles_wait[2], + color=self.colors_wait[2], + linewidth=self.line_width, + alpha=self.alpha_wait[2]) + axs[idx].add_patch( + Rectangle((wait2, -10), + 1, + 20, + label="wait 2: skipped" if idx == 0 else "", + facecolor=self.colors_wait[2], + alpha=self.alpha_wait_rect[2], + hatch='\\\\')) + else: + axs[idx].plot([wait2, wait2], [-10, 10], + linestyle=self.linestyles_wait[2], + color=self.colors_wait[2], + label="wait 2: " + str(waittime2) + " clk" if idx == 0 else "", + linewidth=self.line_width, + alpha=self.alpha_wait[2]) + + # Wait 3 + if waittime3 is not None: + if waittime3 == 0: + axs[idx].plot([wait3, wait3], [-10, 10], + linestyle=self.linestyles_wait[3], + color=self.colors_wait[3], + alpha=self.alpha_wait[3], + linewidth=self.line_width) + axs[idx].plot([wait3 + 1, wait3 + 1], [-10, 10], + linestyle=self.linestyles_wait[3], + color=self.colors_wait[3], + linewidth=self.line_width, + alpha=self.alpha_wait[3]) + axs[idx].add_patch( + Rectangle((wait3, -10), + 1, + 20, + label="wait 3: skipped" if idx == 0 else "", + facecolor=self.colors_wait[3], + alpha=self.alpha_wait_rect[3], + hatch='\\\\')) + else: + axs[idx].plot([wait3, wait3], [-10, 10], + linestyle=self.linestyles_wait[3], + color=self.colors_wait[3], + label="wait 3: " + str(waittime3) + " clk" if idx == 0 else "", + linewidth=self.line_width, + alpha=self.alpha_wait[3]) + + # Wait 4 + if waittime4 is not None: + if waittime4 == 0: + axs[idx].plot([wait4, wait4], [-10, 10], + linestyle=self.linestyles_wait[4], + color=self.colors_wait[4], + alpha=self.alpha_wait[4], + linewidth=self.line_width) + axs[idx].plot([wait4 + 1, wait4 + 1], [-10, 10], + linestyle=self.linestyles_wait[4], + color=self.colors_wait[4], + linewidth=self.line_width, + alpha=self.alpha_wait[4]) + axs[idx].add_patch( + Rectangle((wait4, -10), + 1, + 20, + label="wait 4: skipped" if idx == 0 else "", + facecolor=self.colors_wait[4], + alpha=self.alpha_wait_rect[4], + hatch='\\\\')) + else: + axs[idx].plot([wait4, wait4], [-10, 10], + linestyle=self.linestyles_wait[4], + color=self.colors_wait[4], + label="wait 4: " + str(waittime4) + " clk" if idx == 0 else "", + linewidth=self.line_width, + alpha=self.alpha_wait[4]) + + # Wait 5 + if waittime5 is not None: + if waittime5 == 0: + axs[idx].plot([wait5, wait5], [-10, 10], + linestyle=self.linestyles_wait[5], + color=self.colors_wait[5], + alpha=self.alpha_wait[5], + linewidth=self.line_width) + axs[idx].plot([wait5 + 1, wait5 + 1], [-10, 10], + linestyle=self.linestyles_wait[5], + color=self.colors_wait[5], + linewidth=self.line_width, + alpha=self.alpha_wait[5]) + axs[idx].add_patch( + Rectangle((wait5, -10), + 1, + 20, + label="wait 5: skipped" if idx == 0 else "", + facecolor=self.colors_wait[5], + alpha=self.alpha_wait_rect[5], + hatch='\\\\')) + else: + axs[idx].plot([wait5, wait5], [-10, 10], + linestyle=self.linestyles_wait[5], + color=self.colors_wait[5], + label="wait 5: " + str(waittime5) + " clk" if idx == 0 else "", + linewidth=self.line_width, + alpha=self.alpha_wait[5]) + + # ===================================================================================================== + # Plot the loop lines + # Loop 0 + if nloop0 is not None: + if nloop0 == 0: + axs[idx].plot([loop0_start, loop0_start], [-10, 10], + linestyle=self.linestyles_loop[0], + color=self.colors_loop[0], + alpha=self.alpha_loop[0], + linewidth=self.line_width) + axs[idx].plot([loop0_end + 1, loop0_end + 1], [-10, 10], + linestyle=self.linestyles_loop[0], + color=self.colors_loop[0], + alpha=self.alpha_loop[0], + linewidth=self.line_width) + axs[idx].add_patch( + Rectangle((loop0_start, -10), + loop0_end + 1 - loop0_start, + 20, + label="loop 0: skipped" if idx == 0 else "", + facecolor=self.colors_loop[0], + alpha=self.alpha_loop_rect[0], + hatch='//')) + else: + axs[idx].plot([loop0_start, loop0_start], [-10, 10], + linestyle=self.linestyles_loop[0], + color=self.colors_loop[0], + alpha=self.alpha_loop[0], + label="loop 0: " + str(nloop0) + " times" if idx == 0 else "", + linewidth=self.line_width) + axs[idx].plot([loop0_end, loop0_end], [-10, 10], + linestyle=self.linestyles_loop[0], + color=self.colors_loop[0], + alpha=self.alpha_loop[0], + linewidth=self.line_width) + + # Loop 1 + if nloop1 is not None: + if nloop1 == 0: + axs[idx].plot([loop1_start, loop1_start], [-10, 10], + linestyle=self.linestyles_loop[1], + color=self.colors_loop[1], + alpha=self.alpha_loop[1], + linewidth=self.line_width) + axs[idx].plot([loop1_end + 1, loop1_end + 1], [-10, 10], + linestyle=self.linestyles_loop[1], + color=self.colors_loop[1], + alpha=self.alpha_loop[1], + linewidth=self.line_width) + axs[idx].add_patch( + Rectangle((loop1_start, -10), + loop1_end + 1 - loop1_start, + 20, + label="loop 1: skipped" if idx == 0 else "", + facecolor=self.colors_loop[1], + alpha=self.alpha_loop_rect[1], + hatch='//')) + else: + axs[idx].plot([loop1_start, loop1_start], [-10, 10], + linestyle=self.linestyles_loop[1], + color=self.colors_loop[1], + alpha=self.alpha_loop[1], + label="loop 1: " + str(nloop1) + " times" if idx == 0 else "", + linewidth=self.line_width) + axs[idx].plot([loop1_end, loop1_end], [-10, 10], + linestyle=self.linestyles_loop[1], + color=self.colors_loop[1], + alpha=self.alpha_loop[1], + linewidth=self.line_width) + + # Loop 2 + if nloop2 is not None: + if nloop2 == 0: + axs[idx].plot([loop2_start, loop2_start], [-10, 10], + linestyle=self.linestyles_loop[2], + color=self.colors_loop[2], + alpha=self.alpha_loop[2], + linewidth=self.line_width) + axs[idx].plot([loop2_end + 1, loop2_end + 1], [-10, 10], + linestyle=self.linestyles_loop[2], + color=self.colors_loop[2], + alpha=self.alpha_loop[2], + linewidth=self.line_width) + axs[idx].add_patch( + Rectangle((loop2_start, -10), + loop2_end + 1 - loop2_start, + 20, + label="loop 2: skipped" if idx == 0 else "", + facecolor=self.colors_loop[2], + alpha=self.alpha_loop_rect[2], + hatch='//')) + else: + axs[idx].plot([loop2_start, loop2_start], [-10, 10], + linestyle=self.linestyles_loop[2], + color=self.colors_loop[2], + alpha=self.alpha_loop[2], + label="loop 2: " + str(nloop2) + " times" if idx == 0 else "", + linewidth=self.line_width) + axs[idx].plot([loop2_end, loop2_end], [-10, 10], + linestyle=self.linestyles_loop[2], + color=self.colors_loop[2], + alpha=self.alpha_loop[2], + linewidth=self.line_width) + + # Loop 3 + if nloop3 is not None: + if nloop3 == 0: + axs[idx].plot([loop3_start, loop3_start], [-10, 10], + linestyle=self.linestyles_loop[3], + color=self.colors_loop[3], + alpha=self.alpha_loop[3], + linewidth=self.line_width) + axs[idx].plot([loop3_end + 1, loop3_end + 1], [-10, 10], + linestyle=self.linestyles_loop[3], + color=self.colors_loop[3], + alpha=self.alpha_loop[3], + linewidth=self.line_width) + axs[idx].add_patch( + Rectangle((loop3_start, -10), + loop3_end + 1 - loop3_start, + 20, + label="loop 3: skipped" if idx == 0 else "", + facecolor=self.colors_loop[3], + alpha=self.alpha_loop_rect[3], + hatch='//')) + else: + axs[idx].plot([loop3_start, loop3_start], [-10, 10], + linestyle=self.linestyles_loop[3], + color=self.colors_loop[3], + alpha=self.alpha_loop[3], + label="loop 3: " + str(nloop3) + " times" if idx == 0 else "", + linewidth=self.line_width) + axs[idx].plot([loop3_end, loop3_end], [-10, 10], + linestyle=self.linestyles_loop[3], + color=self.colors_loop[3], + alpha=self.alpha_loop[3], + linewidth=self.line_width) + + # Loop 4 + if nloop4 is not None: + if nloop4 == 0: + axs[idx].plot([loop4_start, loop4_start], [-10, 10], + linestyle=self.linestyles_loop[4], + color=self.colors_loop[4], + alpha=self.alpha_loop[4], + linewidth=self.line_width) + axs[idx].plot([loop4_end + 1, loop4_end + 1], [-10, 10], + linestyle=self.linestyles_loop[4], + color=self.colors_loop[4], + alpha=self.alpha_loop[4], + linewidth=self.line_width) + axs[idx].add_patch( + Rectangle((loop4_start, -10), + loop4_end + 1 - loop4_start, + 20, + label="loop 4: skipped" if idx == 0 else "", + facecolor=self.colors_loop[4], + alpha=self.alpha_loop_rect[4], + hatch='//')) + else: + axs[idx].plot([loop4_start, loop4_start], [-10, 10], + linestyle=self.linestyles_loop[4], + color=self.colors_loop[4], + alpha=self.alpha_loop[4], + label="loop 4: " + str(nloop4) + " times" if idx == 0 else "", + linewidth=self.line_width) + axs[idx].plot([loop4_end, loop4_end], [-10, 10], + linestyle=self.linestyles_loop[4], + color=self.colors_loop[4], + alpha=self.alpha_loop[4], + linewidth=self.line_width) + + # Loop 5 + if nloop5 is not None: + if nloop5 == 0: + axs[idx].plot([loop5_start, loop5_start], [-10, 10], + linestyle=self.linestyles_loop[5], + color=self.colors_loop[5], + alpha=self.alpha_loop[5], + linewidth=self.line_width) + axs[idx].plot([loop5_end + 1, loop5_end + 1], [-10, 10], + linestyle=self.linestyles_loop[5], + color=self.colors_loop[5], + alpha=self.alpha_loop[5], + linewidth=self.line_width) + axs[idx].add_patch( + Rectangle((loop5_start, -10), + loop5_end + 1 - loop5_start, + 20, + label="loop 5: skipped" if idx == 0 else "", + facecolor=self.colors_loop[5], + alpha=self.alpha_loop_rect[5], + hatch='//')) + else: + axs[idx].plot([loop5_start, loop5_start], [-10, 10], + linestyle=self.linestyles_loop[5], + color=self.colors_loop[5], + alpha=self.alpha_loop[5], + label="loop 5: " + str(nloop5) + " times" if idx == 0 else "", + linewidth=self.line_width) + axs[idx].plot([loop5_end, loop5_end], [-10, 10], + linestyle=self.linestyles_loop[5], + color=self.colors_loop[5], + alpha=self.alpha_loop[5], + linewidth=self.line_width) + + n_cols = np.count_nonzero([ + waittime0 != 0, waittime1 != 0, waittime2 != 0, waittime3 != 0, waittime4 != 0, waittime5 != 0, nloop0 + != 0, nloop1 != 0, nloop2 != 0, nloop3 != 0, nloop4 != 0, nloop5 != 0 + ]) + if n_cols > 0: + fig.legend(loc="upper center", ncol=n_cols) + return fig diff --git a/pyctbgui/pyctbgui/utils/recordOrApplyPedestal.py b/pyctbgui/pyctbgui/utils/recordOrApplyPedestal.py new file mode 100644 index 000000000..235e76d22 --- /dev/null +++ b/pyctbgui/pyctbgui/utils/recordOrApplyPedestal.py @@ -0,0 +1,100 @@ +import logging +from pathlib import Path + +import numpy as np + +__frameCount = 0 +__pedestalSum = np.array(0, np.float64) +__pedestal = np.array(0, np.float32) +__loadedPedestal = False + + +def reset(plotTab): + global __frameCount, __pedestalSum, __pedestal, __loadedPedestal + __frameCount = 0 + __pedestalSum = np.array(0, np.float64) + __pedestal = np.array(0, np.float64) + __loadedPedestal = False + + plotTab.updateLabelPedestalFrames() + + +def getFramesCount(): + return __frameCount + + +def getPedestal(): + return __pedestal + + +def calculatePedestal(): + global __pedestalSum, __pedestal + if __loadedPedestal: + return __pedestal + if __frameCount == 0: + __pedestal = np.array(0, np.float64) + else: + __pedestal = __pedestalSum / __frameCount + return __pedestal + + +def savePedestal(path=Path('/tmp/pedestal')): + pedestal = calculatePedestal() + np.save(path, pedestal) + + +def loadPedestal(path: Path): + global __pedestal, __loadedPedestal + __loadedPedestal = True + __pedestal = np.load(path) + + +__logger = logging.getLogger('recordOrApplyPedestal') + + +def recordOrApplyPedestal(func): + """ + decorator function used to apply pedestal functionalities + @param func: processing function that needs to be wrapped + @return: wrapper function to be called + """ + + def wrapper(obj, *args, **kwargs): + """ + wrapeer that calls func (a raw data _processing function) and calculates or applies a pedestal to it + @param obj: reference to func's class instance (self of its class) + @return: if record mode: return frame untouched, if apply mode: return frame - pedestal + """ + global __frameCount, __pedestal, __pedestalSum + + frame = func(obj, *args, **kwargs) + if not np.array_equal(0, __pedestalSum) and __pedestalSum.shape != frame.shape: + # check if __pedestalSum has same different shape as the frame + __logger.info('pedestal shape mismatch. resetting pedestal...') + reset(obj.plotTab) + + if obj.plotTab.pedestalRecord: + if __loadedPedestal: + # reset loaded pedestal if we acquire in record mode + __logger.warning('resetting loaded pedestal...') + reset(obj.plotTab) + __frameCount += 1 + + __pedestalSum = np.add(__pedestalSum, frame, dtype=np.float64) + + obj.plotTab.updateLabelPedestalFrames() + return frame + if obj.plotTab.pedestalApply: + # apply pedestal + # check if pedestal is calculated + if __loadedPedestal and frame.shape != __pedestal.shape: + __logger.warning('pedestal shape mismatch. resetting pedestal...') + obj.plotTab.mainWindow.statusbar.setStyleSheet("color:red") + obj.plotTab.mainWindow.statusbar.showMessage('pedestal shape mismatch. resetting pedestal...') + reset(obj.plotTab) + + return frame - calculatePedestal() + + return frame + + return wrapper diff --git a/pyctbgui/pyproject.toml b/pyctbgui/pyproject.toml new file mode 100644 index 000000000..08de6be1c --- /dev/null +++ b/pyctbgui/pyproject.toml @@ -0,0 +1,55 @@ +[build-system] +requires = ["setuptools", "numpy"] +build-backend = "setuptools.build_meta" + +[tool.ruff] +line-length = 119 +select = ["E","F","UP","PT","RET","SLF","TID","PTH","NPY"] +# other usefule rules "N" "PL" +# rules to ignore +# PTH123 causes issues with gh actions +ignore = ["UP032","PTH123"] + +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", +] + +target-version = "py311" + +[tool.ruff.per-file-ignores] +"__init__.py" = ["F401"] +"pyctbgui/utils/decoder.py" = ["F403"] + + +[tool.yapf] +based_on_style = "pep8" +COLUMN_LIMIT = 119 + +[tool.coverage.run] +branch = false + +[tool.coverage.report] +include = [ + "pyctbgui/*" +] diff --git a/pyctbgui/setup.py b/pyctbgui/setup.py new file mode 100644 index 000000000..1bb1e3848 --- /dev/null +++ b/pyctbgui/setup.py @@ -0,0 +1,44 @@ +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 new file mode 100644 index 000000000..67cb0c4a2 --- /dev/null +++ b/pyctbgui/src/decoder.c @@ -0,0 +1,179 @@ +#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 new file mode 100644 index 000000000..99cbcfc01 --- /dev/null +++ b/pyctbgui/src/pm_decode.c @@ -0,0 +1,17 @@ +#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 +#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 new file mode 100644 index 000000000..bd8f283ef --- /dev/null +++ b/pyctbgui/src/thread_utils.h @@ -0,0 +1,10 @@ +#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/gui/__init__.py b/pyctbgui/tests/gui/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pyctbgui/tests/gui/conftest.py b/pyctbgui/tests/gui/conftest.py new file mode 100644 index 000000000..e60342ab7 --- /dev/null +++ b/pyctbgui/tests/gui/conftest.py @@ -0,0 +1,11 @@ +import pytest + +from pyctbgui.ui import MainWindow + + +@pytest.fixture(scope='package') +def main(): + main = MainWindow() + main.show() + yield main + main.close() diff --git a/pyctbgui/tests/gui/data/matterhorm_image_transceiver.npy b/pyctbgui/tests/gui/data/matterhorm_image_transceiver.npy new file mode 100644 index 0000000000000000000000000000000000000000..37ccc6c2d232fb3124f01af9b0b1fc6fb01184ab GIT binary patch literal 4736 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 literal 0 HcmV?d00001 diff --git a/pyctbgui/tests/gui/data/matterhorn_waveform_transceiver1and2.npz b/pyctbgui/tests/gui/data/matterhorn_waveform_transceiver1and2.npz new file mode 100644 index 0000000000000000000000000000000000000000..942fbf12978830995cf6d9d99ce6009b6c79da17 GIT binary patch literal 5106 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 literal 0 HcmV?d00001 diff --git a/pyctbgui/tests/gui/data/moench04_image_analog.npy b/pyctbgui/tests/gui/data/moench04_image_analog.npy new file mode 100644 index 0000000000000000000000000000000000000000..791e6e3da2d685c85b007213ab0dc0d1508ce7c1 GIT binary patch literal 1280128 zcmeFaaZFuTVz0X%GExFUijZ+6EF=qoYJ-V-kx8ELLs~+n*9P%qA#nU~&`Wvd%7KtO zrN&XQ7_Bm!$f4AzJ$A9sRjYQttSjFm3sND%-zQjxQ-=$lS-;uA|33 zdW_OvGFd{8E%X?nzg7CnCadYOn;vJQdjCYSkRF@p@i;v`pvO{r9HqxS^w>j>Tj}u> zJwBNV-`^dj$IJA1h92Lc#{=}ZiymL5$Em*yf8MmyV^5#9_y4KVR)=Xz=&^+!BlNdQ zf7xU;J$BRMEd3>th4k1&kHhr1On(_%k(()kKulqc6uyXpO5Q3UeNQlGE7@Yk4^MA zOpnX-mqAw2V<$aM(O(>yPmc}sI7p8R^p{GO(_=e5jz_g0F=Q@1*3n}rJ&w}j9(wGd z$F1~uiXNZLgzx8$(&IEe?xV+d=G4q{{CU$(k0tb&xIP~*>-qhiN>>`D z&8No(dK{$31^P=R%jvP59>?h~hRmhMI(qD*$0+?JlO^=nLXVNC_G6X)vdLNUtw&%qiw77c$sXw$d^Wg4d-MdKFClLH#ppkCt~f+ccD^DJo8ybB664~;t#zK^-VJpG&=Ut=D>M~_$O@#~0Q z|0gC3tDnF;ktMvFK zJ-!;~`8VkCO?rHv9zUSRkLa<69-q=G32zHq&D1?{&@CChbOwq3{R&gr<_mVXV8(;cUm2lP}*Z=>|MhhBT= zc`Lo2(m0q$8W(e$ak_jvd|z|sW92)H>jB2OgmF*2uIGE07yJKA+dJyRw3V7iwo~)U zPHCRmI5J=J(0d%8C+~5ZKbM)0XPA%gFdx0gh5b11aXP=mM3uj$9_P7Peql_IXK=*V;tJP_e{6??`{ClfaPkD)d<2eOgsV@&+3|4q1^lB!Tpm>5AL-F>zk?-m z2>)orKML@V1bIL(Lw4aGRrp61=f@Rt1pjEpJ6gyH{;{gp*=({J|LDd)X7zh)B3X!k zG~pk^_{XyL+h&lJ_(!LX)AisVTj}8xy*#0(qx3dSkNfELIz6Y+`#Hv8%bl<<^N4Y} zeOud?KUSV$T;E}w+Zp!~=0VyOy>9=XD|h@}n6_H;$ado&vv@}$S*UsFJ&qo|$7%lD z)_KBQW{W#;VH;3aE`0LH;!C!CI`Rsdoz1ef~ z^Wo;8SlkyR;T>~iFaA+0ZZuysg!94NfGgjEGv9-kuEL@3!=)d<1s}l)pTP}Ziz8gT zIM-c(do$tSn{aUvoO~Z{u7RVU!qu&C_I9|tAOGkQmj_k&M^;py=M{29Tpu*!AI0>T zBo7GY$X@)T7XQeRM+9r+82-_QceIn^_(u$xtJm8){G$*5i0b#)WU>VRXu&@s+E2Tx z{kGX;HU80!e_YpbyEJ+@M=x9MhsW)X)7t}jETz{`dfr3tJ&eOv{Nv@x@aN4V#_jgU z+P=)Vo?)D)829#dZf0JjUDWG$G=^#G@Q*(HBZ_wvm)_a_K^B$)j{QbDF2j|yS z>O8VZ*HxOJR`cDj(+GPA{5lQw@SN9acy9LGY`*#Dz9-xql!^O-6meiMPY&Q8_3Qre z!|?N9-hwMXhcmCjonOMCAHby_!Kt6YtzW~juTF;VCBAgZRgS=9x_;%kdBIarDRM%X^&q@b}}w9-LoSsq@Ju`+l79>zl)I4W6;S*>kh! zX7kNI&&`{Qn}Z7cBV8OAERjR_M~IHbpWm=|e(q3yFxVcI7AV;KKfUeehI}Z{9^*{JpukB`h=d3()%>yun+%uN8@A;jEDW1U5w+)quPGNIJYzI zCCr1gt9rg?-9Ju++qMP&h~OWqnrAkftj0gQ$I;(_(tDhG@b}}w9-LoS8TMc|+4tl8 z`ey#VK4X2e=Vs5%=9_=+dvd+mbMy1z=Aah;$PotyYvdUI(YEd%&Ea|9d=6K>1!uko zcV2}vTi=9Z-=7TM#}(lp_u<|eIQS`C+zKaehnxH1=)G|D1e|>Y?q0+{ zM#SYoGyYK=4S!#dgm=u5z2f|!mL7BPjx}-&|7gQM$~Zr!$SZ<*asclbBp39!O(o0m zk9PcH9RG+RbMcQl?T78te%UDAkxZ88IBX045y3y6;2%fnVVYj{(bIK$OQXkg^txq6 z<8YkbA21H3_{SNIlX+)6e4RPKIPPLxU;ZaOKE=4VGY@*07iryk{_G!wY1{FSar`5O z%+-9eb@+$(IQ8Z|F6_bi`*G^QudCE~WRreB&V2DheeV7G=CFt0JL{YM^=3F_gX=Ur zH`C(=uQz*cem>kB)Z-s{ct@7-6>>&QJ zfOn*l&`T*jjndm5dhDUst@M0~-k&fINAZu#_{W)Xjq^L7 zC=W2MyBO!hzt`ha%!9PQ)^<;~^6cp_Z72RQg?Ge}`S?cz{^322{`h>Qt@r3X&V2jN z-;cB3*5LfQ%CHB!NxmOv{QBl_JcDPfZ}!~mxj8)VeNV18dv1O{+#EFG9|ibFg19i4 zA-mT7<4kydFrRlT--0v0ggak@L*IZ)--J`&hg&~@V?V+_YVePzaBnLdyd5s?hm-fh z%@c6+5x9C0&ORyb4r1|+NwNd~sECI98>EZlgC%kZ|7fJg0{kOEUJ%TXUHC^8{*fiG z2v*1uydy%c>T#P*R^uPt_{Xf?*CvvM_(v1|F^qpKYd>uUS*hc&o%qKT{t<_NoWehz z(92PJnx?mX^mv_K)9Cpey>H?FV{;t;xQ%~Y9@luE`9%2+<9dK`E@9jgKh*O*%!{<2 zY5QzTn6?}Ln8iC1$wK_23IFgOr{28Bg*`ZbKTbXPb(K1gY|`(?nIHT}pL@T)Iqc!P z>zn=cW;kVo>ohz!du}%0{Bz$EZVsC9k7E2I3GbLAd)NJ=mGdK9`8k~V8r=CF9Qr0) z`aYcc0o?i#9QzsmaT5QChkGx;!I^OJO*pv-ZoUsk*TB_J;p|rYBLVN2A-nL8s%W_1 zL6$f^SRqI7k7nj$G5(PxF9_zyUi_mL|HzS71Z(6N-Z4(bkhyx^*5Mz0_(xRlYm>rPn?5+(YkM8HZE& z$0PjXHsf~r6OI2F#`PV>xt(z@VICx2)9ZSErab$HVcI_YBZ_wc{(fB8gY)Yubw1f--;XmtXx2RU>zl(KzPrBJbF=4W^UXic&6|sxgEsu54F5>M zJLbs&aijS`8|O#3@-;Z~J-G8K9Qr<7`T?B!5#0J29Q(Dn*0s{(cDT164&DnFPr%7X z;O0d*`XpQ(4`*M%Ka%i{IkFf3sEvmE9ps4PgEevt|7hcLU50<8$P0pbasdCR$3OD$ zj#zm{Fo|zWk#S@`{?UMc4B{UPdS9DLmg67o_{TW@5kux`e{G$P!}j4HQT!tr|LDO# zw$jTfdU`@{N9l2zUiZ=Sb$U-@9L{N6%*&r@ydN=cw?EPLWybXk<2=Qo9S_b*PA^zKOb%mI`EGQ{3Bgl7%Y)P>;CZ*&W~{AdvNAe zxbsUm^aHr`BRKUlxbNWed4@QqnAku1bNn(&Wd z{9{?~Yct47{G(I*VW+fTHjd24KN@r#b`bwqz&}#)kL&nH8oivOr!D;d8jsWC19~l` z=TUm!!#MP4T+A+wlX-a}?8`j*M0tBud6{ue9MSd^^Pru1(bJ>H_n!^Z4&xuonpZZ1 zti(S$@el8D>dkvx*n{);RaY9DD>WUWAiR!p-q;^aZ#&6VASgf289bOXLv#(HISXPf&n=Bs|de z4B17GRrp7iydYR1NAQnk{G%BENRnp+bNEJ-OeRb4j~4tRf`6>)eQh>bjem6GAG6vo zn@ASoA5A(AJB)uU;~yFL$0+`>hhBQ*qQ@undX%20>3tvLa9!hK4rrXru8FWO z^YSg_qfzB;#<_%XPy9%aPcbjjdbPd(r(xO%{;{fgWwXg@{G%KH@E)h$yvKz-IDbD* zJ@|E%I*)A9@5h-RwCZ#J*3XoFeRJ4D!1`u?y%|o~;5rS@&Gfjz>&>2<>2ZVH9Q5KJ zwfIMlxG-2F$JYHr9BJNyE5C#@UxPc}gF`=qOTUIwUxi!WfMehMG(4}koA^f&+IQ8c}F6_bi`*G^QudCE~WRreB&aZFg z@9Q(xH+yb|Q#?0^=Yik_UvKu@%zWG+HwOdwM?Lmxgm};pTm`}!I|&D zomb(|ui?^H$HIAH-hf-*gk#^wKPK>xBXI8`9DEWkj)#*kz|EO(^i8C$AvvOe?Lw=_;r;!k8INK$C)4eM4$V&dX#>BbJ#<``euK<8BX!nn>8<9@bzZT z&CiFMgCYE*5&tN_KN7@=!Hm+}5J#G~;L7*l%&TzcmvHE-5uHEZfK%UuTi=IcKfpik z;~zC}?^8Iq6)xTmC-=k6d*SE_xcUg3y@-G0;T^Gf$0XSi)qJhMKhp7zC31)!8}W|< z{3Ai05X_KW_(v7~ktNRvR`88gGMlW%Kf3XcS-p=1CRp_R-sQdQ79&bM(B0|KHrM-%=rjDL8KQ=i`B!XBKzAEzGtx=Ni#HtF}{ zj9=dzj&Jac_09fzGo0eDH*21};Ootvo1YIi2P61LGyYMGe`+ zFX7JD;LtZB;ruXf!m01WtslU#AK@R1_{T}OHy#eY02gP%$v5HVA~^a!TwMcaKgB-^ z@Q(z%V}|UCYQ9$CA6ae{9kJf6*PMmk0DzN^hg|xQAYQ z=y@x>pJE)I;2*c~kIRhPnOhpycNo_L5p9<+?uj?FJ+*%S}UALQ~aY9?%fUt_rt||;p7Rp`3M}n2v?tkv*X3xK{5W3gm=u5y-}UdYVnU8ykm_V zqsKPxwI4Qy z%*8+I@Q*$nhmGPL$z%!su@(O~MK4e2=_tKT)8jsRy-v?*^nQ+U*zzwLzeo7TZN}~L zEj@mQaeXJE?RLh!gn5uSsKCbzd`tu$a_Tc>eIQ8Jy zRq8ylNxvWG*EjR`^%?7%JvYNCo}0t-K=6XEH+yboK5mekgGv0O1OKSNKhnjC!IIM4 z5J#HN;mX(G%=h5Vt8nQ1k#K&P58%{~;MULJ*st-AcyX?~0QY9X!8hUJA~^Xz+*|`k zKZUDX;q2}BM;ZQ+f_Kc515wS_di)~~?}(+pNwNd~sK7tc~+@sDM_kIf(}@sCdYV@msB~ax8Tb6;LNLV=a+Ek2a#}on2+Gp z&*0Xt;n-Jy5kBv>;~)KS?_M}~0xmuRCojUyC*kOLxcUN|or!-`;2-ID#}YXd4S!$I zh<_B|9|`m~Lw4aGRrp8NL;c(eIl}p|8UHB8Ka$|)WU>VRXu&@s_{XZ=$7Yk&_(wPX zF{}NsiDV)E(S&~t>p1K(-jP98;vakPj~;s2N>8Wg?Fl^|rPpbC-be4(8HY6dW0%Is zy!_8$59ZM=*&q~DJ-H_qvE@7FhnJp|ub-|Vk9!zuoHv*yPOzTWJ)nfbUuZVu+e zeL*k&QHy`%h!cY~rMV%FG@rwjSK-Vr;m+6K(2pWIkA4QHehs(23dg?ji|~1uiGSRL zdyC-U`*3j$oc#2jUf&8wZ-=Y<;q1NQ?w|_)$ih2T$dPFH^FcHIQH*~i(cc`|i+|MO zA2|>8b8F-n{?Ueil;IyKaC0hIj(@b{ALIB(44JF5e%NHP1pjEkKO#B~ zyNY*YlhydgKK$c4y`<68IeOc||G)7#y*{AlQhFa{9QNQJ2Q*G*7vuKw?XVB?XiRxK zqAX$D6aP-zQ_PFB%i7-mCrXnYrp?uSvUT`JAN~=gKksqs%X?hdgY);})PrAFsq@Ds z{eGOe(V@@1U*8<|5PWBSv%lU9r}*p5njbItdb8(d=HmvrIhYss1q1j;J^qo0cbNaG z^WqIS@-4XXOE~j2xbrx@&rgya_(ui) zkq$R!kd^pHC;l;|pR;jfKK{{we+=Ru3)&BxN|xgv?fA#Ij>E=~x%fvN{!xm5jMB>< zdg`IKt@L<`UZ2qOQF@wE3D>wgLYb#6K43&wHHu@*WrV;QakK_2Ac4>in@uzaQtxf?e zDxCTT-1;UQ`@Xo=72zNE;ocfJ_$gf63MX%eoBQGDy>RseoP7lUsK-C@@Q&DMxF5kJ z*@1sl;2-Jqw?q!%AC34&0sfJ|d2xp9!au6;k1V)3o2cfaiR&T!tpjY;K;Y&%J<;Rt8nL+aOkUW=^JqBn{ey_u=ds{G$>7D8N4wqMD~OWEcKXg@0tx-wHW`e>CGC#rQ`O=fyd) z7yqcmKXTyaT(S=T=)*sv`Z=3Smf#;P_(uf)Sk->mY_b~v=*B;0bsRR4EW|&W@Q>r_ z!9AdtQhFMtw>|XOL$6!u`4qiBVH}R)AD8iuGmP6ix5GZo0mgL~zn=c zW;kVo>ohz!)8huOH+ycT#|?6GuqN&c#_*3e{G)8$KWfE8H{i(6;mWIU=9h5it8nNW zaOsicl(2XO31_(u)?@f7ZDg@d=l#r<&dUbuMzjy?icFT&X;@sDQwqZt24iiZ0g z%#pqLM=k!5Lw{@J82-_Qf0W@LDV!JQ$pLvqP>+A)sXx=8Z8NCn&4ONUQps|?pJ~_o zn{oX<6GP@|KTMtW&-7`3O;r1DlF1SspJ~zYno~M{^F)1^qw2>@t1q)p{h90P)1;|i zb54DmEx!zVGRN_bTX@GM#_RNLJ%5bx+|T&#WV~M)*7J9m4?Fs`ZM&4_ca-JKr*_RR zGp_k&Vo3bW;BN+hGx(dq-wgg{@Hd0M8T`%QZw7xe_?wxHYCrHdgTER4&ERhae>3=- z!QTx2X7D$IzZv|^;BRK#-!9SPX?i?HkNfFyCq2GIk9YVSou$W%e9kPNJ5#Ow$KMS8 zX7D$IzZv|^a39WaAI@+e&Tt>ja39WaAI@+e&Tt>ja39WaAI@+e&P>r`9Laq+!+kiz zeK^B?IKzE7!+kizeK^B?IKzE7!+kizeK@mzAMP|g9;3(o^th8AU!uo5|EPY>(&NGP z`MBOA*H28PUdMen!+kizeK^B?IKzE7!+J`?dP>83O2c|e!+J`?dP>83O2c|e!+J`? zdP>83N;6J>F(m6L4eKcl>nRQEDGlo>4eKcl>nRQEDGlo>4eKcl>nY8;za68;{q(q# z9$%uzhY|I1mL3n%<16d)@v@$`ok~-xWId%}J*8nirC~j#VLhecdvS*E#TmXAXZT*6 z;d^n0@5LFu7iaihoZ)+MhVR80z87aAQSArci!*#L&hWiB!}sD0{$}`IoZ)+MhVR80 zz87csUYy~3afa{3nf3c{`{{8fJ-$Sb56840w<5}e^!N%r-Wk;6JJx&r6K$K{B!5eJ zjmHo3y2t$7ZropwfH5}_T9P2e4{Onk- z;aIQXSg+w&ui;p);aIQXSg+x@?(Dek?6~gi)~`Fes&G8bHO`xdIgh?ek5}mNF+FbM zJp4L69-_w+^mv{gx$bPZ?rga3Y`E@hxbAGY?rga3Y`E@hxbAGY?(Dek?6~gi66lZX z&W`KOj_b~j>&}kr&JKQdTz7U{cXnKNc3gLMTz7U{cXnKNcJQ-uCvw@!t{A}Q713w%1*}%^Rem3y4gP$Gz z?BHhyKRfu@!OsqUcJQ--57FZZdOS~$AJXGpdOQjD#{W|9e*rGegp+T=%|*Y`^Y>?!HE{M* zxV!b=!pB|P7s@hmx=V>_d!8KlpW3b$=es;{zl;5MJw8cx$P-+JyuqbE((_B?(1Nxb z@s1{PSdW`!z0PEimH0=e-q%d&_n0^`U;AMi@Q*?5r&)-GzsIDK<@iUtj?pJE&yGA_3mr%Si>zNbG{9%EehGtLJY_gAj#`HSoR z@n_mL^~y@-Q78T}g?Ge}`H%HF?{VtEdz^Z>#C$x>d_2Z{^d6^vyvM1Bn5gpC)#E%j zhvVnDS@YO)Gu-02S?9G)%FV7o+~*STjv2BG|EPi+gA?lE8eDk~&fE=mz6*yQf=f@p zspsL=58;NpaKu+|MJwF99S-h?i}%9G6L9kp=HntNRS7(8L|uisKP(8)btapv#y`68k6Ha5lSmfg zA5HkjF#fTu{Wck7CH~Q=<1{_^$5whcMK4e2=_tKT)8jsRy-v?*^nQ+U*mB33Z~r}H zoNnFL_N9-Nry18{jPqH>{UGyT#}&QK{<+fpp0b*G)Qx}4;vI=(q2{6S9;d#%$Ek-~ zoF^_ZA5SwMk1-$jGavo^IQ8Z4$Ek;3xBGGadUH5#{(5s%J^1U*`W$~xuQz*cem>mn zip6~{3GbLAd-0E2xG`wZe7FWz9)>gDg*&gnp(o(d^Kik3aKc@<;VU>I9?rc0_h!Pu zH{s$UIQc%@Tmwfxg{xcP?Co%OKmO4rE_YS>ye@0#(CVtP!H2e>)17yqcm zKXT*|ZjBtnKicq)c5)p5h#_-{T`D{mf#;P_(w$hX;!u0CY!9rKf3Xc z>pE_eMi1xcWy^hQzWsNc-X73nDZP%;^B#KdVH~zHE-y{$=N~d|w?5YPCC2qM<9vs4 zKg&G0$h_EbQLi_R$~xvzAN~==JCex~%|qioPJMZgQxE=roO;Cb>@N;1f!t9Y zT>2rLdKYf}3Xa`2srT6q=k~+Bd*R>-xcCU1ya+d+grno(>I-moCft1!|LFZ9e1BJq zf8<29y+)4VA8q(Y8S^nk9^mH50sNyL|H#8TV)2eivIFnvB&YC>I5HpqXuv-P@s9=l z9+OIz;~(w#$2k5GL*{D#O&$Kxr{grE_{Sc4=%JUb^mK~ep3vh_dYz`{ee`~vaY$oa zb}~*cjfef2haW3%F|L;w=T~m(@jJ|e9Ru3Fcv0z`vVnOth<_|-o|#m#9RKhhM}K_2 zyvL~ze?Lw=_;r=)VY7Wd&aZC{$JIS!eY59g&&}rBf1aDe&xzS2-0Ujwk92XMTOx<> zk4Cuh?cdjN--9a;4`}-_+_@VL{SYp_3#Wbsw{C-DU!T;^Wx}~P;oc%R_&!`*11CR) zn_J=N?QnHJoV^$Bp1?l_z6jsn)#D#|QEkVH~+@sDNw9+N><;vb#($CUQd#F6|tE?Gfq3l^>Z(MqI~#skdhs5o9{l|{_2Ac4s)x<`{W!nAIUL_-t#9`0 zG~kpCuG8?>o9S_b*PA^zKOb&(Rrp7iIMA(-Blt)2x_|sg=l{cS<-GxI?}j_yg+uSc zrC-6R+u+vM;n+iy`nkPu?gZR>1P)$=i%-JI@o@76I64!qz6oa+!QJ=qk0Jb{5&tNN zhQH4xh~wQ1*@b^p(PI|gu|kf>6I?U?QH*~i$t&C(*^77dkx@NvlF1VMqXqwn=zYy9 z-jPjKYd=gk{xPflG>K%Pj>9zJAHzCM^8o)SrH4^^*+Wk~^tP2APtofWdOk|;(~QGD z#^o5}w0~UVwUcptX;j+}8RxT%`$6Wxj;nh9;<|sF(sm2;D1v{iYMz;FvKs&J9!Gxz zO7C&%!QYQl4}M*xdf05=kMrxB!*PAq`ex6~o}10L|Gp>Jn>{x_A8vNF_(zU7(5;bU z_($8ie>8{Z2XhauJPc>P3wK_DL%)Jcx525e!>xzl*b|d_pCUN-KHOUa2S0_2TjAvG zaC1K#y%(;YfU}Rl-HZ6gh`8J};~&M*@b|eSykm~+73aHJdd$H)*2ppZqYeKk!#`5w z6>gp!z&i%X1wC$3$#VRo9sd}|KVryS{G(3$VfwURCW?0?lO;M1(}I6Q@Q)|>$5DEi zrk8#6be-PP=Ajt}p$Q9>2r5pJg6gWM1s( z*7NQUl!RyVQo1YIiyL$X15ATQ-7rIHZW8FV~%=xifdGD(73f%b^4&4Tq zz7D4zf?H3(vFG91BXI5_+lXV<{pPw|g2{G$#3D2s;s z=~D2Hd2#^%sHev~yd(BM^!iD%1OKSNKhotDZiyViJBG<+J#I3{O8lb}|CrMInm95a z|7g&Dm_htw0q;m9%XJ*49sd}|KVrz^^zeXQO6h5o-uBRA54~=s=Tr3lgmE~^xLjhK zPLGFum}8$P_cN|L8Ru92UXR~l9_;vQZC~tGy3@)|=Ft@15l80Z9}W12_c;3F^R;8W zNAGdw+kgIkoDF;M>nhd5X8C@cU*8;#r+dcwX3x!@o5S{|DaGdkbj>sB6yGatj9yW!B+;nG8J>It~@JRJKWTw4R@K81T*;o$9XaX*~A z7jB+_qmRJVi*WWyakq=bJ0{5v{G%co?ypP7JC?{H{G*W`3-FJG|Iq7a$S(Y&3jfHG zSGW~&1n-EDt9smIlhyb~H~ulJ_ce)RA^y>Xe+=Uv%XmiyS*hbNo%qKT-VsNh!attS z%Taoorni0cc%5F;==mJIZ5{6i}8;nd4ZcFd-0E2{3AzR;nv78ykne7=zS~WaEftx$T;0% z+%A2h@juPD9%G!(GVTYN2d`Yy>n{FG>HbjJ$2^MS9m!+~{?USec#oq$K40GB)Q7(x zryl&eO7$?yd2zFRKkn^j9hYC<9QN?t_067}JvWKObh3aIhX^}ydzeg z;U@8oDKd`C$3GhIk3sxnLGNo)$#VRo9sd}|KVryS{?69vI7}b@5yd-_$sYV;E4`ee zrziAwlpd$)bss%nr}s3*;T+@g(x)2lhm6~;PqclBaXrmA-(lR(G7kcOw8R1cf=`*CmoSjTs`Tj|#~hdnsfH~V!O zaLNYPY542S^ti$6&7PZ|4>!9G{G$T@NEa8nC30xpKYqgb5w3g}&b$J5K88cj!=)d> zsdwSlui)5ipXz-sz`2=l?@c(k2rj-4C)dEuPvPiRxOzLB-H(5y;2rbi0RB-Q)p;xr z?}&Y%?MbqO9xL#Vba{bWB8TvgM*O1y|46_;X7G(!GLbCAKbr85Vf8oivOr!D;d8jsWC19~l`=TUm!!#MOXE;|{g zmnOo#%)?KVw?>th80S|;w0(zpaF%&-u}6>Fv&v!S(X!^1$sjB7k52r%?jqZ$7w#y^te z8Ey{Wh?2=<3I5T7e?;()RlTptCadv}Zv10b`(+ZzLj0pi$6 z&2Y*F*J*ferpFClZ}!|wj~nD>*NcDD;vYHULbpbat@{TYc^IyI3}^0!JKu#v@4}^D z!KvHe*4N?KL!avWdK1nqf_v}7!8LI4Q#iR5Zr%<@_rukD;p_?gBMa|XAxH3!=BUnN z#rQ{(xZcf?z4Ta%f8^jDYvdUI(T0DN;U6jT3^%Vd3uG!;j(@b{ALIB(44I36)Zrg} z+7A=eewk#l1pjE!ahM4Hv5I$OlhgReK6<%MPigdajvlvsrv8u9^8

WgJEsmt&06 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 literal 0 HcmV?d00001 diff --git a/pyctbgui/tests/gui/data/moench04_waveform_adc.npz b/pyctbgui/tests/gui/data/moench04_waveform_adc.npz new file mode 100644 index 0000000000000000000000000000000000000000..8f94f278303f958231387d35c054fab7556470e1 GIT binary patch literal 327106 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 U&#J!q&Fg#N>sN(kbzZ;wF9}tgF#rGn literal 0 HcmV?d00001 diff --git a/pyctbgui/tests/gui/data/pattern.pat b/pyctbgui/tests/gui/data/pattern.pat new file mode 100644 index 000000000..20ba27158 --- /dev/null +++ b/pyctbgui/tests/gui/data/pattern.pat @@ -0,0 +1,232 @@ +patword 0x0000 0x000000006b2e8001 +patword 0x0001 0x000000006b2e8001 +patword 0x0002 0x000000006b2e8001 +patword 0x0003 0x000000006b2e8001 +patword 0x0004 0x000000006b2e8001 +patword 0x0005 0x000000006b2e8001 +patword 0x0006 0x000000006b2e8001 +patword 0x0007 0x000000006b2e8001 +patword 0x0008 0x000000006b2e8001 +patword 0x0009 0x000000006b2e8001 +patword 0x000a 0x000000006b2e8001 +patword 0x000b 0x000000006b2e8001 +patword 0x000c 0x00000000692e8001 +patword 0x000d 0x00000000692e8001 +patword 0x000e 0x00000000692e8001 +patword 0x000f 0x00000000692e8001 +patword 0x0010 0x00000000692e8001 +patword 0x0011 0x00000000692e8001 +patword 0x0012 0x00000000692e8001 +patword 0x0013 0x00000000692e8001 +patword 0x0014 0x00000000692e8001 +patword 0x0015 0x00000000692e8001 +patword 0x0016 0x00000000682e8001 +patword 0x0017 0x00000000682e8001 +patword 0x0018 0x00000000682e8001 +patword 0x0019 0x00000000682e8001 +patword 0x001a 0x00000000482c8001 +patword 0x001b 0x00000000482c8001 +patword 0x001c 0x00000000482c8001 +patword 0x001d 0x00000000482c8001 +patword 0x001e 0x00000000482c8001 +patword 0x001f 0x00000000482c8001 +patword 0x0020 0x00000000482c8001 +patword 0x0021 0x00000000482c8001 +patword 0x0022 0x00000000482c8001 +patword 0x0023 0x00000000482c8001 +patword 0x0024 0x00000000482c8001 +patword 0x0025 0x00000000482c8001 +patword 0x0026 0x00000000482c8001 +patword 0x0027 0x00000000482c8001 +patword 0x0028 0x00000000482c8001 +patword 0x0029 0x00000000482c8001 +patword 0x002a 0x00000000482c8001 +patword 0x002b 0x00000000482c8001 +patword 0x002c 0x00000000482c8001 +patword 0x002d 0x00000000482c8001 +patword 0x002e 0x00000000582c8001 +patword 0x002f 0x00000000582c8001 +patword 0x0030 0x00000000582c8001 +patword 0x0031 0x00000000582c8001 +patword 0x0032 0x00000000582c8001 +patword 0x0033 0x00000000582c8001 +patword 0x0034 0x00000000582c8001 +patword 0x0035 0x00000000582c8001 +patword 0x0036 0x00000000582c8001 +patword 0x0037 0x00000000582c8001 +patword 0x0038 0x00000000582c8001 +patword 0x0039 0x00000000582c8001 +patword 0x003a 0x00000000582c8001 +patword 0x003b 0x00000000582c8001 +patword 0x003c 0x00000000582c8001 +patword 0x003d 0x00000000582c8001 +patword 0x003e 0x00000000582c8001 +patword 0x003f 0x00000000582c8001 +patword 0x0040 0x00000000582c8001 +patword 0x0041 0x00000000582c8001 +patword 0x0042 0x00000000582c9011 +patword 0x0043 0x00000000582c9011 +patword 0x0044 0x00000000582c8001 +patword 0x0045 0x00000000582c8001 +patword 0x0046 0x00000000582c8001 +patword 0x0047 0x00000000582c8001 +patword 0x0048 0x00000000582c8001 +patword 0x0049 0x00000000582c8001 +patword 0x004a 0x00000000582c8001 +patword 0x004b 0x00000000582c8001 +patword 0x004c 0x00000000582c8001 +patword 0x004d 0x00000000582c8001 +patword 0x004e 0x00000000582c8001 +patword 0x004f 0x00000000582c8001 +patword 0x0050 0x00000000582c8001 +patword 0x0051 0x00000000582c8001 +patword 0x0052 0x00000000582c8001 +patword 0x0053 0x00000000582c8001 +patword 0x0054 0x00000000582c8001 +patword 0x0055 0x00000000582c8001 +patword 0x0056 0x00000000582c8001 +patword 0x0057 0x00000000582c8001 +patword 0x0058 0x00000000582c8001 +patword 0x0059 0x00000000582c8001 +patword 0x005a 0x00000000582c8041 +patword 0x005b 0x00000000582c8041 +patword 0x005c 0x00000000582c8141 +patword 0x005d 0x00000000582c8041 +patword 0x005e 0x00000000582c8041 +patword 0x005f 0x00000000582c8041 +patword 0x0060 0x00000000582c8041 +patword 0x0061 0x00000000582c8041 +patword 0x0062 0x00000000582c8041 +patword 0x0063 0x00000000582c8041 +patword 0x0064 0x00000000582c8041 +patword 0x0065 0x00000000582c8041 +patword 0x0066 0x00000000582c8041 +patword 0x0067 0x00000000582c8041 +patword 0x0068 0x00000000582c8041 +patword 0x0069 0x00000000582c8041 +patword 0x006a 0x00000000582c8041 +patword 0x006b 0x00000000582c8041 +patword 0x006c 0x00000000582c8041 +patword 0x006d 0x00000000582c8041 +patword 0x006e 0x00000000582c8041 +patword 0x006f 0x00000000582c8041 +patword 0x0070 0x00000000582c8041 +patword 0x0071 0x00000000582c8041 +patword 0x0072 0x00000000582c8041 +patword 0x0073 0x00000000582c8041 +patword 0x0074 0x00000000d82c8941 +patword 0x0075 0x00000000d82c8041 +patword 0x0076 0x00000000582c8001 +patword 0x0077 0x00000000582c8001 +patword 0x0078 0xc0000000582c8001 +patword 0x0079 0xc0000000582c8801 +patword 0x007a 0xc0000000582c8001 +patword 0x007b 0xc0000000582c8801 +patword 0x007c 0xc0000000582c8001 +patword 0x007d 0xc0000000582c8801 +patword 0x007e 0xc0000000582c8001 +patword 0x007f 0xc0000000582c8801 +patword 0x0080 0xc0000000582c8001 +patword 0x0081 0xc0000000582c8801 +patword 0x0082 0xc0000000582c8001 +patword 0x0083 0xc0000000582c8801 +patword 0x0084 0xc0000000582c8001 +patword 0x0085 0xc0000000582c8801 +patword 0x0086 0xc0000000582c8001 +patword 0x0087 0xc0000000582c8801 +patword 0x0088 0xc0000000582c8001 +patword 0x0089 0xc0000000582c8801 +patword 0x008a 0xc0000000582c8001 +patword 0x008b 0xc0000000582c8801 +patword 0x008c 0xc0000000582c8001 +patword 0x008d 0xc0000000582c8801 +patword 0x008e 0xc0000000582c8001 +patword 0x008f 0xc0000000582c8801 +patword 0x0090 0xc0000000582c8001 +patword 0x0091 0xc0000000582c8901 +patword 0x0092 0xc0000000582c8001 +patword 0x0093 0xc0000000582c8801 +patword 0x0094 0xc0000000582c8001 +patword 0x0095 0xc0000000582c8801 +patword 0x0096 0xc0000000582c8001 +patword 0x0097 0xc0000000582c8801 +patword 0x0098 0xc0000000582c8001 +patword 0x0099 0xc0000000582c8801 +patword 0x009a 0xc0000000582c8001 +patword 0x009b 0xc0000000582c8801 +patword 0x009c 0xc0000000582c8001 +patword 0x009d 0xc0000000582c8801 +patword 0x009e 0xc0000000582c8001 +patword 0x009f 0xc0000000582c8801 +patword 0x00a0 0xc0000000582c8001 +patword 0x00a1 0xc0000000582c8801 +patword 0x00a2 0xc0000000582c8001 +patword 0x00a3 0xc0000000582c8801 +patword 0x00a4 0xc0000000582c8001 +patword 0x00a5 0xc0000000582c8801 +patword 0x00a6 0xc0000000582c8001 +patword 0x00a7 0xc0000000582c8801 +patword 0x00a8 0xc0000000582c8001 +patword 0x00a9 0xc0000000d82c8901 +patword 0x00aa 0xc0000000d82c8001 +patword 0x00ab 0xc0000000582c8801 +patword 0x00ac 0xc0000000582c8001 +patword 0x00ad 0xc0000000582c8801 +patword 0x00ae 0xc0000000582c8001 +patword 0x00af 0xc0000000582c8801 +patword 0x00b0 0xc0000000582c8001 +patword 0x00b1 0xc0000000582c8801 +patword 0x00b2 0xc0000000582c8001 +patword 0x00b3 0xc0000000582c8801 +patword 0x00b4 0xc0000000582c8001 +patword 0x00b5 0xc0000000582c8801 +patword 0x00b6 0xc0000000582c8001 +patword 0x00b7 0xc0000000582c8801 +patword 0x00b8 0xc0000000582c8001 +patword 0x00b9 0xc0000000582c8801 +patword 0x00ba 0xc0000000582c8001 +patword 0x00bb 0xc0000000582c8801 +patword 0x00bc 0xc0000000582c8001 +patword 0x00bd 0xc0000000582c8801 +patword 0x00be 0xc0000000582c8001 +patword 0x00bf 0xc0000000582c8801 +patword 0x00c0 0xc0000000582c8001 +patword 0x00c1 0xc0000000582c8801 +patword 0x00c2 0xc0000000582c8001 +patword 0x00c3 0xc0000000582c8901 +patword 0x00c4 0xc0000000582c8001 +patword 0x00c5 0xc0000000582c8801 +patword 0x00c6 0xc0000000582c8001 +patword 0x00c7 0xc0000000582c8801 +patword 0x00c8 0xc0000000582c8001 +patword 0x00c9 0xc0000000582c8801 +patword 0x00ca 0xc0000000582c8001 +patword 0x00cb 0xc0000000582c8801 +patword 0x00cc 0xc0000000582c8001 +patword 0x00cd 0xc0000000582c8801 +patword 0x00ce 0xc0000000582c8001 +patword 0x00cf 0xc0000000582c8801 +patword 0x00d0 0xc0000000582c8001 +patword 0x00d1 0xc0000000582c8801 +patword 0x00d2 0xc0000000582c8001 +patword 0x00d3 0xc0000000582c8801 +patword 0x00d4 0xc0000000582c8001 +patword 0x00d5 0xc0000000582c8801 +patword 0x00d6 0xc0000000582c8001 +patword 0x00d7 0xc0000000582c8801 +patword 0x00d8 0xc0000000582c8001 +patword 0x00d9 0xc0000000582c8801 +patword 0x00da 0xc0000000582c8001 +patword 0x00db 0x00000000582c8001 +patword 0x00dc 0x00000000582c8001 +patword 0x00dd 0x00000000482c8901 +patword 0x00de 0x00000000482c8001 +patword 0x00df 0x00000000482c8901 +patword 0x00e0 0x00000000482c8001 +patword 0x00e1 0x000000006b2e8001 +patlimits 0x0000 0x00e1 +patioctrl 0x01000000fb7fdd55 +patloop 0 0x00a9 0x00da +patnloop 0 199 +patwait 0 0x0018 +patwaittime 0 800 diff --git a/pyctbgui/tests/gui/data/pattern.png b/pyctbgui/tests/gui/data/pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..1847bb5a409fa01f70ad98f70f0e3d08f4834d34 GIT binary patch literal 47247 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 Path(tmp_path / 'tmp.npz').stat().st_size + + +@pytest.mark.parametrize('compressed', [True, False]) +@pytest.mark.parametrize('isPath', [True, False]) +@pytest.mark.parametrize('deleteOriginals', [True, False]) +def test_delete_files(compressed, isPath, deleteOriginals, tmp_path): + data = { + 'arr1': np.ones((10, 5, 5)), + 'arr2': np.zeros((10, 5, 5), dtype=np.int32), + 'arr3': np.ones((10, 5, 5), dtype=np.float32) + } + filePaths = [tmp_path / (file + '.npy') for file in data.keys()] + for file in data: + np.save(tmp_path / (file + '.npy'), data[file]) + path = tmp_path / 'file.npz' + path = str(path) if isPath else path + NpzFileWriter.zipNpyFiles(path, + filePaths, + list(data.keys()), + deleteOriginals=deleteOriginals, + compressed=compressed) + if deleteOriginals: + for file in filePaths: + assert not Path.exists(file) + else: + for file in filePaths: + assert Path.exists(file) + + +def test_npz_read_frames(tmp_path): + rng = np.random.default_rng(seed=42) + arr1 = rng.random((10, 5, 5)) + + with NpzFileWriter(tmp_path / 'tmp.npz', 'w') as npz: + npz.writeArray('adc', arr1) + + with NpzFileWriter(tmp_path / 'tmp.npz', 'r') as npz: + frames = npz.readFrames('adc', 5, 8) + assert np.array_equal(frames, arr1[5:8]) + + +def test_file_modes(tmp_path): + rng = np.random.default_rng(seed=42) + arr1 = rng.random((10, 5, 5)) + + # check reopening with mode w + with NpzFileWriter(tmp_path / 'tmp.npz', 'w') as npz: + npz.writeArray('adc', arr1) + + with NpzFileWriter(tmp_path / 'tmp.npz', 'w') as npz: + assert npz.file.namelist() == [] + + # check reopening with mode x + with NpzFileWriter(tmp_path / 'tmp.npz', 'w') as npz: + npz.writeArray('adc', arr1) + + with pytest.raises(FileExistsError): + with NpzFileWriter(tmp_path / 'tmp.npz', 'x'): + pass + # check reopening with mode r + with NpzFileWriter(tmp_path / 'tmp.npz', 'r') as npz: + assert np.array_equal(npz.readFrames('adc', 4, 6), arr1[4:6]) + with pytest.raises(ValueError, match=r'write\(\) requires mode \'w\'\, \'x\'\, or \'a\''): + npz.writeArray('adc2', arr1) + + +@pytest.mark.filterwarnings('ignore::UserWarning') +def test_file_mode_a(tmp_path): + rng = np.random.default_rng(seed=42) + arr1 = rng.random((10, 5, 5)) + # check reopening with mode a + with NpzFileWriter(tmp_path / 'tmp.npz', 'w') as npz: + npz.writeArray('adc', arr1) + + with NpzFileWriter(tmp_path / 'tmp.npz', 'a') as npz: + npz.writeArray('adc2', arr1) + npz.writeArray('adc', arr1) + + +@pytest.mark.parametrize('compressed', [True, False]) +def test_get_item(compressed, tmp_path): + rng = np.random.default_rng(seed=42) + arr1 = rng.random((10, 5, 5)) + arr2 = rng.random((3, 2, 2)) + # check reopening with mode a + npz = NpzFileWriter(tmp_path / 'tmp.npz', 'w', compress_file=compressed) + npz.writeArray('adc1', arr1) + npz.writeArray('adc2', arr2) + npz.writeArray('adc3', arr1) + assert np.array_equal(npz['adc1'].read(3), arr1[:3]) + assert np.array_equal(npz['adc2'].read(1), arr2[:1]) + assert np.array_equal(npz['adc2'].read(1), arr2[1:2]) + assert np.array_equal(npz['adc2'].read(1), arr2[2:3]) + assert np.array_equal(npz['adc1'].read(3), arr1[3:6]) + assert np.array_equal(npz['adc1'].read(3), arr1[6:9]) + + +@pytest.mark.parametrize('compressed', [True, False]) +def test_namelist(compressed, tmp_path): + rng = np.random.default_rng(seed=42) + arr1 = rng.random((10, 5, 5)) + arr2 = rng.random((3, 2, 2)) + # check reopening with mode a + npz = NpzFileWriter(tmp_path / 'tmp.npz', 'w', compress_file=compressed) + npz.writeArray('adc1', arr1) + npz.writeArray('adc2', arr2) + npz.writeArray('adc3', arr1) + assert npz.namelist() == ['adc1', 'adc2', 'adc3']