Commit f217586f authored by Richard Hughes's avatar Richard Hughes

huey: add a test program 'gcm-calculate-fudge' to find calibration parameters

The Vec3 found in the EEPROM is very similar to the dark offset, which I
always theorised was required. Just subtracting this from the raw RGB value
gives the wrong answer, and removing it after the calibration matrix is
even more wrong.

Using the values from argyll -v9 we can get the raw USB values. These can
be decoded (we know how) and compared against the argyll XYZ values.
By using two nested loops we can find the optimal value of pre and post
multipliers. This gives us the ideal pre-ratio of 1982, which seems
pretty random. 2000 makes more sense.

Using the new pre-and-post multipliers, and the dark calibration vector,
we can get within 0.8% of the argyll values. That's close enough for me,
it's late here and I'm tired.
parent c36721cc
......@@ -2,5 +2,6 @@
.libs
gcm-dump-to-values
gcm-parse-huey
gcm-calculate-fudge
*.o
*.parsed
......@@ -5,10 +5,24 @@ INCLUDES = \
$(GTK_CFLAGS) \
$(GLIB_CFLAGS)
COLOR_GLIB_LIBS = \
$(top_builddir)/libcolor-glib/libcolor-glib.la
noinst_PROGRAMS = \
gcm-parse-huey \
gcm-calculate-fudge \
gcm-dump-to-values
gcm_calculate_fudge_SOURCES = \
gcm-calculate-fudge.c
gcm_calculate_fudge_LDADD = \
$(COLOR_GLIB_LIBS) \
$(GLIB_LIBS)
gcm_calculate_fudge_CFLAGS = \
$(WARNINGFLAGS_C)
gcm_parse_huey_SOURCES = \
gcm-parse-huey.c
......@@ -21,9 +35,6 @@ gcm_parse_huey_CFLAGS = \
gcm_dump_to_values_SOURCES = \
gcm-dump-to-values.c
COLOR_GLIB_LIBS = \
$(top_builddir)/libcolor-glib/libcolor-glib.la
gcm_dump_to_values_LDADD = \
$(COLOR_GLIB_LIBS) \
$(GLIB_LIBS)
......
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2010 Richard Hughes <richard@hughsie.com>
*
* Licensed under the GNU Lesser General Public License Version 2.1
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <math.h>
#include <glib.h>
#include <libcolor-glib.h>
typedef struct {
guint16 R;
guint16 G;
guint16 B;
} GcmSensorHueyMultiplier;
#if 0
{
guchar threshold_buffer[16];
guchar result_buffer[16];
GcmSensorHueyMultiplier mult;
threshold_buffer[0] = 0x02;
threshold_buffer[1] = 0x8b;
threshold_buffer[2] = 0x03;
threshold_buffer[3] = 0x64;
threshold_buffer[4] = 0x01;
threshold_buffer[5] = 0x9b;
mult.R = gcm_buffer_read_uint16_be (threshold_buffer+0);
mult.G = gcm_buffer_read_uint16_be (threshold_buffer+2);
mult.B = gcm_buffer_read_uint16_be (threshold_buffer+4);
g_debug ("multiplier = %i, %i, %i", mult.R, mult.G, mult.B);
result_buffer[0] = 0x10;
result_buffer[1] = 0x42;
result_buffer[2] = 0x0f;
result_buffer[3] = 0x5b;
result_buffer[4] = 0x0f;
result_buffer[5] = 0x49;
device_rgb[0].R = (gdouble) mult.R / (gdouble)gcm_buffer_read_uint16_be (result_buffer+0);
device_rgb[0].G = (gdouble) mult.G / (gdouble)gcm_buffer_read_uint16_be (result_buffer+2);
device_rgb[0].B = (gdouble) mult.B / (gdouble)gcm_buffer_read_uint16_be (result_buffer+4);
/* get colors as vectors */
color_native_vec3 = gcm_color_get_RGB_Vec3 (&device_rgb[0]);
color_result_vec3 = gcm_color_get_XYZ_Vec3 (&gcm_xyz);
/* the matrix of data is designed to convert from 'device RGB' to XYZ */
gcm_mat33_vector_multiply (&calibration, color_native_vec3, color_result_vec3);
/* scale correct */
gcm_vec3_scalar_multiply (color_result_vec3, HUEY_XYZ_POST_MULTIPLY_SCALE_FACTOR, color_result_vec3);
}
#endif
static gdouble
get_error (const GcmColorXYZ *actual, const GcmColorXYZ *measured)
{
return fabs ((actual->X - measured->X) / measured->X) +
fabs ((actual->Y - measured->Y) / measured->Y) +
fabs ((actual->Z - measured->Z) / measured->Z);
}
/**
* gcm_sensor_huey_convert_device_RGB_to_XYZ:
*
* / X \ (( / R \ ) / d \ / c a l \ )
* | Y | = (( | G | x pre-scale ) - | r | * | m a t | ) x post_scale
* \ Z / (( \ B / ) \ k / \ l c d / )
*
* The device RGB values have to be scaled to something in the same
* scale as the dark calibration. The results then have to be scaled
* after convolving. I assume the first is a standard value, and the
* second scale must be available in the eeprom somewhere.
**/
static void
gcm_sensor_huey_convert_device_RGB_to_XYZ (GcmColorRGB *src, GcmColorXYZ *dest,
GcmMat3x3 *calibration, GcmVec3 *dark_offset,
gdouble pre_scale, gdouble post_scale)
{
GcmVec3 *color_native_vec3;
GcmVec3 *color_result_vec3;
GcmVec3 temp;
/* pre-multiply */
color_native_vec3 = gcm_color_get_RGB_Vec3 (src);
gcm_vec3_scalar_multiply (color_native_vec3, pre_scale, &temp);
/* remove dark calibration */
gcm_vec3_subtract (&temp, dark_offset, &temp);
/* convolve */
color_result_vec3 = gcm_color_get_XYZ_Vec3 (dest);
gcm_mat33_vector_multiply (calibration, &temp, color_result_vec3);
/* post-multiply */
gcm_vec3_scalar_multiply (color_result_vec3, post_scale, color_result_vec3);
}
gint
main (gint argc, gchar *argv[])
{
GcmColorXYZ actual_xyz[5];
GcmColorRGB device_rgb[5];
GcmColorXYZ gcm_xyz;
GcmMat3x3 calibration;
GcmVec3 dark_offset;
gdouble *data;
gdouble error;
gdouble pre_scalar;
gdouble post_scalar;
gdouble min_error;
gdouble best_post_scalar;
gdouble best_pre_scalar;
guint i;
/* get the device RGB measured values */
gcm_color_init_RGB (&device_rgb[0], 0.082935, 0.053567, 0.001294);
gcm_color_init_RGB (&device_rgb[1], 0.066773, 0.150323, 0.009683);
gcm_color_init_RGB (&device_rgb[2], 0.013250, 0.021211, 0.095019);
gcm_color_init_RGB (&device_rgb[3], 0.156415, 0.220809, 0.105035);
gcm_color_init_RGB (&device_rgb[4], 0.000310, 0.000513, 0.000507);
/* get some results from argyll */
gcm_color_init_XYZ (&actual_xyz[0], 82.537676, 42.634870, 2.142396);
gcm_color_init_XYZ (&actual_xyz[1], 61.758330, 122.072291, 17.345163);
gcm_color_init_XYZ (&actual_xyz[2], 36.544046, 19.224371, 161.438049);
gcm_color_init_XYZ (&actual_xyz[3], 174.129280, 180.500098, 179.302163);
gcm_color_init_XYZ (&actual_xyz[4], 0.407554, 0.419799, 0.849899);
/* get the calibration vector */
gcm_vec3_init (&dark_offset, 0.014000, 0.014000, 0.016226);
/* get the calibration matrix */
data = gcm_mat33_get_data (&calibration);
data[0] = 0.154293;
data[1] = -0.009611;
data[2] = 0.038087;
data[3] = -0.002070;
data[4] = 0.122019;
data[5] = 0.003279;
data[6] = -0.000930;
data[7] = 0.001326;
data[8] = 0.253616;
best_post_scalar = 0.0f;
best_pre_scalar = 0.0f;
min_error = 999999.0f;
for (pre_scalar = 1900.0f; pre_scalar < 2100.0f; pre_scalar+=1.0f) {
for (post_scalar = 0.25f; post_scalar < 5.0f; post_scalar += 0.000125f) {
error = 0.0f;
for (i=0; i<5; i++) {
gcm_sensor_huey_convert_device_RGB_to_XYZ (&device_rgb[i],
&gcm_xyz,
&calibration,
&dark_offset,
pre_scalar,
post_scalar);
// g_debug ("gcolor-XYZ = %f,\t%f,\t%f", gcm_xyz.X, gcm_xyz.Y, gcm_xyz.Z);
// g_debug ("argyll-XYZ = %f,\t%f,\t%f", actual_xyz[i].X, actual_xyz[i].Y, actual_xyz[i].Z);
error += get_error (&actual_xyz[i], &gcm_xyz);
}
if (error < min_error) {
min_error = error;
best_post_scalar = post_scalar;
best_pre_scalar = pre_scalar;
}
}
}
g_debug ("best error=%lf%% @ pre %lf, post %lf", min_error * 100.0f, best_pre_scalar, best_post_scalar);
return 0;
}
......@@ -55,7 +55,7 @@ struct _GcmSensorHueyPrivate
GcmMat3x3 calibration_lcd;
GcmMat3x3 calibration_crt;
gfloat calibration_value;
GcmVec3 calibration_vector;
GcmVec3 dark_offset;
gchar unlock_string[5];
};
......@@ -329,7 +329,7 @@ G_DEFINE_TYPE (GcmSensorHuey, gcm_sensor_huey, GCM_TYPE_SENSOR)
/* Picked out of thin air, just to try to match reality...
* I have no idea why we need to do this, although it probably
* indicates we doing something wrong. */
#define HUEY_XYZ_POST_MULTIPLY_SCALE_FACTOR 6880.0f
#define HUEY_XYZ_POST_MULTIPLY_SCALE_FACTOR 3.347250f
/*
* Register map:
......@@ -350,7 +350,7 @@ G_DEFINE_TYPE (GcmSensorHuey, gcm_sensor_huey, GCM_TYPE_SENSOR)
#define HUEY_EEPROM_ADDR_CALIBRATION_TIME_LCD 0x32 /* 4 bytes */
#define HUEY_EEPROM_ADDR_CALIBRATION_DATA_CRT 0x36 /* 36 bytes */
#define HUEY_EEPROM_ADDR_CALIBRATION_TIME_CRT 0x5a /* 4 bytes */
#define HUEY_EEPROM_ADDR_CALIB_VECTOR 0x67 /* 12 bytes */
#define HUEY_EEPROM_ADDR_DARK_OFFSET 0x67 /* 12 bytes */
#define HUEY_EEPROM_ADDR_UNLOCK 0x7a /* 5 bytes */
#define HUEY_EEPROM_ADDR_CALIB_VALUE 0x94 /* 4 bytes */
......@@ -839,10 +839,41 @@ out:
return ret;
}
/* in a dark box, the sensors still report a reading */
#define HUEY_ABSOLUTE_OFFSET_RED 0.000119
#define HUEY_ABSOLUTE_OFFSET_GREEN 0.000119
#define HUEY_ABSOLUTE_OFFSET_BLUE 0.000018
/**
* gcm_sensor_huey_convert_device_RGB_to_XYZ:
*
* / X \ (( / R \ ) / d \ / c a l \ )
* | Y | = (( | G | x pre-scale ) - | r | * | m a t | ) x post_scale
* \ Z / (( \ B / ) \ k / \ l c d / )
*
* The device RGB values have to be scaled to something in the same
* scale as the dark calibration. The results then have to be scaled
* after convolving. I assume the first is a standard value, and the
* second scale must be available in the eeprom somewhere.
**/
static void
gcm_sensor_huey_convert_device_RGB_to_XYZ (GcmColorRGB *src, GcmColorXYZ *dest,
GcmMat3x3 *calibration, GcmVec3 *dark_offset,
gdouble pre_scale, gdouble post_scale)
{
GcmVec3 *color_native_vec3;
GcmVec3 *color_result_vec3;
GcmVec3 temp;
/* pre-multiply */
color_native_vec3 = gcm_color_get_RGB_Vec3 (src);
gcm_vec3_scalar_multiply (color_native_vec3, pre_scale, &temp);
/* remove dark calibration */
gcm_vec3_subtract (&temp, dark_offset, &temp);
/* convolve */
color_result_vec3 = gcm_color_get_XYZ_Vec3 (dest);
gcm_mat33_vector_multiply (calibration, &temp, color_result_vec3);
/* post-multiply */
gcm_vec3_scalar_multiply (color_result_vec3, post_scale, color_result_vec3);
}
static void
gcm_sensor_huey_sample_thread_cb (GSimpleAsyncResult *res, GObject *object, GCancellable *cancellable)
......@@ -856,8 +887,6 @@ gcm_sensor_huey_sample_thread_cb (GSimpleAsyncResult *res, GObject *object, GCan
GcmColorXYZ *tmp;
GcmSensorHueyMultiplier multiplier;
GcmSensorHuey *sensor_huey = GCM_SENSOR_HUEY (sensor);
GcmVec3 *color_native_vec3;
GcmVec3 *color_result_vec3;
GcmMat3x3 *device_calibration;
GcmSensorOutputType output_type;
......@@ -918,12 +947,7 @@ gcm_sensor_huey_sample_thread_cb (GSimpleAsyncResult *res, GObject *object, GCan
goto out;
}
/* get colors as vectors */
color_native_vec3 = gcm_color_get_RGB_Vec3 (&color_native);
color_result_vec3 = gcm_color_get_XYZ_Vec3 (&color_result);
egg_debug ("scaled values: red=%0.6lf, green=%0.6lf, blue=%0.6lf", color_native.R, color_native.G, color_native.B);
egg_debug ("PRE MULTIPLY: %s\n", gcm_vec3_to_string (color_native_vec3));
/* we use different calibration matrices for each output type */
if (output_type == GCM_SENSOR_OUTPUT_TYPE_LCD) {
......@@ -934,13 +958,13 @@ gcm_sensor_huey_sample_thread_cb (GSimpleAsyncResult *res, GObject *object, GCan
device_calibration = &sensor_huey->priv->calibration_crt;
}
/* the matrix of data is designed to convert from 'device RGB' to XYZ */
gcm_mat33_vector_multiply (device_calibration, color_native_vec3, color_result_vec3);
/* scale correct */
gcm_vec3_scalar_multiply (color_result_vec3, HUEY_XYZ_POST_MULTIPLY_SCALE_FACTOR, color_result_vec3);
egg_debug ("POST MULTIPLY: %s\n", gcm_vec3_to_string (color_result_vec3));
/* convert from device RGB to XYZ */
gcm_sensor_huey_convert_device_RGB_to_XYZ (&color_native,
&color_result,
device_calibration,
&sensor_huey->priv->dark_offset,
2000.0f,
HUEY_XYZ_POST_MULTIPLY_SCALE_FACTOR);
/* save result */
tmp = g_new0 (GcmColorXYZ, 1);
......@@ -1088,12 +1112,12 @@ gcm_sensor_huey_startup (GcmSensor *sensor, GError **error)
egg_debug ("device matrix2: %s", gcm_mat33_to_string (&priv->calibration_crt));
/* this number is different on all three hueys */
ret = gcm_sensor_huey_read_register_float (sensor_huey, HUEY_EEPROM_ADDR_CALIBRATION_DATA_CRT, &priv->calibration_value, error);
ret = gcm_sensor_huey_read_register_float (sensor_huey, HUEY_EEPROM_ADDR_CALIB_VALUE, &priv->calibration_value, error);
if (!ret)
goto out;
/* this vector changes between sensor 1 and 3 */
ret = gcm_sensor_huey_read_register_vector (sensor_huey, HUEY_EEPROM_ADDR_CALIBRATION_DATA_CRT, &priv->calibration_vector, error);
ret = gcm_sensor_huey_read_register_vector (sensor_huey, HUEY_EEPROM_ADDR_DARK_OFFSET, &priv->dark_offset, error);
if (!ret)
goto out;
......@@ -1137,10 +1161,10 @@ gcm_sensor_huey_dump (GcmSensor *sensor, GString *data, GError **error)
g_string_append_printf (data, "huey-dump-version:%i\n", 2);
g_string_append_printf (data, "unlock-string:%s\n", priv->unlock_string);
g_string_append_printf (data, "calibration-value:%f\n", priv->calibration_value);
g_string_append_printf (data, "calibration-vector:%f,%f,%f\n",
priv->calibration_vector.v0,
priv->calibration_vector.v1,
priv->calibration_vector.v2);
g_string_append_printf (data, "dark-offset:%f,%f,%f\n",
priv->dark_offset.v0,
priv->dark_offset.v1,
priv->dark_offset.v2);
/* read all the register space */
for (i=0; i<0xff; i++) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment