Files
tenmon/rawimage.cpp
T
2023-06-17 21:47:06 +02:00

553 lines
14 KiB
C++

#include "rawimage.h"
#include <QDebug>
#include <cstring>
int THUMB_SIZE = 128;
int THUMB_SIZE_BORDER = 138;
int THUMB_SIZE_BORDER_Y = 158;
double SATURATION = 0.95;
size_t RawImage::typeSize(RawImage::DataType type)
{
switch(type)
{
case RawImage::UINT8:
return 1;
case RawImage::UINT16:
return 2;
case RawImage::UINT32:
case RawImage::FLOAT32:
return 4;
case RawImage::FLOAT64:
return 8;
default: return 1;
}
}
void RawImage::allocate(uint32_t w, uint32_t h, uint32_t ch, DataType type)
{
m_width = w;
m_height = h;
m_channels = ch;
m_ch = ch == 3 ? 4 : ch;
m_origType = m_type = type;
m_pixels = std::make_unique<PixelType[]>(m_width * m_height * m_ch * typeSize(type));
}
RawImage::RawImage()
{
}
RawImage::RawImage(uint32_t w, uint32_t h, uint32_t ch, DataType type)
{
allocate(w, h, ch, type);
}
RawImage::RawImage(const RawImage &d)
{
allocate(d.m_width, d.m_height, d.m_channels, d.m_type);
std::memcpy(m_pixels.get(), d.m_pixels.get(), m_width * m_height * m_ch * typeSize(m_type));
m_stats = d.m_stats;
m_saturated = d.m_saturated;
}
RawImage::RawImage(RawImage &&d)
{
m_pixels = std::move(d.m_pixels);
m_original = std::move(d.m_original);
m_width = d.m_width;
m_height = d.m_height;
m_channels = d.m_channels;
m_ch = d.m_ch;
m_type = d.m_type;
m_origType = d.m_origType;
m_stats = d.m_stats;
m_thumbAspect = d.m_thumbAspect;
m_saturated = d.m_saturated;
}
RawImage::RawImage(const QImage &img)
{
qDebug() << img;
if(img.format() == QImage::Format_RGBX8888)
{
allocate(img.width(), img.height(), 3, UINT8);
for(int i=0; i<img.height(); i++)
std::memcpy(data(i), img.scanLine(i), img.width()*4);
}
else if(img.format() == QImage::Format_RGBA8888)
{
allocate(img.width(), img.height(), 4, UINT8);
for(int i=0; i<img.height(); i++)
std::memcpy(data(i), img.scanLine(i), img.width()*4);
}
else if(img.format() == QImage::Format_RGBX64)
{
allocate(img.width(), img.height(), 3, UINT16);
for(int i=0; i<img.height(); i++)
std::memcpy(data(i), img.scanLine(i), img.width()*8);
}
else if(img.format() == QImage::Format_RGBA64)
{
allocate(img.width(), img.height(), 4, UINT16);
for(int i=0; i<img.height(); i++)
std::memcpy(data(i), img.scanLine(i), img.width()*8);
}
else if(img.format() == QImage::Format_Grayscale8)
{
allocate(img.width(), img.height(), 1, UINT8);
for(int i=0; i<img.height(); i++)
std::memcpy(data(i), img.scanLine(i), img.width());
}
else if(img.format() == QImage::Format_Grayscale16)
{
allocate(img.width(), img.height(), 1, UINT16);
for(int i=0; i<img.height(); i++)
std::memcpy(data(i), img.scanLine(i), img.width()*2);
}
else
{
QImage tmp = img.convertToFormat(QImage::Format_RGBA8888);
allocate(img.width(), img.height(), 4, UINT8);
for(int i=0; i<tmp.height(); i++)
std::memcpy(data(i), tmp.scanLine(i), tmp.width()*4);
}
m_stats.m_stats = false;
}
bool RawImage::imageStats(double *mean, double *stdDev, double *median, double *min, double *max, double *mad, uint32_t *saturated)
{
if(!m_stats.m_stats)calcStats();
if(mean)*mean = m_stats.m_mean[0];
if(stdDev)*stdDev = m_stats.m_stdDev[0];
if(median)*median = m_stats.m_median[0];
if(min)*min = m_stats.m_min[0];
if(max)*max = m_stats.m_max[0];
if(mad)*mad = m_stats.m_mad[0];
if(saturated)*saturated = m_saturated;
return true;
}
void RawImage::calcStats()
{
if(m_stats.m_stats)return;
m_stats.m_stats = true;
/*cv::Scalar meanS, stdDevS;
cv::meanStdDev(m_img, meanS, stdDevS);
cv::minMaxIdx(m_img, &m_min, &m_max);
cv::Mat img;
if(m_img.channels() == 1)img = m_img;
else if (m_img.channels() == 3)cv::cvtColor(m_img, img, cv::COLOR_BGR2GRAY);
else if (m_img.channels() == 4)cv::cvtColor(m_img, img, cv::COLOR_BGRA2GRAY);
int histSize = 256;
if(img.type() == CV_16U || img.type() == CV_32F)histSize = 65536;
float range[] = {0, (float)histSize};
if(img.type() == CV_32F)range[1] = 1.0f;
const float *ranges[] = {range};
cv::Mat hist;
cv::calcHist(&img, 1, nullptr, cv::Mat(), hist, 1, &histSize, ranges);
m_mean = meanS[0];
m_stdDev = stdDevS[0];
size_t halfImageSize = size()/2;
size_t medianSum = 0;
for(int i=0; i < histSize; i++)
{
medianSum += hist.at<float>(0, i);
if(medianSum >= halfImageSize)
{
m_median = i;
break;
}
}
if(img.type() == CV_32F)m_median /= histSize;
int threshold = SATURATION * histSize;
m_saturated = 0;
for(int i = histSize-1; i >= threshold; i--)
m_saturated += hist.at<float>(0, i);
cv::Mat absDev;
img.convertTo(absDev, CV_32F, 1, -m_median);
absDev = cv::abs(absDev);
cv::Mat madHist;
medianSum = 0;
cv::calcHist(&absDev, 1, nullptr, cv::Mat(), madHist, 1, &histSize, ranges);
for(int i=0; i < histSize; i++)
{
medianSum += madHist.at<float>(0, i);
if(medianSum >= halfImageSize)
{
m_mad = i;
break;
}
}
if(img.type() == CV_32F)m_mad /= histSize;*/
}
void RawImage::rect(int &x, int &y, int w, int h, std::vector<double> &r) const
{
/*r.resize(w*h);
x -= w/2;
y -= h/2;
if(x<0)x = 0;
if(y<0)y = 0;
if(x+w >= m_img.cols)x = m_img.cols-w;
if(y+h >= m_img.rows)y = m_img.rows-h;
cv::Mat roiImg(m_img, cv::Rect(x, y, w, h));
cv::Mat doubleMat;
roiImg.convertTo(doubleMat, CV_64F);
r = std::vector<double>(doubleMat.begin<double>(), doubleMat.end<double>());*/
}
int RawImage::findPeaks(double background, double distance, std::vector<Peak> &peaks) const
{
/*std::vector<std::vector<cv::Point>> contours;
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(distance, distance));
cv::Mat img, mask, dilate, locMax, result;
if(m_img.channels() == 1)img = m_img;
else cv::cvtColor(m_img, img, cv::COLOR_RGB2GRAY);
cv::dilate(img, dilate, kernel);
cv::compare(img, dilate, locMax, cv::CMP_GE);
cv::compare(img, cv::Scalar(background), mask, cv::CMP_GT);
cv::bitwise_and(locMax, mask, result);
cv::findContours(result, contours, cv::noArray(), cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
peaks.reserve(contours.size());
for(auto contour : contours)
{
peaks.push_back(Peak(1, contour[0].x, contour[0].y));
}
return peaks.size();*/
}
uint32_t RawImage::width() const
{
return m_width;
}
uint32_t RawImage::height() const
{
return m_height;
}
uint32_t RawImage::channels() const
{
return m_channels;
}
uint32_t RawImage::size() const
{
return width()*height();
}
RawImage::DataType RawImage::type() const
{
return m_type;
}
uint32_t RawImage::norm() const
{
switch(m_type)
{
case UINT8:
return UINT8_MAX;
case UINT16:
return UINT16_MAX;
case UINT32:
return UINT32_MAX;
default:
return 1;
}
}
void* RawImage::data()
{
return m_pixels.get();
}
const void *RawImage::data() const
{
return m_pixels.get();
}
void *RawImage::data(uint32_t row, uint32_t col)
{
return m_pixels.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_type);
}
const void *RawImage::data(uint32_t row, uint32_t col) const
{
return m_pixels.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_type);
}
void *RawImage::origData(uint32_t row, uint32_t col) const
{
if(m_original)
return m_original.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_origType);
else
return m_pixels.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_type);
}
void RawImage::convertToThumbnail()
{
m_thumbAspect = (float)width() / height();
std::unique_ptr<PixelType[]> outptr = std::make_unique<PixelType[]>(THUMB_SIZE * THUMB_SIZE * 4 * sizeof(uint16_t));
uint16_t *out = reinterpret_cast<uint16_t*>(outptr.get());
auto loop = [&](uint16_t *out, auto *in, auto scale)
{
for(int i=0; i<THUMB_SIZE; i++)
{
for(int o=0; o<THUMB_SIZE; o++)
{
int idx = (i*THUMB_SIZE + o)*4;
int idx2 = ((i * m_height / THUMB_SIZE * m_width) + (o * m_width / THUMB_SIZE)) * m_ch;
if(m_channels == 1)
{
out[idx] = out[idx + 1] = out[idx + 2] = in[idx2] * scale;
}
else
{
out[idx] = in[idx2] * scale;;
out[idx + 1] = in[idx2 + 1] * scale;;
out[idx + 2] = in[idx2 + 2] * scale;;
}
out[idx + 3] = UINT16_MAX;
}
}
};
switch(m_type)
{
case UINT8:
loop(out, reinterpret_cast<uint8_t*>(m_pixels.get()), 256);
break;
case UINT16:
loop(out, reinterpret_cast<uint16_t*>(m_pixels.get()), 1);
break;
case UINT32:
loop(out, reinterpret_cast<uint32_t*>(m_pixels.get()), UINT16_MAX/(float)UINT32_MAX);
break;
case FLOAT32:
loop(out, reinterpret_cast<float*>(m_pixels.get()), 65535.0);
break;
default:
qWarning() << "FLOAT64 should not happend";
return;
}
m_pixels = std::move(outptr);
m_width = THUMB_SIZE;
m_height = THUMB_SIZE;
m_ch = 4;
m_channels = 3;
m_type = UINT16;
}
void RawImage::convertToGLFormat()
{
size_t s = size() * m_ch;
if(m_type == UINT32)
{
m_original = std::move(m_pixels);
allocate(m_width, m_height, m_channels, FLOAT32);
m_origType = UINT32;
float *dst = reinterpret_cast<float*>(m_pixels.get());
uint32_t *src = reinterpret_cast<uint32_t*>(m_original.get());
for(size_t i = 0; i < s; i++)
dst[i] = src[i] / (float)UINT32_MAX;
}
else if(m_type == FLOAT64)
{
m_original = std::move(m_pixels);
allocate(m_width, m_height, m_channels, FLOAT32);
m_origType = FLOAT64;
float *dst = reinterpret_cast<float*>(m_pixels.get());
double *src = reinterpret_cast<double*>(m_original.get());
for(size_t i = 0; i < s; i++)
dst[i] = src[i];
}
}
float RawImage::thumbAspect() const
{
return m_thumbAspect;
}
bool RawImage::pixel(int x, int y, double &r, double &g, double &b) const
{
if(x < 0 || y < 0 || x >= (int)width() || y >= (int)height())return false;
switch(m_origType)
{
case UINT8:
{
const uint8_t *v = static_cast<const uint8_t*>(origData(y, x));
if(m_channels == 1)
{
r = g = b = *v;
}
else
{
r = v[0];
g = v[1];
b = v[2];
}
break;
}
case UINT16:
{
const uint16_t *v = static_cast<const uint16_t*>(origData(y, x));
if(m_channels == 1)
{
r = g = b = *v;
}
else
{
r = v[0];
g = v[1];
b = v[2];
}
break;
}
case UINT32:
{
const uint32_t *v = static_cast<const uint32_t*>(origData(y, x));
if(m_channels == 1)
{
r = g = b = *v;
}
else
{
r = v[0];
g = v[1];
b = v[2];
}
break;
}
case FLOAT32:
{
const float *v = static_cast<const float*>(origData(y, x));
if(m_channels == 1)
{
r = g = b = *v;
}
else
{
r = v[0];
g = v[1];
b = v[2];
}
break;
}
case FLOAT64:
{
const double *v = static_cast<const double*>(origData(y, x));
if(m_channels == 1)
{
r = g = b = *v;
}
else
{
r = v[0];
g = v[1];
b = v[2];
}
break;
}
}
return true;
}
void RawImage::scaleToUnit()
{
/*if(CV_MAT_DEPTH(m_img.type()) == CV_32F)
{
double min, max;
cv::minMaxIdx(m_img, &min, &max);
if(min < 0 || max > 1)
{
float scale = 1.0 / (max - min);
float zero = min * scale;
m_img = m_img * scale - zero;
}
}*/
}
void RawImage::downscaleTo(uint32_t size)
{
/*if(size < width() || size < height())
{
double s = (double)size / std::max(width(), height());
cv::Size dsize(std::floor(width() * s), std::floor(height() * s));
cv::resize(m_img, m_img, dsize, 0, 0, cv::INTER_AREA);
}*/
}
std::shared_ptr<RawImage> RawImage::fromPlanar(const RawImage &img)
{
return RawImage::fromPlanar(img.data(), img.width(), img.height(), img.channels(), img.type());
}
std::shared_ptr<RawImage> RawImage::fromPlanar(const void *pixels, uint32_t w, uint32_t h, uint32_t ch, RawImage::DataType type)
{
std::shared_ptr<RawImage> image = std::make_shared<RawImage>(w, h, ch, type);
size_t size = w * h;
size_t ch2 = ch == 1 ? 1 : 4;
auto convert = [&](auto *in, auto *out, auto alpha)
{
for(size_t i=0; i<size; i++)
for(size_t o=0; o<ch; o++)
out[i*ch2 + o] = in[o*size + i];
if(ch != ch2)
for(size_t i=0; i<size; i++)
out[i*ch2 + 3] = alpha;
};
switch(type)
{
case UINT8:
convert(static_cast<const uint8_t*>(pixels), static_cast<uint8_t*>(image->data()), UINT8_MAX);
break;
case UINT16:
convert(static_cast<const uint16_t*>(pixels), static_cast<uint16_t*>(image->data()), UINT16_MAX);
break;
case UINT32:
convert(static_cast<const uint32_t*>(pixels), static_cast<uint32_t*>(image->data()), UINT32_MAX);
break;
case FLOAT32:
convert(static_cast<const float*>(pixels), static_cast<float*>(image->data()), 1);
break;
case FLOAT64:
convert(static_cast<const double*>(pixels), static_cast<double*>(image->data()), 1);
break;
}
return image;
}
std::vector<RawImage> RawImage::split() const
{
std::vector<RawImage> planes;
planes.resize(m_channels);
for(size_t i=0; i<m_channels; i++)
planes[i].allocate(m_width, m_height, 1, m_type);
return planes;
}