Skip to content

Commit

Permalink
Use per call switch for color_mode -- rather than per object instance
Browse files Browse the repository at this point in the history
I say "color_mode", but this also includes the coupled legacy behavior
(merged symbols and colors).

Motivation: per-frame bouncing between color_mode, so we can test
success/failure for mode B/4C and pick the one that's working.

Details:
When running a decoder across multiple frames, we have certain state
(loaded hashes for imagehash hamming distance, for example) that is
longer lived -- that we don't want to run each frame. This *currently*
is stored in the CimbDecoder (also the fountain_decoder_sink, though
that's not relevant to this change). To allow us to switch between
modes, we need the CimbDecoder to not store color_mode, but rather take
it as an argument where required.

Misc:
There are other ways to accomplish this, but this change seems like the
best immediate path forward. The actual technical requirement is:
(1) to run in different modes
(2) on different threads
(3) at the same time.
  • Loading branch information
sz3 committed Mar 1, 2024
1 parent ae13549 commit 7b11358
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 93 deletions.
27 changes: 13 additions & 14 deletions src/exe/cimbar/cimbar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ int encode(const FilenameIterable& infiles, const std::string& outpath, int ecc,
}

template <typename FilenameIterable>
int decode(const FilenameIterable& infiles, const std::function<int(cv::UMat, bool, int)>& decodefun, bool no_deskew, bool undistort, int preprocess, int color_correct)
int decode(const FilenameIterable& infiles, const std::function<int(cv::UMat, unsigned, bool, int)>& decodefun, bool no_deskew, bool undistort, unsigned color_mode, int preprocess, int color_correct)
{
int err = 0;
for (const string& inf : infiles)
Expand Down Expand Up @@ -152,7 +152,7 @@ int decode(const FilenameIterable& infiles, const std::function<int(cv::UMat, bo
shouldPreprocess = true;
}

int bytes = decodefun(img, shouldPreprocess, color_correct);
int bytes = decodefun(img, color_mode, shouldPreprocess, color_correct);
if (!bytes)
err |= 4;
}
Expand All @@ -162,10 +162,10 @@ int decode(const FilenameIterable& infiles, const std::function<int(cv::UMat, bo
// see also "decodefun" for non-fountain decodes, defined as a lambda inline below.
// this one needs its own function since it's a template (:
template <typename SINK>
std::function<int(cv::UMat,bool,int)> fountain_decode_fun(SINK& sink, Decoder& d)
std::function<int(cv::UMat,unsigned,bool,int)> fountain_decode_fun(SINK& sink, Decoder& d)
{
return [&sink, &d] (cv::UMat m, bool pre, int cc) {
return d.decode_fountain(m, sink, pre, cc);
return [&sink, &d] (cv::UMat m, unsigned cm, bool pre, int cc) {
return d.decode_fountain(m, sink, cm, pre, cc);
};
}

Expand Down Expand Up @@ -247,8 +247,7 @@ int main(int argc, char** argv)
int preprocess = result["preprocess"].as<int>();

unsigned color_mode = legacy_mode? 0 : 1;
bool coupled = legacy_mode;
Decoder d(ecc, colorBits, color_mode, coupled);
Decoder d(ecc, colorBits);

if (no_fountain)
{
Expand All @@ -257,13 +256,13 @@ int main(int argc, char** argv)

// simpler encoding, just the basics + ECC. No compression, fountain codes, etc.
std::ofstream f(outpath);
std::function<int(cv::UMat,bool,int)> decodefun = [&f, &d] (cv::UMat m, bool pre, int cc) {
return d.decode(m, f, pre, cc);
std::function<int(cv::UMat,unsigned,bool,int)> decodefun = [&f, &d] (cv::UMat m, unsigned cm, bool pre, int cc) {
return d.decode(m, f, cm, pre, cc);
};
if (useStdin)
return decode(StdinLineReader(), decodefun, no_deskew, undistort, preprocess, color_correct);
return decode(StdinLineReader(), decodefun, no_deskew, undistort, color_mode, preprocess, color_correct);
else
return decode(infiles, decodefun, no_deskew, undistort, preprocess, color_correct);
return decode(infiles, decodefun, no_deskew, undistort, color_mode, preprocess, color_correct);
}

// else, the good stuff
Expand All @@ -273,16 +272,16 @@ int main(int argc, char** argv)
if (compressionLevel <= 0)
{
fountain_decoder_sink<std::ofstream> sink(outpath, chunkSize, true);
res = decode(infiles, fountain_decode_fun(sink, d), no_deskew, undistort, preprocess, color_correct);
res = decode(infiles, fountain_decode_fun(sink, d), no_deskew, undistort, color_mode, preprocess, color_correct);
}
else // default case, all bells and whistles
{
fountain_decoder_sink<cimbar::zstd_decompressor<std::ofstream>> sink(outpath, chunkSize, true);

if (useStdin)
res = decode(StdinLineReader(), fountain_decode_fun(sink, d), no_deskew, undistort, preprocess, color_correct);
res = decode(StdinLineReader(), fountain_decode_fun(sink, d), no_deskew, undistort, color_mode, preprocess, color_correct);
else
res = decode(infiles, fountain_decode_fun(sink, d), no_deskew, undistort, preprocess, color_correct);
res = decode(infiles, fountain_decode_fun(sink, d), no_deskew, undistort, color_mode, preprocess, color_correct);
}
if (not color_correction_file.empty())
d.save_ccm(color_correction_file);
Expand Down
6 changes: 3 additions & 3 deletions src/exe/cimbar_recv/recv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ int main(int argc, char** argv)
string mode = result["mode"].as<string>();
legacy_mode = (mode == "4c") or (mode == "4C");
}
unsigned color_mode = legacy_mode? 0 : 1;

unsigned fps = result["fps"].as<unsigned>();
if (fps == 0)
Expand Down Expand Up @@ -104,8 +105,7 @@ int main(int argc, char** argv)
window.auto_scale_to_window();

Extractor ext;
unsigned color_mode = legacy_mode? 0 : 1;
Decoder dec(-1, -1, color_mode, legacy_mode);
Decoder dec(-1, -1);

unsigned chunkSize = cimbar::Config::fountain_chunk_size(ecc, colorBits+cimbar::Config::symbol_bits(), legacy_mode);
fountain_decoder_sink<cimbar::zstd_decompressor<std::ofstream>> sink(outpath, chunkSize);
Expand Down Expand Up @@ -147,7 +147,7 @@ int main(int argc, char** argv)
shouldPreprocess = true;

// decode
int bytes = dec.decode_fountain(img, sink, shouldPreprocess);
int bytes = dec.decode_fountain(img, sink, color_mode, shouldPreprocess);
if (bytes > 0)
std::cerr << "got some bytes " << bytes << std::endl;
}
Expand Down
15 changes: 7 additions & 8 deletions src/lib/cimb_translator/CimbDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,10 @@ namespace {
}
}

CimbDecoder::CimbDecoder(unsigned symbol_bits, unsigned color_bits, unsigned color_mode, bool dark, uchar ahashThreshold)
CimbDecoder::CimbDecoder(unsigned symbol_bits, unsigned color_bits, bool dark, uchar ahashThreshold)
: _symbolBits(symbol_bits)
, _numSymbols(1 << symbol_bits)
, _numColors(1 << color_bits)
, _colorMode(color_mode)
, _dark(dark)
, _ahashThreshold(ahashThreshold)
{
Expand Down Expand Up @@ -160,12 +159,12 @@ unsigned CimbDecoder::check_color_distance(std::tuple<uchar,uchar,uchar> a, std:
return color_diff(a, b);
}

std::tuple<uchar,uchar,uchar> CimbDecoder::get_color(int i) const
std::tuple<uchar,uchar,uchar> CimbDecoder::get_color(int i, unsigned color_mode) const
{
return cimbar::getColor(i, _numColors, _colorMode);
return cimbar::getColor(i, _numColors, color_mode);
}

unsigned CimbDecoder::get_best_color(float r, float g, float b) const
unsigned CimbDecoder::get_best_color(float r, float g, float b, unsigned color_mode) const
{
// transform color with ccm
if (internal_ccm().active())
Expand All @@ -188,7 +187,7 @@ unsigned CimbDecoder::get_best_color(float r, float g, float b) const
float best_distance = 1000000;
for (unsigned i = 0; i < _numColors; ++i)
{
std::tuple<uchar,uchar,uchar> candidate = get_color(i);
std::tuple<uchar,uchar,uchar> candidate = get_color(i, color_mode);
unsigned distance = check_color_distance(c, candidate);
if (distance < best_distance)
{
Expand All @@ -208,12 +207,12 @@ std::tuple<uchar,uchar,uchar> CimbDecoder::avg_color(const Cell& color_cell) con
return center.mean_rgb();
}

unsigned CimbDecoder::decode_color(const Cell& color_cell) const
unsigned CimbDecoder::decode_color(const Cell& color_cell, unsigned color_mode) const
{
if (_numColors <= 1)
return 0;
auto [r, g, b] = avg_color(color_cell);
return get_best_color(r, g, b);
return get_best_color(r, g, b, color_mode);
}

bool CimbDecoder::expects_binary_threshold() const
Expand Down
9 changes: 4 additions & 5 deletions src/lib/cimb_translator/CimbDecoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
class CimbDecoder
{
public:
CimbDecoder(unsigned symbol_bits, unsigned color_bits, unsigned color_mode=1, bool dark=true, uchar ahashThreshold=0);
CimbDecoder(unsigned symbol_bits, unsigned color_bits, bool dark=true, uchar ahashThreshold=0);

const color_correction& get_ccm() const;
void update_color_correction(cv::Matx<float, 3, 3>&& ccm);
Expand All @@ -22,10 +22,10 @@ class CimbDecoder
unsigned decode_symbol(const cv::Mat& cell, unsigned& drift_offset, unsigned& best_distance, unsigned cooldown=0xFF) const;
unsigned decode_symbol(const bitmatrix& cell, unsigned& drift_offset, unsigned& best_distance, unsigned cooldown=0xFF) const;

std::tuple<uchar,uchar,uchar> get_color(int i) const;
std::tuple<uchar,uchar,uchar> get_color(int i, unsigned color_mode) const;
std::tuple<uchar,uchar,uchar> avg_color(const Cell& color_cell) const;
unsigned get_best_color(float r, float g, float b) const;
unsigned decode_color(const Cell& cell) const;
unsigned get_best_color(float r, float g, float b, unsigned color_mode) const;
unsigned decode_color(const Cell& cell, unsigned color_mode) const;

bool expects_binary_threshold() const;
unsigned symbol_bits() const;
Expand All @@ -44,7 +44,6 @@ class CimbDecoder
unsigned _symbolBits;
unsigned _numSymbols;
unsigned _numColors;
unsigned _colorMode;
bool _dark;
uchar _ahashThreshold;
};
11 changes: 6 additions & 5 deletions src/lib/cimb_translator/CimbReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,29 +90,30 @@ namespace {
}
}

CimbReader::CimbReader(const cv::Mat& img, CimbDecoder& decoder, bool needs_sharpen, int color_correction)
CimbReader::CimbReader(const cv::Mat& img, CimbDecoder& decoder, unsigned color_mode, bool needs_sharpen, int color_correction)
: _image(img)
, _fountainColorHeader(0U)
, _cellSize(Config::cell_size() + 2)
, _positions(Config::cell_spacing(), Config::cells_per_col(), Config::cell_offset(), Config::corner_padding())
, _decoder(decoder)
, _good(_image.cols >= Config::image_size() and _image.rows >= Config::image_size())
, _colorCorrection(color_correction)
, _colorMode(color_mode)
{
_grayscale = preprocessSymbolGrid(img, needs_sharpen);
if (_good and color_correction == 1)
simpleColorCorrection(_image, decoder);
}

CimbReader::CimbReader(const cv::UMat& img, CimbDecoder& decoder, bool needs_sharpen, int color_correction)
: CimbReader(img.getMat(cv::ACCESS_READ), decoder, needs_sharpen, color_correction)
CimbReader::CimbReader(const cv::UMat& img, CimbDecoder& decoder, unsigned color_mode, bool needs_sharpen, int color_correction)
: CimbReader(img.getMat(cv::ACCESS_READ), decoder, color_mode, needs_sharpen, color_correction)
{
}

unsigned CimbReader::read_color(const PositionData& pos) const
{
Cell color_cell(_image, pos.x, pos.y, Config::cell_size(), Config::cell_size());
return _decoder.decode_color(color_cell);
return _decoder.decode_color(color_cell, _colorMode);
}

unsigned CimbReader::read(PositionData& pos)
Expand Down Expand Up @@ -219,7 +220,7 @@ void CimbReader::init_ccm(unsigned color_bits, unsigned interleave_blocks, unsig
cv::Mat arow = (cv::Mat_<float>(1,3) << std::get<1>(it.second), std::get<2>(it.second), std::get<3>(it.second));
actual.push_back(arow);

cimbar::RGB cc = _decoder.get_color(it.first);
cimbar::RGB cc = _decoder.get_color(it.first, _colorMode);
cv::Mat drow = (cv::Mat_<float>(1,3) << std::get<0>(cc), std::get<1>(cc), std::get<2>(cc));
desired.push_back(drow);
}
Expand Down
5 changes: 3 additions & 2 deletions src/lib/cimb_translator/CimbReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
class CimbReader
{
public:
CimbReader(const cv::Mat& img, CimbDecoder& decoder, bool needs_sharpen=false, int color_correction=2);
CimbReader(const cv::UMat& img, CimbDecoder& decoder, bool needs_sharpen=false, int color_correction=2);
CimbReader(const cv::Mat& img, CimbDecoder& decoder, unsigned color_mode, bool needs_sharpen=false, int color_correction=2);
CimbReader(const cv::UMat& img, CimbDecoder& decoder, unsigned color_mode, bool needs_sharpen=false, int color_correction=2);

unsigned read(PositionData& pos);
unsigned read_color(const PositionData& pos) const;
Expand All @@ -34,4 +34,5 @@ class CimbReader
CimbDecoder& _decoder;
bool _good;
int _colorCorrection;
unsigned _colorMode;
};
64 changes: 46 additions & 18 deletions src/lib/cimb_translator/test/CimbDecoderTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace {
cv::Rect crop(1+x, 1+y, tile10.cols-2, tile10.rows-2);
cv::Mat tile8 = tile10(crop);

bits |= cd.decode_color(tile8) << cd.symbol_bits();
bits |= cd.decode_color(tile8, 1) << cd.symbol_bits();
return bits;
}
}
Expand Down Expand Up @@ -74,32 +74,60 @@ TEST_CASE( "CimbDecoderTest/testPrethresholdDecode", "[unit]" )
}
}

TEST_CASE( "CimbDecoderTest/test_get_best_color__dark", "[unit]" )
TEST_CASE( "CimbDecoderTest/test_get_best_color_mode0", "[unit]" )
{
CimbDecoder cd(4, 2);

// obvious ones
assertEquals(3, cd.get_best_color(255, 0, 255));
assertEquals(2, cd.get_best_color(255, 255, 0));
assertEquals(1, cd.get_best_color(0, 255, 255));
assertEquals(0, cd.get_best_color(0, 255, 0));
assertEquals(2, cd.get_best_color(255, 0, 255, 0));
assertEquals(1, cd.get_best_color(255, 255, 0, 0));
assertEquals(0, cd.get_best_color(0, 255, 255, 0));
assertEquals(3, cd.get_best_color(0, 255, 0, 0));

// arbitrary edge cases. We can't really say anything about the value of these colors, but we can at least pick a consistent one
assertEquals(0, cd.get_best_color(0, 0, 0));
assertEquals(0, cd.get_best_color(70, 70, 70));
assertEquals(0, cd.get_best_color(0, 0, 0, 0));
assertEquals(0, cd.get_best_color(70, 70, 70, 0));

// these we can use!
assertEquals(0, cd.get_best_color(20, 200, 20));
assertEquals(0, cd.get_best_color(50, 155, 50));
assertEquals(3, cd.get_best_color(20, 200, 20, 0));
assertEquals(3, cd.get_best_color(50, 155, 50, 0));

assertEquals(3, cd.get_best_color(200, 30, 200));
assertEquals(3, cd.get_best_color(155, 50, 155));
assertEquals(2, cd.get_best_color(200, 30, 200, 0));
assertEquals(2, cd.get_best_color(155, 50, 155, 0));

assertEquals(2, cd.get_best_color(200, 155, 20));
assertEquals(2, cd.get_best_color(155, 155, 50));
assertEquals(1, cd.get_best_color(200, 155, 20, 0));
assertEquals(1, cd.get_best_color(155, 155, 50, 0));

assertEquals(1, cd.get_best_color(50, 155, 200));
assertEquals(1, cd.get_best_color(50, 155, 155));
assertEquals(0, cd.get_best_color(50, 155, 200, 0));
assertEquals(0, cd.get_best_color(50, 155, 155, 0));
}

TEST_CASE( "CimbDecoderTest/test_get_best_color_mode1", "[unit]" )
{
CimbDecoder cd(4, 2);

// obvious ones
assertEquals(3, cd.get_best_color(255, 0, 255, 1));
assertEquals(2, cd.get_best_color(255, 255, 0, 1));
assertEquals(1, cd.get_best_color(0, 255, 255, 1));
assertEquals(0, cd.get_best_color(0, 255, 0, 1));

// arbitrary edge cases. We can't really say anything about the value of these colors, but we can at least pick a consistent one
assertEquals(0, cd.get_best_color(0, 0, 0, 1));
assertEquals(0, cd.get_best_color(70, 70, 70, 1));

// these we can use!
assertEquals(0, cd.get_best_color(20, 200, 20, 1));
assertEquals(0, cd.get_best_color(50, 155, 50, 1));

assertEquals(3, cd.get_best_color(200, 30, 200, 1));
assertEquals(3, cd.get_best_color(155, 50, 155, 1));

assertEquals(2, cd.get_best_color(200, 155, 20, 1));
assertEquals(2, cd.get_best_color(155, 155, 50, 1));

assertEquals(1, cd.get_best_color(50, 155, 200, 1));
assertEquals(1, cd.get_best_color(50, 155, 155, 1));
}

TEST_CASE( "CimbDecoderTest/testColorDecode", "[unit]" )
Expand All @@ -109,7 +137,7 @@ TEST_CASE( "CimbDecoderTest/testColorDecode", "[unit]" )
cv::Mat tile = cimbar::getTile(4, 2, true, 4, 2);
cv::resize(tile, tile, cv::Size(10, 10));

unsigned color = cd.decode_color(Cell(tile));
unsigned color = cd.decode_color(Cell(tile), 1);
assertEquals(2, color);
unsigned res = decode(cd, tile);
assertEquals(34, res);
Expand All @@ -128,7 +156,7 @@ TEST_CASE( "CimbDecoderTest/testAllColorDecodes", "[unit]" )
cv::Mat tenxten(10, 10, tile.type(), {0,0,0});
tile.copyTo(tenxten(cv::Rect(cv::Point(1, 1), tile.size())));

unsigned color = cd.decode_color(Cell(tenxten));
unsigned color = cd.decode_color(Cell(tenxten), 1);
assertEquals(c, color);
unsigned res = decode(cd, tenxten);
assertEquals(i+16*c, res);
Expand Down
Loading

0 comments on commit 7b11358

Please sign in to comment.