Skip to content

Commit 1aa79e5

Browse files
jrojasUNCdzenanz
authored andcommitted
ENH: Add bad pixel correction
CCD cameras usually have "bad pixels" these are either dead pixels, pixels with significantly lower intensities than neighbors, or pixels whose intensity does not scale with exposure time. Resolution cells (for example 4x4 pixels if bin=4) with more than 20% bad pixels are replaced by the median of surrounding cells. Co-authored-by: Dženan Zukić <[email protected]>
1 parent 43d8166 commit 1aa79e5

File tree

2 files changed

+145
-7
lines changed

2 files changed

+145
-7
lines changed

src/PlusDataCollection/Andor/vtkPlusAndorVideoSource.cxx

+129-5
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ vtkStandardNewMacro(vtkPlusAndorVideoSource);
1818
// put these here so there is no public dependence on OpenCV
1919
cv::Mat cvCameraIntrinsics;
2020
cv::Mat cvDistanceCoefficients;
21+
cv::Mat cvBadPixelImage;
2122
cv::Mat cvFlatCorrection;
2223
cv::Mat cvBiasDarkCorrection;
24+
using CellIndices = std::vector<uint>;
25+
std::map<int, CellIndices> cellsToCorrect;
2326

2427
// ----------------------------------------------------------------------------
2528
void vtkPlusAndorVideoSource::PrintSelf(ostream& os, vtkIndent indent)
@@ -46,6 +49,7 @@ void vtkPlusAndorVideoSource::PrintSelf(ostream& os, vtkIndent indent)
4649
os << indent << "UseFrameCorrections: " << UseFrameCorrections << std::endl;
4750
os << indent << "FlatCorrection: " << flatCorrection << std::endl;
4851
os << indent << "BiasDarkCorrection: " << biasDarkCorrection << std::endl;
52+
os << indent << "BadPixelCorrection: " << badPixelCorrection << std::endl;
4953
}
5054

5155
// ----------------------------------------------------------------------------
@@ -129,11 +133,13 @@ PlusStatus vtkPlusAndorVideoSource::ReadConfiguration(vtkXMLDataElement* rootCon
129133
deviceConfig->GetVectorAttribute("OutputSpacing", 3, OutputSpacing);
130134
deviceConfig->GetVectorAttribute("CameraIntrinsics", 9, cameraIntrinsics);
131135
deviceConfig->GetVectorAttribute("DistanceCoefficients", 4, distanceCoefficients);
136+
badPixelCorrection = deviceConfig->GetAttribute("BadPixelCorrection");
132137
flatCorrection = deviceConfig->GetAttribute("FlatCorrection");
133138
biasDarkCorrection = deviceConfig->GetAttribute("BiasDarkCorrection");
134139

135140
cvCameraIntrinsics = cv::Mat(3, 3, CV_64FC1, cameraIntrinsics);
136141
cvDistanceCoefficients = cv::Mat(1, 4, CV_64FC1, distanceCoefficients);
142+
this->SetBadPixelCorrectionImage(badPixelCorrection); // load the image
137143
this->SetFlatCorrectionImage(flatCorrection); // load and normalize if needed
138144
this->SetBiasDarkCorrectionImage(biasDarkCorrection); // load the image
139145

@@ -164,6 +170,7 @@ PlusStatus vtkPlusAndorVideoSource::WriteConfiguration(vtkXMLDataElement* rootCo
164170
deviceConfig->SetVectorAttribute("DistanceCoefficients", 4, distanceCoefficients);
165171
deviceConfig->SetAttribute("FlatCorrection", flatCorrection.c_str());
166172
deviceConfig->SetAttribute("BiasDarkCorrection", biasDarkCorrection.c_str());
173+
deviceConfig->SetAttribute("BadPixelCorrection", badPixelCorrection.c_str());
167174

168175
XML_WRITE_BOOL_ATTRIBUTE(UseFrameCorrections, deviceConfig);
169176

@@ -539,11 +546,101 @@ void vtkPlusAndorVideoSource::AddFrameToDataSource(DataSourceArray& ds)
539546
}
540547
}
541548

549+
// ----------------------------------------------------------------------------
550+
void vtkPlusAndorVideoSource::FindBadCells(int binning)
551+
{
552+
std::vector<cv::Point> badIndicesXY;
553+
cv::findNonZero(cvBadPixelImage, badIndicesXY);
554+
555+
std::map<uint, int> badPixelCount;
556+
for (int i = 0; i < badIndicesXY.size(); i++)
557+
{
558+
uint resolutionCellIndexX = badIndicesXY[i].x / binning;
559+
uint resolutionCellIndexY = badIndicesXY[i].y / binning;
560+
uint resolutionCellIndex = frameSize[1] * resolutionCellIndexY + resolutionCellIndexX;
561+
badPixelCount[resolutionCellIndex] += 1;
562+
}
563+
564+
std::vector<uint> resolutionCellsToCorrect;
565+
for (auto const& bpc : badPixelCount)
566+
{
567+
if (binning * binning / bpc.second < 5) // tolerate up to 20% dead pixels
568+
{
569+
resolutionCellsToCorrect.push_back(bpc.first);
570+
}
571+
}
572+
573+
cellsToCorrect[binning] = resolutionCellsToCorrect;
574+
}
542575

543576
// ----------------------------------------------------------------------------
544-
void vtkPlusAndorVideoSource::ApplyFrameCorrections()
577+
void vtkPlusAndorVideoSource::CorrectBadPixels(int binning, cv::Mat& cvIMG)
578+
{
579+
if (cellsToCorrect.find(binning) == cellsToCorrect.end()) // it needs to be calculated
580+
{
581+
FindBadCells(binning);
582+
}
583+
std::vector<uint> resolutionCellsToCorrect = cellsToCorrect[binning];
584+
uint resolutionCellIndexX, resolutionCellIndexY;
585+
std::vector<uint> valuesForMedian, correctedCells;
586+
uint medianValue;
587+
int startX, startY;
588+
unsigned endX, endY;
589+
int numCellsToCorrect = resolutionCellsToCorrect.size();
590+
for (uint cell : resolutionCellsToCorrect)
591+
{
592+
resolutionCellIndexX = cell - frameSize[0] * (cell / frameSize[0]);
593+
resolutionCellIndexY = cell / frameSize[0];
594+
startX = resolutionCellIndexX - 1;
595+
endX = resolutionCellIndexX + 1;
596+
startY = resolutionCellIndexY - 1;
597+
endY = resolutionCellIndexY + 1;
598+
if (startX < 0) { startX = 0; }
599+
if (startY < 0) { startY = 0; }
600+
if (endX > frameSize[0]) { endX = frameSize[0]; }
601+
if (endY > frameSize[0]) { endY = frameSize[0]; }
602+
603+
for (uint x = startX; x <= endX; x++)
604+
{
605+
for (uint y = startY; y <= endY; y++)
606+
{
607+
if (std::find(resolutionCellsToCorrect.begin(), resolutionCellsToCorrect.end(), frameSize[0] * y + x) != resolutionCellsToCorrect.end())
608+
{
609+
if (std::find(correctedCells.begin(), correctedCells.end(), frameSize[0] * y + x) != correctedCells.end())
610+
{
611+
valuesForMedian.push_back(cvIMG.at<ushort>(y, x));
612+
}
613+
}
614+
else
615+
{
616+
valuesForMedian.push_back(cvIMG.at<ushort>(y, x));
617+
}
618+
}
619+
}
620+
621+
sort(valuesForMedian.begin(), valuesForMedian.end());
622+
if (valuesForMedian.size() % 2 == 0)
623+
{
624+
medianValue = (valuesForMedian[valuesForMedian.size() / 2 - 1] + valuesForMedian[valuesForMedian.size() / 2]) / 2;
625+
}
626+
else
627+
{
628+
medianValue = valuesForMedian[valuesForMedian.size() / 2];
629+
}
630+
631+
cvIMG.at<ushort>(resolutionCellIndexY, resolutionCellIndexX) = medianValue;
632+
correctedCells.push_back(cell);
633+
valuesForMedian.clear();
634+
}
635+
}
636+
637+
// ----------------------------------------------------------------------------
638+
void vtkPlusAndorVideoSource::ApplyFrameCorrections(int binning)
545639
{
546640
cv::Mat cvIMG(frameSize[0], frameSize[1], CV_16UC1, &rawFrame[0]); // uses rawFrame as buffer
641+
CorrectBadPixels(binning, cvIMG);
642+
LOG_INFO("Applied bad pixel correction");
643+
547644
cv::Mat floatImage;
548645
cvIMG.convertTo(floatImage, CV_32FC1);
549646
cv::Mat result;
@@ -571,7 +668,7 @@ PlusStatus vtkPlusAndorVideoSource::AcquireBLIFrame(int binning, int vsSpeed, in
571668

572669
if(this->UseFrameCorrections)
573670
{
574-
ApplyFrameCorrections();
671+
ApplyFrameCorrections(binning);
575672
AddFrameToDataSource(BLICorrected);
576673
}
577674

@@ -588,7 +685,7 @@ PlusStatus vtkPlusAndorVideoSource::AcquireGrayscaleFrame(int binning, int vsSpe
588685

589686
if(this->UseFrameCorrections)
590687
{
591-
ApplyFrameCorrections();
688+
ApplyFrameCorrections(binning);
592689
AddFrameToDataSource(GrayCorrected);
593690
}
594691

@@ -600,8 +697,35 @@ PlusStatus vtkPlusAndorVideoSource::AcquireCorrectionFrame(const std::string cor
600697
{
601698
AcquireFrame(exposureTime, shutter, binning, vsSpeed, hsSpeed);
602699
++this->FrameNumber;
603-
cv::Mat saveImage(frameSize[0], frameSize[1], CV_16UC1, &rawFrame[0]);
604-
cv::imwrite(correctionFilePath, saveImage);
700+
701+
cv::Mat cvIMG(frameSize[0], frameSize[1], CV_16UC1, &rawFrame[0]); // uses rawFrame as buffer
702+
if(this->UseFrameCorrections)
703+
{
704+
CorrectBadPixels(binning, cvIMG);
705+
LOG_INFO("Applied bad pixel correction");
706+
}
707+
708+
cv::imwrite(correctionFilePath, cvIMG);
709+
return PLUS_SUCCESS;
710+
}
711+
712+
//-----------------------------------------------------------------------------
713+
PlusStatus vtkPlusAndorVideoSource::SetBadPixelCorrectionImage(const std::string badPixelFilePath)
714+
{
715+
try
716+
{
717+
cellsToCorrect.clear();
718+
cvBadPixelImage = cv::imread(badPixelFilePath, cv::IMREAD_GRAYSCALE);
719+
if (cvBadPixelImage.empty())
720+
{
721+
throw "Bad pixel image empty!";
722+
}
723+
}
724+
catch (...)
725+
{
726+
LOG_ERROR("Could not load bad pixel image from file: " << badPixelFilePath);
727+
return PLUS_FAIL;
728+
}
605729
return PLUS_SUCCESS;
606730
}
607731

src/PlusDataCollection/Andor/vtkPlusAndorVideoSource.h

+16-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
#include "vtkPlusDataCollectionExport.h"
1111
#include "vtkPlusDevice.h"
12+
#include "opencv2/imgproc.hpp"
13+
#include "opencv2/imgcodecs.hpp"
1214

1315
/*!
1416
\class vtkPlusAndorVideoSource
@@ -140,7 +142,12 @@ class vtkPlusDataCollectionExport vtkPlusAndorVideoSource: public vtkPlusDevice
140142
/*! Get the current temperature of the camera in degrees celsius. */
141143
float GetCurrentTemperature();
142144

143-
/*! Paths to additive and multiplicative bias+dark charge correction images. */
145+
/*! Paths to correction images for dead pixels, additive and multiplicative bias. */
146+
PlusStatus SetBadPixelCorrectionImage(const std::string badPixelFilePath);
147+
std::string GetBadPixelCorrectionImage()
148+
{
149+
return badPixelCorrection;
150+
}
144151
PlusStatus SetBiasDarkCorrectionImage(const std::string biasDarkFilePath);
145152
std::string GetBiasDarkCorrectionImage()
146153
{
@@ -226,8 +233,14 @@ class vtkPlusDataCollectionExport vtkPlusAndorVideoSource: public vtkPlusDevice
226233
/*! Data from the frameBuffer ivar is added to the provided data source. */
227234
void AddFrameToDataSource(DataSourceArray& ds);
228235

236+
/*! Calculates which cells need bad-pixel correction for the given binning level. */
237+
void FindBadCells(int binning);
238+
239+
/*! Applies correction for bad pixels. */
240+
void CorrectBadPixels(int binning, cv::Mat& cvIMG);
241+
229242
/*! Applies bias correction for dark current, flat correction and lens distortion. */
230-
void ApplyFrameCorrections();
243+
void ApplyFrameCorrections(int binning);
231244

232245
/*! Flag whether to call ApplyFrameCorrections on the raw acquired frame on acquisition
233246
or to skip frame corrections.
@@ -295,6 +308,7 @@ class vtkPlusDataCollectionExport vtkPlusAndorVideoSource: public vtkPlusDevice
295308
// {0}{0}{1}
296309
double cameraIntrinsics[9] = { 0 };
297310
double distanceCoefficients[4] = { 0 }; // k_1, k_2, p_1, p_2
311+
std::string badPixelCorrection; //filepath to bad pixel image
298312
std::string flatCorrection; // filepath to master flat image
299313
std::string biasDarkCorrection; // filepath to master bias+dark image
300314

0 commit comments

Comments
 (0)