Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci-scripts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ jobs:
WITH_BITSHUFFLE=YES
BITSHUFFLE_EXTERNAL=NO
HDF5_STATIC_BUILD=\$(STATIC_BUILD)
WITH_JSON=YES
EOF

- name: Build main module
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ test_dim2.h5
test1_001.h5
testing_0.5
valid_points.xml
test_badpixel_*.json
*.info
2 changes: 1 addition & 1 deletion ADApp/Db/NDBadPixel.template
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ record(waveform, "$(P)$(R)FileName")
field(PINI, "YES")
field(INP, "@asyn($(PORT),$(ADDR=0),$(TIMEOUT=1))BAD_PIXEL_FILE_NAME")
field(FTVL, "CHAR")
field(NELM, "256")
field(NELM, "$(JSONSIZE=256)")
info(Q:form, "String")
}
111 changes: 69 additions & 42 deletions ADApp/pluginSrc/NDPluginBadPixel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
#include <fstream>
#include <iomanip>
#include <algorithm>
#include <json.hpp>
using nlohmann::json;


#include <iocsh.h>

Expand Down Expand Up @@ -89,6 +88,7 @@ epicsInt64 NDPluginBadPixel::computePixelOffset(pixelCoordinate coord, badPixDim
}
return offset;
}

template <typename epicsType>
void NDPluginBadPixel::fixBadPixelsT(NDArray *pArray, badPixelList_t &badPixels, NDArrayInfo_t *pArrayInfo)
{
Expand Down Expand Up @@ -219,7 +219,6 @@ void NDPluginBadPixel::processCallbacks(NDArray *pArray)
* structures don't need to be protected.
*/
NDArray *pArrayOut = NULL;
static const char* functionName = "processCallbacks";

/* Call the base class method */
NDPluginDriver::beginProcessCallbacks(pArray);
Expand All @@ -237,7 +236,7 @@ void NDPluginBadPixel::processCallbacks(NDArray *pArray)
if (NULL == pArrayOut) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s:%s Processing aborted; cannot allocate an NDArray for storage of temporary data.\n",
driverName, functionName);
driverName, __func__);
goto doCallbacks;
}
fixBadPixels(pArrayOut, badPixels, &arrayInfo);
Expand All @@ -253,47 +252,76 @@ void NDPluginBadPixel::processCallbacks(NDArray *pArray)
callParamCallbacks();
}

asynStatus NDPluginBadPixel::readBadPixelFile(const char *fileName)
badPixelList_t NDPluginBadPixel::parseBadPixelList(json j){
badPixelList_t badPixels;

// Support both "Bad pixels" and "bad_pixels" as top-level key
auto topIt = findJsonKey(j, {"Bad pixels", "BadPixels", "bad_pixels"});
if (topIt == j.end()) return badPixels;
auto badPixelsJSON = *topIt;

pixelCoordinate coord;
for (auto pixel : badPixelsJSON) {
// Support "Pixel" or "pixel"
auto pixelIt = findJsonKey(pixel, {"Pixel", "pixel"});
if (pixelIt == pixel.end()) continue;
coord.x = (*pixelIt)[0];
coord.y = (*pixelIt)[1];
badPixel bp(coord);

// Support "Median" or "median"
auto medianIt = findJsonKey(pixel, {"Median", "median"});
if (medianIt != pixel.end()) {
bp.mode = badPixelModeMedian;
bp.medianCoordinate.x = (*medianIt)[0];
bp.medianCoordinate.y = (*medianIt)[1];
}

// Support "Set" or "set"
auto setIt = findJsonKey(pixel, {"Set", "set"});
if (setIt != pixel.end()) {
bp.mode = badPixelModeSet;
bp.setValue = *setIt;
}

// Support "Replace" or "replace"
auto replaceIt = findJsonKey(pixel, {"Replace", "replace"});
if (replaceIt != pixel.end()) {
bp.mode = badPixelModeReplace;
bp.replaceCoordinate.x = (*replaceIt)[0];
bp.replaceCoordinate.y = (*replaceIt)[1];
}
badPixels.insert(bp);
}
return badPixels;
}

asynStatus NDPluginBadPixel::handleBadPixelFileUpdate(const char* fileName)
{
json j;
static const char *functionName = "readBadPixelFile";
std::ifstream file(fileName);
try {
std::ifstream file(fileName);
file >> j;
auto badPixels = j["Bad pixels"];
badPixelList.clear();
pixelCoordinate coord;
for (auto pixel : badPixels) {
coord.x = pixel["Pixel"][0];
coord.y = pixel["Pixel"][1];
badPixel bp(coord);
if (pixel.find("Median") != pixel.end()) {
bp.mode = badPixelModeMedian;
bp.medianCoordinate.x = pixel["Median"][0];
bp.medianCoordinate.y = pixel["Median"][1];
}
if (pixel.find("Set") != pixel.end()) {
bp.mode = badPixelModeSet;
bp.setValue = pixel["Set"];
}
if (pixel.find("Replace") != pixel.end()) {
bp.mode = badPixelModeReplace;
bp.replaceCoordinate.x = pixel["Replace"][0];
bp.replaceCoordinate.y = pixel["Replace"][1];
}
badPixelList.insert(bp);
if (!file.is_open()) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_WARNING,
"%s:%s: Bad pixel file not found. Checking if the value is valid JSON: %s\n",
driverName, __func__, fileName);
j = json::parse(fileName);
} else {
file >> j;
}
}
catch (const json::parse_error& e) {
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
"%s::%s JSON error parsing bad pixel file: %s\n", driverName, functionName, e.what());
} catch (json::parse_error& e) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s:%s: error parsing bad pixel information file %s: %s\n",
driverName, __func__, fileName, e.what());
return asynError;
}
catch (std::exception& e) {
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
"%s::%s other error parsing bad pixel file: %s\n", driverName, functionName, e.what());
} catch (std::exception& e) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s:%s: error parsing bad pixel information file %s: %s\n",
driverName, __func__, fileName, e.what());
return asynError;
}
this->badPixelList.clear();
this->badPixelList = this->parseBadPixelList(j);
return asynSuccess;
}

Expand All @@ -309,7 +337,6 @@ asynStatus NDPluginBadPixel::writeOctet(asynUser *pasynUser, const char *value,
int addr=0;
int function = pasynUser->reason;
asynStatus status = asynSuccess;
const char *functionName = "writeOctet";

status = getAddress(pasynUser, &addr); if (status != asynSuccess) return(status);
// Set the parameter in the parameter library.
Expand All @@ -318,7 +345,7 @@ asynStatus NDPluginBadPixel::writeOctet(asynUser *pasynUser, const char *value,

if (function == NDPluginBadPixelFileName) {
if ((nChars > 0) && (value[0] != 0)) {
status = this->readBadPixelFile(value);
status = handleBadPixelFileUpdate(value);
}
}

Expand All @@ -333,11 +360,11 @@ asynStatus NDPluginBadPixel::writeOctet(asynUser *pasynUser, const char *value,
if (status){
epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize,
"%s:%s: status=%d, function=%d, value=%s",
driverName, functionName, status, function, value);
driverName, __func__, status, function, value);
} else {
asynPrint(pasynUser, ASYN_TRACEIO_DRIVER,
"%s:%s: function=%d, value=%s\n",
driverName, functionName, function, value);
driverName, __func__, function, value);
}
*nActual = nChars;
return status;
Expand Down
30 changes: 22 additions & 8 deletions ADApp/pluginSrc/NDPluginBadPixel.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,23 @@
#define NDPluginProcess_H

#include <vector>

#include <set>
#include <initializer_list>
#include <json.hpp>
using nlohmann::json;
#include "NDPluginDriver.h"

/** Search for a key in a JSON object, trying multiple alternative names.
* Returns an iterator to the first matching key, or obj.end() if none match. */
inline json::const_iterator findJsonKey(const json& obj, std::initializer_list<const char*> keys)
{
for (auto key : keys) {
auto it = obj.find(key);
if (it != obj.end()) return it;
}
return obj.end();
}

