[gnome-color-manager] huey: add a test program 'gcm-calculate-fudge' to find calibration parameters



commit f217586fba4ef027c3b331095732a1a04a183462
Author: Richard Hughes <richard hughsie com>
Date:   Fri Sep 24 23:38:51 2010 +0100

    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.

 docs/huey/.gitignore            |    1 +
 docs/huey/Makefile.am           |   17 +++-
 docs/huey/gcm-calculate-fudge.c |  195 +++++++++++++++++++++++++++++++++++++++
 libcolor-glib/gcm-sensor-huey.c |   78 ++++++++++------
 4 files changed, 261 insertions(+), 30 deletions(-)
---
diff --git a/docs/huey/.gitignore b/docs/huey/.gitignore
index afa4bb6..91487bd 100644
--- a/docs/huey/.gitignore
+++ b/docs/huey/.gitignore
@@ -2,5 +2,6 @@
 .libs
 gcm-dump-to-values
 gcm-parse-huey
+gcm-calculate-fudge
 *.o
 *.parsed
diff --git a/docs/huey/Makefile.am b/docs/huey/Makefile.am
index 2bc378e..f3d3c81 100644
--- a/docs/huey/Makefile.am
+++ b/docs/huey/Makefile.am
@@ -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)
diff --git a/docs/huey/gcm-calculate-fudge.c b/docs/huey/gcm-calculate-fudge.c
new file mode 100644
index 0000000..4bd0f59
--- /dev/null
+++ b/docs/huey/gcm-calculate-fudge.c
@@ -0,0 +1,195 @@
+/* -*- 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;
+}
diff --git a/libcolor-glib/gcm-sensor-huey.c b/libcolor-glib/gcm-sensor-huey.c
index 1728f3f..9e767a6 100644
--- a/libcolor-glib/gcm-sensor-huey.c
+++ b/libcolor-glib/gcm-sensor-huey.c
@@ -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++) {



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]