mirror of
https://github.com/slsdetectorgroup/slsDetectorPackage.git
synced 2026-11-09 18:33:53 +01:00
Merge branch 'developer' into dev/mac-build
This commit is contained in:
67 files changed
+29667
-488
No files matched your search
@@ -16,8 +16,6 @@
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/js/theme.js"></script>
|
||||
<link rel="index" title="Index" href="genindex.html" />
|
||||
<link rel="search" title="Search" href="search.html" />
|
||||
<link rel="next" title="10.0.0" href="https://slsdetectorgroup.github.io/slsDetectorPackage/10.0.0/index.html" />
|
||||
</head>
|
||||
|
||||
@@ -32,13 +30,6 @@
|
||||
<a href="#" class="icon icon-home">
|
||||
slsDetectorPackage
|
||||
</a>
|
||||
<div role="search">
|
||||
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
|
||||
<input type="text" name="q" placeholder="Search docs" aria-label="Search docs" />
|
||||
<input type="hidden" name="check_keywords" value="yes" />
|
||||
<input type="hidden" name="area" value="default" />
|
||||
</form>
|
||||
</div>
|
||||
</div><div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
|
||||
<p class="caption" role="heading"><span class="caption-text">Versions:</span></p>
|
||||
<ul>
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/js/theme.js"></script>
|
||||
<link rel="index" title="Index" href="genindex.html" />
|
||||
<link rel="search" title="Search" href="search.html" />
|
||||
<link rel="next" title="{{ versions[0]["version"] }}" href="https://slsdetectorgroup.github.io/slsDetectorPackage/{{ versions[0]["version"] }}/index.html" />
|
||||
</head>
|
||||
|
||||
@@ -32,13 +30,6 @@
|
||||
<a href="#" class="icon icon-home">
|
||||
slsDetectorPackage
|
||||
</a>
|
||||
<div role="search">
|
||||
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
|
||||
<input type="text" name="q" placeholder="Search docs" aria-label="Search docs" />
|
||||
<input type="hidden" name="check_keywords" value="yes" />
|
||||
<input type="hidden" name="area" value="default" />
|
||||
</form>
|
||||
</div>
|
||||
</div><div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
|
||||
<p class="caption" role="heading"><span class="caption-text">Versions:</span></p>
|
||||
<ul>
|
||||
|
||||
@@ -179,9 +179,8 @@ class AdcTab(QtWidgets.QWidget):
|
||||
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)
|
||||
self.mainWindow.analog_frame = self._processImageData(data, aSamples, self.mainWindow.nADCEnabled)
|
||||
self.mainWindow.plotAnalogImage.setImage(self.mainWindow.analog_frame, autoLevels=False) # manually update image levels
|
||||
except Exception:
|
||||
self.logger.exception('Exception Caught')
|
||||
self.mainWindow.statusbar.setStyleSheet("color:red")
|
||||
@@ -193,8 +192,6 @@ class AdcTab(QtWidgets.QWidget):
|
||||
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
|
||||
|
||||
@@ -133,7 +133,7 @@ class AcquisitionTab(QtWidgets.QWidget):
|
||||
self.view.labelDigital.setDisabled(True)
|
||||
self.view.labelTransceiver.setDisabled(True)
|
||||
self.view.spinBoxTransceiver.setDisabled(True)
|
||||
self.plotTab.enableallCounterCheckBox(False)
|
||||
self.plotTab.showallCounterCheckBox(False)
|
||||
case readoutMode.DIGITAL_ONLY:
|
||||
self.view.spinBoxAnalog.setDisabled(True)
|
||||
self.view.labelAnalog.setDisabled(True)
|
||||
@@ -141,7 +141,7 @@ class AcquisitionTab(QtWidgets.QWidget):
|
||||
self.view.labelDigital.setEnabled(True)
|
||||
self.view.labelTransceiver.setDisabled(True)
|
||||
self.view.spinBoxTransceiver.setDisabled(True)
|
||||
self.plotTab.enableallCounterCheckBox(False)
|
||||
self.plotTab.showallCounterCheckBox(False)
|
||||
case readoutMode.ANALOG_AND_DIGITAL:
|
||||
self.view.spinBoxAnalog.setEnabled(True)
|
||||
self.view.labelAnalog.setEnabled(True)
|
||||
@@ -149,7 +149,7 @@ class AcquisitionTab(QtWidgets.QWidget):
|
||||
self.view.labelDigital.setEnabled(True)
|
||||
self.view.labelTransceiver.setDisabled(True)
|
||||
self.view.spinBoxTransceiver.setDisabled(True)
|
||||
self.plotTab.enableallCounterCheckBox(False)
|
||||
self.plotTab.showallCounterCheckBox(False)
|
||||
case readoutMode.TRANSCEIVER_ONLY:
|
||||
self.view.spinBoxAnalog.setDisabled(True)
|
||||
self.view.labelAnalog.setDisabled(True)
|
||||
@@ -157,7 +157,7 @@ class AcquisitionTab(QtWidgets.QWidget):
|
||||
self.view.labelDigital.setDisabled(True)
|
||||
self.view.labelTransceiver.setEnabled(True)
|
||||
self.view.spinBoxTransceiver.setEnabled(True)
|
||||
self.plotTab.enableallCounterCheckBox(self.plotTab.view.radioButtonImage.isChecked()) # enable counter checkboxes for matterhorn
|
||||
self.plotTab.showallCounterCheckBox(self.plotTab.view.radioButtonImage.isChecked()) # enable counter checkboxes for matterhorn
|
||||
case _:
|
||||
self.view.spinBoxAnalog.setDisabled(True)
|
||||
self.view.labelAnalog.setDisabled(True)
|
||||
@@ -165,7 +165,7 @@ class AcquisitionTab(QtWidgets.QWidget):
|
||||
self.view.labelDigital.setEnabled(True)
|
||||
self.view.labelTransceiver.setEnabled(True)
|
||||
self.view.spinBoxTransceiver.setEnabled(True)
|
||||
self.plotTab.enableallCounterCheckBox(self.plotTab.view.radioButtonImage.isChecked()) # enable counter checkboxes for matterhorn
|
||||
self.plotTab.showallCounterCheckBox(self.plotTab.view.radioButtonImage.isChecked()) # enable counter checkboxes for matterhorn
|
||||
|
||||
self.view.comboBoxROMode.currentIndexChanged.connect(self.setReadOut)
|
||||
self.view.spinBoxAnalog.editingFinished.connect(self.setAnalog)
|
||||
|
||||
+258
-108
@@ -21,8 +21,6 @@ class PlotTab(QtWidgets.QWidget):
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
pg.setConfigOptions(imageAxisOrder="row-major")
|
||||
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
|
||||
@@ -31,11 +29,27 @@ class PlotTab(QtWidgets.QWidget):
|
||||
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] = []
|
||||
|
||||
self.shownCounterImages: list[bool] = [True] * Defines.max_counters # per default show all 4 counter images
|
||||
|
||||
self.showtransceiverImage : bool = False
|
||||
self.showdigitalImage : bool = False
|
||||
self.showanalogImage : bool = False
|
||||
|
||||
# color range variables
|
||||
self.colorRangeMode: Defines.colorRange = Defines.colorRange.auto
|
||||
|
||||
self.digitalcolorrange : list[float] = [0.0, 0.0]
|
||||
self.analogcolorrange : list[float] = [0.0, 0.0]
|
||||
self.transceivercolorrange : list[list[float]] = [[0.0, 0.0] for _ in range(Defines.max_counters)]
|
||||
|
||||
self.digitalcminSpinBoxes : QtWidgets.QSpinBox = None
|
||||
self.digitalcmaxSpinBoxes : QtWidgets.QSpinBox = None
|
||||
self.analogcminSpinBoxes : QtWidgets.QSpinBox = None
|
||||
self.analogcmaxSpinBoxes : QtWidgets.QSpinBox = None
|
||||
self.transceivercminSpinBoxes : list[QtWidgets.QSpinBox] = None
|
||||
self.transceivercmaxSpinBoxes : list[QtWidgets.QSpinBox] = None
|
||||
|
||||
# list of callback functions to notify tabs when we should hide their legend
|
||||
# follows the observer design pattern
|
||||
self.hideLegendObservers = []
|
||||
@@ -56,14 +70,16 @@ class PlotTab(QtWidgets.QWidget):
|
||||
self.checkBoxCounters = [self.view.checkBoxCounter0, self.view.checkBoxCounter1, self.view.checkBoxCounter2, self.view.checkBoxCounter3]
|
||||
|
||||
for checkBox in self.checkBoxCounters:
|
||||
checkBox.setChecked(False)
|
||||
checkBox.setChecked(True) # per default enable all
|
||||
checkBox.setEnabled(False)
|
||||
|
||||
self.imagePlots = (
|
||||
self.mainWindow.plotAnalogImage,
|
||||
self.mainWindow.plotDigitalImage,
|
||||
self.transceiverTab.transceiverImageViews,
|
||||
)
|
||||
self.transceivercminSpinBoxes = [self.view.transceivercminSpinBox0, self.view.transceivercminSpinBox1, self.view.transceivercminSpinBox2, self.view.transceivercminSpinBox3]
|
||||
self.transceivercmaxSpinBoxes = [self.view.transceivercmaxSpinBox0, self.view.transceivercmaxSpinBox1, self.view.transceivercmaxSpinBox2, self.view.transceivercmaxSpinBox3]
|
||||
self.analogcminSpinBoxes = self.view.analogcminSpinBox
|
||||
self.analogcmaxSpinBoxes = self.view.analogcmaxSpinBox
|
||||
self.digitalcminSpinBoxes = self.view.digitalcminSpinBox
|
||||
self.digitalcmaxSpinBoxes = self.view.digitalcmaxSpinBox
|
||||
|
||||
|
||||
def connect_ui(self):
|
||||
self.view.radioButtonNoPlot.clicked.connect(self.plotOptions)
|
||||
@@ -95,14 +111,24 @@ class PlotTab(QtWidgets.QWidget):
|
||||
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.radioButtonAutomatic.clicked.connect(partial(self.setColorRangeMode, Defines.colorRange.auto))
|
||||
self.view.radioButtonFixed.clicked.connect(partial(self.setColorRangeMode, Defines.colorRange.fixed))
|
||||
self.view.radioButtonCenter.clicked.connect(partial(self.setColorRangeMode, Defines.colorRange.center))
|
||||
|
||||
for index, cminSpinBox in enumerate(self.transceivercminSpinBoxes):
|
||||
cminSpinBox.editingFinished.connect(partial(self.setCmin, imagetype = Defines.ImageType.TRANSCEIVER, index=index))
|
||||
|
||||
for index, cmaxSpinBox in enumerate(self.transceivercmaxSpinBoxes):
|
||||
cmaxSpinBox.editingFinished.connect(partial(self.setCmax, imagetype = Defines.ImageType.TRANSCEIVER, index=index))
|
||||
|
||||
self.analogcminSpinBoxes.editingFinished.connect(partial(self.setCmin, imagetype = Defines.ImageType.ANALOG, index=0))
|
||||
self.analogcmaxSpinBoxes.editingFinished.connect(partial(self.setCmax, imagetype = Defines.ImageType.ANALOG, index=0))
|
||||
self.digitalcminSpinBoxes.editingFinished.connect(partial(self.setCmin, imagetype = Defines.ImageType.DIGITAL, index=0))
|
||||
self.digitalcmaxSpinBoxes.editingFinished.connect(partial(self.setCmax, imagetype = Defines.ImageType.DIGITAL, index=0))
|
||||
|
||||
for index, checkBox in enumerate(self.checkBoxCounters):
|
||||
checkBox.stateChanged.connect(partial(self.displayCounter, index))
|
||||
checkBox.stateChanged.connect(lambda state, index=index: self.setCounterCheckBox(index, is_checked=(state == QtCore.Qt.Checked)))
|
||||
|
||||
|
||||
# show image Values for analog image
|
||||
nMaxY = lambda : self.mainWindow.nAnalogRows
|
||||
@@ -110,7 +136,9 @@ class PlotTab(QtWidgets.QWidget):
|
||||
frame = lambda : self.mainWindow.analog_frame
|
||||
plot = self.mainWindow.plotAnalogImage
|
||||
plot.scene.sigMouseMoved.connect(partial(self.showPlotValues, plot, nMaxX, nMaxY, frame))
|
||||
plot.getHistogramWidget().item.sigLevelChangeFinished.connect(partial(self.handleHistogramChange, plot))
|
||||
plot.getImageItem().sigImageChanged.connect(partial(self.updateColorRange, imagetype = Defines.ImageType.ANALOG, plot=plot))
|
||||
plot.getHistogramWidget().item.sigLevelChangeFinished.connect(lambda _histItem, plot=plot: self.handleHistogramChange(plot, imagetype=Defines.ImageType.ANALOG))
|
||||
|
||||
|
||||
# show image Values for digital image
|
||||
nMaxY = lambda : self.mainWindow.nDigitalRows
|
||||
@@ -118,7 +146,9 @@ class PlotTab(QtWidgets.QWidget):
|
||||
frame = lambda : self.mainWindow.digital_frame
|
||||
plot = self.mainWindow.plotDigitalImage
|
||||
plot.scene.sigMouseMoved.connect(partial(self.showPlotValues, plot, nMaxX, nMaxY, frame))
|
||||
plot.getHistogramWidget().item.sigLevelChangeFinished.connect(partial(self.handleHistogramChange, plot))
|
||||
plot.getImageItem().sigImageChanged.connect(partial(self.updateColorRange, imagetype = Defines.ImageType.DIGITAL, plot=plot))
|
||||
plot.getHistogramWidget().item.sigLevelChangeFinished.connect(lambda _histItem, plot=plot: self.handleHistogramChange(plot, imagetype=Defines.ImageType.DIGITAL))
|
||||
|
||||
|
||||
# show image Values for transceiver image
|
||||
nMaxY = lambda : self.transceiverTab.nTransceiverRows
|
||||
@@ -127,28 +157,39 @@ class PlotTab(QtWidgets.QWidget):
|
||||
for index, image_view in enumerate(self.transceiverTab.transceiverImageViews):
|
||||
frame = lambda image_view=image_view: image_view.getImageItem().image
|
||||
image_view.scene.sigMouseMoved.connect(partial(self.showPlotValues, image_view, nMaxX, nMaxY, frame))
|
||||
image_view.getHistogramWidget().item.sigLevelChangeFinished.connect(partial(self.handleHistogramChange, image_view))
|
||||
image_view.getImageItem().sigImageChanged.connect(partial(self.updateColorRange, imagetype = Defines.ImageType.TRANSCEIVER, plot=image_view, index=index))
|
||||
|
||||
image_view.getHistogramWidget().item.sigLevelChangeFinished.connect(
|
||||
lambda _histItem, image_view=image_view, index=index: self.handleHistogramChange(plot = image_view, imagetype=Defines.ImageType.TRANSCEIVER, index=index)) # sigLevelChangeFinished passes the histogram item as the first argument, we need to ignore it and pass the image_view instead
|
||||
|
||||
self.view.checkBoxShowLegend.stateChanged.connect(self.toggleLegend)
|
||||
|
||||
|
||||
def displayCounter(self, index : int, state : int):
|
||||
# toggle the display of the counter i and update the splitter
|
||||
self.transceiverTab.shownCounters[index] = (state == QtCore.Qt.Checked)
|
||||
def setCounterCheckBox(self, index : int, is_checked : bool):
|
||||
"""
|
||||
slot function for the counter checkboxes, enables/disables the cmin/cmax spinboxes for the counter at index, updates image splitter to show counter image
|
||||
"""
|
||||
self.transceivercmaxSpinBoxes[index].setEnabled(is_checked and self.colorRangeMode == Defines.colorRange.fixed)
|
||||
self.transceivercminSpinBoxes[index].setEnabled(is_checked and self.colorRangeMode == Defines.colorRange.fixed)
|
||||
self.shownCounterImages[index] = is_checked # TODO: maybe I dont need that
|
||||
self.transceiverTab.update_ImageSplitter()
|
||||
|
||||
def enableCounterCheckBox(self, index : int, enabled : bool):
|
||||
self.checkBoxCounters[index].setEnabled(enabled)
|
||||
is_checked = self.checkBoxCounters[index].isChecked()
|
||||
self.transceivercmaxSpinBoxes[index].setEnabled(enabled and is_checked and self.colorRangeMode == Defines.colorRange.fixed)
|
||||
self.transceivercminSpinBoxes[index].setEnabled(enabled and is_checked and self.colorRangeMode == Defines.colorRange.fixed)
|
||||
|
||||
def setCounterCheckBox(self, index : int, check : bool):
|
||||
self.checkBoxCounters[index].setChecked(check)
|
||||
|
||||
def enableCounterCheckBox(self, index : int, enabled : bool):
|
||||
is_transceiver = self.mainWindow.romode.value in [3, 4]
|
||||
is_image = self.view.radioButtonImage.isChecked()
|
||||
self.checkBoxCounters[index].setEnabled(enabled and is_transceiver and is_image)
|
||||
|
||||
def enableallCounterCheckBox(self, enabled : bool):
|
||||
def showallCounterCheckBox(self, enabled : bool):
|
||||
for checkBox in self.checkBoxCounters:
|
||||
checkBox.setEnabled(enabled)
|
||||
checkBox.setVisible(enabled)
|
||||
|
||||
self.view.labelCounters.setVisible(enabled)
|
||||
|
||||
for cminSpinBox, cmaxSpinBox in zip(self.transceivercminSpinBoxes, self.transceivercmaxSpinBoxes):
|
||||
cminSpinBox.setEnabled(enabled and self.colorRangeMode == Defines.colorRange.fixed and self.checkBoxCounters[self.transceivercminSpinBoxes.index(cminSpinBox)].isChecked())
|
||||
cmaxSpinBox.setEnabled(enabled and self.colorRangeMode == Defines.colorRange.fixed and self.checkBoxCounters[self.transceivercmaxSpinBoxes.index(cmaxSpinBox)].isChecked())
|
||||
|
||||
def refresh(self):
|
||||
self.getZMQHWM()
|
||||
|
||||
@@ -231,29 +272,81 @@ class PlotTab(QtWidgets.QWidget):
|
||||
for notify_function in self.hideLegendObservers:
|
||||
notify_function()
|
||||
|
||||
def setCmin(self, value=None):
|
||||
def setCmin(self, imagetype : Defines.ImageType, value = None, index = 0, updateColorBar=True):
|
||||
"""
|
||||
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()
|
||||
Set the minimum color range value
|
||||
|
||||
def setCmax(self, value=None):
|
||||
Parameters
|
||||
----------
|
||||
imagetype : Defines.ImageType
|
||||
The type of image (DIGITAL, ANALOG, or TRANSCEIVER).
|
||||
value : float, optional
|
||||
The value to set for the minimum color range (default is None).
|
||||
index : int, optional
|
||||
The index of the counter (default is 0).
|
||||
updateColorBar : bool, optional
|
||||
Whether to update the color bar (default is True).
|
||||
"""
|
||||
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):
|
||||
if value is None:
|
||||
if imagetype == Defines.ImageType.DIGITAL:
|
||||
self.digitalcolorrange[0] = self.digitalcminSpinBoxes.value()
|
||||
elif imagetype == Defines.ImageType.ANALOG:
|
||||
self.analogcolorrange[0] = self.analogcminSpinBoxes.value()
|
||||
else:
|
||||
self.transceivercolorrange[index][0] = self.transceivercminSpinBoxes[index].value()
|
||||
else:
|
||||
if imagetype == Defines.ImageType.DIGITAL:
|
||||
self.digitalcolorrange[0] = value
|
||||
self.digitalcminSpinBoxes.setValue(self.digitalcolorrange[0])
|
||||
elif imagetype == Defines.ImageType.ANALOG:
|
||||
self.analogcolorrange[0] = value
|
||||
self.analogcminSpinBoxes.setValue(self.analogcolorrange[0])
|
||||
else:
|
||||
self.transceivercolorrange[index][0] = value
|
||||
self.transceivercminSpinBoxes[index].setValue(self.transceivercolorrange[index][0])
|
||||
|
||||
if updateColorBar:
|
||||
self.updateColorBar(imagetype = imagetype, index=index)
|
||||
|
||||
def setCmax(self, imagetype : Defines.ImageType, value=None, index = 0, updateColorBar=True):
|
||||
"""
|
||||
Set the maximum color range value
|
||||
|
||||
Parameters
|
||||
----------
|
||||
imagetype : Defines.ImageType
|
||||
The type of image (DIGITAL, ANALOG, or TRANSCEIVER).
|
||||
value : float, optional
|
||||
The value to set for the minimum color range (default is None).
|
||||
index : int, optional
|
||||
The index of the counter (default is 0).
|
||||
updateColorBar : bool, optional
|
||||
Whether to update the color bar (default is True).
|
||||
"""
|
||||
|
||||
if value is None:
|
||||
if imagetype == Defines.ImageType.DIGITAL:
|
||||
self.digitalcolorrange[1] = self.digitalcmaxSpinBoxes.value()
|
||||
elif imagetype == Defines.ImageType.ANALOG:
|
||||
self.analogcolorrange[1] = self.analogcmaxSpinBoxes.value()
|
||||
else:
|
||||
self.transceivercolorrange[index][1] = self.transceivercmaxSpinBoxes[index].value()
|
||||
else:
|
||||
if imagetype == Defines.ImageType.DIGITAL:
|
||||
self.digitalcolorrange[1] = value
|
||||
self.digitalcmaxSpinBoxes.setValue(self.digitalcolorrange[1])
|
||||
elif imagetype == Defines.ImageType.ANALOG:
|
||||
self.analogcolorrange[1] = value
|
||||
self.analogcmaxSpinBoxes.setValue(self.analogcolorrange[1])
|
||||
else:
|
||||
self.transceivercolorrange[index][1] = value
|
||||
self.transceivercmaxSpinBoxes[index].setValue(self.transceivercolorrange[index][1])
|
||||
|
||||
if updateColorBar:
|
||||
self.updateColorBar(imagetype=imagetype, index=index)
|
||||
|
||||
def setColorRangeMode(self, mode : Defines.colorRange):
|
||||
"""
|
||||
slot for setting the color range mode (all,fixed,3-97%)
|
||||
"""
|
||||
@@ -261,73 +354,123 @@ class PlotTab(QtWidgets.QWidget):
|
||||
|
||||
# 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)
|
||||
for cminSpinBox, cmaxSpinBox, checkBox in zip(self.transceivercminSpinBoxes, self.transceivercmaxSpinBoxes, self.checkBoxCounters):
|
||||
cminSpinBox.setEnabled(enableSpinBoxes and (checkBox.isChecked() and checkBox.isEnabled() and self.showtransceiverImage)) # if the checkbox is disabled, we still want to enable the spinbox
|
||||
cmaxSpinBox.setEnabled(enableSpinBoxes and (checkBox.isChecked() and checkBox.isEnabled() and self.showtransceiverImage)) # if the checkbox is disabled, we still want to enable the spinbox
|
||||
|
||||
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()
|
||||
self.analogcminSpinBoxes.setEnabled(enableSpinBoxes and self.showanalogImage)
|
||||
self.analogcmaxSpinBoxes.setEnabled(enableSpinBoxes and self.showanalogImage)
|
||||
|
||||
def updateColorRange(self):
|
||||
self.digitalcminSpinBoxes.setEnabled(enableSpinBoxes and self.showdigitalImage)
|
||||
self.digitalcmaxSpinBoxes.setEnabled(enableSpinBoxes and self.showdigitalImage)
|
||||
|
||||
for index in range(Defines.max_counters):
|
||||
self.updateColorRange(imagetype=Defines.ImageType.TRANSCEIVER, index=index)
|
||||
|
||||
self.updateColorRange(imagetype=Defines.ImageType.ANALOG)
|
||||
|
||||
self.updateColorRange(imagetype=Defines.ImageType.DIGITAL)
|
||||
|
||||
def handleHistogramChange(self, plot, imagetype : Defines.ImageType, index=0):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
plot : Plot
|
||||
The plot object whose histogram has changed.
|
||||
imagetype : Defines.ImageType
|
||||
The type of image (DIGITAL, ANALOG, or TRANSCEIVER).
|
||||
index : int, optional
|
||||
The index of the counter (default is 0).
|
||||
"""
|
||||
cmin, cmax = plot.getHistogramWidget().item.getLevels()
|
||||
self.setCmin(imagetype=imagetype, value=cmin, index=index, updateColorBar=False)
|
||||
self.setCmax(imagetype=imagetype, value=cmax, index=index, updateColorBar=False)
|
||||
|
||||
|
||||
def updateColorRange(self, imagetype : Defines.ImageType, plot = None, index=0):
|
||||
"""
|
||||
Updates the color range of the plot at index based on the current color range mode.
|
||||
for mode:
|
||||
- all: sets cmin and cmax to the maximums/minimum values of the frame
|
||||
- auto: 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):
|
||||
Parameters
|
||||
----------
|
||||
imagetype : Defines.ImageType
|
||||
The type of image (DIGITAL, ANALOG, or TRANSCEIVER).
|
||||
plot : Plot, optional
|
||||
The plot object to update (default is None).
|
||||
index : int, optional
|
||||
The index of the counter (default is 0).
|
||||
"""
|
||||
updates UI views should be called after every change to cmin or cmax
|
||||
"""
|
||||
for plot in self.imagePlots:
|
||||
if isinstance(plot, list): # hacky for now only transceiver image is a PlotSplitter
|
||||
for p in plot:
|
||||
p.getHistogramWidget().item.setLevels(min=self.cmin, max=self.cmax)
|
||||
if imagetype == Defines.ImageType.TRANSCEIVER: # update transceiver
|
||||
defaultPlot = self.transceiverTab.transceiverImageViews[index]
|
||||
cmin, cmax = self.transceivercolorrange[index]
|
||||
elif imagetype == Defines.ImageType.DIGITAL: # update digital
|
||||
defaultPlot = self.mainWindow.plotDigitalImage
|
||||
cmin, cmax = self.digitalcolorrange
|
||||
elif imagetype == Defines.ImageType.ANALOG: # update analog
|
||||
defaultPlot = self.mainWindow.plotAnalogImage
|
||||
cmin, cmax = self.analogcolorrange
|
||||
if plot is None:
|
||||
plot = defaultPlot
|
||||
|
||||
if self.colorRangeMode == Defines.colorRange.auto:
|
||||
if plot.getImageItem().image is None or plot.getImageItem().image.size == 0:
|
||||
return
|
||||
else:
|
||||
plot.getHistogramWidget().item.setLevels(min=self.cmin, max=self.cmax)
|
||||
self.view.cminSpinBox.setValue(self.cmin)
|
||||
self.view.cmaxSpinBox.setValue(self.cmax)
|
||||
frame_min = np.min(plot.getImageItem().image)
|
||||
frame_max = np.max(plot.getImageItem().image)
|
||||
self.setCmin(imagetype=imagetype, value=frame_min, index=index)
|
||||
self.setCmax(imagetype=imagetype, value=frame_max, index=index)
|
||||
elif self.colorRangeMode == Defines.colorRange.center:
|
||||
if plot.getImageItem().image is None or plot.getImageItem().image.size == 0:
|
||||
return
|
||||
|
||||
else:
|
||||
frame_min = np.min(plot.getImageItem().image)
|
||||
frame_max = np.max(plot.getImageItem().image)
|
||||
self.setCmin(imagetype=imagetype, value=frame_min + 0.03 * (frame_max - frame_min), index=index)
|
||||
self.setCmax(imagetype=imagetype, value=frame_max - 0.03 * (frame_max - frame_min), index=index)
|
||||
|
||||
elif self.colorRangeMode == Defines.colorRange.fixed:
|
||||
self.setCmin(imagetype=imagetype, value=cmin, index=index)
|
||||
self.setCmax(imagetype=imagetype, value=cmax, index=index)
|
||||
|
||||
def updateColorBar(self, imagetype : Defines.ImageType, index = 0):
|
||||
"""
|
||||
updates the color bar of the plot at index
|
||||
"""
|
||||
|
||||
plot = None
|
||||
cmin, cmax = 0.0, 0.0
|
||||
if imagetype == Defines.ImageType.TRANSCEIVER:
|
||||
plot = self.transceiverTab.transceiverImageViews[index]
|
||||
cmin, cmax = self.transceivercolorrange[index]
|
||||
elif imagetype == Defines.ImageType.DIGITAL: # update digital
|
||||
plot = self.mainWindow.plotDigitalImage
|
||||
cmin, cmax = self.digitalcolorrange
|
||||
elif imagetype == Defines.ImageType.ANALOG: # update analog
|
||||
plot = self.mainWindow.plotAnalogImage
|
||||
cmin, cmax = self.analogcolorrange
|
||||
|
||||
if plot is not None:
|
||||
histItem = plot.getHistogramWidget().item
|
||||
histItem.blockSignals(True) # avoid triggering handleHistogramChange while setting the levels
|
||||
histItem.setLevels(min=cmin, max=cmax)
|
||||
histItem.blockSignals(False)
|
||||
|
||||
|
||||
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)
|
||||
for i, showncounter in enumerate(self.transceiverTab.shownCounters):
|
||||
if showncounter:
|
||||
self.transceiverTab.transceiverImageViews[i].setColorMap(cm)
|
||||
for i, showncounter in enumerate(self.shownCounterImages):
|
||||
self.transceiverTab.transceiverImageViews[i].setColorMap(cm)
|
||||
|
||||
def getZMQHWM(self):
|
||||
|
||||
@@ -399,11 +542,11 @@ class PlotTab(QtWidgets.QWidget):
|
||||
self.mainWindow.transceiverPlots[i].hide()
|
||||
|
||||
def addAllSelectedTransceiverPlots(self):
|
||||
for i in range(Defines.transceiver.maxcount):
|
||||
for i in range(Defines.max_counters):
|
||||
self.addSelectedTransceiverPlots(i)
|
||||
|
||||
def removeAllTransceiverPlots(self):
|
||||
for i in range(Defines.transceiver.maxcount):
|
||||
for i in range(Defines.max_counters):
|
||||
self.mainWindow.transceiverPlots[i].hide()
|
||||
|
||||
def showPlot(self):
|
||||
@@ -417,15 +560,21 @@ class PlotTab(QtWidgets.QWidget):
|
||||
self.view.radioButtonOverlay.setDisabled(True)
|
||||
self.view.radioButtonStripe.setDisabled(True)
|
||||
|
||||
self.showtransceiverImage = False
|
||||
self.showanalogImage = False
|
||||
self.showdigitalImage = False
|
||||
|
||||
if self.mainWindow.romode.value in [0, 2]:
|
||||
if self.view.radioButtonWaveform.isChecked():
|
||||
self.mainWindow.plotAnalogWaveform.show()
|
||||
elif self.view.radioButtonImage.isChecked():
|
||||
self.showanalogImage = True
|
||||
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.showdigitalImage = True
|
||||
self.mainWindow.plotDigitalImage.show()
|
||||
self.view.labelDigitalWaveformOption.setEnabled(True)
|
||||
self.view.radioButtonOverlay.setEnabled(True)
|
||||
@@ -435,7 +584,8 @@ class PlotTab(QtWidgets.QWidget):
|
||||
if self.view.radioButtonWaveform.isChecked():
|
||||
self.mainWindow.plotTransceiverWaveform.show()
|
||||
elif self.view.radioButtonImage.isChecked():
|
||||
for i, showncounter in enumerate(self.transceiverTab.shownCounters):
|
||||
self.showtransceiverImage = True
|
||||
for i, showncounter in enumerate(self.shownCounterImages):
|
||||
self.transceiverTab.transceiverImageViews[i].setVisible(showncounter)
|
||||
|
||||
self.transceiverTab.transceiverImageSplitter.show()
|
||||
@@ -463,18 +613,18 @@ class PlotTab(QtWidgets.QWidget):
|
||||
'bottom', "<span style=\"color:black;font-size:14px\">Transceiver Sample [#]</span>")
|
||||
|
||||
self.view.stackedWidgetPlotType.setCurrentIndex(0)
|
||||
self.enableallCounterCheckBox(False) # disable counter checkboxes for waveform
|
||||
self.showallCounterCheckBox(False) # disable counter checkboxes for waveform
|
||||
|
||||
elif self.view.radioButtonImage.isChecked():
|
||||
self.view.stackedWidgetPlotType.setCurrentIndex(2)
|
||||
is_transceiver = self.mainWindow.romode.value in [3, 4]
|
||||
self.enableallCounterCheckBox(is_transceiver) # enable counter checkboxes for matterhorn
|
||||
self.showallCounterCheckBox(is_transceiver) # enable counter checkboxes for matterhorn
|
||||
self.setDecoder()
|
||||
|
||||
if self.view.radioButtonNoPlot.isChecked():
|
||||
self.view.labelPlotOptions.hide()
|
||||
self.view.stackedWidgetPlotType.hide()
|
||||
self.enableallCounterCheckBox(False) # disable counter checkboxes when no plot is selected
|
||||
self.showallCounterCheckBox(False) # disable counter checkboxes when no plot is selected
|
||||
# enable plotting
|
||||
else:
|
||||
self.view.labelPlotOptions.show()
|
||||
|
||||
@@ -193,7 +193,7 @@ class SignalsTab(QtWidgets.QWidget):
|
||||
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.plotDigitalImage.setImage(self.mainWindow.digital_frame, autoLevels=False)
|
||||
self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotDigitalImage, 4)
|
||||
|
||||
def updateSignalNames(self):
|
||||
|
||||
@@ -24,18 +24,17 @@ class TransceiverTab(QtWidgets.QWidget):
|
||||
self.plotTab = None
|
||||
self.legend: LegendItem | None = None
|
||||
self.acquisitionTab = None
|
||||
self.nCounters: int = Defines.transceiver.maxcount
|
||||
self.nCounters: int = Defines.max_counters
|
||||
self.nTransceiverRows : int = 0
|
||||
self.nTransceiverCols : int = 0
|
||||
self.shownCounters: list[bool] = [True] * Defines.transceiver.maxcount # per default show all 4 counters
|
||||
self.transceiverImageSplitter = QtWidgets.QSplitter(QtCore.Qt.Vertical)
|
||||
self.transceiverImageViews : list = [] # pg image view for each counter
|
||||
self.firstTransceiverImage : list[bool] = [True] * Defines.transceiver.maxcount # to keep track of first image for each counter to maintain zoom state
|
||||
self.firstTransceiverImage : list[bool] = [True] * Defines.max_counters # to keep track of first image for each counter to maintain zoom state
|
||||
|
||||
def setup_ui(self):
|
||||
self.plotTab = self.mainWindow.plotTab
|
||||
self.acquisitionTab = self.mainWindow.acquisitionTab
|
||||
for i in range(Defines.transceiver.maxcount):
|
||||
for i in range(Defines.max_counters):
|
||||
self.setTransceiverButtonColor(i, self.plotTab.getRandomColor())
|
||||
self.initializeAllTransceiverPlots()
|
||||
|
||||
@@ -46,7 +45,7 @@ class TransceiverTab(QtWidgets.QWidget):
|
||||
self.plotTab.subscribeToggleLegend(self.updateLegend)
|
||||
|
||||
def connect_ui(self):
|
||||
for i in range(Defines.transceiver.maxcount):
|
||||
for i in range(Defines.max_counters):
|
||||
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))
|
||||
@@ -62,7 +61,7 @@ class TransceiverTab(QtWidgets.QWidget):
|
||||
"""
|
||||
enabledPlots = []
|
||||
self.legend.clear()
|
||||
for i in range(Defines.transceiver.maxcount):
|
||||
for i in range(Defines.max_counters):
|
||||
if getattr(self.view, f'checkBoxTransceiver{i}Plot').isChecked():
|
||||
plotName = getattr(self.view, f"labelTransceiver{i}").text()
|
||||
enabledPlots.append((self.mainWindow.transceiverPlots[i], plotName))
|
||||
@@ -111,7 +110,7 @@ class TransceiverTab(QtWidgets.QWidget):
|
||||
trans_array = self._processWaveformData(data, dSamples, self.mainWindow.romode.value,
|
||||
self.mainWindow.nDBitEnabled, self.nTransceiverEnabled)
|
||||
idx = 0
|
||||
for i in range(Defines.transceiver.maxcount):
|
||||
for i in range(Defines.max_counters):
|
||||
checkBoxPlot = getattr(self.view, f"checkBoxTransceiver{i}Plot")
|
||||
checkBoxEn = getattr(self.view, f"checkBoxTransceiver{i}")
|
||||
if checkBoxEn.isChecked() and checkBoxPlot.isChecked():
|
||||
@@ -156,21 +155,18 @@ class TransceiverTab(QtWidgets.QWidget):
|
||||
try:
|
||||
transceiver_frame = self._processImageData(data, dSamples, self.mainWindow.romode.value,
|
||||
self.mainWindow.nDBitEnabled)
|
||||
self.plotTab.ignoreHistogramSignal = True
|
||||
|
||||
for i in range(transceiver_frame.shape[0]):
|
||||
self.transceiverImageViews[i].setImage(transceiver_frame[i])
|
||||
self.transceiverImageViews[i].setImage(transceiver_frame[i], autoLevels=False) #manually update image levels
|
||||
except Exception as e:
|
||||
self.mainWindow.statusbar.setStyleSheet("color:red")
|
||||
self.acquisitionTab.updateCurrentFrame('Invalid Image')
|
||||
self.mainWindow.statusbar.showMessage(str(e))
|
||||
print("Error: ", str(e))
|
||||
|
||||
self.plotTab.setFrameLimits(transceiver_frame)
|
||||
|
||||
# keep the zoomed in state (not 1st image)
|
||||
for idx, image_view in enumerate(self.transceiverImageViews):
|
||||
if(self.firstTransceiverImage[idx] and self.shownCounters[idx]):
|
||||
if(self.firstTransceiverImage[idx] and self.plotTab.shownCounterImages[idx]):
|
||||
self.firstTransceiverImage[idx] = False
|
||||
else:
|
||||
image_view.getView().setState(image_states[idx])
|
||||
@@ -180,16 +176,16 @@ class TransceiverTab(QtWidgets.QWidget):
|
||||
def update_numCounters(self, num_counters):
|
||||
# update the number of counters and adjust the image splitter accordingly
|
||||
self.nCounters = num_counters
|
||||
for i in range(Defines.transceiver.maxcount):
|
||||
self.shownCounters[i] = i < self.nCounters
|
||||
self.plotTab.setCounterCheckBox(i, self.shownCounters[i]) # check the counter checkbox
|
||||
self.plotTab.enableCounterCheckBox(i, self.shownCounters[i]) # disable counter checkbox
|
||||
for i in range(Defines.max_counters):
|
||||
self.plotTab.shownCounterImages[i] = i < self.nCounters
|
||||
self.plotTab.setCounterCheckBox(i, self.plotTab.shownCounterImages[i]) # check the counter checkbox
|
||||
self.plotTab.enableCounterCheckBox(i, self.plotTab.shownCounterImages[i]) # disable counter checkbox
|
||||
|
||||
self.update_ImageSplitter() # update the splitter to show/hide image views based on the number of counters
|
||||
|
||||
|
||||
def update_ImageSplitter(self):
|
||||
for i, showncounter in enumerate(self.shownCounters):
|
||||
for i, showncounter in enumerate(self.plotTab.shownCounterImages):
|
||||
self.transceiverImageViews[i].setVisible(showncounter)
|
||||
|
||||
def initializeAllTransceiverPlots(self):
|
||||
@@ -198,7 +194,7 @@ class TransceiverTab(QtWidgets.QWidget):
|
||||
self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotTransceiverWaveform, 5)
|
||||
self.mainWindow.transceiverPlots = {}
|
||||
waveform = np.zeros(1000)
|
||||
for i in range(Defines.transceiver.maxcount):
|
||||
for i in range(Defines.max_counters):
|
||||
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,
|
||||
|
||||
+377
-184
@@ -425,7 +425,7 @@
|
||||
<string>Moench04</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Moench05</string>
|
||||
</property>
|
||||
@@ -505,7 +505,7 @@
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>440</y>
|
||||
<y>540</y>
|
||||
<width>841</width>
|
||||
<height>400</height>
|
||||
</rect>
|
||||
@@ -1150,7 +1150,7 @@
|
||||
<x>10</x>
|
||||
<y>221</y>
|
||||
<width>841</width>
|
||||
<height>91</height>
|
||||
<height>191</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@@ -1166,197 +1166,390 @@
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="topMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<widget class="QFrame" name="frame_3">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QRadioButton" name="radioButtonCenter">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>15</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>3-97%</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup_4</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QRadioButton" name="radioButtonAutomatic">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>15</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>All</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup_4</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QRadioButton" name="radioButtonFixed">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>15</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Fixed</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup_4</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="5">
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>31</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>31</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Color Range:</string>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="horizontalLayoutColorRange">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Color Range:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioButtonAutomatic">
|
||||
<property name="text">
|
||||
<string>Auto</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup_4</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioButtonCenter">
|
||||
<property name="text">
|
||||
<string>3-97%</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup_4</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioButtonFixed">
|
||||
<property name="text">
|
||||
<string>Fixed</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup_4</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacerColorRange">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="4">
|
||||
<widget class="QDoubleSpinBox" name="cminSpinBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>31</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
<item row="1" column="0">
|
||||
<widget class="QTabWidget" name="tabWidgetColorRange">
|
||||
<property name="currentIndex">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tabColorRangeDigital">
|
||||
<attribute name="title">
|
||||
<string>Digital</string>
|
||||
</attribute>
|
||||
<layout class="QHBoxLayout" name="horizontalLayoutColorRangeDigital">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelColorRangeValuesDigital">
|
||||
<property name="text">
|
||||
<string>Color range values:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="digitalcminSpinBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string>min: </string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="digitalcmaxSpinBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string>max: </string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacerColorRangeDigital">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tabColorRangeAnalog">
|
||||
<attribute name="title">
|
||||
<string>Analog</string>
|
||||
</attribute>
|
||||
<layout class="QHBoxLayout" name="horizontalLayoutColorRangeAnalog">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelColorRangeValuesAnalog">
|
||||
<property name="text">
|
||||
<string>Color range values:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="analogcminSpinBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string>min: </string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="analogcmaxSpinBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string>max: </string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacerColorRangeAnalog">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tabColorRangeTransceiver">
|
||||
<attribute name="title">
|
||||
<string>Transceiver</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayoutColorRangeCounters">
|
||||
<item row="0" column="0" colspan="8">
|
||||
<widget class="QLabel" name="labelColorRangeValuesTransceiver">
|
||||
<property name="text">
|
||||
<string>Color range values:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" rowspan="2">
|
||||
<widget class="QLabel" name="labelColorRangeCounter0">
|
||||
<property name="text">
|
||||
<string>Counter 0:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="transceivercminSpinBox0">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string>min: </string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QDoubleSpinBox" name="transceivercmaxSpinBox0">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string>max: </string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2" rowspan="2">
|
||||
<widget class="QLabel" name="labelColorRangeCounter1">
|
||||
<property name="text">
|
||||
<string>Counter 1:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QDoubleSpinBox" name="transceivercminSpinBox1">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string>min: </string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QDoubleSpinBox" name="transceivercmaxSpinBox1">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string>max: </string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="4" rowspan="2">
|
||||
<widget class="QLabel" name="labelColorRangeCounter2">
|
||||
<property name="text">
|
||||
<string>Counter 2:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="5">
|
||||
<widget class="QDoubleSpinBox" name="transceivercminSpinBox2">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string>min: </string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="5">
|
||||
<widget class="QDoubleSpinBox" name="transceivercmaxSpinBox2">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string>max: </string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="6" rowspan="2">
|
||||
<widget class="QLabel" name="labelColorRangeCounter3">
|
||||
<property name="text">
|
||||
<string>Counter 3:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="7">
|
||||
<widget class="QDoubleSpinBox" name="transceivercminSpinBox3">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string>min: </string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="7">
|
||||
<widget class="QDoubleSpinBox" name="transceivercmaxSpinBox3">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string>max: </string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="8" rowspan="2">
|
||||
<spacer name="horizontalSpacerColorRange2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="6">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>31</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>max:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="7">
|
||||
<widget class="QDoubleSpinBox" name="cmaxSpinBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>31</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>31</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>min:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<spacer name="horizontalSpacer_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QFrame" name="frame_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>322</y>
|
||||
<y>422</y>
|
||||
<width>841</width>
|
||||
<height>108</height>
|
||||
</rect>
|
||||
@@ -1541,9 +1734,9 @@
|
||||
<resources/>
|
||||
<connections/>
|
||||
<buttongroups>
|
||||
<buttongroup name="buttonGroup_4"/>
|
||||
<buttongroup name="buttonGroup_2"/>
|
||||
<buttongroup name="buttonGroup_3"/>
|
||||
<buttongroup name="buttonGroup_4"/>
|
||||
<buttongroup name="buttonGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
@@ -12,6 +12,15 @@ class Defines:
|
||||
Acquisition_Tab_Index = 7
|
||||
Max_Tabs = 9
|
||||
|
||||
|
||||
max_counters = 4 # max number of counters
|
||||
max_counters_tabIndex = 4
|
||||
|
||||
class ImageType(Enum):
|
||||
DIGITAL = 0
|
||||
ANALOG = 1
|
||||
TRANSCEIVER = 2
|
||||
|
||||
class adc:
|
||||
tabIndex = 5
|
||||
count = 32
|
||||
@@ -34,10 +43,6 @@ class Defines:
|
||||
tabIndex = 6
|
||||
loops_count = 6
|
||||
|
||||
class transceiver:
|
||||
maxcount = 4
|
||||
maxtabIndex = 4
|
||||
|
||||
class slowAdc:
|
||||
tabIndex = 2
|
||||
count = 8
|
||||
@@ -88,6 +93,6 @@ class Defines:
|
||||
LineStyles = ['-', '--', '-.', ':']
|
||||
|
||||
class colorRange(Enum):
|
||||
all = 0
|
||||
auto = 0
|
||||
center = 1
|
||||
fixed = 2
|
||||
@@ -296,8 +296,8 @@ class jungfrauLGADStrixelsData : public slsDetectorData<uint16_t> {
|
||||
/*nr_strixel*/ 2 * g1_nrows + 2 * g2_nrows + 2 * g3_nrows,
|
||||
g1_ncols * (2 * g1_nrows + 2 * g2_nrows + 2 * g3_nrows) * 2 +
|
||||
sizeof(header)) {
|
||||
std::cout << "Jungfrau strixels 2X single chip with full module data "
|
||||
<< std::endl;
|
||||
// std::cout << "Jungfrau strixels 2X single chip with full module data "
|
||||
// << std::endl;
|
||||
|
||||
// Fill all strixels with dummy values
|
||||
for (int ix = 0; ix != g1_ncols; ++ix) {
|
||||
@@ -307,9 +307,9 @@ class jungfrauLGADStrixelsData : public slsDetectorData<uint16_t> {
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "sizeofheader = " << sizeof(header) << std::endl;
|
||||
std::cout << "Jungfrau strixels 2X single chip with full module data "
|
||||
<< std::endl;
|
||||
// std::cout << "sizeofheader = " << sizeof(header) << std::endl;
|
||||
// std::cout << "Jungfrau strixels 2X single chip with full module data "
|
||||
// << std::endl;
|
||||
|
||||
if (xmin < xmax && ymin < ymax) {
|
||||
|
||||
@@ -331,7 +331,7 @@ class jungfrauLGADStrixelsData : public slsDetectorData<uint16_t> {
|
||||
}
|
||||
|
||||
iframe = 0;
|
||||
std::cout << "data struct created" << std::endl;
|
||||
// std::cout << "data struct created" << std::endl; // per frame: too noisy
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,6 +55,13 @@ target_compile_definitions(jungfrauRawDataProcessStrxOldAldo PRIVATE JFSTRXOLD A
|
||||
list(APPEND JUNGFRAU_EXECUTABLES jungfrauRawDataProcessStrxOldAldo)
|
||||
|
||||
|
||||
# jf_online_display_gui (Qt5 + ROOT online display / event finder). Off by
|
||||
# default: it needs Qt5 and ROOT, which the rest of this directory does not.
|
||||
option(BUILD_JF_ONLINE_DISPLAY_GUI "build jf_online_display_gui" OFF)
|
||||
if(BUILD_JF_ONLINE_DISPLAY_GUI)
|
||||
add_subdirectory(jf_online_display_gui)
|
||||
endif()
|
||||
|
||||
# others to be added if needed (might already be there in Makefile.cluster_finder TO BE CHECKED)
|
||||
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Makefile.zmqrootdisp
|
||||
@@ -0,0 +1,22 @@
|
||||
ROOTSYS=/opt/cern/v6/build
|
||||
|
||||
LIBZMQDIR = $(PWD)
|
||||
LIBZMQ = -L$(LIBZMQDIR) -lzmq
|
||||
SHLIB_PATH=/opt/cern/v6/root/lib
|
||||
CMAKE_PREFIX_PATH=/opt/cern/v6/root
|
||||
DYLD_LIBRARY_PATH=/opt/cern/v6/root/lib
|
||||
|
||||
QT_CXXFLAGS = $(shell pkg-config --cflags Qt5Core Qt5Gui Qt5Widgets 2>/dev/null)
|
||||
QT_LIBS = $(shell pkg-config --libs Qt5Core Qt5Gui Qt5Widgets 2>/dev/null)
|
||||
|
||||
INCDIR= -I. -I../dataStructures ../tiffio/src/tiffIO.cpp -I../ -I../interpolations/ -I../../slsSupportLib/include/ -I../../slsReceiverSoftware/include/ -I../../libs/rapidjson/ -I../tiffio/include
|
||||
LDFLAG= -L/usr/lib64/ -lpthread -lm -lstdc++ -lzmq -pthread -lrt -ltiff -O3 -std=c++17 -Wall -Wl,-rpath-link,$(ROOTSYS)/lib
|
||||
|
||||
|
||||
default: onlinedisp_zmq
|
||||
|
||||
onlinedisp_zmq: onlinedisp_zmq.cpp onlinedisp_zmq.h ../dataStructures/jungfrauLGADStrixelsData_new.h
|
||||
g++ -o onlinedisp_zmq onlinedisp_zmq.cpp $(QT_CXXFLAGS) $(QT_LIBS) -I. -I$(ROOTSYS)/include -Wall -g -lm -L. -lzmq -pthread -lrt -L$(ROOTSYS)/lib -lGui -lCore -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint -lPostscript -lMatrix -lPhysics -lMathCore -lThread -m64 -L../../build/bin/ -lSlsSupportStatic ../../build/_deps/libzmq-build/lib/libzmq.a -lstdc++ -lsodium $(LDFLAG) $(INCDIR)
|
||||
|
||||
#-lCint
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# build outputs (make and cmake)
|
||||
cmake-build/
|
||||
jf_online_display_gui
|
||||
jf_online_display_gui_JFSTRX
|
||||
gate_contig_test
|
||||
*.o
|
||||
*.JFSTRX.o
|
||||
test/jf_feeder
|
||||
test/xclick
|
||||
# run artefacts
|
||||
current_pede.dat
|
||||
jfroi_*.json
|
||||
*.json.lock
|
||||
roi_list.dat
|
||||
save_dest_list.dat
|
||||
testPHmapsave/
|
||||
@@ -0,0 +1,170 @@
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-other
|
||||
# Copyright (C) 2021 Contributors to the SLS Detector Package
|
||||
#
|
||||
# jf_online_display_gui -- single multithreaded program merging
|
||||
# jf_event_finder_cli / jf_event_finder_gui (ZMQ gate)
|
||||
# onlinedisp_zmq (Qt5 + ROOT display)
|
||||
#
|
||||
# Standalone build (this directory):
|
||||
# cmake -S . -B build && cmake --build build -j
|
||||
# ./build/jf_online_display_gui (normal)
|
||||
# ./build/jf_online_display_gui_JFSTRX (strip-xel)
|
||||
#
|
||||
# The Makefile in this directory builds the same two programs; this file exists
|
||||
# so the program can be built like the rest of the package.
|
||||
#
|
||||
# Targets
|
||||
# jf_online_display_gui normal geometry
|
||||
# jf_online_display_gui_JFSTRX same sources with -DJFSTRX (strip-xel)
|
||||
# gate_contig_test gate unit test (JF_BUILD_TESTS=ON, default)
|
||||
# jf_feeder ZMQ test feeder (JF_BUILD_TESTS=ON, default)
|
||||
#
|
||||
# The two variants must never share object files: the geometry and the struct
|
||||
# layouts differ, so they are compiled twice (JFSTRX/common object libraries).
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(jf_online_display_gui LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
option(JF_BUILD_TESTS "build the gate unit test and the ZMQ feeder" ON)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# paths relative to this directory (same layout as the Makefile)
|
||||
# PKG = ../../.. package root
|
||||
# CAL = ../.. slsDetectorCalibration
|
||||
# ---------------------------------------------------------------------------
|
||||
get_filename_component(PKG "${CMAKE_CURRENT_SOURCE_DIR}/../../.." ABSOLUTE)
|
||||
get_filename_component(CAL "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# dependencies
|
||||
# ---------------------------------------------------------------------------
|
||||
find_package(Qt5 5.9 COMPONENTS Widgets Gui Core REQUIRED)
|
||||
# online_jf.h includes Qt headers, and every translation unit of this program
|
||||
# sees them (the core/root/h5 objects too), so the include paths are added to the
|
||||
# flag list rather than left to the link interface.
|
||||
set(JF_QT_INCLUDES
|
||||
${Qt5Widgets_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} ${Qt5Core_INCLUDE_DIRS})
|
||||
find_package(ROOT REQUIRED COMPONENTS Core RIO Hist Graf Gpad Thread)
|
||||
|
||||
# HDF5: CMake's own module finds both C and C++ (the C++ headers are not in the
|
||||
# default include path on every distro).
|
||||
find_package(HDF5 REQUIRED COMPONENTS C CXX)
|
||||
|
||||
# libzmq: prefer the prebuilt static library from the package build tree (that is
|
||||
# what the Makefile links), then a system library as a fallback.
|
||||
set(JF_ZMQ_STATIC "${PKG}/build/_deps/libzmq-build/lib/libzmq.a"
|
||||
CACHE FILEPATH "static libzmq to link")
|
||||
if(EXISTS "${JF_ZMQ_STATIC}")
|
||||
set(JF_ZMQ_LIBRARY "${JF_ZMQ_STATIC}")
|
||||
set(JF_ZMQ_INCLUDE "${PKG}/build/_deps/libzmq-src/include")
|
||||
find_package(Threads REQUIRED) # the static build needs pthread/sodium
|
||||
set(JF_ZMQ_EXTRA Threads::Threads sodium)
|
||||
else()
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(ZMQ REQUIRED IMPORTED_TARGET libzmq)
|
||||
set(JF_ZMQ_LIBRARY PkgConfig::ZMQ)
|
||||
set(JF_ZMQ_INCLUDE "")
|
||||
set(JF_ZMQ_EXTRA "")
|
||||
endif()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# includes / flags shared by every source of this program
|
||||
# ---------------------------------------------------------------------------
|
||||
set(JF_INCLUDES
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
"${CAL}/dataStructures"
|
||||
"${CAL}/interpolations"
|
||||
"${CAL}/tiffio/include"
|
||||
"${PKG}/slsSupportLib/include"
|
||||
"${PKG}/slsReceiverSoftware/include"
|
||||
"${PKG}/libs/rapidjson"
|
||||
"${JF_ZMQ_INCLUDE}"
|
||||
${JF_QT_INCLUDES}
|
||||
${HDF5_INCLUDE_DIRS}
|
||||
${ROOT_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
# The strip-xel decoder header is included by the -DJFSTRX build only; listing it
|
||||
# as a dependency makes both build systems rebuild when it changes.
|
||||
set(JF_STRIXEL_HDR "${CAL}/dataStructures/jungfrauLGADStrixelsData_new.h")
|
||||
|
||||
set(JF_SOURCES
|
||||
online_jf_core.cpp
|
||||
online_jf_root.cpp
|
||||
online_jf_h5.cpp
|
||||
)
|
||||
set(JF_GUI_SOURCE online_jf_gui.cpp)
|
||||
|
||||
# Everything except the definition of JFSTRX: the two object libraries below
|
||||
# differ only in that definition (and therefore in their compiled geometry).
|
||||
set(JF_COMMON_WARN -Wall -O3 -pthread)
|
||||
|
||||
foreach(variant IN ITEMS "" "JFSTRX")
|
||||
if(variant STREQUAL "")
|
||||
set(objlib jf_common)
|
||||
set(app jf_online_display_gui)
|
||||
else()
|
||||
set(objlib jf_common_jfstrx)
|
||||
set(app jf_online_display_gui_JFSTRX)
|
||||
endif()
|
||||
|
||||
add_library(${objlib} OBJECT ${JF_SOURCES})
|
||||
target_include_directories(${objlib} PRIVATE ${JF_INCLUDES})
|
||||
target_compile_options(${objlib} PRIVATE ${JF_COMMON_WARN})
|
||||
if(NOT variant STREQUAL "")
|
||||
target_compile_definitions(${objlib} PRIVATE ${variant})
|
||||
endif()
|
||||
set_source_files_properties(${JF_SOURCES} PROPERTIES
|
||||
OBJECT_DEPENDS "${JF_STRIXEL_HDR}")
|
||||
|
||||
add_executable(${app} ${JF_GUI_SOURCE} $<TARGET_OBJECTS:${objlib}>)
|
||||
target_include_directories(${app} PRIVATE ${JF_INCLUDES})
|
||||
target_compile_options(${app} PRIVATE ${JF_COMMON_WARN})
|
||||
if(NOT variant STREQUAL "")
|
||||
target_compile_definitions(${app} PRIVATE ${variant})
|
||||
endif()
|
||||
target_link_libraries(${app} PRIVATE
|
||||
Qt5::Widgets Qt5::Gui Qt5::Core
|
||||
${ROOT_LIBRARIES}
|
||||
${JF_ZMQ_LIBRARY}
|
||||
${JF_ZMQ_EXTRA}
|
||||
${HDF5_LIBRARIES}
|
||||
pthread rt m
|
||||
)
|
||||
endforeach()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# tests / helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
if(JF_BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_test(NAME gate_contig COMMAND gate_contig_test)
|
||||
# gate decision logic in isolation (no Qt, no ROOT usage at run time)
|
||||
add_executable(gate_contig_test
|
||||
test/gate_contig_test.cpp
|
||||
$<TARGET_OBJECTS:jf_common>)
|
||||
target_include_directories(gate_contig_test PRIVATE ${JF_INCLUDES})
|
||||
target_compile_options(gate_contig_test PRIVATE ${JF_COMMON_WARN})
|
||||
target_link_libraries(gate_contig_test PRIVATE
|
||||
Qt5::Widgets Qt5::Gui Qt5::Core
|
||||
${ROOT_LIBRARIES}
|
||||
${JF_ZMQ_LIBRARY}
|
||||
${JF_ZMQ_EXTRA}
|
||||
${HDF5_LIBRARIES}
|
||||
pthread rt m
|
||||
)
|
||||
|
||||
# stand-in slsReceiver: single- or dual-interface ZMQ publisher
|
||||
add_executable(jf_feeder test/jf_feeder.cpp)
|
||||
target_include_directories(jf_feeder PRIVATE
|
||||
"${PKG}/libs/rapidjson" "${JF_ZMQ_INCLUDE}")
|
||||
target_compile_options(jf_feeder PRIVATE ${JF_COMMON_WARN})
|
||||
target_link_libraries(jf_feeder PRIVATE ${JF_ZMQ_LIBRARY} ${JF_ZMQ_EXTRA})
|
||||
endif()
|
||||
@@ -0,0 +1,106 @@
|
||||
# ===========================================================================
|
||||
# jf_online_display_gui -- single multithreaded program merging
|
||||
# jf_event_finder_cli / jf_event_finder_gui (ZMQ gate)
|
||||
# onlinedisp_zmq (Qt5 + ROOT display)
|
||||
#
|
||||
# Usage:
|
||||
# make -> ./jf_online_display_gui (normal)
|
||||
# ./jf_online_display_gui_JFSTRX (strip-xel)
|
||||
# make normal | make jfstrx single variant (aliases)
|
||||
# ./jf_online_display_gui <rx_host> [rx_port] [gate_threshold] [min_pixels] [pede_file]
|
||||
# [--pub PORT] [--contig] [--no-gate] [--pede-frames N]
|
||||
#
|
||||
# Deps: system ROOT (root-config), Qt5 (pkg-config), the prebuilt static
|
||||
# libzmq from the package build tree, and rapidjson headers.
|
||||
# ===========================================================================
|
||||
QT_CXXFLAGS = $(shell pkg-config --cflags Qt5Core Qt5Gui Qt5Widgets 2>/dev/null)
|
||||
QT_LIBS = $(shell pkg-config --libs Qt5Core Qt5Gui Qt5Widgets 2>/dev/null)
|
||||
ROOT_CFLAGS = $(shell root-config --cflags 2>/dev/null)
|
||||
ROOT_LIBS = $(shell root-config --libs 2>/dev/null)
|
||||
|
||||
# paths relative to this directory (jf_online_display_gui/)
|
||||
PKG = ../../..
|
||||
CAL = ../..
|
||||
|
||||
ZMQ_STATIC = $(PKG)/build/_deps/libzmq-build/lib/libzmq.a
|
||||
|
||||
# HDF5 C++ headers are NOT in the default include path on every distro (Debian/
|
||||
# Ubuntu put them in /usr/include/hdf5/serial). Discover the include/library
|
||||
# directories with pkg-config (the .pc may be "hdf5" or "hdf5-serial"), then the
|
||||
# h5c++ wrapper, and let the user override both explicitly:
|
||||
# make HDF5_INC=-I/usr/include/hdf5/serial \
|
||||
# HDF5_LIB=-L/usr/lib/x86_64-linux-gnu/hdf5/serial
|
||||
HDF5_PC := $(shell pkg-config --exists hdf5 && echo hdf5 || (pkg-config --exists hdf5-serial && echo hdf5-serial))
|
||||
HDF5_INC ?= $(shell pkg-config --cflags $(HDF5_PC) 2>/dev/null) \
|
||||
$(shell h5c++ -show 2>/dev/null | tr ' ' '\n' | grep '^-I' | tr '\n' ' ')
|
||||
HDF5_LIB ?= $(shell pkg-config --libs $(HDF5_PC) 2>/dev/null) \
|
||||
$(shell h5c++ -show 2>/dev/null | tr ' ' '\n' | grep '^-L' | tr '\n' ' ')
|
||||
HDF5_LIBS = $(HDF5_LIB) -lhdf5_cpp -lhdf5
|
||||
|
||||
INCDIR = -I. \
|
||||
$(HDF5_INC) \
|
||||
-I$(CAL)/dataStructures \
|
||||
-I$(CAL)/interpolations \
|
||||
-I$(CAL)/tiffio/include \
|
||||
-I$(PKG)/slsSupportLib/include \
|
||||
-I$(PKG)/slsReceiverSoftware/include \
|
||||
-I$(PKG)/libs/rapidjson \
|
||||
-I$(PKG)/build/_deps/libzmq-src/include
|
||||
|
||||
CXX ?= g++
|
||||
CXXFLAGS ?= -std=c++17 -O3 -Wall -pthread
|
||||
|
||||
LIBS = $(QT_LIBS) $(ROOT_LIBS) $(ZMQ_STATIC) $(HDF5_LIBS) -lsodium -lpthread -lrt -lm
|
||||
|
||||
OBJS = online_jf_core.o online_jf_root.o online_jf_gui.o online_jf_h5.o
|
||||
# The strip-xel variant compiles the same sources with -DJFSTRX into its own
|
||||
# objects: the two builds have different geometry and struct layouts, so they
|
||||
# must never share object files (mixing them is a hard crash).
|
||||
JFSTRX_OBJS = online_jf_core.JFSTRX.o online_jf_root.JFSTRX.o \
|
||||
online_jf_gui.JFSTRX.o online_jf_h5.JFSTRX.o
|
||||
|
||||
BIN = jf_online_display_gui
|
||||
BIN_JFSTRX = jf_online_display_gui_JFSTRX
|
||||
|
||||
ALL_CXXFLAGS = $(CXXFLAGS) $(ROOT_CFLAGS) $(QT_CXXFLAGS) $(INCDIR)
|
||||
|
||||
# `make` builds BOTH executables: the normal one and the LGAD strip-xel one
|
||||
# (the decoder is header-only, no extra library).
|
||||
all: $(BIN) $(BIN_JFSTRX)
|
||||
|
||||
# the strip-xel decoder header is header-only and lives outside this directory:
|
||||
# touch it and the variant that uses it must be rebuilt
|
||||
STRIXEL_HDR = $(CAL)/dataStructures/jungfrauLGADStrixelsData_new.h
|
||||
|
||||
%.JFSTRX.o: %.cpp online_jf.h $(STRIXEL_HDR)
|
||||
$(CXX) $(ALL_CXXFLAGS) -DJFSTRX -c $< -o $@
|
||||
|
||||
%.o: %.cpp online_jf.h
|
||||
$(CXX) $(ALL_CXXFLAGS) -c $< -o $@
|
||||
|
||||
gate_contig_test: test/gate_contig_test.cpp online_jf_core.o online_jf_root.o online_jf_h5.o
|
||||
$(CXX) $(ALL_CXXFLAGS) -o $@ $^ $(LIBS)
|
||||
|
||||
$(BIN): $(OBJS)
|
||||
$(CXX) -o $@ $(OBJS) $(LIBS)
|
||||
|
||||
$(BIN_JFSTRX): $(JFSTRX_OBJS)
|
||||
$(CXX) -o $@ $(JFSTRX_OBJS) $(LIBS)
|
||||
|
||||
# kept so the old habits still work
|
||||
normal: $(BIN)
|
||||
jfstrx: $(BIN_JFSTRX)
|
||||
|
||||
# Synthetic slsReceiver stand-in used to exercise the program without
|
||||
# hardware (see test/jf_feeder.cpp).
|
||||
feeder: test/jf_feeder
|
||||
|
||||
test/jf_feeder: test/jf_feeder.cpp
|
||||
$(CXX) -std=c++17 -O2 -pthread $(INCDIR) -o $@ $< $(ZMQ_STATIC) -lsodium -lpthread -lrt -lm
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(JFSTRX_OBJS) $(BIN) $(BIN_JFSTRX) test/jf_feeder
|
||||
|
||||
FORCE:
|
||||
|
||||
.PHONY: all clean jfstrx normal feeder
|
||||
@@ -0,0 +1,950 @@
|
||||
# Notes
|
||||
|
||||
Working notes for `jf_online_display_gui`. The README is the reference for how the
|
||||
program behaves; this file records provenance, verification and things that are
|
||||
easy to trip over.
|
||||
|
||||
## 2026-09-23 — handoff from another session (verified here)
|
||||
|
||||
An all-night run showed the **display followed the pedestal drift but the saved
|
||||
HDF5 did not**. Three changes were made by that session and are on disk; I
|
||||
re-verified them in this workspace on 2026-09-23.
|
||||
|
||||
### What changed
|
||||
|
||||
| file | change |
|
||||
|---|---|
|
||||
| `online_jf_h5.cpp` | `RoiSaver::offer()` corrects each frame with the **live** pedestal (`PedestalStore::instance().get()`), not the snapshot taken by the GUI at Start Save. Periodic checkpoint: shrink `/data` + the 14 parameter datasets to exactly `framesWritten` (undoing the `kExtendBlock = 128` over-allocation), `H5Fflush(SCOPE_GLOBAL)`, rewrite the JSON. JSON writer factored into `writeJsonSidecar(bool complete)` writing `<name>.json.part` → `fsync` → `rename` → directory `fsync` (`syncDirOf`). File created as **v110 (SWMR)**: `H5Pset_libver_bounds(LATEST)` + `H5Fcreate`, all objects created up front, then `H5Fstart_swmr_write()` before the first frame. |
|
||||
| `online_jf.h` | `StartInfo::checkpointMs = 30000` (0 disables) |
|
||||
| `README.md` | pedestal tracking / saving / file format / JSON / crash-and-mid-run sections, test description, files table |
|
||||
| `test/pede_live_test.cpp` | standalone check: live pedestal + checkpoint + SWMR live read |
|
||||
|
||||
Consequences of SWMR worth remembering: objects cannot be created after it
|
||||
starts, so `/pedestal tracking` and `/pedestal tracking frame` are now created
|
||||
(empty) for **every** save, and the baseline pedestal image is written just after
|
||||
the SWMR switch. (The 10-minute cadence of that series, and its ROI-shaped
|
||||
images, are unchanged.)
|
||||
|
||||
### Verification done here
|
||||
|
||||
* `make` — clean, binary current with the sources.
|
||||
* `test/pede_live_test` (built with the README's line, plus `$(root-config --cflags)`
|
||||
which that line omits) — **PASS**:
|
||||
|
||||
```
|
||||
checkpoint1: periodic json (complete:false)=true live swmr read=true h5 extent=1 px=900.0
|
||||
checkpoint2: live swmr read=true h5 extent=2 slice1 px=850.0
|
||||
final: complete=true no .part left=true
|
||||
PASS: live pedestal + periodic clean checkpoint
|
||||
```
|
||||
|
||||
The two different pixel values are the point: the first frame was corrected with
|
||||
pedestal A, the second with pedestal B installed mid-run, so the file follows the
|
||||
tracked pedestal. Generated artefacts were deleted afterwards.
|
||||
|
||||
### Operational caveats
|
||||
|
||||
* **v110 needs HDF5 ≥ 1.10.** Older analysis installs cannot open these files at
|
||||
all — this is the one to check before an overnight run.
|
||||
* **Reading mid-run needs the SWMR read flag**: `h5py.File(path, "r",
|
||||
libver="latest", swmr=True)` and `dset.refresh()` to see new frames.
|
||||
`h5dump`/`h5ls` on the live file will not work. A closed file reads normally.
|
||||
* `Pedestal corrected` is still **off by default** (raw data). Pedestal tracking
|
||||
only reaches `/data` when it is ticked; in raw mode the only pedestal in the
|
||||
file is slice 0.
|
||||
* `/pedestal data` is the pedestal at save start; `/pedestal tracking`
|
||||
(+ `/pedestal tracking frame`) is the history of what was actually used, one
|
||||
image per checkpoint interval.
|
||||
* `checkpointMs` defaults to 30 s and is **not exposed** in the GUI or on the
|
||||
command line yet.
|
||||
|
||||
### Open / optional
|
||||
|
||||
* `--no-swmr` (or a `StartInfo` fallback) if a downstream reader turns out to be
|
||||
on HDF5 < 1.10.
|
||||
* Expose the checkpoint interval (`--checkpoint MS` and/or a GUI field).
|
||||
* If an external reader does not see new frames after a checkpoint, add explicit
|
||||
per-dataset `H5Dflush`; `H5Fflush(SCOPE_GLOBAL)` was enough in the test.
|
||||
* Unrelated: the older `onlinedisp_zmq.cpp` extracts the ROI from
|
||||
`zHeader.detSpec1`, which is a hack (that field is the bunch id) — the stream
|
||||
header already carries `zHeader.rx_roi` (`std::array<int,4>`, filled from the
|
||||
receiver's per-port ROI). That file was not touched.
|
||||
|
||||
## Workspace health
|
||||
|
||||
* `../_AGENT.txt` was rewritten (2026-09-23, on request): it now points at
|
||||
`jf_online_display_gui/` as the active work, with the old `onlinedisp_zmq` +
|
||||
`jf_event_finder_*` set kept in a clearly marked "legacy / superseded"
|
||||
section. The design rules that both generations share (ROOT-thread rule,
|
||||
photon map, display cadence, gate semantics, SWMR files) are listed there too.
|
||||
* `test/_run/` was removed (2026-09-23, on request). For the record, what was
|
||||
in it was not worth keeping: the `..._084120_...` run had only its sidecar left
|
||||
(the HDF5 was already gone) and `..._090709_...` had `frames.saved = 0` — an
|
||||
empty save written before the handoff, with no `complete` field. `test/` now
|
||||
holds only `jf_feeder[.cpp]` and `pede_live_test.cpp`.
|
||||
* `jf_online_display_gui/` and the h5/write path are untracked in git; nothing here is
|
||||
committed.
|
||||
* `.gitignore` in this directory excludes `*.o`, the `jf_online_display_gui` and
|
||||
`test/jf_feeder` binaries, and runtime data (`current_pede.dat`,
|
||||
`roi_list.dat`, `save_dest_list.dat`, `jfroi_*.h5/json`). Note it does *not*
|
||||
cover `test/pede_live_test` (the test binary), so that one should be deleted
|
||||
after use.
|
||||
|
||||
## 2026-09-30 — JFSTRX display + startup defaults
|
||||
|
||||
* `-DJFSTRX`: the **ADC and gain windows are now unmapped** (raw 1024x512), so
|
||||
gate-ROI selection and the hover read-out stay in raw coordinates. The mapped
|
||||
view is a separate, display-only window, **"ADC Strixel Panels (JFSTRX)"**,
|
||||
with the six groups arranged like the ROOT canvas (left chip1 g3/g2/g1, right
|
||||
chip6 g1/g2/g3).
|
||||
* The six panels use the strixel **pixel aspect** `X = 75*(75/Y)`, `Y =
|
||||
25/15/18.75 um` -> 9:1, 25:1, 16:1, via `FastImageView::setPixelAspectX`
|
||||
(non-uniform x/y scaling; ADC and gain stay square).
|
||||
* The receiver ROI is parsed from the JSON `rx_roi` array (the old `detSpec1`
|
||||
hack is gone) and drawn as the **red** rectangle on the raw ADC image.
|
||||
* Raw geometry is now `kRawWidth`/`kRawHeight` (1024x512); `nx`/`ny` under
|
||||
JFSTRX are the strixel *display* geometry only. Fixed the assembled-frame
|
||||
height, the multi-interface slab split, the gate-ROI defaults and the HDF5
|
||||
pedestal extraction, which all wrongly used the mapped `nx`/`ny`.
|
||||
* **Startup defaults changed**: the signal gate is **off** (`--gate` enables it)
|
||||
and pedestal subtraction is **off** (`--use-pede` enables it; the "No pede"
|
||||
button starts checked). README examples now pass `--gate` where gating is
|
||||
expected.
|
||||
* `test/panel_map_test.cpp` (JFSTRX objects) checks the six-panel fill geometry
|
||||
against the decoder: all six panels fully covered, only the decoder's 183
|
||||
extra right-edge columns dropped (same as the ROOT histograms).
|
||||
* The strixel ROI-outline experiment (`buildReceiverRoiOutline`) was removed -
|
||||
ROI on the mapped panels was judged too complex.
|
||||
* At ~14:40 the on-disk `jf_online_display_gui` was found truncated to 0 bytes
|
||||
(concurrent build in this directory); `make jfstrx` rebuilt it (14:41).
|
||||
|
||||
## 2026-09-30 (later) — three reported bugs
|
||||
|
||||
1. **Zoom-out stopped early** (`FastImageView::wheelEvent`): it called
|
||||
`matchViewportToWindow()` on every wheel step, so the image dimension that
|
||||
reached its limit first froze the other and the whole frame could never be
|
||||
shown. Outward zoom now only `clampViewport()`s; the window-shape matching
|
||||
still runs while zooming in and on resize.
|
||||
2. **ROI PH (A7) empty**: the fill condition used `gate_.roiActive()`, which is
|
||||
only true while "ROI only" is on, so a merely *selected* ROI left A7 empty.
|
||||
The ROI snapshot/spectra now use `gate_.roiSet()`; "ROI only" still controls
|
||||
the gate restriction (unrelated to showing the ROI).
|
||||
3. **"Reset His" took 30-50 s**: the ROOT thread executed the reset and blocked
|
||||
on `gHistoMtx`, which the analysis thread holds around every frame - under
|
||||
the heavy JFSTRX fill it can starve. The reset is now performed by the
|
||||
analysis thread at the top of its next frame (lock already held), with a
|
||||
non-blocking `try_lock` fallback on the ROOT thread for the idle case. The
|
||||
ROOT thread also now wakes immediately (`gCmdCv`) for commands and forced
|
||||
redraws instead of waiting for the `ROOT update (ms)` tick, so a large
|
||||
update interval no longer delays a button.
|
||||
|
||||
**Build footgun:** the Makefile does not track flags. After `make jfstrx`,
|
||||
a plain `make` only rebuilds changed files and then links a mix of JFSTRX and
|
||||
non-JFSTRX objects (I hit a link error that way). Always `make clean` before
|
||||
switching between `make` and `make jfstrx`.
|
||||
|
||||
## HDF5 C++ headers (build dependency)
|
||||
|
||||
`#include "H5Cpp.h"` needs the HDF5 **C++ dev** package, not just the runtime:
|
||||
|
||||
| distro | package | where the header lands |
|
||||
|---|---|---|
|
||||
| RHEL / Rocky / Alma / Fedora | `hdf5-devel` | `/usr/include/H5Cpp.h` |
|
||||
| Debian / Ubuntu | `libhdf5-dev` | `/usr/include/hdf5/serial/H5Cpp.h` |
|
||||
| openSUSE / SLES | `hdf5-devel` | default include dir |
|
||||
| Arch | `hdf5` | `/usr/include` |
|
||||
| conda-forge | `hdf5` | `$CONDA_PREFIX/include` |
|
||||
| macOS (brew) | `hdf5` | `$(brew --prefix hdf5)/include` |
|
||||
|
||||
The Makefile now discovers the include/library dirs via `pkg-config` (`hdf5` or
|
||||
`hdf5-serial`) and `h5c++ -show`, and both can be overridden, e.g. on Ubuntu:
|
||||
|
||||
```
|
||||
make HDF5_INC=-I/usr/include/hdf5/serial \
|
||||
HDF5_LIB=-L/usr/lib/x86_64-linux-gnu/hdf5/serial
|
||||
```
|
||||
|
||||
## 2026-10-06 — external review (`../../../REVIEW_CLAUDE.md`): triage and what was applied
|
||||
|
||||
A 39-item review by Claude covers this program plus the Qt6 port, the legacy
|
||||
tools, `test_bench/` and repo hygiene. **Part of it was written against a
|
||||
different checkout**: items 2, 3, 4 (mostly) and 9 are already fixed in *this*
|
||||
tree, and the review's own item 37 mentions `build-qt6/` cache paths under
|
||||
`~/swtests/slsDetectorPackage/...`, which does not exist here. Everything below
|
||||
was checked against the code in this workspace before touching it.
|
||||
|
||||
### Applied and verified here
|
||||
|
||||
| item | what was wrong | fix |
|
||||
|---|---|---|
|
||||
| 4 (rest) | a header with **no data part** would make the data `recv` swallow the next message's header as pixels | require `ZMQ_RCVMORE` after a valid header, else count an error and drop the header (the header-length clamp and the drain-on-reject paths were already present) |
|
||||
| 8 | the save recorded the **startup** gate settings, so `gate` in the HDF5 attribute and JSON sidecar was wrong after any live edit | read `enabled/threshold/minPixels/contiguous` from `GateEngine` (new getters) instead of `g_cfg`; verified end to end - a save started from the command line records the live values (`threshold 137`, `min_pixels 9`) |
|
||||
| 11 (part) | the acq thread **busy-spun at 100 % CPU** waiting for a lagging partner: a held slab kept its socket polled and level-triggered `zmq_poll` fired forever | clear `events` for a socket whose slab is held, so it is not polled until it is released |
|
||||
| 18 | ten shortcuts never fired: `QKeySequence("s") == QKeySequence("S")`, so s/S, b/B, r/R, o/O, p/P were ambiguous and Qt activated neither | upper-case actions are now `Shift+S/B/R/O/P` (verified: `Shift+S` opens Set Ranges, 352 → 366 windows) |
|
||||
| 19 | the run examples still passed the removed `<energy>` argument, so `2000` was parsed as the host | README examples, the header comment and the Makefile comment corrected (the two remaining `<energy>` mentions are the *legacy* chain and a historical note, both correct) |
|
||||
| 22 (part) | `--pede-tracking` left the button unchecked, and `--gate-roi` left `ROI only` unchecked so the first press toggled the wrong way | the tracking button is initialised from `gPedeTracking`, and a restricting `--gate-roi` now checks `ROI only` (through the `m_applyingRoi` guard, so nothing re-captures or warns) |
|
||||
|
||||
### Found while fixing item 22 — a bug of my own
|
||||
|
||||
The tracking button's enable rule lived in the 500 ms `updateFlow()` poll, which
|
||||
also *unchecked* the button when the gate was off. During start-up that cleared
|
||||
a `--pede-tracking` button (`ptrack=off`, reproduced headless). The force-off is
|
||||
now event-driven (in the gate checkbox's own handler); the poll only enables.
|
||||
After the change: `--pede-tracking` gives `ptrack=on`.
|
||||
|
||||
`test/jf_feeder` had also gone missing from the tree (the Makefile has no target
|
||||
for the test programs — review item 34); rebuilt by hand for the verification.
|
||||
|
||||
### Already fixed in this tree (nothing to do)
|
||||
|
||||
| item | state |
|
||||
|---|---|
|
||||
| 2 (HDF5 never closed) | the file is built with the C++ constructor `H5File(h5Path, …)` so it owns the only reference; the code comments say so explicitly |
|
||||
| 3 (budget/stop not enforced) | `offer()` returns early on `stopRequested`, and the writer stops at `saved >= maxFrames` |
|
||||
| 4 (clamps) | `hn > hdr.size()` clamp, `truncated` data check and the drain-on-reject paths are all present |
|
||||
| 9 (cumulative drop rate) | uses the 1 Hz window (`pedeTrackSignalDropRate(passCur, dropCur, …)`) — fixed earlier the same day |
|
||||
|
||||
### Checked and found *correct* (not defects)
|
||||
|
||||
While verifying item 8 I chased two values in a saved file that looked wrong and
|
||||
are not: the file recorded `enabled: false` and `no_pede: true` for a run whose
|
||||
command line had neither `--gate` nor `--use-pede`. Both are the documented
|
||||
defaults - `online_jf.h:189` (`gateEnabled = false`) with `--gate` to enable, and
|
||||
`gNoPede{true}` with `--use-pede` to clear. The review's item 22 is the flip
|
||||
side of this (the widgets were not synced from the config); the config itself is
|
||||
intentional. Since it is easy to lose an hour to this, both switches are now
|
||||
spelled out in the README's options section, and a regression run with
|
||||
`--gate --use-pede` behaves as expected (with a 100 ADU pedestal and a 250 ADU
|
||||
threshold on 3000/200 ADU frames: `pass=0 drop=148`, i.e. the ROI half is
|
||||
reduced to 100 and correctly rejected).
|
||||
|
||||
### Deliberately deferred, with reasons
|
||||
|
||||
* **13 — chip/ROI spectra saturate (top physics item).** `TH1I` bin contents
|
||||
saturate at `INT_MAX` and the `uint32_t` accumulators wrap: ~55 k pixels per
|
||||
chip per frame land in 1–2 noise bins, so at 500–1300 fps the spectra are
|
||||
wrong within an hour, and an all-night run at 25 fps is close to the limit too.
|
||||
The fix is mechanical but wide (37 `TH1I` sites across four files plus the
|
||||
accumulator and sync signatures) and touches the core data path, so it gets
|
||||
its own careful pass with a test rather than a rushed edit at the end of a
|
||||
long session. Plan: accumulators → `uint64_t`, histograms/clones → `TH1D`,
|
||||
then check that entries, means and the ADU/keV ratio (40.0000) are unchanged.
|
||||
* **10** (the tracker can overwrite a just-loaded pedestal — re-prime from the
|
||||
store under the lock, skip a stale install), **12** (per-port `rx_roi` with the
|
||||
`rowBase` offset), **20** (closing the window should quit — the parentless ADC
|
||||
/Gain windows keep the process alive), **21** (Delete in the ROI/save-destination
|
||||
dialogs uses stale indices), **23–30** (races on `g_pheneThreshold`/the ROI,
|
||||
`renderImage`'s 524 k `setPixel` calls and the 500 ms `roi_list.dat` re-read,
|
||||
NaN pedestals, rapidjson asserts, the finish/start save race, frame 0, and the
|
||||
small GUI defects).
|
||||
* **11 (the rest)** — partial-frame assembly is a feature, not a fix; the README
|
||||
now documents the real behaviour and calls the promise an open item.
|
||||
* **Everything outside `jf_online_display_gui`** — the Qt6 port (1, 16, 17, 35, 36), the
|
||||
legacy tools (5, 6, 7, 14, 15, 31 — the project's own `_AGENT.txt` marks them
|
||||
superseded), `test_bench` (32), repo hygiene (33, 37, 38 — `git checkout --`
|
||||
for the symlinks needs repository write access, which is still deferred) and
|
||||
maintainability (39). These need decisions of their own; none of them were
|
||||
changed here.
|
||||
|
||||
## 2026-10-14 — virtual photon counting: display side done, save side specified
|
||||
|
||||
Requested: take the photon map out of ROOT, give it its own Qt window filled the
|
||||
same way but **not cumulative by default** and plotted every N images (default
|
||||
2000), put those controls plus the photon threshold in a "Virtual photon
|
||||
counting" panel left of the Display, and add a save mode that writes the
|
||||
non-cumulative maps every M images (M >= 200) with the first frame number and
|
||||
timestamp, M and the thresholds recorded.
|
||||
|
||||
### Done (build green, window present, no crashes)
|
||||
|
||||
* Core: `gPmapEvery` (default 2000) and `gPmapCumulative` (default **false**)
|
||||
atomics; the photon map is published into the image mailbox every
|
||||
`gPmapEvery` **analysed frames** (independent of the ADC image decimation),
|
||||
and when not cumulative the accumulator is zeroed right after the publish, so
|
||||
each image holds the photons counted since the previous one. The counting
|
||||
itself is unchanged.
|
||||
* GUI: a **"Virtual photon counting"** panel (left column, under the gates -
|
||||
the Display panel is in the right column at that height) with the
|
||||
`cumulative` checkbox, `plot every N images` (1..1000000, default 2000) and
|
||||
the **photon threshold (ADU)** spin moved out of the Display group.
|
||||
* GUI: a new top-level window **"Photon Map (virtual photon counting)"**
|
||||
(1104x552, same colour palette as the ADC image, colour range 0..8 photons
|
||||
per pixel), refreshed from the mailbox in `processImages()`.
|
||||
* Fixed while doing it: the panel was built before `photonThrSpin_` existed, so
|
||||
the grid received a null widget and the spin floated at (0,0) - the same
|
||||
add-before-create mistake as the Quit button earlier. The panel is now
|
||||
created after the spin and inserted with `leftCol->insertWidget(2, ...)`.
|
||||
|
||||
### Stage 3, part A — done and verified
|
||||
|
||||
* **The ROOT photon map is gone**: no `his3000` booking, no `disp3000` clone and
|
||||
no `A5` canvas. All the remaining references are behind `if (his3000)` /
|
||||
`if (disp3000)` / `if (A5)` guards, so they are simply inert; the two
|
||||
unguarded dereferences (the clone and the z-range) were the only ones that
|
||||
needed deleting. Verified: no `Plotting Canvas Photon Map` window appears.
|
||||
* The **P.Map** range pair now reads and writes the Qt photon window's colour
|
||||
range (`liveMapRange` -> `m_photonView->colorRange`, and the panel's opening
|
||||
range), and the ADC zoom also sets the photon window's viewport.
|
||||
* **Save with no gate ROI works**: the early refusal in `toggleRoiSave` is gone;
|
||||
`roiSnapshot()` already reports the full frame when no ROI is set. Verified:
|
||||
with no `--gate-roi`, `/data` came out `(151, 512, 1024)` - the pedestal slice
|
||||
plus 150 full frames.
|
||||
|
||||
### Stage 3, part B — the photon-map save mode (next)
|
||||
|
||||
Design, so this can be written mechanically:
|
||||
|
||||
1. Save panel: a radio pair (`save frames` / `virtual photon counting`) plus an
|
||||
`M` spinbox, minimum **200**, default 2000; the spinbox is only meaningful in
|
||||
the photon mode.
|
||||
2. Core: `gPmapSaveEvery` (int, default 2000) and `gSaveMode` (0 = frames,
|
||||
1 = photon maps) atomics; a **second accumulator** `g_pmapSaveAccum` (NCH
|
||||
floats) filled in the same worker pass (one extra add per counted photon
|
||||
pixel, gated by a `FrameJob::pmapSave` flag so there is no atomic read per
|
||||
pixel); every `gPmapSaveEvery` analysed frames it is copied into a single-slot
|
||||
mailbox and zeroed - never cumulative.
|
||||
3. Window provenance: the acq thread owns the saver, so let the acq loop keep the
|
||||
window bookkeeping while in this mode: on the first gate-passed frame after a
|
||||
publish record `firstFrame = hdr.frameNumber` and `firstStamp =
|
||||
hdr.timestamp`, count frames, and when the analysis publishes a map, tag it
|
||||
with those two values. The map's own frame count goes in too.
|
||||
4. Writer: a new dataset `/virtual photon counting` (float32, 512x1024, one image
|
||||
per publish, extendable) plus `/virtual photon counting frame` (uint64) and
|
||||
`/virtual photon counting timestamp`; JSON gains `save_mode`,
|
||||
`photon_map_every` (M), `photon_threshold` (ADU) and the window count.
|
||||
`/data` stays frames-only and is not created in this mode.
|
||||
5. `--pmap-every N` on the command line would make this testable without the GUI
|
||||
(the display cadence is otherwise 2000 frames = 80 s at 25 fps).
|
||||
|
||||
### Stage 3, part B — plumbing in place, inert
|
||||
|
||||
Done in the tree and building:
|
||||
|
||||
* `gSaveMode` (0 = frames, 1 = virtual photon counting) and `gPmapSaveEvery`
|
||||
(default 2000) atomics, plus `pmapSaveReset()`.
|
||||
* A **second accumulator** `g_pmapSaveAccum` filled in the same worker pass
|
||||
(`FrameJob::pmapSave`, so no atomic read per pixel), published into a
|
||||
single-slot `PmapSlot` every `gPmapSaveEvery` analysed frames and zeroed
|
||||
immediately - never cumulative.
|
||||
* `Pipeline::offerToSaver()` replaces the two direct `saver_.offer()` calls: in
|
||||
the frame mode it is the old path, in the photon mode it keeps the window
|
||||
bookkeeping (first frame number, first timestamp, frame count) in the acq
|
||||
thread and tags each published map with it.
|
||||
* `RoiSaver::offerPhotonMap()` declared; its body currently only counts the map
|
||||
in `pmapPending` and writes nothing - **deliberately inert**: `gSaveMode` is
|
||||
still 0 everywhere, so no path can select the photon mode yet and the existing
|
||||
behaviour is unchanged. Nothing can silently produce a frames-only file while
|
||||
claiming the photon mode.
|
||||
|
||||
Still to write (the design is above):
|
||||
|
||||
1. The writer body: `/virtual photon counting` (float32, 512x1024, extendable,
|
||||
**created up front** because SWMR forbids creating objects after the start)
|
||||
plus `/virtual photon counting frame` and `/virtual photon counting timestamp`
|
||||
(uint64), drained from a `pmapQueue` in the writer loop, and the JSON fields
|
||||
(`save_mode`, `photon_map_every`, `photon_threshold`, window count).
|
||||
2. The Save panel controls: the radio pair (`save frames` /
|
||||
`virtual photon counting`) and the `M` spinbox (minimum 200), filling
|
||||
`StartInfo::photonMode/photonEvery/photonThreshold` (the fields are already
|
||||
there) and calling `pmapSaveReset()` when a save starts.
|
||||
3. `--pmap-every N` and `--pmap-save-every N` (or one `--save-photon` switch) so
|
||||
the mode can be exercised headlessly; with the display default of 2000 frames
|
||||
a test needs 80 s at 25 fps otherwise.
|
||||
|
||||
### Stage 3, part B — finished and verified (this session)
|
||||
|
||||
Everything listed as "still to write" above is in the tree, plus the GUI
|
||||
controls and the headless switches:
|
||||
|
||||
* Writer: `/virtual photon counting` (float32, {nmaps, h, w}, chunked, created
|
||||
before SWMR starts) + `/virtual photon counting frame` + `/virtual photon
|
||||
counting timestamp` (U64, one value per map), drained from `Impl::pmapQueue`
|
||||
in the writer loop; `RoiSaver::offerPhotonMap()` is live and inert unless
|
||||
`info.photonMode` and a save is running. JSON: `save_mode`,
|
||||
`photon_map_every`, `photon_threshold`, and a `photon_maps` block with
|
||||
`saved`, `every`, `threshold_adu`, `cumulative: false`, `pedestal_active`,
|
||||
`dataset`. In photon mode `frames.saved` reports the **maps**.
|
||||
* Budget: in photon mode "Number of frames to be saved" budgets the **maps**
|
||||
(the GUI relabels the row to "Number of photon maps to be saved"), and the
|
||||
run stops by itself with `stop_reason = "photon-map budget reached"`, leaving
|
||||
a closed `complete: true` pair. Frame mode is unchanged
|
||||
(`frame budget reached`).
|
||||
* Save panel: radio pair + `photon map every N frames` spinbox (minimum 200,
|
||||
greyed while "save frames" is selected). The radio also re-labels the budget
|
||||
row. The photon-map mode is allowed with **no gate ROI** (full frame).
|
||||
* Virtual photon counting panel (left column, under the gates): `cumulative`
|
||||
(default **off**), `plot every N images` (default 2000), `photon threshold
|
||||
(ADU)` (moved out of the Display panel). The map lives in its own Qt window
|
||||
`Photon Map (virtual photon counting)`; it is **not** a ROOT histogram any
|
||||
more (that was the old `his3000`, removed).
|
||||
* CLI: `--pmap-every N`, `--pmap-save-every N` (min 200), `--save-photon`,
|
||||
`--save-budget N`, `--start-save` — the same actions as the GUI controls, so
|
||||
a save can run headless.
|
||||
|
||||
**Bug found and fixed while verifying (important).** Step 5a of the analysis
|
||||
loop (the save-map publish) took `gHistoMtx` although the enclosing frame scope
|
||||
already holds it (`histoLock`, top of the frame). `std::mutex` is not
|
||||
recursive, so the **analysis thread deadlocked on the first frame** whenever
|
||||
`gSaveMode == 1` — with `--save-photon`, and even with no save running. The
|
||||
symptom was a program that kept counting frames (`in=` growing, because the acq
|
||||
thread does not depend on the analysis thread) while nothing downstream ever
|
||||
appeared: no photon map, no ROOT update, `save=0/0`. Two `gdb` snapshots
|
||||
caught it (`analysisLoop` parked in `pthread_mutex_lock` at a call site that
|
||||
only exists in the photon path). Fix: no second lock (the frame scope holds it)
|
||||
and the whole save-map path is now gated on `saver_.running()` as well, so
|
||||
`gSaveMode == 1` without a running save costs nothing and cannot block.
|
||||
|
||||
**Verified end to end** (Xvfb + mwm, `test/jf_feeder`, zero-filled pedestal —
|
||||
the real `current_pede.dat` is ~2000 ADU, so the synthetic 100/300 ADU frames
|
||||
would count nothing):
|
||||
|
||||
| check | result |
|
||||
|---|---|
|
||||
| `--save-photon --pmap-save-every 200 --start-save`, 40 s | maps at frames 0/201/401/601/801, `[pmap] map N written (200 frames, first frame F)` |
|
||||
| `/virtual photon counting` | (5, 512, 1024) float32; every map 40 non-zero pixels, sum ~5320 (40 px x 133 counts = 2/3 of 200 frames), identical maps = non-cumulative |
|
||||
| provenance | `/virtual photon counting frame` `[0 201 401 601 801]` |
|
||||
| `--save-budget 2` | exactly 2 maps, self-stop `photon-map budget reached`, `complete: true` |
|
||||
| frame mode `--save-budget 100` | `/data` (101, 512, 1024), 100 frames, `frame budget reached`, no photon datasets |
|
||||
| GUI path | clicking `virtual photon counting` re-labels the row to *Number of photon maps to be saved*, enables the M spinbox and sets `Status: saving virtual photon counting maps`; `Start/Stop save` then writes maps (first map at the frame the save started, e.g. 431) |
|
||||
| photon window | `Photon Map (virtual photon counting)` renders the 40-pixel cluster (green, ~133 counts on the 0..256 scale) |
|
||||
| no pedestal | `--save-photon` with `--no-pede` (or no pedestal file) is **refused**: `[save] refusing virtual photon counting: "No pede" is on, ...`, no HDF5 file is created, and the scripted `--start-save` path is not blocked by a dialog; frame mode with no pedestal still saves |
|
||||
|
||||
The photon-map window draws **zero-count pixels white** (`FastImageView::
|
||||
setZeroWhite`, opt-in and used only there), reproducing the ROOT `colz` look
|
||||
where empty cells show the canvas background; the palette and the colour bar are
|
||||
untouched, and single photons are still coloured (the special case is `v <= 0`,
|
||||
not "below the range"). Verified on screen: white map, cluster still green.
|
||||
|
||||
Housekeeping: `test/_scratch/` (XTest driver, screenshots, run logs, HDF5
|
||||
outputs, the synthetic pedestal) was removed after the runs; the tree keeps only
|
||||
`test/jf_feeder[.cpp]`, `test/pede_live_test.cpp`, `test/panel_map_test.cpp`.
|
||||
|
||||
Two things left open for the next session, both decisions rather than bugs:
|
||||
|
||||
1. ~~**Photon counting needs a pedestal.**~~ **Decided by the operator: no
|
||||
photon-map saving without a pedestal.** Counting still requires `g0`
|
||||
(unchanged), and a virtual-photon-counting save now *refuses to start* when
|
||||
no pedestal is active (`No pede`, or nothing loaded): the button drops back,
|
||||
the status line and stdout say why, no file is created, and the modal warning
|
||||
is skipped under `--start-save` so scripted runs never wait on a dialog.
|
||||
Frame saving is unaffected.
|
||||
2. The map's `first frame number`/`timestamp` come from the acq thread, i.e.
|
||||
from the first **gate-passed** frame of the window (none is available if the
|
||||
gate drops the whole window, in which case the previous values persist). The
|
||||
analysed-frame count in `PmapItem::frames` is exact.
|
||||
|
||||
### ROI in photon mode, greying convention, control names (follow-up)
|
||||
|
||||
* **The gate ROI applies to the saved photon maps too** (operator's choice):
|
||||
a map is the ROI window of the full-frame counting map, cropped in the same
|
||||
data coordinates as `/data`. This fixed a real bug: `writePhotonMap` used to
|
||||
hand `h*w` floats straight to HDF5, i.e. the first `h*w` pixels of the
|
||||
1024x512 map, which is the *wrong region* whenever a ROI is set. Verified
|
||||
against the full-frame map: `/virtual photon counting` for a `0 0 100 50` ROI
|
||||
is bit-identical to `full[0:50, 0:100]`, with the 16 non-zero pixels exactly
|
||||
where the feeder's wrapped cluster is (row 1, x 0..15); a frame save with the
|
||||
same ROI gives `/data` `{nframes, 50, 100}`, so both modes crop alike.
|
||||
* **Greying is a look, not an enablement** (`greyStage(QGroupBox*, bool,
|
||||
std::initializer_list<QWidget*> keepActive)`): the box, its title and its
|
||||
other contents dim, while the widget that *switches the stage back on* keeps
|
||||
normal text - otherwise a bypassed stage looks stuck. Kept readable:
|
||||
`Pede/Raw toggle` (Pedestal), `Gate enabled` (signal gate), `Pedestal
|
||||
tracking`, `gain correction on/off` (Gain), and the storage-cell combo. The
|
||||
`keV PH` checkbox stays disabled+greyed while the gain is off (earlier
|
||||
request: it keeps its tick for next time).
|
||||
* **Virtual photon counting box greys without a pedestal** (`greyStage(
|
||||
pmapGroup_, !pede)`), matching the fact that the counting needs `g0` and the
|
||||
save refuses to start: one rule, the panel and the file agree.
|
||||
* Renames: `No pede (raw ADC)` -> **`Pede/Raw toggle`** (state still spelled out
|
||||
by the label next to it), `Apply gain (g)` -> **`gain correction on/off (g)`**,
|
||||
plus a read-only **`gain corrected`** indicator beside it (no mouse events, no
|
||||
focus; it ticks with the flag in `updateFlow()` and dims with the panel).
|
||||
|
||||
* Pedestal tracking panel follow-up: the button is `pedestal tracking on/off`
|
||||
and the box now greys on **selectability**, not on activity - `greyStage(
|
||||
pedeTrackGroup_, !gateOn)`, matching the button's own `setEnabled(gateOn)`,
|
||||
while the value label under it (`off` / `on: N samples, ...`) reports the
|
||||
activation. Verified in three states: gate off -> box and button greyed;
|
||||
gate on + tracking off -> box black and label `off`; after clicking the
|
||||
toggle -> button pressed and label `on: 0 samples, delta 0.0 ADU, 0 updates`
|
||||
(0 samples is correct with the feeder, which only drops a third of the frames
|
||||
while the tracker needs >90 %).
|
||||
|
||||
### Photon threshold in keV while the gain correction is on
|
||||
|
||||
Requested: leave the photon-counting threshold in ADU with the gain off, switch
|
||||
it to keV (default **4 keV**) with the gain on, and remember both values.
|
||||
|
||||
* Core: `g_pheneThresholdKev` (default `PHOTON_THRESHOLD_KEV_DEFAULT` 4.0) next
|
||||
to `g_pheneThreshold` (150 ADU). The analysis picks by `gain_flag` and hands
|
||||
the worker `FrameJob::pcGain` / `pcNominalGain`, so the counting value is
|
||||
`adcpedecorr / (g0[i] or 40)` - the same ADU/keV the keV spectra use - and the
|
||||
increment follows in the same unit: `(value + thr) / (2 x thr)`. The unit
|
||||
follows the gain stage itself, not the `keV PH` checkbox (that one only
|
||||
decides what the spectra display).
|
||||
* GUI: the threshold row is a `QDoubleSpinBox` (integer ADU, one decimal keV)
|
||||
whose label switches `(ADU)` <-> `(keV)`; `setApplyGain()` stores the value
|
||||
that is on screen into its own unit and loads the other one (signals blocked),
|
||||
so toggling never destroys either. `--apply-gain` added as the CLI mirror of
|
||||
the Gain button (the gain stage was the only one without one) - needed to test
|
||||
this headlessly.
|
||||
* Reported: `--stats` prints `pthr=150ADU` / `pthr=4keV`; the JSON carries
|
||||
`photon_threshold_unit` and `photon_maps.threshold` + `threshold_unit`
|
||||
(`threshold_adu` is gone - the key was wrong as soon as the unit could
|
||||
change).
|
||||
* Verified with the synthetic feeder, a zero pedestal and a **g0 = 1.0
|
||||
ADU/keV** gain map (float64 - the loader wants 6 x 1024x512 doubles; a float32
|
||||
file is rejected with the byte count):
|
||||
- gain off: `pthr=150ADU`, one 200-frame map with **40 non-zero pixels, sum
|
||||
5320, max 133** - i.e. only the 300 ADU cluster, unchanged from before;
|
||||
- `--apply-gain`: `pthr=4keV`, the **same frames** give **524288 non-zero
|
||||
pixels** (every pixel: keV = ADC/1.0, so even the 100 ADU baseline is
|
||||
100 keV > 4 keV) with max 5925 - proof that the counting really divides by
|
||||
ADU/keV before comparing;
|
||||
- GUI: starting with `--apply-gain` shows `photon threshold (keV): 4.0`;
|
||||
clicking the gain off shows `photon threshold (ADU): 150`; clicking it back
|
||||
on gives a screenshot byte-identical to the first keV one, so both values
|
||||
survive the round trip.
|
||||
* Mid-save guard: toggling the gain while a photon-map save runs changes the
|
||||
unit of the maps written after it, so `setApplyGain()` prints
|
||||
`[gain] warning: the photon-counting unit is now keV while a photon-map save
|
||||
is running ...` (and the same for ADU). Verified with a photon save started
|
||||
by `--start-save` and a gain click: the warning appears once, the save keeps
|
||||
running and writes its map, the JSON keeps the start-of-save threshold/unit.
|
||||
|
||||
### Window handling, labels and the flow arrows
|
||||
|
||||
* **Quit closes every window.** The ADC / gain / photon-map windows are
|
||||
separate top-levels and the ROOT canvases live in the ROOT thread, so
|
||||
`quitApp()` and `closeEvent()` now call `closeSideWindows()` (hide + close
|
||||
each) and `QApplication::quit()`. Closing only the control window used to
|
||||
leave the side windows - and the process, because Qt quits on the *last*
|
||||
window - on screen. Verified on all three paths with the window count taken
|
||||
from `xwininfo -root -tree`:
|
||||
- Quit button, dialog confirmed: 6 app windows -> **0**, process gone;
|
||||
- Quit button with a photon-map save running: the save is flushed
|
||||
(`[shutdown] flushing the ROI save ...`, JSON `complete: true`), then
|
||||
**0** windows / no process;
|
||||
- window-manager close (a `WM_DELETE_WINDOW` ClientMessage, the path that was
|
||||
broken): confirmed -> **0** windows / no process.
|
||||
* Gain panel label: the no-map case is now
|
||||
`no gain map loaded : using defaults` (the "defaults" are the nominal
|
||||
40 ADU/keV, used for the keV spectra *and* the keV photon threshold).
|
||||
* Flow arrows: two new connectors - **Gain -> Virtual photon counting** (the
|
||||
calibration; its threshold is keV while the gain is on) and **Virtual photon
|
||||
counting -> Display** (the map has a window of its own) - and the save path is
|
||||
now **orange**: the Gain -> ROI rail and the ROI -> Save arrow. Blue stays
|
||||
the analysis/display chain. `FlowStrip::Path` gained a `save` flag and
|
||||
`makeFlowArrow()` a colour argument (`#4673af` blue, `#de7d14` orange).
|
||||
Verified on screen in the gain-on/pede-on state: both new arrows land on the
|
||||
right panels, the rail and the ROI -> Save arrow are orange, the rest blue.
|
||||
* The `JF_TIMING` `[rect]` diagnostic also prints `quit`, `track` and `pmap`
|
||||
now, which is how the click-based tests above locate those widgets.
|
||||
|
||||
### Dynamic flow colours (arrows static, colours not)
|
||||
|
||||
Reworked after the operator's feedback: the geometry of the diagram is fixed,
|
||||
only the colour class of each connector changes.
|
||||
|
||||
* Three classes (`FlowClass` + `flowColor()`): `FlowData` orange `#de7d14`,
|
||||
`FlowNormal` light blue `#5b9bd5`, `FlowPedestal` lighter blue `#a8c8e8`.
|
||||
* The **data path follows the save mode**: `FlowStrip::setSaveMode(photonMaps)`
|
||||
flips the Gain -> ROI rail and the Gain -> counting connector, and
|
||||
`updateFlow()` (called from the radio handler) flips the two label arrows
|
||||
ROI -> Save and counting -> Save. So in `save frames` the orange pair is
|
||||
rail + ROI -> Save, and in `virtual photon counting` it is Gain -> counting +
|
||||
counting -> Save - exactly two arrows at a time.
|
||||
* Completeness: every stage now has an incoming and an outgoing connector.
|
||||
The missing one was **Virtual photon counting -> Save** (a new arrow under the
|
||||
counting panel, the left-hand twin of ROI -> Save). Display and Save are the
|
||||
sinks; the storage cell has no incoming connector because the receiver stream
|
||||
enters there.
|
||||
* The colour legend is in the flow strip's tooltip and in the README table.
|
||||
* Verified on screen: frames mode -> rail + ROI -> Save orange, counting arrows
|
||||
light blue; after clicking `virtual photon counting` (and the label switching
|
||||
to "Number of photon maps to be saved") -> Gain -> counting and counting ->
|
||||
Save orange, rail + ROI -> Save light blue. `[save] mode: ...` is logged on
|
||||
every change.
|
||||
* Test note: the `JF_TIMING` `[rect]` lines can be ~30 px stale (they are printed
|
||||
on a layout pass that may run before the panels settle), so a scripted click
|
||||
should target the widget with a small downward tolerance - found the hard way
|
||||
when a radio click missed.
|
||||
|
||||
### The gate is a funnel on the line (not a stage)
|
||||
|
||||
Operator's rule: the signal gate does not modify the data, so no connector may
|
||||
enter or leave its box. Reworked accordingly:
|
||||
|
||||
* The flow is now `Pedestal -> [funnel] -> Gain` (and `[funnel] -> tracking`),
|
||||
i.e. one junction where the gate acts, drawn as a small trapezoid on the line.
|
||||
The gate panel's only connector is a short blue **control arrow** into the
|
||||
funnel's side; with the gate off the funnel and that arrow are drawn pale
|
||||
(`FlowOff`, #c9c9c9) instead of disappearing.
|
||||
* Two feeders reach the funnel and both are always drawn: through the **Pedestal**
|
||||
(`FlowPedestal`) and straight from the **storage cell** (`FlowNormal`), the
|
||||
raw bypass that avoids the greyed Pedestal box. `setStages(pede, gate, pmap)`
|
||||
makes the live one coloured and the idle one pale grey - so in raw mode the
|
||||
data visibly comes from block 1, as the operator asked.
|
||||
* `FlowStrip::Path` grew `x` (vertical position), `head` (false = merge into the
|
||||
junction) and `startAtJog` (begin on the vertical, for the funnel -> Gain and
|
||||
funnel -> tracking branches); `cross()` honours them; `drawFunnel()` paints the
|
||||
symbol plus its control arrow; `kFunnelX = 24` is where the flow passes the
|
||||
gate.
|
||||
* `updateFlow()` calls `setStages(pede, gateOn, gSaveMode == 1)`, so the four
|
||||
states (pede/raw x gate on/off) are all live: verified with screenshots in all
|
||||
four combinations - pedestal feeder live + funnel blue (pede+gate), funnel and
|
||||
control arrow pale (pede+no gate), bypass live and pedestal feeder pale
|
||||
(raw+gate), and the bypass stays live with the funnel pale (raw+no gate).
|
||||
|
||||
### Flow diagram, final pass (operator's corrections)
|
||||
|
||||
* **Lighter blue is now only the pedestal tracking pair** - the gate block ->
|
||||
tracking input and the tracking -> Pedestal feedback. The storage cell ->
|
||||
Pedestal and Pedestal -> funnel connectors are part of the data flow, so they
|
||||
are orange like the rest of the route.
|
||||
* **Orange is the whole save route**, entry point to Save panel, and it follows
|
||||
the mode: frames = storage cell -> Pedestal -> funnel -> Gain -> ROI -> Save,
|
||||
maps = ... -> Gain -> VPC -> Save. Display-only branches stay light blue, and
|
||||
in photon mode the Gain -> ROI rail is light blue as well.
|
||||
* **A route that carries no data is not drawn** (the operator found the pale
|
||||
grey confusing): the raw bypass exists only in raw mode, the Pedestal path
|
||||
only while the pedestal is in use, and only the selected mode's Save arrow
|
||||
(ROI -> Save or VPC -> Save) is shown. The one thing that still greys is the
|
||||
gate funnel when the gate is off.
|
||||
* The **tracking input** now leaves the top-right corner of the gate block
|
||||
(`yIn(sigGroup_, 6)`), not the funnel.
|
||||
* Verified with screenshots in four states: pede+gate frames (orange: SC ->
|
||||
Pedestal -> funnel -> Gain -> rail -> ROI -> Save; bypass absent; tracking
|
||||
pair lighter blue), the same in photon mode (orange through Gain -> counting
|
||||
and the counting -> Save arrow, rail light blue, ROI -> Save absent),
|
||||
raw+gate frames (orange storage cell -> funnel -> Gain -> ROI -> Save; the
|
||||
Pedestal box carries no in/out at all, only the tracking feedback),
|
||||
and pede+no-gate (funnel and its control arrow pale, everything else
|
||||
unchanged).
|
||||
|
||||
### The display path and the save path can differ (last refinement)
|
||||
|
||||
The operator pointed out that the orange route must follow the *save* logic, not
|
||||
the `Pede/Raw` toggle: `flowStageStates()` now computes
|
||||
|
||||
* `displayPede = PedestalStore::loaded() && !gNoPede` (unchanged: the display,
|
||||
gate, spectra and the map accumulation),
|
||||
* `savePede = (gSaveMode == 1) || (saveCorrected_ && !gNoPede)` (the maps always
|
||||
need `g0`; the frames are corrected only with the checkbox),
|
||||
* and `FlowStrip::setStages(displayPede, savePede, gateOn, photonMaps)` draws:
|
||||
|
||||
| situation | what the diagram shows |
|
||||
|---|---|
|
||||
| display pede, `save frames`, `Pedestal corrected` **off** (the default!) | Pedestal feeder **light blue** (display only) + bypass **orange** (the file) - both live, the divergence visible |
|
||||
| `Pedestal corrected` **on** (or photon maps) | Pedestal feeder **orange**, bypass not drawn |
|
||||
| `Pede/Raw` = raw (both raw) | Pedestal path not drawn at all, bypass orange |
|
||||
| photon mode | pedestal route orange through Gain -> counting -> Save; the Gain -> ROI rail is light blue and the ROI -> Save arrow is absent |
|
||||
|
||||
`saveCorrected_` also got a `toggled -> updateFlow()` connection (it had none) so
|
||||
the diagram follows the checkbox, and a `saveCorrected` entry in the `[rect]`
|
||||
geometry diagnostics for the click tests.
|
||||
|
||||
Verified with screenshots: `--use-pede --gate` in frames mode showed the blue
|
||||
storage cell -> Pedestal -> funnel next to the orange storage cell -> funnel ->
|
||||
Gain -> ROI -> Save (both feeders into one funnel), and clicking `Pedestal
|
||||
corrected` turned the pedestal route orange and removed the bypass.
|
||||
|
||||
### ROI input arrow + "Unset ROI" button
|
||||
|
||||
* The ROI's incoming connector is now mode-dependent and always orange:
|
||||
`Gain -> ROI` (the rail) while frames are written, `counting -> ROI` (a new
|
||||
connector) while the maps are - they are cropped to the ROI on the way out, so
|
||||
the ROI is only ever on the save path. Only one of the two is drawn at a
|
||||
time; `setStages()` flips them with `p4_.on = !photonMaps` / `p9_.on =
|
||||
photonMaps`.
|
||||
* New **Unset ROI** button in the ROI panel (`unsetRoi()`): releases the "ROI
|
||||
only" restriction if it is on, then `GateEngine::clearRoi()` (so `roiSet_` and
|
||||
`roiOn_` are false and the rectangle is the full frame), clears the yellow
|
||||
overlay, resets the counters and updates the read-outs. The ROI list file is
|
||||
not touched.
|
||||
* Verified: with `--gate-roi 100 50 200 100 --gate --stats` the live line showed
|
||||
`roi=200x100@100,50`; clicking **Unset ROI** logged `[roi] ROI unset: full
|
||||
frame` and the same line then showed `roi=off`, the panel reading
|
||||
"no ROI selected | gate: full frame". In `--save-photon` (photon mode) the
|
||||
screenshot shows the orange connector from the counting panel into the ROI and
|
||||
no rail from the Gain.
|
||||
|
||||
### Save-time locking, truthful JSON, ROI->Save, and small labels
|
||||
|
||||
Operator requests, all in and verified with a running `--save-photon` run:
|
||||
|
||||
* **The arrow into the Save panel always leaves the ROI** (never the counting
|
||||
panel): `arrPmapDown_` is gone, `arrRoiDown_` is always drawn and always
|
||||
orange. The maps reach the file through the ROI, which is also why the ROI's
|
||||
own input flips between the Gain (frames) and the counting (maps).
|
||||
* `cumulative` -> **`cumulative (toggle to reset)`**, and the toggle now really
|
||||
resets (`resetHis()`), so the label is true.
|
||||
* **`Pedestal corrected` moved under the `save frames` radio** (it is a property
|
||||
of the saved frames; the maps are always pedestal-subtracted) and is greyed
|
||||
while the photon-map mode is selected.
|
||||
* **The save counters no longer read 0 in photon mode**: `RoiSaver::Status`
|
||||
gained `pmaps` + `photonMode` (Impl::pmapsInFile, fed by the writer loop), so
|
||||
the GUI status line says "N maps written, M dropped" and `--stats` says
|
||||
`save=1map/0` instead of `0/0`.
|
||||
* **Settings are locked while a save runs** (`applySaveLock()`): the whole save
|
||||
panel except `Start/Stop save` and the status label, the storage-cell filter,
|
||||
the manual pedestal work, the gain correction (keV PH stays live) and the
|
||||
ROI select/load/unset buttons. Live on purpose: everything display-only, the
|
||||
pedestal tracker, the gate **threshold** and **min pixels**. Those two open
|
||||
`Changing settings during a run, are you sure?`; No puts the old value back
|
||||
(`pipeline_.gate().threshold()/minPixels()`), Yes applies it and sets
|
||||
`gGateChangedDuringSave`, which makes `writeJsonSidecar()` add
|
||||
`gate_settings_modified_during_run: true` and a `gate_settings_note` (the
|
||||
`gate` block stays the criterion from save start). The lock is applied on
|
||||
start, released on stop - including the automatic stop (budget/error), which
|
||||
`updateStatus()` detects through `m_saveWasRunning`.
|
||||
* Verified on screen during a running photon-map save: save panel greyed except
|
||||
Start/Stop, SC/pede/gain greyed, Threshold 150.0 and Min pixels 5 live, the
|
||||
Virtual photon counting panel's two display controls live with the new label,
|
||||
the photon threshold greyed, `Pedestal corrected` greyed under the frames
|
||||
radio, and the Save status reading "1 map written, 0 dropped".
|
||||
|
||||
### Layout bug (mine), destination list, "queue full"
|
||||
|
||||
* **The `virtual photon counting` radio was rendered outside the Save box.** When
|
||||
I moved `Pedestal corrected` under the `save frames` radio I dropped the
|
||||
`saveForm->addRow(saveModePhoton_);` line, so the radio had no layout slot and
|
||||
Qt gave it a default geometry (measured: x=9, y=915, 100x30 - i.e. floating at
|
||||
the left edge above the Save box, which is exactly what showed up as a stray
|
||||
"Virtual phot..." fragment). Re-added; the `[rect]` diagnostic now reads
|
||||
`saveFramesRadio 21,996 | saveCorrected 21,1025 | savePhotonRadio 21,1054`,
|
||||
all 788 px wide inside the box. (`layoutFlow()` also repaints the central
|
||||
widget after solving now, which is cheap insurance against leftovers in the
|
||||
pads' wake.)
|
||||
* **The previous-destinations list is no longer polluted by the default**: the
|
||||
start-up `--h5-dir`/`--h5-base` pair is captured once (`saveDefaultDir_`,
|
||||
`saveDefaultBase_`) and a save that uses exactly that pair logs
|
||||
`[save] <dir>/<base> is the default destination, not added to the
|
||||
previous-destinations list` instead of recording it. Verified: the list stayed
|
||||
at 22 lines over a `--start-save` run with the default destination. My own
|
||||
test entries (`*_scratch*`, 23 of them) were pruned from
|
||||
`save_dest_list.dat`; the real `/mnt/sls_det_storage/...` destinations and the
|
||||
`test/_run`/`testPHmapsave` entries were left alone.
|
||||
* The dropped counter now reads `(writer behind: queue full)` and the README
|
||||
explains it: the writer buffers at most 4096 items / 256 MB, so a slower disk
|
||||
than the stream means the excess is dropped (and still counted by the gate
|
||||
counters).
|
||||
|
||||
* The drop note was attached unconditionally, so a healthy run read
|
||||
"0 dropped (queue full)" - a problem that was not there. It now says
|
||||
`0 dropped (nothing lost)` while nothing is lost and only switches to
|
||||
`(writer behind: queue full)` when the counter is non-zero. Verified on a
|
||||
running photon-map save: "1 map written, 0 dropped (nothing lost)".
|
||||
|
||||
* The "Changing settings during a run, are you sure?" popup is now raised only
|
||||
when **`Gate enabled` is ticked** (`confirmGateChangeDuringRun` returns early
|
||||
on `!pipeline_.gate().enabled()`): with the gate off (passthrough) the
|
||||
threshold and min-pixels change nothing about the saved frames, so they go
|
||||
through silently and no `gate_settings_*` note is written. Verified with a
|
||||
running save: gate on + clicking a gate spinbox -> dialog shown and declining
|
||||
logged `[save] gate change refused during the run (Min pixels above threshold
|
||||
-> 6)`; the same click with `--no-gate` -> no dialog, no log line.
|
||||
|
||||
### Image windows redrawn as plots (frame, axes, pixel grid, scale panel)
|
||||
|
||||
Operator request: replace the side colour bar with a panel and make the images
|
||||
read as plots.
|
||||
|
||||
* `FastImageView::imageRect()` now reserves axis margins (52 left / 26 bottom /
|
||||
6 top-right) instead of the old bar strip; `paintEvent()` draws the frame,
|
||||
tick labels outside the plot (Y = data rows, row 0 at the bottom), a dotted
|
||||
grid inside it, and keeps the gate-ROI / receiver-ROI overlays as before.
|
||||
* `m_sx` (screen pixels per image pixel, from the last paint) drives the pixel
|
||||
detail: thin outlines from 16 px, in-pixel values from 50 px
|
||||
(`setIntegerValues(true)` for the photon map -> whole numbers, one decimal for
|
||||
the real-valued images). The label colour is chosen from the luminance of the
|
||||
pixel that was actually painted; the loops are capped (<=256 px visible for
|
||||
the grid, <=64 for the labels) so a deep zoom can never stall the painter.
|
||||
* The colour bar is off (`m_showBar = false`; `scaleRect()` returns an empty
|
||||
rect so the old drag code is inert) and each image window now carries a
|
||||
`ScalePanel` under it: `ScaleHisto` (the image's own 256-bin distribution with
|
||||
the mapped range highlighted, refreshed at most every 300 ms from
|
||||
`renderImage()`), a palette chooser wired to `paletteLut()` (kept in sync with
|
||||
the `b`/`Shift+B`/`c` shortcuts), a lin/log switch, and min/max spin boxes
|
||||
(also synced from `Set Ranges...`).
|
||||
* log mode maps `log10(max(v, 1e-2))` against `log10(max(range, 1e-2))`, so
|
||||
values <= 0 land at 1e-2 = -2 instead of -inf, and the "zero is white" rule
|
||||
for the photon map still applies first.
|
||||
* Verified on screen: the framed plot with the X labels (475..505) and Y labels
|
||||
(97..90, high at the top) outside the frame, the dotted grid, the per-pixel
|
||||
outlines at ~33 screen px/pixel, and the panel below with the histogram,
|
||||
`palette: Col (jet)`, `lin`, `min -200.0`, `max 1000.0` (the ADC defaults).
|
||||
Not yet seen on screen: the in-pixel numbers (they need >50 px/pixel, my
|
||||
scripted wheel zoom stopped at ~33) and the histogram with a varied image
|
||||
(a uniform test frame gives a single bin). The old bar's drag-to-stretch on
|
||||
the scale is gone by design: the range is set with the spin boxes (dragging on
|
||||
the histogram is a possible follow-up).
|
||||
|
||||
* Scale histogram refined to the operator's spec: fixed 38 px strip (the image
|
||||
keeps the window stretch, the panel has a fixed height - about a tenth of the
|
||||
window), one **solid** bar per bin whose width is the bin spacing on screen,
|
||||
and the two range markers pinned at **1/4 and 3/4** of the width with the value
|
||||
axis rescaling under them (the drawn window is the selected range widened by
|
||||
half a range on each side, so dragging a marker moves that end of the range
|
||||
and the marker snaps back to its fixed spot). The bars are draggable
|
||||
(`onRangeChanged` -> the panel pushes the new range into the view and the spin
|
||||
boxes) and the ends are printed next to the markers. A **second lin/log
|
||||
switch** was added: `image` for the intensity mapping, `histo` for the bar
|
||||
heights (log10(count+1)); the histogram's value axis always follows the image
|
||||
switch. Verified on screen: the thin strip with the two orange markers at
|
||||
1/4 and 3/4 labelled `702.7` and `1000`, and a drag of the left marker moving
|
||||
`min` from -200 to 702.7 (the bars were off-window by then, which is the
|
||||
fixed-marker behaviour).
|
||||
|
||||
### Palette list widened to the matplotlib family
|
||||
|
||||
The scale panel's palette chooser now offers 18 maps instead of 3 (the original
|
||||
`Col (jet)`, the two ROOT-like greys, then the new ones). The `Palette` enum
|
||||
keeps `Color/BW1/BW2` first so the `b` / `Shift+B` / `c` shortcuts and
|
||||
`setPaletteChoice()` keep working, and `paletteName()` (new, in
|
||||
`online_jf_root.cpp`, deliberately outside the anonymous namespace) feeds the
|
||||
combo so the names live in one place.
|
||||
|
||||
* New: Turbo, Viridis, Plasma, Inferno, Magma, Cividis, Hot, Grey (linear),
|
||||
Grey reversed, Coolwarm, BWR, Seismic, RdBu, YlGnBu, Sunset.
|
||||
* Anchors: the perceptually uniform ones are matplotlib's published anchors and
|
||||
Turbo is Google's; the diverging/sequential set uses matplotlib's definitions
|
||||
and the ColorBrewer / Paul Tol anchors (YlGnBu, Sunset) as republished in
|
||||
`bokeh/palettes.py`, which is where I looked them up - bokeh's licence header
|
||||
attributes Viridis/Magma/Plasma/Inferno to Nathaniel Smith et al. (CC0),
|
||||
Turbo to Google (Apache-2.0), the Brewer maps to Cynthia Brewer (Apache-2.0)
|
||||
and Tol's to Paul Tol (BSD-3). Everything is interpolated by the existing
|
||||
piecewise-linear `PaletteDef`, whose stop/colour arrays were grown from 5 to
|
||||
24 entries to hold the 15-anchor turbo ramp; the LUTs are now one
|
||||
`g_luts[kNumPalettes][256]` table built by the same `buildLut()` loop, so a new
|
||||
palette is only an enum entry plus its anchors.
|
||||
* Verified: the entry list builds and the app runs; selecting `Seismic
|
||||
(diverging)` in the ADC window turned the image seismic-blue (raw ~100 ADU on
|
||||
the -200..1000 range lands just below the white midpoint, as expected) with
|
||||
the panel and histogram following.
|
||||
|
||||
* The **gain window** lost its scale panel entirely (no histogram, no palette /
|
||||
lin-log / range controls): its image holds four valid numbers - the gain bits
|
||||
0..3 - so there is neither a distribution to show nor a scale to choose. It
|
||||
now uses a fixed `Palette::Gain4` map (0 muted blue, 1 muted green, 2 muted
|
||||
yellow, 3 muted red, all desaturated on purpose so a large flat map does not
|
||||
glare) with the range pinned to 0..3, and whole-number in-pixel labels. The
|
||||
palette is in the enum but deliberately **not** in the chooser loop
|
||||
(`i <= int(Palette::Sunset)`), so the three image windows keep their own
|
||||
choices.
|
||||
|
||||
* In-pixel numbers: the font now also has to fit the *width* of the label, not
|
||||
just the pixel height - five characters for the photon counts, six for the
|
||||
real-valued images ("XXXXX.y"), at ~0.62 em per digit, still capped at 0.30 em
|
||||
of the pixel so the "small tidy number" look is kept (range now 7..20 px).
|
||||
At 50 px/pixel that is 12 px instead of 15, which is what makes five digits
|
||||
fit; the widest label stays under the pixel edge for every zoom level above
|
||||
the 50 px threshold.
|
||||
|
||||
### Zoom coordination switch (Display panel)
|
||||
|
||||
Operator request: zooming used to be ADC-master only (the ADC view propagated to
|
||||
the gain/photon windows and the ROOT plots, never the other way round). Now
|
||||
`zoomTo(FastImageView *src, ...)` takes the view that asked, and every image view
|
||||
reports its zoom through the same wiring (`wireZoom()` for ADC, gain and photon),
|
||||
so **any** window zooms all of them. A new `zoom coordination` checkbox in the
|
||||
Display panel (default **on**) gates the propagation: when it is off, `zoomTo()`
|
||||
returns immediately, so each window keeps its own zoom and nothing is pushed to
|
||||
the others - including the ROOT canvases, which as a consequence no longer follow
|
||||
the ADC while the box is off (say the word if they should keep following it
|
||||
regardless). Both transitions are logged (`[zoom] coordination on/off`) and
|
||||
every coordinated zoom logs its source (`[zoom] coordinated from ADC/gain/photon`).
|
||||
|
||||
Verified: with the default on, five wheel steps over the ADC window produced 10
|
||||
`coordinated from ADC` events (two per step: wheel plus the drag path); the
|
||||
checkbox is present at a sane place in the Display panel
|
||||
(`[rect] zoomCoord 476,730 333x23`, ticked). The OFF case is a single
|
||||
`if (!zoomCoord_) return;` on that same path.
|
||||
|
||||
### Makefile: both variants from one `make`
|
||||
|
||||
`make jfstrx` used to rebuild the *same* binary with `-DJFSTRX` (the old
|
||||
`.buildflags` stamp existed only to force a full rebuild when switching), so the
|
||||
two variants overwrote each other. Now the objects and the executables are
|
||||
separate:
|
||||
|
||||
* `online_jf_*.o` + `jf_online_display_gui` (normal),
|
||||
* `online_jf_*.JFSTRX.o` + `jf_online_display_gui_JFSTRX` (compiled with
|
||||
`-DJFSTRX`),
|
||||
|
||||
and `all` builds both, so the two can never be mixed by accident (they have
|
||||
different geometry and struct layouts). `make normal` and `make jfstrx` remain
|
||||
as aliases, the flag stamp is gone, `clean` removes both sets, and `.gitignore`
|
||||
covers the second binary and the `.JFSTRX.o` files.
|
||||
|
||||
* Zoomed images were painted over the axes: the scaled image is much larger than
|
||||
the plot area (only the viewport window is meant to be visible), and the ROI
|
||||
overlays were drawn in widget coordinates without a clip, so a ROI partly
|
||||
outside the visible window ran across the tick labels and up to the window
|
||||
edge. `paintEvent()` now sets `setClipRect(ir)` around the image and around
|
||||
the two overlays (restoring before the axes so the frame, grid and labels are
|
||||
drawn on top of a clean widget). Verified by sampling a deep-zoom screenshot:
|
||||
the margin left of the frame is the widget's black background, not image
|
||||
colour, while the plot interior still carries the image.
|
||||
|
||||
* The strip-xel decoder (../../dataStructures/jungfrauLGADStrixelsData_new.h)
|
||||
dumped its header on every construction - "Jungfrau strixels 2X single chip
|
||||
with full module data" / "sizeofheader = 112" - once per displayed frame in
|
||||
the JFSTRX build. Those three `std::cout` statements are commented out (the
|
||||
header is shared, outside this project, so the edit needed the wider sandbox
|
||||
permission; it is a comment-only change and easy to revert).
|
||||
* Deepest zoom is now build-dependent (`kMinViewport`): 8 image pixels across in
|
||||
the plain build as before, **2** in the JFSTRX build, so single strixel pixels
|
||||
can be inspected (the mapped panels are ~1000 x 1900). The same constant
|
||||
guards `setViewport()` and `zoomTo()`, so a coordinated zoom propagates down
|
||||
to the new limit.
|
||||
|
||||
### Dual-interface alignment: drain fast, pair index-aligned, shed on backlog
|
||||
|
||||
Operator report: after the machine had been busy for a while the two interfaces
|
||||
stopped being aligned and never recovered, with the deltas fluctuating. There
|
||||
were two causes, one on each side.
|
||||
|
||||
* **Holding a slab is back-pressure.** The old pairing stopped polling an
|
||||
interface whose slab was waiting for its partner (`events = 0`) and accepted
|
||||
only *equal* frame numbers. That fills slsReceiver's ZMQ ring, and the
|
||||
receiver then skips frames **on its own side** - per interface, not per
|
||||
frame - so the two frame numbers drift apart and, with exact matching, never
|
||||
meet again. Waiting "until the partner catches up" made it worse.
|
||||
|
||||
What the code does now (`Pipeline::tryAssemble()` in `online_jf_core.cpp`):
|
||||
|
||||
* **Always drain.** Every socket is polled on every turn and drained down to
|
||||
its newest message (bounded to 32 messages per socket and wake-up, so no
|
||||
interface is starved). Nothing is ever held, so the receiver's buffer stays
|
||||
empty - the whole point at 2 kHz.
|
||||
* **Pair index-aligned or not at all.** When the newest slabs carry different
|
||||
frame numbers (one interface skipped), the pair is **dropped** (`resync`) and
|
||||
the next one is tried, so every frame that is displayed or saved is stitched
|
||||
from a single detector frame. No chasing, no unbounded wait, no half frames.
|
||||
* **Latch for a permanent offset.** If nothing pairs for 500 ms the offset is
|
||||
structural (an interface that keeps skipping), so the newest slabs are paired
|
||||
anyway (`skewedPairs_`, logged) to keep the stream alive; strict pairing
|
||||
comes back by itself as soon as a pair matches again.
|
||||
* **Decimation is manual by default**, `--decimate 2|4|8` (1 frame in N, dropped
|
||||
by detector frame number so both interfaces drop the same ones). The automatic
|
||||
ladder (1 -> 2 -> 4 -> 8, stepping back up after 5 s clean) only runs with
|
||||
`--auto-decimate`: frames are queued in the *gate*, so the backlog is
|
||||
`framesIn - framesPass - framesDrop - framesScDrop` - counting gate rejects as
|
||||
backlog used to pin the ladder at 1/8 whenever the gate rejected anything.
|
||||
* **A silent interface is reported, not waited for**, so a dead stream cannot
|
||||
freeze the pipeline.
|
||||
* Audit: `--stats` prints `resync` / `behind` / `incomplete` and the
|
||||
per-second `[align] keep=1/<N> shed=<n>` line; ladder steps and "dropping the
|
||||
pair" messages are rate-limited.
|
||||
|
||||
Verified against the live receiver `10.1.1.106 30026+30027` at ~2000 frames/s:
|
||||
20 s runs alone and with eight CPU hogs (`yes`) both keep `drop=0`,
|
||||
`in == pass`, `keep=1/1`, `shed=0`, with no unpaired-interface message. The
|
||||
synthetic feeder with equal keys gives `resync=0`; with a permanent one-frame
|
||||
offset (`dual ... 1`) the latch keeps ~16 frames/s at 20 fps instead of
|
||||
stalling. At 2 kHz the interfaces are a frame apart often enough that roughly
|
||||
one pair in four is dropped by the strict rule - the intended trade (aligned
|
||||
frames only). A one-frame lookahead per interface (swap two buffers, no copy)
|
||||
would recover them; it is the obvious follow-up if the saved-frame rate ever
|
||||
matters.
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+184
@@ -0,0 +1,184 @@
|
||||
// Unit test for the gate's contiguity proxy ("Contiguous (N px within circle,
|
||||
// r = N/2+1)").
|
||||
//
|
||||
// The rule must be: pass when SOME circle of radius r contains at least N
|
||||
// above-threshold pixels. It used to require every above-threshold pixel to be
|
||||
// within r of the centroid, which rejected perfectly valid events (two
|
||||
// separated clusters) - the bug this test pins down.
|
||||
//
|
||||
// Build: make gate_contig_test Run: ./test/gate_contig_test
|
||||
#include "online_jf.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
int gFail = 0;
|
||||
|
||||
void expect(const char *what, bool got, bool want) {
|
||||
const bool ok = (got == want);
|
||||
if (!ok)
|
||||
gFail++;
|
||||
std::printf("%-58s %s (got %s, want %s)\n", what, ok ? "ok " : "FAIL",
|
||||
got ? "pass" : "drop", want ? "pass" : "drop");
|
||||
}
|
||||
|
||||
struct Frame {
|
||||
std::vector<short> px = std::vector<short>(NCH, 0);
|
||||
|
||||
void hit(int x, int y, short v = 2000) {
|
||||
px[static_cast<size_t>(y) * kRawWidth + x] = v;
|
||||
}
|
||||
void clear() { std::fill(px.begin(), px.end(), 0); }
|
||||
};
|
||||
|
||||
// Runs the real GateEngine over the frame, raw ADC values, no pedestals.
|
||||
bool passes(GateEngine &g, Frame &f) {
|
||||
uint64_t above = 0;
|
||||
bool scDropped = false;
|
||||
return g.evaluate(f.px.data(), NCH, 0, nullptr, 0, 0, above, &scDropped);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
GateEngine g;
|
||||
g.setEnabled(true);
|
||||
g.setThreshold(1000.0f); // the frame's hits are 2000 -> above threshold
|
||||
g.setMinPixels(5);
|
||||
g.setContiguous(true);
|
||||
|
||||
Frame f;
|
||||
|
||||
// 1. one tight cluster of exactly N hits: must pass
|
||||
f.clear();
|
||||
for (int i = 0; i < 5; i++)
|
||||
f.hit(100 + i, 100);
|
||||
expect("5 hits in a row (spread 4 px <= 2r)", passes(g, f), true);
|
||||
|
||||
// 2. a compact blob: must pass
|
||||
f.clear();
|
||||
f.hit(300, 200);
|
||||
f.hit(301, 200);
|
||||
f.hit(302, 200);
|
||||
f.hit(301, 201);
|
||||
f.hit(301, 199);
|
||||
expect("5 hits in a compact cross", passes(g, f), true);
|
||||
|
||||
// 3. N hits far apart, no cluster: must be rejected
|
||||
f.clear();
|
||||
for (int i = 0; i < 5; i++)
|
||||
f.hit(100 + 100 * i, 300);
|
||||
expect("5 isolated hits 100 px apart", passes(g, f), false);
|
||||
|
||||
// 4. fewer than N hits, however tight: must be rejected
|
||||
f.clear();
|
||||
for (int i = 0; i < 4; i++)
|
||||
f.hit(500 + i, 400);
|
||||
expect("4 hits (below min pixels)", passes(g, f), false);
|
||||
|
||||
// 5. THE REPORTED BUG: two separated clusters, each with N hits.
|
||||
// The centroid falls between them, which used to reject the frame.
|
||||
f.clear();
|
||||
for (int i = 0; i < 5; i++)
|
||||
f.hit(100 + i, 50); // cluster A
|
||||
for (int i = 0; i < 5; i++)
|
||||
f.hit(700 + i, 450); // cluster B
|
||||
expect("two clusters of 5, 700 px apart (must pass)", passes(g, f), true);
|
||||
|
||||
// 6. two groups of 4: no circle holds 5 -> reject
|
||||
f.clear();
|
||||
for (int i = 0; i < 4; i++)
|
||||
f.hit(100 + i, 60);
|
||||
for (int i = 0; i < 4; i++)
|
||||
f.hit(600 + i, 440);
|
||||
expect("two clusters of 4 (no circle holds 5)", passes(g, f), false);
|
||||
|
||||
// 7. a spread-out event with one dense core: pass
|
||||
f.clear();
|
||||
for (int i = 0; i < 5; i++)
|
||||
f.hit(400 + i, 300);
|
||||
f.hit(800, 100);
|
||||
f.hit(900, 200);
|
||||
expect("dense core plus 2 stragglers", passes(g, f), true);
|
||||
|
||||
// 8. N follows min_pixels: 3 hits pass when min_pixels is 3
|
||||
g.setMinPixels(3);
|
||||
f.clear();
|
||||
for (int i = 0; i < 3; i++)
|
||||
f.hit(200 + i, 200);
|
||||
expect("3 hits with min_pixels = 3", passes(g, f), true);
|
||||
f.clear();
|
||||
for (int i = 0; i < 2; i++)
|
||||
f.hit(200 + i, 200);
|
||||
expect("2 hits with min_pixels = 3", passes(g, f), false);
|
||||
|
||||
// 9. contiguity off: the plain count decides (2 hits < 3 -> reject)
|
||||
g.setContiguous(false);
|
||||
f.clear();
|
||||
f.hit(10, 10);
|
||||
f.hit(900, 400);
|
||||
expect("contiguity off, 2 hits with min_pixels = 3", passes(g, f), false);
|
||||
f.clear();
|
||||
for (int i = 0; i < 3; i++)
|
||||
f.hit(10 + i, 10);
|
||||
expect("contiguity off, 3 hits with min_pixels = 3", passes(g, f), true);
|
||||
|
||||
// ---- hot pixel mask taken from a photon map ---------------------------
|
||||
{
|
||||
GateEngine h;
|
||||
h.setEnabled(true);
|
||||
h.setThreshold(1000.0f);
|
||||
h.setMinPixels(5);
|
||||
h.setContiguous(false);
|
||||
h.setHotSuppress(true);
|
||||
|
||||
// The map has two very bright pixels: those are the hot ones.
|
||||
std::vector<float> map(NCH, 0.0f);
|
||||
map[10 * kRawWidth + 10] = 400.0f;
|
||||
map[10 * kRawWidth + 11] = 900.0f;
|
||||
hotSetMaskFromMap(map.data(), map.size(), 100.0);
|
||||
std::printf("mask from map > 100: %llu px (want 2)\n",
|
||||
static_cast<unsigned long long>(hotPixelCount()));
|
||||
if (hotPixelCount() != 2)
|
||||
gFail++;
|
||||
|
||||
Frame f3;
|
||||
// 5 hits, two of them on the masked pixels -> only 3 count -> drop
|
||||
f3.clear();
|
||||
for (int i = 0; i < 5; i++)
|
||||
f3.hit(10 + i, 10);
|
||||
expect("5 hits, 2 on hot pixels (min 5)", passes(h, f3), false);
|
||||
|
||||
// a real cluster away from the mask still passes
|
||||
f3.clear();
|
||||
for (int i = 0; i < 5; i++)
|
||||
f3.hit(300 + i, 300);
|
||||
expect("real cluster of 5 with the mask active", passes(h, f3), true);
|
||||
|
||||
// cleared mask: the hot pixels count again
|
||||
hotClearMask();
|
||||
expect("after hotClearMask, 5 hits count", passes(h, f3), true);
|
||||
f3.clear();
|
||||
for (int i = 0; i < 5; i++)
|
||||
f3.hit(10 + i, 10);
|
||||
expect("after hotClearMask, the 5 hits pass", passes(h, f3), true);
|
||||
|
||||
// suppression off: the mask is ignored by the gate
|
||||
hotSetMaskFromMap(map.data(), map.size(), 100.0);
|
||||
h.setHotSuppress(false);
|
||||
f3.clear();
|
||||
for (int i = 0; i < 5; i++)
|
||||
f3.hit(10 + i, 10);
|
||||
expect("suppression off, masked pixels count again", passes(h, f3),
|
||||
true);
|
||||
hotClearMask();
|
||||
}
|
||||
|
||||
std::printf("\n%s (%d failure(s))\n", gFail ? "FAILED" : "all tests passed",
|
||||
gFail);
|
||||
return gFail ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-other
|
||||
// ===========================================================================
|
||||
// test/jf_feeder.cpp -- synthetic slsReceiver stand-in.
|
||||
//
|
||||
// Publishes the same <json header><binary frame> ZMQ message pair that
|
||||
// slsReceiver sends, so jf_online_display_gui can be exercised without hardware.
|
||||
//
|
||||
// Every 3rd frame carries NO signal; the other frames carry a 40-pixel
|
||||
// cluster at ADC 300 on a baseline of 100. With
|
||||
// ./jf_online_display_gui 400 127.0.0.1 30777 --stats --pede /nonexistent
|
||||
// the gate threshold is 200, so exactly 2/3 of the frames must pass.
|
||||
//
|
||||
// make feeder
|
||||
// ./test/jf_feeder tcp://*:30777 200 20 &
|
||||
// ./jf_online_display_gui 400 127.0.0.1 30777 --stats --pede /nonexistent
|
||||
//
|
||||
// Subscriber mode (counts the frames republished by --pub):
|
||||
// ./test/jf_feeder sub tcp://127.0.0.1:30778 8
|
||||
//
|
||||
// 4th positional arg = initial silent period in seconds (idle-receiver test).
|
||||
// ===========================================================================
|
||||
#include <zmq.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
int runSubscriber(const char *addr, int seconds) {
|
||||
void *ctx = zmq_ctx_new();
|
||||
void *sub = zmq_socket(ctx, ZMQ_SUB);
|
||||
int rcvtimeo = 200;
|
||||
zmq_setsockopt(sub, ZMQ_RCVTIMEO, &rcvtimeo, sizeof(rcvtimeo));
|
||||
zmq_setsockopt(sub, ZMQ_SUBSCRIBE, "", 0);
|
||||
if (zmq_connect(sub, addr) != 0) {
|
||||
fprintf(stderr, "connect %s failed: %s\n", addr,
|
||||
zmq_strerror(zmq_errno()));
|
||||
return 1;
|
||||
}
|
||||
printf("subscriber: connected to %s for %d s\n", addr, seconds);
|
||||
fflush(stdout);
|
||||
|
||||
std::vector<char> data(2 * 1024 * 1024);
|
||||
char hdr[16384];
|
||||
int frames = 0, endMarkers = 0;
|
||||
int dumped = 0;
|
||||
const auto deadline =
|
||||
std::chrono::steady_clock::now() + std::chrono::seconds(seconds);
|
||||
while (std::chrono::steady_clock::now() < deadline) {
|
||||
const int hn = zmq_recv(sub, hdr, sizeof(hdr) - 1, 0);
|
||||
if (hn < 0)
|
||||
continue;
|
||||
hdr[hn] = '\0';
|
||||
const bool isData = (strstr(hdr, "\"data\":1") != nullptr);
|
||||
if (isData)
|
||||
frames++;
|
||||
else if (strstr(hdr, "\"data\":0") != nullptr)
|
||||
endMarkers++;
|
||||
int more = 0;
|
||||
size_t moreSize = sizeof(more);
|
||||
zmq_getsockopt(sub, ZMQ_RCVMORE, &more, &moreSize);
|
||||
int dn = -1;
|
||||
if (more)
|
||||
dn = zmq_recv(sub, data.data(), data.size(), 0);
|
||||
if (dumped < 3) {
|
||||
dumped++;
|
||||
char head[400];
|
||||
snprintf(head, sizeof(head), "%.399s", hdr);
|
||||
printf("subscriber: hdr_len=%d data_len=%d more=%d head=%s\n", hn,
|
||||
dn, more, head);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
printf("subscriber: received %d data frames, %d end markers\n", frames,
|
||||
endMarkers);
|
||||
fflush(stdout);
|
||||
zmq_close(sub);
|
||||
zmq_ctx_term(ctx);
|
||||
return frames > 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
// Dual-interface stand-in: two PUB sockets on base and base+1, each sending
|
||||
// one 1024x256 Y-slab of the same frame (the slsReceiver multi-UDP-interface
|
||||
// layout). The bottom slab is filled with botVal, the top slab with topVal,
|
||||
// so the assembled image can be verified from the gate counters.
|
||||
// ./test/jf_feeder dual <basePort> <nframes> <fps> [delaySec] [botVal]
|
||||
// [topVal] [topLagFrames]
|
||||
int runDual(const char *baseStr, int nframes, int fps, int delayMs, int botVal,
|
||||
int topVal, int topLag) {
|
||||
const int base = atoi(baseStr);
|
||||
const int W = 1024, H = 256;
|
||||
const int bytes = W * H * 2;
|
||||
|
||||
void *ctx = zmq_ctx_new();
|
||||
void *bot = zmq_socket(ctx, ZMQ_PUB);
|
||||
void *top = zmq_socket(ctx, ZMQ_PUB);
|
||||
int hwm = 1000;
|
||||
zmq_setsockopt(bot, ZMQ_SNDHWM, &hwm, sizeof(hwm));
|
||||
zmq_setsockopt(top, ZMQ_SNDHWM, &hwm, sizeof(hwm));
|
||||
const std::string a0 = "tcp://*:" + std::to_string(base);
|
||||
const std::string a1 = "tcp://*:" + std::to_string(base + 1);
|
||||
if (zmq_bind(bot, a0.c_str()) != 0 || zmq_bind(top, a1.c_str()) != 0) {
|
||||
fprintf(stderr, "dual bind failed: %s\n", zmq_strerror(zmq_errno()));
|
||||
return 1;
|
||||
}
|
||||
printf("feeder: dual mode on %s and %s, %d frames at %d fps "
|
||||
"(bot=%d top=%d topLag=%d)\n",
|
||||
a0.c_str(), a1.c_str(), nframes, fps, botVal, topVal, topLag);
|
||||
fflush(stdout);
|
||||
|
||||
std::vector<short> slab(W * H);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(delayMs));
|
||||
|
||||
auto sendSlab = [&](void *sock, int value, int frameNo) {
|
||||
for (size_t i = 0; i < slab.size(); i++)
|
||||
slab[i] = static_cast<short>(value);
|
||||
char hdr[512];
|
||||
// detSpec3 bits 8-11 carry the storage cell; cycle 0..15 so the SC
|
||||
// filter can be exercised end to end
|
||||
const unsigned detSpec3 = ((unsigned)(frameNo % 16) & 0xF) << 8;
|
||||
const int hn = snprintf(
|
||||
hdr, sizeof(hdr),
|
||||
"{\"jsonversion\":5,\"data\":1,\"size\":%d,"
|
||||
"\"shape\":[%d,%d],\"frameNumber\":%d,\"frameIndex\":%d,"
|
||||
"\"detSpec1\":0,\"detSpec3\":%u}",
|
||||
bytes, W, H, frameNo, frameNo, detSpec3);
|
||||
zmq_send(sock, hdr, hn, ZMQ_SNDMORE);
|
||||
zmq_send(sock, slab.data(), bytes, 0);
|
||||
};
|
||||
|
||||
for (int f = 0; f < nframes; f++) {
|
||||
sendSlab(bot, botVal, f);
|
||||
const int topFrame = (f - topLag >= 0) ? f - topLag : f;
|
||||
sendSlab(top, topVal, topFrame);
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(1000 / (fps > 0 ? fps : 1)));
|
||||
}
|
||||
const char *endhdr = "{\"jsonversion\":5,\"data\":0}";
|
||||
zmq_send(bot, endhdr, strlen(endhdr), 0);
|
||||
zmq_send(top, endhdr, strlen(endhdr), 0);
|
||||
printf("feeder: dual done\n");
|
||||
fflush(stdout);
|
||||
zmq_close(bot);
|
||||
zmq_close(top);
|
||||
zmq_ctx_term(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc > 1 && std::string(argv[1]) == "sub") {
|
||||
const char *addr = (argc > 2) ? argv[2] : "tcp://127.0.0.1:30778";
|
||||
const int seconds = (argc > 3) ? atoi(argv[3]) : 8;
|
||||
return runSubscriber(addr, seconds);
|
||||
}
|
||||
if (argc > 1 && std::string(argv[1]) == "dual") {
|
||||
return runDual(argc > 2 ? argv[2] : "30777", argc > 3 ? atoi(argv[3]) : 60,
|
||||
argc > 4 ? atoi(argv[4]) : 25,
|
||||
argc > 5 ? atoi(argv[5]) * 1000 : 800,
|
||||
argc > 6 ? atoi(argv[6]) : 100,
|
||||
argc > 7 ? atoi(argv[7]) : 150,
|
||||
argc > 8 ? atoi(argv[8]) : 0);
|
||||
}
|
||||
|
||||
const char *addr = (argc > 1) ? argv[1] : "tcp://*:30777";
|
||||
const int nframes = (argc > 2) ? atoi(argv[2]) : 200;
|
||||
const int fps = (argc > 3) ? atoi(argv[3]) : 20;
|
||||
// optional 4th arg: stay bound and connected but SILENT for N seconds
|
||||
// first (checks that an idle-but-alive receiver is not called "stopped")
|
||||
const int delayMs = (argc > 4) ? atoi(argv[4]) * 1000 : 800;
|
||||
const int NCH = 524288;
|
||||
|
||||
void *ctx = zmq_ctx_new();
|
||||
void *pub = zmq_socket(ctx, ZMQ_PUB);
|
||||
int hwm = 1000;
|
||||
zmq_setsockopt(pub, ZMQ_SNDHWM, &hwm, sizeof(hwm));
|
||||
if (zmq_bind(pub, addr) != 0) {
|
||||
fprintf(stderr, "bind %s failed: %s\n", addr,
|
||||
zmq_strerror(zmq_errno()));
|
||||
return 1;
|
||||
}
|
||||
printf("feeder: bound %s, sending %d frames at %d fps\n", addr, nframes,
|
||||
fps);
|
||||
fflush(stdout);
|
||||
|
||||
std::vector<short> frame(NCH);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(delayMs));
|
||||
|
||||
// detSpec1 packs the receiver ROI as 4 x uint16: xmin|xmax|ymin|ymax
|
||||
const unsigned long long detSpec1 = ((unsigned long long)10u << 48) |
|
||||
((unsigned long long)400u << 32) |
|
||||
((unsigned long long)20u << 16) | 300u;
|
||||
|
||||
for (int f = 0; f < nframes; f++) {
|
||||
const bool signal = (f % 3) != 0;
|
||||
for (int i = 0; i < NCH; i++)
|
||||
frame[i] = 100; // baseline, gain 0
|
||||
if (signal)
|
||||
for (int k = 0; k < 40; k++)
|
||||
frame[1000 + k] = 300; // 40 px above the 200 threshold
|
||||
|
||||
char hdr[512];
|
||||
const int hn = snprintf(
|
||||
hdr, sizeof(hdr),
|
||||
"{\"jsonversion\":5,\"data\":1,\"size\":%d,\"detSpec1\":%llu,"
|
||||
"\"frameIndex\":%d,\"bitmode\":1,\"fileIndex\":0,"
|
||||
"\"detshape\":[1,1],\"shape\":[1024,512],\"acqIndex\":%d,"
|
||||
"\"frameNumber\":%d,\"expLength\":0,\"packetNumber\":0,"
|
||||
"\"timestamp\":0,\"modId\":0,\"row\":0,\"column\":0,\"detSpec2\":0,"
|
||||
"\"detSpec3\":%u,\"detSpec4\":0,\"detType\":0,\"version\":0,"
|
||||
"\"flipRows\":0,\"quad\":0,\"completeImage\":1}",
|
||||
NCH * 2, detSpec1, f, f, f,
|
||||
((unsigned)(f % 16) & 0xF) << 8);
|
||||
zmq_send(pub, hdr, hn, ZMQ_SNDMORE);
|
||||
zmq_send(pub, frame.data(), NCH * 2, 0);
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(1000 / (fps > 0 ? fps : 1)));
|
||||
}
|
||||
|
||||
const char *endhdr = "{\"jsonversion\":5,\"data\":0}";
|
||||
zmq_send(pub, endhdr, strlen(endhdr), 0);
|
||||
printf("feeder: done\n");
|
||||
fflush(stdout);
|
||||
zmq_close(pub);
|
||||
zmq_ctx_term(ctx);
|
||||
return 0;
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
// Standalone JFSTRX check that the six-panel fill geometry is consistent with
|
||||
// the strixel decoder: every real (non-dummy) mapped pixel must land inside a
|
||||
// valid panel at an in-range local coordinate, and each panel must be fully
|
||||
// covered by the decoder map (so no panel stays empty / has holes).
|
||||
#include "online_jf.h"
|
||||
|
||||
#include "jungfrauLGADStrixelsData_new.h"
|
||||
#include "sls/sls_detector_defs.h"
|
||||
#include "slsDetectorData.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
using header = sls::defs::sls_receiver_header;
|
||||
|
||||
int main() {
|
||||
initGeometry();
|
||||
jungfrauLGADStrixelsData *dec = new jungfrauLGADStrixelsData();
|
||||
const int g1_nrows = 165, g2_nrows = 320, g3_nrows = 476;
|
||||
const int mc6g3_start = g1_nrows + g2_nrows + g3_nrows;
|
||||
static const int panelStartY[kNumAdcPanels] = {0, 165, 485,
|
||||
1757, 1437, 961};
|
||||
long perPanel[kNumAdcPanels] = {0};
|
||||
long real = 0;
|
||||
long dropped = 0; // real pixels outside the booked panel rectangles
|
||||
bool ok = true;
|
||||
for (int ipx = 0; ipx < nx; ++ipx) {
|
||||
for (int ipy = 0; ipy < ny; ++ipy) {
|
||||
if (dec->dataMap[ipy][ipx] == int(sizeof(header)))
|
||||
continue; // dummy strixel
|
||||
++real;
|
||||
const int group = dec->groupmap[ipy][ipx];
|
||||
if (group < 0 || group > 2) {
|
||||
ok = false;
|
||||
continue;
|
||||
}
|
||||
const int base = (ipy >= mc6g3_start) ? 3 : 0;
|
||||
const int p = base + group;
|
||||
const int lx = ipx;
|
||||
const int ly = ipy - panelStartY[p];
|
||||
if (lx < 0 || lx >= kAdcPanelW[p] || ly < 0 || ly >= kAdcPanelH[p]) {
|
||||
// the decoder maps a couple of extra right-edge columns for
|
||||
// chip1 g2/g3 that the ROOT histograms (and these panels) do
|
||||
// not book; dropping them matches the original display
|
||||
++dropped;
|
||||
continue;
|
||||
}
|
||||
++perPanel[p];
|
||||
}
|
||||
}
|
||||
delete dec;
|
||||
|
||||
std::printf("mapped %d x %d, real pixels %ld, dropped edge pixels %ld\n",
|
||||
nx, ny, real, dropped);
|
||||
for (int p = 0; p < kNumAdcPanels; ++p) {
|
||||
const long need = long(kAdcPanelW[p]) * kAdcPanelH[p];
|
||||
std::printf("panel %d: %dx%d filled %ld/%ld%s\n", p, kAdcPanelW[p],
|
||||
kAdcPanelH[p], perPanel[p], need,
|
||||
perPanel[p] == need ? "" : " <-- NOT FULL");
|
||||
if (perPanel[p] != need)
|
||||
ok = false;
|
||||
}
|
||||
std::printf("%s\n", ok ? "PASS: six-panel geometry"
|
||||
: "FAIL: six-panel geometry");
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
// Standalone check for the ROI writer (no hardware, no GUI):
|
||||
//
|
||||
// 1. a Pedestal corrected save follows the LIVE pedestal, not the snapshot
|
||||
// taken at Start Save (one frame before, one after a mid-run swap);
|
||||
// 2. the periodic checkpoint leaves a CLEAN pair on disk while the save is
|
||||
// still running: the JSON sidecar is published atomically (.part ->
|
||||
// rename) and the HDF5 is shrunk to exactly the frames written, so a
|
||||
// copy taken at a checkpoint boundary is self-consistent.
|
||||
//
|
||||
// Not part of the build; compile by hand, see README "Testing without
|
||||
// hardware".
|
||||
#include "online_jf.h"
|
||||
|
||||
#include "H5Cpp.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
using namespace H5;
|
||||
|
||||
static bool fileExists(const std::string &p) {
|
||||
std::ifstream f(p);
|
||||
return f.good();
|
||||
}
|
||||
|
||||
static std::string readFile(const std::string &p) {
|
||||
std::ifstream f(p, std::ios::binary);
|
||||
std::ostringstream ss;
|
||||
ss << f.rdbuf();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// Read the /data frame count and the first pixel of one slice from the LIVE
|
||||
// file with the SWMR read flag - exactly what an external checker does while
|
||||
// the save runs.
|
||||
static bool readSlice(const std::string &path, hsize_t slice, hsize_t &frames,
|
||||
float &px) {
|
||||
hid_t f = H5Fopen(path.c_str(), H5F_ACC_RDONLY | H5F_ACC_SWMR_READ,
|
||||
H5P_DEFAULT);
|
||||
if (f < 0)
|
||||
return false;
|
||||
hid_t d = H5Dopen2(f, "/data", H5P_DEFAULT);
|
||||
if (d < 0) {
|
||||
H5Fclose(f);
|
||||
return false;
|
||||
}
|
||||
hid_t s = H5Dget_space(d);
|
||||
hsize_t dims[3] = {0, 0, 0};
|
||||
H5Sget_simple_extent_dims(s, dims, nullptr);
|
||||
frames = dims[0];
|
||||
bool ok = false;
|
||||
if (dims[0] > slice) {
|
||||
hsize_t off[3] = {slice, 0, 0};
|
||||
hsize_t cnt[3] = {1, 1, 1};
|
||||
H5Sselect_hyperslab(s, H5S_SELECT_SET, off, nullptr, cnt, nullptr);
|
||||
hid_t m = H5Screate_simple(3, cnt, nullptr);
|
||||
ok = H5Dread(d, H5T_NATIVE_FLOAT, m, s, H5P_DEFAULT, &px) >= 0;
|
||||
H5Sclose(m);
|
||||
}
|
||||
H5Sclose(s);
|
||||
H5Dclose(d);
|
||||
H5Fclose(f);
|
||||
return ok;
|
||||
}
|
||||
|
||||
// wait until the periodic JSON says at least `saved` frames are committed
|
||||
static std::string waitJson(const std::string &jsonPath, uint64_t saved) {
|
||||
const std::string needle = "\"saved\": " + std::to_string(saved);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
if (fileExists(jsonPath)) {
|
||||
const std::string t = readFile(jsonPath);
|
||||
if (t.find(needle) != std::string::npos)
|
||||
return t;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
int main() {
|
||||
const int W = 1024, H = 4;
|
||||
const hsize_t h = 4, w = 8;
|
||||
|
||||
RoiSaver saver;
|
||||
RoiSaver::StartInfo info;
|
||||
info.x = 0;
|
||||
info.y = 0;
|
||||
info.w = static_cast<int>(w);
|
||||
info.h = static_cast<int>(h);
|
||||
info.dir = ".";
|
||||
info.baseName = "pede_live_test";
|
||||
info.corrected = true;
|
||||
info.checkpointMs = 300; // fast checkpoints for the test
|
||||
|
||||
auto setA = std::make_shared<PedeSet>();
|
||||
std::fill(setA->g0.begin(), setA->g0.end(), 100.0f);
|
||||
PedestalStore::instance().install(setA);
|
||||
info.peds = setA; // the snapshot the GUI would have taken at Start Save
|
||||
|
||||
std::string err;
|
||||
if (!saver.start(info, err)) {
|
||||
std::printf("FAIL start: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
const std::string h5 = saver.status().h5Path;
|
||||
const std::string js = saver.status().jsonPath;
|
||||
|
||||
std::vector<short> frame(static_cast<size_t>(W) * H, 1000);
|
||||
SavedFrameHeader h1;
|
||||
h1.frameNumber = 1;
|
||||
saver.offer(frame.data(), W, H, h1);
|
||||
|
||||
// --- checkpoint 1: exactly one frame, corrected with pedestal A -------
|
||||
const std::string j1 = waitJson(js, 1);
|
||||
if (j1.empty()) {
|
||||
std::printf("FAIL: no periodic JSON with 1 frame\n");
|
||||
return 2;
|
||||
}
|
||||
const bool liveMid = j1.find("\"complete\": false") != std::string::npos;
|
||||
hsize_t n1 = 0;
|
||||
float p1 = -1.0f;
|
||||
const bool live1 = readSlice(h5, 0, n1, p1); // live file, SWMR read
|
||||
std::printf("checkpoint1: periodic json (complete:false)=%s live swmr "
|
||||
"read=%s h5 extent=%llu px=%.1f (expect true, true, 1, 900)\n",
|
||||
liveMid ? "true" : "false", live1 ? "true" : "false",
|
||||
(unsigned long long)n1, p1);
|
||||
|
||||
// --- the tracker swaps the pedestal mid-run ---------------------------
|
||||
auto setB = std::make_shared<PedeSet>();
|
||||
std::fill(setB->g0.begin(), setB->g0.end(), 150.0f);
|
||||
PedestalStore::instance().install(setB);
|
||||
SavedFrameHeader h2;
|
||||
h2.frameNumber = 2;
|
||||
saver.offer(frame.data(), W, H, h2);
|
||||
|
||||
const std::string j2 = waitJson(js, 2);
|
||||
if (j2.empty()) {
|
||||
std::printf("FAIL: no periodic JSON with 2 frames\n");
|
||||
return 3;
|
||||
}
|
||||
hsize_t n2 = 0;
|
||||
float p2 = -1.0f;
|
||||
const bool live2 = readSlice(h5, 1, n2, p2); // live file, SWMR read
|
||||
std::printf("checkpoint2: live swmr read=%s h5 extent=%llu slice1 px=%.1f "
|
||||
"(expect true, 2, 850)\n",
|
||||
live2 ? "true" : "false", (unsigned long long)n2, p2);
|
||||
|
||||
const bool noPart = !fileExists(js + ".part");
|
||||
|
||||
saver.requestStop("test");
|
||||
saver.finish();
|
||||
const std::string jf = readFile(js);
|
||||
const bool finalComplete =
|
||||
jf.find("\"complete\": true") != std::string::npos;
|
||||
|
||||
const bool ok = liveMid && live1 && (n1 == 1) && (p1 == 900.0f) && live2 &&
|
||||
(n2 == 2) && (p2 == 850.0f) && noPart && finalComplete;
|
||||
std::printf("final: complete=%s no .part left=%s\n",
|
||||
finalComplete ? "true" : "false", noPart ? "true" : "false");
|
||||
std::printf("%s\n",
|
||||
ok ? "PASS: live pedestal + periodic clean checkpoint"
|
||||
: "FAIL");
|
||||
return ok ? 0 : 4;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Minimal XTest driver: xclick <display> click <x> <y> | key <keysym>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
extern int XTestFakeMotionEvent(Display *, int, int, int, unsigned long);
|
||||
extern int XTestFakeButtonEvent(Display *, unsigned int, int, unsigned long);
|
||||
extern int XTestFakeKeyEvent(Display *, unsigned int, int, unsigned long);
|
||||
int main(int argc, char **argv) {
|
||||
Display *d = XOpenDisplay(argc > 1 ? argv[1] : NULL);
|
||||
if (!d) { fprintf(stderr, "no display\n"); return 1; }
|
||||
if (argc > 3 && strcmp(argv[2], "click") == 0) {
|
||||
int x = atoi(argv[3]), y = atoi(argv[4]);
|
||||
XTestFakeMotionEvent(d, -1, x, y, 0);
|
||||
XTestFakeButtonEvent(d, 1, True, 0);
|
||||
XTestFakeButtonEvent(d, 1, False, 0);
|
||||
} else if (argc > 3 && strcmp(argv[2], "press") == 0) {
|
||||
XTestFakeMotionEvent(d, -1, atoi(argv[3]), atoi(argv[4]), 0);
|
||||
XTestFakeButtonEvent(d, 1, True, 0);
|
||||
} else if (argc > 3 && strcmp(argv[2], "move") == 0) {
|
||||
XTestFakeMotionEvent(d, -1, atoi(argv[3]), atoi(argv[4]), 0);
|
||||
} else if (argc > 2 && strcmp(argv[2], "release") == 0) {
|
||||
XTestFakeButtonEvent(d, 1, False, 0);
|
||||
} else if (argc > 2 && strcmp(argv[2], "raise") == 0) {
|
||||
const char *want = argc > 3 ? argv[3] : "";
|
||||
Window root = DefaultRootWindow(d), r2, par, *kids = NULL;
|
||||
unsigned n = 0;
|
||||
if (XQueryTree(d, root, &r2, &par, &kids, &n)) {
|
||||
for (unsigned i = 0; i < n; i++) {
|
||||
char *nm = NULL;
|
||||
if (XFetchName(d, kids[i], &nm) && nm) {
|
||||
if (strstr(nm, want)) {
|
||||
XRaiseWindow(d, kids[i]);
|
||||
XSetInputFocus(d, kids[i], RevertToParent, CurrentTime);
|
||||
printf("raised: %s\n", nm);
|
||||
}
|
||||
XFree(nm);
|
||||
}
|
||||
}
|
||||
if (kids) XFree(kids);
|
||||
}
|
||||
} else if (argc > 4 && strcmp(argv[2], "movewin") == 0) {
|
||||
const char *want = argv[3];
|
||||
Window root = DefaultRootWindow(d), r2, par, *kids = NULL;
|
||||
unsigned n = 0;
|
||||
if (XQueryTree(d, root, &r2, &par, &kids, &n)) {
|
||||
for (unsigned i = 0; i < n; i++) {
|
||||
char *nm = NULL;
|
||||
if (XFetchName(d, kids[i], &nm) && nm) {
|
||||
if (strstr(nm, want)) {
|
||||
XMoveWindow(d, kids[i], atoi(argv[4]), atoi(argv[5]));
|
||||
printf("moved: %s\n", nm);
|
||||
}
|
||||
XFree(nm);
|
||||
}
|
||||
}
|
||||
if (kids) XFree(kids);
|
||||
}
|
||||
} else if (argc > 2 && strcmp(argv[2], "key") == 0) {
|
||||
KeySym ks = XStringToKeysym(argc > 3 ? argv[3] : "Return");
|
||||
KeyCode kc = XKeysymToKeycode(d, ks);
|
||||
XTestFakeKeyEvent(d, kc, True, 0);
|
||||
XTestFakeKeyEvent(d, kc, False, 0);
|
||||
}
|
||||
XFlush(d);
|
||||
XCloseDisplay(d);
|
||||
return 0;
|
||||
}
|
||||
@@ -10,8 +10,10 @@ script_dir = ../../etc/
|
||||
|
||||
CROSS = bfin-uclinux-
|
||||
CC = $(CROSS)gcc
|
||||
CFLAGS += -Wall -std=gnu99 -DCHIPTESTBOARDD -DSTOP_SERVER -I$(main_inc) -I$(support_lib) -I$(det_lib) -I$(current_dir) #-DDEBUG1 #-DVERBOSEI #-DVERBOSE
|
||||
CFLAGS += -O2 -Wall -Wextra -Wno-unused-parameter -Wformat=2 -Wredundant-decls -Werror=return-type -std=gnu99 -DCHIPTESTBOARDD -DSTOP_SERVER -I$(main_inc) -I$(support_lib) -I$(det_lib) -I$(current_dir) #-DDEBUG1 #-DVERBOSEI #-DVERBOSE
|
||||
LDLIBS += -lm -lrt -pthread
|
||||
# FLAT stack size: elf2flt defaults to 4 KB, the update and startup paths need more than 8 KB
|
||||
LDLIBS += -Wl,-elf2flt=-s262144
|
||||
PROGS = ctbDetectorServer
|
||||
DESTDIR ?= bin
|
||||
INSTMODE = 0777
|
||||
|
||||
Binary file not shown.
@@ -1355,7 +1355,7 @@ int setVLimit(int val, char *mess) {
|
||||
}
|
||||
|
||||
int validateDACIndex(enum DACINDEX ind, char *mess) {
|
||||
if (ind < 0 || ind >= NDAC_ONLY) {
|
||||
if (ind >= NDAC_ONLY) {
|
||||
sprintf(mess, "Could not set/get DAC. Invalid index %d\n", ind);
|
||||
LOG(logERROR, (mess));
|
||||
return FAIL;
|
||||
@@ -1464,7 +1464,7 @@ int setDAC(enum DACINDEX ind, int val, bool mV, char *mess) {
|
||||
}
|
||||
|
||||
int validatePowerDACIndex(enum powerIndex ind, char *mess) {
|
||||
if (ind < 0 || ind > V_POWER_IO) {
|
||||
if (ind > V_POWER_IO) {
|
||||
sprintf(mess, "Could not set/get Power DAC. Invalid index %d\n", ind);
|
||||
LOG(logERROR, (mess));
|
||||
return FAIL;
|
||||
@@ -2158,8 +2158,9 @@ int configureMAC() {
|
||||
|
||||
// start addr
|
||||
uint32_t addr = RXR_ENDPOINT_START_REG;
|
||||
// get struct memory
|
||||
udp_header *udp = (udp_header *)(Blackfin_getBaseAddress() + addr / 2);
|
||||
// build udp header
|
||||
udp_header header;
|
||||
udp_header *udp = &header;
|
||||
memset(udp, 0, sizeof(udp_header));
|
||||
|
||||
// mac addresses
|
||||
@@ -2191,6 +2192,13 @@ int configureMAC() {
|
||||
|
||||
calcChecksum(udp);
|
||||
|
||||
// force FPGA writes to be aligned and in order
|
||||
uint32_t words[sizeof(udp_header) / sizeof(uint32_t)];
|
||||
memcpy(words, udp, sizeof(udp_header));
|
||||
for (size_t i = 0; i != sizeof(words) / sizeof(words[0]); ++i) {
|
||||
bus_w(addr + i * 2, words[i]);
|
||||
}
|
||||
|
||||
cleanFifos(); // FIXME: resetPerpheral() for ctb?
|
||||
resetPeripheral();
|
||||
LOG(logINFO, ("Waiting for %d s for mac to be up\n",
|
||||
@@ -2330,7 +2338,7 @@ int validatePhaseinDegrees(enum CLKINDEX ind, int val, int retval) {
|
||||
}
|
||||
|
||||
int setFrequency(enum CLKINDEX ind, int val) {
|
||||
if (ind < 0 || ind >= NUM_CLOCKS) {
|
||||
if (ind >= NUM_CLOCKS) {
|
||||
LOG(logERROR, ("Unknown clock index %d to set frequency\n", ind));
|
||||
return FAIL;
|
||||
}
|
||||
@@ -2380,7 +2388,7 @@ int setFrequency(enum CLKINDEX ind, int val) {
|
||||
}
|
||||
|
||||
int getFrequency(enum CLKINDEX ind) {
|
||||
if (ind < 0 || ind >= NUM_CLOCKS) {
|
||||
if (ind >= NUM_CLOCKS) {
|
||||
LOG(logERROR, ("Unknown clock index %d to get frequency\n", ind));
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "xparameters.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
@@ -14,20 +15,8 @@ extern int isControlServer;
|
||||
|
||||
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
|
||||
|
||||
struct LocalLinkInterface ll_beb_local, *ll_beb;
|
||||
|
||||
struct udp_header_type udp_header;
|
||||
|
||||
int Beb_send_ndata;
|
||||
unsigned int Beb_send_buffer_size;
|
||||
unsigned int *Beb_send_data_raw;
|
||||
unsigned int *Beb_send_data;
|
||||
|
||||
int Beb_recv_ndata;
|
||||
unsigned int Beb_recv_buffer_size;
|
||||
unsigned int *Beb_recv_data_raw;
|
||||
unsigned int *Beb_recv_data;
|
||||
|
||||
short Beb_bit_mode;
|
||||
int BEB_MMAP_SIZE = 0x1000;
|
||||
|
||||
@@ -47,28 +36,6 @@ int Beb_deactivated_right_datastream = 1;
|
||||
int Beb_deactivated_num_destinations = 1;
|
||||
|
||||
int Beb_Beb() {
|
||||
Beb_send_ndata = 0;
|
||||
Beb_send_buffer_size = 1026;
|
||||
|
||||
Beb_send_data_raw =
|
||||
malloc((Beb_send_buffer_size + 1) * sizeof(unsigned int));
|
||||
if (Beb_send_data_raw == NULL) {
|
||||
LOG(logERROR, ("Could not allocate memory for beb (send_data_raw)\n"));
|
||||
return 0;
|
||||
}
|
||||
Beb_send_data = &Beb_send_data_raw[1];
|
||||
|
||||
Beb_recv_ndata = 0;
|
||||
Beb_recv_buffer_size = 1026;
|
||||
|
||||
Beb_recv_data_raw =
|
||||
malloc((Beb_recv_buffer_size + 1) * sizeof(unsigned int));
|
||||
if (Beb_recv_data_raw == NULL) {
|
||||
LOG(logERROR, ("Could not allocate memory for beb (recv_data_raw)\n"));
|
||||
return 0;
|
||||
}
|
||||
Beb_recv_data = &Beb_recv_data_raw[1];
|
||||
|
||||
udp_header = (struct udp_header_type){
|
||||
{0x00, 0x50, 0xc5, 0xb2, 0xcb, 0x46}, // DST MAC
|
||||
{0x00, 0x50, 0xc2, 0x46, 0xd9, 0x02}, // SRC MAC
|
||||
@@ -157,19 +124,12 @@ int Beb_SetHeaderData(uint64_t src_mac, uint32_t src_ip, uint16_t src_port,
|
||||
memcpy(&(udp_header.dst_port[0]), &dst_port, sizeof(udp_header.dst_port));
|
||||
|
||||
Beb_AdjustIPChecksum(&udp_header);
|
||||
|
||||
unsigned int *base_ptr = (unsigned int *)&udp_header;
|
||||
unsigned int num_words = (sizeof(struct udp_header_type) + 3) / 4;
|
||||
for (unsigned int i = 0; i < num_words; i++)
|
||||
Beb_send_data[i + 2] = base_ptr[i];
|
||||
for (unsigned int i = num_words; i < 16; i++)
|
||||
Beb_send_data[i + 2] = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void Beb_AdjustIPChecksum(struct udp_header_type *ip) {
|
||||
unsigned char *cptr = (unsigned char *)ip->ver_headerlen;
|
||||
unsigned char *cptr =
|
||||
(unsigned char *)ip + offsetof(struct udp_header_type, ver_headerlen);
|
||||
|
||||
ip->ip_header_checksum[0] = 0;
|
||||
ip->ip_header_checksum[1] = 0;
|
||||
@@ -372,7 +332,7 @@ int Beb_SetTop(enum TOPINDEX ind) {
|
||||
}
|
||||
|
||||
char *top_names[] = {TOP_NAMES};
|
||||
int newval = Beb_Write32(csp0base, BEB_CONFIG_WR_OFST, value);
|
||||
u_int32_t newval = Beb_Write32(csp0base, BEB_CONFIG_WR_OFST, value);
|
||||
if (newval != value) {
|
||||
LOG(logERROR,
|
||||
("Could not set Top flag to %s in Beb\n", top_names[ind]));
|
||||
@@ -417,7 +377,7 @@ int Beb_SetMaster(enum MASTERINDEX ind) {
|
||||
}
|
||||
|
||||
char *master_names[] = {MASTER_NAMES};
|
||||
int newval = Beb_Write32(csp0base, BEB_CONFIG_WR_OFST, value);
|
||||
u_int32_t newval = Beb_Write32(csp0base, BEB_CONFIG_WR_OFST, value);
|
||||
if (newval != value) {
|
||||
LOG(logERROR,
|
||||
("Could not set Master flag to %s in Beb\n", master_names[ind]));
|
||||
@@ -1167,7 +1127,7 @@ int Beb_SetDetectorPosition(int pos[]) {
|
||||
value | ((posval << UDP_HEADER_Y_OFST) & UDP_HEADER_Y_MSK));
|
||||
value = Beb_Read32(csp0base, UDP_HEADER_B_LEFT_OFST);
|
||||
if ((value & UDP_HEADER_Y_MSK) !=
|
||||
((posval << UDP_HEADER_Y_OFST) & UDP_HEADER_Y_MSK)) {
|
||||
(uint32_t)((posval << UDP_HEADER_Y_OFST) & UDP_HEADER_Y_MSK)) {
|
||||
LOG(logERROR, ("Could not set column position for left port\n"));
|
||||
ret = FAIL;
|
||||
}
|
||||
@@ -1180,7 +1140,7 @@ int Beb_SetDetectorPosition(int pos[]) {
|
||||
value | ((posval << UDP_HEADER_Y_OFST) & UDP_HEADER_Y_MSK));
|
||||
value = Beb_Read32(csp0base, UDP_HEADER_B_RIGHT_OFST);
|
||||
if ((value & UDP_HEADER_Y_MSK) !=
|
||||
((posval << UDP_HEADER_Y_OFST) & UDP_HEADER_Y_MSK)) {
|
||||
(uint32_t)((posval << UDP_HEADER_Y_OFST) & UDP_HEADER_Y_MSK)) {
|
||||
LOG(logERROR, ("Could not set column position for right port\n"));
|
||||
ret = FAIL;
|
||||
}
|
||||
@@ -1379,9 +1339,7 @@ int Beb_open(u_int32_t **csp0base, u_int32_t offset) {
|
||||
}
|
||||
|
||||
u_int32_t Beb_Read32(u_int32_t *baseaddr, u_int32_t offset) {
|
||||
volatile u_int32_t value;
|
||||
value = *(u_int32_t *)(baseaddr + offset / (sizeof(u_int32_t)));
|
||||
return value;
|
||||
return *(volatile u_int32_t *)(baseaddr + offset / sizeof(u_int32_t));
|
||||
}
|
||||
|
||||
u_int32_t Beb_Write32(u_int32_t *baseaddr, u_int32_t offset, u_int32_t data) {
|
||||
|
||||
@@ -46,7 +46,6 @@ int64_t Feb_Control_RateTable_Tau_in_nsec = -1;
|
||||
int64_t Feb_Control_RateTable_Period_in_nsec = -1;
|
||||
unsigned int Feb_Control_rate_correction_table[1024] = {};
|
||||
double Feb_Control_rate_meas[16384] = {};
|
||||
double ratemax = -1;
|
||||
|
||||
// setup
|
||||
void Feb_Control_activate(int activate) { Feb_Control_activated = activate; }
|
||||
@@ -138,13 +137,6 @@ void Feb_Control_CloseSerialCommunication() {
|
||||
|
||||
int Feb_Control_CheckSetup(int master) {
|
||||
LOG(logDEBUG1, ("Checking Set up\n"));
|
||||
|
||||
for (unsigned int j = 0; j < 4; j++) {
|
||||
if (Feb_Control_idelay[j] < 0) {
|
||||
LOG(logERROR, ("idelay chip %d not set.\n", j));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
int value = 0;
|
||||
if ((Feb_Control_master) && (!Feb_Control_GetHighVoltage(&value))) {
|
||||
LOG(logERROR, ("high voltage not set.\n"));
|
||||
@@ -492,7 +484,7 @@ float Feb_Control_DACToVoltage(unsigned int digital, unsigned int nsteps,
|
||||
|
||||
int Feb_Control_SetDAC(unsigned int ch, int value) {
|
||||
unsigned int dst_num = Feb_Control_rightAddress;
|
||||
if (ch < 0 || ch > 15) {
|
||||
if (ch > 15) {
|
||||
LOG(logERROR, ("invalid ch for SetDAC.\n"));
|
||||
return 0;
|
||||
}
|
||||
@@ -664,7 +656,7 @@ int Feb_Control_SetTrimbits(unsigned int *trimbits, int top) {
|
||||
|
||||
int Feb_Control_SaveAllTrimbitsTo(int value, int top) {
|
||||
unsigned int chanregs[Feb_Control_trimbit_size];
|
||||
for (int i = 0; i < Feb_Control_trimbit_size; i++)
|
||||
for (int i = 0; i < (int)Feb_Control_trimbit_size; i++)
|
||||
chanregs[i] = value;
|
||||
return Feb_Control_SetTrimbits(chanregs, top);
|
||||
}
|
||||
@@ -1982,6 +1974,7 @@ int Feb_Control_SetRateCorrectionTau(int64_t tau_in_Nsec) {
|
||||
unsigned int np = 16384; // max slope 16 * 1024
|
||||
double b0[1024];
|
||||
double m[1024];
|
||||
double ratemax = -1;
|
||||
|
||||
if (tau_in_sec < 0 || period_in_sec < 0) {
|
||||
if (dr == 32) {
|
||||
@@ -1999,7 +1992,7 @@ int Feb_Control_SetRateCorrectionTau(int64_t tau_in_Nsec) {
|
||||
LOG(logINFO, ("Changing Rate Correction Table tau:%0.8f sec, period:%f sec",
|
||||
tau_in_sec, period_in_sec));
|
||||
LOG(logINFO, ("\tCalculating table for tau of %lld ns.\n", tau_in_Nsec));
|
||||
for (int i = 0; i < np; i++) {
|
||||
for (int i = 0; i < (int)np; i++) {
|
||||
Feb_Control_rate_meas[i] = i * exp(-i / period_in_sec * tau_in_sec);
|
||||
if (Feb_Control_rate_meas[i] > ratemax)
|
||||
ratemax = Feb_Control_rate_meas[i];
|
||||
@@ -2022,7 +2015,7 @@ int Feb_Control_SetRateCorrectionTau(int64_t tau_in_Nsec) {
|
||||
*/
|
||||
|
||||
int next_i = 0;
|
||||
double beforemax;
|
||||
double beforemax = 0;
|
||||
b0[0] = 0;
|
||||
m[0] = 1;
|
||||
|
||||
@@ -2032,8 +2025,9 @@ int Feb_Control_SetRateCorrectionTau(int64_t tau_in_Nsec) {
|
||||
for (int b = 1; b < 1024; b++) {
|
||||
if (m[b - 1] < 15) {
|
||||
double s = 0, sx = 0, sy = 0, sxx = 0, sxy = 0;
|
||||
int filled = 0;
|
||||
for (;; next_i++) {
|
||||
if (next_i >= np) {
|
||||
if (next_i >= (int)np) {
|
||||
for (; b < 1024; b++) {
|
||||
if (beforemax > ratemax)
|
||||
b0[b] = beforemax;
|
||||
@@ -2044,7 +2038,7 @@ int Feb_Control_SetRateCorrectionTau(int64_t tau_in_Nsec) {
|
||||
(((int)(m[b] + 0.5) & 0xf) << 14) |
|
||||
((int)(b0[b] + 0.5) & 0x3fff);
|
||||
}
|
||||
b = 1024;
|
||||
filled = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2069,6 +2063,8 @@ int Feb_Control_SetRateCorrectionTau(int64_t tau_in_Nsec) {
|
||||
Feb_Control_rate_meas[next_i]: %f\n", x, y, s, sx, sy, sxx, sxy,
|
||||
next_i, b, Feb_Control_rate_meas[next_i]));*/
|
||||
}
|
||||
if (filled)
|
||||
break;
|
||||
double delta = s * sxx - sx * sx;
|
||||
b0[b] = (sxx * sy - sx * sxy) / delta;
|
||||
m[b] = (s * sxy - sx * sy) / delta;
|
||||
|
||||
@@ -8,7 +8,10 @@ xfs_u8 HWIO_xfs_in8(xfs_u32 InAddress) {
|
||||
*/
|
||||
|
||||
xfs_u8 IoContents;
|
||||
__asm__ volatile("eieio; lbz %0,0(%1)" : "=r"(IoContents) : "b"(InAddress));
|
||||
__asm__ volatile("eieio; lbz %0,0(%1)"
|
||||
: "=r"(IoContents)
|
||||
: "b"(InAddress)
|
||||
: "memory");
|
||||
return IoContents;
|
||||
}
|
||||
|
||||
@@ -20,7 +23,10 @@ xfs_u16 HWIO_xfs_in16(xfs_u32 InAddress) {
|
||||
*/
|
||||
|
||||
xfs_u16 IoContents;
|
||||
__asm__ volatile("eieio; lhz %0,0(%1)" : "=r"(IoContents) : "b"(InAddress));
|
||||
__asm__ volatile("eieio; lhz %0,0(%1)"
|
||||
: "=r"(IoContents)
|
||||
: "b"(InAddress)
|
||||
: "memory");
|
||||
return IoContents;
|
||||
}
|
||||
|
||||
@@ -32,7 +38,10 @@ xfs_u32 HWIO_xfs_in32(xfs_u32 InAddress) {
|
||||
*/
|
||||
|
||||
xfs_u32 IoContents;
|
||||
__asm__ volatile("eieio; lwz %0,0(%1)" : "=r"(IoContents) : "b"(InAddress));
|
||||
__asm__ volatile("eieio; lwz %0,0(%1)"
|
||||
: "=r"(IoContents)
|
||||
: "b"(InAddress)
|
||||
: "memory");
|
||||
return IoContents;
|
||||
}
|
||||
|
||||
@@ -43,7 +52,8 @@ void HWIO_xfs_out8(xfs_u32 OutAddress, xfs_u8 Value) {
|
||||
* such that the I/O operation completes before proceeding on
|
||||
*/
|
||||
|
||||
__asm__ volatile("stb %0,0(%1); eieio" ::"r"(Value), "b"(OutAddress));
|
||||
__asm__ volatile("stb %0,0(%1); eieio" ::"r"(Value), "b"(OutAddress)
|
||||
: "memory");
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
@@ -52,7 +62,8 @@ void HWIO_xfs_out16(xfs_u32 OutAddress, xfs_u16 Value) {
|
||||
* such that the I/O operation completes before proceeding on
|
||||
*/
|
||||
|
||||
__asm__ volatile("sth %0,0(%1); eieio" ::"r"(Value), "b"(OutAddress));
|
||||
__asm__ volatile("sth %0,0(%1); eieio" ::"r"(Value), "b"(OutAddress)
|
||||
: "memory");
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
@@ -62,5 +73,6 @@ void HWIO_xfs_out32(xfs_u32 OutAddress, xfs_u32 Value) {
|
||||
* such that the I/O operation completes before proceeding on
|
||||
*/
|
||||
|
||||
__asm__ volatile("stw %0,0(%1); eieio" ::"r"(Value), "b"(OutAddress));
|
||||
__asm__ volatile("stw %0,0(%1); eieio" ::"r"(Value), "b"(OutAddress)
|
||||
: "memory");
|
||||
}
|
||||
@@ -95,7 +95,7 @@ int Local_Write(struct LocalLinkInterface *ll, unsigned int buffer_len,
|
||||
|
||||
LOG(logDEBUG1, ("LL Write - Len: %2d - If: %X - Data: ", buffer_len,
|
||||
ll->ll_fifo_base));
|
||||
for (int i = 0; i < buffer_len / 4; i++)
|
||||
for (int i = 0; i < (int)buffer_len / 4; i++)
|
||||
LOG(logDEBUG1, ("%.8X ", *(((unsigned *)buffer) + i)));
|
||||
|
||||
while (words_send <= last_word) {
|
||||
|
||||
@@ -10,7 +10,7 @@ script_dir = ../../etc/
|
||||
BLACKFIN_CC = bfin-uclinux-gcc
|
||||
CROSS = powerpc-4xx-softfloat-
|
||||
CC = $(CROSS)gcc
|
||||
CFLAGS += -Wall -std=gnu99 -DEIGERD -DSTOP_SERVER -I$(main_inc) -I$(support_lib) -I$(current_dir) #-DDEBUG1 #-DVERBOSEI #-DVERBOSE
|
||||
CFLAGS += -O2 -Wall -Wextra -Wno-unused-parameter -Wformat=2 -Wredundant-decls -Werror=return-type -std=gnu99 -DEIGERD -DSTOP_SERVER -I$(main_inc) -I$(support_lib) -I$(current_dir) #-DDEBUG1 #-DVERBOSEI #-DVERBOSE
|
||||
LDLIBS += -lm -lrt -pthread
|
||||
PROGS = eigerDetectorServer
|
||||
DESTDIR = bin
|
||||
|
||||
Binary file not shown.
@@ -890,7 +890,7 @@ int getDefaultDac(enum DACINDEX index, enum detectorSettings sett,
|
||||
if (sett != UNDEFINED) {
|
||||
return FAIL;
|
||||
}
|
||||
if (index < 0 || index >= NDAC)
|
||||
if (index >= NDAC)
|
||||
return FAIL;
|
||||
*retval = defaultDacValues[index];
|
||||
return OK;
|
||||
@@ -900,7 +900,7 @@ int setDefaultDac(enum DACINDEX index, enum detectorSettings sett, int value) {
|
||||
if (sett != UNDEFINED) {
|
||||
return FAIL;
|
||||
}
|
||||
if (index < 0 || index >= NDAC)
|
||||
if (index >= NDAC)
|
||||
return FAIL;
|
||||
|
||||
char *dac_names[] = {DAC_NAMES};
|
||||
@@ -1428,7 +1428,7 @@ int setThresholdEnergy(int ev) {
|
||||
/* parameters - dac, adc, hv */
|
||||
int validateDACIndex(enum DACINDEX ind, char *mess) {
|
||||
// including E_VTHRESHOLD
|
||||
if (ind < 0 || ind >= NDAC + 1) {
|
||||
if (ind >= NDAC + 1) {
|
||||
sprintf(mess, "Could not set DAC. Invalid index %d\n", ind);
|
||||
LOG(logERROR, (mess));
|
||||
return FAIL;
|
||||
|
||||
@@ -9,7 +9,7 @@ script_dir = ../../etc/
|
||||
|
||||
CROSS = nios2-buildroot-linux-gnu-
|
||||
CC = $(CROSS)gcc
|
||||
CFLAGS += -Wall -std=gnu99 -DGOTTHARD2D -DSTOP_SERVER -I$(main_inc) -I$(support_lib) -I$(current_dir) #-DDEBUG1 #-DVERBOSEI #-DVERBOSE
|
||||
CFLAGS += -O2 -Wall -Wextra -Wno-unused-parameter -Wformat=2 -Wredundant-decls -Werror=return-type -std=gnu99 -DGOTTHARD2D -DSTOP_SERVER -I$(main_inc) -I$(support_lib) -I$(current_dir) #-DDEBUG1 #-DVERBOSEI #-DVERBOSE
|
||||
LDLIBS += -lm -lrt -pthread
|
||||
PROGS = gotthard2DetectorServer
|
||||
DESTDIR ?= bin
|
||||
|
||||
Binary file not shown.
@@ -2052,9 +2052,9 @@ void setupHeader(int iRxEntry, int vetoInterface, uint32_t destip,
|
||||
iRxEntry += RXR_ENDPOINTS_MAX;
|
||||
}
|
||||
addr += (iRxEntry * RXR_ENDPOINT_OFST);
|
||||
// get struct memory
|
||||
udp_header *udp =
|
||||
(udp_header *)(Nios_getBaseAddress() + addr / (sizeof(u_int32_t)));
|
||||
// build udp header
|
||||
udp_header header;
|
||||
udp_header *udp = &header;
|
||||
memset(udp, 0, sizeof(udp_header));
|
||||
|
||||
// mac addresses
|
||||
@@ -2085,6 +2085,14 @@ void setupHeader(int iRxEntry, int vetoInterface, uint32_t destip,
|
||||
// total length is redefined in firmware
|
||||
|
||||
calcChecksum(udp);
|
||||
|
||||
// force FPGA writes to be aligned and in order
|
||||
uint32_t words[sizeof(udp_header) / sizeof(uint32_t)];
|
||||
memcpy(words, udp, sizeof(udp_header));
|
||||
for (size_t i = 0; i != sizeof(words) / sizeof(words[0]); ++i) {
|
||||
bus_w(addr + i * sizeof(uint32_t), words[i]);
|
||||
}
|
||||
|
||||
if (iRxEntry < numUdpDestinations) {
|
||||
LOG(logINFO, ("\tIP checksum : 0x%lx\n\n", udp->ip_checksum));
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ script_dir = ../../etc/
|
||||
|
||||
CROSS = bfin-uclinux-
|
||||
CC = $(CROSS)gcc
|
||||
CFLAGS += -Wall -std=gnu99 -DJUNGFRAUD -DSTOP_SERVER -I$(main_inc) -I$(support_lib) -I$(current_dir) #-DVERBOSEI #-DVERBOSE
|
||||
CFLAGS += -O2 -Wall -Wextra -Wno-unused-parameter -Wformat=2 -Wredundant-decls -Werror=return-type -std=gnu99 -DJUNGFRAUD -DSTOP_SERVER -I$(main_inc) -I$(support_lib) -I$(current_dir) #-DVERBOSEI #-DVERBOSE
|
||||
LDLIBS += -lm -lrt -pthread
|
||||
# FLAT stack size: elf2flt defaults to 4 KB, the update and startup paths need more than 8 KB
|
||||
LDLIBS += -Wl,-elf2flt=-s262144
|
||||
PROGS = jungfrauDetectorServer
|
||||
DESTDIR ?= bin
|
||||
INSTMODE = 0777
|
||||
|
||||
Binary file not shown.
@@ -416,7 +416,7 @@ int setChipIndex(enum CHIPINDEX ind, char *mess) {
|
||||
enum CHIPINDEX getChipIndex() { return chipIndex; }
|
||||
|
||||
int validateChipIndex(enum CHIPINDEX ind, char *mess) {
|
||||
if (ind < 0 || ind >= NUM_CHIP_INDICES) {
|
||||
if (ind >= NUM_CHIP_INDICES) {
|
||||
sprintf(mess, "Invalid chip index %d. Options: %s\n", (int)ind,
|
||||
CHIP_VALS_HELP);
|
||||
LOG(logERROR, (mess));
|
||||
@@ -846,7 +846,7 @@ int getDefaultDac(enum DACINDEX index, enum detectorSettings sett,
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
if (index < 0 || index >= NDAC)
|
||||
if (index >= NDAC)
|
||||
return FAIL;
|
||||
*retval = defaultDacValues[index];
|
||||
return OK;
|
||||
@@ -882,7 +882,7 @@ int setDefaultDac(enum DACINDEX index, enum detectorSettings sett, int value) {
|
||||
// not a special dac
|
||||
return FAIL;
|
||||
}
|
||||
if (index < 0 || index >= NDAC)
|
||||
if (index >= NDAC)
|
||||
return FAIL;
|
||||
LOG(logINFO, ("Setting Default Dac [%d - %s]: %d\n", (int)index,
|
||||
dac_names[index], value));
|
||||
@@ -1564,7 +1564,7 @@ void setGainMode(enum gainMode mode) {
|
||||
|
||||
/* parameters - dac, adc, hv */
|
||||
int validateDACIndex(enum DACINDEX ind, char *mess) {
|
||||
if (ind < 0 || ind >= NDAC) {
|
||||
if (ind >= NDAC) {
|
||||
sprintf(mess, "Could not set DAC. Invalid index %d\n", ind);
|
||||
LOG(logERROR, (mess));
|
||||
return FAIL;
|
||||
@@ -1870,8 +1870,9 @@ void setupHeader(int iRxEntry, enum interfaceType type, uint32_t destip,
|
||||
: RXR_ENDPOINT_OUTER_START_REG);
|
||||
// calculate rxr endpoint offset
|
||||
addr += (iRxEntry * RXR_ENDPOINT_OFST);
|
||||
// get struct memory
|
||||
udp_header *udp = (udp_header *)(Blackfin_getBaseAddress() + addr / 2);
|
||||
// build udp header
|
||||
udp_header header;
|
||||
udp_header *udp = &header;
|
||||
memset(udp, 0, sizeof(udp_header));
|
||||
|
||||
// mac addresses
|
||||
@@ -1902,6 +1903,14 @@ void setupHeader(int iRxEntry, enum interfaceType type, uint32_t destip,
|
||||
// total length is redefined in firmware
|
||||
|
||||
calcChecksum(udp);
|
||||
|
||||
// force FPGA writes to be aligned and in order
|
||||
uint32_t words[sizeof(udp_header) / sizeof(uint32_t)];
|
||||
memcpy(words, udp, sizeof(udp_header));
|
||||
for (size_t i = 0; i != sizeof(words) / sizeof(words[0]); ++i) {
|
||||
bus_w(addr + i * 2, words[i]);
|
||||
}
|
||||
|
||||
if (iRxEntry < numUdpDestinations) {
|
||||
LOG(logINFO, ("\tIP checksum : 0x%lx\n\n", udp->ip_checksum));
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ script_dir = ../../etc/
|
||||
|
||||
CROSS = bfin-uclinux-
|
||||
CC = $(CROSS)gcc
|
||||
CFLAGS += -Wall -std=gnu99 -DMOENCHD -DSTOP_SERVER -I$(main_inc) -I$(support_lib) -I$(current_dir) #-DVERBOSEI #-DVERBOSE
|
||||
CFLAGS += -O2 -Wall -Wextra -Wno-unused-parameter -Wformat=2 -Wredundant-decls -Werror=return-type -std=gnu99 -DMOENCHD -DSTOP_SERVER -I$(main_inc) -I$(support_lib) -I$(current_dir) #-DVERBOSEI #-DVERBOSE
|
||||
LDLIBS += -lm -lrt -pthread
|
||||
# FLAT stack size: elf2flt defaults to 4 KB, the update and startup paths need more than 8 KB
|
||||
LDLIBS += -Wl,-elf2flt=-s262144
|
||||
PROGS = moenchDetectorServer
|
||||
DESTDIR ?= bin
|
||||
INSTMODE = 0777
|
||||
|
||||
Binary file not shown.
@@ -548,7 +548,7 @@ int resetToDefaultDacs(int hardReset, char *mess) {
|
||||
|
||||
int getDefaultDac(enum DACINDEX index, enum detectorSettings sett,
|
||||
int *retval) {
|
||||
if (index < 0 || index >= NDAC)
|
||||
if (index >= NDAC)
|
||||
return FAIL;
|
||||
*retval = defaultDacValues[index];
|
||||
return OK;
|
||||
@@ -556,7 +556,7 @@ int getDefaultDac(enum DACINDEX index, enum detectorSettings sett,
|
||||
|
||||
int setDefaultDac(enum DACINDEX index, enum detectorSettings sett, int value) {
|
||||
char *dac_names[] = {DAC_NAMES};
|
||||
if (index < 0 || index >= NDAC)
|
||||
if (index >= NDAC)
|
||||
return FAIL;
|
||||
LOG(logINFO, ("Setting Default Dac [%d - %s]: %d\n", (int)index,
|
||||
dac_names[index], value));
|
||||
@@ -913,7 +913,7 @@ enum detectorSettings getSettings() {
|
||||
/* parameters - dac, adc, hv */
|
||||
/* parameters - dac, adc, hv */
|
||||
int validateDACIndex(enum DACINDEX ind, char *mess) {
|
||||
if (ind < 0 || ind >= NDAC) {
|
||||
if (ind >= NDAC) {
|
||||
sprintf(mess, "Could not set DAC. Invalid index %d\n", ind);
|
||||
LOG(logERROR, (mess));
|
||||
return FAIL;
|
||||
@@ -1203,8 +1203,9 @@ void setupHeader(int iRxEntry, enum interfaceType type, uint32_t destip,
|
||||
: RXR_ENDPOINT_OUTER_START_REG);
|
||||
// calculate rxr endpoint offset
|
||||
addr += (iRxEntry * RXR_ENDPOINT_OFST);
|
||||
// get struct memory
|
||||
udp_header *udp = (udp_header *)(Blackfin_getBaseAddress() + addr / 2);
|
||||
// build udp header
|
||||
udp_header header;
|
||||
udp_header *udp = &header;
|
||||
memset(udp, 0, sizeof(udp_header));
|
||||
|
||||
// mac addresses
|
||||
@@ -1235,6 +1236,14 @@ void setupHeader(int iRxEntry, enum interfaceType type, uint32_t destip,
|
||||
// total length is redefined in firmware
|
||||
|
||||
calcChecksum(udp);
|
||||
|
||||
// force FPGA writes to be aligned and in order
|
||||
uint32_t words[sizeof(udp_header) / sizeof(uint32_t)];
|
||||
memcpy(words, udp, sizeof(udp_header));
|
||||
for (size_t i = 0; i != sizeof(words) / sizeof(words[0]); ++i) {
|
||||
bus_w(addr + i * 2, words[i]);
|
||||
}
|
||||
|
||||
if (iRxEntry < numUdpDestinations) {
|
||||
LOG(logINFO, ("\tIP checksum : 0x%lx\n\n", udp->ip_checksum));
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ script_dir = ../../etc/
|
||||
|
||||
CROSS = nios2-buildroot-linux-gnu-
|
||||
CC = $(CROSS)gcc
|
||||
CFLAGS += -Wall -std=gnu99 -DMYTHEN3D -DSTOP_SERVER -I$(main_inc) -I$(support_lib) -I$(det_lib) -I$(current_dir) #-DDEBUG1 #-DVERBOSEI #-DVERBOSE
|
||||
CFLAGS += -O2 -Wall -Wextra -Wno-unused-parameter -Wformat=2 -Wredundant-decls -Werror=return-type -std=gnu99 -DMYTHEN3D -DSTOP_SERVER -I$(main_inc) -I$(support_lib) -I$(det_lib) -I$(current_dir) #-DDEBUG1 #-DVERBOSEI #-DVERBOSE
|
||||
LDLIBS += -lm -lrt -pthread
|
||||
PROGS = mythen3DetectorServer
|
||||
DESTDIR ?= bin
|
||||
|
||||
Binary file not shown.
@@ -2225,9 +2225,9 @@ int configureMAC() {
|
||||
uint32_t addr = BASE_UDP_RAM;
|
||||
// calculate rxr endpoint offset
|
||||
addr += (iRxEntry * RXR_ENDPOINT_OFST); // TODO: is there round robin
|
||||
// get struct memory
|
||||
udp_header *udp =
|
||||
(udp_header *)(Nios_getBaseAddress() + addr / (sizeof(u_int32_t)));
|
||||
// build udp header
|
||||
udp_header header;
|
||||
udp_header *udp = &header;
|
||||
memset(udp, 0, sizeof(udp_header));
|
||||
|
||||
// mac addresses
|
||||
@@ -2258,6 +2258,14 @@ int configureMAC() {
|
||||
// total length is redefined in firmware
|
||||
|
||||
calcChecksum(udp);
|
||||
|
||||
// force FPGA writes to be aligned and in order
|
||||
uint32_t words[sizeof(udp_header) / sizeof(uint32_t)];
|
||||
memcpy(words, udp, sizeof(udp_header));
|
||||
for (size_t i = 0; i != sizeof(words) / sizeof(words[0]); ++i) {
|
||||
bus_w(addr + i * sizeof(uint32_t), words[i]);
|
||||
}
|
||||
|
||||
if (iRxEntry < numUdpDestinations) {
|
||||
LOG(logINFO, ("\tIP checksum : 0x%lx\n\n", udp->ip_checksum));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "sls/sls_detector_defs.h"
|
||||
|
||||
#define REQRD_FRMWRE_VRSN (0x241113)
|
||||
#define KERNEL_DATE_VRSN "Mon May 10 18:00:21 CEST 2021"
|
||||
#define KERNEL_DATE_VRSN "Mon Jan 18 14:24:30 CET 2021"
|
||||
#define ID_FILE "detid_mythen3.txt"
|
||||
|
||||
#define NUM_HARDWARE_VERSIONS (2)
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
#include <sys/types.h>
|
||||
|
||||
void bus_w(u_int32_t offset, u_int32_t data);
|
||||
void bus_w_csp1(u_int32_t offset, u_int32_t data);
|
||||
void bus_w_csp2(u_int32_t offset, u_int32_t data);
|
||||
u_int32_t bus_r(u_int32_t offset);
|
||||
u_int32_t bus_r_csp1(u_int32_t offset);
|
||||
u_int32_t bus_r_csp2(u_int32_t offset);
|
||||
uint64_t getU64BitReg(int aLSB, int aMSB);
|
||||
void setU64BitReg(uint64_t value, int aLSB, int aMSB);
|
||||
|
||||
@@ -120,5 +120,14 @@ uint32_t *Blackfin_getBaseAddress();
|
||||
*/
|
||||
int mapCSP0(void);
|
||||
|
||||
/** sleep for only usecs */
|
||||
void usleep_bf(uint64_t i);
|
||||
/**
|
||||
* Prepare the core cycle counter used by usleep_bf (called from mapCSP0)
|
||||
*/
|
||||
int initCycleCounter(void);
|
||||
|
||||
/**
|
||||
* busy-wait for usecs on the core cycle counter
|
||||
* @returns 0, or -1 without sleeping if the wait does not fit the 32-bit
|
||||
* cycle count (about 8 s at 525 MHz)
|
||||
*/
|
||||
int usleep_bf(uint32_t us);
|
||||
@@ -94,16 +94,21 @@ int LTC2620_D_SetDacValue(int dacnum, int val, char *dacname, char *mess) {
|
||||
// fname to write to
|
||||
char fname[MAX_STR_LENGTH];
|
||||
memset(fname, 0, MAX_STR_LENGTH);
|
||||
char fnameFormat[MAX_STR_LENGTH];
|
||||
memset(fnameFormat, 0, MAX_STR_LENGTH);
|
||||
strcpy(fnameFormat, LTC2620_D_DriverFileName);
|
||||
sprintf(fname, "%s%d", fnameFormat, dacnum);
|
||||
#if defined(XILINX_CHIPTESTBOARDD)
|
||||
// driver file names are printf formats with the dac number in the middle
|
||||
// (CURRENT_BOARD_LINKS_FOLDER "/ao%d" and "/ao%d_pd"), so the format
|
||||
// cannot be a string literal here
|
||||
const char *fnameFormat = LTC2620_D_DriverFileName;
|
||||
if (val == LTC2620_D_PWR_DOWN_VAL) {
|
||||
strcpy(fnameFormat, LTC2620_D_PowerDownDriverFileName);
|
||||
fnameFormat = LTC2620_D_PowerDownDriverFileName;
|
||||
val = 1; //-100?:Invalid Argument
|
||||
}
|
||||
sprintf(fname, fnameFormat, dacnum);
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
|
||||
snprintf(fname, sizeof(fname), fnameFormat, dacnum);
|
||||
#pragma GCC diagnostic pop
|
||||
#else
|
||||
snprintf(fname, sizeof(fname), "%s%d", LTC2620_D_DriverFileName, dacnum);
|
||||
#endif
|
||||
LOG(logDEBUG1, ("fname %s\n", fname));
|
||||
|
||||
|
||||
@@ -28,6 +28,18 @@ void bus_w(u_int32_t offset, u_int32_t data) {
|
||||
*ptr1 = data;
|
||||
}
|
||||
|
||||
void bus_w_csp1(u_int32_t offset, u_int32_t data) {
|
||||
volatile u_int32_t *ptr1;
|
||||
ptr1 = (u_int32_t *)(csp1base + offset / (sizeof(u_int32_t)));
|
||||
*ptr1 = data;
|
||||
}
|
||||
|
||||
u_int32_t bus_r_csp1(u_int32_t offset) {
|
||||
volatile u_int32_t *ptr1;
|
||||
ptr1 = (u_int32_t *)(csp1base + offset / (sizeof(u_int32_t)));
|
||||
return *ptr1;
|
||||
}
|
||||
|
||||
u_int32_t bus_r(u_int32_t offset) {
|
||||
volatile u_int32_t *ptr1;
|
||||
ptr1 = (u_int32_t *)(csp0base + offset / (sizeof(u_int32_t)));
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
#include "sls/sls_detector_defs.h"
|
||||
|
||||
#include <fcntl.h> // open
|
||||
#include <stdio.h> // fopen
|
||||
#include <sys/mman.h> // mmap
|
||||
#include <unistd.h> // usleep
|
||||
|
||||
/* global variables */
|
||||
const uint64_t BFIN_CYCLES_1uSECOND = 20;
|
||||
u_int32_t *csp0base = 0;
|
||||
|
||||
#define CSP0 0x20200000
|
||||
@@ -125,6 +126,8 @@ int mapCSP0(void) {
|
||||
#endif
|
||||
LOG(logINFO, ("csp0base mapped from %p to %p\n", csp0base,
|
||||
(csp0base + MEM_SIZE)));
|
||||
if (initCycleCounter() == FAIL)
|
||||
return FAIL;
|
||||
LOG(logINFO, ("Status Register: %08x\n", bus_r(STATUS_REG)));
|
||||
} else
|
||||
LOG(logINFO, ("Memory already mapped before\n"));
|
||||
@@ -133,10 +136,62 @@ int mapCSP0(void) {
|
||||
|
||||
uint32_t *Blackfin_getBaseAddress() { return csp0base; }
|
||||
|
||||
// usleep is not viable on blackfin
|
||||
void usleep_bf(uint64_t i) {
|
||||
uint64_t j = i * BFIN_CYCLES_1uSECOND;
|
||||
while (--j) {
|
||||
asm volatile("");
|
||||
/* BF537 core clock; refined from /proc/cpuinfo in initCycleCounter() */
|
||||
#define BFIN_CCLK_HZ_DEFAULT 525000000UL
|
||||
static uint32_t bfinCyclesPerUs = BFIN_CCLK_HZ_DEFAULT / 1000000;
|
||||
|
||||
#ifdef __bfin__
|
||||
static inline uint32_t get_cycles(void) {
|
||||
uint32_t cycles;
|
||||
__asm__ volatile("%0 = CYCLES;" : "=d"(cycles));
|
||||
return cycles;
|
||||
}
|
||||
#endif
|
||||
|
||||
int initCycleCounter(void) {
|
||||
#ifndef __bfin__
|
||||
return OK; // virtual build: usleep_bf falls back to usleep
|
||||
#else
|
||||
// kernel reports "cpu MHz : 525.000/131.250" (core/system)
|
||||
FILE *fp = fopen("/proc/cpuinfo", "r");
|
||||
if (fp != NULL) {
|
||||
char line[128];
|
||||
double mhz = 0;
|
||||
while (fgets(line, sizeof(line), fp) != NULL) {
|
||||
if (sscanf(line, "cpu MHz : %lf", &mhz) == 1 && mhz > 0) {
|
||||
bfinCyclesPerUs = (uint32_t)mhz;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
// CYCLES only counts if the kernel set SYSCFG.CCEN; refuse to run
|
||||
// otherwise, because usleep_bf would spin forever
|
||||
uint32_t c0 = get_cycles();
|
||||
for (volatile int i = 0; i < 1000; ++i)
|
||||
;
|
||||
if (get_cycles() == c0) {
|
||||
LOG(logERROR, ("Blackfin cycle counter is not running\n"));
|
||||
return FAIL;
|
||||
}
|
||||
LOG(logINFO, ("Cycle counter running at %u cycles/us\n", bfinCyclesPerUs));
|
||||
return OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
// usleep is not viable on blackfin (10 ms tick); spin on the core cycle counter
|
||||
int usleep_bf(uint32_t us) {
|
||||
// the wait is counted in core cycles in a uint32_t: about 8 s at 525 MHz
|
||||
if (us > UINT32_MAX / bfinCyclesPerUs) {
|
||||
return -1;
|
||||
}
|
||||
#ifndef __bfin__
|
||||
usleep(us);
|
||||
#else
|
||||
uint32_t start = get_cycles();
|
||||
uint32_t wait = us * bfinCyclesPerUs;
|
||||
while ((uint32_t)(get_cycles() - start) < wait)
|
||||
;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ else
|
||||
endif
|
||||
CC = $(CROSS)gcc
|
||||
#TODO: allow these warnings and fix code
|
||||
CFLAGS += -Wall -std=gnu99 -Wno-format-overflow -Wno-format-truncation -DXILINX_CHIPTESTBOARDD -DARMPROCESSOR -DSTOP_SERVER -I$(main_inc) -I$(support_lib) -I$(det_lib) -I$(current_dir) #-DDEBUG1 #-DVERBOSEI #-DVERBOSE
|
||||
CFLAGS += -O2 -Wall -Wextra -Wno-unused-parameter -Wformat=2 -Wredundant-decls -Werror=return-type -std=gnu99 -Wno-format-overflow -Wno-format-truncation -DXILINX_CHIPTESTBOARDD -DARMPROCESSOR -DSTOP_SERVER -I$(main_inc) -I$(support_lib) -I$(det_lib) -I$(current_dir) #-DDEBUG1 #-DVERBOSEI #-DVERBOSE
|
||||
#CFLAGS += -Wall -std=gnu99 -DXILINX_CHIPTESTBOARDD -DARMPROCESSOR -DSTOP_SERVER -I$(main_inc) -I$(support_lib) -I$(det_lib) -I$(current_dir) #-DDEBUG1 #-DVERBOSEI #-DVERBOSE
|
||||
LDLIBS += -lm -lrt -pthread
|
||||
PROGS = xilinx_ctbDetectorServer
|
||||
|
||||
Binary file not shown.
@@ -145,7 +145,7 @@ void basictests() {
|
||||
}
|
||||
|
||||
// check for API compatibility - old server
|
||||
if (sw_fw_apiversion > requiredfwversion) {
|
||||
if ((uint64_t)sw_fw_apiversion > requiredfwversion) {
|
||||
sprintf(initErrorMessage,
|
||||
"This firmware-software api version (0x%lx) is incompatible "
|
||||
"with the software's minimum required firmware version "
|
||||
@@ -158,7 +158,7 @@ void basictests() {
|
||||
}
|
||||
|
||||
// check for firmware compatibility - old firmware
|
||||
if (requiredfwversion > fwversion) {
|
||||
if (requiredfwversion > (uint64_t)fwversion) {
|
||||
sprintf(initErrorMessage,
|
||||
"This firmware version (0x%lx) is incompatible.\n"
|
||||
"Please update firmware (min. 0x%lx) to be compatible with "
|
||||
@@ -1502,8 +1502,9 @@ int configureMAC() {
|
||||
}
|
||||
#endif
|
||||
|
||||
// get struct memory
|
||||
udp_header *udp = (udp_header *)(Arm_getUDPBaseAddress());
|
||||
// build udp header
|
||||
udp_header header;
|
||||
udp_header *udp = &header;
|
||||
memset(udp, 0, sizeof(udp_header));
|
||||
|
||||
// mac addresses
|
||||
@@ -1535,6 +1536,13 @@ int configureMAC() {
|
||||
|
||||
calcChecksum(udp);
|
||||
|
||||
// force FPGA writes to be aligned and in order
|
||||
uint32_t words[sizeof(udp_header) / sizeof(uint32_t)];
|
||||
memcpy(words, udp, sizeof(udp_header));
|
||||
for (size_t i = 0; i != sizeof(words) / sizeof(words[0]); ++i) {
|
||||
bus_w_csp1(i * sizeof(uint32_t), words[i]);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -1037,6 +1037,9 @@ class Detector {
|
||||
|
||||
void clearRxROI();
|
||||
|
||||
/** Returns true if the current ROI covers the complete detector (default)*/
|
||||
bool isCompleteRxRoi() const;
|
||||
|
||||
///@}
|
||||
|
||||
/** @name File */
|
||||
|
||||
@@ -1335,6 +1335,7 @@ Result<bool> Detector::getUDPDataStream(const defs::portPosition port,
|
||||
|
||||
void Detector::setUDPDataStream(const defs::portPosition port,
|
||||
const bool enable, Positions pos) {
|
||||
pimpl->validatePortEnable(port, enable, pos);
|
||||
pimpl->setUDPDataStream(port, enable, pos);
|
||||
}
|
||||
|
||||
@@ -1485,6 +1486,8 @@ void Detector::setRxROI(const std::vector<defs::ROI> &args) {
|
||||
|
||||
void Detector::clearRxROI() { pimpl->clearRxROI(); }
|
||||
|
||||
bool Detector::isCompleteRxRoi() const { return pimpl->isCompleteRxRoi(); }
|
||||
|
||||
// File
|
||||
|
||||
Result<defs::fileFormat> Detector::getFileFormat(Positions pos) const {
|
||||
|
||||
@@ -48,6 +48,15 @@ bool DetectorImpl::isAllPositions(Positions pos) const {
|
||||
(pos.size() == modules.size()));
|
||||
}
|
||||
|
||||
std::vector<int> DetectorImpl::fillInPositions(Positions pos) const {
|
||||
if (isAllPositions(pos)) {
|
||||
std::vector<int> positions(modules.size());
|
||||
std::iota(begin(positions), end(positions), 0);
|
||||
return positions;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
void DetectorImpl::setAcquiringFlag(bool flag) { shm()->acquiringFlag = flag; }
|
||||
|
||||
int DetectorImpl::getDetectorIndex() const { return detectorIndex; }
|
||||
@@ -1640,6 +1649,7 @@ Result<bool> DetectorImpl::getUDPDataStream(const defs::portPosition port,
|
||||
void DetectorImpl::setUDPDataStream(const defs::portPosition port,
|
||||
const bool enable, Positions pos) {
|
||||
assertTwoUDPDataInterfaces("set enable/disable UDP ports");
|
||||
validatePortEnable(port, enable, pos);
|
||||
Parallel(&Module::setUDPDataStream, pos, port, enable);
|
||||
updateRxUDPDatastreamMetadata();
|
||||
}
|
||||
@@ -1857,6 +1867,49 @@ void DetectorImpl::convertGlobalRoiToPortLevel(
|
||||
}
|
||||
}
|
||||
|
||||
void DetectorImpl::validatePortEnable(const defs::portPosition port,
|
||||
const bool enable,
|
||||
std::vector<int> pos) const {
|
||||
if (isCompleteRxRoi())
|
||||
return;
|
||||
auto positions = fillInPositions(pos);
|
||||
for (int i : positions) {
|
||||
if (i < 0 || static_cast<size_t>(i) >= modules.size()) {
|
||||
throw RuntimeError("Invalid module index: " + std::to_string(i));
|
||||
}
|
||||
auto modRois = modules[i]->getRxROI();
|
||||
auto portList = getPortPositionList();
|
||||
int portIndex = 0;
|
||||
for (auto p : portList) {
|
||||
if (p == port)
|
||||
break;
|
||||
portIndex++;
|
||||
}
|
||||
validatePortEnableRoiState(i, port, modRois[portIndex], enable);
|
||||
}
|
||||
}
|
||||
|
||||
/** Assumption:
|
||||
* 1. Check only for Eiger because port disable is only for Eiger now
|
||||
* 2. This function called only when an Roi being set (not complete detector)
|
||||
* ie. complete ROI enabled and some ports disabled is allowed
|
||||
*/
|
||||
void DetectorImpl::validatePortEnableRoiState(const int moduleIndex,
|
||||
defs::portPosition changedPort,
|
||||
const defs::ROI &portRoi,
|
||||
bool enabled) const {
|
||||
// enabled or disabled and no roi
|
||||
if (enabled || portRoi.noRoi())
|
||||
return;
|
||||
// diabled but ROI specified
|
||||
std::ostringstream oss;
|
||||
oss << ToString(changedPort) << " port of module " << moduleIndex << " ("
|
||||
<< modules[moduleIndex]->getHostname()
|
||||
<< ") is not active but ROI is specified for it: " << ToString(portRoi)
|
||||
<< ". Please disable the ROI for this port or enable the port.";
|
||||
throw RuntimeError(oss.str());
|
||||
}
|
||||
|
||||
void DetectorImpl::setRxROI(const std::vector<defs::ROI> &args) {
|
||||
if (shm()->detType == CHIPTESTBOARD ||
|
||||
shm()->detType == defs::XILINX_CHIPTESTBOARD) {
|
||||
@@ -1871,10 +1924,13 @@ void DetectorImpl::setRxROI(const std::vector<defs::ROI> &args) {
|
||||
}
|
||||
|
||||
validateROIs(args);
|
||||
|
||||
int nPortsPerModule =
|
||||
Parallel(&Module::getNumberofUDPInterfacesFromShm, {})
|
||||
.tsquash("Inconsistent number of udp ports set up per module");
|
||||
std::vector<std::vector<defs::ROI>> moduleRois;
|
||||
|
||||
// further validate and create port level rois for each module
|
||||
for (size_t iModule = 0; iModule < modules.size(); ++iModule) {
|
||||
auto moduleGlobalRoi = getModuleROI(iModule);
|
||||
// at most 2 rois per module (for each port)
|
||||
@@ -1886,15 +1942,30 @@ void DetectorImpl::setRxROI(const std::vector<defs::ROI> &args) {
|
||||
roi.ymax = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// check overlap with module
|
||||
// check overlap of each roi with module
|
||||
for (const auto &arg : args) {
|
||||
if (arg.overlap(moduleGlobalRoi)) {
|
||||
convertGlobalRoiToPortLevel(arg, moduleGlobalRoi, portRois);
|
||||
}
|
||||
}
|
||||
modules[iModule]->setRxROI(portRois);
|
||||
// validate roi against port enables
|
||||
if (nPortsPerModule == 2) {
|
||||
auto ports = getPortPositionList();
|
||||
for (int iport = 0; iport != nPortsPerModule; ++iport) {
|
||||
defs::portPosition port = ports[iport];
|
||||
auto enable = modules[iModule]->getUDPDataStream(port);
|
||||
validatePortEnableRoiState(iModule, port, portRois[iport],
|
||||
enable);
|
||||
}
|
||||
}
|
||||
moduleRois.push_back(portRois);
|
||||
}
|
||||
|
||||
// set rois for each module
|
||||
for (size_t iModule = 0; iModule < modules.size(); ++iModule) {
|
||||
modules[iModule]->setRxROI(moduleRois[iModule]);
|
||||
}
|
||||
|
||||
// metadata
|
||||
modules[0]->setRxROIMetadata(args);
|
||||
}
|
||||
@@ -1909,6 +1980,14 @@ void DetectorImpl::clearRxROI() {
|
||||
modules[0]->setRxROIMetadata(std::vector<defs::ROI>(1));
|
||||
}
|
||||
|
||||
bool DetectorImpl::isCompleteRxRoi() const {
|
||||
const auto rois = getRxROI();
|
||||
if (rois.empty() || (rois.size() == 1 && rois[0].completeRoi())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DetectorImpl::getBadChannels(const std::string &fname,
|
||||
Positions pos) const {
|
||||
auto res = Parallel(&Module::getBadChannels, pos);
|
||||
|
||||
@@ -183,6 +183,8 @@ class DetectorImpl : public virtual slsDetectorDefs {
|
||||
|
||||
bool isAllPositions(Positions pos) const;
|
||||
|
||||
std::vector<int> fillInPositions(Positions pos) const;
|
||||
|
||||
inline bool isChipTestBoard() const {
|
||||
return (shm()->detType == defs::CHIPTESTBOARD ||
|
||||
shm()->detType == defs::XILINX_CHIPTESTBOARD);
|
||||
@@ -320,8 +322,16 @@ class DetectorImpl : public virtual slsDetectorDefs {
|
||||
|
||||
defs::xy getPortGeometry() const;
|
||||
std::vector<defs::ROI> getRxROI(int module_id = -1) const;
|
||||
void validatePortEnable(const defs::portPosition port, const bool enable,
|
||||
std::vector<int> pos) const;
|
||||
void validatePortEnableRoiState(const int moduleIndex,
|
||||
defs::portPosition changedPort,
|
||||
const defs::ROI &portRoi,
|
||||
bool enabled) const;
|
||||
|
||||
void setRxROI(const std::vector<defs::ROI> &args);
|
||||
void clearRxROI();
|
||||
bool isCompleteRxRoi() const;
|
||||
|
||||
void getBadChannels(const std::string &fname, Positions pos) const;
|
||||
void setBadChannels(const std::string &fname, Positions pos);
|
||||
@@ -510,4 +520,4 @@ class DetectorImpl : public virtual slsDetectorDefs {
|
||||
void *pCallbackArg{nullptr};
|
||||
};
|
||||
|
||||
} // namespace sls
|
||||
} // namespace sls
|
||||
@@ -855,6 +855,94 @@ TEST_CASE("rx_roi", "[.detectorintegration][.disable_check_data_file]") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("rx_roi_port_disabled", "[.detectorintegration]") {
|
||||
Detector det;
|
||||
auto det_type = det.getDetectorType().squash();
|
||||
auto two_ports = (det_type == defs::EIGER);
|
||||
if (det_type == defs::JUNGFRAU || det_type == defs::MOENCH) {
|
||||
int numinterfaces = det.getNumberofUDPInterfaces().tsquash(
|
||||
"inconsistent number of interfaces");
|
||||
two_ports = (numinterfaces == 2);
|
||||
}
|
||||
|
||||
if (two_ports) {
|
||||
auto prev_roi = det.getRxROI();
|
||||
auto ports = det.getPortPositionList();
|
||||
auto prev_port0 = det.getUDPDataStream(ports[0], {0})[0];
|
||||
auto prev_port1 = det.getUDPDataStream(ports[1], {0})[0];
|
||||
bool prev_tengiga = false;
|
||||
if (det_type == defs::EIGER) {
|
||||
prev_tengiga = det.getTenGiga().squash(false);
|
||||
}
|
||||
|
||||
std::vector<defs::ROI> rois(2);
|
||||
switch (det_type) {
|
||||
case defs::EIGER:
|
||||
det.setTenGiga(true);
|
||||
rois[0] = defs::ROI{0, 10, 0, 20};
|
||||
rois[1] = defs::ROI{600, 610, 0, 30};
|
||||
break;
|
||||
case defs::JUNGFRAU:
|
||||
rois[0] = defs::ROI{0, 10, 0, 10};
|
||||
rois[1] = defs::ROI{0, 20, 300, 310};
|
||||
break;
|
||||
case defs::MOENCH:
|
||||
rois[0] = defs::ROI{0, 10, 0, 10};
|
||||
rois[1] = defs::ROI{0, 20, 300, 310};
|
||||
break;
|
||||
default:
|
||||
REQUIRE(false);
|
||||
break;
|
||||
}
|
||||
|
||||
det.clearRxROI();
|
||||
det.setUDPDataStream(ports[0], true, {0});
|
||||
det.setUDPDataStream(ports[1], true, {0});
|
||||
det.setRxROI(rois);
|
||||
REQUIRE_THROWS(det.setUDPDataStream(ports[0], false));
|
||||
REQUIRE_THROWS(det.setUDPDataStream(ports[1], false));
|
||||
|
||||
det.clearRxROI();
|
||||
det.setUDPDataStream(ports[0], true, {0});
|
||||
det.setUDPDataStream(ports[1], true, {0});
|
||||
det.setRxROI({rois[0]});
|
||||
REQUIRE_THROWS(det.setUDPDataStream(ports[0], false));
|
||||
REQUIRE_NOTHROW(det.setUDPDataStream(ports[1], false));
|
||||
|
||||
det.clearRxROI();
|
||||
det.setUDPDataStream(ports[0], true, {0});
|
||||
det.setUDPDataStream(ports[1], true, {0});
|
||||
det.setRxROI({rois[1]});
|
||||
REQUIRE_NOTHROW(det.setUDPDataStream(ports[0], false));
|
||||
REQUIRE_THROWS(det.setUDPDataStream(ports[1], false));
|
||||
|
||||
det.clearRxROI();
|
||||
det.setUDPDataStream(ports[0], false, {0});
|
||||
det.setUDPDataStream(ports[1], true, {0});
|
||||
REQUIRE_THROWS(det.setRxROI({rois[0]}));
|
||||
REQUIRE_NOTHROW(det.setRxROI({rois[1]}));
|
||||
|
||||
det.clearRxROI();
|
||||
det.setUDPDataStream(ports[0], true, {0});
|
||||
det.setUDPDataStream(ports[1], false, {0});
|
||||
REQUIRE_NOTHROW(det.setRxROI({rois[0]}));
|
||||
REQUIRE_THROWS(det.setRxROI({rois[1]}));
|
||||
|
||||
/* eiger virtual detector doesnt understand it cant disable in 1g mode,
|
||||
* so cant test in 1g mode */
|
||||
|
||||
if (prev_roi.size() == 1 && prev_roi[0].completeRoi())
|
||||
det.clearRxROI();
|
||||
else
|
||||
det.setRxROI(prev_roi);
|
||||
det.setUDPDataStream(ports[0], prev_port0, {0});
|
||||
det.setUDPDataStream(ports[1], prev_port1, {0});
|
||||
if (det_type == defs::EIGER) {
|
||||
det.setTenGiga(prev_tengiga);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("rx_clearroi", "[.detectorintegration]") {
|
||||
Detector det;
|
||||
Caller caller(&det);
|
||||
|
||||
@@ -1632,7 +1632,8 @@ int ClientInterface::set_threshold(Interface &socket) {
|
||||
LOG(logDEBUG) << "Threshold: " << arg << " eV";
|
||||
if (detType != EIGER)
|
||||
functionNotImplemented();
|
||||
verifyIdle(socket);
|
||||
// allow setting threshold metadata on the fly
|
||||
// without checking for idle state
|
||||
impl()->setThresholdEnergy(arg);
|
||||
return socket.Send(OK);
|
||||
}
|
||||
@@ -1659,7 +1660,8 @@ int ClientInterface::set_all_threshold(Interface &socket) {
|
||||
LOG(logDEBUG) << "Threshold:" << ToString(eVs);
|
||||
if (detType != MYTHEN3)
|
||||
functionNotImplemented();
|
||||
verifyIdle(socket);
|
||||
// allow setting threshold metadata on the fly
|
||||
// without checking for idle state
|
||||
impl()->setThresholdEnergy(eVs);
|
||||
return socket.Send(OK);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
/** API versions */
|
||||
#define APILIB "0.0.0 0x250909"
|
||||
#define APIRECEIVER "0.0.0 0x250822"
|
||||
#define APICTB "0.0.0 0x260506"
|
||||
#define APIGOTTHARD2 "0.0.0 0x260427"
|
||||
#define APIMOENCH "0.0.0 0x260424"
|
||||
#define APIEIGER "0.0.0 0x260807"
|
||||
#define APIXILINXCTB "0.0.0 0x260813"
|
||||
#define APIJUNGFRAU "0.0.0 0x261002"
|
||||
#define APIMYTHEN3 "0.0.0 0x261022"
|
||||
#define APICTB "0.0.0 0x261103"
|
||||
#define APIGOTTHARD2 "0.0.0 0x261103"
|
||||
#define APIMOENCH "0.0.0 0x261103"
|
||||
#define APIEIGER "0.0.0 0x261027"
|
||||
#define APIXILINXCTB "0.0.0 0x261103"
|
||||
#define APIJUNGFRAU "0.0.0 0x261103"
|
||||
#define APIMYTHEN3 "0.0.0 0x261103"
|
||||
#define APIMATTERHORN "0.0.0 0x260529"
|
||||
Reference in new issue
Block a user