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 <erik.frojdh@gmail.com>

* 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 <erik.frojdh@gmail.com>
Co-authored-by: Braham Bechir <braham_b@pc11979.psi.ch>
Co-authored-by: Bechir <bechir.braham@psi.ch>
Co-authored-by: Bechir <bechir.brahem420@gmail.com>
This commit is contained in:
2024-09-10 16:00:04 +02:00
committed by GitHub
parent c6477e0ed6
commit 5b61ff24bb
93 changed files with 25399 additions and 7292 deletions

View File

View File

@@ -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.<br><br>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.")

View File

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

View File

@@ -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

View File

@@ -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

View File

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

View File

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

View File

@@ -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]
```

View File

@@ -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

View File

@@ -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

View File

@@ -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