From 46623c946a8249c58cbe7d2988560b42e90fa417 Mon Sep 17 00:00:00 2001 From: lrlunin Date: Thu, 9 Jul 2026 18:59:41 +0200 Subject: [PATCH] reworked pattern generator --- python/slsdet/PatternGenerator.py | 226 ---------------------- python/slsdet/__init__.py | 26 +-- python/slsdet/bits.py | 39 ++-- python/slsdet/pattern.py | 52 +++-- python/slsdet/pattern_generator.py | 215 ++++++++++++++++++++ python/src/pattern.cpp | 4 +- python/src/py_headers.h | 1 + slsDetectorSoftware/include/sls/Pattern.h | 5 +- slsDetectorSoftware/src/Pattern.cpp | 8 +- 9 files changed, 277 insertions(+), 299 deletions(-) delete mode 100644 python/slsdet/PatternGenerator.py create mode 100644 python/slsdet/pattern_generator.py diff --git a/python/slsdet/PatternGenerator.py b/python/slsdet/PatternGenerator.py deleted file mode 100644 index 74c61fc54..000000000 --- a/python/slsdet/PatternGenerator.py +++ /dev/null @@ -1,226 +0,0 @@ -from . import Detector, Pattern -from .bits import setbit, clearbit -import textwrap -from pathlib import Path - - -class PatternGenerator: - """ - Class to generate a pattern for the SLS detector. Intents to as closely as possible - mimic the old pattern generation in the C code. - """ - def __init__(self): - self.pattern = Pattern() - self.iaddr = 0 - - def SB(self, *bits): - """ - Set one or several bits. Change will take affect with the next PW. - """ - for bit in bits: - self.pattern.word[self.iaddr] = setbit(bit, self.pattern.word[self.iaddr]) - return self.pattern.word[self.iaddr] - - def CB(self, *bits): - """ - Clear one or several bits. Change will take affect with the next PW. - """ - for bit in bits: - self.pattern.word[self.iaddr] = clearbit(bit, self.pattern.word[self.iaddr]) - return self.pattern.word[self.iaddr] - - - def _pw(self, verbose = False): - if verbose: - print(f'{self.iaddr:#06x} {self.pattern.word[self.iaddr]:#018x}') - - #Limits are inclusive so we need to increment the address before writing the next word - self.pattern.limits[1] = self.iaddr - self.iaddr += 1 - self.pattern.word[self.iaddr] = self.pattern.word[self.iaddr-1] - - def PW(self, x = 1, verbose = False): - for i in range(x): - self._pw(verbose) - - # def REPEAT(self, x, verbose = False): - # for i in range(x): - # self._pw(verbose) - - # def PW2(self, verbose = 0): - # self.REPEAT(2, verbose) - - - def CLOCKS(self, bit, times = 1, length = 1, verbose = False): - """ - clocks "bit" n "times", every half clock is long "length" - length is optional, default value is 1 - """ - for i in range(0, times): - self.SB(bit); self.PW(length, verbose) - self.CB(bit); self.PW(length, verbose) - - def CLOCK(self, bit, length = 1, verbose = 0): - self.CLOCKS(bit, 1, length ,verbose) - - def serializer(self, value, serInBit, clkBit, nbits, msbfirst = True, length = 1): - """serializer(value,serInBit,clkBit,nbits,msbfirst=1,length=1) - Produces the .pat file needed to serialize a word into a shift register. - value: value to be serialized - serInBit: control bit corresponding to serial in - clkBit: control bit corresponding to the clock - nbits: number of bits of the target register to load - msbfirst: if 1 pushes in the MSB first (default), - if 0 pushes in the LSB first - length: length of all the PWs in the pattern - It produces no output because it modifies directly the members of the class pat via SB and CB""" - - c = value - self.CB(serInBit, clkBit) - self.PW(length) #generate initial line with clk and serIn to 0 - - start = 0 - stop = nbits - step = 1 - - if msbfirst: - start = nbits - 1 - stop = -1 - step =- 1 #reverts loop if msb has to be pushed in first - - for i in range(start, stop, step): - if c & (1< 0: - self.pattern.word[n] = self.pattern.word[n-1] - - - def send_to_detector(self, det): - """ - Load the pattern into the detector. - """ - det.setPattern(self.pattern) \ No newline at end of file diff --git a/python/slsdet/__init__.py b/python/slsdet/__init__.py index f08e8acab..d5fad44ab 100755 --- a/python/slsdet/__init__.py +++ b/python/slsdet/__init__.py @@ -1,6 +1,8 @@ # SPDX-License-Identifier: LGPL-3.0-or-other # Copyright (C) 2021 Contributors to the SLS Detector Package -# from .detector import Detector, DetectorError, free_shared_memory +from importlib.metadata import PackageNotFoundError, version +from pathlib import Path + from .eiger import Eiger from .ctb import Ctb from .dacs import NamedDacs, DetectorDacs, Dac @@ -12,7 +14,7 @@ from .gotthard2 import Gotthard2 from .moench import Moench from .pattern import Pattern, patternParameters from .gaincaps import Mythen3GainCapsWrapper -from .PatternGenerator import PatternGenerator +from .pattern_generator import PatternGenerator from . import _slsdet from ._slsdet import freeSharedMemory, getUserDetails @@ -20,7 +22,7 @@ from ._slsdet import freeSharedMemory, getUserDetails xy = _slsdet.xy defs = _slsdet.slsDetectorDefs -#Make enums and #defines available at top level +# Make enums and #defines available at top level from .enums import * from .defines import * @@ -38,17 +40,7 @@ Hz = _slsdet.Hz kHz = _slsdet.kHz MHz = _slsdet.MHz -import os -def read_version(): - try: - version_file = os.path.join(os.path.dirname(__file__), 'VERSION') - with open(version_file, "r") as f: - return f.read().strip() - except: - raise RuntimeError("VERSION file not found in slsdet package from init.py") - -__version__ = read_version() - - - - +try: + __version__ = version("slsdet") +except PackageNotFoundError: + __version__ = Path(__file__).parent.joinpath("VERSION").read_text().strip() diff --git a/python/slsdet/bits.py b/python/slsdet/bits.py index 5ccff764f..27006b78e 100644 --- a/python/slsdet/bits.py +++ b/python/slsdet/bits.py @@ -1,31 +1,26 @@ import numpy as np +def _bitmask(bit, word): + dtype = word.dtype if hasattr(word, 'dtype') else np.uint64 + if bit >= np.iinfo(dtype).bits: + raise ValueError(f"bit {bit} out of range for {np.dtype(dtype).name}") + return np.dtype(dtype).type(1 << bit) + def setbit(bit, word): - if isinstance(word, np.generic): - mask = word.dtype.type(1) - mask = mask << bit - else: - mask = 1 << bit - return word | mask - - -def setbit_arr(bit, arr): - arr |= arr.dtype.type(1 << bit) - + """ + Set the bit at position bit in word(s). + """ + return word | _bitmask(bit, word) def clearbit(bit, word): """ - Clear the bit at position bit in word. - Two paths to avoid converting the types. + Clear the bit at position bit in word(s). """ - if isinstance(word, np.generic): - mask = word.dtype.type(1) - mask = ~(mask << bit) - else: - mask = ~(1 << bit) - return word & mask + return word & ~_bitmask(bit, word) - -def clearbit_arr(bit, arr): - arr &= arr.dtype.type(~(1 << bit)) \ No newline at end of file +def flipbit(bit, word): + """ + Flip the bit at position bit in word(s). + """ + return word ^ _bitmask(bit, word) \ No newline at end of file diff --git a/python/slsdet/pattern.py b/python/slsdet/pattern.py index 273865c65..14060b4b9 100644 --- a/python/slsdet/pattern.py +++ b/python/slsdet/pattern.py @@ -5,11 +5,14 @@ from . import _slsdet from ._slsdet import Pattern -class patternParameters(_slsdet.patternParameters): - def __init__(self): - super().__init__() - self.view = self.numpy_view() - self.names = self.view.dtype.names +class _StructViewMixin: + """ + Exposes the fields of a numpy structured dtype view as attributes. + """ + + def _init_view(self, view): + self.__dict__["view"] = view + self.__dict__["names"] = view.dtype.names def __getattr__(self, name): if name in self.names: @@ -18,9 +21,7 @@ class patternParameters(_slsdet.patternParameters): raise KeyError(f"Key: {name} not found") def __setattr__(self, name, value): - if name in ["view", "names"]: - self.__dict__[name] = value - elif name in self.names: + if name in self.names: self.view[name] = value else: raise KeyError(f"Key: {name} not found") @@ -29,27 +30,24 @@ class patternParameters(_slsdet.patternParameters): def __dir__(self): return self.names + def copy(self): + """ + Return a new instance with the same field values. + """ + new = type(self)() + new.view[:] = self.view + return new -class Pattern(_slsdet.Pattern): + __copy__ = copy + + +class patternParameters(_StructViewMixin, _slsdet.patternParameters): def __init__(self): super().__init__() - self.view = self.data().numpy_view() - self.names = self.view.dtype.names + self._init_view(self.numpy_view()) - def __getattr__(self, name): - if name in self.names: - return self.view[name][0] - else: - raise KeyError(f"Key: {name} not found") - def __setattr__(self, name, value): - if name in ["view", "names"]: - self.__dict__[name] = value - elif name in self.names: - self.view[name] = value - else: - raise KeyError(f"Key: {name} not found") - - # Provide custom dir for tab completion - def __dir__(self): - return self.names \ No newline at end of file +class Pattern(_StructViewMixin, _slsdet.Pattern): + def __init__(self): + super().__init__() + self._init_view(self.data().numpy_view()) \ No newline at end of file diff --git a/python/slsdet/pattern_generator.py b/python/slsdet/pattern_generator.py new file mode 100644 index 000000000..6961e97eb --- /dev/null +++ b/python/slsdet/pattern_generator.py @@ -0,0 +1,215 @@ +from . import Pattern +from .bits import setbit, clearbit, flipbit +import textwrap + +class PatternGenerator: + """ + Class to generate a pattern for the SLS detector. Intents to as closely as possible + mimic the old pattern generation in the C code. + """ + def __init__(self, verbose = False): + self.pattern = Pattern() + self.iaddr = 0 + self.verbose = verbose + + def clear_pattern(self): + """ + Clear the pattern and reset the address to 0. + """ + self.pattern[:] = 0 + self.iaddr = 0 + + def SB(self, *bits): + """ + Set one or multiple bits. Change will take affect with the next PW. + """ + for bit in bits: + self.pattern.word[self.iaddr] = setbit(bit, self.pattern.word[self.iaddr]) + return self.pattern.word[self.iaddr] + + def CB(self, *bits): + """ + Clear one or multiple bits. Change will take affect with the next PW. + """ + for bit in bits: + self.pattern.word[self.iaddr] = clearbit(bit, self.pattern.word[self.iaddr]) + return self.pattern.word[self.iaddr] + + def FB(self, *bits): + """ + Flip one or multiple bits. Change will take affect with the next PW. + """ + for bit in bits: + self.pattern.word[self.iaddr] = flipbit(bit, self.pattern.word[self.iaddr]) + return self.pattern.word[self.iaddr] + + def _pw(self): + if self.verbose: + print(f'{self.iaddr:#06x} {self.pattern.word[self.iaddr]:#018x}') + + # Increment the address before the next word since limits are inclusive + self.pattern.limits[1] = self.iaddr + self.iaddr += 1 + self.pattern.word[self.iaddr] = self.pattern.word[self.iaddr - 1] + + def PW(self, x = 1): + for _ in range(x): + self._pw() + + def CLOCKS(self, bits, repeats = 1, clock_duration = 1): + """ + Generate clock pulses on the specified bits. + + Parameters + ---------- + bit_mask : int + Bitmask selecting which clock line(s) to pulse. + repeats : int, optional + Number of full clock cycles to generate (default 1). + half_period : int, optional + Hold time for each half of the cycle (default 1). + verbose : bool, optional + If True, print timing/debug info during the wait (default False). + """ + for _ in range(repeats): + self.FB(*bits) + self.PW(clock_duration) + self.FB(*bits) + self.PW(clock_duration) + + def serializer(self, value, ser_in_bit, clk_bit, nbits, msb_first = True, length = 1): + """ + Serialize `value` into a shift register via ser_in_bit/clk_bit. + + Parameters + ---------- + value : int + Value to serialize. + ser_in_bit : int + Control bit corresponding to serial in. + clk_bit : int + Control bit corresponding to the clock. + nbits : int + Number of bits of the target register to load. + msb_first : bool, optional + Push in the MSB first if True (default), else LSB first. + length : int, optional + Duration of each PW in the pattern (default 1). + """ + bit_order = range(nbits - 1, -1, -1) if msb_first else range(nbits) + + self.CB(ser_in_bit, clk_bit) + self.PW(length) # initial line with clk and serIn low + + for i in bit_order: + if value & (1 << i): + self.SB(ser_in_bit) + else: + self.CB(ser_in_bit) + self.PW(length) + self.SB(clk_bit) + self.PW(length) + self.CB(clk_bit) + self.PW(length) + + self.CB(ser_in_bit, clk_bit) + self.PW(length) # final line with clk and serIn low + + + #NOT IMPLEMENTED YET + #TODO! What should setstop do? Or can we remove it? + #def setstop(): + + def setoutput(self, *bits): + for bit in bits: + self.pattern.ioctrl = setbit(bit, self.pattern.ioctrl) + + def setinput(self, *bits): + for bit in bits: + self.pattern.ioctrl = clearbit(bit, self.pattern.ioctrl) + + #TODO! What should setclk do? Or can we remove it? + # def setclk(bit): + # self.clkctrl=self.setbit(bit,self.clkctrl) + + #def setclks(self, *args): + # for i in args: + # self.setclk(i) + + def setnloop(self, i, reps): + self.pattern.nloop[i] = reps + + def setstartloop(self, i): + """ + Set startloop[i] to the current address. + """ + self.pattern.startloop[i] = self.iaddr + + def setstoploop(self, i): + """ + Set stoploop[i] to the current address. + """ + self.pattern.stoploop[i] = self.iaddr + + def setstart(self): + """ + Set start of pattern to the current address. + """ + self.pattern.limits[0] = self.iaddr + + def setstop(self): + """ + Set stop of pattern to the current address. + """ + self.pattern.limits[1] = self.iaddr + + def setwaitpoint(self, i): + """ + Set wait[i] to the current address. + """ + self.pattern.wait[i] = self.iaddr + + def setwaittime(self, i, t): + """ + Set waittime[i] to t. + """ + self.pattern.waittime[i] = t + + def setwait(self, i, t): + """ + Set wait[i] to the current address and waittime[i] to t. + """ + self.setwait(i) + self.setwaittime(i, t) + + def __repr__(self): + return textwrap.dedent(f"""\ + PatternGenerator: + patlimits: {self.pattern.limits} + startloop: {self.pattern.startloop} + stoploop: {self.pattern.stoploop} + nloop: {self.pattern.nloop} + wait: {self.pattern.wait} + waittime: {self.pattern.waittime}""") + + def __str__(self): + return self.pattern.str() + + def export_pattern(self): + """ + Generate the pattern and return it as a Pattern object. + """ + return self.pattern.copy() + + def load_pattern(self, fname): + """Load pattern from text file""" + iaddr = self.pattern.load(fname) + + # Assume an empty pattern if the first and only word is zero + if iaddr == 1 and self.pattern.word[0] == 0: + iaddr = 0 + self.iaddr = iaddr + + # Set (last + 1) word to the last word + if iaddr > 0: + self.pattern.word[iaddr] = self.pattern.word[iaddr - 1] \ No newline at end of file diff --git a/python/src/pattern.cpp b/python/src/pattern.cpp index 67447fff3..366d8696a 100644 --- a/python/src/pattern.cpp +++ b/python/src/pattern.cpp @@ -25,5 +25,7 @@ void init_pattern(py::module &m) { .def("save", &sls::Pattern::save) .def("str", &sls::Pattern::str) .def("data", (pat * (sls::Pattern::*)()) & sls::Pattern::data, - py::return_value_policy::reference); + py::return_value_policy::reference) + .def(py::self == py::self) + .def(py::self != py::self); } diff --git a/python/src/py_headers.h b/python/src/py_headers.h index 40368b010..afb79415d 100644 --- a/python/src/py_headers.h +++ b/python/src/py_headers.h @@ -10,5 +10,6 @@ ODR warnings #include #include #include +#include #include #include "typecaster.h" diff --git a/slsDetectorSoftware/include/sls/Pattern.h b/slsDetectorSoftware/include/sls/Pattern.h index 90d6aae12..ad3617cae 100644 --- a/slsDetectorSoftware/include/sls/Pattern.h +++ b/slsDetectorSoftware/include/sls/Pattern.h @@ -4,6 +4,7 @@ #include "sls/sls_detector_defs.h" #ifdef __cplusplus +#include #include namespace sls { #endif @@ -35,8 +36,8 @@ class Pattern { patternParameters *data() const; size_t size() const noexcept { return sizeof(patternParameters); } void validate() const; - size_t load(const std::string &fname); - void save(const std::string &fname); + size_t load(const std::filesystem::path &fname); + void save(const std::filesystem::path &fname); std::string str() const; }; diff --git a/slsDetectorSoftware/src/Pattern.cpp b/slsDetectorSoftware/src/Pattern.cpp index 1a5f7b80c..30c487a2b 100644 --- a/slsDetectorSoftware/src/Pattern.cpp +++ b/slsDetectorSoftware/src/Pattern.cpp @@ -93,11 +93,11 @@ void Pattern::validate() const { } } -size_t Pattern::load(const std::string &fname) { +size_t Pattern::load(const std::filesystem::path &fname) { size_t numPatWords = 0; std::ifstream input_file(fname); if (!input_file) { - throw RuntimeError("Could not open pattern file " + fname + + throw RuntimeError("Could not open pattern file " + fname.string() + " for reading"); } for (std::string line; std::getline(input_file, line);) { @@ -276,10 +276,10 @@ std::ostream &Pattern::stream(std::ostream &os) const { return os; } -void Pattern::save(const std::string &fname) { +void Pattern::save(const std::filesystem::path &fname) { std::ofstream output_file(fname); if (!output_file) { - throw RuntimeError("Could not open pattern file " + fname + + throw RuntimeError("Could not open pattern file " + fname.string() + " for writing"); } stream(output_file);