Skip to content

Commit

Permalink
ENH: Add bad pixel correction
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
2 people authored and Sunderlandkyl committed Dec 18, 2020
1 parent 43d8166 commit 1aa79e5
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 7 deletions.
134 changes: 129 additions & 5 deletions src/PlusDataCollection/Andor/vtkPlusAndorVideoSource.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ vtkStandardNewMacro(vtkPlusAndorVideoSource);
// put these here so there is no public dependence on OpenCV
cv::Mat cvCameraIntrinsics;
cv::Mat cvDistanceCoefficients;
cv::Mat cvBadPixelImage;
cv::Mat cvFlatCorrection;
cv::Mat cvBiasDarkCorrection;
using CellIndices = std::vector<uint>;
std::map<int, CellIndices> cellsToCorrect;

// ----------------------------------------------------------------------------
void vtkPlusAndorVideoSource::PrintSelf(ostream& os, vtkIndent indent)
Expand All @@ -46,6 +49,7 @@ void vtkPlusAndorVideoSource::PrintSelf(ostream& os, vtkIndent indent)
os << indent << "UseFrameCorrections: " << UseFrameCorrections << std::endl;
os << indent << "FlatCorrection: " << flatCorrection << std::endl;
os << indent << "BiasDarkCorrection: " << biasDarkCorrection << std::endl;
os << indent << "BadPixelCorrection: " << badPixelCorrection << std::endl;
}

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -129,11 +133,13 @@ PlusStatus vtkPlusAndorVideoSource::ReadConfiguration(vtkXMLDataElement* rootCon
deviceConfig->GetVectorAttribute("OutputSpacing", 3, OutputSpacing);
deviceConfig->GetVectorAttribute("CameraIntrinsics", 9, cameraIntrinsics);
deviceConfig->GetVectorAttribute("DistanceCoefficients", 4, distanceCoefficients);
badPixelCorrection = deviceConfig->GetAttribute("BadPixelCorrection");
flatCorrection = deviceConfig->GetAttribute("FlatCorrection");
biasDarkCorrection = deviceConfig->GetAttribute("BiasDarkCorrection");

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

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

XML_WRITE_BOOL_ATTRIBUTE(UseFrameCorrections, deviceConfig);

Expand Down Expand Up @@ -539,11 +546,101 @@ void vtkPlusAndorVideoSource::AddFrameToDataSource(DataSourceArray& ds)
}
}

// ----------------------------------------------------------------------------
void vtkPlusAndorVideoSource::FindBadCells(int binning)
{
std::vector<cv::Point> badIndicesXY;
cv::findNonZero(cvBadPixelImage, badIndicesXY);

std::map<uint, int> badPixelCount;
for (int i = 0; i < badIndicesXY.size(); i++)
{
uint resolutionCellIndexX = badIndicesXY[i].x / binning;
uint resolutionCellIndexY = badIndicesXY[i].y / binning;
uint resolutionCellIndex = frameSize[1] * resolutionCellIndexY + resolutionCellIndexX;
badPixelCount[resolutionCellIndex] += 1;
}

std::vector<uint> resolutionCellsToCorrect;
for (auto const& bpc : badPixelCount)
{
if (binning * binning / bpc.second < 5) // tolerate up to 20% dead pixels
{
resolutionCellsToCorrect.push_back(bpc.first);
}
}

cellsToCorrect[binning] = resolutionCellsToCorrect;
}

// ----------------------------------------------------------------------------
void vtkPlusAndorVideoSource::ApplyFrameCorrections()
void vtkPlusAndorVideoSource::CorrectBadPixels(int binning, cv::Mat& cvIMG)
{
if (cellsToCorrect.find(binning) == cellsToCorrect.end()) // it needs to be calculated
{
FindBadCells(binning);
}
std::vector<uint> resolutionCellsToCorrect = cellsToCorrect[binning];
uint resolutionCellIndexX, resolutionCellIndexY;
std::vector<uint> valuesForMedian, correctedCells;
uint medianValue;
int startX, startY;
unsigned endX, endY;
int numCellsToCorrect = resolutionCellsToCorrect.size();
for (uint cell : resolutionCellsToCorrect)
{
resolutionCellIndexX = cell - frameSize[0] * (cell / frameSize[0]);
resolutionCellIndexY = cell / frameSize[0];
startX = resolutionCellIndexX - 1;
endX = resolutionCellIndexX + 1;
startY = resolutionCellIndexY - 1;
endY = resolutionCellIndexY + 1;
if (startX < 0) { startX = 0; }
if (startY < 0) { startY = 0; }
if (endX > frameSize[0]) { endX = frameSize[0]; }
if (endY > frameSize[0]) { endY = frameSize[0]; }

for (uint x = startX; x <= endX; x++)
{
for (uint y = startY; y <= endY; y++)
{
if (std::find(resolutionCellsToCorrect.begin(), resolutionCellsToCorrect.end(), frameSize[0] * y + x) != resolutionCellsToCorrect.end())
{
if (std::find(correctedCells.begin(), correctedCells.end(), frameSize[0] * y + x) != correctedCells.end())
{
valuesForMedian.push_back(cvIMG.at<ushort>(y, x));
}
}
else
{
valuesForMedian.push_back(cvIMG.at<ushort>(y, x));
}
}
}

sort(valuesForMedian.begin(), valuesForMedian.end());
if (valuesForMedian.size() % 2 == 0)
{
medianValue = (valuesForMedian[valuesForMedian.size() / 2 - 1] + valuesForMedian[valuesForMedian.size() / 2]) / 2;
}
else
{
medianValue = valuesForMedian[valuesForMedian.size() / 2];
}

cvIMG.at<ushort>(resolutionCellIndexY, resolutionCellIndexX) = medianValue;
correctedCells.push_back(cell);
valuesForMedian.clear();
}
}

