Skip to content

Commit

Permalink
Python interface with keywords
Browse files Browse the repository at this point in the history
  • Loading branch information
gywn committed Nov 23, 2017
1 parent 0965bd8 commit 54c98dd
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 85 deletions.
11 changes: 6 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
*.dSYM
*.dat
*.dll
*.so
*.dylib
*.ipynb
*.o
*.dSYM
build/
*.dat
.vscode/
*.so
.vscode/
build/
3 changes: 1 addition & 2 deletions include/cma-es/cmaes.h
Original file line number Diff line number Diff line change
Expand Up @@ -1707,9 +1707,8 @@ template <typename Scalar, typename Ordered = Scalar> 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;
}

Expand Down
20 changes: 17 additions & 3 deletions include/fitness.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ inline double offRange(double x, double a, double b);

double offRGB(const color::LAB &lab);

LexiProduct<double> fitnessFunc(const std::vector<color::LAB> &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<double> fitnessFunc(const std::vector<color::LAB> &lab,
size_t M = 0);

class PerceptionResult {
public:
Expand All @@ -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);
/*
* @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<color::LAB> const *fixed = nullptr,
bool quiet = false);
6 changes: 2 additions & 4 deletions include/lexi.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,8 @@ template <typename Scalar> 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;
}
Expand All @@ -82,9 +81,8 @@ template <typename Scalar> 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;
}
Expand Down
116 changes: 64 additions & 52 deletions src/fitness.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@
#include <utility>

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;
}

Expand All @@ -26,33 +24,40 @@ double offRGB(const color::LAB &lab) {
return std::sqrt(dr * dr + dg * dg + db * db);
}

LexiProduct<double> fitnessFunc(const std::vector<color::LAB> &lab) {
// number of colors
size_t M = lab.size();
std::vector<double> 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<double> fitnessFunc(const std::vector<color::LAB> &lab, size_t M) {
size_t totalM = lab.size();
std::vector<double> 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<size_t> cached_ref; /* for almost-sorted O(n) sorting */
static std::vector<std::pair<size_t, size_t>> ref_combi;
static std::vector<Combination> 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<size_t, size_t>(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;
}
}
Expand All @@ -61,10 +66,10 @@ LexiProduct<double> fitnessFunc(const std::vector<color::LAB> &lab) {
std::vector<std::pair<double, size_t>> 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<double, size_t>(
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(
Expand All @@ -80,11 +85,10 @@ LexiProduct<double> fitnessFunc(const std::vector<color::LAB> &lab) {
return LexiProduct<double>(std::move(fitness));
}

void fill_lab(const std::vector<double> &x, std::vector<color::LAB> &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<double> &x, double L,
std::vector<color::LAB> &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) {
Expand All @@ -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<color::LAB> const *fixed, bool quiet) {
bool freeL = L < 0;
bool noFixed = fixed == nullptr || fixed->size() == 0;
size_t fixedM = fixed == nullptr ? 0 : fixed->size();

CMAES<double, LexiProduct<double>> evo;
Individual<double, LexiProduct<double>> *pop;

const size_t N = M * 2 + 1; //!< number of variables
const size_t N = M * 2 + freeL; //!< number of variables
std::vector<double> 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<double> 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<double, LexiProduct<double>> parameters;
parameters.lambda = (int)(300. * log(N)); /* 100x default */
Expand All @@ -127,23 +140,23 @@ PerceptionResult perceptionL(color::LAB foreground, color::LAB background,
if (!quiet)
std::cout << evo.sayHello() << std::endl;

std::vector<color::LAB> lab(M + 2);
lab[M] = foreground;
lab[M + 1] = background;
std::vector<color::LAB> lab(M + fixedM);
if (!noFixed)
std::copy(fixed->begin(), fixed->end(), lab.begin() + (long)M);

std::vector<LexiProduct<double>> 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<double, LexiProduct<double>>::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);
Expand All @@ -156,10 +169,9 @@ PerceptionResult perceptionL(color::LAB foreground, color::LAB background,
std::cout << "Stop: flags " << flags << std::endl << evo.getStopMessage();

std::vector<color::RGB> 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)};
}
83 changes: 70 additions & 13 deletions src/python.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include <fitness.h>
#include <iostream>

//< RAII wrapper for PyObject
//< RAII wrapper for PyObject*
class PyObj {
public:
PyObj() = delete;
Expand All @@ -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<color::LAB> 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);
Expand All @@ -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",
Expand Down
6 changes: 3 additions & 3 deletions test/testfitness.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ int testcolor() {
{50, 10, -10}})[0],
-12.8001, "fitnessFunc");

check(fitnessFunc(std::vector<color::LAB>{
{50, -200, -200}, {50, 200, 200}, {50, 0, 0}, {50, 0, 0}})[0],
3337.715201, "fitnessFunc (off boundary)");
check(
fitnessFunc(std::vector<color::LAB>{{50, -200, -200}, {50, 200, 200}})[0],
1626.445599, "fitnessFunc (off boundary)");

std::cout << std::endl;
int ret = EXIT_SUCCESS;
Expand Down
Loading

0 comments on commit 54c98dd

Please sign in to comment.