kopia lustrzana https://gitlab.com/sane-project/backends
genesys: Implement support for host-side calibration
rodzic
cf5f30dec3
commit
323f37753a
|
@ -1022,6 +1022,11 @@ void sanei_genesys_init_shading_data(Genesys_Device* dev, const Genesys_Sensor&
|
||||||
int pixels_per_line)
|
int pixels_per_line)
|
||||||
{
|
{
|
||||||
DBG_HELPER(dbg);
|
DBG_HELPER(dbg);
|
||||||
|
|
||||||
|
if (dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int channels;
|
int channels;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
@ -2400,6 +2405,11 @@ compute_shifted_coefficients (Genesys_Device * dev,
|
||||||
static void genesys_send_shading_coefficient(Genesys_Device* dev, const Genesys_Sensor& sensor)
|
static void genesys_send_shading_coefficient(Genesys_Device* dev, const Genesys_Sensor& sensor)
|
||||||
{
|
{
|
||||||
DBG_HELPER(dbg);
|
DBG_HELPER(dbg);
|
||||||
|
|
||||||
|
if (dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t pixels_per_line;
|
uint32_t pixels_per_line;
|
||||||
uint8_t channels;
|
uint8_t channels;
|
||||||
int o;
|
int o;
|
||||||
|
|
|
@ -1058,7 +1058,8 @@ static void gl843_init_optical_regs_scan(Genesys_Device* dev, const Genesys_Sens
|
||||||
r = sanei_genesys_get_address (reg, REG01);
|
r = sanei_genesys_get_address (reg, REG01);
|
||||||
r->value &= ~REG01_SCAN;
|
r->value &= ~REG01_SCAN;
|
||||||
if ((session.params.flags & SCAN_FLAG_DISABLE_SHADING) ||
|
if ((session.params.flags & SCAN_FLAG_DISABLE_SHADING) ||
|
||||||
(dev->model->flags & GENESYS_FLAG_NO_CALIBRATION))
|
(dev->model->flags & GENESYS_FLAG_NO_CALIBRATION ||
|
||||||
|
(dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE)))
|
||||||
{
|
{
|
||||||
r->value &= ~REG01_DVDSET;
|
r->value &= ~REG01_DVDSET;
|
||||||
}
|
}
|
||||||
|
|
|
@ -653,6 +653,55 @@ bool ImagePipelineNodeExtract::get_next_row_data(std::uint8_t* out_data)
|
||||||
return got_data;
|
return got_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImagePipelineNodeCalibrate::ImagePipelineNodeCalibrate(ImagePipelineNode& source,
|
||||||
|
const std::vector<std::uint16_t>& bottom,
|
||||||
|
const std::vector<std::uint16_t>& top) :
|
||||||
|
source_{source}
|
||||||
|
{
|
||||||
|
auto size = std::min(bottom.size(), top.size());
|
||||||
|
offset_.reserve(size);
|
||||||
|
multiplier_.reserve(size);
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < size; ++i) {
|
||||||
|
offset_.push_back(bottom[i] / 65535.0f);
|
||||||
|
multiplier_.push_back(65535.0f / (top[i] - bottom[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImagePipelineNodeCalibrate::get_next_row_data(std::uint8_t* out_data)
|
||||||
|
{
|
||||||
|
bool ret = source_.get_next_row_data(out_data);
|
||||||
|
|
||||||
|
auto format = get_format();
|
||||||
|
auto depth = get_pixel_format_depth(format);
|
||||||
|
std::size_t max_value = 1;
|
||||||
|
switch (depth) {
|
||||||
|
case 8: max_value = 255; break;
|
||||||
|
case 16: max_value = 65535; break;
|
||||||
|
default:
|
||||||
|
throw SaneException("Unsupported depth for calibration %d", depth);
|
||||||
|
}
|
||||||
|
unsigned channels = get_pixel_channels(format);
|
||||||
|
|
||||||
|
std::size_t max_calib_i = offset_.size();
|
||||||
|
std::size_t curr_calib_i = 0;
|
||||||
|
|
||||||
|
for (std::size_t x = 0, width = get_width(); x < width && curr_calib_i < max_calib_i; ++x) {
|
||||||
|
for (unsigned ch = 0; ch < channels && curr_calib_i < max_calib_i; ++ch) {
|
||||||
|
std::int32_t value = get_raw_channel_from_row(out_data, x, ch, format);
|
||||||
|
|
||||||
|
float value_f = static_cast<float>(value) / max_value;
|
||||||
|
value_f = (value_f - offset_[curr_calib_i]) * multiplier_[curr_calib_i];
|
||||||
|
value_f = std::round(value_f * max_value);
|
||||||
|
value = clamp<std::int32_t>(static_cast<std::int32_t>(value_f), 0, max_value);
|
||||||
|
set_raw_channel_to_row(out_data, x, ch, value, format);
|
||||||
|
|
||||||
|
curr_calib_i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
ImagePipelineNodeDebug::ImagePipelineNodeDebug(ImagePipelineNode& source,
|
ImagePipelineNodeDebug::ImagePipelineNodeDebug(ImagePipelineNode& source,
|
||||||
const std::string& path) :
|
const std::string& path) :
|
||||||
source_(source),
|
source_(source),
|
||||||
|
|
|
@ -474,6 +474,29 @@ private:
|
||||||
std::vector<uint8_t> cached_line_;
|
std::vector<uint8_t> cached_line_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A pipeline node that mimics the calibration behavior on Genesys chips
|
||||||
|
class ImagePipelineNodeCalibrate : public ImagePipelineNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
ImagePipelineNodeCalibrate(ImagePipelineNode& source, const std::vector<std::uint16_t>& bottom,
|
||||||
|
const std::vector<std::uint16_t>& top);
|
||||||
|
|
||||||
|
std::size_t get_width() const override { return source_.get_width(); }
|
||||||
|
std::size_t get_height() const override { return source_.get_height(); }
|
||||||
|
PixelFormat get_format() const override { return source_.get_format(); }
|
||||||
|
|
||||||
|
bool eof() const override { return source_.eof(); }
|
||||||
|
|
||||||
|
bool get_next_row_data(std::uint8_t* out_data) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ImagePipelineNode& source_;
|
||||||
|
|
||||||
|
std::vector<float> offset_;
|
||||||
|
std::vector<float> multiplier_;
|
||||||
|
};
|
||||||
|
|
||||||
class ImagePipelineNodeDebug : public ImagePipelineNode
|
class ImagePipelineNodeDebug : public ImagePipelineNode
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -1670,6 +1670,19 @@ void build_image_pipeline(Genesys_Device* dev, const ScanSession& session)
|
||||||
"_2_after_stagger.pnm");
|
"_2_after_stagger.pnm");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((dev->model->flags & GENESYS_FLAG_CALIBRATION_HOST_SIDE) &&
|
||||||
|
!(dev->model->flags & GENESYS_FLAG_NO_CALIBRATION))
|
||||||
|
{
|
||||||
|
dev->pipeline.push_node<ImagePipelineNodeCalibrate>(dev->dark_average_data,
|
||||||
|
dev->white_average_data);
|
||||||
|
|
||||||
|
if (DBG_LEVEL >= DBG_io2) {
|
||||||
|
dev->pipeline.push_node<ImagePipelineNodeDebug>("gl_pipeline_" +
|
||||||
|
std::to_string(s_pipeline_index) +
|
||||||
|
"_3_after_calibrate.pnm");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (session.output_pixels != session.params.get_requested_pixels()) {
|
if (session.output_pixels != session.params.get_requested_pixels()) {
|
||||||
dev->pipeline.push_node<ImagePipelineNodeScaleRows>(session.params.get_requested_pixels());
|
dev->pipeline.push_node<ImagePipelineNodeScaleRows>(session.params.get_requested_pixels());
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,6 +136,8 @@
|
||||||
#define GENESYS_FLAG_FULL_HWDPI_MODE (1 << 19) /**< scanner always use maximum hw dpi to setup the sensor */
|
#define GENESYS_FLAG_FULL_HWDPI_MODE (1 << 19) /**< scanner always use maximum hw dpi to setup the sensor */
|
||||||
// scanner has infrared transparency scanning capability
|
// scanner has infrared transparency scanning capability
|
||||||
#define GENESYS_FLAG_HAS_UTA_INFRARED (1 << 20)
|
#define GENESYS_FLAG_HAS_UTA_INFRARED (1 << 20)
|
||||||
|
// scanner calibration is handled on the host side
|
||||||
|
#define GENESYS_FLAG_CALIBRATION_HOST_SIDE (1 << 21)
|
||||||
|
|
||||||
#define GENESYS_HAS_NO_BUTTONS 0 /**< scanner has no supported button */
|
#define GENESYS_HAS_NO_BUTTONS 0 /**< scanner has no supported button */
|
||||||
#define GENESYS_HAS_SCAN_SW (1 << 0) /**< scanner has SCAN button */
|
#define GENESYS_HAS_SCAN_SW (1 << 0) /**< scanner has SCAN button */
|
||||||
|
|
|
@ -424,6 +424,78 @@ void test_node_pixel_shift_lines()
|
||||||
ASSERT_EQ(out_data, expected_data);
|
ASSERT_EQ(out_data, expected_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_node_calibrate_8bit()
|
||||||
|
{
|
||||||
|
using Data = std::vector<std::uint8_t>;
|
||||||
|
|
||||||
|
Data in_data = {
|
||||||
|
0x20, 0x38, 0x38
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::uint16_t> bottom = {
|
||||||
|
0x1000, 0x2000, 0x3000
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::uint16_t> top = {
|
||||||
|
0x3000, 0x4000, 0x5000
|
||||||
|
};
|
||||||
|
|
||||||
|
ImagePipelineStack stack;
|
||||||
|
stack.push_first_node<ImagePipelineNodeArraySource>(1, 1, PixelFormat::RGB888,
|
||||||
|
std::move(in_data));
|
||||||
|
stack.push_node<ImagePipelineNodeCalibrate>(bottom, top);
|
||||||
|
|
||||||
|
ASSERT_EQ(stack.get_output_width(), 1u);
|
||||||
|
ASSERT_EQ(stack.get_output_height(), 1u);
|
||||||
|
ASSERT_EQ(stack.get_output_row_bytes(), 3u);
|
||||||
|
ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB888);
|
||||||
|
|
||||||
|
auto out_data = stack.get_all_data();
|
||||||
|
|
||||||
|
Data expected_data = {
|
||||||
|
// note that we don't handle rounding properly in the implementation
|
||||||
|
0x80, 0xc1, 0x41
|
||||||
|
};
|
||||||
|
|
||||||
|
ASSERT_EQ(out_data, expected_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_node_calibrate_16bit()
|
||||||
|
{
|
||||||
|
using Data = std::vector<std::uint8_t>;
|
||||||
|
|
||||||
|
Data in_data = {
|
||||||
|
0x00, 0x20, 0x00, 0x38, 0x00, 0x38
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::uint16_t> bottom = {
|
||||||
|
0x1000, 0x2000, 0x3000
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::uint16_t> top = {
|
||||||
|
0x3000, 0x4000, 0x5000
|
||||||
|
};
|
||||||
|
|
||||||
|
ImagePipelineStack stack;
|
||||||
|
stack.push_first_node<ImagePipelineNodeArraySource>(1, 1, PixelFormat::RGB161616,
|
||||||
|
std::move(in_data));
|
||||||
|
stack.push_node<ImagePipelineNodeCalibrate>(bottom, top);
|
||||||
|
|
||||||
|
ASSERT_EQ(stack.get_output_width(), 1u);
|
||||||
|
ASSERT_EQ(stack.get_output_height(), 1u);
|
||||||
|
ASSERT_EQ(stack.get_output_row_bytes(), 6u);
|
||||||
|
ASSERT_EQ(stack.get_output_format(), PixelFormat::RGB161616);
|
||||||
|
|
||||||
|
auto out_data = stack.get_all_data();
|
||||||
|
|
||||||
|
Data expected_data = {
|
||||||
|
// note that we don't handle rounding properly in the implementation
|
||||||
|
0x00, 0x80, 0xff, 0xbf, 0x00, 0x40
|
||||||
|
};
|
||||||
|
|
||||||
|
ASSERT_EQ(out_data, expected_data);
|
||||||
|
}
|
||||||
|
|
||||||
void test_image_pipeline()
|
void test_image_pipeline()
|
||||||
{
|
{
|
||||||
test_image_buffer_genesys_usb();
|
test_image_buffer_genesys_usb();
|
||||||
|
@ -438,4 +510,6 @@ void test_image_pipeline()
|
||||||
test_node_split_mono_lines();
|
test_node_split_mono_lines();
|
||||||
test_node_component_shift_lines();
|
test_node_component_shift_lines();
|
||||||
test_node_pixel_shift_lines();
|
test_node_pixel_shift_lines();
|
||||||
|
test_node_calibrate_8bit();
|
||||||
|
test_node_calibrate_16bit();
|
||||||
}
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue