summaryrefslogtreecommitdiff
path: root/spectro/i1pro_imp.c
diff options
context:
space:
mode:
Diffstat (limited to 'spectro/i1pro_imp.c')
-rw-r--r--spectro/i1pro_imp.c12093
1 files changed, 12093 insertions, 0 deletions
diff --git a/spectro/i1pro_imp.c b/spectro/i1pro_imp.c
new file mode 100644
index 0000000..2211bd4
--- /dev/null
+++ b/spectro/i1pro_imp.c
@@ -0,0 +1,12093 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * Gretag i1Pro implementation functions
+ *
+ * Author: Graeme W. Gill
+ * Date: 24/11/2006
+ *
+ * Copyright 2006 - 2013 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
+ * see the License2.txt file for licencing details.
+ */
+
+/*
+ If you make use of the instrument driver code here, please note
+ that it is the author(s) of the code who take responsibility
+ for its operation. Any problems or queries regarding driving
+ instruments with the Argyll drivers, should be directed to
+ the Argyll's author(s), and not to any other party.
+
+ If there is some instrument feature or function that you
+ would like supported here, it is recommended that you
+ contact Argyll's author(s) first, rather than attempt to
+ modify the software yourself, if you don't have firm knowledge
+ of the instrument communicate protocols. There is a chance
+ that an instrument could be damaged by an incautious command
+ sequence, and the instrument companies generally cannot and
+ will not support developers that they have not qualified
+ and agreed to support.
+ */
+
+/* TTBD:
+
+ Some things probably aren't quite correct:
+ The way the sensor saturation and optimal target is
+ computed probably doesn't account for the dark level
+ correctly, since the targets are in sensor value,
+ but the comparison is done after subtracting black ??
+ See the Munki implementation for an approach to fix this ??
+
+ It should be possible to add a refresh-display calibration
+ routine based on an emissive scan + the auto-correlation
+ (see i1d3.c). Whether this will noticably improve repeatibility
+ remains to be seen.
+*/
+
+/*
+ Notes:
+
+ Naming of spectral values:
+
+ sensor - the 16 bit values from the sensor including any dummy/shielded values
+ raw - the floating point values of the spectral section
+ absraw - raw after scaling for integration time and gain settings.
+
+ The Rev D seems to die if it is ever given a GET_STATUS. This is why
+ the WinUSB driver can't be used with it.
+
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#include <math.h>
+#if defined(UNIX)
+# include <utime.h>
+#else
+# include <sys/utime.h>
+#endif
+#include <sys/stat.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "rspl.h"
+#else /* SALONEINSTLIB */
+#include <fcntl.h>
+#include "sa_config.h"
+#include "numsup.h"
+#include "rspl1.h"
+#endif /* SALONEINSTLIB */
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "sort.h"
+
+/* Configuration */
+#define ENABLE_2 /* [Def] Enable i1pro2/Rev E driver code, else treat i1pro2 as i1pro */
+#undef USE_HIGH_GAIN_MODE /* [Und] Make use of high gain mode in Rev A-D mode */
+#define USE_THREAD /* Need to use thread, or there are 1.5 second internal */
+ /* instrument delays ! */
+#undef WAIT_FOR_DELAY_TRIGGER /* [Und] Hack to diagnose threading problems */
+#undef ENABLE_WRITE /* [Und] Enable writing of calibration and log data to the EEProm */
+#define ENABLE_NONVCAL /* [Def] Enable saving calibration state between program runs in a file */
+#define ENABLE_NONLINCOR /* [Def] Enable non-linear correction */
+#define WLCALTOUT (24 * 60 * 60) /* [24 Hrs] Wavelength calibration timeout in seconds */
+#define DCALTOUT ( 60 * 60) /* [60 Minuites] Dark Calibration timeout in seconds */
+#define DCALTOUT2 ( 1 * 60 * 60) /* [1 Hr] i1pro2 Dark Calibration timeout in seconds */
+#define WCALTOUT ( 1 * 60 * 60) /* [1 Hr] White Calibration timeout in seconds */
+#define MAXSCANTIME 15.0 /* [15] Maximum scan time in seconds */
+#define SW_THREAD_TIMEOUT (10 * 60.0) /* [10 Min] Switch read thread timeout */
+
+#define SINGLE_READ /* [Def] Use a single USB read for scan to eliminate latency issues. */
+#define HIGH_RES /* [Def] Enable high resolution spectral mode code. Dissable */
+ /* to break dependency on rspl library. */
+
+/* Debug [Und] */
+#undef DEBUG /* Turn on debug printfs */
+#undef PLOT_DEBUG /* Use plot to show readings & processing */
+#undef PLOT_REFRESH /* Plot refresh rate measurement info */
+#undef DUMP_SCANV /* Dump scan readings to a file "i1pdump.txt" */
+#undef DUMP_DARKM /* Append raw dark readings to file "i1pddump.txt" */
+#undef APPEND_MEAN_EMMIS_VAL /* Append averaged uncalibrated reading to file "i1pdump.txt" */
+#undef TEST_DARK_INTERP /* Test out the dark interpolation (need DEBUG for plot) */
+#undef PATREC_DEBUG /* Print & Plot patch/flash recognition information */
+#undef PATREC_ALLBANDS /* Plot all bands of scan */
+#undef IGNORE_WHITE_INCONS /* Ignore define reference reading inconsistency */
+#undef HIGH_RES_DEBUG
+#undef HIGH_RES_PLOT
+#undef PLOT_BLACK_SUBTRACT /* Plot temperature corrected black subtraction */
+#undef FAKE_AMBIENT /* Fake the ambient mode for a Rev A */
+
+#define MATCH_SPOT_OMD /* [Def] Match Original Manufacturers Driver. Reduce */
+ /* integration time and lamp turn on time */
+
+#define DISP_INTT 2.0 /* Seconds per reading in display spot mode */
+ /* More improves repeatability in dark colors, but limits */
+ /* the maximum brightness level befor saturation. */
+ /* A value of 2.0 seconds has a limit of about 110 cd/m^2 */
+#define DISP_INTT2 0.8 /* High brightness display spot mode seconds per reading, */
+ /* Should be good up to 275 cd/m^2 */
+#define DISP_INTT3 0.3 /* High brightness display spot mode seconds per reading, */
+ /* Should be good up to 700 cd/m^2 */
+
+#define ADARKINT_MAX 2.0 /* Max cal time for adaptive dark cal */
+#define ADARKINT_MAX2 4.0 /* Max cal time for adaptive dark cal Rev E or no high gain */
+
+#define EMIS_SCALE_FACTOR 1.0 /* Emission mode scale factor */
+#define AMB_SCALE_FACTOR (1.0/3.141592654) /* Ambient mode scale factor - convert */
+ /* from Lux to Lux/PI */
+ /* These factors get the same behaviour as the GMB drivers. */
+
+#define NSEN_MAX 140 /* Maximum nsen value we can cope with */
+
+/* High res mode settings */
+#define HIGHRES_SHORT 350
+#define HIGHRES_LONG 740
+#define HIGHRES_WIDTH (10.0/3.0) /* (The 3.3333 spacing and lanczos2 seems a good combination) */
+#define HIGHRES_REF_MIN 375.0 /* Too much stray light below this in reflective mode */
+ /* (i1pro2 could go lower with different correction) */
+
+#include "i1pro.h"
+#include "i1pro_imp.h"
+
+/* - - - - - - - - - - - - - - - - - - */
+#define LAMP_OFF_TIME 1500 /* msec to make sure lamp is dark for dark measurement */
+#define PATCH_CONS_THR 0.1 /* Dark measurement consistency threshold */
+#define TRIG_DELAY 10 /* Measure trigger delay to allow pending read, msec */
+
+/* - - - - - - - - - - - - - - - - - - */
+
+#if defined(DEBUG) || defined(PLOT_DEBUG) || defined(PATREC_DEBUG)
+# include <plot.h>
+#endif
+
+
+#if defined(DEBUG) || defined(PLOT_DEBUG) || defined(HIGH_RES_PLOT) || defined(PATREC_DEBUG)
+static int disdebplot = 0;
+
+#define DISDPLOT disdebplot = 1;
+#define ENDPLOT disdebplot = 0;
+
+#else
+
+#define DISDPLOT
+#define ENDPLOT
+
+#endif /* DEBUG */
+
+#if defined(DEBUG) || defined(PLOT_DEBUG) || defined(PATREC_DEBUG)
+/* ============================================================ */
+/* Debugging support */
+
+/* Plot a CCD spectra */
+void plot_raw(double *data) {
+ int i;
+ double xx[NSEN_MAX];
+ double yy[NSEN_MAX];
+
+ if (disdebplot)
+ return;
+
+ for (i = 0; i < 128; i++) {
+ xx[i] = (double)i;
+ yy[i] = data[i];
+ }
+ do_plot(xx, yy, NULL, NULL, 128);
+}
+
+/* Plot two CCD spectra */
+void plot_raw2(double *data1, double *data2) {
+ int i;
+ double xx[128];
+ double y1[128];
+ double y2[128];
+
+ if (disdebplot)
+ return;
+
+ for (i = 0; i < 128; i++) {
+ xx[i] = (double)i;
+ y1[i] = data1[i];
+ y2[i] = data2[i];
+ }
+ do_plot(xx, y1, y2, NULL, 128);
+}
+
+/* Plot a converted spectra */
+void plot_wav(i1proimp *m, int hires, double *data) {
+ int i;
+ double xx[128];
+ double yy[128];
+
+ if (disdebplot)
+ return;
+
+ for (i = 0; i < m->nwav[hires]; i++) {
+ xx[i] = XSPECT_WL(m->wl_short[hires], m->wl_long[hires], m->nwav[hires], i);
+ yy[i] = data[i];
+ }
+ do_plot(xx, yy, NULL, NULL, m->nwav[hires]);
+}
+
+/* Plot two converted spectra for the current res. mode */
+void plot_wav_2(i1proimp *m, int hires, double *data1, double *data2) {
+ int i;
+ double xx[128];
+ double y1[128];
+ double y2[128];
+
+ if (disdebplot)
+ return;
+
+ for (i = 0; i < m->nwav[hires]; i++) {
+ xx[i] = XSPECT_WL(m->wl_short[hires], m->wl_long[hires], m->nwav[hires], i);
+ y1[i] = data1[i];
+ y2[i] = data2[i];
+ }
+ do_plot(xx, y1, y2, NULL, m->nwav[hires]);
+}
+
+#endif /* PLOT_DEBUG */
+
+/* ============================================================ */
+
+/* Implementation struct */
+
+/* Add an implementation structure */
+i1pro_code add_i1proimp(i1pro *p) {
+ i1proimp *m;
+
+ if ((m = (i1proimp *)calloc(1, sizeof(i1proimp))) == NULL) {
+ a1logd(p->log,1,"add_i1proimp malloc %ld bytes failed (1)\n",sizeof(i1proimp));
+ return I1PRO_INT_MALLOC;
+ }
+ m->p = p;
+
+ /* EEProm data store */
+ if ((m->data = new_i1data(m)) == NULL)
+ return I1PRO_INT_CREATE_EEPROM_STORE;
+
+ m->lo_secs = 2000000000; /* A very long time */
+ m->msec = msec_time();
+
+ p->m = (void *)m;
+ return I1PRO_OK;
+}
+
+/* Shutdown instrument, and then destroy */
+/* implementation structure */
+void del_i1proimp(i1pro *p) {
+
+ a1logd(p->log,5,"i1pro_del called\n");
+
+#ifdef ENABLE_NONVCAL
+ /* Touch it so that we know when the instrument was last open */
+ i1pro_touch_calibration(p);
+#endif /* ENABLE_NONVCAL */
+
+ if (p->m != NULL) {
+ int i, j;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s;
+ i1pro_code ev;
+
+ if (p->itype != instI1Pro2 && (ev = i1pro_update_log(p)) != I1PRO_OK) {
+ a1logd(p->log,2,"i1pro_update_log: Updating the cal and log parameters to"
+ " EEProm failed failed\n");
+ }
+
+ /* i1pro_terminate_switch() seems to fail on a rev A & Rev C ?? */
+ if (m->th != NULL) { /* Terminate switch monitor thread */
+ m->th_term = 1; /* Tell thread to exit on error */
+ i1pro_terminate_switch(p);
+
+ for (i = 0; m->th_termed == 0 && i < 5; i++)
+ msec_sleep(50); /* Wait for thread to terminate */
+ if (i >= 5) {
+ a1logd(p->log,5,"i1pro switch thread termination failed\n");
+ }
+ m->th->del(m->th);
+ usb_uninit_cancel(&m->cancelt); /* Don't need cancel token now */
+ }
+ a1logd(p->log,5,"i1pro switch thread terminated\n");
+
+ /* Free any per mode data */
+ for (i = 0; i < i1p_no_modes; i++) {
+ s = &m->ms[i];
+
+ free_dvector(s->dark_data, -1, m->nraw-1);
+ free_dvector(s->dark_data2, -1, m->nraw-1);
+ free_dvector(s->dark_data3, -1, m->nraw-1);
+ free_dvector(s->white_data, -1, m->nraw-1);
+ free_dmatrix(s->idark_data, 0, 3, -1, m->nraw-1);
+
+ free_dvector(s->cal_factor[0], 0, m->nwav[0]-1);
+ free_dvector(s->cal_factor[1], 0, m->nwav[1]-1);
+ }
+
+ /* Free EEProm key data */
+ if (m->data != NULL)
+ m->data->del(m->data);
+
+ /* Free all Rev E and high res raw2wav filters */
+ for (i = 0; i < 2; i++) {
+ for (j = 0; j < 2; j++) {
+ if (m->mtx_c[i][j].index != NULL)
+ free(m->mtx_c[i][j].index);
+ if (m->mtx_c[i][j].nocoef != NULL)
+ free(m->mtx_c[i][j].nocoef);
+ if (m->mtx_c[i][j].coef != NULL)
+ free(m->mtx_c[i][j].coef);
+ }
+ }
+
+ /* Free RevE straylight arrays */
+ for (i = 0; i < 2; i++) {
+ if (m->straylight[i] != NULL)
+ free_dmatrix(m->straylight[i], 0, m->nwav[i]-1, 0, m->nwav[i]-1);
+ }
+
+ /* RevA-D high res. recal support */
+ if (m->raw2wav != NULL)
+ m->raw2wav->del(m->raw2wav);
+
+ free(m);
+ p->m = NULL;
+ }
+}
+
+/* ============================================================ */
+/* High level functions */
+
+/* Initialise our software state from the hardware */
+i1pro_code i1pro_imp_init(i1pro *p) {
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_code ev = I1PRO_OK;
+ unsigned char *eeprom; /* EEProm contents, i1pro = half, i1pro2 = full */
+ int len = 8192;
+
+ a1logd(p->log,5,"i1pro_init:\n");
+
+ /* Revert to i1pro if i1pro2 driver is not enabled */
+ if (p->itype == instI1Pro2
+#ifdef ENABLE_2
+ && getenv("ARGYLL_DISABLE_I1PRO2_DRIVER") != NULL /* Disabled by environment */
+#endif
+ ) {
+ p->itype = instI1Pro;
+ }
+
+ if (p->itype != instI1Monitor
+ && p->itype != instI1Pro
+ && p->itype != instI1Pro2)
+ return I1PRO_UNKNOWN_MODEL;
+
+ m->trig = inst_opt_trig_user;
+ m->scan_toll_ratio = 1.0;
+
+ /* Take conservative approach to when the light was last on. */
+ /* Assume it might have been on right before init was called again. */
+ m->slamponoff = msec_time();
+ m->llampoffon = msec_time();
+ m->llamponoff = msec_time();
+
+ if ((ev = i1pro_reset(p, 0x1f)) != I1PRO_OK)
+ return ev;
+
+ usb_init_cancel(&m->cancelt); /* Init cancel token */
+
+#ifdef USE_THREAD
+ /* Setup the switch monitoring thread */
+ if ((m->th = new_athread(i1pro_switch_thread, (void *)p)) == NULL)
+ return I1PRO_INT_THREADFAILED;
+#endif
+
+ /* Get the current misc. status, fwrev etc */
+ if ((ev = i1pro_getmisc(p, &m->fwrev, NULL, &m->maxpve, NULL, &m->powmode)) != I1PRO_OK)
+ return ev;
+ a1logd(p->log,2,"Firmware rev = %d, max +ve value = 0x%x\n",m->fwrev, m->maxpve);
+
+ if (p->itype == instI1Pro2 && m->fwrev < 600) { /* Hmm */
+ a1logd(p->log,2, "Strange, firmware isn't up to i1pro2 but has extra pipe..revert to i1pro driver\n",m->fwrev);
+ p->itype = instI1Pro;
+ }
+
+ /* Get the EEProm size */
+ m->eesize = 8192; /* Rev A..D */
+ if (p->itype == instI1Pro2) {
+#ifdef NEVER
+// ~~99 Hmm. makes it worse. Why ???
+// /* Make sure LED sequence is finished, because it interferes with EEProm read! */
+// if ((ev = i1pro2_indLEDoff(p)) != I1PRO_OK)
+// return ev;
+#endif
+
+ if ((ev = i1pro2_geteesize(p, &m->eesize)) != I1PRO_OK) {
+ return ev;
+ }
+
+ }
+
+ if (m->eesize < 8192) {
+ a1logd(p->log,2,"Strange, EEProm size is < 8192!\n",m->fwrev);
+ return I1PRO_HW_EE_SIZE;
+ }
+
+ if ((eeprom = (unsigned char *)malloc(m->eesize)) == NULL) {
+ a1logd(p->log,1,"Malloc %d bytes for eeprom failed\n",m->eesize);
+ return I1PRO_INT_MALLOC;
+ }
+
+ /* Read the EEProm */
+ if ((ev = i1pro_readEEProm(p, eeprom, 0, m->eesize)) != I1PRO_OK) {
+ free(eeprom);
+ return ev;
+ }
+
+ if (p->itype == instI1Pro2) {
+ /* Get the Chip ID (This doesn't work until after reading the EEProm !) */
+ if ((ev = i1pro2_getchipid(p, m->chipid)) != I1PRO_OK) {
+ free(eeprom);
+ return ev;
+ }
+ }
+
+ /* Parse the i1pro data */
+ if ((ev = m->data->parse_eeprom(m->data, eeprom, m->eesize, 0)) != I1PRO_OK) {
+ free(eeprom);
+ return ev;
+ }
+
+ /* Parse the i1pro2 extra data */
+ if (p->itype == instI1Pro2) {
+ if ((ev = m->data->parse_eeprom(m->data, eeprom, m->eesize, 1)) != I1PRO_OK) {
+ free(eeprom);
+ return ev;
+ }
+ }
+ free(eeprom); eeprom = NULL;
+
+ /* Setup various calibration parameters from the EEprom */
+ {
+ int *ip, i, xcount;
+ unsigned int count;
+ double *dp;
+
+ /* Information about the instrument */
+
+ if ((ip = m->data->get_ints(m->data, &count, key_serno)) == NULL || count < 1)
+ return I1PRO_HW_CALIBINFO;
+ m->serno = ip[0];
+ a1logd(p->log,2,"Serial number = %d\n",m->serno);
+ sprintf(m->sserno,"%ud",m->serno);
+
+ if ((ip = m->data->get_ints(m->data, &count, key_dom)) == NULL || count < 1)
+ return I1PRO_HW_CALIBINFO;
+ m->dom = ip[0];
+ a1logd(p->log,2, "Date of manufactur = %d-%d-%d\n",
+ m->dom/1000000, (m->dom/10000) % 100, m->dom % 10000);
+
+ if ((ip = m->data->get_ints(m->data, &count, key_cpldrev)) == NULL || count < 1)
+ return I1PRO_HW_CALIBINFO;
+ m->cpldrev = ip[0];
+ a1logd(p->log,2,"CPLD rev = %d\n",m->cpldrev);
+
+ if ((ip = m->data->get_ints(m->data, &count, key_capabilities)) == NULL || count < 1)
+ return I1PRO_HW_CALIBINFO;
+ m->capabilities = ip[0];
+ if (m->capabilities & 0x6000) /* Has ambient */
+ m->capabilities2 |= I1PRO_CAP2_AMBIENT; /* Mimic in capabilities2 */
+ a1logd(p->log,2,"Capabilities flag = 0x%x\n",m->capabilities);
+ if (m->capabilities & 0x6000)
+ a1logd(p->log,2," Can read ambient\n");
+
+ if ((ip = m->data->get_ints(m->data, &count, key_physfilt)) == NULL || count < 1)
+ return I1PRO_HW_CALIBINFO;
+ m->physfilt = ip[0];
+ if (m->physfilt == 0x82)
+ m->capabilities2 |= I1PRO_CAP2_UV_FILT; /* Mimic in cap 2 */
+ a1logd(p->log,2,"Physical filter flag = 0x%x\n",m->physfilt);
+ if (m->physfilt == 0x80)
+ a1logd(p->log,2," No filter fitted\n");
+ else if (m->physfilt == 0x81)
+ a1logd(p->log,2," Emission only ??\n");
+ else if (m->physfilt == 0x82)
+ a1logd(p->log,2," Is fitted with Ultra Violet Filter\n");
+
+ /* Underlying calibration information */
+
+ m->nsen = 128;
+ m->nraw = 128;
+ if (p->itype == instI1Pro2) {
+ int clkusec, subdiv, xraw, nraw;
+ if ((ev = i1pro2_getmeaschar(p, &clkusec, &xraw, &nraw, &subdiv)) != I1PRO_OK)
+ return ev;
+ m->intclkp2 = clkusec * 1e-6; /* Rev E integration clock period, ie. 36 usec */
+ m->subclkdiv2 = subdiv; /* Rev E sub clock divider, ie. 136 */
+
+ m->nsen = nraw + xraw;
+ if (clkusec != 36 || xraw != 6 || nraw != 128 || subdiv != 136)
+ return I1PRO_HW_UNEX_SPECPARMS;
+
+ if (m->nsen > NSEN_MAX) /* Static allocation assumed */
+ return I1PRO_HW_UNEX_SPECPARMS;
+ }
+ if (m->data->get_ints(m->data, &m->nwav[0], key_mtx_index) == 0)
+ return I1PRO_HW_CALIBINFO;
+ if (m->nwav[0] != 36)
+ return I1PRO_HW_CALIBINFO;
+ m->wl_short[0] = 380.0; /* Normal res. range */
+ m->wl_long[0] = 730.0;
+
+ /* Fill high res in too */
+ m->wl_short[1] = HIGHRES_SHORT;
+ m->wl_long[1] = HIGHRES_LONG;
+ m->nwav[1] = (int)((m->wl_long[1]-m->wl_short[1])/HIGHRES_WIDTH + 0.5) + 1;
+
+ if ((dp = m->data->get_doubles(m->data, &count, key_hg_factor)) == NULL || count < 1)
+ return I1PRO_HW_CALIBINFO;
+ m->highgain = dp[0];
+ a1logd(p->log,2,"High gain = %.10f\n",m->highgain);
+
+ if ((m->lin0 = m->data->get_doubles(m->data, &m->nlin0, key_ng_lin)) == NULL
+ || m->nlin0 < 1)
+ return I1PRO_HW_CALIBINFO;
+
+ if ((m->lin1 = m->data->get_doubles(m->data, &m->nlin1, key_hg_lin)) == NULL
+ || m->nlin1 < 1)
+ return I1PRO_HW_CALIBINFO;
+
+ if (p->log->debug >= 2) {
+ char oline[200] = { '\000' }, *bp = oline;
+
+ bp += sprintf(bp,"Normal non-lin =");
+ for(i = 0; i < m->nlin0; i++)
+ bp += sprintf(bp," %1.10f",m->lin0[i]);
+ bp += sprintf(bp,"\n");
+ a1logd(p->log,2,oline);
+
+ bp = oline;
+ bp += sprintf(bp,"High Gain non-lin =");
+ for(i = 0; i < m->nlin1; i++)
+ bp += sprintf(bp," %1.10f",m->lin1[i]);
+ bp += sprintf(bp,"\n");
+ a1logd(p->log,2,oline);
+ }
+
+ if ((dp = m->data->get_doubles(m->data, &count, key_min_int_time)) == NULL || count < 1)
+ return I1PRO_HW_CALIBINFO;
+ m->min_int_time = dp[0];
+
+ /* And then override it */
+ if (p->itype == instI1Pro2) {
+ m->min_int_time = m->subclkdiv2 * m->intclkp2; /* 0.004896 */
+ } else {
+ if (m->fwrev >= 301)
+ m->min_int_time = 0.004716;
+ else
+ m->min_int_time = 0.00884; /* == 1 sub clock */
+ }
+
+ if ((dp = m->data->get_doubles(m->data, &count, key_max_int_time)) == NULL || count < 1)
+ return I1PRO_HW_CALIBINFO;
+ m->max_int_time = dp[0];
+
+
+ if ((m->mtx_o.index = m->data->get_ints(m->data, &count, key_mtx_index)) == NULL
+ || count != m->nwav[0])
+ return I1PRO_HW_CALIBINFO;
+
+ if ((m->mtx_o.nocoef = m->data->get_ints(m->data, &count, key_mtx_nocoef)) == NULL
+ || count != m->nwav[0])
+ return I1PRO_HW_CALIBINFO;
+
+ for (xcount = i = 0; i < m->nwav[0]; i++) /* Count number expected in matrix coeffs */
+ xcount += m->mtx_o.nocoef[i];
+
+ if ((m->mtx_o.coef = m->data->get_doubles(m->data, &count, key_mtx_coef)) == NULL
+ || count != xcount)
+ return I1PRO_HW_CALIBINFO;
+
+ if ((m->white_ref[0] = m->data->get_doubles(m->data, &count, key_white_ref)) == NULL
+ || count != m->nwav[0]) {
+ if (p->itype != instI1Monitor)
+ return I1PRO_HW_CALIBINFO;
+ m->white_ref[0] = NULL;
+ }
+
+ if ((m->emis_coef[0] = m->data->get_doubles(m->data, &count, key_emis_coef)) == NULL
+ || count != m->nwav[0])
+ return I1PRO_HW_CALIBINFO;
+
+ if ((m->amb_coef[0] = m->data->get_doubles(m->data, &count, key_amb_coef)) == NULL
+ || count != m->nwav[0]) {
+ if (p->itype != instI1Monitor
+ && m->capabilities & 0x6000) /* Expect ambient calibration */
+ return I1PRO_HW_CALIBINFO;
+ m->amb_coef[0] = NULL;
+ }
+ /* Default to original EEProm raw to wav filters values*/
+ m->mtx[0][0] = m->mtx_o; /* Std res reflective */
+ m->mtx[0][1] = m->mtx_o; /* Std res emissive */
+
+ if ((ip = m->data->get_ints(m->data, &count, key_sens_target)) == NULL || count < 1)
+ return I1PRO_HW_CALIBINFO;
+ m->sens_target = ip[0];
+
+ if ((ip = m->data->get_ints(m->data, &count, key_sens_dark)) == NULL || count < 1)
+ return I1PRO_HW_CALIBINFO;
+ m->sens_dark = ip[0];
+
+ if ((ip = m->data->get_ints(m->data, &count, key_ng_sens_sat)) == NULL || count < 1)
+ return I1PRO_HW_CALIBINFO;
+ m->sens_sat0 = ip[0];
+
+ if ((ip = m->data->get_ints(m->data, &count, key_hg_sens_sat)) == NULL || count < 1)
+ return I1PRO_HW_CALIBINFO;
+ m->sens_sat1 = ip[0];
+
+ a1logd(p->log,2,"sens_target %d, sens_dark %d, sens_sat0 %d, sens_sat1 %d\n",
+ m->sens_target, m->sens_dark, m->sens_sat0, m->sens_sat1);
+
+ /* Then read the log data. Don't fatal error if there is a problem with this. */
+ for (;;) {
+
+ /* Total Measure (Emis/Remis/Ambient/Trans/Cal) count */
+ if ((ip = m->data->get_ints(m->data, &count, key_meascount)) == NULL || count < 1)
+ break;
+ m->meascount = ip[0];
+
+ /* Remspotcal last calibration date */
+ if ((ip = m->data->get_ints(m->data, &count, key_caldate)) == NULL || count < 1)
+ break;
+ m->caldate = ip[0];
+
+ /* Remission spot measure count at last Remspotcal. */
+ if ((ip = m->data->get_ints(m->data, &count, key_calcount)) == NULL || count < 1)
+ break;
+ m->calcount = ip[0];
+
+ /* Last remision spot reading integration time */
+ if ((dp = m->data->get_doubles(m->data, &count, key_rpinttime)) == NULL || count < 1)
+ break;
+ m->rpinttime = dp[0];
+
+ /* Remission spot measure count */
+ if ((ip = m->data->get_ints(m->data, &count, key_rpcount)) == NULL || count < 1)
+ break;
+ m->rpcount = ip[0];
+
+ /* Remission scan measure count (??) */
+ if ((ip = m->data->get_ints(m->data, &count, key_acount)) == NULL || count < 1)
+ break;
+ m->acount = ip[0];
+
+ /* Total lamp usage time in seconds (??) */
+ if ((dp = m->data->get_doubles(m->data, &count, key_lampage)) == NULL || count < 1)
+ break;
+ m->lampage = dp[0];
+ a1logd(p->log,3,"Read log information OK\n");
+
+ break;
+ }
+ }
+
+ /* Read Rev E specific keys */
+ if (p->itype == instI1Pro2) {
+ int i, j;
+ double *dp;
+ int *sip;
+ unsigned int count;
+ int *ip;
+
+ /* Capability bits */
+ if ((ip = m->data->get_ints(m->data, &count, key2_capabilities)) == NULL
+ || count != 1)
+ return I1PRO_HW_CALIBINFO;
+ m->capabilities2 = *ip;
+ if (p->log->debug >= 2) {
+ a1logd(p->log,2,"Capabilities2 flag = 0x%x\n",m->capabilities2);
+ if (m->capabilities2 & I1PRO_CAP2_AMBIENT)
+ a1logd(p->log,2," Can read ambient\n");
+ if (m->capabilities2 & I1PRO_CAP2_WL_LED)
+ a1logd(p->log,2," Has Wavelength Calibration LED\n");
+ if (m->capabilities2 & I1PRO_CAP2_UV_LED)
+ a1logd(p->log,2," Has Ultra Violet LED\n");
+ if (m->capabilities2 & I1PRO_CAP2_ZEB_RUL)
+ a1logd(p->log,2," Has Zebra Ruler sensor\n");
+ if (m->capabilities2 & I1PRO_CAP2_IND_LED)
+ a1logd(p->log,2," Has user indicator LEDs\n");
+ if (m->capabilities2 & I1PRO_CAP2_UV_FILT)
+ a1logd(p->log,2," Is fitted with Ultra Violet Filter\n");
+ }
+
+ if (m->capabilities2 & I1PRO_CAP2_WL_LED) {
+ /* wavelength LED calibration integration time (0.56660) */
+ if ((dp = m->data->get_doubles(m->data, &count, key2_wlcal_intt)) == NULL
+ || count != 1)
+ return I1PRO_HW_CALIBINFO;
+ m->wl_cal_inttime = *dp;
+
+ /* Wavelength calibration minimum level */
+ if ((ip = m->data->get_ints(m->data, &count, key2_wlcal_minlev)) == NULL
+ || count != 1)
+ return I1PRO_HW_CALIBINFO;
+ /* Normalize it to 1.0 seconds (ie. 500/0.56660) */
+ m->wl_cal_min_level = (double)(*ip) / m->wl_cal_inttime;
+
+ /* wavelength LED measurement expected FWHM in nm */
+ if ((dp = m->data->get_doubles(m->data, &count, key2_wlcal_fwhm)) == NULL
+ || count != 1)
+ return I1PRO_HW_CALIBINFO;
+ m->wl_cal_fwhm = *dp;
+
+ /* wavelength LED measurement FWHM tollerance in nm */
+ if ((dp = m->data->get_doubles(m->data, &count, key2_wlcal_fwhm_tol)) == NULL
+ || count != 1)
+ return I1PRO_HW_CALIBINFO;
+ m->wl_cal_fwhm_tol = *dp;
+
+ /* wavelength LED reference spectrum */
+ if ((m->wl_led_spec = m->data->get_doubles(m->data, &m->wl_led_count, key2_wlcal_spec)) == NULL)
+ return I1PRO_HW_CALIBINFO;
+
+ /* wavelength LED spectraum reference offset */
+ if ((ip = m->data->get_ints(m->data, &count, key2_wlcal_ooff)) == NULL
+ || count != 1)
+ return I1PRO_HW_CALIBINFO;
+ m->wl_led_ref_off = *ip;
+ /* Hmm. this is odd, but it doesn't work correctly otherwise... */
+ m->wl_led_ref_off--;
+
+ /* wavelength calibration maximum error */
+ if ((dp = m->data->get_doubles(m->data, &count, key2_wlcal_max)) == NULL
+ || count != 1)
+ return I1PRO_HW_CALIBINFO;
+ m->wl_err_max = *dp;
+ }
+
+ /* CCD bin to wavelength polinomial */
+ if ((m->wlpoly1 = m->data->get_doubles(m->data, &count, key2_wlpoly_1)) == NULL || count != 4)
+ return I1PRO_HW_CALIBINFO;
+
+ if ((m->wlpoly2 = m->data->get_doubles(m->data, &count, key2_wlpoly_2)) == NULL || count != 4)
+ return I1PRO_HW_CALIBINFO;
+
+ /* Stray light compensation. Note that 16 bit numbers are signed. */
+ if ((sip = m->data->get_shorts(m->data, &count, key2_straylight)) == NULL
+ || count != (36 * 36))
+ return I1PRO_HW_CALIBINFO;
+
+ /* stray light scale factor */
+ if ((dp = m->data->get_doubles(m->data, &count, key2_straylight_scale)) == NULL
+ || count != 1)
+ return I1PRO_HW_CALIBINFO;
+
+ /* Convert from ints to floats */
+ m->straylight[0] = dmatrixz(0, 35, 0, 35);
+ for (i = 0; i < 36; i++) {
+ for (j = 0; j < 36; j++) {
+ m->straylight[0][i][j] = *dp * sip[i * 36 + j]/32767.0;
+ if (i == j)
+ m->straylight[0][i][j] += 1.0;
+ }
+
+ }
+
+ if (p->log->debug >= 7) {
+ a1logd(p->log,7,"Stray Light matrix:\n");
+ for(i = 0; i < 36; i++) {
+ double sum = 0.0;
+ for (j = 0; j < 36; j++) {
+ sum += m->straylight[0][i][j];
+ a1logd(p->log,7," Wt %d = %f\n",j, m->straylight[0][i][j]);
+ }
+ a1logd(p->log,7," Sum = %f\n",sum);
+ }
+ }
+
+#ifdef NEVER
+ /* Plot raw2wav polinomials for Rev E */
+ {
+ double *xx;
+ double *y1, *y2; /* Rev E poly1 and poly2 */
+ double d1, d2;
+ int i, k;
+
+ xx = dvector(0, m->nraw); /* X index = raw bin */
+ y1 = dvector(0, m->nraw); /* Y = nm */
+ y2 = dvector(0, m->nraw); /* Y = nm */
+
+ d1 = d2 = 0.0;
+ for (i = 0; i < m->nraw; i++) {
+ double iv, v1, v2;
+ xx[i] = i;
+
+ iv = (double)(128-i);
+
+ for (v1 = m->wlpoly1[4-1], k = 4-2; k >= 0; k--)
+ v1 = v1 * iv + m->wlpoly1[k];
+ y1[i] = v1;
+ d1 += fabs(y2[i] - yy[i]);
+
+ for (v2 = m->wlpoly2[4-1], k = 4-2; k >= 0; k--)
+ v2 = v2 * iv + m->wlpoly2[k];
+ y2[i] = v2;
+ d2 += fabs(y3[i] - yy[i]);
+
+// printf("ix %d, poly1 %f, poly2 %f, del12 %f\n",i, y1[i], y2[i], y2[i] - y1[i]);
+ }
+ printf("Avge del of poly1 = %f, poly2 = %f\n",d1/128.0, d2/128.0);
+
+ printf("CCD bin to wavelength mapping of RevE polinomial:\n");
+ do_plot6(xx, y1, y2, NULL, NULL, NULL, NULL, m->nraw);
+ free_dvector(xx, 0, m->nraw);
+ free_dvector(y1, 0, m->nraw);
+ free_dvector(y2, 0, m->nraw);
+ }
+#endif
+
+ }
+
+ /* Set up the current state of each mode */
+ {
+ int i, j;
+ i1pro_state *s;
+
+ /* First set state to basic configuration */
+ /* (We assume it's been zero'd) */
+ for (i = 0; i < i1p_no_modes; i++) {
+ s = &m->ms[i];
+
+ s->mode = i;
+
+ /* Default to an emissive configuration */
+ s->targoscale = 1.0; /* Default full scale */
+ s->targmaxitime = 2.0; /* Maximum integration time to aim for */
+ s->targoscale2 = 0.15; /* Proportion of targoscale to meed etargmaxitime2 (!higain) */
+ s->gainmode = 0; /* Normal gain mode */
+
+ s->inttime = 0.5; /* Integration time */
+ s->lamptime = 0.50; /* Lamp turn on time (up to 1.0 sec is better, */
+
+ s->wl_valid = 0;
+ s->wl_led_off = m->wl_led_ref_off;
+
+ s->dark_valid = 0; /* Dark cal invalid */
+ s->dark_data = dvectorz(-1, m->nraw-1);
+ s->dark_data2 = dvectorz(-1, m->nraw-1);
+ s->dark_data3 = dvectorz(-1, m->nraw-1);
+
+ s->cal_valid = 0; /* Scale cal invalid */
+ s->cal_factor[0] = dvectorz(0, m->nwav[0]-1);
+ s->cal_factor[1] = dvectorz(0, m->nwav[1]-1);
+ s->white_data = dvectorz(-1, m->nraw-1);
+
+ s->idark_valid = 0; /* Dark cal invalid */
+ s->idark_data = dmatrixz(0, 3, -1, m->nraw-1);
+
+ s->min_wl = 0.0; /* No minimum by default */
+
+ s->dark_int_time = DISP_INTT; /* 2.0 */
+ s->dark_int_time2 = DISP_INTT2; /* 0.8 */
+ s->dark_int_time3 = DISP_INTT3; /* 0.3 */
+
+ s->idark_int_time[0] = s->idark_int_time[2] = m->min_int_time;
+ if (p->itype == instI1Pro2) {
+ s->idark_int_time[1] = s->idark_int_time[3] = ADARKINT_MAX2; /* 4.0 */
+ } else {
+#ifdef USE_HIGH_GAIN_MODE
+ s->idark_int_time[1] = s->idark_int_time[3] = ADARKINT_MAX; /* 2.0 */
+#else
+ s->idark_int_time[1] = s->idark_int_time[3] = ADARKINT_MAX2; /* 4.0 */
+#endif
+ }
+
+ s->want_calib = 1; /* Do an initial calibration */
+ s->want_dcalib = 1;
+ }
+
+ /* Then add mode specific settings */
+ for (i = 0; i < i1p_no_modes; i++) {
+ s = &m->ms[i];
+ switch(i) {
+ case i1p_refl_spot:
+ s->targoscale = 1.0; /* Optimised sensor scaling to full */
+ s->reflective = 1;
+ s->adaptive = 1;
+ s->inttime = 0.02366; /* Should get this from the log ?? */
+ s->dark_int_time = s->inttime;
+
+ s->dadaptime = 0.10;
+ s->wadaptime = 0.10;
+#ifdef MATCH_SPOT_OMD
+ s->lamptime = 0.18; /* Lamp turn on time, close to OMD */
+ /* (Not ideal, but partly compensated by calib.) */
+ /* (The actual value the OMD uses is 0.20332) */
+ s->dcaltime = 0.05; /* Longer than the original driver for lower */
+ /* noise, and lamptime has been reduces to */
+ /* compensate. (OMD uses 0.014552) */
+ s->wcaltime = 0.05;
+ s->dreadtime = 0.05;
+ s->wreadtime = 0.05;
+#else
+ s->lamptime = 0.5; /* This should give better accuracy, and better */
+ s->dcaltime = 0.5; /* match the scan readings. Difference is about */
+ s->wcaltime = 0.5; /* 0.1 DE though, but would be lost in the */
+ s->dreadtime = 0.5; /* repeatability noise... */
+ s->wreadtime = 0.5;
+#endif
+ s->maxscantime = 0.0;
+ s->min_wl = HIGHRES_REF_MIN;/* Too much stray light below this */
+ /* given low illumination < 375nm */
+ break;
+ case i1p_refl_scan:
+ s->reflective = 1;
+ s->scan = 1;
+ s->adaptive = 1;
+ s->inttime = m->min_int_time; /* Maximize scan rate */
+ s->dark_int_time = s->inttime;
+ if (m->fwrev >= 301) /* (We're not using scan targoscale though) */
+ s->targoscale = 0.25;
+ else
+ s->targoscale = 0.5;
+
+ s->lamptime = 0.5; /* Lamp turn on time - lots to match scan */
+ s->dadaptime = 0.10;
+ s->wadaptime = 0.10;
+ s->dcaltime = 0.5;
+ s->wcaltime = 0.5;
+ s->dreadtime = 0.10;
+ s->wreadtime = 0.10;
+ s->maxscantime = MAXSCANTIME;
+ s->min_wl = HIGHRES_REF_MIN; /* Too much stray light below this */
+ break;
+
+ case i1p_emiss_spot_na: /* Emissive spot not adaptive */
+ s->targoscale = 0.90; /* Allow extra 10% margine for drift */
+ for (j = 0; j < m->nwav[0]; j++)
+ s->cal_factor[0][j] = EMIS_SCALE_FACTOR * m->emis_coef[0][j];
+ s->cal_valid = 1;
+ s->emiss = 1;
+ s->adaptive = 0;
+
+ s->inttime = DISP_INTT; /* Default disp integration time (ie. 2.0 sec) */
+ s->lamptime = 0.20; /* ???? */
+ s->dark_int_time = s->inttime;
+ s->dark_int_time2 = DISP_INTT2; /* Alternate disp integration time (ie. 0.8) */
+ s->dark_int_time3 = DISP_INTT3; /* Alternate disp integration time (ie. 0.3) */
+
+ s->dadaptime = 0.0;
+ s->wadaptime = 0.10;
+ s->dcaltime = DISP_INTT; /* ie. determines number of measurements */
+ s->dcaltime2 = DISP_INTT2 * 2; /* Make it 1.6 seconds (ie, 2 x 0.8 seconds) */
+ s->dcaltime3 = DISP_INTT3 * 3; /* Make it 0.9 seconds (ie, 3 x 0.3 seconds) */
+ s->wcaltime = 0.0;
+ s->dreadtime = 0.0;
+ s->wreadtime = DISP_INTT;
+ s->maxscantime = 0.0;
+ break;
+ case i1p_emiss_spot:
+ s->targoscale = 0.90; /* Allow extra 10% margine for drift */
+ for (j = 0; j < m->nwav[0]; j++)
+ s->cal_factor[0][j] = EMIS_SCALE_FACTOR * m->emis_coef[0][j];
+ s->cal_valid = 1;
+ s->emiss = 1;
+ s->adaptive = 1;
+
+ s->lamptime = 0.20; /* ???? */
+ s->dadaptime = 0.0;
+ s->wadaptime = 0.10;
+ s->dcaltime = 1.0;
+ s->wcaltime = 0.0;
+ s->dreadtime = 0.0;
+ s->wreadtime = 1.0;
+ s->maxscantime = 0.0;
+ break;
+ case i1p_emiss_scan:
+ for (j = 0; j < m->nwav[0]; j++)
+ s->cal_factor[0][j] = EMIS_SCALE_FACTOR * m->emis_coef[0][j];
+ s->cal_valid = 1;
+ s->emiss = 1;
+ s->scan = 1;
+ s->adaptive = 1; /* ???? */
+ s->inttime = m->min_int_time; /* Maximize scan rate */
+ s->lamptime = 0.20; /* ???? */
+ s->dark_int_time = s->inttime;
+ if (m->fwrev >= 301)
+ s->targoscale = 0.25; /* (We're not using scan targoscale though) */
+ else
+ s->targoscale = 0.5;
+
+ s->dadaptime = 0.0;
+ s->wadaptime = 0.10;
+ s->dcaltime = 1.0;
+ s->wcaltime = 0.0;
+ s->dreadtime = 0.0;
+ s->wreadtime = 0.10;
+ s->maxscantime = MAXSCANTIME;
+ break;
+
+ case i1p_amb_spot:
+#ifdef FAKE_AMBIENT
+ for (j = 0; j < m->nwav[0]; j++)
+ s->cal_factor[0][j] = EMIS_SCALE_FACTOR * m->emis_coef[0][j];
+ s->cal_valid = 1;
+#else
+ if (m->amb_coef[0] != NULL) {
+ for (j = 0; j < m->nwav[0]; j++)
+ s->cal_factor[0][j] = AMB_SCALE_FACTOR * m->emis_coef[0][j] * m->amb_coef[0][j];
+ s->cal_valid = 1;
+ }
+#endif
+ s->emiss = 1;
+ s->ambient = 1;
+ s->adaptive = 1;
+
+ s->lamptime = 0.20; /* ???? */
+ s->dadaptime = 0.0;
+ s->wadaptime = 0.10;
+ s->dcaltime = 1.0;
+ s->wcaltime = 0.0;
+ s->dreadtime = 0.0;
+ s->wreadtime = 1.0;
+ s->maxscantime = 0.0;
+ break;
+ case i1p_amb_flash:
+ /* This is intended for measuring flashes */
+#ifdef FAKE_AMBIENT
+ for (j = 0; j < m->nwav[0]; j++)
+ s->cal_factor[0][j] = EMIS_SCALE_FACTOR * m->emis_coef[0][j];
+ s->cal_valid = 1;
+#else
+ if (m->amb_coef[0] != NULL) {
+ for (j = 0; j < m->nwav[0]; j++)
+ s->cal_factor[0][j] = AMB_SCALE_FACTOR * m->emis_coef[0][j] * m->amb_coef[0][j];
+ s->cal_valid = 1;
+ }
+#endif
+ s->emiss = 1;
+ s->ambient = 1;
+ s->scan = 1;
+ s->adaptive = 0;
+ s->flash = 1;
+
+ s->inttime = m->min_int_time; /* Maximize scan rate */
+ s->lamptime = 0.20; /* ???? */
+ s->dark_int_time = s->inttime;
+ if (m->fwrev >= 301)
+ s->targoscale = 0.25; /* (We're not using scan targoscale though) */
+ else
+ s->targoscale = 0.5;
+
+ s->dadaptime = 0.0;
+ s->wadaptime = 0.10;
+ s->dcaltime = 1.0;
+ s->wcaltime = 0.0;
+ s->dreadtime = 0.0;
+ s->wreadtime = 0.12;
+ s->maxscantime = MAXSCANTIME;
+ break;
+
+ case i1p_trans_spot:
+ s->trans = 1;
+ s->adaptive = 1;
+
+ s->lamptime = 0.20; /* ???? */
+ s->dadaptime = 0.10;
+ s->wadaptime = 0.10;
+ s->dcaltime = 1.0;
+ s->wcaltime = 1.0;
+ s->dreadtime = 0.0;
+ s->wreadtime = 1.0;
+ s->maxscantime = 0.0;
+ s->min_wl = HIGHRES_REF_MIN; /* Too much stray light below this */
+ break;
+ case i1p_trans_scan:
+ s->trans = 1;
+ s->scan = 1;
+ s->adaptive = 0;
+ s->inttime = m->min_int_time; /* Maximize scan rate */
+ s->dark_int_time = s->inttime;
+ if (m->fwrev >= 301) /* (We're not using scan targoscale though) */
+ s->targoscale = 0.25;
+ else
+ s->targoscale = 0.5;
+
+ s->lamptime = 0.20; /* ???? */
+ s->dadaptime = 0.10;
+ s->wadaptime = 0.10;
+ s->dcaltime = 1.0;
+ s->wcaltime = 1.0;
+ s->dreadtime = 0.00;
+ s->wreadtime = 0.10;
+ s->maxscantime = MAXSCANTIME;
+ s->min_wl = HIGHRES_REF_MIN; /* Too much stray light below this */
+ break;
+ }
+ }
+ }
+
+ if (p->itype != instI1Monitor /* Monitor doesn't have reflective cal */
+ && p->itype != instI1Pro2) { /* Rev E mode has different calibration */
+ /* Restore the previous reflective spot calibration from the EEProm */
+ /* Get ready to operate the instrument */
+ if ((ev = i1pro_restore_refspot_cal(p)) != I1PRO_OK)
+ return ev;
+ }
+
+#ifdef ENABLE_NONVCAL
+ /* Restore the all modes calibration from the local system */
+ i1pro_restore_calibration(p);
+ /* Touch it so that we know when the instrument was last opened */
+ i1pro_touch_calibration(p);
+#endif
+
+ /* Get ready to operate the instrument */
+ if ((ev = i1pro_establish_high_power(p)) != I1PRO_OK)
+ return ev;
+
+ /* Get the current measurement parameters (why ?) */
+ if ((ev = i1pro_getmeasparams(p, &m->r_intclocks, &m->r_lampclocks, &m->r_nummeas, &m->r_measmodeflags)) != I1PRO_OK)
+ return ev;
+
+ if (p->log->verb >= 1) {
+ a1logv(p->log,1,"Instrument Type: %s\n",inst_name(p->itype));
+ a1logv(p->log,1,"Serial Number: %d\n",m->serno);
+ a1logv(p->log,1,"Firmware version: %d\n",m->fwrev);
+ a1logv(p->log,1,"CPLD version: %d\n",m->cpldrev);
+ if (p->itype == instI1Pro2)
+ a1logv(p->log,1,"Chip ID: %02x-%02x%02x%02x%02x%02x%02x%02x\n",
+ m->chipid[0], m->chipid[1], m->chipid[2], m->chipid[3],
+ m->chipid[4], m->chipid[5], m->chipid[6], m->chipid[7]);
+ a1logv(p->log,1,"Date manufactured: %d-%d-%d\n",
+ m->dom/1000000, (m->dom/10000) % 100, m->dom % 10000);
+ // Hmm. physfilt == 0x81 for instI1Monitor ???
+ a1logv(p->log,1,"U.V. filter ?: %s\n",m->physfilt == 0x82 ? "Yes" : "No");
+ a1logv(p->log,1,"Measure Ambient ?: %s\n",m->capabilities & 0x6000 ? "Yes" : "No");
+
+ a1logv(p->log,1,"Tot. Measurement Count: %d\n",m->meascount);
+ a1logv(p->log,1,"Remission Spot Count: %d\n",m->rpcount);
+ a1logv(p->log,1,"Remission Scan Count: %d\n",m->acount);
+ a1logv(p->log,1,"Date of last Remission spot cal: %s",ctime(&m->caldate));
+ a1logv(p->log,1,"Remission Spot Count at last cal: %d\n",m->calcount);
+ a1logv(p->log,1,"Total lamp usage: %f\n",m->lampage);
+ }
+
+#ifdef NEVER
+// ~~99 play with LED settings
+ if (p->itype == instI1Pro2) {
+
+ /* Makes it white */
+ unsigned char b2[] = {
+ 0x00, 0x00, 0x00, 0x02,
+
+ 0x00, 0x00, 0x00, 0x0a,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x36, 0x00,
+ 0x00, 0x00, 0x01,
+
+ 0x00, 0x00, 0x00, 0x0a,
+ 0xff, 0xff, 0xff, 0xff,
+ 0x3f, 0x36, 0x40,
+ 0x00, 0x00, 0x01
+ };
+
+ printf("~1 send led sequence length %d\n",sizeof(b2));
+ if ((ev = i1pro2_indLEDseq(p, b2, sizeof(b2))) != I1PRO_OK)
+ return ev;
+ }
+ /* Make sure LED sequence is finished, because it interferes with EEProm read! */
+ if ((ev = i1pro2_indLEDoff(p)) != I1PRO_OK)
+ return ev;
+#endif
+
+ return ev;
+}
+
+/* Return a pointer to the serial number */
+char *i1pro_imp_get_serial_no(i1pro *p) {
+ i1proimp *m = (i1proimp *)p->m;
+
+ return m->sserno;
+}
+
+/* Return non-zero if capable of ambient mode */
+int i1pro_imp_ambient(i1pro *p) {
+
+ if (p->inited) {
+ i1proimp *m = (i1proimp *)p->m;
+ if (m->capabilities & 0x6000) /* Expect ambient calibration */
+ return 1;
+#ifdef FAKE_AMBIENT
+ return 1;
+#endif
+ return 0;
+
+ } else {
+ return 0;
+ }
+}
+
+/* Set the measurement mode. It may need calibrating */
+i1pro_code i1pro_imp_set_mode(
+ i1pro *p,
+ i1p_mode mmode, /* Operating mode */
+ inst_mode mode /* Full mode mask for options */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+
+ a1logd(p->log,2,"i1pro_imp_set_mode called with %d\n",mmode);
+ switch(mmode) {
+ case i1p_refl_spot:
+ case i1p_refl_scan:
+ if (p->itype == instI1Monitor)
+ return I1PRO_INT_ILLEGALMODE; /* i1Monitor */
+ /* Fall through */
+ case i1p_emiss_spot_na:
+ case i1p_emiss_spot:
+ case i1p_emiss_scan:
+ case i1p_amb_spot:
+ case i1p_amb_flash:
+ case i1p_trans_spot:
+ case i1p_trans_scan:
+ m->mmode = mmode;
+ break;
+ default:
+ return I1PRO_INT_ILLEGALMODE;
+ }
+ m->spec_en = (mode & inst_mode_spectral) != 0;
+ m->uv_en = 0;
+
+ if (mmode == i1p_refl_spot
+ || mmode == i1p_refl_scan)
+ m->uv_en = (mode & inst_mode_ref_uv) != 0;
+
+ return I1PRO_OK;
+}
+
+/* Return needed and available inst_cal_type's */
+i1pro_code i1pro_imp_get_n_a_cals(i1pro *p, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *cs = &m->ms[m->mmode];
+ time_t curtime = time(NULL);
+ inst_cal_type n_cals = inst_calt_none;
+ inst_cal_type a_cals = inst_calt_none;
+
+ a1logd(p->log,2,"i1pro_imp_get_n_a_cals: checking mode %d\n",m->mmode);
+
+ /* Timout calibrations that are too old */
+ if (m->capabilities2 & I1PRO_CAP2_WL_LED) {
+ if ((curtime - cs->wldate) > WLCALTOUT) {
+ a1logd(p->log,2,"Invalidating wavelength cal as %d secs from last cal\n",curtime - cs->wldate);
+ cs->wl_valid = 0;
+ }
+ }
+ if ((curtime - cs->iddate) > ((p->itype == instI1Pro) ? DCALTOUT2 : DCALTOUT)) {
+ a1logd(p->log,2,"Invalidating adaptive dark cal as %d secs from last cal\n",curtime - cs->iddate);
+ cs->idark_valid = 0;
+ }
+ if ((curtime - cs->ddate) > ((p->itype == instI1Pro) ? DCALTOUT2 : DCALTOUT)) {
+ a1logd(p->log,2,"Invalidating dark cal as %d secs from last cal\n",curtime - cs->ddate);
+ cs->dark_valid = 0;
+ }
+ if (!cs->emiss && (curtime - cs->cfdate) > WCALTOUT) {
+ a1logd(p->log,2,"Invalidating white cal as %d secs from last cal\n",curtime - cs->cfdate);
+ cs->cal_valid = 0;
+ }
+
+#ifdef NEVER
+ printf("~1 reflective = %d, adaptive = %d, emiss = %d, trans = %d, scan = %d\n",
+ cs->reflective, cs->adaptive, cs->emiss, cs->trans, cs->scan);
+ printf("~1 idark_valid = %d, dark_valid = %d, cal_valid = %d\n",
+ cs->idark_valid,cs->dark_valid,cs->cal_valid);
+ printf("~1 want_calib = %d, want_dcalib = %d, noinitcalib = %d\n",
+ cs->want_calib,cs->want_dcalib, m->noinitcalib);
+#endif /* NEVER */
+
+ if (m->capabilities2 & I1PRO_CAP2_WL_LED) {
+ if (!cs->wl_valid
+ || (cs->want_dcalib && !m->noinitcalib)) // ?? want_dcalib ??
+ n_cals |= inst_calt_wavelength;
+ a_cals |= inst_calt_wavelength;
+ }
+ if (cs->reflective) {
+ if (!cs->dark_valid
+ || (cs->want_dcalib && !m->noinitcalib))
+ n_cals |= inst_calt_ref_dark;
+ a_cals |= inst_calt_ref_dark;
+
+ if (!cs->cal_valid
+ || (cs->want_calib && !m->noinitcalib))
+ n_cals |= inst_calt_ref_white;
+ a_cals |= inst_calt_ref_white;
+ }
+ if (cs->emiss) {
+ if ((!cs->adaptive && !cs->dark_valid)
+ || (cs->adaptive && !cs->idark_valid)
+ || (cs->want_dcalib && !m->noinitcalib))
+ n_cals |= inst_calt_em_dark;
+ a_cals |= inst_calt_em_dark;
+ }
+ if (cs->trans) {
+ if ((!cs->adaptive && !cs->dark_valid)
+ || (cs->adaptive && !cs->idark_valid)
+ || (cs->want_dcalib && !m->noinitcalib))
+ n_cals |= inst_calt_trans_dark;
+ a_cals |= inst_calt_trans_dark;
+
+ if (!cs->cal_valid
+ || (cs->want_calib && !m->noinitcalib))
+ n_cals |= inst_calt_trans_vwhite;
+ a_cals |= inst_calt_trans_vwhite;
+ }
+ if (cs->emiss && !cs->adaptive && !cs->scan) {
+ if (!cs->done_dintsel)
+ n_cals |= inst_calt_emis_int_time;
+ a_cals |= inst_calt_emis_int_time;
+ }
+
+ if (pn_cals != NULL)
+ *pn_cals = n_cals;
+
+ if (pa_cals != NULL)
+ *pa_cals = a_cals;
+
+ a1logd(p->log,3,"i1pro_imp_get_n_a_cals: returning n_cals 0x%x, a_cals 0x%x\n",n_cals, a_cals);
+
+ return I1PRO_OK;
+}
+
+/* - - - - - - - - - - - - - - - - */
+/* Calibrate for the current mode. */
+/* Request an instrument calibration of the current mode. */
+i1pro_code i1pro_imp_calibrate(
+ i1pro *p,
+ inst_cal_type *calt, /* Calibration type to do/remaining */
+ inst_cal_cond *calc, /* Current condition/desired condition */
+ char id[CALIDLEN] /* Condition identifier (ie. white reference ID) */
+) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ int mmode = m->mmode;
+ i1pro_state *cs = &m->ms[m->mmode];
+ int sx1, sx2, sx;
+ time_t cdate = time(NULL);
+ int nummeas = 0;
+ int ltocmode = 0; /* 1 = Lamp turn on compensation mode */
+ int i, k;
+ inst_cal_type needed, available;
+
+ a1logd(p->log,2,"i1pro_imp_calibrate called with calt 0x%x, calc 0x%x\n",*calt, *calc);
+
+ if ((ev = i1pro_imp_get_n_a_cals(p, &needed, &available)) != I1PRO_OK)
+ return ev;
+
+ /* Translate inst_calt_all/needed into something specific */
+ if (*calt == inst_calt_all
+ || *calt == inst_calt_needed
+ || *calt == inst_calt_available) {
+ if (*calt == inst_calt_all)
+ *calt = (needed & inst_calt_n_dfrble_mask) | inst_calt_ap_flag;
+ else if (*calt == inst_calt_needed)
+ *calt = needed & inst_calt_n_dfrble_mask;
+ else if (*calt == inst_calt_available)
+ *calt = available & inst_calt_n_dfrble_mask;
+
+ a1logd(p->log,4,"i1pro_imp_calibrate: doing calt 0x%x\n",calt);
+
+ if ((*calt & inst_calt_n_dfrble_mask) == 0) /* Nothing todo */
+ return I1PRO_OK;
+ }
+
+ /* See if it's a calibration we understand */
+ if (*calt & ~available & inst_calt_all_mask) {
+ return I1PRO_UNSUPPORTED;
+ }
+
+ if (*calt & inst_calt_ap_flag) {
+ sx1 = 0; sx2 = i1p_no_modes; /* Go through all the modes */
+ } else {
+ sx1 = m->mmode; sx2 = sx1 + 1; /* Just current mode */
+ }
+
+ /* Go through the modes we are going to cover */
+ for (sx = sx1; sx < sx2; sx++) {
+ i1pro_state *s = &m->ms[sx];
+ m->mmode = sx; /* A lot of functions we call rely on this */
+
+ a1logd(p->log,2,"\nCalibrating mode %d\n", s->mode);
+
+ /* Sanity check scan mode settings, in case something strange */
+ /* has been restored from the persistence file. */
+ if (s->scan && s->inttime > (2.1 * m->min_int_time)) {
+ s->inttime = m->min_int_time; /* Maximize scan rate */
+ }
+
+ /* Wavelength calibration: */
+ if ((m->capabilities2 & I1PRO_CAP2_WL_LED)
+ && (*calt & (inst_calt_wavelength | inst_calt_ap_flag))
+ && (*calc == inst_calc_man_ref_white
+ || *calc == inst_calc_man_am_dark)) {
+ double *wlraw;
+ double optscale;
+ double *abswav;
+
+ a1logd(p->log,2,"\nDoing wavelength calibration\n");
+
+ wlraw = dvectorz(-1, m->nraw-1);
+
+ if ((ev = i1pro2_wl_measure(p, wlraw, &optscale, &m->wl_cal_inttime, 1.0)) != I1PRO_OK) {
+ a1logd(p->log,2,"i1pro2_wl_measure() failed\n");
+ return ev;
+ }
+
+ /* Find the best fit of the measured values to the reference spectrum */
+ if ((ev = i1pro2_match_wl_meas(p, &cs->wl_led_off, wlraw)) != I1PRO_OK) {
+ a1logd(p->log,2,"i1pro2_match_wl_meas() failed\n");
+ return ev;
+ }
+
+ free_dvector(wlraw, -1, m->nraw-1);
+
+ /* Compute normal res. reflective wavelength corrected filters */
+ if ((ev = i1pro2_compute_wav_filters(p, 1)) != I1PRO_OK) {
+ a1logd(p->log,2,"i1pro2_compute_wav_filters() failed\n");
+ return ev;
+ }
+
+ /* Compute normal res. emissive/transmissive wavelength corrected filters */
+ if ((ev = i1pro2_compute_wav_filters(p, 0)) != I1PRO_OK) {
+ a1logd(p->log,2,"i1pro2_compute_wav_filters() failed\n");
+ return ev;
+ }
+
+ /* Re-compute high res. wavelength corrected filters */
+ if (m->hr_inited && (ev = i1pro_create_hr(p)) != I1PRO_OK) {
+ a1logd(p->log,2,"i1pro_create_hr() failed\n");
+ return ev;
+ }
+
+ cs->wl_valid = 1;
+ cs->wldate = cdate;
+ *calt &= ~inst_calt_wavelength;
+
+ /* Save the calib to all modes */
+ a1logd(p->log,5,"Saving wavelength calib to similar modes\n");
+ for (i = 0; i < i1p_no_modes; i++) {
+ i1pro_state *ss = &m->ms[i];
+ if (ss == cs)
+ continue;
+ ss->wl_valid = cs->wl_valid;
+ ss->wldate = cs->wldate;
+ ss->wl_led_off = cs->wl_led_off;
+ }
+ }
+
+ /* Fixed int. time black calibration: */
+ /* Reflective uses on the fly black, even for adaptive. */
+ /* Emiss and trans can use single black ref only for non-adaptive */
+ /* using the current inttime & gainmode, while display mode */
+ /* does an extra fallback black cal for bright displays. */
+ if ((*calt & (inst_calt_ref_dark
+ | inst_calt_em_dark
+ | inst_calt_trans_dark | inst_calt_ap_flag))
+ && (*calc == inst_calc_man_ref_white /* Any condition conducive to dark calib */
+ || *calc == inst_calc_man_em_dark
+ || *calc == inst_calc_man_am_dark
+ || *calc == inst_calc_man_trans_dark)
+ && ( s->reflective
+ || (s->emiss && !s->adaptive && !s->scan)
+ || (s->trans && !s->adaptive))) {
+ int stm;
+ int usesdct23 = 0; /* Is a mode that uses dcaltime2 & 3 */
+
+ if (s->emiss && !s->adaptive && !s->scan)
+ usesdct23 = 1;
+
+ nummeas = i1pro_comp_nummeas(p, s->dcaltime, s->inttime);
+
+ a1logd(p->log,2,"\nDoing initial black calibration with dcaltime %f, int_time %f, nummeas %d, gainmode %d\n", s->dcaltime, s->inttime, nummeas, s->gainmode);
+ stm = msec_time();
+ if ((ev = i1pro_dark_measure(p, s->dark_data,
+ nummeas, &s->inttime, s->gainmode)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ a1logd(p->log,2,"Execution time of dark calib time %f sec = %d msec\n",s->inttime,msec_time() - stm);
+
+ /* Special display mode alternate integration time black measurement */
+ if (usesdct23) {
+ nummeas = i1pro_comp_nummeas(p, s->dcaltime2, s->dark_int_time2);
+ a1logd(p->log,2,"Doing 2nd initial black calibration with dcaltime2 %f, dark_int_time2 %f, nummeas %d, gainmode %d\n", s->dcaltime2, s->dark_int_time2, nummeas, s->gainmode);
+ stm = msec_time();
+ if ((ev = i1pro_dark_measure(p, s->dark_data2,
+ nummeas, &s->dark_int_time2, s->gainmode)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ a1logd(p->log,2,"Execution time of 2nd dark calib time %f sec = %d msec\n",s->inttime,msec_time() - stm);
+
+ nummeas = i1pro_comp_nummeas(p, s->dcaltime3, s->dark_int_time3);
+ a1logd(p->log,2,"Doing 3rd initial black calibration with dcaltime3 %f, dark_int_time3 %f, nummeas %d, gainmode %d\n", s->dcaltime3, s->dark_int_time3, nummeas, s->gainmode);
+ nummeas = i1pro_comp_nummeas(p, s->dcaltime3, s->dark_int_time3);
+ stm = msec_time();
+ if ((ev = i1pro_dark_measure(p, s->dark_data3,
+ nummeas, &s->dark_int_time3, s->gainmode)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ a1logd(p->log,2,"Execution time of 3rd dark calib time %f sec = %d msec\n",s->inttime,msec_time() - stm);
+ }
+ s->dark_valid = 1;
+ s->want_dcalib = 0;
+ s->ddate = cdate;
+ s->dark_int_time = s->inttime;
+ s->dark_gain_mode = s->gainmode;
+ *calt &= ~(inst_calt_ref_dark
+ | inst_calt_em_dark
+ | inst_calt_trans_dark);
+
+ /* Save the calib to all similar modes */
+ for (i = 0; i < i1p_no_modes; i++) {
+ i1pro_state *ss = &m->ms[i];
+ if (ss == s || ss->ddate == cdate)
+ continue;
+ if (( s->reflective
+ || (ss->emiss && !ss->adaptive && !ss->scan)
+ || (ss->trans && !ss->adaptive))
+ && ss->dark_int_time == s->dark_int_time
+ && ss->dark_gain_mode == s->dark_gain_mode) {
+
+ ss->dark_valid = s->dark_valid;
+ ss->want_dcalib = s->want_dcalib;
+ ss->ddate = s->ddate;
+ ss->dark_int_time = s->dark_int_time;
+ ss->dark_gain_mode = s->dark_gain_mode;
+ for (k = -1; k < m->nraw; k++)
+ ss->dark_data[k] = s->dark_data[k];
+ /* If this is a mode with dark_data2/3, tranfer it too */
+ if (usesdct23 && ss->emiss && !ss->adaptive && !ss->scan) {
+ ss->dark_int_time2 = s->dark_int_time2;
+ ss->dark_int_time3 = s->dark_int_time2;
+ for (k = -1; k < m->nraw; k++) {
+ ss->dark_data2[k] = s->dark_data2[k];
+ ss->dark_data3[k] = s->dark_data3[k];
+ }
+ }
+ }
+ }
+ }
+
+ /* Emissive scan black calibration: */
+ /* Emsissive scan (flash) uses the fastest possible scan rate (??) */
+ if ((*calt & (inst_calt_em_dark | inst_calt_ap_flag))
+ && (*calc == inst_calc_man_ref_white /* Any condition conducive to dark calib */
+ || *calc == inst_calc_man_em_dark
+ || *calc == inst_calc_man_am_dark
+ || *calc == inst_calc_man_trans_dark)
+ && (s->emiss && !s->adaptive && s->scan)) {
+ int stm;
+
+ nummeas = i1pro_comp_nummeas(p, s->dcaltime, s->inttime);
+
+ a1logd(p->log,2,"\nDoing emissive (flash) black calibration with dcaltime %f, int_time %f, nummeas %d, gainmode %d\n", s->dcaltime, s->inttime, nummeas, s->gainmode);
+ stm = msec_time();
+ if ((ev = i1pro_dark_measure(p, s->dark_data,
+ nummeas, &s->inttime, s->gainmode)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ a1logd(p->log,2,"Execution time of dark calib time %f sec = %d msec\n",s->inttime,msec_time() - stm);
+
+ s->dark_valid = 1;
+ s->want_dcalib = 0;
+ s->ddate = cdate;
+ s->dark_int_time = s->inttime;
+ s->dark_gain_mode = s->gainmode;
+ *calt &= ~inst_calt_em_dark;
+
+ /* Save the calib to all similar modes */
+ /* We're assuming they have the same int times */
+ a1logd(p->log,5,"Saving emissive scan black calib to similar modes\n");
+ for (i = 0; i < i1p_no_modes; i++) {
+ i1pro_state *ss = &m->ms[i];
+ if (ss == s || ss->ddate == s->ddate)
+ continue;
+ if (ss->emiss && !ss->adaptive && ss->scan) {
+ ss->dark_valid = s->dark_valid;
+ ss->want_dcalib = s->want_dcalib;
+ ss->ddate = s->ddate;
+ ss->dark_int_time = s->dark_int_time;
+ ss->dark_gain_mode = s->dark_gain_mode;
+ for (k = -1; k < m->nraw; k++)
+ ss->dark_data[k] = s->dark_data[k];
+ }
+ }
+ }
+
+ /* Adaptive black calibration: */
+ /* Deal with an emmissive/transmissive black reference */
+ /* in non-scan mode, where the integration time and gain may vary. */
+ /* The black is interpolated from readings with two extreme integration times */
+ if ((*calt & (inst_calt_ref_dark
+ | inst_calt_em_dark
+ | inst_calt_trans_dark | inst_calt_ap_flag))
+ && (*calc == inst_calc_man_ref_white /* Any condition conducive to dark calib */
+ || *calc == inst_calc_man_em_dark
+ || *calc == inst_calc_man_am_dark
+ || *calc == inst_calc_man_trans_dark)
+ && ((s->emiss && s->adaptive && !s->scan)
+ || (s->trans && s->adaptive && !s->scan))) {
+ int i, j, k;
+
+ a1logd(p->log,2,"\nDoing emis/trans adapative black calibration\n");
+
+ /* Adaptive where we can't measure the black reference on the fly, */
+ /* so bracket it and interpolate. */
+ /* The black reference is probably temperature dependent, but */
+ /* there's not much we can do about this. */
+
+ nummeas = i1pro_comp_nummeas(p, s->dcaltime, s->idark_int_time[0]);
+ a1logd(p->log,2,"\nDoing adaptive interpolated black calibration, dcaltime %f, idark_int_time[0] %f, nummeas %d, gainmode %d\n", s->dcaltime, s->idark_int_time[0], nummeas, 0);
+ if ((ev = i1pro_dark_measure(p, s->idark_data[0],
+ nummeas, &s->idark_int_time[0], 0)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+
+ nummeas = i1pro_comp_nummeas(p, s->dcaltime, s->idark_int_time[1]);
+ a1logd(p->log,2,"Doing adaptive interpolated black calibration, dcaltime %f, idark_int_time[1] %f, nummeas %d, gainmode %d\n", s->dcaltime, s->idark_int_time[1], nummeas, 0);
+ if ((ev = i1pro_dark_measure(p, s->idark_data[1],
+ nummeas, &s->idark_int_time[1], 0)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+
+#ifdef USE_HIGH_GAIN_MODE
+ if (p->itype != instI1Pro2) { /* Rev E doesn't have high gain mode */
+ nummeas = i1pro_comp_nummeas(p, s->dcaltime, s->idark_int_time[2]);
+ a1logd(p->log,2,"Doing adaptive interpolated black calibration, dcaltime %f, idark_int_time[2] %f, nummeas %d, gainmode %d\n", s->dcaltime, s->idark_int_time[2], nummeas, 1);
+ if ((ev = i1pro_dark_measure(p, s->idark_data[2],
+ nummeas, &s->idark_int_time[2], 1)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+
+ a1logd(p->log,2,"Doing adaptive interpolated black calibration, dcaltime %f, idark_int_time[3] %f, nummeas %d, gainmode %d\n", s->dcaltime, s->idark_int_time[3], nummeas, 1);
+ nummeas = i1pro_comp_nummeas(p, s->dcaltime, s->idark_int_time[3]);
+ if ((ev = i1pro_dark_measure(p, s->idark_data[3],
+ nummeas, &s->idark_int_time[3], 1)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ }
+#endif /* USE_HIGH_GAIN_MODE */
+
+ i1pro_prepare_idark(p);
+ s->idark_valid = 1;
+ s->iddate = cdate;
+
+ if ((ev = i1pro_interp_dark(p, s->dark_data, s->inttime, s->gainmode)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ s->dark_valid = 1;
+ s->want_dcalib = 0;
+ s->ddate = s->iddate;
+ s->dark_int_time = s->inttime;
+ s->dark_gain_mode = s->gainmode;
+ *calt &= ~(inst_calt_ref_dark
+ | inst_calt_em_dark
+ | inst_calt_trans_dark);
+
+ /* Save the calib to all similar modes */
+ /* We're assuming they have the same int times */
+ a1logd(p->log,5,"Saving adaptive black calib to similar modes\n");
+ for (i = 0; i < i1p_no_modes; i++) {
+ i1pro_state *ss = &m->ms[i];
+ if (ss == s || ss->iddate == s->iddate)
+ continue;
+ if ((ss->emiss || ss->trans) && ss->adaptive && !ss->scan) {
+ ss->idark_valid = s->idark_valid;
+ ss->want_dcalib = s->want_dcalib;
+ ss->iddate = s->iddate;
+ ss->dark_int_time = s->dark_int_time;
+ ss->dark_gain_mode = s->dark_gain_mode;
+#ifdef USE_HIGH_GAIN_MODE
+ for (j = 0; j < (p->itype != instI1Pro2) ? 4 : 2; j++)
+#else
+ for (j = 0; j < 2; j++)
+#endif
+ {
+ ss->idark_int_time[j] = s->idark_int_time[j];
+ for (k = -1; k < m->nraw; k++)
+ ss->idark_data[j][k] = s->idark_data[j][k];
+ }
+ }
+ }
+
+ a1logd(p->log,5,"Done adaptive interpolated black calibration\n");
+
+ /* Test accuracy of dark level interpolation */
+#ifdef TEST_DARK_INTERP
+ {
+ double tinttime;
+ double ref[128], interp[128];
+
+ // fprintf(stderr,"Normal gain offsets:\n");
+ // plot_raw(s->idark_data[0]);
+ // fprintf(stderr,"Normal gain multiplier:\n");
+ // plot_raw(s->idark_data[1]);
+
+#ifdef DUMP_DARKM
+ extern int ddumpdarkm;
+ ddumpdarkm = 1;
+#endif
+ for (tinttime = m->min_int_time; ; tinttime *= 2.0) {
+ if (tinttime >= m->max_int_time)
+ tinttime = m->max_int_time;
+ nummeas = i1pro_comp_nummeas(p, s->dcaltime, tinttime);
+ if ((ev = i1pro_dark_measure(p, ref, nummeas, &tinttime, 0)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ i1pro_interp_dark(p, interp, tinttime, 0);
+#ifdef DEBUG
+ fprintf(stderr,"Low gain ref vs. interp dark offset for inttime %f:\n",tinttime);
+ plot_raw2(ref, interp);
+#endif
+ if ((tinttime * 1.1) > m->max_int_time)
+ break;
+ }
+#ifdef DUMP_DARKM
+ ddumpdarkm = 0;
+#endif
+
+#ifdef USE_HIGH_GAIN_MODE
+ if (p->itype != instI1Pro2) { /* Rev E doesn't have high gain mode */
+ // fprintf(stderr,"High gain offsets:\n");
+ // plot_raw(s->idark_data[2]);
+ // fprintf(stderr,"High gain multiplier:\n");
+ // plot_raw(s->idark_data[3]);
+
+ for (tinttime = m->min_int_time; ; tinttime *= 2.0) {
+ if (tinttime >= m->max_int_time)
+ tinttime = m->max_int_time;
+ nummeas = i1pro_comp_nummeas(p, s->dcaltime, tinttime);
+ if ((ev = i1pro_dark_measure(p, ref, nummeas, &tinttime, 1)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ i1pro_interp_dark(p, interp, tinttime, 1);
+#ifdef DEBUG
+ fprintf(stderr,"High gain ref vs. interp dark offset for inttime %f:\n",tinttime);
+ plot_raw2(ref, interp);
+#endif
+ if ((tinttime * 1.1) > m->max_int_time)
+ break;
+ }
+ }
+#endif /* USE_HIGH_GAIN_MODE */
+ }
+#endif /* NEVER */
+
+ }
+
+ /* Deal with an emissive/transmisive adaptive black reference */
+ /* when in scan mode. */
+ if ((*calt & (inst_calt_ref_dark
+ | inst_calt_em_dark
+ | inst_calt_trans_dark | inst_calt_ap_flag))
+ && (*calc == inst_calc_man_ref_white /* Any condition conducive to dark calib */
+ || *calc == inst_calc_man_em_dark
+ || *calc == inst_calc_man_am_dark
+ || *calc == inst_calc_man_trans_dark)
+ && ((s->emiss && s->adaptive && s->scan)
+ || (s->trans && s->adaptive && s->scan))) {
+ int j;
+
+ a1logd(p->log,2,"\nDoing emis/trans adapative scan mode black calibration\n");
+
+ /* We know scan is locked to the minimum integration time, */
+ /* so we can measure the dark data at that integration time, */
+ /* but we don't know what gain mode will be used, so measure both, */
+ /* and choose the appropriate one on the fly. */
+
+ s->idark_int_time[0] = s->inttime;
+ nummeas = i1pro_comp_nummeas(p, s->dcaltime, s->idark_int_time[0]);
+ a1logd(p->log,2,"\nDoing adaptive scan black calibration, dcaltime %f, idark_int_time[0] %f, nummeas %d, gainmode %d\n", s->dcaltime, s->idark_int_time[0], nummeas, s->gainmode);
+ if ((ev = i1pro_dark_measure(p, s->idark_data[0],
+ nummeas, &s->idark_int_time[0], 0)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+
+#ifdef USE_HIGH_GAIN_MODE
+ if (p->itype != instI1Pro2) { /* Rev E doesn't have high gain mode */
+ s->idark_int_time[2] = s->inttime;
+ nummeas = i1pro_comp_nummeas(p, s->dcaltime, s->idark_int_time[2]);
+ a1logd(p->log,2,"Doing adaptive scan black calibration, dcaltime %f, idark_int_time[2] %f, nummeas %d, gainmode %d\n", s->dcaltime, s->idark_int_time[2], nummeas, s->gainmode);
+ if ((ev = i1pro_dark_measure(p, s->idark_data[2],
+ nummeas, &s->idark_int_time[2], 1)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ }
+#endif
+
+ s->idark_valid = 1;
+ s->iddate = cdate;
+
+#ifdef USE_HIGH_GAIN_MODE
+ if (s->gainmode) {
+ for (j = -1; j < m->nraw; j++)
+ s->dark_data[j] = s->idark_data[2][j];
+ } else
+#endif
+ {
+ for (j = -1; j < m->nraw; j++)
+ s->dark_data[j] = s->idark_data[0][j];
+ }
+ s->dark_valid = 1;
+ s->want_dcalib = 0;
+ s->ddate = s->iddate;
+ s->dark_int_time = s->inttime;
+ s->dark_gain_mode = s->gainmode;
+ *calt &= ~(inst_calt_ref_dark
+ | inst_calt_em_dark
+ | inst_calt_trans_dark);
+
+ a1logd(p->log,2,"Done adaptive scan black calibration\n");
+
+ /* Save the calib to all similar modes */
+ /* We're assuming they have the same int times */
+ a1logd(p->log,5,"Saving adaptive scan black calib to similar modes\n");
+ for (i = 0; i < i1p_no_modes; i++) {
+ i1pro_state *ss = &m->ms[i];
+ if (ss == s || ss->iddate == s->iddate)
+ continue;
+ if ((ss->emiss || ss->trans) && ss->adaptive && s->scan) {
+ ss->idark_valid = s->idark_valid;
+ ss->want_dcalib = s->want_dcalib;
+ ss->iddate = s->iddate;
+ ss->dark_int_time = s->dark_int_time;
+ ss->dark_gain_mode = s->dark_gain_mode;
+#ifdef USE_HIGH_GAIN_MODE
+ for (j = 0; j < (p->itype != instI1Pro2) ? 4 : 2; j += 2)
+#else
+ for (j = 0; j < 2; j += 2)
+#endif
+ {
+ ss->idark_int_time[j] = s->idark_int_time[j];
+ for (k = -1; k < m->nraw; k++)
+ ss->idark_data[j][k] = s->idark_data[j][k];
+ }
+ }
+ }
+ }
+
+ /* If we are doing a white reference calibrate */
+ if ((*calt & (inst_calt_ref_white
+ | inst_calt_trans_vwhite | inst_calt_ap_flag))
+ && ((*calc == inst_calc_man_ref_white && s->reflective)
+ || (*calc == inst_calc_man_trans_white && s->trans))) {
+ double scale;
+
+ a1logd(p->log,2,"\nDoing initial white calibration with current inttime %f, gainmode %d\n",
+ s->inttime, s->gainmode);
+ nummeas = i1pro_comp_nummeas(p, s->wcaltime, s->inttime);
+ ev = i1pro_whitemeasure(p, s->cal_factor[0], s->cal_factor[1], s->white_data, &scale, nummeas,
+ &s->inttime, s->gainmode, s->scan ? 1.0 : s->targoscale, 0);
+ if (ev == I1PRO_RD_SENSORSATURATED) {
+ scale = 0.0; /* Signal it this way */
+ ev = I1PRO_OK;
+ }
+ if (ev != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+
+ /* For non-scan modes, we adjust the integration time to avoid saturation, */
+ /* and to try and match the target optimal sensor value */
+ if (!s->scan) {
+ /* If it's adaptive and not good, or if it's not adaptive and even worse, */
+ /* or if we're using lamp dynamic compensation for reflective scan, */
+ /* change the parameters until the white is optimal. */
+ if ((s->adaptive && (scale < 0.95 || scale > 1.05))
+ || (scale < 0.3 || scale > 2.0)) {
+
+ /* Need to have done adaptive black measure to change inttime/gain params */
+ if (*calc != inst_calc_man_ref_white && !s->idark_valid) {
+ m->mmode = mmode; /* Restore actual mode */
+ return I1PRO_RD_TRANSWHITERANGE;
+ }
+
+ if (scale == 0.0) { /* If sensor was saturated */
+ s->inttime = m->min_int_time;
+ s->gainmode = 0;
+ s->dark_valid = 0;
+ if (!s->emiss)
+ s->cal_valid = 0;
+
+ if (*calc == inst_calc_man_ref_white) {
+ nummeas = i1pro_comp_nummeas(p, s->dadaptime, s->inttime);
+ a1logd(p->log,2,"Doing another black calibration with dadaptime %f, min inttime %f, nummeas %d, gainmode %d\n", s->dadaptime, s->inttime, nummeas, s->gainmode);
+ if ((ev = i1pro_dark_measure(p, s->dark_data,
+ nummeas, &s->inttime, s->gainmode)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+
+ } else if (s->idark_valid) {
+ /* compute interpolated dark refence for chosen inttime & gainmode */
+ a1logd(p->log,2,"Interpolate dark calibration reference\n");
+ if ((ev = i1pro_interp_dark(p, s->dark_data,
+ s->inttime, s->gainmode)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ s->dark_valid = 1;
+ s->ddate = s->iddate;
+ s->dark_int_time = s->inttime;
+ s->dark_gain_mode = s->gainmode;
+ } else {
+ m->mmode = mmode; /* Restore actual mode */
+ return I1PRO_INT_NOINTERPDARK;
+ }
+ a1logd(p->log,2,"Doing another white calibration with min inttime %f, gainmode %d\n",
+ s->inttime,s->gainmode);
+ nummeas = i1pro_comp_nummeas(p, s->wadaptime, s->inttime);
+ if ((ev = i1pro_whitemeasure(p, s->cal_factor[0], s->cal_factor[1], s->white_data,
+ &scale, nummeas, &s->inttime, s->gainmode, s->targoscale, 0))
+ != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ }
+
+ /* Compute a new integration time and gain mode */
+ /* in order to optimise the sensor values. Error if can't get */
+ /* scale we want. */
+ if ((ev = i1pro_optimise_sensor(p, &s->inttime, &s->gainmode,
+ s->inttime, s->gainmode, s->trans, 0, s->targoscale, scale)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ a1logd(p->log,2,"Computed optimal white inttime %f and gainmode %d\n",
+ s->inttime,s->gainmode);
+
+ if (*calc == inst_calc_man_ref_white) {
+ nummeas = i1pro_comp_nummeas(p, s->dcaltime, s->inttime);
+ a1logd(p->log,2,"Doing final black calibration with dcaltime %f, opt inttime %f, nummeas %d, gainmode %d\n", s->dcaltime, s->inttime, nummeas, s->gainmode);
+ if ((ev = i1pro_dark_measure(p, s->dark_data,
+ nummeas, &s->inttime, s->gainmode)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ s->dark_valid = 1;
+ s->ddate = cdate;
+ s->dark_int_time = s->inttime;
+ s->dark_gain_mode = s->gainmode;
+
+ } else if (s->idark_valid) {
+ /* compute interpolated dark refence for chosen inttime & gainmode */
+ a1logd(p->log,2,"Interpolate dark calibration reference\n");
+ if ((ev = i1pro_interp_dark(p, s->dark_data,
+ s->inttime, s->gainmode)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ s->dark_valid = 1;
+ s->ddate = s->iddate;
+ s->dark_int_time = s->inttime;
+ s->dark_gain_mode = s->gainmode;
+ } else {
+ m->mmode = mmode; /* Restore actual mode */
+ return I1PRO_INT_NOINTERPDARK;
+ }
+
+ a1logd(p->log,2,"Doing final white calibration with opt int_time %f, gainmode %d\n",
+ s->inttime,s->gainmode);
+ nummeas = i1pro_comp_nummeas(p, s->wcaltime, s->inttime);
+ if ((ev = i1pro_whitemeasure(p, s->cal_factor[0], s->cal_factor[1], s->white_data,
+ &scale, nummeas, &s->inttime, s->gainmode, s->targoscale, ltocmode)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ }
+
+ /* For scan we take a different approach. We try and use the minimum possible */
+ /* integration time so as to maximize sampling rate, and adjust the gain */
+ /* if necessary. */
+ } else if (s->adaptive) {
+ int j;
+ if (scale == 0.0) { /* If sensor was saturated */
+ a1logd(p->log,3,"Scan illuminant is saturating sensor\n");
+ if (s->gainmode == 0) {
+ m->mmode = mmode; /* Restore actual mode */
+ return I1PRO_RD_SENSORSATURATED; /* Nothing we can do */
+ }
+ a1logd(p->log,3,"Switching to low gain mode\n");
+ s->gainmode = 0;
+ /* Measure white again with low gain */
+ nummeas = i1pro_comp_nummeas(p, s->wcaltime, s->inttime);
+ if ((ev = i1pro_whitemeasure(p, s->cal_factor[0], s->cal_factor[1], s->white_data,
+ &scale, nummeas, &s->inttime, s->gainmode, 1.0, 0)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ } else if (p->itype != instI1Pro2 && s->gainmode == 0 && scale > m->highgain) {
+#ifdef USE_HIGH_GAIN_MODE
+ a1logd(p->log,3,"Scan signal is so low we're switching to high gain mode\n");
+ s->gainmode = 1;
+ /* Measure white again with high gain */
+ nummeas = i1pro_comp_nummeas(p, s->wcaltime, s->inttime);
+ if ((ev = i1pro_whitemeasure(p, s->cal_factor[0], s->cal_factor[1], s->white_data,
+ &scale, nummeas, &s->inttime, s->gainmode, 1.0, 0)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+#endif /* USE_HIGH_GAIN_MODE */
+ }
+
+ a1logd(p->log,2,"After scan gain adaption scale = %f\n",scale);
+ if (scale > 6.0) {
+ m->transwarn |= 2;
+ a1logd(p->log,2, "scan white reference is not bright enough by %f\n",scale);
+ }
+
+ if (*calc == inst_calc_man_ref_white) {
+ nummeas = i1pro_comp_nummeas(p, s->dcaltime, s->inttime);
+ a1logd(p->log,2,"Doing final black calibration with dcaltime %f, opt inttime %f, nummeas %d, gainmode %d\n", s->dcaltime, s->inttime, nummeas, s->gainmode);
+ if ((ev = i1pro_dark_measure(p, s->dark_data,
+ nummeas, &s->inttime, s->gainmode)) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ s->dark_valid = 1;
+ s->ddate = cdate;
+ s->dark_int_time = s->inttime;
+ s->dark_gain_mode = s->gainmode;
+
+ } else if (s->idark_valid) {
+ /* compute interpolated dark refence for chosen inttime & gainmode */
+ a1logd(p->log,2,"Interpolate dark calibration reference\n");
+ if (s->gainmode) {
+ for (j = -1; j < m->nraw; j++)
+ s->dark_data[j] = s->idark_data[2][j];
+ } else {
+ for (j = -1; j < m->nraw; j++)
+ s->dark_data[j] = s->idark_data[0][j];
+ }
+ s->dark_valid = 1;
+ s->ddate = s->iddate;
+ s->dark_int_time = s->inttime;
+ s->dark_gain_mode = s->gainmode;
+ } else {
+ m->mmode = mmode; /* Restore actual mode */
+ return I1PRO_INT_NOINTERPDARK;
+ }
+ a1logd(p->log,2,"Doing final white calibration with opt int_time %f, gainmode %d\n",
+ s->inttime,s->gainmode);
+ }
+
+ /* We've settled on the inttime and gain mode to get a good white reference. */
+ if (s->reflective) { /* We read the white reference - check it */
+ /* Check a reflective white measurement, and check that */
+ /* it seems reasonable. Return I1PRO_OK if it is, error if not. */
+ /* (Using cal_factor[] as temp.) */
+ a1logd(p->log,2,"Checking white reference\n");
+ if ((ev = i1pro_check_white_reference1(p, s->cal_factor[0])) != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ /* Compute a calibration factor given the reading of the white reference. */
+ i1pro_compute_white_cal(p, s->cal_factor[0], m->white_ref[0], s->cal_factor[0],
+ s->cal_factor[1], m->white_ref[1], s->cal_factor[1]);
+
+ } else {
+ /* Compute a calibration factor given the reading of the white reference. */
+ m->transwarn |= i1pro_compute_white_cal(p, s->cal_factor[0], NULL, s->cal_factor[0],
+ s->cal_factor[1], NULL, s->cal_factor[1]);
+ }
+ s->cal_valid = 1;
+ s->cfdate = cdate;
+ s->want_calib = 0;
+ *calt &= ~(inst_calt_ref_white
+ | inst_calt_trans_vwhite);
+ }
+
+ /* Deal with a display integration time selection */
+ if ((*calt & (inst_calt_emis_int_time | inst_calt_ap_flag))
+ && *calc == inst_calc_emis_white
+ && (s->emiss && !s->adaptive && !s->scan)) {
+ double scale;
+ double *data;
+ double *tt, tv;
+
+ data = dvectorz(-1, m->nraw-1);
+
+ a1logd(p->log,2,"\nDoing display integration time calibration\n");
+
+ /* Undo any previous swaps */
+ if (s->dispswap == 1) {
+ tv = s->inttime; s->inttime = s->dark_int_time2; s->dark_int_time2 = tv;
+ tt = s->dark_data; s->dark_data = s->dark_data2; s->dark_data2 = tt;
+ } else if (s->dispswap == 2) {
+ tv = s->inttime; s->inttime = s->dark_int_time3; s->dark_int_time3 = tv;
+ tt = s->dark_data; s->dark_data = s->dark_data3; s->dark_data3 = tt;
+ }
+ s->dispswap = 0;
+
+ /* Simply measure the full display white, and if it's close to */
+ /* saturation, switch to the alternate display integration time */
+ nummeas = i1pro_comp_nummeas(p, s->wreadtime, s->inttime);
+ ev = i1pro_whitemeasure(p, NULL, NULL, data , &scale, nummeas,
+ &s->inttime, s->gainmode, s->targoscale, 0);
+ /* Switch to the alternate if things are too bright */
+ /* We do this simply by swapping the alternate values in. */
+ if (ev == I1PRO_RD_SENSORSATURATED || scale < 1.0) {
+ a1logd(p->log,2,"Switching to alternate display integration time %f seconds\n",s->dark_int_time2);
+ tv = s->inttime; s->inttime = s->dark_int_time2; s->dark_int_time2 = tv;
+ tt = s->dark_data; s->dark_data = s->dark_data2; s->dark_data2 = tt;
+ s->dispswap = 1;
+
+ /* Do another measurement of the full display white, and if it's close to */
+ /* saturation, switch to the 3rd alternate display integration time */
+ nummeas = i1pro_comp_nummeas(p, s->wreadtime, s->inttime);
+ ev = i1pro_whitemeasure(p, NULL, NULL, data , &scale, nummeas,
+ &s->inttime, s->gainmode, s->targoscale, 0);
+ /* Switch to the 3rd alternate if things are too bright */
+ /* We do this simply by swapping the alternate values in. */
+ if (ev == I1PRO_RD_SENSORSATURATED || scale < 1.0) {
+ a1logd(p->log,2,"Switching to 3rd alternate display integration time %f seconds\n",s->dark_int_time3);
+ /* Undo previous swap */
+ tv = s->inttime; s->inttime = s->dark_int_time2; s->dark_int_time2 = tv;
+ tt = s->dark_data; s->dark_data = s->dark_data2; s->dark_data2 = tt;
+ /* swap in 2nd alternate */
+ tv = s->inttime; s->inttime = s->dark_int_time3; s->dark_int_time3 = tv;
+ tt = s->dark_data; s->dark_data = s->dark_data3; s->dark_data3 = tt;
+ s->dispswap = 2;
+ }
+ }
+ free_dvector(data, -1, m->nraw-1);
+ if (ev != I1PRO_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ s->done_dintsel = 1;
+ s->diseldate = cdate;
+ *calt &= ~inst_calt_emis_int_time;
+
+ a1logd(p->log,5,"Done display integration time calibration\n");
+ }
+
+ } /* Look at next mode */
+ m->mmode = mmode; /* Restore actual mode */
+
+ /* Make sure there's the right condition for any remaining calibrations */
+ if (*calt & inst_calt_wavelength) { /* Wavelength calibration */
+ if (cs->emiss && cs->ambient) {
+ id[0] = '\000';
+ if (*calc != inst_calc_man_am_dark) {
+ *calc = inst_calc_man_am_dark; /* Calibrate using ambient adapter */
+ return I1PRO_CAL_SETUP;
+ }
+ } else {
+ sprintf(id, "Serial no. %d",m->serno);
+ if (*calc != inst_calc_man_ref_white) {
+ *calc = inst_calc_man_ref_white; /* Calibrate using white tile */
+ return I1PRO_CAL_SETUP;
+ }
+ }
+ } else if (*calt & (inst_calt_ref_dark | inst_calt_ref_white)) {
+ sprintf(id, "Serial no. %d",m->serno);
+ if (*calc != inst_calc_man_ref_white) {
+ *calc = inst_calc_man_ref_white; /* Calibrate using white tile */
+ return I1PRO_CAL_SETUP;
+ }
+ } else if (*calt & inst_calt_em_dark) { /* Emissive Dark calib */
+ id[0] = '\000';
+ if (*calc != inst_calc_man_em_dark) {
+ *calc = inst_calc_man_em_dark; /* Any sort of dark reference */
+ return I1PRO_CAL_SETUP;
+ }
+ } else if (*calt & inst_calt_trans_dark) { /* Transmissvice dark */
+ id[0] = '\000';
+ if (*calc != inst_calc_man_trans_dark) {
+ *calc = inst_calc_man_trans_dark;
+ return I1PRO_CAL_SETUP;
+ }
+ } else if (*calt & inst_calt_trans_vwhite) {/* Transmissvice white for emulated transmission */
+ id[0] = '\000';
+ if (*calc != inst_calc_man_trans_white) {
+ *calc = inst_calc_man_trans_white;
+ return I1PRO_CAL_SETUP;
+ }
+ } else if (*calt & inst_calt_emis_int_time) {
+ id[0] = '\000';
+ if (*calc != inst_calc_emis_white) {
+ *calc = inst_calc_emis_white;
+ return I1PRO_CAL_SETUP;
+ }
+ }
+
+ /* Go around again if we've still got calibrations to do */
+ if (*calt & inst_calt_all_mask) {
+ return I1PRO_CAL_SETUP;
+ }
+
+ /* We must be done */
+
+ /* Update and write the EEProm log if the is a refspot calibration */
+ if (cs->reflective && !cs->scan && cs->dark_valid && cs->cal_valid) {
+ m->calcount = m->rpcount;
+ m->caldate = cdate;
+ if ((ev = i1pro_update_log(p)) != I1PRO_OK) {
+ a1logd(p->log,2,"i1pro_update_log: Updating the cal and log parameters"
+ " to EEProm failed\n");
+ }
+ }
+
+#ifdef ENABLE_NONVCAL
+ /* Save the calibration to a file */
+ i1pro_save_calibration(p);
+#endif
+
+ if (m->transwarn) {
+ *calc = inst_calc_message;
+ if (m->transwarn & 2)
+ strcpy(id, "Warning: Transmission light source is too low for accuracy!");
+ else
+ strcpy(id, "Warning: Transmission light source is low at some wavelengths!");
+ m->transwarn = 0;
+ }
+
+ a1logd(p->log,2,"Finished cal with dark_valid = %d, cal_valid = %d\n",cs->dark_valid, cs->cal_valid);
+
+ return I1PRO_OK;
+}
+
+/* Interpret an icoms error into a I1PRO error */
+int icoms2i1pro_err(int se) {
+ if (se != ICOM_OK)
+ return I1PRO_COMS_FAIL;
+ return I1PRO_OK;
+}
+
+/* - - - - - - - - - - - - - - - - */
+/* Measure a patch or strip in the current mode. */
+/* To try and speed up the reaction time between */
+/* triggering a scan measurement and being able to */
+/* start moving the instrument, we pre-allocate */
+/* all the buffers and arrays, and pospone processing */
+/* until after the scan is complete. */
+i1pro_code i1pro_imp_measure(
+ i1pro *p,
+ ipatch *vals, /* Pointer to array of instrument patch value */
+ int nvals, /* Number of values */
+ instClamping clamp /* Clamp XYZ/Lab to be +ve */
+) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ unsigned char *buf = NULL; /* Raw USB reading buffer for reflection dark cal */
+ unsigned int bsize;
+ unsigned char *mbuf = NULL; /* Raw USB reading buffer for measurement */
+ unsigned int mbsize;
+ int nummeas = 0, maxnummeas = 0;
+ int nmeasuered = 0; /* Number actually measured */
+ double **specrd = NULL; /* Cooked spectral patch values */
+ double duration = 0.0; /* Possible flash duration value */
+ int user_trig = 0;
+
+ a1logd(p->log,2,"i1pro_imp_measure: Taking %d measurments in %s%s%s%s%s%s mode called\n", nvals,
+ s->emiss ? "Emission" : s->trans ? "Trans" : "Refl",
+ s->emiss && s->ambient ? " Ambient" : "",
+ s->scan ? " Scan" : "",
+ s->flash ? " Flash" : "",
+ s->adaptive ? " Adaptive" : "",
+ m->uv_en ? " UV" : "");
+
+
+ if ((s->emiss && s->adaptive && !s->idark_valid)
+ || ((!s->emiss || !s->adaptive) && !s->dark_valid)
+ || !s->cal_valid) {
+ a1logd(p->log,3,"emis %d, adaptive %d, idark_valid %d\n",s->emiss,s->adaptive,s->idark_valid);
+ a1logd(p->log,3,"dark_valid %d, cal_valid %d\n",s->dark_valid,s->cal_valid);
+ a1logd(p->log,3,"i1pro_imp_measure need calibration\n");
+ return I1PRO_RD_NEEDS_CAL;
+ }
+
+ if (nvals <= 0
+ || (!s->scan && nvals > 1)) {
+ a1logd(p->log,2,"i1pro_imp_measure wrong number of patches\n");
+ return I1PRO_INT_WRONGPATCHES;
+ }
+
+ /* Notional number of measurements, befor adaptive and not counting scan */
+ nummeas = i1pro_comp_nummeas(p, s->wreadtime, s->inttime);
+
+ /* Allocate buf for pre-measurement dark calibration */
+ if (s->reflective) {
+ bsize = m->nsen * 2 * nummeas;
+ if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
+ a1logd(p->log,1,"i1pro_imp_measure malloc %d bytes failed (5)\n",bsize);
+ return I1PRO_INT_MALLOC;
+ }
+ }
+
+ /* Allocate buffer for measurement */
+ maxnummeas = i1pro_comp_nummeas(p, s->maxscantime, s->inttime);
+ if (maxnummeas < nummeas)
+ maxnummeas = nummeas;
+ mbsize = m->nsen * 2 * maxnummeas;
+ if ((mbuf = (unsigned char *)malloc(sizeof(unsigned char) * mbsize)) == NULL) {
+ if (buf != NULL)
+ free(buf);
+ a1logd(p->log,1,"i1pro_imp_measure malloc %d bytes failed (6)\n",mbsize);
+ return I1PRO_INT_MALLOC;
+ }
+ specrd = dmatrix(0, nvals-1, 0, m->nwav[m->highres]-1);
+
+ if (m->trig == inst_opt_trig_user_switch) {
+ m->hide_switch = 1; /* Supress switch events */
+
+#ifdef USE_THREAD
+ {
+ int currcount = m->switch_count; /* Variable set by thread */
+ while (currcount == m->switch_count) {
+ inst_code rc;
+ int cerr;
+
+ /* Don't trigger on user key if scan, only trigger */
+ /* on instrument switch */
+ if (p->uicallback != NULL
+ && (rc = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (rc == inst_user_abort) {
+ ev = I1PRO_USER_ABORT;
+ break; /* Abort */
+ }
+ if (!s->scan && rc == inst_user_trig) {
+ ev = I1PRO_USER_TRIG;
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ msec_sleep(100);
+ }
+ }
+#else
+ /* Throw one away in case the switch was pressed prematurely */
+ i1pro_waitfor_switch_th(p, 0.01);
+
+ for (;;) {
+ inst_code rc;
+ int cerr;
+
+ if ((ev = i1pro_waitfor_switch_th(p, 0.1)) != I1PRO_OK
+ && ev != I1PRO_INT_BUTTONTIMEOUT)
+ break; /* Error */
+
+ if (ev == I1PRO_OK)
+ break; /* switch triggered */
+
+ /* Don't trigger on user key if scan, only trigger */
+ /* on instrument switch */
+ if (p->uicallback != NULL
+ && (rc = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (rc == inst_user_abort) {
+ ev = I1PRO_USER_ABORT;
+ break; /* Abort */
+ }
+ if (!s->scan && rc == inst_user_trig) {
+ ev = I1PRO_USER_TRIG;
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ }
+#endif
+ a1logd(p->log,3,"############# triggered ##############\n");
+ if (p->uicallback) /* Notify of trigger */
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ m->hide_switch = 0; /* Enable switch events again */
+
+ } else if (m->trig == inst_opt_trig_user) {
+ if (p->uicallback == NULL) {
+ a1logd(p->log, 1, "hcfr: inst_opt_trig_user but no uicallback function set!\n");
+ ev = I1PRO_UNSUPPORTED;
+
+ } else {
+
+ for (;;) {
+ inst_code rc;
+ if ((rc = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (rc == inst_user_abort) {
+ ev = I1PRO_USER_ABORT; /* Abort */
+ break;
+ }
+ if (rc == inst_user_trig) {
+ ev = I1PRO_USER_TRIG;
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ msec_sleep(200);
+ }
+ }
+ a1logd(p->log,3,"############# triggered ##############\n");
+ if (p->uicallback) /* Notify of trigger */
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ /* Progromatic Trigger */
+ } else {
+ /* Check for abort */
+ if (p->uicallback != NULL
+ && (ev = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_abort)
+ ev = I1PRO_USER_ABORT; /* Abort */
+ }
+
+ if (ev != I1PRO_OK && ev != I1PRO_USER_TRIG) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav[m->highres]-1);
+ free(mbuf);
+ if (buf != NULL)
+ free(buf);
+ a1logd(p->log,2,"i1pro_imp_measure user aborted, terminated, command, or failure\n");
+ return ev; /* User abort, term, command or failure */
+ }
+
+ if (s->emiss && !s->scan && s->adaptive) {
+ int saturated = 0;
+ double optscale = 1.0;
+ s->inttime = 0.25;
+ s->gainmode = 0;
+ s->dark_valid = 0;
+
+ a1logd(p->log,2,"Trial measure emission with inttime %f, gainmode %d\n",s->inttime,s->gainmode);
+
+ /* Take a trial measurement reading using the current mode. */
+ /* Used to determine if sensor is saturated, or not optimal */
+// nummeas = i1pro_comp_nummeas(p, s->wreadtime, s->inttime);
+ nummeas = 1;
+ if ((ev = i1pro_trialmeasure(p, &saturated, &optscale, nummeas, &s->inttime, s->gainmode,
+ s->targoscale)) != I1PRO_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav[m->highres]-1);
+ free(mbuf);
+ a1logd(p->log,2,"i1pro_imp_measure trial measure failed\n");
+ return ev;
+ }
+
+ if (saturated) {
+ s->inttime = m->min_int_time;
+
+ a1logd(p->log,2,"2nd trial measure emission with inttime %f, gainmode %d\n",
+ s->inttime,s->gainmode);
+ /* Take a trial measurement reading using the current mode. */
+ /* Used to determine if sensor is saturated, or not optimal */
+ nummeas = i1pro_comp_nummeas(p, 0.25, s->inttime);
+ if ((ev = i1pro_trialmeasure(p, &saturated, &optscale, nummeas, &s->inttime,
+ s->gainmode, s->targoscale)) != I1PRO_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav[m->highres]-1);
+ free(mbuf);
+ a1logd(p->log,2,"i1pro_imp_measure trial measure failed\n");
+ return ev;
+ }
+ }
+
+ a1logd(p->log,2,"Compute optimal integration time\n");
+ /* For adaptive mode, compute a new integration time and gain mode */
+ /* in order to optimise the sensor values. */
+ if ((ev = i1pro_optimise_sensor(p, &s->inttime, &s->gainmode,
+ s->inttime, s->gainmode, 1, 1, s->targoscale, optscale)) != I1PRO_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav[m->highres]-1);
+ free(mbuf);
+ a1logd(p->log,2,"i1pro_imp_measure optimise sensor failed\n");
+ return ev;
+ }
+ a1logd(p->log,2,"Computed optimal emiss inttime %f and gainmode %d\n",s->inttime,s->gainmode);
+
+ a1logd(p->log,2,"Interpolate dark calibration reference\n");
+ if ((ev = i1pro_interp_dark(p, s->dark_data, s->inttime, s->gainmode)) != I1PRO_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav[m->highres]-1);
+ free(mbuf);
+ a1logd(p->log,2,"i1pro_imp_measure interplate dark ref failed\n");
+ return ev;
+ }
+ s->dark_valid = 1;
+ s->dark_int_time = s->inttime;
+ s->dark_gain_mode = s->gainmode;
+
+ /* Recompute number of measurements and realloc measurement buffer */
+ free(mbuf);
+ nummeas = i1pro_comp_nummeas(p, s->wreadtime, s->inttime);
+ maxnummeas = i1pro_comp_nummeas(p, s->maxscantime, s->inttime);
+ if (maxnummeas < nummeas)
+ maxnummeas = nummeas;
+ mbsize = m->nsen * 2 * maxnummeas;
+ if ((mbuf = (unsigned char *)malloc(sizeof(unsigned char) * mbsize)) == NULL) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav[m->highres]-1);
+ a1logd(p->log,1,"i1pro_imp_measure malloc %d bytes failed (7)\n",mbsize);
+ return I1PRO_INT_MALLOC;
+ }
+
+ } else if (s->reflective) {
+
+ DISDPLOT
+
+ a1logd(p->log,2,"Doing on the fly black calibration_1 with nummeas %d int_time %f, gainmode %d\n",
+ nummeas, s->inttime, s->gainmode);
+
+ if ((ev = i1pro_dark_measure_1(p, nummeas, &s->inttime, s->gainmode, buf, bsize))
+ != I1PRO_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav[m->highres]-1);
+ free(buf);
+ free(mbuf);
+ a1logd(p->log,2,"i1pro_imp_measure dak measure 1 failed\n");
+ return ev;
+ }
+
+ ENDPLOT
+ }
+ /* Take a measurement reading using the current mode. */
+ /* Converts to completely processed output readings. */
+
+ a1logd(p->log,2,"Do main measurement reading\n");
+
+ /* Indicate to the user that they can now scan the instrument, */
+ /* after a little delay that allows for the instrument reaction time. */
+ if (s->scan) {
+ /* 500msec delay, 1KHz for 200 msec */
+ msec_beep(200 + (int)(s->lamptime * 1000.0 + 0.5), 1000, 200);
+ }
+
+ /* Retry loop for certaing cases */
+ for (;;) {
+
+ /* Trigger measure and gather raw readings */
+ if ((ev = i1pro_read_patches_1(p, nummeas, maxnummeas, &s->inttime, s->gainmode,
+ &nmeasuered, mbuf, mbsize)) != I1PRO_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav[m->highres]-1);
+ if (buf != NULL)
+ free(buf);
+ free(mbuf);
+ a1logd(p->log,2,"i1pro_imp_measure failed at i1pro_read_patches_1\n");
+ return ev;
+ }
+
+ /* Complete reflective black reference measurement */
+ if (s->reflective) {
+ a1logd(p->log,3,"Calling black calibration_2 calc with nummeas %d, inttime %f, gainmode %d\n", nummeas, s->inttime,s->gainmode);
+ if ((ev = i1pro_dark_measure_2(p, s->dark_data,
+ nummeas, s->inttime, s->gainmode, buf, bsize)) != I1PRO_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav[m->highres]-1);
+ free(buf);
+ free(mbuf);
+ a1logd(p->log,2,"i1pro_imp_measure failed at i1pro_dark_measure_2\n");
+ return ev;
+ }
+ s->dark_valid = 1;
+ s->dark_int_time = s->inttime;
+ s->dark_gain_mode = s->gainmode;
+ free(buf);
+ }
+
+ /* Process the raw measurement readings into final spectral readings */
+ ev = i1pro_read_patches_2(p, &duration, specrd, nvals, s->inttime, s->gainmode,
+ nmeasuered, mbuf, mbsize);
+ /* Special case display mode read. If the sensor is saturated, and */
+ /* we haven't already done so, switch to the alternate integration time */
+ /* and try again. */
+ if (s->emiss && !s->scan && !s->adaptive
+ && ev == I1PRO_RD_SENSORSATURATED
+ && s->dispswap < 2) {
+ double *tt, tv;
+
+ if (s->dispswap == 0) {
+ a1logd(p->log,2,"Switching to alternate display integration time %f seconds\n",s->dark_int_time2);
+ tv = s->inttime; s->inttime = s->dark_int_time2; s->dark_int_time2 = tv;
+ tt = s->dark_data; s->dark_data = s->dark_data2; s->dark_data2 = tt;
+ s->dispswap = 1;
+ } else if (s->dispswap == 1) {
+ a1logd(p->log,2,"Switching to 2nd alternate display integration time %f seconds\n",s->dark_int_time3);
+ /* Undo first swap */
+ tv = s->inttime; s->inttime = s->dark_int_time2; s->dark_int_time2 = tv;
+ tt = s->dark_data; s->dark_data = s->dark_data2; s->dark_data2 = tt;
+ /* Do 2nd swap */
+ tv = s->inttime; s->inttime = s->dark_int_time3; s->dark_int_time3 = tv;
+ tt = s->dark_data; s->dark_data = s->dark_data3; s->dark_data3 = tt;
+ s->dispswap = 2;
+ }
+ /* Recompute number of measurements and realloc measurement buffer */
+ free(mbuf);
+ nummeas = i1pro_comp_nummeas(p, s->wreadtime, s->inttime);
+ maxnummeas = i1pro_comp_nummeas(p, s->maxscantime, s->inttime);
+ if (maxnummeas < nummeas)
+ maxnummeas = nummeas;
+ mbsize = m->nsen * 2 * maxnummeas;
+ if ((mbuf = (unsigned char *)malloc(sizeof(unsigned char) * mbsize)) == NULL) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav[m->highres]-1);
+ a1logd(p->log,1,"i1pro_imp_measure malloc %d bytes failed (7)\n",mbsize);
+ return I1PRO_INT_MALLOC;
+ }
+ continue; /* Do the measurement again */
+ }
+
+ if (ev != I1PRO_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav[m->highres]-1);
+ free(mbuf);
+ a1logd(p->log,2,"i1pro_imp_measure failed at i1pro_read_patches_2\n");
+ return ev;
+ }
+ break; /* Don't repeat */
+ }
+ free(mbuf);
+
+ /* Transfer spectral and convert to XYZ */
+ if ((ev = i1pro_conv2XYZ(p, vals, nvals, specrd, clamp)) != I1PRO_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav[m->highres]-1);
+ a1logd(p->log,2,"i1pro_imp_measure failed at i1pro_conv2XYZ\n");
+ return ev;
+ }
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav[m->highres]-1);
+
+ if (nvals > 0)
+ vals[0].duration = duration; /* Possible flash duration */
+
+ /* Update log counters */
+ if (s->reflective) {
+ if (s->scan)
+ m->acount++;
+ else {
+ m->rpinttime = s->inttime;
+ m->rpcount++;
+ }
+ }
+
+ a1logd(p->log,3,"i1pro_imp_measure sucessful return\n");
+ if (user_trig)
+ return I1PRO_USER_TRIG;
+ return ev;
+}
+
+/* - - - - - - - - - - - - - - - - */
+/*
+
+ Determining the refresh rate for a refresh type display.
+
+ This is easy when the max sample rate of the i1 is above
+ the nyquist of the display, and will always be the case
+ for the range we are prepared to measure (up to 100Hz)
+ when using an Rev B, D or E, but is a problem for the
+ rev A and ColorMunki, which can only sample at 113Hz.
+
+ We work around this problem by detecting when
+ we are measuring an alias of the refresh rate, and
+ average the aliasing corrected measurements.
+
+ If there is no aparent refresh, or the refresh rate is not determinable,
+ return a period of 0.0 and inst_ok;
+*/
+
+i1pro_code i1pro_measure_rgb(i1pro *p, double *inttime, double *rgb);
+
+#ifndef PSRAND32L
+# define PSRAND32L(S) ((S) * 1664525L + 1013904223L)
+#endif
+#undef FREQ_SLOW_PRECISE /* [und] Interpolate then autocorrelate, else autc & filter */
+#define NFSAMPS 80 /* Number of samples to read */
+#define NFMXTIME 6.0 /* Maximum time to take (2000 == 6) */
+#define PBPMS 20 /* bins per msec */
+#define PERMIN ((1000 * PBPMS)/40) /* 40 Hz */
+#define PERMAX ((1000 * PBPMS)/4) /* 4 Hz*/
+#define NPER (PERMAX - PERMIN + 1)
+#define PWIDTH (8 * PBPMS) /* 8 msec bin spread to look for peak in */
+#define MAXPKS 20 /* Number of peaks to find */
+#define TRIES 8 /* Number of different sample rates to try */
+
+i1pro_code i1pro_imp_meas_refrate(
+ i1pro *p,
+ double *ref_rate
+) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ int i, j, k, mm;
+ double **multimeas; /* Spectral measurements */
+ int nummeas;
+ double rgbw[3] = { 610.0, 520.0, 460.0 };
+ double ucalf = 1.0; /* usec_time calibration factor */
+ double inttime;
+ static unsigned int randn = 0x12345678;
+ struct {
+ double sec;
+ double rgb[3];
+ } samp[NFSAMPS * 2];
+ int nfsamps; /* Actual samples read */
+ double minv[3]; /* Minimum reading */
+ double maxv[3]; /* Maximum reading */
+ double maxt; /* Time range */
+#ifdef FREQ_SLOW_PRECISE
+ int nbins;
+ double *bins[3]; /* PBPMS sample bins */
+#else
+ double tcorr[NPER]; /* Temp for initial autocorrelation */
+ int ntcorr[NPER]; /* Number accumulated */
+#endif
+ double corr[NPER]; /* Filtered correlation for each period value */
+ double mincv, maxcv; /* Max and min correlation values */
+ double crange; /* Correlation range */
+ double peaks[MAXPKS]; /* Peak wavelength */
+ double peakh[MAXPKS]; /* Peak heighheight */
+ int npeaks; /* Number of peaks */
+ double pval; /* Period value */
+ double rfreq[TRIES]; /* Computed refresh frequency for each try */
+ double rsamp[TRIES]; /* Sampling rate used to measure frequency */
+ int tix = 0; /* try index */
+
+ a1logd(p->log,2,"i1pro_imp_meas_refrate called\n");
+
+ if (!s->emiss) {
+ a1logd(p->log,2,"i1pro_imp_meas_refrate not in emissive mode\n");
+ return I1PRO_UNSUPPORTED;
+ }
+
+ for (mm = 0; mm < TRIES; mm++) {
+ rfreq[mm] = 0.0;
+ npeaks = 0; /* Number of peaks */
+ nummeas = NFSAMPS;
+ multimeas = dmatrix(0, nummeas-1, -1, m->nwav[m->highres]-1);
+
+ if (mm == 0)
+ inttime = m->min_int_time;
+ else {
+ double rval, dmm;
+ randn = PSRAND32L(randn);
+ rval = (double)randn/4294967295.0;
+ dmm = ((double)mm + rval - 0.5)/(TRIES - 0.5);
+ inttime = m->min_int_time * (1.0 + dmm * 0.80);
+ }
+
+ if ((ev = i1pro_read_patches_all(p, multimeas, nummeas, &inttime, 0)) != inst_ok) {
+ free_dmatrix(multimeas, 0, nummeas-1, 0, m->nwav[m->highres]-1);
+ return ev;
+ }
+
+ rsamp[tix] = 1.0/inttime;
+
+ /* Convert the samples to RGB */
+ for (i = 0; i < nummeas && i < NFSAMPS; i++) {
+ samp[i].sec = i * inttime;
+ samp[i].rgb[0] = samp[i].rgb[1] = samp[i].rgb[2] = 0.0;
+ for (j = 0; j < m->nwav[m->highres]; j++) {
+ double wl = XSPECT_WL(m->wl_short[m->highres], m->wl_long[m->highres], m->nwav[m->highres], j);
+
+//printf("~1 multimeas %d %d = %f\n",i, j, multimeas[i][j]);
+ for (k = 0; k < 3; k++) {
+ double tt = (double)(wl - rgbw[k]);
+ tt = (40.0 - fabs(tt))/40.0;
+ if (tt < 0.0)
+ tt = 0.0;
+ samp[i].rgb[k] += tt * multimeas[i][j];
+ }
+ }
+ }
+ nfsamps = i;
+
+ a1logd(p->log, 3, "i1pro_measure_refresh: Read %d samples for refresh calibration\n",nfsamps);
+
+#ifdef NEVER
+ /* Plot the raw sensor values */
+ {
+ double xx[NFSAMPS];
+ double y1[NFSAMPS];
+ double y2[NFSAMPS];
+ double y3[NFSAMPS];
+
+ for (i = 0; i < nfsamps; i++) {
+ xx[i] = samp[i].sec;
+ y1[i] = samp[i].rgb[0];
+ y2[i] = samp[i].rgb[1];
+ y3[i] = samp[i].rgb[2];
+// printf("%d: %f -> %f\n",i,samp[i].sec, samp[i].rgb[0]);
+ }
+ printf("Fast scan sensor values and time (sec)\n");
+ do_plot6(xx, y1, y2, y3, NULL, NULL, NULL, nfsamps);
+ }
+#endif
+
+ /* Locate the smallest values and maximum time */
+ maxt = -1e6;
+ minv[0] = minv[1] = minv[2] = 1e20;
+ maxv[0] = maxv[1] = maxv[2] = -11e20;
+ for (i = nfsamps-1; i >= 0; i--) {
+ if (samp[i].sec > maxt)
+ maxt = samp[i].sec;
+ for (j = 0; j < 3; j++) {
+ if (samp[i].rgb[j] < minv[j])
+ minv[j] = samp[i].rgb[j];
+ if (samp[i].rgb[j] > maxv[j])
+ maxv[j] = samp[i].rgb[j];
+ }
+ }
+ /* Re-zero the sample times, and normalise the readings */
+ for (i = nfsamps-1; i >= 0; i--) {
+ samp[i].sec -= samp[0].sec;
+ samp[i].sec *= ucalf;
+ if (samp[i].sec > maxt)
+ maxt = samp[i].sec;
+ for (j = 0; j < 3; j++) {
+ samp[i].rgb[j] -= minv[j];
+ }
+ }
+
+#ifdef FREQ_SLOW_PRECISE /* Interp then autocorrelate */
+
+ /* Create PBPMS bins and interpolate readings into them */
+ nbins = 1 + (int)(maxt * 1000.0 * PBPMS + 0.5);
+ for (j = 0; j < 3; j++) {
+ if ((bins[j] = (double *)calloc(sizeof(double), nbins)) == NULL) {
+ a1loge(p->log, inst_internal_error, "i1pro_measure_refresh: malloc failed\n");
+ return I1PRO_INT_MALLOC;
+ }
+ }
+
+ /* Do the interpolation */
+ for (k = 0; k < (nfsamps-1); k++) {
+ int sbin, ebin;
+ sbin = (int)(samp[k].sec * 1000.0 * PBPMS + 0.5);
+ ebin = (int)(samp[k+1].sec * 1000.0 * PBPMS + 0.5);
+ for (i = sbin; i <= ebin; i++) {
+ double bl;
+#if defined(__APPLE__) && defined(__POWERPC__)
+ gcc_bug_fix(i);
+#endif
+ bl = (i - sbin)/(double)(ebin - sbin); /* 0.0 to 1.0 */
+ for (j = 0; j < 3; j++) {
+ bins[j][i] = (1.0 - bl) * samp[k].rgb[j] + bl * samp[k+1].rgb[j];
+ }
+ }
+ }
+
+#ifdef NEVER
+
+ /* Plot interpolated values */
+ {
+ double *xx;
+ double *y1;
+ double *y2;
+ double *y3;
+
+ xx = malloc(sizeof(double) * nbins);
+ y1 = malloc(sizeof(double) * nbins);
+ y2 = malloc(sizeof(double) * nbins);
+ y3 = malloc(sizeof(double) * nbins);
+
+ if (xx == NULL || y1 == NULL || y2 == NULL || y3 == NULL) {
+ a1loge(p->log, inst_internal_error, "i1pro_measure_refresh: malloc failed\n");
+ for (j = 0; j < 3; j++)
+ free(bins[j]);
+ return I1PRO_INT_MALLOC;
+ }
+ for (i = 0; i < nbins; i++) {
+ xx[i] = i / (double)PBPMS; /* msec */
+ y1[i] = bins[0][i];
+ y2[i] = bins[1][i];
+ y3[i] = bins[2][i];
+ }
+ printf("Interpolated fast scan sensor values and time (msec) for inttime %f\n",inttime);
+ do_plot6(xx, y1, y2, y3, NULL, NULL, NULL, nbins);
+
+ free(xx);
+ free(y1);
+ free(y2);
+ free(y3);
+ }
+#endif /* PLOT_REFRESH */
+
+ /* Compute auto-correlation at 1/PBPMS msec intervals */
+ /* from 25 msec (40Hz) to 100msec (10 Hz) */
+ mincv = 1e48, maxcv = -1e48;
+ for (i = 0; i < NPER; i++) {
+ int poff = PERMIN + i; /* Offset to corresponding sample */
+
+ corr[i] = 0;
+ for (k = 0; (k + poff) < nbins; k++) {
+ corr[i] += bins[0][k] * bins[0][k + poff]
+ + bins[1][k] * bins[1][k + poff]
+ + bins[2][k] * bins[2][k + poff];
+ }
+ corr[i] /= (double)k; /* Normalize */
+
+ if (corr[i] > maxcv)
+ maxcv = corr[i];
+ if (corr[i] < mincv)
+ mincv = corr[i];
+ }
+ /* Free the bins */
+ for (j = 0; j < 3; j++)
+ free(bins[j]);
+
+#else /* !FREQ_SLOW_PRECISE Fast - autocorrellate then filter */
+
+ /* Upsample by a factor of 2 */
+ for (i = nfsamps-1; i >= 0; i--) {
+ j = 2 * i;
+ samp[j].sec = samp[i].sec;
+ samp[j].rgb[0] = samp[i].rgb[0];
+ samp[j].rgb[1] = samp[i].rgb[1];
+ samp[j].rgb[2] = samp[i].rgb[2];
+ if (i > 0) {
+ j--;
+ samp[j].sec = 0.5 * (samp[i].sec + samp[i-1].sec);
+ samp[j].rgb[0] = 0.5 * (samp[i].rgb[0] + samp[i-1].rgb[0]);
+ samp[j].rgb[1] = 0.5 * (samp[i].rgb[1] + samp[i-1].rgb[1]);
+ samp[j].rgb[2] = 0.5 * (samp[i].rgb[2] + samp[i-1].rgb[2]);
+ }
+ }
+ nfsamps = 2 * nfsamps - 1;
+
+ /* Do point by point correllation of samples */
+ for (i = 0; i < NPER; i++) {
+ tcorr[i] = 0.0;
+ ntcorr[i] = 0;
+ }
+
+ for (j = 0; j < (nfsamps-1); j++) {
+
+ for (k = j+1; k < nfsamps; k++) {
+ double del, cor;
+ int bix;
+
+ del = samp[k].sec - samp[j].sec;
+ bix = (int)(del * 1000.0 * PBPMS + 0.5);
+ if (bix < PERMIN)
+ continue;
+ if (bix > PERMAX)
+ break;
+ bix -= PERMIN;
+
+ cor = samp[j].rgb[0] * samp[k].rgb[0]
+ + samp[j].rgb[1] * samp[k].rgb[1]
+ + samp[j].rgb[2] * samp[k].rgb[2];
+
+//printf("~1 j %d k %d, del %f bix %d cor %f\n",j,k,del,bix,cor);
+ tcorr[bix] += cor;
+ ntcorr[bix]++;
+ }
+ }
+ /* Divide out count and linearly interpolate */
+ j = 0;
+ for (i = 0; i < NPER; i++) {
+ if (ntcorr[i] > 0) {
+ tcorr[i] /= ntcorr[i];
+ if ((i - j) > 1) {
+ if (j == 0) {
+ for (k = j; k < i; k++)
+ tcorr[k] = tcorr[i];
+
+ } else { /* Linearly interpolate from last value */
+ double ww = (double)i-j;
+ for (k = j+1; k < i; k++) {
+ double bl = (k-j)/ww;
+ tcorr[k] = (1.0 - bl) * tcorr[j] + bl * tcorr[i];
+ }
+ }
+ }
+ j = i;
+ }
+ }
+ if (j < (NPER-1)) {
+ for (k = j+1; k < NPER; k++) {
+ tcorr[k] = tcorr[j];
+ }
+ }
+
+#ifdef PLOT_REFRESH
+ /* Plot unfiltered auto correlation */
+ {
+ double xx[NPER];
+ double y1[NPER];
+
+ for (i = 0; i < NPER; i++) {
+ xx[i] = (i + PERMIN) / (double)PBPMS; /* msec */
+ y1[i] = tcorr[i];
+ }
+ printf("Unfiltered auto correlation (msec)\n");
+ do_plot6(xx, y1, NULL, NULL, NULL, NULL, NULL, NPER);
+ }
+#endif /* PLOT_REFRESH */
+
+ /* Apply a gausian filter */
+#define FWIDTH 100
+ {
+ double gaus_[2 * FWIDTH * PBPMS + 1];
+ double *gaus = &gaus_[FWIDTH * PBPMS];
+ double bb = 1.0/pow(2, 5.0);
+ double fw = inttime * 1000.0;
+ int ifw;
+
+//printf("~1 sc = %f = %f msec\n",1.0/inttime, fw);
+//printf("~1 fw = %f, ifw = %d\n",fw,ifw);
+
+ fw *= 0.9;
+ ifw = (int)ceil(fw * PBPMS);
+ if (ifw > FWIDTH * PBPMS)
+ error("i1pro: Not enough space for lanczos 2 filter");
+ for (j = -ifw; j <= ifw; j++) {
+ double x, y;
+ x = j/(PBPMS * fw);
+ if (fabs(x) > 1.0)
+ y = 0.0;
+ else
+ y = 1.0/pow(2, 5.0 * x * x) - bb;
+ gaus[j] = y;
+//printf("~1 gaus[%d] = %f\n",j,y);
+ }
+
+ for (i = 0; i < NPER; i++) {
+ double sum = 0.0;
+ double wght = 0.0;
+
+ for (j = -ifw; j <= ifw; j++) {
+ double w;
+ int ix = i + j;
+ if (ix < 0)
+ ix = -ix;
+ if (ix > (NPER-1))
+ ix = 2 * NPER-1 - ix;
+ w = gaus[j];
+ sum += w * tcorr[ix];
+ wght += w;
+ }
+//printf("~1 corr[%d] wgt = %f\n",i,wght);
+ corr[i] = sum / wght;
+ }
+ }
+
+ /* Compute min & max */
+ mincv = 1e48, maxcv = -1e48;
+ for (i = 0; i < NPER; i++) {
+ if (corr[i] > maxcv)
+ maxcv = corr[i];
+ if (corr[i] < mincv)
+ mincv = corr[i];
+ }
+
+#endif /* !FREQ_SLOW_PRECISE Fast - autocorrellate then filter */
+
+ crange = maxcv - mincv;
+ a1logd(p->log,3,"Correlation value range %f - %f = %f = %f%%\n",mincv, maxcv,crange, 100.0 * (maxcv-mincv)/maxcv);
+
+#ifdef PLOT_REFRESH
+ /* Plot this measuremnts auto correlation */
+ {
+ double xx[NPER];
+ double y1[NPER];
+
+ for (i = 0; i < NPER; i++) {
+ xx[i] = (i + PERMIN) / (double)PBPMS; /* msec */
+ y1[i] = corr[i];
+ }
+ printf("Auto correlation (msec)\n");
+ do_plot6(xx, y1, NULL, NULL, NULL, NULL, NULL, NPER);
+ }
+#endif /* PLOT_REFRESH */
+
+#define PFDB 4 // normally 4
+ /* If there is sufficient level and distict correlations */
+ if (crange/maxcv >= 0.1) {
+
+ a1logd(p->log,PFDB,"Searching for peaks\n");
+
+ /* Locate all the peaks starting at the longest correllation */
+ for (i = (NPER-1-PWIDTH); i >= 0 && npeaks < MAXPKS; i--) {
+ double v1, v2, v3;
+ v1 = corr[i];
+ v2 = corr[i + PWIDTH/2]; /* Peak */
+ v3 = corr[i + PWIDTH];
+
+ if (fabs(v3 - v1)/crange < 0.05
+ && (v2 - v1)/crange > 0.025
+ && (v2 - v3)/crange > 0.025
+ && (v2 - mincv)/crange > 0.5) {
+ double pkv; /* Peak value */
+ int pki; /* Peak index */
+ double ii, bl;
+
+#ifdef PLOT_REFRESH
+ a1logd(p->log,PFDB,"Max between %f and %f msec\n",
+ (i + PERMIN)/(double)PBPMS,(i + PWIDTH + PERMIN)/(double)PBPMS);
+#endif
+
+ /* Locate the actual peak */
+ pkv = -1.0;
+ pki = 0;
+ for (j = i; j < (i + PWIDTH); j++) {
+ if (corr[j] > pkv) {
+ pkv = corr[j];
+ pki = j;
+ }
+ }
+#ifdef PLOT_REFRESH
+ a1logd(p->log,PFDB,"Peak is at %f msec, %f corr\n", (pki + PERMIN)/(double)PBPMS, pkv);
+#endif
+
+ /* Interpolate the peak value for higher precision */
+ /* j = bigest */
+ if (corr[pki-1] > corr[pki+1]) {
+ j = pki-1;
+ k = pki+1;
+ } else {
+ j = pki+1;
+ k = pki-1;
+ }
+ bl = (corr[pki] - corr[j])/(corr[pki] - corr[k]);
+ bl = (bl + 1.0)/2.0;
+ ii = bl * pki + (1.0 - bl) * j;
+ pval = (ii + PERMIN)/(double)PBPMS;
+#ifdef PLOT_REFRESH
+ a1logd(p->log,PFDB,"Interpolated peak is at %f msec\n", pval);
+#endif
+ peaks[npeaks] = pval;
+ peakh[npeaks] = corr[pki];
+ npeaks++;
+
+ i -= PWIDTH;
+ }
+#ifdef NEVER
+ if (v2 > v1 && v2 > v3) {
+ printf("Peak rehjected:\n");
+ printf("(v3 - v1)/crange = %f < 0.05 ?\n",fabs(v3 - v1)/crange);
+ printf("(v2 - v1)/crange = %f > 0.025 ?\n",(v2 - v1)/crange);
+ printf("(v2 - v3)/crange = %f > 0.025 ?\n",(v2 - v3)/crange);
+ printf("(v2 - mincv)/crange = %f > 0.5 ?\n",(v2 - mincv)/crange);
+ }
+#endif
+ }
+ a1logd(p->log,3,"Number of peaks located = %d\n",npeaks);
+
+ } else {
+ a1logd(p->log,3,"All rejected, crange/maxcv = %f < 0.06\n",crange/maxcv);
+ }
+#undef PFDB
+
+ a1logd(p->log,3,"Number of peaks located = %d\n",npeaks);
+
+ if (npeaks > 1) { /* Compute aparent refresh rate */
+ int nfails;
+ double div, avg, ano;
+ /* Try and locate a common divisor amongst all the peaks. */
+ /* This is likely to be the underlying refresh rate. */
+ for (k = 0; k < npeaks; k++) {
+ for (j = 1; j < 25; j++) {
+ avg = ano = 0.0;
+ div = peaks[k]/(double)j;
+ if (div < 5.0)
+ continue; /* Skip anything higher than 200Hz */
+//printf("~1 trying %f Hz\n",1000.0/div);
+ for (nfails = i = 0; i < npeaks; i++) {
+ double rem, cnt;
+
+ rem = peaks[i]/div;
+ cnt = floor(rem + 0.5);
+ rem = fabs(rem - cnt);
+
+#ifdef PLOT_REFRESH
+ a1logd(p->log, 3, "remainder for peak %d = %f\n",i,rem);
+#endif
+ if (rem > 0.06) {
+ if (++nfails > 2)
+ break; /* Fail this divisor */
+ } else {
+ avg += peaks[i]; /* Already weighted by cnt */
+ ano += cnt;
+ }
+ }
+
+ if (nfails == 0 || (nfails <= 2 && npeaks >= 6))
+ break; /* Sucess */
+ /* else go and try a different divisor */
+ }
+ if (j < 25)
+ break; /* Success - found common divisor */
+ }
+ if (k >= npeaks) {
+ a1logd(p->log,3,"Failed to locate common divisor\n");
+
+ } else {
+ pval = 0.001 * avg/ano;
+ if (pval < inttime) {
+ a1logd(p->log,3,"Discarding frequency %f > sample rate %f\n",1.0/pval, 1.0/inttime);
+ } else {
+ pval = 1.0/pval; /* Convert to frequency */
+ rfreq[tix++] = pval;
+ a1logd(p->log,3,"Located frequency %f sum %f dif %f\n",pval, pval + 1.0/inttime, fabs(pval - 1.0/inttime));
+ }
+ }
+ }
+ }
+
+ if (tix >= 3) {
+
+ for (mm = 0; mm < tix; mm++) {
+ a1logd(p->log, 3, "Try %d, samp %f Hz, Meas %f Hz, Sum %f Hz, Dif %f Hz\n",mm,rsamp[mm],rfreq[mm], rsamp[mm] + rfreq[mm], fabs(rsamp[mm] - rfreq[mm]));
+ }
+
+ /* Decide if we are above the nyquist, or whether */
+ /* we have aliases of the fundamental */
+ {
+ double brange = 1e38;
+ double brate = 0.0;
+ int bsplit = -1;
+ double min, max, avg, range;
+ int split, mul, niia;
+
+ /* Compute fundamental and sub aliases at all possible splits. */
+ /* Skip the reading at the split. */
+ for (split = tix; split >= -1; split--) {
+ min = 1e38; max = -1e38; avg = 0.0; niia = 0;
+ for (mm = 0; mm < tix; mm++) {
+ double alias;
+
+ if (mm == split)
+ continue;
+ if (mm < split)
+ alias = rfreq[mm];
+ else
+ alias = fabs(rsamp[mm] - rfreq[mm]);
+
+ avg += alias;
+ niia++;
+
+ if (alias < min)
+ min = alias;
+ if (alias > max)
+ max = alias;
+ }
+ avg /= (double)niia;
+ range = (max - min)/(max + min);
+//printf("~1 split %d avg = %f, range = %f\n",split,avg,range);
+ if (range < brange) {
+ brange = range;
+ brate = avg;
+ bsplit = split;
+ }
+ }
+
+ /* Compute sub and add aliases at all possible splits */
+ /* Skip the reading at the split. */
+ for (split = tix; split >= -1; split--) {
+ min = 1e38; max = -1e38; avg = 0.0; niia = 0;
+ for (mm = 0; mm < tix; mm++) {
+ double alias;
+
+ if (mm == split)
+ continue;
+ if (mm < split)
+ alias = fabs(rsamp[mm] - rfreq[mm]);
+ else
+ alias = rsamp[mm] + rfreq[mm];
+
+ avg += alias;
+ niia++;
+
+ if (alias < min)
+ min = alias;
+ if (alias > max)
+ max = alias;
+ }
+ avg /= (double)niia;
+ range = (max - min)/(max + min);
+//printf("~1 split %d avg = %f, range = %f\n",100 + split,avg,range);
+ if (range < brange) {
+ brange = range;
+ brate = avg;
+ bsplit = 100 + split;
+ }
+ }
+
+ a1logd(p->log, 3, "Selected split %d range %f\n",bsplit,brange);
+
+ /* Hmm. Could reject result and re-try if brange is too large ? ( > 0.005 ?) */
+
+ if (brange > 0.05) {
+ a1logd(p->log, 3, "Readings are too inconsistent (brange %.1f%%) - should retry ?\n",brange * 100.0);
+ } else {
+ if (ref_rate != NULL)
+ *ref_rate = brate;
+
+ /* Error against my 85Hz CRT - GWG */
+// a1logd(p->log, 1, "Refresh rate %f Hz, error = %.4f%%\n",brate,100.0 * fabs(brate - 85.0)/(85.0));
+ return I1PRO_OK;
+ }
+ }
+ } else {
+ a1logd(p->log, 3, "Not enough tries suceeded to determine refresh rate\n");
+ }
+
+ if (ref_rate != NULL)
+ *ref_rate = 0.0;
+
+ return I1PRO_RD_NOREFR_FOUND;
+}
+#undef NFSAMPS
+#undef PBPMS
+#undef PERMIN
+#undef PERMAX
+#undef NPER
+#undef PWIDTH
+
+/* - - - - - - - - - - - - - - - - - - - - - - */
+/* i1 refspot calibration/log stored on instrument */
+/* RevA..D only! */
+
+/* Restore the reflective spot calibration information from the EEPRom */
+/* Always returns success, even if the restore fails, */
+/* which may happen for an instrument that's never been used or had calibration */
+/* written to its EEProm */
+/* RevA..D only! */
+i1pro_code i1pro_restore_refspot_cal(i1pro *p) {
+ int chsum1, *chsum2;
+ int *ip, i;
+ unsigned int count;
+ double *dp;
+ unsigned char buf[256];
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[i1p_refl_spot]; /* NOT current mode, refspot mode */
+ i1key offst = 0; /* Offset to copy to use */
+ i1pro_code ev = I1PRO_OK;
+ int o_nsen; /* Actual nsen data */
+
+ a1logd(p->log,2,"Doing Restoring reflective spot calibration information from the EEProm\n");
+
+ chsum1 = m->data->checksum(m->data, 0);
+ if ((chsum2 = m->data->get_int(m->data, key_checksum, 0)) == NULL || chsum1 != *chsum2) {
+ offst = key_2logoff;
+ chsum1 = m->data->checksum(m->data, key_2logoff);
+ if ((chsum2 = m->data->get_int(m->data, key_checksum + key_2logoff, 0)) == NULL
+ || chsum1 != *chsum2) {
+ a1logd(p->log,2,"Neither EEPRom checksum was valid\n");
+ return I1PRO_OK;
+ }
+ }
+
+ /* Get the calibration gain mode */
+ if ((ip = m->data->get_ints(m->data, &count, key_gainmode + offst)) == NULL || count < 1) {
+ a1logd(p->log,2,"Failed to read calibration gain mode from EEPRom\n");
+ return I1PRO_OK;
+ }
+ if (ip[0] == 0) {
+#ifdef USE_HIGH_GAIN_MODE
+ s->gainmode = 1;
+#else
+ s->gainmode = 0;
+ a1logd(p->log,2,"Calibration gain mode was high, and high gain not compiled in\n");
+ return I1PRO_OK;
+#endif /* !USE_HIGH_GAIN_MODE */
+ } else
+ s->gainmode = 0;
+
+ /* Get the calibration integrattion time */
+ if ((dp = m->data->get_doubles(m->data, &count, key_inttime + offst)) == NULL || count < 1) {
+ a1logd(p->log,2,"Failed to read calibration integration time from EEPRom\n");
+ return I1PRO_OK;
+ }
+ s->inttime = dp[0];
+ if (s->inttime < m->min_int_time) /* Hmm. EEprom is occasionaly screwed up */
+ s->inttime = m->min_int_time;
+
+ /* Get the dark data */
+ if ((ip = m->data->get_ints(m->data, &count, key_darkreading + offst)) == NULL
+ || count != 128) {
+ a1logv(p->log,1,"Failed to read calibration dark data from EEPRom\n");
+ return I1PRO_OK;
+ }
+
+ /* Convert back to a single raw big endian instrument readings */
+ for (i = 0; i < 128; i++) {
+ buf[i * 2 + 0] = (ip[i] >> 8) & 0xff;
+ buf[i * 2 + 1] = ip[i] & 0xff;
+ }
+
+ /* Convert to calibration data */
+ a1logd(p->log,3,"Calling black calibration_2 calc with nummeas %d, inttime %f, gainmode %d\n", 1, s->inttime,s->gainmode);
+ o_nsen = m->nsen;
+ m->nsen = 128; /* Assume EEprom cal data is <= Rev D format */
+ if ((ev = i1pro_dark_measure_2(p, s->dark_data, 1, s->inttime, s->gainmode,
+ buf, 256)) != I1PRO_OK) {
+ a1logd(p->log,2,"Failed to convert EEProm dark data to calibration\n");
+ m->nsen = o_nsen;
+ return I1PRO_OK;
+ }
+
+ /* We've sucessfully restored the dark calibration */
+ s->dark_valid = 1;
+ s->ddate = m->caldate;
+
+ /* Get the white calibration data */
+ if ((ip = m->data->get_ints(m->data, &count, key_whitereading + offst)) == NULL
+ || count != 128) {
+ a1logd(p->log,2,"Failed to read calibration white data from EEPRom\n");
+ m->nsen = o_nsen;
+ return I1PRO_OK;
+ }
+
+ /* Convert back to a single raw big endian instrument readings */
+ for (i = 0; i < 128; i++) {
+ buf[i * 2 + 0] = (ip[i] >> 8) & 0xff;
+ buf[i * 2 + 1] = ip[i] & 0xff;
+ }
+
+ /* Convert to calibration data */
+ m->nsen = 128; /* Assume EEprom cal data is <= Rev D format */
+ if ((ev = i1pro_whitemeasure_buf(p, s->cal_factor[0], s->cal_factor[1], s->white_data,
+ s->inttime, s->gainmode, buf)) != I1PRO_OK) {
+ /* This may happen for an instrument that's never been used */
+ a1logd(p->log,2,"Failed to convert EEProm white data to calibration\n");
+ m->nsen = o_nsen;
+ return I1PRO_OK;
+ }
+ m->nsen = o_nsen;
+
+ /* Check a reflective white measurement, and check that */
+ /* it seems reasonable. Return I1PRO_OK if it is, error if not. */
+ /* (Using cal_factor[] as temp.) */
+ if ((ev = i1pro_check_white_reference1(p, s->cal_factor[0])) != I1PRO_OK) {
+ /* This may happen for an instrument that's never been used */
+ a1logd(p->log,2,"Failed to convert EEProm white data to calibration\n");
+ return I1PRO_OK;
+ }
+ /* Compute a calibration factor given the reading of the white reference. */
+ i1pro_compute_white_cal(p, s->cal_factor[0], m->white_ref[0], s->cal_factor[0],
+ s->cal_factor[1], m->white_ref[1], s->cal_factor[1]);
+
+ /* We've sucessfully restored the calibration */
+ s->cal_valid = 1;
+ s->cfdate = m->caldate;
+
+ return I1PRO_OK;
+}
+
+/* Save the reflective spot calibration information to the EEPRom data object. */
+/* Note we don't actually write to the EEProm here! */
+/* For RevA..D only! */
+static i1pro_code i1pro_set_log_data(i1pro *p) {
+ int *ip, i;
+ unsigned int count;
+ double *dp;
+ double absmeas[128];
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[i1p_refl_spot]; /* NOT current mode, refspot mode */
+ i1key offst = 0; /* Offset to copy to use */
+ i1pro_code ev = I1PRO_OK;
+
+ a1logd(p->log,3,"i1pro_set_log_data called\n");
+
+ if (s->dark_valid == 0 || s->cal_valid == 0)
+ return I1PRO_INT_NO_CAL_TO_SAVE;
+
+ /* Set the calibration gain mode */
+ if ((ip = m->data->get_ints(m->data, &count, key_gainmode + offst)) == NULL || count < 1) {
+ a1logd(p->log,2,"Failed to access calibration gain mode from EEPRom\n");
+ return I1PRO_INT_EEPROM_DATA_MISSING;
+ }
+ if (s->gainmode == 0)
+ ip[0] = 1;
+ else
+ ip[0] = 0;
+
+ /* Set the calibration integration time */
+ if ((dp = m->data->get_doubles(m->data, &count, key_inttime + offst)) == NULL || count < 1) {
+ a1logd(p->log,2,"Failed to read calibration integration time from EEPRom\n");
+ return I1PRO_INT_EEPROM_DATA_MISSING;
+ }
+ dp[0] = s->inttime;
+
+ /* Set the dark data */
+ if ((ip = m->data->get_ints(m->data, &count, key_darkreading + offst)) == NULL
+ || count != 128) {
+ a1logd(p->log,2,"Failed to access calibration dark data from EEPRom\n");
+ return I1PRO_INT_EEPROM_DATA_MISSING;
+ }
+
+ /* Convert abs dark_data to raw data */
+ if ((ev = i1pro_absraw_to_meas(p, ip, s->dark_data, s->inttime, s->gainmode)) != I1PRO_OK)
+ return ev;
+
+ /* Add back black level to white data */
+ for (i = 0; i < 128; i++)
+ absmeas[i] = s->white_data[i] + s->dark_data[i];
+
+ /* Get the white data */
+ if ((ip = m->data->get_ints(m->data, &count, key_whitereading + offst)) == NULL
+ || count != 128) {
+ a1logd(p->log,2,"Failed to access calibration white data from EEPRom\n");
+ return I1PRO_INT_EEPROM_DATA_MISSING;
+ }
+
+ /* Convert abs white_data to raw data */
+ if ((ev = i1pro_absraw_to_meas(p, ip, absmeas, s->inttime, s->gainmode)) != I1PRO_OK)
+ return ev;
+
+ /* Set all the log counters */
+
+ /* Total Measure (Emis/Remis/Ambient/Trans/Cal) count */
+ if ((ip = m->data->get_ints(m->data, &count, key_meascount)) == NULL || count < 1) {
+ a1logd(p->log,2,"Failed to access meascount log counter from EEPRom\n");
+ return I1PRO_INT_EEPROM_DATA_MISSING;
+ }
+ ip[0] = m->meascount;
+
+ /* Remspotcal last calibration date */
+ if ((ip = m->data->get_ints(m->data, &count, key_caldate)) == NULL || count < 1) {
+ a1logd(p->log,2,"Failed to access caldate log counter from EEPRom\n");
+ return I1PRO_INT_EEPROM_DATA_MISSING;
+ }
+ ip[0] = m->caldate;
+
+ /* Remission spot measure count at last Remspotcal. */
+ if ((ip = m->data->get_ints(m->data, &count, key_calcount)) == NULL || count < 1) {
+ a1logd(p->log,2,"Failed to access calcount log counter from EEPRom\n");
+ return I1PRO_INT_EEPROM_DATA_MISSING;
+ }
+ ip[0] = m->calcount;
+
+ /* Last remision spot reading integration time */
+ if ((dp = m->data->get_doubles(m->data, &count, key_rpinttime)) == NULL || count < 1) {
+ a1logd(p->log,2,"Failed to access rpinttime log counter from EEPRom\n");
+ return I1PRO_INT_EEPROM_DATA_MISSING;
+ }
+ dp[0] = m->rpinttime;
+
+ /* Remission spot measure count */
+ if ((ip = m->data->get_ints(m->data, &count, key_rpcount)) == NULL || count < 1) {
+ a1logd(p->log,2,"Failed to access rpcount log counter from EEPRom\n");
+ return I1PRO_INT_EEPROM_DATA_MISSING;
+ }
+ ip[0] = m->rpcount;
+
+ /* Remission scan measure count (??) */
+ if ((ip = m->data->get_ints(m->data, &count, key_acount)) == NULL || count < 1) {
+ a1logd(p->log,2,"Failed to access acount log counter from EEPRom\n");
+ return I1PRO_INT_EEPROM_DATA_MISSING;
+ }
+ ip[0] = m->acount;
+
+ /* Total lamp usage time in seconds (??) */
+ if ((dp = m->data->get_doubles(m->data, &count, key_lampage)) == NULL || count < 1) {
+ a1logd(p->log,2,"Failed to access lampage log counter from EEPRom\n");
+ return I1PRO_INT_EEPROM_DATA_MISSING;
+ }
+ dp[0] = m->lampage;
+
+ a1logd(p->log,5,"i1pro_set_log_data done\n");
+
+ return I1PRO_OK;
+}
+
+/* Update the single remission calibration and instrument usage log */
+/* For RevA..D only! */
+i1pro_code i1pro_update_log(i1pro *p) {
+ i1pro_code ev = I1PRO_OK;
+#ifdef ENABLE_WRITE
+ i1proimp *m = (i1proimp *)p->m;
+ unsigned char *buf; /* Buffer to write to EEProm */
+ unsigned int len;
+
+ a1logd(p->log,5,"i1pro_update_log:\n");
+
+ /* Copy refspot calibration and log data to EEProm data store */
+ if ((ev = i1pro_set_log_data(p)) != I1PRO_OK) {
+ a1logd(p->log,2,"i1pro_update_log i1pro_set_log_data failed\n");
+ return ev;
+ }
+
+ /* Compute checksum and serialise into buffer ready to write */
+ if ((ev = m->data->prep_section1(m->data, &buf, &len)) != I1PRO_OK) {
+ a1logd(p->log,2,"i1pro_update_log prep_section1 failed\n");
+ return ev;
+ }
+
+ /* First copy of log */
+ if ((ev = i1pro_writeEEProm(p, buf, 0x0000, len)) != I1PRO_OK) {
+ a1logd(p->log,2,"i1pro_update_log i1pro_writeEEProm 0x0000 failed\n");
+ return ev;
+ }
+ /* Second copy of log */
+ if ((ev = i1pro_writeEEProm(p, buf, 0x0800, len)) != I1PRO_OK) {
+ a1logd(p->log,2,"i1pro_update_log i1pro_writeEEProm 0x0800 failed\n");
+ return ev;
+ }
+ free(buf);
+
+ a1logd(p->log,5,"i1pro_update_log done\n");
+#else
+ a1logd(p->log,5,"i1pro_update_log: skipped as EPRom write is disabled\n");
+#endif
+
+ return ev;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Save the calibration for all modes, stored on local system */
+
+#ifdef ENABLE_NONVCAL
+
+/* non-volatile save/restor state */
+typedef struct {
+ int ef; /* Error flag, 1 = write failed, 2 = close failed */
+ unsigned int chsum; /* Checksum */
+ int nbytes; /* Number of bytes checksummed */
+} i1pnonv;
+
+static void update_chsum(i1pnonv *x, unsigned char *p, int nn) {
+ int i;
+ for (i = 0; i < nn; i++, p++)
+ x->chsum = ((x->chsum << 13) | (x->chsum >> (32-13))) + *p;
+ x->nbytes += nn;
+}
+
+/* Write an array of ints to the file. Set the error flag to nz on error */
+static void write_ints(i1pnonv *x, FILE *fp, int *dp, int n) {
+
+ if (fwrite((void *)dp, sizeof(int), n, fp) != n) {
+ x->ef = 1;
+ } else {
+ update_chsum(x, (unsigned char *)dp, n * sizeof(int));
+ }
+}
+
+/* Write an array of doubles to the file. Set the error flag to nz on error */
+static void write_doubles(i1pnonv *x, FILE *fp, double *dp, int n) {
+
+ if (fwrite((void *)dp, sizeof(double), n, fp) != n) {
+ x->ef = 1;
+ } else {
+ update_chsum(x, (unsigned char *)dp, n * sizeof(double));
+ }
+}
+
+/* Write an array of time_t's to the file. Set the error flag to nz on error */
+/* (This will cause file checksum fail if different executables on the same */
+/* system have different time_t values) */
+static void write_time_ts(i1pnonv *x, FILE *fp, time_t *dp, int n) {
+
+ if (fwrite((void *)dp, sizeof(time_t), n, fp) != n) {
+ x->ef = 1;
+ } else {
+ update_chsum(x, (unsigned char *)dp, n * sizeof(time_t));
+ }
+}
+
+/* Read an array of ints from the file. Set the error flag to nz on error */
+static void read_ints(i1pnonv *x, FILE *fp, int *dp, int n) {
+
+ if (fread((void *)dp, sizeof(int), n, fp) != n) {
+ x->ef = 1;
+ } else {
+ update_chsum(x, (unsigned char *)dp, n * sizeof(int));
+ }
+}
+
+/* Read an array of doubles from the file. Set the error flag to nz on error */
+static void read_doubles(i1pnonv *x, FILE *fp, double *dp, int n) {
+
+ if (fread((void *)dp, sizeof(double), n, fp) != n) {
+ x->ef = 1;
+ } else {
+ update_chsum(x, (unsigned char *)dp, n * sizeof(double));
+ }
+}
+
+/* Read an array of time_t's from the file. Set the error flag to nz on error */
+/* (This will cause file checksum fail if different executables on the same */
+/* system have different time_t values) */
+static void read_time_ts(i1pnonv *x, FILE *fp, time_t *dp, int n) {
+
+ if (fread((void *)dp, sizeof(time_t), n, fp) != n) {
+ x->ef = 1;
+ } else {
+ update_chsum(x, (unsigned char *)dp, n * sizeof(time_t));
+ }
+}
+
+i1pro_code i1pro_save_calibration(i1pro *p) {
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_code ev = I1PRO_OK;
+ i1pro_state *s;
+ int i;
+ char nmode[10];
+ char cal_name[100]; /* Name */
+ char **cal_paths = NULL;
+ int no_paths = 0;
+ FILE *fp;
+ i1pnonv x;
+ int ss;
+ int argyllversion = ARGYLL_VERSION;
+
+ strcpy(nmode, "w");
+#if !defined(O_CREAT) && !defined(_O_CREAT)
+# error "Need to #include fcntl.h!"
+#endif
+#if defined(O_BINARY) || defined(_O_BINARY)
+ strcat(nmode, "b");
+#endif
+
+ /* Create the file name */
+ sprintf(cal_name, "ArgyllCMS/.i1p_%d.cal", m->serno);
+ if ((no_paths = xdg_bds(NULL, &cal_paths, xdg_cache, xdg_write, xdg_user, cal_name)) < 1) {
+ a1logd(p->log,1,"i1pro_save_calibration xdg_bds returned no paths\n");
+ return I1PRO_INT_CAL_SAVE;
+ }
+
+ a1logd(p->log,2,"i1pro_save_calibration saving to file '%s'\n",cal_paths[0]);
+
+ if (create_parent_directories(cal_paths[0])
+ || (fp = fopen(cal_paths[0], nmode)) == NULL) {
+ a1logd(p->log,2,"i1pro_save_calibration failed to open file for writing\n");
+ xdg_free(cal_paths, no_paths);
+ return I1PRO_INT_CAL_SAVE;
+ }
+
+ x.ef = 0;
+ x.chsum = 0;
+ x.nbytes = 0;
+
+ /* A crude structure signature */
+ ss = sizeof(i1pro_state) + sizeof(i1proimp);
+
+ /* Some file identification */
+ write_ints(&x, fp, &argyllversion, 1);
+ write_ints(&x, fp, &ss, 1);
+ write_ints(&x, fp, &m->serno, 1);
+ write_ints(&x, fp, (int *)&m->nraw, 1);
+ write_ints(&x, fp, (int *)&m->nwav[0], 1);
+ write_ints(&x, fp, (int *)&m->nwav[1], 1);
+
+ /* For each mode, save the calibration if it's valid */
+ for (i = 0; i < i1p_no_modes; i++) {
+ s = &m->ms[i];
+
+ /* Mode identification */
+ write_ints(&x, fp, &s->emiss, 1);
+ write_ints(&x, fp, &s->trans, 1);
+ write_ints(&x, fp, &s->reflective, 1);
+ write_ints(&x, fp, &s->scan, 1);
+ write_ints(&x, fp, &s->flash, 1);
+ write_ints(&x, fp, &s->ambient, 1);
+ write_ints(&x, fp, &s->adaptive, 1);
+
+ /* Configuration calibration is valid for */
+ write_ints(&x, fp, &s->gainmode, 1);
+ write_doubles(&x, fp, &s->inttime, 1);
+
+ /* Calibration information */
+ write_ints(&x, fp, &s->wl_valid, 1);
+ write_time_ts(&x, fp, &s->wldate, 1);
+ write_doubles(&x, fp, &s->wl_led_off, 1);
+ write_ints(&x, fp, &s->dark_valid, 1);
+ write_time_ts(&x, fp, &s->ddate, 1);
+ write_doubles(&x, fp, &s->dark_int_time, 1);
+ write_doubles(&x, fp, s->dark_data-1, m->nraw+1);
+ write_doubles(&x, fp, &s->dark_int_time2, 1);
+ write_doubles(&x, fp, s->dark_data2-1, m->nraw+1);
+ write_doubles(&x, fp, &s->dark_int_time3, 1);
+ write_doubles(&x, fp, s->dark_data3-1, m->nraw+1);
+ write_ints(&x, fp, &s->dark_gain_mode, 1);
+
+ if (!s->emiss) {
+ write_ints(&x, fp, &s->cal_valid, 1);
+ write_time_ts(&x, fp, &s->cfdate, 1);
+ write_doubles(&x, fp, s->cal_factor[0], m->nwav[0]);
+ write_doubles(&x, fp, s->cal_factor[1], m->nwav[1]);
+ write_doubles(&x, fp, s->white_data-1, m->nraw+1);
+ }
+
+ write_ints(&x, fp, &s->idark_valid, 1);
+ write_time_ts(&x, fp, &s->iddate, 1);
+ write_doubles(&x, fp, s->idark_int_time, 4);
+ write_doubles(&x, fp, s->idark_data[0]-1, m->nraw+1);
+ write_doubles(&x, fp, s->idark_data[1]-1, m->nraw+1);
+ write_doubles(&x, fp, s->idark_data[2]-1, m->nraw+1);
+ write_doubles(&x, fp, s->idark_data[3]-1, m->nraw+1);
+ }
+
+ a1logd(p->log,3,"nbytes = %d, Checkum = 0x%x\n",x.nbytes,x.chsum);
+ write_ints(&x, fp, (int *)&x.chsum, 1);
+
+ if (fclose(fp) != 0)
+ x.ef = 2;
+
+ if (x.ef != 0) {
+ a1logd(p->log,2,"Writing calibration file failed with %d\n",x.ef);
+ delete_file(cal_paths[0]);
+ return I1PRO_INT_CAL_SAVE;
+ } else {
+ a1logd(p->log,2,"Writing calibration file succeeded\n");
+ }
+ xdg_free(cal_paths, no_paths);
+
+ return ev;
+}
+
+/* Restore the all modes calibration from the local system */
+i1pro_code i1pro_restore_calibration(i1pro *p) {
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_code ev = I1PRO_OK;
+ i1pro_state *s, ts;
+ int i, j;
+ char nmode[10];
+ char cal_name[100]; /* Name */
+ char **cal_paths = NULL;
+ int no_paths = 0;
+ FILE *fp;
+ i1pnonv x;
+ int argyllversion;
+ int ss, serno, nraw, nwav0, nwav1, nbytes, chsum1, chsum2;
+
+ strcpy(nmode, "r");
+#if !defined(O_CREAT) && !defined(_O_CREAT)
+# error "Need to #include fcntl.h!"
+#endif
+#if defined(O_BINARY) || defined(_O_BINARY)
+ strcat(nmode, "b");
+#endif
+ /* Create the file name */
+ sprintf(cal_name, "ArgyllCMS/.i1p_%d.cal" SSEPS "color/.i1p_%d.cal", m->serno, m->serno);
+ if ((no_paths = xdg_bds(NULL, &cal_paths, xdg_cache, xdg_read, xdg_user, cal_name)) < 1) {
+ a1logd(p->log,2,"i1pro_restore_calibration xdg_bds failed to locate file'\n");
+ return I1PRO_INT_CAL_RESTORE;
+ }
+
+ a1logd(p->log,2,"i1pro_restore_calibration restoring from file '%s'\n",cal_paths[0]);
+
+ /* Check the last modification time */
+ {
+ struct sys_stat sbuf;
+
+ if (sys_stat(cal_paths[0], &sbuf) == 0) {
+ m->lo_secs = time(NULL) - sbuf.st_mtime;
+ a1logd(p->log,2,"i1pro_restore_calibration: %d secs from instrument last open\n",m->lo_secs);
+ } else {
+ a1logd(p->log,2,"i1pro_restore_calibration: stat on file failed\n");
+ }
+ }
+
+ if ((fp = fopen(cal_paths[0], nmode)) == NULL) {
+ a1logd(p->log,2,"i1pro_restore_calibration failed to open file for reading\n");
+ xdg_free(cal_paths, no_paths);
+ return I1PRO_INT_CAL_RESTORE;
+ }
+
+ x.ef = 0;
+ x.chsum = 0;
+ x.nbytes = 0;
+
+ /* Check the file identification */
+ read_ints(&x, fp, &argyllversion, 1);
+ read_ints(&x, fp, &ss, 1);
+ read_ints(&x, fp, &serno, 1);
+ read_ints(&x, fp, &nraw, 1);
+ read_ints(&x, fp, &nwav0, 1);
+ read_ints(&x, fp, &nwav1, 1);
+ if (x.ef != 0
+ || argyllversion != ARGYLL_VERSION
+ || ss != (sizeof(i1pro_state) + sizeof(i1proimp))
+ || serno != m->serno
+ || nraw != m->nraw
+ || nwav0 != m->nwav[0]
+ || nwav1 != m->nwav[1]) {
+ a1logd(p->log,2,"Identification didn't verify\n");
+ goto reserr;
+ }
+
+ /* Do a dummy read to check the checksum */
+ for (i = 0; i < i1p_no_modes; i++) {
+ int di;
+ double dd;
+ time_t dt;
+ int emiss, trans, reflective, ambient, scan, flash, adaptive;
+
+ s = &m->ms[i];
+
+ /* Mode identification */
+ read_ints(&x, fp, &emiss, 1);
+ read_ints(&x, fp, &trans, 1);
+ read_ints(&x, fp, &reflective, 1);
+ read_ints(&x, fp, &scan, 1);
+ read_ints(&x, fp, &flash, 1);
+ read_ints(&x, fp, &ambient, 1);
+ read_ints(&x, fp, &adaptive, 1);
+
+ /* Configuration calibration is valid for */
+ read_ints(&x, fp, &di, 1);
+ read_doubles(&x, fp, &dd, 1);
+
+ /* Calibration information */
+ read_ints(&x, fp, &di, 1);
+ read_time_ts(&x, fp, &dt, 1);
+ read_doubles(&x, fp, &dd, 1);
+
+ read_ints(&x, fp, &di, 1);
+ read_time_ts(&x, fp, &dt, 1);
+ read_doubles(&x, fp, &dd, 1);
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1);
+ read_doubles(&x, fp, &dd, 1);
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1);
+ read_doubles(&x, fp, &dd, 1);
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1);
+ read_ints(&x, fp, &di, 1);
+
+ if (!s->emiss) {
+ read_ints(&x, fp, &di, 1);
+ read_time_ts(&x, fp, &dt, 1);
+ for (j = 0; j < m->nwav[0]; j++)
+ read_doubles(&x, fp, &dd, 1);
+ for (j = 0; j < m->nwav[1]; j++)
+ read_doubles(&x, fp, &dd, 1);
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1);
+ }
+
+ read_ints(&x, fp, &di, 1);
+ read_time_ts(&x, fp, &dt, 1);
+ for (j = 0; j < 4; j++)
+ read_doubles(&x, fp, &dd, 1);
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1);
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1);
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1);
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1);
+ }
+
+ chsum1 = x.chsum;
+ nbytes = x.nbytes;
+ read_ints(&x, fp, &chsum2, 1);
+
+ if (x.ef != 0
+ || chsum1 != chsum2) {
+ a1logd(p->log,2,"Checksum didn't verify, bytes %d, got 0x%x, expected 0x%x\n",nbytes,chsum1, chsum2);
+ goto reserr;
+ }
+
+ rewind(fp);
+ x.ef = 0;
+ x.chsum = 0;
+ x.nbytes = 0;
+
+ /* Allocate space in temp structure */
+
+ ts.dark_data = dvectorz(-1, m->nraw-1);
+ ts.dark_data2 = dvectorz(-1, m->nraw-1);
+ ts.dark_data3 = dvectorz(-1, m->nraw-1);
+ ts.cal_factor[0] = dvectorz(0, m->nwav[0]-1);
+ ts.cal_factor[1] = dvectorz(0, m->nwav[1]-1);
+ ts.white_data = dvectorz(-1, m->nraw-1);
+ ts.idark_data = dmatrixz(0, 3, -1, m->nraw-1);
+
+ /* Read the identification */
+ read_ints(&x, fp, &argyllversion, 1);
+ read_ints(&x, fp, &ss, 1);
+ read_ints(&x, fp, &m->serno, 1);
+ read_ints(&x, fp, (int *)&m->nraw, 1);
+ read_ints(&x, fp, (int *)&m->nwav[0], 1);
+ read_ints(&x, fp, (int *)&m->nwav[1], 1);
+
+ /* For each mode, restore the calibration if it's valid */
+ for (i = 0; i < i1p_no_modes; i++) {
+ s = &m->ms[i];
+
+ /* Mode identification */
+ read_ints(&x, fp, &ts.emiss, 1);
+ read_ints(&x, fp, &ts.trans, 1);
+ read_ints(&x, fp, &ts.reflective, 1);
+ read_ints(&x, fp, &ts.scan, 1);
+ read_ints(&x, fp, &ts.flash, 1);
+ read_ints(&x, fp, &ts.ambient, 1);
+ read_ints(&x, fp, &ts.adaptive, 1);
+
+ /* Configuration calibration is valid for */
+ read_ints(&x, fp, &ts.gainmode, 1);
+ read_doubles(&x, fp, &ts.inttime, 1);
+
+ /* Calibration information: */
+
+ /* Wavelength */
+ read_ints(&x, fp, &ts.wl_valid, 1);
+ read_time_ts(&x, fp, &ts.wldate, 1);
+ read_doubles(&x, fp, &ts.wl_led_off, 1);
+
+ /* Static Dark */
+ read_ints(&x, fp, &ts.dark_valid, 1);
+ read_time_ts(&x, fp, &ts.ddate, 1);
+ read_doubles(&x, fp, &ts.dark_int_time, 1);
+ read_doubles(&x, fp, ts.dark_data-1, m->nraw+1);
+ read_doubles(&x, fp, &ts.dark_int_time2, 1);
+ read_doubles(&x, fp, ts.dark_data2-1, m->nraw+1);
+ read_doubles(&x, fp, &ts.dark_int_time3, 1);
+ read_doubles(&x, fp, ts.dark_data3-1, m->nraw+1);
+ read_ints(&x, fp, &ts.dark_gain_mode, 1);
+
+ if (!ts.emiss) {
+ /* Reflective */
+ read_ints(&x, fp, &ts.cal_valid, 1);
+ read_time_ts(&x, fp, &ts.cfdate, 1);
+ read_doubles(&x, fp, ts.cal_factor[0], m->nwav[0]);
+ read_doubles(&x, fp, ts.cal_factor[1], m->nwav[1]);
+ read_doubles(&x, fp, ts.white_data-1, m->nraw+1);
+ }
+
+ /* Adaptive Dark */
+ read_ints(&x, fp, &ts.idark_valid, 1);
+ read_time_ts(&x, fp, &ts.iddate, 1);
+ read_doubles(&x, fp, ts.idark_int_time, 4);
+ read_doubles(&x, fp, ts.idark_data[0]-1, m->nraw+1);
+ read_doubles(&x, fp, ts.idark_data[1]-1, m->nraw+1);
+ read_doubles(&x, fp, ts.idark_data[2]-1, m->nraw+1);
+ read_doubles(&x, fp, ts.idark_data[3]-1, m->nraw+1);
+
+ /* If the configuration for this mode matches */
+ /* that of the calibration, restore the calibration */
+ /* for this mode. */
+ if (x.ef == 0 /* No read error */
+ && s->emiss == ts.emiss
+ && s->trans == ts.trans
+ && s->reflective == ts.reflective
+ && s->scan == ts.scan
+ && s->flash == ts.flash
+ && s->ambient == ts.ambient
+ && s->adaptive == ts.adaptive
+ && (s->adaptive || fabs(s->inttime - ts.inttime) < 0.01)
+ && (s->adaptive || fabs(s->dark_int_time - ts.dark_int_time) < 0.01)
+ && (s->adaptive || fabs(s->dark_int_time2 - ts.dark_int_time2) < 0.01)
+ && (s->adaptive || fabs(s->dark_int_time3 - ts.dark_int_time3) < 0.01)
+ && (!s->adaptive || fabs(s->idark_int_time[0] - ts.idark_int_time[0]) < 0.01)
+ && (!s->adaptive || fabs(s->idark_int_time[1] - ts.idark_int_time[1]) < 0.01)
+ && (!s->adaptive || fabs(s->idark_int_time[2] - ts.idark_int_time[2]) < 0.01)
+ && (!s->adaptive || fabs(s->idark_int_time[3] - ts.idark_int_time[3]) < 0.01)
+ ) {
+ /* Copy all the fields read above */
+ s->emiss = ts.emiss;
+ s->trans = ts.trans;
+ s->reflective = ts.reflective;
+ s->scan = ts.scan;
+ s->flash = ts.flash;
+ s->ambient = ts.ambient;
+ s->adaptive = ts.adaptive;
+
+ s->gainmode = ts.gainmode;
+ s->inttime = ts.inttime;
+
+ s->wl_valid = ts.wl_valid;
+ s->wldate = ts.wldate;
+ s->wl_led_off = ts.wl_led_off;
+
+ s->dark_valid = ts.dark_valid;
+ s->ddate = ts.ddate;
+ s->dark_int_time = ts.dark_int_time;
+ for (j = -1; j < m->nraw; j++)
+ s->dark_data[j] = ts.dark_data[j];
+ s->dark_int_time2 = ts.dark_int_time2;
+ for (j = -1; j < m->nraw; j++)
+ s->dark_data2[j] = ts.dark_data2[j];
+ s->dark_int_time3 = ts.dark_int_time3;
+ for (j = -1; j < m->nraw; j++)
+ s->dark_data3[j] = ts.dark_data3[j];
+ s->dark_gain_mode = ts.dark_gain_mode;
+ if (!ts.emiss) {
+ s->cal_valid = ts.cal_valid;
+ s->cfdate = ts.cfdate;
+ for (j = 0; j < m->nwav[0]; j++)
+ s->cal_factor[0][j] = ts.cal_factor[0][j];
+ for (j = 0; j < m->nwav[1]; j++)
+ s->cal_factor[1][j] = ts.cal_factor[1][j];
+ for (j = -1; j < m->nraw; j++)
+ s->white_data[j] = ts.white_data[j];
+ }
+ s->idark_valid = ts.idark_valid;
+ s->iddate = ts.iddate;
+ for (j = 0; j < 4; j++)
+ s->idark_int_time[j] = ts.idark_int_time[j];
+ for (j = -1; j < m->nraw; j++)
+ s->idark_data[0][j] = ts.idark_data[0][j];
+ for (j = -1; j < m->nraw; j++)
+ s->idark_data[1][j] = ts.idark_data[1][j];
+ for (j = -1; j < m->nraw; j++)
+ s->idark_data[2][j] = ts.idark_data[2][j];
+ for (j = -1; j < m->nraw; j++)
+ s->idark_data[3][j] = ts.idark_data[3][j];
+
+ } else {
+ a1logd(p->log,2,"Not restoring cal for mode %d since params don't match:\n",i);
+ a1logd(p->log,2,"emis = %d : %d, trans = %d : %d, ref = %d : %d\n",s->emiss,ts.emiss,s->trans,ts.trans,s->reflective,ts.reflective);
+ a1logd(p->log,2,"scan = %d : %d, flash = %d : %d, ambi = %d : %d, adapt = %d : %d\n",s->scan,ts.scan,s->flash,ts.flash,s->ambient,ts.ambient,s->adaptive,ts.adaptive);
+ a1logd(p->log,2,"inttime = %f : %f\n",s->inttime,ts.inttime);
+ a1logd(p->log,2,"darkit1 = %f : %f, 2 = %f : %f, 3 = %f : %f\n",s->dark_int_time,ts.dark_int_time,s->dark_int_time2,ts.dark_int_time2,s->dark_int_time3,ts.dark_int_time3);
+ a1logd(p->log,2,"idarkit0 = %f : %f, 1 = %f : %f, 2 = %f : %f, 3 = %f : %f\n",s->idark_int_time[0],ts.idark_int_time[0],s->idark_int_time[1],ts.idark_int_time[1],s->idark_int_time[2],ts.idark_int_time[2],s->idark_int_time[3],ts.idark_int_time[3]);
+ }
+ }
+
+ /* Free up temporary space */
+ free_dvector(ts.dark_data, -1, m->nraw-1);
+ free_dvector(ts.dark_data2, -1, m->nraw-1);
+ free_dvector(ts.dark_data3, -1, m->nraw-1);
+ free_dvector(ts.white_data, -1, m->nraw-1);
+ free_dmatrix(ts.idark_data, 0, 3, -1, m->nraw-1);
+
+ free_dvector(ts.cal_factor[0], 0, m->nwav[0]-1);
+ free_dvector(ts.cal_factor[1], 0, m->nwav[1]-1);
+
+ a1logd(p->log,5,"i1pro_restore_calibration done\n");
+ reserr:;
+
+ fclose(fp);
+ xdg_free(cal_paths, no_paths);
+
+ return ev;
+}
+
+i1pro_code i1pro_touch_calibration(i1pro *p) {
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_code ev = I1PRO_OK;
+ char cal_name[100]; /* Name */
+ char **cal_paths = NULL;
+ int no_paths = 0;
+ int rv;
+
+ /* Locate the file name */
+ sprintf(cal_name, "ArgyllCMS/.i1p_%d.cal" SSEPS "color/.i1p_%d.cal", m->serno, m->serno);
+ if ((no_paths = xdg_bds(NULL, &cal_paths, xdg_cache, xdg_read, xdg_user, cal_name)) < 1) {
+ a1logd(p->log,2,"i1pro_restore_calibration xdg_bds failed to locate file'\n");
+ return I1PRO_INT_CAL_TOUCH;
+ }
+
+ a1logd(p->log,2,"i1pro_touch_calibration touching file '%s'\n",cal_paths[0]);
+
+ if ((rv = sys_utime(cal_paths[0], NULL)) != 0) {
+ a1logd(p->log,2,"i1pro_touch_calibration failed with %d\n",rv);
+ xdg_free(cal_paths, no_paths);
+ return I1PRO_INT_CAL_TOUCH;
+ }
+ xdg_free(cal_paths, no_paths);
+
+ return ev;
+}
+
+#endif /* ENABLE_NONVCAL */
+
+/* ============================================================ */
+/* Intermediate routines - composite commands/processing */
+
+/* Some sort of configuration needed get instrument ready. */
+/* Does it have a sleep mode that we need to deal with ?? */
+/* Note this always does a reset. */
+i1pro_code
+i1pro_establish_high_power(i1pro *p) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ int i;
+
+ /* Get the current misc. status */
+ if ((ev = i1pro_getmisc(p, &m->fwrev, NULL, &m->maxpve, NULL, &m->powmode)) != I1PRO_OK)
+ return ev;
+
+ if (m->powmode != 8) { /* In high power mode */
+ if ((ev = i1pro_reset(p, 0x1f)) != I1PRO_OK)
+ return ev;
+
+ return I1PRO_OK;
+ }
+
+ a1logd(p->log,4,"Switching to high power mode\n");
+
+ /* Switch to high power mode */
+ if ((ev = i1pro_reset(p, 1)) != I1PRO_OK)
+ return ev;
+
+ /* Wait up to 1.5 seconds for it return high power indication */
+ for (i = 0; i < 15; i++) {
+
+ /* Get the current misc. status */
+ if ((ev = i1pro_getmisc(p, &m->fwrev, NULL, &m->maxpve, NULL, &m->powmode)) != I1PRO_OK)
+ return ev;
+
+ if (m->powmode != 8) { /* In high power mode */
+ if ((ev = i1pro_reset(p, 0x1f)) != I1PRO_OK)
+ return ev;
+
+ return I1PRO_OK;
+ }
+
+ msec_sleep(100);
+ }
+
+ /* Failed to switch into high power mode */
+ return I1PRO_HW_HIGHPOWERFAIL;
+}
+
+/* Take a dark reference measurement - part 1 */
+i1pro_code i1pro_dark_measure_1(
+ i1pro *p,
+ int nummeas, /* Number of readings to take */
+ double *inttime, /* Integration time to use/used */
+ int gainmode, /* Gain mode to use, 0 = normal, 1 = high */
+ unsigned char *buf, /* USB reading buffer to use */
+ unsigned int bsize /* Size of buffer */
+) {
+ i1pro_code ev = I1PRO_OK;
+
+ if (nummeas <= 0)
+ return I1PRO_INT_ZEROMEASURES;
+
+ if ((ev = i1pro_trigger_one_measure(p, nummeas, inttime, gainmode, i1p_dark_cal)) != I1PRO_OK)
+ return ev;
+
+ if ((ev = i1pro_readmeasurement(p, nummeas, 0, buf, bsize, NULL, i1p_dark_cal)) != I1PRO_OK)
+ return ev;
+
+ return ev;
+}
+
+/* Take a dark reference measurement - part 2 */
+i1pro_code i1pro_dark_measure_2(
+ i1pro *p,
+ double *absraw, /* Return array [-1 nraw] of absraw values */
+ int nummeas, /* Number of readings to take */
+ double inttime, /* Integration time to use/used */
+ int gainmode, /* Gain mode to use, 0 = normal, 1 = high */
+ unsigned char *buf, /* raw USB reading buffer to process */
+ unsigned int bsize /* Buffer size to process */
+) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ double **multimes; /* Multiple measurement results */
+ double sensavg; /* Overall average of sensor readings */
+ double satthresh; /* Saturation threshold */
+ double darkthresh; /* Dark threshold */
+ int rv;
+
+ multimes = dmatrix(0, nummeas-1, -1, m->nraw-1); /* -1 is RevE shielded cells values */
+
+ if (gainmode == 0)
+ satthresh = m->sens_sat0;
+ else
+ satthresh = m->sens_sat1;
+
+ darkthresh = m->sens_dark + inttime * 900.0;
+ if (gainmode)
+ darkthresh *= m->highgain;
+
+ /* Take a buffer full of raw readings, and convert them to */
+ /* absolute linearised sensor values. */
+ if ((ev = i1pro_sens_to_absraw(p, multimes, buf, nummeas, inttime, gainmode, &darkthresh))
+ != I1PRO_OK) {
+ return ev;
+ }
+
+ satthresh = i1pro_raw_to_absraw(p, satthresh, inttime, gainmode);
+ darkthresh = i1pro_raw_to_absraw(p, darkthresh, inttime, gainmode);
+
+ /* Average a set of measurements into one. */
+ /* Return zero if readings are consistent and not saturated. */
+ /* Return nz with bit 1 set if the readings are not consistent */
+ /* Return nz with bit 2 set if the readings are saturated */
+ /* Return the highest individual element. */
+ /* Return the overall average. */
+ rv = i1pro_average_multimeas(p, absraw, multimes, nummeas, NULL, &sensavg,
+ satthresh, darkthresh);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+
+#ifdef PLOT_DEBUG
+ printf("Average absolute sensor readings, average = %f, satthresh %f:\n",sensavg, satthresh);
+ plot_raw(absraw);
+#endif
+
+ if (rv & 1)
+ return I1PRO_RD_DARKREADINCONS;
+
+ if (rv & 2)
+ return I1PRO_RD_SENSORSATURATED;
+
+ a1logd(p->log,3,"Dark threshold = %f\n",darkthresh);
+
+ if (sensavg > (2.0 * darkthresh))
+ return I1PRO_RD_DARKNOTVALID;
+
+ return ev;
+}
+
+#ifdef DUMP_DARKM
+int ddumpdarkm = 0;
+#endif
+
+/* Take a dark reference measurement (combined parts 1 & 2) */
+i1pro_code i1pro_dark_measure(
+ i1pro *p,
+ double *absraw, /* Return array [-1 nraw] of absraw values */
+ int nummeas, /* Number of readings to take */
+ double *inttime, /* Integration time to use/used */
+ int gainmode /* Gain mode to use, 0 = normal, 1 = high */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_code ev = I1PRO_OK;
+ unsigned char *buf; /* Raw USB reading buffer */
+ unsigned int bsize;
+
+ bsize = m->nsen * 2 * nummeas;
+ if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
+ a1logd(p->log,1,"i1pro_dark_measure malloc %d bytes failed (8)\n",bsize);
+ return I1PRO_INT_MALLOC;
+ }
+
+ if ((ev = i1pro_dark_measure_1(p, nummeas, inttime, gainmode, buf, bsize)) != I1PRO_OK) {
+ free(buf);
+ return ev;
+ }
+
+ if ((ev = i1pro_dark_measure_2(p, absraw,
+ nummeas, *inttime, gainmode, buf, bsize)) != I1PRO_OK) {
+ free(buf);
+ return ev;
+ }
+ free(buf);
+
+#ifdef DUMP_DARKM
+ /* Dump raw dark readings to a file "i1pddump.txt" */
+ if (ddumpdarkm) {
+ int j;
+ FILE *fp;
+
+ if ((fp = fopen("i1pddump.txt", "a")) == NULL)
+ a1logw(p->log,"Unable to open debug file i1pddump.txt\n");
+ else {
+
+ fprintf(fp, "\nDark measure: nummeas %d, inttime %f, gainmode %d, darkcells %f\n",nummeas,*inttime,gainmode, absraw[-1]);
+ fprintf(fp,"\t\t\t{ ");
+ for (j = 0; j < (m->nraw-1); j++)
+ fprintf(fp, "%f, ",absraw[j]);
+ fprintf(fp, "%f },\n",absraw[j]);
+ fclose(fp);
+ }
+ }
+#endif
+
+ return ev;
+}
+
+
+/* Take a white reference measurement */
+/* (Subtracts black and processes into wavelenths) */
+i1pro_code i1pro_whitemeasure(
+ i1pro *p,
+ double *abswav0, /* Return array [nwav[0]] of abswav values (may be NULL) */
+ double *abswav1, /* Return array [nwav[1]] of abswav values (if hr_init, may be NULL) */
+ double *absraw, /* Return array [-1 nraw] of absraw values */
+ double *optscale, /* Factor to scale gain/int time by to make optimal (may be NULL) */
+ int nummeas, /* Number of readings to take */
+ double *inttime, /* Integration time to use/used */
+ int gainmode, /* Gain mode to use, 0 = normal, 1 = high */
+ double targoscale, /* Optimal reading scale factor */
+ int ltocmode /* 1 = Lamp turn on compensation mode */
+) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ unsigned char *buf; /* Raw USB reading buffer */
+ unsigned int bsize;
+ double **multimes; /* Multiple measurement results */
+ double darkthresh; /* Consitency threshold scale limit */
+ int rv;
+
+ a1logd(p->log,3,"i1pro_whitemeasure called \n");
+
+ darkthresh = m->sens_dark + *inttime * 900.0; /* Default */
+ if (gainmode)
+ darkthresh *= m->highgain;
+
+ if (nummeas <= 0)
+ return I1PRO_INT_ZEROMEASURES;
+
+ /* Allocate temporaries up front to avoid delay between trigger and read */
+ bsize = m->nsen * 2 * nummeas;
+ if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
+ a1logd(p->log,1,"i1pro_whitemeasure malloc %d bytes failed (10)\n",bsize);
+ return I1PRO_INT_MALLOC;
+ }
+ multimes = dmatrix(0, nummeas-1, -1, m->nraw-1);
+
+ a1logd(p->log,3,"Triggering measurement cycle, nummeas %d, inttime %f, gainmode %d\n",
+ nummeas, *inttime, gainmode);
+
+ if ((ev = i1pro_trigger_one_measure(p, nummeas, inttime, gainmode, i1p_cal)) != I1PRO_OK) {
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ free(buf);
+ return ev;
+ }
+
+ a1logd(p->log,4,"Gathering readings\n");
+
+ if ((ev = i1pro_readmeasurement(p, nummeas, 0, buf, bsize, NULL, i1p_cal)) != I1PRO_OK) {
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ free(buf);
+ return ev;
+ }
+
+ /* Take a buffer full of raw readings, and convert them to */
+ /* absolute linearised sensor values. */
+ if ((ev = i1pro_sens_to_absraw(p, multimes, buf, nummeas, *inttime, gainmode, &darkthresh))
+ != I1PRO_OK) {
+ return ev;
+ }
+
+#ifdef PLOT_DEBUG
+ printf("Dark data:\n");
+ plot_raw(s->dark_data);
+#endif
+
+ /* Subtract the black level */
+ i1pro_sub_absraw(p, nummeas, *inttime, gainmode, multimes, s->dark_data);
+
+ /* Convert linearised white value into output wavelength white reference */
+ ev = i1pro_whitemeasure_3(p, abswav0, abswav1, absraw, optscale, nummeas,
+ *inttime, gainmode, targoscale, multimes, darkthresh);
+
+ free(buf);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+
+ return ev;
+}
+
+/* Process a single raw white reference measurement */
+/* (Subtracts black and processes into wavelenths) */
+/* Used for restoring calibration from the EEProm */
+i1pro_code i1pro_whitemeasure_buf(
+ i1pro *p,
+ double *abswav0, /* Return array [nwav[0]] of abswav values (may be NULL) */
+ double *abswav1, /* Return array [nwav[1]] of abswav values (if hr_init, may be NULL) */
+ double *absraw, /* Return array [-1 nraw] of absraw values */
+ double inttime, /* Integration time to used */
+ int gainmode, /* Gain mode to use, 0 = normal, 1 = high */
+ unsigned char *buf /* Raw buffer */
+) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ double *meas; /* Multiple measurement results */
+ double darkthresh; /* Consitency threshold scale limit */
+
+ a1logd(p->log,3,"i1pro_whitemeasure_buf called \n");
+
+ meas = dvector(-1, m->nraw-1);
+
+ darkthresh = m->sens_dark + inttime * 900.0; /* Default */
+ if (gainmode)
+ darkthresh *= m->highgain;
+
+ /* Take a buffer full of raw readings, and convert them to */
+ /* absolute linearised sensor values. */
+ if ((ev = i1pro_sens_to_absraw(p, &meas, buf, 1, inttime, gainmode, &darkthresh))
+ != I1PRO_OK) {
+ return ev;
+ }
+
+ /* Subtract the black level */
+ i1pro_sub_absraw(p, 1, inttime, gainmode, &meas, s->dark_data);
+
+ /* Convert linearised white value into output wavelength white reference */
+ ev = i1pro_whitemeasure_3(p, abswav0, abswav1, absraw, NULL, 1, inttime, gainmode,
+ 0.0, &meas, darkthresh);
+
+ free_dvector(meas, -1, m->nraw-1);
+
+ return ev;
+}
+
+/* Take a white reference measurement - part 3 */
+/* Average, check, and convert to output wavelengths */
+i1pro_code i1pro_whitemeasure_3(
+ i1pro *p,
+ double *abswav0, /* Return array [nwav[0]] of abswav values (may be NULL) */
+ double *abswav1, /* Return array [nwav[1]] of abswav values (if hr_init, may be NULL) */
+ double *absraw, /* Return array [-1 nraw] of absraw values */
+ double *optscale, /* Factor to scale gain/int time by to make optimal (may be NULL) */
+ int nummeas, /* Number of readings to take */
+ double inttime, /* Integration time to use/used */
+ int gainmode, /* Gain mode to use, 0 = normal, 1 = high */
+ double targoscale, /* Optimal reading scale factor */
+ double **multimes, /* Multiple measurement results */
+ double darkthresh /* Raw dark threshold */
+) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ double highest; /* Highest of sensor readings */
+ double sensavg; /* Overall average of sensor readings */
+ double satthresh; /* Saturation threshold */
+ double opttarget; /* Optimal sensor target */
+ int rv;
+
+ a1logd(p->log,3,"i1pro_whitemeasure_3 called \n");
+
+ if (gainmode == 0)
+ satthresh = m->sens_sat0;
+ else
+ satthresh = m->sens_sat1;
+ satthresh = i1pro_raw_to_absraw(p, satthresh, inttime, gainmode);
+
+ darkthresh = i1pro_raw_to_absraw(p, darkthresh, inttime, gainmode);
+
+ /* Average a set of measurements into one. */
+ /* Return zero if readings are consistent and not saturated. */
+ /* Return nz with bit 1 set if the readings are not consistent */
+ /* Return nz with bit 2 set if the readings are saturated */
+ /* Return the highest individual element. */
+ /* Return the overall average. */
+ rv = i1pro_average_multimeas(p, absraw, multimes, nummeas, &highest, &sensavg,
+ satthresh, darkthresh);
+#ifdef PLOT_DEBUG
+ printf("Average absolute sensor readings, average = %f, satthresh %f:\n",sensavg, satthresh);
+ plot_raw(absraw);
+#endif
+
+#ifndef IGNORE_WHITE_INCONS
+ if (rv & 1) {
+ return I1PRO_RD_WHITEREADINCONS;
+ }
+#endif /* IGNORE_WHITE_INCONS */
+
+ if (rv & 2) {
+ return I1PRO_RD_SENSORSATURATED;
+ }
+
+ /* Convert an absraw array from raw wavelengths to output wavelenths */
+ if (abswav0 != NULL) {
+ i1pro_absraw_to_abswav(p, 0, s->reflective, 1, &abswav0, &absraw);
+
+#ifdef PLOT_DEBUG
+ printf("Converted to wavelengths std res:\n");
+ plot_wav(m, 0, abswav0);
+#endif
+ }
+
+#ifdef HIGH_RES
+ if (abswav1 != NULL && m->hr_inited) {
+ i1pro_absraw_to_abswav(p, 1, s->reflective, 1, &abswav1, &absraw);
+
+#ifdef PLOT_DEBUG
+ printf("Converted to wavelengths high res:\n");
+ plot_wav(m, 1, abswav1);
+#endif
+ }
+#endif /* HIGH_RES */
+
+ if (optscale != NULL) {
+ double lhighest = highest;
+
+ if (lhighest < 1.0)
+ lhighest = 1.0;
+
+ /* Compute correction factor to make sensor optimal */
+ opttarget = i1pro_raw_to_absraw(p, (double)m->sens_target, inttime, gainmode);
+ opttarget *= targoscale;
+
+
+ a1logd(p->log,3,"Optimal target = %f, amount to scale = %f\n",opttarget, opttarget/lhighest);
+
+ *optscale = opttarget/lhighest;
+ }
+
+ return ev;
+}
+
+/* Take a wavelength reference measurement */
+/* (Measure and subtracts black and convert to absraw) */
+i1pro_code i1pro2_wl_measure(
+ i1pro *p,
+ double *absraw, /* Return array [-1 nraw] of absraw values */
+ double *optscale, /* Factor to scale gain/int time by to make optimal (may be NULL) */
+ double *inttime, /* Integration time to use/used */
+ double targoscale /* Optimal reading scale factor */
+) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ int nummeas = 1; /* Number of measurements to take */
+ int gainmode = 0; /* Gain mode to use, 0 = normal, 1 = high */
+ unsigned char *buf; /* Raw USB reading buffer */
+ unsigned int bsize;
+ double *dark; /* Dark reading */
+ double **multimes; /* Measurement results */
+ double darkthresh; /* Consitency threshold scale limit/reading dark cell values */
+ double highest; /* Highest of sensor readings */
+ double sensavg; /* Overall average of sensor readings */
+ double satthresh; /* Saturation threshold */
+ double opttarget; /* Optimal sensor target */
+ int rv;
+
+ a1logd(p->log,3,"i1pro2_wl_measure called \n");
+
+ /* Allocate temporaries up front to avoid delay between trigger and read */
+ bsize = m->nsen * 2;
+ if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
+ a1logd(p->log,1,"i1pro2_wl_measure malloc %d bytes failed (10)\n",bsize);
+ return I1PRO_INT_MALLOC;
+ }
+
+ /* Do a dark reading at our integration time */
+ dark = dvector(-1, m->nraw-1);
+ multimes = dmatrix(0, nummeas-1, -1, m->nraw-1);
+
+ if ((ev = i1pro_dark_measure(p, dark, nummeas, inttime, gainmode)) != I1PRO_OK) {
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ free_dvector(dark, -1, m->nraw-1);
+ free(buf);
+ return ev;
+ }
+
+#ifdef PLOT_DEBUG
+ printf("Absraw dark data:\n");
+ plot_raw(dark);
+#endif
+
+ a1logd(p->log,3,"Triggering wl measurement cycle, inttime %f\n", *inttime);
+
+ if ((ev = i1pro_trigger_one_measure(p, nummeas, inttime, gainmode, i1p2_wl_cal)) != I1PRO_OK) {
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ free_dvector(dark, -1, m->nraw-1);
+ free(buf);
+ return ev;
+ }
+
+ a1logd(p->log,4,"Gathering readings\n");
+
+ if ((ev = i1pro_readmeasurement(p, nummeas, 0, buf, bsize, NULL, i1p2_wl_cal)) != I1PRO_OK) {
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ free_dvector(dark, -1, m->nraw-1);
+ free(buf);
+ return ev;
+ }
+
+ /* Take a buffer full of raw readings, and convert them to */
+ /* absolute linearised sensor values. */
+ if ((ev = i1pro_sens_to_absraw(p, multimes, buf, nummeas, *inttime, gainmode, &darkthresh))
+ != I1PRO_OK) {
+ return ev;
+ }
+
+ /* Convert satthresh and darkthresh/dark_cell values to abs */
+ if (gainmode == 0)
+ satthresh = m->sens_sat0;
+ else
+ satthresh = m->sens_sat1;
+ satthresh = i1pro_raw_to_absraw(p, satthresh, *inttime, gainmode);
+ darkthresh = i1pro_raw_to_absraw(p, darkthresh, *inttime, gainmode);
+
+#ifdef PLOT_DEBUG
+ printf("Absraw WL data:\n");
+ plot_raw(multimes[0]);
+#endif
+
+ /* Subtract the black level */
+ i1pro_sub_absraw(p, nummeas, *inttime, gainmode, multimes, dark);
+
+#ifdef PLOT_DEBUG
+ printf("Absraw WL - black data:\n");
+ plot_raw(multimes[0]);
+#endif
+
+ /* Average a set of measurements into one. */
+ /* Return zero if readings are consistent and not saturated. */
+ /* Return nz with bit 1 set if the readings are not consistent */
+ /* Return nz with bit 2 set if the readings are saturated */
+ /* Return the highest individual element. */
+ /* Return the overall average. */
+ rv = i1pro_average_multimeas(p, absraw, multimes, 1, &highest, &sensavg,
+ satthresh, darkthresh);
+#ifdef PLOT_DEBUG
+ printf("Average absolute sensor readings, average = %f, satthresh %f, absraw WL result:\n",sensavg, satthresh);
+ plot_raw(absraw);
+#endif
+
+#ifndef IGNORE_WHITE_INCONS
+ if (rv & 1) {
+ return I1PRO_RD_WHITEREADINCONS;
+ }
+#endif /* IGNORE_WHITE_INCONS */
+
+ if (rv & 2) {
+ return I1PRO_RD_SENSORSATURATED;
+ }
+
+ if (optscale != NULL) {
+ double lhighest = highest;
+
+ if (lhighest < 1.0)
+ lhighest = 1.0;
+
+ /* Compute correction factor to make sensor optimal */
+ opttarget = i1pro_raw_to_absraw(p, (double)m->sens_target, *inttime, gainmode);
+ opttarget *= targoscale;
+
+
+ a1logd(p->log,3,"Optimal target = %f, amount to scale = %f\n",opttarget, opttarget/lhighest);
+
+ *optscale = opttarget/lhighest;
+ }
+
+ free(buf);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ free_dvector(dark, -1, m->nraw-1);
+
+ return ev;
+}
+
+/* Take a measurement reading using the current mode, part 1 */
+/* Converts to completely processed output readings. */
+/* (NOTE:- this can't be used for calibration, as it implements uv mode) */
+i1pro_code i1pro_read_patches_1(
+ i1pro *p,
+ int minnummeas, /* Minimum number of measurements to take */
+ int maxnummeas, /* Maximum number of measurements to allow for */
+ double *inttime, /* Integration time to use/used */
+ int gainmode, /* Gain mode to use, 0 = normal, 1 = high */
+ int *nmeasuered, /* Number actually measured */
+ unsigned char *buf, /* Raw USB reading buffer */
+ unsigned int bsize
+) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ i1p_mmodif mmod = i1p_norm;
+ int rv = 0;
+
+ if (minnummeas <= 0)
+ return I1PRO_INT_ZEROMEASURES;
+ if (minnummeas > maxnummeas)
+ maxnummeas = minnummeas;
+
+ if (m->uv_en)
+ mmod = i1p2_UV;
+
+ a1logd(p->log,3,"Triggering & gathering cycle, minnummeas %d, inttime %f, gainmode %d\n",
+ minnummeas, *inttime, gainmode);
+
+ if ((ev = i1pro_trigger_one_measure(p, minnummeas, inttime, gainmode, mmod)) != I1PRO_OK) {
+ return ev;
+ }
+
+ if ((ev = i1pro_readmeasurement(p, minnummeas, m->c_measmodeflags & I1PRO_MMF_SCAN,
+ buf, bsize, nmeasuered, mmod)) != I1PRO_OK) {
+ return ev;
+ }
+
+ return ev;
+}
+
+/* Take a measurement reading using the current mode, part 2 */
+/* Converts to completely processed output readings. */
+i1pro_code i1pro_read_patches_2(
+ i1pro *p,
+ double *duration, /* Return flash duration */
+ double **specrd, /* Return array [numpatches][nwav] of spectral reading values */
+ int numpatches, /* Number of patches to return */
+ double inttime, /* Integration time to used */
+ int gainmode, /* Gain mode useed, 0 = normal, 1 = high */
+ int nmeasuered, /* Number actually measured */
+ unsigned char *buf, /* Raw USB reading buffer */
+ unsigned int bsize
+) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ double **multimes; /* Multiple measurement results [maxnummeas|nmeasuered][-1 nraw]*/
+ double **absraw; /* Linearsised absolute sensor raw values [numpatches][-1 nraw]*/
+ double satthresh; /* Saturation threshold */
+ double darkthresh; /* Dark threshold for consistency scaling limit */
+ int rv = 0;
+
+ if (duration != NULL)
+ *duration = 0.0; /* default value */
+
+ darkthresh = m->sens_dark + inttime * 900.0; /* Default */
+ if (gainmode)
+ darkthresh *= m->highgain;
+
+ /* Allocate temporaries */
+ multimes = dmatrix(0, nmeasuered-1, -1, m->nraw-1);
+ absraw = dmatrix(0, numpatches-1, -1, m->nraw-1);
+
+ /* Take a buffer full of raw readings, and convert them to */
+ /* absolute linearised sensor values. */
+ if ((ev = i1pro_sens_to_absraw(p, multimes, buf, nmeasuered, inttime, gainmode, &darkthresh))
+ != I1PRO_OK) {
+ return ev;
+ }
+
+ /* Subtract the black level */
+ i1pro_sub_absraw(p, nmeasuered, inttime, gainmode, multimes, s->dark_data);
+
+#ifdef DUMP_SCANV
+ /* Dump raw scan readings to a file "i1pdump.txt" */
+ {
+ int i, j;
+ FILE *fp;
+
+ if ((fp = fopen("i1pdump.txt", "w")) == NULL)
+ a1logw(p->log,"Unable to open debug file i1pdump.txt\n");
+ else {
+ for (i = 0; i < nmeasuered; i++) {
+ fprintf(fp, "%d ",i);
+ for (j = 0; j < m->nraw; j++) {
+ fprintf(fp, "%f ",multimes[i][j]);
+ }
+ fprintf(fp,"\n");
+ }
+ fclose(fp);
+ }
+ }
+#endif
+ if (gainmode == 0)
+ satthresh = m->sens_sat0;
+ else
+ satthresh = m->sens_sat1;
+ satthresh = i1pro_raw_to_absraw(p, satthresh, inttime, gainmode);
+
+ darkthresh = i1pro_raw_to_absraw(p, darkthresh, inttime, gainmode);
+
+ if (!s->scan) {
+ if (numpatches != 1) {
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+ free_dmatrix(multimes, 0, nmeasuered-1, -1, m->nraw-1);
+ a1logd(p->log,2,"i1pro_read_patches_2 spot read failed because numpatches != 1\n");
+ return I1PRO_INT_WRONGPATCHES;
+ }
+
+ /* Average a set of measurements into one. */
+ /* Return zero if readings are consistent and not saturated. */
+ /* Return nz with bit 1 set if the readings are not consistent */
+ /* Return nz with bit 2 set if the readings are saturated */
+ /* Return the highest individual element. */
+ /* Return the overall average. */
+ rv = i1pro_average_multimeas(p, absraw[0], multimes, nmeasuered, NULL, NULL,
+ satthresh, darkthresh);
+ } else {
+ if (s->flash) {
+
+ if (numpatches != 1) {
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+ free_dmatrix(multimes, 0, nmeasuered-1, -1, m->nraw-1);
+ a1logd(p->log,2,"i1pro_read_patches_2 spot read failed because numpatches != 1\n");
+ return I1PRO_INT_WRONGPATCHES;
+ }
+ if ((ev = i1pro_extract_patches_flash(p, &rv, duration, absraw[0], multimes,
+ nmeasuered, inttime)) != I1PRO_OK) {
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+ free_dmatrix(multimes, 0, nmeasuered-1, -1, m->nraw-1);
+ a1logd(p->log,2,"i1pro_read_patches_2 spot read failed at i1pro_extract_patches_flash\n");
+ return ev;
+ }
+
+ } else {
+ a1logd(p->log,3,"Number of patches measured = %d\n",nmeasuered);
+
+ /* Recognise the required number of ref/trans patch locations, */
+ /* and average the measurements within each patch. */
+ if ((ev = i1pro_extract_patches_multimeas(p, &rv, absraw, numpatches, multimes,
+ nmeasuered, NULL, satthresh, inttime)) != I1PRO_OK) {
+ free_dmatrix(multimes, 0, nmeasuered-1, -1, m->nraw-1);
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+ a1logd(p->log,2,"i1pro_read_patches_2 spot read failed at i1pro_extract_patches_multimeas\n");
+ return ev;
+ }
+ }
+ }
+
+ if (rv & 1) {
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+ a1logd(p->log,3,"i1pro_read_patches_2 spot read failed with inconsistent readings\n");
+ return I1PRO_RD_READINCONS;
+ }
+
+ if (rv & 2) {
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+ a1logd(p->log,3,"i1pro_read_patches_2 spot read failed with sensor saturated\n");
+ return I1PRO_RD_SENSORSATURATED;
+ }
+
+ /* Convert an absraw array from raw wavelengths to output wavelenths */
+ i1pro_absraw_to_abswav(p, m->highres, s->reflective, numpatches, specrd, absraw);
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+
+#ifdef APPEND_MEAN_EMMIS_VAL
+ /* Append averaged emission reading to file "i1pdump.txt" */
+ {
+ int i, j;
+ FILE *fp;
+
+ /* Create wavelegth label */
+ if ((fp = fopen("i1pdump.txt", "r")) == NULL) {
+ if ((fp = fopen("i1pdump.txt", "w")) == NULL)
+ a1logw(p->log,"Unable to reate debug file i1pdump.txt\n");
+ else {
+ for (j = 0; j < m->nwav[m->highres]; j++)
+ fprintf(fp,"%f ",XSPECT_WL(m->wl_short[m->highres], m->wl_long[m->highres], m->nwav[m->highres], j));
+ fprintf(fp,"\n");
+ fclose(fp);
+ }
+ }
+ if ((fp = fopen("i1pdump.txt", "a")) == NULL) {
+ a1logw(p->log,"Unable to open debug file i1pdump.txt\n");
+ else {
+ for (j = 0; j < m->nwav[m->highres]; j++)
+ fprintf(fp, "%f ",specrd[0][j] * m->emis_coef[j]);
+ fprintf(fp,"\n");
+ fclose(fp);
+ }
+ }
+#endif
+
+#ifdef PLOT_DEBUG
+ printf("Converted to wavelengths:\n");
+ plot_wav(m, m->highres, specrd[0]);
+#endif
+
+ /* Scale to the calibrated output values */
+ i1pro_scale_specrd(p, specrd, numpatches, specrd);
+
+#ifdef PLOT_DEBUG
+ printf("Calibrated measuerment spectra:\n");
+ plot_wav(m, m->highres, specrd[0]);
+#endif
+
+ return ev;
+}
+
+/* Take a measurement reading using the current mode, part 2a */
+/* Converts to completely processed output readings, */
+/* but don't average together or extract patches or flash. */
+/* (! Note that we aren't currently detecting saturation here!) */
+i1pro_code i1pro_read_patches_2a(
+ i1pro *p,
+ double **specrd, /* Return array [numpatches][nwav] of spectral reading values */
+ int numpatches, /* Number of patches measured and to return */
+ double inttime, /* Integration time to used */
+ int gainmode, /* Gain mode useed, 0 = normal, 1 = high */
+ unsigned char *buf, /* Raw USB reading buffer */
+ unsigned int bsize
+) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ double **absraw; /* Linearsised absolute sensor raw values [numpatches][-1 nraw]*/
+ double satthresh; /* Saturation threshold */
+ double darkthresh; /* Dark threshold for consistency scaling limit */
+
+ darkthresh = m->sens_dark + inttime * 900.0; /* Default */
+ if (gainmode)
+ darkthresh *= m->highgain;
+
+ /* Allocate temporaries */
+ absraw = dmatrix(0, numpatches-1, -1, m->nraw-1);
+
+ /* Take a buffer full of raw readings, and convert them to */
+ /* absolute linearised sensor values. */
+ if ((ev = i1pro_sens_to_absraw(p, absraw, buf, numpatches, inttime, gainmode, &darkthresh))
+ != I1PRO_OK) {
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+ return ev;
+ }
+
+ /* Subtract the black level */
+ i1pro_sub_absraw(p, numpatches, inttime, gainmode, absraw, s->dark_data);
+
+ if (gainmode == 0)
+ satthresh = m->sens_sat0;
+ else
+ satthresh = m->sens_sat1;
+ satthresh = i1pro_raw_to_absraw(p, satthresh, inttime, gainmode);
+
+ darkthresh = i1pro_raw_to_absraw(p, darkthresh, inttime, gainmode);
+
+ a1logd(p->log,3,"Number of patches measured = %d\n",numpatches);
+
+ /* Convert an absraw array from raw wavelengths to output wavelenths */
+ i1pro_absraw_to_abswav(p, m->highres, s->reflective, numpatches, specrd, absraw);
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+
+#ifdef PLOT_DEBUG
+ printf("Converted to wavelengths:\n");
+ plot_wav(m, m->highres, specrd[0]);
+#endif
+
+ /* Scale to the calibrated output values */
+ i1pro_scale_specrd(p, specrd, numpatches, specrd);
+
+#ifdef PLOT_DEBUG
+ printf("Calibrated measuerment spectra:\n");
+ plot_wav(m, m->highres, specrd[0]);
+#endif
+
+ return ev;
+}
+
+/* Take a measurement reading using the current mode (combined parts 1 & 2) */
+/* Converts to completely processed output readings. */
+/* (NOTE:- this can't be used for calibration, as it implements uv mode) */
+i1pro_code i1pro_read_patches(
+ i1pro *p,
+ double *duration, /* Return flash duration */
+ double **specrd, /* Return array [numpatches][nwav] of spectral reading values */
+ int numpatches, /* Number of patches to return */
+ int minnummeas, /* Minimum number of measurements to take */
+ int maxnummeas, /* Maximum number of measurements to allow for */
+ double *inttime, /* Integration time to use/used */
+ int gainmode /* Gain mode to use, 0 = normal, 1 = high */
+) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ unsigned char *buf; /* Raw USB reading buffer */
+ unsigned int bsize;
+ int nmeasuered; /* Number actually measured */
+ int rv = 0;
+
+ if (minnummeas <= 0)
+ return I1PRO_INT_ZEROMEASURES;
+ if (minnummeas > maxnummeas)
+ maxnummeas = minnummeas;
+
+ /* Allocate temporaries */
+ bsize = m->nsen * 2 * maxnummeas;
+ if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
+ a1logd(p->log,1,"i1pro_read_patches malloc %d bytes failed (11)\n",bsize);
+ return I1PRO_INT_MALLOC;
+ }
+
+ /* Trigger measure and gather raw readings */
+ if ((ev = i1pro_read_patches_1(p, minnummeas, maxnummeas, inttime, gainmode,
+ &nmeasuered, buf, bsize)) != I1PRO_OK) {
+ free(buf);
+ return ev;
+ }
+
+ /* Process the raw readings */
+ if ((ev = i1pro_read_patches_2(p, duration, specrd, numpatches, *inttime, gainmode,
+ nmeasuered, buf, bsize)) != I1PRO_OK) {
+ free(buf);
+ return ev;
+ }
+ free(buf);
+ return ev;
+}
+
+/* Take a measurement reading using the current mode (combined parts 1 & 2a) */
+/* Converts to completely processed output readings, without averaging or extracting */
+/* sample patches. */
+/* (NOTE:- this can't be used for calibration, as it implements uv mode) */
+i1pro_code i1pro_read_patches_all(
+ i1pro *p,
+ double **specrd, /* Return array [numpatches][nwav] of spectral reading values */
+ int numpatches, /* Number of sample to measure */
+ double *inttime, /* Integration time to use/used */
+ int gainmode /* Gain mode to use, 0 = normal, 1 = high */
+) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ unsigned char *buf; /* Raw USB reading buffer */
+ unsigned int bsize;
+ int rv = 0;
+
+ bsize = m->nsen * 2 * numpatches;
+ if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
+ a1logd(p->log,1,"i1pro_read_patches malloc %d bytes failed (11)\n",bsize);
+ return I1PRO_INT_MALLOC;
+ }
+
+ /* Trigger measure and gather raw readings */
+ if ((ev = i1pro_read_patches_1(p, numpatches, numpatches, inttime, gainmode,
+ NULL, buf, bsize)) != I1PRO_OK) {
+ free(buf);
+ return ev;
+ }
+
+ /* Process the raw readings without averaging or extraction */
+ if ((ev = i1pro_read_patches_2a(p, specrd, numpatches, *inttime, gainmode,
+ buf, bsize)) != I1PRO_OK) {
+ free(buf);
+ return ev;
+ }
+ free(buf);
+ return ev;
+}
+
+/* Take a trial measurement reading using the current mode. */
+/* Used to determine if sensor is saturated, or not optimal */
+/* in adaptive emission mode. */
+i1pro_code i1pro_trialmeasure(
+ i1pro *p,
+ int *saturated, /* Return nz if sensor is saturated */
+ double *optscale, /* Factor to scale gain/int time by to make optimal (may be NULL) */
+ int nummeas, /* Number of readings to take */
+ double *inttime, /* Integration time to use/used */
+ int gainmode, /* Gain mode to use, 0 = normal, 1 = high */
+ double targoscale /* Optimal reading scale factor */
+) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ unsigned char *buf; /* Raw USB reading buffer */
+ unsigned int bsize;
+ double **multimes; /* Multiple measurement results */
+ double *absraw; /* Linearsised absolute sensor raw values */
+ int nmeasuered; /* Number actually measured */
+ double highest; /* Highest of sensor readings */
+ double sensavg; /* Overall average of sensor readings */
+ double satthresh; /* Saturation threshold */
+ double darkthresh; /* Dark threshold */
+ double opttarget; /* Optimal sensor target */
+ int rv;
+
+ if (nummeas <= 0)
+ return I1PRO_INT_ZEROMEASURES;
+
+ darkthresh = m->sens_dark + *inttime * 900.0;
+ if (gainmode)
+ darkthresh *= m->highgain;
+
+ /* Allocate up front to avoid delay between trigger and read */
+ bsize = m->nsen * 2 * nummeas;
+ if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
+ a1logd(p->log,1,"i1pro_trialmeasure malloc %d bytes failed (12)\n",bsize);
+ return I1PRO_INT_MALLOC;
+ }
+ multimes = dmatrix(0, nummeas-1, -1, m->nraw-1);
+ absraw = dvector(-1, m->nraw-1);
+
+ a1logd(p->log,3,"Triggering measurement cycle, nummeas %d, inttime %f, gainmode %d\n",
+ nummeas, *inttime, gainmode);
+
+ if ((ev = i1pro_trigger_one_measure(p, nummeas, inttime, gainmode, i1p_cal)) != I1PRO_OK) {
+ free_dvector(absraw, -1, m->nraw-1);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ free(buf);
+ return ev;
+ }
+
+ a1logd(p->log,4,"Gathering readings\n");
+ if ((ev = i1pro_readmeasurement(p, nummeas, m->c_measmodeflags & I1PRO_MMF_SCAN,
+ buf, bsize, &nmeasuered, i1p_cal)) != I1PRO_OK) {
+ free_dvector(absraw, -1, m->nraw-1);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ free(buf);
+ return ev;
+ }
+
+ /* Take a buffer full of raw readings, and convert them to */
+ /* absolute linearised sensor values. */
+ if ((ev = i1pro_sens_to_absraw(p, multimes, buf, nmeasuered, *inttime, gainmode, &darkthresh))
+ != I1PRO_OK) {
+ return ev;
+ }
+
+ /* Compute dark subtraction for this trial's parameters */
+ if ((ev = i1pro_interp_dark(p, s->dark_data,
+ s->inttime, s->gainmode)) != I1PRO_OK) {
+ free_dvector(absraw, -1, m->nraw-1);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ free(buf);
+ a1logd(p->log,2,"i1pro_trialmeasure interplate dark ref failed\n");
+ return ev;
+ }
+
+ /* Subtract the black level */
+ i1pro_sub_absraw(p, nummeas, *inttime, gainmode, multimes, s->dark_data);
+
+ if (gainmode == 0)
+ satthresh = m->sens_sat0;
+ else
+ satthresh = m->sens_sat1;
+ satthresh = i1pro_raw_to_absraw(p, satthresh, *inttime, gainmode);
+
+ darkthresh = i1pro_raw_to_absraw(p, darkthresh, *inttime, gainmode);
+
+ /* Average a set of measurements into one. */
+ /* Return zero if readings are consistent and not saturated. */
+ /* Return nz with bit 1 set if the readings are not consistent */
+ /* Return nz with bit 2 set if the readings are saturated */
+ /* Return the highest individual element. */
+ /* Return the overall average. */
+ rv = i1pro_average_multimeas(p, absraw, multimes, nmeasuered, &highest, &sensavg,
+ satthresh, darkthresh);
+#ifdef PLOT_DEBUG
+ printf("Average absolute sensor readings, average = %f, satthresh %f:\n",sensavg, satthresh);
+ plot_raw(absraw);
+#endif
+
+ if (saturated != NULL) {
+ *saturated = 0;
+ if (rv & 2)
+ *saturated = 1;
+ }
+
+ /* Compute correction factor to make sensor optimal */
+ opttarget = (double)m->sens_target * targoscale;
+ opttarget = i1pro_raw_to_absraw(p, opttarget, *inttime, gainmode);
+
+ if (optscale != NULL) {
+ double lhighest = highest;
+
+ if (lhighest < 1.0)
+ lhighest = 1.0;
+
+ *optscale = opttarget/lhighest;
+ }
+
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ free_dvector(absraw, -1, m->nraw-1);
+ free(buf);
+
+ return ev;
+}
+
+/* Trigger a single measurement cycle. This could be a dark calibration, */
+/* a calibration, or a real measurement. This is used to create the */
+/* higher level "calibrate" and "take reading" functions. */
+/* The setup for the operation is in the current mode state. */
+/* Call i1pro_readmeasurement() to collect the results */
+i1pro_code
+i1pro_trigger_one_measure(
+ i1pro *p,
+ int nummeas, /* Minimum number of measurements to make */
+ double *inttime, /* Integration time to use/used */
+ int gainmode, /* Gain mode to use, 0 = normal, 1 = high */
+ i1p_mmodif mmodif /* Measurement modifier enum */
+) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ unsigned int timssinceoff; /* time in msec since lamp turned off */
+ double dintclocks;
+ int intclocks; /* Number of integration clocks */
+ double dlampclocks;
+ int lampclocks; /* Number of lamp turn on sub-clocks */
+ int measmodeflags; /* Measurement mode command flags */
+ int measmodeflags2; /* Rev E Measurement mode command flags */
+
+ /* The Rev E measure mode has it's own settings */
+ if (p->itype == instI1Pro2) {
+ m->intclkp = m->intclkp2; /* From i1pro2_getmeaschar() ? */
+ m->subclkdiv = m->subclkdiv2;
+ m->subtmode = 0;
+
+ } else {
+ /* Set any special hardware up for this sort of read */
+ if (*inttime != m->c_inttime) { /* integration time is different */
+ int mcmode, maxmcmode;
+ int intclkusec;
+ int subtmodeflags;
+
+ /* Setting for fwrev < 301 */
+ /* (This is what getmcmode() returns for mcmode = 1 on fwrev >= 301) */
+ m->intclkp = 68.0e-6;
+ m->subclkdiv = 130;
+ m->subtmode = 0;
+
+ if (m->fwrev >= 301) { /* Special hardware in latter versions of instrument */
+
+#ifdef DEBUG
+ /* Show all the available clock modes */
+ for (mcmode = 1;; mcmode++) {
+ int rmcmode, subclkdiv;
+
+ if ((ev = i1pro_setmcmode(p, mcmode)) != I1PRO_OK)
+ break;
+
+ if ((ev = i1pro_getmcmode(p, &maxmcmode, &rmcmode, &subclkdiv,
+ &intclkusec, &subtmodeflags) ) != I1PRO_OK)
+ break;
+
+ fprintf(stderr,"getcmode %d: maxcmode %d, mcmode %d, subclkdif %d, intclkusec %d, subtmodeflags 0x%x\n",mcmode,maxmcmode,rmcmode,subclkdiv,intclkusec,subtmodeflags);
+ if (mcmode >= maxmcmode)
+ break;
+ }
+#endif
+ /* Configure a clock mode that gives us an optimal integration time ? */
+ for (mcmode = 1;; mcmode++) {
+ if ((ev = i1pro_setmcmode(p, mcmode)) != I1PRO_OK)
+ return ev;
+
+ if ((ev = i1pro_getmcmode(p, &maxmcmode, &mcmode, &m->subclkdiv,
+ &intclkusec, &subtmodeflags) ) != I1PRO_OK)
+ return ev;
+
+ if ((*inttime/(intclkusec * 1e-6)) > 65535.0) {
+ return I1PRO_INT_INTTOOBIG;
+ }
+
+ if (*inttime >= (intclkusec * m->subclkdiv * 1e-6 * 0.99))
+ break; /* Setting is optimal */
+
+ /* We need to go around again */
+ if (mcmode >= maxmcmode) {
+ return I1PRO_INT_INTTOOSMALL;
+ }
+ }
+ m->c_mcmode = mcmode;
+ m->intclkp = intclkusec * 1e-6;
+ a1logd(p->log,3,"Switched to perfect mode, subtmode flag = 0x%x, intclk = %f Mhz\n",subtmodeflags & 0x01, 1.0/intclkusec);
+ if (subtmodeflags & 0x01)
+ m->subtmode = 1; /* Last reading subtract mode */
+ }
+ }
+ }
+ a1logd(p->log,3,"Integration clock period = %f ussec\n",m->intclkp * 1e6);
+
+ /* Compute integration clocks */
+ dintclocks = floor(*inttime/m->intclkp + 0.5);
+ if (p->itype == instI1Pro2) {
+ if (dintclocks > 4294967296.0) /* This is probably not the actual limit */
+ return I1PRO_INT_INTTOOBIG;
+ } else {
+ if (dintclocks > 65535.0)
+ return I1PRO_INT_INTTOOBIG;
+ }
+ intclocks = (int)dintclocks;
+ *inttime = (double)intclocks * m->intclkp; /* Quantized integration time */
+
+ if (s->reflective && (mmodif & 0x10)) {
+ dlampclocks = floor(s->lamptime/(m->subclkdiv * m->intclkp) + 0.5);
+ if (dlampclocks > 256.0) /* Clip - not sure why. Silly value anyway */
+ dlampclocks = 256.0;
+ lampclocks = (int)dlampclocks;
+ s->lamptime = dlampclocks * m->subclkdiv * m->intclkp; /* Quantized lamp time */
+ } else {
+ dlampclocks = 0.0;
+ lampclocks = 0;
+ }
+
+ if (nummeas > 65535)
+ nummeas = 65535; /* Or should we error ? */
+
+ /* Create measurement mode flag values for this operation for both */
+ /* legacy and Rev E mode. Other code will examine legacy mode flags */
+ measmodeflags = 0;
+ if (s->scan && !(mmodif & 0x20)) /* Never scan on a calibration */
+ measmodeflags |= I1PRO_MMF_SCAN;
+ if (!s->reflective || !(mmodif & 0x10))
+ measmodeflags |= I1PRO_MMF_NOLAMP; /* No lamp if not reflective or dark measure */
+ if (gainmode == 0)
+ measmodeflags |= I1PRO_MMF_LOWGAIN; /* Normal gain mode */
+
+ if (p->itype == instI1Pro2) {
+ measmodeflags2 = 0;
+ if (s->scan && !(mmodif & 0x20)) /* Never scan on a calibration */
+ measmodeflags2 |= I1PRO2_MMF_SCAN;
+
+ if (mmodif == i1p2_UV)
+ measmodeflags2 |= I1PRO2_MMF_UV_LED; /* UV LED illumination measurement */
+ else if (mmodif == i1p2_wl_cal)
+ measmodeflags2 |= I1PRO2_MMF_WL_LED; /* Wavelegth illumination cal */
+ else if (s->reflective && (mmodif & 0x10))
+ measmodeflags2 |= I1PRO2_MMF_LAMP; /* lamp if reflective and mmodif possible */
+
+ if (gainmode != 0)
+ return I1PRO_INT_NO_HIGH_GAIN;
+ }
+
+ a1logd(p->log,2,"i1pro: Int time %f msec, delay %f msec, no readings %d, expect %f msec\n",
+ *inttime * 1000.0,
+ ((measmodeflags & I1PRO_MMF_NOLAMP) ? 0.0 : s->lamptime) * 1000.0,
+ nummeas,
+ (nummeas * *inttime + ((measmodeflags & I1PRO_MMF_NOLAMP) ? 0.0 : s->lamptime)) * 1000.0);
+
+ /* Do a setmeasparams */
+#ifdef NEVER
+ if (intclocks != m->c_intclocks /* If any parameters have changed */
+ || lampclocks != m->c_lampclocks
+ || nummeas != m->c_nummeas
+ || measmodeflags != m->c_measmodeflags
+ || measmodeflags2 != m->c_measmodeflags2)
+#endif /* NEVER */
+ {
+
+ if (p->itype != instI1Pro2) { /* Rev E sets the params in the measure command */
+ /* Set the hardware for measurement */
+ if ((ev = i1pro_setmeasparams(p, intclocks, lampclocks, nummeas, measmodeflags)) != I1PRO_OK)
+ return ev;
+ } else {
+ a1logd(p->log,2,"\ni1pro: SetMeasureParam2 %d, %d, %d, 0x%04x @ %d msec\n",
+ intclocks, lampclocks, nummeas, measmodeflags2,
+ msec_time() - m->msec);
+ }
+
+ m->c_intclocks = intclocks;
+ m->c_lampclocks = lampclocks;
+ m->c_nummeas = nummeas;
+ m->c_measmodeflags = measmodeflags;
+ m->c_measmodeflags2 = measmodeflags2;
+
+ m->c_inttime = *inttime; /* Special harware is configured */
+ m->c_lamptime = s->lamptime;
+ }
+
+ /* If the lamp needs to be off, make sure at least 1.5 seconds */
+ /* have elapsed since it was last on, to make sure it's dark. */
+ if ((measmodeflags & I1PRO_MMF_NOLAMP)
+ && (timssinceoff = (msec_time() - m->llamponoff)) < LAMP_OFF_TIME) {
+ a1logd(p->log,3,"Sleep %d msec for lamp cooldown\n",LAMP_OFF_TIME - timssinceoff);
+ msec_sleep(LAMP_OFF_TIME - timssinceoff); /* Make sure time adds up to 1.5 seconds */
+ }
+
+ /* Trigger a measurement */
+ if (p->itype != instI1Pro2) {
+ if ((ev = i1pro_triggermeasure(p, TRIG_DELAY)) != I1PRO_OK)
+ return ev;
+ } else {
+ if ((ev = i1pro2_triggermeasure(p, TRIG_DELAY)) != I1PRO_OK)
+ return ev;
+ }
+
+ return ev;
+}
+
+/* ============================================================ */
+/* Big endian wire format conversion routines */
+
+/* Take an int, and convert it into a byte buffer big endian */
+static void int2buf(unsigned char *buf, int inv) {
+ buf[0] = (inv >> 24) & 0xff;
+ buf[1] = (inv >> 16) & 0xff;
+ buf[2] = (inv >> 8) & 0xff;
+ buf[3] = (inv >> 0) & 0xff;
+}
+
+/* Take a short, and convert it into a byte buffer big endian */
+static void short2buf(unsigned char *buf, int inv) {
+ buf[0] = (inv >> 8) & 0xff;
+ buf[1] = (inv >> 0) & 0xff;
+}
+
+/* Take a word sized buffer, and convert it to an int */
+static int buf2int(unsigned char *buf) {
+ int val;
+ val = buf[0]; /* Hmm. should this be sign extended ?? */
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[3]));
+ return val;
+}
+
+/* Take a short sized buffer, and convert it to an int */
+static int buf2short(unsigned char *buf) {
+ int val;
+ val = *((signed char *)buf); /* Sign extend */
+ val = ((val << 8) + (0xff & buf[1]));
+ return val;
+}
+
+/* Take a unsigned short sized buffer, and convert it to an int */
+static int buf2ushort(unsigned char *buf) {
+ int val;
+ val = (0xff & buf[0]);
+ val = ((val << 8) + (0xff & buf[1]));
+ return val;
+}
+
+/* ============================================================ */
+/* lower level reading processing and computation */
+
+/* Take a buffer full of sensor readings, and convert them to */
+/* absolute raw values. Linearise if Rev A..D */
+/* If RevE, fill in the [-1] value with the shielded cell values */
+/* Note the rev E darkthresh returned has NOT been converted to an absolute raw value */
+i1pro_code i1pro_sens_to_absraw(
+ i1pro *p,
+ double **absraw, /* Array of [nummeas][-1 nraw] value to return */
+ unsigned char *buf, /* Raw measurement data must be nsen * nummeas */
+ int nummeas, /* Return number of readings measured */
+ double inttime, /* Integration time used */
+ int gainmode, /* Gain mode, 0 = normal, 1 = high */
+ double *pdarkthresh /* Return a raw dark threshold value (Rev E) */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ int i, j, k;
+ unsigned char *bp;
+ unsigned int maxpve = m->maxpve; /* maximum +ve sensor value + 1 */
+ double avlastv = 0.0;
+ double darkthresh = 0.0; /* Rev E calculated values */
+ double ndarkthresh = 0.0;
+ double gain;
+ int npoly; /* Number of linearisation coefficients */
+ double *polys; /* the coeficients */
+ double scale; /* Absolute scale value */
+ int sskip = 0; /* Bytes to skip at start */
+ int eskip = 0; /* Bytes to skip at end */
+
+ if (gainmode) {
+ gain = m->highgain;
+ npoly = m->nlin1;
+ polys = m->lin1;
+ } else {
+ gain = 1.0;
+ npoly = m->nlin0;
+ polys = m->lin0;
+ }
+ scale = 1.0/(inttime * gain);
+
+ /* Now process the buffer values */
+ if (m->nsen > m->nraw) { /* It's a Rev E, so we have extra values, */
+ /* and don't linearize here. */
+ sskip = 6 * 2; /* 6 dark reading values */
+ eskip = 0 * 2; /* none to skip at end */
+
+ if ((sskip + m->nraw * 2 + eskip) != (m->nsen * 2)) {
+ a1loge(p->log,1,"i1pro Rev E - sskip %d + nraw %d + eskip %d != nsen %d\n"
+ ,sskip, m->nraw * 2, eskip, m->nsen * 2);
+ return I1PRO_INT_ASSERT;
+ }
+
+ for (bp = buf, i = 0; i < nummeas; i++, bp += eskip) {
+ unsigned int rval;
+ double fval;
+
+ /* The first 6 readings (xraw from i1pro2_getmeaschar()) are shielded cells, */
+ /* and we use them as an estimate of the dark reading consistency, as well as for */
+ /* compensating the dark level calibration for any temperature changes. */
+
+ /* raw average of all measurement shielded cell values */
+ for (k = 0; k < 6; k++) {
+ darkthresh += (double)buf2ushort(bp + k * 2);
+ ndarkthresh++;
+ }
+
+ /* absraw of shielded cells per reading */
+ absraw[i][-1] = 0.0;
+ for (k = 0; k < 6; k++) {
+ rval = buf2ushort(bp + k * 2);
+ fval = (double)(int)rval;
+
+ /* And scale to be an absolute sensor reading */
+ absraw[i][-1] += fval * scale;
+ }
+ absraw[i][-1] /= 6.0;
+
+ for (bp += sskip, j = 0; j < m->nraw; j++, bp += 2) {
+ rval = buf2ushort(bp);
+ a1logd(p->log,9,"% 3d:rval 0x%x, ",j, rval);
+ a1logd(p->log,9,"srval 0x%x, ",rval);
+ fval = (double)(int)rval;
+ a1logd(p->log,9,"fval %.0f, ",fval);
+
+ /* And scale to be an absolute sensor reading */
+ absraw[i][j] = fval * scale;
+ a1logd(p->log,9,"absval %.1f\n",fval * scale);
+ }
+ }
+ darkthresh /= ndarkthresh;
+ if (pdarkthresh != NULL)
+ *pdarkthresh = darkthresh;
+ a1logd(p->log,3,"i1pro_sens_to_absraw: Dark threshold = %f\n",darkthresh);
+
+ } else {
+ /* if subtmode is set, compute the average last reading raw value. */
+ /* Could this be some sort of temperature compensation offset ??? */
+ /* (Rev A produces a value that is quite different to a sensor value, */
+ /* ie. 1285 = 0x0505, while RevD and RevE in legacy mode have a value of 0 */
+ /* I've not seen anything actually use subtmode - maybe this is Rev B only ?) */
+ /* The 0 band seens to contain values similar to band 1, so it's not clear */
+ /* why the manufacturers driver appears to be discarding it ? */
+
+ /* (Not sure if it's reasonable to extend the sign and then do this */
+ /* computation, or whether it makes any difference, since I've never */
+ /* seen this mode triggered. */
+ if (m->subtmode) {
+ for (bp = buf + 254, i = 0; i < nummeas; i++, bp += (m->nsen * 2)) {
+ unsigned int lastv;
+ lastv = buf2ushort(bp);
+ if (lastv >= maxpve) {
+ lastv -= 0x00010000; /* Convert to -ve */
+ }
+ avlastv += (double)lastv;
+ }
+ avlastv /= (double)nummeas;
+ a1logd(p->log,3,"subtmode got avlastv = %f\n",avlastv);
+ }
+
+ for (bp = buf, i = 0; i < nummeas; i++) {
+ absraw[i][-1] = 1.0; /* Not used in RevA-D */
+
+ for (j = 0; j < 128; j++, bp += 2) {
+ unsigned int rval;
+ double fval, lval;
+
+ rval = buf2ushort(bp);
+ a1logd(p->log,9,"% 3d:rval 0x%x, ",j, rval);
+ if (rval >= maxpve)
+ rval -= 0x00010000; /* Convert to -ve */
+ a1logd(p->log,9,"srval 0x%x, ",rval);
+ fval = (double)(int)rval;
+ a1logd(p->log,9,"fval %.0f, ",fval);
+ fval -= avlastv;
+ a1logd(p->log,9,"fval-av %.0f, ",fval);
+
+#ifdef ENABLE_NONLINCOR
+ /* Linearise */
+ for (lval = polys[npoly-1], k = npoly-2; k >= 0; k--)
+ lval = lval * fval + polys[k];
+#else
+ lval = fval;
+#endif
+ a1logd(p->log,9,"lval %.1f, ",lval);
+
+ /* And scale to be an absolute sensor reading */
+ absraw[i][j] = lval * scale;
+ a1logd(p->log,9,"absval %.1f\n",lval * scale);
+ // a1logd(p->log,3,"Meas %d band %d raw = %f\n",i,j,fval);
+ }
+
+ /* Duplicate last values in buffer to make up to 128 */
+ absraw[i][0] = absraw[i][1];
+ absraw[i][127] = absraw[i][126];
+ }
+ }
+
+ return I1PRO_OK;
+}
+
+/* Take a raw value, and convert it into an absolute raw value. */
+/* Note that linearisation is ignored, since it is assumed to be insignificant */
+/* to the black threshold and saturation values. */
+double i1pro_raw_to_absraw(
+ i1pro *p,
+ double raw, /* Input value */
+ double inttime, /* Integration time used */
+ int gainmode /* Gain mode, 0 = normal, 1 = high */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ int i, j, k;
+ double gain;
+ double scale; /* Absolute scale value */
+ double fval;
+
+ if (gainmode) {
+ gain = m->highgain;
+ } else {
+ gain = 1.0;
+ }
+ scale = 1.0/(inttime * gain);
+
+ return raw * scale;
+}
+
+
+/* Invert a polinomial equation. */
+/* Since the linearisation is nearly a straight line, */
+/* a simple Newton inversion will suffice. */
+static double inv_poly(double *polys, int npoly, double inv) {
+ double outv = inv, lval, del = 100.0;
+ int i, k;
+
+ for (i = 0; i < 200 && fabs(del) > 1e-7; i++) {
+ for (lval = polys[npoly-1], k = npoly-2; k >= 0; k--) {
+ lval = lval * outv + polys[k];
+ }
+ del = (inv - lval);
+ outv += 0.99 * del;
+ }
+
+ return outv;
+}
+
+/* Take a single set of absolute linearised sensor values and */
+/* convert them back into Rev A..D raw reading values. */
+/* This is used for saving a calibration to the EEProm */
+i1pro_code i1pro_absraw_to_meas(
+ i1pro *p,
+ int *meas, /* Return raw measurement data */
+ double *absraw, /* Array of [-1 nraw] value to process */
+ double inttime, /* Integration time used */
+ int gainmode /* Gain mode, 0 = normal, 1 = high */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ unsigned int maxpve = m->maxpve; /* maximum +ve sensor value + 1 */
+ int i, j, k;
+ double avlastv = 0.0;
+ double gain;
+ int npoly; /* Number of linearisation coefficients */
+ double *polys; /* the coeficients */
+ double scale; /* Absolute scale value */
+
+ if (m->subtmode) {
+ a1logd(p->log,1,"i1pro_absraw_to_meas subtmode set\n");
+ return I1PRO_INT_MALLOC;
+ }
+
+ if (gainmode) {
+ gain = m->highgain;
+ npoly = m->nlin1;
+ polys = m->lin1;
+ } else {
+ gain = 1.0;
+ npoly = m->nlin0;
+ polys = m->lin0;
+ }
+ scale = 1.0/(inttime * gain);
+
+ for (j = 0; j < 128; j++) {
+ double fval, lval;
+ unsigned int rval;
+
+ /* Unscale from absolute sensor reading */
+ lval = absraw[j] / scale;
+
+#ifdef ENABLE_NONLINCOR
+ /* Un-linearise */
+ fval = inv_poly(polys, npoly, lval);
+#else
+ fval = lval;
+#endif
+
+ if (fval < (double)((int)maxpve-65536))
+ fval = (double)((int)maxpve-65536);
+ else if (fval > (double)(maxpve-1))
+ fval = (double)(maxpve-1);
+
+ rval = (unsigned int)(int)floor(fval + 0.5);
+ meas[j] = rval;
+ }
+ return I1PRO_OK;
+}
+
+/* Average a set of measurements into one. */
+/* Return zero if readings are consistent and not saturated. */
+/* Return nz with bit 1 set if the readings are not consistent */
+/* Return nz with bit 2 set if the readings are saturated */
+/* Return the highest individual element. */
+/* Return the overall average. */
+int i1pro_average_multimeas(
+ i1pro *p,
+ double *avg, /* return average [-1 nraw] */
+ double **multimeas, /* Array of [nummeas][-1 nraw] value to average */
+ int nummeas, /* number of readings to be averaged */
+ double *phighest, /* If not NULL, return highest value from all bands and msrmts. */
+ double *poallavg, /* If not NULL, return overall average of bands and measurements */
+ double satthresh, /* Sauration threshold, 0 for none */
+ double darkthresh /* Dark threshold (used for consistency check scaling) */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ int i, j;
+ double highest = -1e6;
+ double oallavg = 0.0;
+ double avgoverth = 0.0; /* Average over threshold */
+ double maxavg = -1e38; /* Track min and max averages of readings */
+ double minavg = 1e38;
+ double norm;
+ int rv = 0;
+
+ a1logd(p->log,3,"i1pro_average_multimeas %d readings\n",nummeas);
+
+ for (j = -1; j < 128; j++)
+ avg[j] = 0.0;
+
+ /* Now process the buffer values */
+ for (i = 0; i < nummeas; i++) {
+ double measavg = 0.0;
+ int k;
+
+ for (j = k = 0; j < m->nraw; j++) {
+ double val;
+
+ val = multimeas[i][j];
+
+ avg[j] += val; /* Per value average */
+
+ /* Skip 0 and 127 cell values for RevA-D */
+ if (m->nsen == m->nraw && (j == 0 || j == 127))
+ continue;
+
+ if (val > highest)
+ highest = val;
+ if (val > satthresh)
+ avgoverth++;
+ measavg += val;
+ k++;
+ }
+ measavg /= (double)k;
+ oallavg += measavg;
+ if (measavg < minavg)
+ minavg = measavg;
+ if (measavg > maxavg)
+ maxavg = measavg;
+
+ /* and shielded values */
+ avg[-1] += multimeas[i][-1];
+ }
+
+ for (j = -1; j < 128; j++)
+ avg[j] /= (double)nummeas;
+ oallavg /= (double)nummeas;
+ avgoverth /= (double)nummeas;
+
+ if (phighest != NULL)
+ *phighest = highest;
+
+ if (poallavg != NULL)
+ *poallavg = oallavg;
+
+ if (satthresh > 0.0 && avgoverth > 0.0)
+ rv |= 2;
+
+ norm = fabs(0.5 * (maxavg+minavg));
+ a1logd(p->log,4,"norm = %f, dark thresh = %f\n",norm,darkthresh);
+ if (norm < (2.0 * darkthresh))
+ norm = 2.0 * darkthresh;
+
+ a1logd(p->log,4,"overall avg = %f, minavg = %f, maxavg = %f, variance %f, shielded avg %f\n",
+ oallavg,minavg,maxavg,(maxavg - minavg)/norm, avg[-1]);
+ if ((maxavg - minavg)/norm > PATCH_CONS_THR) {
+ a1logd(p->log,2,"Reading is inconsistent: (maxavg %f - minavg %f)/norm %f = %f > thresh %f, darkthresh %f\n",maxavg,minavg,norm,(maxavg - minavg)/norm,PATCH_CONS_THR, darkthresh);
+ rv |= 1;
+ }
+ return rv;
+}
+
+/* Minimum number of scan samples in a patch */
+#define MIN_SAMPLES 3
+
+/* Range of bands to detect transitions */
+#define BL 5 /* Start */
+#define BH 105 /* End */
+#define BW 5 /* Width */
+
+/* Record of possible patch */
+typedef struct {
+ int ss; /* Start sample index */
+ int no; /* Number of samples */
+ int use; /* nz if patch is to be used */
+} i1pro_patch;
+
+/* Recognise the required number of ref/trans patch locations, */
+/* and average the measurements within each patch. */
+/* *flags returns zero if readings are consistent and not saturated. */
+/* *flags returns nz with bit 1 set if the readings are not consistent */
+/* *flags returns nz with bit 2 set if the readings are saturated */
+/* *phighest returns the highest individual element. */
+/* (Doesn't extract [-1] shielded values, since they have already been used) */
+i1pro_code i1pro_extract_patches_multimeas(
+ i1pro *p,
+ int *flags, /* return flags */
+ double **pavg, /* return patch average [naptch][-1 nraw] */
+ int tnpatch, /* Target number of patches to recognise */
+ double **multimeas, /* Array of [nummeas][-1 nraw] value to extract from */
+ int nummeas, /* number of readings made */
+ double *phighest, /* If not NULL, return highest value from all bands and msrmts. */
+ double satthresh, /* Sauration threshold, 0 for none */
+ double inttime /* Integration time (used to adjust consistency threshold) */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ int i, j, k, pix;
+ double **sslope; /* Signed difference between i and i+1 */
+ double *slope; /* Accumulated absolute difference between i and i+1 */
+ double *fslope; /* Filtered slope */
+ i1pro_patch *pat; /* Possible patch information */
+ int npat, apat = 0;
+ double *maxval; /* Maximum input value for each wavelength */
+ double fmaxslope = 0.0;
+ double maxslope = 0.0;
+ double minslope = 1e38;
+ double thresh = 0.4; /* Slope threshold */
+ int try; /* Thresholding try */
+ double avglegth; /* Average length of patches */
+ int *sizepop; /* Size popularity of potential patches */
+ double median; /* median potential patch width */
+ double window; /* +/- around median to accept */
+ double highest = -1e6;
+ double white_avg; /* Average of (aproximate) white data */
+ int b_lo = BL; /* Patch detection low band */
+ int b_hi = BH; /* Patch detection low band */
+ int rv = 0;
+ double patch_cons_thr = PATCH_CONS_THR * m->scan_toll_ratio;
+#ifdef PATREC_DEBUG
+ double **plot;
+#endif
+
+ a1logd(p->log,2,"i1pro_extract_patches_multimeas looking for %d patches out of %d samples\n",tnpatch,nummeas);
+
+ /* Adjust bands if UV mode */
+ /* (This is insufficient for useful patch recognition) */
+ if (m->uv_en) {
+ b_lo = 91;
+ b_hi = 117;
+ }
+
+ maxval = dvectorz(-1, m->nraw-1);
+
+ /* Loosen consistency threshold for short integation time, */
+ /* to allow for extra noise */
+ if (inttime < 0.012308) /* Smaller than Rev A minimum int. time */
+ patch_cons_thr *= sqrt(0.012308/inttime);
+
+ /* Discover the maximum input value for normalisation */
+ for (j = 0; j < m->nraw; j ++) {
+ for (i = 0; i < nummeas; i++) {
+ if (multimeas[i][j] > maxval[j])
+ maxval[j] = multimeas[i][j];
+ }
+ if (maxval[j] < 1.0)
+ maxval[j] = 1.0;
+ }
+
+#ifdef PATREC_DEBUG
+ /* Plot out 6 lots of 8 values each */
+ plot = dmatrixz(0, 8, 0, nummeas-1);
+// for (j = 45; j <= (m->nraw-8); j += 100) /* Do just one band */
+#ifdef PATREC_ALLBANDS
+ for (j = 0; j <= (m->nraw-8); j += 8) /* Plot all the bands */
+#else
+ for (j = 5; j <= (m->nraw-8); j += 30) /* Do four bands */
+#endif
+ {
+ for (k = 0; k < 8; k ++) {
+ for (i = 0; i < nummeas; i++) {
+ plot[k][i] = multimeas[i][j+k]/maxval[j+k];
+ }
+ }
+ for (i = 0; i < nummeas; i++)
+ plot[8][i] = (double)i;
+ printf("Bands %d - %d\n",j,j+7);
+ do_plot10(plot[8], plot[0], plot[1], plot[2], plot[3], plot[4], plot[5], plot[6], plot[7], NULL, NULL, nummeas, 0);
+ }
+#endif /* PATREC_DEBUG */
+
+ sslope = dmatrixz(0, nummeas-1, -1, m->nraw-1);
+ slope = dvectorz(0, nummeas-1);
+ fslope = dvectorz(0, nummeas-1);
+ sizepop = ivectorz(0, nummeas-1);
+
+#ifndef NEVER /* Good with this on */
+ /* Average bands together */
+ for (i = 0; i < nummeas; i++) {
+ for (j = b_lo + BW; j < (b_hi - BW); j++) {
+ for (k = -b_lo; k <= BW; k++) /* Box averaging filter over bands */
+ sslope[i][j] += multimeas[i][j + k]/maxval[j];
+ }
+ }
+#else
+ /* Don't average bands */
+ for (i = 0; i < nummeas; i++) {
+ for (j = 0; j < m->nraw; j++) {
+ sslope[i][j] = multimeas[i][j]/maxval[j];
+ }
+ }
+#endif
+
+ /* Compute slope result over readings and bands */
+ /* Compute signed slope result over readings and bands */
+
+#ifdef NEVER /* Works well for non-noisy readings */
+ /* Median of 5 differences from 6 points */
+ for (i = 2; i < (nummeas-3); i++) {
+ for (j = b_lo; j < b_hi; j++) {
+ double sl, asl[5];
+ int r, s;
+ asl[0] = fabs(sslope[i-2][j] - sslope[i-1][j]);
+ asl[1] = fabs(sslope[i-1][j] - sslope[i-0][j]);
+ asl[2] = fabs(sslope[i-0][j] - sslope[i+1][j]);
+ asl[3] = fabs(sslope[i+1][j] - sslope[i+2][j]);
+ asl[4] = fabs(sslope[i+2][j] - sslope[i+3][j]);
+
+ /* Sort them */
+ for (r = 0; r < (5-1); r++) {
+ for (s = r+1; s < 5; s++) {
+ if (asl[s] < asl[r]) {
+ double tt;
+ tt = asl[s];
+ asl[s] = asl[r];
+ asl[r] = tt;
+ }
+ }
+ }
+ /* Pick middle one */
+ sl = asl[2];
+ if (sl > slope[i])
+ slope[i] = sl;
+ }
+ }
+
+#else /* Works better for noisy readings */
+
+ /* Compute sliding window average and deviation that contains */
+ /* our output point, and chose the average with the minimum deviation. */
+#define FW 3 /* Number of delta's to average */
+ for (i = FW-1; i < (nummeas-FW); i++) { /* Samples */
+ double basl, bdev; /* Best average slope, Best deviation */
+ double sl[2 * FW -1];
+ double asl[FW], dev[FW];
+ int slopen = 0;
+ double slopeth = 0.0;
+ int m, pp;
+
+ for (pp = 0; pp < 2; pp++) { /* For each pass */
+
+ for (j = b_lo; j < b_hi; j++) { /* Bands */
+
+ /* Compute differences for the range of our windows */
+ for (k = 0; k < (2 * FW -1); k++)
+ sl[k] = sslope[i+k-FW+1][j] - sslope[i+k+-FW+2][j];
+
+ /* For each window offset, compute average and deviation squared */
+ bdev = 1e38;
+ for (k = 0; k < FW; k++) {
+
+ /* Compute average of this window offset */
+ asl[k] = 0.0;
+ for (m = 0; m < FW; m++) /* For slope in window */
+ asl[k] += sl[k+m];
+ asl[k] /= (double)FW;
+
+ /* Compute deviation squared */
+ dev[k] = 0.0;
+ for (m = 0; m < FW; m++) {
+ double tt;
+ tt = sl[k+m] - asl[k];
+ dev[k] += tt * tt;
+ }
+ if (dev[k] < bdev)
+ bdev = dev[k];
+ }
+
+#ifndef NEVER
+ /* Weight the deviations with a triangular weighting */
+ /* to skew slightly towards the center */
+ for (k = 0; k < FW; k++) {
+ double wt;
+
+ wt = fabs(2.0 * k - (FW -1.0))/(FW-1.0);
+ dev[k] += wt * bdev;
+ }
+#endif
+
+ /* For each window offset, choose the one to use. */
+ bdev = 1e38;
+ basl = 0.0;
+ for (k = 0; k < FW; k++) {
+
+ /* Choose window average with smallest deviation squared */
+ if (dev[k] < bdev) {
+ bdev = dev[k];
+ basl = fabs(asl[k]);
+ }
+ }
+
+ if (pp == 0) { /* First pass, compute average slope over bands */
+ slope[i] += basl;
+
+ } else { /* Second pass, average slopes of bands over threshold */
+ if (basl > slopeth) {
+ slope[i] += basl;
+ slopen++;
+ }
+ }
+ } /* Next band */
+
+ if (pp == 0) {
+ slopeth = 1.0 * slope[i]/j; /* Compute threshold */
+ slope[i] = 0.0;
+ } else {
+ if (slopen > 0)
+ slope[i] /= slopen; /* Compute average of those over threshold */
+ }
+ } /* Next pass */
+ }
+#undef FW
+#endif
+
+#ifndef NEVER /* Good with this on */
+ /* Normalise the slope values */
+ /* Locate the minumum and maximum values */
+ maxslope = 0.0;
+ minslope = 1e38;
+ for (i = 4; i < (nummeas-4); i++) {
+ double avs;
+
+ if (slope[i] > maxslope)
+ maxslope = slope[i];
+
+ /* Simple moving average for min comp. */
+ avs = 0.0;
+ for (j = -2; j <= 2; j++)
+ avs += slope[i+j];
+ avs /= 5.0;
+ if (avs < minslope)
+ minslope = avs;
+ }
+
+ /* Normalise the slope */
+ maxslope *= 0.5;
+ minslope *= 3.0;
+ for (i = 0; i < nummeas; i++) {
+ slope[i] = (slope[i] - minslope) / (maxslope - minslope);
+ if (slope[i] < 0.0)
+ slope[i] = 0.0;
+ else if (slope[i] > 1.0)
+ slope[i] = 1.0;
+ }
+
+ /* "Automatic Gain control" the raw slope information. */
+#define LFW 20 /* Half width of triangular filter */
+ for (i = 0; i < nummeas; i++) {
+ double sum, twt;
+
+ sum = twt = 0.0;
+ for (j = -LFW; j <= LFW; j++) {
+ double wt;
+ if ((i+j) < 0 || (i+j) >= nummeas)
+ continue;
+
+ wt = ((LFW-abs(j))/(double)LFW);
+
+ sum += wt * slope[i+j];
+ twt += wt;
+ }
+ fslope[i] = sum/twt;
+ if (fslope[i] > fmaxslope)
+ fmaxslope = fslope[i];
+ }
+#undef LFW
+
+#ifdef NEVER /* Better with the off, for very noisy samples */
+ /* Apply AGC with limited gain */
+ for (i = 0; i < nummeas; i++) {
+ if (fslope[i] > fmaxslope/4.0)
+ slope[i] = slope[i]/fslope[i];
+ else
+ slope[i] = slope[i] * 4.0/fmaxslope;
+ }
+#endif
+#endif /* NEVER */
+
+ /* Locate the minumum and maximum values */
+ maxslope = 0.0;
+ minslope = 1e38;
+ for (i = 4; i < (nummeas-4); i++) {
+ double avs;
+
+ if (slope[i] > maxslope)
+ maxslope = slope[i];
+
+ /* Simple moving average for min comp. */
+ avs = 0.0;
+ for (j = -2; j <= 2; j++)
+ avs += slope[i+j];
+ avs /= 5.0;
+ if (avs < minslope)
+ minslope = avs;
+ }
+
+#ifndef NEVER /* Good with this on */
+ /* Normalise the slope again */
+ maxslope *= 0.3;
+ minslope *= 3.0;
+ for (i = 0; i < nummeas; i++) {
+ slope[i] = (slope[i] - minslope) / (maxslope - minslope);
+ if (slope[i] < 0.0)
+ slope[i] = 0.0;
+ else if (slope[i] > 1.0)
+ slope[i] = 1.0;
+ }
+#endif
+
+#ifdef PATREC_DEBUG
+ printf("Slope filter output\n");
+ for (i = 0; i < nummeas; i++) {
+ int jj;
+ for (jj = 0, j = b_lo; jj < 6 && j < b_hi; jj++, j += ((b_hi-b_lo)/6)) {
+ double sum = 0.0;
+ for (k = -b_lo; k <= BW; k++) /* Box averaging filter over bands */
+ sum += multimeas[i][j + k];
+ plot[jj][i] = sum/((2.0 * b_lo + 1.0) * maxval[j+k]);
+ }
+ }
+ for (i = 0; i < nummeas; i++)
+ plot[6][i] = (double)i;
+ do_plot6(plot[6], slope, plot[0], plot[1], plot[2], plot[3], plot[4], nummeas);
+#endif /* PATREC_DEBUG */
+
+ free_dvector(fslope, 0, nummeas-1);
+ free_dmatrix(sslope, 0, nummeas-1, -1, m->nraw-1);
+
+ /* Now threshold the measurements into possible patches */
+ apat = 2 * nummeas;
+ if ((pat = (i1pro_patch *)malloc(sizeof(i1pro_patch) * apat)) == NULL) {
+ a1logd(p->log, 1, "i1pro: malloc of patch structures failed!\n");
+ return I1PRO_INT_MALLOC;
+ }
+
+ avglegth = 0.0;
+ for (npat = i = 0; i < (nummeas-1); i++) {
+ if (slope[i] > thresh)
+ continue;
+
+ /* Start of a new patch */
+ if (npat >= apat) {
+ apat *= 2;
+ if ((pat = (i1pro_patch *)realloc(pat, sizeof(i1pro_patch) * apat)) == NULL) {
+ a1logd(p->log, 1, "i1pro: reallloc of patch structures failed!\n");
+ return I1PRO_INT_MALLOC;
+ }
+ }
+ pat[npat].ss = i;
+ pat[npat].no = 2;
+ pat[npat].use = 0;
+ for (i++; i < (nummeas-1); i++) {
+ if (slope[i] > thresh)
+ break;
+ pat[npat].no++;
+ }
+ avglegth += (double) pat[npat].no;
+ npat++;
+ }
+ a1logd(p->log,7,"Number of patches = %d\n",npat);
+
+ /* We don't count the first and last patches, as we assume they are white leader */
+ if (npat < (tnpatch + 2)) {
+ free_ivector(sizepop, 0, nummeas-1);
+ free_dvector(slope, 0, nummeas-1);
+ free_dvector(maxval, -1, m->nraw-1);
+ free(pat);
+ a1logd(p->log,2,"Patch recog failed - unable to detect enough possible patches\n");
+ return I1PRO_RD_NOTENOUGHPATCHES;
+ } else if (npat >= (2 * tnpatch) + 2) {
+ free_ivector(sizepop, 0, nummeas-1);
+ free_dvector(slope, 0, nummeas-1);
+ free_dvector(maxval, -1, m->nraw-1);
+ free(pat);
+ a1logd(p->log,2,"Patch recog failed - detecting too many possible patches\n");
+ return I1PRO_RD_TOOMANYPATCHES;
+ }
+ avglegth /= (double)npat;
+
+ for (i = 0; i < npat; i++) {
+ a1logd(p->log,7,"Raw patch %d, start %d, length %d\n",i, pat[i].ss, pat[i].no);
+ }
+
+ /* Accumulate popularity ccount of possible patches */
+ for (i = 1; i < (npat-1); i++)
+ sizepop[pat[i].no]++;
+
+ /* Locate the median potential patch width */
+ for (j = 0, i = 0; i < nummeas; i++) {
+ j += sizepop[i];
+ if (j >= ((npat-2)/2))
+ break;
+ }
+ median = (double)i;
+
+ a1logd(p->log,7,"Median patch width %f\n",median);
+
+ /* Now decide which patches to use. */
+ /* Try a widening window around the median. */
+ for (window = 0.2, try = 0; try < 15; window *= 1.4, try++) {
+ int bgcount = 0, bgstart = 0;
+ int gcount, gstart;
+ double wmin = median/(1.0 + window);
+ double wmax = median * (1.0 + window);
+
+ a1logd(p->log,7,"Window = %f - %f\n",wmin, wmax);
+ /* Track which is the largest contiguous group that */
+ /* is within our window */
+ gcount = gstart = 0;
+ for (i = 1; i < npat; i++) {
+ if (i < (npat-1) && pat[i].no <= wmax) { /* Small enough */
+ if (pat[i].no >= wmin) { /* And big enough */
+ if (gcount == 0) { /* Start of new group */
+ gcount++;
+ gstart = i;
+ a1logd(p->log,7,"Start group at %d\n",gstart);
+ } else {
+ gcount++; /* Continuing new group */
+ a1logd(p->log,7,"Continue group at %d, count %d\n",gstart,gcount);
+ }
+ }
+ } else { /* Too big or end of patches, end this group */
+ a1logd(p->log,7,"Terminating group group at %d, count %d\n",gstart,gcount);
+ if (gcount > bgcount) { /* New biggest group */
+ bgcount = gcount;
+ bgstart = gstart;
+ a1logd(p->log,7,"New biggest\n");
+ }
+ gcount = gstart = 0; /* End this group */
+ }
+ }
+ a1logd(p->log,7,"Biggest group is at %d, count %d\n",bgstart,bgcount);
+
+ if (bgcount == tnpatch) { /* We're done */
+ for (i = bgstart, j = 0; i < npat && j < tnpatch; i++) {
+ if (pat[i].no <= wmax && pat[i].no >= wmin) {
+ pat[i].use = 1;
+ j++;
+ if (pat[i].no < MIN_SAMPLES) {
+ a1logd(p->log,7,"Too few samples\n");
+ free_ivector(sizepop, 0, nummeas-1);
+ free_dvector(slope, 0, nummeas-1);
+ free_dvector(maxval, -1, m->nraw-1);
+ free(pat);
+ a1logd(p->log,2,"Patch recog failed - patches sampled too sparsely\n");
+ return I1PRO_RD_NOTENOUGHSAMPLES;
+ }
+ }
+ }
+ break;
+
+ } else if (bgcount > tnpatch) {
+ a1logd(p->log,7,"Too many patches\n");
+ free_ivector(sizepop, 0, nummeas-1);
+ free_dvector(slope, 0, nummeas-1);
+ free_dvector(maxval, -1, m->nraw-1);
+ free(pat);
+ a1logd(p->log,2,"Patch recog failed - detected too many consistent patches\n");
+ return I1PRO_RD_TOOMANYPATCHES;
+ }
+ }
+ if (try >= 15) {
+ a1logd(p->log,7,"Not enough patches\n");
+ free_ivector(sizepop, 0, nummeas-1);
+ free_dvector(slope, 0, nummeas-1);
+ free_dvector(maxval, -1, m->nraw-1);
+ free(pat);
+ a1logd(p->log,2,"Patch recog failed - unable to find enough consistent patches\n");
+ return I1PRO_RD_NOTENOUGHPATCHES;
+ }
+
+ a1logd(p->log,7,"Got %d patches out of potential %d:\n",tnpatch, npat);
+ a1logd(p->log,7,"Average patch legth %f\n",avglegth);
+ for (i = 1; i < (npat-1); i++) {
+ if (pat[i].use == 0)
+ continue;
+ a1logd(p->log,7,"Patch %d, start %d, length %d:\n",i, pat[i].ss, pat[i].no, pat[i].use);
+ }
+
+ /* Now trim the patches simply by shrinking their windows */
+ for (k = 1; k < (npat-1); k++) {
+ int nno, trim;
+
+ if (pat[k].use == 0)
+ continue;
+
+
+ nno = (pat[k].no * 3)/4;
+ trim = (pat[k].no - nno)/2;
+
+ pat[k].ss += trim;
+ pat[k].no = nno;
+ }
+
+#ifdef PATREC_DEBUG
+ a1logd(p->log,7,"After trimming got:\n");
+ for (i = 1; i < (npat-1); i++) {
+ if (pat[i].use == 0)
+ continue;
+ printf("Patch %d, start %d, length %d:\n",i, pat[i].ss, pat[i].no, pat[i].use);
+ }
+
+ /* Create fake "slope" value that marks patches */
+ for (i = 0; i < nummeas; i++)
+ slope[i] = 1.0;
+ for (k = 1; k < (npat-1); k++) {
+ if (pat[k].use == 0)
+ continue;
+ for (i = pat[k].ss; i < (pat[k].ss + pat[k].no); i++)
+ slope[i] = 0.0;
+ }
+
+ printf("Trimmed output:\n");
+ for (i = 0; i < nummeas; i++) {
+ int jj;
+ for (jj = 0, j = b_lo; jj < 6 && j < b_hi; jj++, j += ((b_hi-b_lo)/6)) {
+ double sum = 0.0;
+ for (k = -b_lo; k <= BW; k++) /* Box averaging filter over bands */
+ sum += multimeas[i][j + k];
+ plot[jj][i] = sum/((2.0 * b_lo + 1.0) * maxval[j+k]);
+ }
+ }
+ for (i = 0; i < nummeas; i++)
+ plot[6][i] = (double)i;
+ do_plot6(plot[6], slope, plot[0], plot[1], plot[2], plot[3], plot[4], nummeas);
+#endif /* PATREC_DEBUG */
+
+#ifdef PATREC_DEBUG
+ free_dmatrix(plot, 0, 6, 0, nummeas-1);
+#endif /* PATREC_DEBUG */
+
+ /* Compute average of (aproximate) white */
+ white_avg = 0.0;
+ for (j = 1; j < (m->nraw-1); j++)
+ white_avg += maxval[j];
+ white_avg /= (m->nraw - 2.0);
+
+ /* Now process the buffer values */
+ for (i = 0; i < tnpatch; i++) {
+ for (j = 0; j < m->nraw; j++)
+ pavg[i][j] = 0.0;
+ }
+
+ for (pix = 0, k = 1; k < (npat-1); k++) {
+ double maxavg = -1e38; /* Track min and max averages of readings for consistency */
+ double minavg = 1e38;
+ double avgoverth = 0.0; /* Average over saturation threshold */
+ double cons; /* Consistency */
+
+ if (pat[k].use == 0)
+ continue;
+
+ if (pat[k].no <= MIN_SAMPLES) {
+ a1logd(p->log,7,"Too few samples (%d, need %d)\n",pat[k].no,MIN_SAMPLES);
+ free_dvector(slope, 0, nummeas-1);
+ free_ivector(sizepop, 0, nummeas-1);
+ free_dvector(maxval, -1, m->nraw-1);
+ free(pat);
+ a1logd(p->log,2,"Patch recog failed - patches sampled too sparsely\n");
+ return I1PRO_RD_NOTENOUGHSAMPLES;
+ }
+
+ /* Measure samples that make up patch value */
+ for (i = pat[k].ss; i < (pat[k].ss + pat[k].no); i++) {
+ double measavg = 0.0;
+
+ for (j = 1; j < m->nraw-1; j++) {
+ double val;
+
+ val = multimeas[i][j];
+
+ if (val > highest)
+ highest = val;
+ if (val > satthresh)
+ avgoverth++;
+ measavg += val;
+ pavg[pix][j] += val;
+ }
+ measavg /= (m->nraw-2.0);
+ if (measavg < minavg)
+ minavg = measavg;
+ if (measavg > maxavg)
+ maxavg = measavg;
+
+ /* and the duplicated values at the end */
+ pavg[pix][0] += multimeas[i][0];
+ pavg[pix][127] += multimeas[i][127];
+ }
+
+ for (j = 0; j < m->nraw; j++)
+ pavg[pix][j] /= (double)pat[k].no;
+ avgoverth /= (double)pat[k].no;
+
+ if (satthresh > 0.0 && avgoverth >= 10.0)
+ rv |= 2;
+
+ cons = (maxavg - minavg)/white_avg;
+ a1logd(p->log,7,"Patch %d: consistency = %f%%, thresh = %f%%\n",pix,100.0 * cons, 100.0 * patch_cons_thr);
+ if (cons > patch_cons_thr) {
+ a1logd(p->log,2,"Patch recog failed - patch %d is inconsistent (%f%% > %f)\n",pix,cons, patch_cons_thr);
+ rv |= 1;
+ }
+ pix++;
+ }
+
+ if (phighest != NULL)
+ *phighest = highest;
+ if (flags != NULL)
+ *flags = rv;
+
+ free_dvector(slope, 0, nummeas-1);
+ free_ivector(sizepop, 0, nummeas-1);
+ free_dvector(maxval, -1, m->nraw-1);
+ free(pat);
+
+ if (rv & 2)
+ a1logd(p->log,2,"Patch recog failed - some patches are saturated\n");
+
+ a1logd(p->log,2,"i1pro_extract_patches_multimeas done, sat = %s, inconsist = %s\n",
+ rv & 2 ? "true" : "false", rv & 1 ? "true" : "false");
+
+ return I1PRO_OK;
+}
+#undef BL
+#undef BH
+#undef BW
+
+
+/* Recognise any flashes in the readings, and */
+/* and average their values together as well as summing their duration. */
+/* Return nz on an error */
+/* (Doesn't extract [-1] shielded values, since they have already been used) */
+i1pro_code i1pro_extract_patches_flash(
+ i1pro *p,
+ int *flags, /* return flags */
+ double *duration, /* return duration */
+ double *pavg, /* return patch average [-1 nraw] */
+ double **multimeas, /* Array of [nummeas][-1 nraw] value to extract from */
+ int nummeas, /* number of readings made */
+ double inttime /* Integration time (used to compute duration) */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ int i, j, k, pix;
+ double minval, maxval; /* min and max input value at wavelength of maximum input */
+ double mean; /* Mean of the max wavelength band */
+ int maxband; /* Band of maximum value */
+ double thresh; /* Level threshold */
+ int fsampl; /* Index of the first sample over the threshold */
+ int nsampl; /* Number of samples over the threshold */
+ double *aavg; /* ambient average [-1 nraw] */
+ double finttime; /* Flash integration time */
+ int rv = 0;
+#ifdef PATREC_DEBUG
+ double **plot;
+#endif
+
+ a1logd(p->log,2,"i1pro_extract_patches_flash looking for flashes in %d measurements\n",nummeas);
+
+ /* Discover the maximum input value for flash dection */
+ maxval = -1e6;
+ maxband = 0;
+ for (j = 0; j < m->nraw; j ++) {
+ for (i = 0; i < nummeas; i++) {
+ if (multimeas[i][j] > maxval) {
+ maxval = multimeas[i][j];
+ maxband = j;
+ }
+ }
+ }
+
+ if (maxval <= 0.0) {
+ a1logd(p->log,2,"No flashes found in measurement\n");
+ return I1PRO_RD_NOFLASHES;
+ }
+
+ minval = 1e6;
+ mean = 0.0;
+ for (i = 0; i < nummeas; i++) {
+ mean += multimeas[i][maxband];
+ if (multimeas[i][maxband] < minval)
+ minval = multimeas[i][maxband];
+ }
+ mean /= (double)nummeas;
+
+ /* Set the threshold at 5% from mean towards max */
+ thresh = (3.0 * mean + maxval)/4.0;
+ a1logd(p->log,7,"i1pro_extract_patches_flash band %d minval %f maxval %f, mean = %f, thresh = %f\n",maxband,minval,maxval,mean, thresh);
+
+#ifdef PATREC_DEBUG
+ /* Plot out 6 lots of 6 values each */
+ plot = dmatrixz(0, 6, 0, nummeas-1);
+ for (j = maxband -3; j>= 0 && j < (m->nraw-6); j += 100) /* Do one set around max */
+ {
+ for (k = 0; k < 6; k ++) {
+ for (i = 0; i < nummeas; i++) {
+ plot[k][i] = multimeas[i][j+k]/maxval;
+ }
+ }
+ for (i = 0; i < nummeas; i++)
+ plot[6][i] = (double)i;
+ printf("Bands %d - %d\n",j,j+5);
+ do_plot6(plot[6], plot[0], plot[1], plot[2], plot[3], plot[4], plot[5], nummeas);
+ }
+ free_dmatrix(plot,0,6,0,nummeas-1);
+#endif /* PATREC_DEBUG */
+
+#ifdef PATREC_DEBUG
+ /* Plot just the pulses */
+ {
+ int start, end;
+
+ plot = dmatrixz(0, 6, 0, nummeas-1);
+
+ for(j = 0, start = -1, end = 0;;) {
+
+ for (start = -1, i = end; i < nummeas; i++) {
+ if (multimeas[i][maxband] >= thresh) {
+ if (start < 0)
+ start = i;
+ } else if (start >= 0) {
+ end = i;
+ break;
+ }
+ }
+ if (start < 0)
+ break;
+ start -= 3;
+ if (start < 0)
+ start = 0;
+ end += 4;
+ if (end > nummeas)
+ end = nummeas;
+
+ for (i = start; i < end; i++, j++) {
+ int q;
+
+ plot[6][j] = (double)j;
+#ifdef NEVER /* Plot +/-3 around maxband */
+ for (q = 0, k = maxband -3; k < (maxband+3) && k >= 0 && k < m->nraw; k++, q++) {
+ plot[q][j] = multimeas[i][k]/maxval;
+ }
+#else
+ /* plot max of bands in 6 segments */
+ for (q = 0; q < 6; q++) {
+ int ss, ee;
+
+ plot[q][j] = -1e60;
+ ss = q * (m->nraw/6);
+ ee = (q+1) * (m->nraw/6);
+ for (k = ss; k < ee; k++) {
+ if (multimeas[i][k]/maxval > plot[q][j])
+ plot[q][j] = multimeas[i][k]/maxval;
+ }
+ }
+#endif
+ }
+ }
+ do_plot6(plot[6], plot[0], plot[1], plot[2], plot[3], plot[4], plot[5], j);
+ free_dmatrix(plot,0,6,0,nummeas-1);
+ }
+#endif
+
+ /* Locate the first sample over the threshold, and the */
+ /* total number of samples in the pulses. */
+ fsampl = -1;
+ for (nsampl = i = 0; i < nummeas; i++) {
+ for (j = 0; j < m->nraw-1; j++) {
+ if (multimeas[i][j] >= thresh)
+ break;
+ }
+ if (j < m->nraw-1) {
+ if (fsampl < 0)
+ fsampl = i;
+ nsampl++;
+ }
+ }
+ a1logd(p->log,7,"Number of flash patches = %d\n",nsampl);
+ if (nsampl == 0)
+ return I1PRO_RD_NOFLASHES;
+
+ /* See if there are as many samples before the first flash */
+ if (nsampl < 6)
+ nsampl = 6;
+
+ /* Average nsample samples of ambient */
+ i = (fsampl-3-nsampl);
+ if (i < 0)
+ return I1PRO_RD_NOAMBB4FLASHES;
+ a1logd(p->log,7,"Ambient samples %d to %d \n",i,fsampl-3);
+ aavg = dvectorz(-1, m->nraw-1);
+ for (nsampl = 0; i < (fsampl-3); i++) {
+ for (j = 0; j < m->nraw-1; j++)
+ aavg[j] += multimeas[i][j];
+ nsampl++;
+ }
+
+ /* Average all the values over the threshold, */
+ /* and also one either side of flash */
+ for (j = 0; j < m->nraw-1; j++)
+ pavg[j] = 0.0;
+
+ for (k = 0, i = 1; i < (nummeas-1); i++) {
+ int sample = 0;
+ for (j = 0; j < m->nraw-1; j++) {
+ if (multimeas[i-1][j] >= thresh) {
+ sample = 1;
+ break;
+ }
+ if (multimeas[i][j] >= thresh) {
+ sample = 1;
+ break;
+ }
+ if (multimeas[i+1][j] >= thresh) {
+ sample = 1;
+ break;
+ }
+ }
+ if (j < m->nraw-1) {
+ a1logd(p->log,7,"Integrating flash sample no %d \n",i);
+ for (j = 0; j < m->nraw-1; j++)
+ pavg[j] += multimeas[i][j];
+ k++;
+ }
+ }
+ for (j = 0; j < m->nraw-1; j++)
+ pavg[j] = pavg[j]/(double)k - aavg[j]/(double)nsampl;
+
+ a1logd(p->log,7,"Number of flash patches integrated = %d\n",k);
+
+ finttime = inttime * (double)k;
+ if (duration != NULL)
+ *duration = finttime;
+
+ /* Convert to cd/m^2 seconds */
+ for (j = 0; j < m->nraw-1; j++)
+ pavg[j] *= finttime;
+
+ if (flags != NULL)
+ *flags = rv;
+
+ free_dvector(aavg, -1, m->nraw-1);
+
+ return I1PRO_OK;
+}
+
+
+/* Subtract the black level. */
+/* If Rev E, also adjust according to shielded cells, and linearise. */
+void i1pro_sub_absraw(
+ i1pro *p,
+ int nummeas, /* Return number of readings measured */
+ double inttime, /* Integration time used */
+ int gainmode, /* Gain mode, 0 = normal, 1 = high */
+ double **absraw, /* Source/Desination array [-1 nraw] */
+ double *sub /* Black value to subtract [-1 nraw] */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ double gain;
+ int npoly; /* Number of linearisation coefficients */
+ double *polys; /* the coeficients */
+ double scale; /* Absolute scale value */
+ double submax = -1e6; /* Subtraction value maximum */
+ int i, j;
+
+ if (gainmode) {
+ gain = m->highgain;
+ npoly = m->nlin1;
+ polys = m->lin1;
+ } else {
+ gain = 1.0;
+ npoly = m->nlin0;
+ polys = m->lin0;
+ }
+ scale = 1.0/(inttime * gain); /* To scale RevE linearity */
+
+ /* Adjust black to allow for temperature change by using the */
+ /* shielded cell values as a reference. */
+ /* We use a heuristic to compute a zero based scale for adjusting the */
+ /* black. It's not clear why it works best this way, or how */
+ /* dependent on the particular instrument the magic numbers are, */
+ /* but it reduces the black level error from over 10% to about 0.3% */
+ if (p->itype == instI1Pro2) {
+// double xx[NSEN_MAX], in[NSEN_MAX], res[NSEN_MAX];
+ double asub[NSEN_MAX];
+ double avgscell, zero;
+
+ /* Locate largest of black */
+ for (j = 0; j < m->nraw; j++) {
+ if (sub[j] > submax)
+ submax = sub[j];
+ }
+
+ /* Average the shielded cell value of all the readings */
+ avgscell = 0.0;
+ for (i = 0; i < nummeas; i++)
+ avgscell += absraw[i][-1];
+ avgscell /= (double)nummeas;
+
+ /* Compute scaling zero */
+ zero = 1.144 * 0.5 * (avgscell + sub[-1]);
+
+ /* make sure that the zero point is above any black value */
+ if (zero < (1.01 * avgscell))
+ zero = 1.01 * avgscell;
+ if (zero < (1.01 * sub[-1]))
+ zero = 1.01 * sub[-1];
+ if (zero < (1.01 * submax))
+ zero = 1.01 * submax;
+
+ a1logd(p->log,2,"Black shielded value = %f, Reading shielded value = %f\n",sub[-1], avgscell);
+ /* Compute the adjusted black */
+ for (j = 0; j < m->nraw; j++) {
+#ifdef NEVER
+ /* simple additive correction */
+# pragma message("######### i1pro2 Simple shielded cell temperature correction! ########")
+ asub[j] = sub[j] + avgscell - sub[-1];
+#else
+ /* heuristic scaled correction */
+ asub[j] = zero - (zero - sub[j]) * (zero - avgscell)/(zero - sub[-1]);
+#endif
+ }
+
+ /* Subtract the black */
+ for (i = 0; i < nummeas; i++) {
+ for (j = 0; j < m->nraw; j++) {
+// xx[j] = j, in[j] = absraw[i][j];
+
+ absraw[i][j] -= asub[j]; /* Subtract adjusted black */
+
+// res[j] = absraw[i][j] + (double)((int)(avgscell/20.0)) * 20.0;
+#ifdef ENABLE_NONLINCOR
+ /* Linearise */
+ {
+ int k;
+ double fval, lval;
+
+ fval = absraw[i][j] / scale; /* Scale back to sensor value range */
+
+ for (lval = polys[npoly-1], k = npoly-2; k >= 0; k--)
+ lval = lval * fval + polys[k];
+
+ absraw[i][j] = scale * lval; /* Rescale back to absolute range */
+ }
+#endif
+ }
+#ifdef PLOT_BLACK_SUBTRACT /* Plot black adjusted levels */
+ printf("black = meas, red = black, green = adjuste black, blue = result\n");
+ do_plot6(xx, in, sub, adjsub, res, NULL, NULL, m->nraw);
+#endif
+ }
+
+ /* Rev A-D don't have shielded reference cells */
+ } else {
+
+ /* For each measurement */
+ for (i = 0; i < nummeas; i++) {
+ for (j = -1; j < m->nraw; j++) {
+ absraw[i][j] -= sub[j];
+ }
+ }
+ }
+}
+
+/* Convert an absraw array from raw wavelengths to output wavelenths */
+/* for a given [std res, high res] and [emis/tras, reflective] mode */
+void i1pro_absraw_to_abswav(
+ i1pro *p,
+ int highres, /* 0 for std res, 1 for high res */
+ int refl, /* 0 for emis/trans, 1 for reflective */
+ int nummeas, /* Return number of readings measured */
+ double **abswav, /* Desination array [nwav] */
+ double **absraw /* Source array [-1 nraw] */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ int i, j, k, cx, sx;
+ double *tm; /* Temporary array */
+
+ tm = dvector(0, m->nwav[highres]-1);
+
+ /* For each measurement */
+ for (i = 0; i < nummeas; i++) {
+
+ /* For each output wavelength */
+ for (cx = j = 0; j < m->nwav[highres]; j++) {
+ double oval = 0.0;
+
+ /* For each matrix value */
+ sx = m->mtx[highres][refl].index[j]; /* Starting index */
+ for (k = 0; k < m->mtx[highres][refl].nocoef[j]; k++, cx++, sx++) {
+ oval += m->mtx[highres][refl].coef[cx] * absraw[i][sx];
+ }
+ abswav[i][j] = tm[j] = oval;
+ }
+
+ if (p->itype == instI1Pro2) {
+ /* Now apply stray light compensation */
+ /* For each output wavelength */
+ for (j = 0; j < m->nwav[highres]; j++) {
+ double oval = 0.0;
+
+ /* For each matrix value */
+ for (k = 0; k < m->nwav[highres]; k++)
+ oval += m->straylight[highres][j][k] * tm[k];
+ abswav[i][j] = oval;
+ }
+#ifdef PLOT_DEBUG
+ printf("Before & after stray light correction:\n");
+ plot_wav_2(m, highres, tm, abswav[i]);
+#endif /* PLOT_DEBUG */
+ }
+ }
+ free_dvector(tm, 0, m->nwav[highres]-1);
+}
+
+/* Convert an abswav array of output wavelengths to scaled output readings. */
+void i1pro_scale_specrd(
+ i1pro *p,
+ double **outspecrd, /* Destination */
+ int numpatches, /* Number of readings/patches */
+ double **inspecrd /* Source */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ int i, j;
+
+ /* For each measurement */
+ for (i = 0; i < numpatches; i++) {
+
+ /* For each output wavelength */
+ for (j = 0; j < m->nwav[m->highres]; j++) {
+ outspecrd[i][j] = inspecrd[i][j] * s->cal_factor[m->highres][j];
+ }
+ }
+}
+
+
+/* =============================================== */
+/* Rev E wavelength calibration */
+
+/*
+ The Rev E has a wavelength reference LED amd
+ stores a reference raw spectrum of it in its
+ calibrated state, together with an polinomial
+ defining the raw bin no. to wavelength conversion.
+
+ By measuring the wavelength LED and finding
+ the best positional match against the reference
+ spectrum, a CCD bin offset can be computed
+ to compensate for any shift in the optical or
+ physical alignment of spectrum against CCD.
+
+ To use the adjustment, the raw to wave subsampling
+ filters need to be regenerated, and to ensure that
+ the instrument returns readings very close to the
+ manufacturers driver, the same underlying filter
+ creation mathematics needs to be used.
+
+ The manufacturers filter weights are the accumulated
+ third order Lagrange polynomial weights of the
+ integration of a 20 nm wide triange spectrum
+ centered at each output wavelength, discretely
+ integrated between the range of the middle two points
+ of the Lagrange interpolator. The triangle response
+ being integrated has an area of exactly 1.0.
+
+ */
+
+/* Invert a raw2wavlength polinomial equation. */
+/* Use simple Newton inversion will suffice. */
+static double inv_raw2wav(double *polys, int npoly, double inv) {
+ double outv = 560.0, lval, del = 100.0;
+ int i, k;
+
+ for (i = 0; i < 200 && fabs(del) > 1e-7; i++) {
+ for (lval = polys[npoly-1], k = npoly-2; k >= 0; k--) {
+ lval = lval * outv + polys[k];
+ }
+ del = (inv - lval);
+ outv += 0.4 * del;
+ }
+
+ return 128.0 - outv;
+}
+
+/* return the uncalibrated wavelength given a raw bin value */
+/* (Always uses reflective RevE wav2cal) */
+static double i1pro_raw2wav_uncal(i1pro *p, double raw) {
+ i1proimp *m = (i1proimp *)p->m;
+ double ov;
+ int k;
+
+ if (p->itype == instI1Pro2) {
+ raw = 128.0 - raw; /* Quadratic expects +ve correlation */
+
+ /* Compute polinomial */
+ for (ov = m->wlpoly1[4-1], k = 4-2; k >= 0; k--)
+ ov = ov * raw + m->wlpoly1[k];
+ } else {
+ co pp;
+
+ if (m->raw2wav == NULL) {
+ a1loge(p->log,1,"i1pro_raw2wav_uncal called when hi-res not inited\n");
+ return I1PRO_INT_ASSERT;
+ }
+
+ pp.p[0] = raw;
+ m->raw2wav->interp(m->raw2wav, &pp);
+ ov = pp.v[0];
+ }
+
+ return ov;
+}
+
+/* return the calibrated wavelength given a raw bin value for the given mode */
+static double i1pro_raw2wav(i1pro *p, int refl, double raw) {
+ i1proimp *m = (i1proimp *)p->m;
+ double ov;
+ int k;
+
+ if (p->itype == instI1Pro2) {
+ i1pro_state *s = &m->ms[m->mmode];
+
+ /* Correct for CCD offset and scale back to reference */
+ raw = raw - s->wl_led_off + m->wl_led_ref_off;
+
+ raw = 128.0 - raw; /* Quadratic expects +ve correlation */
+
+ /* Compute polinomial */
+ if (refl) {
+ for (ov = m->wlpoly1[4-1], k = 4-2; k >= 0; k--)
+ ov = ov * raw + m->wlpoly1[k];
+ } else {
+ for (ov = m->wlpoly2[4-1], k = 4-2; k >= 0; k--)
+ ov = ov * raw + m->wlpoly2[k];
+ }
+ } else {
+ co pp;
+
+ /* If not RevE there is no WL calibration */
+ if (m->raw2wav == NULL) {
+ a1loge(p->log,1,"i1pro_raw2wav_uncal called when hi-res not inited\n");
+ return I1PRO_INT_ASSERT;
+ }
+
+ pp.p[0] = raw;
+ m->raw2wav->interp(m->raw2wav, &pp);
+ ov = pp.v[0];
+ }
+
+ return ov;
+}
+
+/* Powell minimisation contxt for WL calibration */
+typedef struct {
+ double ref_max; /* reference maximum level */
+ double *wl_ref; /* Wavlength reference samples */
+ int wl_ref_n; /* Number of wavelength references */
+ double *wl_meas; /* Wavelength measurement samples */
+ int wl_meas_n; /* Number of wavelength measurement samples */
+ int plot; /* Plot each try */
+} wlcal_cx;
+
+/* Powell minimisation callback function */
+/* Parameters being optimized are magnitude, offset and scale */
+static double wlcal_opt1(void *vcx, double tp[]) {
+#ifdef PLOT_DEBUG
+ int pix = 0;
+ double xx[1024];
+ double y1[1024]; /* interpolate ref */
+ double y2[1024]; /* Measurement */
+ double y3[1024]; /* Error */
+#endif
+ wlcal_cx *cx = (wlcal_cx *)vcx;
+ double vv, rv = 0.0;
+ int si, i;
+
+ si = (int)tp[1];
+
+ /* i = Measurement index */
+ for (i = si; i < cx->wl_meas_n; i++) {
+
+ double xv; /* offset & scaled measurement index */
+ int ix; /* Lagrange base offset */
+ double yv;
+
+ xv = ((double)i - tp[1]); /* fitted measurement location in reference no scale */
+
+ ix = ((int)xv) - 1; /* Reference index of Lagrange for this xv */
+ if (ix < 0)
+ continue;
+ if ((ix + 3) > cx->wl_ref_n)
+ break;
+
+ /* Compute interpolated value of reference using Lagrange: */
+ yv = cx->wl_ref[ix+0] * (xv-(ix+1)) * (xv-(ix+2)) * (xv-(ix+3))
+ /((0.0-1.0) * (0.0-2.0) * (0.0-3.0))
+ + cx->wl_ref[ix+1] * (xv-(ix+0)) * (xv-(ix+2)) * (xv-(ix+3))
+ /((1.0-0.0) * (1.0-2.0) * (1.0-3.0))
+ + cx->wl_ref[ix+2] * (xv-(ix+0)) * (xv-(ix+1)) * (xv-(ix+3))
+ /((2.0-0.0) * (2.0-1.0) * (2.0-3.0))
+ + cx->wl_ref[ix+3] * (xv-(ix+0)) * (xv-(ix+1)) * (xv-(ix+2))
+ /((3.0-0.0) * (3.0-1.0) * (3.0-2.0));
+ vv = yv - tp[0] * cx->wl_meas[i];
+
+ /* Weight error linearly with magnitude, to emphasise peak error */
+ /* rather than what's happening down in the noise */
+ vv = vv * vv * (yv + 1.0)/(cx->ref_max+1.0);
+
+#ifdef PLOT_DEBUG
+ if (cx->plot) {
+ xx[pix] = (double)i;
+ y1[pix] = yv;
+ y2[pix] = tp[0] * cx->wl_meas[i];
+// y3[pix] = 2000.0 * (0.02 + yv/cx->ref_max); /* Weighting */
+ y3[pix] = 0.5 * vv; /* Error squared */
+ pix++;
+ }
+#endif
+ rv += vv;
+ }
+#ifdef PLOT_DEBUG
+ if (cx->plot) {
+ printf("Params %f %f -> err %f\n", tp[0], tp[1], rv);
+ do_plot(xx, y1, y2, y3, pix);
+ }
+#endif
+//printf("~1 %f %f -> %f\n", tp[0], tp[1], rv);
+ return rv;
+}
+
+#ifdef SALONEINSTLIB
+/* Do a rudimetrary 2d optimization that uses exaustive */
+/* search with hierarchical step sizes */
+int wloptimize(double *cparm,
+ double *ss,
+ double tol,
+ double (*funk)(void *fdata, double tp[]),
+ void *fdata
+) {
+ double range[2][2]; /* [dim][min/max] */
+ double val[2]; /* Current test values */
+ double bfit = 1e38; /* Current best fit values */
+ int dim;
+
+ for (dim = 0; dim < 2; dim++) {
+ range[dim][0] = cparm[dim] - ss[dim];
+ range[dim][1] = cparm[dim] + ss[dim];
+ val[dim] = cparm[dim];
+ }
+
+ /* Until we reach the tollerance */
+ for (;;) {
+ double mstep = 1e38;
+
+ for (dim = 0; dim < 2; dim++) {
+ double stepsz;
+ stepsz = (range[dim][1] - range[dim][0])/10.0;
+ if (stepsz < mstep)
+ mstep = stepsz;
+
+ /* Search in this dimension */
+ for (val[dim] = range[dim][0]; val[dim] <= range[dim][1]; val[dim] += stepsz) {
+ double fit;
+ fit = funk(fdata, val);
+ if (fit < bfit) {
+ cparm[dim] = val[dim];
+ bfit = fit;
+ }
+ }
+ val[dim] = cparm[dim];
+ range[dim][0] = val[dim] - stepsz;
+ range[dim][1] = val[dim] + stepsz;
+ }
+ if (mstep <= tol)
+ break;
+ }
+ return 0;
+}
+#endif /* SALONEINSTLIB */
+
+
+/* Given a raw measurement of the wavelength LED, */
+/* Compute the base offset that best fits it to the reference */
+i1pro_code i1pro2_match_wl_meas(i1pro *p, double *pled_off, double *wlraw) {
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_code ev = I1PRO_OK;
+ int i;
+ int rpoff, mpoff; /* Peak offset */
+ int roff, moff; /* Base index */
+ double lhalf, rhalf;
+ double fwhm; /* Measured half width */
+ double rmax, mmax;
+ double magscale;
+ double led_off, off_nm;
+
+ /* Do simple match first - locate maximum */
+ rmax = -1e6;
+ rpoff = -1;
+ for (i = 0; i < m->wl_led_count; i++) {
+ if (m->wl_led_spec[i] > rmax) {
+ rmax = m->wl_led_spec[i]; /* Max of reference */
+ rpoff = i;
+ }
+ }
+
+ mmax = -1e6;
+ mpoff = -1;
+ for (i = 0; i < m->nraw; i++) {
+ if (wlraw[i] > mmax) {
+ mmax = wlraw[i]; /* Max of measurement */
+ mpoff = i;
+ }
+ }
+
+ if (mpoff < 0 || mpoff >= m->nraw) {
+ a1logd(p->log,1,"Couldn't locate WL measurement peak\n");
+ return I1PRO_WL_SHAPE;
+ }
+
+ /* Check magnitude is sufficient (not sure this is right, typically 5900 > 882) */
+ a1logd(p->log,2,"Measured WL level = %f, minimum needed = %f\n",mmax, m->wl_cal_min_level);
+ if (mmax < m->wl_cal_min_level) {
+ a1logd(p->log,1,"i1pro2_match_wl_meas peak magnitude too low\n");
+ return I1PRO_WL_TOOLOW;
+ }
+
+ /* Locate the half peak values */
+ for (i = 1; i < mpoff; i++) {
+ if (wlraw[i] > (mmax/2.0)) { /* Use linear interp */
+ lhalf = (wlraw[i] - mmax/2.0)/(wlraw[i] - wlraw[i-1]);
+ lhalf = lhalf * (i-1.0) + (1.0 - lhalf) * (double)i;
+ break;
+ }
+ }
+ if (i >= mpoff) {
+ a1logd(p->log,1,"Couldn't locate WL left half level\n");
+ return I1PRO_WL_SHAPE;
+ }
+ for (; i < m->nraw; i++) {
+ if (wlraw[i] < (mmax/2.0)) { /* Use linear interp */
+ rhalf = (mmax/2.0 - wlraw[i])/(wlraw[i-1] - wlraw[i]);
+ rhalf = rhalf * (i-1.0) + (1.0 - rhalf) * (double)i;
+ break;
+ }
+ }
+ if (i >= m->nraw) {
+ a1logd(p->log,1,"Couldn't locate WL righ half level\n");
+ return I1PRO_WL_SHAPE;
+ }
+ a1logd(p->log,5,"WL half levels at %f (%f nm) and %f (%f nm)\n",lhalf, i1pro_raw2wav_uncal(p, lhalf), rhalf, i1pro_raw2wav_uncal(p, rhalf));
+ fwhm = i1pro_raw2wav_uncal(p, lhalf) - i1pro_raw2wav_uncal(p, rhalf);
+ a1logd(p->log,3, "WL spectrum fwhm = %f\n",fwhm);
+ if (fwhm < (m->wl_cal_fwhm - m->wl_cal_fwhm_tol)
+ || fwhm > (m->wl_cal_fwhm + m->wl_cal_fwhm_tol)) {
+ a1logd(p->log,1,"WL fwhm %f is out of range %f .. %f\n",fwhm,m->wl_cal_fwhm - m->wl_cal_fwhm_tol,m->wl_cal_fwhm + m->wl_cal_fwhm_tol);
+ return I1PRO_WL_SHAPE;
+ }
+
+ roff = m->wl_led_ref_off; /* reference raw offset */
+ moff = mpoff - rpoff; /* rough measured raw offset */
+
+ a1logd(p->log,3, "Preliminary WL peak match at ref base offset %d into measurement\n", moff);
+
+ magscale = rmax/mmax; /* Initial scale to make them match */
+
+#ifdef PLOT_DEBUG
+ /* Plot the match */
+ {
+ double xx[1024];
+ double y1[1024];
+ double y2[1024];
+
+ for (i = 0; i < m->nraw; i++) {
+ xx[i] = (double)i;
+ y1[i] = 0.0;
+ if (i >= moff && (i - moff) < m->wl_led_count) {
+ y1[i] = m->wl_led_spec[i- moff];
+ }
+ y2[i] = wlraw[i] * magscale;
+ }
+ printf("Simple WL match, ref = black, meas = red:\n");
+ do_plot(xx, y1, y2, NULL, m->nraw);
+ }
+#endif
+
+ /* Now do a good match */
+ /*
+ Do Lagrange interpolation on the reference curve,
+ and use a minimizer to find the best fit (minimum weighted y error)
+ by optimizing the magnitude, offset and scale.
+ */
+
+ {
+ wlcal_cx cx;
+ double cparm[2]; /* fit parameters */
+ double ss[2]; /* Search range */
+
+ cparm[0] = magscale;
+ ss[0] = 0.2;
+ cparm[1] = (double)moff;
+ ss[1] = 4.0; /* == +- 12 nm */
+
+ cx.ref_max = rmax;
+ cx.wl_ref = m->wl_led_spec;
+ cx.wl_ref_n = m->wl_led_count;
+ cx.wl_meas = wlraw;
+ cx.wl_meas_n = m->nraw;
+// cx.plot = 1; /* Plot each trial */
+
+ /* We could use the scale to adjust the whole CCD range, */
+ /* but the manufacturers driver doesn't seem to do this, */
+ /* and it may be making the calibration sensitive to any */
+ /* changes in the WL LED spectrum shape. Instead we minimize */
+ /* the error weighted for the peak of the shape. */
+
+#ifdef SALONEINSTLIB
+ if (wloptimize(cparm, ss, 1e-7, wlcal_opt1, &cx))
+ a1logw(p->log,"wlcal_opt1 failed\n");
+#else
+ if (powell(NULL, 2, cparm, ss, 1e-6, 1000, wlcal_opt1, &cx, NULL, NULL))
+ a1logw(p->log,"wlcal_opt1 failed\n");
+#endif
+ a1logd(p->log,3,"WL best fit parameters: %f %f\n", cparm[0], cparm[1]);
+
+ led_off = cparm[1];
+
+#ifdef PLOT_DEBUG
+ /* Plot the final result */
+ printf("Best WL match, ref = black, meas = red, err = green:\n");
+ cx.plot = 1;
+ wlcal_opt1(&cx, cparm);
+#endif
+
+ /* If we have calibrated on the ambient cap, correct */
+ /* for the emissive vs. reflective raw2wav scaling factor */
+ if (mmax < 2500.0) {
+ double wlraw2 = m->wl_led_ref_off + (double)rpoff;
+ double raw, wlnm, wlraw1, refnm;
+ int k;
+
+ /* Convert from raw to wavelength using poly2 (emission) */
+ raw = 128.0 - wlraw2; /* Quadratic expects +ve correlation */
+ for (wlnm = m->wlpoly2[4-1], k = 4-2; k >= 0; k--)
+ wlnm = wlnm * raw + m->wlpoly2[k];
+
+ /* Convert from wavelength to raw using poly1 (reflectance) */
+ wlraw1 = inv_raw2wav(m->wlpoly1, 4, wlnm);
+//printf("emiss raw %f -> ref raw %f\n",wlraw2, wlraw1);
+
+ /* Adjust the raw correction to account for measuring it in emissive mode */
+ led_off = led_off + wlraw2 - wlraw1;
+
+ /* Hmm. This is rather suspect. The difference between the white reference */
+ /* calibrated wavelength offset and the ambient cap one is about -0.2788 raw. */
+ /* This is not explained by the poly1 vs. poly2 difference at the WL LED peak */
+ /* at 550 nm. (see above), which amounts to about +0.026, leaving 0.2528 */
+ /* unexplained. It appears the CCD wavelength has a dependence on the */
+ /* angle that the light enters the optics ?? */
+
+ led_off += 0.2528; /* Hack to make ambient cap correction == white tile correction */
+
+ a1logd(p->log,3,"Adjusted raw correction by %f to account for measurement using ambient cap\n",wlraw2 - wlraw1 + 0.2528);
+ }
+
+ /* Check that the correction is not excessive */
+ off_nm = i1pro_raw2wav_uncal(p, led_off) - i1pro_raw2wav_uncal(p, m->wl_led_ref_off);
+ a1logd(p->log,2, "Final WL offset = %f, correction %f nm\n",led_off, off_nm);
+ if (fabs(off_nm)> m->wl_err_max) {
+ a1logd(p->log,1,"Final WL correction of %f nm is too big\n",off_nm);
+ return I1PRO_WL_ERR2BIG;
+ }
+
+ /* Do a verification plot */
+ /* Plot the measurement against calibrated wavelength, */
+ /* and reference measurement verses reference wavelength */
+
+#ifdef PLOT_DEBUG
+ {
+ double xx[1024];
+ double y1[1024]; /* interpolate ref */
+ double y2[1024]; /* Measurement */
+ int ii;
+
+ /* i = index into measurement */
+ for (ii = 0, i = m->wl_led_ref_off; i < (m->wl_led_ref_off + m->wl_led_count); i++) {
+ double raw;
+ double mwl; /* Measurment wavelength */
+ double rraw; /* Reference raw value */
+ int ix; /* Lagrange base offset */
+ int k;
+ double yv;
+
+ raw = (double)i;
+ raw = raw - led_off + m->wl_led_ref_off;
+ raw = 128.0 - raw; /* Quadratic expects +ve correlation */
+ if (mmax < 2500.0) {
+ for (mwl = m->wlpoly2[4-1], k = 4-2; k >= 0; k--)
+ mwl = mwl * raw + m->wlpoly2[k];
+ } else {
+ for (mwl = m->wlpoly1[4-1], k = 4-2; k >= 0; k--)
+ mwl = mwl * raw + m->wlpoly1[k];
+ }
+ xx[ii] = mwl;
+ y1[ii] = cparm[0] * wlraw[i];
+ y2[ii] = 0.0;
+
+ /* Compute the reference index corresponding to this wavelength */
+ rraw = inv_raw2wav(m->wlpoly1, 4, mwl) - (double)m->wl_led_ref_off;
+
+ /* Use Lagrange to interpolate the reference level for this wavelength */
+ ix = ((int)rraw) - 1; /* Reference index of Lagrange for this xv */
+ if (ix < 0)
+ continue;
+ if ((ix + 3) >= m->wl_led_count)
+ break;
+
+ /* Compute interpolated value of reference using Lagrange: */
+ yv = m->wl_led_spec[ix+0] * (rraw-(ix+1)) * (rraw-(ix+2)) * (rraw-(ix+3))
+ /((0.0-1.0) * (0.0-2.0) * (0.0-3.0))
+ + m->wl_led_spec[ix+1] * (rraw-(ix+0)) * (rraw-(ix+2)) * (rraw-(ix+3))
+ /((1.0-0.0) * (1.0-2.0) * (1.0-3.0))
+ + m->wl_led_spec[ix+2] * (rraw-(ix+0)) * (rraw-(ix+1)) * (rraw-(ix+3))
+ /((2.0-0.0) * (2.0-1.0) * (2.0-3.0))
+ + m->wl_led_spec[ix+3] * (rraw-(ix+0)) * (rraw-(ix+1)) * (rraw-(ix+2))
+ /((3.0-0.0) * (3.0-1.0) * (3.0-2.0));
+ y2[ii] = yv;
+ ii++;
+ }
+ printf("Verification fit in nm:\n");
+ do_plot(xx, y1, y2, NULL, ii);
+ }
+#endif
+
+ if (pled_off != NULL)
+ *pled_off = led_off;
+ }
+
+ return ev;
+}
+
+/* Compute standard res. downsampling filters for the given mode */
+/* given the current wl_led_off, and set them as current. */
+i1pro_code i1pro2_compute_wav_filters(i1pro *p, int refl) {
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ i1pro_code ev = I1PRO_OK;
+ double twidth; /* Target filter width */
+ int six, eix; /* raw starting index and one past end index */
+ int wlix; /* current wavelenght index */
+ double *wlcop; /* This wavelength base filter coefficient pointer */
+ double trh, trx; /* Triangle height and triangle equation x weighting */
+ int i, j, k;
+
+ a1logd(p->log,2,"i1pro2_compute_wav_filters called with correction %f raw\n",s->wl_led_off - m->wl_led_ref_off);
+
+ twidth = (m->wl_long[0] - m->wl_short[0])/(m->nwav[0] - 1.0); /* Filter width */
+
+ trh = 1.0/twidth; /* Triangle height */
+ trx = trh/twidth; /* Triangle equation x weighting */
+
+ /* Allocate separate space for the calibrated versions, so that the */
+ /* original eeprom values are preserved */
+ if (m->mtx_c[0][refl].index == NULL) {
+
+ if ((m->mtx_c[0][refl].index = (int *)calloc(m->nwav[0], sizeof(int))) == NULL) {
+ a1logd(p->log,1,"i1pro: malloc ndex1 failed!\n");
+ return I1PRO_INT_MALLOC;
+ }
+
+ if ((m->mtx_c[0][refl].nocoef = (int *)calloc(m->nwav[0], sizeof(int))) == NULL) {
+ a1logd(p->log,1,"i1pro: malloc nocoef failed!\n");
+ return I1PRO_INT_MALLOC;
+ }
+
+ if ((m->mtx_c[0][refl].coef = (double *)calloc(16 * m->nwav[0], sizeof(double)))
+ == NULL) {
+ a1logd(p->log,1,"i1pro: malloc coef failed!\n");
+ return I1PRO_INT_MALLOC;
+ }
+ }
+
+ /* For each output wavelength */
+ wlcop = m->mtx_c[0][refl].coef;
+ for (wlix = 0; wlix < m->nwav[0]; wlix++) {
+ double owl = wlix/(m->nwav[0]-1.0) * (m->wl_long[0] - m->wl_short[0]) + m->wl_short[0];
+ int lip; /* Lagrange interpolation position */
+
+// printf("Generating filter for %.1f nm width %.1f nm\n",owl, twidth);
+
+ /* The filter is based on a triangle centered at owl and extending */
+ /* from owl - twidth to owl + twidth. We therefore need to locate the */
+ /* raw values that will overlap this range */
+
+ /* Do a dumb search from high to low nm */
+ for (six = 0; six < m->nraw; six++) {
+ if (i1pro_raw2wav(p, refl, (double)six) < (owl + twidth))
+ break;
+ }
+ if (six < 2 || six >= m->nraw) {
+ a1loge(p->log,1,"i1pro: compute_wav_filters() six %d out of raw range to cover output filter %.1f nm width %.1f nm\n",six, owl, twidth);
+ return I1PRO_INT_ASSERT;
+ }
+ eix = six;
+ six -= 2; /* Outside */
+
+ for (; eix < m->nraw; eix++) {
+ if (i1pro_raw2wav(p, refl, (double)eix) <= (owl - twidth))
+ break;
+ }
+ if (eix > (m->nraw - 2) ) {
+ a1loge(p->log,1,"i1pro: compute_wav_filters() eix %d out of raw range to cover output filter %.1f nm width %.1f nm\n",eix, owl, twidth);
+ return I1PRO_INT_ASSERT;
+ }
+ eix += 2;
+
+// for (j = six; j < eix; j++)
+// printf("Using raw %d @ %.1f nm\n",j, i1pro_raw2wav(p, refl, (double)j));
+
+ /* Set start index for this wavelength */
+ m->mtx_c[0][refl].index[wlix] = six;
+
+ /* Set number of filter coefficients */
+ m->mtx_c[0][refl].nocoef[wlix] = eix - six;
+
+ if (m->mtx_c[0][refl].nocoef[wlix] > 16) {
+ a1loge(p->log,1,"i1pro: compute_wav_filters() too many filter %d\n",m->mtx_c[0][refl].nocoef[wlix]);
+ return I1PRO_INT_ASSERT;
+ }
+
+ /* Start with zero filter weightings */
+ for (i = 0; i < m->mtx_c[0][refl].nocoef[wlix]; i++)
+ wlcop[i] = 0.0;
+
+ /* for each Lagrange interpolation position */
+ for (lip = six; (lip + 3) < eix; lip++) {
+ double rwav[4]; /* Relative wavelength of these Lagrange points */
+ double den[4]; /* Denominator values for points */
+ double num[4][4]; /* Numerator polinomial components x^3, x^2, x, 1 */
+ double ilow, ihigh; /* Integration points */
+
+ /* Relative wavelengths to owl of each basis point */
+ for (i = 0; i < 4; i++)
+ rwav[i] = i1pro_raw2wav(p, refl, (double)lip + i) - owl;
+// printf("\n~1 rwav = %f %f %f %f\n", rwav[0], rwav[1], rwav[2], rwav[3]);
+
+ /* Compute each basis points Lagrange denominator values */
+ den[0] = (rwav[0]-rwav[1]) * (rwav[0]-rwav[2]) * (rwav[0]-rwav[3]);
+ den[1] = (rwav[1]-rwav[0]) * (rwav[1]-rwav[2]) * (rwav[1]-rwav[3]);
+ den[2] = (rwav[2]-rwav[0]) * (rwav[2]-rwav[1]) * (rwav[2]-rwav[3]);
+ den[3] = (rwav[3]-rwav[0]) * (rwav[3]-rwav[1]) * (rwav[3]-rwav[2]);
+// printf("~1 denominators = %f %f %f %f\n", den[0], den[1], den[2], den[3]);
+
+ /* Compute each basis points Langrange numerator components. */
+ /* We make the numerator have polinomial form, so that it is easy */
+ /* to compute the integral equation from it. */
+ num[0][0] = 1.0;
+ num[0][1] = -rwav[1] - rwav[2] - rwav[3];
+ num[0][2] = rwav[1] * rwav[2] + rwav[1] * rwav[3] + rwav[2] * rwav[3];
+ num[0][3] = -rwav[1] * rwav[2] * rwav[3];
+ num[1][0] = 1.0;
+ num[1][1] = -rwav[0] - rwav[2] - rwav[3];
+ num[1][2] = rwav[0] * rwav[2] + rwav[0] * rwav[3] + rwav[2] * rwav[3];
+ num[1][3] = -rwav[0] * rwav[2] * rwav[3];
+ num[2][0] = 1.0;
+ num[2][1] = -rwav[0] - rwav[1] - rwav[3];
+ num[2][2] = rwav[0] * rwav[1] + rwav[0] * rwav[3] + rwav[1] * rwav[3];
+ num[2][3] = -rwav[0] * rwav[1] * rwav[3];
+ num[3][0] = 1.0;
+ num[3][1] = -rwav[0] - rwav[1] - rwav[2];
+ num[3][2] = rwav[0] * rwav[1] + rwav[0] * rwav[2] + rwav[1] * rwav[2];
+ num[3][3] = -rwav[0] * rwav[1] * rwav[2];
+
+// printf("~1 num %d = %f %f %f %f\n", 0, num[0][0], num[0][1], num[0][2], num[0][3]);
+// printf("~1 num %d = %f %f %f %f\n", 1, num[1][0], num[1][1], num[1][2], num[1][3]);
+// printf("~1 num %d = %f %f %f %f\n", 2, num[2][0], num[2][1], num[2][2], num[2][3]);
+// printf("~1 num %d = %f %f %f %f\n", 3, num[3][0], num[3][1], num[3][2], num[3][3]);
+
+ /* Now compute the integral difference between the two middle points */
+ /* of the Lagrange over the triangle shape, and accumulate the resulting */
+ /* Lagrange weightings to the filter coefficients. */
+
+ /* For high and then low side of the triangle. */
+ for (k = 0; k < 2; k++) {
+
+ ihigh = rwav[1];
+ ilow = rwav[2];
+
+ if ((k == 0 && ilow <= twidth && ihigh >= 0.0) /* Portion is +ve side */
+ || (k == 1 && ilow <= 0.0 && ihigh >= -twidth)) { /* Portion is -ve side */
+
+ if (k == 0) {
+ if (ilow < 0.0)
+ ilow = 0.0;
+ if (ihigh > twidth)
+ ihigh = twidth;
+// printf("~1 doing +ve triangle between %f %f\n",ilow,ihigh);
+ } else {
+ if (ilow < -twidth)
+ ilow = -twidth;
+ if (ihigh > 0.0)
+ ihigh = 0.0;
+// printf("~1 doing -ve triangle between %f %f\n",ilow,ihigh);
+ }
+
+ /* For each Lagrange point */
+ for (i = 0; i < 4; i++) {
+ double xnum[5]; /* Expanded numerator components */
+ double nvall, nvalh; /* Numerator low and high values */
+
+ /* Because the y value is a function of x, we need to */
+ /* expand the Lagrange 3rd order polinomial into */
+ /* a 4th order polinomial using the triangle edge equation */
+ /* y = trh +- trx * x */
+ for (j = 0; j < 4; j++)
+ xnum[j] = (k == 0 ? -trx : trx) * num[i][j];
+ xnum[j] = 0.0;
+ for (j = 0; j < 4; j++)
+ xnum[j+1] += trh * num[i][j];
+
+ /* The 4th order equation becomes a 5th order one */
+ /* when we convert it to an integral, ie. x^4 becomes x^5/5 etc. */
+ for (j = 0; j < 4; j++)
+ xnum[j] /= (5.0 - (double)j); /* Integral denom. */
+
+ /* Compute ihigh integral as 5th order polynomial */
+ nvalh = xnum[0];
+ nvalh = nvalh * ihigh + xnum[1];
+ nvalh = nvalh * ihigh + xnum[2];
+ nvalh = nvalh * ihigh + xnum[3];
+ nvalh = nvalh * ihigh + xnum[4];
+ nvalh = nvalh * ihigh;
+
+ /* Compute ilow integral as 5th order polynomial */
+ nvall = xnum[0];
+ nvall = nvall * ilow + xnum[1];
+ nvall = nvall * ilow + xnum[2];
+ nvall = nvall * ilow + xnum[3];
+ nvall = nvall * ilow + xnum[4];
+ nvall = nvall * ilow;
+
+ /* Compute ihigh - ilow and add to filter weightings */
+ wlcop[lip -six + i] += (nvalh - nvall)/den[i];
+// printf("~1 k = %d, comp %d weight += %e now %e\n",k,lip-six+i,(nvalh - nvall)/den[i], wlcop[lip-six+i]);
+ }
+ }
+ }
+ }
+// printf("~1 Weightings for for %.1f nm are:\n",owl);
+// for (i = 0; i < m->mtx_c[0][refl].nocoef[wlix]; i++)
+// printf("~1 comp %d weight %e\n",i,wlcop[i]);
+
+ wlcop += m->mtx_c[0][refl].nocoef[wlix]; /* Next group of weightings */
+ }
+#ifdef DEBUG
+ /* Check against orginal filters */
+ {
+ int ix1, ix1c;
+ double aerr = 0.0;
+
+ a1logd(p->log,2,"Checking gemertated tables against EEProm table\n");
+ ix1 = ix1c = 0;
+ for (i = 0; i < m->nwav[0]; i++) {
+ double err;
+ int six, eix;
+
+ if (m->mtx_o.index[i] < m->mtx_o.index[i])
+ six = m->mtx_o.index[i];
+ else
+ six = m->mtx_o.index[i];
+
+ if ((m->mtx_o.index[i] + m->mtx_o.nocoef[i]) > (m->mtx_o.index[i] + m->mtx_o.nocoef[i]))
+ eix = m->mtx_o.index[i] + m->mtx_o.nocoef[i];
+ else
+ eix = m->mtx_o.index[i] + m->mtx_o.nocoef[i];
+// printf("~1 filter %d from %d to %d\n",i,six,eix);
+
+ err = 0.0;
+ for (j = six; j < eix; j++) {
+ double w1, w1c;
+
+ if (j < m->mtx_o.index[i] || j >= (m->mtx_o.index[i] + m->mtx_o.nocoef[i]))
+ w1 = 0.0;
+ else
+ w1 = m->mtx_o.coef[ix1 + j - m->mtx_o.index[i]];
+ if (j < m->mtx_c[0][refl].index[i]
+ || j >= (m->mtx_c[0][refl].index[i] + m->mtx_c[0][refl].nocoef[i]))
+ w1c = 0.0;
+ else
+ w1c = m->mtx_c[0][refl].coef[ix1c + j - m->mtx_c[0][refl].index[i]];
+
+ err += fabs(w1 - w1c);
+// printf("Weight %d, %e should be %e\n", j, w1c, w1);
+ }
+// printf("Filter %d average weighting error = %f\n",i, err/j);
+ aerr += err/j;
+
+ ix1 += m->mtx_o.nocoef[i];
+ ix1c += m->mtx_c[0][refl].nocoef[i];
+ }
+ a1logd(p->log,2,"Overall average filter weighting change = %f\n",aerr/m->nwav[0]);
+ }
+#endif /* DEBUG */
+
+ /* Switch normal res. to use wavelength calibrated version */
+ m->mtx[0][refl] = m->mtx_c[0][refl];
+
+ return ev;
+}
+
+
+/* =============================================== */
+#ifdef HIGH_RES
+
+#undef ANALIZE_EXISTING /* Analize the manufacturers existing filter shape */
+
+/* High res congiguration */
+/* Pick one of these: */
+#define USE_LANCZOS2 /* [def] Use lanczos2 filter shape */
+#undef USE_DECONV /* [und] Use deconvolution curve */
+#undef USE_GAUSSIAN /* [und] Use gaussian filter shape*/
+#undef USE_BLACKMAN /* [und] Use Blackman windowed sinc shape */
+#undef USE_CUBIC /* [und] Use cubic spline filter */
+
+#undef COMPUTE_DISPERSION /* Compute slit & optics dispersion from red laser data */
+
+#ifdef NEVER
+/* Plot the matrix coefficients */
+static void i1pro_debug_plot_mtx_coef(i1pro *p) {
+ i1proimp *m = (i1proimp *)p->m;
+ int i, j, k, cx, sx;
+ double *xx, *ss;
+ double **yy;
+
+ xx = dvectorz(-1, m->nraw-1); /* X index */
+ yy = dmatrixz(0, 5, -1, m->nraw-1); /* Curves distributed amongst 5 graphs */
+
+ for (i = 0; i < m->nraw; i++)
+ xx[i] = i;
+
+ /* For each output wavelength */
+ for (cx = j = 0; j < m->nwav; j++) {
+ i = j % 5;
+
+// printf("Out wave = %d\n",j);
+ /* For each matrix value */
+ sx = m->mtx_index[j]; /* Starting index */
+// printf("start index = %d, nocoef %d\n",sx,m->mtx_nocoef[j]);
+ for (k = 0; k < m->mtx_nocoef[j]; k++, cx++, sx++) {
+// printf("offset %d, coef ix %d val %f from ccd %d\n",k, cx, m->mtx_coef[cx], sx);
+ yy[5][sx] += 0.5 * m->mtx_coef[cx];
+ yy[i][sx] = m->mtx_coef[cx];
+ }
+ }
+
+ do_plot6(xx, yy[0], yy[1], yy[2], yy[3], yy[4], yy[5], m->nraw);
+ free_dvector(xx, -1, m->nraw-1);
+ free_dmatrix(yy, 0, 2, -1, m->nraw-1);
+}
+#endif
+
+#ifdef COMPUTE_DISPERSION
+
+/* Gausian filter implementation */
+/* parameters are amplidude [0], center wavelength [1], std. dev. [2] */
+static double gaussf(double tp[], double x) {
+ double y;
+
+ x = (x - tp[1])/(sqrt(2.0) * tp[2]);
+ y = tp[0] * exp(-(x * x));
+
+ return y;
+}
+
+/* Gausian integral implementatation */
+/* parameters are amplidude [0], center wavelength [1], std. dev. [2] */
+/* return an aproximation to the intergral between w1 and w2 */
+static double gaussint(double tp[], double w1, double w2) {
+ int j, nn;
+ double lw, ll, vv;
+
+ /* Intergate in 0.1 nm increments */
+ nn = (int)(fabs(w2 - w1)/0.1 + 0.5);
+
+ lw = w1;
+ ll = gaussf(tp, lw);
+ vv = 0.0;
+ for (j = 0; j < nn; j++) {
+ double cw, cl;
+ cw = w1 + (j+1)/(nn +1.0) * (w2 - w1);
+ cl = gaussf(tp, cw);
+ vv += 0.5 * (cl + ll) * (lw - cw);
+ ll = cl;
+ lw = cw;
+ }
+ return fabs(vv);
+}
+
+/* Powell minimisation context */
+typedef struct {
+ double nsp; /* Number of samples of dispersion data */
+ double *llv; /* [nsamp] laser values */
+ double *lwl; /* [nsamp+1] CCD boundary wavelegths */
+} hropt_cx;
+
+/* Powell minimisation callback function */
+/* to match dispersion data */
+static double hropt_opt1(void *vcx, double tp[]) {
+ hropt_cx *cx = (hropt_cx *)vcx;
+ double rv = 0.0;
+ int i, j;
+
+ /* For each CCD sample */
+ for (i = 0; i < cx->nsp; i++) {
+ double vv;
+
+ /* Actual CCD integrated value */
+ vv = cx->llv[i] * (cx->lwl[i] - cx->lwl[i+1]);
+ /* Computed intergral with current curve */
+ vv -= gaussint(tp, cx->lwl[i], cx->lwl[i+1]);
+ rv += vv * vv;
+ }
+// printf("~1 params %f %f %f, rv = %f\n", tp[0],tp[1],tp[2],rv);
+ return rv;
+}
+
+#endif /* COMPUTE_DISPERSION */
+
+/* Filter shape point */
+typedef struct {
+ double wl, we;
+} i1pro_fs;
+
+/* Filter cooeficient values */
+typedef struct {
+ int ix; /* Raw index */
+ double we; /* Weighting */
+} i1pro_fc;
+
+/* Wavelenth calibration crossover point information */
+typedef struct {
+ double wav; /* Wavelegth of point */
+ double raw; /* Raw index of point */
+ double wei; /* Weigting of the point */
+} i1pro_xp;
+
+/* Linearly interpolate the filter shape */
+static double lin_fshape(i1pro_fs *fsh, int n, double x) {
+ int i;
+ double y;
+
+ if (x <= fsh[0].wl)
+ return fsh[0].we;
+ else if (x >= fsh[n-1].wl)
+ return fsh[n-1].we;
+
+ for (i = 0; i < (n-2); i++)
+ if (x >= fsh[i].wl && x <= fsh[i+1].wl)
+ break;
+
+ x = (x - fsh[i].wl)/(fsh[i+1].wl - fsh[i].wl);
+ y = fsh[i].we + (fsh[i+1].we - fsh[i].we) * x;
+
+ return y;
+}
+
+/* Generate a sample from a lanczos2 filter shape */
+/* wi is the width of the filter */
+static double lanczos2(double wi, double x) {
+ double y;
+
+#ifdef USE_DECONV
+ /* For 3.333, created by i1deconv.c */
+ static i1pro_fs fshape[49] = {
+ { -7.200000, 0.0 },
+ { -6.900000, 0.013546 },
+ { -6.600000, 0.035563 },
+ { -6.300000, 0.070500 },
+ { -6.000000, 0.106543 },
+ { -5.700000, 0.148088 },
+ { -5.400000, 0.180888 },
+ { -5.100000, 0.186637 },
+ { -4.800000, 0.141795 },
+ { -4.500000, 0.046101 },
+ { -4.200000, -0.089335 },
+ { -3.900000, -0.244652 },
+ { -3.600000, -0.391910 },
+ { -3.300000, -0.510480 },
+ { -3.000000, -0.573177 },
+ { -2.700000, -0.569256 },
+ { -2.400000, -0.489404 },
+ { -2.100000, -0.333957 },
+ { -1.800000, -0.116832 },
+ { -1.500000, 0.142177 },
+ { -1.200000, 0.411639 },
+ { -0.900000, 0.658382 },
+ { -0.600000, 0.851521 },
+ { -0.300000, 0.967139 },
+ { 0.000000, 1.000000 },
+ { 0.300000, 0.967139 },
+ { 0.600000, 0.851521 },
+ { 0.900000, 0.658382 },
+ { 1.200000, 0.411639 },
+ { 1.500000, 0.142177 },
+ { 1.800000, -0.116832 },
+ { 2.100000, -0.333957 },
+ { 2.400000, -0.489404 },
+ { 2.700000, -0.569256 },
+ { 3.000000, -0.573177 },
+ { 3.300000, -0.510480 },
+ { 3.600000, -0.391910 },
+ { 3.900000, -0.244652 },
+ { 4.200000, -0.089335 },
+ { 4.500000, 0.046101 },
+ { 4.800000, 0.141795 },
+ { 5.100000, 0.186637 },
+ { 5.400000, 0.180888 },
+ { 5.700000, 0.148088 },
+ { 6.000000, 0.106543 },
+ { 6.300000, 0.070500 },
+ { 6.600000, 0.035563 },
+ { 6.900000, 0.013546 },
+ { 7.200000, 0.0 }
+ };
+
+ return lin_fshape(fshape, 49, x);
+#endif
+
+#ifdef USE_GAUSSIAN
+ /* gausian */
+ wi = wi/(2.0 * sqrt(2.0 * log(2.0))); /* Convert width at half max to std. dev. */
+ x = x/(sqrt(2.0) * wi);
+// y = 1.0/(wi * sqrt(2.0 * DBL_PI)) * exp(-(x * x)); /* Unity area */
+ y = exp(-(x * x)); /* Center at 1.0 */
+#endif
+
+#ifdef USE_LANCZOS2
+ /* lanczos2 */
+ x = fabs(1.0 * x/wi);
+ if (x >= 2.0)
+ return 0.0;
+ if (x < 1e-5)
+ return 1.0;
+ y = sin(DBL_PI * x)/(DBL_PI * x) * sin(DBL_PI * x/2.0)/(DBL_PI * x/2.0);
+#endif
+
+#ifdef USE_BLACKMAN /* Use Blackman windowed sinc shape */
+ double xx = x, w;
+ double a0, a1, a2, a3;
+ double bb, cc;
+
+ xx = fabs(1.0 * x/wi);
+ if (xx >= 2.0)
+ return 0.0;
+ if (xx < 1e-5)
+ return 1.0;
+ y = sin(DBL_PI * xx)/(DBL_PI * xx); /* sinc */
+
+ /* gausian window */
+// wi *= 1.5;
+// wi = wi/(2.0 * sqrt(2.0 * log(2.0))); /* Convert width at half max to std. dev. */
+// x = x/(sqrt(2.0) * wi);
+// w = exp(-(x * x));
+
+ xx = (xx/4.0 + 0.5); /* Convert to standard window cos() range */
+
+ /* Hamming window */
+// a0 = 0.54; a1 = 0.46;
+// w = a0 - a1 * cos(2.0 * DBL_PI * xx);
+
+ /* Blackman window */
+ a0 = 7938.0/18608.0; a1 = 9240.0/18608.0; a2 = 1430.0/18608.0;
+ w = a0 - a1 * cos(2.0 * DBL_PI * xx) + a2 * cos(4.0 * DBL_PI * xx);
+
+ /* Nuttall window */
+// a0 = 0.355768; a1=0.487396; a2=0.144232; a3=0.012604;
+// w = a0 - a1 * cos(2.0 * DBL_PI * xx) + a2 * cos(4.0 * DBL_PI * xx) - a3 * cos(6.0 * DBL_PI * xx);
+
+ /* Blackman Harris window */
+// a0=0.35875; a1=0.48829; a2=0.14128; a3=0.01168;
+// w = a0 - a1 * cos(2.0 * DBL_PI * xx) + a2 * cos(4.0 * DBL_PI * xx) - a3 * cos(6.0 * DBL_PI * xx);
+
+ /* Blackman Nuttall window */
+// a0=0.3635819; a1=0.4891775; a2=0.1365995; a3=0.0106411;
+// w = a0 - a1 * cos(2.0 * DBL_PI * xx) + a2 * cos(4.0 * DBL_PI * xx) - a3 * cos(6.0 * DBL_PI * xx);
+
+ y *= w;
+#endif
+#ifdef USE_CUBIC /* Use cubic sline */
+ double xx = x;
+ double bb, cc;
+
+ xx = fabs(1.0 * x/wi);
+
+// bb = cc = 1.0/3.0; /* Mitchell */
+ bb = 0.5;
+ cc = 0.5;
+ xx *= 1.2;
+
+ if (xx < 1.0) {
+ y = ( 12.0 - 9.0 * bb - 6.0 * cc) * xx * xx * xx
+ + (-18.0 + 12.0 * bb + 6.0 * cc) * xx * xx
+ + ( 6.0 - 2.0 * bb);
+ y /= (6.0 - 2.0 * bb);
+ } else if (xx < 2.0) {
+ y = ( -1.0 * bb - 6.0 * cc) * xx * xx * xx
+ + ( 6.0 * bb + 30.0 * cc) * xx * xx
+ + (-12.0 * bb - 48.0 * cc) * xx
+ + ( 8.0 * bb + 24.0 * cc);
+ y /= (6.0 - 2.0 * bb);
+ } else {
+ y = 0.0;
+ }
+#endif
+ return y;
+}
+
+#if defined(__APPLE__) && defined(__POWERPC__)
+
+/* Workaround for a ppc gcc 3.3 optimiser bug... */
+static int gcc_bug_fix(int i) {
+ static int nn;
+ nn += i;
+ return nn;
+}
+#endif /* APPLE */
+
+/* Create or re-create high resolution mode references */
+i1pro_code i1pro_create_hr(i1pro *p) {
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_code ev = I1PRO_OK;
+ int refl;
+ int i, j, k, cx, sx;
+
+ /* If we don't have any way of converting raw2wav (ie. RevE polinomial equations), */
+ /* use the orginal filters to figure this out. */
+ if (p->itype != instI1Pro2 && m->raw2wav == NULL) {
+ i1pro_fc coeff[100][16]; /* Existing filter cooefficients */
+ i1pro_xp xp[101]; /* Crossover points each side of filter */
+ i1pro_fs fshape[100 * 16]; /* Existing filter shape */
+ int ncp = 0; /* Number of shape points */
+
+ /* Convert the native filter cooeficient representation to */
+ /* a 2D array we can randomly index. */
+ for (cx = j = 0; j < m->nwav[0]; j++) { /* For each output wavelength */
+ if (j >= 100) { /* Assert */
+ a1loge(p->log,1,"i1pro: number of output wavelenths is > 100\n");
+ return I1PRO_INT_ASSERT;
+ }
+
+ /* For each matrix value */
+ sx = m->mtx_o.index[j]; /* Starting index */
+ for (k = 0; k < m->mtx_o.nocoef[j]; k++, cx++, sx++) {
+ if (k >= 16) { /* Assert */
+ a1loge(p->log,1,"i1pro: number of filter coeefs is > 16\n");
+ return I1PRO_INT_ASSERT;
+ }
+
+ coeff[j][k].ix = sx;
+ coeff[j][k].we = m->mtx_o.coef[cx];
+// printf("Output %d, filter %d weight = %e\n",j,k,coeff[j][k].we);
+ }
+ }
+
+#ifdef HIGH_RES_PLOT
+ /* Plot original re-sampling curves */
+ {
+ double *xx, *ss;
+ double **yy;
+
+ xx = dvectorz(-1, m->nraw-1); /* X index */
+ yy = dmatrixz(0, 5, -1, m->nraw-1); /* Curves distributed amongst 5 graphs */
+
+ for (i = 0; i < m->nraw; i++)
+ xx[i] = i;
+
+ /* For each output wavelength */
+ for (j = 0; j < m->nwav[0]; j++) {
+ i = j % 5;
+
+ /* For each matrix value */
+ for (k = 0; k < m->mtx_o.nocoef[j]; k++) {
+ yy[5][coeff[j][k].ix] += 0.5 * coeff[j][k].we;
+ yy[i][coeff[j][k].ix] = coeff[j][k].we;
+ }
+ }
+
+ printf("Original wavelength sampling curves:\n");
+ do_plot6(xx, yy[0], yy[1], yy[2], yy[3], yy[4], yy[5], m->nraw);
+ free_dvector(xx, -1, m->nraw-1);
+ free_dmatrix(yy, 0, 2, -1, m->nraw-1);
+ }
+#endif /* HIGH_RES_PLOT */
+
+ /* Compute the crossover points between each filter */
+ for (i = 0; i < (m->nwav[0]-1); i++) {
+ double den, y1, y2, y3, y4, yn, xn; /* Location of intersection */
+
+ /* between filter i and i+1, we want to find the two */
+ /* raw indexes where the weighting values cross over */
+ /* Do a brute force search to avoid making assumptions */
+ /* about the raw order */
+ for (j = 0; j < (m->mtx_o.nocoef[i]-1); j++) {
+ for (k = 0; k < (m->mtx_o.nocoef[i+1]-1); k++) {
+// printf("~1 checking %d, %d: %d = %d, %d = %d\n",j,k, coeff[i][j].ix, coeff[i+1][k].ix, coeff[i][j+1].ix, coeff[i+1][k+1].ix);
+ if (coeff[i][j].ix == coeff[i+1][k].ix
+ && coeff[i][j+1].ix == coeff[i+1][k+1].ix
+ && coeff[i][j].we > 0.0 && coeff[i][j+1].we > 0.0
+ && coeff[i][k].we > 0.0 && coeff[i][k+1].we > 0.0
+ && (( coeff[i][j].we >= coeff[i+1][k].we
+ && coeff[i][j+1].we <= coeff[i+1][k+1].we)
+ || ( coeff[i][j].we <= coeff[i+1][k].we
+ && coeff[i][j+1].we >= coeff[i+1][k+1].we))) {
+// printf("~1 got it at %d, %d: %d = %d, %d = %d\n",j,k, coeff[i][j].ix, coeff[i+1][k].ix, coeff[i][j+1].ix, coeff[i+1][k+1].ix);
+ goto gotit;
+ }
+ }
+ }
+ gotit:;
+ if (j >= m->mtx_o.nocoef[i]) { /* Assert */
+ a1loge(p->log,1,"i1pro: failed to locate crossover between resampling filters\n");
+ return I1PRO_INT_ASSERT;
+ }
+// printf("~1 %d: overlap at %d, %d: %f : %f, %f : %f\n",i, j,k, coeff[i][j].we, coeff[i+1][k].we, coeff[i][j+1].we, coeff[i+1][k+1].we);
+
+ /* Compute the intersection of the two line segments */
+ y1 = coeff[i][j].we;
+ y2 = coeff[i][j+1].we;
+ y3 = coeff[i+1][k].we;
+ y4 = coeff[i+1][k+1].we;
+ den = -y4 + y3 + y2 - y1;
+ yn = (y2 * y3 - y1 * y4)/den;
+ xn = (y3 - y1)/den;
+// printf("~1 den = %f, yn = %f, xn = %f\n",den,yn,xn);
+ xp[i+1].wav = XSPECT_WL(m->wl_short[0], m->wl_long[0], m->nwav[0], i + 0.5);
+ xp[i+1].raw = (1.0 - xn) * coeff[i][j].ix + xn * coeff[i][j+1].ix;
+ xp[i+1].wei = yn;
+// printf("Intersection %d: wav %f, raw %f, wei %f\n",i+1,xp[i+1].wav,xp[i+1].raw,xp[i+1].wei);
+// printf("\n");
+ }
+
+ /* Add the two points for the end filters */
+ {
+ double x5, x6, y5, y6; /* Points on intesecting line */
+ double den, y1, y2, y3, y4, yn, xn; /* Location of intersection */
+
+ x5 = xp[1].raw;
+ y5 = xp[1].wei;
+ x6 = xp[2].raw;
+ y6 = xp[2].wei;
+
+ /* Search for possible intersection point with first curve */
+ /* Create equation for line from next two intersection points */
+ for (j = 0; j < (m->mtx_o.nocoef[0]-1); j++) {
+ /* Extrapolate line to this segment */
+ y3 = y5 + (coeff[0][j].ix - x5)/(x6 - x5) * (y6 - y5);
+ y4 = y5 + (coeff[0][j+1].ix - x5)/(x6 - x5) * (y6 - y5);
+ /* This segment of curve */
+ y1 = coeff[0][j].we;
+ y2 = coeff[0][j+1].we;
+ if ( (( y1 >= y3 && y2 <= y4) /* Segments overlap */
+ || ( y1 <= y3 && y2 >= y4))
+ && (( coeff[0][j].ix < x5 && coeff[0][j].ix < x6
+ && coeff[0][j+1].ix < x5 && coeff[0][j+1].ix < x6)
+ || ( coeff[0][j+1].ix > x5 && coeff[0][j+1].ix > x6
+ && coeff[0][j].ix > x5 && coeff[0][j].ix > x6))) {
+ break;
+ }
+ }
+ if (j >= m->mtx_o.nocoef[0]) { /* Assert */
+ a1loge(p->log,1,"i1pro: failed to end crossover\n");
+ return I1PRO_INT_ASSERT;
+ }
+
+ den = -y4 + y3 + y2 - y1;
+ yn = (y2 * y3 - y1 * y4)/den;
+ xn = (y3 - y1)/den;
+// printf("~1 den = %f, yn = %f, xn = %f\n",den,yn,xn);
+ xp[0].wav = XSPECT_WL(m->wl_short[0], m->wl_long[0], m->nwav[0], -0.5);
+ xp[0].raw = (1.0 - xn) * coeff[0][j].ix + xn * coeff[0][j+1].ix;
+ xp[0].wei = yn;
+// printf("End 0 intersection %d: wav %f, raw %f, wei %f\n",0,xp[0].wav,xp[0].raw,xp[0].wei);
+// printf("\n");
+
+ x5 = xp[m->nwav[0]-2].raw;
+ y5 = xp[m->nwav[0]-2].wei;
+ x6 = xp[m->nwav[0]-1].raw;
+ y6 = xp[m->nwav[0]-1].wei;
+
+// printf("~1 x5 %f, y5 %f, x6 %f, y6 %f\n",x5,y5,x6,y6);
+ /* Search for possible intersection point with first curve */
+ /* Create equation for line from next two intersection points */
+ for (j = 0; j < (m->mtx_o.nocoef[0]-1); j++) {
+ /* Extrapolate line to this segment */
+ y3 = y5 + (coeff[m->nwav[0]-1][j].ix - x5)/(x6 - x5) * (y6 - y5);
+ y4 = y5 + (coeff[m->nwav[0]-1][j+1].ix - x5)/(x6 - x5) * (y6 - y5);
+ /* This segment of curve */
+ y1 = coeff[m->nwav[0]-1][j].we;
+ y2 = coeff[m->nwav[0]-1][j+1].we;
+ if ( (( y1 >= y3 && y2 <= y4) /* Segments overlap */
+ || ( y1 <= y3 && y2 >= y4))
+ && (( coeff[m->nwav[0]-1][j].ix < x5 && coeff[m->nwav[0]-1][j].ix < x6
+ && coeff[m->nwav[0]-1][j+1].ix < x5 && coeff[m->nwav[0]-1][j+1].ix < x6)
+ || ( coeff[m->nwav[0]-1][j+1].ix > x5 && coeff[m->nwav[0]-1][j+1].ix > x6
+ && coeff[m->nwav[0]-1][j].ix > x5 && coeff[m->nwav[0]-1][j].ix > x6))) {
+ break;
+ }
+ }
+ if (j >= m->mtx_o.nocoef[m->nwav[0]-1]) { /* Assert */
+ a1loge(p->log,1,"i1pro: failed to end crossover\n");
+ return I1PRO_INT_ASSERT;
+ }
+ den = -y4 + y3 + y2 - y1;
+ yn = (y2 * y3 - y1 * y4)/den;
+ xn = (y3 - y1)/den;
+// printf("~1 den = %f, yn = %f, xn = %f\n",den,yn,xn);
+ xp[m->nwav[0]].wav = XSPECT_WL(m->wl_short[0], m->wl_long[0], m->nwav[0], m->nwav[0]-0.5);
+ xp[m->nwav[0]].raw = (1.0 - xn) * coeff[m->nwav[0]-1][j].ix + xn * coeff[m->nwav[0]-1][j+1].ix;
+ xp[m->nwav[0]].wei = yn;
+// printf("End 36 intersection %d: wav %f, raw %f, wei %f\n",m->nwav[0]+1,xp[m->nwav[0]].wav,xp[m->nwav[0]].raw,xp[m->nwav[0]].wei);
+// printf("\n");
+ }
+
+#ifdef HIGH_RES_DEBUG
+ /* Check to see if the area of each filter curve is the same */
+ /* (yep, width times 2 * xover height is close to 1.0, and the */
+ /* sum of the weightings is exactly 1.0) */
+ for (i = 0; i < m->nwav[0]; i++) {
+ double area1, area2;
+ area1 = fabs(xp[i].raw - xp[i+1].raw) * (xp[i].wei + xp[i+1].wei);
+
+ area2 = 0.0;
+ for (j = 0; j < (m->mtx_o.nocoef[i]); j++)
+ area2 += coeff[i][j].we;
+
+ printf("Area of curve %d = %f, %f\n",i,area1, area2);
+ }
+#endif /* HIGH_RES_DEBUG */
+
+ /* From our crossover data, create a rspl that maps raw CCD index */
+ /* value into wavelegth. */
+ {
+ co sd[101]; /* Scattered data points */
+ datai glow, ghigh;
+ datao vlow, vhigh;
+ int gres[1];
+ double avgdev[1];
+
+ if ((m->raw2wav = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) {
+ a1logd(p->log,1,"i1pro: creating rspl for high res conversion failed\n");
+ return I1PRO_INT_NEW_RSPL_FAILED;
+ }
+
+ vlow[0] = 1e6;
+ vhigh[0] = -1e6;
+ for (i = 0; i < (m->nwav[0]+1); i++) {
+ sd[i].p[0] = xp[i].raw;
+ sd[i].v[0] = xp[i].wav;
+
+ if (sd[i].v[0] < vlow[0])
+ vlow[0] = sd[i].v[0];
+ if (sd[i].v[0] > vhigh[0])
+ vhigh[0] = sd[i].v[0];
+ }
+ glow[0] = 0.0;
+ ghigh[0] = 127.0;
+ gres[0] = 128;
+ avgdev[0] = 0.0;
+
+ m->raw2wav->fit_rspl(m->raw2wav, 0, sd, m->nwav[0]+1, glow, ghigh, gres, vlow, vhigh, 0.5, avgdev, NULL);
+
+#ifdef HIGH_RES_PLOT
+ /* Plot raw to wav lookup */
+ {
+ double *xx, *yy, *y2;
+
+ xx = dvector(0, m->nwav[0]+1); /* X index = raw bin */
+ yy = dvector(0, m->nwav[0]+1); /* Y = nm */
+ y2 = dvector(0, m->nwav[0]+1); /* Y = nm */
+
+ for (i = 0; i < (m->nwav[0]+1); i++) {
+ co pp;
+ double iv, v1, v2;
+ xx[i] = xp[i].raw;
+ yy[i] = xp[i].wav;
+
+ pp.p[0] = xp[i].raw;
+ m->raw2wav->interp(m->raw2wav, &pp);
+ y2[i] = pp.v[0];
+ }
+
+ printf("CCD bin to wavelength mapping of original filters + rspl:\n");
+ do_plot6(xx, yy, y2, NULL, NULL, NULL, NULL, m->nwav+1);
+ free_dvector(xx, 0, m->nwav[0]+1);
+ free_dvector(yy, 0, m->nwav[0]+1);
+ free_dvector(y2, 0, m->nwav[0]+1);
+ }
+#endif /* HIGH_RES_PLOT */
+ }
+ }
+
+#ifdef ANALIZE_EXISTING
+ /* Convert each weighting curves values into normalized values and */
+ /* accumulate into a single curve. */
+ if (!m->hr_inited) {
+ for (i = 0; i < m->nwav[0]; i++) {
+ double cwl; /* center wavelegth */
+ double weight = 0.0;
+
+ for (j = 0; j < (m->mtx_o.nocoef[i]); j++) {
+ double w1, w2, cellw;
+
+ /* Translate CCD cell boundaries index to wavelength */
+ w1 = i1pro_raw2wav_uncal(p, (double)coeff[i][j].ix - 0.5);
+
+ w2 = i1pro_raw2wav_uncal(p, (double)coeff[i][j].ix + 0.5);
+
+ cellw = fabs(w2 - w1);
+
+ cwl = XSPECT_WL(m->wl_short[0], m->wl_long[0], m->nwav[0], i);
+
+ /* Translate CCD index to wavelength */
+ fshape[ncp].wl = i1pro_raw2wav_uncal(p, (double)coeff[i][j].ix) - cwl;
+ fshape[ncp].we = coeff[i][j].we / (0.09 * cellw);
+ ncp++;
+ }
+ }
+
+ /* Now sort by wavelength */
+#define HEAP_COMPARE(A,B) (A.wl < B.wl)
+ HEAPSORT(i1pro_fs, fshape, ncp)
+#undef HEAP_COMPARE
+
+ /* Strip out leading zero's */
+ for (i = 0; i < ncp; i++) {
+ if (fshape[i].we != 0.0)
+ break;
+ }
+ if (i > 1 && i < ncp) {
+ memmove(&fshape[0], &fshape[i-1], sizeof(i1pro_fs) * (ncp - i + 1));
+ ncp = ncp - i + 1;
+ for (i = 0; i < ncp; i++) {
+ if (fshape[i].we != 0.0)
+ break;
+ }
+ }
+
+#ifdef HIGH_RES_PLOT
+ /* Plot the shape of the accumulated curve */
+ {
+ double *x1 = dvectorz(0, ncp-1);
+ double *y1 = dvectorz(0, ncp-1);
+
+ for (i = 0; i < ncp; i++) {
+ double x;
+ x1[i] = fshape[i].wl;
+ y1[i] = fshape[i].we;
+ }
+ printf("Accumulated curve:\n");
+ do_plot(x1, y1, NULL, NULL, ncp);
+
+ free_dvector(x1, 0, ncp-1);
+ free_dvector(y1, 0, ncp-1);
+ }
+#endif /* HIGH_RES_PLOT */
+
+#ifdef HIGH_RES_DEBUG
+ /* Check that the orginal filter sums to a constant */
+ {
+ double x, sum;
+
+ for (x = 0.0; x < 10.0; x += 0.2) {
+ sum = 0;
+ sum += lin_fshape(fshape, ncp, x - 30.0);
+ sum += lin_fshape(fshape, ncp, x - 20.0);
+ sum += lin_fshape(fshape, ncp, x - 10.0);
+ sum += lin_fshape(fshape, ncp, x - 0.0);
+ sum += lin_fshape(fshape, ncp, x + 10.0);
+ sum += lin_fshape(fshape, ncp, x + 20.0);
+ printf("Offset %f, sum %f\n",x, sum);
+ }
+ }
+#endif /* HIGH_RES_DEBUG */
+ }
+#endif /* ANALIZE_EXISTING */
+
+#ifdef COMPUTE_DISPERSION
+ if (!m->hr_inited) {
+ /* Fit our red laser CCD data to a slit & optics Gaussian dispersion model */
+ {
+ double spf[3]; /* Spread function parameters */
+
+ /* Measured CCD values of red laser from CCD indexes 29 to 48 inclusive */
+ /* (It would be nice to have similar data from a monochromic source */
+ /* at other wavelegths such as green and blue!) */
+ double llv[20] = {
+ 53.23,
+ 81.3,
+ 116.15,
+ 176.16,
+ 305.87,
+ 613.71,
+ 8500.52,
+ 64052.0,
+ 103134.13,
+ 89154.03,
+ 21742.89,
+ 1158.86,
+ 591.44,
+ 369.75,
+ 241.01,
+ 166.48,
+ 126.79,
+ 97.76,
+ 63.88,
+ 46.46
+ };
+ double lwl[21]; /* Wavelegth of boundary between CCD cells */
+ double ccd;
+ hropt_cx cx;
+ double ss[3];
+
+ /* Add CCD boundary wavelengths to dispersion data */
+ for (ccd = 29.0 - 0.5, i = 0; i < 21; i++, ccd += 1.0) {
+ /* Translate CCD cell boundaries index to wavelength */
+ lwl[i] = i1pro_raw2wav_uncal(p, ccd);
+ }
+
+ /* Fit a gausian to it */
+ cx.nsp = 20;
+ cx.llv = llv;
+ cx.lwl = lwl;
+
+ /* parameters are amplidude [0], center wavelength [1], std. dev. [2] */
+ spf[0] = 115248.0;
+ spf[1] = 653.78;
+ spf[2] = 3.480308;
+ ss[0] = 500.0;
+ ss[1] = 0.5;
+ ss[2] = 0.5;
+
+ if (powell(NULL, 3, spf, ss, 1e-5, 2000, hropt_opt1, &cx))
+ a1logw(p->log,"hropt_opt1 failed\n");
+
+#ifdef HIGH_RES_PLOT
+ /* Plot dispersion spectra */
+ {
+ double xx[200];
+ double y1[200];
+ double y2[200];
+ double w1, w2;
+
+ w1 = lwl[0] + 5.0;
+ w2 = lwl[20] - 5.0;
+ for (i = 0; i < 200; i++) {
+ double wl;
+ wl = w1 + (i/199.0) * (w2-w1);
+ xx[i] = wl;
+ for (j = 0; j < 20; j++) {
+ if (lwl[j] >= wl && wl >= lwl[j+1])
+ break;
+ }
+ if (j < 20)
+ y1[i] = llv[j];
+ else
+ y1[i] = 0.0;
+ y2[i] = gaussf(spf, wl);
+ }
+ printf("Gauss Parameters %f %f %f\n",spf[0],spf[1],spf[2]);
+ printf("Red laser dispersion data:\n");
+ do_plot(xx, y1, y2, NULL, 200);
+ }
+#endif /* HIGH_RES_PLOT */
+
+ /* Normalize the gausian to have an area of 1 */
+ spf[0] *= 1.0/(spf[0] * spf[2] * sqrt(2.0 * DBL_PI));
+
+// printf("~1 Normalized intergral = %f\n",gaussint(spf, spf[1] - 30.0, spf[1] + 30.0));
+// printf("~1 Half width = %f\n",2.0 * sqrt(2.0 * log(2.0)) * spf[2]);
+ }
+ }
+#endif /* COMPUTE_DISPERSION */
+
+ /* Compute the upsampled calibration references */
+ if (!m->hr_inited) {
+ rspl *trspl; /* Upsample rspl */
+ cow sd[40 * 40]; /* Scattered data points of existing references */
+ datai glow, ghigh;
+ datao vlow, vhigh;
+ int gres[2];
+ double avgdev[2];
+ int ii;
+ co pp;
+
+ if ((trspl = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) {
+ a1logd(p->log,1,"i1pro: creating rspl for high res conversion failed\n");
+ return I1PRO_INT_NEW_RSPL_FAILED;
+ }
+
+ for (ii = 0; ii < 3; ii++) {
+ double **ref2, *ref1;
+ double smooth = 1.0;
+
+ if (ii == 0) {
+ ref1 = m->white_ref[0];
+ ref2 = &m->white_ref[1];
+ smooth = 0.5;
+ } else if (ii == 1) {
+ ref1 = m->emis_coef[0];
+ ref2 = &m->emis_coef[1];
+ smooth = 500.0; /* Hmm. Lagrange may work better ?? */
+ } else {
+ if (m->amb_coef[0] == NULL)
+ break;
+ ref1 = m->amb_coef[0];
+ ref2 = &m->amb_coef[1];
+ smooth = 0.2;
+ }
+
+ if (ref1 == NULL)
+ continue; /* The instI1Monitor doesn't have a reflective cal */
+
+ vlow[0] = 1e6;
+ vhigh[0] = -1e6;
+ for (i = 0; i < m->nwav[0]; i++) {
+
+ sd[i].p[0] = XSPECT_WL(m->wl_short[0], m->wl_long[0], m->nwav[0], i);
+ sd[i].v[0] = ref1[i];
+ sd[i].w = 1.0;
+
+ if (sd[i].v[0] < vlow[0])
+ vlow[0] = sd[i].v[0];
+ if (sd[i].v[0] > vhigh[0])
+ vhigh[0] = sd[i].v[0];
+ }
+
+ if (ii == 1) { /* fudge factors */
+
+#ifdef NEVER /* Doesn't help */
+ /* Increase boost at short wavelegths */
+ if (m->wl_short[1] < 380.0) {
+ sd[i].p[0] = m->wl_short[1];
+ sd[i].v[0] = sd[0].v[0] * (1.0 + (380.0 - m->wl_short[1])/20.0);
+ sd[i++].w = 0.6;
+ }
+#endif
+
+#ifdef NEVER /* Doesn't help */
+ /* Reduces boost at long wavelegths */
+ if (m->wl_long[1] > 730.0) {
+ sd[i].p[0] = m->wl_long[1];
+ sd[i].v[0] = sd[m->nwav[0]-1].v[0] * (1.0 + (m->wl_long[1] - 730.0)/100.0);
+ sd[i++].w = 0.3;
+ }
+#endif
+ }
+
+ glow[0] = m->wl_short[1];
+ ghigh[0] = m->wl_long[1];
+ gres[0] = m->nwav[1];
+ avgdev[0] = 0.0;
+
+ trspl->fit_rspl_w(trspl, 0, sd, i, glow, ghigh, gres, vlow, vhigh, smooth, avgdev, NULL);
+
+ if ((*ref2 = (double *)calloc(m->nwav[1], sizeof(double))) == NULL) {
+ trspl->del(trspl);
+ a1logw(p->log, "i1pro: malloc ref2 failed!\n");
+ return I1PRO_INT_MALLOC;
+ }
+
+ for (i = 0; i < m->nwav[1]; i++) {
+ pp.p[0] = m->wl_short[1]
+ + (double)i * (m->wl_long[1] - m->wl_short[1])/(m->nwav[1]-1.0);
+ trspl->interp(trspl, &pp);
+ (*ref2)[i] = pp.v[0];
+ }
+#ifdef HIGH_RES_PLOT
+ /* Plot original and upsampled reference */
+ {
+ double *x1 = dvectorz(0, m->nwav[1]-1);
+ double *y1 = dvectorz(0, m->nwav[1]-1);
+ double *y2 = dvectorz(0, m->nwav[1]-1);
+
+ for (i = 0; i < m->nwav[1]; i++) {
+ double wl = m->wl_short[1] + (double)i * (m->wl_long[1] - m->wl_short[1])/(m->nwav[1]-1.0);
+ x1[i] = wl;
+ y1[i] = (*ref2)[i];
+ if (wl < m->wl_short[0] || wl > m->wl_long[0]) {
+ y2[i] = 0.0;
+ } else {
+ double x, wl1, wl2;
+ for (j = 0; j < (m->nwav[0]-1); j++) {
+ wl1 = XSPECT_WL(m->wl_short[0], m->wl_long[0], m->nwav[0], j);
+ wl2 = XSPECT_WL(m->wl_short[0], m->wl_long[0], m->nwav[0], j+1);
+ if (wl >= wl1 && wl <= wl2)
+ break;
+ }
+ x = (wl - wl1)/(wl2 - wl1);
+ y2[i] = ref1[j] + (ref1[j+1] - ref1[j]) * x;
+ }
+ }
+ printf("Original and up-sampled ");
+ if (ii == 0) {
+ printf("Reflective cal. curve:\n");
+ } else if (ii == 1) {
+ ref1 = m->emis_coef[0];
+ ref2 = &m->emis_coef[1];
+ printf("Emission cal. curve:\n");
+ } else {
+ printf("Ambient cal. curve:\n");
+ }
+ do_plot(x1, y1, y2, NULL, m->nwav[1]);
+
+ free_dvector(x1, 0, m->nwav[1]-1);
+ free_dvector(y1, 0, m->nwav[1]-1);
+ free_dvector(y2, 0, m->nwav[1]-1);
+ }
+#endif /* HIGH_RES_PLOT */
+ }
+ trspl->del(trspl);
+
+ /* Upsample stray light */
+ if (p->itype == instI1Pro2) {
+#ifdef SALONEINSTLIB
+ double **slp; /* 2D Array of stray light values */
+
+ /* Then the 2D stray light using linear interpolation */
+ slp = dmatrix(0, m->nwav[0]-1, 0, m->nwav[0]-1);
+
+ /* Set scattered points */
+ for (i = 0; i < m->nwav[0]; i++) { /* Output wavelength */
+ for (j = 0; j < m->nwav[0]; j++) { /* Input wavelength */
+
+ slp[i][j] = m->straylight[0][i][j];
+
+ /* Use interpolate/extrapolate for middle points */
+ if (j == (i-1) || j == i || j == (i+1)) {
+ int j0, j1;
+ double w0, w1;
+ if (j == (i-1)) {
+ if (j <= 0)
+ j0 = j+3, j1 = j+4;
+ else if (j >= (m->nwav[0]-3))
+ j0 = j-2, j1 = j-1;
+ else
+ j0 = j-1, j1 = j+3;
+ } else if (j == i) {
+ if (j <= 1)
+ j0 = j+2, j1 = j+3;
+ else if (j >= (m->nwav[0]-2))
+ j0 = j-3, j1 = j-2;
+ else
+ j0 = j-2, j1 = j+2;
+ } else if (j == (i+1)) {
+ if (j <= 2)
+ j0 = j+1, j1 = j+2;
+ else if (j >= (m->nwav[0]-1))
+ j0 = j-4, j1 = j-3;
+ else
+ j0 = j-3, j1 = j+1;
+ }
+ w1 = (j - j0)/(j1 - j0);
+ w0 = 1.0 - w1;
+ slp[i][j] = w0 * m->straylight[0][i][j0]
+ + w1 * m->straylight[0][i][j1];
+
+ }
+ }
+ }
+#else /* !SALONEINSTLIB */
+ /* Then setup 2D stray light using rspl */
+ if ((trspl = new_rspl(RSPL_NOFLAGS, 2, 1)) == NULL) {
+ a1logd(p->log,1,"i1pro: creating rspl for high res conversion failed\n");
+ return I1PRO_INT_NEW_RSPL_FAILED;
+ }
+
+ /* Set scattered points */
+ for (i = 0; i < m->nwav[0]; i++) { /* Output wavelength */
+ for (j = 0; j < m->nwav[0]; j++) { /* Input wavelength */
+ int ix = i * m->nwav[0] + j;
+
+ sd[ix].p[0] = XSPECT_WL(m->wl_short[0], m->wl_long[0], m->nwav[0], i);
+ sd[ix].p[1] = XSPECT_WL(m->wl_short[0], m->wl_long[0], m->nwav[0], j);
+ sd[ix].v[0] = m->straylight[0][i][j];
+ sd[ix].w = 1.0;
+ if (j == (i-1) || j == i || j == (i+1))
+ sd[ix].w = 0.0;
+ }
+ }
+
+ glow[0] = m->wl_short[1];
+ glow[1] = m->wl_short[1];
+ ghigh[0] = m->wl_long[1];
+ ghigh[1] = m->wl_long[1];
+ gres[0] = m->nwav[1];
+ gres[1] = m->nwav[1];
+ avgdev[0] = 0.0;
+ avgdev[1] = 0.0;
+
+ trspl->fit_rspl_w(trspl, 0, sd, m->nwav[0] * m->nwav[0], glow, ghigh, gres, NULL, NULL, 0.5, avgdev, NULL);
+#endif /* !SALONEINSTLIB */
+
+ m->straylight[1] = dmatrixz(0, m->nwav[1]-1, 0, m->nwav[1]-1);
+
+ /* Create upsampled version */
+ for (i = 0; i < m->nwav[1]; i++) { /* Output wavelength */
+ for (j = 0; j < m->nwav[1]; j++) { /* Input wavelength */
+ double p0, p1;
+ p0 = XSPECT_WL(m->wl_short[1], m->wl_long[1], m->nwav[1], i);
+ p1 = XSPECT_WL(m->wl_short[1], m->wl_long[1], m->nwav[1], j);
+#ifdef SALONEINSTLIB
+ /* Do linear interp with clipping at ends */
+ {
+ int x0, x1, y0, y1;
+ double xx, yy, w0, w1, v0, v1;
+
+ xx = (m->nwav[0]-1.0) * (p0 - m->wl_short[0])/(m->wl_long[0] - m->wl_short[0]);
+ x0 = (int)floor(xx);
+ if (x0 <= 0)
+ x0 = 0;
+ else if (x0 >= (m->nwav[0]-2))
+ x0 = m->nwav[0]-2;
+ x1 = x0 + 1;
+ w1 = xx - (double)x0;
+ w0 = 1.0 - w1;
+
+ yy = (m->nwav[0]-1.0) * (p1 - m->wl_short[0])/(m->wl_long[0] - m->wl_short[0]);
+ y0 = (int)floor(yy);
+ if (y0 <= 0)
+ y0 = 0;
+ else if (y0 >= (m->nwav[0]-2))
+ y0 = m->nwav[0]-2;
+ y1 = y0 + 1;
+ v1 = yy - (double)y0;
+ v0 = 1.0 - v1;
+
+ pp.v[0] = w0 * v0 * slp[x0][y0]
+ + w0 * v1 * slp[x0][y1]
+ + w1 * v0 * slp[x1][y0]
+ + w1 * v1 * slp[x1][y1];
+ }
+#else /* !SALONEINSTLIB */
+ pp.p[0] = p0;
+ pp.p[1] = p1;
+ trspl->interp(trspl, &pp);
+#endif /* !SALONEINSTLIB */
+ m->straylight[1][i][j] = pp.v[0] * HIGHRES_WIDTH/10.0;
+ if (m->straylight[1][i][j] > 0.0)
+ m->straylight[1][i][j] = 0.0;
+ }
+ }
+
+ /* Fix primary wavelength weight and neighbors */
+ for (i = 0; i < m->nwav[1]; i++) { /* Output wavelength */
+ double sum;
+
+ if (i > 0)
+ m->straylight[1][i][i-1] = 0.0;
+ m->straylight[1][i][i] = 0.0;
+ if (i < (m->nwav[1]-1))
+ m->straylight[1][i][i+1] = 0.0;
+
+ for (sum = 0.0, j = 0; j < m->nwav[1]; j++)
+ sum += m->straylight[1][i][j];
+
+ m->straylight[1][i][i] = 1.0 - sum; /* Total sum should be 1.0 */
+ }
+
+#ifdef HIGH_RES_PLOT_STRAYL
+ /* Plot original and upsampled reference */
+ {
+ double *x1 = dvectorz(0, m->nwav[1]-1);
+ double *y1 = dvectorz(0, m->nwav[1]-1);
+ double *y2 = dvectorz(0, m->nwav[1]-1);
+
+ for (i = 0; i < m->nwav[1]; i++) { /* Output wavelength */
+ double wli = XSPECT_WL(m->wl_short[1], m->wl_long[1], m->nwav[1], i);
+ int i1 = XSPECT_IX(m->wl_short[0], m->wl_long[0], m->nwav[0], wli);
+
+ for (j = 0; j < m->nwav[1]; j++) {
+ double wl = XSPECT_WL(m->wl_short[1], m->wl_long[1], m->nwav[1], j);
+ x1[j] = wl;
+ y1[j] = m->straylight[1][i][j];
+ if (y1[j] == 0.0)
+ y1[j] = -8.0;
+ else
+ y1[j] = log10(fabs(y1[j]));
+ if (wli < m->wl_short[0] || wli > m->wl_long[0]
+ || wl < m->wl_short[0] || wl > m->wl_long[0]) {
+ y2[j] = -8.0;
+ } else {
+ double x, wl1, wl2;
+ for (k = 0; k < (m->nwav[0]-1); k++) {
+ wl1 = XSPECT_WL(m->wl_short[0], m->wl_long[0], m->nwav[0], k);
+ wl2 = XSPECT_WL(m->wl_short[0], m->wl_long[0], m->nwav[0], k+1);
+ if (wl >= wl1 && wl <= wl2)
+ break;
+ }
+ x = (wl - wl1)/(wl2 - wl1);
+ y2[j] = m->straylight[0][i1][k] + (m->straylight[0][i1][k+1]
+ - m->straylight[0][i1][k]) * x;
+ if (y2[j] == 0.0)
+ y2[j] = -8.0;
+ else
+ y2[j] = log10(fabs(y2[j]));
+ }
+ }
+ do_plot(x1, y1, y2, NULL, m->nwav[1]);
+ }
+
+ free_dvector(x1, 0, m->nwav[1]-1);
+ free_dvector(y1, 0, m->nwav[1]-1);
+ free_dvector(y2, 0, m->nwav[1]-1);
+ }
+#endif /* HIGH_RES_PLOT */
+
+#ifdef SALONEINSTLIB
+ free_dmatrix(slp, 0, m->nwav[0]-1, 0, m->nwav[0]-1);
+#else /* !SALONEINSTLIB */
+ trspl->del(trspl);
+#endif /* !SALONEINSTLIB */
+ }
+ }
+
+ /* Create or re-create the high resolution filters */
+ for (refl = 0; refl < 2; refl++) { /* for emis/trans and reflective */
+ double fshmax; /* filter shape max wavelength from center */
+#define MXNOFC 64
+ i1pro_fc coeff2[500][MXNOFC]; /* New filter cooefficients */
+ double twidth;
+
+ /* Construct a set of filters that uses more CCD values */
+ twidth = HIGHRES_WIDTH;
+
+ if (m->nwav[1] > 500) { /* Assert */
+ a1loge(p->log,1,"High res filter has too many bands\n");
+ return I1PRO_INT_ASSERT;
+ }
+
+ /* Use a simple means of determining width */
+ for (fshmax = 50.0; fshmax >= 0.0; fshmax -= 0.1) {
+ if (fabs(lanczos2(twidth, fshmax)) > 0.0001) {
+ fshmax += 0.1;
+ break;
+ }
+ }
+ if (fshmax <= 0.0) {
+ a1logw(p->log, "i1pro: fshmax search failed\n");
+ return I1PRO_INT_ASSERT;
+ }
+
+// printf("~1 fshmax = %f\n",fshmax);
+
+#ifdef HIGH_RES_DEBUG
+ /* Check that the filter sums to a constant */
+ {
+ double x, sum;
+
+ for (x = 0.0; x < 5.0; x += 0.1) {
+ sum = 0;
+ sum += lin_fshape(fshape, ncp, x - 15.0);
+ sum += lin_fshape(fshape, ncp, x - 10.0);
+ sum += lin_fshape(fshape, ncp, x - 5.0);
+ sum += lin_fshape(fshape, ncp, x - 0.0);
+ sum += lin_fshape(fshape, ncp, x + 5.0);
+ sum += lin_fshape(fshape, ncp, x + 10.0);
+ printf("Offset %f, sum %f\n",x, sum);
+ }
+ }
+#endif /* HIGH_RES_DEBUG */
+
+ /* Create all the filters */
+ if (m->mtx_c[1][refl].nocoef != NULL)
+ free(m->mtx_c[1][refl].nocoef);
+ if ((m->mtx_c[1][refl].nocoef = (int *)calloc(m->nwav[1], sizeof(int))) == NULL) {
+ a1logw(p->log, "i1pro: malloc nocoef failed!\n");
+ return I1PRO_INT_MALLOC;
+ }
+
+ /* For all the useful CCD bands */
+ for (i = 1; i < 127; i++) {
+ double w1, wl, w2;
+
+ /* Translate CCD center and boundaries to calibrated wavelength */
+ wl = i1pro_raw2wav(p, refl, (double)i);
+ w1 = i1pro_raw2wav(p, refl, (double)i - 0.5);
+ w2 = i1pro_raw2wav(p, refl, (double)i + 0.5);
+
+// printf("~1 CCD %d, w1 %f, wl %f, w2 %f\n",i,w1,wl,w2);
+
+ /* For each filter */
+ for (j = 0; j < m->nwav[1]; j++) {
+ double cwl, rwl; /* center, relative wavelegth */
+ double we;
+
+ cwl = m->wl_short[1] + (double)j * (m->wl_long[1] - m->wl_short[1])/(m->nwav[1]-1.0);
+ rwl = wl - cwl; /* relative wavelength to filter */
+
+ if (fabs(w1 - cwl) > fshmax && fabs(w2 - cwl) > fshmax)
+ continue; /* Doesn't fall into this filter */
+
+ /* Integrate in 0.05 nm increments from filter shape */
+ /* using triangular integration. */
+ {
+ int nn;
+ double lw, ll;
+
+ nn = (int)(fabs(w2 - w1)/0.05 + 0.5); /* Number to integrate over */
+
+ lw = w1; /* start at lower boundary of CCD cell */
+ ll = lanczos2(twidth, w1- cwl);
+ we = 0.0;
+ for (k = 0; k < nn; k++) {
+ double cw, cl;
+
+#if defined(__APPLE__) && defined(__POWERPC__)
+ gcc_bug_fix(k);
+#endif
+ cw = w1 + (k+1.0)/(nn +1.0) * (w2 - w1); /* wl to sample */
+ cl = lanczos2(twidth, cw- cwl);
+ we += 0.5 * (cl + ll) * (lw - cw); /* Area under triangle */
+ ll = cl;
+ lw = cw;
+ }
+ }
+
+ if (m->mtx_c[1][refl].nocoef[j] >= MXNOFC) {
+ a1logw(p->log, "i1pro: run out of high res filter space\n");
+ return I1PRO_INT_ASSERT;
+ }
+
+ coeff2[j][m->mtx_c[1][refl].nocoef[j]].ix = i;
+ coeff2[j][m->mtx_c[1][refl].nocoef[j]++].we = we;
+// printf("~1 filter %d, cwl %f, rwl %f, ix %d, we %f, nocoefs %d\n",j,cwl,rwl,i,we,m->mtx_c[1][refl].nocoef[j]-1);
+ }
+ }
+
+#ifdef HIGH_RES_PLOT
+ /* Plot resampled curves */
+ {
+ double *xx, *ss;
+ double **yy;
+
+ xx = dvectorz(-1, m->nraw-1); /* X index */
+ yy = dmatrixz(0, 5, -1, m->nraw-1); /* Curves distributed amongst 5 graphs */
+
+ for (i = 0; i < m->nraw; i++)
+ xx[i] = i;
+
+ /* For each output wavelength */
+ for (j = 0; j < m->nwav[1]; j++) {
+ i = j % 5;
+
+ /* For each matrix value */
+ for (k = 0; k < m->mtx_c[1][refl].nocoef[j]; k++) {
+ yy[5][coeff2[j][k].ix] += 0.5 * coeff2[j][k].we;
+ yy[i][coeff2[j][k].ix] = coeff2[j][k].we;
+ }
+ }
+
+ printf("Hi-Res wavelength sampling curves:\n");
+ do_plot6(xx, yy[0], yy[1], yy[2], yy[3], yy[4], yy[5], m->nraw);
+ free_dvector(xx, -1, m->nraw-1);
+ free_dmatrix(yy, 0, 2, -1, m->nraw-1);
+ }
+#endif /* HIGH_RES_PLOT */
+
+ /* Normalise the filters area in CCD space, while maintaining the */
+ /* total contribution of each CCD at the target too. */
+ {
+ int ii;
+ double tot = 0.0;
+ double ccdweight[128], avgw; /* Weighting determined by cell widths */
+ double ccdsum[128];
+
+ /* Normalize the overall filter weightings */
+ for (j = 0; j < m->nwav[1]; j++)
+ for (k = 0; k < m->mtx_c[1][refl].nocoef[j]; k++)
+ tot += coeff2[j][k].we;
+ tot /= (double)m->nwav[1];
+ for (j = 0; j < m->nwav[1]; j++)
+ for (k = 0; k < m->mtx_c[1][refl].nocoef[j]; k++)
+ coeff2[j][k].we /= tot;
+
+ /* Determine the relative weights for each CCD */
+ avgw = 0.0;
+ for (i = 1; i < 127; i++) {
+
+ ccdweight[i] = i1pro_raw2wav(p, refl, (double)i - 0.5)
+ - i1pro_raw2wav(p, refl, (double)i + 0.5);
+ ccdweight[i] = fabs(ccdweight[i]);
+ avgw += ccdweight[i];
+ }
+ avgw /= 126.0;
+ // ~~this isn't right because not all CCD's get used!!
+
+#ifdef NEVER
+ /* Itterate */
+ for (ii = 0; ; ii++) {
+
+ /* Normalize the individual filter weightings */
+ for (j = 0; j < m->nwav[1]; j++) {
+ double err;
+ tot = 0.0;
+ for (k = 0; k < m->mtx_c[1][refl].nocoef[j]; k++)
+ tot += coeff2[j][k].we;
+ err = 1.0 - tot;
+
+ for (k = 0; k < m->mtx_c[1][refl].nocoef[j]; k++)
+ coeff2[j][k].we += err/m->mtx_c[1][refl].nocoef[j];
+// for (k = 0; k < m->mtx_c[1][refl].nocoef[j]; k++)
+// coeff2[j][k].we *= 1.0/tot;
+ }
+
+ /* Check CCD sums */
+ for (i = 1; i < 127; i++)
+ ccdsum[i] = 0.0;
+
+ for (j = 0; j < m->nwav[1]; j++) {
+ for (k = 0; k < m->mtx_c[1][refl].nocoef[j]; k++)
+ ccdsum[coeff2[j][k].ix] += coeff2[j][k].we;
+ }
+
+ if (ii >= 6)
+ break;
+
+ /* Readjust CCD sum */
+ for (i = 1; i < 127; i++) {
+ ccdsum[i] = ccdtsum[i]/ccdsum[i]; /* Amount to adjust */
+ }
+
+ for (j = 0; j < m->nwav[1]; j++) {
+ for (k = 0; k < m->mtx_c[1][refl].nocoef[j]; k++)
+ coeff2[j][k].we *= ccdsum[coeff2[j][k].ix];
+ }
+ }
+#endif /* NEVER */
+ }
+
+#ifdef HIGH_RES_PLOT
+ /* Plot resampled curves */
+ {
+ double *xx, *ss;
+ double **yy;
+
+ xx = dvectorz(-1, m->nraw-1); /* X index */
+ yy = dmatrixz(0, 5, -1, m->nraw-1); /* Curves distributed amongst 5 graphs */
+
+ for (i = 0; i < m->nraw; i++)
+ xx[i] = i;
+
+ /* For each output wavelength */
+ for (j = 0; j < m->nwav[1]; j++) {
+ i = j % 5;
+
+ /* For each matrix value */
+ for (k = 0; k < m->mtx_c[1][refl].nocoef[j]; k++) {
+ yy[5][coeff2[j][k].ix] += 0.5 * coeff2[j][k].we;
+ yy[i][coeff2[j][k].ix] = coeff2[j][k].we;
+ }
+ }
+
+ printf("Normalized Hi-Res wavelength sampling curves:\n");
+ do_plot6(xx, yy[0], yy[1], yy[2], yy[3], yy[4], yy[5], m->nraw);
+ free_dvector(xx, -1, m->nraw-1);
+ free_dmatrix(yy, 0, 2, -1, m->nraw-1);
+ }
+#endif /* HIGH_RES_PLOT */
+
+ /* Convert hires filters into runtime format */
+ {
+ int xcount;
+
+ /* Allocate or reallocate high res filter tables */
+ if (m->mtx_c[1][refl].index != NULL)
+ free(m->mtx_c[1][refl].index);
+ if (m->mtx_c[1][refl].coef != NULL)
+ free(m->mtx_c[1][refl].coef);
+
+ if ((m->mtx_c[1][refl].index = (int *)calloc(m->nwav[1], sizeof(int))) == NULL) {
+ a1logw(p->log, "i1pro: malloc index failed!\n");
+ return I1PRO_INT_MALLOC;
+ }
+
+ xcount = 0;
+ for (j = 0; j < m->nwav[1]; j++) {
+ m->mtx_c[1][refl].index[j] = coeff2[j][0].ix;
+ xcount += m->mtx_c[1][refl].nocoef[j];
+ }
+
+ if ((m->mtx_c[1][refl].coef = (double *)calloc(xcount, sizeof(double))) == NULL) {
+ a1logw(p->log, "i1pro: malloc coef failed!\n");
+ return I1PRO_INT_MALLOC;
+ }
+
+ for (i = j = 0; j < m->nwav[1]; j++)
+ for (k = 0; k < m->mtx_c[1][refl].nocoef[j]; k++, i++)
+ m->mtx_c[1][refl].coef[i] = coeff2[j][k].we;
+
+ /* Set high res tables to new allocations */
+ m->mtx[1][refl] = m->mtx_c[1][refl];
+ }
+ }
+
+ /* Generate high res. per mode calibration factors. */
+ if (!m->hr_inited) {
+
+ m->hr_inited = 1; /* Set so that i1pro_compute_white_cal() etc. does right thing */
+
+ for (i = 0; i < i1p_no_modes; i++) {
+ i1pro_state *s = &m->ms[i];
+
+ if (s->cal_factor[1] == NULL)
+ s->cal_factor[1] = dvectorz(0, m->nwav[1]-1);
+
+ switch(i) {
+ case i1p_refl_spot:
+ case i1p_refl_scan:
+ if (s->cal_valid) {
+ /* (Using cal_factor[] as temp. for i1pro_absraw_to_abswav()) */
+#ifdef NEVER
+ printf("~1 regenerating calibration for reflection\n");
+ printf("~1 raw white data:\n");
+ plot_raw(s->white_data);
+#endif /* NEVER */
+ i1pro_absraw_to_abswav(p, 0, s->reflective, 1, &s->cal_factor[0], &s->white_data);
+#ifdef NEVER
+ printf("~1 Std res intmd. cal_factor:\n");
+ plot_wav(m, 0, s->cal_factor[0]);
+#endif /* NEVER */
+ i1pro_absraw_to_abswav(p, 1, s->reflective, 1, &s->cal_factor[1], &s->white_data);
+#ifdef NEVER
+ printf("~1 High intmd. cal_factor:\n");
+ plot_wav(m, 1, s->cal_factor[1]);
+ printf("~1 Std res white ref:\n");
+ plot_wav(m, 0, m->white_ref[0]);
+ printf("~1 High res white ref:\n");
+ plot_wav(m, 1, m->white_ref[1]);
+#endif /* NEVER */
+ i1pro_compute_white_cal(p, s->cal_factor[0], m->white_ref[0], s->cal_factor[0],
+ s->cal_factor[1], m->white_ref[1], s->cal_factor[1]);
+#ifdef NEVER
+ printf("~1 Std res final cal_factor:\n");
+ plot_wav(m, 0, s->cal_factor[0]);
+ printf("~1 High final cal_factor:\n");
+ plot_wav(m, 1, s->cal_factor[1]);
+#endif /* NEVER */
+ }
+ break;
+
+ case i1p_emiss_spot_na:
+ case i1p_emiss_spot:
+ case i1p_emiss_scan:
+ for (j = 0; j < m->nwav[1]; j++)
+ s->cal_factor[1][j] = EMIS_SCALE_FACTOR * m->emis_coef[1][j];
+ break;
+
+ case i1p_amb_spot:
+ case i1p_amb_flash:
+#ifdef FAKE_AMBIENT
+ for (j = 0; j < m->nwav[1]; j++)
+ s->cal_factor[1][j] = EMIS_SCALE_FACTOR * m->emis_coef[1][j];
+ s->cal_valid = 1;
+#else
+
+ if (m->amb_coef[0] != NULL) {
+ for (j = 0; j < m->nwav[1]; j++)
+ s->cal_factor[1][j] = AMB_SCALE_FACTOR * m->emis_coef[1][j] * m->amb_coef[1][j];
+ s->cal_valid = 1;
+ }
+#endif
+ break;
+ case i1p_trans_spot:
+ case i1p_trans_scan:
+ if (s->cal_valid) {
+ /* (Using cal_factor[] as temp. for i1pro_absraw_to_abswav()) */
+ i1pro_absraw_to_abswav(p, 0, s->reflective, 1, &s->cal_factor[0], &s->white_data);
+ i1pro_absraw_to_abswav(p, 1, s->reflective, 1, &s->cal_factor[1], &s->white_data);
+ i1pro_compute_white_cal(p, s->cal_factor[0], NULL, s->cal_factor[0],
+ s->cal_factor[1], NULL, s->cal_factor[1]);
+ }
+ break;
+ }
+ }
+ }
+
+ /* Hires has been initialised */
+ m->hr_inited = 1;
+
+ return ev;
+}
+
+#endif /* HIGH_RES */
+
+
+/* return nz if high res is supported */
+int i1pro_imp_highres(i1pro *p) {
+#ifdef HIGH_RES
+ return 1;
+#else
+ return 0;
+#endif /* HIGH_RES */
+}
+
+/* Set to high resolution mode */
+i1pro_code i1pro_set_highres(i1pro *p) {
+ int i;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_code ev = I1PRO_OK;
+
+#ifdef HIGH_RES
+ if (m->hr_inited == 0) {
+ if ((ev = i1pro_create_hr(p)) != I1PRO_OK)
+ return ev;
+ }
+ m->highres = 1;
+#else
+ ev = I1PRO_UNSUPPORTED;
+#endif /* HIGH_RES */
+
+ return ev;
+}
+
+/* Set to standard resolution mode */
+i1pro_code i1pro_set_stdres(i1pro *p) {
+ int i;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_code ev = I1PRO_OK;
+
+#ifdef HIGH_RES
+ m->highres = 0;
+#else
+ ev = I1PRO_UNSUPPORTED;
+#endif /* HIGH_RES */
+
+ return ev;
+}
+
+/* =============================================== */
+
+/* Modify the scan consistency tolerance */
+i1pro_code i1pro_set_scan_toll(i1pro *p, double toll_ratio) {
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_code ev = I1PRO_OK;
+
+ m->scan_toll_ratio = toll_ratio;
+
+ return I1PRO_OK;
+}
+
+
+/* Optics adjustment weights */
+static double opt_adj_weights[21] = {
+ 1.4944496665144658e-282, 2.0036175483913455e-070, 1.2554893022685038e+232,
+ 2.3898157055642966e+190, 1.5697625128432372e-076, 6.6912978722191457e+281,
+ 1.2369092402930559e+277, 1.4430907501246712e-153, 3.0017439193018232e+238,
+ 1.2978311824382444e+161, 5.5068703318775818e-311, 7.7791723264455314e-260,
+ 6.4560484084110176e+170, 8.9481529920968425e+165, 1.3565405878488529e-153,
+ 2.0835868791190880e-076, 5.4310198502711138e+241, 4.8689849775675438e+275,
+ 9.2709981544886391e+122, 3.7958270103353899e-153, 7.1366083837501666e-154
+};
+
+/* Convert from spectral to XYZ, and transfer to the ipatch array */
+i1pro_code i1pro_conv2XYZ(
+ i1pro *p,
+ ipatch *vals, /* Values to return */
+ int nvals, /* Number of values */
+ double **specrd, /* Spectral readings */
+ instClamping clamp /* Clamp XYZ/Lab to be +ve */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ xsp2cie *conv; /* Spectral to XYZ conversion object */
+ int i, j, k;
+ int six = 0; /* Starting index */
+ int nwl = m->nwav[m->highres]; /* Number of wavelegths */
+ double wl_short = m->wl_short[m->highres]; /* Starting wavelegth */
+ double sms; /* Weighting */
+
+ if (s->emiss)
+ conv = new_xsp2cie(icxIT_none, NULL, icxOT_CIE_1931_2, NULL, icSigXYZData, (icxClamping)clamp);
+ else
+ conv = new_xsp2cie(icxIT_D50, NULL, icxOT_CIE_1931_2, NULL, icSigXYZData, (icxClamping)clamp);
+ if (conv == NULL)
+ return I1PRO_INT_CIECONVFAIL;
+
+ /* Don't report any wavelengths below the minimum for this mode */
+ if ((s->min_wl-1e-3) > wl_short) {
+ double wl = 0.0;
+ for (j = 0; j < m->nwav[m->highres]; j++) {
+ wl = XSPECT_WL(m->wl_short[m->highres], m->wl_long[m->highres], m->nwav[m->highres], j);
+ if (wl >= (s->min_wl-1e-3))
+ break;
+ }
+ six = j;
+ wl_short = wl;
+ nwl -= six;
+ }
+
+ a1logd(p->log,5,"i1pro_conv2XYZ got wl_short %f, wl_long %f, nwav %d, min_wl %f\n",
+ m->wl_short[m->highres], m->wl_long[m->highres], m->nwav[m->highres], s->min_wl);
+ a1logd(p->log,5," after skip got wl_short %f, nwl = %d\n", wl_short, nwl);
+
+ for (sms = 0.0, i = 1; i < 21; i++)
+ sms += opt_adj_weights[i];
+ sms *= opt_adj_weights[0];
+
+ for (i = 0; i < nvals; i++) {
+
+ vals[i].loc[0] = '\000';
+ vals[i].mtype = inst_mrt_none;
+ vals[i].XYZ_v = 0;
+ vals[i].sp.spec_n = 0;
+ vals[i].duration = 0.0;
+
+ vals[i].sp.spec_n = nwl;
+ vals[i].sp.spec_wl_short = wl_short;
+ vals[i].sp.spec_wl_long = m->wl_long[m->highres];
+
+ if (s->emiss) {
+ /* Leave spectral values as mW/m^2 */
+ for (j = six, k = 0; j < m->nwav[m->highres]; j++, k++) {
+ vals[i].sp.spec[k] = specrd[i][j] * sms;
+ }
+ vals[i].sp.norm = 1.0;
+
+ /* Set the XYZ */
+ conv->convert(conv, vals[i].XYZ, &vals[i].sp);
+ vals[i].XYZ_v = 1;
+
+ if (s->ambient) {
+ if (s->flash)
+ vals[i].mtype = inst_mrt_ambient_flash;
+ else
+ vals[i].mtype = inst_mrt_ambient;
+ } else {
+ if (s->flash)
+ vals[i].mtype = inst_mrt_emission_flash;
+ else
+ vals[i].mtype = inst_mrt_emission;
+ }
+
+ } else {
+ /* Scale spectral values to percentage reflectance */
+ for (j = six, k = 0; j < m->nwav[m->highres]; j++, k++) {
+ vals[i].sp.spec[k] = 100.0 * specrd[i][j] * sms;
+ }
+ vals[i].sp.norm = 100.0;
+
+ /* Set the XYZ */
+ conv->convert(conv, vals[i].XYZ, &vals[i].sp);
+ vals[i].XYZ_v = 1;
+ vals[i].XYZ[0] *= 100.0;
+ vals[i].XYZ[1] *= 100.0;
+ vals[i].XYZ[2] *= 100.0;
+
+ if (s->trans)
+ vals[i].mtype = inst_mrt_transmissive;
+ else
+ vals[i].mtype = inst_mrt_reflective;
+ }
+
+ /* Don't return spectral if not asked for */
+ if (!m->spec_en) {
+ vals[i].sp.spec_n = 0;
+ }
+ }
+
+ conv->del(conv);
+ return I1PRO_OK;
+}
+
+/* Check a reflective white reference measurement to see if */
+/* it seems reasonable. Return I1PRO_OK if it is, error if not. */
+i1pro_code i1pro_check_white_reference1(
+ i1pro *p,
+ double *abswav /* [nwav[0]] Measurement to check */
+) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ double *emiswav, normfac;
+ double avg01, avg2227;
+ int j;
+
+ emiswav = dvector(-1, m->nraw-1);
+
+ /* Convert from absolute wavelength converted sensor reading, */
+ /* to calibrated emission wavelength spectrum. */
+
+ /* For each output wavelength */
+ for (j = 0; j < m->nwav[0]; j++) {
+ emiswav[j] = m->emis_coef[0][j] * abswav[j];
+ }
+#ifdef PLOT_DEBUG
+ printf("White ref read converted to emissive spectrum:\n");
+ plot_wav(m, 0, emiswav);
+#endif
+
+ /* Normalise the measurement to the reflectance of the 17 wavelength */
+ /* of the white reference (550nm), as well as dividing out the */
+ /* reference white. This should leave us with the iluminant spectrum, */
+ normfac = m->white_ref[0][17]/emiswav[17];
+
+ for (j = 0; j < m->nwav[0]; j++) {
+ emiswav[j] *= normfac/m->white_ref[0][j];
+ }
+
+#ifdef PLOT_DEBUG
+ printf("normalised to reference white read:\n");
+ plot_wav(m, 0, emiswav);
+#endif
+
+ /* Compute two sample averages of the illuminant spectrum. */
+ avg01 = 0.5 * (emiswav[0] + emiswav[1]);
+
+ for (avg2227 = 0, j = 22; j < 28; j++) {
+ avg2227 += emiswav[j];
+ }
+ avg2227 /= (double)(28 - 22);
+
+ free_dvector(emiswav, -1, m->nraw-1);
+
+ /* And check them against tolerance for the illuminant. */
+ if (m->physfilt == 0x82) { /* UV filter */
+ if (0.0 < avg01 && avg01 < 0.05
+ && 1.2 < avg2227 && avg2227 < 1.76) {
+ return I1PRO_OK;
+ }
+
+ } else { /* No filter */
+ if (0.11 < avg01 && avg01 < 0.22
+ && 1.35 < avg2227 && avg2227 < 1.6) {
+ return I1PRO_OK;
+ }
+ }
+ a1logd(p->log,2,"Checking white reference failed, 0.11 < avg01 %f < 0.22, 1.35 < avg2227 %f < 1.6\n",avg01,avg2227);
+ return I1PRO_RD_WHITEREFERROR;
+}
+
+/* Compute a mode calibration factor given the reading of the white reference. */
+/* Return 1 if any of the transmission wavelengths are low. */
+int i1pro_compute_white_cal(
+ i1pro *p,
+ double *cal_factor0, /* [nwav[0]] Calibration factor to compute */
+ double *white_ref0, /* [nwav[0]] White reference to aim for, NULL for 1.0 */
+ double *white_read0, /* [nwav[0]] The white that was read */
+ double *cal_factor1, /* [nwav[1]] Calibration factor to compute */
+ double *white_ref1, /* [nwav[1]] White reference to aim for, NULL for 1.0 */
+ double *white_read1 /* [nwav[1]] The white that was read */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ int j, warn = 0;;
+
+ if (white_ref0 == NULL) { /* transmission white reference */
+ double avgwh = 0.0;
+
+ /* Compute average white reference reading */
+ for (j = 0; j < m->nwav[0]; j++)
+ avgwh += white_read0[j];
+ avgwh /= (double)m->nwav[0];
+
+ /* For each wavelength */
+ for (j = 0; j < m->nwav[0]; j++) {
+ /* If reference is < 0.4% of average */
+ if (white_read0[j]/avgwh < 0.004) {
+ cal_factor0[j] = 1.0/(0.004 * avgwh);
+ warn = 1;
+ } else {
+ cal_factor0[j] = 1.0/white_read0[j];
+ }
+ }
+
+ } else { /* Reflection white reference */
+
+ /* For each wavelength */
+ for (j = 0; j < m->nwav[0]; j++) {
+ if (white_read0[j] < 1000.0)
+ cal_factor0[j] = white_ref0[j]/1000.0;
+ else
+ cal_factor0[j] = white_ref0[j]/white_read0[j];
+ }
+ }
+
+#ifdef HIGH_RES
+ if (m->hr_inited == 0)
+ return warn;
+
+ if (white_ref1 == NULL) { /* transmission white reference */
+ double avgwh = 0.0;
+
+ /* Compute average white reference reading */
+ for (j = 0; j < m->nwav[1]; j++)
+ avgwh += white_read1[j];
+ avgwh /= (double)m->nwav[1];
+
+ /* For each wavelength */
+ for (j = 0; j < m->nwav[1]; j++) {
+ /* If reference is < 0.4% of average */
+ if (white_read1[j]/avgwh < 0.004) {
+ cal_factor1[j] = 1.0/(0.004 * avgwh);
+ warn = 1;
+ } else {
+ cal_factor1[j] = 1.0/white_read1[j];
+ }
+ }
+
+ } else { /* Reflection white reference */
+
+ /* For each wavelength */
+ for (j = 0; j < m->nwav[1]; j++) {
+ if (white_read1[j] < 1000.0)
+ cal_factor1[j] = white_ref1[j]/1000.0;
+ else
+ cal_factor1[j] = white_ref1[j]/white_read1[j];
+ }
+ }
+#endif /* HIGH_RES */
+ return warn;
+}
+
+/* For adaptive mode, compute a new integration time and gain mode */
+/* in order to optimise the sensor values. Note that the Rev E doesn't have */
+/* a high gain mode. */
+i1pro_code i1pro_optimise_sensor(
+ i1pro *p,
+ double *pnew_int_time,
+ int *pnew_gain_mode,
+ double cur_int_time,
+ int cur_gain_mode,
+ int permithg, /* nz to permit switching to high gain mode */
+ int permitclip, /* nz to permit clipping out of range int_time, else error */
+ double targoscale, /* Optimising target scale ( <= 1.0) */
+ double scale /* scale needed of current int time to reach optimum */
+) {
+ i1pro_code ev = I1PRO_OK;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ double new_int_time;
+ int new_gain_mode;
+
+ a1logd(p->log,3,"i1pro_optimise_sensor called, inttime %f, gain mode %d, targ scale %f, scale %f\n",cur_int_time,cur_gain_mode, targoscale, scale);
+
+ /* Compute new normal gain integration time */
+ if (cur_gain_mode) /* If high gain */
+ new_int_time = cur_int_time * scale * m->highgain;
+ else
+ new_int_time = cur_int_time * scale;
+ new_gain_mode = 0;
+
+ a1logd(p->log,3,"target inttime %f, gain mode %d\n",new_int_time,new_gain_mode);
+
+ /* Adjust to low light situation by increasing the integration time. */
+ if (new_int_time > s->targmaxitime) { /* Exceeding target integration time */
+ if (s->targmaxitime/new_int_time > s->targoscale2) { /* But within range */
+ /* Compromise sensor target value to maintain targmaxitime */
+ new_int_time = s->targmaxitime;
+ a1logd(p->log,3,"Using targmaxitime with compromise sensor target\n");
+ } else {
+ /* Target reduced sensor value to give improved measurement time and continuity */
+ new_int_time *= s->targoscale2;
+ a1logd(p->log,3,"Using compromse sensor target\n");
+ }
+#ifdef USE_HIGH_GAIN_MODE
+ /* !! Should change this so that it doesn't affect int. time, */
+ /* but that we simply switch to high gain mode when the */
+ /* expected level is < target_level/gain */
+ /* Hmm. It may not be a good idea to use high gain mode if it compromises */
+ /* the longer integration time which reduces noise. */
+ if (p->itype != instI1Pro2 && new_int_time > m->max_int_time && permithg) {
+ new_int_time /= m->highgain;
+ new_gain_mode = 1;
+ a1logd(p->log,3,"Switching to high gain mode\n");
+ }
+#endif
+ }
+ a1logd(p->log,3,"after low light adjust, inttime %f, gain mode %d\n",new_int_time,new_gain_mode);
+
+ /* Deal with still low light */
+ if (new_int_time > m->max_int_time) {
+ if (permitclip)
+ new_int_time = m->max_int_time;
+ else
+ return I1PRO_RD_LIGHTTOOLOW;
+ }
+ a1logd(p->log,3,"after low light clip, inttime %f, gain mode %d\n",new_int_time,new_gain_mode);
+
+ /* Adjust to high light situation */
+ if (new_int_time < m->min_int_time && targoscale < 1.0) {
+ new_int_time /= targoscale; /* Aim for non-scaled sensor optimum */
+ if (new_int_time > m->min_int_time) /* But scale as much as possible */
+ new_int_time = m->min_int_time;
+ }
+ a1logd(p->log,3,"after high light adjust, inttime %f, gain mode %d\n",new_int_time,new_gain_mode);
+
+ /* Deal with still high light */
+ if (new_int_time < m->min_int_time) {
+ if (permitclip)
+ new_int_time = m->min_int_time;
+ else
+ return I1PRO_RD_LIGHTTOOHIGH;
+ }
+ a1logd(p->log,3,"after high light clip, returning inttime %f, gain mode %d\n",new_int_time,new_gain_mode);
+
+ if (pnew_int_time != NULL)
+ *pnew_int_time = new_int_time;
+
+ if (pnew_gain_mode != NULL)
+ *pnew_gain_mode = new_gain_mode;
+
+ return I1PRO_OK;
+}
+
+/* Compute the number of measurements needed, given the target */
+/* measurement time and integration time. Will return 0 if target time is 0 */
+int i1pro_comp_nummeas(
+ i1pro *p,
+ double meas_time,
+ double int_time
+) {
+ int nmeas;
+ if (meas_time <= 0.0)
+ return 0;
+ nmeas = (int)floor(meas_time/int_time + 0.5);
+ if (nmeas < 1)
+ nmeas = 1;
+ return nmeas;
+}
+
+/* Convert the dark interpolation data to a useful state */
+/* (also allow for interpolating the shielded cell values) */
+void
+i1pro_prepare_idark(
+ i1pro *p
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ int i, j;
+
+ /* For normal and high gain */
+ for (i = 0; i < 4; i+= 2) {
+ for (j = -1; j < m->nraw; j++) {
+ double d01, d1;
+ d01 = s->idark_data[i+0][j] * s->idark_int_time[i+0];
+ d1 = s->idark_data[i+1][j] * s->idark_int_time[i+1];
+
+ /* Compute increment */
+ s->idark_data[i+1][j] = (d1 - d01)/(s->idark_int_time[i+1] - s->idark_int_time[i+0]);
+
+ /* Compute base */
+ s->idark_data[i+0][j] = d01 - s->idark_data[i+1][j] * s->idark_int_time[i+0];
+ }
+ if (p->itype == instI1Pro2) /* Rev E doesn't have high gain mode */
+ break;
+ }
+}
+
+/* Create the dark reference for the given integration time and gain */
+/* by interpolating from the 4 readings taken earlier. */
+i1pro_code
+i1pro_interp_dark(
+ i1pro *p,
+ double *result, /* Put result of interpolation here */
+ double inttime,
+ int gainmode
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ int i, j;
+
+ if (!s->idark_valid)
+ return I1PRO_INT_NOTCALIBRATED;
+
+ i = 0;
+#ifdef USE_HIGH_GAIN_MODE
+ if (gainmode)
+ i = 2;
+#endif
+
+ for (j = -1; j < m->nraw; j++) {
+ double tt;
+ tt = s->idark_data[i+0][j] + inttime * s->idark_data[i+1][j];
+ tt /= inttime;
+ result[j] = tt;
+ }
+ return I1PRO_OK;
+}
+
+/* Set the noinitcalib mode */
+void i1pro_set_noinitcalib(i1pro *p, int v, int losecs) {
+ i1proimp *m = (i1proimp *)p->m;
+
+ /* Ignore disabling init calib if more than losecs since instrument was open */
+ if (v && losecs != 0 && m->lo_secs >= losecs) {
+ a1logd(p->log,3,"initcalib disable ignored because %d >= %d secs\n",m->lo_secs,losecs);
+ return;
+ }
+ m->noinitcalib = v;
+}
+
+/* Set the trigger config */
+void i1pro_set_trig(i1pro *p, inst_opt_type trig) {
+ i1proimp *m = (i1proimp *)p->m;
+ m->trig = trig;
+}
+
+/* Return the trigger config */
+inst_opt_type i1pro_get_trig(i1pro *p) {
+ i1proimp *m = (i1proimp *)p->m;
+ return m->trig;
+}
+
+/* Switch thread handler */
+int i1pro_switch_thread(void *pp) {
+ int nfailed = 0;
+ i1pro *p = (i1pro *)pp;
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_code rv = I1PRO_OK;
+ a1logd(p->log,3,"Switch thread started\n");
+ for (nfailed = 0;nfailed < 5;) {
+ rv = i1pro_waitfor_switch_th(p, SW_THREAD_TIMEOUT);
+ a1logd(p->log,8,"Switch handler triggered with rv %d, th_term %d\n",rv,m->th_term);
+ if (m->th_term) {
+ m->th_termed = 1;
+ break;
+ }
+ if (rv == I1PRO_INT_BUTTONTIMEOUT) {
+ nfailed = 0;
+ continue;
+ }
+ if (rv != I1PRO_OK) {
+ nfailed++;
+ a1logd(p->log,3,"Switch thread failed with 0x%x\n",rv);
+ continue;
+ }
+ m->switch_count++;
+ if (!m->hide_switch && p->eventcallback != NULL) {
+ p->eventcallback(p->event_cntx, inst_event_switch);
+ }
+ }
+ a1logd(p->log,3,"Switch thread returning\n");
+ return rv;
+}
+
+/* ============================================================ */
+/* Low level i1pro commands */
+
+/* USB Instrument commands */
+
+/* Reset the instrument */
+i1pro_code
+i1pro_reset(
+ i1pro *p,
+ int mask /* reset mask ?. Known values ar 0x1f, 0x07, 0x01 */
+ /* 0x1f = normal resent */
+ /* 0x01 = establish high power mode */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ unsigned char pbuf[2]; /* 1 or 2 bytes to write */
+ int len = 1; /* Message length */
+ int se, rv = I1PRO_OK;
+ int stime = 0;
+
+ a1logd(p->log,2,"i1pro_reset: reset with mask 0x%02x @ %d msec\n",
+ mask,(stime = msec_time()) - m->msec);
+
+ pbuf[0] = mask;
+
+ if (p->itype == instI1Pro2) {
+ pbuf[1] = 0; /* Not known what i1pro2 second byte is for */
+ len = 2;
+ }
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xCA, 0, 0, pbuf, len, 2.0);
+
+ rv = icoms2i1pro_err(se);
+
+ a1logd(p->log,2,"i1pro_reset: complete, ICOM err 0x%x (%d msec)\n",se,msec_time()-stime);
+
+ /* Allow time for hardware to stabalize */
+ msec_sleep(100);
+
+ /* Make sure that we re-initialize the measurement mode */
+ m->c_intclocks = 0;
+ m->c_lampclocks = 0;
+ m->c_nummeas = 0;
+ m->c_measmodeflags = 0;
+
+ return rv;
+}
+
+/* Read from the EEProm */
+i1pro_code
+i1pro_readEEProm(
+ i1pro *p,
+ unsigned char *buf, /* Where to read it to */
+ int addr, /* Address in EEprom to read from */
+ int size /* Number of bytes to read (max 65535) */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ int rwbytes; /* Data bytes read or written */
+ unsigned char pbuf[8]; /* Write EEprom parameters */
+ int len = 8; /* Message length */
+ int se, rv = I1PRO_OK;
+ int stime;
+
+ if (size >= 0x10000)
+ return I1PRO_INT_EETOOBIG;
+
+ a1logd(p->log,2,"i1pro_readEEProm: address 0x%x size 0x%x @ %d msec\n",
+ addr, size, (stime = msec_time()) - m->msec);
+
+ int2buf(&pbuf[0], addr);
+ short2buf(&pbuf[4], size);
+ pbuf[6] = pbuf[7] = 0; /* Ignored */
+
+ if (p->itype == instI1Pro2)
+ len = 6;
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC4, 0, 0, pbuf, len, 2.0);
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro_readEEProm: read failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ /* Now read the bytes */
+ se = p->icom->usb_read(p->icom, NULL, 0x82, buf, size, &rwbytes, 5.0);
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro_readEEProm: read failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ if (rwbytes != size) {
+ a1logd(p->log,1,"i1pro_readEEProm: 0x%x bytes, short read error\n",rwbytes);
+ return I1PRO_HW_EE_SHORTREAD;
+ }
+
+ if (p->log->debug >= 7) {
+ int i;
+ char oline[100], *bp = oline;
+ for (i = 0; i < size; i++) {
+ if ((i % 16) == 0)
+ bp += sprintf(bp," %04x:",i);
+ bp += sprintf(bp," %02x",buf[i]);
+ if ((i+1) >= size || ((i+1) % 16) == 0) {
+ bp += sprintf(bp,"\n");
+ a1logd(p->log,7,oline);
+ bp = oline;
+ }
+ }
+ }
+
+ a1logd(p->log,2,"i1pro_readEEProm: 0x%x bytes, ICOM err 0x%x (%d msec)\n",
+ rwbytes, se, msec_time()-stime);
+
+ return rv;
+}
+
+/* Write to the EEProm */
+i1pro_code
+i1pro_writeEEProm(
+ i1pro *p,
+ unsigned char *buf, /* Where to write from */
+ int addr, /* Address in EEprom to write to */
+ int size /* Number of bytes to write (max 65535) */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ int rwbytes; /* Data bytes read or written */
+ unsigned char pbuf[8]; /* Write EEprom parameters */
+ int len = 8; /* Message length */
+ int se = 0, rv = I1PRO_OK;
+ int i;
+ int stime;
+
+ /* Don't write over fixed values, as the instrument could become unusable.. */
+ if (addr < 0 || addr > 0x1000 || (addr + size) >= 0x1000)
+ return I1PRO_INT_EETOOBIG;
+
+ a1logd(p->log,2,"i1pro_writeEEProm: address 0x%x size 0x%x @ %d msec\n",
+ addr,size, (stime = msec_time()) - m->msec);
+
+ if (p->log->debug >= 6) {
+ int i;
+ char oline[100], *bp = oline;
+ for (i = 0; i < size; i++) {
+ if ((i % 16) == 0)
+ bp += sprintf(bp," %04x:",i);
+ bp += sprintf(bp," %02x",buf[i]);
+ if ((i+1) >= size || ((i+1) % 16) == 0) {
+ bp += sprintf(bp,"\n");
+ a1logd(p->log,6,oline);
+ bp = oline;
+ }
+ }
+ }
+
+#ifdef ENABLE_WRITE
+ int2buf(&pbuf[0], addr);
+ short2buf(&pbuf[4], size);
+ short2buf(&pbuf[6], 0x100); /* Might be accidental, left over from getmisc.. */
+
+ if (p->itype == instI1Pro2)
+ len = 6;
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC3, 0, 0, pbuf, len, 2.0);
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,2,"i1pro_writeEEProm: write failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ /* Now write the bytes */
+ se = p->icom->usb_write(p->icom, NULL, 0x03, buf, size, &rwbytes, 5.0);
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro_writeEEProm: failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ if (rwbytes != size) {
+ a1logd(p->log,1,"i1pro_writeEEProm: 0x%x bytes, short write error\n",rwbytes);
+ return I1PRO_HW_EE_SHORTWRITE;
+ }
+
+ /* Now we write two separate bytes of 0 - confirm write ?? */
+ for (i = 0; i < 2; i++) {
+ pbuf[0] = 0;
+
+ /* Now write the bytes */
+ se = p->icom->usb_write(p->icom, NULL, 0x03, pbuf, 1, &rwbytes, 5.0);
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro_writeEEProm: write failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ if (rwbytes != 1) {
+ a1logd(p->log,1,"i1pro_writeEEProm: 0x%x bytes, short write error\n",rwbytes);
+ return I1PRO_HW_EE_SHORTWRITE;
+ }
+ }
+ a1logd(p->log,2,"i1pro_writeEEProm: 0x%x bytes, ICOM err 0x%x (%d msec)\n",
+ size, se, msec_time()-stime);
+
+ /* The instrument needs some recovery time after a write */
+ msec_sleep(50);
+
+#else /* ENABLE_WRITE */
+
+ a1logd(p->log,2,"i1pro_writeEEProm: (NOT) 0x%x bytes, ICOM err 0x%x\n",size, se);
+
+#endif /* ENABLE_WRITE */
+
+ return rv;
+}
+
+/* Get the miscellaneous status */
+/* return pointers may be NULL if not needed. */
+i1pro_code
+i1pro_getmisc(
+ i1pro *p,
+ int *fwrev, /* Return the hardware version number */
+ int *unkn1, /* Unknown status, set after doing a measurement */
+ int *maxpve, /* Maximum positive value in sensor readings */
+ int *unkn3, /* Unknown status, usually 1 */
+ int *powmode /* 0 = high power mode, 8 = low power mode */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ unsigned char pbuf[8]; /* status bytes read */
+ int _fwrev;
+ int _unkn1;
+ int _maxpve;
+ int _unkn3;
+ int _powmode;
+ int se, rv = I1PRO_OK;
+ int stime = 0;
+
+ a1logd(p->log,2,"i1pro_getmisc: @ %d msec\n",(stime = msec_time()) - m->msec);
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC9, 0, 0, pbuf, 8, 2.0);
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro_getmisc: failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ _fwrev = buf2ushort(&pbuf[0]);
+ _unkn1 = buf2ushort(&pbuf[2]); /* Value set after each read. Average ?? */
+ _maxpve = buf2ushort(&pbuf[4]);
+ _unkn3 = pbuf[6]; /* Flag values are tested, but don't seem to be used ? */
+ _powmode = pbuf[7];
+
+ a1logd(p->log,2,"i1pro_getmisc: returning %d, 0x%04x, 0x%04x, 0x%02x, 0x%02x ICOM err 0x%x (%d msec)\n",
+ _fwrev, _unkn1, _maxpve, _unkn3, _powmode, se, msec_time()-stime);
+
+ if (fwrev != NULL) *fwrev = _fwrev;
+ if (unkn1 != NULL) *unkn1 = _unkn1;
+ if (maxpve != NULL) *maxpve = _maxpve;
+ if (unkn3 != NULL) *unkn3 = _unkn3;
+ if (powmode != NULL) *powmode = _powmode;
+
+ return rv;
+}
+
+/* Get the current measurement parameters */
+/* Return pointers may be NULL if not needed. */
+i1pro_code
+i1pro_getmeasparams(
+ i1pro *p,
+ int *intclocks, /* Number of integration clocks */
+ int *lampclocks, /* Number of lamp turn on sub-clocks */
+ int *nummeas, /* Number of measurements */
+ int *measmodeflags /* Measurement mode flags */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ unsigned char pbuf[8]; /* status bytes read */
+ int _intclocks;
+ int _lampclocks;
+ int _nummeas;
+ int _measmodeflags;
+ int se, rv = I1PRO_OK;
+ int stime = 0;
+
+ a1logd(p->log,2,"i1pro_getmeasparams: @ %d msec\n", (stime = msec_time()) - m->msec);
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC2, 0, 0, pbuf, 8, 2.0);
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro_getmeasparams: failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ _intclocks = buf2ushort(&pbuf[0]);
+ _lampclocks = buf2ushort(&pbuf[2]);
+ _nummeas = buf2ushort(&pbuf[4]);
+ _measmodeflags = pbuf[6];
+
+ a1logd(p->log,2,"i1pro_getmeasparams: returning %d, %d, %d, 0x%02x ICOM err 0x%x (%d msec)\n",
+ _intclocks, _lampclocks, _nummeas, _measmodeflags, se, msec_time()-stime);
+
+ if (intclocks != NULL) *intclocks = _intclocks;
+ if (lampclocks != NULL) *lampclocks = _lampclocks;
+ if (nummeas != NULL) *nummeas = _nummeas;
+ if (measmodeflags != NULL) *measmodeflags = _measmodeflags;
+
+ return rv;
+}
+
+/* Set the current measurement parameters */
+/* Return pointers may be NULL if not needed. */
+/* Quirks:
+
+ Rev. A upgrade:
+ Rev. B:
+ Appears to have a bug where the measurement time
+ is the sum of the previous measurement plus the current measurement.
+ It doesn't seem to alter the integration time though.
+ There is no obvious way of fixing this (ie. reseting the instrument
+ doesn't work).
+
+ Rev. D:
+ It appears that setting intclocks to 0, toggles to/from
+ a half clock speed mode. (?)
+*/
+
+i1pro_code
+i1pro_setmeasparams(
+ i1pro *p,
+ int intclocks, /* Number of integration clocks */
+ int lampclocks, /* Number of lamp turn on sub-clocks */
+ int nummeas, /* Number of measurements */
+ int measmodeflags /* Measurement mode flags */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ unsigned char pbuf[8]; /* command bytes written */
+ int se, rv = I1PRO_OK;
+ int stime = 0;
+
+ a1logd(p->log,2,"i1pro_setmeasparams: %d, %d, %d, 0x%02x @ %d msec\n",
+ intclocks, lampclocks, nummeas, measmodeflags,
+ (stime = msec_time()) - m->msec);
+
+ short2buf(&pbuf[0], intclocks);
+ short2buf(&pbuf[2], lampclocks);
+ short2buf(&pbuf[4], nummeas);
+ pbuf[6] = measmodeflags;
+ pbuf[7] = 0;
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC1, 0, 0, pbuf, 8, 2.0);
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro_setmeasparams: failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ a1logd(p->log,2,"i1pro_setmeasparams: returning ICOM err 0x%x (%d msec)\n",
+ se,msec_time()-stime);
+ return rv;
+}
+
+/* Delayed trigger implementation, called from thread */
+static int
+i1pro_delayed_trigger(void *pp) {
+ i1pro *p = (i1pro *)pp;
+ i1proimp *m = (i1proimp *)p->m;
+ int se, rv = I1PRO_OK;
+ int stime = 0;
+
+ if ((m->c_measmodeflags & I1PRO_MMF_NOLAMP) == 0) { /* Lamp will be on for measurement */
+ m->llampoffon = msec_time(); /* Record when it turned on */
+// printf("~1 got lamp off -> on at %d (%f)\n",m->llampoffon, (m->llampoffon - m->llamponoff)/1000.0);
+
+ }
+
+ a1logd(p->log,2,"i1pro_delayed_trigger: start sleep @ %d msec\n", msec_time() - m->msec);
+
+ /* Delay the trigger */
+ msec_sleep(m->trig_delay);
+
+ m->tr_t1 = msec_time(); /* Diagnostic */
+
+ a1logd(p->log,2,"i1pro_delayed_trigger: trigger @ %d msec\n",(stime = msec_time()) - m->msec);
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC0, 0, 0, NULL, 0, 2.0);
+
+ m->tr_t2 = msec_time(); /* Diagnostic */
+
+ m->trig_se = se;
+ m->trig_rv = icoms2i1pro_err(se);
+
+ a1logd(p->log,2,"i1pro_delayed_trigger: returning ICOM err 0x%x (%d msec)\n",
+ se,msec_time()-stime);
+
+ return 0;
+}
+
+/* Trigger a measurement after the nominated delay */
+/* The actual return code will be in m->trig_rv after the delay. */
+/* This allows us to start the measurement read before the trigger, */
+/* ensuring that process scheduling latency can't cause the read to fail. */
+i1pro_code
+i1pro_triggermeasure(i1pro *p, int delay) {
+ i1proimp *m = (i1proimp *)p->m;
+ int rv = I1PRO_OK;
+
+ a1logd(p->log,2,"i1pro_triggermeasure: trigger after %dmsec delay @ %d msec\n",
+ delay, msec_time() - m->msec);
+
+ /* NOTE := would be better here to create thread once, and then trigger it */
+ /* using a condition variable. */
+ if (m->trig_thread != NULL)
+ m->trig_thread->del(m->trig_thread);
+
+ m->tr_t1 = m->tr_t2 = m->tr_t3 = m->tr_t4 = m->tr_t5 = m->tr_t6 = m->tr_t7 = 0;
+ m->trig_delay = delay;
+
+ if ((m->trig_thread = new_athread(i1pro_delayed_trigger, (void *)p)) == NULL) {
+ a1logd(p->log,1,"i1pro_triggermeasure: creating delayed trigger thread failed\n");
+ return I1PRO_INT_THREADFAILED;
+ }
+
+#ifdef WAIT_FOR_DELAY_TRIGGER /* hack to diagnose threading problems */
+ while (m->tr_t2 == 0) {
+ Sleep(1);
+ }
+#endif
+ a1logd(p->log,2,"i1pro_triggermeasure: scheduled triggering OK\n");
+
+ return rv;
+}
+
+/* Read a measurements results. */
+/* A buffer full of bytes is returned. */
+/* (This will fail on a Rev. A if there is more than about a 40 msec delay */
+/* between triggering the measurement and starting this read. */
+/* It appears though that the read can be pending before triggering though. */
+/* Scan reads will also terminate if there is too great a delay beteween each read.) */
+i1pro_code
+i1pro_readmeasurement(
+ i1pro *p,
+ int inummeas, /* Initial number of measurements to expect */
+ int scanflag, /* NZ if in scan mode to continue reading */
+ unsigned char *buf, /* Where to read it to */
+ int bsize, /* Bytes available in buffer */
+ int *nummeas, /* Return number of readings measured */
+ i1p_mmodif mmodif /* Measurement modifier enum */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ unsigned char *ibuf = buf; /* Incoming buffer */
+ int nmeas; /* Number of measurements for this read */
+ double top, extra; /* Time out period */
+ int rwbytes; /* Data bytes read or written */
+ int se, rv = I1PRO_OK;
+ int treadings = 0;
+ int stime = 0;
+// int gotshort = 0; /* nz when got a previous short reading */
+
+ if ((bsize % (m->nsen * 2)) != 0) {
+ return I1PRO_INT_ODDREADBUF;
+ }
+
+ a1logd(p->log,2,"i1pro_readmeasurement: inummeas %d, scanflag %d, address %p bsize 0x%x "
+ "@ %d msec\n",inummeas, scanflag, buf, bsize, (stime = msec_time()) - m->msec);
+
+ extra = 2.0; /* Extra timeout margin */
+
+ /* Deal with Rev A+ & Rev B quirk: */
+ if ((m->fwrev >= 200 && m->fwrev < 300)
+ || (m->fwrev >= 300 && m->fwrev < 400))
+ extra += m->l_inttime;
+ m->l_inttime = m->c_inttime;
+
+#ifdef SINGLE_READ
+ if (scanflag == 0)
+ nmeas = inummeas;
+ else
+ nmeas = bsize / (m->nsen * 2); /* Use a single large read */
+#else
+ nmeas = inummeas; /* Smaller initial number of measurements */
+#endif
+
+ top = extra + m->c_inttime * nmeas;
+ if ((m->c_measmodeflags & I1PRO_MMF_NOLAMP) == 0) /* Lamp is on */
+ top += m->c_lamptime;
+
+ /* NOTE :- for a scan on Rev. A, if we don't read fast enough the Eye-One will */
+ /* assume it should stop sending, even though the user has the switch pressed. */
+ /* For the rev A, this is quite a small margin (aprox. 1 msec ?) */
+ /* The Rev D has a lot more buffering, and is quite robust. */
+ /* By using the delayed trigger and a single read, this problem is usually */
+ /* eliminated. */
+ /* An unexpected short read seems to lock the instrument up. Not currently */
+ /* sure what sequence would recover it for a retry of the read. */
+ for (;;) {
+ int size; /* number of bytes to read */
+
+ size = m->nsen * 2 * nmeas;
+
+ if (size > bsize) { /* oops, no room for read */
+ a1logd(p->log,1,"i1pro_readmeasurement: buffer was too short for scan\n");
+ return I1PRO_INT_MEASBUFFTOOSMALL;
+ }
+
+ m->tr_t6 = msec_time(); /* Diagnostic, start of subsequent reads */
+ if (m->tr_t3 == 0) m->tr_t3 = m->tr_t6; /* Diagnostic, start of first read */
+
+ se = p->icom->usb_read(p->icom, NULL, 0x82, buf, size, &rwbytes, top);
+
+ m->tr_t5 = m->tr_t7;
+ m->tr_t7 = msec_time(); /* Diagnostic, end of subsequent reads */
+ if (m->tr_t4 == 0) {
+ m->tr_t5 = m->tr_t2;
+ m->tr_t4 = m->tr_t7; /* Diagnostic, end of first read */
+ }
+
+#ifdef NEVER /* Use short + timeout to terminate scan */
+ if (gotshort != 0 && se == ICOM_TO) { /* We got a timeout after a short read. */
+ a1logd(p->log,2,"i1pro_readmeasurement: timed out in %f secs after getting short read\n",top);
+ a1logd(p->log,2,"i1pro_readmeasurement: trig & rd times %d %d %d %d)\n",
+ m->tr_t2-m->tr_t1, m->tr_t3-m->tr_t2, m->tr_t4-m->tr_t3, m->tr_t6-m->tr_t5);
+ break; /* We're done */
+ } else
+#endif
+ if (se == ICOM_SHORT) { /* Expect this to terminate scan reading */
+ a1logd(p->log,2,"i1pro_readmeasurement: short read, read %d bytes, asked for %d\n",
+ rwbytes,size);
+ a1logd(p->log,2,"i1pro_readmeasurement: trig & rd times %d %d %d %d)\n",
+ m->tr_t2-m->tr_t1, m->tr_t3-m->tr_t2, m->tr_t4-m->tr_t3, m->tr_t6-m->tr_t5);
+ } else if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ if (m->trig_rv != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro_readmeasurement: trigger failed, ICOM err 0x%x\n",
+ m->trig_se);
+ return m->trig_rv;
+ }
+ if (se & ICOM_TO)
+ a1logd(p->log,1,"i1pro_readmeasurement: timed out with top = %f\n",top);
+ a1logd(p->log,1,"i1pro_readmeasurement: failed, bytes read 0x%x, ICOM err 0x%x\n",
+ rwbytes, se);
+ return rv;
+ }
+
+ /* If we didn't read a multiple of m->nsen * 2, we've got problems */
+ if ((rwbytes % (m->nsen * 2)) != 0) {
+ a1logd(p->log,1,"i1pro_readmeasurement: read 0x%x bytes, odd read error\n",rwbytes);
+ return I1PRO_HW_ME_ODDREAD;
+ }
+
+ /* Track where we're up to */
+ bsize -= rwbytes;
+ buf += rwbytes;
+ treadings += rwbytes/(m->nsen * 2);
+
+ if (scanflag == 0) { /* Not scanning */
+
+ /* Expect to read exactly what we asked for */
+ if (rwbytes != size) {
+ a1logd(p->log,1,"i1pro_readmeasurement: unexpected short read, got %d expected %d\n"
+ ,rwbytes,size);
+ return I1PRO_HW_ME_SHORTREAD;
+ }
+ break; /* And we're done */
+ }
+
+#ifdef NEVER /* Use short + timeout to terminate scan */
+ /* We expect to get a short read at the end of a scan, */
+ /* or we might have the USB transfer truncated by somethinge else. */
+ /* Note the short read, and keep reading until we get a time out */
+ if (rwbytes != size) {
+ gotshort = 1;
+ } else {
+ gotshort = 0;
+ }
+#else /* Use short to terminate scan */
+ /* We're scanning and expect to get a short read at the end of the scan. */
+ if (rwbytes != size) {
+ break;
+ }
+#endif
+
+ if (bsize == 0) { /* oops, no room for more scanning read */
+ unsigned char tbuf[NSEN_MAX * 2];
+
+ /* We need to clean up, so soak up all the data and throw it away */
+ while ((se = p->icom->usb_read(p->icom, NULL, 0x82, tbuf, m->nsen * 2, &rwbytes, top)) == ICOM_OK)
+ ;
+ a1logd(p->log,1,"i1pro_readmeasurement: buffer was too short for scan\n");
+ return I1PRO_INT_MEASBUFFTOOSMALL;
+ }
+
+ /* Read a bunch more readings until the read is short or times out */
+ nmeas = bsize / (m->nsen * 2);
+ if (nmeas > 64)
+ nmeas = 64;
+ top = extra + m->c_inttime * nmeas;
+ }
+
+ if ((m->c_measmodeflags & I1PRO_MMF_NOLAMP) == 0) { /* Lamp was on for measurement */
+ m->slamponoff = m->llamponoff; /* remember second last */
+ m->llamponoff = msec_time(); /* Record when it turned off */
+// printf("~1 got lamp on -> off at %d (%f)\n",m->llamponoff, (m->llamponoff - m->llampoffon)/1000.0);
+ m->lampage += (m->llamponoff - m->llampoffon)/1000.0; /* Time lamp was on */
+ }
+ /* Update log values */
+ if (mmodif != i1p_dark_cal)
+ m->meascount++;
+
+ /* Must have timed out in initial readings */
+ if (treadings < inummeas) {
+ a1logd(p->log,1,"i1pro_readmeasurement: read failed, bytes read 0x%x, ICOM err 0x%x\n",
+ rwbytes, se);
+ return I1PRO_RD_SHORTMEAS;
+ }
+
+ if (p->log->debug >= 6) {
+ int i, size = treadings * m->nsen * 2;
+ char oline[100], *bp = oline;
+ for (i = 0; i < size; i++) {
+ if ((i % 16) == 0)
+ bp += sprintf(bp," %04x:",i);
+ bp += sprintf(bp," %02x",ibuf[i]);
+ if ((i+1) >= size || ((i+1) % 16) == 0) {
+ bp += sprintf(bp,"\n");
+ a1logd(p->log,6,oline);
+ bp = oline;
+ }
+ }
+ }
+
+ a1logd(p->log,2,"i1pro_readmeasurement: read %d readings, ICOM err 0x%x (%d msec)\n",
+ treadings, se, msec_time()-stime);
+ a1logd(p->log,2,"i1pro_readmeasurement: (trig & rd times %d %d %d %d)\n",
+ m->tr_t2-m->tr_t1, m->tr_t3-m->tr_t2, m->tr_t4-m->tr_t3, m->tr_t6-m->tr_t5);
+
+ if (nummeas != NULL) *nummeas = treadings;
+
+ return rv;
+}
+
+/* Set the measurement clock mode */
+/* Firmware Version >= 301 only */
+i1pro_code
+i1pro_setmcmode(
+ i1pro *p,
+ int mcmode /* Measurement clock mode, 1..mxmcmode */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ unsigned char pbuf[1]; /* 1 bytes to write */
+ int se, rv = I1PRO_OK;
+ int stime = 0;
+
+ a1logd(p->log,2,"i1pro_setmcmode: mode %d @ %d msec\n",
+ mcmode, (stime = msec_time()) - m->msec);
+
+ pbuf[0] = mcmode;
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xCF, 0, 0, pbuf, 1, 2.0);
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro_setmcmode: failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ a1logd(p->log,2,"i1pro_setmcmode: done, ICOM err 0x%x (%d msec)\n",
+ se, msec_time()-stime);
+ return rv;
+}
+
+/* Get the current measurement clock mode */
+/* Return pointers may be NULL if not needed. */
+/* Firmware Version >= 301 only */
+i1pro_code
+i1pro_getmcmode(
+ i1pro *p,
+ int *maxmcmode, /* mcmode must be <= maxmcmode */
+ int *mcmode, /* readback current mcmode */
+ int *subclkdiv, /* Sub clock divider ratio */
+ int *intclkusec, /* Integration clock in usec */
+ int *subtmode /* Subtract mode on read using average of value 127 */
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ unsigned char pbuf[8]; /* status bytes read */
+ int _maxmcmode; /* mcmode must be < maxmcmode */
+ int _mcmode; /* readback current mcmode */
+ int _unknown; /* Unknown */
+ int _subclkdiv; /* Sub clock divider ratio */
+ int _intclkusec; /* Integration clock in usec */
+ int _subtmode; /* Subtract mode on read using average of value 127 */
+ int se, rv = I1PRO_OK;
+ int stime = 0;
+
+ a1logd(p->log,2,"i1pro_getmcmode: called @ %d msec\n",
+ (stime = msec_time()) - m->msec);
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xD1, 0, 0, pbuf, 6, 2.0);
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro_getmcmode: failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ _maxmcmode = pbuf[0];
+ _mcmode = pbuf[1];
+ _unknown = pbuf[2];
+ _subclkdiv = pbuf[3];
+ _intclkusec = pbuf[4];
+ _subtmode = pbuf[5];
+
+ a1logd(p->log,2,"i1pro_getmcmode: returns %d, %d, (%d), %d, %d 0x%x ICOM err 0x%x (%d msec)\n",
+ _maxmcmode, _mcmode, _unknown, _subclkdiv, _intclkusec, _subtmode, se, msec_time()-stime);
+
+ if (maxmcmode != NULL) *maxmcmode = _maxmcmode;
+ if (mcmode != NULL) *mcmode = _mcmode;
+ if (subclkdiv != NULL) *subclkdiv = _subclkdiv;
+ if (intclkusec != NULL) *intclkusec = _intclkusec;
+ if (subtmode != NULL) *subtmode = _subtmode;
+
+ return rv;
+}
+
+
+/* Wait for a reply triggered by an instrument switch press */
+i1pro_code i1pro_waitfor_switch(i1pro *p, double top) {
+ i1proimp *m = (i1proimp *)p->m;
+ int rwbytes; /* Data bytes read */
+ unsigned char buf[8]; /* Result */
+ int se, rv = I1PRO_OK;
+ int stime = 0;
+
+ a1logd(p->log,2,"i1pro_waitfor_switch: read 1 byte from switch hit port @ %d msec\n",
+ (stime = msec_time()) - m->msec);
+
+ /* Now read 1 byte */
+ se = p->icom->usb_read(p->icom, NULL, 0x84, buf, 1, &rwbytes, top);
+
+ if (se & ICOM_TO) {
+ a1logd(p->log,2,"i1pro_waitfor_switch: read 0x%x bytes, timed out (%d msec)\n",
+ rwbytes,msec_time()-stime);
+ return I1PRO_INT_BUTTONTIMEOUT;
+ }
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro_waitfor_switch: failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ if (rwbytes != 1) {
+ a1logd(p->log,1,"i1pro_waitfor_switch: read 0x%x bytes, short read error (%d msec)\n",
+ rwbytes,msec_time()-stime);
+ return I1PRO_HW_SW_SHORTREAD;
+ }
+
+ a1logd(p->log,2,"i1pro_waitfor_switch: read 0x%x bytes value 0x%x ICOM err 0x%x (%d msec)\n",
+ rwbytes, buf[0], se, msec_time()-stime);
+
+ return rv;
+}
+
+/* Wait for a reply triggered by a key press (thread version) */
+/* Returns I1PRO_OK if the switch has been pressed, */
+/* or I1PRO_INT_BUTTONTIMEOUT if */
+/* no switch was pressed befor the time expired, */
+/* or some other error. */
+i1pro_code i1pro_waitfor_switch_th(i1pro *p, double top) {
+ i1proimp *m = (i1proimp *)p->m;
+ int rwbytes; /* Data bytes read */
+ unsigned char buf[8]; /* Result */
+ int se, rv = I1PRO_OK;
+ int stime = 0;
+
+ a1logd(p->log,2,"i1pro_waitfor_switch_th: read 1 byte from switch hit port @ %d msec\n",
+ (stime = msec_time()) - m->msec);
+
+ /* Now read 1 byte */
+ se = p->icom->usb_read(p->icom, &m->cancelt, 0x84, buf, 1, &rwbytes, top);
+
+ if (se & ICOM_TO) {
+ a1logd(p->log,2,"i1pro_waitfor_switch_th: read 0x%x bytes, timed out (%d msec)\n",
+ rwbytes,msec_time()-stime);
+ return I1PRO_INT_BUTTONTIMEOUT;
+ }
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,2,"i1pro_waitfor_switch_th: failed with ICOM err 0x%x (%d msec)\n",
+ se,msec_time()-stime);
+ return rv;
+ }
+
+ if (rwbytes != 1) {
+ a1logd(p->log,2,"i1pro_waitfor_switch_th: read 0x%x bytes, short read error (%d msec)\n",
+ rwbytes,msec_time()-stime);
+ return I1PRO_HW_SW_SHORTREAD;
+ }
+
+ a1logd(p->log,2,"i1pro_waitfor_switch_th: read 0x%x bytes value 0x%x ICOM err 0x%x (%d msec)\n",
+ rwbytes, buf[0], se,msec_time()-stime);
+
+ return rv;
+}
+
+/* Terminate switch handling */
+/* This seems to always return an error ? */
+i1pro_code
+i1pro_terminate_switch(
+ i1pro *p
+) {
+ i1proimp *m = (i1proimp *)p->m;
+ unsigned char pbuf[8]; /* 8 bytes to write */
+ int se, rv = I1PRO_OK;
+
+ a1logd(p->log,2,"i1pro_terminate_switch: called\n");
+
+ /* These values may not be significant */
+ pbuf[0] = pbuf[1] = pbuf[2] = pbuf[3] = 0xff;
+ pbuf[4] = 0xfc;
+ pbuf[5] = 0xee;
+ pbuf[6] = 0x12;
+ pbuf[7] = 0x00;
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xD0, 3, 0, pbuf, 8, 2.0);
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,2,"i1pro_terminate_switch: Warning: Terminate Switch Handling failed with ICOM err 0x%x\n",se);
+ } else {
+ a1logd(p->log,2,"i1pro_terminate_switch: done, ICOM err 0x%x\n",se);
+ }
+
+ /* In case the above didn't work, cancel the I/O */
+ msec_sleep(50);
+ if (m->th_termed == 0) {
+ a1logd(p->log,3,"i1pro terminate switch thread failed, canceling I/O\n");
+ p->icom->usb_cancel_io(p->icom, &m->cancelt);
+ }
+
+ return rv;
+}
+
+/* ============================================================ */
+/* Low level i1pro2 (Rev E) commands */
+
+/* Get the EEProm size */
+i1pro_code
+i1pro2_geteesize(
+ i1pro *p,
+ int *eesize
+) {
+ int se, rv = I1PRO_OK;
+ unsigned char buf[4]; /* Result */
+ int _eesize = 0;
+
+ a1logd(p->log,2,"i1pro2_geteesize: called\n");
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xD9, 0, 0, buf, 4, 2.0);
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro2_geteesize: failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ _eesize = buf2int(buf);
+
+ a1logd(p->log,2,"i1pro2_geteesize: returning %d ICOM err 0x%x\n", _eesize, se);
+
+ if (eesize != NULL)
+ *eesize = _eesize;
+
+ return rv;
+}
+
+/* Get the Chip ID */
+/* This does actually work with the Rev D. */
+/* (It returns all zero's unless you've read the EEProm first !) */
+i1pro_code
+i1pro2_getchipid(
+ i1pro *p,
+ unsigned char chipid[8]
+) {
+ int se, rv = I1PRO_OK;
+
+ a1logd(p->log,2,"i1pro2_getchipid: called\n");
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xD2, 0, 0, chipid, 8, 2.0);
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro2_getchipid: failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ a1logd(p->log,2,"i1pro2_getchipid: returning %02X-%02X%02X%02X%02X%02X%02X%02X ICOM err 0x%x\n",
+ chipid[0], chipid[1], chipid[2], chipid[3],
+ chipid[4], chipid[5], chipid[6], chipid[7], se);
+ return rv;
+}
+
+/* Get Rev E measure characteristics. */
+i1pro_code
+i1pro2_getmeaschar(
+ i1pro *p,
+ int *clkusec, /* Return integration clock length in usec ? (ie. 36) */
+ int *xraw, /* Return number of extra non-reading (dark) raw bands ? (ie. 6) */
+ int *nraw, /* Return number of reading raw bands ? (ie. 128) */
+ int *subdiv /* Sub divider and minium integration clocks ? (ie. 136) */
+) {
+ int se, rv = I1PRO_OK;
+ unsigned char buf[16]; /* Result */
+ int _clkusec;
+ int _xraw;
+ int _nraw;
+ int _subdiv;
+
+ a1logd(p->log,2,"i1pro2_getmeaschar: called\n");
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xD5, 0, 0, buf, 16, 2.0);
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro2_getmeaschar: failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ _clkusec = buf2int(buf + 0);
+ _xraw = buf2int(buf + 4);
+ _nraw = buf2int(buf + 8);
+ _subdiv = buf2int(buf + 12);
+
+ a1logd(p->log,2,"i1pro2_getmeaschar: returning clkusec %d, xraw %d, nraw %d, subdiv %d ICOM err 0x%x\n", _clkusec, _xraw, _nraw, _subdiv, se);
+
+ if (clkusec != NULL)
+ *clkusec = _clkusec;
+ if (xraw != NULL)
+ *xraw = _xraw;
+ if (nraw != NULL)
+ *nraw = _nraw;
+ if (subdiv != NULL)
+ *subdiv = _subdiv;
+
+ return rv;
+}
+
+/* Delayed trigger implementation, called from thread */
+/* We assume that the Rev E measurement parameters have been set in */
+/* the i1proimp structure c_* values */
+static int
+i1pro2_delayed_trigger(void *pp) {
+ i1pro *p = (i1pro *)pp;
+ i1proimp *m = (i1proimp *)p->m;
+ unsigned char pbuf[14]; /* 14 bytes to write */
+ int se, rv = I1PRO_OK;
+ int stime = 0;
+
+ int2buf(pbuf + 0, m->c_intclocks);
+ int2buf(pbuf + 4, m->c_lampclocks);
+ int2buf(pbuf + 8, m->c_nummeas);
+ short2buf(pbuf + 12, m->c_measmodeflags2);
+
+ if ((m->c_measmodeflags & I1PRO_MMF_NOLAMP) == 0) { /* Lamp will be on for measurement */
+ m->llampoffon = msec_time(); /* Record when it turned on */
+ }
+
+ a1logd(p->log,2,"i1pro2_delayed_trigger: Rev E start sleep @ %d msec\n",
+ msec_time() - m->msec);
+
+ /* Delay the trigger */
+ msec_sleep(m->trig_delay);
+
+ m->tr_t1 = msec_time(); /* Diagnostic */
+
+ a1logd(p->log,2,"i1pro2_delayed_trigger: trigger Rev E @ %d msec\n",
+ (stime = msec_time()) - m->msec);
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xD4, 0, 0, pbuf, 14, 2.0);
+
+ m->tr_t2 = msec_time(); /* Diagnostic */
+
+ m->trig_se = se;
+ m->trig_rv = icoms2i1pro_err(se);
+
+ a1logd(p->log,2,"i1pro2_delayed_trigger: done ICOM err 0x%x (%d msec)\n",
+ se,msec_time()-stime);
+ return 0;
+}
+
+/* Trigger a measurement after the nominated delay */
+/* The actual return code will be in m->trig_rv after the delay. */
+/* This allows us to start the measurement read before the trigger, */
+/* ensuring that process scheduling latency can't cause the read to fail. */
+i1pro_code
+i1pro2_triggermeasure(i1pro *p, int delay) {
+ i1proimp *m = (i1proimp *)p->m;
+ int rv = I1PRO_OK;
+
+ a1logd(p->log,2,"i1pro2_triggermeasure: triggering Rev Emeasurement after %dmsec "
+ "delay @ %d msec\n", delay, msec_time() - m->msec);
+
+ /* NOTE := would be better here to create thread once, and then trigger it */
+ /* using a condition variable. */
+ if (m->trig_thread != NULL)
+ m->trig_thread->del(m->trig_thread);
+
+ m->tr_t1 = m->tr_t2 = m->tr_t3 = m->tr_t4 = m->tr_t5 = m->tr_t6 = m->tr_t7 = 0;
+ m->trig_delay = delay;
+
+ if ((m->trig_thread = new_athread(i1pro2_delayed_trigger, (void *)p)) == NULL) {
+ a1logd(p->log,1,"i1pro2_triggermeasure: creating delayed trigger Rev E thread failed\n");
+ return I1PRO_INT_THREADFAILED;
+ }
+
+#ifdef WAIT_FOR_DELAY_TRIGGER /* hack to diagnose threading problems */
+ while (m->tr_t2 == 0) {
+ Sleep(1);
+ }
+#endif
+ a1logd(p->log,2,"i1pro2_triggermeasure: scheduled triggering Rev E OK\n");
+
+ return rv;
+}
+
+/* Get the UV before and after measurement voltage drop */
+i1pro_code
+i1pro2_getUVvolts(
+ i1pro *p,
+ int *before,
+ int *after
+) {
+ int se, rv = I1PRO_OK;
+ unsigned char buf[4]; /* Result */
+ int _before = 0;
+ int _after = 0;
+
+ a1logd(p->log,2,"i1pro2_getUVvolts: called\n");
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xD8, 0, 0, buf, 4, 2.0);
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro2_getUVvolts: failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ _before = buf2ushort(buf);
+ _after = buf2ushort(buf+2);
+
+ a1logd(p->log,2,"i1pro2_getUVvolts: returning %d, %d ICOM err 0x%x\n", _before, _after, se);
+
+ if (before != NULL)
+ *before = _before;
+
+ if (after != NULL)
+ *after = _after;
+
+ return rv;
+}
+
+/* Terminate Ruler tracking (???) */
+/* The parameter seems to be always 0 ? */
+static int
+i1pro2_stop_ruler(void *pp, int parm) {
+ i1pro *p = (i1pro *)pp;
+ i1proimp *m = (i1proimp *)p->m;
+ unsigned char pbuf[2]; /* 2 bytes to write */
+ int se, rv = I1PRO_OK;
+
+ short2buf(pbuf, parm);
+
+ a1logd(p->log,2,"i1pro2_stop_ruler: called with 0x%x\n", parm);
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xD7, 0, 0, pbuf, 2, 2.0);
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro2_stop_ruler: failed with ICOM err 0x%x\n",rv);
+ return rv;
+ }
+
+ a1logd(p->log,2,"i1pro2_stop_ruler: returning ICOM err 0x%x\n",rv);
+
+ return rv;
+}
+
+ /* Send a raw indicator LED sequence. */
+/*
+ The byte sequence has the following format:
+ (all values are big endian)
+
+ XXXX Number of following blocks, BE.
+
+ Blocks are:
+
+ YYYY Number of bytes in the block
+ RRRR Number of repeats of the block, FFFFFFFF = infinite
+
+ A sequence of codes:
+
+ LL TTTT
+ L = Led mask:
+ 01 = Red Right
+ 02 = Green Right
+ 04 = Blue Right
+ 08 = Red Left
+ 10 = Green Left
+ 20 = Blue Left
+ TTT = clock count for this mask (ie. aprox. 73 usec clock period)
+ PWM typically alternates between on & off state with a period total of
+ 0x50 clocks = 170 Hz. 255 clocks would be 54 Hz.
+
+ */
+
+static int
+i1pro2_indLEDseq(void *pp, unsigned char *buf, int size) {
+ i1pro *p = (i1pro *)pp;
+ i1proimp *m = (i1proimp *)p->m;
+ int rwbytes; /* Data bytes written */
+ unsigned char pbuf[4]; /* Number of bytes being send */
+ int se, rv = I1PRO_OK;
+
+ int2buf(pbuf, size);
+
+ a1logd(p->log,2,"i1pro2_indLEDseq: length %d bytes\n", size);
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xD6, 0, 0, pbuf, 4, 2.0);
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro2_indLEDseq: failed with ICOM err 0x%x\n",rv);
+ return rv;
+ }
+
+ a1logd(p->log,2,"i1pro2_geteesize: command got ICOM err 0x%x\n", se);
+
+ /* Now write the bytes */
+ se = p->icom->usb_write(p->icom, NULL, 0x03, buf, size, &rwbytes, 5.0);
+
+ if ((rv = icoms2i1pro_err(se)) != I1PRO_OK) {
+ a1logd(p->log,1,"i1pro2_indLEDseq: data write failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ if (rwbytes != size) {
+ a1logd(p->log,1,"i1pro2_indLEDseq: wrote 0x%x bytes, short write error\n",rwbytes);
+ return I1PRO_HW_LED_SHORTWRITE;
+ }
+
+ a1logd(p->log,2,"i1pro2_indLEDseq: wrote 0x%x bytes LED sequence, ICOM err 0x%x\n", size, rv);
+
+ return rv;
+}
+
+/* Turn indicator LEDs off */
+static int
+i1pro2_indLEDoff(void *pp) {
+ i1pro *p = (i1pro *)pp;
+ int rv = I1PRO_OK;
+ unsigned char seq[] = {
+ 0x00, 0x00, 0x00, 0x01,
+
+ 0x00, 0x00, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x10
+ };
+
+ a1logd(p->log,2,"i1pro2_indLEDoff: called\n");
+ rv = i1pro2_indLEDseq(p, seq, sizeof(seq));
+ a1logd(p->log,2,"i1pro2_indLEDoff: returning ICOM err 0x%x\n",rv);
+
+ return rv;
+}
+
+#ifdef NEVER
+
+ // ~~99 play with LED settings
+ if (p->itype == instI1Pro2) {
+
+ // LED is capable of white and red
+
+ /* Turns it off */
+ unsigned char b1[] = {
+ 0x00, 0x00, 0x00, 0x01,
+
+ 0x00, 0x00, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x01
+ };
+
+ /* Makes it white */
+ unsigned char b2[] = {
+ 0x00, 0x00, 0x00, 0x02,
+
+ 0x00, 0x00, 0x00, 0x0a,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x36, 0x00,
+ 0x00, 0x00, 0x01,
+
+ 0x00, 0x00, 0x00, 0x0a,
+ 0xff, 0xff, 0xff, 0xff,
+ 0x3f, 0x36, 0x40,
+ 0x00, 0x00, 0x01
+ };
+
+ /* Makes it pulsing white */
+ unsigned char b3[] = {
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x36, 0x40, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x08, 0xec, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43,
+ 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00,
+ 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00,
+ 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43,
+ 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00,
+ 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00,
+ 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43,
+ 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00,
+ 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00,
+ 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43,
+ 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00,
+ 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00,
+ 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43,
+ 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00,
+ 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00,
+ 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43,
+ 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00,
+ 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00,
+ 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43,
+ 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00,
+ 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00,
+ 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43,
+ 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00,
+ 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00,
+ 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43,
+ 0x3f, 0x00, 0x03, 0x00, 0x00, 0x50, 0x3f, 0x00, 0x03, 0x00, 0x00, 0x50, 0x3f, 0x00, 0x03, 0x00,
+ 0x00, 0x4d, 0x3f, 0x00, 0x03, 0x00, 0x00, 0x49, 0x3f, 0x00, 0x03, 0x00, 0x00, 0x45, 0x3f, 0x00,
+ 0x03, 0x00, 0x00, 0x42, 0x3f, 0x00, 0x03, 0x00, 0x00, 0x42, 0x3f, 0x00, 0x04, 0x00, 0x00, 0x4f,
+ 0x3f, 0x00, 0x04, 0x00, 0x00, 0x4d, 0x3f, 0x00, 0x04, 0x00, 0x00, 0x49, 0x3f, 0x00, 0x04, 0x00,
+ 0x00, 0x46, 0x3f, 0x00, 0x04, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x04, 0x00, 0x00, 0x41, 0x3f, 0x00,
+ 0x05, 0x00, 0x00, 0x4d, 0x3f, 0x00, 0x05, 0x00, 0x00, 0x49, 0x3f, 0x00, 0x05, 0x00, 0x00, 0x46,
+ 0x3f, 0x00, 0x05, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x06, 0x00, 0x00, 0x4d, 0x3f, 0x00, 0x06, 0x00,
+ 0x00, 0x4a, 0x3f, 0x00, 0x06, 0x00, 0x00, 0x47, 0x3f, 0x00, 0x06, 0x00, 0x00, 0x44, 0x3f, 0x00,
+ 0x06, 0x00, 0x00, 0x41, 0x3f, 0x00, 0x07, 0x00, 0x00, 0x49, 0x3f, 0x00, 0x07, 0x00, 0x00, 0x46,
+ 0x3f, 0x00, 0x07, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x08, 0x00, 0x00, 0x4a, 0x3f, 0x00, 0x07, 0x00,
+ 0x00, 0x3e, 0x3f, 0x00, 0x08, 0x00, 0x00, 0x44, 0x3f, 0x00, 0x09, 0x00, 0x00, 0x4a, 0x3f, 0x00,
+ 0x09, 0x00, 0x00, 0x47, 0x3f, 0x00, 0x09, 0x00, 0x00, 0x44, 0x3f, 0x00, 0x0a, 0x00, 0x00, 0x49,
+ 0x3f, 0x00, 0x09, 0x00, 0x00, 0x3f, 0x3f, 0x00, 0x09, 0x00, 0x00, 0x3d, 0x3f, 0x00, 0x0b, 0x00,
+ 0x00, 0x48, 0x3f, 0x00, 0x0b, 0x00, 0x00, 0x45, 0x3f, 0x00, 0x0a, 0x00, 0x00, 0x3c, 0x3f, 0x00,
+ 0x0c, 0x00, 0x00, 0x46, 0x3f, 0x00, 0x0c, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x0d, 0x00, 0x00, 0x46,
+ 0x3f, 0x00, 0x0c, 0x00, 0x00, 0x3e, 0x3f, 0x00, 0x0c, 0x00, 0x00, 0x3c, 0x3f, 0x00, 0x0d, 0x00,
+ 0x00, 0x3f, 0x3f, 0x00, 0x0d, 0x00, 0x00, 0x3d, 0x3f, 0x00, 0x0e, 0x00, 0x00, 0x3f, 0x3f, 0x00,
+ 0x0e, 0x00, 0x00, 0x3d, 0x3f, 0x00, 0x0e, 0x00, 0x00, 0x3b, 0x3f, 0x00, 0x0f, 0x00, 0x00, 0x3d,
+ 0x3f, 0x00, 0x0e, 0x00, 0x00, 0x37, 0x3f, 0x00, 0x0f, 0x00, 0x00, 0x39, 0x3f, 0x00, 0x10, 0x00,
+ 0x00, 0x3b, 0x3f, 0x00, 0x12, 0x00, 0x00, 0x40, 0x3f, 0x00, 0x10, 0x00, 0x00, 0x37, 0x3f, 0x00,
+ 0x13, 0x00, 0x00, 0x3f, 0x3f, 0x00, 0x13, 0x00, 0x00, 0x3d, 0x3f, 0x00, 0x11, 0x00, 0x00, 0x34,
+ 0x3f, 0x00, 0x12, 0x00, 0x00, 0x36, 0x3f, 0x00, 0x12, 0x00, 0x00, 0x34, 0x3f, 0x00, 0x14, 0x00,
+ 0x00, 0x38, 0x3f, 0x00, 0x14, 0x00, 0x00, 0x36, 0x3f, 0x00, 0x17, 0x00, 0x00, 0x3c, 0x3f, 0x00,
+ 0x17, 0x00, 0x00, 0x3a, 0x3f, 0x00, 0x18, 0x00, 0x00, 0x3a, 0x3f, 0x00, 0x15, 0x00, 0x00, 0x31,
+ 0x3f, 0x00, 0x17, 0x00, 0x00, 0x34, 0x3f, 0x00, 0x16, 0x00, 0x00, 0x30, 0x3f, 0x00, 0x1a, 0x00,
+ 0x00, 0x37, 0x3f, 0x00, 0x1b, 0x00, 0x00, 0x37, 0x3f, 0x00, 0x18, 0x00, 0x00, 0x2f, 0x3f, 0x00,
+ 0x1c, 0x00, 0x00, 0x35, 0x3f, 0x00, 0x1c, 0x00, 0x00, 0x33, 0x3f, 0x00, 0x1c, 0x00, 0x00, 0x31,
+ 0x3f, 0x00, 0x1d, 0x00, 0x00, 0x31, 0x3f, 0x00, 0x1b, 0x00, 0x00, 0x2c, 0x3f, 0x00, 0x1c, 0x00,
+ 0x00, 0x2c, 0x3f, 0x00, 0x1d, 0x00, 0x00, 0x2c, 0x3f, 0x00, 0x1e, 0x00, 0x00, 0x2c, 0x3f, 0x00,
+ 0x1d, 0x00, 0x00, 0x29, 0x3f, 0x00, 0x1e, 0x00, 0x00, 0x29, 0x3f, 0x00, 0x23, 0x00, 0x00, 0x2e,
+ 0x3f, 0x00, 0x22, 0x00, 0x00, 0x2b, 0x3f, 0x00, 0x25, 0x00, 0x00, 0x2d, 0x3f, 0x00, 0x24, 0x00,
+ 0x00, 0x2a, 0x3f, 0x00, 0x22, 0x00, 0x00, 0x26, 0x3f, 0x00, 0x27, 0x00, 0x00, 0x2a, 0x3f, 0x00,
+ 0x22, 0x00, 0x00, 0x23, 0x3f, 0x00, 0x23, 0x00, 0x00, 0x23, 0x3f, 0x00, 0x2a, 0x00, 0x00, 0x28,
+ 0x3f, 0x00, 0x24, 0x00, 0x00, 0x21, 0x3f, 0x00, 0x29, 0x00, 0x00, 0x24, 0x3f, 0x00, 0x2c, 0x00,
+ 0x00, 0x25, 0x3f, 0x00, 0x28, 0x00, 0x00, 0x20, 0x3f, 0x00, 0x2b, 0x00, 0x00, 0x21, 0x3f, 0x00,
+ 0x2d, 0x00, 0x00, 0x21, 0x3f, 0x00, 0x2b, 0x00, 0x00, 0x1e, 0x3f, 0x00, 0x2a, 0x00, 0x00, 0x1c,
+ 0x3f, 0x00, 0x2f, 0x00, 0x00, 0x1e, 0x3f, 0x00, 0x2b, 0x00, 0x00, 0x1a, 0x3f, 0x00, 0x2d, 0x00,
+ 0x00, 0x1a, 0x3f, 0x00, 0x31, 0x00, 0x00, 0x1b, 0x3f, 0x00, 0x2e, 0x00, 0x00, 0x18, 0x3f, 0x00,
+ 0x37, 0x00, 0x00, 0x1b, 0x3f, 0x00, 0x38, 0x00, 0x00, 0x1a, 0x3f, 0x00, 0x37, 0x00, 0x00, 0x18,
+ 0x3f, 0x00, 0x31, 0x00, 0x00, 0x14, 0x3f, 0x00, 0x39, 0x00, 0x00, 0x16, 0x3f, 0x00, 0x3d, 0x00,
+ 0x00, 0x16, 0x3f, 0x00, 0x36, 0x00, 0x00, 0x12, 0x3f, 0x00, 0x3a, 0x00, 0x00, 0x12, 0x3f, 0x00,
+ 0x3b, 0x00, 0x00, 0x11, 0x3f, 0x00, 0x40, 0x00, 0x00, 0x11, 0x3f, 0x00, 0x3a, 0x00, 0x00, 0x0e,
+ 0x3f, 0x00, 0x3b, 0x00, 0x00, 0x0d, 0x3f, 0x00, 0x3c, 0x00, 0x00, 0x0c, 0x3f, 0x00, 0x3d, 0x00,
+ 0x00, 0x0b, 0x3f, 0x00, 0x3e, 0x00, 0x00, 0x0a, 0x3f, 0x00, 0x3f, 0x00, 0x00, 0x09, 0x3f, 0x00,
+ 0x40, 0x00, 0x00, 0x08, 0x3f, 0x00, 0x42, 0x00, 0x00, 0x07, 0x3f, 0x00, 0x44, 0x00, 0x00, 0x06,
+ 0x3f, 0x00, 0x47, 0x00, 0x00, 0x05, 0x3f, 0x00, 0x4b, 0x00, 0x00, 0x04, 0x3f, 0x00, 0x50, 0x00,
+ 0x00, 0x03, 0x3f, 0x00, 0x44, 0x00, 0x00, 0x01, 0x3f, 0x00, 0x45, 0x3f, 0x00, 0x45, 0x3f, 0x00,
+ 0x45, 0x3f, 0x00, 0x45, 0x3f, 0x00, 0x44, 0x00, 0x00, 0x01, 0x3f, 0x00, 0x50, 0x00, 0x00, 0x03,
+ 0x3f, 0x00, 0x4b, 0x00, 0x00, 0x04, 0x3f, 0x00, 0x47, 0x00, 0x00, 0x05, 0x3f, 0x00, 0x44, 0x00,
+ 0x00, 0x06, 0x3f, 0x00, 0x42, 0x00, 0x00, 0x07, 0x3f, 0x00, 0x40, 0x00, 0x00, 0x08, 0x3f, 0x00,
+ 0x3f, 0x00, 0x00, 0x09, 0x3f, 0x00, 0x3e, 0x00, 0x00, 0x0a, 0x3f, 0x00, 0x3d, 0x00, 0x00, 0x0b,
+ 0x3f, 0x00, 0x3c, 0x00, 0x00, 0x0c, 0x3f, 0x00, 0x3b, 0x00, 0x00, 0x0d, 0x3f, 0x00, 0x3a, 0x00,
+ 0x00, 0x0e, 0x3f, 0x00, 0x40, 0x00, 0x00, 0x11, 0x3f, 0x00, 0x3b, 0x00, 0x00, 0x11, 0x3f, 0x00,
+ 0x3a, 0x00, 0x00, 0x12, 0x3f, 0x00, 0x36, 0x00, 0x00, 0x12, 0x3f, 0x00, 0x3d, 0x00, 0x00, 0x16,
+ 0x3f, 0x00, 0x39, 0x00, 0x00, 0x16, 0x3f, 0x00, 0x31, 0x00, 0x00, 0x14, 0x3f, 0x00, 0x37, 0x00,
+ 0x00, 0x18, 0x3f, 0x00, 0x38, 0x00, 0x00, 0x1a, 0x3f, 0x00, 0x37, 0x00, 0x00, 0x1b, 0x3f, 0x00,
+ 0x2e, 0x00, 0x00, 0x18, 0x3f, 0x00, 0x31, 0x00, 0x00, 0x1b, 0x3f, 0x00, 0x2d, 0x00, 0x00, 0x1a,
+ 0x3f, 0x00, 0x2b, 0x00, 0x00, 0x1a, 0x3f, 0x00, 0x2f, 0x00, 0x00, 0x1e, 0x3f, 0x00, 0x2a, 0x00,
+ 0x00, 0x1c, 0x3f, 0x00, 0x2b, 0x00, 0x00, 0x1e, 0x3f, 0x00, 0x2d, 0x00, 0x00, 0x21, 0x3f, 0x00,
+ 0x2b, 0x00, 0x00, 0x21, 0x3f, 0x00, 0x28, 0x00, 0x00, 0x20, 0x3f, 0x00, 0x2c, 0x00, 0x00, 0x25,
+ 0x3f, 0x00, 0x29, 0x00, 0x00, 0x24, 0x3f, 0x00, 0x24, 0x00, 0x00, 0x21, 0x3f, 0x00, 0x2a, 0x00,
+ 0x00, 0x28, 0x3f, 0x00, 0x23, 0x00, 0x00, 0x23, 0x3f, 0x00, 0x22, 0x00, 0x00, 0x23, 0x3f, 0x00,
+ 0x27, 0x00, 0x00, 0x2a, 0x3f, 0x00, 0x22, 0x00, 0x00, 0x26, 0x3f, 0x00, 0x24, 0x00, 0x00, 0x2a,
+ 0x3f, 0x00, 0x25, 0x00, 0x00, 0x2d, 0x3f, 0x00, 0x22, 0x00, 0x00, 0x2b, 0x3f, 0x00, 0x23, 0x00,
+ 0x00, 0x2e, 0x3f, 0x00, 0x1e, 0x00, 0x00, 0x29, 0x3f, 0x00, 0x1d, 0x00, 0x00, 0x29, 0x3f, 0x00,
+ 0x1e, 0x00, 0x00, 0x2c, 0x3f, 0x00, 0x1d, 0x00, 0x00, 0x2c, 0x3f, 0x00, 0x1c, 0x00, 0x00, 0x2c,
+ 0x3f, 0x00, 0x1b, 0x00, 0x00, 0x2c, 0x3f, 0x00, 0x1d, 0x00, 0x00, 0x31, 0x3f, 0x00, 0x1c, 0x00,
+ 0x00, 0x31, 0x3f, 0x00, 0x1c, 0x00, 0x00, 0x33, 0x3f, 0x00, 0x1c, 0x00, 0x00, 0x35, 0x3f, 0x00,
+ 0x18, 0x00, 0x00, 0x2f, 0x3f, 0x00, 0x1b, 0x00, 0x00, 0x37, 0x3f, 0x00, 0x1a, 0x00, 0x00, 0x37,
+ 0x3f, 0x00, 0x16, 0x00, 0x00, 0x30, 0x3f, 0x00, 0x17, 0x00, 0x00, 0x34, 0x3f, 0x00, 0x15, 0x00,
+ 0x00, 0x31, 0x3f, 0x00, 0x18, 0x00, 0x00, 0x3a, 0x3f, 0x00, 0x17, 0x00, 0x00, 0x3a, 0x3f, 0x00,
+ 0x17, 0x00, 0x00, 0x3c, 0x3f, 0x00, 0x14, 0x00, 0x00, 0x36, 0x3f, 0x00, 0x14, 0x00, 0x00, 0x38,
+ 0x3f, 0x00, 0x12, 0x00, 0x00, 0x34, 0x3f, 0x00, 0x12, 0x00, 0x00, 0x36, 0x3f, 0x00, 0x11, 0x00,
+ 0x00, 0x34, 0x3f, 0x00, 0x13, 0x00, 0x00, 0x3d, 0x3f, 0x00, 0x13, 0x00, 0x00, 0x3f, 0x3f, 0x00,
+ 0x10, 0x00, 0x00, 0x37, 0x3f, 0x00, 0x12, 0x00, 0x00, 0x40, 0x3f, 0x00, 0x10, 0x00, 0x00, 0x3b,
+ 0x3f, 0x00, 0x0f, 0x00, 0x00, 0x39, 0x3f, 0x00, 0x0e, 0x00, 0x00, 0x37, 0x3f, 0x00, 0x0f, 0x00,
+ 0x00, 0x3d, 0x3f, 0x00, 0x0e, 0x00, 0x00, 0x3b, 0x3f, 0x00, 0x0e, 0x00, 0x00, 0x3d, 0x3f, 0x00,
+ 0x0e, 0x00, 0x00, 0x3f, 0x3f, 0x00, 0x0d, 0x00, 0x00, 0x3d, 0x3f, 0x00, 0x0d, 0x00, 0x00, 0x3f,
+ 0x3f, 0x00, 0x0c, 0x00, 0x00, 0x3c, 0x3f, 0x00, 0x0c, 0x00, 0x00, 0x3e, 0x3f, 0x00, 0x0d, 0x00,
+ 0x00, 0x46, 0x3f, 0x00, 0x0c, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x0c, 0x00, 0x00, 0x46, 0x3f, 0x00,
+ 0x0a, 0x00, 0x00, 0x3c, 0x3f, 0x00, 0x0b, 0x00, 0x00, 0x45, 0x3f, 0x00, 0x0b, 0x00, 0x00, 0x48,
+ 0x3f, 0x00, 0x09, 0x00, 0x00, 0x3d, 0x3f, 0x00, 0x09, 0x00, 0x00, 0x3f, 0x3f, 0x00, 0x0a, 0x00,
+ 0x00, 0x49, 0x3f, 0x00, 0x09, 0x00, 0x00, 0x44, 0x3f, 0x00, 0x09, 0x00, 0x00, 0x47, 0x3f, 0x00,
+ 0x09, 0x00, 0x00, 0x4a, 0x3f, 0x00, 0x08, 0x00, 0x00, 0x44, 0x3f, 0x00, 0x07, 0x00, 0x00, 0x3e,
+ 0x3f, 0x00, 0x08, 0x00, 0x00, 0x4a, 0x3f, 0x00, 0x07, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x07, 0x00,
+ 0x00, 0x46, 0x3f, 0x00, 0x07, 0x00, 0x00, 0x49, 0x3f, 0x00, 0x06, 0x00, 0x00, 0x41, 0x3f, 0x00,
+ 0x06, 0x00, 0x00, 0x44, 0x3f, 0x00, 0x06, 0x00, 0x00, 0x47, 0x3f, 0x00, 0x06, 0x00, 0x00, 0x4a,
+ 0x3f, 0x00, 0x06, 0x00, 0x00, 0x4d, 0x3f, 0x00, 0x05, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x05, 0x00,
+ 0x00, 0x46, 0x3f, 0x00, 0x05, 0x00, 0x00, 0x49, 0x3f, 0x00, 0x05, 0x00, 0x00, 0x4d, 0x3f, 0x00,
+ 0x04, 0x00, 0x00, 0x41, 0x3f, 0x00, 0x04, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x04, 0x00, 0x00, 0x46,
+ 0x3f, 0x00, 0x04, 0x00, 0x00, 0x49, 0x3f, 0x00, 0x04, 0x00, 0x00, 0x4d, 0x3f, 0x00, 0x04, 0x00,
+ 0x00, 0x4f, 0x3f, 0x00, 0x03, 0x00, 0x00, 0x42, 0x3f, 0x00, 0x03, 0x00, 0x00, 0x42, 0x3f, 0x00,
+ 0x03, 0x00, 0x00, 0x45, 0x3f, 0x00, 0x03, 0x00, 0x00, 0x49, 0x3f, 0x00, 0x03, 0x00, 0x00, 0x4d,
+ 0x3f, 0x00, 0x03, 0x00, 0x00, 0x50, 0x3f, 0x00, 0x03, 0x00, 0x00, 0x50, 0x3f, 0x00, 0x02, 0x00,
+ 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00,
+ 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43,
+ 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00,
+ 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00,
+ 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43,
+ 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00,
+ 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00,
+ 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43,
+ 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00,
+ 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00,
+ 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43,
+ 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00,
+ 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00,
+ 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43,
+ 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00,
+ 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00,
+ 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43,
+ 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00,
+ 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00,
+ 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43,
+ 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00,
+ 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00,
+ 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43,
+ 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00, 0x00, 0x43, 0x3f, 0x00, 0x02, 0x00,
+ 0x00, 0x43
+ };
+
+ unsigned char b4[] = {
+ 0x00, 0x00, 0x00, 0x01, // blocks
+
+ 0x00, 0x00, 0x00, 0x0a, // bytes
+ 0x00, 0x00, 0x00, 0x04, // repeat
+// 0xff, 0xff, 0xff, 0xff, // repeat
+// 0x3f, 0x00, 0x04, // Level ????
+// 0x00, 0x00, 0x3c // msec ????
+ 0x3f, 0x20, 0x00, // "
+ 0x00, 0x20, 0x00 // LED mask + clocks
+ };
+
+ printf("~1 send led sequence length %d\n",sizeof(b4));
+ if ((ev = i1pro2_indLEDseq(p, b4, sizeof(b4))) != I1PRO_OK)
+ return ev;
+ }
+#endif /* NEVER */
+
+/* ============================================================ */
+/* key/value dictionary support for EEProm contents */
+
+/* Search the linked list for the given key */
+/* Return NULL if not found */
+static i1keyv *i1data_find_key(i1data *d, i1key key) {
+ i1keyv *k;
+
+ for (k = d->head; k != NULL; k = k->next) {
+ if (k->key == key)
+ return k;
+ }
+ return NULL;
+}
+
+/* Search the linked list for the given key and */
+/* return it, or add it to the list if it doesn't exist. */
+/* Return NULL on error */
+static i1keyv *i1data_make_key(i1data *d, i1key key) {
+ i1keyv *k;
+
+ for (k = d->head; k != NULL; k = k->next)
+ if (k->key == key)
+ return k;
+
+ if ((k = (i1keyv *)calloc(1, sizeof(i1keyv))) == NULL) {
+ a1logw(d->log, "i1data: malloc failed!\n");
+ return NULL;
+ }
+
+ k->key = key;
+ k->next = NULL;
+ if (d->last == NULL) {
+ d->head = d->last = k;
+ } else {
+ d->last->next = k;
+ d->last = k;
+ }
+ return k;
+}
+
+/* Return type of data associated with key. Return i1_dtype_unknown if not found */
+static i1_dtype i1data_get_type(i1data *d, i1key key) {
+ i1keyv *k;
+
+ if ((k = d->find_key(d, key)) != NULL)
+ return k->type;
+ return i1_dtype_unknown;
+}
+
+/* Return the number of data items in a keyv. Return 0 if not found */
+static unsigned int i1data_get_count(i1data *d, i1key key) {
+ i1keyv *k;
+
+ if ((k = d->find_key(d, key)) != NULL)
+ return k->count;
+ return 0;
+}
+
+/* Return a pointer to the short data for the key. */
+/* Return NULL if not found or wrong type */
+static int *i1data_get_shorts(i1data *d, unsigned int *count, i1key key) {
+ i1keyv *k;
+
+ if ((k = d->find_key(d, key)) == NULL)
+ return NULL;
+
+ if (k->type != i1_dtype_short)
+ return NULL;
+
+ if (count != NULL)
+ *count = k->count;
+
+ return (int *)k->data;
+}
+
+/* Return a pointer to the int data for the key. */
+/* Return NULL if not found or wrong type */
+static int *i1data_get_ints(i1data *d, unsigned int *count, i1key key) {
+ i1keyv *k;
+
+ if ((k = d->find_key(d, key)) == NULL)
+ return NULL;
+
+ if (k->type != i1_dtype_int)
+ return NULL;
+
+ if (count != NULL)
+ *count = k->count;
+
+ return (int *)k->data;
+}
+
+/* Return a pointer to the double data for the key. */
+/* Return NULL if not found or wrong type */
+static double *i1data_get_doubles(i1data *d, unsigned int *count, i1key key) {
+ i1keyv *k;
+
+ if ((k = d->find_key(d, key)) == NULL)
+ return NULL;
+
+ if (k->type != i1_dtype_double)
+ return NULL;
+
+ if (count != NULL)
+ *count = k->count;
+
+ return (double *)k->data;
+}
+
+
+/* Return pointer to one of the int data for the key. */
+/* Return NULL if not found or wrong type or out of range index. */
+static int *i1data_get_int(i1data *d, i1key key, unsigned int index) {
+ i1keyv *k;
+
+ if ((k = d->find_key(d, key)) == NULL)
+ return NULL;
+
+ if (k->type != i1_dtype_int)
+ return NULL;
+
+ if (index >= k->count)
+ return NULL;
+
+ return ((int *)k->data) + index;
+}
+
+/* Return pointer to one of the double data for the key. */
+/* Return NULL if not found or wrong type or out of range index. */
+static double *i1data_get_double(i1data *d, i1key key, double *data, unsigned int index) {
+ i1keyv *k;
+
+ if ((k = d->find_key(d, key)) == NULL)
+ return NULL;
+
+ if (k->type != i1_dtype_double)
+ return NULL;
+
+ if (index >= k->count)
+ return NULL;
+
+ return ((double *)k->data) + index;
+}
+
+/* Un-serialize a char buffer into an i1key keyv */
+static i1pro_code i1data_unser_shorts(
+ i1data *d,
+ i1key key,
+ int addr,
+ unsigned char *buf,
+ unsigned int size
+) {
+ i1keyv *k;
+ int i, count;
+
+ count = size/2;
+
+ if (count == 0)
+ return I1PRO_DATA_COUNT;
+
+ if ((k = d->make_key(d, key)) == NULL)
+ return I1PRO_DATA_MAKE_KEY;
+
+ if (k->data != NULL)
+ free(k->data);
+
+ if ((k->data = (void *)malloc(sizeof(int) * count)) == NULL)
+ return I1PRO_DATA_MEMORY;
+
+ for (i = 0; i < count; i++, buf += 2) {
+ ((int *)k->data)[i] = buf2short(buf);
+ }
+
+ k->count = count;
+ k->size = size;
+ k->type = i1_dtype_short;
+ if (addr != -1)
+ k->addr = addr;
+
+ return I1PRO_OK;
+}
+
+/* Un-serialize a char buffer into an i1key keyv */
+static i1pro_code i1data_unser_ints(
+ i1data *d,
+ i1key key,
+ int addr,
+ unsigned char *buf,
+ unsigned int size
+) {
+ i1keyv *k;
+ int i, count;
+
+ count = size/4;
+
+ if (count == 0)
+ return I1PRO_DATA_COUNT;
+
+ if ((k = d->make_key(d, key)) == NULL)
+ return I1PRO_DATA_MAKE_KEY;
+
+ if (k->data != NULL)
+ free(k->data);
+
+ if ((k->data = (void *)malloc(sizeof(int) * count)) == NULL)
+ return I1PRO_DATA_MEMORY;
+
+ for (i = 0; i < count; i++, buf += 4) {
+ ((int *)k->data)[i] = buf2int(buf);
+ }
+
+ k->count = count;
+ k->size = size;
+ k->type = i1_dtype_int;
+ if (addr != -1)
+ k->addr = addr;
+
+ return I1PRO_OK;
+}
+
+/* Create an entry for an end of section marker */
+static i1pro_code i1data_add_eosmarker(
+ i1data *d,
+ i1key key, /* section number */
+ int addr
+) {
+ i1keyv *k;
+
+ if ((k = d->make_key(d, key)) == NULL)
+ return I1PRO_DATA_MAKE_KEY;
+
+ if (k->data != NULL) {
+ free(k->data);
+ k->data = NULL;
+ }
+
+ k->count = 0;
+ k->size = 0;
+ k->type = i1_dtype_section;
+ if (addr != -1)
+ k->addr = addr;
+
+ return I1PRO_OK;
+}
+
+/* Un-serialize a char buffer of floats into a double keyv */
+static i1pro_code i1data_unser_doubles(
+ i1data *d,
+ i1key key,
+ int addr,
+ unsigned char *buf,
+ unsigned int size
+) {
+ i1keyv *k;
+ int i, count;
+
+ count = size/4;
+
+ if (count == 0)
+ return I1PRO_DATA_COUNT;
+
+ if ((k = d->make_key(d, key)) == NULL)
+ return I1PRO_DATA_MAKE_KEY;
+
+ if (k->data != NULL)
+ free(k->data);
+
+ if ((k->data = (void *)malloc(sizeof(double) * count)) == NULL)
+ return I1PRO_DATA_MEMORY;
+
+ for (i = 0; i < count; i++, buf += 4) {
+ int val;
+ val = buf2int(buf);
+ ((double *)k->data)[i] = IEEE754todouble((unsigned int)val);
+ }
+
+ k->count = count;
+ k->size = size;
+ k->type = i1_dtype_double;
+ if (addr != -1)
+ k->addr = addr;
+
+ return I1PRO_OK;
+}
+
+
+/* Serialize an i1key keyv into a char buffer. Error if it is outside the buffer */
+static i1pro_code i1data_ser_ints(
+ i1data *d,
+ i1keyv *k,
+ unsigned char *buf,
+ unsigned int size
+) {
+ i1pro *p = d->p;
+ int i, len;
+
+ if (k->type != i1_dtype_int)
+ return I1PRO_DATA_WRONGTYPE;
+
+ len = k->count * 4;
+ if (len > k->size)
+ return I1PRO_DATA_BUFSIZE;
+
+ if (k->addr < 0 || k->addr >= size || (k->addr + k->size) > size)
+ return I1PRO_DATA_BUFSIZE;
+
+ buf += k->addr;
+ for (i = 0; i < k->count; i++, buf += 4) {
+ int2buf(buf, ((int *)k->data)[i]);
+ }
+
+ return I1PRO_OK;
+}
+
+/* Serialize a double keyv as floats into a char buffer. Error if the buf is not big enough */
+static i1pro_code i1data_ser_doubles(
+ i1data *d,
+ i1keyv *k,
+ unsigned char *buf,
+ unsigned int size
+) {
+ i1pro *p = d->p;
+ int i, len;
+
+ if (k->type != i1_dtype_double)
+ return I1PRO_DATA_WRONGTYPE;
+
+ len = k->count * 4;
+ if (len > k->size)
+ return I1PRO_DATA_BUFSIZE;
+
+ if (k->addr < 0 || k->addr >= size || (k->addr + k->size) > size)
+ return I1PRO_DATA_BUFSIZE;
+
+ buf += k->addr;
+ for (i = 0; i < k->count; i++, buf += 4) {
+ int2buf(buf, doubletoIEEE754(((double *)k->data)[i]));
+ }
+
+ return I1PRO_OK;
+}
+
+/* Copy an array full of ints to the key */
+/* Note the count must be the same as the existing key value, */
+/* since we are not prepared to re-allocate key/values within */
+/* the EEProm, or re-write the directory. */
+static i1pro_code i1data_add_ints(i1data *d, i1key key, int *data, unsigned int count) {
+ i1keyv *k;
+ int i;
+
+ if ((k = d->make_key(d, key)) == NULL)
+ return I1PRO_DATA_MAKE_KEY;
+
+ if (count != k->count)
+ return I1PRO_DATA_COUNT;
+
+ if (k->data != NULL)
+ free(k->data);
+
+ if ((k->data = (void *)malloc(sizeof(int) * count)) == NULL)
+ return I1PRO_DATA_MEMORY;
+
+ for (i = 0; i < count; i++) {
+ ((int *)k->data)[i] = data[i];
+ }
+
+ k->count = count;
+ k->type = i1_dtype_int;
+
+ return I1PRO_OK;
+}
+
+/* Copy an array full of doubles to the key */
+/* Note the count must be the same as the existing key value, */
+/* since we are not prepared to re-allocate key/values within */
+/* the EEProm, or re-write the directory. */
+static i1pro_code i1data_add_doubles(i1data *d, i1key key, double *data, unsigned int count) {
+ i1keyv *k;
+ int i;
+
+ if ((k = d->make_key(d, key)) == NULL)
+ return I1PRO_DATA_MAKE_KEY;
+
+ if (count != k->count)
+ return I1PRO_DATA_COUNT;
+
+ if (k->data != NULL)
+ free(k->data);
+
+ if ((k->data = (void *)malloc(sizeof(double) * count)) == NULL)
+ return I1PRO_DATA_MEMORY;
+
+ for (i = 0; i < count; i++) {
+ ((double *)k->data)[i] = data[i];
+ }
+
+ k->count = count;
+ k->type = i1_dtype_double;
+
+ return I1PRO_OK;
+}
+
+/* Initialise the data from the EEProm contents */
+static i1pro_code i1data_parse_eeprom(i1data *d, unsigned char *buf, unsigned int len, int extra) {
+ i1pro *p = d->p;
+ int rv = I1PRO_OK;
+ int dir = 0x1000; /* Location of key directory */
+ int block_id; /* Block id */
+ int nokeys;
+ i1key key, off, nkey = 0, noff = 0;
+ int size; /* size of key in bytes */
+ unsigned char *bp;
+ int i;
+
+ if (extra)
+ dir = 0x2000; /* Directory is at half way in i1pro2 2nd table */
+
+ a1logd(p->log,3,"i1pro_parse_eeprom called with %d bytes\n",len);
+
+ /* Room for minimum number of keys ? */
+ if ((dir + 300) > len)
+ return I1PRO_DATA_KEY_COUNT;
+
+ block_id = buf2ushort(buf + dir);
+ if ((extra == 0 && block_id != 1) /* Must be 1 for base data */
+ || (extra == 1 && block_id != 2)) /* Must be 2 for i1pro2 extra data*/
+ return I1PRO_DATA_KEY_CORRUPT;
+
+ nokeys = buf2ushort(buf + dir + 2); /* Bytes in key table */
+ if (nokeys < 300 || nokeys > 512)
+ return I1PRO_DATA_KEY_COUNT;
+
+ nokeys = (nokeys - 4)/6; /* Number of 6 byte entries */
+
+ a1logd(p->log,3,"%d key/values in EEProm table %d\n",nokeys, extra);
+
+ /* We need current and next value to figure data size out */
+ bp = buf + dir + 4;
+ key = buf2ushort(bp);
+ off = buf2int(bp+2);
+ bp += 6;
+ for (i = 0; i < nokeys; i++, bp += 6, key = nkey, off = noff) {
+ i1_dtype type;
+
+ if (i < (nokeys-1)) {
+ nkey = buf2ushort(bp);
+ noff = buf2int(bp+2);
+ }
+ size = noff - off;
+ if (size < 0)
+ size = 0;
+ type = d->det_type(d, key);
+
+ a1logd(p->log,3,"Table entry %d is Key 0x%04x, type %d addr 0x%x, size %d\n",
+ i,key,type,off,size);
+
+ /* Check data is within range */
+ if (off >= len || noff < off || noff > len) {
+ a1logd(p->log,3,"Key 0x%04x offset %d and length %d out of range\n",key,off,noff);
+ return I1PRO_DATA_KEY_MEMRANGE;
+ }
+
+ if (type == i1_dtype_unknown) {
+ if (d->log->debug >= 7) {
+ int i;
+ char oline[100], *bp = oline;
+ bp = oline;
+ a1logd(d->log,7,"Key 0x%04x is unknown type\n",key);
+ for (i = 0; i < size; i++) {
+ if ((i % 16) == 0)
+ bp += sprintf(bp," %04x:",i);
+ bp += sprintf(bp," %02x",buf[off + i]);
+ if ((i+1) >= size || ((i+1) % 16) == 0) {
+ bp += sprintf(bp,"\n");
+ a1logd(p->log,7,oline);
+ bp = oline;
+ }
+ }
+ }
+ if (type == i1_dtype_unknown)
+ continue; /* Ignore it */
+ }
+ if (type == i1_dtype_section) {
+ if ((rv = i1data_add_eosmarker(d, key, off)) != I1PRO_OK) {
+ a1logd(p->log,3,"Key 0x%04x section marker failed with 0x%x\n",key,rv);
+ return rv;
+ }
+ continue;
+ }
+ if (i >= nokeys) {
+ a1logd(p->log,3,"Last key wasn't a section marker!\n");
+ return I1PRO_DATA_KEY_ENDMARK;
+ }
+ if (type == i1_dtype_short) {
+ if ((rv = i1data_unser_shorts(d, key, off, buf + off, size)) != I1PRO_OK) {
+ a1logd(p->log,3,"Key 0x%04x short unserialise failed with 0x%x\n",key,rv);
+ return rv;
+ }
+ } else if (type == i1_dtype_int) {
+ if ((rv = i1data_unser_ints(d, key, off, buf + off, size)) != I1PRO_OK) {
+ a1logd(p->log,3,"Key 0x%04x int unserialise failed with 0x%x\n",key,rv);
+ return rv;
+ }
+ } else if (type == i1_dtype_double) {
+ if ((rv = i1data_unser_doubles(d, key, off, buf + off, size)) != I1PRO_OK) {
+ a1logd(p->log,3,"Key 0x%04x double unserialise failed with 0x%x\n",key,rv);
+ return rv;
+ }
+ } else {
+ a1logd(p->log,3,"Key 0x%04x has type we can't handle!\n",key);
+ }
+ }
+
+ return I1PRO_OK;
+}
+
+/* Compute and set the checksum, then serialise all the keys up */
+/* to the first marker into a buffer, ready for writing back to */
+/* the EEProm. It is an error if this buffer is not located at */
+/* zero in the EEProm */
+static i1pro_code i1data_prep_section1(
+i1data *d,
+unsigned char **buf, /* return allocated buffer */
+unsigned int *len
+) {
+ i1pro *p = d->p;
+ i1proimp *m = d->m;
+ int chsum1, *chsum2;
+ i1keyv *k, *sk, *j;
+ i1pro_code ev = I1PRO_OK;
+
+ a1logd(p->log,5,"i1data_prep_section1 called\n");
+
+ /* Compute the checksum for the first copy of the log data */
+ chsum1 = m->data->checksum(m->data, 0);
+
+ /* Locate and then set the checksum */
+ if ((chsum2 = m->data->get_int(m->data, key_checksum, 0)) == NULL) {
+ a1logd(p->log,2,"i1data_prep_section1 failed to locate checksum\n");
+ return I1PRO_INT_PREP_LOG_DATA;
+ }
+ *chsum2 = chsum1;
+
+ /* Locate the first section marker */
+ for (sk = d->head; sk != NULL; sk = sk->next) {
+ if (sk->type == i1_dtype_section)
+ break;
+ }
+ if (sk == NULL) {
+ a1logd(p->log,2,"i1data_prep_section1 failed to find section marker\n");
+ return I1PRO_INT_PREP_LOG_DATA;
+ }
+
+ /* for each key up to the first section marker */
+ /* check it resides within that section, and doesn't */
+ /* overlap any other key. */
+ for (k = d->head; k != NULL; k = k->next) {
+ if (k->type == i1_dtype_section)
+ break;
+ if (k->addr < 0 || k->addr >= sk->addr || (k->addr + k->size) > sk->addr) {
+ a1logd(p->log,2,"i1data_prep_section1 found key outside section\n");
+ return I1PRO_INT_PREP_LOG_DATA;
+ }
+ for (j = k->next; j != NULL; j = j->next) {
+ if (j->type == i1_dtype_section)
+ break;
+ if ((j->addr >= k->addr && j->addr < (k->addr + k->size))
+ || ((j->addr + j->size) > k->addr && (j->addr + j->size) <= (k->addr + k->size))) {
+ a1logd(p->log,2,"i1data_prep_section1 found key overlap section, 0x%x %d and 0x%x %d\n",
+ k->addr, k->size, j->addr, j->size);
+ return I1PRO_INT_PREP_LOG_DATA;
+ }
+ }
+ }
+
+ /* Allocate the buffer for the data */
+ *len = sk->addr;
+ if ((*buf = (unsigned char *)calloc(sk->addr, sizeof(unsigned char))) == NULL) {
+ a1logw(p->log, "i1data: malloc failed!\n");
+ return I1PRO_INT_MALLOC;
+ }
+
+ /* Serialise it into the buffer */
+ for (k = d->head; k != NULL; k = k->next) {
+ if (k->type == i1_dtype_section)
+ break;
+ else if (k->type == i1_dtype_int) {
+ if ((ev = m->data->ser_ints(m->data, k, *buf, *len)) != I1PRO_OK) {
+ a1logd(p->log,2,"i1data_prep_section1 serializing ints failed\n");
+ return ev;
+ }
+ } else if (k->type == i1_dtype_double) {
+ if ((ev = m->data->ser_doubles(m->data, k, *buf, *len)) != I1PRO_OK) {
+ a1logd(p->log,2,"i1data_prep_section1 serializing doubles failed\n");
+ return ev;
+ }
+ } else {
+ a1logd(p->log,2,"i1data_prep_section1 tried to serialise unknown type\n");
+ return I1PRO_INT_PREP_LOG_DATA;
+ }
+ }
+ a1logd(p->log,5,"a_prep_section1 done\n");
+ return ev;
+}
+
+
+/* Return the data type for the given key identifier */
+static i1_dtype i1data_det_type(i1data *d, i1key key) {
+
+ if (key < 0x100)
+ return i1_dtype_section;
+
+ switch(key) {
+ /* Log keys */
+ case key_meascount:
+ case key_meascount + 1000:
+ return i1_dtype_int;
+ case key_darkreading:
+ case key_darkreading + 1000:
+ return i1_dtype_int;
+ case key_whitereading:
+ case key_whitereading + 1000:
+ return i1_dtype_int;
+ case key_gainmode:
+ case key_gainmode + 1000:
+ return i1_dtype_int;
+ case key_inttime:
+ case key_inttime + 1000:
+ return i1_dtype_double;
+ case key_caldate:
+ case key_caldate + 1000:
+ return i1_dtype_int;
+ case key_calcount:
+ case key_calcount + 1000:
+ return i1_dtype_int;
+ case key_checksum:
+ case key_checksum + 1000:
+ return i1_dtype_int;
+ case key_rpinttime:
+ case key_rpinttime + 1000:
+ return i1_dtype_double;
+ case key_rpcount:
+ case key_rpcount + 1000:
+ return i1_dtype_int;
+ case key_acount:
+ case key_acount + 1000:
+ return i1_dtype_int;
+ case key_lampage:
+ case key_lampage + 1000:
+ return i1_dtype_double;
+
+
+ /* Intstrument calibration keys */
+ case key_ng_lin:
+ return i1_dtype_double;
+ case key_hg_lin:
+ return i1_dtype_double;
+ case key_min_int_time:
+ return i1_dtype_double;
+ case key_max_int_time:
+ return i1_dtype_double;
+ case key_mtx_index:
+ return i1_dtype_int;
+ case key_mtx_nocoef:
+ return i1_dtype_int;
+ case key_mtx_coef:
+ return i1_dtype_double;
+ case key_0bb9:
+ return i1_dtype_int;
+ case key_0bba:
+ return i1_dtype_int;
+ case key_white_ref:
+ return i1_dtype_double;
+ case key_emis_coef:
+ return i1_dtype_double;
+ case key_amb_coef:
+ return i1_dtype_double;
+ case key_0fa0:
+ return i1_dtype_int;
+ case key_0bbf:
+ return i1_dtype_int;
+ case key_cpldrev:
+ return i1_dtype_int;
+ case key_0bc1:
+ return i1_dtype_int;
+ case key_capabilities:
+ return i1_dtype_int;
+ case key_0bc3:
+ return i1_dtype_int;
+ case key_physfilt:
+ return i1_dtype_int;
+ case key_0bc5:
+ return i1_dtype_int;
+ case key_0bc6:
+ return i1_dtype_double;
+ case key_sens_target:
+ return i1_dtype_int;
+ case key_sens_dark:
+ return i1_dtype_int;
+ case key_ng_sens_sat:
+ return i1_dtype_int;
+ case key_hg_sens_sat:
+ return i1_dtype_int;
+ case key_serno:
+ return i1_dtype_int;
+ case key_dom:
+ return i1_dtype_int;
+ case key_hg_factor:
+ return i1_dtype_double;
+ default:
+ return i1_dtype_unknown;
+
+ /* i1pro2 keys */
+ case 0x2ee0:
+ return i1_dtype_unknown; // ~~
+ case 0x2ee1:
+ return i1_dtype_char;
+ case 0x2ee2:
+ return i1_dtype_int;
+ case 0x2ee3:
+ return i1_dtype_int;
+ case 0x2ee4:
+ return i1_dtype_unknown; // ~~
+
+ case 0x2eea:
+ return i1_dtype_int;
+ case 0x2eeb:
+ return i1_dtype_int;
+ case 0x2eec:
+ return i1_dtype_int;
+
+ case 0x2ef4:
+ return i1_dtype_double;
+ case 0x2ef5:
+ return i1_dtype_double;
+ case 0x2ef6:
+ return i1_dtype_double;
+ case 0x2ef9:
+ return i1_dtype_double;
+ case 0x2efa:
+ return i1_dtype_double;
+ case 0x2efe:
+ return i1_dtype_int;
+ case 0x2eff:
+ return i1_dtype_int;
+
+ case 0x2f08:
+ return i1_dtype_double;
+ case 0x2f09:
+ return i1_dtype_double;
+ case 0x2f12:
+ return i1_dtype_double;
+ case 0x2f13:
+ return i1_dtype_double;
+ case 0x2f14:
+ return i1_dtype_double;
+ case 0x2f15:
+ return i1_dtype_double;
+
+ case 0x2f44: /* Wavelength LED reference shape ? */
+ return i1_dtype_double;
+ case 0x2f45:
+ return i1_dtype_int;
+ case 0x2f46:
+ return i1_dtype_double;
+ case 0x2f4e:
+ return i1_dtype_double;
+ case 0x2f4f:
+ return i1_dtype_double;
+ case 0x2f50:
+ return i1_dtype_double;
+ case 0x2f58:
+ return i1_dtype_short; /* Stray light compensation table */
+ case 0x2f59:
+ return i1_dtype_double; /* Stray light scale factor ? */
+ case 0x2f62:
+ return i1_dtype_double;
+ case 0x2f63:
+ return i1_dtype_double;
+ case 0x2f6c:
+ return i1_dtype_double;
+ case 0x2f6d:
+ return i1_dtype_double;
+ case 0x2f6e:
+ return i1_dtype_double;
+ case 0x2f76:
+ return i1_dtype_double;
+ case 0x2f77:
+ return i1_dtype_double;
+
+ case 0x32c8:
+ return i1_dtype_int; // Date
+ case 0x32c9:
+ return i1_dtype_int; // Date
+ case 0x32ca:
+ return i1_dtype_unknown; // ~~
+
+ case 0x36b0:
+ return i1_dtype_int; // Date
+ case 0x36b1:
+ return i1_dtype_int; // Date
+ case 0x36b2:
+ return i1_dtype_unknown; // ~~
+
+ case 0x3a99:
+ return i1_dtype_unknown; // ~~
+ case 0x3a9a:
+ return i1_dtype_unknown; // ~~
+ case 0x3a9b:
+ return i1_dtype_unknown; // ~~
+ case 0x3a9c:
+ return i1_dtype_unknown; // ~~
+ case 0x3a9d:
+ return i1_dtype_unknown; // ~~
+
+ case 0x3e81:
+ return i1_dtype_char; // "X-Rite"
+ case 0x3e82:
+ return i1_dtype_unknown; // ~~
+ case 0x3e8a:
+ return i1_dtype_unknown; // ~~
+
+ case 0x3e94:
+ return i1_dtype_unknown; // ~~
+
+ }
+ return i1_dtype_unknown;
+}
+
+/* Given an index starting at 0, return the matching key code */
+/* for keys that get checksummed. Return 0 if outside range. */
+static i1key i1data_chsum_keys(
+ i1data *d,
+ int index
+) {
+ switch(index) {
+ case 0:
+ return key_meascount;
+ case 1:
+ return key_darkreading;
+ case 2:
+ return key_whitereading;
+ case 3:
+ return key_gainmode;
+ case 4:
+ return key_inttime;
+ case 5:
+ return key_caldate;
+ case 6:
+ return key_calcount;
+ case 7:
+ return key_rpinttime;
+ case 8:
+ return key_rpcount;
+ case 9:
+ return key_acount;
+ case 10:
+ return key_lampage;
+ }
+ return 0;
+}
+
+/* Compute a checksum. */
+static int i1data_checksum(
+ i1data *d,
+ i1key keyoffset /* Offset to apply to keys */
+) {
+ int i, n, j;
+ int chsum = 0;
+
+ for (i = 0; ; i++) {
+ i1key key;
+ i1keyv *k;
+
+ if ((key = d->chsum_keys(d, i)) == 0)
+ break; /* we're done */
+
+ key += keyoffset;
+
+ if ((k = d->find_key(d, key)) == NULL)
+ continue; /* Hmm */
+
+ if (k->type == i1_dtype_int) {
+ for (j = 0; j < k->count; j++)
+ chsum += ((int *)k->data)[j];
+ } else if (k->type == i1_dtype_double) {
+ for (j = 0; j < k->count; j++)
+ chsum += doubletoIEEE754(((double *)k->data)[j]);
+ }
+ }
+
+ return chsum;
+}
+
+/* Destroy ourselves */
+static void i1data_del(i1data *d) {
+ i1keyv *k, *nk;
+
+ del_a1log(d->log); /* Unref it */
+
+ /* Free all the keys and their data */
+ for (k = d->head; k != NULL; k = nk) {
+ nk = k->next;
+ if (k->data != NULL)
+ free(k->data);
+ free(k);
+ }
+ free(d);
+}
+
+/* Constructor for i1data */
+i1data *new_i1data(i1proimp *m) {
+ i1data *d;
+ if ((d = (i1data *)calloc(1, sizeof(i1data))) == NULL) {
+ a1loge(m->p->log, 1, "new_i1data: malloc failed!\n");
+ return NULL;
+ }
+
+ d->p = m->p;
+ d->m = m;
+
+ d->log = new_a1log_d(m->p->log); /* Take reference */
+
+ d->find_key = i1data_find_key;
+ d->make_key = i1data_make_key;
+ d->get_type = i1data_get_type;
+ d->get_count = i1data_get_count;
+ d->get_shorts = i1data_get_shorts;
+ d->get_ints = i1data_get_ints;
+ d->get_doubles = i1data_get_doubles;
+ d->get_int = i1data_get_int;
+ d->get_double = i1data_get_double;
+ d->unser_ints = i1data_unser_ints;
+ d->unser_doubles = i1data_unser_doubles;
+ d->ser_ints = i1data_ser_ints;
+ d->ser_doubles = i1data_ser_doubles;
+ d->parse_eeprom = i1data_parse_eeprom;
+ d->prep_section1 = i1data_prep_section1;
+ d->add_ints = i1data_add_ints;
+ d->add_doubles = i1data_add_doubles;
+ d->del = i1data_del;
+
+ d->det_type = i1data_det_type;
+ d->chsum_keys = i1data_chsum_keys;
+ d->checksum = i1data_checksum;
+
+ return d;
+}
+
+/* ----------------------------------------------------------------- */