From 836708971575225449acc48ebd4fd77911b81785 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Tue, 13 Oct 2026 10:25:18 -0400 Subject: [PATCH 1/8] Use CIRC_NO_OVERWRITE mode, and keep track of frames pending in circular buffer. Also track dropped frames --- kinetixApp/Db/ADKinetix.template | 55 ++++++++++ kinetixApp/Db/ADKinetix_settings.req | 2 + kinetixApp/src/ADKinetix.cpp | 149 +++++++++++++++++++++------ kinetixApp/src/ADKinetix.h | 30 +++++- kinetixApp/src/ADKinetixDS.h | 6 +- 5 files changed, 204 insertions(+), 38 deletions(-) diff --git a/kinetixApp/Db/ADKinetix.template b/kinetixApp/Db/ADKinetix.template index e34af71..568d86e 100644 --- a/kinetixApp/Db/ADKinetix.template +++ b/kinetixApp/Db/ADKinetix.template @@ -149,6 +149,61 @@ record(ai, "$(P)$(R)WaitForFrameTO_RBV") { } +record(longin, "$(P)$(R)DroppedFrames_RBV") { + field(DTYP, "asynInt32") + field(DESC, "Frames lost before driver readout") + field(INP, "@asyn($(PORT),$(ADDR=0),$(TIMEOUT=1))KTX_DROPPED_FRAMES") + field(SCAN, "I/O Intr") +} + + +record(longin, "$(P)$(R)PendingFrames_RBV") { + field(DTYP, "asynInt32") + field(DESC, "Frames awaiting driver readout") + field(INP, "@asyn($(PORT),$(ADDR=0),$(TIMEOUT=1))KTX_PENDING_FRAMES") + field(SCAN, "I/O Intr") +} + + +record(bo, "$(P)$(R)UniqueIdSource") { + field(DTYP, "asynInt32") + field(DESC, "Source of NDArray UniqueId") + field(OUT, "@asyn($(PORT),$(ADDR=0),$(TIMEOUT=1))KTX_UNIQUE_ID_SRC") + field(ZNAM, "Camera") + field(ONAM, "Array Counter") + field(VAL, "0") + field(PINI, "YES") +} + + +record(bi, "$(P)$(R)UniqueIdSource_RBV") { + field(DTYP, "asynInt32") + field(DESC, "Source of NDArray UniqueId") + field(INP, "@asyn($(PORT),$(ADDR=0),$(TIMEOUT=1))KTX_UNIQUE_ID_SRC") + field(ZNAM, "Camera") + field(ONAM, "Array Counter") + field(SCAN, "I/O Intr") +} + + +record(longout, "$(P)$(R)CircBuffSize") { + field(DTYP, "asynInt32") + field(DESC, "Circular buffer size in frames") + field(OUT, "@asyn($(PORT),$(ADDR=0),$(TIMEOUT=1))KTX_CIRC_BUFF_SIZE") + field(DRVL, "1") + field(VAL, "50") + field(PINI, "YES") +} + + +record(longin, "$(P)$(R)CircBuffSize_RBV") { + field(DTYP, "asynInt32") + field(DESC, "Circular buffer size in frames") + field(INP, "@asyn($(PORT),$(ADDR=0),$(TIMEOUT=1))KTX_CIRC_BUFF_SIZE") + field(SCAN, "I/O Intr") +} + + record(ai, "$(P)$(R)Temperature_RBV") { field(DTYP, "asynFloat64") field(INP, "@asyn($(PORT),$(ADDR=0),$(TIMEOUT=1))KTX_TEMP") diff --git a/kinetixApp/Db/ADKinetix_settings.req b/kinetixApp/Db/ADKinetix_settings.req index 1a1e1fc..a6ab28e 100644 --- a/kinetixApp/Db/ADKinetix_settings.req +++ b/kinetixApp/Db/ADKinetix_settings.req @@ -6,3 +6,5 @@ $(P)$(R)MinExpRes $(P)$(R)StopAcqOnTimeout $(P)$(R)WaitForFrameTO $(P)$(R)FanSpeed +$(P)$(R)UniqueIdSource +$(P)$(R)CircBuffSize diff --git a/kinetixApp/src/ADKinetix.cpp b/kinetixApp/src/ADKinetix.cpp index 7923b13..aa3686c 100644 --- a/kinetixApp/src/ADKinetix.cpp +++ b/kinetixApp/src/ADKinetix.cpp @@ -90,10 +90,11 @@ static void exitCallbackC(void *drvPvt) { * @brief Function that blocks until either timeout is reached, or new frame callback is detected * * @param timeoutMs Time to wait before returning in ms + * @param pendingFrames Set to the number of frames still pending after this call * @return true if frame successfully was awaited * @return false if acquisition was aborted or timed out */ -bool ADKinetix::waitForEofEvent(uns32 timeoutMs) { +bool ADKinetix::waitForEofEvent(uns32 timeoutMs, uns32 *pendingFrames) { const char *functionName = "waitForEofEvent"; std::unique_lock lock(this->cameraContext->eofEvent.mutex); @@ -101,19 +102,18 @@ bool ADKinetix::waitForEofEvent(uns32 timeoutMs) { this->cameraContext; // For lambda we need temporary local reference to ctx pointer this->cameraContext->eofEvent.cond.wait_for( lock, std::chrono::milliseconds(timeoutMs), - [ctx]() { return ctx->eofEvent.flag || ctx->threadAbortFlag; }); + [ctx]() { return ctx->pendingFrames > 0 || ctx->threadAbortFlag; }); + *pendingFrames = this->cameraContext->pendingFrames; if (!this->acquisitionActive) { DEBUG_ARGS("Acquisition completed on camera %d\n", this->cameraContext->hcam); return false; } - if (!this->cameraContext->eofEvent.flag) { + if (this->cameraContext->pendingFrames == 0) { WARN_ARGS("Camera %d timed out waiting for a frame\n", this->cameraContext->hcam); return false; } - this->cameraContext->eofEvent.flag = false; // Reset flag - if (!this->cameraContext->eofFrame) { - return false; - } + this->cameraContext->pendingFrames--; + *pendingFrames = this->cameraContext->pendingFrames; return true; } @@ -456,14 +456,11 @@ void ADKinetix::updateImageFormat() { static void PV_DECL newFrameCallback(FRAME_INFO *pFrameInfo, CameraContext *ctx) { if (ctx == nullptr) return; - ctx->eofFrameInfo = *pFrameInfo; - - // Get reference to latest frame - if (PV_OK != pl_exp_get_latest_frame(ctx->hcam, &ctx->eofFrame)) { - ctx->eofFrame = nullptr; - } else { + // Count every EOF so back-to-back frames are not coalesced into one event + { std::lock_guard lock(ctx->eofEvent.mutex); - ctx->eofEvent.flag = true; + if (pFrameInfo != nullptr) ctx->eofFrameInfo = *pFrameInfo; + ctx->pendingFrames++; } // Notify acquisition thread of new frame event @@ -542,6 +539,13 @@ ADKinetix::ADKinetix(int deviceIndex, const char *portName) createParam(KTX_SpeedIdxString, asynParamInt32, &KTX_SpeedIdx); createParam(KTX_GainIdxString, asynParamInt32, &KTX_GainIdx); createParam(KTX_GainDescString, asynParamOctet, &KTX_GainDesc); + createParam(KTX_DroppedFramesString, asynParamInt32, &KTX_DroppedFrames); + setIntegerParam(KTX_DroppedFrames, 0); + createParam(KTX_PendingFramesString, asynParamInt32, &KTX_PendingFrames); + setIntegerParam(KTX_PendingFrames, 0); + createParam(KTX_UniqueIdSourceString, asynParamInt32, &KTX_UniqueIdSource); + setIntegerParam(KTX_UniqueIdSource, KTX_UID_FRAME_NR); + createParam(KTX_CircBuffSizeString, asynParamInt32, &KTX_CircBuffSize); // Initialize the PVCam SDK if (!pl_pvcam_init()) { @@ -844,6 +848,13 @@ void ADKinetix::acquisitionThread() { int16 pvcamExposureMode; NDArray *pArray; NDArrayInfo arrayInfo; + FRAME_INFO *pFrameInfo = nullptr; + int32 lastFrameNr = -1; + int32 frameNr = 0; + uns32 pendingFrames = 0; + int droppedFrames = 0; + int uniqueIdSource; + int circBuffSize; // Retrieve current frame information uns32 frameBufferSize; @@ -871,6 +882,8 @@ void ADKinetix::acquisitionThread() { getIntegerParam(KTX_StopAcqOnTimeout, &stopAcqOnTO); getIntegerParam(KTX_WaitForFrameTimeout, &waitForFrameTO); getIntegerParam(KTX_MinExpRes, &minExpRes); + getIntegerParam(KTX_UniqueIdSource, &uniqueIdSource); + getIntegerParam(KTX_CircBuffSize, &circBuffSize); // Convert EPICS selected trigger mode to PvCam format if (triggerMode == KTX_TRIG_INTERNAL) @@ -880,7 +893,9 @@ void ADKinetix::acquisitionThread() { else pvcamExposureMode = EXT_TRIG_LEVEL | EXPOSE_OUT_FIRST_ROW; - const int16 circBuffMode = CIRC_OVERWRITE; + // Locked frames can't be overwritten mid-copy; overflows show up as FrameNr gaps instead + const int16 circBuffMode = CIRC_NO_OVERWRITE; + const bool continuousMode = (acquisitionMode != ADImageSingle); this->acquisitionActive = true; @@ -892,8 +907,17 @@ void ADKinetix::acquisitionThread() { INFO("Resetting image counter..."); setIntegerParam(ADNumImagesCounter, 0); + setIntegerParam(KTX_DroppedFrames, 0); + setIntegerParam(KTX_PendingFrames, 0); callParamCallbacks(); + // Discard any EOF events left over from a previous acquisition + { + std::lock_guard lock(this->cameraContext->eofEvent.mutex); + this->cameraContext->pendingFrames = 0; + this->cameraContext->threadAbortFlag = false; + } + // In single mode, use exposure setup API call if (acquisitionMode == ADImageSingle) { pl_exp_setup_seq(this->cameraContext->hcam, 1, 1, &this->cameraContext->region, @@ -914,16 +938,31 @@ void ADKinetix::acquisitionThread() { pvcamExposureMode, (uns32) (exposureTime * pow(1000, minExpRes)), &frameBufferSize, circBuffMode); - // Allocate entire circular buffer - this->frameBuffer = (uns8 *) calloc( - KTX_CIRC_BUFF_SIZE, (size_t) frameBufferSize); // Allocate memory for circular buffer - INFO_ARGS("Allocated circular buffer for %d frames. Total size: %d bytes.", - KTX_CIRC_BUFF_SIZE, frameBufferSize * KTX_CIRC_BUFF_SIZE); + // Allocate entire circular buffer; PVCam takes its total size as uns32 + uint64_t circBuffBytes = (uint64_t) circBuffSize * frameBufferSize; + if (circBuffSize < 1 || circBuffBytes > UINT32_MAX) { + ERR_ARGS("Invalid circular buffer size of %d frames (%llu bytes)! Can be at most %u bytes.", circBuffSize, + (unsigned long long) circBuffBytes, UINT32_MAX); + this->frameBuffer = nullptr; + } else { + this->frameBuffer = (uns8 *) calloc((size_t) circBuffSize, (size_t) frameBufferSize); + } - if (PV_OK != pl_exp_start_cont(this->cameraContext->hcam, this->frameBuffer, - KTX_CIRC_BUFF_SIZE * frameBufferSize)) { + if (!this->frameBuffer) { + ERR("Failed to allocate circular buffer!"); + setIntegerParam(ADStatus, ADStatusError); + this->acquisitionActive = false; + } else if (PV_OK != pl_create_frame_info_struct(&pFrameInfo)) { reportKinetixError(functionName); this->acquisitionActive = false; + } else { + INFO_ARGS("Allocated circular buffer for %d frames. Total size: %llu bytes.", + circBuffSize, (unsigned long long) circBuffBytes); + if (PV_OK != pl_exp_start_cont(this->cameraContext->hcam, this->frameBuffer, + (uns32) circBuffBytes)) { + reportKinetixError(functionName); + this->acquisitionActive = false; + } } } @@ -938,8 +977,39 @@ void ADKinetix::acquisitionThread() { getIntegerParam(ADNumImagesCounter, &collectedImages); // Wait for new frame EOF event notification from callback - eofSuccess = this->waitForEofEvent((uns32) waitForFrameTO); + eofSuccess = this->waitForEofEvent((uns32) waitForFrameTO, &pendingFrames); + setIntegerParam(KTX_PendingFrames, (int) pendingFrames); if (eofSuccess) { + void *pFrame = this->frameBuffer; + if (continuousMode) { + if (PV_OK != pl_exp_get_oldest_frame_ex(this->cameraContext->hcam, &pFrame, + pFrameInfo)) { + // Expected after acquireStop() aborts the acquisition + if (this->acquisitionActive) reportKinetixError(functionName); + // EOF count no longer matches buffered frames; resync on the next EOF + { + std::lock_guard lock(this->cameraContext->eofEvent.mutex); + this->cameraContext->pendingFrames = 0; + } + setIntegerParam(KTX_PendingFrames, 0); + callParamCallbacks(); + continue; + } + + frameNr = pFrameInfo->FrameNr; + if (lastFrameNr >= 0 && pFrameInfo->FrameNr > lastFrameNr + 1) { + int missed = pFrameInfo->FrameNr - lastFrameNr - 1; + droppedFrames += missed; + setIntegerParam(KTX_DroppedFrames, droppedFrames); + WARN_ARGS("Dropped %d frame(s) between frame %d and frame %d", missed, + lastFrameNr, pFrameInfo->FrameNr); + } + lastFrameNr = pFrameInfo->FrameNr; + } else { + std::lock_guard lock(this->cameraContext->eofEvent.mutex); + frameNr = this->cameraContext->eofFrameInfo.FrameNr; + } + // Allocate the NDArray of the correct size this->pArrays[0] = pNDArrayPool->alloc(2, dims, dataType, 0, NULL); if (this->pArrays[0] != NULL) { @@ -947,10 +1017,13 @@ void ADKinetix::acquisitionThread() { } else { ERR("Failed to allocate array!"); setIntegerParam(ADStatus, ADStatusError); - if (acquisitionMode == ADImageSingle) + if (acquisitionMode == ADImageSingle) { pl_exp_finish_seq(this->cameraContext->hcam, this->frameBuffer, 0); - else + } else { + pl_exp_unlock_oldest_frame(this->cameraContext->hcam); pl_exp_abort(this->cameraContext->hcam, CCS_HALT); + } + this->acquisitionActive = false; callParamCallbacks(); break; } @@ -968,8 +1041,10 @@ void ADKinetix::acquisitionThread() { setIntegerParam(ADStatus, ADStatusAcquire); - // Copy data from eofFrame to pArray - memcpy(pArray->pData, this->cameraContext->eofFrame, arrayInfo.totalBytes); + // Copy the frame, then release its circular buffer slot back to PVCam + memcpy(pArray->pData, pFrame, arrayInfo.totalBytes); + if (continuousMode && PV_OK != pl_exp_unlock_oldest_frame(this->cameraContext->hcam)) + reportKinetixError(functionName); // increment the array counter int arrayCounter; @@ -977,8 +1052,9 @@ void ADKinetix::acquisitionThread() { arrayCounter++; setIntegerParam(NDArrayCounter, arrayCounter); - // set the image unique ID to the number in the sequence - pArray->uniqueId = arrayCounter; + // set the image unique ID to either the camera frame number or the array counter + pArray->uniqueId = + (uniqueIdSource == KTX_UID_ARRAY_COUNTER) ? arrayCounter : (int) frameNr; pArray->pAttributeList->add("ColorMode", "Color Mode", NDAttrInt32, &colorMode); @@ -1018,10 +1094,12 @@ void ADKinetix::acquisitionThread() { } INFO("Acquisition done. Freeing framebuffers."); + if (pFrameInfo != nullptr) pl_release_frame_info_struct(pFrameInfo); free(this->frameBuffer); setIntegerParam(ADStatus, ADStatusIdle); setIntegerParam(ADAcquire, 0); + setIntegerParam(KTX_PendingFrames, 0); callParamCallbacks(); } @@ -1105,11 +1183,12 @@ asynStatus ADKinetix::readEnum(asynUser *pasynUser, char *strings[], int values[ asynStatus ADKinetix::writeInt32(asynUser *pasynUser, epicsInt32 value) { const char *functionName = "writeInt32"; int function = pasynUser->reason; - int currentExpRes, acquiring; + int currentExpRes, acquiring, prevCircBuffSize; asynStatus status = asynSuccess; // Need to know current exp res when switching in case we cannot update getIntegerParam(KTX_MinExpRes, ¤tExpRes); + getIntegerParam(KTX_CircBuffSize, &prevCircBuffSize); // Certain actions depend on whether or not we are currently acquiring getIntegerParam(ADAcquire, &acquiring); @@ -1157,6 +1236,16 @@ asynStatus ADKinetix::writeInt32(asynUser *pasynUser, epicsInt32 value) { } else { ERR("Changing fan speed while acquiring is not permitted!"); } + } else if (function == KTX_CircBuffSize) { + if (acquiring) { + ERR("Changing circular buffer size while acquiring is not permitted!"); + setIntegerParam(KTX_CircBuffSize, prevCircBuffSize); + status = asynError; + } else if (value < 1) { + ERR_ARGS("Circular buffer size must be at least 1 frame, got %d!", value); + setIntegerParam(KTX_CircBuffSize, prevCircBuffSize); + status = asynError; + } } else if (function == ADBinX || function == ADBinY || function == ADMinX || function == ADMinY || function == ADSizeX || function == ADSizeY) { // If we change binning or image dims, update the internal region state diff --git a/kinetixApp/src/ADKinetix.h b/kinetixApp/src/ADKinetix.h index 580d2d2..06b7acb 100644 --- a/kinetixApp/src/ADKinetix.h +++ b/kinetixApp/src/ADKinetix.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -52,9 +53,6 @@ // Helper data structure definitions #include "ADKinetixDS.h" -// Size of the continuous acquisition circular buffer -#define KTX_CIRC_BUFF_SIZE 50 - // Driver name static const char* driverName = "ADKinetix"; @@ -66,6 +64,18 @@ static const char* driverName = "ADKinetix"; #define KTX_StopAcqOnTimeoutString "KTX_STOP_ACQ_ON_TO" #define KTX_WaitForFrameTimeoutString "KTX_WAIT_FOR_FRAME_TO" +// Frames lost by the camera/SDK before the driver could read them +#define KTX_DroppedFramesString "KTX_DROPPED_FRAMES" + +// EOF events received from PVCam but not yet read out by the driver +#define KTX_PendingFramesString "KTX_PENDING_FRAMES" + +// Selects the source of NDArray uniqueId +#define KTX_UniqueIdSourceString "KTX_UNIQUE_ID_SRC" + +// Number of frames in the continuous acquisition circular buffer +#define KTX_CircBuffSizeString "KTX_CIRC_BUFF_SIZE" + // Communication interface signal #define KTX_CommInterfaceString "KTX_INTERFACE" @@ -111,6 +121,12 @@ typedef enum { KTX_MIN_EXP_RES_US = 2, } KTX_MIN_EXP_RES; +// Source for NDArray uniqueId +typedef enum { + KTX_UID_FRAME_NR = 0, + KTX_UID_ARRAY_COUNTER = 1, +} KTX_UNIQUE_ID_SRC; + class ADKinetix : public ADDriver { public: ADKinetix(int deviceIndex, const char* portName); @@ -124,7 +140,7 @@ class ADKinetix : public ADDriver { void monitorThread(); void acquisitionThread(); ~ADKinetix(); - bool waitForEofEvent(uns32 timeoutMs); + bool waitForEofEvent(uns32 timeoutMs, uns32* pendingFrames); protected: int KTX_Temperature; @@ -143,7 +159,11 @@ class ADKinetix : public ADDriver { int KTX_SpeedDesc; int KTX_GainIdx; int KTX_GainDesc; -#define LAST_KTX_PARAM KTX_GainDesc + int KTX_DroppedFrames; + int KTX_PendingFrames; + int KTX_UniqueIdSource; + int KTX_CircBuffSize; +#define LAST_KTX_PARAM KTX_CircBuffSize private: void reportKinetixError(const char* functionName); diff --git a/kinetixApp/src/ADKinetixDS.h b/kinetixApp/src/ADKinetixDS.h index a5d2d2f..a34fc1b 100644 --- a/kinetixApp/src/ADKinetixDS.h +++ b/kinetixApp/src/ADKinetixDS.h @@ -148,12 +148,12 @@ struct CameraContext { // Event used for communication between acq. loop and EOF callback routine Event eofEvent{}; + // Number of EOF events not yet consumed by the acquisition thread, guarded by eofEvent.mutex + uns32 pendingFrames{0}; // Storage for a code sample specific context data if needed for callback acquisition void* eofContext{nullptr}; - // Frame info structure used to store data, for example, in EOF callback handlers + // Frame info of the most recent EOF event, guarded by eofEvent.mutex FRAME_INFO eofFrameInfo{}; - // The address of latest frame stored, for example, in EOF callback handlers - void* eofFrame{nullptr}; // Used as an acquisition thread or for other independent tasks std::thread* thread{nullptr}; From f4f17c054c505ba225ad07af8eb2647f3fb84688 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Tue, 13 Oct 2026 12:37:44 -0400 Subject: [PATCH 2/8] Remove defaults --- kinetixApp/src/ADKinetix.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/kinetixApp/src/ADKinetix.cpp b/kinetixApp/src/ADKinetix.cpp index aa3686c..49c487e 100644 --- a/kinetixApp/src/ADKinetix.cpp +++ b/kinetixApp/src/ADKinetix.cpp @@ -540,11 +540,8 @@ ADKinetix::ADKinetix(int deviceIndex, const char *portName) createParam(KTX_GainIdxString, asynParamInt32, &KTX_GainIdx); createParam(KTX_GainDescString, asynParamOctet, &KTX_GainDesc); createParam(KTX_DroppedFramesString, asynParamInt32, &KTX_DroppedFrames); - setIntegerParam(KTX_DroppedFrames, 0); createParam(KTX_PendingFramesString, asynParamInt32, &KTX_PendingFrames); - setIntegerParam(KTX_PendingFrames, 0); createParam(KTX_UniqueIdSourceString, asynParamInt32, &KTX_UniqueIdSource); - setIntegerParam(KTX_UniqueIdSource, KTX_UID_FRAME_NR); createParam(KTX_CircBuffSizeString, asynParamInt32, &KTX_CircBuffSize); // Initialize the PVCam SDK From b9c261a8ecebc86510c0668edeee2ce2cf5e59b8 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Tue, 13 Oct 2026 12:56:48 -0400 Subject: [PATCH 3/8] Add overhauled and updated phoebus bobfile --- kinetixApp/op/bob/ADKinetix.bob | 8386 +++++++++++++------------------ 1 file changed, 3547 insertions(+), 4839 deletions(-) diff --git a/kinetixApp/op/bob/ADKinetix.bob b/kinetixApp/op/bob/ADKinetix.bob index 4f7253e..620528d 100644 --- a/kinetixApp/op/bob/ADKinetix.bob +++ b/kinetixApp/op/bob/ADKinetix.bob @@ -1,702 +1,513 @@ - ADKinetix - 670 - 172 - 1100 - 1050 + ADKinetix Control - $(P) + +

XF:31ID1-ES{Kinetix-Det:1}

+ cam1: +
+ 1125 + 900 - - - 5 - 5 - - Rectangle_15 - - - - 718 - 41 - 350 - 110 - 0 - - - - - true - - - $(pv_name) -$(pv_value) - - - Rectangle_16 - - - - 810 - 43 - 163 - 21 - 0 - - - - - - - $(pv_name) -$(pv_value) - - - Rectangle - - - - 4 - 1072 - 25 - 0 - - - - - - - $(pv_name) -$(pv_value) - + 4 + 4 - Label - Kinetix Control - $(P)$(R) - 5 - 1075 + text #25_2 + Kinetix Detector Control - $(P)$(R) + 1125 25 - + + + + + + false 1 1 - false - - - - - - - - - Rectangle_1 - - - - 127 - 43 - 107 - 21 - 0 - - - - - - - $(pv_name) -$(pv_value) - - - Rectangle_2 - - - - 6 - 41 - 350 - 340 - 0 - - - - - true - - - $(pv_name) -$(pv_value) - - Label_1 - Setup - 155 - 44 - 50 + + Setup + 35 + 375 + 350 - + - 1 - 1 - false - - - - - - - - - Label_2 - Debugging - 74 - 356 - 90 - - - - - 2 - 1 - false - - - - - - - - - Menu Button - - - ../../../../$(pathAsyn)/asynRecord.opi - - $(R)AsynIO - - tab - asyn record - - - ../../../../$(pathAutosave)/save_restoreStatus_more.opi - tab - Save restore status - - - - 173 - 356 - 70 - 20 - + - $(actions) - false - - - Label_3 - Connection - 64 - 327 - - + true + + text update #77 + $(P)$(R)Model_RBV + 145 + 85 + 190 + 18 + + + - - 2 - 1 - false - - - - - - - - - Action Button - - - $(P)$(R)AsynIO.CNCT - 1 - - - $(P)$(R)AsynIO.CNCT - Connect - 172 - 327 - 80 - 20 - - - - - false - - - Action Button_1 - - - $(P)$(R)AsynIO.CNCT - 0 - - - $(P)$(R)AsynIO.CNCT - Disconnect - 257 - 327 - 80 - 20 - - - - - false - - - Label_4 - Connected - 203 - 300 - 90 - - + + + + + + + + + 6 + false + false + + + text update #81 + $(P)$(R)SerialNumber_RBV + 145 + 110 + 190 + 18 + + + - - - - - - 1 - 1 - false - - - - - - true - - - false - - $(P)$(R)AsynIO.CNCT - - - - - - - - - Label_5 - Disconnected - 188 - 300 - 120 - - + + + + + + + + + 6 + false + false + + + text update #85 + $(P)$(R)FirmwareVersion_RBV + 145 + 135 + 190 + 18 + + + - - - - - - 1 - 1 - false - - - - - - true - - - false - - $(P)$(R)AsynIO.CNCT - - - - - - - - - Label_6 - EPICS name - 76 - 100 - - + + + + + + + + + 6 + false + false + + + text update #89 + $(P)$(R)SDKVersion_RBV + 145 + 160 + 190 + 18 + + + - - 2 - 1 - false - - - - - - - - - Label_7 - Manufacturer - 56 - 125 - 120 - - + + + + + + + + + 6 + false + false + + + text update #93 + $(P)$(R)DriverVersion_RBV + 145 + 184 + 190 + 18 + + + - - 2 - 1 - false - - - - - - - - - Label_8 - asyn port - 86 - 75 - 90 - - + + + + + + + + + 6 + false + false + + + text update #97 + $(P)$(R)ADCoreVersion_RBV + 145 + 209 + 190 + 18 + + + - - 2 - 1 - false - - - - - - - - - Label_9 - Model - 126 - 150 - 50 - - + + + + + + + + + 6 + false + false + + + text #15 + Debugging + 48 + 286 + 90 + + + - - 2 - 1 - false - - - - - - - - - Label_10 - Serial number - 46 - 175 - 130 - - + 2 + + + related display #18 + + + asynRecord.opi + + $(R)AsynIO + + tab + asyn record + + + save_restoreStatus_more.opi + tab + Save restore status + + + + + 147 + 286 + 70 + 20 + + + + + $(actions) + + + text #22 + Connection + 38 + 257 + + + - - 2 - 1 - false - - - - - - - - - Label_11 - Firmware version - 16 - 200 - 160 - - + 2 + + + message button #25 + + + $(P)$(R)AsynIO.CNCT + 1 + Write + + + $(P)$(R)AsynIO.CNCT + Connect + 146 + 257 + 80 + 20 + + + + + + + message button #28 + + + $(P)$(R)AsynIO.CNCT + 0 + Write + + + $(P)$(R)AsynIO.CNCT + Disconnect + 231 + 257 + 80 + 20 + + + + + + + text #31 + Connected + 177 + 230 + 90 + + + - - 2 - 1 - false - - - - - - - - - Label_12 - SDK version - 66 - 225 - 110 - - + + + + + 1 + + + + false + + $(P)$(R)AsynIO.CNCT + + + + + text #35 + Disconnected + 162 + 230 + 120 + + + - - 2 - 1 - false - - - - - - - - - Label_13 - Driver version - 36 - 250 - 140 - - + + + + + 1 + + + + false + + $(P)$(R)AsynIO.CNCT + + + + + text #39 + EPICS name + 10 + 34 + 125 + + + - - 2 - 1 - false - - - - - - - - - Label_14 - ADCore version - 36 - 275 - 140 - - + 2 + + + text #42 + Manufacturer + 10 + 59 + 125 + + + - - 2 - 1 - false - - - - - - - - - Label_15 - $(P)$(R) - 181 - 101 - 165 - 18 - - + 2 + + + text #45 + asyn port + 10 + 9 + 125 + + + - - - - - - 1 - false - - - - - - - - - Text Update - $(P)$(R)Manufacturer_RBV - 181 - 126 - 165 - 18 - - + 2 + + + text #48 + Model + 10 + 84 + 125 + + + - - - - - - - - - - 6 - false - 1 - false - - - - - - - - - Text Update_1 - $(P)$(R)PortName_RBV - 181 - 76 - 165 - 18 - - + 2 + + + text #51 + Serial number + 10 + 109 + 125 + + + - - - - - - - - - - 6 - false - 1 - false - - - - - - - - - Text Update_2 - $(P)$(R)Model_RBV - 181 - 151 - 165 - 18 - - + 2 + + + text #54 + Firmware version + 10 + 134 + 125 + + + - - - - - - - - - - 6 - false - 1 - false - - - - - - - - - Text Update_3 - $(P)$(R)SerialNumber_RBV - 181 - 176 - 165 - 18 - - + 2 + + + text #57 + SDK version + 10 + 159 + 125 + + + - - - - - - - - - - 6 - false - 1 - false - - - - - - - - - Text Update_4 - $(P)$(R)FirmwareVersion_RBV - 181 - 201 - 165 - 18 - - + 2 + + + text #60 + Driver version + 10 + 183 + 125 + + + - - - - - - - - - - 6 - false - 1 - false - - - - - - + 2 + + + text #63 + ADCore version + 10 + 208 + 125 + + + + + 2 + + + text #66 + $(P)$(R) + 145 + 35 + 190 + 19 + + + + + + + + + + + text update #69 + $(P)$(R)Manufacturer_RBV + 145 + 60 + 190 + 18 + + + + + + + + + + + + + 6 + false + false + + + text update #73 + $(P)$(R)PortName_RBV + 145 + 10 + 190 + 18 + + + + + + + + + + + + + 6 + false + false + - - Text Update_5 - $(P)$(R)SDKVersion_RBV - 181 - 226 - 165 - 18 + + Plugins + 385 + 375 + 100 - + @@ -704,29 +515,514 @@ $(pv_value) - + - 6 - false - 1 - false - - - - - - + true + + text #18 + File + 78 + 11 + 26 + 18 + + + + + true + + + text #21 + ROI + 218 + 11 + 29 + 18 + + + + + true + + + related display #24 + + + NDFileNetCDF.opi + + nc + netCDF1: + + tab + netCDF file #1 + + + NDFileTIFF.opi + + tif + TIFF1: + + tab + TIFF file #1 + + + NDFileJPEG.opi + + jpg + JPEG1: + + tab + JPEG file #1 + + + NDFileNexus.opi + + h5 + Nexus1: + + tab + NeXus file #1 + + + NDFileMagick.opi + + tif + Magick1: + + tab + Magick file #1 + + + NDFileHDF5.opi + + h5 + HDF1: + + tab + HDF5 file #1 + + + NDFileNull.opi + + null + Null1: + + tab + Null file #1 + + + + + 123 + 11 + 64 + 20 + + + + + $(actions) + + + related display #33 + + + NDROI.opi + + ROI1: + + tab + ROI #1 + + + NDROI.opi + + ROI2: + + tab + ROI #2 + + + NDROI.opi + + ROI3: + + tab + ROI #3 + + + NDROI.opi + + ROI4: + + tab + ROI #4 + + + NDROI4.opi + + ROI1: + ROI2: + ROI3: + ROI4: + + tab + ROI 1-4 combined + + + + + 253 + 11 + 64 + 20 + + + + + $(actions) + + + related display #40 + + + ../../../../ADCore/ADApp/op/bob/autoconvert/commonPlugins.bob + tab + Common plugins + + + All + 4 + 11 + 64 + 20 + + + + + $(actions) + + + text #43 + Stats + 4 + 37 + 37 + 18 + + + + + true + + + related display #46 + + + NDStats.opi + + Stats1: + + tab + Statistics #1 + + + NDStats.opi + + Stats2: + + tab + Statistics #2 + + + NDStats.opi + + Stats3: + + tab + Statistics #3 + + + NDStats.opi + + Stats4: + + tab + Statistics #4 + + + NDStats.opi + + Stats5: + + tab + Statistics #5 + + + NDStats5.opi + + Stats1: + Stats2: + Stats3: + Stats4: + Stats5: + + tab + Statistics 1-5 + + + + + 53 + 37 + 64 + 20 + + + + + $(actions) + + + related display #54 + + + NDStdArrays.opi + + image1: + + tab + Image #1 + + + NDPva.opi + + Pva1: + + tab + Pva #1 + + + NDProcess.opi + + Proc1: + + tab + Process #1 + + + NDTransform.opi + + Trans1: + + tab + Transform #1 + + + NDColorConvert.opi + + CC1: + + tab + Color convert #1 + + + NDColorConvert.opi + + CC2: + + tab + Color convert #2 + + + NDOverlay.opi + + Over1: + + tab + Overlay #1 + + + NDOverlay8.opi + + Over1:1: + Over1:2: + Over1:3: + Over1:4: + Over1:5: + Over1:6: + Over1:7: + Over1:8: + Over1: + + tab + Overlays 1-8 + + + NDCircularBuff.opi + + CB1: + + tab + Circular buffer #1 + + + NDROIStat.opi + + ROIStat1: + + tab + ROI Statistics #1 + + + NDPluginAttribute.opi + + Attr1: + + tab + Attribute #1 + + + NDFFT.opi + + FFT1: + + tab + FFT #1 + + + NDScatter.opi + + Scatter1: + + tab + Scatter #1 + + + NDGather8.opi + + Gather1: + + tab + Gather #1 + + + NDCodec.opi + + Codec1: + + tab + Codec #1 + + + NDCodec.opi + + Codec2: + + tab + Codec #2 + + + Other #1 + 165 + 37 + 80 + 20 + + + + + $(actions) + + + related display #72 + + + NDStdArrays.opi + + image2: + + tab + Image #2 + + + scan_more.opi + + 1 + scan1 + + tab + Scan #1 + + + yySseq.opi + + AcquireSequence + + tab + Acquire Sequence + + + ioc_stats_soft.opi + + $(P) + + tab + devIocStats + + + ADCompVision.opi + + CV1: + + tab + Open CV #1 + + + NDPluginEdge.opi + + Edge1: + + tab + Edge #1 + + + NDBadPixel.opi + + BadPix1: + + tab + Bad Pixel #1 + + + configMenu.opi + + ADAutoSave + + tab + AS configMenu + + + Other #2 + 252 + 37 + 80 + 20 + + + + + $(actions) + - - Text Update_6 - $(P)$(R)DriverVersion_RBV - 181 - 251 - 165 - 18 + + Detector Status + 485 + 375 + 211 - + @@ -734,29 +1030,164 @@ $(pv_value) - + - 6 - false - 1 - false - - - - - - + true + + Label_1 + Detector state + 18 + 17 + 140 + + + + + 2 + + + TextUpdate_2 + $(P)$(R)DetectorState_RBV + 163 + 17 + 160 + 18 + + + + + + + + + + + + + 6 + false + + + Label_3 + Temperature (C) + 18 + 42 + 140 + + + + + 2 + + + TextUpdate_4 + $(P)$(R)Temperature_RBV + 163 + 42 + 160 + + + + + + + + + + + + + + + Label_5 + Fan speed + 18 + 67 + 140 + + + + + 2 + + + ComboBox_6 + $(P)$(R)FanSpeed + 163 + 67 + 80 + 20 + + + + + + + + + + + TextUpdate_7 + $(P)$(R)FanSpeed_RBV + 248 + 67 + 80 + + + + + + + + + + + + + + + Label_8 + Status Message + 4 + 97 + 151 + + + + + + + TextUpdate_9 + $(P)$(R)StatusMessage_RBV + 4 + 122 + 318 + 18 + + + + + + + + + + + + + 6 + false + false + - - Text Update_7 - $(P)$(R)ADCoreVersion_RBV - 181 - 276 - 165 - 18 + + Shutter + 696 + 375 + 166 - + @@ -764,779 +1195,258 @@ $(pv_value) - + - 6 - false - 1 - false - - - - - - - - - Rectangle_3 - - - - 8 - 386 - 350 - 80 - 0 - - - - true - - - $(pv_name) -$(pv_value) - - - Rectangle_4 - - - - 130 - 388 - 107 - 21 - 0 - - - - - - - $(pv_name) -$(pv_value) - - - Label_16 - - 130 - 396 - 1 - 40 - - + + text #15_4 + Shutter mode + 46 + 5 + 120 + + + - - 1 - false - - - - - - - - - Label_17 - Plugins - 148 - 389 - 70 - - + 2 + + + menu #18 + $(P)$(R)ShutterMode + 173 + 5 + 120 + 20 + + + + + false + + + text #21_2 + Open/Close + 66 + 54 + + + - - - - - - 1 - 1 - false - - - - - - - - - Label_18 - File - 98 - 415 - 40 - - + 2 + + + message button #24 + + + $(P)$(R)ShutterControl + 1 + Write + + + $(P)$(R)ShutterControl + Open + 173 + 54 + 60 + 20 + + + + + + + message button #27 + + + $(P)$(R)ShutterControl + 0 + Write + + + $(P)$(R)ShutterControl + Close + 240 + 54 + 60 + 20 + + + + + + + text #30 + Status: Det. + 6 + 29 + 120 + + + - - 1 - false - - - - - - - - - Label_19 - ROI - 238 - 415 - 30 - - + 2 + + + text update #33_1 + $(P)$(R)ShutterStatus_RBV + 132 + 30 + 60 + 18 + + + - - 1 - false - - - - - - - - - Menu Button_1 - - - NDFileNetCDF.opi - - nc - netCDF1: - - tab - netCDF file #1 - - - NDFileTIFF.opi - - tif - TIFF1: - - tab - TIFF file #1 - - - NDFileJPEG.opi - - jpg - JPEG1: - - tab - JPEG file #1 - - - NDFileNexus.opi - - h5 - Nexus1: - - tab - NeXus file #1 - - - NDFileMagick.opi - - tif - Magick1: - - tab - Magick file #1 - - - NDFileHDF5.opi - - h5 - HDF1: - - tab - HDF5 file #1 - - - NDFileNull.opi - - null - Null1: - - tab - Null file #1 - - - - 143 - 415 - 64 - 20 - - - - - $(actions) - false - - - Menu Button_2 - - - NDROI.opi - - ROI1: - - tab - ROI #1 - - - NDROI.opi - - ROI2: - - tab - ROI #2 - - - NDROI.opi - - ROI3: - - tab - ROI #3 - - - NDROI.opi - - ROI4: - - tab - ROI #4 - - - NDROI4.opi - - ROI1: - ROI2: - ROI3: - ROI4: - - tab - ROI 1-4 combined - - - - 273 - 415 - 64 - 20 - - - - - $(actions) - false - - - Action Button_2 - - - ../ADCore/master/commonPlugins.opi - tab - Common plugins - - - All - 18 - 415 - 64 - 20 - - - - - $(actions) - false - - - Label_20 - Stats - 18 - 441 - 50 - - + + + + + + + + + 6 + false + 1 + + + text entry #37 + $(P)$(R)ShutterOpenDelay + 132 + 79 + 60 + + + - - 1 - false - - - - - - - - - Menu Button_3 - - - NDStats.opi - - Stats1: - - tab - Statistics #1 - - - NDStats.opi - - Stats2: - - tab - Statistics #2 - - - NDStats.opi - - Stats3: - - tab - Statistics #3 - - - NDStats.opi - - Stats4: - - tab - Statistics #4 - - - NDStats.opi - - Stats5: - - tab - Statistics #5 - - - NDStats5.opi - - Stats1: - Stats2: - Stats3: - Stats4: - Stats5: - - tab - Statistics 1-5 - - - - 73 - 441 - 64 - 20 - - - - - $(actions) - false - - - Menu Button_4 - - - NDStdArrays.opi - - image1: - - tab - Image #1 - - - NDPva.opi - - Pva1: - - tab - Pva #1 - - - NDProcess.opi - - Proc1: - - tab - Process #1 - - - NDTransform.opi - - Trans1: - - tab - Transform #1 - - - NDColorConvert.opi - - CC1: - - tab - Color convert #1 - - - NDColorConvert.opi - - CC2: - - tab - Color convert #2 - - - NDOverlay.opi - - Over1: - - tab - Overlay #1 - - - NDOverlay8.opi - - Over1:1: - Over1:2: - Over1:3: - Over1:4: - Over1:5: - Over1:6: - Over1:7: - Over1:8: - Over1: - - tab - Overlays 1-8 - - - NDCircularBuff.opi - - CB1: - - tab - Circular buffer #1 - - - NDROIStat.opi - - ROIStat1: - - tab - ROI Statistics #1 - - - NDPluginAttribute.opi - - Attr1: - - tab - Attribute #1 - - - NDFFT.opi - - FFT1: - - tab - FFT #1 - - - NDScatter.opi - - Scatter1: - - tab - Scatter #1 - - - NDGather8.opi - - Gather1: - - tab - Gather #1 - - - NDCodec.opi - - Codec1: - - tab - Codec #1 - - - NDCodec.opi - - Codec2: - - tab - Codec #2 - - - Other #1 - 185 - 441 - 80 - 20 - - - - - $(actions) - false - - - Menu Button_5 - - - NDStdArrays.opi - - image2: - - tab - Image #2 - - - ../../../../$(pathSscan)/scan_more.opi - - 1 - scan1 - - tab - Scan #1 - - - ../../../../$(pathCalc)/yySseq.opi - - AcquireSequence - - tab - Acquire Sequence - - - ../../../../$(pathIocStats)/ioc_stats_soft.opi - - $(P) - - tab - devIocStats - - - Other #2 - 272 - 441 - 80 - 20 - - - - - $(actions) - false - - - Rectangle_5 - - - - 124 - 475 - 117 - 21 - 0 - - - - - - - $(pv_name) -$(pv_value) - - - Rectangle_6 - - - - 7 - 473 - 350 - 556 - 0 - - - - - true - - - $(pv_name) -$(pv_value) - - - Label_21 - Readout - 147 - 476 - 70 - - + + + + + 1 + false + false + + + text #41 + Delay: Open + 15 + 79 + 110 + + + - - - - - - 1 - 1 - false - - - - - - - - - Label_22 - X - 175 - 503 - 10 - - + 2 + + + text #44 + Close + 215 + 79 + 50 + + + - - 2 - 1 - false - - - - - - - - - Label_23 - Y - 268 - 503 - 10 - - + 2 + + + text entry #47 + $(P)$(R)ShutterCloseDelay + 272 + 79 + 60 + + + - - 2 - 1 - false - - - - - - - - - Text Input - $(P)$(R)BinX - 150 - 573 - 60 - - + + + + + 1 + false + false + + + related display #51 + + + ADEpicsShutter.opi + tab + EPICS shutter setup + + + + + 205 + 107 + 70 + 20 + + + + + $(actions) + + + text #54_1 + EPICS shutter setup + 6 + 107 + 190 + + + - - - - - - 1 - false - - - false - - - - - - - Text Input_1 - $(P)$(R)BinY - 243 - 573 - 60 - - + 2 + + + text #57_1 + EPICS + 215 + 29 + 50 + + + - - - - - - 1 - false - - - false - - - - - - - Text Update_8 - $(P)$(R)BinX_RBV - 150 - 553 - 61 - 18 - - + 2 + + + text update #60 + $(P)$(R)ShutterStatusEPICS_RBV + 272 + 30 + 60 + 18 + + + - - - - - - - - - - 1 - false - 1 - 1 - false - - - - - - + + + + + + + + + 6 + false + 1 + - - Text Update_9 - $(P)$(R)BinY_RBV - 243 - 553 - 61 - 18 + + Acquisition Setup + 375 + 35 + 375 + 298 - + @@ -1544,2214 +1454,391 @@ $(pv_value) - + - 1 - false - 1 - 1 - false - - - - - - - - - Label_24 - Binning - 69 - 573 - 70 - - - - - 2 - 1 - false - - - - - - - - - Text Update_10 - $(P)$(R)MinX_RBV - 150 - 598 - 61 - 18 - - + true + + Label_10 + Exposure time + 28 + 115 + + + - - - - - - - - - - 1 - false - 1 - 1 - false - - - - - - - - - Text Input_2 - $(P)$(R)MinX - 150 - 618 - 60 - - + 2 + + + TextEntry_11 + $(P)$(R)AcquireTime + 123 + 28 + + + - - - - - - 1 - false - - - false - - - - - - - Text Input_3 - $(P)$(R)MinY - 243 - 618 - 60 - - + + + + + + + TextUpdate_12 + $(P)$(R)AcquireTime_RBV + 227 + 28 + 18 + + + - - - - - - 1 - false - - - false - - - - - - - Text Update_11 - $(P)$(R)MinY_RBV - 243 - 598 - 61 - 18 - - + + + + + + + + + 1 + false + + + Label_13 + Acquire period + 53 + 115 + + + - - - - - - - - - - 1 - false - 1 - 1 - false - - - - - - - - - Label_25 - Region start - 19 - 618 - 120 - - - - - 2 - 1 - false - - - - - - - - - Label_26 - Region size - 29 - 663 - 110 - - - - - 2 - 1 - false - - - - - - - - - Text Input_4 - $(P)$(R)SizeX - 150 - 663 - 60 - - - - - - - - - 1 - false - - - false - - - - - - - Text Input_5 - $(P)$(R)SizeY - 243 - 663 - 60 - - - - - - - - - 1 - false - - - false - - - - - - - Text Update_12 - $(P)$(R)SizeX_RBV - 150 - 643 - 61 - 18 - - - - - - - - - - - - - 1 - false - 1 - 1 - false - - - - - - - - - Text Update_13 - $(P)$(R)SizeY_RBV - 243 - 643 - 61 - 18 - - - - - - - - - - - - - 1 - false - 1 - 1 - false - - - - - - - - - Label_27 - Sensor size - 29 - 528 - 110 - - - - - 2 - 1 - false - - - - - - - - - Text Update_14 - $(P)$(R)MaxSizeX_RBV - 150 - 529 - 61 - 18 - - - - - - - - - - - - - 1 - false - 1 - 1 - false - - - - - - - - - Text Update_15 - $(P)$(R)MaxSizeY_RBV - 243 - 529 - 61 - 18 - - - - - - - - - - - - - 1 - false - 1 - 1 - false - - - - - - - - - Label_28 - Image size (bytes) - 36 - 753 - 180 - - - - - 2 - 1 - false - - - - - - - - - Text Update_16 - $(P)$(R)ArraySize_RBV - 243 - 754 - 61 - 18 - - - - - - - - - - - - - 1 - false - 1 - 1 - false - - - - - - - - - Label_30 - Data type - 106 - 982 - 90 - - - - - 2 - 1 - false - - - - - - - - - Text Update_18 - $(P)$(R)DataType_RBV - 198 - 983 - 79 - 18 - - - - - - - - - - - - - 6 - false - 1 - 1 - false - - - - - - - - - Text Update_19 - $(P)$(R)ReverseX_RBV - 150 - 688 - 61 - 18 - - - - - - - - - - - - - 6 - false - 1 - 1 - false - - - - - - - - - Text Update_20 - $(P)$(R)ReverseY_RBV - 243 - 688 - 61 - 18 - - - - - - - - - - - - - 6 - false - 1 - 1 - false - - - - - - - - - Label_31 - Reverse - 69 - 708 - 70 - - - - - 2 - 1 - false - - - - - - - - - Menu Button_7 - $(P)$(R)ReverseX - 150 - 708 - 60 - 18 - - - - - false - - - Menu Button_8 - $(P)$(R)ReverseY - 243 - 708 - 60 - 18 - - - - - false - - - Label_32 - Color mode - 98 - 1006 - - - - - 2 - 1 - false - - - - - - - - - Text Update_21 - $(P)$(R)ColorMode_RBV - 197 - 1007 - 79 - 18 - - - - - - - - - - - - - 6 - false - 1 - 1 - false - - - - - - - - - Label_33 - Image size - 39 - 728 - - - - - 2 - 1 - false - - - - - - - - - Text Update_22 - $(P)$(R)ArraySizeX_RBV - 150 - 729 - 61 - 18 - - - - - - - - - - - - - 1 - false - 1 - 1 - false - - - - - - - - - Text Update_23 - $(P)$(R)ArraySizeY_RBV - 243 - 729 - 61 - 18 - - - - - - - - - - - - - 1 - false - 1 - 1 - false - - - - - - - - - Rectangle_7 - - - - 724 - 281 - 350 - 60 - 0 - - - - - true - - - $(pv_name) -$(pv_value) - - - Grouping Container - 851 - 283 - 107 - 21 - - - - - - - - - - true - - - - Grouping Container - 107 - 21 - - - - - - - - - - true - - - - Rectangle - - - - 107 - 21 - 0 - - - - - - - $(pv_name) -$(pv_value) - - - - Label - File - 33 - 40 - - - - - - - - - 1 - 1 - false - - - - - - - - - - Label_34 - Driver file I/O - 737 - 310 - 150 - - - - - 2 - 1 - false - - - - - - - - - Action Button_3 - - - NDFile.opi - tab - - - - 894 - 310 - 70 - 20 - - - - - $(actions) - false - - - Rectangle_8 - - - - 484 - 43 - 107 - 21 - 0 - - - - - - - $(pv_name) -$(pv_value) - - - Rectangle_9 - - - - 362 - 41 - 350 - 165 - 0 - - - - - true - - - $(pv_name) -$(pv_value) - - - Label_35 - Shutter - 502 - 44 - 70 - - - - - - - - - 1 - 1 - false - - - - - - - - - Label_36 - Shutter mode - 408 - 75 - 120 - - - - - 2 - 1 - false - - - - - - - - - Menu Button_10 - $(P)$(R)ShutterMode - 535 - 75 - 120 - 20 - - - - - false - - - Label_37 - Open/Close - 428 - 124 - - - - - 2 - 1 - false - - - - - - - - - Action Button_4 - - - $(P)$(R)ShutterControl - 1 - - - $(P)$(R)ShutterControl - Open - 535 - 124 - 60 - 20 - - - - - false - - - Action Button_5 - - - $(P)$(R)ShutterControl - 0 - - - $(P)$(R)ShutterControl - Close - 602 - 124 - 60 - 20 - - - - - false - - - Label_38 - Status: Det. - 368 - 99 - 120 - - - - - 2 - 1 - false - - - - - - - - - Text Update_24 - $(P)$(R)ShutterStatus_RBV - 494 - 100 - 60 - 18 - - - - - - - - - - - - - 6 - false - 1 - 1 - false - - - false - - - - - - - Text Input_7 - $(P)$(R)ShutterOpenDelay - 494 - 149 - 60 - - - - - - - - - 1 - false - - - false - - - - - - - Label_39 - Delay: Open - 377 - 149 - 110 - - - - - 2 - 1 - false - - - - - - - - - Label_40 - Close - 577 - 149 - 50 - - - - - 2 - 1 - false - - - - - - - - - Text Input_8 - $(P)$(R)ShutterCloseDelay - 634 - 149 - 60 - - - - - - - - - 1 - false - - - false - - - - - - - Action Button_6 - - - ADEpicsShutter.opi - tab - EPICS shutter setup - - - - 567 - 177 - 70 - 20 - - - - - $(actions) - false - - - Label_41 - EPICS shutter setup - 368 - 177 - 190 - - - - - 2 - 1 - false - - - - - - - - - Label_42 - EPICS - 577 - 99 - 50 - - - - - 2 - 1 - false - - - - - - - - - Text Update_25 - $(P)$(R)ShutterStatusEPICS_RBV - 634 - 100 - 60 - 18 - - - - - - - - - - - - - 6 - false - 1 - 1 - false - - - false - - - - - - - Rectangle_10 - - - - 363 - 213 - 350 - 563 - 0 - - - - - true - - - $(pv_name) -$(pv_value) - - - Rectangle_11 - - - - 486 - 215 - 105 - 21 - 0 - - - - - - - $(pv_name) -$(pv_value) - - - Label_43 - Exposure time - 408 - 276 - 130 - - - - - 2 - 1 - false - - - - - - - - - Text Input_9 - $(P)$(R)AcquireTime - 543 - 276 - 60 - - - - - - - - - 1 - false - - - false - - - - - - - Text Update_26 - $(P)$(R)AcquireTime_RBV - 608 - 277 - 80 - 18 - - - - - - - - - - - - - 1 - false - 1 - false - - - - - - - - - Label_44 - Acquire period - 398 - 301 - 140 - - - - - 2 - 1 - false - - - - - - - - - Text Input_10 - $(P)$(R)AcquirePeriod - 543 - 301 - 60 - - - - - - - - - 1 - false - - - false - - - - - - - Text Update_27 - $(P)$(R)AcquirePeriod_RBV - 608 - 302 - 80 - 18 - - - - - - - - - - - - - 1 - false - 1 - false - - - - - - - - - Label_45 - # Images - 458 - 326 - 80 - - - - - 2 - 1 - false - - - - - - - - - Text Input_11 - $(P)$(R)NumImages - 543 - 326 - 60 - - - - - - - - - 1 - false - - - false - - - - - - - Text Update_28 - $(P)$(R)NumImages_RBV - 608 - 327 - 80 - 18 - - - - - - - - - - - - - 1 - false - 1 - false - - - - - - - - - Text Update_29 - $(P)$(R)NumImagesCounter_RBV - 608 - 352 - 80 - 18 - - - - - - - - - - - - - 1 - false - 1 - false - - - - - - - - - Label_46 - # Images complete - 368 - 351 - 170 - - - - - 2 - 1 - false - - - - - - - - - Label_47 - # Exp./image - 418 - 376 - 120 - - - - - 2 - 1 - false - - - - - - - - - Text Input_12 - $(P)$(R)NumExposures - 543 - 376 - 60 - - - - - - - - - 1 - false - - - false - - - - - - - Text Update_30 - $(P)$(R)NumExposures_RBV - 608 - 377 - 80 - 18 - - - - - - - - - - - - - 1 - false - 1 - false - - - - - - - - - Label_48 - Done - 586 - 456 - 40 - - - - - - - - - 1 - 1 - false - - - - - - true - - - false - - $(P)$(R)Acquire - - - - - - - - - Label_49 - Collecting - 557 - 456 - - - - - - - - - 1 - 1 - false - - - - - - true - - - false - - $(P)$(R)Acquire - - - - - - - - - Action Button_7 - - - $(P)$(R)Acquire - 1 - - - $(P)$(R)Acquire - Start - 543 - 476 - 59 - 20 - - - - - false - - - Action Button_8 - - - $(P)$(R)Acquire - 0 - - - $(P)$(R)Acquire - Stop - 610 - 476 - 59 - 20 - - - - - false - - - Label_50 - Acquire - 468 - 476 - 70 - - - - - 2 - 1 - false - - - - - - - - - Label_51 - Collect - 503 - 216 - 70 - - + 2 + + + TextEntry_14 + $(P)$(R)AcquirePeriod + 123 + 53 + + + - - - - - - 1 - 1 - false - - - - - - - - - Label_52 - Image mode - 388 - 401 - - + + + + + + + TextUpdate_15 + $(P)$(R)AcquirePeriod_RBV + 227 + 53 + 18 + + + - - 2 - 1 - false - - - - - - - - - Menu Button_11 - $(P)$(R)ImageMode - 493 - 401 - 120 - 20 - - - - - false - - - Text Update_31 - $(P)$(R)ImageMode_RBV - 618 - 403 - 80 - 18 - - + + + + + + + + + 1 + false + + + Label_16 + Min exp. res. + 78 + 115 + + + - - - - - - - - - - 6 - false - 1 - false - - - - - - - - - Label_53 - Detector state - 398 - 580 - 140 - - + 2 + + + ComboBox_17 + $(P)$(R)MinExpRes + 123 + 78 + 20 + + + - - 2 - 1 - false - - - - - - - - - Text Update_32 - $(P)$(R)DetectorState_RBV - 543 - 580 - 160 - 18 - - + + + + + + + TextUpdate_18 + $(P)$(R)MinExpRes_RBV + 227 + 78 + 18 + + + - - - - - - - - - - 6 - false - 1 - false - - - false - - - - - - - Label_54 - Time remaining - 398 - 630 - 140 - - + + + + + + + + + 1 + false + + + Label_19 + # Images + 103 + 115 + + + - - 2 - 1 - false - - - - - - - - - Text Update_33 - $(P)$(R)TimeRemaining_RBV - 543 - 631 - 67 - 18 - - + 2 + + + TextEntry_20 + $(P)$(R)NumImages + 123 + 103 + + + - - - - - - - - - - 1 - false - 1 - false - - - - - - - - - Text Input_13 - $(P)$(R)ArrayCounter - 543 - 655 - 60 - - + + + + + + + TextUpdate_21 + $(P)$(R)NumImages_RBV + 227 + 103 + 18 + + + - - - - - - 1 - false - - - false - - - - - - - Label_55 - Image counter - 408 - 655 - 130 - - + + + + + + + + + 1 + false + + + Label_22 + # Exp./image + 128 + 115 + + + - - 2 - 1 - false - - - - - - - - - Text Update_34 - $(P)$(R)ArrayCounter_RBV - 608 - 656 - 80 - 18 - - + 2 + + + TextEntry_23 + $(P)$(R)NumExposures + 123 + 128 + + + - - - - - - - - - - 1 - false - 1 - false - - - - - - - - - Label_56 - Image rate - 438 - 680 - - + + + + + + + TextUpdate_24 + $(P)$(R)NumExposures_RBV + 227 + 128 + 18 + + + - - 2 - 1 - false - - - - - - - - - Text Update_35 - $(P)$(R)ArrayRate_RBV - 543 - 681 - 18 - - + + + + + + + + + 1 + false + + + Label_25 + Image mode + 153 + 115 + + + - - - - - - - - - - 1 - false - 1 - false - - - - - - - - - Label_57 - Array callbacks - 368 - 705 - 150 - - + 2 + + + ComboBox_26 + $(P)$(R)ImageMode + 123 + 153 + 20 + + + - - 2 - 1 - false - - - - - - - - - Menu Button_12 - $(P)$(R)ArrayCallbacks - 523 - 705 - 90 - 20 - - - - - false - - - Text Update_36 - $(P)$(R)ArrayCallbacks_RBV - 618 - 707 - 80 - 18 - - + + + + + + + TextUpdate_27 + $(P)$(R)ImageMode_RBV + 227 + 153 + 18 + + + - - - - - - - - - - 6 - false - 1 - 1 - false - - - - - - - - - Label_58 - Status - 368 - 605 - 60 - - + + + + + + + + + 1 + false + + + Label_28 + Trigger mode + 178 + 115 + + + - - 2 - 1 - false - - - - - - - - - Text Update_37 - $(P)$(R)StatusMessage_RBV - 433 - 605 - 275 - 18 - - + 2 + + + ComboBox_29 + $(P)$(R)TriggerMode + 123 + 178 + 20 + + + - - - - - - - - - - 6 - false - 1 - false - - - - - - - - - Label_59 - # Queued arrays - 388 - 501 - 150 - - + + + + + + + TextUpdate_30 + $(P)$(R)TriggerMode_RBV + 227 + 178 + 18 + + + - - 2 - 1 - false - - - - - - + + + + + + + + + 1 + false + + + Label_31 + Array callbacks + 203 + 115 + + + + + 2 + + + ComboBox_32 + $(P)$(R)ArrayCallbacks + 123 + 203 + 20 + + + + + + + + + + + TextUpdate_33 + $(P)$(R)ArrayCallbacks_RBV + 227 + 203 + 18 + + + + + + + + + + + + + 1 + false + - - Text Update_38 - $(P)$(R)NumQueuedArrays - 543 - 502 - 60 - 18 + + Acquisition Status + 375 + 333 + 375 + 310 - + @@ -3759,277 +1846,427 @@ $(pv_value) - + - 1 - false - 2 - 1 - false - - - - - - - - - Label_60 - Trigger mode - 368 - 426 - 120 - - + true + + text #111 + Image counter + 1 + 230 + 150 + + + - - 2 - 1 - false - - - - - - - - - Menu Button_13 - $(P)$(R)TriggerMode - 493 - 426 - 120 - 20 - - - - - false - - - Text Update_39 - $(P)$(R)TriggerMode_RBV - 618 - 427 - 80 - 18 - - + 2 + + + text entry #107 + $(P)$(R)ArrayCounter + 164 + 229 + 60 + + + - - - - - - - - - - 6 - false - 1 - false - - - - - - - - - Label_61 - Wait for plugins - 378 - 526 - 160 - - + + + + + 1 + false + false + + + text update #114 + $(P)$(R)ArrayCounter_RBV + 229 + 230 + 80 + 18 + + + - - 2 - 1 - false - - - - - - - - - Menu Button_14 - $(P)$(R)WaitForPlugins - 543 - 526 - 80 - 20 - - - - - false - - - Label_62 - Acquire busy - 418 - 555 - 120 - - + + + + + + + + + 1 + false + false + + + text update #121_1 + $(P)$(R)ArrayRate_RBV + 164 + 255 + 18 + + + - - 2 - 1 - false - - - - - - - - - Text Update_40 - $(P)$(R)AcquireBusy - 543 - 556 - 160 - 18 - - + + + + + + + + + 1 + false + false + + + text #118 + Image rate + 59 + 254 + + + - - - - - - - - - - 6 - false - 1 - false - - - false - - - - - - - Rectangle_12 - - - - 365 - 786 - 350 - 160 - 0 - - - - - true - - - $(pv_name) -$(pv_value) - - - Grouping Container_2 - 488 - 788 - 105 - 21 - - - - - - - - - - true - - - - Rectangle - - - - 105 - 21 - 0 - - - - + 2 + + + text #89 + Image size (bytes) + 29 + 177 + 150 + + + + + 2 + + + text update #92 + $(P)$(R)ArraySize_RBV + 205 + 178 + 98 + 18 + + + + + + + + + + + + + 1 + false + 1 + false + + + text #144 + Image size + 29 + 151 + + + + + 2 + + + text update #147 + $(P)$(R)ArraySizeX_RBV + 140 + 152 + 61 + 18 + + + + + + + + + + + + + 1 + false + 1 + false + + + text update #151 + $(P)$(R)ArraySizeY_RBV + 233 + 152 + 61 + 18 + + + + + + + + + + + + + 1 + false + 1 + false + + + text #46 + # Images complete + 29 + 202 + 150 + + + + + 2 + + + text update #42 + $(P)$(R)NumImagesCounter_RBV + 205 + 203 + 98 + 18 + + + + + + + + + + + + + 1 + false + false + + + text #60_1 + Done + 197 + 11 + 40 + + + + + + + + + 1 + + + + false + + $(P)$(R)Acquire + + + + + text #64 + Collecting + 168 + 11 + + + + + + + + + 1 + + + + false + + $(P)$(R)Acquire + + + + + message button #68 + + $(P)$(R)Acquire + 1 + Write + - $(pv_name) -$(pv_value) + $(P)$(R)Acquire + Start + 154 + 31 + 59 + 20 + + + + - - - Label_63 - Buffers - 462 - 789 - 157 - - + + message button #71 + + + $(P)$(R)Acquire + 0 + Write + + + $(P)$(R)Acquire + Stop + 221 + 31 + 59 + 20 + + + + + + + text #74 + Acquire + 79 + 31 + 70 + + + - - - - - - 1 - 1 - false - - - - - - - - - Label_64 - Buffers used - 415 - 816 - 160 - - + 2 + + + text #142 + # Queued arrays + 56 + 150 + + + + + 2 + + + text update #145 + $(P)$(R)NumQueuedArrays + 154 + 57 + 60 + 18 + + + + + + + + + + + + + 1 + false + 2 + false + + + text #159 + Wait for plugins + 81 + 160 + + + + + 2 + + + menu #162 + $(P)$(R)WaitForPlugins + 184 + 81 + 80 + 20 + + + + + false + + + text #165 + Acquire busy + 29 + 106 + 120 + + + + + 2 + + + text update #168 + $(P)$(R)AcquireBusy + 154 + 107 + 160 + 18 + + + - - 2 - 1 - false - - - - - - + + + + + + + + + 6 + false + - - Text Update_41 - $(P)$(R)PoolUsedBuffers - 580 - 817 - 50 - 18 + + Buffers + 375 + 643 + 375 + 177 - + @@ -4037,81 +2274,269 @@ $(pv_value) - + - 1 - false - 2 - 1 - false - - - - - - - - - Label_65 - Buffers alloc/free - 395 - 841 - 180 - - + true + + text #15_16 + Pre-allocate + 1 + 12 + 120 + + + - - 2 - 1 - false - - - - - - - - - Text Update_42 - $(P)$(R)PoolAllocBuffers - 580 - 842 - 50 - 18 - - + 2 + + + text update #18_1 + $(P)$(R)PoolUsedBuffers + 156 + 38 + 50 + 18 + + + - - - - - - - - - - 1 - false - 2 - 1 - false - - - - - - + + + + + + + + + 1 + false + 2 + false + + + text #22_3 + Used/Alloc/Free + 1 + 37 + 150 + + + + + 2 + + + text update #25_1 + $(P)$(R)PoolAllocBuffers + 211 + 38 + 50 + 18 + + + + + + + + + + + + + 1 + false + 2 + false + + + text update #29_2 + $(P)$(R)PoolFreeBuffers + 266 + 38 + 50 + 18 + + + + + + + + + + + + + 1 + false + 2 + false + + + menu #36_1 + $(P)$(R)PoolPollStats.SCAN + 236 + 87 + 90 + 20 + + + + + false + + + text #39_2 + Buffer & memory polling + 1 + 87 + 230 + + + + + 2 + + + text update #42_2 + $(P)$(R)PoolMaxMem + 206 + 63 + 60 + 18 + + + + + + + + + + + + + 1 + false + 2 + false + + + text update #46_1 + $(P)$(R)PoolUsedMem + 271 + 63 + 60 + 18 + + + + + + + + + + + + + 1 + false + 2 + false + + + text #50_1 + Memory max/used (MB) + 1 + 62 + 200 + + + + + 2 + + + text #53_1 + Empty free list + 51 + 112 + 150 + + + + + 2 + + + message button #56_1 + + + $(P)$(R)EmptyFreeList + 1 + Write + + + $(P)$(R)EmptyFreeList + Empty + 206 + 112 + 83 + 20 + + + + + + + text entry #59_1 + $(P)$(R)NumPreAllocBuffers + 126 + 12 + + + + + + + + + 1 + false + false + + + message button #63_1 + + + $(P)$(R)PreAllocBuffers + 1 + Write + + + $(P)$(R)PreAllocBuffers + Allocate + 231 + 12 + 81 + 20 + + + + + - - Text Update_43 - $(P)$(R)PoolFreeBuffers - 635 - 842 - 50 - 18 + + File + 375 + 820 + 375 + 71 - + @@ -4119,63 +2544,52 @@ $(pv_value) - - - - 1 - false - 2 - 1 - false - - - - - - - - - Menu Button_15 - $(P)$(R)PoolUsedMem.SCAN - 610 - 891 - 90 - 20 - - + - false - - - Label_66 - Buffer & memory polling - 375 - 891 - 230 - - + true + + text #21_1 + Driver file I/O + 18 + 10 + 150 + + + - - 2 - 1 - false - - - - - - + 2 + + + related display #24_1 + + + NDFile.opi + tab + Open Display + + + + + 175 + 10 + 94 + 20 + + + + + $(actions) + - - Text Update_44 - $(P)$(R)PoolMaxMem - 580 - 867 - 60 - 18 + + Detector Info + 750 + 35 + 375 + 145 - + @@ -4183,30 +2597,178 @@ $(pv_value) - + - 1 - false - 2 - 1 - false - - - - - - + true + + Label_34 + Sensor size + 6 + 10 + 125 + + + + + + + + + 2 + + + TextUpdate_35 + $(P)$(R)MaxSizeX_RBV + 140 + 10 + 61 + + + + + + + + + + + + + + + TextUpdate_36 + $(P)$(R)MaxSizeY_RBV + 222 + 10 + 61 + + + + + + + + + + + + + + + Label_37 + Interface + 6 + 35 + 125 + + + + + + + + + 2 + + + TextUpdate_38 + $(P)$(R)SelectedInterface_RBV + 140 + 35 + 150 + + + + + + + + + + + + + + + Label_39 + Data type + 6 + 60 + 125 + + + + + + + + + 2 + + + TextUpdate_40 + $(P)$(R)DataType_RBV + 140 + 60 + 150 + + + + + + + + + + + + + + + Label_41 + Color mode + 6 + 85 + 125 + + + + + + + + + 2 + + + TextUpdate_42 + $(P)$(R)ColorMode_RBV + 140 + 85 + 150 + + + + + + + + + + + + + - - Text Update_45 - $(P)$(R)PoolUsedMem - 645 - 867 - 60 - 18 + + Region + 750 + 180 + 375 + 163 - + @@ -4214,324 +2776,377 @@ $(pv_value) - + - 1 - false - 2 - 1 - false - - - - - - - - - Label_67 - Memory max/used (MB) - 375 - 866 - 200 - - + true + + Label_43 + X + 97 + 5 + 106 + + + - - 2 - 1 - false - - - - - - - - - Label_68 - Empty free list - 425 - 916 - 150 - - + 1 + + + Label_44 + Y + 210 + 5 + 106 + + + - - 2 - 1 - false - - - - - - - - - Action Button_9 - - - $(P)$(R)EmptyFreeList - 1 - - - $(P)$(R)EmptyFreeList - Empty - 580 - 916 - 83 - 20 - - - - - false - - - Rectangle_13 - - - - 851 - 163 - 107 - 21 - 0 - - - - - - - $(pv_name) -$(pv_value) - - - Rectangle_14 - - - - 724 - 161 - 350 - 110 - 0 - - - - - true - - - $(pv_name) -$(pv_value) - - - Label_69 - Attributes - 854 - 163 - - + 1 + + + Label_45 + Binning + 28 + 92 + + + - - - - - - 1 - 1 - false - - - - - - - - - Label_70 - File - 749 - 191 - 40 - - + + + + + 2 + + + TextEntry_46 + $(P)$(R)BinX + 97 + 28 + 52 + + + - - 2 - 1 - false - - - - - - - - - Text Input_14 - $(P)$(R)NDAttributesFile - 795 - 192 - 275 - - + + + + + + + TextUpdate_47 + $(P)$(R)BinX_RBV + 151 + 28 + 52 + + + - - - - - - 6 - false - - - false - - - - - - - Label_71 - Macros - 729 - 216 - 60 - - + + + + + + + + + + + TextEntry_48 + $(P)$(R)BinY + 210 + 28 + 52 + + + - - 2 - 1 - false - - - - - - - - - Text Input_15 - $(P)$(R)NDAttributesMacros - 794 - 217 - 275 - - + + + + + + + TextUpdate_49 + $(P)$(R)BinY_RBV + 264 + 28 + 52 + + + - - - - - - 6 - false - - - false - - - - - - - Label_72 - Status - 729 - 241 - 60 - - + + + + + + + + + + + Label_50 + Region start + 53 + 92 + + + - - 2 - 1 - false - - - - - - - - - Text Update_46 - $(P)$(R)NDAttributesStatus - 794 - 242 - 275 - 18 - - + + + + + 2 + + + TextEntry_51 + $(P)$(R)MinX + 97 + 53 + 52 + + + - - - - - - - - - - 6 - false - 1 - false - - - false - - - - - - - Label_73 - Readout Port - 18 - 831 - 120 - - + + + + + + + TextUpdate_52 + $(P)$(R)MinX_RBV + 151 + 53 + 52 + + + + + + + + + + + + + + + TextEntry_53 + $(P)$(R)MinY + 210 + 53 + 52 + + + + + + + + + + + TextUpdate_54 + $(P)$(R)MinY_RBV + 264 + 53 + 52 + + + + + + + + + + + + + + + Label_55 + Region size + 78 + 92 + + + + + + + + + 2 + + + TextEntry_56 + $(P)$(R)SizeX + 97 + 78 + 52 + + + + + + + + + + + TextUpdate_57 + $(P)$(R)SizeX_RBV + 151 + 78 + 52 + + + + + + + + + + + + + + + TextEntry_58 + $(P)$(R)SizeY + 210 + 78 + 52 + + + + + + + + + + + TextUpdate_59 + $(P)$(R)SizeY_RBV + 264 + 78 + 52 + + + + + + + + + + + + + + + Label_60 + Reverse + 103 + 92 + + + + + + + + + 2 + + + ComboBox_61 + $(P)$(R)ReverseX + 97 + 103 + 52 + 20 + + + + + + + + + + + TextUpdate_62 + $(P)$(R)ReverseX_RBV + 151 + 103 + 52 + + + + + + + + + + + + + + + ComboBox_63 + $(P)$(R)ReverseY + 210 + 103 + 52 + 20 + + + - - 2 - 1 - false - - - - - - - - - Menu Button_16 - $(P)$(R)ReadoutPortIdx - 143 - 831 - 51 - 20 - - - - - false + + + + + + + TextUpdate_64 + $(P)$(R)ReverseY_RBV + 264 + 103 + 52 + + + + + + + + + + + + + - - Text Update_47 - $(P)$(R)ReadoutPortName_RBV - 201 - 832 - 147 - 18 + + Readout Mode + 750 + 343 + 375 + 195 - + @@ -4539,420 +3154,260 @@ $(pv_value) - + - 6 - false - 1 - false - - - - - - - - - Label_74 - Speed Index - 18 - 859 - 120 - - + true + + Label_65 + Readout port + 10 + 125 + + + - - 2 - 1 - false - - - - - - - - - Menu Button_17 - $(P)$(R)SpeedIdx - 143 - 859 - 51 - 20 - - - - - false - - - Text Update_48 - $(P)$(R)SpeedDesc_RBV - 201 - 860 - 147 - 18 - - + + + + + 2 + + + ComboBox_66 + $(P)$(R)ReadoutPortIdx + 135 + 10 + 70 + 20 + + + - - - - - - - - - - 6 - false - 1 - false - - - - - - - - - Label_75 - Gain Index - 17 - 887 - 120 - - + + + + + + + TextUpdate_67 + $(P)$(R)ReadoutPortName_RBV + 210 + 10 + 122 + + + - - 2 - 1 - false - - - - - - - - - Menu Button_18 - $(P)$(R)GainIdx - 142 - 887 - 51 - 20 - - - - - false - - - Text Update_49 - $(P)$(R)GainDesc_RBV - 200 - 888 - 147 - 18 - - + + + + + + + + + + + Label_68 + Speed + 35 + 125 + + + - - - - - - - - - - 6 - false - 1 - false - - - - - - - - - Label_77 - Connection - 847 - 43 - 86 - - + + + + + 2 + + + ComboBox_69 + $(P)$(R)SpeedIdx + 135 + 35 + 70 + 20 + + + - - - - - - 1 - 1 - false - - - - - - - - - Label_78 - Connection Mode - 764 - 73 - 127 - - + + + + + + + TextUpdate_70 + $(P)$(R)SpeedDesc_RBV + 210 + 35 + 122 + + + - - 2 - 1 - false - - - - - - - - - Text Update_51 - $(P)$(R)SelectedInterface_RBV - 893 - 74 - 79 - 18 - - + + + + + + + + + + + Label_71 + Gain + 60 + 125 + + + - - - - - - - - - - 1 - false - 1 - 1 - false - - - - - - - - - Label_79 - Stop on Timeout - 369 - 731 - 150 - - + + + + + 2 + + + ComboBox_72 + $(P)$(R)GainIdx + 135 + 60 + 70 + 20 + + + - - 2 - 1 - false - - - - - - - - - Menu Button_19 - $(P)$(R)StopAcqOnTimeout - 524 - 731 - 90 - 20 - - - - - false - - - Text Update_52 - $(P)$(R)StopAcqOnTimeout_RBV - 619 - 733 - 80 - 18 - - + + + + + + + TextUpdate_73 + $(P)$(R)GainDesc_RBV + 210 + 60 + 122 + + + - - - - - - - - - - 6 - false - 1 - 1 - false - - - - - - - - - Action Button_10 - - - $(P)$(R)ApplyReadoutMode - 1 - - - $(P)$(R)ApplyReadoutMode - Apply Readout Mode - 99 - 921 - 157 - 20 - - - - - false - - - Label_80 - Readout Mode Selection - 11 - 950 - 183 - - + + + + + + + + + + + Label_74 + Selection + 85 + 125 + + + - - 2 - 1 - false - - - - - - - - - Text Update_53 - $(P)$(R)ReadoutModeValid_RBV - 201 - 951 - 96 - 18 - - + + + + + 2 + + + TextUpdate_75 + $(P)$(R)ReadoutModeValid_RBV + 135 + 85 + 70 + + + - - - - - - - - - - 6 - false - 1 - false - - - - - - - - - - - $(P)$(R)ReadoutModeValid_RBV - - - false - - - - - - - Label_81 - Frame wait time (ms) - 368 - 761 - 177 - - + + + + + + + + + + + ActionButton_76 + + + $(P)$(R)ApplyReadoutMode + 1 + Write + + + $(P)$(R)ApplyReadoutMode + Apply + 210 + 85 + 80 + 20 + + + + + + + Label_77 + Current mode + 110 + 125 + + + - - 2 - 1 - false - - - - - - - - - Text Input_16 - $(P)$(R)WaitForFrameTO - 550 - 761 - 60 - - + + + + + 2 + + + TextUpdate_78 + $(P)$(R)ReadoutMode_RBV + 6 + 135 + 325 + 18 + + + - - - - - - 1 - false - - - false - - - - + + + + + + + + + 6 + false + - - Text Update_54 - $(P)$(R)WaitForFrameTO_RBV - 615 - 762 - 80 - 18 + + Frame Handling + 750 + 538 + 375 + 195 - + @@ -4960,112 +3415,287 @@ $(pv_value) - + - 1 - false - 1 - false - - - - - - - - - Label_82 - Current Readout Mode - 23 - 782 - 170 - - + true + + Label_79 + Circ. buffer size + 6 + 10 + 125 + + + - - 2 - 1 - false - - - - - - - - - Text Update_55 - $(P)$(R)ReadoutMode_RBV - 28 - 807 - 317 - 18 - - + + + + + 2 + + + TextEntry_80 + $(P)$(R)CircBuffSize + 140 + 10 + + + - - - - - - - - - - 6 - false - 1 - false - - - - - - - - - Label_76 - Min Exp Time - 393 - 251 - 115 - - + + + + + + + TextUpdate_81 + $(P)$(R)CircBuffSize_RBV + 251 + 10 + 80 + + + - - 2 - 1 - false - - - - - - - - - Menu Button_20 - $(P)$(R)MinExpRes - 513 - 251 - 90 - 20 - - - - - false + + + + + + + + + + + Label_82 + Pending frames + 6 + 35 + 125 + + + + + + + + + 2 + + + TextUpdate_83 + $(P)$(R)PendingFrames_RBV + 140 + 35 + 100 + + + + + + + + + + + + + + + Label_84 + Dropped frames + 6 + 60 + 125 + + + + + + + + + 2 + + + TextUpdate_85 + $(P)$(R)DroppedFrames_RBV + 140 + 60 + 100 + + + + + + + + + + + + + + + Label_86 + Unique ID source + 6 + 85 + 125 + + + + + + + + + 2 + + + ComboBox_87 + $(P)$(R)UniqueIdSource + 140 + 85 + 20 + + + + + + + + + + + TextUpdate_88 + $(P)$(R)UniqueIdSource_RBV + 245 + 85 + 88 + + + + + + + + + + + + + + + Label_89 + Frame timeout (ms) + 6 + 110 + 125 + + + + + + + + + 2 + + + TextEntry_90 + $(P)$(R)WaitForFrameTO + 140 + 110 + + + + + + + + + + + TextUpdate_91 + $(P)$(R)WaitForFrameTO_RBV + 251 + 110 + 80 + + + + + + + + + + + + + + + Label_92 + Stop on timeout + 6 + 135 + 125 + + + + + + + + + 2 + + + ComboBox_93 + $(P)$(R)StopAcqOnTimeout + 140 + 135 + 20 + + + + + + + + + + + TextUpdate_94 + $(P)$(R)StopAcqOnTimeout_RBV + 251 + 135 + 80 + + + + + + + + + + + + + - - Text Update_50 - $(P)$(R)MinExpRes_RBV - 608 - 253 - 80 - 18 + + Attributes + 750 + 733 + 375 + 120 - + @@ -5073,19 +3703,97 @@ $(pv_value) - + - 6 - false - 1 - 1 - false - - - - - - + true + + text #15_6 + File + 14 + 6 + 40 + + + + + 2 + + + text entry #18 + $(P)$(R)NDAttributesFile + 60 + 7 + 275 + + + + + + + + + 6 + false + false + + + text #22_2 + Macros + 31 + 60 + + + + + 2 + + + text entry #25_1 + $(P)$(R)NDAttributesMacros + 59 + 32 + 275 + + + + + + + + + 6 + false + false + + + text #29 + Status + 56 + 60 + + + + + 2 + + + text update #32 + $(P)$(R)NDAttributesStatus + 59 + 57 + 275 + 18 + + + + + + + + + 6 + false +
From 734f5b23e69bfaea9ed8ce66466c61b67536d914 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Wed, 14 Oct 2026 16:22:13 -0400 Subject: [PATCH 4/8] Adjustment to lock behavior --- kinetixApp/src/ADKinetix.cpp | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/kinetixApp/src/ADKinetix.cpp b/kinetixApp/src/ADKinetix.cpp index 49c487e..6008529 100644 --- a/kinetixApp/src/ADKinetix.cpp +++ b/kinetixApp/src/ADKinetix.cpp @@ -544,6 +544,9 @@ ADKinetix::ADKinetix(int deviceIndex, const char *portName) createParam(KTX_UniqueIdSourceString, asynParamInt32, &KTX_UniqueIdSource); createParam(KTX_CircBuffSizeString, asynParamInt32, &KTX_CircBuffSize); + setIntegerParam(KTX_DroppedFrames, 0); + setIntegerParam(KTX_PendingFrames, 0); + // Initialize the PVCam SDK if (!pl_pvcam_init()) { reportKinetixError(functionName); @@ -717,14 +720,19 @@ void ADKinetix::monitorThread() { if (!this->acquisitionActive) { this->pollingTemperature = true; int16 temperature = 0; - if (PV_OK != pl_get_param(this->cameraContext->hcam, PARAM_TEMP, ATTR_CURRENT, - (void *) &temperature)) { + bool ok = PV_OK == pl_get_param(this->cameraContext->hcam, PARAM_TEMP, ATTR_CURRENT, + (void *) &temperature); + // Clear before locking: acquisitionThread spins on this flag while holding the lock + this->pollingTemperature = false; + + this->lock(); + if (!ok) { reportKinetixError(functionName); } else { setDoubleParam(KTX_Temperature, (double) (temperature / 100.0)); callParamCallbacks(); } - this->pollingTemperature = false; + this->unlock(); } epicsThreadSleep(1); @@ -762,9 +770,11 @@ void ADKinetix::acquireStop() { // Mark acquisition as inactive this->acquisitionActive = false; - // Wait for acquisition thread to join + // Acquisition thread needs the port lock to finish, so release it while joining INFO("Waiting for acquisition thread to join..."); + this->unlock(); epicsThreadMustJoin(this->acquisitionThreadId); + this->lock(); INFO("Acquisition stopped."); // Refresh all PV values @@ -858,6 +868,8 @@ void ADKinetix::acquisitionThread() { size_t dims[2]; NDColorMode_t colorMode = NDColorModeMono; // only grayscale at the moment NDDataType_t dataType; + + this->lock(); getIntegerParam(NDDataType, (int *) &dataType); getCurrentFrameDimensions(dims); @@ -868,6 +880,7 @@ void ADKinetix::acquisitionThread() { setIntegerParam(ADStatus, ADStatusError); setIntegerParam(ADAcquire, 0); callParamCallbacks(); + this->unlock(); return; } @@ -974,7 +987,9 @@ void ADKinetix::acquisitionThread() { getIntegerParam(ADNumImagesCounter, &collectedImages); // Wait for new frame EOF event notification from callback + this->unlock(); eofSuccess = this->waitForEofEvent((uns32) waitForFrameTO, &pendingFrames); + this->lock(); setIntegerParam(KTX_PendingFrames, (int) pendingFrames); if (eofSuccess) { void *pFrame = this->frameBuffer; @@ -1098,6 +1113,7 @@ void ADKinetix::acquisitionThread() { setIntegerParam(ADAcquire, 0); setIntegerParam(KTX_PendingFrames, 0); callParamCallbacks(); + this->unlock(); } /** @@ -1330,8 +1346,10 @@ void ADKinetix::report(FILE *fp, int details) { ADKinetix::~ADKinetix() { const char *functionName = "~ADKinetix"; - // Stop acquisiton if active + // Stop acquisiton if active; acquireStop expects the port lock to be held + this->lock(); this->acquireStop(); + this->unlock(); // shut down separate threads this->monitoringActive = false; From 4d1d19462dde1af4d65afa260cf076cd4431bbb2 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Wed, 14 Oct 2026 16:37:17 -0400 Subject: [PATCH 5/8] Release lock around doCallbacksGenericPointer --- kinetixApp/src/ADKinetix.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/kinetixApp/src/ADKinetix.cpp b/kinetixApp/src/ADKinetix.cpp index 6008529..fc2e57d 100644 --- a/kinetixApp/src/ADKinetix.cpp +++ b/kinetixApp/src/ADKinetix.cpp @@ -544,9 +544,6 @@ ADKinetix::ADKinetix(int deviceIndex, const char *portName) createParam(KTX_UniqueIdSourceString, asynParamInt32, &KTX_UniqueIdSource); createParam(KTX_CircBuffSizeString, asynParamInt32, &KTX_CircBuffSize); - setIntegerParam(KTX_DroppedFrames, 0); - setIntegerParam(KTX_PendingFrames, 0); - // Initialize the PVCam SDK if (!pl_pvcam_init()) { reportKinetixError(functionName); @@ -1073,8 +1070,10 @@ void ADKinetix::acquisitionThread() { // Sends image to the ArrayDataPV getAttributes(pArray->pAttributeList); - // Perform callbacks on collected frame + // Perform callbacks on collected frame; plugins must not run under the driver lock + this->unlock(); doCallbacksGenericPointer(pArray, NDArrayData, 0); + this->lock(); // If in single mode, finish acq, if in multiple mode and reached target number // complete acquisition. From 188c5c9387996ae7dfb8693cfd28de6ca31210b6 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Fri, 16 Oct 2026 16:03:34 -0400 Subject: [PATCH 6/8] Some debugging of the frame number variations --- kinetixApp/src/ADKinetix.cpp | 43 +++++++++++++++++++----------------- kinetixApp/src/ADKinetix.h | 2 +- kinetixApp/src/ADKinetixDS.h | 7 +++--- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/kinetixApp/src/ADKinetix.cpp b/kinetixApp/src/ADKinetix.cpp index fc2e57d..a2fcccd 100644 --- a/kinetixApp/src/ADKinetix.cpp +++ b/kinetixApp/src/ADKinetix.cpp @@ -91,10 +91,11 @@ static void exitCallbackC(void *drvPvt) { * * @param timeoutMs Time to wait before returning in ms * @param pendingFrames Set to the number of frames still pending after this call + * @param frameNr Set to the camera FrameNr of the consumed EOF event * @return true if frame successfully was awaited * @return false if acquisition was aborted or timed out */ -bool ADKinetix::waitForEofEvent(uns32 timeoutMs, uns32 *pendingFrames) { +bool ADKinetix::waitForEofEvent(uns32 timeoutMs, uns32 *pendingFrames, int32 *frameNr) { const char *functionName = "waitForEofEvent"; std::unique_lock lock(this->cameraContext->eofEvent.mutex); @@ -102,18 +103,19 @@ bool ADKinetix::waitForEofEvent(uns32 timeoutMs, uns32 *pendingFrames) { this->cameraContext; // For lambda we need temporary local reference to ctx pointer this->cameraContext->eofEvent.cond.wait_for( lock, std::chrono::milliseconds(timeoutMs), - [ctx]() { return ctx->pendingFrames > 0 || ctx->threadAbortFlag; }); - *pendingFrames = this->cameraContext->pendingFrames; + [ctx]() { return !ctx->pendingFrameNrs.empty() || ctx->threadAbortFlag; }); + *pendingFrames = (uns32) this->cameraContext->pendingFrameNrs.size(); if (!this->acquisitionActive) { DEBUG_ARGS("Acquisition completed on camera %d\n", this->cameraContext->hcam); return false; } - if (this->cameraContext->pendingFrames == 0) { + if (this->cameraContext->pendingFrameNrs.empty()) { WARN_ARGS("Camera %d timed out waiting for a frame\n", this->cameraContext->hcam); return false; } - this->cameraContext->pendingFrames--; - *pendingFrames = this->cameraContext->pendingFrames; + *frameNr = this->cameraContext->pendingFrameNrs.front(); + this->cameraContext->pendingFrameNrs.pop_front(); + *pendingFrames = (uns32) this->cameraContext->pendingFrameNrs.size(); return true; } @@ -456,11 +458,10 @@ void ADKinetix::updateImageFormat() { static void PV_DECL newFrameCallback(FRAME_INFO *pFrameInfo, CameraContext *ctx) { if (ctx == nullptr) return; - // Count every EOF so back-to-back frames are not coalesced into one event + // Queue every EOF so back-to-back frames are not coalesced into one event { std::lock_guard lock(ctx->eofEvent.mutex); - if (pFrameInfo != nullptr) ctx->eofFrameInfo = *pFrameInfo; - ctx->pendingFrames++; + ctx->pendingFrameNrs.push_back(pFrameInfo != nullptr ? pFrameInfo->FrameNr : -1); } // Notify acquisition thread of new frame event @@ -921,7 +922,7 @@ void ADKinetix::acquisitionThread() { // Discard any EOF events left over from a previous acquisition { std::lock_guard lock(this->cameraContext->eofEvent.mutex); - this->cameraContext->pendingFrames = 0; + this->cameraContext->pendingFrameNrs.clear(); this->cameraContext->threadAbortFlag = false; } @@ -985,7 +986,7 @@ void ADKinetix::acquisitionThread() { // Wait for new frame EOF event notification from callback this->unlock(); - eofSuccess = this->waitForEofEvent((uns32) waitForFrameTO, &pendingFrames); + eofSuccess = this->waitForEofEvent((uns32) waitForFrameTO, &pendingFrames, &frameNr); this->lock(); setIntegerParam(KTX_PendingFrames, (int) pendingFrames); if (eofSuccess) { @@ -998,25 +999,27 @@ void ADKinetix::acquisitionThread() { // EOF count no longer matches buffered frames; resync on the next EOF { std::lock_guard lock(this->cameraContext->eofEvent.mutex); - this->cameraContext->pendingFrames = 0; + this->cameraContext->pendingFrameNrs.clear(); } setIntegerParam(KTX_PendingFrames, 0); callParamCallbacks(); continue; } - frameNr = pFrameInfo->FrameNr; - if (lastFrameNr >= 0 && pFrameInfo->FrameNr > lastFrameNr + 1) { - int missed = pFrameInfo->FrameNr - lastFrameNr - 1; + // TEMP DEBUG: compare EOF callback FrameNr with get_oldest_frame_ex FrameNr + printf("FRAMENR_DEBUG eof=%d oldest=%d pending=%u ts=%lld%s\n", (int) frameNr, + (int) pFrameInfo->FrameNr, (unsigned) pendingFrames, + (long long) pFrameInfo->TimeStamp, + (frameNr != pFrameInfo->FrameNr) ? " MISMATCH" : ""); + + if (lastFrameNr >= 0 && frameNr > lastFrameNr + 1) { + int missed = frameNr - lastFrameNr - 1; droppedFrames += missed; setIntegerParam(KTX_DroppedFrames, droppedFrames); WARN_ARGS("Dropped %d frame(s) between frame %d and frame %d", missed, - lastFrameNr, pFrameInfo->FrameNr); + lastFrameNr, frameNr); } - lastFrameNr = pFrameInfo->FrameNr; - } else { - std::lock_guard lock(this->cameraContext->eofEvent.mutex); - frameNr = this->cameraContext->eofFrameInfo.FrameNr; + lastFrameNr = frameNr; } // Allocate the NDArray of the correct size diff --git a/kinetixApp/src/ADKinetix.h b/kinetixApp/src/ADKinetix.h index 06b7acb..f1b58dc 100644 --- a/kinetixApp/src/ADKinetix.h +++ b/kinetixApp/src/ADKinetix.h @@ -140,7 +140,7 @@ class ADKinetix : public ADDriver { void monitorThread(); void acquisitionThread(); ~ADKinetix(); - bool waitForEofEvent(uns32 timeoutMs, uns32* pendingFrames); + bool waitForEofEvent(uns32 timeoutMs, uns32* pendingFrames, int32* frameNr); protected: int KTX_Temperature; diff --git a/kinetixApp/src/ADKinetixDS.h b/kinetixApp/src/ADKinetixDS.h index a34fc1b..a20cf3e 100644 --- a/kinetixApp/src/ADKinetixDS.h +++ b/kinetixApp/src/ADKinetixDS.h @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -148,12 +149,10 @@ struct CameraContext { // Event used for communication between acq. loop and EOF callback routine Event eofEvent{}; - // Number of EOF events not yet consumed by the acquisition thread, guarded by eofEvent.mutex - uns32 pendingFrames{0}; + // FrameNr of each EOF event not yet consumed by the acquisition thread, guarded by eofEvent.mutex + std::deque pendingFrameNrs{}; // Storage for a code sample specific context data if needed for callback acquisition void* eofContext{nullptr}; - // Frame info of the most recent EOF event, guarded by eofEvent.mutex - FRAME_INFO eofFrameInfo{}; // Used as an acquisition thread or for other independent tasks std::thread* thread{nullptr}; From ee78fbabfdd3f8484fc9cb7fb9e8ab8c4d1fe0da Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Fri, 16 Oct 2026 16:11:02 -0400 Subject: [PATCH 7/8] Log circ buffer slot --- kinetixApp/src/ADKinetix.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kinetixApp/src/ADKinetix.cpp b/kinetixApp/src/ADKinetix.cpp index a2fcccd..e31b01a 100644 --- a/kinetixApp/src/ADKinetix.cpp +++ b/kinetixApp/src/ADKinetix.cpp @@ -1007,9 +1007,10 @@ void ADKinetix::acquisitionThread() { } // TEMP DEBUG: compare EOF callback FrameNr with get_oldest_frame_ex FrameNr - printf("FRAMENR_DEBUG eof=%d oldest=%d pending=%u ts=%lld%s\n", (int) frameNr, + printf("FRAMENR_DEBUG eof=%d oldest=%d pending=%u ts=%lld slot=%ld%s\n", (int) frameNr, (int) pFrameInfo->FrameNr, (unsigned) pendingFrames, (long long) pFrameInfo->TimeStamp, + (long) (((uns8 *) pFrame - (uns8 *) this->frameBuffer) / frameBufferSize), (frameNr != pFrameInfo->FrameNr) ? " MISMATCH" : ""); if (lastFrameNr >= 0 && frameNr > lastFrameNr + 1) { From 5b789cecbe0d03f9c0fb959870ed9b0dfc02be9a Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Fri, 16 Oct 2026 16:22:20 -0400 Subject: [PATCH 8/8] Remove debug stuff, add findings --- docs/frame-info-investigation.md | 201 +++++++++++++++++++++++++++++++ kinetixApp/src/ADKinetix.cpp | 10 +- 2 files changed, 203 insertions(+), 8 deletions(-) create mode 100644 docs/frame-info-investigation.md diff --git a/docs/frame-info-investigation.md b/docs/frame-info-investigation.md new file mode 100644 index 0000000..2f9f93f --- /dev/null +++ b/docs/frame-info-investigation.md @@ -0,0 +1,201 @@ +# Investigation: `FrameNr` from `pl_exp_get_oldest_frame_ex` and false dropped frames + +## Summary + +In continuous acquisition with a `CIRC_NO_OVERWRITE` circular buffer, the `FRAME_INFO` +returned by `pl_exp_get_oldest_frame_ex` describes the **newest** frame that has arrived in +the buffer, not the oldest frame whose pointer is returned. The frame *data* pointer is +correct (oldest frame, in order); only the frame *info* is wrong. + +Because the driver originally detected dropped frames from gaps in that `FrameNr`, it reported +drops whenever it fell behind by more than one frame, even though no frames were lost. The +driver now takes `FrameNr` from the EOF callback instead, which fires once per frame, in order. + +## Symptoms + +1. **Externally triggered run:** 18010 hardware triggers were sent at 80 Hz and exactly 18010 + frames were received, yet `DroppedFrames_RBV` reported a non-zero count. +2. **Continuous run:** `DroppedFrames_RBV` rose steadily, by 1 at the same timestamp as each + `PendingFrames_RBV` value of 1: + + ``` + cam1:DroppedFrames_RBV 2026-10-16 15:42:21.437010 132 + cam1:DroppedFrames_RBV 2026-10-16 15:42:21.460507 133 + cam1:DroppedFrames_RBV 2026-10-16 15:42:21.484584 134 + ``` + + ``` + cam1:PendingFrames_RBV 2026-10-16 15:42:21.437002 1 + cam1:PendingFrames_RBV 2026-10-16 15:42:21.439218 0 + cam1:PendingFrames_RBV 2026-10-16 15:42:21.460500 1 + cam1:PendingFrames_RBV 2026-10-16 15:42:21.462113 0 + cam1:PendingFrames_RBV 2026-10-16 15:42:21.484577 1 + cam1:PendingFrames_RBV 2026-10-16 15:42:21.486177 0 + ``` + +## Original drop-detection code + +```cpp +if (PV_OK != pl_exp_get_oldest_frame_ex(this->cameraContext->hcam, &pFrame, pFrameInfo)) { + ... +} + +frameNr = pFrameInfo->FrameNr; +if (lastFrameNr >= 0 && pFrameInfo->FrameNr > lastFrameNr + 1) { + int missed = pFrameInfo->FrameNr - lastFrameNr - 1; + droppedFrames += missed; + setIntegerParam(KTX_DroppedFrames, droppedFrames); + WARN_ARGS("Dropped %d frame(s) between frame %d and frame %d", missed, + lastFrameNr, pFrameInfo->FrameNr); +} +lastFrameNr = pFrameInfo->FrameNr; +``` + +## What the PVCam documentation says + +From `pvcam.h`, `pl_exp_get_oldest_frame_ex`: + +> Additionally, this function updates the values in the variable pointed to by `frame_info` +> with the data collected at the time of frame reception by the device driver (e.g. frame +> counter value). + +That reads as if `FRAME_INFO` describes the returned (oldest) frame. The test below shows +otherwise. + +## Test setup + +The EOF callback was changed to queue each frame's `FrameNr` in arrival order, and the +acquisition thread pops one entry for each frame it reads: + +```cpp +static void PV_DECL newFrameCallback(FRAME_INFO *pFrameInfo, CameraContext *ctx) { + if (ctx == nullptr) return; + { + std::lock_guard lock(ctx->eofEvent.mutex); + ctx->pendingFrameNrs.push_back(pFrameInfo != nullptr ? pFrameInfo->FrameNr : -1); + } + ctx->eofEvent.cond.notify_all(); +} +``` + +```cpp +// in waitForEofEvent() +*frameNr = this->cameraContext->pendingFrameNrs.front(); +this->cameraContext->pendingFrameNrs.pop_front(); +*pendingFrames = (uns32) this->cameraContext->pendingFrameNrs.size(); +``` + +A temporary debug print was added after each successful `pl_exp_get_oldest_frame_ex` call. +It shows the callback `FrameNr` (`eof`), the `FrameNr` from `get_oldest_frame_ex` +(`oldest`), the number of frames still queued (`pending`), the `FRAME_INFO` timestamp, and +the index of the returned frame in the circular buffer (`slot`): + +```cpp +printf("FRAMENR_DEBUG eof=%d oldest=%d pending=%u ts=%lld slot=%ld%s\n", (int) frameNr, + (int) pFrameInfo->FrameNr, (unsigned) pendingFrames, + (long long) pFrameInfo->TimeStamp, + (long) (((uns8 *) pFrame - (uns8 *) this->frameBuffer) / frameBufferSize), + (frameNr != pFrameInfo->FrameNr) ? " MISMATCH" : ""); +``` + +## Results + +### Run 1: `FrameNr` comparison + +``` +FRAMENR_DEBUG eof=4306 oldest=4315 pending=9 ts=1622820 MISMATCH +FRAMENR_DEBUG eof=4307 oldest=4315 pending=8 ts=1622820 MISMATCH +FRAMENR_DEBUG eof=4308 oldest=4315 pending=7 ts=1622820 MISMATCH +FRAMENR_DEBUG eof=4309 oldest=4316 pending=7 ts=1622950 MISMATCH +FRAMENR_DEBUG eof=4310 oldest=4316 pending=6 ts=1622950 MISMATCH +FRAMENR_DEBUG eof=4311 oldest=4317 pending=6 ts=1623070 MISMATCH +FRAMENR_DEBUG eof=4312 oldest=4317 pending=5 ts=1623070 MISMATCH +FRAMENR_DEBUG eof=4313 oldest=4318 pending=5 ts=1623200 MISMATCH +FRAMENR_DEBUG eof=4314 oldest=4318 pending=4 ts=1623200 MISMATCH +FRAMENR_DEBUG eof=4315 oldest=4318 pending=3 ts=1623200 MISMATCH +FRAMENR_DEBUG eof=4316 oldest=4319 pending=3 ts=1623320 MISMATCH +FRAMENR_DEBUG eof=4317 oldest=4319 pending=2 ts=1623320 MISMATCH +FRAMENR_DEBUG eof=4318 oldest=4319 pending=1 ts=1623320 MISMATCH +FRAMENR_DEBUG eof=4319 oldest=4320 pending=1 ts=1623450 MISMATCH +``` + +On every line, `oldest = eof + pending`, which is the most recent frame that has arrived: + +| eof | pending | eof + pending | oldest | +|------|---------|---------------|--------| +| 4306 | 9 | 4315 | 4315 | +| 4307 | 8 | 4315 | 4315 | +| 4309 | 7 | 4316 | 4316 | +| 4313 | 5 | 4318 | 4318 | +| 4319 | 1 | 4320 | 4320 | + +The `FRAME_INFO` timestamp also repeats whenever `oldest` repeats, so the whole structure +belongs to the newest frame, not just the `FrameNr` field. The `eof` sequence is continuous +(4306 → 4319), so no frames were actually lost. + +### Run 2: is the frame data also from the newest frame? + +``` +FRAMENR_DEBUG eof=2566 oldest=2566 pending=0 ts=985700 slot=15 +FRAMENR_DEBUG eof=2567 oldest=2567 pending=0 ts=985830 slot=16 +FRAMENR_DEBUG eof=2568 oldest=2573 pending=5 ts=986580 slot=17 MISMATCH +FRAMENR_DEBUG eof=2569 oldest=2573 pending=4 ts=986580 slot=18 MISMATCH +FRAMENR_DEBUG eof=2570 oldest=2574 pending=4 ts=986710 slot=19 MISMATCH +FRAMENR_DEBUG eof=2571 oldest=2574 pending=3 ts=986710 slot=20 MISMATCH +FRAMENR_DEBUG eof=2572 oldest=2575 pending=3 ts=986830 slot=21 MISMATCH +FRAMENR_DEBUG eof=2573 oldest=2575 pending=2 ts=986830 slot=22 MISMATCH +FRAMENR_DEBUG eof=2574 oldest=2576 pending=2 ts=986950 slot=23 MISMATCH +FRAMENR_DEBUG eof=2575 oldest=2576 pending=1 ts=986950 slot=24 MISMATCH +FRAMENR_DEBUG eof=2576 oldest=2576 pending=0 ts=986950 slot=25 +FRAMENR_DEBUG eof=2577 oldest=2577 pending=0 ts=987080 slot=26 +FRAMENR_DEBUG eof=2578 oldest=2578 pending=0 ts=987200 slot=27 +FRAMENR_DEBUG eof=2579 oldest=2579 pending=0 ts=987330 slot=28 +FRAMENR_DEBUG eof=2580 oldest=2580 pending=0 ts=987450 slot=29 +FRAMENR_DEBUG eof=2581 oldest=2581 pending=0 ts=987580 slot=30 +FRAMENR_DEBUG eof=2582 oldest=2582 pending=0 ts=987710 slot=31 +FRAMENR_DEBUG eof=2583 oldest=2583 pending=0 ts=987830 slot=32 +``` + +- `slot` increases by exactly one on every line, including the lines where `oldest` repeats. + The returned pointer always moves to the next frame in the buffer. +- With a 50-frame buffer, `slot == (eof - 1) % 50` on every line (e.g. `eof=2568` → + `2567 % 50 = 17`). The returned data is the frame identified by the callback `FrameNr`. +- When `pending == 0`, `eof == oldest`, so the problem only appears when the driver is more + than one frame behind. + +## Conclusion + +| Item returned by `pl_exp_get_oldest_frame_ex` | Correct? | +|-----------------------------------------------|----------| +| Frame data pointer | Yes, oldest unretrieved frame, in order | +| `FRAME_INFO` (`FrameNr`, `TimeStamp`) | No, describes the newest frame in the buffer | + +Gaps in `FRAME_INFO.FrameNr` from `pl_exp_get_oldest_frame_ex` do not mean lost frames. With +`UniqueIdSource=Camera`, the old code would also have given consecutive arrays duplicate +`UniqueId` values whenever the driver fell behind. + +## Resulting driver behaviour + +- The EOF callback queues each frame's `FrameNr`, and the acquisition thread takes one per + frame read. That value is used for both drop detection and `NDArray::uniqueId`. +- `PendingFrames_RBV` is the length of that queue. +- `DroppedFrames_RBV` counts gaps in the callback `FrameNr` sequence. + +```cpp +// get_oldest_frame_ex fills FRAME_INFO for the newest frame, so use the EOF callback's FrameNr +if (lastFrameNr >= 0 && frameNr > lastFrameNr + 1) { + int missed = frameNr - lastFrameNr - 1; + droppedFrames += missed; + setIntegerParam(KTX_DroppedFrames, droppedFrames); + WARN_ARGS("Dropped %d frame(s) between frame %d and frame %d", missed, + lastFrameNr, frameNr); +} +lastFrameNr = frameNr; +``` + +## Related note on `CIRC_NO_OVERWRITE` + +Per `pvcam.h`, in `CIRC_NO_OVERWRITE` mode the oldest frame is still overwritten when the +buffer overruns; PVCam reports this as an error on the next `pl_exp_get_oldest_frame_ex` +call. Buffer overruns therefore show up as that error, not as `FrameNr` gaps, and they are +not currently counted in `DroppedFrames_RBV`. diff --git a/kinetixApp/src/ADKinetix.cpp b/kinetixApp/src/ADKinetix.cpp index e31b01a..a8ce133 100644 --- a/kinetixApp/src/ADKinetix.cpp +++ b/kinetixApp/src/ADKinetix.cpp @@ -901,7 +901,7 @@ void ADKinetix::acquisitionThread() { else pvcamExposureMode = EXT_TRIG_LEVEL | EXPOSE_OUT_FIRST_ROW; - // Locked frames can't be overwritten mid-copy; overflows show up as FrameNr gaps instead + // On overrun PVCam still overwrites the oldest frame, but get_oldest_frame_ex then returns an error const int16 circBuffMode = CIRC_NO_OVERWRITE; const bool continuousMode = (acquisitionMode != ADImageSingle); @@ -1006,13 +1006,7 @@ void ADKinetix::acquisitionThread() { continue; } - // TEMP DEBUG: compare EOF callback FrameNr with get_oldest_frame_ex FrameNr - printf("FRAMENR_DEBUG eof=%d oldest=%d pending=%u ts=%lld slot=%ld%s\n", (int) frameNr, - (int) pFrameInfo->FrameNr, (unsigned) pendingFrames, - (long long) pFrameInfo->TimeStamp, - (long) (((uns8 *) pFrame - (uns8 *) this->frameBuffer) / frameBufferSize), - (frameNr != pFrameInfo->FrameNr) ? " MISMATCH" : ""); - + // get_oldest_frame_ex fills FRAME_INFO for the newest frame, so use the EOF callback's FrameNr if (lastFrameNr >= 0 && frameNr > lastFrameNr + 1) { int missed = frameNr - lastFrameNr - 1; droppedFrames += missed;