// We use epicsInt64 rather than size_t in these structs because we need to do signed arithmetic on these values
typedef struct {
epicsInt64 x;
Expand Down Expand Up @@ -47,6 +61,7 @@ typedef struct {
} badPixDimInfo_t;

typedef std::set<badPixel> badPixelList_t;

/* Bad pixel file*/
#define NDPluginBadPixelFileNameString "BAD_PIXEL_FILE_NAME" /* (asynOctet, r/w) Name of the bad pixel file */

Expand All @@ -60,18 +75,17 @@ class NDPLUGIN_API NDPluginBadPixel : public NDPluginDriver {
void processCallbacks(NDArray *pArray);
asynStatus writeOctet(asynUser *pasynUser, const char *value, size_t nChars, size_t *nActual);
void report(FILE *fp, int details);
template <typename epicsType> void fixBadPixelsT(NDArray *pArray, badPixelList_t &badPixels, NDArrayInfo_t *pArrayInfo);
int fixBadPixels(NDArray *pArray, badPixelList_t &badPixels, NDArrayInfo_t *pArrayInfo);
badPixelList_t parseBadPixelList(json j);
asynStatus handleBadPixelFileUpdate(const char* fileName);
epicsInt64 computePixelOffset(pixelCoordinate coord, badPixDimInfo_t& dimInfo, NDArrayInfo_t *pArrayInfo);
badPixelList_t badPixelList;

protected:
/* Background array subtraction */
int NDPluginBadPixelFileName;
#define FIRST_NDPLUGIN_BAD_PIXEL_PARAM NDPluginBadPixelFileName

private:
template <typename epicsType> void fixBadPixelsT(NDArray *pArray, badPixelList_t &badPixels, NDArrayInfo_t *pArrayInfo);
int fixBadPixels(NDArray *pArray, badPixelList_t &badPixels, NDArrayInfo_t *pArrayInfo);
asynStatus readBadPixelFile(const char* fileName);
epicsInt64 computePixelOffset(pixelCoordinate coord, badPixDimInfo_t& dimInfo, NDArrayInfo_t *pArrayInfo);
badPixelList_t badPixelList;
};

#endif
45 changes: 45 additions & 0 deletions ADApp/pluginTests/BadPixelPluginWrapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* BadPixelPluginWrapper.cpp
*
* Created on: 20 Apr 2026
* Author: Jakub Wlodek
*/

#include <json.hpp>
using nlohmann::json;

#include "BadPixelPluginWrapper.h"

BadPixelPluginWrapper::BadPixelPluginWrapper(const std::string& port, const std::string& detectorPort)
: NDPluginBadPixel(port.c_str(), 50, 0, detectorPort.c_str(), 0, 0, 0, 0, 0, 1),
AsynPortClientContainer(port)
{
}

BadPixelPluginWrapper::BadPixelPluginWrapper(const std::string& port,
int queueSize,
int blocking,
const std::string& detectorPort,
int address,
size_t maxMemory,
int priority,
int stackSize,
int maxThreads)
: NDPluginBadPixel(port.c_str(), queueSize, blocking,
detectorPort.c_str(), address,
0, maxMemory, priority, stackSize, maxThreads),
AsynPortClientContainer(port)
{
}

badPixelList_t BadPixelPluginWrapper::testParseBadPixelList(const std::string& jsonStr)
{
json j = json::parse(jsonStr);
return parseBadPixelList(j);
}

BadPixelPluginWrapper::~BadPixelPluginWrapper ()
{
cleanup();
}

35 changes: 35 additions & 0 deletions ADApp/pluginTests/BadPixelPluginWrapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* BadPixelPluginWrapper.h
*
* Created on: 06 Jul 2026
* Author: Jakub Wlodek
*/

#ifndef ADAPP_PLUGINTESTS_BADPIXELPLUGINWRAPPER_H_
#define ADAPP_PLUGINTESTS_BADPIXELPLUGINWRAPPER_H_

#include <NDPluginBadPixel.h>
#include "AsynPortClientContainer.h"

class BadPixelPluginWrapper : public NDPluginBadPixel, public AsynPortClientContainer
{
public:
BadPixelPluginWrapper(const std::string& port, const std::string& detectorPort);
BadPixelPluginWrapper(const std::string& port,
int queueSize,
int blocking,
const std::string& detectorPort,
int address,
size_t maxMemory,
int priority,
int stackSize,
int maxThreads);
virtual ~BadPixelPluginWrapper ();

// Expose private members for testing
badPixelList_t& getBadPixelList() { return badPixelList; }
badPixelList_t testParseBadPixelList(const std::string& jsonStr);
asynStatus testHandleBadPixelFileUpdate(const char* fileName) { return handleBadPixelFileUpdate(fileName); }
};

#endif /* ADAPP_PLUGINTESTS_BADPIXELPLUGINWRAPPER_H_ */
6 changes: 6 additions & 0 deletions ADApp/pluginTests/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ ifeq ($(WITH_BOOST),YES)
ADTestUtility_SRCS += CodecPluginWrapper.cpp
ADTestUtility_SRCS += AttributePluginWrapper.cpp
ADTestUtility_SRCS += ColorConvertPluginWrapper.cpp
ifeq ($(WITH_JSON), YES)
ADTestUtility_SRCS += BadPixelPluginWrapper.cpp
endif

PROD_IOC_Linux += plugin-test
PROD_IOC_Darwin += plugin-test
Expand All @@ -62,6 +65,9 @@ ifeq ($(WITH_BOOST),YES)
plugin-test_SRCS += test_NDPluginAttrPlot.cpp
plugin-test_SRCS += test_NDPluginROI.cpp
plugin-test_SRCS += test_NDPluginOverlay.cpp
ifeq ($(WITH_JSON), YES)
plugin-test_SRCS += test_NDPluginBadPixel.cpp
endif
plugin-test_SRCS += test_NDArrayPool.cpp

# Add tests for new plugins like this:
Expand Down
Loading
Loading