// ----------------------------------------------------------------------------
void vtkPlusAndorVideoSource::ApplyFrameCorrections(int binning)
{
cv::Mat cvIMG(frameSize[0], frameSize[1], CV_16UC1, &rawFrame[0]); // uses rawFrame as buffer
CorrectBadPixels(binning, cvIMG);
LOG_INFO("Applied bad pixel correction");

cv::Mat floatImage;
cvIMG.convertTo(floatImage, CV_32FC1);
cv::Mat result;
Expand Down Expand Up @@ -571,7 +668,7 @@ PlusStatus vtkPlusAndorVideoSource::AcquireBLIFrame(int binning, int vsSpeed, in

if(this->UseFrameCorrections)
{
ApplyFrameCorrections();
ApplyFrameCorrections(binning);
AddFrameToDataSource(BLICorrected);
}

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

if(this->UseFrameCorrections)
{
ApplyFrameCorrections();
ApplyFrameCorrections(binning);
AddFrameToDataSource(GrayCorrected);
}

Expand All @@ -600,8 +697,35 @@ PlusStatus vtkPlusAndorVideoSource::AcquireCorrectionFrame(const std::string cor
{
AcquireFrame(exposureTime, shutter, binning, vsSpeed, hsSpeed);
++this->FrameNumber;
cv::Mat saveImage(frameSize[0], frameSize[1], CV_16UC1, &rawFrame[0]);
cv::imwrite(correctionFilePath, saveImage);

cv::Mat cvIMG(frameSize[0], frameSize[1], CV_16UC1, &rawFrame[0]); // uses rawFrame as buffer
if(this->UseFrameCorrections)
{
CorrectBadPixels(binning, cvIMG);
LOG_INFO("Applied bad pixel correction");
}

cv::imwrite(correctionFilePath, cvIMG);
return PLUS_SUCCESS;
}

//-----------------------------------------------------------------------------
PlusStatus vtkPlusAndorVideoSource::SetBadPixelCorrectionImage(const std::string badPixelFilePath)
{
try
{
cellsToCorrect.clear();
cvBadPixelImage = cv::imread(badPixelFilePath, cv::IMREAD_GRAYSCALE);
if (cvBadPixelImage.empty())
{
throw "Bad pixel image empty!";
}
}
catch (...)
{
LOG_ERROR("Could not load bad pixel image from file: " << badPixelFilePath);
return PLUS_FAIL;
}
return PLUS_SUCCESS;
}

Expand Down
18 changes: 16 additions & 2 deletions src/PlusDataCollection/Andor/vtkPlusAndorVideoSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

#include "vtkPlusDataCollectionExport.h"
#include "vtkPlusDevice.h"
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"

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

/*! Paths to additive and multiplicative bias+dark charge correction images. */
/*! Paths to correction images for dead pixels, additive and multiplicative bias. */
PlusStatus SetBadPixelCorrectionImage(const std::string badPixelFilePath);
std::string GetBadPixelCorrectionImage()
{
return badPixelCorrection;
}
PlusStatus SetBiasDarkCorrectionImage(const std::string biasDarkFilePath);
std::string GetBiasDarkCorrectionImage()
{
Expand Down Expand Up @@ -226,8 +233,14 @@ class vtkPlusDataCollectionExport vtkPlusAndorVideoSource: public vtkPlusDevice
/*! Data from the frameBuffer ivar is added to the provided data source. */
void AddFrameToDataSource(DataSourceArray& ds);

/*! Calculates which cells need bad-pixel correction for the given binning level. */
void FindBadCells(int binning);

/*! Applies correction for bad pixels. */
void CorrectBadPixels(int binning, cv::Mat& cvIMG);

/*! Applies bias correction for dark current, flat correction and lens distortion. */
void ApplyFrameCorrections();
void ApplyFrameCorrections(int binning);

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

Expand Down

0 comments on commit 1aa79e5

Please sign in to comment.