diff --git a/.gitignore b/.gitignore index 3d61566..cb1161f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ +*.dSYM +*.dat *.dll -*.so *.dylib +*.ipynb *.o -*.dSYM -build/ -*.dat -.vscode/ \ No newline at end of file +*.so +.vscode/ +build/ \ No newline at end of file diff --git a/include/cma-es/cmaes.h b/include/cma-es/cmaes.h index 5450b2a..25c34bb 100644 --- a/include/cma-es/cmaes.h +++ b/include/cma-es/cmaes.h @@ -1707,9 +1707,8 @@ template class CMAES { size_t crit_n = stopCriteria.size(); unsigned long flags = 0; - for (size_t i = 0; i < crit_n; ++i) { + for (size_t i = 0; i < crit_n; ++i) flags |= (unsigned long)stopCriteria[i].first << i; - } return flags; } diff --git a/include/fitness.h b/include/fitness.h index a91517a..c9fd8f7 100644 --- a/include/fitness.h +++ b/include/fitness.h @@ -9,7 +9,13 @@ inline double offRange(double x, double a, double b); double offRGB(const color::LAB &lab); -LexiProduct fitnessFunc(const std::vector &lab); +/** + * @param lab a vector of color::LAB, inter-distances of lab[freeM:] are ignored + * @param M 0 < M <= lab.size(), set to lab.size() if 0 + * @return a lexicographical product of distances + */ +LexiProduct fitnessFunc(const std::vector &lab, + size_t M = 0); class PerceptionResult { public: @@ -21,5 +27,13 @@ class PerceptionResult { std::ostream &operator<<(std::ostream &os, const PerceptionResult &res); -PerceptionResult perceptionL(color::LAB foreground, color::LAB background, - size_t M, bool quiet = false); \ No newline at end of file +/* + * @param M numbers of free colors + * @param L luminocity, < 0 to be ignored + * @param fixed vector of fixed colors, optional + * @param quiet don't write info to stdout + * @return PerceptionResult + */ +PerceptionResult perception(size_t M, double L = -1, + std::vector const *fixed = nullptr, + bool quiet = false); diff --git a/include/lexi.h b/include/lexi.h index a9461db..c495c6e 100644 --- a/include/lexi.h +++ b/include/lexi.h @@ -66,9 +66,8 @@ template class LexiProduct { friend std::ostream &operator<<(std::ostream &os, const LexiProduct &lexi) { const size_t n = lexi.prd.size(); os << "["; - for (size_t i = 0; i < n - 1; ++i) { + for (size_t i = 0; i < n - 1; ++i) os << lexi.prd[i] << ","; - } os << lexi.prd[n - 1] << "]"; return os; } @@ -82,9 +81,8 @@ template class LexiProduct { const size_t n = (lencmp < 0 ? lhs : rhs).prd.size(); for (size_t i = 0; i < n; ++i) { const Scalar d = lhs.prd[i] - rhs.prd[i]; - if (d != 0) { + if (d != 0) return d; - } } return lencmp; } diff --git a/src/fitness.cpp b/src/fitness.cpp index 553e0c0..42c9bee 100644 --- a/src/fitness.cpp +++ b/src/fitness.cpp @@ -8,12 +8,10 @@ #include inline double offRange(double x, double a, double b) { - if (x < a) { + if (x < a) return a - x; - } - if (x > b) { + if (x > b) return x - b; - } return 0; } @@ -26,33 +24,40 @@ double offRGB(const color::LAB &lab) { return std::sqrt(dr * dr + dg * dg + db * db); } -LexiProduct fitnessFunc(const std::vector &lab) { - // number of colors - size_t M = lab.size(); - std::vector penalty(M); - for (size_t i = 0; i < M; ++i) { - penalty[i] = - offRGB(lab[i]) * 300 * M; /* 300 is the approx. diameter of ab-plane */ - } - - // number of combinations between colors, except the last two - size_t K = M * (M - 1) / 2 - 1; +class Combination { +public: + size_t i; + size_t j; +}; + +LexiProduct fitnessFunc(const std::vector &lab, size_t M) { + size_t totalM = lab.size(); + std::vector penalty(totalM); + if (M == 0) + M = totalM; + for (size_t i = 0; i < M; ++i) + // 300 is the approx. diameter of ab-plane + penalty[i] = offRGB(lab[i]) * 300 * totalM; + + // number of combinations between free/fixed colors + size_t K = (M * (2 * totalM - M - 1)) / 2; static size_t cached_K = 0; + static size_t cached_M = 0; static std::vector cached_ref; /* for almost-sorted O(n) sorting */ - static std::vector> ref_combi; + static std::vector ref_combi; // Cache combination order - if (cached_K != K) { + if (!(cached_K == K && cached_M == M)) { cached_K = K; + cached_M = M; cached_ref.resize(K); - for (size_t i = 0; i < K; ++i) { + for (size_t i = 0; i < K; ++i) cached_ref[i] = i; - } ref_combi.resize(K); size_t combi_i = 0; - for (size_t i = 0; i < M - 2; ++i) { - for (size_t j = i + 1; j < M; ++j) { - ref_combi[combi_i] = std::pair(i, j); + for (size_t i = 0; i < M; ++i) { + for (size_t j = i + 1; j < totalM; ++j) { + ref_combi[combi_i] = Combination{i, j}; ++combi_i; } } @@ -61,10 +66,10 @@ LexiProduct fitnessFunc(const std::vector &lab) { std::vector> fitness_ref(K); for (size_t i = 0; i < K; ++i) { const auto ref = cached_ref[i]; - const auto &refs = ref_combi[ref]; + const auto &combi = ref_combi[ref]; fitness_ref[i] = std::pair( - penalty[refs.first] + penalty[refs.second] - - color::CIEDE2000(lab[refs.first], lab[refs.second]), + penalty[combi.i] + penalty[combi.j] - + color::CIEDE2000(lab[combi.i], lab[combi.j]), ref); } std::sort( @@ -80,11 +85,10 @@ LexiProduct fitnessFunc(const std::vector &lab) { return LexiProduct(std::move(fitness)); } -void fill_lab(const std::vector &x, std::vector &lab, - size_t M) { - for (size_t j = 0; j < M; ++j) { - lab[j] = color::LAB{x[2 * M], x[2 * j], x[2 * j + 1]}; - } +void fill_lab(const std::vector &x, double L, + std::vector &lab, size_t M) { + for (size_t j = 0; j < M; ++j) + lab[j] = color::LAB{L, x[2 * j], x[2 * j + 1]}; } std::ostream &operator<<(std::ostream &os, const PerceptionResult &res) { @@ -98,24 +102,33 @@ std::ostream &operator<<(std::ostream &os, const PerceptionResult &res) { return os; } -/* - * @param foreground - * @param background - * @param M numbers of colors - * @param quiet write info to stdout - * @return PerceptionResult - */ -PerceptionResult perceptionL(color::LAB foreground, color::LAB background, - size_t M, bool quiet) { +PerceptionResult perception(size_t M, double L, + std::vector const *fixed, bool quiet) { + bool freeL = L < 0; + bool noFixed = fixed == nullptr || fixed->size() == 0; + size_t fixedM = fixed == nullptr ? 0 : fixed->size(); + CMAES> evo; Individual> *pop; - const size_t N = M * 2 + 1; //!< number of variables + const size_t N = M * 2 + freeL; //!< number of variables std::vector xstart(N); - const double initL = (foreground.l + background.l) / 2.; + const double initL = noFixed + ? 50 + : accumulate(fixed->begin(), fixed->end(), 0, + [](double accu, const color::LAB &c) { + return accu + c.l; + }) / + (double)fixedM; std::vector stddev(N, std::min(100. - initL, initL)); - xstart[2 * M] = initL; // luminocity - stddev[2 * M] = 100.; // luminocity + if (freeL) { + xstart[2 * M] = initL; + stddev[2 * M] = 100.; + } + + if (!quiet) + std::cout << "freeM=" << M << ", fixedM=" << fixedM << ", initL=" << initL + << std::endl; Parameters> parameters; parameters.lambda = (int)(300. * log(N)); /* 100x default */ @@ -127,23 +140,23 @@ PerceptionResult perceptionL(color::LAB foreground, color::LAB background, if (!quiet) std::cout << evo.sayHello() << std::endl; - std::vector lab(M + 2); - lab[M] = foreground; - lab[M + 1] = background; + std::vector lab(M + fixedM); + if (!noFixed) + std::copy(fixed->begin(), fixed->end(), lab.begin() + (long)M); std::vector> populationFitness((size_t)parameters.lambda); unsigned long flags = 0; while (!(flags = evo.testForTermination())) { pop = evo.samplePopulation(); // Do not change content of pop for (size_t i = 0; i < parameters.lambda; ++i) { - fill_lab(pop[i].x, lab, M); - populationFitness[i] = fitnessFunc(lab); + fill_lab(pop[i].x, freeL ? pop[i].x[2 * M] : L, lab, M); + populationFitness[i] = fitnessFunc(lab, M); } evo.updateDistribution(populationFitness); } const auto xfinal = evo.getVector(CMAES>::XMean); - fill_lab(xfinal, lab, M); + fill_lab(xfinal, freeL ? xfinal[2 * M] : L, lab, M); std::sort(lab.begin(), lab.begin() + (long)M, [](const color::LAB &c1, const color::LAB &c2) { const double h1 = std::atan2(-c1.b, -c1.a); @@ -156,10 +169,9 @@ PerceptionResult perceptionL(color::LAB foreground, color::LAB background, std::cout << "Stop: flags " << flags << std::endl << evo.getStopMessage(); std::vector rgb(M); - for (size_t i = 0; i < M; ++i) { + for (size_t i = 0; i < M; ++i) rgb[i] = color::LABtoRGB(lab[i]); - } - return PerceptionResult{flags, xfinal[2 * M], std::move(rgb), + return PerceptionResult{flags, freeL ? xfinal[2 * M] : L, std::move(rgb), std::move(fitness)}; } \ No newline at end of file diff --git a/src/python.cpp b/src/python.cpp index 8198760..dc253e9 100644 --- a/src/python.cpp +++ b/src/python.cpp @@ -3,7 +3,7 @@ #include #include -//< RAII wrapper for PyObject +//< RAII wrapper for PyObject* class PyObj { public: PyObj() = delete; @@ -25,16 +25,64 @@ class PyObj { PyObject *ptr; }; -static PyObject *perception(PyObject *self, PyObject *args) { - color::LAB fg, bg; +static PyObject *LABtoRGB(PyObject *self, PyObject *args) { + color::LAB lab; + + if (!PyArg_ParseTuple(args, "(ddd)", &lab.l, &lab.a, &lab.b)) + return nullptr; + + const auto rgb = color::LABtoRGB(lab); + return Py_BuildValue("(ddd)", rgb.r, rgb.g, rgb.b); +} + +static PyObject *pyperception(PyObject *self, PyObject *args, PyObject *kw) { + // color::LAB fg, bg; unsigned long M; - bool quiet; + double L = -1; + PyObject *fixed_pyo = nullptr; + bool quiet = false; + + static char kw1[] = "M"; + static char kw2[] = "L"; + static char kw3[] = "fixed"; + static char kw4[] = "quiet"; + static char *kwlist[] = {kw1, kw2, kw3, kw4}; + if (!PyArg_ParseTupleAndKeywords(args, kw, "k|dOp", kwlist, &M, &L, + &fixed_pyo, &quiet)) + return nullptr; - if (!PyArg_ParseTuple(args, "(ddd)(ddd)k|p", &fg.l, &fg.a, &fg.b, &bg.l, - &bg.a, &bg.b, &M, &quiet)) - return NULL; + std::vector fixed; + if (fixed_pyo && fixed_pyo != Py_None) { + PyObj fixed_seq = PySequence_Fast(fixed_pyo, "expected a sequence of LAB"); + if (!fixed_seq.ptr) + return nullptr; + auto len = PySequence_Fast_GET_SIZE(fixed_seq.ptr); + for (size_t i = 0; i < len; ++i) { + PyObj lab_seq = + PySequence_Fast(PySequence_Fast_GET_ITEM(fixed_seq.ptr, i), + "expect a tuple of (L, a, b)"); + if (!lab_seq.ptr) + return nullptr; + auto lab_len = PySequence_Fast_GET_SIZE(lab_seq.ptr); + if (lab_len != 3) { + PyErr_SetString(PyExc_TypeError, "expect a tuple of (L, a, b)"); + return nullptr; + } + fixed.push_back(color::LAB{ + PyFloat_AsDouble(PySequence_Fast_GET_ITEM(lab_seq.ptr, 0)), + PyFloat_AsDouble(PySequence_Fast_GET_ITEM(lab_seq.ptr, 1)), + PyFloat_AsDouble(PySequence_Fast_GET_ITEM(lab_seq.ptr, 2))}); + if (PyErr_Occurred()) + return nullptr; + } + } + if (!quiet) { + for (const auto &lab : fixed) + std::cout << lab << " "; + std::cout << std::endl; + } - const auto result = perceptionL(fg, bg, M, quiet); + const auto result = perception(M, L, &fixed, quiet); PyObj rgb = PyObj(PyList_New(0)); for (const auto &c : result.rgb) PyList_Append(rgb.ptr, PyObj(Py_BuildValue("(ddd)", c.r, c.g, c.b)).ptr); @@ -47,11 +95,20 @@ static PyObject *perception(PyObject *self, PyObject *args) { } static PyMethodDef methods[] = { - {"perception", perception, METH_VARARGS, - "perception colors\n-----------------\n\n@param foreground " - "(l,a,b)\n@param background (l,a,b)\n@param M number of colors\n@param " - "quiet default:False"}, - {NULL, NULL, 0, NULL}}; + {"perception", (PyCFunction)pyperception, METH_VARARGS | METH_KEYWORDS, + "perception colors anchored by N colors\n" + "--------------------------------------\n" + "\n" + "@param M number of colors\n" + "@param L=-1 luminocity, < 0 for optimized value\n" + "@param fixed=None iterable of (l, a, b)\n" + "@param quiet=False print messages to stdout"}, + {"LABtoRGB", LABtoRGB, METH_VARARGS, + "convert LAB to RGB\n" + "------------------\n" + "\n" + "@param lab (l,a,b)\n"}, + {nullptr, nullptr, 0, nullptr}}; static struct PyModuleDef module = { PyModuleDef_HEAD_INIT, "pyperception", diff --git a/test/testfitness.cpp b/test/testfitness.cpp index 53332d8..5b0f79c 100644 --- a/test/testfitness.cpp +++ b/test/testfitness.cpp @@ -45,9 +45,9 @@ int testcolor() { {50, 10, -10}})[0], -12.8001, "fitnessFunc"); - check(fitnessFunc(std::vector{ - {50, -200, -200}, {50, 200, 200}, {50, 0, 0}, {50, 0, 0}})[0], - 3337.715201, "fitnessFunc (off boundary)"); + check( + fitnessFunc(std::vector{{50, -200, -200}, {50, 200, 200}})[0], + 1626.445599, "fitnessFunc (off boundary)"); std::cout << std::endl; int ret = EXIT_SUCCESS; diff --git a/test/testfitness2.cpp b/test/testfitness2.cpp index 9eda9dc..a685889 100644 --- a/test/testfitness2.cpp +++ b/test/testfitness2.cpp @@ -14,9 +14,8 @@ unsigned compress(double x, unsigned max = 256) { int main(int argc, char *argv[]) { const size_t M = 7; - const color::LAB foreground{0, 0., 0.}; - const color::LAB background{100, 0., 0.}; - const auto result = perceptionL(foreground, background, M); + const std::vector fixed{{0, 0, 0}, {90, 0, 0}}; + const auto result = perception(M, -1, &fixed); std::cout << result << std::endl; // Write a HTML demo