summaryrefslogtreecommitdiff
path: root/spectro
diff options
context:
space:
mode:
authorJörg Frings-Fürst <debian@jff-webhosting.net>2014-09-01 13:56:46 +0200
committerJörg Frings-Fürst <debian@jff-webhosting.net>2014-09-01 13:56:46 +0200
commit22f703cab05b7cd368f4de9e03991b7664dc5022 (patch)
tree6f4d50beaa42328e24b1c6b56b6ec059e4ef21a5 /spectro
Initial import of argyll version 1.5.1-8debian/1.5.1-8
Diffstat (limited to 'spectro')
-rw-r--r--spectro/IntsLib_Readme.txt21
-rw-r--r--spectro/Jamfile254
-rw-r--r--spectro/License.txt662
-rw-r--r--spectro/License2.txt282
-rw-r--r--spectro/License3.txt674
-rw-r--r--spectro/LzmaDec.c993
-rw-r--r--spectro/LzmaDec.h231
-rw-r--r--spectro/LzmaTypes.h254
-rw-r--r--spectro/Makefile.OSX44
-rw-r--r--spectro/Makefile.SA183
-rw-r--r--spectro/Makefile.UNIX44
-rw-r--r--spectro/Makefile.WNT47
-rw-r--r--spectro/Makefile.am53
-rw-r--r--spectro/Readme.txt37
-rw-r--r--spectro/SOtele.sp23
-rw-r--r--spectro/afiles110
-rw-r--r--spectro/aglob.c141
-rw-r--r--spectro/aglob.h63
-rw-r--r--spectro/average.c405
-rw-r--r--spectro/ccxx.ti122
-rw-r--r--spectro/ccxxmake.c1446
-rw-r--r--spectro/chartread.c2993
-rw-r--r--spectro/colorhug.c1093
-rw-r--r--spectro/colorhug.h90
-rw-r--r--spectro/conv.c1471
-rw-r--r--spectro/conv.h297
-rw-r--r--spectro/dispcal.c5356
-rw-r--r--spectro/dispread.c969
-rw-r--r--spectro/dispsup.c2343
-rw-r--r--spectro/dispsup.h247
-rw-r--r--spectro/dispwin.c6321
-rw-r--r--spectro/dispwin.h327
-rw-r--r--spectro/dtp20.c1695
-rw-r--r--spectro/dtp20.h139
-rw-r--r--spectro/dtp22.c1110
-rw-r--r--spectro/dtp22.h105
-rw-r--r--spectro/dtp41.c1292
-rw-r--r--spectro/dtp41.h110
-rw-r--r--spectro/dtp51.c898
-rw-r--r--spectro/dtp51.h102
-rw-r--r--spectro/dtp92.c1325
-rw-r--r--spectro/dtp92.h103
-rw-r--r--spectro/fakeread.c1025
-rw-r--r--spectro/hcfr.c878
-rw-r--r--spectro/hcfr.h102
-rw-r--r--spectro/hidio.c866
-rw-r--r--spectro/hidio.h87
-rw-r--r--spectro/huey.c1663
-rw-r--r--spectro/huey.h134
-rw-r--r--spectro/i1d3.c3637
-rw-r--r--spectro/i1d3.h161
-rw-r--r--spectro/i1disp.c2591
-rw-r--r--spectro/i1disp.h172
-rw-r--r--spectro/i1pro.c809
-rw-r--r--spectro/i1pro.h56
-rw-r--r--spectro/i1pro_imp.c12093
-rw-r--r--spectro/i1pro_imp.h1376
-rw-r--r--spectro/icoms.c484
-rw-r--r--spectro/icoms.h484
-rw-r--r--spectro/icoms_nt.c563
-rw-r--r--spectro/icoms_ux.c739
-rw-r--r--spectro/ifiles14
-rw-r--r--spectro/illumread.c1232
-rw-r--r--spectro/inflate.c922
-rw-r--r--spectro/inst.c1359
-rw-r--r--spectro/inst.h999
-rw-r--r--spectro/instappsup.c556
-rw-r--r--spectro/instappsup.h100
-rw-r--r--spectro/instlib.ksh191
-rw-r--r--spectro/instlib.txt11
-rw-r--r--spectro/insttypeinst.h25
-rw-r--r--spectro/insttypes.c384
-rw-r--r--spectro/insttypes.h100
-rw-r--r--spectro/iusb.h131
-rw-r--r--spectro/linear.cal275
-rw-r--r--spectro/linear.sp23
-rw-r--r--spectro/mongoose.c4819
-rw-r--r--spectro/mongoose.h308
-rw-r--r--spectro/munki.c940
-rw-r--r--spectro/munki.h72
-rw-r--r--spectro/munki_imp.c9103
-rw-r--r--spectro/munki_imp.h1029
-rw-r--r--spectro/oemarch.c2610
-rw-r--r--spectro/oemarch.h102
-rw-r--r--spectro/oeminst.c276
-rw-r--r--spectro/pollem.c111
-rw-r--r--spectro/pollem.h50
-rw-r--r--spectro/spec2cie.c831
-rw-r--r--spectro/spotread.c2271
-rw-r--r--spectro/spyd2.c3808
-rw-r--r--spectro/spyd2.h168
-rw-r--r--spectro/spyd2PLD.h10
-rw-r--r--spectro/spyd2setup.h119
-rw-r--r--spectro/ss.c2109
-rw-r--r--spectro/ss.h115
-rw-r--r--spectro/ss_imp.c1943
-rw-r--r--spectro/ss_imp.h1335
-rw-r--r--spectro/strange.cal275
-rw-r--r--spectro/synthcal.c350
-rw-r--r--spectro/synthread.c691
-rw-r--r--spectro/usbio.c585
-rw-r--r--spectro/usbio.h247
-rw-r--r--spectro/usbio_lusb.c897
-rw-r--r--spectro/usbio_lx.c1069
-rw-r--r--spectro/usbio_nt.c898
-rw-r--r--spectro/usbio_ox.c986
-rw-r--r--spectro/vinflate.c972
-rw-r--r--spectro/webwin.c455
-rw-r--r--spectro/webwin.h31
-rw-r--r--spectro/xdg_bds.c1088
-rw-r--r--spectro/xdg_bds.h115
111 files changed, 109405 insertions, 0 deletions
diff --git a/spectro/IntsLib_Readme.txt b/spectro/IntsLib_Readme.txt
new file mode 100644
index 0000000..7d9fb0c
--- /dev/null
+++ b/spectro/IntsLib_Readme.txt
@@ -0,0 +1,21 @@
+See instlib.txt in the ArgyllCMS source on how to
+create the instlib.zip archive.
+
+To build it:
+
+If you are on Linux or OS X, you first need to
+build libusb 1.0A, ie::
+
+ cd libusb1
+ sh autogen.sh
+ make
+ cp libusb/libusb-1.0A.a .
+ cd ..
+
+(The libraries are pre-built for MSWin)
+
+To build the standalone instrument lib, you
+need to edit the Makefile to #include the appropriate
+Makefile.XXX for your operating system, and then
+run your "make".
+
diff --git a/spectro/Jamfile b/spectro/Jamfile
new file mode 100644
index 0000000..87dee2f
--- /dev/null
+++ b/spectro/Jamfile
@@ -0,0 +1,254 @@
+
+
+#PREF_CCFLAGS += $(CCOPTFLAG) ; # Turn optimisation on
+PREF_CCFLAGS += $(CCDEBUGFLAG) ; # Debugging flags
+PREF_LINKFLAGS += $(LINKDEBUGFLAG) ;
+
+# Compile .c as .m
+if $(OS) = MACOSX {
+ ObjectCcFlags dispwin : -ObjC ;
+ ObjectCcFlags dispwin_dispwin : -ObjC ;
+}
+
+# Setup the right hardware access libraries
+if $(NT) {
+ if $(USE_NATIVE_USB) = true {
+ DEFINES += NATIVE_USB ;
+ LIBUSBHDRS = ../usb/driver ; # libusb-win32 kernel driver info
+ } else {
+ if $(USE_LIBUSB1) = true {
+ LIBUSBDIR = ../libusb1 ;
+ LIBUSBHDRS = ../libusb1 ;
+ if $(MSVCNT) {
+ LIBUSBHDRS += ../libusb1/msvc ; # So stdint.h can be found
+ }
+ if $(LIBUSB_IS_DLL) = true {
+ LIBUSB = $(LIBUSB1NAME)$(SUFIMPLIB) ;
+ LIBUSBSH = $(LIBUSB1NAME)$(SUFSHLIB) ;
+ } else {
+ LIBUSB = $(LIBUSB1NAME)$(SUFLIB) ;
+ }
+ DEFINES += USE_LIBUSB1 ;
+ } else {
+ LIBUSBDIR = ../libusbw ;
+ LIBUSBHDRS = ../libusbw ;
+ LIBUSB = libusb ;
+ }
+ }
+}
+if $(UNIX) {
+ if $(USE_NATIVE_USB) = true {
+ DEFINES += NATIVE_USB ;
+ } else {
+ if $(USE_LIBUSB1) = true {
+ LIBUSBDIR = ../libusb1 ;
+ LIBUSBHDRS = ../libusb1 ;
+ if $(LIBUSB_IS_DLL) = true {
+ LIBUSB = $(LIBUSB1NAME)$(SUFIMPLIB) ;
+ LIBUSBSH = $(LIBUSB1NAME)$(SUFSHLIB) ;
+ } else {
+ LIBUSB = $(LIBUSB1NAME)$(SUFLIB) ;
+ }
+ DEFINES += USE_LIBUSB1 ;
+ } else {
+ LIBUSBDIR = ../libusb ;
+ LIBUSBHDRS = ../libusb ;
+ LIBUSB = libusb ;
+ }
+ }
+ CONVFILE = pollem.c ;
+}
+
+#Products
+Libraries = libinsttypes libinst libdisp libconv libinstapp ;
+Executables = dispwin synthcal dispread dispcal fakeread synthread
+ chartread spotread illumread ccxxmake spec2cie average oeminst ;
+Headers = inst.h ;
+Samples = SOtele.sp linear.cal strange.cal ccxx.ti1 ;
+
+#Install
+InstallBin $(DESTDIR)$(PREFIX)/bin : $(Executables) ;
+InstallFile $(DESTDIR)$(PREFIX)/$(REFSUBDIR) : $(Samples) ;
+#InstallFile $(DESTDIR)$(PREFIX)/h : $(Headers) ;
+#InstallLib $(DESTDIR)$(PREFIX)/lib : $(Libraries) ;
+
+if $(UNIX) && $(OS) != MACOSX {
+ # Micro Unix CMM for handling monitor profile association
+ CMMHDRS = ../ucmm ;
+ CMMLIBS = ../ucmm/libucmm ../jcnf/libjcnf ../jcnf/yajl/libyajl ;
+}
+
+HDRS = ../h ../numlib ../icc ../cgats ../rspl ../xicc ../gamut ../spectro
+ ../plot $(LIBUSBHDRS) $(CMMHDRS) ;
+
+# Instrument access library library
+SER_INSTS = dtp22.c dtp41.c dtp51.c ss.c ss_imp.c ;
+
+SER_USB_INSTS = dtp92.c ;
+
+USB_INSTS = dtp20.c i1disp.c i1d3.c i1pro.c i1pro_imp.c
+ munki.c munki_imp.c hcfr.c spyd2.c huey.c
+ colorhug.c usbio.c hidio.c ;
+
+if $(USE_SERIAL) = true {
+ DEFINES += ENABLE_SERIAL ;
+ INST_SRCS += $(SER_INSTS) ;
+}
+
+if $(USE_USB) = true {
+ DEFINES += ENABLE_USB ;
+ INST_SRCS += $(USB_INSTS) ;
+}
+
+if $(USE_SERIAL) = true || $(USE_USB) = true {
+ INST_SRCS += $(SER_USB_INSTS) ;
+}
+
+Library libinst : inst.c insttypes.c icoms.c $(INST_SRCS) ;
+
+# Display access library
+ObjectKeep mongoose.c ;
+Library libdisp : dispsup.c dispwin.c webwin.c : : : $(LibWinH) : mongoose ;
+
+# Instrument types utility functions library. Use this instead of libinst when */
+# applications need to know about different instrument types, but not access them. */
+# (Note we're working around a bug in Jam caused by objects shared between libraries)
+Object insttypes2 : insttypes.c ;
+LibraryFromObjects libinsttypes : insttypes2 ;
+
+# System utility functions (keyboard, msec_*, thread)
+Library libconv : xdg_bds.c aglob.c conv.c $(CONVFILE) : : : $(LibWinH) ;
+
+# Command line application instrument related convenience functions
+Library libinstapp : instappsup.c ;
+
+# Support file
+#Object alphix : ../target/alphix.c ;
+
+LINKLIBS = libinst libinstapp
+ ../xicc/libxcolorants ../xicc/libxicc
+ ../gamut/libgamut
+ ../rspl/librspl ../cgats/libcgats
+ ../icc/libicc ../plot/libplot ../plot/libvrml ../numlib/libnum
+ $(CMMLIBS) libconv ;
+
+if $(LIBUSB_IS_DLL) = true {
+ LINKSHLIBS = $(LIBUSBDIR)/$(LIBUSB) ;
+ File $(LIBUSBSH) : $(LIBUSBDIR)/$(LIBUSBSH) ;
+ # executable needs .so/.dll in same directory
+ NDepends $(Executables) : $(LIBUSBSH) ;
+} else {
+ LINKLIBS += $(LIBUSBDIR)/$(LIBUSB) ;
+}
+
+# General target reader program
+Main chartread : chartread.c ../target/alphix.c : : : ../target : : ;
+
+# Illuminant measurement
+Main illumread : illumread.c : : : ../target : : ;
+
+# Printed target spot reader utility
+Main spotread : spotread.c : : : : : ;
+
+# Test code
+if $(HOME) = "d:\\usr\\graeme" && $(PWD) = "/src/argyll/spectro" {
+ Main setoem : setoem.c : : : : : ;
+}
+
+# CCMX and CCSStool
+Main ccxxmake : ccxxmake.c : : : : : libdisp ;
+
+# Gretag Spectroscan/T filmstrip reader
+#Main filmread : filmread.c : : : : : ;
+
+# Create synthetic .cal
+Main synthcal : synthcal.c ;
+
+# Display calibration program
+Main dispcal : dispcal.c : : : ../target : : libdisp ;
+
+# Display tester program
+Main dispread : dispread.c : : : : : libdisp ;
+
+#display test window test/Lut loader utility
+# [ Could avoid need for libisnt libusb etc.
+# by separating system dependent utils to a separate library .]
+MainVariant dispwin : dispwin.c webwin.c : : STANDALONE_TEST : : mongoose : $(LibWin) ;
+
+LINKLIBS = libinsttypes ../xicc/libxicc ../gamut/libgamut ../rspl/librspl
+ ../cgats/libcgats ../icc/libicc ../numlib/libnum ../plot/libplot
+ ../plot/libvrml ;
+
+# Fake device print/read utility using ICC profile
+Main fakeread : fakeread.c ;
+
+# Synthetic device print/read utility
+Main synthread : synthread.c ;
+
+# Add CIE values to a spectral reading file
+Main spec2cie : spec2cie.c ;
+
+# Average RGB or CMYK .ti3 files
+Main average : average.c ;
+
+# Utility to install ccmx's, ccss's or install from OEM EDR files
+Objects oemarch.c vinflate.c inflate.c LzmaDec.c mongoose.c ;
+Main oeminst : oeminst.c : : : : oemarch vinflate inflate LzmaDec : libconv ;
+
+# Generate linear.cal example/diagnostic
+# (NoUpdate so that Cross Compile Win64 hack works)
+NNoUpdate linear.cal ;
+GenFileND linear.cal : synthcal [ NormPaths linear ] ;
+NDepends exe : linear.cal ; # Normally create it
+
+# Generate strange.cal example/diagnostic
+# (NoUpdate so that Cross Compile Win64 hack works)
+NNoUpdate strange.cal ;
+GenFileND strange.cal : synthcal -s 0.7,1.0,0.9 -p 1.7,0.8,0.7 [ NormPaths strange ] ;
+NDepends exe : strange.cal ; # Normally create it
+
+# Dumy ti3 file generator for testing
+#Main dumyti3 : dumyti3.c ;
+
+# Test utility for XYZ matrix spectral correction
+#Main xyzfix : xyzfix.c ;
+
+# Individual stand alone test of xdg_bds library
+MainVariant xdg_bds : xdg_bds.c : : STANDALONE_TEST : : : libconv ;
+
+# Simple test code of aglob
+#Main globtest : globtest.c : : : : : libconv ;
+
+# fp conversion code test
+#Main fp : fp.c ;
+
+# test code
+#Main t : t.c ;
+#Main tt : tt.c ;
+#Main t8 : t8.c ;
+#Main t9 : t9.c ;
+#Main i1d3eval : i1d3eval.c ;
+
+
+# Test code
+if $(HOME) = "d:\\usr\\graeme" && $(PWD) = "/src/argyll/spectro" {
+ # /SUBSYSTEM:WINDOWS on NT link ?
+# GuiBin oemdnld ;
+ Main oemdnld : oemdnld.c : : : : oemarch vinflate inflate LzmaDec mongoose : libconv ;
+ Main fakeindev : fakeindev.c ;
+ Main cmtest : cmtest.c : : : : : libconv ;
+# Main webdisp : webdisp.c : : : : : libconv ;
+}
+
+if $(OLD_GRETAG) && $(UNIX) && $(OS) != MACOSX {
+
+ # test for parsing a VISE archive
+ Main visetest : visetest.c vinflate.c ;
+
+ # Compute deconvolution filter for i1pro
+ #Main i1deconv : i1deconv.c ;
+
+ # Compute stray light calibration for i1pro
+ #Main i1stray : i1stray.c ;
+}
+
diff --git a/spectro/License.txt b/spectro/License.txt
new file mode 100644
index 0000000..a871fcf
--- /dev/null
+++ b/spectro/License.txt
@@ -0,0 +1,662 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
+
diff --git a/spectro/License2.txt b/spectro/License2.txt
new file mode 100644
index 0000000..05ca889
--- /dev/null
+++ b/spectro/License2.txt
@@ -0,0 +1,282 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+
diff --git a/spectro/License3.txt b/spectro/License3.txt
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/spectro/License3.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/spectro/LzmaDec.c b/spectro/LzmaDec.c
new file mode 100644
index 0000000..8c1a148
--- /dev/null
+++ b/spectro/LzmaDec.c
@@ -0,0 +1,993 @@
+/* LzmaDec.c -- LZMA Decoder
+2010-12-15 : Igor Pavlov : Public domain */
+
+#include "LzmaDec.h"
+
+#include <string.h>
+
+#define kNumTopBits 24
+#define kTopValue ((UInt32)1 << kNumTopBits)
+
+#define kNumBitModelTotalBits 11
+#define kBitModelTotal (1 << kNumBitModelTotalBits)
+#define kNumMoveBits 5
+
+#define RC_INIT_SIZE 5
+
+#define NORMALIZE if (range < kTopValue) { range <<= 8; code = (code << 8) | (*buf++); }
+
+#define IF_BIT_0(p) ttt = *(p); NORMALIZE; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound)
+#define UPDATE_0(p) range = bound; *(p) = (CLzmaProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits));
+#define UPDATE_1(p) range -= bound; code -= bound; *(p) = (CLzmaProb)(ttt - (ttt >> kNumMoveBits));
+#define GET_BIT2(p, i, A0, A1) IF_BIT_0(p) \
+ { UPDATE_0(p); i = (i + i); A0; } else \
+ { UPDATE_1(p); i = (i + i) + 1; A1; }
+#define GET_BIT(p, i) GET_BIT2(p, i, ; , ;)
+
+#define TREE_GET_BIT(probs, i) { GET_BIT((probs + i), i); }
+#define TREE_DECODE(probs, limit, i) \
+ { i = 1; do { TREE_GET_BIT(probs, i); } while (i < limit); i -= limit; }
+
+/* #define _LZMA_SIZE_OPT */
+
+#ifdef _LZMA_SIZE_OPT
+#define TREE_6_DECODE(probs, i) TREE_DECODE(probs, (1 << 6), i)
+#else
+#define TREE_6_DECODE(probs, i) \
+ { i = 1; \
+ TREE_GET_BIT(probs, i); \
+ TREE_GET_BIT(probs, i); \
+ TREE_GET_BIT(probs, i); \
+ TREE_GET_BIT(probs, i); \
+ TREE_GET_BIT(probs, i); \
+ TREE_GET_BIT(probs, i); \
+ i -= 0x40; }
+#endif
+
+#define NORMALIZE_CHECK if (range < kTopValue) { if (buf >= bufLimit) return DUMMY_ERROR; range <<= 8; code = (code << 8) | (*buf++); }
+
+#define IF_BIT_0_CHECK(p) ttt = *(p); NORMALIZE_CHECK; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound)
+#define UPDATE_0_CHECK range = bound;
+#define UPDATE_1_CHECK range -= bound; code -= bound;
+#define GET_BIT2_CHECK(p, i, A0, A1) IF_BIT_0_CHECK(p) \
+ { UPDATE_0_CHECK; i = (i + i); A0; } else \
+ { UPDATE_1_CHECK; i = (i + i) + 1; A1; }
+#define GET_BIT_CHECK(p, i) GET_BIT2_CHECK(p, i, ; , ;)
+#define TREE_DECODE_CHECK(probs, limit, i) \
+ { i = 1; do { GET_BIT_CHECK(probs + i, i) } while (i < limit); i -= limit; }
+
+
+#define kNumPosBitsMax 4
+#define kNumPosStatesMax (1 << kNumPosBitsMax)
+
+#define kLenNumLowBits 3
+#define kLenNumLowSymbols (1 << kLenNumLowBits)
+#define kLenNumMidBits 3
+#define kLenNumMidSymbols (1 << kLenNumMidBits)
+#define kLenNumHighBits 8
+#define kLenNumHighSymbols (1 << kLenNumHighBits)
+
+#define LenChoice 0
+#define LenChoice2 (LenChoice + 1)
+#define LenLow (LenChoice2 + 1)
+#define LenMid (LenLow + (kNumPosStatesMax << kLenNumLowBits))
+#define LenHigh (LenMid + (kNumPosStatesMax << kLenNumMidBits))
+#define kNumLenProbs (LenHigh + kLenNumHighSymbols)
+
+
+#define kNumStates 12
+#define kNumLitStates 7
+
+#define kStartPosModelIndex 4
+#define kEndPosModelIndex 14
+#define kNumFullDistances (1 << (kEndPosModelIndex >> 1))
+
+#define kNumPosSlotBits 6
+#define kNumLenToPosStates 4
+
+#define kNumAlignBits 4
+#define kAlignTableSize (1 << kNumAlignBits)
+
+#define kMatchMinLen 2
+#define kMatchSpecLenStart (kMatchMinLen + kLenNumLowSymbols + kLenNumMidSymbols + kLenNumHighSymbols)
+
+#define IsMatch 0
+#define IsRep (IsMatch + (kNumStates << kNumPosBitsMax))
+#define IsRepG0 (IsRep + kNumStates)
+#define IsRepG1 (IsRepG0 + kNumStates)
+#define IsRepG2 (IsRepG1 + kNumStates)
+#define IsRep0Long (IsRepG2 + kNumStates)
+#define PosSlot (IsRep0Long + (kNumStates << kNumPosBitsMax))
+#define SpecPos (PosSlot + (kNumLenToPosStates << kNumPosSlotBits))
+#define Align (SpecPos + kNumFullDistances - kEndPosModelIndex)
+#define LenCoder (Align + kAlignTableSize)
+#define RepLenCoder (LenCoder + kNumLenProbs)
+#define Literal (RepLenCoder + kNumLenProbs)
+
+#define LZMA_BASE_SIZE 1846
+#define LZMA_LIT_SIZE 768
+
+#define LzmaProps_GetNumProbs(p) ((UInt32)LZMA_BASE_SIZE + (LZMA_LIT_SIZE << ((p)->lc + (p)->lp)))
+
+#if Literal != LZMA_BASE_SIZE
+StopCompilingDueBUG
+#endif
+
+#define LZMA_DIC_MIN (1 << 12)
+
+/* First LZMA-symbol is always decoded.
+And it decodes new LZMA-symbols while (buf < bufLimit), but "buf" is without last normalization
+Out:
+ Result:
+ SZ_OK - OK
+ SZ_ERROR_DATA - Error
+ p->remainLen:
+ < kMatchSpecLenStart : normal remain
+ = kMatchSpecLenStart : finished
+ = kMatchSpecLenStart + 1 : Flush marker
+ = kMatchSpecLenStart + 2 : State Init Marker
+*/
+
+static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte *bufLimit)
+{
+ CLzmaProb *probs = p->probs;
+
+ unsigned state = p->state;
+ UInt32 rep0 = p->reps[0], rep1 = p->reps[1], rep2 = p->reps[2], rep3 = p->reps[3];
+ unsigned pbMask = ((unsigned)1 << (p->prop.pb)) - 1;
+ unsigned lpMask = ((unsigned)1 << (p->prop.lp)) - 1;
+ unsigned lc = p->prop.lc;
+
+ Byte *dic = p->dic;
+ SizeT dicBufSize = p->dicBufSize;
+ SizeT dicPos = p->dicPos;
+
+ UInt32 processedPos = p->processedPos;
+ UInt32 checkDicSize = p->checkDicSize;
+ unsigned len = 0;
+
+ const Byte *buf = p->buf;
+ UInt32 range = p->range;
+ UInt32 code = p->code;
+
+ do
+ {
+ CLzmaProb *prob;
+ UInt32 bound;
+ unsigned ttt;
+ unsigned posState = processedPos & pbMask;
+
+ prob = probs + IsMatch + (state << kNumPosBitsMax) + posState;
+ IF_BIT_0(prob)
+ {
+ unsigned symbol;
+ UPDATE_0(prob);
+ prob = probs + Literal;
+ if (checkDicSize != 0 || processedPos != 0)
+ prob += (LZMA_LIT_SIZE * (((processedPos & lpMask) << lc) +
+ (dic[(dicPos == 0 ? dicBufSize : dicPos) - 1] >> (8 - lc))));
+
+ if (state < kNumLitStates)
+ {
+ state -= (state < 4) ? state : 3;
+ symbol = 1;
+ do { GET_BIT(prob + symbol, symbol) } while (symbol < 0x100);
+ }
+ else
+ {
+ unsigned matchByte = p->dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)];
+ unsigned offs = 0x100;
+ state -= (state < 10) ? 3 : 6;
+ symbol = 1;
+ do
+ {
+ unsigned bit;
+ CLzmaProb *probLit;
+ matchByte <<= 1;
+ bit = (matchByte & offs);
+ probLit = prob + offs + bit + symbol;
+ GET_BIT2(probLit, symbol, offs &= ~bit, offs &= bit)
+ }
+ while (symbol < 0x100);
+ }
+ dic[dicPos++] = (Byte)symbol;
+ processedPos++;
+ continue;
+ }
+ else
+ {
+ UPDATE_1(prob);
+ prob = probs + IsRep + state;
+ IF_BIT_0(prob)
+ {
+ UPDATE_0(prob);
+ state += kNumStates;
+ prob = probs + LenCoder;
+ }
+ else
+ {
+ UPDATE_1(prob);
+ if (checkDicSize == 0 && processedPos == 0)
+ return SZ_ERROR_DATA;
+ prob = probs + IsRepG0 + state;
+ IF_BIT_0(prob)
+ {
+ UPDATE_0(prob);
+ prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState;
+ IF_BIT_0(prob)
+ {
+ UPDATE_0(prob);
+ dic[dicPos] = dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)];
+ dicPos++;
+ processedPos++;
+ state = state < kNumLitStates ? 9 : 11;
+ continue;
+ }
+ UPDATE_1(prob);
+ }
+ else
+ {
+ UInt32 distance;
+ UPDATE_1(prob);
+ prob = probs + IsRepG1 + state;
+ IF_BIT_0(prob)
+ {
+ UPDATE_0(prob);
+ distance = rep1;
+ }
+ else
+ {
+ UPDATE_1(prob);
+ prob = probs + IsRepG2 + state;
+ IF_BIT_0(prob)
+ {
+ UPDATE_0(prob);
+ distance = rep2;
+ }
+ else
+ {
+ UPDATE_1(prob);
+ distance = rep3;
+ rep3 = rep2;
+ }
+ rep2 = rep1;
+ }
+ rep1 = rep0;
+ rep0 = distance;
+ }
+ state = state < kNumLitStates ? 8 : 11;
+ prob = probs + RepLenCoder;
+ }
+ {
+ unsigned limit, offset;
+ CLzmaProb *probLen = prob + LenChoice;
+ IF_BIT_0(probLen)
+ {
+ UPDATE_0(probLen);
+ probLen = prob + LenLow + (posState << kLenNumLowBits);
+ offset = 0;
+ limit = (1 << kLenNumLowBits);
+ }
+ else
+ {
+ UPDATE_1(probLen);
+ probLen = prob + LenChoice2;
+ IF_BIT_0(probLen)
+ {
+ UPDATE_0(probLen);
+ probLen = prob + LenMid + (posState << kLenNumMidBits);
+ offset = kLenNumLowSymbols;
+ limit = (1 << kLenNumMidBits);
+ }
+ else
+ {
+ UPDATE_1(probLen);
+ probLen = prob + LenHigh;
+ offset = kLenNumLowSymbols + kLenNumMidSymbols;
+ limit = (1 << kLenNumHighBits);
+ }
+ }
+ TREE_DECODE(probLen, limit, len);
+ len += offset;
+ }
+
+ if (state >= kNumStates)
+ {
+ UInt32 distance;
+ prob = probs + PosSlot +
+ ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << kNumPosSlotBits);
+ TREE_6_DECODE(prob, distance);
+ if (distance >= kStartPosModelIndex)
+ {
+ unsigned posSlot = (unsigned)distance;
+ int numDirectBits = (int)(((distance >> 1) - 1));
+ distance = (2 | (distance & 1));
+ if (posSlot < kEndPosModelIndex)
+ {
+ distance <<= numDirectBits;
+ prob = probs + SpecPos + distance - posSlot - 1;
+ {
+ UInt32 mask = 1;
+ unsigned i = 1;
+ do
+ {
+ GET_BIT2(prob + i, i, ; , distance |= mask);
+ mask <<= 1;
+ }
+ while (--numDirectBits != 0);
+ }
+ }
+ else
+ {
+ numDirectBits -= kNumAlignBits;
+ do
+ {
+ NORMALIZE
+ range >>= 1;
+
+ {
+ UInt32 t;
+ code -= range;
+ t = (0 - ((UInt32)code >> 31)); /* (UInt32)((Int32)code >> 31) */
+ distance = (distance << 1) + (t + 1);
+ code += range & t;
+ }
+ /*
+ distance <<= 1;
+ if (code >= range)
+ {
+ code -= range;
+ distance |= 1;
+ }
+ */
+ }
+ while (--numDirectBits != 0);
+ prob = probs + Align;
+ distance <<= kNumAlignBits;
+ {
+ unsigned i = 1;
+ GET_BIT2(prob + i, i, ; , distance |= 1);
+ GET_BIT2(prob + i, i, ; , distance |= 2);
+ GET_BIT2(prob + i, i, ; , distance |= 4);
+ GET_BIT2(prob + i, i, ; , distance |= 8);
+ }
+ if (distance == (UInt32)0xFFFFFFFF)
+ {
+ len += kMatchSpecLenStart;
+ state -= kNumStates;
+ break;
+ }
+ }
+ }
+ rep3 = rep2;
+ rep2 = rep1;
+ rep1 = rep0;
+ rep0 = distance + 1;
+ if (checkDicSize == 0)
+ {
+ if (distance >= processedPos)
+ return SZ_ERROR_DATA;
+ }
+ else if (distance >= checkDicSize)
+ return SZ_ERROR_DATA;
+ state = (state < kNumStates + kNumLitStates) ? kNumLitStates : kNumLitStates + 3;
+ }
+
+ len += kMatchMinLen;
+
+ if (limit == dicPos)
+ return SZ_ERROR_DATA;
+ {
+ SizeT rem = limit - dicPos;
+ unsigned curLen = ((rem < len) ? (unsigned)rem : len);
+ SizeT pos = (dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0);
+
+ processedPos += curLen;
+
+ len -= curLen;
+ if (pos + curLen <= dicBufSize)
+ {
+ Byte *dest = dic + dicPos;
+ ptrdiff_t src = (ptrdiff_t)pos - (ptrdiff_t)dicPos;
+ const Byte *lim = dest + curLen;
+ dicPos += curLen;
+ do
+ *(dest) = (Byte)*(dest + src);
+ while (++dest != lim);
+ }
+ else
+ {
+ do
+ {
+ dic[dicPos++] = dic[pos];
+ if (++pos == dicBufSize)
+ pos = 0;
+ }
+ while (--curLen != 0);
+ }
+ }
+ }
+ }
+ while (dicPos < limit && buf < bufLimit);
+ NORMALIZE;
+ p->buf = buf;
+ p->range = range;
+ p->code = code;
+ p->remainLen = len;
+ p->dicPos = dicPos;
+ p->processedPos = processedPos;
+ p->reps[0] = rep0;
+ p->reps[1] = rep1;
+ p->reps[2] = rep2;
+ p->reps[3] = rep3;
+ p->state = state;
+
+ return SZ_OK;
+}
+
+static void MY_FAST_CALL LzmaDec_WriteRem(CLzmaDec *p, SizeT limit)
+{
+ if (p->remainLen != 0 && p->remainLen < kMatchSpecLenStart)
+ {
+ Byte *dic = p->dic;
+ SizeT dicPos = p->dicPos;
+ SizeT dicBufSize = p->dicBufSize;
+ unsigned len = p->remainLen;
+ UInt32 rep0 = p->reps[0];
+ if (limit - dicPos < len)
+ len = (unsigned)(limit - dicPos);
+
+ if (p->checkDicSize == 0 && p->prop.dicSize - p->processedPos <= len)
+ p->checkDicSize = p->prop.dicSize;
+
+ p->processedPos += len;
+ p->remainLen -= len;
+ while (len != 0)
+ {
+ len--;
+ dic[dicPos] = dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)];
+ dicPos++;
+ }
+ p->dicPos = dicPos;
+ }
+}
+
+static int MY_FAST_CALL LzmaDec_DecodeReal2(CLzmaDec *p, SizeT limit, const Byte *bufLimit)
+{
+ do
+ {
+ SizeT limit2 = limit;
+ if (p->checkDicSize == 0)
+ {
+ UInt32 rem = p->prop.dicSize - p->processedPos;
+ if (limit - p->dicPos > rem)
+ limit2 = p->dicPos + rem;
+ }
+ RINOK(LzmaDec_DecodeReal(p, limit2, bufLimit));
+ if (p->processedPos >= p->prop.dicSize)
+ p->checkDicSize = p->prop.dicSize;
+ LzmaDec_WriteRem(p, limit);
+ }
+ while (p->dicPos < limit && p->buf < bufLimit && p->remainLen < kMatchSpecLenStart);
+
+ if (p->remainLen > kMatchSpecLenStart)
+ {
+ p->remainLen = kMatchSpecLenStart;
+ }
+ return 0;
+}
+
+typedef enum
+{
+ DUMMY_ERROR, /* unexpected end of input stream */
+ DUMMY_LIT,
+ DUMMY_MATCH,
+ DUMMY_REP
+} ELzmaDummy;
+
+static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inSize)
+{
+ UInt32 range = p->range;
+ UInt32 code = p->code;
+ const Byte *bufLimit = buf + inSize;
+ CLzmaProb *probs = p->probs;
+ unsigned state = p->state;
+ ELzmaDummy res;
+
+ {
+ CLzmaProb *prob;
+ UInt32 bound;
+ unsigned ttt;
+ unsigned posState = (p->processedPos) & ((1 << p->prop.pb) - 1);
+
+ prob = probs + IsMatch + (state << kNumPosBitsMax) + posState;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK
+
+ /* if (bufLimit - buf >= 7) return DUMMY_LIT; */
+
+ prob = probs + Literal;
+ if (p->checkDicSize != 0 || p->processedPos != 0)
+ prob += (LZMA_LIT_SIZE *
+ ((((p->processedPos) & ((1 << (p->prop.lp)) - 1)) << p->prop.lc) +
+ (p->dic[(p->dicPos == 0 ? p->dicBufSize : p->dicPos) - 1] >> (8 - p->prop.lc))));
+
+ if (state < kNumLitStates)
+ {
+ unsigned symbol = 1;
+ do { GET_BIT_CHECK(prob + symbol, symbol) } while (symbol < 0x100);
+ }
+ else
+ {
+ unsigned matchByte = p->dic[p->dicPos - p->reps[0] +
+ ((p->dicPos < p->reps[0]) ? p->dicBufSize : 0)];
+ unsigned offs = 0x100;
+ unsigned symbol = 1;
+ do
+ {
+ unsigned bit;
+ CLzmaProb *probLit;
+ matchByte <<= 1;
+ bit = (matchByte & offs);
+ probLit = prob + offs + bit + symbol;
+ GET_BIT2_CHECK(probLit, symbol, offs &= ~bit, offs &= bit)
+ }
+ while (symbol < 0x100);
+ }
+ res = DUMMY_LIT;
+ }
+ else
+ {
+ unsigned len;
+ UPDATE_1_CHECK;
+
+ prob = probs + IsRep + state;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK;
+ state = 0;
+ prob = probs + LenCoder;
+ res = DUMMY_MATCH;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ res = DUMMY_REP;
+ prob = probs + IsRepG0 + state;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK;
+ prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK;
+ NORMALIZE_CHECK;
+ return DUMMY_REP;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ }
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ prob = probs + IsRepG1 + state;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ prob = probs + IsRepG2 + state;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ }
+ }
+ }
+ state = kNumStates;
+ prob = probs + RepLenCoder;
+ }
+ {
+ unsigned limit, offset;
+ CLzmaProb *probLen = prob + LenChoice;
+ IF_BIT_0_CHECK(probLen)
+ {
+ UPDATE_0_CHECK;
+ probLen = prob + LenLow + (posState << kLenNumLowBits);
+ offset = 0;
+ limit = 1 << kLenNumLowBits;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ probLen = prob + LenChoice2;
+ IF_BIT_0_CHECK(probLen)
+ {
+ UPDATE_0_CHECK;
+ probLen = prob + LenMid + (posState << kLenNumMidBits);
+ offset = kLenNumLowSymbols;
+ limit = 1 << kLenNumMidBits;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ probLen = prob + LenHigh;
+ offset = kLenNumLowSymbols + kLenNumMidSymbols;
+ limit = 1 << kLenNumHighBits;
+ }
+ }
+ TREE_DECODE_CHECK(probLen, limit, len);
+ len += offset;
+ }
+
+ if (state < 4)
+ {
+ unsigned posSlot;
+ prob = probs + PosSlot +
+ ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) <<
+ kNumPosSlotBits);
+ TREE_DECODE_CHECK(prob, 1 << kNumPosSlotBits, posSlot);
+ if (posSlot >= kStartPosModelIndex)
+ {
+ int numDirectBits = ((posSlot >> 1) - 1);
+
+ /* if (bufLimit - buf >= 8) return DUMMY_MATCH; */
+
+ if (posSlot < kEndPosModelIndex)
+ {
+ prob = probs + SpecPos + ((2 | (posSlot & 1)) << numDirectBits) - posSlot - 1;
+ }
+ else
+ {
+ numDirectBits -= kNumAlignBits;
+ do
+ {
+ NORMALIZE_CHECK
+ range >>= 1;
+ code -= range & (((code - range) >> 31) - 1);
+ /* if (code >= range) code -= range; */
+ }
+ while (--numDirectBits != 0);
+ prob = probs + Align;
+ numDirectBits = kNumAlignBits;
+ }
+ {
+ unsigned i = 1;
+ do
+ {
+ GET_BIT_CHECK(prob + i, i);
+ }
+ while (--numDirectBits != 0);
+ }
+ }
+ }
+ }
+ }
+ NORMALIZE_CHECK;
+ return res;
+}
+
+
+static void LzmaDec_InitRc(CLzmaDec *p, const Byte *data)
+{
+ p->code = ((UInt32)data[1] << 24) | ((UInt32)data[2] << 16) | ((UInt32)data[3] << 8) | ((UInt32)data[4]);
+ p->range = 0xFFFFFFFF;
+ p->needFlush = 0;
+}
+
+void LzmaDec_InitDicAndState(CLzmaDec *p, Bool initDic, Bool initState)
+{
+ p->needFlush = 1;
+ p->remainLen = 0;
+ p->tempBufSize = 0;
+
+ if (initDic)
+ {
+ p->processedPos = 0;
+ p->checkDicSize = 0;
+ p->needInitState = 1;
+ }
+ if (initState)
+ p->needInitState = 1;
+}
+
+void LzmaDec_Init(CLzmaDec *p)
+{
+ p->dicPos = 0;
+ LzmaDec_InitDicAndState(p, True, True);
+}
+
+static void LzmaDec_InitStateReal(CLzmaDec *p)
+{
+ UInt32 numProbs = Literal + ((UInt32)LZMA_LIT_SIZE << (p->prop.lc + p->prop.lp));
+ UInt32 i;
+ CLzmaProb *probs = p->probs;
+ for (i = 0; i < numProbs; i++)
+ probs[i] = kBitModelTotal >> 1;
+ p->reps[0] = p->reps[1] = p->reps[2] = p->reps[3] = 1;
+ p->state = 0;
+ p->needInitState = 0;
+}
+
+SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, const Byte *src, SizeT *srcLen,
+ ELzmaFinishMode finishMode, ELzmaStatus *status)
+{
+ SizeT inSize = *srcLen;
+ (*srcLen) = 0;
+ LzmaDec_WriteRem(p, dicLimit);
+
+ *status = LZMA_STATUS_NOT_SPECIFIED;
+
+ while (p->remainLen != kMatchSpecLenStart)
+ {
+ int checkEndMarkNow;
+
+ if (p->needFlush != 0)
+ {
+ for (; inSize > 0 && p->tempBufSize < RC_INIT_SIZE; (*srcLen)++, inSize--)
+ p->tempBuf[p->tempBufSize++] = *src++;
+ if (p->tempBufSize < RC_INIT_SIZE)
+ {
+ *status = LZMA_STATUS_NEEDS_MORE_INPUT;
+ return SZ_OK;
+ }
+ if (p->tempBuf[0] != 0)
+ return SZ_ERROR_DATA;
+
+ LzmaDec_InitRc(p, p->tempBuf);
+ p->tempBufSize = 0;
+ }
+
+ checkEndMarkNow = 0;
+ if (p->dicPos >= dicLimit)
+ {
+ if (p->remainLen == 0 && p->code == 0)
+ {
+ *status = LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK;
+ return SZ_OK;
+ }
+ if (finishMode == LZMA_FINISH_ANY)
+ {
+ *status = LZMA_STATUS_NOT_FINISHED;
+ return SZ_OK;
+ }
+ if (p->remainLen != 0)
+ {
+ *status = LZMA_STATUS_NOT_FINISHED;
+ return SZ_ERROR_DATA;
+ }
+ checkEndMarkNow = 1;
+ }
+
+ if (p->needInitState)
+ LzmaDec_InitStateReal(p);
+
+ if (p->tempBufSize == 0)
+ {
+ SizeT processed;
+ const Byte *bufLimit;
+ if (inSize < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow)
+ {
+ int dummyRes = LzmaDec_TryDummy(p, src, inSize);
+ if (dummyRes == DUMMY_ERROR)
+ {
+ memcpy(p->tempBuf, src, inSize);
+ p->tempBufSize = (unsigned)inSize;
+ (*srcLen) += inSize;
+ *status = LZMA_STATUS_NEEDS_MORE_INPUT;
+ return SZ_OK;
+ }
+ if (checkEndMarkNow && dummyRes != DUMMY_MATCH)
+ {
+ *status = LZMA_STATUS_NOT_FINISHED;
+ return SZ_ERROR_DATA;
+ }
+ bufLimit = src;
+ }
+ else
+ bufLimit = src + inSize - LZMA_REQUIRED_INPUT_MAX;
+ p->buf = src;
+ if (LzmaDec_DecodeReal2(p, dicLimit, bufLimit) != 0)
+ return SZ_ERROR_DATA;
+ processed = (SizeT)(p->buf - src);
+ (*srcLen) += processed;
+ src += processed;
+ inSize -= processed;
+ }
+ else
+ {
+ unsigned rem = p->tempBufSize, lookAhead = 0;
+ while (rem < LZMA_REQUIRED_INPUT_MAX && lookAhead < inSize)
+ p->tempBuf[rem++] = src[lookAhead++];
+ p->tempBufSize = rem;
+ if (rem < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow)
+ {
+ int dummyRes = LzmaDec_TryDummy(p, p->tempBuf, rem);
+ if (dummyRes == DUMMY_ERROR)
+ {
+ (*srcLen) += lookAhead;
+ *status = LZMA_STATUS_NEEDS_MORE_INPUT;
+ return SZ_OK;
+ }
+ if (checkEndMarkNow && dummyRes != DUMMY_MATCH)
+ {
+ *status = LZMA_STATUS_NOT_FINISHED;
+ return SZ_ERROR_DATA;
+ }
+ }
+ p->buf = p->tempBuf;
+ if (LzmaDec_DecodeReal2(p, dicLimit, p->buf) != 0)
+ return SZ_ERROR_DATA;
+ lookAhead -= (rem - (unsigned)(p->buf - p->tempBuf));
+ (*srcLen) += lookAhead;
+ src += lookAhead;
+ inSize -= lookAhead;
+ p->tempBufSize = 0;
+ }
+ }
+ if (p->code == 0)
+ *status = LZMA_STATUS_FINISHED_WITH_MARK;
+ return (p->code == 0) ? SZ_OK : SZ_ERROR_DATA;
+}
+
+SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status)
+{
+ SizeT outSize = *destLen;
+ SizeT inSize = *srcLen;
+ *srcLen = *destLen = 0;
+ for (;;)
+ {
+ SizeT inSizeCur = inSize, outSizeCur, dicPos;
+ ELzmaFinishMode curFinishMode;
+ SRes res;
+ if (p->dicPos == p->dicBufSize)
+ p->dicPos = 0;
+ dicPos = p->dicPos;
+ if (outSize > p->dicBufSize - dicPos)
+ {
+ outSizeCur = p->dicBufSize;
+ curFinishMode = LZMA_FINISH_ANY;
+ }
+ else
+ {
+ outSizeCur = dicPos + outSize;
+ curFinishMode = finishMode;
+ }
+
+ res = LzmaDec_DecodeToDic(p, outSizeCur, src, &inSizeCur, curFinishMode, status);
+ src += inSizeCur;
+ inSize -= inSizeCur;
+ *srcLen += inSizeCur;
+ outSizeCur = p->dicPos - dicPos;
+ memcpy(dest, p->dic + dicPos, outSizeCur);
+ dest += outSizeCur;
+ outSize -= outSizeCur;
+ *destLen += outSizeCur;
+ if (res != 0)
+ return res;
+ if (outSizeCur == 0 || outSize == 0)
+ return SZ_OK;
+ }
+}
+
+void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc)
+{
+ alloc->Free(alloc, p->probs);
+ p->probs = 0;
+}
+
+static void LzmaDec_FreeDict(CLzmaDec *p, ISzAlloc *alloc)
+{
+ alloc->Free(alloc, p->dic);
+ p->dic = 0;
+}
+
+void LzmaDec_Free(CLzmaDec *p, ISzAlloc *alloc)
+{
+ LzmaDec_FreeProbs(p, alloc);
+ LzmaDec_FreeDict(p, alloc);
+}
+
+SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size)
+{
+ UInt32 dicSize;
+ Byte d;
+
+ if (size < LZMA_PROPS_SIZE)
+ return SZ_ERROR_UNSUPPORTED;
+ else
+ dicSize = data[1] | ((UInt32)data[2] << 8) | ((UInt32)data[3] << 16) | ((UInt32)data[4] << 24);
+
+ if (dicSize < LZMA_DIC_MIN)
+ dicSize = LZMA_DIC_MIN;
+ p->dicSize = dicSize;
+
+ d = data[0];
+ if (d >= (9 * 5 * 5))
+ return SZ_ERROR_UNSUPPORTED;
+
+ p->lc = d % 9;
+ d /= 9;
+ p->pb = d / 5;
+ p->lp = d % 5;
+
+ return SZ_OK;
+}
+
+static SRes LzmaDec_AllocateProbs2(CLzmaDec *p, const CLzmaProps *propNew, ISzAlloc *alloc)
+{
+ UInt32 numProbs = LzmaProps_GetNumProbs(propNew);
+ if (p->probs == 0 || numProbs != p->numProbs)
+ {
+ LzmaDec_FreeProbs(p, alloc);
+ p->probs = (CLzmaProb *)alloc->Alloc(alloc, numProbs * sizeof(CLzmaProb));
+ p->numProbs = numProbs;
+ if (p->probs == 0)
+ return SZ_ERROR_MEM;
+ }
+ return SZ_OK;
+}
+
+SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc)
+{
+ CLzmaProps propNew;
+ RINOK(LzmaProps_Decode(&propNew, props, propsSize));
+ RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc));
+ p->prop = propNew;
+ return SZ_OK;
+}
+
+SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc)
+{
+ CLzmaProps propNew;
+ SizeT dicBufSize;
+ RINOK(LzmaProps_Decode(&propNew, props, propsSize));
+ RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc));
+ dicBufSize = propNew.dicSize;
+ if (p->dic == 0 || dicBufSize != p->dicBufSize)
+ {
+ LzmaDec_FreeDict(p, alloc);
+ p->dic = (Byte *)alloc->Alloc(alloc, dicBufSize);
+ if (p->dic == 0)
+ {
+ LzmaDec_FreeProbs(p, alloc);
+ return SZ_ERROR_MEM;
+ }
+ }
+ p->dicBufSize = dicBufSize;
+ p->prop = propNew;
+ return SZ_OK;
+}
+
+SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen,
+ const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode,
+ ELzmaStatus *status, ISzAlloc *alloc)
+{
+ CLzmaDec p;
+ SRes res;
+ SizeT outSize = *destLen, inSize = *srcLen;
+ *destLen = *srcLen = 0;
+ *status = LZMA_STATUS_NOT_SPECIFIED;
+ if (inSize < RC_INIT_SIZE)
+ return SZ_ERROR_INPUT_EOF;
+ LzmaDec_Construct(&p);
+ RINOK(LzmaDec_AllocateProbs(&p, propData, propSize, alloc));
+ p.dic = dest;
+ p.dicBufSize = outSize;
+ LzmaDec_Init(&p);
+ *srcLen = inSize;
+ res = LzmaDec_DecodeToDic(&p, outSize, src, srcLen, finishMode, status);
+ *destLen = p.dicPos;
+ if (res == SZ_OK && *status == LZMA_STATUS_NEEDS_MORE_INPUT)
+ res = SZ_ERROR_INPUT_EOF;
+ LzmaDec_FreeProbs(&p, alloc);
+ return res;
+}
diff --git a/spectro/LzmaDec.h b/spectro/LzmaDec.h
new file mode 100644
index 0000000..6045fae
--- /dev/null
+++ b/spectro/LzmaDec.h
@@ -0,0 +1,231 @@
+/* LzmaDec.h -- LZMA Decoder
+2009-02-07 : Igor Pavlov : Public domain */
+
+#ifndef __LZMA_DEC_H
+#define __LZMA_DEC_H
+
+#include "LzmaTypes.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* #define _LZMA_PROB32 */
+/* _LZMA_PROB32 can increase the speed on some CPUs,
+ but memory usage for CLzmaDec::probs will be doubled in that case */
+
+#ifdef _LZMA_PROB32
+#define CLzmaProb UInt32
+#else
+#define CLzmaProb UInt16
+#endif
+
+
+/* ---------- LZMA Properties ---------- */
+
+#define LZMA_PROPS_SIZE 5
+
+typedef struct _CLzmaProps
+{
+ unsigned lc, lp, pb;
+ UInt32 dicSize;
+} CLzmaProps;
+
+/* LzmaProps_Decode - decodes properties
+Returns:
+ SZ_OK
+ SZ_ERROR_UNSUPPORTED - Unsupported properties
+*/
+
+SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size);
+
+
+/* ---------- LZMA Decoder state ---------- */
+
+/* LZMA_REQUIRED_INPUT_MAX = number of required input bytes for worst case.
+ Num bits = log2((2^11 / 31) ^ 22) + 26 < 134 + 26 = 160; */
+
+#define LZMA_REQUIRED_INPUT_MAX 20
+
+typedef struct
+{
+ CLzmaProps prop;
+ CLzmaProb *probs;
+ Byte *dic;
+ const Byte *buf;
+ UInt32 range, code;
+ SizeT dicPos;
+ SizeT dicBufSize;
+ UInt32 processedPos;
+ UInt32 checkDicSize;
+ unsigned state;
+ UInt32 reps[4];
+ unsigned remainLen;
+ int needFlush;
+ int needInitState;
+ UInt32 numProbs;
+ unsigned tempBufSize;
+ Byte tempBuf[LZMA_REQUIRED_INPUT_MAX];
+} CLzmaDec;
+
+#define LzmaDec_Construct(p) { (p)->dic = 0; (p)->probs = 0; }
+
+void LzmaDec_Init(CLzmaDec *p);
+
+/* There are two types of LZMA streams:
+ 0) Stream with end mark. That end mark adds about 6 bytes to compressed size.
+ 1) Stream without end mark. You must know exact uncompressed size to decompress such stream. */
+
+typedef enum
+{
+ LZMA_FINISH_ANY, /* finish at any point */
+ LZMA_FINISH_END /* block must be finished at the end */
+} ELzmaFinishMode;
+
+/* ELzmaFinishMode has meaning only if the decoding reaches output limit !!!
+
+ You must use LZMA_FINISH_END, when you know that current output buffer
+ covers last bytes of block. In other cases you must use LZMA_FINISH_ANY.
+
+ If LZMA decoder sees end marker before reaching output limit, it returns SZ_OK,
+ and output value of destLen will be less than output buffer size limit.
+ You can check status result also.
+
+ You can use multiple checks to test data integrity after full decompression:
+ 1) Check Result and "status" variable.
+ 2) Check that output(destLen) = uncompressedSize, if you know real uncompressedSize.
+ 3) Check that output(srcLen) = compressedSize, if you know real compressedSize.
+ You must use correct finish mode in that case. */
+
+typedef enum
+{
+ LZMA_STATUS_NOT_SPECIFIED, /* use main error code instead */
+ LZMA_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */
+ LZMA_STATUS_NOT_FINISHED, /* stream was not finished */
+ LZMA_STATUS_NEEDS_MORE_INPUT, /* you must provide more input bytes */
+ LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK /* there is probability that stream was finished without end mark */
+} ELzmaStatus;
+
+/* ELzmaStatus is used only as output value for function call */
+
+
+/* ---------- Interfaces ---------- */
+
+/* There are 3 levels of interfaces:
+ 1) Dictionary Interface
+ 2) Buffer Interface
+ 3) One Call Interface
+ You can select any of these interfaces, but don't mix functions from different
+ groups for same object. */
+
+
+/* There are two variants to allocate state for Dictionary Interface:
+ 1) LzmaDec_Allocate / LzmaDec_Free
+ 2) LzmaDec_AllocateProbs / LzmaDec_FreeProbs
+ You can use variant 2, if you set dictionary buffer manually.
+ For Buffer Interface you must always use variant 1.
+
+LzmaDec_Allocate* can return:
+ SZ_OK
+ SZ_ERROR_MEM - Memory allocation error
+ SZ_ERROR_UNSUPPORTED - Unsupported properties
+*/
+
+SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc);
+void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc);
+
+SRes LzmaDec_Allocate(CLzmaDec *state, const Byte *prop, unsigned propsSize, ISzAlloc *alloc);
+void LzmaDec_Free(CLzmaDec *state, ISzAlloc *alloc);
+
+/* ---------- Dictionary Interface ---------- */
+
+/* You can use it, if you want to eliminate the overhead for data copying from
+ dictionary to some other external buffer.
+ You must work with CLzmaDec variables directly in this interface.
+
+ STEPS:
+ LzmaDec_Constr()
+ LzmaDec_Allocate()
+ for (each new stream)
+ {
+ LzmaDec_Init()
+ while (it needs more decompression)
+ {
+ LzmaDec_DecodeToDic()
+ use data from CLzmaDec::dic and update CLzmaDec::dicPos
+ }
+ }
+ LzmaDec_Free()
+*/
+
+/* LzmaDec_DecodeToDic
+
+ The decoding to internal dictionary buffer (CLzmaDec::dic).
+ You must manually update CLzmaDec::dicPos, if it reaches CLzmaDec::dicBufSize !!!
+
+finishMode:
+ It has meaning only if the decoding reaches output limit (dicLimit).
+ LZMA_FINISH_ANY - Decode just dicLimit bytes.
+ LZMA_FINISH_END - Stream must be finished after dicLimit.
+
+Returns:
+ SZ_OK
+ status:
+ LZMA_STATUS_FINISHED_WITH_MARK
+ LZMA_STATUS_NOT_FINISHED
+ LZMA_STATUS_NEEDS_MORE_INPUT
+ LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK
+ SZ_ERROR_DATA - Data error
+*/
+
+SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit,
+ const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status);
+
+
+/* ---------- Buffer Interface ---------- */
+
+/* It's zlib-like interface.
+ See LzmaDec_DecodeToDic description for information about STEPS and return results,
+ but you must use LzmaDec_DecodeToBuf instead of LzmaDec_DecodeToDic and you don't need
+ to work with CLzmaDec variables manually.
+
+finishMode:
+ It has meaning only if the decoding reaches output limit (*destLen).
+ LZMA_FINISH_ANY - Decode just destLen bytes.
+ LZMA_FINISH_END - Stream must be finished after (*destLen).
+*/
+
+SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen,
+ const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status);
+
+
+/* ---------- One Call Interface ---------- */
+
+/* LzmaDecode
+
+finishMode:
+ It has meaning only if the decoding reaches output limit (*destLen).
+ LZMA_FINISH_ANY - Decode just destLen bytes.
+ LZMA_FINISH_END - Stream must be finished after (*destLen).
+
+Returns:
+ SZ_OK
+ status:
+ LZMA_STATUS_FINISHED_WITH_MARK
+ LZMA_STATUS_NOT_FINISHED
+ LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK
+ SZ_ERROR_DATA - Data error
+ SZ_ERROR_MEM - Memory allocation error
+ SZ_ERROR_UNSUPPORTED - Unsupported properties
+ SZ_ERROR_INPUT_EOF - It needs more bytes in input buffer (src).
+*/
+
+SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen,
+ const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode,
+ ELzmaStatus *status, ISzAlloc *alloc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/spectro/LzmaTypes.h b/spectro/LzmaTypes.h
new file mode 100644
index 0000000..7732c24
--- /dev/null
+++ b/spectro/LzmaTypes.h
@@ -0,0 +1,254 @@
+/* Types.h -- Basic types
+2010-10-09 : Igor Pavlov : Public domain */
+
+#ifndef __7Z_TYPES_H
+#define __7Z_TYPES_H
+
+#include <stddef.h>
+
+#ifdef _WIN32
+#include <windows.h>
+#endif
+
+#ifndef EXTERN_C_BEGIN
+#ifdef __cplusplus
+#define EXTERN_C_BEGIN extern "C" {
+#define EXTERN_C_END }
+#else
+#define EXTERN_C_BEGIN
+#define EXTERN_C_END
+#endif
+#endif
+
+EXTERN_C_BEGIN
+
+#define SZ_OK 0
+
+#define SZ_ERROR_DATA 1
+#define SZ_ERROR_MEM 2
+#define SZ_ERROR_CRC 3
+#define SZ_ERROR_UNSUPPORTED 4
+#define SZ_ERROR_PARAM 5
+#define SZ_ERROR_INPUT_EOF 6
+#define SZ_ERROR_OUTPUT_EOF 7
+#define SZ_ERROR_READ 8
+#define SZ_ERROR_WRITE 9
+#define SZ_ERROR_PROGRESS 10
+#define SZ_ERROR_FAIL 11
+#define SZ_ERROR_THREAD 12
+
+#define SZ_ERROR_ARCHIVE 16
+#define SZ_ERROR_NO_ARCHIVE 17
+
+typedef int SRes;
+
+#ifdef _WIN32
+typedef DWORD WRes;
+#else
+typedef int WRes;
+#endif
+
+#ifndef RINOK
+#define RINOK(x) { int __result__ = (x); if (__result__ != 0) return __result__; }
+#endif
+
+typedef unsigned char Byte;
+typedef short Int16;
+typedef unsigned short UInt16;
+
+#ifdef _LZMA_UINT32_IS_ULONG
+typedef long Int32;
+typedef unsigned long UInt32;
+#else
+typedef int Int32;
+typedef unsigned int UInt32;
+#endif
+
+#ifdef _SZ_NO_INT_64
+
+/* define _SZ_NO_INT_64, if your compiler doesn't support 64-bit integers.
+ NOTES: Some code will work incorrectly in that case! */
+
+typedef long Int64;
+typedef unsigned long UInt64;
+
+#else
+
+#if defined(_MSC_VER) || defined(__BORLANDC__)
+typedef __int64 Int64;
+typedef unsigned __int64 UInt64;
+#define UINT64_CONST(n) n
+#else
+typedef long long int Int64;
+typedef unsigned long long int UInt64;
+#define UINT64_CONST(n) n ## ULL
+#endif
+
+#endif
+
+#ifdef _LZMA_NO_SYSTEM_SIZE_T
+typedef UInt32 SizeT;
+#else
+typedef size_t SizeT;
+#endif
+
+typedef int Bool;
+#define True 1
+#define False 0
+
+
+#ifdef _WIN32
+#define MY_STD_CALL __stdcall
+#else
+#define MY_STD_CALL
+#endif
+
+#ifdef _MSC_VER
+
+#if _MSC_VER >= 1300
+#define MY_NO_INLINE __declspec(noinline)
+#else
+#define MY_NO_INLINE
+#endif
+
+#define MY_CDECL __cdecl
+#define MY_FAST_CALL __fastcall
+
+#else
+
+#define MY_CDECL
+#define MY_FAST_CALL
+
+#endif
+
+
+/* The following interfaces use first parameter as pointer to structure */
+
+typedef struct
+{
+ Byte (*Read)(void *p); /* reads one byte, returns 0 in case of EOF or error */
+} IByteIn;
+
+typedef struct
+{
+ void (*Write)(void *p, Byte b);
+} IByteOut;
+
+typedef struct
+{
+ SRes (*Read)(void *p, void *buf, size_t *size);
+ /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream.
+ (output(*size) < input(*size)) is allowed */
+} ISeqInStream;
+
+/* it can return SZ_ERROR_INPUT_EOF */
+SRes SeqInStream_Read(ISeqInStream *stream, void *buf, size_t size);
+SRes SeqInStream_Read2(ISeqInStream *stream, void *buf, size_t size, SRes errorType);
+SRes SeqInStream_ReadByte(ISeqInStream *stream, Byte *buf);
+
+typedef struct
+{
+ size_t (*Write)(void *p, const void *buf, size_t size);
+ /* Returns: result - the number of actually written bytes.
+ (result < size) means error */
+} ISeqOutStream;
+
+typedef enum
+{
+ SZ_SEEK_SET = 0,
+ SZ_SEEK_CUR = 1,
+ SZ_SEEK_END = 2
+} ESzSeek;
+
+typedef struct
+{
+ SRes (*Read)(void *p, void *buf, size_t *size); /* same as ISeqInStream::Read */
+ SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin);
+} ISeekInStream;
+
+typedef struct
+{
+ SRes (*Look)(void *p, const void **buf, size_t *size);
+ /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream.
+ (output(*size) > input(*size)) is not allowed
+ (output(*size) < input(*size)) is allowed */
+ SRes (*Skip)(void *p, size_t offset);
+ /* offset must be <= output(*size) of Look */
+
+ SRes (*Read)(void *p, void *buf, size_t *size);
+ /* reads directly (without buffer). It's same as ISeqInStream::Read */
+ SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin);
+} ILookInStream;
+
+SRes LookInStream_LookRead(ILookInStream *stream, void *buf, size_t *size);
+SRes LookInStream_SeekTo(ILookInStream *stream, UInt64 offset);
+
+/* reads via ILookInStream::Read */
+SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes errorType);
+SRes LookInStream_Read(ILookInStream *stream, void *buf, size_t size);
+
+#define LookToRead_BUF_SIZE (1 << 14)
+
+typedef struct
+{
+ ILookInStream s;
+ ISeekInStream *realStream;
+ size_t pos;
+ size_t size;
+ Byte buf[LookToRead_BUF_SIZE];
+} CLookToRead;
+
+void LookToRead_CreateVTable(CLookToRead *p, int lookahead);
+void LookToRead_Init(CLookToRead *p);
+
+typedef struct
+{
+ ISeqInStream s;
+ ILookInStream *realStream;
+} CSecToLook;
+
+void SecToLook_CreateVTable(CSecToLook *p);
+
+typedef struct
+{
+ ISeqInStream s;
+ ILookInStream *realStream;
+} CSecToRead;
+
+void SecToRead_CreateVTable(CSecToRead *p);
+
+typedef struct
+{
+ SRes (*Progress)(void *p, UInt64 inSize, UInt64 outSize);
+ /* Returns: result. (result != SZ_OK) means break.
+ Value (UInt64)(Int64)-1 for size means unknown value. */
+} ICompressProgress;
+
+typedef struct
+{
+ void *(*Alloc)(void *p, size_t size);
+ void (*Free)(void *p, void *address); /* address can be 0 */
+} ISzAlloc;
+
+#define IAlloc_Alloc(p, size) (p)->Alloc((p), size)
+#define IAlloc_Free(p, a) (p)->Free((p), a)
+
+#ifdef _WIN32
+
+#define CHAR_PATH_SEPARATOR '\\'
+#define WCHAR_PATH_SEPARATOR L'\\'
+#define STRING_PATH_SEPARATOR "\\"
+#define WSTRING_PATH_SEPARATOR L"\\"
+
+#else
+
+#define CHAR_PATH_SEPARATOR '/'
+#define WCHAR_PATH_SEPARATOR L'/'
+#define STRING_PATH_SEPARATOR "/"
+#define WSTRING_PATH_SEPARATOR L"/"
+
+#endif
+
+EXTERN_C_END
+
+#endif
diff --git a/spectro/Makefile.OSX b/spectro/Makefile.OSX
new file mode 100644
index 0000000..f98d078
--- /dev/null
+++ b/spectro/Makefile.OSX
@@ -0,0 +1,44 @@
+# MAC OSX, derived from UNIX setup
+
+# Copyright 2000 - 2007 Graeme W. Gill
+# This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
+# see the License2.txt file for licencing details.
+
+SLASH = /
+SUFLIB = .a
+SUFOBJ = .o
+SUFEXE =
+CMDSEP = ;
+
+INCFLAG = -I
+DEFFLAG = -D
+UNDEFFLAG = -U
+CCOPTFLAG = -O
+CCDEBUGFLAG = -g
+CCPROFFLAG =
+LINKDEBUGFLAG =
+LINKPROFFLAG =
+
+STDHDRSDEF = /usr/include
+
+MAKEU = make
+LIBU = ar -r
+LIBOF =
+RANLIB = ranlib
+AS = as
+CCFLAGSDEF = -DUNIX -c
+CC = cc $(CCFLAGS) $(STDHDRS)
+CCOF = -o
+LINKFLAGSDEF = -lm -framework Carbon -framework IOKit -framework CoreFoundation -framework AudioToolbox
+LINKLIBS =
+LINK = cc $(LINKFLAGS) $(LINKLIBS)
+LINKOF = -o
+CP = cp
+RM = rm
+
+.SUFFIXES:
+.SUFFIXES: .c $(SUFLIB) $(SUFOBJ) $(SUFEXE)
+
+.c$(SUFOBJ):
+ $(CC) $(CCOF)$*$(SUFOBJ) $<
+
diff --git a/spectro/Makefile.SA b/spectro/Makefile.SA
new file mode 100644
index 0000000..4d253ec
--- /dev/null
+++ b/spectro/Makefile.SA
@@ -0,0 +1,183 @@
+
+# Boilerplate Makefile for compiling standalone instrumement driver
+
+# Copyright 2000 - 2007 Graeme W. Gill
+# This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
+# see the License2.txt file for licencing details.
+
+# "include" the right environment for your system,
+# by uncommenting the appropriate line:
+
+# Microsoft VC++, WinNT setup
+include Makefile.WNT
+
+# Generic UNIX setup
+#include Makefile.UNIX
+
+# Apple OS X
+#include Makefile.OSX
+
+###############################
+
+
+CCDEFINES = $(DEFFLAG)SALONEINSTLIB $(DEFFLAG)ENABLE_SERIAL $(DEFFLAG)ENABLE_USB $(DEFFLAG)NATIVE_USB
+
+#Set optimisation on
+CCFLAGS = $(CCFLAGSDEF) $(CCOPTFLAG) $(CCDEFINES) $(BCONFIG)
+
+# Set debugging on
+#CCFLAGS = $(CCFLAGSDEF) $(CCDEBUGFLAG) $(CCDEFINES) $(BCONFIG)
+# Debugging and debugging #define
+#CCFLAGS = $(CCFLAGSDEF) $(CCDEBUGFLAG) $(CCDEFINES) $(DEFFLAG)DEBUG
+
+LINKFLAGS = $(LINKFLAGSDEF) $(LINKDEBUGFLAG)
+
+# Where headers come from
+STDHDRS = $(INCFLAG)$(STDHDRSDEF)
+WIN_STDHDRS = $(INCFLAG)usb$(SLASH)driver
+
+all:: libinst$(SUFLIB) libinstappsup$(SUFLIB) spotread$(SUFEXE) oeminst$(SUFEXE)
+
+INSTHEADERS = dtp20.h dtp22.h dtp41.h dtp51.h dtp92.h ss.h ss_imp.h i1disp.h i1d3.h i1pro.h i1pro_imp.h munki.h munki_imp.h hcfr.h huey.h colorhug.h spyd2.h spyd2setup.h spyd2PLD.h
+INSOBJS = dtp20$(SUFOBJ) dtp22$(SUFOBJ) dtp41$(SUFOBJ) dtp51$(SUFOBJ) dtp92$(SUFOBJ) ss$(SUFOBJ) ss_imp$(SUFOBJ) i1disp$(SUFOBJ) i1d3$(SUFOBJ) i1pro$(SUFOBJ) i1pro_imp$(SUFOBJ) munki$(SUFOBJ) munki_imp$(SUFOBJ) hcfr$(SUFOBJ) huey$(SUFOBJ) colorhug$(SUFOBJ) spyd2$(SUFOBJ)
+
+HEADERS = pollem.h conv.h aglob.h hidio.h icoms.h inst.c inst.h insttypeinst.h insttypes.h $(INSTHEADERS) usbio.h xspect.h rspl1.h sort.h xdg_bds.h ccss.h ccmx.h pars.h cgats.h instappsup.h usb$(SLASH)driver$(SLASH)driver_api.h
+
+# libinst objects
+OBJS = conv$(SUFOBJ) aglob$(SUFOBJ) inst$(SUFOBJ) numsup$(SUFOBJ) rspl1$(SUFOBJ) icoms$(SUFOBJ) usbio$(SUFOBJ) hidio$(SUFOBJ) insttypes$(SUFOBJ) pollem$(SUFOBJ) xspect$(SUFOBJ) xdg_bds$(SUFOBJ) ccss$(SUFOBJ) ccmx$(SUFOBJ) pars$(SUFOBJ) cgats$(SUFOBJ) $(INSOBJS)
+
+
+# instrument library
+
+conv$(SUFOBJ): conv.c $(HEADERS)
+ $(CC) conv.c
+
+aglob$(SUFOBJ): aglob.c $(HEADERS)
+ $(CC) aglob.c
+
+inst$(SUFOBJ): inst.c $(HEADERS)
+ $(CC) inst.c
+
+numsup$(SUFOBJ): numsup.c $(HEADERS)
+ $(CC) numsup.c
+
+rspl1$(SUFOBJ): rspl1.c $(HEADERS)
+ $(CC) rspl1.c
+
+icoms$(SUFOBJ): icoms.c $(HEADERS)
+ $(CC) icoms.c
+
+usbio$(SUFOBJ): usbio.c $(HEADERS)
+ $(CC) usbio.c
+
+hidio$(SUFOBJ): hidio.c $(HEADERS)
+ $(CC) hidio.c
+
+insttypes$(SUFOBJ): insttypes.c $(HEADERS)
+ $(CC) insttypes.c
+
+pollem$(SUFOBJ): pollem.c $(HEADERS)
+ $(CC) pollem.c
+
+xspect$(SUFOBJ): xspect.c $(HEADERS)
+ $(CC) xspect.c
+
+pars$(SUFOBJ): pars.c parsstd.c $(HEADERS)
+ $(CC) pars.c
+
+cgats$(SUFOBJ): cgats.c cgatsstd.c $(HEADERS)
+ $(CC) cgats.c
+
+dtp20$(SUFOBJ): dtp20.c $(HEADERS)
+ $(CC) dtp20.c
+
+dtp22$(SUFOBJ): dtp22.c $(HEADERS)
+ $(CC) dtp22.c
+
+dtp41$(SUFOBJ): dtp41.c $(HEADERS)
+ $(CC) dtp41.c
+
+dtp51$(SUFOBJ): dtp51.c $(HEADERS)
+ $(CC) dtp51.c
+
+dtp92$(SUFOBJ): dtp92.c $(HEADERS)
+ $(CC) dtp92.c
+
+ss$(SUFOBJ): ss.c $(HEADERS)
+ $(CC) ss.c
+
+ss_imp$(SUFOBJ): ss_imp.c $(HEADERS)
+ $(CC) ss_imp.c
+
+i1disp$(SUFOBJ): i1disp.c $(HEADERS)
+ $(CC) i1disp.c
+
+i1d3$(SUFOBJ): i1d3.c $(HEADERS)
+ $(CC) i1d3.c
+
+i1pro$(SUFOBJ): i1pro.c $(HEADERS)
+ $(CC) i1pro.c
+
+i1pro_imp$(SUFOBJ): i1pro_imp.c $(HEADERS)
+ $(CC) i1pro_imp.c
+
+munki$(SUFOBJ): munki.c $(HEADERS)
+ $(CC) munki.c
+
+munki_imp$(SUFOBJ): munki_imp.c $(HEADERS)
+ $(CC) munki_imp.c
+
+hcfr$(SUFOBJ): hcfr.c $(HEADERS)
+ $(CC) hcfr.c
+
+huey$(SUFOBJ): huey.c $(HEADERS)
+ $(CC) huey.c
+
+colorhug$(SUFOBJ): colorhug.c $(HEADERS)
+ $(CC) colorhug.c
+
+spyd2$(SUFOBJ): spyd2.c $(HEADERS)
+ $(CC) spyd2.c
+
+oemarch$(SUFOBJ): oemarch.c $(HEADERS)
+ $(CC) oemarch.c
+
+oeminst$(SUFOBJ): oeminst.c $(HEADERS)
+ $(CC) oeminst.c
+
+vinflate$(SUFOBJ): vinflate.c $(HEADERS)
+ $(CC) vinflate.c
+
+inflate$(SUFOBJ): inflate.c $(HEADERS)
+ $(CC) inflate.c
+
+LzmaDec$(SUFOBJ): LzmaDec.c LzmaDec.h LzmaTypes.h $(HEADERS)
+ $(CC) LzmaDec.c
+
+libinst$(SUFLIB): $(OBJS)
+ $(LIBU) $(LIBOF)$@ $(OBJS)
+ $(RANLIB) libinst$(SUFLIB)
+
+# instappsup objects
+SUPOBJS = instappsup$(SUFOBJ)
+
+# instappsup library
+
+instappsup$(SUFOBJ): instappsup.c $(HEADERS)
+ $(CC) instappsup.c
+
+libinstappsup$(SUFLIB): $(SUPOBJS)
+ $(LIBU) $(LIBOF)$@ $(SUPOBJS)
+ $(RANLIB) instappsup$(SUFLIB)
+
+# test/example code
+
+spotread$(SUFEXE): spotread$(SUFOBJ) libinst$(SUFLIB) inst.h
+ $(LINK) $(LINKOF)spotread$(SUFEXE) spotread$(SUFOBJ) libinst$(SUFLIB) libinstappsup$(SUFLIB) $(LINKLIBS)
+
+oeminst$(SUFEXE): oeminst$(SUFOBJ) oemarch$(SUFOBJ) inflate$(SUFOBJ) vinflate$(SUFOBJ) LzmaDec$(SUFOBJ) libinst$(SUFLIB) libinstappsup$(SUFLIB)
+ $(LINK) $(LINKOF)oeminst$(SUFEXE) oeminst$(SUFOBJ) oemarch$(SUFOBJ) inflate$(SUFOBJ) vinflate$(SUFOBJ) LzmaDec$(SUFOBJ) libinst$(SUFLIB) libinstappsup$(SUFLIB) $(LINKLIBS)
+
+clean:
+ $(RM) *$(SUFOBJ) *$(SUFLIB) spotread$(SUFEXE) i1d3css$(SUFEXE)
+
diff --git a/spectro/Makefile.UNIX b/spectro/Makefile.UNIX
new file mode 100644
index 0000000..5eaf6b5
--- /dev/null
+++ b/spectro/Makefile.UNIX
@@ -0,0 +1,44 @@
+# Generic UNIX setup
+
+# Copyright 2000 - 2007 Graeme W. Gill
+# This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
+# see the License2.txt file for licencing details.
+
+SLASH = /
+SUFLIB = .a
+SUFOBJ = .o
+SUFEXE =
+CMDSEP = ;
+
+INCFLAG = -I
+DEFFLAG = -D
+UNDEFFLAG = -U
+CCOPTFLAG = -O
+CCDEBUGFLAG = -g
+CCPROFFLAG =
+LINKDEBUGFLAG =
+LINKPROFFLAG =
+
+STDHDRSDEF = /usr/include
+
+MAKEU = make
+LIBU = ar -r
+LIBOF =
+RANLIB = echo
+AS = as
+CCFLAGSDEF = -DUNIX -DNATIVE_USB -c
+CC = cc $(CCFLAGS) $(STDHDRS)
+CCOF = -o
+LINKFLAGSDEF = -lm -lpthread -lrt
+LINKLIBS =
+LINK = cc $(LINKFLAGS) $(LINKLIBS)
+LINKOF = -o
+CP = cp
+RM = rm
+
+.SUFFIXES:
+.SUFFIXES: .c $(SUFLIB) $(SUFOBJ) $(SUFEXE)
+
+.c$(SUFOBJ):
+ $(CC) $(CCOF)$*$(SUFOBJ) $<
+
diff --git a/spectro/Makefile.WNT b/spectro/Makefile.WNT
new file mode 100644
index 0000000..83eef28
--- /dev/null
+++ b/spectro/Makefile.WNT
@@ -0,0 +1,47 @@
+# Microsoft VC++, WinNT setup
+
+# Copyright 2000 - 2007 Graeme W. Gill
+# This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
+# see the License2.txt file for licencing details.
+
+SLASH = \
+SUFLIB = .lib
+SUFOBJ = .obj
+SUFEXE = .exe
+CMDSEP = &
+
+INCFLAG = /I
+DEFFLAG = /D
+UNDEFFLAG = /U
+CCOPTFLAG = /Ox
+CCDEBUGFLAG = /Z7 /Od
+CCPROFFLAG = /Z7
+LINKDEBUGFLAG = /DEBUG
+LINKPROFFLAG = /PROFILE
+
+STDHDRSDEF = $(VCINSTALLDIR)\include
+LINKDIR = $(MSSDK)
+
+MAKEU = nmake
+LIBU = lib
+LIBOF = /OUT:
+RANLIB = rem
+AS = masm386
+# DLL build by default
+# /Wall /wd4820 /wd4100 /wd4255 /wd4711 /wd4668 /wd4018 /wd4389
+CCFLAGSDEF = /DNT /MD /c $(DEFFLAG)_CRT_SECURE_NO_DEPRECATE $(DEFFLAG)_CRT_NONSTDC_NO_DEPRECATE
+CC = cl /nologo $(CCFLAGS) $(STDHDRS) $(WIN_STDHDRS)
+CCOF = /Fo
+LINKLIBS = $(LINKDIR)\lib\user32.lib $(LINKDIR)\lib\gdi32.lib $(LINKDIR)\lib\advapi32.lib $(LINKDIR)\lib\setupapi.lib $(LINKDIR)\lib\ole32.lib
+LINKFLAGSDEF = /link /INCREMENTAL:NO
+LINK = link $(LINKFLAGS)
+LINKOF = /OUT:
+CP = copy
+RM = del
+
+.SUFFIXES:
+.SUFFIXES: .c $(SUFLIB) $(SUFOBJ) $(SUFEXE)
+
+.c$(SUFOBJ):
+ $(CC) $(CCOF)$*$(SUFOBJ) $<
+
diff --git a/spectro/Makefile.am b/spectro/Makefile.am
new file mode 100644
index 0000000..835336b
--- /dev/null
+++ b/spectro/Makefile.am
@@ -0,0 +1,53 @@
+include $(top_srcdir)/Makefile.shared
+
+privatelib_LTLIBRARIES = libinsttypes.la libconv.la libinst.la libinstapp.la libdisp.la
+privatelibdir = $(pkglibdir)
+
+libinsttypes_la_SOURCES = insttypes.h insttypes.c insttypeinst.h
+libinsttypes_la_LIBADD = ../libargyll.la
+
+libinst_la_SOURCES = inst.h inst.c insttypes.c dtp20.c dtp20.h dtp22.c \
+ dtp22.h dtp41.c dtp41.h dtp51.c dtp51.h dtp92.c dtp92.h \
+ i1disp.c i1disp.h i1pro.c i1pro.h i1pro_imp.c i1pro_imp.h \
+ munki.c munki_imp.c ss.c ss.h ss_imp.c ss_imp.h hcfr.c hcfr.h \
+ spyd2.c spyd2.h spyd2setup.h spyd2PLD.h huey.c huey.h \
+ usbio.c hidio.c pollem.c pollem.h icoms.h conv.h usbio.h \
+ hidio.h i1d3.h i1d3.c colorhug.c colorhug.h icoms.c \
+ oemarch.h oemarch.c iusb.h vinflate.c inflate.c
+libinst_la_LIBADD = $(ICC_LIBS) ../numlib/libargyllnum.la \
+ ../libargyll.la ../rspl/librspl.la libconv.la
+
+libinst_la_LDFLAGS = $(shell libusb-config --libs)
+
+libdisp_la_SOURCES = dispsup.c dispwin.c dispwin.h dispsup.h webwin.c webwin.h mongoose.c mongoose.h
+libdisp_la_LIBADD = $(X_LIBS) ../ucmm/libucmm.la $(ICC_LIBS) -ldl \
+ ../numlib/libargyllnum.la libconv.la libinst.la libinstapp.la \
+ ../libargyll.la
+
+libconv_la_SOURCES = conv.c pollem.c xdg_bds.h xdg_bds.c aglob.c ../xicc/ccss.c
+libconv_la_LIBADD = ../libargyll.la ../numlib/libargyllnum.la \
+ ../cgats/libcgats.la
+
+libinstapp_la_SOURCES = instappsup.c instappsup.h
+libinstapp_la_LIBADD = libinst.la ../libargyll.la ../numlib/libargyllnum.la \
+ libconv.la
+
+LDADD = ./libinsttypes.la ./libinstapp.la ./libdisp.la ./libinst.la \
+ ./libconv.la ../ucmm/libucmm.la ../jcnf/libjcnf.la $(YAJL_LIBS) \
+ ../xicc/libxicc.la $(ICC_LIBS) ../cgats/libcgats.la \
+ ../rspl/librspl.la ../gamut/libgamut.la ../target/libtarget.la \
+ ../plot/libplot.la ../numlib/libargyllnum.la $(X_LIBS) \
+ ../libargyll.la
+
+bin_PROGRAMS = dispwin synthcal dispread dispcal fakeread synthread \
+ chartread spotread illumread ccxxmake spec2cie average oeminst
+
+dispwin_CFLAGS = $(AM_CFLAGS) -DSTANDALONE_TEST
+
+synthcal_DEPENDENCIES = ../gamut/libgammap.la ../target/libtarget.la
+
+refdir = $(datadir)/color/argyll/ref
+
+ref_DATA = ccxx.ti1 SOtele.sp $(wildcard *.cal)
+
+EXTRA_DIST = Readme.txt
diff --git a/spectro/Readme.txt b/spectro/Readme.txt
new file mode 100644
index 0000000..b9fe0aa
--- /dev/null
+++ b/spectro/Readme.txt
@@ -0,0 +1,37 @@
+This directory containts the routines to operate a variety
+of color instruments.
+
+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.
+
+
+chartread.exe Is used to read the test chart and create
+ the chart readings file.
+
+filmread.exe Is used to read the film test chart and create
+ the chart readings file.
+
+dispcal.exe Calibrate a display
+
+dispread.exe Read test chart values for a display
+
+fakeread.exe Fake reading of a device, using a profile instead.
+
+spec2cie.exe Convert a spectral reading file into a CIE tristimulus file.
+
+spotread.exe Read spot values.
+
+instlib.txt Explanation of how to extract and build a standalone instrument library.
diff --git a/spectro/SOtele.sp b/spectro/SOtele.sp
new file mode 100644
index 0000000..f63d90e
--- /dev/null
+++ b/spectro/SOtele.sp
@@ -0,0 +1,23 @@
+SPECT
+
+DESCRIPTOR "Argyll Spectrolino tele adapter compensation filter"
+ORIGINATOR "Argyll CMS"
+CREATED "Wed Jun 21 17:49:57 2006"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "36"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "380.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "730.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "1.000000"
+
+NUMBER_OF_FIELDS 36
+BEGIN_DATA_FORMAT
+SPEC_380 SPEC_390 SPEC_400 SPEC_410 SPEC_420 SPEC_430 SPEC_440 SPEC_450 SPEC_460 SPEC_470 SPEC_480 SPEC_490 SPEC_500 SPEC_510 SPEC_520 SPEC_530 SPEC_540 SPEC_550 SPEC_560 SPEC_570 SPEC_580 SPEC_590 SPEC_600 SPEC_610 SPEC_620 SPEC_630 SPEC_640 SPEC_650 SPEC_660 SPEC_670 SPEC_680 SPEC_690 SPEC_700 SPEC_710 SPEC_720 SPEC_730
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+1.7378 1.3294 1.1691 1.1313 1.1326 1.1301 1.1302 1.1290 1.1288 1.1274 1.1286 1.1280 1.1273 1.1263 1.1242 1.1231 1.1231 1.1231 1.1231 1.1231 1.1231 1.1231 1.1231 1.1231 1.1231 1.1231 1.1231 1.1231 1.1231 1.1231 1.1231 1.1231 1.1231 1.1231 1.1231 1.1231
+END_DATA
diff --git a/spectro/afiles b/spectro/afiles
new file mode 100644
index 0000000..045614c
--- /dev/null
+++ b/spectro/afiles
@@ -0,0 +1,110 @@
+Readme.txt
+instlib.txt
+instlib.ksh
+IntsLib_Readme.txt
+License.txt
+License2.txt
+License3.txt
+afiles
+ifiles
+Jamfile
+chartread.c
+dispread.c
+dispcal.c
+illumread.c
+ccxxmake.c
+dispsup.h
+dispsup.c
+webwin.h
+webwin.c
+mongoose.h
+mongoose.c
+insttypes.h
+insttypes.c
+insttypeinst.h
+inst.c
+inst.h
+instappsup.c
+instappsup.h
+dtp20.c
+dtp20.h
+dtp22.c
+dtp22.h
+dtp41.c
+dtp41.h
+dtp51.c
+dtp51.h
+dtp92.c
+dtp92.h
+ss.h
+ss.c
+ss_imp.h
+ss_imp.c
+i1disp.c
+i1disp.h
+i1d3.c
+i1d3.h
+i1pro.h
+i1pro.c
+i1pro_imp.h
+i1pro_imp.c
+munki.h
+munki.c
+munki_imp.h
+munki_imp.c
+hcfr.c
+hcfr.h
+spyd2.c
+spyd2.h
+spyd2setup.h
+spyd2PLD.h
+oemarch.h
+oemarch.c
+oeminst.c
+inflate.c
+vinflate.c
+LzmaTypes.h
+LzmaDec.h
+LzmaDec.c
+huey.c
+huey.h
+colorhug.c
+colorhug.h
+spec2cie.c
+average.c
+conv.h
+conv.c
+aglob.h
+aglob.c
+xdg_bds.h
+xdg_bds.c
+icoms.h
+icoms.c
+icoms_nt.c
+icoms_ux.c
+iusb.h
+usbio.h
+usbio.c
+usbio_lusb.c
+usbio_nt.c
+usbio_ox.c
+usbio_lx.c
+hidio.h
+hidio.c
+pollem.h
+pollem.c
+dispwin.c
+dispwin.h
+synthcal.c
+spotread.c
+fakeread.c
+synthread.c
+linear.sp
+SOtele.sp
+linear.cal
+strange.cal
+ccxx.ti1
+Makefile.SA
+Makefile.OSX
+Makefile.UNIX
+Makefile.WNT
diff --git a/spectro/aglob.c b/spectro/aglob.c
new file mode 100644
index 0000000..8d4c846
--- /dev/null
+++ b/spectro/aglob.c
@@ -0,0 +1,141 @@
+
+/* Provide a system independent glob type function */
+
+/*************************************************************************
+ Copyright 2011 Graeme W. Gill
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+
+ *************************************************************************/
+
+#if defined (NT)
+# if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0501
+# if defined _WIN32_WINNT
+# undef _WIN32_WINNT
+# endif
+# define _WIN32_WINNT 0x0501
+# endif
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+# include <io.h>
+#endif
+
+#if defined (UNIX)
+# include <unistd.h>
+# include <glob.h>
+# include <pthread.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <time.h>
+
+#include "numsup.h"
+#include "aglob.h"
+
+/* Create the aglob */
+/* Return nz on malloc error */
+int aglob_create(aglob *g, char *spath) {
+#ifdef NT
+ char *pp;
+ int rlen;
+ /* Figure out where the filename starts */
+ if ((pp = strrchr(spath, '/')) == NULL
+ && (pp = strrchr(spath, '\\')) == NULL)
+ rlen = 0;
+ else
+ rlen = pp - spath + 1;
+
+ if ((g->base = malloc(rlen + 1)) == NULL) {
+ a1loge(g_log, 1, "aglob_create: malloc failed\n");
+ return 1;
+ }
+
+ memmove(g->base, spath, rlen);
+ g->base[rlen] = '\000';
+
+ g->first = 1;
+ g->ff = _findfirst(spath, &g->ffs);
+#else /* UNIX */
+ memset(&g->g, 0, sizeof(g->g));
+ g->rv = glob(spath, GLOB_NOSORT, NULL, &g->g);
+//a1loge(g_log, 0, "~1 glob '%s' returns %d and gl_pathc = %d\n",spath,g->rv,g->g.gl_pathc);
+ if (g->rv == GLOB_NOSPACE) {
+ a1loge(g_log, 1, "aglob_create: glob returned GLOB_NOSPACE\n");
+ return 1;
+ }
+ g->ix = 0;
+#endif
+ g->merr = 0;
+ return 0;
+}
+
+/* Return an allocated string of the next match. */
+/* Return NULL if no more matches */
+char *aglob_next(aglob *g) {
+ char *fpath;
+
+#ifdef NT
+ if (g->ff == -1L) {
+ return NULL;
+ }
+ if (g->first == 0) {
+ if (_findnext(g->ff, &g->ffs) != 0) {
+ return NULL;
+ }
+ }
+ g->first = 0;
+
+ /* Convert match filename to full path */
+ if ((fpath = malloc(strlen(g->base) + strlen(g->ffs.name) + 1)) == NULL) {
+ a1loge(g_log, 1, "aglob_next: malloc failed\n");
+ g->merr = 1;
+ return NULL;
+ }
+ strcpy(fpath, g->base);
+ strcat(fpath, g->ffs.name);
+ return fpath;
+#else
+ if (g->rv != 0 || g->ix >= g->g.gl_pathc)
+ return NULL;
+ if ((fpath = strdup(g->g.gl_pathv[g->ix])) == NULL) {
+ a1loge(g_log, 1, "aglob_next: strdup failed\n");
+ g->merr = 1;
+ return NULL;
+ }
+ g->ix++;
+ return fpath;
+#endif
+}
+
+void aglob_cleanup(aglob *g) {
+#ifdef NT
+ if (g->ff != -1L)
+ _findclose(g->ff);
+ free(g->base);
+#else /* UNIX */
+ if (g->rv == 0)
+ globfree(&g->g);
+#endif
+}
+
diff --git a/spectro/aglob.h b/spectro/aglob.h
new file mode 100644
index 0000000..6c46746
--- /dev/null
+++ b/spectro/aglob.h
@@ -0,0 +1,63 @@
+#ifndef AGLOB_H
+
+/* Provide a system independent glob type function */
+
+/*************************************************************************
+ Copyright 2011 Graeme W. Gill
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+
+ *************************************************************************/
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+typedef struct {
+#ifdef NT
+ char *base; /* Base path */
+ struct _finddata_t ffs;
+ long ff;
+ int first;
+#else /* UNIX */
+ glob_t g;
+ int rv; /* glob return value */
+ size_t ix;
+#endif
+ int merr; /* NZ on malloc error */
+} aglob;
+
+
+/* Create the aglob for files matching the given path and pattern. */
+/* Return nz on malloc error */
+int aglob_create(aglob *g, char *spath);
+
+/* Return an allocated string of the next match. */
+/* Return NULL if no more matches */
+char *aglob_next(aglob *g);
+
+/* Free the aglob once we're done with it */
+void aglob_cleanup(aglob *g);
+
+#ifdef __cplusplus
+ }
+#endif
+
+#define AGLOB_H
+#endif /* AGLOB_H */
diff --git a/spectro/average.c b/spectro/average.c
new file mode 100644
index 0000000..d031154
--- /dev/null
+++ b/spectro/average.c
@@ -0,0 +1,405 @@
+/*
+ * Argyll Color Correction System
+ * Average one or more .ti3 (or other CGATS like) file values together.
+ *
+ * Author: Graeme W. Gill
+ * Date: 18/1/2011
+ *
+ * Copyright 2005, 2010, 2011 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ *
+ * (based on splitti3.c)
+ */
+
+/*
+ * TTBD:
+
+ Should probably re-index SAMPLE_ID field
+
+ */
+
+#undef DEBUG
+
+#define verbo stdout
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <time.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "cgats.h"
+#include "xicc.h"
+#include "insttypes.h"
+
+void usage(char *diag, ...) {
+ int i;
+ fprintf(stderr,"Average or merge values in .ti3 like files, Version %s\n",ARGYLL_VERSION_STR);
+ if (diag != NULL) {
+ va_list args;
+ fprintf(stderr," Diagnostic: ");
+ va_start(args, diag);
+ vfprintf(stderr, diag, args);
+ va_end(args);
+ fprintf(stderr,"\n");
+ }
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ fprintf(stderr,"usage: average [-options] input1.ti3 input2.ti3 ... output.ti3\n");
+ fprintf(stderr," -v Verbose\n");
+ fprintf(stderr," -m Merge rather than average\n");
+ fprintf(stderr," input1.ti3 First input file\n");
+ fprintf(stderr," input2.ti3 Second input file\n");
+ fprintf(stderr," ... etc.\n");
+ fprintf(stderr," output.ti3 Resulting averaged or merged output file\n");
+ exit(1);
+}
+
+/* Information about each file */
+struct _inpinfo {
+ char name[MAXNAMEL+1];
+ cgats *c;
+}; typedef struct _inpinfo inpinfo;
+
+int main(int argc, char *argv[]) {
+ int fa,nfa; /* current argument we're looking at */
+ int verb = 0;
+ int domerge = 0; /* Merge rather than average */
+
+ int ninps = 0; /* Number of input files */
+ inpinfo *inps; /* Input file info. inp[ninp] == output file */
+ cgats *ocg; /* Copy of output cgats * */
+
+ cgats_set_elem *setel; /* Array of set value elements */
+ int *flags; /* Point to destination of set */
+
+ int nchan; /* Number of device channels */
+ int chix[ICX_MXINKS]; /* Device chanel indexes */
+ int pcsix[3]; /* PCS chanel indexes */
+ int isLab = 0;
+
+ int i, j, n;
+
+ error_program = "average";
+
+ if (argc <= 3)
+ usage("Too few arguments (%d, minimum is 2)",argc-1);
+
+ /* Process the arguments */
+ for(fa = 1;fa < argc;fa++) {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage("Usage requested");
+
+ /* Merge */
+ else if (argv[fa][1] == 'm' || argv[fa][1] == 'M') {
+ domerge = 1;
+ }
+
+ /* Verbosity */
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') {
+ verb = 1;
+ }
+
+ else {
+ usage("Unknown flag '%c'",argv[fa][1]);
+ }
+
+ } else if (argv[fa][0] != '\000') {
+ /* Get the next filename */
+
+ if (ninps == 0)
+ inps = (inpinfo *)malloc(sizeof(inpinfo));
+ else
+ inps = (inpinfo *)realloc(inps, (ninps+1) * sizeof(inpinfo));
+ if (inps == NULL)
+ error("Malloc failed in allocating space for file info.");
+
+ memset((void *)&inps[ninps], 0, sizeof(inpinfo));
+ strncpy(inps[ninps].name,argv[fa],MAXNAMEL);
+ inps[ninps].name[MAXNAMEL] = '\000';
+
+ ninps++;
+ } else {
+ break;
+ }
+ }
+
+ if (ninps < 2)
+ error("Must be at least one input and one output file specified");
+
+ ninps--; /* Number of inputs */
+
+ /* Open and read each input file */
+ for (n = 0; n <= ninps; n++) {
+
+ if ((inps[n].c = new_cgats()) == NULL)
+ error("Failed to create cgats object for file '%s'",inps[n].name);
+
+ if (n < ninps) { /* If input file, read it */
+ inps[n].c->add_other(inps[n].c, ""); /* Allow any signature file */
+
+ if (inps[n].c->read_name(inps[n].c, inps[n].name))
+ error("CGATS file '%s' read error : %s",inps[n].name,inps[n].c->err);
+
+ if (inps[n].c->ntables < 1)
+ error ("Input file '%s' doesn't contain at least one table",inps[n].name);
+ }
+ }
+ ocg = inps[ninps].c; /* Alias for output file */
+
+ /* Duplicate everything from the first input file into the output file. */
+ for (n = 0; n < inps[0].c->ntables; n++) {
+
+ if (inps[0].c->t[n].tt == cgats_X) {
+ ocg->add_other(ocg, inps[0].c->cgats_type);
+ ocg->add_table(ocg, tt_other, 0);
+ } else if (inps[0].c->t[n].tt == tt_other) {
+ int oi;
+ oi = ocg->add_other(ocg, inps[0].c->others[inps[0].c->t[n].oi]);
+ ocg->add_table(ocg, tt_other, oi);
+ } else {
+ ocg->add_table(ocg, inps[0].c->t[n].tt, 0);
+ }
+
+ /* Duplicate all the keywords */
+ for (i = 0; i < inps[0].c->t[n].nkwords; i++) {
+//printf("~1 table %d, adding keyword '%s'\n",n,inps[0].c->t[n].ksym[i]);
+ ocg->add_kword(ocg, n, inps[0].c->t[n].ksym[i], inps[0].c->t[n].kdata[i], NULL);
+ }
+
+ /* Duplicate all of the fields */
+ for (i = 0; i < inps[0].c->t[n].nfields; i++) {
+ ocg->add_field(ocg, n, inps[0].c->t[n].fsym[i], inps[0].c->t[n].ftype[i]);
+ }
+
+ /* Duplicate all of the data */
+ if ((setel = (cgats_set_elem *)malloc(
+ sizeof(cgats_set_elem) * inps[0].c->t[n].nfields)) == NULL)
+ error("Malloc failed!");
+
+ for (i = 0; i < inps[0].c->t[n].nsets; i++) {
+ inps[0].c->get_setarr(inps[0].c, n, i, setel);
+ ocg->add_setarr(ocg, n, setel);
+ }
+ free(setel);
+ }
+
+ /* Figure out the indexes of the device channels */
+ {
+ int ti;
+ char *buf;
+ char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" };
+ char *labfname[3] = { "LAB_L", "LAB_A", "LAB_B" };
+ char *outc;
+ int nmask;
+ char *bident;
+
+ if ((ti = inps[0].c->find_kword(inps[0].c, 0, "COLOR_REP")) < 0)
+ error("Input file '%s' doesn't contain keyword COLOR_REP", inps[0].name);
+
+ if ((buf = strdup(inps[0].c->t[0].kdata[ti])) == NULL)
+ error("Malloc failed");
+
+ /* Split COLOR_REP into device and PCS space */
+ if ((outc = strchr(buf, '_')) == NULL)
+ error("COLOR_REP '%s' invalid", inps[0].c->t[0].kdata[ti]);
+ *outc++ = '\000';
+
+ if (strcmp(outc, "XYZ") == 0) {
+ isLab = 0;
+ } else if (strcmp(outc, "LAB") == 0) {
+ isLab = 1;
+ } else
+ error("COLOR_REP '%s' invalid (Neither XYZ nor LAB)", inps[0].c->t[0].kdata[ti]);
+
+ if ((nmask = icx_char2inkmask(buf)) == 0) {
+ error ("File '%s' keyword COLOR_REP has unknown device value '%s'",inps[0].name,buf);
+ }
+
+ nchan = icx_noofinks(nmask);
+ bident = icx_inkmask2char(nmask, 0);
+
+ /* Find device fields */
+ for (j = 0; j < nchan; j++) {
+ int ii, imask;
+ char fname[100];
+
+ imask = icx_index2ink(nmask, j);
+ sprintf(fname,"%s_%s",nmask == ICX_W || nmask == ICX_K ? "GRAY" : bident,
+ icx_ink2char(imask));
+
+ if ((ii = inps[0].c->find_field(inps[0].c, 0, fname)) < 0)
+ error ("Input file doesn't contain field %s",fname);
+ if (inps[0].c->t[0].ftype[ii] != r_t)
+ error ("Field %s is wrong type",fname);
+ chix[j] = ii;
+ }
+
+ /* Find PCS fields */
+ for (j = 0; j < 3; j++) {
+ int ii;
+
+ if ((ii = inps[0].c->find_field(inps[0].c, 0, isLab ? labfname[j] : xyzfname[j])) < 0)
+ error ("Input file doesn't contain field %s",isLab ? labfname[j] : xyzfname[j]);
+ if (inps[0].c->t[0].ftype[ii] != r_t)
+ error ("Field %s is wrong type",isLab ? labfname[j] : xyzfname[j]);
+ pcsix[j] = ii;
+ }
+ free(bident);
+ }
+
+ if (!domerge && verb) {
+ printf("Averaging the following fields:");
+ for (j = 0; j < inps[0].c->t[0].nfields; j++) {
+ int jj;
+
+ /* Only real types */
+ if (inps[0].c->t[0].ftype[j] != r_t) {
+ continue;
+ }
+
+ /* Not device channels */
+ for (jj = 0; jj < nchan; jj++) {
+ if (chix[jj] == j)
+ break;
+ }
+ if (jj < nchan) {
+ continue;
+ }
+
+ printf(" %s",inps[0].c->t[0].fsym[j]);
+ }
+ printf("\n");
+ }
+
+ /* Get ready to add more values to output */
+ if ((setel = (cgats_set_elem *)malloc(
+ sizeof(cgats_set_elem) * inps[0].c->t[0].nfields)) == NULL)
+ error("Malloc failed!");
+
+ /* Process all the other input files */
+ for (n = 1; n < ninps; n++) {
+
+ /* Check all the fields match */
+ if (inps[0].c->t[0].nfields != inps[n].c->t[0].nfields)
+ error ("File '%s' has %d fields, file '%s has %d",
+ inps[n].name, inps[n].c->t[0].nfields, inps[0].name, inps[0].c->t[0].nfields);
+ for (j = 0; j < inps[0].c->t[0].nfields; j++) {
+ if (inps[0].c->t[0].ftype[j] != inps[n].c->t[0].ftype[j])
+ error ("File '%s' field no. %d named '%s' doesn't match file '%s' field '%s'",
+ inps[n].name, j, inps[n].c->t[0].fsym[j], inps[0].name, inps[0].c->t[0].fsym[j]);
+ }
+
+ /* If merging, append all the values */
+ if (domerge) {
+ for (i = 0; i < inps[n].c->t[0].nsets; i++) {
+ inps[n].c->get_setarr(inps[0].c, 0, i, setel);
+ ocg->add_setarr(ocg, 0, setel);
+ }
+
+ } else { /* Averaging */
+ /* Check the number of values matches */
+ if (inps[0].c->t[0].nsets != inps[n].c->t[0].nsets)
+ error ("File '%s' has %d sets, file '%s has %d",
+ inps[n].name, inps[n].c->t[0].nsets, inps[0].name, inps[0].c->t[0].nsets);
+
+ /* Add the numeric field values to corresponding output */
+ for (i = 0; i < inps[n].c->t[0].nsets; i++) {
+
+ /* Check that the device values match */
+ for (j = 0; j < nchan; j++) {
+ double diff;
+ diff = *((double *)inps[0].c->t[0].fdata[i][chix[j]])
+ - *((double *)inps[n].c->t[0].fdata[i][chix[j]]);
+
+ if (diff > 0.001)
+ error ("File '%s' set %d has field '%s' value that differs from '%s'",
+ inps[n].name, i+1, inps[n].c->t[0].fsym[j], inps[0].name);
+ }
+
+ /* Add all the non-device real field values */
+ for (j = 0; j < inps[0].c->t[0].nfields; j++) {
+ int jj;
+
+ /* Only real types */
+ if (inps[0].c->t[0].ftype[j] != r_t)
+ continue;
+
+ /* Not device channels */
+ for (jj = 0; jj < nchan; jj++) {
+ if (chix[jj] == j)
+ break;
+ }
+ if (jj < nchan)
+ continue;
+
+ *((double *)ocg->t[0].fdata[i][j])
+ += *((double *)inps[n].c->t[0].fdata[i][j]);
+ }
+ }
+ }
+ }
+
+ /* If averaging, divide out the number of files */
+ if (!domerge) {
+
+ for (i = 0; i < inps[n].c->t[0].nsets; i++) {
+
+ for (j = 0; j < inps[0].c->t[0].nfields; j++) {
+ int jj;
+
+ /* Only real types */
+ if (inps[0].c->t[0].ftype[j] != r_t)
+ continue;
+
+ /* Not device channels */
+ for (jj = 0; jj < nchan; jj++) {
+ if (chix[jj] == j)
+ break;
+ }
+ if (jj < nchan)
+ continue;
+
+ *((double *)ocg->t[0].fdata[i][j]) /= (double)ninps;
+ }
+ }
+ }
+
+ /* Write out the output and free the cgats * */
+ for (n = 0; n <= ninps; n++) {
+
+ if (n >= ninps) { /* If ouput file, write it */
+ if (inps[n].c->write_name(inps[n].c, inps[ninps].name))
+ error("CGATS file '%s' write error : %s",inps[n].name,inps[n].c->err);
+ }
+ inps[n].c->del(inps[n].c);
+ }
+
+ free(setel);
+ free(inps);
+
+ return 0;
+}
+
+
+
+
+
diff --git a/spectro/ccxx.ti1 b/spectro/ccxx.ti1
new file mode 100644
index 0000000..47f2b13
--- /dev/null
+++ b/spectro/ccxx.ti1
@@ -0,0 +1,22 @@
+CTI1
+
+DESCRIPTOR "Argyll Calibration Target chart information 1 for creating .ti3 for ccxxmake"
+ORIGINATOR "Argyll targen"
+CREATED "Thu Apr 19 13:24:37 2012"
+KEYWORD "APPROX_WHITE_POINT"
+APPROX_WHITE_POINT "95.045781 100.000003 108.905751"
+KEYWORD "COLOR_REP"
+COLOR_REP "RGB"
+
+NUMBER_OF_FIELDS 7
+BEGIN_DATA_FORMAT
+SAMPLE_ID RGB_R RGB_G RGB_B XYZ_X XYZ_Y XYZ_Z
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 4
+BEGIN_DATA
+1 100.00 100.00 100.00 95.046 100.00 108.91
+2 100.00 0.0000 0.0000 41.238 21.260 1.9306
+3 0.0000 100.00 0.0000 35.757 71.520 11.921
+4 0.0000 0.0000 100.00 18.050 7.2205 95.055
+END_DATA
diff --git a/spectro/ccxxmake.c b/spectro/ccxxmake.c
new file mode 100644
index 0000000..737600c
--- /dev/null
+++ b/spectro/ccxxmake.c
@@ -0,0 +1,1446 @@
+
+/* Colorimeter Correction Matrix and */
+/* Colorimeter Calibration Spectral Sample creation utility */
+
+/*
+ * Argyll Color Correction System
+ * Author: Graeme W. Gill
+ * Date: 19/8/2010
+ *
+ * Copyright 2010, 2011, 2013 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/* This program uses display measurements from a colorimeter and */
+/* a spectrometer to create a correction matrix for a particular */
+/* colorimeter/display combination,. */
+/* or */
+/* It uses display measurements from a spectrometer to create */
+/* calibration samples that can be used with a Colorimeter that */
+/* knowns its own spectral sensitivity curves (ie. X-Rite i1d3, Spyder 4). */
+
+/* Based on spotread.c, illumread.c, dispcal.c */
+
+/*
+ TTBD:
+
+ Should add an option to set a UI_SELECTORS value.
+
+ If any spectrometer gets a display type function (ie. refresh/non-refresh)
+ then it becomes difficult to know what to do with the -y option :-
+
+ * Ignore the problem - don't set -y option on spectrometers.
+ Error shouldn't be significant for ref/nonref ?
+
+ * Force the colorimeter to go first, record the ref/nonref state and
+ set in the spectrometer ? Make .ti3 file order the same for consistency ?
+
+ Would be nice to have a veryify option that produces
+ a fit report of a matrix vs. the input files.
+
+ Would be nice to have the option of procssing a Spyder 3 correction.txt file.
+ (See post from umberto.guidali@tiscali.it)
+
+ Would be nice to be able to use an i1D3 to correct other instruments,
+ or an i1D3 created .ti3 as the reference.
+
+ Would be nice to have an option of providing two ICC profiles,
+ instead of using .ti3 files (?? How well would it work though ?)
+ */
+
+
+#undef DEBUG
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/types.h>
+#include <time.h>
+#include <string.h>
+#include <stdarg.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "cgats.h"
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "inst.h"
+#include "dispwin.h"
+#include "webwin.h"
+#include "dispsup.h"
+#include "ccss.h"
+#include "ccmx.h"
+#include "instappsup.h"
+#include "spyd2setup.h"
+
+#if defined (NT)
+#include <conio.h>
+#endif
+
+#define DEFAULT_MSTEPS 1
+#undef SHOW_WINDOW_ONFAKE /* Display a test window up for a fake device */
+#define COMPORT 1 /* Default com port 1..4 */
+
+
+#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 */
+
+/* Invoke with -dfake for testing with a fake device. */
+/* Invoke with -dFAKE for automatic creation of test matrix. */
+/* Will use a fake.icm/.icc profile if present, or a built in fake */
+/* device behaviour if not. */
+
+void
+usage(char *diag, ...) {
+ disppath **dp;
+ icompaths *icmps = new_icompaths(0);
+ inst2_capability cap = 0;
+
+ fprintf(stderr,"Create CCMX or CCSS, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ if (setup_spyd2() == 2)
+ fprintf(stderr,"WARNING: This file contains a proprietary firmware image, and may not be freely distributed !\n");
+ if (diag != NULL) {
+ va_list args;
+ fprintf(stderr,"Diagnostic: ");
+ va_start(args, diag);
+ vfprintf(stderr, diag, args);
+ va_end(args);
+ fprintf(stderr,"\n");
+ }
+ fprintf(stderr,"usage: ccmxmake [-options] output.ccmx\n");
+ fprintf(stderr," -v Verbose mode\n");
+ fprintf(stderr," -S Create CCSS rather than CCMX\n");
+ fprintf(stderr," -f file1.ti3[,file2.ti3] Create from one or two .ti3 files rather than measure.\n");
+#if defined(UNIX_X11)
+ fprintf(stderr," -display displayname Choose X11 display name\n");
+ fprintf(stderr," -d n[,m] Choose the display n from the following list (default 1)\n");
+ fprintf(stderr," Optionally choose different display m for VideoLUT access\n");
+#else
+ fprintf(stderr," -d n Choose the display from the following list (default 1)\n");
+#endif
+ dp = get_displays();
+ if (dp == NULL || dp[0] == NULL)
+ fprintf(stderr," ** No displays found **\n");
+ else {
+ int i;
+ for (i = 0; ; i++) {
+ if (dp[i] == NULL)
+ break;
+ fprintf(stderr," %d name = '%s'\n",i+1,dp[i]->name);
+ fprintf(stderr," %d = '%s'\n",i+1,dp[i]->description);
+ }
+ }
+ free_disppaths(dp);
+ fprintf(stderr," -dweb[:port] Display via a web server at port (default 8080)\n");
+// fprintf(stderr," -d fake Use a fake display device for testing, fake%s if present\n",ICC_FILE_EXT);
+ fprintf(stderr," -p Use telephoto mode (ie. for a projector) (if available)\n");
+ cap = inst_show_disptype_options(stderr, " -y c|l ", icmps, 1);
+ fprintf(stderr," -P ho,vo,ss[,vs] Position test window and scale it\n");
+ fprintf(stderr," ho,vi: 0.0 = left/top, 0.5 = center, 1.0 = right/bottom etc.\n");
+ fprintf(stderr," ss: 0.5 = half, 1.0 = normal, 2.0 = double etc.\n");
+ fprintf(stderr," -F Fill whole screen with black background\n");
+#if defined(UNIX_X11)
+ fprintf(stderr," -n Don't set override redirect on test window\n");
+#endif
+ fprintf(stderr," -N Disable initial calibration of instrument if possible\n");
+ fprintf(stderr," -H Use high resolution spectrum mode (if available)\n");
+// fprintf(stderr," -V Use adaptive measurement mode (if available)\n");
+ fprintf(stderr," -C \"command\" Invoke shell \"command\" each time a color is set\n");
+ fprintf(stderr," -o observ Choose CIE Observer for CCMX spectrometer data:\n");
+ fprintf(stderr," 1931_2 (def), 1964_10, S&B 1955_2, shaw, J&V 1978_2\n");
+ fprintf(stderr," -s steps Override default patch sequence combination steps (default %d)\n",DEFAULT_MSTEPS);
+ fprintf(stderr," -W n|h|x Override serial port flow control: n = none, h = HW, x = Xon/Xoff\n");
+ fprintf(stderr," -D [level] Print debug diagnostics to stderr\n");
+ fprintf(stderr," -E desciption Override the default overall description\n");
+ fprintf(stderr," -I displayname Set display make and model description\n");
+ fprintf(stderr," -T displaytech Set display technology description (ie. CRT, LCD etc.)\n");
+ fprintf(stderr," -U c Set UI selection character(s)\n");
+ fprintf(stderr," -Y r|n Set or override refresh/non-refresh display type\n");
+ fprintf(stderr," -Y A Use non-adaptive integration time mode (if available).\n");
+ fprintf(stderr," correction.ccmx | calibration.ccss\n");
+ fprintf(stderr," File to save result to\n");
+ if (icmps != NULL)
+ icmps->del(icmps);
+ exit(1);
+}
+
+typedef double ary3[3];
+
+int main(int argc, char *argv[])
+{
+ int i,j;
+ int fa, nfa, mfa; /* current argument we're looking at */
+ disppath *disp = NULL; /* Display being used */
+ double hpatscale = 1.0, vpatscale = 1.0; /* scale factor for test patch size */
+ double ho = 0.0, vo = 0.0; /* Test window offsets, -1.0 to 1.0 */
+ int blackbg = 0; /* NZ if whole screen should be filled with black */
+ int verb = 0;
+ int debug = 0;
+ int doccss = 0; /* Create CCSS rather than CCMX */
+ int fake = 0; /* Use the fake device for testing, 2 for auto */
+ int faketoggle = 0; /* Toggle fake between "colorimeter" and "spectro" */
+ int fakeseq = 0; /* Fake auto CCMX sequence */
+ int spec = 0; /* Need spectral data to implement option */
+ icxObserverType observ = icxOT_CIE_1931_2;
+ int override = 1; /* Override redirect on X11 */
+ icompaths *icmps = NULL; /* Ports to choose from */
+ int comno = COMPORT; /* COM port used */
+ flow_control fc = fc_nc; /* Default flow control */
+ int highres = 0; /* High res mode if available */
+ int dtype = 0; /* Display kind, 0 = default, 1 = CRT, 2 = LCD */
+ int refrmode = -1; /* Refresh mode */
+ int cbid = 0; /* Calibration base display mode ID */
+ int nadaptive = 0; /* Use non-adaptive mode if available */
+ int tele = 0; /* NZ if telephoto mode */
+ int noinitcal = 0; /* Disable initial calibration */
+ int webdisp = 0; /* NZ for web display, == port number */
+ char *ccallout = NULL; /* Change color Shell callout */
+ int msteps = DEFAULT_MSTEPS; /* Patch surface size */
+ int npat = 0; /* Number of patches/colors */
+ ary3 *refs = NULL; /* Reference XYZ values */
+ int gotref = 0;
+ char *refname = NULL; /* Name of reference instrument */
+ ary3 *cols = NULL; /* Colorimeter XYZ values */
+ int gotcol = 0;
+ char *colname = NULL; /* Name of colorimeter instrument */
+ col *rdcols = NULL; /* Internal storage of all the patch colors */
+ int saved = 0; /* Saved result */
+ char innames[2][MAXNAMEL+1] = { "\000", "\000" }; /* .ti3 input names */
+ char outname[MAXNAMEL+1] = "\000"; /* ccmx output file name */
+ char *description = NULL; /* Given overall description */
+ char *displayname = NULL; /* Given display name */
+ char *displaytech = NULL; /* Given display technology */
+ char *uisel = NULL; /* UI selection letters */
+ int rv;
+
+ set_exe_path(argv[0]); /* Set global exe_path and error_program */
+ check_if_not_interactive();
+ setup_spyd2(); /* Load firware if available */
+
+ /* Process the arguments */
+ mfa = 0; /* Minimum final arguments */
+ for(fa = 1;fa < argc;fa++) {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1+mfa) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?') {
+ usage("Usage requested");
+
+ } else if (argv[fa][1] == 'v') {
+ verb = 1;
+ g_log->verb = verb;
+
+ } else if (argv[fa][1] == 'S') {
+ doccss = 1;
+
+ } else if (argv[fa][1] == 'f') {
+ char *cna, *f1 = NULL;
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to input file flag -f");
+
+ if ((cna = strdup(na)) == NULL)
+ error("Malloc failed");
+
+ /* If got just one file - enough for CCSS */
+ if ((f1 = strchr(cna, ',')) == NULL) {
+ strncpy(innames[0],cna,MAXNAMEL-1); innames[0][MAXNAMEL-1] = '\000';
+ free(cna);
+
+ /* Got two files - needed for CCMX */
+ } else {
+ *f1++ = '\000';
+ strncpy(innames[0],cna,MAXNAMEL-1); innames[0][MAXNAMEL-1] = '\000';
+ strncpy(innames[1],f1,MAXNAMEL-1); innames[1][MAXNAMEL-1] = '\000';
+ free(cna);
+ }
+
+ /* Display number */
+ } else if (argv[fa][1] == 'd') {
+ if (strncmp(na,"web",3) == 0
+ || strncmp(na,"WEB",3) == 0) {
+ webdisp = 8080;
+ if (na[3] == ':') {
+ webdisp = atoi(na+4);
+ if (webdisp == 0 || webdisp > 65535)
+ usage("Web port number must be in range 1..65535");
+ }
+ fa = nfa;
+ } else {
+#if defined(UNIX_X11)
+ int ix, iv;
+
+ if (strcmp(&argv[fa][2], "isplay") == 0 || strcmp(&argv[fa][2], "ISPLAY") == 0) {
+ if (++fa >= argc || argv[fa][0] == '-') usage("Parameter expected following -display");
+ setenv("DISPLAY", argv[fa], 1);
+ } else {
+ if (na == NULL) usage("Parameter expected following -d");
+ fa = nfa;
+ if (strcmp(na,"fake") == 0 || strcmp(na,"FAKE") == 0) {
+ fake = 1;
+ if (strcmp(na,"FAKE") == 0)
+ fakeseq = 1;
+ } else {
+ if (sscanf(na, "%d,%d",&ix,&iv) != 2) {
+ ix = atoi(na);
+ iv = 0;
+ }
+ if (disp != NULL)
+ free_a_disppath(disp);
+ if ((disp = get_a_display(ix-1)) == NULL)
+ usage("-d parameter %d out of range",ix);
+ if (iv > 0)
+ disp->rscreen = iv-1;
+ }
+ }
+#else
+ int ix;
+ if (na == NULL) usage("Parameter expected following -d");
+ fa = nfa;
+ if (strcmp(na,"fake") == 0 || strcmp(na,"FAKE") == 0) {
+ fake = 1;
+ if (strcmp(na,"FAKE") == 0)
+ fakeseq = 1;
+ } else {
+ ix = atoi(na);
+ if (disp != NULL)
+ free_a_disppath(disp);
+ if ((disp = get_a_display(ix-1)) == NULL)
+ usage("-d parameter %d out of range",ix);
+ }
+#endif
+ }
+#if defined(UNIX_X11)
+ } else if (argv[fa][1] == 'n') {
+ override = 0;
+#endif /* UNIX */
+
+ /* COM port */
+ } else if (argv[fa][1] == 'c') {
+ fa = nfa;
+ if (na == NULL) usage("Paramater expected following -c");
+ comno = atoi(na);
+ if (comno < 1 || comno > 40) usage("-c parameter %d out of range",comno);
+
+ /* Telephoto */
+ } else if (argv[fa][1] == 'p') {
+ tele = 1;
+
+ /* Display type */
+ } else if (argv[fa][1] == 'y') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -y");
+ dtype = na[0];
+
+ /* For ccss, set a default */
+ if (na[0] == 'r') {
+ refrmode = 1;
+ } else if (na[0] == 'n') {
+ refrmode = 0;
+ }
+
+ /* Test patch offset and size */
+ } else if (argv[fa][1] == 'P') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -P");
+ if (sscanf(na, " %lf,%lf,%lf,%lf ", &ho, &vo, &hpatscale, &vpatscale) == 4) {
+ ;
+ } else if (sscanf(na, " %lf,%lf,%lf ", &ho, &vo, &hpatscale) == 3) {
+ vpatscale = hpatscale;
+ } else {
+ usage("-P parameter '%s' not recognised",na);
+ }
+ if (ho < 0.0 || ho > 1.0
+ || vo < 0.0 || vo > 1.0
+ || hpatscale <= 0.0 || hpatscale > 50.0
+ || vpatscale <= 0.0 || vpatscale > 50.0)
+ usage("-P parameters %f %f %f %f out of range",ho,vo,hpatscale,vpatscale);
+ ho = 2.0 * ho - 1.0;
+ vo = 2.0 * vo - 1.0;
+
+ /* Black background */
+ } else if (argv[fa][1] == 'F') {
+ blackbg = 1;
+
+ /* No initial calibration */
+ } else if (argv[fa][1] == 'N') {
+ noinitcal = 1;
+
+ /* High res spectral mode */
+ } else if (argv[fa][1] == 'H') {
+ highres = 1;
+
+ /* Adaptive mode - now default, so flag is deprecated */
+ } else if (argv[fa][1] == 'V') {
+ warning("dispcal -V flag is deprecated");
+
+ /* Spectral Observer type (only relevant for CCMX) */
+ } else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expecte after -o");
+ if (strcmp(na, "1931_2") == 0) { /* Classic 2 degree */
+ spec = 2;
+ observ = icxOT_CIE_1931_2;
+ } else if (strcmp(na, "1964_10") == 0) { /* Classic 10 degree */
+ spec = 2;
+ observ = icxOT_CIE_1964_10;
+ } else if (strcmp(na, "1955_2") == 0) { /* Stiles and Burch 1955 2 degree */
+ spec = 2;
+ observ = icxOT_Stiles_Burch_2;
+ } else if (strcmp(na, "1978_2") == 0) { /* Judd and Voss 1978 2 degree */
+ spec = 2;
+ observ = icxOT_Judd_Voss_2;
+ } else if (strcmp(na, "shaw") == 0) { /* Shaw and Fairchilds 1997 2 degree */
+ spec = 2;
+ observ = icxOT_Shaw_Fairchild_2;
+ } else
+ usage("-o parameter '%s' not recognised",na);
+
+ } else if (argv[fa][1] == 's') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expecte after -s");
+ msteps = atoi(na);
+ if (msteps < 1 || msteps > 16)
+ usage("-s parameter value %d is outside the range 1 to 16",msteps);
+
+ /* Change color callout */
+ } else if (argv[fa][1] == 'C') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -C");
+ ccallout = na;
+
+ /* Serial port flow control */
+ } else if (argv[fa][1] == 'W') {
+ fa = nfa;
+ if (na == NULL) usage("Paramater expected following -W");
+ if (na[0] == 'n' || na[0] == 'N')
+ fc = fc_none;
+ else if (na[0] == 'h' || na[0] == 'H')
+ fc = fc_Hardware;
+ else if (na[0] == 'x' || na[0] == 'X')
+ fc = fc_XonXOff;
+ else
+ usage("-W parameter '%c' not recognised",na[0]);
+
+ } else if (argv[fa][1] == 'D') {
+ debug = 1;
+ if (na != NULL && na[0] >= '0' && na[0] <= '9') {
+ debug = atoi(na);
+ fa = nfa;
+ }
+ g_log->debug = debug;
+
+ } else if (argv[fa][1] == 'I') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to display description flag -I");
+ displayname = strdup(na);
+
+ } else if (argv[fa][1] == 'T') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to display technology flag -T");
+ displaytech = strdup(na);
+
+ /* Copyright string */
+ } else if (argv[fa][1] == 'E') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to overall description flag -E");
+ description = strdup(na);
+
+ /* Extra flags */
+ } else if (argv[fa][1] == 'Y') {
+ if (na == NULL)
+ usage("Flag '-Y' expects extra flag");
+
+ if (na[0] == 'A') {
+ nadaptive = 1;
+ } else if (na[0] == 'r') {
+ refrmode = 1;
+ } else if (na[0] == 'n') {
+ refrmode = 0;
+ } else {
+ usage("Flag '-Z %c' not recognised",na[0]);
+ }
+
+ /* UI selection character */
+ } else if (argv[fa][1] == 'U') {
+ fa = nfa;
+ if (na == NULL || na[0] == '\000') usage("Expect argument to flag -U");
+ uisel = na;
+ for (i = 0; uisel[i] != '\000'; i++) {
+ if (!( (uisel[i] >= '0' && uisel[i] <= '9')
+ || (uisel[i] >= 'A' && uisel[i] <= 'Z')
+ || (uisel[i] >= 'a' && uisel[i] <= 'z'))) {
+ usage("-U character(s) must be 0-9,A-Z,a-z");
+ }
+ }
+
+ } else
+ usage("Flag '-%c' not recognised",argv[fa][1]);
+ }
+ else
+ break;
+ }
+
+ /* Get the output ccmx file name argument */
+ if (fa >= argc)
+ usage("Output filname expected");
+
+ strncpy(outname,argv[fa++],MAXNAMEL-1); outname[MAXNAMEL-1] = '\000';
+
+ if (fakeseq && doccss)
+ error("Fake CCSS test not implemeted");
+
+ printf("\n");
+
+ if (displayname == NULL && displaytech == NULL)
+ error("Either the display description (-I) or technology (-T) needs to be set");
+
+ /* CCSS: See if we're working from a .ti3 file */
+ if (doccss && innames[0][0] != '\000') {
+ cgats *cgf = NULL; /* cgats file data */
+ int sidx; /* Sample ID index */
+ int ii, ti;
+ char buf[100];
+ int spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */
+ xspect sp, *samples = NULL;
+ ccss *cc;
+ double bigv = -1e60;
+
+ /* Open spectral values file */
+ cgf = new_cgats(); /* Create a CGATS structure */
+ cgf->add_other(cgf, ""); /* Allow any signature file */
+
+ if (cgf->read_name(cgf, innames[0]))
+ error("CGATS file '%s' read error : %s",innames[0],cgf->err);
+
+ if (cgf->ntables < 1)
+ error ("Input file '%s' doesn't contain at least one table",innames[0]);
+
+ if ((npat = cgf->t[0].nsets) <= 0)
+ error("No sets of data in file '%s'",innames[0]);
+
+ if ((samples = (xspect *)malloc(npat * sizeof(xspect))) == NULL)
+ error("malloc failed");
+
+ if ((ii = cgf->find_kword(cgf, 0, "TARGET_INSTRUMENT")) < 0)
+ error ("Can't find keyword TARGET_INSTRUMENT in '%s'",innames[0]);
+
+ if ((ti = cgf->find_kword(cgf, 0, "DISPLAY_TYPE_REFRESH")) >= 0) {
+ if (stricmp(cgf->t[0].kdata[ti], "YES") == 0)
+ refrmode = 1;
+ else if (stricmp(cgf->t[0].kdata[ti], "NO") == 0)
+ refrmode = 0;
+ }
+
+ if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_BANDS")) < 0)
+ error ("Input file '%s' doesn't contain keyword SPECTRAL_BANDS",innames[0]);
+ sp.spec_n = atoi(cgf->t[0].kdata[ii]);
+ if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_START_NM")) < 0)
+ error ("Input file '%s' doesn't contain keyword SPECTRAL_START_NM",innames[0]);
+ sp.spec_wl_short = atof(cgf->t[0].kdata[ii]);
+ if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_END_NM")) < 0)
+ error ("Input file '%s' doesn't contain keyword SPECTRAL_END_NM",innames[0]);
+ sp.spec_wl_long = atof(cgf->t[0].kdata[ii]);
+ sp.norm = 1.0; /* We assume emssive */
+
+ /* Find the fields for spectral values */
+ for (j = 0; j < sp.spec_n; j++) {
+ int nm;
+
+ /* Compute nearest integer wavelength */
+ nm = (int)(sp.spec_wl_short + ((double)j/(sp.spec_n-1.0))
+ * (sp.spec_wl_long - sp.spec_wl_short) + 0.5);
+
+ sprintf(buf,"SPEC_%03d",nm);
+
+ if ((spi[j] = cgf->find_field(cgf, 0, buf)) < 0)
+ error("Input file '%s' doesn't contain field %s",innames[0],buf);
+ }
+
+ /* Transfer all the spectral values */
+ for (i = 0; i < npat; i++) {
+
+ XSPECT_COPY_INFO(&samples[i], &sp);
+
+ for (j = 0; j < sp.spec_n; j++) {
+ samples[i].spec[j] = *((double *)cgf->t[0].fdata[i][spi[j]]);
+ }
+ }
+ cgf->del(cgf); /* Clean up */
+ cgf = NULL;
+
+ if (description == NULL) {
+ char *disp = displaytech != NULL ? displaytech : displayname;
+ char *tt = "CCSS for ";
+ if ((description = malloc(strlen(disp) + strlen(tt) + 1)) == NULL)
+ error("Malloc failed");
+ strcpy(description, tt);
+ strcat(description, disp);
+ }
+
+ /* See what the highest value is */
+ for (i = 0; i < npat; i++) { /* For all grid points */
+
+ for (j = 0; j < samples[i].spec_n; j++) {
+ if (samples[i].spec[j] > bigv)
+ bigv = samples[i].spec[j];
+ }
+ }
+
+ /* Normalize the values */
+ for (i = 0; i < npat; i++) { /* For all grid points */
+ double scale = 100.0;
+
+ for (j = 0; j < samples[i].spec_n; j++)
+ samples[i].spec[j] *= scale / bigv;
+ }
+
+ if (refrmode < 0)
+ error("The display refresh mode is not known - use the -Y flag");
+
+ if ((cc = new_ccss()) == NULL)
+ error("new_ccss() failed");
+
+ if (cc->set_ccss(cc, "Argyll ccxxmake", NULL, description, displayname,
+ displaytech, refrmode, uisel, refname, samples, npat)) {
+ error("set_ccss failed with '%s'\n",cc->err);
+ }
+ if(cc->write_ccss(cc, outname))
+ printf("\nWriting CCXX file '%s' failed\n",outname);
+ else
+ printf("\nWriting CCXX file '%s' succeeded\n",outname);
+ cc->del(cc);
+ free(samples);
+
+#ifdef DEBUG
+ printf("About to exit\n");
+#endif
+ return 0;
+ }
+
+ /* CCMX: See if we're working from two files */
+ if (!doccss && innames[0][0] != '\000') {
+ int n;
+ char *oname = NULL; /* Observer name */
+ ccmx *cc;
+
+ if (innames[1][0] == '\000') {
+ error("Need two .ti3 files to create CCMX");
+ }
+
+ /* Open up each CIE file in turn, target then measured, */
+ /* and read in the CIE values. */
+ for (n = 0; n < 2; n++) {
+ cgats *cgf = NULL; /* cgats file data */
+ int isLab = 0; /* 0 if file CIE is XYZ, 1 if is Lab */
+ double wxyz[3], scale = 1.0;/* Scale factor back to absolute */
+ int sidx; /* Sample ID index */
+ int xix, yix, zix;
+ ary3 *current = NULL; /* Current value array */
+ int ii, ti;
+ int instspec = 0; /* File is spectrale */
+
+ /* Open CIE target values */
+ cgf = new_cgats(); /* Create a CGATS structure */
+ cgf->add_other(cgf, ""); /* Allow any signature file */
+
+ if (cgf->read_name(cgf, innames[n]))
+ error("CGATS file '%s' read error : %s",innames[n],cgf->err);
+
+ if (cgf->ntables < 1)
+ error ("Input file '%s' doesn't contain at least one table",innames[n]);
+
+ /* Check if the file is suitable */
+ if (cgf->find_field(cgf, 0, "LAB_L") < 0
+ && cgf->find_field(cgf, 0, "XYZ_X") < 0) {
+
+ error ("No CIE data found in file '%s'",innames[n]);
+ }
+
+ if (cgf->find_field(cgf, 0, "LAB_L") >= 0)
+ isLab = 1;
+
+ if (cols == NULL) {
+ if ((npat = cgf->t[0].nsets) <= 0)
+ error("No sets of data in file '%s'",innames[n]);
+
+ if ((refs = (ary3 *)malloc(npat * sizeof(ary3))) == NULL)
+ error("malloc failed");
+ if ((cols = (ary3 *)malloc(npat * sizeof(ary3))) == NULL)
+ error("malloc failed");
+
+ } else {
+ if (npat != cgf->t[0].nsets)
+ error ("Number of sets %d in file '%s' doesn't match other file %d",cgf->t[0].nsets,innames[n],npat);
+ }
+
+ if ((ii = cgf->find_kword(cgf, 0, "TARGET_INSTRUMENT")) < 0)
+ error ("Can't find keyword TARGET_INSTRUMENT in '%s'",innames[n]);
+
+ if ((ti = cgf->find_kword(cgf, 0, "INSTRUMENT_TYPE_SPECTRAL")) < 0)
+ error ("Can't find keyword INSTRUMENT_TYPE_SPECTRAL in '%s'",innames[n]);
+
+ if (strcmp(cgf->t[0].kdata[ti],"YES") == 0) {
+ instspec = 1; /* Currently is a spectral file */
+ if (gotref)
+ error("Found two spectral files - expect one colorimtric file");
+ current = refs;
+ refname = strdup(cgf->t[0].kdata[ii]);
+ gotref = 1;
+
+ } else if (strcmp(cgf->t[0].kdata[ti],"NO") == 0) {
+ instspec = 0; /* Currently is not spectral file */
+ if (gotcol) {
+ /* Copy what we though was cols to refs */
+ refname = colname;
+ for (i = 0; i < npat; i++) {
+ refs[i][0] = cols[i][0];
+ refs[i][1] = cols[i][1];
+ refs[i][2] = cols[i][2];
+ }
+ gotref = 1;
+ warning("Got two colorimetric files - assuming '%s' is the refrence",innames[0]);
+ refrmode = -1;
+ cbid = 0;
+
+ if (spec) {
+ error("Spectral reference is required to use non-standard observer");
+ }
+ }
+ if ((ti = cgf->find_kword(cgf, 0, "DISPLAY_TYPE_REFRESH")) >= 0) {
+ if (stricmp(cgf->t[0].kdata[ti], "YES") == 0)
+ refrmode = 1;
+ else if (stricmp(cgf->t[0].kdata[ti], "NO") == 0)
+ refrmode = 0;
+ }
+ if ((ti = cgf->find_kword(cgf, 0, "DISPLAY_TYPE_BASE_ID")) >= 0) {
+ cbid = atoi(cgf->t[0].kdata[ti]);
+ } else {
+ cbid = 0;
+ }
+ current = cols;
+ colname = strdup(cgf->t[0].kdata[ii]);
+ gotcol = 1;
+ } else {
+ error ("Unknown INSTRUMENT_TYPE_SPECTRAL value '%s'",cgf->t[0].kdata[ti]);
+ }
+
+ if ((ti = cgf->find_kword(cgf, 0, "NORMALIZED_TO_Y_100")) < 0
+ || strcmp(cgf->t[0].kdata[ti],"NO") == 0) {
+ scale = 1.0; /* Leave absolute */
+ } else {
+ if ((ti = cgf->find_kword(cgf, 0, "LUMINANCE_XYZ_CDM2")) < 0)
+ error ("Can't find keyword LUMINANCE_XYZ_CDM2 in '%s'",innames[n]);
+ if (sscanf(cgf->t[0].kdata[ti],"%lf %lf %lf", &wxyz[0], &wxyz[1], &wxyz[2]) != 3)
+ error ("Unable to parse LUMINANCE_XYZ_CDM2 in '%s'",innames[n]);
+ scale = wxyz[1]/100.0; /* Convert from Y = 100 normalise back to absolute */
+ }
+
+ if (instspec && spec) {
+ int ii;
+ xspect sp;
+ char buf[100];
+ int spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */
+ xsp2cie *sp2cie; /* Spectral conversion object */
+
+ if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_BANDS")) < 0)
+ error ("Input file '%s' doesn't contain keyword SPECTRAL_BANDS",innames[n]);
+ sp.spec_n = atoi(cgf->t[0].kdata[ii]);
+ if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_START_NM")) < 0)
+ error ("Input file '%s' doesn't contain keyword SPECTRAL_START_NM",innames[n]);
+ sp.spec_wl_short = atof(cgf->t[0].kdata[ii]);
+ if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_END_NM")) < 0)
+ error ("Input file '%s' doesn't contain keyword SPECTRAL_END_NM",innames[n]);
+ sp.spec_wl_long = atof(cgf->t[0].kdata[ii]);
+ sp.norm = 1.0; /* We assume emssive */
+
+ /* Find the fields for spectral values */
+ for (j = 0; j < sp.spec_n; j++) {
+ int nm;
+
+ /* Compute nearest integer wavelength */
+ nm = (int)(sp.spec_wl_short + ((double)j/(sp.spec_n-1.0))
+ * (sp.spec_wl_long - sp.spec_wl_short) + 0.5);
+
+ sprintf(buf,"SPEC_%03d",nm);
+
+ if ((spi[j] = cgf->find_field(cgf, 0, buf)) < 0)
+ error("Input file '%s' doesn't contain field %s",innames[n],buf);
+ }
+
+ /* Create a spectral conversion object */
+ if ((sp2cie = new_xsp2cie(icxIT_none, NULL, observ, NULL, icSigXYZData, icxClamp)) == NULL)
+ error("Creation of spectral conversion object failed");
+
+ for (i = 0; i < npat; i++) {
+
+ /* Read the spectral values for this patch */
+ for (j = 0; j < sp.spec_n; j++) {
+ sp.spec[j] = *((double *)cgf->t[0].fdata[i][spi[j]]) * scale;
+ }
+
+ /* Convert it to CIE space */
+ sp2cie->convert(sp2cie, current[i], &sp);
+ }
+ sp2cie->del(sp2cie); /* Done with this */
+
+ /* Colorimetric file - assume it's the target */
+ } else {
+
+ if (isLab) { /* Expect Lab */
+ if ((xix = cgf->find_field(cgf, 0, "LAB_L")) < 0)
+ error("Input file '%s' doesn't contain field LAB_L",innames[n]);
+ if (cgf->t[0].ftype[xix] != r_t)
+ error("Field LAB_L is wrong type");
+ if ((yix = cgf->find_field(cgf, 0, "LAB_A")) < 0)
+ error("Input file '%s' doesn't contain field LAB_A",innames[n]);
+ if (cgf->t[0].ftype[yix] != r_t)
+ error("Field LAB_A is wrong type");
+ if ((zix = cgf->find_field(cgf, 0, "LAB_B")) < 0)
+ error("Input file '%s' doesn't contain field LAB_B",innames[n]);
+ if (cgf->t[0].ftype[zix] != r_t)
+ error("Field LAB_B is wrong type");
+
+ } else { /* Expect XYZ */
+ if ((xix = cgf->find_field(cgf, 0, "XYZ_X")) < 0)
+ error("Input file '%s' doesn't contain field XYZ_X",innames[n]);
+ if (cgf->t[0].ftype[xix] != r_t)
+ error("Field XYZ_X is wrong type");
+ if ((yix = cgf->find_field(cgf, 0, "XYZ_Y")) < 0)
+ error("Input file '%s' doesn't contain field XYZ_Y",innames[n]);
+ if (cgf->t[0].ftype[yix] != r_t)
+ error("Field XYZ_Y is wrong type");
+ if ((zix = cgf->find_field(cgf, 0, "XYZ_Z")) < 0)
+ error("Input file '%s' doesn't contain field XYZ_Z",innames[n]);
+ if (cgf->t[0].ftype[zix] != r_t)
+ error("Field XYZ_Z is wrong type");
+ }
+
+ for (i = 0; i < npat; i++) {
+ current[i][0] = *((double *)cgf->t[0].fdata[i][xix]);
+ current[i][1] = *((double *)cgf->t[0].fdata[i][yix]);
+ current[i][2] = *((double *)cgf->t[0].fdata[i][zix]);
+ if (isLab) { /* Convert test patch Lab to XYZ scale 100 */
+ icmLab2XYZ(&icmD50_100, current[i], current[i]);
+ }
+ /* Rescale to absolute if needed */
+ current[i][0] *= scale;
+ current[i][1] *= scale;
+ current[i][2] *= scale;
+ }
+ }
+ cgf->del(cgf); /* Clean up */
+ cgf = NULL;
+ }
+
+ if (spec != 0 && observ != icxOT_CIE_1931_2)
+ oname = standardObserverDescription(observ);
+
+ if (oname != NULL) {
+ char *tt = colname;
+
+ if ((colname = malloc(strlen(tt) + strlen(oname) + 3)) == NULL)
+ error("Malloc failed");
+ strcpy(colname, tt);
+ strcat(colname, " (");
+ strcat(colname, oname);
+ strcat(colname, ")");
+ }
+ if (description == NULL) {
+ char *disp = displaytech != NULL ? displaytech : displayname;
+ if ((description = malloc(strlen(colname) + strlen(disp) + 4)) == NULL)
+ error("Malloc failed");
+ strcpy(description, colname);
+ strcat(description, " & ");
+ strcat(description, disp);
+ }
+
+ if (refrmode < 0)
+ error("The display refresh mode is not known - use the -Y flag");
+
+ if (cbid == 0)
+ error("The calibration base display mode not specified in the .ti3 file");
+
+ if ((cc = new_ccmx()) == NULL)
+ error("new_ccmx() failed");
+
+ if (cc->create_ccmx(cc, description, colname, displayname, displaytech,
+ refrmode, cbid, uisel, refname, npat, refs, cols)) {
+ error("create_ccmx failed with '%s'\n",cc->err);
+ }
+ if (verb) {
+ printf("Fit error is max %f, avg %f DE94\n",cc->mx_err,cc->av_err);
+ printf("Correction matrix is:\n");
+ printf(" %f %f %f\n", cc->matrix[0][0], cc->matrix[0][1], cc->matrix[0][2]);
+ printf(" %f %f %f\n", cc->matrix[1][0], cc->matrix[1][1], cc->matrix[1][2]);
+ printf(" %f %f %f\n", cc->matrix[2][0], cc->matrix[2][1], cc->matrix[2][2]);
+ }
+
+ if(cc->write_ccmx(cc, outname))
+ printf("\nWriting CCMX file '%s' failed\n",outname);
+ else
+ printf("\nWriting CCMX file '%s' succeeded\n",outname);
+ cc->del(cc);
+
+ /* Do interactive measurements */
+ } else {
+
+ /* No explicit display has been set */
+ if (
+#ifndef SHOW_WINDOW_ONFAKE
+ !fake &&
+#endif
+ webdisp == 0 && disp == NULL) {
+ int ix = 0;
+#if defined(UNIX_X11)
+ char *dn, *pp;
+
+ if ((dn = getenv("DISPLAY")) != NULL) {
+ if ((pp = strrchr(dn, ':')) != NULL) {
+ if ((pp = strchr(pp, '.')) != NULL) {
+ if (pp[1] != '\000')
+ ix = atoi(pp+1);
+ }
+ }
+ }
+#endif
+ if ((disp = get_a_display(ix)) == NULL)
+ error("Unable to open the default display");
+
+ if (displayname == NULL && (displayname = strdup(disp->description)) == NULL)
+ error("Malloc failed");
+
+ printf("Display description is '%s'\n",displayname);
+ }
+ if (fake) {
+ displayname = strdup("fake display");
+ }
+
+ /* Create grid of device test values */
+ {
+ int j;
+ int gc[3]; /* Grid coordinate */
+
+ if (msteps == 1)
+ npat = 4;
+ else
+ npat = msteps * msteps * msteps;
+
+ if ((rdcols = (col *)malloc(npat * sizeof(col))) == NULL) {
+ error("malloc failed");
+ }
+ if ((refs = (ary3 *)malloc(npat * sizeof(ary3))) == NULL) {
+ free(rdcols);
+ error("malloc failed");
+ }
+ if ((cols = (ary3 *)malloc(npat * sizeof(ary3))) == NULL) {
+ free(rdcols);
+ free(refs);
+ error("malloc failed");
+ }
+
+ /* RGBW */
+ if (msteps == 1) {
+ npat = 0;
+ rdcols[npat].r = 1.0;
+ rdcols[npat].g = 0.0;
+ rdcols[npat].b = 0.0;
+ npat++;
+ rdcols[npat].r = 0.0;
+ rdcols[npat].g = 1.0;
+ rdcols[npat].b = 0.0;
+ npat++;
+ rdcols[npat].r = 0.0;
+ rdcols[npat].g = 0.0;
+ rdcols[npat].b = 1.0;
+ npat++;
+ rdcols[npat].r = 1.0;
+ rdcols[npat].g = 1.0;
+ rdcols[npat].b = 1.0;
+ npat++;
+#ifdef DEBUG
+ for (j = 0; j < 4; j++)
+ printf("Dev val %f %f %f\n",rdcols[j].r,rdcols[j].g,rdcols[j].b);
+#endif
+ } else {
+ for (j = 0; j < 3; j++)
+ gc[j] = 0; /* init coords */
+
+ for (npat = 0; ;) { /* For all grid points */
+
+ /* Just colors with at least one channel at 100% */
+ if (gc[0] == (msteps-1)
+ || gc[1] == (msteps-1)
+ || gc[2] == (msteps-1))
+ {
+
+ rdcols[npat].r = (double)gc[0]/(msteps-1);
+ rdcols[npat].g = (double)gc[1]/(msteps-1);
+ rdcols[npat].b = (double)gc[2]/(msteps-1);
+#ifdef DEBUG
+ printf("Dev val %f %f %f\n",rdcols[npat].r,rdcols[npat].g,rdcols[npat].b);
+#endif
+ npat++;
+ }
+
+ /* Increment grid index and position */
+ for (j = 0; j < 3; j++) {
+ gc[j]++;
+ if (gc[j] < msteps)
+ break; /* No carry */
+ gc[j] = 0;
+ }
+ if (j >= 3)
+ break; /* Done grid */
+ }
+ }
+ if (verb)
+ printf("Total test patches = %d\n",npat);
+ }
+
+ /* Until the measurements are done, or we give up */
+ for (;;) {
+ int c;
+
+ /* Print the menue of adjustments */
+ printf("\n");
+ if (gotref)
+ printf("[Got spectrometer readings]\n");
+ if (gotcol)
+ printf("[Got colorimeter readings]\n");
+ printf("Press 1 .. 4:\n");
+ {
+ printf("1) Select an instrument, Currently %d (", comno);
+ if (icmps == NULL)
+ icmps = new_icompaths(g_log);
+ else
+ icmps->refresh(icmps);
+ if (icmps != NULL) {
+ icompath **paths;
+ if ((paths = icmps->paths) != NULL) {
+ int i;
+ for (i = 0; ; i++) {
+ if (paths[i] == NULL)
+ break;
+ if ((i+1) == comno) {
+ printf(" '%s'",paths[i]->name);
+ break;
+ }
+ }
+ }
+ }
+ printf(")\n");
+ }
+ if (doccss)
+ printf("2) Measure test patches with current (spectrometer) instrument\n");
+ else
+ printf("2) Measure test patches with current instrument\n");
+
+ if (doccss) {
+ if (gotref)
+ printf("3) Save Colorimeter Calibration Spectral Set\n");
+ else
+ printf("3) [ Save Colorimeter Calibration Spectral Set ]\n");
+
+ } else {
+ if (gotref && gotcol)
+ printf("3) Compute Colorimeter Correction Matrix & save it\n");
+ else
+ printf("3) [ Compute Colorimeter Correction Matrix & save it ]\n");
+ }
+ printf("4) Exit\n");
+
+ if (fakeseq == 0) {
+ empty_con_chars();
+ c = next_con_char();
+ } else {
+ switch (fakeseq) {
+ case 1:
+ c = '2';
+ fakeseq = 2;
+ break;
+ case 2:
+ c = '2';
+ fakeseq = 3;
+ break;
+ case 3:
+ c = '3';
+ fakeseq = 4;
+ break;
+ default:
+ c = '4';
+ break;
+ }
+ }
+ printf("'%c'\n",c);
+
+
+ /* Deal with selecting the instrument */
+ if (c == '1') {
+ if (icmps == NULL)
+ icmps = new_icompaths(g_log);
+ else
+ icmps->refresh(icmps);
+ if (icmps != NULL) {
+ icompath **paths;
+ if ((paths = icmps->paths) != NULL) {
+ int i;
+ for (i = 0; ; i++) {
+ if (paths[i] == NULL)
+ break;
+ if (paths[i]->itype == instSpyder2 && setup_spyd2() == 0)
+ fprintf(stderr," %d = '%s' !! Disabled - no firmware !!\n",i+1,paths[i]->name);
+ else
+ fprintf(stderr," %d = '%s'\n",i+1,paths[i]->name);
+ }
+ printf("Select device 1 - %d: \n",i);
+ empty_con_chars();
+ c = next_con_char();
+
+ if (c < '1' || c > ('0' + i)) {
+ printf("'%c' is out of range - ignored !\n",c);
+ } else {
+ comno = c - '0';
+ }
+
+ } else {
+ fprintf(stderr,"No ports to select from!\n");
+ }
+ }
+ continue;
+ }
+
+ /* Deal with doing a measurement */
+ if (c == '2') {
+ int errc; /* Return value from new_disprd() */
+ disprd *dr; /* Display patch read object */
+ inst *it; /* Instrument */
+ inst_mode cap = inst_mode_none; /* Instrument mode capabilities */
+ inst2_capability cap2 = inst2_none; /* Instrument capabilities 2 */
+ inst3_capability cap3 = inst3_none; /* Instrument capabilities 3 */
+
+ if (fake)
+ comno = -99;
+ if (icmps == NULL)
+ icmps = new_icompaths(g_log);
+
+ /* Should we use current cal rather than native ??? */
+ if ((dr = new_disprd(&errc, icmps->get_path(icmps, comno),
+ fc, dtype, 1, tele, nadaptive,
+ noinitcal, highres, 2, NULL, NULL, 0, 0, disp, blackbg,
+ override, webdisp, ccallout, NULL,
+ 100.0 * hpatscale, 100.0 * vpatscale, ho, vo,
+ NULL, NULL, 0, 2, icxOT_default, NULL,
+ 0, 0, "fake" ICC_FILE_EXT, g_log)) == NULL)
+ error("new_disprd failed with '%s'\n",disprd_err(errc));
+
+ it = dr->it;
+
+ if (fake) {
+ if (faketoggle)
+ cap = inst_mode_spectral;
+ else
+ cap = inst_mode_colorimeter;
+ cap2 = inst2_none;
+ cap3 = inst3_none;
+ refrmode = 0;
+ cbid = 1;
+ } else {
+ it->capabilities(it, &cap, &cap2, &cap3);
+ if (!IMODETST(cap, inst_mode_spectral)) {
+ dr->get_disptype(dr, &refrmode, &cbid); /* Get the display type info */
+ }
+ }
+
+ if (doccss && !IMODETST(cap, inst_mode_spectral)) {
+ printf("You have to use a spectrometer to create a CCSS!\n");
+ continue;
+ }
+
+ if (faketoggle)
+ dr->fake2 = 1;
+ else
+ dr->fake2 = -1;
+
+ /* Test the CRT with all of the test points */
+ if ((rv = dr->read(dr, rdcols, npat, 1, npat, 1, 0, instClamp)) != 0) {
+ dr->del(dr);
+ error("disprd returned error code %d\n",rv);
+ }
+
+ if (doccss) { /* We'll use the rdcols values */
+ gotref = 1;
+ } else {
+ if (IMODETST(cap, inst_mode_spectral)) {
+ xsp2cie *sp2cie = NULL;
+
+ if (spec) {
+ /* Create a spectral conversion object */
+ if ((sp2cie = new_xsp2cie(icxIT_none, NULL, observ, NULL, icSigXYZData, icxClamp)) == NULL)
+ error("Creation of spectral conversion object failed");
+ }
+ for (i = 0; i < npat; i++) { /* For all grid points */
+ if (spec) {
+ if (rdcols[i].sp.spec_n <= 0)
+ error("Didn't get spectral value");
+ sp2cie->convert(sp2cie, refs[i], &rdcols[i].sp);
+ } else {
+ if (rdcols[i].XYZ_v == 0)
+ error("Didn't get XYZ value");
+ refs[i][0] = rdcols[i].XYZ[0];
+ refs[i][1] = rdcols[i].XYZ[1];
+ refs[i][2] = rdcols[i].XYZ[2];
+ }
+ }
+ if (fake)
+ refname = "fake spectrometer";
+ else
+ refname = inst_name(it->itype);
+ gotref = 1;
+ if (sp2cie != NULL)
+ sp2cie->del(sp2cie);
+ } else if (IMODETST(cap, inst_mode_colorimeter)) {
+ for (i = 0; i < npat; i++) { /* For all grid points */
+ if (rdcols[i].XYZ_v == 0)
+ error("Didn't get XYZ value");
+ cols[i][0] = rdcols[i].XYZ[0];
+ cols[i][1] = rdcols[i].XYZ[1];
+ cols[i][2] = rdcols[i].XYZ[2];
+ }
+ if (fake)
+ colname = "fake colorimeter";
+ else
+ colname = inst_name(it->itype);
+ gotcol = 1;
+ }
+ }
+ dr->del(dr);
+
+ faketoggle ^= 1;
+
+ } /* End of take a measurement */
+
+ if (c == '3') { /* Compute result and save */
+ /* Save the CCSS */
+ if (doccss) {
+ ccss *cc;
+ xspect *samples = NULL;
+ double bigv = -1e60;
+
+ if (!gotref) {
+ printf("You have to read the spectrometer values first!\n");
+ continue;
+ }
+
+ if (description == NULL) {
+ char *disp = displaytech != NULL ? displaytech : displayname;
+ char *tt = "CCSS for ";
+ if ((description = malloc(strlen(disp) + strlen(tt) + 1)) == NULL)
+ error("Malloc failed");
+ strcpy(description, tt);
+ strcat(description, disp);
+ }
+
+ if ((samples = (xspect *)malloc(sizeof(xspect) * npat)) == NULL)
+ error("Malloc failed");
+
+ /* See what the highest value is */
+ for (i = 0; i < npat; i++) { /* For all grid points */
+ if (rdcols[i].sp.spec_n <= 0)
+ error("Didn't get spectral values");
+ for (j = 0; j < rdcols[i].sp.spec_n; j++) {
+ if (rdcols[i].sp.spec[j] > bigv)
+ bigv = rdcols[i].sp.spec[j];
+ }
+ }
+
+ /* Copy all the values and normalize them */
+ for (i = 0; i < npat; i++) { /* For all grid points */
+ double scale = 100.0;
+
+ samples[i] = rdcols[i].sp; /* Structure copy */
+ for (j = 0; j < rdcols[i].sp.spec_n; j++)
+ samples[i].spec[j] *= scale / bigv;
+ }
+
+ if (refrmode < 0)
+ warning("No refresh mode specified! Assuming non-refresh !");
+
+ if ((cc = new_ccss()) == NULL)
+ error("new_ccss() failed");
+
+ if (cc->set_ccss(cc, "Argyll ccxxmake", NULL, description, displayname,
+ displaytech, refrmode, NULL, refname, samples, npat)) {
+ error("set_ccss failed with '%s'\n",cc->err);
+ }
+ if(cc->write_ccss(cc, outname))
+ printf("\nWriting CCSS file '%s' failed\n",outname);
+ else
+ printf("\nWriting CCSS file '%s' succeeded\n",outname);
+ cc->del(cc);
+ free(samples);
+ saved = 1;
+
+ /* Compute and save CCMX */
+ } else {
+ char *oname = NULL; /* Observer desciption */
+ ccmx *cc;
+
+ if (!gotref) {
+ printf("You have to read the spectrometer values first!\n");
+ continue;
+ }
+ if (!gotcol) {
+ printf("You have to read the colorimeter values first!\n");
+ continue;
+ }
+
+ if (spec != 0 && observ != icxOT_CIE_1931_2)
+ oname = standardObserverDescription(observ);
+
+ if (oname != NULL) { /* Incorporate observer name in colname */
+ char *tt = colname;
+
+ if ((colname = malloc(strlen(tt) + strlen(oname) + 3)) == NULL)
+ error("Malloc failed");
+ strcpy(colname, tt);
+ strcat(colname, " (");
+ strcat(colname, oname);
+ strcat(colname, ")");
+ }
+ if (description == NULL) {
+ if ((description = malloc(strlen(colname) + strlen(displayname) + 4)) == NULL)
+ error("Malloc failed");
+ strcpy(description, colname);
+ strcat(description, " & ");
+ strcat(description, displayname);
+ }
+
+ if (refrmode < 0)
+ error("Internal error - the instrument did not return a refmode");
+ if (cbid == 0)
+ error("Internal error - the instrument did not return a cbid");
+
+ if ((cc = new_ccmx()) == NULL)
+ error("new_ccmx() failed");
+
+ if (cc->create_ccmx(cc, description, colname, displayname, displaytech,
+ refrmode, cbid, uisel, refname, npat, refs, cols)) {
+ error("create_ccmx failed with '%s'\n",cc->err);
+ }
+ if (verb) {
+ printf("Fit error is avg %f, max %f DE94\n",cc->av_err,cc->mx_err);
+ printf("Correction matrix is:\n");
+ printf(" %f %f %f\n", cc->matrix[0][0], cc->matrix[0][1], cc->matrix[0][2]);
+ printf(" %f %f %f\n", cc->matrix[1][0], cc->matrix[1][1], cc->matrix[1][2]);
+ printf(" %f %f %f\n", cc->matrix[2][0], cc->matrix[2][1], cc->matrix[2][2]);
+ }
+
+ if(cc->write_ccmx(cc, outname))
+ printf("\nWriting CCMX file '%s' failed\n",outname);
+ else
+ printf("\nWriting CCMX file '%s' succeeded\n",outname);
+ cc->del(cc);
+ saved = 1;
+ }
+ }
+
+ if (c == '4' || c == 0x3) { /* Exit */
+ if (!saved) {
+ printf("Not saved yet, are you sure ? (y/n): "); fflush(stdout);
+ empty_con_chars();
+ c = next_con_char();
+ printf("\n");
+ if (c != 'y' && c != 'Y')
+ continue;
+ }
+ break;
+ }
+
+ } /* Next command */
+
+ free(displayname);
+ if (icmps != NULL)
+ icmps->del(icmps);
+ }
+
+#ifdef DEBUG
+ /* Do a CCMX verification */
+ if (!doccss) {
+ ccmx *cc;
+ double av_err, mx_err;
+ int wix;
+ double maxy = -1e6;
+ icmXYZNumber wh;
+
+ for (i = 0; i < npat; i++) {
+ if (refs[i][1] > maxy) {
+ maxy = refs[i][1];
+ wix = i;
+ }
+ }
+ wh.X = refs[wix][0];
+ wh.Y = refs[wix][1];
+ wh.Z = refs[wix][2];
+
+ if ((cc = new_ccmx()) == NULL)
+ error("new_ccmx() failed");
+ if(cc->read_ccmx(cc, outname))
+ printf("Reading file '%s' failed\n",outname);
+
+ av_err = mx_err = 0.0;
+ for (i = 0; i < npat; i++) {
+ double txyz[3], tlab[3], xyz[3], _xyz[3], lab[3], de;
+ icmCpy3(txyz, refs[i]);
+ icmXYZ2Lab(&wh, tlab, txyz);
+ icmCpy3(xyz, cols[i]);
+ cc->xform(cc,_xyz, xyz);
+ icmXYZ2Lab(&wh, lab, _xyz);
+ de = icmCIE94(tlab, lab);
+ av_err += de;
+ if (de > mx_err)
+ mx_err = de;
+ printf("%d: txyz %f %f %f\n",i,txyz[0], txyz[1], txyz[2]);
+ printf("%d: xyz %f %f %f, _xyz %f %f %f\n",i,xyz[0], xyz[1], xyz[2], _xyz[0], _xyz[1], _xyz[2]);
+ printf("%d: tlab %f %f %f, lab %f %f %f\n",i,tlab[0], tlab[1], tlab[2], lab[0], lab[1], lab[2]);
+ printf("%d: de %f\n",i,de);
+ }
+ av_err /= npat;
+ printf("Avg = %f, max = %f\n",av_err,mx_err);
+ cc->del(cc);
+ }
+#endif
+
+#ifdef DEBUG
+ printf("About to exit\n");
+#endif
+
+ return 0;
+}
+
+
+
+
diff --git a/spectro/chartread.c b/spectro/chartread.c
new file mode 100644
index 0000000..1c2117a
--- /dev/null
+++ b/spectro/chartread.c
@@ -0,0 +1,2993 @@
+
+/*
+ * Argyll Color Correction System
+ * Spectrometer/Colorimeter target test chart reader
+ *
+ * Author: Graeme W. Gill
+ * Date: 4/10/96
+ *
+ * Copyright 1996 - 2013, Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/* This program reads a reflective or transmissive print target chart */
+/* using a spectrometer or colorimeter. */
+
+/* TTBD
+ *
+ * Should an a -X [xl] mode that reads a simple list of readings
+ * from a file.
+ *
+ * Someone reported that XY mode (spectroscan) didn't work for one paper
+ * orientation ?
+ *
+ * Should fix XY chart read to also allow interruption/save/resume,
+ * just like the strip reading code.
+ *
+ * Should add verbose option to print average & max DE to expected value
+ * for each patch/strip read.
+ *
+ */
+
+/*
+ * Nomencalture:
+ *
+ * Largely due to how the strip readers name things, the following terms
+ * are used for how patches are grouped:
+ *
+ * Step: One test patch in a pass, usually labelled with a number.
+ * Pass: One row of patches in a strip. A pass is usually labeled
+ * with a unique alphabetic label.
+ * Strip: A group of passes that can be read by a strip reader.
+ * For an XY instrument, the strip is a complete sheet, and
+ * a each pass is one column. The rows of an XY chart are
+ * the step numbers within a pass.
+ * Sheet: One sheet of paper, containing full and partial strips.
+ * For an XY instrument, there will be only one strip per sheet.
+ *
+ */
+
+#undef DEBUG
+
+#define COMPORT 1 /* Default com port 1..4 */
+
+#ifdef __MINGW32__
+# define WINVER 0x0500
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/types.h>
+#include <time.h>
+#include <ctype.h>
+#include <string.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "cgats.h"
+#include "numlib.h"
+#include "icc.h"
+#include "xicc.h"
+#include "ccmx.h"
+#include "ccss.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "inst.h"
+#include "dispwin.h"
+#include "dispsup.h"
+#include "alphix.h"
+#include "sort.h"
+#include "instappsup.h"
+#include "spyd2setup.h"
+
+#include <stdarg.h>
+
+#if defined (NT)
+#include <conio.h>
+#endif
+
+#ifdef NEVER /* Not currently used */
+
+/* Convert control chars to ^[A-Z] notation in a string */
+static char *
+fix_asciiz(char *s) {
+ static char buf [200];
+ char *d;
+ for(d = buf; ;) {
+ if (*s < ' ' && *s > '\000') {
+ *d++ = '^';
+ *d++ = *s++ + '@';
+ } else
+ *d++ = *s++;
+ if (s[-1] == '\000')
+ break;
+ }
+ return buf;
+}
+#endif /* NEVER */
+
+/* Return the normal Delta E, given two 100 scaled XYZ values */
+static double xyzLabDE(double ynorm, double *pat, double *ref) {
+ int i;
+ double Lab1[3];
+ double Lab2[3];
+
+ for (i = 0; i < 3; i++) {
+ Lab1[i] = ynorm * pat[i]/100.0;
+ Lab2[i] = ref[i]/100.0;
+ }
+
+ icmXYZ2Lab(&icmD50, Lab1, Lab1);
+ icmXYZ2Lab(&icmD50, Lab2, Lab2);
+
+ return icmLabDE(Lab1, Lab2);
+}
+
+/* A chart read color structure */
+/* This can hold all representations simultaniously */
+typedef struct {
+ char *id; /* Id string (e.g. "1") */
+ char *loc; /* Location string (e.g. "A1") */
+ int loci; /* Location integer = pass * 256 + step */
+
+ int n; /* Number of colorants */
+ double dev[ICX_MXINKS]; /* Value of colorants */
+ double eXYZ[3]; /* Expected XYZ values (100.0 scale for ref.) */
+
+ int rr; /* nz if reading read (used for tracking unread patches) */
+
+ inst_meas_type mtype; /* Measurement type */
+ double XYZ[3]; /* Colorimeter readings (100.0 scale for ref.) */
+
+ xspect sp; /* Spectrum. sp.spec_n > 0 if valid, 100 scaled for ref. */
+} chcol;
+
+/* Convert a base 62 character into a number */
+/* (This is used for converting the PASSES_IN_STRIPS string */
+/* (Could convert this to using an alphix("0-9A-Za-Z")) */
+static int b62_int(char *p) {
+ int cv, rv;
+
+ cv = *p;
+ if (cv == '\000')
+ rv = 0;
+ else if (cv <= '9')
+ rv = cv - '0';
+ else if (cv <= 'Z')
+ rv = cv - 'A' + 10;
+ else
+ rv = cv - 'a' + 36;
+ return rv;
+}
+
+/* Deal with an instrument error. */
+/* Return 0 to retry, 1 to abort */
+static int ierror(inst *it, inst_code ic) {
+ int ch;
+ empty_con_chars();
+ printf("Got '%s' (%s) error.\nHit Esc or 'q' to give up, any other key to retry:",
+ it->inst_interp_error(it, ic), it->interp_error(it, ic));
+ fflush(stdout);
+ ch = next_con_char();
+ printf("\n");
+ if (ch == 0x03 || ch == 0x1b || ch == 'q' || ch == 'Q') /* ^C, Escape or Q */
+ return 1;
+ return 0;
+}
+
+/* Read all the strips, and return nonzero on abort/error */
+static int
+read_strips(
+instType itype, /* Chart instrument type */
+chcol **scols, /* Location sorted pointers to cols (return values) */
+instType *atype, /* Return the instrument type used to read the chart */
+int npat, /* Total valid patches */
+int totpa, /* Total passes (rows) */
+int stipa, /* Steps (patches) in pass (Excluding DTP51 Max/Min) */
+int *pis, /* Passes in each strip (rows in each sheet), 0 terminated */
+alphix *paix, /* Pass (row) index generators */
+alphix *saix, /* Step (patch) index generators */
+int ixord, /* Index order, 0 = pass then step */
+int rstart, /* Random start/chart id */
+int hex, /* Hexagon test patches */
+icompath *ipath, /* Instrument path to open */
+flow_control fc, /* flow control */
+double plen, /* Patch length in mm (used by DTP20/41) */
+double glen, /* Gap length in mm (used by DTP20/41) */
+double tlen, /* Trailer length in mm (used by DTP41T) */
+int trans, /* Use transmission mode */
+int emis, /* Use emissive mode */
+int displ, /* 1 = Use display emissive mode, 2 = display bright rel. */
+ /* 3 = display white rel. */
+int dtype, /* Display type selection charater */
+inst_opt_filter fe, /* Optional filter */
+int nocal, /* Disable initial calibration */
+int disbidi, /* Disable automatic bi-directional strip recognition */
+int highres, /* Use high res spectral mode */
+char *ccxxname, /* Colorimeter Correction/Colorimeter Calibration name */
+icxObserverType obType, /* ccss observer */
+double scan_tol, /* Modify patch consistency tolerance */
+int pbypatch, /* Patch by patch measurement */
+int xtern, /* Use external (user supplied) values rather than instument read */
+int spectral, /* Generate spectral info flag */
+int uvmode, /* ~~~ i1pro2 test mode ~~~ */
+int accurate_expd, /* Expected values can be assumed to be accurate */
+int emit_warnings, /* Emit warnings for wrong strip, unexpected value */
+a1log *log /* verb, debug & error log */
+) {
+ inst *it = NULL;
+ inst_mode cap; /* Mode capability */
+ inst2_capability cap2;
+ inst3_capability cap3;
+ int n, i, j;
+ int rmode = 0; /* Read mode, 0 = spot, 1 = strip, 2 = xy, 3 = chart */
+ int svdmode = 0; /* Saved mode, 0 = no, 1 = use saved mode */
+ inst_code rv;
+ baud_rate br = baud_38400; /* Target baud rate */
+ int skipp = 0; /* Initial strip readings to skip */
+ int nextrap = 0; /* Number of extra patches for max and min */
+ int ch;
+
+ if (xtern == 0) { /* Use instrument values */
+
+ /* Instrument that the chart is set up for */
+ if (itype == instDTP51) {
+ skipp = 1; /* First reading is the Max density patch */
+ nextrap = 2;
+ }
+
+ if ((it = new_inst(ipath, 0, log, DUIH_FUNC_AND_CONTEXT)) == NULL) {
+ printf("Unknown, inappropriate or no instrument detected\n");
+ return -1;
+ }
+ /* Establish communications */
+ if ((rv = it->init_coms(it, br, fc, 15.0)) != inst_ok) {
+ printf("Establishing communications with instrument failed with message '%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+
+ /* set filter configuration before initialising/calibrating */
+ if (fe != inst_opt_filter_unknown) {
+ if ((rv = it->get_set_opt(it, inst_opt_set_filter, fe)) != inst_ok) {
+ printf("Setting filter configuration not supported by instrument\n");
+ it->del(it);
+ return -1;
+ }
+ }
+
+ /* Set it up the way we want */
+ if ((rv = it->init_inst(it)) != inst_ok) {
+ printf("Initialising instrument failed with message '%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+
+ *atype = it->get_itype(it); /* Actual instrument type */
+ if (*atype != itype)
+ a1logv(log, 1, "Warning: chart is for %s, using instrument %s\n",inst_name(itype),inst_name(*atype));
+
+ {
+ int ccssset = 0;
+ inst_mode mode = 0;
+
+ it->capabilities(it, &cap, &cap2, &cap3);
+
+ if (trans) {
+ if (!IMODETST(cap, inst_mode_transmission)) {
+ printf("Need transmission reading capability,\n");
+ printf("and instrument doesn't support it\n");
+ it->del(it);
+ return -1;
+ }
+
+ } else if (emis || displ) {
+
+ if (emis) {
+ if (!IMODETST(cap, inst_mode_emis_spot)
+ && !IMODETST(cap, inst_mode_emis_strip)) {
+ printf("Need emissive spot or strip reading capability\n");
+ printf("and instrument doesn't support it\n");
+ it->del(it);
+ return -1;
+ }
+ } else {
+ /* Should we allow for non-adaptive mode ? */
+ if (!IMODETST(cap, inst_mode_emis_spot)) {
+ printf("Need emissive reading capability\n");
+ printf("and instrument doesn't support it\n");
+ it->del(it);
+ return -1;
+ }
+ }
+
+ } else {
+ if (!IMODETST(cap, inst_mode_reflection)) {
+ printf("Need reflection spot, strip, xy or chart reading capability,\n");
+ printf("and instrument doesn't support it\n");
+ it->del(it);
+ return -1;
+ }
+ }
+
+ /* Set display type */
+ if (dtype != 0) {
+
+ if (cap2 & inst2_disptype) {
+ int ix;
+ if ((ix = inst_get_disptype_index(it, dtype, 0)) < 0) {
+ printf("Setting display type ix %d failed\n",ix);
+ it->del(it);
+ return -1;
+ }
+
+ if ((rv = it->set_disptype(it, ix)) != inst_ok) {
+ printf("Setting display type ix %d not supported by instrument\n",ix);
+ it->del(it);
+ return -1;
+ }
+ } else
+ printf("Display type ignored - instrument doesn't support display type\n");
+ }
+
+ if (spectral && !IMODETST(cap, inst_mode_spectral)) {
+ printf("Warning: Instrument isn't capable of spectral measurement\n");
+ spectral = 0;
+ }
+
+ /* Colorimeter Correction Matrix */
+ if (ccxxname[0] != '\000') {
+ ccss *cs = NULL;
+ ccmx *cx = NULL;
+
+ if ((cx = new_ccmx()) == NULL) {
+ printf("\nnew_ccmx failed\n");
+ it->del(it);
+ return -1;
+ }
+ if (cx->read_ccmx(cx,ccxxname) == 0) {
+ if ((cap2 & inst2_ccmx) == 0) {
+ printf("\nInstrument doesn't have Colorimeter Correction Matrix capability\n");
+ it->del(it);
+ return -1;
+ }
+ if ((rv = it->col_cor_mat(it, cx->matrix)) != inst_ok) {
+ printf("\nSetting Colorimeter Correction Matrix failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ cx->del(cx);
+ it->del(it);
+ return -1;
+ }
+ cx->del(cx);
+ } else {
+ cx->del(cx);
+ cx = NULL;
+
+ /* CCMX failed, try CCSS */
+ if ((cs = new_ccss()) == NULL) {
+ printf("\nnew_ccss failed\n");
+ it->del(it);
+ return -1;
+ }
+ if (cs->read_ccss(cs,ccxxname)) {
+ printf("\nReading CCMX/CCSS File '%s' failed with error %d:'%s'\n",
+ ccxxname, cs->errc, cs->err);
+ cs->del(cs);
+ it->del(it);
+ return -1;
+ }
+ if ((cap2 & inst2_ccss) == 0) {
+ printf("\nInstrument doesn't have Colorimeter Calibration Spectral Sample capability\n");
+ cs->del(cs);
+ it->del(it);
+ return -1;
+ }
+ if ((rv = it->get_set_opt(it, inst_opt_set_ccss_obs, obType, NULL)) != inst_ok) {
+ printf("\nSetting CCSS observer failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ cs->del(cs);
+ it->del(it);
+ return -1;
+ }
+ if ((rv = it->col_cal_spec_set(it, cs->samples, cs->no_samp)) != inst_ok) {
+ printf("\nSetting Colorimeter Calibration Spectral Samples failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ cs->del(cs);
+ it->del(it);
+ return -1;
+ }
+ ccssset = 1;
+ cs->del(cs);
+ }
+ }
+
+ /* If non-standard observer wasn't set by a CCSS file above */
+ if (obType != icxOT_default && (cap2 & inst2_ccss) && ccssset == 0) {
+ if ((rv = it->get_set_opt(it, inst_opt_set_ccss_obs, obType, NULL)) != inst_ok) {
+ printf("\nSetting CCSS observer failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+ }
+
+ /* Disable initial calibration of machine if selected */
+ if (nocal != 0){
+ if ((rv = it->get_set_opt(it,inst_opt_noinitcalib, 0)) != inst_ok) {
+ printf("Setting no-initial calibrate failed with '%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ printf("Disable inital-calibrate not supported\n");
+ }
+ }
+
+ /* If it battery powered, show the status of the battery */
+ if ((cap2 & inst2_has_battery)) {
+ double batstat = 0.0;
+ if ((rv = it->get_set_opt(it, inst_stat_battery, &batstat)) != inst_ok) {
+ printf("\nGetting instrument battery status failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+ printf("The battery charged level is %.0f%%\n",batstat * 100.0);
+ }
+
+ /* Set it to the appropriate mode */
+ if (highres) {
+ if (IMODETST(cap, inst_mode_highres)) {
+ inst_code ev;
+ if ((ev = it->get_set_opt(it, inst_opt_highres)) != inst_ok) {
+ printf("\nSetting high res mode failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, ev), it->interp_error(it, ev));
+ it->del(it);
+ return -1;
+ }
+ highres = 1;
+ } else {
+ a1logv(log, 1, "high resolution ignored - instrument doesn't support high res. mode\n");
+ }
+ }
+
+ if (scan_tol != 1.0) {
+ if (cap2 & inst2_has_scan_toll) {
+ inst_code ev;
+ if ((ev = it->get_set_opt(it, inst_opt_scan_toll, scan_tol)) != inst_ok) {
+ printf("\nSetting patch consistency tolerance to %f failed with error :'%s' (%s)\n",
+ scan_tol, it->inst_interp_error(it, ev), it->interp_error(it, ev));
+ it->del(it);
+ return -1;
+ }
+ highres = 1;
+ } else {
+ a1logv(log, 1, "Modified patch consistency tolerance ignored - instrument doesn't support it\n");
+ }
+ }
+
+ /* Should look at instrument type & user spec ??? */
+ if (trans) {
+ if (pbypatch && IMODETST(cap, inst_mode_trans_spot)
+ && it->check_mode(it, inst_mode_trans_spot) == inst_ok) {
+ mode = inst_mode_trans_spot;
+ rmode = 0;
+ } else if (IMODETST(cap, inst_mode_trans_chart)
+ && it->check_mode(it, inst_mode_trans_chart) == inst_ok) {
+ mode = inst_mode_trans_chart;
+ rmode = 3;
+ } else if (IMODETST(cap, inst_mode_trans_xy)
+ && it->check_mode(it, inst_mode_trans_xy) == inst_ok) {
+ mode = inst_mode_trans_xy;
+ rmode = 2;
+ } else if (IMODETST(cap, inst_mode_trans_strip)
+ && it->check_mode(it, inst_mode_trans_strip) == inst_ok) {
+ mode = inst_mode_trans_strip;
+ rmode = 1;
+ } else {
+ mode = inst_mode_trans_spot;
+ rmode = 0;
+ }
+ } else if (displ) {
+ /* We assume a display mode will always be spot by spot */
+ mode = inst_mode_emis_spot;
+ rmode = 0;
+ } else if (emis) {
+ if (pbypatch && IMODETST(cap, inst_mode_emis_spot)
+ && it->check_mode(it, inst_mode_emis_spot) == inst_ok) {
+ mode = inst_mode_emis_spot;
+ rmode = 0;
+ } else if (IMODETST(cap, inst_mode_emis_strip)
+ && it->check_mode(it, inst_mode_emis_strip) == inst_ok) {
+ mode = inst_mode_emis_strip;
+ rmode = 1;
+ } else {
+ mode = inst_mode_emis_spot;
+ rmode = 0;
+ }
+ } else {
+ inst_stat_savdrd sv = inst_stat_savdrd_none;
+
+ /* See if instrument has a saved mode, and if it has data that */
+ /* could match this chart */
+ if (IMODETST(cap, inst_mode_s_reflection)) {
+
+ a1logv(log, 2, "Instrument has a svaed chart mode\n");
+
+ if ((rv = it->get_set_opt(it, inst_stat_saved_readings, &sv)) != inst_ok) {
+ printf("Getting saved reading status failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+
+ if (sv & inst_stat_savdrd_chart) {
+ int no_patches, no_rows, pat_per_row, chart_id, missing_row;
+
+ a1logv(log, 2, "There is a saved chart\n");
+
+ if ((rv = it->get_set_opt(it, inst_stat_s_chart,
+ &no_patches, &no_rows, &pat_per_row, &chart_id, &missing_row))
+ != inst_ok) {
+ printf("Getting saved chart details failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+
+ if (npat != no_patches || totpa != no_rows
+ || stipa != pat_per_row || rstart != chart_id) {
+ printf("Can't use saved chart because it doesn't match\n");
+ sv &= ~inst_stat_savdrd_chart;
+ }
+
+ if (missing_row >= 0) {
+ printf("Can't use saved chart because row %d hasn't been read\n",missing_row);
+ sv &= ~inst_stat_savdrd_chart;
+ }
+ }
+
+ if (sv & inst_stat_savdrd_xy) {
+ int nstr;
+ int no_sheets, no_patches, no_rows, pat_per_row;
+
+ /* Count the number of strips (sheets) */
+ for (nstr = 0; pis[nstr] != 0; nstr++)
+ ;
+
+ if ((rv = it->get_set_opt(it, inst_stat_s_xy,
+ &no_sheets, &no_patches, &no_rows, &pat_per_row)) != inst_ok) {
+ printf("Getting saved sheet details failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+
+ if (nstr != no_sheets || npat != no_patches
+ || totpa != no_rows || stipa != pat_per_row) {
+ a1logv(log, 1, "Can't use saved sheets because they don't match chart\n");
+ a1logv(log, 1, "Got %d sheets, expect %d. Got %d patches, expect %d.\n",
+ no_sheets,nstr,no_patches,npat);
+ a1logv(log, 1, "Got %d rows, expect %d. Got %d patches per row, expect %d.\n",
+ no_rows,totpa,pat_per_row,stipa);
+ sv &= ~inst_stat_savdrd_xy;
+ }
+ }
+
+ if (sv & inst_stat_savdrd_strip) {
+ int no_patches, no_rows, pat_per_row;
+
+ if ((rv = it->get_set_opt(it, inst_stat_s_strip,
+ &no_patches, &no_rows, &pat_per_row)) != inst_ok) {
+ printf("Getting saved strip details failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+
+ if (npat != no_patches || totpa != no_rows || stipa != pat_per_row) {
+ a1logv(log, 1, "Can't use saved strips because they don't match chart\n");
+ sv &= ~inst_stat_savdrd_strip;
+ }
+ }
+
+ if (sv & inst_stat_savdrd_spot) {
+ int no_patches;
+
+ if ((rv = it->get_set_opt(it, inst_stat_s_spot, &no_patches)) != inst_ok) {
+ printf("Getting saved spot details failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+
+ if (npat != no_patches) {
+ a1logv(log, 1, "Can't use saved spots because they don't match chart - got %d patches, expect %d\n",no_patches,npat);
+ sv &= ~inst_stat_savdrd_spot;
+ }
+ }
+ }
+
+ if (pbypatch
+ && IMODETST(cap, inst_mode_s_ref_spot)
+ && it->check_mode(it, inst_mode_s_ref_spot) == inst_ok
+ && (sv & inst_stat_savdrd_spot)) {
+ mode = inst_mode_s_ref_spot;
+ svdmode = 1;
+ rmode = 0;
+
+ } else if (IMODETST(cap, inst_mode_s_ref_chart)
+ && it->check_mode(it, inst_mode_s_ref_chart) == inst_ok
+ && (sv & inst_stat_savdrd_chart)) {
+ mode = inst_mode_s_ref_chart;
+ svdmode = 1;
+ rmode = 3;
+
+ } else if (IMODETST(cap, inst_mode_s_ref_xy)
+ && it->check_mode(it, inst_mode_s_ref_xy) == inst_ok
+ && (sv & inst_stat_savdrd_xy)) {
+ mode = inst_mode_s_ref_xy;
+ svdmode = 1;
+ rmode = 2;
+
+ } else if (IMODETST(cap, inst_mode_s_ref_strip)
+ && it->check_mode(it, inst_mode_s_ref_strip) == inst_ok
+ && (sv & inst_stat_savdrd_strip)) {
+ mode = inst_mode_s_ref_strip;
+ svdmode = 1;
+ rmode = 1;
+
+ } else if (IMODETST(cap, inst_mode_s_ref_spot)
+ && it->check_mode(it, inst_mode_s_ref_spot) == inst_ok
+ && (sv & inst_stat_savdrd_spot)) {
+ mode = inst_mode_s_ref_spot;
+ svdmode = 1;
+ rmode = 0;
+
+ } else if (pbypatch && IMODETST(cap, inst_mode_ref_spot)
+ && it->check_mode(it, inst_mode_ref_spot) == inst_ok) {
+ mode = inst_mode_ref_spot;
+ rmode = 0;
+
+ } else if (IMODETST(cap, inst_mode_ref_chart)
+ && it->check_mode(it, inst_mode_ref_chart) == inst_ok) {
+ mode = inst_mode_ref_chart;
+ rmode = 3;
+
+ } else if (IMODETST(cap, inst_mode_ref_xy)
+ && it->check_mode(it, inst_mode_ref_xy) == inst_ok) {
+ mode = inst_mode_ref_xy;
+ rmode = 2;
+
+ } else if (IMODETST(cap, inst_mode_ref_strip)
+ && it->check_mode(it, inst_mode_ref_strip) == inst_ok) {
+ mode = inst_mode_ref_strip;
+ rmode = 1;
+
+ } else {
+ mode = inst_mode_ref_spot;
+ rmode = 0;
+ }
+ }
+ if (spectral)
+ mode |= inst_mode_spectral;
+
+ // ~~~ i1pro2 test code ~~~ */
+ if (uvmode) {
+ if (!IMODETST(cap, inst_mode_ref_uv)) {
+ warning("UV measurement mode requested, but instrument doesn't support this mode");
+ uvmode = 0;
+ } else {
+ mode |= inst_mode_ref_uv;
+ }
+ }
+
+ if ((rv = it->set_mode(it, mode)) != inst_ok) {
+ printf("\nSetting instrument mode failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+ it->capabilities(it, &cap, &cap2, &cap3);
+ }
+ }
+
+ /* -------------------------------------------------- */
+ if (rmode == 3) { /* For chart mode, read all at once */
+ int chid; /* Chart ID number */
+ ipatch *vals; /* Array of values */
+
+ if (svdmode)
+ printf("Reading chart from values saved in instrument\n");
+ else {
+ /* ~~999 ??? Need to setup trigger and wait for it appropriately ??? */
+ printf("Reading the whole chart in one go\n");
+ }
+
+ /* Allocate space for patches */
+ if ((vals = (ipatch *)calloc(sizeof(ipatch), npat)) == NULL)
+ error("Malloc failed!");
+
+ /* Initialise return values */
+ for (i = 0; i < npat; i++) {
+ strncpy(vals[i].loc, scols[i]->loc, ICOM_MAX_LOC_LEN-1);
+ vals[i].loc[ICOM_MAX_LOC_LEN-1] = '\000';
+ vals[i].XYZ_v = 0;
+ }
+
+ for (;;) { /* retry loop */
+ if ((rv = it->read_chart(it, npat, totpa, stipa, pis, rstart, vals)) == inst_ok)
+ break;
+ if (ierror(it, rv) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (rv != inst_ok) {
+ free(vals);
+ it->del(it);
+ return -1;
+ }
+
+ printf("Chart read OK\n");
+
+ /* Transfer the values */
+ /* We assume they are all in the right order */
+ for (i = 0; i < npat; i++) {
+ /* Copy XYZ */
+ if (vals[i].XYZ_v == 0)
+ error("Instrument didn't return XYZ value for patch %d, loc %s",i,scols[i]->loc);
+ for (j = 0; j < 3; j++)
+ scols[i]->XYZ[j] = vals[i].XYZ[j];
+
+ /* Copy spectral */
+ if (vals[i].sp.spec_n > 0) {
+ scols[i]->sp = vals[i].sp;
+ }
+ scols[i]->mtype = vals[i].mtype;
+ scols[i]->rr = 1; /* Has been read */
+ }
+ free(vals);
+
+ /* -------------------------------------------------- */
+ /* !!! Hmm. Should really allow user to navigate amongst the sheets, */
+ /* !!! and skip any sheets already read. */
+ } else if (rmode == 2) { /* For xy mode, read each sheet */
+ ipatch *vals;
+ int nsheets, sheet; /* Total sheets/current sheet (sheet == pass) */
+ int rpat = npat; /* Remaining total patches */
+ int pai; /* Overall pass index */
+ int sti; /* Overall step index */
+ char *pn[3] = { NULL, NULL, NULL} ; /* Location 1/2/3 Pass name (letters) */
+ char *sn[3] = { NULL, NULL, NULL} ; /* Location 1/2/3 Step name (numbers) */
+ int k;
+
+ { /* Figure the maximum number sheets and of patches in a sheet, for allocation */
+ int lpaist = 0; /* Largest number of passes in strip/sheet */
+ for (nsheets = 0; pis[nsheets] != 0; nsheets++) {
+ if (pis[nsheets] > lpaist)
+ lpaist = pis[nsheets];
+ }
+
+ if ((vals = (ipatch *)calloc(sizeof(ipatch), (lpaist * stipa))) == NULL)
+ error("Malloc failed!");
+ }
+
+ /* Make sure we can access the instrument table */
+ if (cap2 & inst2_xy_holdrel) {
+ it->xy_clear(it);
+ }
+
+ /* XY mode doesn't use the trigger mode */
+
+ /* For each pass (==sheet) */
+ for (sheet = 1, pai = sti = 0; pis[sheet-1] != 0; sheet++) {
+ int paist; /* Passes in current Strip (== columns in current sheet) */
+ int rnpatch; /* Rounded up (inc. padding) Patches in current pass (sheet) */
+ int npatch; /* Patches in pass (sheet), excluding padding */
+ int fspipa; /* First pass steps in pass */
+ int nloc; /* Number of fiducial locations needed */
+ double ox = 0.0, oy = 0.0; /* Origin */
+ double ax = 1.0, ay = 0.0; /* pass increment */
+ double aax = 0.0, aay = 0.0; /* pass offset for hex odd steps */
+ double px = 0.0, py = 1.0; /* step (==patch) increment */
+
+ fspipa = stipa;
+ paist = pis[sheet-1]; /* columns (letters) in sheet (strip) */
+ npatch = rnpatch = paist * stipa; /* Total patches including padding */
+ if (npatch > rpat) { /* This is a non-full pass */
+ if (paist == 1) {
+ fspipa -= (npatch - rpat);/* Last patch in first strip */
+ if (fspipa < 1)
+ error ("Assert in read_strips, fspipa = %d",fspipa);
+ }
+ npatch = rpat; /* Total patches excluding padding */
+ }
+
+ nloc = 3;
+ if (paist == 1) {
+ nloc = 2; /* Only one strip, so only 2 locations needed */
+ if (fspipa == 1)
+ nloc = 1; /* Only one strip, one patch, so one location */
+ }
+ for (k = 0; k < 3; k++) {
+ if (pn[k] != NULL)
+ free(pn[k]);
+ if (sn[k] != NULL)
+ free(sn[k]);
+ }
+ pn[0] = paix->aix(paix, pai); /* First pass (letter) */
+ sn[0] = saix->aix(saix, 0); /* First step (patch) (number) */
+ pn[1] = paix->aix(paix, pai); /* First pass (letter) */
+ sn[1] = saix->aix(saix, 0 + fspipa-1); /* Last step (patch) (number) */
+ pn[2] = paix->aix(paix, pai + paist-1); /* Last pass (letter) */
+ sn[2] = saix->aix(saix, 0); /* First step (patch) (number) */
+
+ empty_con_chars();
+ if (sheet == 1) {
+ printf("Please place sheet %d of %d on table, then\n",sheet, nsheets);
+ } else
+ printf("\nPlease remove previous sheet, then place sheet %d of %d on table, then\n",sheet, nsheets);
+ printf("hit return to continue, Esc or 'q' to give up"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ for (k = 0; k < 3; k++) {
+ if (pn[k] != NULL)
+ free(pn[k]);
+ if (sn[k] != NULL)
+ free(sn[k]);
+ }
+ it->del(it);
+ return -1;
+ }
+ printf("\n");
+
+ if (cap2 & inst2_xy_holdrel) {
+
+ /* Hold table */
+ for (;;) { /* retry loop */
+ if ((rv = it->xy_sheet_hold(it)) == inst_ok)
+ break;
+ if (ierror(it, rv) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (rv != inst_ok) {
+ for (k = 0; k < 3; k++) {
+ if (pn[k] != NULL)
+ free(pn[k]);
+ if (sn[k] != NULL)
+ free(sn[k]);
+ }
+ it->del(it);
+ return -1;
+ }
+ }
+
+ if (cap2 & inst2_xy_locate) {
+ int ll;
+ double x[3], y[3];
+
+ /* Allow user location of points */
+ for (;;) { /* retry loop */
+ if ((rv = it->xy_locate_start(it)) == inst_ok)
+ break;
+ if (ierror(it, rv) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (rv != inst_ok) {
+ for (k = 0; k < 3; k++) {
+ if (pn[k] != NULL)
+ free(pn[k]);
+ if (sn[k] != NULL)
+ free(sn[k]);
+ }
+ it->del(it);
+ return -1;
+ }
+
+ /* For each location point */
+ for (ll = 0; ll < nloc; ll++) {
+ empty_con_chars();
+ printf("\nUsing the XY table controls, locate patch %s%s with the sight,\n",
+ pn[ll], sn[ll]);
+ printf("then hit return to continue, Esc or 'q' to give up"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ for (k = 0; k < 3; k++) {
+ if (pn[k] != NULL)
+ free(pn[k]);
+ if (sn[k] != NULL)
+ free(sn[k]);
+ }
+ it->del(it);
+ return -1;
+ }
+ printf("\n");
+
+ for (;;) { /* retry loop */
+ if ((rv = it->xy_get_location(it, &x[ll], &y[ll])) == inst_ok)
+ break;
+ if (ierror(it, rv) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (rv != inst_ok) {
+ for (k = 0; k < 3; k++) {
+ if (pn[k] != NULL)
+ free(pn[k]);
+ if (sn[k] != NULL)
+ free(sn[k]);
+ }
+ it->del(it);
+ return -1;
+ }
+ }
+
+ /* We're done with user control */
+ for (;;) { /* retry loop */
+ if ((rv = it->xy_locate_end(it)) == inst_ok)
+ break;
+ if (ierror(it, rv) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (rv != inst_ok) {
+ for (k = 0; k < 3; k++) {
+ if (pn[k] != NULL)
+ free(pn[k]);
+ if (sn[k] != NULL)
+ free(sn[k]);
+ }
+ it->del(it);
+ return -1;
+ }
+
+ /* Convert point locations into navigation values */
+ ox = x[0];
+ oy = y[0];
+ if (hex) {
+ double kk = sqrt(1.0/3.0);
+ double nn = fspipa - 1.0;
+ if (((fspipa-1) & 1) == 0) { /* [0] & [1] are lined up */
+ if (nloc == 3) {
+ px = (x[1] - x[0])/nn;
+ py = (y[1] - y[0])/nn;
+ ax = (x[2] - x[0])/(paist-1);
+ ay = (y[2] - y[0])/(paist-1);
+ aax = 0.5 * ax;
+ aay = 0.5 * ay;
+ } else if (nloc == 2) {
+ px = (x[1] - x[0])/nn;
+ py = (y[1] - y[0])/nn;
+ aax = kk * py; /* Scale and rotate */
+ aay = kk * -px;
+ }
+ } else { /* [0] & [1] are offset by aa[xy] */
+ if (nloc == 3) {
+ ax = (x[2] - x[0])/(paist-1);
+ ay = (y[2] - y[0])/(paist-1);
+ aax = 0.5 * ax;
+ aay = 0.5 * ay;
+ px = (x[1] - x[0] - aax)/nn;
+ py = (y[1] - y[0] - aay)/nn;
+ } else if (nloc == 2) {
+ px = (nn * (x[1] - x[0]) - kk * (y[1] - y[0]))/(kk * kk + nn * nn);
+ py = (nn * (y[1] - y[0]) + kk * (x[1] - x[0]))/(kk * kk + nn * nn);
+ aax = kk * py; /* Scale and rotate */
+ aay = kk * -px;
+ }
+ }
+
+ } else { /* Rectangular patches */
+ if (paist > 1) {
+ ax = (x[2] - x[0])/(paist-1);
+ ay = (y[2] - y[0])/(paist-1);
+ }
+ if (fspipa > 1) {
+ px = (x[1] - x[0])/(fspipa-1);
+ py = (y[1] - y[0])/(fspipa-1);
+ }
+ aax = aay = 0.0;
+ }
+ }
+
+ /* Read the sheets patches */
+ for (;;) { /* retry loop */
+ if ((rv = it->read_xy(it, paist, stipa, npatch, pn[0], sn[0],
+ ox, oy, ax, ay, aax, aay, px, py, vals)) == inst_ok)
+ break;
+ if (ierror(it, rv) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (rv != inst_ok) {
+ for (k = 0; k < 3; k++) {
+ if (pn[k] != NULL)
+ free(pn[k]);
+ if (sn[k] != NULL)
+ free(sn[k]);
+ }
+ it->del(it);
+ return -1;
+ }
+
+ printf("Sheet %d of %d read OK\n",sheet, nsheets);
+
+ /* Transfer the values */
+ /* We assume they are all in the right order */
+ for (i = 0; i < npatch; i++, sti++) {
+ /* Copy XYZ */
+ if (vals[i].XYZ_v == 0)
+ error("Instrument didn't return XYZ value for patch %d, loc %s",i,scols[sti]->loc);
+ for (j = 0; j < 3; j++)
+ scols[sti]->XYZ[j] = vals[i].XYZ[j];
+
+ /* Copy spectral */
+ if (vals[i].sp.spec_n > 0) {
+ scols[sti]->sp = vals[i].sp;
+ }
+ scols[sti]->rr = 1; /* Has been read */
+ }
+
+ if (cap2 & inst2_xy_holdrel) {
+
+ /* Release table and reset head */
+ for (;;) { /* retry loop */
+ if ((rv = it->xy_clear(it)) == inst_ok)
+ break;
+ if (ierror(it, rv) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (rv != inst_ok) {
+ for (k = 0; k < 3; k++) {
+ if (pn[k] != NULL)
+ free(pn[k]);
+ if (sn[k] != NULL)
+ free(sn[k]);
+ }
+ it->del(it);
+ return -1;
+ }
+ }
+
+ pai += paist; /* Tracj next first pass in strip */
+ rpat -= npatch; /* Track remaining patches */
+ }
+ for (k = 0; k < 3; k++) {
+ if (pn[k] != NULL)
+ free(pn[k]);
+ if (sn[k] != NULL)
+ free(sn[k]);
+ }
+ free(vals);
+
+ printf("\nPlease remove last sheet from table\n"); fflush(stdout);
+
+ /* -------------------------------------------------- */
+ } else if (rmode == 1) { /* For strip mode, simply read each strip */
+ int uswitch = 0; /* 0 if switch can be used, 1 if switch or keyboard */
+ ipatch *vals; /* Values read for a strip pass */
+ int incflag = 0; /* 0 = no change, 1 = increment, 2 = inc unread, */
+ /* -1 = decrement, -2 = done */
+ int stix; /* Strip index */
+ int pai; /* Current pass in current strip */
+ int oroi; /* Overall row index */
+
+ /* Do any needed calibration before the user places the instrument on a desired spot */
+ if (it->needs_calibration(it) & inst_calt_n_dfrble_mask) {
+ if ((rv = inst_handle_calibrate(it, inst_calt_needed, inst_calc_none, NULL, NULL))
+ != inst_ok) {
+ printf("\nCalibration failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+ }
+
+ /* Enable switch or user via uicallback trigger if possible */
+ if (cap2 & inst2_user_switch_trig) {
+ rv = it->get_set_opt(it, inst_opt_trig_user_switch);
+ uswitch = 2;
+
+ /* Or use just switch trigger */
+ } else if (cap2 & inst2_switch_trig) {
+ rv = it->get_set_opt(it, inst_opt_trig_switch);
+ uswitch = 1;
+
+ /* Or go for user vi uicallback trigger */
+ } else if (cap2 & inst2_user_trig) {
+ rv = it->get_set_opt(it, inst_opt_trig_user);
+
+ /* Or something is wrong with instrument capabilities */
+ } else {
+ printf("\nNo reasonable trigger mode avilable for this instrument\n");
+ it->del(it);
+ return -1;
+ }
+ if (rv != inst_ok) {
+ printf("\nSetting trigger mode failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+
+ /* Set so that return or any other key triggers, */
+ /* but retain our abort keys */
+ inst_set_uih(0x00, 0xff, DUIH_TRIG);
+ inst_set_uih('f', 'f', DUIH_CMND);
+ inst_set_uih('F', 'F', DUIH_CMND);
+ inst_set_uih('b', 'b', DUIH_CMND);
+ inst_set_uih('B', 'B', DUIH_CMND);
+ inst_set_uih('n', 'n', DUIH_CMND);
+ inst_set_uih('N', 'N', DUIH_CMND);
+ inst_set_uih('g', 'g', DUIH_CMND);
+ inst_set_uih('G', 'G', DUIH_CMND);
+ inst_set_uih('d', 'd', DUIH_CMND);
+ inst_set_uih('D', 'D', DUIH_CMND);
+ inst_set_uih('q', 'q', DUIH_ABORT);
+ inst_set_uih('Q', 'Q', DUIH_ABORT);
+ inst_set_uih(0x03, 0x03, DUIH_ABORT); /* ^c */
+ inst_set_uih(0x1b, 0x1b, DUIH_ABORT); /* Esc */
+
+ /* Allocate space for values from a pass/strip */
+ if ((vals = (ipatch *)calloc(sizeof(ipatch), (stipa+nextrap))) == NULL)
+ error("Malloc failed!");
+
+ /* Until we're done reading rows */
+ incflag = 0;
+
+ /* Skip to next unread if first has been read */
+ if (pis[0] != 0 && scols[0]->rr != 0)
+ incflag = 2;
+
+ for (oroi = stix = pai = 0;pis[0] != 0;) {
+ char *nn = NULL; /* Pass name */
+ int guide;
+ chcol **scb;
+ int boff = 0; /* Best offset */
+ int bdir = 0; /* Best overall direction */
+ int done = 0; /* nz if there are no unread rows */
+
+//printf("\n~1 incflag = %d, oroi %d, pai %d, stix %d\n", incflag, oroi, pai, stix);
+
+ /* Increment or decrement to the next row */
+ if (incflag > 0) {
+ int s_oroi = oroi;
+
+ /* Until we get to an unread pass */
+ for (;;) {
+ oroi++;
+ if (++pai >= pis[stix]) { /* Carry */
+ if (pis[++stix] == 0) { /* Carry */
+ stix = 0;
+ oroi = 0;
+ }
+ pai = 0;
+ }
+//printf("~1 stix = %d, pis[stix] = %d, oroi = %d, rr %d\n",stix, pis[stix],oroi,scols[oroi * stipa]->rr);
+ if (incflag == 1 || scols[oroi * stipa]->rr == 0 || oroi == s_oroi)
+ break;
+ }
+
+ /* Decrement the row */
+ } else if (incflag < 0) {
+ oroi--;
+ if (--pai < 0) { /* Carry */
+ if (--stix < 0) { /* Carry */
+ for (oroi = stix = 0; pis[stix] != 0; stix++) {
+ oroi += pis[stix];
+ }
+ stix--;
+ oroi--;
+ }
+ pai = pis[stix]-1;
+ }
+ }
+ incflag = 0;
+
+ /* See if there are any unread patches */
+ for (done = i = 0; i < npat; i += stipa) {
+ if (scols[i]->rr == 0)
+ break; /* At least one patch read */
+ }
+ if (i >= npat)
+ done = 1;
+
+//printf("~1 oroi %d, pai %d, stix %d pis[stix] %d, rr = %d\n", oroi, pai, stix, pis[stix],scols[oroi * stipa]->rr);
+ /* Convert overall pass number index into alpha label */
+ if (nn != NULL)
+ free(nn);
+ nn = paix->aix(paix, oroi);
+
+ guide = (pis[stix] - pai) * 5; /* Mechanical guide offset */
+
+ for (;;) { /* Until we give up reading this row */
+
+ /* Read a strip pass */
+ printf("\nReady to read strip pass %s%s\n",nn, done ? " (!! ALL ROWS READ !!)" : scols[oroi *stipa]->rr ? " (This row has been read)" : "" );
+ printf("Press 'f' to move forward, 'b' to move back, 'n' for next unread,\n");
+ printf(" 'd' when done, Esc or 'q' to quit without saving.\n");
+
+ if (uswitch == 1) {
+ printf("Trigger instrument switch to start reading,");
+ } else if (uswitch == 2) {
+ printf("Trigger instrument switch or any other key to start:");
+ } else {
+ printf("Press any other key to start:");
+ }
+ fflush(stdout);
+ if ((rv = it->read_strip(it, "STRIP", stipa+nextrap, nn, guide, plen, glen, tlen, vals)) != inst_ok
+ && (rv & inst_mask) != inst_user_trig) {
+
+#ifdef DEBUG
+ printf("read_strip returned '%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+#endif /* DEBUG */
+ /* Deal with user abort or command */
+ if ((rv & inst_mask) == inst_user_abort) {
+ int keyc = inst_get_uih_char();
+
+ /* Deal with a command */
+ if (keyc & DUIH_CMND) {
+ ch = keyc & 0xff;
+
+ printf("\n");
+ if (ch == 'f' || ch == 'F') {
+ incflag = 1;
+ break;
+ } else if (ch == 'b' || ch == 'B') {
+ incflag = -1;
+ break;
+ } else if (ch == 'n' || ch == 'N') {
+ incflag = 2;
+ break;
+ } else { /* Assume 'd' or 'D' */
+
+ /* See if there are any unread patches */
+ for (done = i = 0; i < npat; i += stipa) {
+ if (scols[i]->rr == 0)
+ break; /* At least one patch read */
+ }
+ if (i >= npat)
+ done = 1;
+
+ if (done) {
+ incflag = -2;
+ break;
+ }
+
+ /* Not all rows have been read */
+ empty_con_chars();
+ printf("\nDone ? - At least one unread patch (%s), Are you sure [y/n]: ",
+ scols[i]->loc);
+ fflush(stdout);
+ ch = next_con_char();
+ printf("\n");
+ if (ch == 'y' || ch == 'Y') {
+ incflag = -2;
+ break;
+ }
+ continue;
+ }
+
+ /* Deal with a user abort */
+ } else if (keyc & DUIH_ABORT) {
+ empty_con_chars();
+ printf("\n\nStrip read stopped at user request!\n");
+ printf("Hit Esc or 'q' to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ it->del(it);
+ return -1;
+ }
+ printf("\n");
+ continue;
+ }
+
+ /* Deal with needs calibration */
+ } else if ((rv & inst_mask) == inst_needs_cal) {
+ inst_code ev;
+
+ if (cap2 & inst2_no_feedback)
+ bad_beep();
+ printf("\nStrip read failed because instruments needs calibration\n");
+ ev = inst_handle_calibrate(it, inst_calt_needed, inst_calc_none, NULL, NULL);
+ if (ev != inst_ok) { /* Abort or fatal error */
+ it->del(it);
+ return -1;
+ }
+ continue;
+
+ /* Deal with a bad sensor position */
+ } else if ((rv & inst_mask) == inst_wrong_config) {
+ printf("\n\nSpot read failed due to the sensor being in the wrong position\n(%s)\n",it->interp_error(it, rv));
+ continue;
+
+ /* Deal with a misread */
+ } else if ((rv & inst_mask) == inst_misread) {
+ if (cap2 & inst2_no_feedback)
+ bad_beep();
+ empty_con_chars();
+ printf("\nStrip read failed due to misread (%s)\n",it->interp_error(it, rv));
+ printf("Hit Esc to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ it->del(it);
+ return -1;
+ }
+ printf("\n");
+ continue;
+ /* Deal with a communications error */
+ } else if ((rv & inst_mask) == inst_coms_fail) {
+ if (cap2 & inst2_no_feedback)
+ bad_beep();
+ empty_con_chars();
+ printf("\nStrip read failed due to communication problem.\n");
+ printf("Hit Esc or 'q' to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ it->del(it);
+ return -1;
+ }
+ printf("\n");
+ if (it->icom->port_type(it->icom) == icomt_serial) {
+ /* Allow retrying at a lower baud rate */
+ int tt = it->last_scomerr(it);
+ if (tt & (ICOM_BRK | ICOM_FER | ICOM_PER | ICOM_OER)) {
+ if (br == baud_57600) br = baud_38400;
+ else if (br == baud_38400) br = baud_9600;
+ else if (br == baud_9600) br = baud_4800;
+ else if (br == baud_9600) br = baud_4800;
+ else if (br == baud_2400) br = baud_1200;
+ else br = baud_1200;
+ }
+ if ((rv = it->init_coms(it, br, fc, 15.0)) != inst_ok) {
+#ifdef DEBUG
+ printf("init_coms returned '%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+#endif /* DEBUG */
+ it->del(it);
+ return -1;
+ }
+ }
+ continue;
+ } else {
+ /* Some other error. Treat it as fatal */
+ if (cap2 & inst2_no_feedback)
+ bad_beep();
+ printf("\nStrip read failed due unexpected error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ printf("Hit Esc or 'q' to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ it->del(it);
+ return -1;
+ }
+ printf("\n");
+ continue;
+ }
+
+ /* Successfully read the strip */
+ /* See which expected row best correlates with the one we've read. */
+ /* Figure out if there is an "off by one" error for a DTP51 */
+ } else {
+ int choroi; /* Check overall row index */
+ double corr; /* Correlation with expected value */
+ int loff = 0, hoff = 0; /* DTP51 offset test range */
+ int toff; /* Test offset */
+ int dir, dirrg = 1; /* Direction range, 1 = forward, 2 = fwd & bwd */
+
+ int boroi = -1; /* Best overall row index */
+
+ double bcorr = 1e6; /* Best correlation value */
+ double werror = 0.0; /* Worst case error in best correlation strip */
+
+ double xbcorr = 1e6; /* Expected pass correlation value */
+ int xboff; /* Expected pass offset */
+ int xbdir; /* Expected pass overall pass direction */
+ double xwerror = 0.0; /* Expected pass worst error in best strip */
+
+ if (disbidi == 0 && (cap2 & inst2_bidi_scan))
+ dirrg = 2; /* Enable bi-directional strip recognition */
+
+ /* DTP51 has a nasty habit of misaligning test squares by +/- 1 */
+ /* See if this might have happened */
+ if (it->itype == instDTP51) {
+ loff = -1;
+ hoff = 1;
+ }
+
+ for (choroi = 0; choroi < totpa; choroi++) {
+ /* Explore strip direction */
+ for (dir = 0; dir < dirrg; dir++) {
+ double pwerr; /* This rows worst error */
+ scb = &scols[choroi * stipa];
+
+ /* Explore off by +/-1 error for DTP51 */
+ for (toff = loff; toff <= hoff; toff++) {
+ double ynorm = 1.0;
+
+ /* Compute a Y scaling value to give correlation */
+ /* a chance for absolute readings */
+ if (vals[skipp+toff].XYZ_v != 0) {
+ double refnorm = 0.0;
+ ynorm = 0.0;
+ for (i = 0; i < stipa; i++) {
+ int ix = i+skipp+toff;
+ if (dir != 0)
+ ix = stipa - 1 - ix;
+ refnorm += scb[i]->eXYZ[1];
+ ynorm += vals[ix].XYZ[1];
+ }
+ ynorm = refnorm/ynorm;
+ }
+
+ /* Compare just sample patches (not padding Max/Min) */
+ for (pwerr = corr = 0.0, n = 0, i = 0; i < stipa; i++, n++) {
+ double vcorr;
+ int ix = i+skipp+toff;
+ if (dir != 0)
+ ix = stipa - 1 - ix;
+ if (vals[ix].XYZ_v == 0)
+ error("Instrument didn't return XYZ value");
+ vcorr = xyzLabDE(ynorm, vals[ix].XYZ, scb[i]->eXYZ);
+//printf("DE %f from vals[%d] %f %f %f and scols[%d] %f %f %f\n", vcorr, ix, vals[ix].XYZ[0], vals[ix].XYZ[1], vals[ix].XYZ[2], i + choroi * stipa, scb[i]->eXYZ[0], scb[i]->eXYZ[1], scb[i]->eXYZ[2]);
+ corr += vcorr;
+ if (vcorr > pwerr)
+ pwerr = vcorr;
+ }
+ corr /= (double)n;
+#ifdef DEBUG
+ printf(" Strip %d dir %d offset %d correlation = %f\n",choroi,dir,toff,corr);
+
+#endif
+ /* Expected strip correlation and */
+ /* best fir to off by 1 and direction */
+ if (choroi == oroi && corr < xbcorr) {
+ xbcorr = corr;
+ xboff = toff;
+ xbdir = dir;
+ xwerror = pwerr; /* Expected passes worst error */
+ }
+
+ /* Best matched strip correlation */
+ if (corr < bcorr) {
+ boroi = choroi;
+ bcorr = corr;
+ boff = toff;
+ bdir = dir;
+ werror = pwerr;
+ }
+ }
+ }
+ }
+ if (emit_warnings != 0 && boroi != oroi) { /* Looks like the wrong strip */
+ char *mm = NULL;
+ mm = paix->aix(paix, boroi);
+#ifdef DEBUG
+ printf("Strip pass %s (%d) seems to have a better correlation that strip %s (%d)\n",
+ mm, boroi, nn, oroi);
+#endif
+ if (cap2 & inst2_no_feedback)
+ bad_beep();
+ empty_con_chars();
+ printf("\n(Warning) Seem to have read strip pass %s rather than %s!\n",mm,nn);
+ printf("Hit Return to use it anyway, any other key to retry, Esc or 'q' to give up:"); fflush(stdout);
+ free(mm);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ it->del(it);
+ return -1;
+ }
+ if (ch != 0x0d && ch != 0x0a) { /* !(CR or LF) */
+ printf("\n");
+ continue; /* Try again */
+ }
+ printf("\n");
+
+ /* Switch to state for expected strip */
+ bcorr = xbcorr;
+ boff = xboff;
+ bdir = xbdir;
+ werror = xwerror;
+ }
+ /* Arbitrary threshold. Good seems about 15-35, bad 95-130 */
+ if (emit_warnings != 0 && accurate_expd != 0 && werror >= 30.0) {
+#ifdef DEBUG
+ printf("(Warning) Patch error %f (>35 not good, >95 bad)\n",werror);
+#endif
+ if (cap2 & inst2_no_feedback)
+ bad_beep();
+ empty_con_chars();
+ printf("\nThere is at least one patch with an very unexpected response! (DeltaE %f)\n",werror);
+ printf("Hit Return to use it anyway, any other key to retry, Esc or 'q' to give up:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ it->del(it);
+ return -1;
+ }
+ if (ch != 0x0d && ch != 0x0a) { /* !Cr */
+ printf("\n");
+ continue;
+ }
+ printf("\n");
+ break;
+ }
+
+ /* Must be OK - save the readings */
+ if (cap2 & inst2_no_feedback)
+ good_beep();
+ printf(" Strip read OK");
+ if (boff != 0)
+ printf(" (DTP51 offset fix of %d applied)",boff);
+ if (bdir != 0)
+ printf(" (Strip read in reverse direction)");
+ printf("\n");
+ break; /* Break out of retry loop */
+ }
+ }
+
+ if (nn != NULL) /* Finished with strip alpha index */
+ free(nn);
+ nn = NULL;
+
+ /* If we're done */
+ if (incflag == -2)
+ break;
+
+ /* If we are moving row, rather than having read one. */
+ if (incflag != 0)
+ continue;
+
+ /* Transfer the values (including DTP51 offset) */
+ scb = &scols[oroi * stipa];
+ for (n = 0, i = 0; i < stipa; i++, n++) {
+ int ix = i+skipp+boff;
+ if (bdir != 0)
+ ix = stipa - 1 - ix;
+
+ /* Copy XYZ */
+ if (vals[ix].XYZ_v == 0)
+ error("Instrument didn't return XYZ value");
+ for (j = 0; j < 3; j++)
+ scb[i]->XYZ[j] = vals[ix].XYZ[j];
+
+ /* Copy spectral */
+ if (vals[ix].sp.spec_n > 0) {
+ scb[i]->sp = vals[ix].sp;
+ }
+ scb[i]->rr = 1; /* Has been read */
+ }
+ incflag = 2; /* Skip to next unread */
+ } /* Go around to read another row */
+ free(vals);
+
+ /* -------------------------------------------------- */
+ /* Spot mode. This will be used if xtern != 0 */
+ } else {
+ int pix = 0;
+ int uswitch = 0; /* nz if switch can be used */
+ int incflag = 0; /* 0 = no change, 1 = increment, 2 = inc by 10, */
+ /* 3 = inc next unread, -1 = decrement, -2 = dec by 10 */
+ /* 4 = goto specific patch */
+ inst_opt_type omode; /* The option mode used */
+ ipatch val;
+
+ if (xtern == 0) { /* Instrument patch by patch */
+
+ /* Do any needed calibration before the user places the instrument on a desired spot */
+ if (it->needs_calibration(it) & inst_calt_n_dfrble_mask) {
+ if ((rv = inst_handle_calibrate(it, inst_calt_needed, inst_calc_none, NULL, NULL))
+ != inst_ok) {
+ printf("\nCalibration failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+ }
+
+ /* Enable switch or user via uicallback trigger if possible */
+ if (cap2 & inst2_user_switch_trig) {
+ omode = inst_opt_trig_user_switch;
+ rv = it->get_set_opt(it, omode);
+ uswitch = 1;
+
+ /* Or go for user via uicallback trigger */
+ } else if (cap2 & inst2_user_trig) {
+ omode = inst_opt_trig_user;
+ rv = it->get_set_opt(it, omode);
+
+ /* Or something is wrong with instrument capabilities */
+ } else {
+ printf("\nNo reasonable trigger mode avilable for this instrument\n");
+ it->del(it);
+ return -1;
+ }
+ if (rv != inst_ok) {
+ printf("\nSetting trigger mode failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+
+ /* Setup the keyboard trigger to return our commands */
+ inst_set_uih('f', 'f', DUIH_CMND);
+ inst_set_uih('F', 'F', DUIH_CMND);
+ inst_set_uih('b', 'b', DUIH_CMND);
+ inst_set_uih('B', 'B', DUIH_CMND);
+ inst_set_uih('n', 'n', DUIH_CMND);
+ inst_set_uih('N', 'N', DUIH_CMND);
+ inst_set_uih('g', 'g', DUIH_CMND);
+ inst_set_uih('G', 'G', DUIH_CMND);
+ inst_set_uih('d', 'd', DUIH_CMND);
+ inst_set_uih('D', 'D', DUIH_CMND);
+ inst_set_uih('q', 'q', DUIH_ABORT);
+ inst_set_uih('Q', 'Q', DUIH_ABORT);
+ inst_set_uih(0xd, 0xd, DUIH_TRIG); /* Return */
+ inst_set_uih(' ', ' ', DUIH_TRIG);
+ }
+
+ /* Skip to next unread if first has been read */
+ /* !!! would be nice to skip padding patches !!! */
+ incflag = 0;
+ if (npat > 0 && scols[0]->rr != 0)
+ incflag = 3;
+
+ /* Until we're done */
+ for(;pix < npat;) {
+ char buf[200], *bp = NULL, *ep = NULL;
+ char ch = 0;
+
+ /* Adjust the location */
+ if (incflag > 0 && incflag <= 2) { /* Incremente by 1 or 10 */
+
+ if (incflag == 2)
+ pix += 10;
+ else
+ pix++;
+ pix = pix % npat;
+
+ } else if (incflag < 0 && incflag >= -2) { /* Decrement by 1 or 10 */
+
+ if (incflag == -2)
+ pix -= 10;
+ else
+ pix--;
+ pix = pix % npat;
+ if (pix < 0)
+ pix += npat;
+
+ } else if (incflag == 3) { /* Increment to next unread */
+ int opix = pix;
+
+ if (pix >= npat)
+ pix = 0;
+ for (;;) {
+ if (scols[pix]->rr == 0 && strcmp(scols[pix]->id, "0") != 0)
+ break;
+ pix++;
+ if (pix >= npat)
+ pix = 0;
+ if (pix == opix)
+ break;
+ }
+ } else if (incflag == 4) { /* Goto specific patch */
+ printf("\nEnter patch to go to: "); fflush(stdout);
+
+ /* Read in the next line from stdin. */
+ if (fgets(buf, 200, stdin) == NULL) {
+ printf("Error - unrecognised input\n");
+ } else {
+ int opix = pix;
+
+ /* Skip whitespace */
+ for (bp = buf; *bp != '\000' && isspace(*bp); bp++)
+ ;
+
+ /* Skip non-whitespace */
+ for (ep = bp; *ep != '\000' && !isspace(*ep); ep++)
+ ;
+ *ep = '\000';
+
+ if (pix >= npat)
+ pix = 0;
+ for (;;) {
+ if (stricmp(scols[pix]->loc, bp) == 0)
+ break;
+ pix++;
+ if (pix >= npat)
+ pix = 0;
+ if (pix == opix) {
+ printf("Patch '%s' not found\n",bp);
+ break;
+ }
+ }
+ }
+ }
+ incflag = 0;
+
+ /* See if there are any unread patches */
+ for (i = 0; i < npat; i++) {
+ if (scols[i]->rr == 0 && strcmp(scols[i]->id, "0") != 0)
+ break; /* At least one patch read */
+ }
+
+ if (xtern != 0) { /* User entered values */
+ printf("\nReady to read patch '%s'%s\n",scols[pix]->loc,
+ i >= npat ? "(All patches read!)" :
+ strcmp(scols[pix]->id, "0") == 0 ? " (Padding Patch)" :
+ scols[pix]->rr ? " (Already read)" : "");
+ printf("Enter %s value (separated by spaces), or\n",
+ xtern == 1 ? "L*a*b*" : "XYZ");
+ printf(" 'f' to move forward, 'F' move forward 10,\n");
+ printf(" 'b' to move back, 'B; to move back 10,\n");
+ printf(" 'n' for next unread, 'g' to goto patch,\n");
+ printf(" 'd' when done, 'q' to abort, then press <return>: ");
+ fflush(stdout);
+
+ /* Read in the next line from stdin. */
+ if (fgets(buf, 200, stdin) == NULL) {
+ printf("Error - unrecognised input\n");
+ continue;
+ }
+ /* Skip whitespace */
+ for (bp = buf; *bp != '\000' && isspace(*bp); bp++)
+ ;
+
+ ch = *bp;
+ if (ch == '\000') {
+ printf("Error - unrecognised input\n");
+ continue;
+ }
+
+ } else { /* Using instrument */
+
+ empty_con_chars();
+
+ printf("\nReady to read patch '%s'%s\n",scols[pix]->loc,
+ i >= npat ? "(All patches read!)" :
+ strcmp(scols[pix]->id, "0") == 0 ? " (Padding Patch)" :
+ scols[pix]->rr ? " (Already read)" : "");
+
+ printf("hit 'f' to move forward, 'F' move forward 10,\n");
+ printf(" 'b' to move back, 'B; to move back 10,\n");
+ printf(" 'n' for next unread, 'g' to goto patch,\n");
+ printf(" 'd' when done, <esc> to abort,\n");
+
+ if (uswitch)
+ printf(" Instrument switch, <return> or <space> to read:");
+ else
+ printf(" <return> or <space> to read:");
+ fflush(stdout);
+
+ rv = it->read_sample(it, "SPOT", &val, 1);
+
+ /* Deal with reading */
+ if (rv == inst_ok) {
+ /* Read OK */
+ if (cap2 & inst2_no_feedback)
+ good_beep();
+ ch = '0';
+
+ /* Deal with user trigger */
+ } else if ((rv & inst_mask) == inst_user_trig) {
+ if (cap2 & inst2_no_feedback)
+ good_beep();
+ ch = inst_get_uih_char();
+
+ /* Deal with a abort or command */
+ } else if ((rv & inst_mask) == inst_user_abort) {
+ int keyc = inst_get_uih_char();
+
+ /* User issued a command */
+ if (keyc & DUIH_CMND) {
+ ch = keyc & 0xff;
+ printf("\n");
+
+ /* User aborted */
+ } else if (keyc & DUIH_ABORT) {
+ empty_con_chars();
+ printf("\n\nSpot read stopped at user request!\n");
+ printf("Hit Esc or 'q' to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ it->del(it);
+ return -1;
+ }
+ printf("\n");
+ continue;
+ }
+
+ /* Deal with needs calibration */
+ } else if ((rv & inst_mask) == inst_needs_cal) {
+ inst_code ev;
+
+ if (cap2 & inst2_no_feedback)
+ bad_beep();
+ printf("\nSpot read failed because instruments needs calibration\n");
+ ev = inst_handle_calibrate(it, inst_calt_needed, inst_calc_none, NULL, NULL);
+ if (ev != inst_ok) { /* Abort or fatal error */
+ it->del(it);
+ return -1;
+ }
+ continue;
+ /* Deal with a misread */
+ } else if ((rv & inst_mask) == inst_misread) {
+ if (cap2 & inst2_no_feedback)
+ bad_beep();
+ empty_con_chars();
+ printf("\nStrip read failed due to misread (%s)\n",it->interp_error(it, rv));
+ printf("Hit Esc or 'q' to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ it->del(it);
+ return -1;
+ }
+ printf("\n");
+ continue;
+ /* Deal with a communications error */
+ } else if ((rv & inst_mask) == inst_coms_fail) {
+ if (cap2 & inst2_no_feedback)
+ bad_beep();
+ empty_con_chars();
+ printf("\nStrip read failed due to communication problem.\n");
+ printf("Hit Esc or 'q' to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ it->del(it);
+ return -1;
+ }
+ printf("\n");
+ if (it->icom->port_type(it->icom) == icomt_serial) {
+ /* Allow retrying at a lower baud rate */
+ int tt = it->last_scomerr(it);
+ if (tt & (ICOM_BRK | ICOM_FER | ICOM_PER | ICOM_OER)) {
+ if (br == baud_57600) br = baud_38400;
+ else if (br == baud_38400) br = baud_9600;
+ else if (br == baud_9600) br = baud_4800;
+ else if (br == baud_9600) br = baud_4800;
+ else if (br == baud_2400) br = baud_1200;
+ else br = baud_1200;
+ }
+ if ((rv = it->init_coms(it, br, fc, 15.0)) != inst_ok) {
+#ifdef DEBUG
+ printf("init_coms returned '%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+#endif /* DEBUG */
+ it->del(it);
+ return -1;
+ }
+ }
+ continue;
+
+
+ } else {
+ /* Some other error. Treat it as fatal */
+ if (cap2 & inst2_no_feedback)
+ bad_beep();
+ printf("\nPatch read failed due unexpected error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ printf("Hit Esc or 'q' to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ it->del(it);
+ return -1;
+ }
+ printf("\n");
+ continue;
+ }
+ }
+
+ if (ch == 'q' || ch == 0x1b || ch == 0x03) { /* q or Esc or ^C */
+ empty_con_chars();
+ printf("\nAbort ? - Are you sure ? [y/n]:"); fflush(stdout);
+ if ((ch = next_con_char()) == 'y' || ch == 'Y') {
+ printf("\n");
+ it->del(it);
+ return -1;
+ }
+ printf("\n");
+ continue;
+ } else if (ch == 'f') {
+ incflag = 1;
+ continue;
+ } else if (ch == 'F') {
+ incflag = 2;
+ continue;
+ } else if (ch == 'b') {
+ incflag = -1;
+ continue;
+ } else if (ch == 'B') {
+ incflag = -2;
+ continue;
+ } else if (ch == 'n' || ch == 'N') {
+ incflag = 3;
+ continue;
+ } else if (ch == 'g' || ch == 'G') {
+ incflag = 4;
+ continue;
+ } else if (ch == 'd' || ch == 'D') {
+ int i;
+ for (i = 0; i < npat; i++) {
+ if (scols[i]->rr == 0 && strcmp(scols[i]->id, "0") != 0)
+ break;
+ }
+ if (i >= npat)
+ break; /* None unread, so done */
+
+ /* Not all patches have been read */
+ empty_con_chars();
+ printf("\nDone ? - At least one unread patch (%s), Are you sure [y/n]: ",
+ scols[i]->loc);
+ fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b) {
+ printf("\n");
+ it->del(it);
+ return -1;
+ }
+ printf("\n");
+ if (ch == 'y' || ch == 'Y')
+ break;
+ continue;
+
+ /* Read the external sample */
+ } else if (xtern != 0 && (isdigit(*bp) || ch == '-' || ch == '+' || ch == '.')) {
+ int i;
+
+ /* For each input number */
+ for (i = 0; *bp != '\000' && i < 3; i++) {
+ char *tp, *nbp;
+
+ /* Find the start of the number */
+ while(*bp != '\000' && !isdigit(*bp)
+ && *bp != '-' && *bp != '+' && *bp != '.')
+ bp++;
+ if (!isdigit(*bp) && *bp != '-' && *bp != '+' && *bp != '.')
+ break;
+
+ /* Find the end of the number */
+ for (tp = bp+1; isdigit(*tp) || *tp == 'e' || *tp == 'E'
+ || *tp == '-' || *tp == '+' || *tp == '.'; tp++)
+ ;
+ if (*tp != '\000')
+ nbp = tp+1;
+ else
+ nbp = tp;
+ *tp = '\000';
+
+ /* Read the number */
+ scols[pix]->XYZ[i] = atof(bp);
+
+ bp = nbp;
+ }
+ if (i < 3) { /* Didn't find 3 numbers */
+ printf("Error - unrecognised input\n");
+ continue;
+ }
+ if (xtern == 1) {
+ icmLab2XYZ(&icmD50, scols[pix]->XYZ,scols[pix]->XYZ);
+ scols[pix]->XYZ[0] *= 100.0;
+ scols[pix]->XYZ[1] *= 100.0;
+ scols[pix]->XYZ[2] *= 100.0;
+ }
+
+ scols[pix]->rr = 1; /* Has been read */
+ printf(" Got XYZ value %f %f %f\n",scols[pix]->XYZ[0], scols[pix]->XYZ[1], scols[pix]->XYZ[2]);
+
+ /* Advance to next patch. */
+ incflag = 1;
+
+ /* We've read the spot sample */
+ } else if (xtern == 0 && (ch == '0' || ch == ' ' || ch == '\r')) {
+
+ /* Save the reading */
+ if (val.XYZ_v == 0)
+ error("Instrument didn't return XYZ value");
+
+ for (j = 0; j < 3; j++)
+ scols[pix]->XYZ[j] = val.XYZ[j];
+
+ /* Copy spectral */
+ if (val.sp.spec_n > 0) {
+ scols[pix]->sp = val.sp;
+ }
+ scols[pix]->rr = 1; /* Has been read */
+ printf(" Patch read OK\n");
+ /* Advance to next patch. */
+ incflag = 1;
+ } else { /* Unrecognised response */
+ continue;
+ }
+ }
+ }
+ /* clean up */
+ if (it != NULL)
+ it->del(it);
+ return 0;
+}
+
+void
+usage() {
+ icompaths *icmps;
+ inst2_capability cap2 = 0;
+ fprintf(stderr,"Read Target Test Chart, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ fprintf(stderr,"usage: chartread [-options] outfile\n");
+ fprintf(stderr," -v Verbose mode\n");
+ fprintf(stderr," -c listno Set communication port from the following list (default %d)\n",COMPORT);
+ if ((icmps = new_icompaths(NULL)) != NULL) {
+ icompath **paths;
+ if ((paths = icmps->paths) != NULL) {
+ int i;
+ for (i = 0; ; i++) {
+ if (paths[i] == NULL)
+ break;
+ fprintf(stderr," %d = '%s'\n",i+1,paths[i]->name);
+ }
+ } else
+ fprintf(stderr," ** No ports found **\n");
+ }
+ fprintf(stderr," -t Use transmission measurement mode\n");
+ fprintf(stderr," -d Use display measurement mode (white Y relative results)\n");
+ cap2 = inst_show_disptype_options(stderr, " -y ", icmps, 0);
+ fprintf(stderr," -e Emissive for transparency on a light box\n");
+ fprintf(stderr," -p Measure patch by patch rather than strip\n");
+ fprintf(stderr," -x [lx] Take external values, either L*a*b* (-xl) or XYZ (-xx).\n");
+ fprintf(stderr," -n Don't save spectral information (default saves spectral)\n");
+ fprintf(stderr," -l Save CIE as D50 L*a*b* rather than XYZ\n");
+ fprintf(stderr," -L Save CIE as D50 L*a*b* as well as XYZ\n");
+ fprintf(stderr," -r Resume reading partly read chart\n");
+ fprintf(stderr," -I file.cal Override calibration info from .ti2 in resulting .ti3\n");
+ fprintf(stderr," -F filter Set filter configuration (if aplicable):\n");
+ fprintf(stderr," n None\n");
+ fprintf(stderr," p Polarising filter\n");
+ fprintf(stderr," 6 D65\n");
+ fprintf(stderr," u U.V. Cut\n");
+ fprintf(stderr," -N Disable initial calibration of instrument if possible\n");
+ fprintf(stderr," -B Disable auto bi-directional strip recognition\n");
+ fprintf(stderr," -H Use high resolution spectrum mode (if available)\n");
+ if (cap2 & inst2_ccmx)
+ fprintf(stderr," -X file.ccmx Apply Colorimeter Correction Matrix\n");
+ if (cap2 & inst2_ccss) {
+ int i;
+ fprintf(stderr," -X file.ccss Use Colorimeter Calibration Spectral Samples for calibration\n");
+ fprintf(stderr," -Q observ Choose CIE Observer for CCSS instrument:\n");
+ fprintf(stderr," 1931_2 (def), 1964_10, S&B 1955_2, shaw, J&V 1978_2\n");
+ }
+ fprintf(stderr," -T ratio Modify strip patch consistency tolerance by ratio\n");
+ fprintf(stderr," -S Suppress wrong strip & unexpected value warnings\n");
+// fprintf(stderr," -Y U Test i1pro2 UV measurement mode\n");
+ fprintf(stderr," -W n|h|x Override serial port flow control: n = none, h = HW, x = Xon/Xoff\n");
+ fprintf(stderr," -D [level] Print debug diagnostics to stderr\n");
+ fprintf(stderr," outfile Base name for input[ti2]/output[ti3] file\n");
+ if (icmps != NULL)
+ icmps->del(icmps);
+ exit(1);
+ }
+
+int main(int argc, char *argv[]) {
+ int i, j;
+ int fa, nfa, mfa; /* current argument we're looking at */
+ int verb = 0;
+ int debug = 0;
+ int comport = COMPORT; /* COM port used */
+ icompaths *icmps = NULL;
+ icompath *ipath = NULL;
+ flow_control fc = fc_nc; /* Default flow control */
+ instType itype = instUnknown; /* Instrument chart is targeted to */
+ instType atype = instUnknown; /* Instrument used to read the chart */
+ int trans = 0; /* Use transmission mode */
+ int emis = 0; /* Use emissive mode */
+ int displ = 0; /* 1 = Use display emissive mode, 2 = display bright rel. */
+ /* 3 = display white rel. */
+ int dtype = 0; /* Display type selection charater */
+ inst_opt_filter fe = inst_opt_filter_unknown;
+ int pbypatch = 0; /* Read patch by patch */
+ int disbidi = 0; /* Disable bi-directional strip recognition */
+ int highres = 0; /* Use high res mode if available */
+ double scan_tol = 1.0; /* Patch consistency tolerance modification */
+ int xtern = 0; /* Take external values, 1 = Lab, 2 = XYZ */
+ int spectral = 1; /* Save spectral information */
+ int uvmode = 0; /* ~~~ i1pro2 test mode ~~~ */
+ int accurate_expd = 0; /* Expected value assumed to be accurate */
+ int emit_warnings = 1; /* Emit warnings for wrong strip, unexpected value */
+ int dolab = 0; /* 1 = Save CIE as Lab, 2 = Save CIE as XYZ and Lab */
+ int doresume = 0; /* Resume reading a chart */
+ int nocal = 0; /* Disable initial calibration */
+ char ccxxname[MAXNAMEL+1] = "\000"; /* Colorimeter Correction/Colorimeter Calibration name */
+ icxObserverType obType = icxOT_default; /* ccss observer */
+ static char inname[MAXNAMEL+1] = { 0 }; /* Input cgats file base name */
+ static char outname[MAXNAMEL+1] = { 0 }; /* Output cgats file base name */
+ cgats *icg; /* input cgats structure */
+ cgats *ocg; /* output cgats structure */
+ static char calname[MAXNAMEL+1] = { 0 }; /* User supplied calibration filename */
+ xcal *cal = NULL; /* Any calibration to be output as well */
+ int nmask = 0; /* Device colorant mask */
+ char *pixpat = "A-Z, A-Z"; /* Pass index pattern */
+ char *sixpat = "0-9,@-9,@-9;1-999"; /* Step index pattern */
+ alphix *paix, *saix; /* Pass and Step index generators */
+ int ixord = 0; /* Index order, 0 = pass then step */
+ int rstart = 0; /* Random start/chart id */
+ int hex = 0; /* Hexagon pattern layout */
+ time_t clk = time(0);
+ struct tm *tsp = localtime(&clk);
+ char *atm = asctime(tsp); /* Ascii time */
+ chcol *cols; /* Internal storage of all the patch colors */
+ chcol **scols; /* Location sorted pointers to cols */
+ int nchan = 0; /* Number of device chanels */
+ int npat; /* Number of overall patches */
+ int *pis; /* Passes in eachstrip, zero terminated */
+ int stipa; /* Steps in each Pass */
+ int totpa; /* Total Passes Needed */
+ int runpat; /* Rounded Up to (totpa * stipa) Number of patches */
+ int wpat; /* Set to index of white patch for display */
+ int si; /* Sample id index */
+ int li; /* Location id index */
+ int ti; /* Temp index */
+ int fi; /* Colorspace index */
+ double plen = 7.366, glen = 2.032, tlen = 18.8; /* Patch, gap and trailer length in mm */
+
+ set_exe_path(argv[0]); /* Set global exe_path and error_program */
+ check_if_not_interactive();
+
+ setup_spyd2(); /* Load firware if available */
+
+ if (argc <= 1)
+ usage();
+
+ /* Process the arguments */
+ mfa = 1; /* Minimum final arguments */
+ for(fa = 1;fa < argc;fa++) {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1+mfa) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage();
+
+ /* Verbose */
+ else if (argv[fa][1] == 'v') {
+ verb = 1;
+ g_log->verb = verb;
+
+ /* No auto calibration */
+ } else if (argv[fa][1] == 'N')
+ nocal = 1;
+
+ /* Disable bi-directional strip recognition */
+ else if (argv[fa][1] == 'B')
+ disbidi = 1;
+
+ /* High res mode */
+ else if (argv[fa][1] == 'H')
+ highres = 1;
+
+ /* Colorimeter Correction Matrix or */
+ /* or Colorimeter Calibration Spectral Samples */
+ else if (argv[fa][1] == 'X') {
+ int ix;
+ fa = nfa;
+ if (na == NULL) usage();
+ strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
+
+ /* CCSS Spectral Observer type */
+ } else if (argv[fa][1] == 'Q') {
+ fa = nfa;
+ if (na == NULL) usage();
+ if (strcmp(na, "1931_2") == 0) { /* Classic 2 degree */
+ obType = icxOT_CIE_1931_2;
+ } else if (strcmp(na, "1964_10") == 0) { /* Classic 10 degree */
+ obType = icxOT_CIE_1964_10;
+ } else if (strcmp(na, "1955_2") == 0) { /* Stiles and Burch 1955 2 degree */
+ obType = icxOT_Stiles_Burch_2;
+ } else if (strcmp(na, "1978_2") == 0) { /* Judd and Voss 1978 2 degree */
+ obType = icxOT_Judd_Voss_2;
+ } else if (strcmp(na, "shaw") == 0) { /* Shaw and Fairchilds 1997 2 degree */
+ obType = icxOT_Shaw_Fairchild_2;
+ } else
+ usage();
+ }
+
+ /* Scan tolerance ratio */
+ else if (argv[fa][1] == 'T') {
+ if (na == NULL)
+ usage();
+ scan_tol = atof(na);
+
+ /* Suppress warnings */
+ } else if (argv[fa][1] == 'S') {
+ emit_warnings = 0;
+
+ /* Serial port flow control */
+ } else if (argv[fa][1] == 'W') {
+ fa = nfa;
+ if (na == NULL) usage();
+ if (na[0] == 'n' || na[0] == 'N')
+ fc = fc_none;
+ else if (na[0] == 'h' || na[0] == 'H')
+ fc = fc_Hardware;
+ else if (na[0] == 'x' || na[0] == 'X')
+ fc = fc_XonXOff;
+ else
+ usage();
+
+
+ /* Debug coms */
+ } else if (argv[fa][1] == 'D') {
+ debug = 1;
+ if (na != NULL && na[0] >= '0' && na[0] <= '9') {
+ debug = atoi(na);
+ fa = nfa;
+ }
+ g_log->debug = debug;
+
+ /* COM port */
+ } else if (argv[fa][1] == 'c') {
+ fa = nfa;
+ if (na == NULL) usage();
+ comport = atoi(na);
+ if (comport < 1 || comport > 99) usage();
+
+ /* Request transmission measurement */
+ } else if (argv[fa][1] == 't') {
+ emis = 0;
+ trans = 1;
+ displ = 0;
+
+ /* Request display measurement */
+ } else if (argv[fa][1] == 'd') {
+
+ emis = 0;
+ trans = 0;
+ displ = 2;
+
+ /* Request emissive measurement */
+ } else if (argv[fa][1] == 'e') {
+ emis = 1;
+ trans = 0;
+ displ = 0;
+
+ /* Display type */
+ } else if (argv[fa][1] == 'y') {
+ fa = nfa;
+ if (na == NULL) usage();
+ dtype = na[0];
+
+ /* Request patch by patch measurement */
+ } else if (argv[fa][1] == 'p') {
+ pbypatch = 1;
+
+ /* Request external values */
+ } else if (argv[fa][1] == 'x') {
+ fa = nfa;
+ if (na == NULL) usage();
+
+ if (na[0] == 'l' || na[0] == 'L')
+ xtern = 1;
+ else if (na[0] == 'x' || na[0] == 'X')
+ xtern = 2;
+ else
+ usage();
+
+ /* Turn off spectral measurement */
+ } else if (argv[fa][1] == 'n')
+ spectral = 0;
+
+ /* Save as Lab */
+ else if (argv[fa][1] == 'l')
+ dolab = 1;
+
+ /* Save as Lab */
+ else if (argv[fa][1] == 'L')
+ dolab = 2;
+
+ /* Resume reading a chart */
+ else if (argv[fa][1] == 'r')
+ doresume = 1;
+
+ /* Printer calibration info */
+ else if (argv[fa][1] == 'I') {
+ fa = nfa;
+ if (na == NULL) usage();
+ strncpy(calname,na,MAXNAMEL); calname[MAXNAMEL] = '\000';
+
+ /* Filter configuration */
+ } else if (argv[fa][1] == 'F') {
+ fa = nfa;
+ if (na == NULL) usage();
+ if (na[0] == 'n' || na[0] == 'N')
+ fe = inst_opt_filter_none;
+ else if (na[0] == 'p' || na[0] == 'P')
+ fe = inst_opt_filter_pol;
+ else if (na[0] == '6')
+ fe = inst_opt_filter_D65;
+ else if (na[0] == 'u' || na[0] == 'U')
+ fe = inst_opt_filter_UVCut;
+ else
+ usage();
+
+ /* Extra flags */
+ } else if (argv[fa][1] == 'Y') {
+ if (na == NULL)
+ usage();
+
+ /* ~~~ i1pro2 test code ~~~ */
+ if (na[0] == 'U') {
+ uvmode = 1;
+ } else {
+ usage();
+ }
+
+ } else
+ usage();
+ } else
+ break;
+ }
+
+ /* Get the file name argument */
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strcpy(inname,argv[fa]);
+ strcat(inname,".ti2");
+ strcpy(outname,argv[fa]);
+ strcat(outname,".ti3");
+
+ /* See if there is an environment variable ccxx */
+ if (ccxxname[0] == '\000') {
+ char *na;
+ if ((na = getenv("ARGYLL_COLMTER_CAL_SPEC_SET")) != NULL) {
+ strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
+
+ } else if ((na = getenv("ARGYLL_COLMTER_COR_MATRIX")) != NULL) {
+ strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
+ }
+ }
+
+ icg = new_cgats(); /* Create a CGATS structure */
+ icg->add_other(icg, "CTI2"); /* our special input type is Calibration Target Information 2 */
+ icg->add_other(icg, "CAL"); /* There may be a calibration too */
+
+ if (icg->read_name(icg, inname))
+ error("CGATS file read error : %s",icg->err);
+
+ if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0)
+ error ("Input file isn't a CTI2 format file");
+ if (icg->ntables < 1)
+ error ("Input file doesn't contain at least one table");
+
+ if ((npat = icg->t[0].nsets) <= 0)
+ error ("No sets of data in first table");
+
+ /* Setup output cgats file */
+ ocg = new_cgats(); /* Create a CGATS structure */
+ ocg->add_other(ocg, "CTI3"); /* our special type is Calibration Target Information 3 */
+ ocg->add_table(ocg, tt_other, 0); /* Start the first table */
+
+ ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 3",NULL);
+ ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll chartread", NULL);
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+ ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
+ if (displ != 0)
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","DISPLAY", NULL); /* What sort of device this is */
+ else
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL); /* What sort of device this is */
+
+ if (itype == instUnknown) {
+ if ((ti = icg->find_kword(icg, 0, "TARGET_INSTRUMENT")) >= 0) {
+
+ if ((itype = inst_enum(icg->t[0].kdata[ti])) == instUnknown)
+ error ("Unrecognised chart target instrument '%s'", icg->t[0].kdata[ti]);
+ } else {
+ itype = instDTP41; /* Default chart target instrument */
+ }
+ }
+
+ if ((ti = icg->find_kword(icg, 0, "SINGLE_DIM_STEPS")) >= 0)
+ ocg->add_kword(ocg, 0, "SINGLE_DIM_STEPS",icg->t[0].kdata[ti], NULL);
+
+ if ((ti = icg->find_kword(icg, 0, "COMP_GREY_STEPS")) >= 0)
+ ocg->add_kword(ocg, 0, "COMP_GREY_STEPS",icg->t[0].kdata[ti], NULL);
+
+ if ((ti = icg->find_kword(icg, 0, "MULTI_DIM_STEPS")) >= 0)
+ ocg->add_kword(ocg, 0, "MULTI_DIM_STEPS",icg->t[0].kdata[ti], NULL);
+
+ if ((ti = icg->find_kword(icg, 0, "FULL_SPREAD_PATCHES")) >= 0)
+ ocg->add_kword(ocg, 0, "FULL_SPREAD_PATCHES",icg->t[0].kdata[ti], NULL);
+
+ if ((ti = icg->find_kword(icg, 0, "ACCURATE_EXPECTED_VALUES")) >= 0
+ && strcmp(icg->t[0].kdata[ti], "true") == 0)
+ accurate_expd = 1;
+
+ if ((ti = icg->find_kword(icg, 0, "STEPS_IN_PASS")) < 0)
+ error ("Input file doesn't contain keyword STEPS_IN_PASS");
+ stipa = atoi(icg->t[0].kdata[ti]);
+
+ /* Old style */
+ if ((ti = icg->find_kword(icg, 0, "PASSES_IN_STRIPS")) >= 0) {
+ char *paists = icg->t[0].kdata[ti];
+ int nstr;
+
+ /* Count the number of strips (sheets) */
+ for (nstr = 0; paists[nstr] != '\000'; nstr++)
+ ;
+
+ /* Allocate space for passes in strips */
+ if ((pis = (int *)calloc(sizeof(int), nstr+1)) == NULL)
+ error("Malloc failed!");
+
+ /* Set the number or passes per strip */
+ for (i = 0; i < nstr; i++) {
+ pis[i] = b62_int(&paists[i]);
+ }
+ pis[i] = 0;
+
+ /* New style */
+ } else if ((ti = icg->find_kword(icg, 0, "PASSES_IN_STRIPS2")) >= 0) {
+ char *cp, *paists = icg->t[0].kdata[ti];
+ int nstr;
+
+
+ /* Count the number of strips (sheets) */
+ for (nstr = 1, cp = paists; *cp != '\000'; cp++) {
+ if (*cp == ',') {
+ nstr++;
+ *cp = '\000';
+ }
+ }
+
+
+ /* Allocate space for passes in strips */
+ if ((pis = (int *)calloc(sizeof(int), nstr+1)) == NULL)
+ error("Malloc failed!");
+
+ /* Set the number or passes per strip */
+ for (i = 0, cp = paists; i < nstr; i++) {
+ pis[i] = atoi(cp);
+ cp += strlen(cp) + 1;
+ }
+ pis[i] = 0;
+
+ } else
+ error ("Input file doesn't contain keyword PASSES_IN_STRIPS");
+
+ /* Get specified location indexing patterns */
+ if ((ti = icg->find_kword(icg, 0, "STRIP_INDEX_PATTERN")) >= 0)
+ pixpat = icg->t[0].kdata[ti];
+ if ((ti = icg->find_kword(icg, 0, "PATCH_INDEX_PATTERN")) >= 0)
+ sixpat = icg->t[0].kdata[ti];
+ if ((ti = icg->find_kword(icg, 0, "INDEX_ORDER")) >= 0) {
+ if (strcmp(icg->t[0].kdata[ti], "PATCH_THEN_STRIP") == 0)
+ ixord = 1;
+ }
+
+ if ((ti = icg->find_kword(icg, 0, "RANDOM_START")) >= 0)
+ rstart = atoi(icg->t[0].kdata[ti]);
+ else if ((ti = icg->find_kword(icg, 0, "CHART_ID")) >= 0)
+ rstart = atoi(icg->t[0].kdata[ti]);
+
+ if ((ti = icg->find_kword(icg, 0, "HEXAGON_PATCHES")) >= 0)
+ hex = 1;
+
+ if ((paix = new_alphix(pixpat)) == NULL)
+ error("Strip indexing pattern '%s' doesn't parse",pixpat);
+
+ if ((saix = new_alphix(sixpat)) == NULL)
+ error("Patch in strip indexing pattern '%s' doesn't parse",sixpat);
+
+ if ((ti = icg->find_kword(icg, 0, "TOTAL_INK_LIMIT")) >= 0)
+ ocg->add_kword(ocg, 0, "TOTAL_INK_LIMIT",icg->t[0].kdata[ti], NULL);
+
+ if (itype == instDTP20
+ || itype == instDTP41) { /* DTP20/41 specific info */
+ if ((ti = icg->find_kword(icg, 0, "PATCH_LENGTH")) < 0)
+ error ("Input file doesn't contain keyword PATCH_LENGTH");
+ plen = atof(icg->t[0].kdata[ti]);
+ if ((ti = icg->find_kword(icg, 0, "GAP_LENGTH")) < 0)
+ error ("Input file doesn't contain keyword GAP_LENGTH");
+ glen = atof(icg->t[0].kdata[ti]);
+ if ((ti = icg->find_kword(icg, 0, "TRAILER_LENGTH")) < 0) {
+ tlen = 18.0; /* Default for backwards compatibility */
+ } else
+ tlen = atof(icg->t[0].kdata[ti]);
+ }
+
+ if (verb) {
+ printf("Steps in each Pass = %d\n",stipa);
+ printf("Passes in each Strip = ");
+ for (i = 0; pis[i] != 0; i++) {
+ printf("%s%d",i > 0 ? ", " : "", pis[i]);
+ }
+ printf("\n");
+ }
+
+ /* Fields we want */
+
+ if ((si = icg->find_field(icg, 0, "SAMPLE_ID")) < 0)
+ error ("Input file doesn't contain field SAMPLE_ID");
+ if (icg->t[0].ftype[si] != nqcs_t)
+ error ("Field SAMPLE_ID is wrong type");
+ ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t);
+
+ if ((li = icg->find_field(icg, 0, "SAMPLE_LOC")) < 0)
+ error ("Input file doesn't contain field SAMPLE_LOC");
+ if (icg->t[0].ftype[li] != cs_t)
+ error ("Field SAMPLE_LOC is wrong type");
+ ocg->add_field(ocg, 0, "SAMPLE_LOC", cs_t);
+
+ totpa = (npat + stipa -1)/stipa; /* Total passes for all strips */
+ runpat = stipa * totpa; /* Rounded up totao number of patches */
+ if ((cols = (chcol *)malloc(sizeof(chcol) * runpat)) == NULL)
+ error("Malloc failed!");
+ if ((scols = (chcol **)calloc(sizeof(chcol *), runpat)) == NULL)
+ error("Malloc failed!");
+
+ /* Figure out the color space */
+ if ((fi = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
+ error ("Input file doesn't contain keyword COLOR_REP");
+
+ if ((nmask = icx_char2inkmask(icg->t[0].kdata[fi])) != 0) {
+ int i, j, ii;
+ int chix[ICX_MXINKS]; /* Device chanel indexes */
+ int xyzix[3]; /* XYZ/Lab chanel indexes */
+ char *ident; /* Full ident */
+ char *bident; /* Base ident */
+ char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" };
+ char *labfname[3] = { "LAB_L", "LAB_A", "LAB_B" };
+ int gotexyz = 1; /* Flag set if input file already has approx XYZ */
+ icxColorantLu *clu; /* Xcolorants model based Device -> CIE */
+
+ nchan = icx_noofinks(nmask);
+ ident = icx_inkmask2char(nmask, 1);
+ bident = icx_inkmask2char(nmask, 0);
+
+ /* Device channels */
+ for (j = 0; j < nchan; j++) {
+ int imask;
+ char fname[100];
+
+ imask = icx_index2ink(nmask, j);
+ sprintf(fname,"%s_%s",nmask == ICX_W || nmask == ICX_K ? "GRAY" : bident,
+ icx_ink2char(imask));
+
+ if ((ii = icg->find_field(icg, 0, fname)) < 0)
+ error ("Input file doesn't contain field %s",fname);
+ if (icg->t[0].ftype[ii] != r_t)
+ error ("Field %s is wrong type",fname);
+
+ ocg->add_field(ocg, 0, fname, r_t);
+ chix[j] = ii;
+ }
+
+ /* Approximate XYZ */
+ for (j = 0; j < 3; j++) {
+ if ((ii = icg->find_field(icg, 0, xyzfname[j])) >= 0) {
+ if (icg->t[0].ftype[ii] != r_t)
+ error ("Field %s is wrong type",xyzfname[j]);
+ xyzix[j] = ii;
+ } else {
+ gotexyz = 0;
+ }
+ }
+
+ /* Measured XYZ and/or Lab */
+ if (dolab == 0 || dolab == 2) {
+ for (j = 0; j < 3; j++)
+ ocg->add_field(ocg, 0, xyzfname[j], r_t);
+ }
+ if (dolab == 1 || dolab == 2) {
+ for (j = 0; j < 3; j++)
+ ocg->add_field(ocg, 0, labfname[j], r_t);
+ }
+
+ if ((clu = new_icxColorantLu(nmask)) == NULL)
+ error ("Creation of xcolorant lu object failed");
+
+ {
+ char fname[100];
+ if (dolab)
+ sprintf(fname, "%s_LAB", ident);
+ else
+ sprintf(fname, "%s_XYZ", ident);
+ ocg->add_kword(ocg, 0, "COLOR_REP", fname, NULL);
+ }
+
+ /* Read all the test patches in */
+ for (i = 0; i < npat; i++) {
+ cols[i].id = ((char *)icg->t[0].fdata[i][si]);
+ cols[i].loc = ((char *)icg->t[0].fdata[i][li]);
+ cols[i].n = nchan;
+ for (j = 0; j < nchan; j++)
+ cols[i].dev[j] = *((double *)icg->t[0].fdata[i][chix[j]]) / 100.0;
+ if (gotexyz) {
+ for (j = 0; j < 3; j++)
+ cols[i].eXYZ[j] = *((double *)icg->t[0].fdata[i][xyzix[j]]);
+ } else {
+ clu->dev_to_XYZ(clu, cols[i].eXYZ, cols[i].dev);
+ for (j = 0; j < 3; j++)
+ cols[i].eXYZ[j] *= 100.0;
+ }
+ cols[i].XYZ[0] = cols[i].XYZ[1] = cols[i].XYZ[2] = -1.0;
+ }
+
+ for (; i < runpat; i++) {
+ cols[i].id = cols[i].loc = "-1";
+ for (j = 0; j < nchan; j++)
+ cols[i].dev[j] = 0.0;
+ clu->dev_to_XYZ(clu, cols[i].eXYZ, cols[i].dev);
+ for (j = 0; j < 3; j++)
+ cols[i].eXYZ[j] *= 100.0;
+ cols[i].XYZ[0] = cols[i].XYZ[1] = cols[i].XYZ[2] = -1.0;
+ }
+
+ clu->del(clu);
+ free(ident);
+ free(bident);
+ } else
+ error ("Input file keyword COLOR_REP has unknown value");
+
+ /* Read any user supplied calibration information */
+ if (calname[0] != '\000') {
+ if ((cal = new_xcal()) == NULL)
+ error("new_xcal failed");
+ if ((cal->read(cal, calname)) != 0)
+ error("%s",cal->err);
+ }
+
+ /* If the user hasn't overridden it, get any calibration in the .ti2 */
+ if (cal == NULL) { /* No user supplied calibration info */
+ int oi, tab;
+
+ oi = icg->get_oi(icg, "CAL");
+
+ for (tab = 0; tab < icg->ntables; tab++) {
+ if (icg->t[tab].tt == tt_other && icg->t[tab].oi == oi) {
+ break;
+ }
+ }
+ if (tab < icg->ntables) {
+ if ((cal = new_xcal()) == NULL) {
+ error("new_xcal failed");
+ }
+ if (cal->read_cgats(cal, icg, tab, inname) != 0) {
+ error("%s",cal->err);
+ }
+ }
+ }
+
+ /* If there is calibration information, write it to the .ti3 */
+ if (cal != NULL) { /* No user supplied calibration info */
+ if (cal->write_cgats(cal, ocg) != 0) {
+ error("%s",cal->err);
+ }
+ cal->del(cal);
+ cal = NULL;
+ }
+
+ /* Set up the location sorted array of pointers */
+ for (i = 0; i < npat; i++) {
+ scols[i] = &cols[i];
+ if ((cols[i].loci = patch_location_order(paix, saix, ixord, cols[i].loc)) < 0)
+ error ("Bad location field value '%s' on patch %d", cols[i].loc, i);
+ }
+ for (; i < runpat; i++) { /* Extra on end */
+ scols[i] = &cols[i];
+ cols[i].loci = (totpa-1) * (256 - stipa) + i;
+/* printf("~~extra = %d, %d\n",cols[i].loci >> 8, cols[i].loci & 255); */
+ }
+
+ /* Reset 'read' flag and all data */
+ for (i = 0; i < runpat; i++) {
+ cols[i].rr = 0;
+ cols[i].XYZ[0] = -1.0;
+ cols[i].XYZ[1] = -1.0;
+ cols[i].XYZ[2] = -1.0;
+ cols[i].sp.spec_n = 0;
+ }
+
+#define HEAP_COMPARE(A,B) (A->loci < B->loci)
+ HEAPSORT(chcol *, scols, npat);
+
+ /* If we're resuming a chartread, fill in all the patches that */
+ /* have been read. */
+ if (doresume) {
+ cgats *rcg; /* output cgats structure */
+ int nrpat; /* Number of resumed patches */
+ int lix; /* Patch location index */
+ int islab = 0; /* nz if Lab, z if XYZ */
+ int cieix[3]; /* CIE value indexes */
+ int hasspec = 0; /* nz if has spectral */
+ xspect sp; /* Parameters of spectrum */
+ int spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */
+ char *fname[2][3] = { { "XYZ_X", "XYZ_Y", "XYZ_Z" },
+ { "LAB_L", "LAB_A", "LAB_B" } };
+ int k, ii;
+ char buf[200];
+
+ /* Open and look at the .ti3 profile patches file */
+ rcg = new_cgats(); /* Create a CGATS structure */
+ rcg->add_other(rcg, "CTI3"); /* our special input type is Calibration Target Information 3 */
+ rcg->add_other(rcg, "CAL"); /* our special device Calibration state */
+
+ if (rcg->read_name(rcg, outname))
+ error("Unable to read chart being resumed '%s' : %s",outname, rcg->err);
+
+ if (rcg->ntables == 0 || rcg->t[0].tt != tt_other || rcg->t[0].oi != 0)
+ error ("Resumed file '%s' isn't a CTI3 format file",outname);
+ if (rcg->ntables < 1)
+ error ("Resumed file '%s' doesn't contain at least one table",outname);
+
+ if ((lix = rcg->find_field(rcg, 0, "SAMPLE_LOC")) < 0)
+ error ("Resumed file '%s' doesn't contain SAMPLE_LOC field",outname);
+ if (rcg->t[0].ftype[lix] != cs_t)
+ error("Field SAMPLE_LOC is wrong type - corrupted file ?");
+
+ /* Get the CIE field indexes */
+ if (rcg->find_field(rcg, 0, "LAB_L") >= 0)
+ islab = 1;
+
+ for (j = 0; j < 3; j++) {
+ if ((cieix[j] = rcg->find_field(rcg, 0, fname[islab][j])) < 0)
+ error("Input file doesn't contain field %s",fname[islab][j]);
+ if (rcg->t[0].ftype[cieix[j]] != r_t)
+ error("Field %s is wrong type - corrupted file ?",fname[islab][j]);
+ }
+
+ if ((ii = rcg->find_kword(rcg, 0, "SPECTRAL_BANDS")) >= 0) {
+ hasspec = 1;
+ sp.spec_n = atoi(rcg->t[0].kdata[ii]);
+ if ((ii = rcg->find_kword(rcg, 0, "SPECTRAL_START_NM")) < 0)
+ error ("Resumed file '%s' doesn't contain keyword SPECTRAL_START_NM",outname);
+ sp.spec_wl_short = atof(rcg->t[0].kdata[ii]);
+ if ((ii = rcg->find_kword(rcg, 0, "SPECTRAL_END_NM")) < 0)
+ error ("Resumed file '%s' doesn't contain keyword SPECTRAL_END_NM",outname);
+ sp.spec_wl_long = atof(rcg->t[0].kdata[ii]);
+
+ /* Find the fields for spectral values */
+ for (j = 0; j < sp.spec_n; j++) {
+ int nm;
+
+ /* Compute nearest integer wavelength */
+ nm = (int)(sp.spec_wl_short + ((double)j/(sp.spec_n-1.0))
+ * (sp.spec_wl_long - sp.spec_wl_short) + 0.5);
+
+ sprintf(buf,"SPEC_%03d",nm);
+
+ if ((spi[j] = rcg->find_field(rcg, 0, buf)) < 0)
+ error("Resumed file '%s' doesn't contain field %s",outname,buf);
+ }
+ }
+
+ nrpat = rcg->t[0].nsets;
+
+ /* Now see if we can match the previously read patches. */
+ /* We'll use the patch location to do this. */
+ for (i = 0; i < runpat; i++) {
+ int k;
+ for (k = 0; k < nrpat; k++) {
+ if (strcmp(cols[i].loc, ((char *)rcg->t[0].fdata[k][lix])) == 0)
+ break;
+ }
+ if (k >= nrpat)
+ continue;
+
+#ifdef DEBUG
+ printf("Recovering patch '%s' value from .ti3 file\n",cols[i].loc);
+#endif
+ cols[i].XYZ[0] = *((double *)rcg->t[0].fdata[k][cieix[0]]);
+ cols[i].XYZ[1] = *((double *)rcg->t[0].fdata[k][cieix[1]]);
+ cols[i].XYZ[2] = *((double *)rcg->t[0].fdata[k][cieix[2]]);
+ if (islab) {
+ icmLab2XYZ(&icmD50, cols[i].XYZ, cols[i].XYZ);
+ cols[i].XYZ[0] *= 100.0;
+ cols[i].XYZ[1] *= 100.0;
+ cols[i].XYZ[2] *= 100.0;
+ }
+ if (hasspec) {
+ cols[i].sp.spec_n = sp.spec_n;
+ cols[i].sp.spec_wl_short = sp.spec_wl_short;
+ cols[i].sp.spec_wl_long = sp.spec_wl_long;
+ for (j = 0; j < sp.spec_n; j++)
+ cols[i].sp.spec[j] = *((double *)rcg->t[0].fdata[k][spi[j]]);
+ }
+ cols[i].rr = 1;
+ }
+ rcg->del(rcg);
+ }
+
+ /* We can't fiddle white point with spectral data, */
+ /* so turn spectral off for display with white point relative. */
+ if (displ == 2 || displ == 3) {
+ spectral = 0;
+
+ /* Check that there is a white patch, so that we can compute Y relative */
+ /* Read all the test patches in */
+ if (nmask != ICX_RGB)
+ error("Don't know how to handle non-RGB display space");
+
+ for (wpat = 0; wpat < npat; wpat++) {
+ if (cols[wpat].dev[0] > 0.9999999 &&
+ cols[wpat].dev[1] > 0.9999999 &&
+ cols[wpat].dev[2] > 0.9999999) {
+ break;
+ }
+ }
+ if (wpat >= npat) { /* Create a white patch */
+ error("Can't compute white Y relative display values without a white test patch");
+ }
+ }
+
+ if ((icmps = new_icompaths(g_log)) == NULL)
+ error("Finding instrument paths failed");
+ if ((ipath = icmps->get_path(icmps, comport)) == NULL)
+ error("No instrument at port %d",comport);
+
+ /* Read all of the strips in */
+ if (read_strips(itype, scols, &atype, npat, totpa, stipa, pis, paix,
+ saix, ixord, rstart, hex, ipath, fc, plen, glen, tlen,
+ trans, emis, displ, dtype, fe, nocal, disbidi, highres, ccxxname, obType,
+ scan_tol, pbypatch, xtern, spectral, uvmode, accurate_expd,
+ emit_warnings, g_log) == 0) {
+ /* And save the result */
+
+ int nrpat; /* Number of read patches */
+ int vpix = 0; /* Valid patch index, if nrpatch > 0 */
+ int nsetel = 0;
+ cgats_set_elem *setel; /* Array of set value elements */
+
+ /* Note what instrument the chart was read with */
+ ocg->add_kword(ocg, 0, "TARGET_INSTRUMENT", inst_name(atype) , NULL);
+
+ /* Count patches actually read */
+ for (nrpat = i = 0; i < npat; i++) {
+ if (cols[i].rr) {
+ vpix = i;
+ nrpat++;
+ }
+ }
+
+ /* If we've used a display white relative mode, record the absolute white */
+ if (displ == 2 || displ == 3) {
+ double nn[3];
+ char buf[100];
+
+ if (cols[wpat].rr == 0) {
+ error("Can't compute white Y relative display values without reading a white test patch");
+ }
+ sprintf(buf,"%f %f %f", cols[wpat].XYZ[0], cols[wpat].XYZ[1], cols[wpat].XYZ[2]);
+ ocg->add_kword(ocg, 0, "LUMINANCE_XYZ_CDM2",buf, NULL);
+
+ /* Normalise to white Y 100 */
+ if (displ == 2) {
+ nn[0] = 100.0 / cols[wpat].XYZ[1];
+ nn[1] = 100.0 / cols[wpat].XYZ[1];
+ nn[2] = 100.0 / cols[wpat].XYZ[1];
+ /* Normalise to the white point */
+ } else {
+ nn[0] = 100.0 * icmD50.X / cols[wpat].XYZ[0];
+ nn[1] = 100.0 * icmD50.Y / cols[wpat].XYZ[1];
+ nn[2] = 100.0 * icmD50.Z / cols[wpat].XYZ[2];
+ }
+
+ for (i = 0; i < npat; i++) {
+ if (cols[i].rr) {
+ cols[i].XYZ[0] *= nn[0];
+ cols[i].XYZ[1] *= nn[1];
+ cols[i].XYZ[2] *= nn[2];
+ }
+ }
+ }
+
+ nsetel += 1; /* For id */
+ nsetel += 1; /* For loc */
+ nsetel += nchan; /* For device values */
+ nsetel += 3; /* For XYZ or Lab */
+ if (dolab == 2)
+ nsetel += 3; /* For XYZ and Lab */
+
+ /* If we have spectral information, output it too */
+ if (nrpat > 0 && cols[vpix].sp.spec_n > 0) {
+ char buf[100];
+
+ nsetel += cols[vpix].sp.spec_n; /* Spectral values */
+ sprintf(buf,"%d", cols[vpix].sp.spec_n);
+ ocg->add_kword(ocg, 0, "SPECTRAL_BANDS",buf, NULL);
+ sprintf(buf,"%f", cols[vpix].sp.spec_wl_short);
+ ocg->add_kword(ocg, 0, "SPECTRAL_START_NM",buf, NULL);
+ sprintf(buf,"%f", cols[vpix].sp.spec_wl_long);
+ ocg->add_kword(ocg, 0, "SPECTRAL_END_NM",buf, NULL);
+
+ /* Generate fields for spectral values */
+ for (i = 0; i < cols[vpix].sp.spec_n; i++) {
+ int nm;
+
+ /* Compute nearest integer wavelength */
+ nm = (int)(cols[vpix].sp.spec_wl_short + ((double)i/(cols[vpix].sp.spec_n-1.0))
+ * (cols[vpix].sp.spec_wl_long - cols[vpix].sp.spec_wl_short) + 0.5);
+
+ sprintf(buf,"SPEC_%03d",nm);
+ ocg->add_field(ocg, 0, buf, r_t);
+ }
+ }
+
+ if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL)
+ error("Malloc failed!");
+
+ /* Write out the patch info to the output CGATS file */
+ for (i = 0; i < npat; i++) {
+ int k = 0;
+
+ if (cols[i].rr == 0 /* If this patch wasn't read */
+ || strcmp(cols[i].id, "0") == 0) /* or it is a padding patch. */
+ continue; /* Skip it */
+
+ setel[k++].c = cols[i].id;
+ setel[k++].c = cols[i].loc;
+
+ for (j = 0; j < nchan; j++)
+ setel[k++].d = 100.0 * cols[i].dev[j];
+
+ if (dolab == 0 || dolab == 2) {
+ setel[k++].d = cols[i].XYZ[0];
+ setel[k++].d = cols[i].XYZ[1];
+ setel[k++].d = cols[i].XYZ[2];
+ }
+ if (dolab == 1 || dolab == 2) {
+ double lab[3];
+ double xyz[3];
+
+ xyz[0] = cols[i].XYZ[0]/100.0;
+ xyz[1] = cols[i].XYZ[1]/100.0;
+ xyz[2] = cols[i].XYZ[2]/100.0;
+ icmXYZ2Lab(&icmD50, lab, xyz);
+ setel[k++].d = lab[0];
+ setel[k++].d = lab[1];
+ setel[k++].d = lab[2];
+ }
+
+ /* Check that the spectral matches, in case we're resuming */
+ if ( cols[i].sp.spec_n != cols[vpix].sp.spec_n
+ || fabs(cols[i].sp.spec_wl_short - cols[vpix].sp.spec_wl_short) > 0.01
+ || fabs(cols[i].sp.spec_wl_long - cols[vpix].sp.spec_wl_long) > 0.01 ) {
+ error("The resumed spectral type seems to have changed!");
+ }
+
+ for (j = 0; j < cols[i].sp.spec_n; j++) {
+ setel[k++].d = cols[i].sp.spec[j];
+ }
+
+ ocg->add_setarr(ocg, 0, setel);
+ }
+
+ free(setel);
+
+ if (ocg->write_name(ocg, outname))
+ error("Write error : %s",ocg->err);
+ }
+
+ icmps->del(icmps);
+ free(pis);
+ saix->del(saix);
+ paix->del(paix);
+ free(cols);
+ ocg->del(ocg); /* Clean up */
+ icg->del(icg); /* Clean up */
+
+ return 0;
+}
+
+
diff --git a/spectro/colorhug.c b/spectro/colorhug.c
new file mode 100644
index 0000000..dad7012
--- /dev/null
+++ b/spectro/colorhug.c
@@ -0,0 +1,1093 @@
+
+
+/*
+ * Argyll Color Correction System
+ *
+ * Hughski ColorHug related functions
+ *
+ * Author: Richard Hughes
+ * Date: 30/11/2011
+ *
+ * Copyright 2006 - 2013, Graeme W. Gill
+ * Copyright 2011, Richard Hughes
+ * All rights reserved.
+ *
+ * (Based on huey.c)
+ *
+ * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
+ * see the License2.txt file for licencing details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#include <math.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* SALONEINSTLIB */
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* SALONEINSTLIB */
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "colorhug.h"
+
+static inst_code colorhug_interp_code(inst *pp, int ec);
+
+/* Interpret an icoms error into a ColorHug error */
+static int icoms2colorhug_err(int se) {
+ if (se != ICOM_OK)
+ return COLORHUG_COMS_FAIL;
+ return COLORHUG_OK;
+}
+
+/* ColorHug commands that we care about */
+typedef enum {
+ ch_set_mult = 0x04, /* Set multiplier value */
+ ch_set_integral = 0x06, /* Set integral time */
+ ch_get_firmware_version = 0x07, /* Get the Firmware version number */
+ ch_get_serial = 0x0b, /* Gets the serial number */
+ ch_set_leds = 0x0e, /* Sets the LEDs */
+ ch_take_reading = 0x22, /* Takes a raw reading minus dark offset */
+ ch_take_reading_xyz = 0x23, /* Takes an XYZ reading using the current matrix */
+ ch_get_post_scale = 0x2a /* Get the post scaling factor */
+} ColorHugCmd;
+
+/* Diagnostic - return a description given the instruction code */
+static char *inst_desc(int cc) {
+ static char buf[40];
+ switch(cc) {
+ case 0x04:
+ return "SetMultiplier";
+ case 0x06:
+ return "SetIntegral";
+ case 0x07:
+ return "GetFirmwareVersion";
+ case 0x0b:
+ return "GetSerial";
+ case 0x0e:
+ return "SetLeds";
+ case 0x22:
+ return "TakeReading";
+ case 0x23:
+ return "TakeReadingXYZ";
+ case 0x2a:
+ return "GetPostScale";
+ }
+ sprintf(buf,"Unknown %02x",cc);
+ return buf;
+}
+
+/* Error codes interpretation */
+static char *
+colorhug_interp_error(inst *pp, int ec) {
+ ec &= inst_imask;
+ switch (ec) {
+ case COLORHUG_INTERNAL_ERROR:
+ return "Internal software error";
+ case COLORHUG_COMS_FAIL:
+ return "Communications failure";
+ case COLORHUG_UNKNOWN_MODEL:
+ return "Not a known ColorHug Model";
+
+ case COLORHUG_OK:
+ return "OK";
+ case COLORHUG_UNKNOWN_CMD:
+ return "Unknown connamd";
+ case COLORHUG_WRONG_UNLOCK_CODE:
+ return "Wrong unlock code";
+ case COLORHUG_NOT_IMPLEMENTED:
+ return "Not implemented";
+ case COLORHUG_UNDERFLOW_SENSOR:
+ return "Sensor underflow";
+ case COLORHUG_NO_SERIAL:
+ return "No serial";
+ case COLORHUG_WATCHDOG:
+ return "Watchdog";
+ case COLORHUG_INVALID_ADDRESS:
+ return "Invalid address";
+ case COLORHUG_INVALID_LENGTH:
+ return "Invalid length";
+ case COLORHUG_INVALID_CHECKSUM:
+ return "Invlid checksum";
+ case COLORHUG_INVALID_VALUE:
+ return "Invalid value";
+ case COLORHUG_UNKNOWN_CMD_FOR_BOOTLOADER:
+ return "Unknown command for bootloader";
+ case COLORHUG_NO_CALIBRATION:
+ return "No calibration";
+ case COLORHUG_OVERFLOW_MULTIPLY:
+ return "Multiply overflow";
+ case COLORHUG_OVERFLOW_ADDITION:
+ return "Addition overflow";
+ case COLORHUG_OVERFLOW_SENSOR:
+ return "Sensor overflow";
+ case COLORHUG_OVERFLOW_STACK:
+ return "Stack overflow";
+ case COLORHUG_DEVICE_DEACTIVATED:
+ return "Device deactivated";
+ case COLORHUG_INCOMPLETE_REQUEST:
+ return "Incomplete request";
+
+ /* Internal errors */
+ case COLORHUG_NO_COMS:
+ return "Communications hasn't been established";
+ case COLORHUG_NOT_INITED:
+ return "Insrument hasn't been initialised";
+ default:
+ return "Unknown error code";
+ }
+}
+
+/* Do a command/response exchange with the colorhug */
+static inst_code
+colorhug_command(colorhug *p,
+ ColorHugCmd cmd,
+ unsigned char *in, unsigned int in_size,
+ unsigned char *out, unsigned int out_size,
+ double timeout)
+{
+ int i;
+ unsigned char buf[64];
+ int xwbytes, wbytes;
+ int xrbytes, rbytes;
+ int se, ua = 0, rv = inst_ok;
+ int ishid = p->icom->port_type(p->icom) == icomt_hid;
+
+ a1logd(p->log,5,"colorhg_command: sending cmd '%s' args '%s'\n",
+ inst_desc(cmd), icoms_tohex(in, in_size));
+
+ /* Send the command with any specified data */
+ memset(buf, 0, 64);
+ buf[0] = cmd;
+ if (in != NULL)
+ memcpy(buf + 1, in, in_size);
+ if (ishid) {
+ xwbytes = 64;
+ se = p->icom->hid_write(p->icom, buf, xwbytes, &wbytes, timeout);
+ } else {
+// xwbytes = in_size + 1; /* cmd + arguments */
+ xwbytes = 64;
+ se = p->icom->usb_write(p->icom, NULL, 0x01, buf, xwbytes, &wbytes, timeout);
+ }
+ if (se != 0) {
+ a1logd(p->log,1,"colorhug_command: command send failed with ICOM err 0x%x\n",se);
+ return colorhug_interp_code((inst *)p, COLORHUG_COMS_FAIL);
+ }
+ rv = colorhug_interp_code((inst *)p, icoms2colorhug_err(ua));
+ if (rv == inst_ok && wbytes != xwbytes)
+ rv = colorhug_interp_code((inst *)p, COLORHUG_BAD_WR_LENGTH);
+ a1logd(p->log,6,"colorhug_command: got inst code \n",rv);
+
+ if (rv != inst_ok) {
+ /* Flush any response if write failed */
+ if (ishid)
+ p->icom->hid_read(p->icom, buf, 64, &rbytes, timeout);
+ else
+ p->icom->usb_read(p->icom, NULL, 0x81, buf, out_size + 2, &rbytes, timeout);
+ return rv;
+ }
+
+ /* Now fetch the response */
+ a1logd(p->log,6,"colorhug_command: Reading response\n");
+
+ if (ishid) {
+ xrbytes = 64;
+ se = p->icom->hid_read(p->icom, buf, xrbytes, &rbytes, timeout);
+ } else {
+// xrbytes = out_size + 2;
+ xrbytes = 64;
+ se = p->icom->usb_read(p->icom, NULL, 0x81, buf, xrbytes, &rbytes, timeout);
+ }
+
+ if (rbytes >= 2) {
+ a1logd(p->log,6,"colorhug_command: recieved cmd '%s' error '%s' args '%s'\n",
+ inst_desc(buf[1]),
+ colorhug_interp_error((inst *) p, buf[0]),
+ icoms_tohex(buf, rbytes - 2));
+ }
+
+ if (se != 0) {
+
+ /* deal with command error */
+// if (rbytes == 2 && buf[0] != COLORHUG_OK) {
+ if (buf[0] != COLORHUG_OK) {
+ a1logd(p->log,1,"colorhug_command: Got Colorhug !OK\n");
+ rv = colorhug_interp_code((inst *)p, buf[0]);
+ return rv;
+ }
+
+ /* deal with underrun or overrun */
+ if (rbytes != xrbytes) {
+ a1logd(p->log,1,"colorhug_command: got underrun or overrun\n");
+ rv = colorhug_interp_code((inst *)p, COLORHUG_BAD_RD_LENGTH);
+ return rv;
+ }
+
+ /* there's another reason it failed */
+ a1logd(p->log,1,"colorhug_command: read failed with ICOM err 0x%x\n",se);
+ return colorhug_interp_code((inst *)p, COLORHUG_COMS_FAIL);
+ }
+ rv = colorhug_interp_code((inst *)p, icoms2colorhug_err(ua));
+
+ /* check the command was the same */
+ if (rv == inst_ok && buf[1] != cmd) {
+ a1logd(p->log,1,"colorhug_command: command wasn't echo'd\n");
+ rv = colorhug_interp_code((inst *)p, COLORHUG_BAD_RET_CMD);
+ return rv;
+ }
+ if (rv == inst_ok && out != NULL)
+ memcpy(out, buf + 2, out_size);
+
+ a1logd(p->log,5,"colorhg_command: returning '%s' ICOM err 0x%x\n",
+ icoms_tohex(buf + 2, out_size),ua);
+ return rv;
+}
+
+/* --------------------------------------------- */
+/* Little endian wire format conversion routines */
+
+/* Take an int, and convert it into a byte buffer */
+static void int2buf_le(unsigned char *buf, int inv) {
+ buf[0] = (inv >> 0) & 0xff;
+ buf[1] = (inv >> 8) & 0xff;
+ buf[2] = (inv >> 16) & 0xff;
+ buf[3] = (inv >> 24) & 0xff;
+}
+
+/* Take an unsigned int, and convert it into a byte buffer */
+static void uint2buf_le(unsigned char *buf, unsigned int inv) {
+ buf[0] = (inv >> 0) & 0xff;
+ buf[1] = (inv >> 8) & 0xff;
+ buf[2] = (inv >> 16) & 0xff;
+ buf[3] = (inv >> 24) & 0xff;
+}
+
+/* Take a short, and convert it into a byte buffer */
+static void short2buf_le(unsigned char *buf, int inv) {
+ buf[0] = (inv >> 0) & 0xff;
+ buf[1] = (inv >> 8) & 0xff;
+}
+
+/* Take an unsigned short, and convert it into a byte buffer */
+static void ushort2buf_le(unsigned char *buf, unsigned int inv) {
+ buf[0] = (inv >> 0) & 0xff;
+ buf[1] = (inv >> 8) & 0xff;
+}
+
+/* Take a word sized buffer, and convert it to an int */
+static int buf2int_le(unsigned char *buf) {
+ int val;
+ val = buf[3];
+ val = ((val << 8) + buf[2]);
+ val = ((val << 8) + buf[1]);
+ val = ((val << 8) + buf[0]);
+ return val;
+}
+
+/* Take a word sized buffer, and convert it to an unsigned int */
+static unsigned int buf2uint_le(unsigned char *buf) {
+ unsigned int val;
+ val = buf[3];
+ val = ((val << 8) + buf[2]);
+ val = ((val << 8) + buf[1]);
+ val = ((val << 8) + buf[0]);
+ return val;
+}
+
+/* Take a short sized buffer, and convert it to an int */
+static int buf2short_le(unsigned char *buf) {
+ int val;
+ val = buf[1];
+ val = ((val << 8) + buf[0]);
+ return val;
+}
+
+/* Take an unsigned short sized buffer, and convert it to an int */
+static unsigned int buf2ushort_le(unsigned char *buf) {
+ unsigned int val;
+ val = buf[1];
+ val = ((val << 8) + buf[0]);
+ return val;
+}
+
+/* --------------------------------------------- */
+
+
+/* Converts 4 bytes of packed float into a double */
+static double buf2pfdouble(unsigned char *buf)
+{
+ return (double) buf2int_le(buf) / (double) 0x10000;
+}
+
+/* Set the device LED state */
+static inst_code
+colorhug_set_LEDs(colorhug *p, int mask)
+{
+ int i;
+ unsigned char ibuf[4];
+ inst_code ev;
+
+ mask &= 0x3;
+ p->led_state = mask;
+
+ ibuf[0] = mask;
+ ibuf[1] = 0; /* repeat */
+ ibuf[2] = 0; /* on */
+ ibuf[3] = 0; /* off */
+
+ /* Do command */
+ ev = colorhug_command(p, ch_set_leds,
+ ibuf, sizeof (ibuf), /* input */
+ NULL, 0, /* output */
+ 2.0);
+ return ev;
+}
+
+/* Take a measurement from the device */
+/* There are 64 calibration matricies, index 0..63 */
+/* 0 is the factory calibration, while 1..63 are */
+/* applied on top of the factory calibration as corrections. */
+/* Index 64..70 are mapped via the mapping table */
+/* to an index between 0 and 63, and notionaly correspond */
+/* as follows: */
+/* LCD = 0 */
+/* CRT = 1 */
+/* Projector = 2 */
+/* LED = 3 */
+/* Custom1 = 4 */
+/* Custom2 = 5 */
+static inst_code
+colorhug_take_measurement(colorhug *p, double XYZ[3])
+{
+ inst_code ev;
+ int i;
+ ORD8 ibuf[2];
+
+ if (!p->inited)
+ return colorhug_interp_code((inst *)p, COLORHUG_NOT_INITED);
+
+ if (p->icx == 11) { /* Raw */
+ unsigned char obuf[3 * 4];
+
+ /* Do the measurement, and return the values */
+ ev = colorhug_command(p, ch_take_reading,
+ NULL, 0,
+ obuf, 3 * 4,
+ 30.0);
+ if (ev != inst_ok)
+ return ev;
+
+ /* Convert to doubles */
+ for (i = 0; i < 3; i++)
+ XYZ[i] = p->postscale * buf2pfdouble(obuf + i * 4);
+ } else {
+ int icx = 64 + p->icx;
+ unsigned char obuf[3 * 4];
+
+ if (p->icx == 10) /* Factory */
+ icx = 0;
+
+ /* Choose the calibration matrix */
+ short2buf_le(ibuf + 0, icx);
+
+ /* Do the measurement, and return the values */
+ ev = colorhug_command(p, ch_take_reading_xyz,
+ ibuf, sizeof (ibuf),
+ obuf, 3 * 4,
+ 30.0);
+ if (ev != inst_ok)
+ return ev;
+
+ /* Convert to doubles */
+ for (i = 0; i < 3; i++)
+ XYZ[i] = buf2pfdouble(obuf + i * 4);
+ }
+
+ /* Apply the colorimeter correction matrix */
+ icmMulBy3x3(XYZ, p->ccmat, XYZ);
+
+ a1logd(p->log,3,"colorhug_take_measurement: XYZ = %f %f %f\n",XYZ[0],XYZ[1],XYZ[2]);
+
+ return inst_ok;
+}
+
+/* Establish communications with a ColorHug */
+static inst_code
+colorhug_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ int se;
+ colorhug *p = (colorhug *) pp;
+
+ a1logd(p->log, 2, "colorhug_init_coms: About to init coms\n");
+
+ /* Open as an HID if available */
+ if (p->icom->port_type(p->icom) == icomt_hid) {
+
+ a1logd(p->log, 3, "colorhug_init_coms: About to init HID\n");
+
+ /* Set config, interface */
+ if ((se = p->icom->set_hid_port(p->icom, icomuf_none, 0, NULL)) != ICOM_OK) {
+ a1logd(p->log, 1, "colorhug_init_coms: set_hid_port failed ICOM err 0x%x\n",se);
+ return colorhug_interp_code((inst *)p, icoms2colorhug_err(se));
+ }
+
+ } else if (p->icom->port_type(p->icom) == icomt_usb) {
+
+ a1logd(p->log, 3, "colorhug_init_coms: About to init USB\n");
+
+ /* Set config, interface, write end point, read end point */
+ if ((se = p->icom->set_usb_port(p->icom, 1, 0x00, 0x00, icomuf_detach, 0, NULL))
+ != ICOM_OK) {
+ a1logd(p->log, 1, "colorhug_init_coms: set_usb_port failed ICOM err 0x%x\n",se);
+ return colorhug_interp_code((inst *)p, icoms2colorhug_err(se));
+ }
+
+ } else {
+ a1logd(p->log, 1, "colorhug_init_coms: wrong communications type for device!\n");
+ return colorhug_interp_code((inst *)p, COLORHUG_UNKNOWN_MODEL);
+ }
+
+ a1logd(p->log, 2, "colorhug_init_coms: inited coms OK\n");
+
+ p->gotcoms = 1;
+ return inst_ok;
+}
+
+/* Get the firmware version */
+static inst_code
+colorhug_get_firmwareversion (colorhug *p)
+{
+ inst_code ev;
+ unsigned char obuf[6];
+
+ /* Hmm. The post scale is in the 2nd short returned */
+ ev = colorhug_command(p, ch_get_firmware_version,
+ NULL, 0,
+ obuf, 6,
+ 2.0);
+ if (ev != inst_ok)
+ return ev;
+
+ p->maj = buf2short_le(obuf + 0);
+ p->min = buf2short_le(obuf + 2);
+ p->uro = buf2short_le(obuf + 4);
+
+ a1logd(p->log,2,"colorhug: Firware version = %d.%d.%d\n",p->maj,p->min,p->uro);
+
+ return ev;
+}
+
+/* Set the device multiplier */
+static inst_code
+colorhug_set_multiplier (colorhug *p, int multiplier)
+{
+ inst_code ev;
+ unsigned char ibuf[1];
+
+ /* Set the desired multiplier */
+ ibuf[0] = multiplier;
+ ev = colorhug_command(p, ch_set_mult,
+ ibuf, sizeof (ibuf),
+ NULL, 0,
+ 2.0);
+ return ev;
+}
+
+/* Set the device integral time */
+static inst_code
+colorhug_set_integral (colorhug *p, int integral)
+{
+ inst_code ev;
+ unsigned char ibuf[2];
+
+ /* Set the desired integral time */
+ short2buf_le(ibuf + 0, integral);
+ ev = colorhug_command(p, ch_set_integral,
+ ibuf, sizeof (ibuf),
+ NULL, 0,
+ 2.0);
+ return ev;
+}
+
+/* Get the post scale factor */
+static inst_code
+colorhug_get_postscale (colorhug *p, double *postscale)
+{
+ inst_code ev;
+ unsigned char obuf[4];
+
+ /* Hmm. The post scale is in the 2nd short returned */
+ ev = colorhug_command(p, ch_get_post_scale,
+ NULL, 0,
+ obuf, 4,
+ 2.0);
+ *postscale = buf2pfdouble(obuf);
+ return ev;
+}
+
+static inst_code set_default_disp_type(colorhug *p);
+
+/* Initialise the ColorHug */
+static inst_code
+colorhug_init_inst(inst *pp)
+{
+ colorhug *p = (colorhug *)pp;
+ inst_code ev;
+ int i;
+
+ a1logd(p->log, 2, "colorhug_init_coms: About to init coms\n");
+
+ /* Must establish coms first */
+ if (p->gotcoms == 0)
+ return colorhug_interp_code((inst *)p, COLORHUG_NO_COMS);
+
+ /* Get the firmware version */
+ ev = colorhug_get_firmwareversion(p);
+ if (ev != inst_ok)
+ return ev;
+
+ /* Turn the LEDs off */
+ ev = colorhug_set_LEDs(p, 0x0);
+ if (ev != inst_ok)
+ return ev;
+
+ /* Turn the sensor on */
+ ev = colorhug_set_multiplier(p, 0x03);
+ if (ev != inst_ok)
+ return ev;
+
+ /* Set the integral time to maximum precision */
+ ev = colorhug_set_integral(p, 0xffff);
+ if (ev != inst_ok)
+ return ev;
+
+ if (p->maj <= 1 && p->min <= 1 && p->uro <= 4) {
+
+ /* Get the post scale factor */
+ ev = colorhug_get_postscale(p, &p->postscale);
+ if (ev != inst_ok)
+ return ev;
+
+
+ /* In firmware >= 1.1.5, the postscale is done in the firmware */
+ } else {
+ p->postscale = 1.0;
+ }
+
+ p->trig = inst_opt_trig_user;
+
+ /* Setup the default display type */
+ if ((ev = set_default_disp_type(p)) != inst_ok) {
+ return ev;
+ }
+
+ p->inited = 1;
+ a1logd(p->log, 2, "colorhug_init: inited coms OK\n");
+
+ /* Flash the LEDs */
+ ev = colorhug_set_LEDs(p, 0x1);
+ if (ev != inst_ok)
+ return ev;
+ msec_sleep(50);
+ ev = colorhug_set_LEDs(p, 0x2);
+ if (ev != inst_ok)
+ return ev;
+ msec_sleep(50);
+ ev = colorhug_set_LEDs(p, 0x1);
+ if (ev != inst_ok)
+ return ev;
+ msec_sleep(50);
+ ev = colorhug_set_LEDs(p, 0x0);
+ if (ev != inst_ok)
+ return ev;
+
+ return inst_ok;
+}
+
+/* Read a single sample */
+static inst_code
+colorhug_read_sample(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+ipatch *val, /* Pointer to instrument patch value */
+instClamping clamp) { /* NZ if clamp XYZ/Lab to be +ve */
+ colorhug *p = (colorhug *)pp;
+ int user_trig = 0;
+ int rv = inst_protocol_error;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->trig == inst_opt_trig_user) {
+
+ if (p->uicallback == NULL) {
+ a1logd(p->log, 1, "colorhug: inst_opt_trig_user but no uicallback function set!\n");
+ return inst_unsupported;
+ }
+
+ for (;;) {
+ if ((rv = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (rv == inst_user_abort)
+ return rv; /* Abort */
+ if (rv == inst_user_trig) {
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ msec_sleep(200);
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ /* Progromatic Trigger */
+ } else {
+ /* Check for abort */
+ if (p->uicallback != NULL
+ && (rv = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_abort)
+ return rv; /* Abort */
+ }
+
+ /* Read the XYZ value */
+ if ((rv = colorhug_take_measurement(p, val->XYZ)) != inst_ok) {
+ return rv;
+ }
+ /* This may not change anything since instrument may clamp */
+ if (clamp)
+ icmClamp3(val->XYZ, val->XYZ);
+
+ val->mtype = inst_mrt_emission;
+ val->XYZ_v = 1; /* These are absolute XYZ readings ? */
+ val->sp.spec_n = 0;
+ val->duration = 0.0;
+
+ if (user_trig)
+ return inst_user_trig;
+ return rv;
+}
+
+/* Insert a colorimetric correction matrix */
+inst_code colorhug_col_cor_mat(
+inst *pp,
+double mtx[3][3]
+) {
+ colorhug *p = (colorhug *)pp;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+
+ if (mtx == NULL) {
+ icmSetUnity3x3(p->ccmat);
+ } else {
+ if (p->cbid == 0) {
+ a1loge(p->log, 1, "colorhug: can't set col_cor_mat over non base display type\n");
+ inst_wrong_setup;
+ }
+ icmCpy3x3(p->ccmat, mtx);
+ }
+
+ return inst_ok;
+}
+
+/* Convert a machine specific error code into an abstract dtp code */
+static inst_code
+colorhug_interp_code(inst *pp, int ec) {
+ ec &= inst_imask;
+ switch (ec) {
+
+ case COLORHUG_OK:
+ return inst_ok;
+
+ case COLORHUG_INTERNAL_ERROR:
+ case COLORHUG_NO_COMS:
+ case COLORHUG_NOT_INITED:
+ return inst_internal_error | ec;
+
+ case COLORHUG_COMS_FAIL:
+ return inst_coms_fail | ec;
+
+ case COLORHUG_UNKNOWN_MODEL:
+ return inst_unknown_model | ec;
+
+ case COLORHUG_UNKNOWN_CMD:
+ case COLORHUG_WRONG_UNLOCK_CODE:
+ case COLORHUG_NOT_IMPLEMENTED:
+ case COLORHUG_UNDERFLOW_SENSOR:
+ case COLORHUG_NO_SERIAL:
+ case COLORHUG_WATCHDOG:
+ case COLORHUG_INVALID_ADDRESS:
+ case COLORHUG_INVALID_LENGTH:
+ case COLORHUG_INVALID_CHECKSUM:
+ case COLORHUG_INVALID_VALUE:
+ case COLORHUG_UNKNOWN_CMD_FOR_BOOTLOADER:
+ case COLORHUG_NO_CALIBRATION:
+ case COLORHUG_OVERFLOW_MULTIPLY:
+ case COLORHUG_OVERFLOW_ADDITION:
+ case COLORHUG_OVERFLOW_SENSOR:
+ case COLORHUG_OVERFLOW_STACK:
+ case COLORHUG_DEVICE_DEACTIVATED:
+ case COLORHUG_INCOMPLETE_REQUEST:
+ case COLORHUG_BAD_WR_LENGTH:
+ case COLORHUG_BAD_RD_LENGTH:
+ case COLORHUG_BAD_RET_CMD:
+ case COLORHUG_BAD_RET_STAT:
+ return inst_protocol_error | ec;
+ }
+ return inst_other_error | ec;
+}
+
+/* Destroy ourselves */
+static void
+colorhug_del(inst *pp) {
+ colorhug *p = (colorhug *)pp;
+ if (p != NULL) {
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ inst_del_disptype_list(p->dtlist, p->ndtlist);
+ free(p);
+ }
+}
+
+/* Return the instrument mode capabilities */
+void colorhug_capabilities(inst *pp,
+inst_mode *pcap1,
+inst2_capability *pcap2,
+inst3_capability *pcap3) {
+ colorhug *p = (colorhug *)pp;
+ inst_mode cap = 0;
+ inst2_capability cap2 = 0;
+
+ cap |= inst_mode_emis_spot
+ | inst_mode_colorimeter
+ ;
+
+ cap2 |= inst2_prog_trig
+ | inst2_user_trig
+ | inst2_has_leds
+ | inst2_disptype
+ | inst2_ccmx
+ ;
+
+ if (pcap1 != NULL)
+ *pcap1 = cap;
+ if (pcap2 != NULL)
+ *pcap2 = cap2;
+ if (pcap3 != NULL)
+ *pcap3 = inst3_none;
+}
+
+/* Check device measurement mode */
+inst_code colorhug_check_mode(inst *pp, inst_mode m) {
+ colorhug *p = (colorhug *)pp;
+ inst_mode cap;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ pp->capabilities(pp, &cap, NULL, NULL);
+
+ /* Simple test */
+ if (m & ~cap)
+ return inst_unsupported;
+
+ /* only display emission mode and ambient supported */
+ if (!IMODETST(m, inst_mode_emis_spot)
+ && !IMODETST(m, inst_mode_emis_ambient)) {
+ return inst_unsupported;
+ }
+
+ return inst_ok;
+}
+
+/* Set device measurement mode */
+inst_code colorhug_set_mode(inst *pp, inst_mode m) {
+ colorhug *p = (colorhug *)pp;
+ inst_code ev;
+
+ if ((ev = colorhug_check_mode(pp, m)) != inst_ok)
+ return ev;
+
+ p->mode = m;
+
+ return inst_ok;
+}
+
+/* The HW handles up to 6, + 2 special */
+static inst_disptypesel colorhug_disptypesel[7] = {
+ {
+ inst_dtflags_default, /* flags */
+ 0, /* cbix */
+ "l", /* sel */
+ "LCD, CCFL Backlight", /* desc */
+ 0, /* refr */
+ 0 /* ix */
+ },
+ {
+ inst_dtflags_none,
+ 0,
+ "c",
+ "CRT display",
+ 0,
+ 1
+ },
+ {
+ inst_dtflags_none,
+ 0,
+ "p",
+ "Projector",
+ 0,
+ 2
+ },
+ {
+ inst_dtflags_none,
+ 0,
+ "e",
+ "LCD, White LED Backlight",
+ 0,
+ 3
+ },
+ {
+ inst_dtflags_none,
+ 1,
+ "F",
+ "Factory matrix (For Calibration)",
+ 0,
+ 10
+ },
+ {
+ inst_dtflags_none,
+ 2,
+ "R",
+ "Raw Reading (For Factory matrix Calibration)",
+ 0,
+ 11
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ 0
+ }
+};
+
+/* Get mode and option details */
+static inst_code colorhug_get_disptypesel(
+inst *pp,
+int *pnsels, /* Return number of display types */
+inst_disptypesel **psels, /* Return the array of display types */
+int allconfig, /* nz to return list for all configs, not just current. */
+int recreate /* nz to re-check for new ccmx & ccss files */
+) {
+ colorhug *p = (colorhug *)pp;
+ inst_code rv = inst_ok;
+
+ /* Create/Re-create a current list of abailable display types */
+ if (p->dtlist == NULL || recreate) {
+ if ((rv = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
+ colorhug_disptypesel, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return rv;
+ }
+
+ if (pnsels != NULL)
+ *pnsels = p->ndtlist;
+
+ if (psels != NULL)
+ *psels = p->dtlist;
+
+ return inst_ok;
+}
+
+/* Given a display type entry, setup for that type */
+static inst_code set_disp_type(colorhug *p, inst_disptypesel *dentry) {
+ int ix;
+
+ /* The HW handles up to 6 calibrations */
+ ix = dentry->ix;
+ if (ix != 10 && ix != 11 && (ix < 0 || ix > 3))
+ return inst_unsupported;
+
+ p->icx = ix;
+ p->refrmode = dentry->refr;
+ p->cbid = dentry->cbid;
+ if (dentry->flags & inst_dtflags_ccmx) {
+ icmCpy3x3(p->ccmat, dentry->mat);
+ } else {
+ icmSetUnity3x3(p->ccmat);
+ }
+
+ return inst_ok;
+}
+
+/* Setup the default display type */
+static inst_code set_default_disp_type(colorhug *p) {
+ inst_code ev;
+ int i;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list((inst *)p, &p->ndtlist, &p->dtlist,
+ colorhug_disptypesel, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ for (i = 0; !(p->dtlist[i].flags & inst_dtflags_end); i++) {
+ if (p->dtlist[i].flags & inst_dtflags_default)
+ break;
+ }
+ if (p->dtlist[i].flags & inst_dtflags_end) {
+ a1loge(p->log, 1, "set_default_disp_type: failed to find type!\n");
+ return inst_internal_error;
+ }
+ if ((ev = set_disp_type(p, &p->dtlist[i])) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+/* Set the display type */
+static inst_code colorhug_set_disptype(inst *pp, int ix) {
+ colorhug *p = (colorhug *)pp;
+ inst_code ev;
+ inst_disptypesel *dentry;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
+ colorhug_disptypesel, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ if (ix < 0 || ix >= p->ndtlist)
+ return inst_unsupported;
+
+ dentry = &p->dtlist[ix];
+
+ if ((ev = set_disp_type(p, dentry)) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+/*
+ * Set or reset an optional mode.
+ *
+ * Some options talk to the instrument, and these will
+ * error if it hasn't been initialised.
+ */
+static inst_code
+colorhug_get_set_opt(inst *pp, inst_opt_type m, ...)
+{
+ colorhug *p = (colorhug *)pp;
+ inst_code ev = inst_ok;
+
+ /* Record the trigger mode */
+ if (m == inst_opt_trig_prog
+ || m == inst_opt_trig_user) {
+ p->trig = m;
+ return inst_ok;
+ }
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* Get the display type information */
+ if (m == inst_opt_get_dtinfo) {
+ va_list args;
+ int *refrmode, *cbid;
+
+ va_start(args, m);
+ refrmode = va_arg(args, int *);
+ cbid = va_arg(args, int *);
+ va_end(args);
+
+ if (refrmode != NULL)
+ *refrmode = p->refrmode;
+ if (cbid != NULL)
+ *cbid = p->cbid;
+
+ return inst_ok;
+ }
+
+ /* Operate the LEDs */
+ if (m == inst_opt_get_gen_ledmask) {
+ va_list args;
+ int *mask = NULL;
+
+ va_start(args, m);
+ mask = va_arg(args, int *);
+ va_end(args);
+ *mask = 0x3; /* Two general LEDs */
+ return inst_ok;
+ } else if (m == inst_opt_get_led_state) {
+ va_list args;
+ int *mask = NULL;
+
+ va_start(args, m);
+ mask = va_arg(args, int *);
+ va_end(args);
+ *mask = p->led_state;
+ return inst_ok;
+ } else if (m == inst_opt_set_led_state) {
+ va_list args;
+ int mask = 0;
+
+ va_start(args, m);
+ mask = va_arg(args, int);
+ va_end(args);
+ return colorhug_set_LEDs(p, mask);
+ }
+
+ return inst_unsupported;
+}
+
+/* Constructor */
+extern colorhug *new_colorhug(icoms *icom, instType itype) {
+ colorhug *p;
+ int i;
+
+ if ((p = (colorhug *)calloc(sizeof(colorhug),1)) == NULL) {
+ a1loge(icom->log, 1, "new_colorhug: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(icom->log);
+
+ p->init_coms = colorhug_init_coms;
+ p->init_inst = colorhug_init_inst;
+ p->capabilities = colorhug_capabilities;
+ p->check_mode = colorhug_check_mode;
+ p->set_mode = colorhug_set_mode;
+ p->get_disptypesel = colorhug_get_disptypesel;
+ p->set_disptype = colorhug_set_disptype;
+ p->get_set_opt = colorhug_get_set_opt;
+ p->read_sample = colorhug_read_sample;
+ p->col_cor_mat = colorhug_col_cor_mat;
+ p->interp_error = colorhug_interp_error;
+ p->del = colorhug_del;
+
+ p->icom = icom;
+ p->itype = icom->itype;
+
+ icmSetUnity3x3(p->ccmat);
+
+ return p;
+}
+
diff --git a/spectro/colorhug.h b/spectro/colorhug.h
new file mode 100644
index 0000000..a591608
--- /dev/null
+++ b/spectro/colorhug.h
@@ -0,0 +1,90 @@
+#ifndef COLORHUG_H
+
+/*
+ * Argyll Color Correction System
+ *
+ * Hughski ColorHug related defines
+ *
+ * Author: Richard Hughes
+ * Date: 30/11/2011
+ *
+ * Copyright 2006 - 2013, Graeme W. Gill
+ * Copyright 2011, Richard Hughes
+ * All rights reserved.
+ *
+ * (Based on huey.h)
+ *
+ * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
+ * see the License2.txt file for licencing details.
+ */
+
+#include "inst.h"
+
+/* Note: update colorhug_interp_error() and colorhug_interp_code() in colorhug.c */
+/* if anything of these #defines are added or subtracted */
+
+/* Fake Error codes */
+#define COLORHUG_INTERNAL_ERROR 0x61 /* Internal software error */
+#define COLORHUG_COMS_FAIL 0x62 /* Communication failure */
+#define COLORHUG_UNKNOWN_MODEL 0x63 /* Not an colorhug */
+#define COLORHUG_DATA_PARSE_ERROR 0x64 /* Read data parsing error */
+
+/* Real error codes */
+#define COLORHUG_OK 0x00
+#define COLORHUG_UNKNOWN_CMD 0x01
+#define COLORHUG_WRONG_UNLOCK_CODE 0x02
+#define COLORHUG_NOT_IMPLEMENTED 0x03
+#define COLORHUG_UNDERFLOW_SENSOR 0x04
+#define COLORHUG_NO_SERIAL 0x05
+#define COLORHUG_WATCHDOG 0x06
+#define COLORHUG_INVALID_ADDRESS 0x07
+#define COLORHUG_INVALID_LENGTH 0x08
+#define COLORHUG_INVALID_CHECKSUM 0x09
+#define COLORHUG_INVALID_VALUE 0x0a
+#define COLORHUG_UNKNOWN_CMD_FOR_BOOTLOADER 0x0b
+#define COLORHUG_NO_CALIBRATION 0x0c
+#define COLORHUG_OVERFLOW_MULTIPLY 0x0d
+#define COLORHUG_OVERFLOW_ADDITION 0x0e
+#define COLORHUG_OVERFLOW_SENSOR 0x0f
+#define COLORHUG_OVERFLOW_STACK 0x10
+#define COLORHUG_DEVICE_DEACTIVATED 0x11
+#define COLORHUG_INCOMPLETE_REQUEST 0x12
+
+/* Internal errors */
+#define COLORHUG_NO_COMS 0x22
+#define COLORHUG_NOT_INITED 0x23
+#define COLORHUG_BAD_WR_LENGTH 0x25
+#define COLORHUG_BAD_RD_LENGTH 0x26
+#define COLORHUG_BAD_RET_CMD 0x27
+#define COLORHUG_BAD_RET_STAT 0x28
+
+
+/* COLORHUG communication object */
+struct _colorhug {
+ INST_OBJ_BASE
+
+ inst_mode mode; /* Currently selected mode */
+
+ inst_opt_type trig; /* Reading trigger mode */
+
+ int maj, min, uro; /* Version number */
+ int ser_no; /* Serial number */
+
+ inst_disptypesel *dtlist; /* Display Type list */
+ int ndtlist; /* Number of valid dtlist entries */
+ int icx; /* Internal calibration matrix index, 11 = Raw */
+ int cbid; /* calibration base ID, 0 if not a base */
+ int refrmode; /* Refresh mode (always 0) */
+ double postscale; /* Post scale factor (for Raw) */
+ double ccmat[3][3]; /* Colorimeter correction matrix */
+
+ int led_state; /* Current LED state */
+
+}; typedef struct _colorhug colorhug;
+
+/* Constructor */
+extern colorhug *new_colorhug(icoms *icom, instType itype);
+
+
+#define COLORHUG_H
+#endif /* COLORHUG_H */
diff --git a/spectro/conv.c b/spectro/conv.c
new file mode 100644
index 0000000..0f56ff9
--- /dev/null
+++ b/spectro/conv.c
@@ -0,0 +1,1471 @@
+
+ /* Platform isolation convenience functions */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 28/9/97
+ *
+ * Copyright 1997 - 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.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <time.h>
+
+#ifdef NT
+# if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0501
+# define _WIN32_WINNT 0x0501
+# endif
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+#include <conio.h>
+#include <tlhelp32.h>
+#include <direct.h>
+#endif
+
+#if defined(UNIX)
+#include <fcntl.h>
+#include <termios.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <string.h>
+
+/* select() defined, but not poll(), so emulate poll() */
+#if defined(FD_CLR) && !defined(POLLIN)
+#include "pollem.h"
+#define poll_x pollem
+#else
+#include <sys/poll.h> /* Else assume poll() is native */
+#define poll_x poll
+#endif
+#endif
+
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#else
+#include "sa_config.h"
+#endif
+#include "numsup.h"
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+
+#ifdef __APPLE__
+//#include <stdbool.h>
+#include <sys/sysctl.h>
+#include <sys/param.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/serial/IOSerialKeys.h>
+#include <IOKit/IOBSD.h>
+#include <mach/mach_init.h>
+#include <mach/task_policy.h>
+#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050
+# include <AudioToolbox/AudioServices.h>
+#endif
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
+# include <objc/objc-auto.h>
+#endif
+#endif /* __APPLE__ */
+
+#undef DEBUG
+
+#ifdef DEBUG
+# define DBG(xx) a1logd(g_log, 0, xx )
+# define DBGA g_log, 0 /* First argument to DBGF() */
+# define DBGF(xx) a1logd xx
+#else
+# define DBG(xx)
+# define DBGF(xx)
+#endif
+
+#ifdef __BORLANDC__
+#define _kbhit kbhit
+#endif
+
+/* ============================================================= */
+/* MS WINDOWS */
+/* ============================================================= */
+#ifdef NT
+
+/* wait for and then return the next character from the keyboard */
+/* (If not_interactive, return getchar()) */
+int next_con_char(void) {
+ int c;
+
+ if (not_interactive) {
+ HANDLE stdinh;
+ char buf[1];
+ DWORD bread;
+
+ if ((stdinh = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) {
+ return 0;
+ }
+
+ /* Ignore end of line characters */
+ for (;;) {
+ if (ReadFile(stdinh, buf, 1, &bread, NULL)
+ && bread == 1
+ && buf[0] != '\r' && buf[0] != '\n') {
+ return buf[0];
+ }
+ }
+ }
+
+ c = _getch();
+ return c;
+}
+
+/* Horrible hack to poll stdin when we're not interactive */
+static int th_read_char(void *pp) {
+ char *rp = (char *)pp;
+ HANDLE stdinh;
+ char buf[1];
+ DWORD bread;
+
+ if ((stdinh = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE)
+ return 0;
+
+ if (ReadFile(stdinh, buf, 1, &bread, NULL)
+ && bread == 1
+ && buf[0] != '\r' && buf[0] != '\n') {
+ *rp = buf[0];
+ }
+
+ return 0;
+}
+
+/* If there is one, return the next character from the keyboard, else return 0 */
+/* (If not_interactive, always returns 0) */
+int poll_con_char(void) {
+
+ if (not_interactive) { /* Can't assume that it's the console */
+ athread *getch_thread = NULL;
+ char c = 0;
+
+ /* This is pretty horrible. The problem is that we can't use */
+ /* any of MSWin's async file read functions, because we */
+ /* have no way of ensuring that the STD_INPUT_HANDLE has been */
+ /* opened with FILE_FLAG_OVERLAPPED. Used a thread instead... */
+ if ((getch_thread = new_athread(th_read_char, &c)) != NULL) {
+ HANDLE stdinh;
+
+ if ((stdinh = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) {
+ return 0;
+ }
+
+ Sleep(1); /* We just hope 1 msec is enough for the thread to start */
+ CancelIo(stdinh);
+ getch_thread->del(getch_thread);
+ return c;
+ }
+ return 0;
+ }
+
+ /* Assume it's the console */
+ if (_kbhit() != 0) {
+ int c = next_con_char();
+ return c;
+ }
+ return 0;
+}
+
+/* Suck all characters from the keyboard */
+/* (If not_interactive, does nothing) */
+void empty_con_chars(void) {
+
+ if (not_interactive) {
+ return;
+ }
+
+ Sleep(50); /* _kbhit seems to have a bug */
+ while (_kbhit()) {
+ if (next_con_char() == 0x3) /* ^C Safety */
+ break;
+ }
+}
+
+/* Sleep for the given number of seconds */
+void sleep(unsigned int secs) {
+ Sleep(secs * 1000);
+}
+
+/* Sleep for the given number of msec */
+void msec_sleep(unsigned int msec) {
+ Sleep(msec);
+}
+
+/* Return the current time in msec since */
+/* the first invokation of msec_time() */
+/* (Is this based on timeGetTime() ? ) */
+unsigned int msec_time() {
+ unsigned int rv;
+ static unsigned int startup = 0;
+
+ rv = GetTickCount();
+ if (startup == 0)
+ startup = rv;
+
+ return rv - startup;
+}
+
+/* Return the current time in usec */
+/* since the first invokation of usec_time() */
+/* Return -1.0 if not available */
+double usec_time() {
+ double rv;
+ LARGE_INTEGER val;
+ static double scale = 0.0;
+ static LARGE_INTEGER startup;
+
+ if (scale == 0.0) {
+ if (QueryPerformanceFrequency(&val) == 0)
+ return -1.0;
+ scale = 1000000.0/val.QuadPart;
+ QueryPerformanceCounter(&val);
+ startup.QuadPart = val.QuadPart;
+
+ } else {
+ QueryPerformanceCounter(&val);
+ }
+ val.QuadPart -= startup.QuadPart;
+
+ rv = val.QuadPart * scale;
+
+ return rv;
+}
+
+static athread *beep_thread = NULL;
+static int beep_delay;
+static int beep_freq;
+static int beep_msec;
+
+/* Delayed beep handler */
+static int delayed_beep(void *pp) {
+ msec_sleep(beep_delay);
+ Beep(beep_freq, beep_msec);
+ return 0;
+}
+
+/* Activate the system beeper */
+void msec_beep(int delay, int freq, int msec) {
+ if (delay > 0) {
+ if (beep_thread != NULL)
+ beep_thread->del(beep_thread);
+ beep_delay = delay;
+ beep_freq = freq;
+ beep_msec = msec;
+ if ((beep_thread = new_athread(delayed_beep, NULL)) == NULL)
+ a1logw(g_log, "msec_beep: Delayed beep failed to create thread\n");
+ } else {
+ Beep(freq, msec);
+ }
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+#ifdef NEVER /* Not currently needed, or effective */
+
+/* Set the current threads priority */
+int set_interactive_priority() {
+ if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) == 0)
+ return 1;
+ return 0;
+}
+
+int set_normal_priority() {
+ if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL) == 0)
+ return 1;
+ return 0;
+}
+
+#endif /* NEVER */
+
+
+#undef USE_BEGINTHREAD
+
+/* Wait for the thread to exit. Return the result */
+static int athread_wait(struct _athread *p) {
+
+ if (p->finished)
+ return p->result;
+
+ WaitForSingleObject(p->th, INFINITE);
+
+ return p->result;
+}
+
+/* Destroy the thread */
+static void athread_del(
+athread *p
+) {
+ DBG("athread_del called\n");
+
+ if (p == NULL)
+ return;
+
+ if (p->th != NULL) {
+ if (!p->finished) {
+ DBG("athread_del calling TerminateThread() because thread hasn't finished\n");
+ TerminateThread(p->th, (DWORD)-1); /* But it is worse to leave it hanging around */
+ }
+ CloseHandle(p->th);
+ }
+
+ free(p);
+}
+
+/* _beginthread doesn't leak memory, but */
+/* needs to be linked to a different library */
+#ifdef USE_BEGINTHREAD
+/* Thread function */
+static void __cdecl threadproc(
+ void *lpParameter
+) {
+#else
+DWORD WINAPI threadproc(
+ LPVOID lpParameter
+) {
+#endif
+ athread *p = (athread *)lpParameter;
+
+ p->result = p->function(p->context);
+ p->finished = 1;
+#ifdef USE_BEGINTHREAD
+#else
+ return 0;
+#endif
+}
+
+
+athread *new_athread(
+ int (*function)(void *context),
+ void *context
+) {
+ athread *p = NULL;
+
+ DBG("new_athread called\n");
+
+ if ((p = (athread *)calloc(sizeof(athread), 1)) == NULL) {
+ a1loge(g_log, 1, "new_athread: calloc failed\n");
+ return NULL;
+ }
+
+ p->function = function;
+ p->context = context;
+ p->wait = athread_wait;
+ p->del = athread_del;
+
+ /* Create a thread */
+#ifdef USE_BEGINTHREAD
+ p->th = _beginthread(threadproc, 0, (void *)p);
+ if (p->th == -1) {
+#else
+ p->th = CreateThread(NULL, 0, threadproc, (void *)p, 0, NULL);
+ if (p->th == NULL) {
+#endif
+ a1loge(g_log, 1, "new_athread: CreateThread failed with %d\n",GetLastError());
+ p->th = NULL;
+ athread_del(p);
+ return NULL;
+ }
+
+ DBG("new_athread returning OK\n");
+ return p;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Delete a file */
+void delete_file(char *fname) {
+ _unlink(fname);
+}
+
+/* Given the path to a file, ensure that all the parent directories */
+/* are created. return nz on error */
+int create_parent_directories(char *path) {
+ struct _stat sbuf;
+ char *pp = path;
+
+ if (*pp != '\000' /* Skip drive number */
+ && ((*pp >= 'a' && *pp <= 'z') || (*pp >= 'A' && *pp <= 'Z'))
+ && pp[1] == ':')
+ pp += 2;
+ if (*pp == '/')
+ pp++; /* Skip root directory */
+ for (;pp != NULL && *pp != '\000';) {
+ if ((pp = strchr(pp, '/')) != NULL) {
+ *pp = '\000';
+ if (_stat(path,&sbuf) != 0)
+ {
+ if (_mkdir(path) != 0)
+ return 1;
+ }
+ *pp = '/';
+ pp++;
+ }
+ }
+ return 0;
+}
+
+#endif /* NT */
+
+
+/* ============================================================= */
+/* UNIX/OS X */
+/* ============================================================= */
+
+#if defined(UNIX)
+
+/* Wait for and return the next character from the keyboard */
+/* (If not_interactive, return getchar()) */
+int next_con_char(void) {
+ struct pollfd pa[1]; /* Poll array to monitor stdin */
+ struct termios origs, news;
+ char rv = 0;
+
+ if (!not_interactive) {
+ /* Configure stdin to be ready with just one character */
+ if (tcgetattr(STDIN_FILENO, &origs) < 0)
+ a1logw(g_log, "next_con_char: tcgetattr failed with '%s' on stdin", strerror(errno));
+ news = origs;
+ news.c_lflag &= ~(ICANON | ECHO);
+ news.c_cc[VTIME] = 0;
+ news.c_cc[VMIN] = 1;
+ if (tcsetattr(STDIN_FILENO,TCSANOW, &news) < 0)
+ a1logw(g_log, "next_con_char: tcsetattr failed with '%s' on stdin", strerror(errno));
+ }
+
+ /* Wait for stdin to have a character */
+ pa[0].fd = STDIN_FILENO;
+ pa[0].events = POLLIN | POLLPRI;
+ pa[0].revents = 0;
+
+ if (poll_x(pa, 1, -1) > 0
+ && (pa[0].revents == POLLIN
+ || pa[0].revents == POLLPRI)) {
+ char tb[3];
+ if (read(STDIN_FILENO, tb, 1) > 0) { /* User hit a key */
+ rv = tb[0] ;
+ }
+ } else {
+ if (!not_interactive)
+ tcsetattr(STDIN_FILENO, TCSANOW, &origs);
+ a1logw(g_log, "next_con_char: poll on stdin returned unexpected value 0x%x",pa[0].revents);
+ }
+
+ /* Restore stdin */
+ if (!not_interactive && tcsetattr(STDIN_FILENO, TCSANOW, &origs) < 0) {
+ a1logw(g_log, "next_con_char: tcsetattr failed with '%s' on stdin", strerror(errno));
+ }
+
+ return rv;
+}
+
+/* If here is one, return the next character from the keyboard, else return 0 */
+/* (If not_interactive, always returns 0) */
+int poll_con_char(void) {
+ struct pollfd pa[1]; /* Poll array to monitor stdin */
+ struct termios origs, news;
+ char rv = 0;
+
+ if (!not_interactive) {
+ /* Configure stdin to be ready with just one character */
+ if (tcgetattr(STDIN_FILENO, &origs) < 0)
+ a1logw(g_log, "poll_con_char: tcgetattr failed with '%s' on stdin", strerror(errno));
+ news = origs;
+ news.c_lflag &= ~(ICANON | ECHO);
+ news.c_cc[VTIME] = 0;
+ news.c_cc[VMIN] = 1;
+ if (tcsetattr(STDIN_FILENO,TCSANOW, &news) < 0)
+ a1logw(g_log, "poll_con_char: tcsetattr failed with '%s' on stdin", strerror(errno));
+ }
+
+ /* Wait for stdin to have a character */
+ pa[0].fd = STDIN_FILENO;
+ pa[0].events = POLLIN | POLLPRI;
+ pa[0].revents = 0;
+
+ if (poll_x(pa, 1, 0) > 0
+ && (pa[0].revents == POLLIN
+ || pa[0].revents == POLLPRI)) {
+ char tb[3];
+ if (read(STDIN_FILENO, tb, 1) > 0) { /* User hit a key */
+ rv = tb[0] ;
+ }
+ }
+
+ /* Restore stdin */
+ if (!not_interactive && tcsetattr(STDIN_FILENO, TCSANOW, &origs) < 0)
+ a1logw(g_log, "poll_con_char: tcsetattr failed with '%s' on stdin", strerror(errno));
+
+ return rv;
+}
+
+/* Suck all characters from the keyboard */
+/* (If not_interactive, does nothing) */
+void empty_con_chars(void) {
+ if (not_interactive)
+ return;
+
+ tcflush(STDIN_FILENO, TCIFLUSH);
+}
+
+/* Sleep for the given number of msec */
+void msec_sleep(unsigned int msec) {
+#ifdef NEVER
+ if (msec > 1000) {
+ unsigned int secs;
+ secs = msec / 1000;
+ msec = msec % 1000;
+ sleep(secs);
+ }
+ usleep(msec * 1000);
+#else
+ struct timespec ts;
+
+ ts.tv_sec = msec / 1000;
+ ts.tv_nsec = (msec % 1000) * 1000000;
+ nanosleep(&ts, NULL);
+#endif
+}
+
+/* Return the current time in msec */
+/* since the first invokation of msec_time() */
+unsigned int msec_time() {
+ unsigned int rv;
+ static struct timeval startup = { 0, 0 };
+ struct timeval cv;
+
+ /* Is this monotonic ? */
+ /* On Linux, should clock_gettime with CLOCK_MONOTONIC be used instead ? */
+ /* On OS X, should mach_absolute_time() be used ? */
+ /* or host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &clk) */
+ gettimeofday(&cv, NULL);
+
+ /* Set time to 0 on first invocation */
+ if (startup.tv_sec == 0 && startup.tv_usec == 0)
+ startup = cv;
+
+ /* Subtract, taking care of carry */
+ cv.tv_sec -= startup.tv_sec;
+ if (startup.tv_usec > cv.tv_usec) {
+ cv.tv_sec--;
+ cv.tv_usec += 1000000;
+ }
+ cv.tv_usec -= startup.tv_usec;
+
+ /* Convert usec to msec */
+ rv = cv.tv_sec * 1000 + cv.tv_usec / 1000;
+
+ return rv;
+}
+
+/* Return the current time in usec */
+/* since the first invokation of usec_time() */
+double usec_time() {
+ double rv;
+ static struct timeval startup = { 0, 0 };
+ struct timeval cv;
+
+ /* Is this monotonic ? */
+ /* On Linux, should clock_gettime with CLOCK_MONOTONIC/CLOCK_REALTIME/CLOCK_REALTIME_HR ? */
+ /* On OS X, should mach_absolute_time() be used ? */
+ /* or host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &clk) */
+ gettimeofday(&cv, NULL);
+
+ /* Set time to 0 on first invocation */
+ if (startup.tv_sec == 0 && startup.tv_usec == 0)
+ startup = cv;
+
+ /* Subtract, taking care of carry */
+ cv.tv_sec -= startup.tv_sec;
+ if (startup.tv_usec > cv.tv_usec) {
+ cv.tv_sec--;
+ cv.tv_usec += 1000000;
+ }
+ cv.tv_usec -= startup.tv_usec;
+
+ /* Convert to usec */
+ rv = cv.tv_sec * 1000000.0 + cv.tv_usec;
+
+ return rv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - */
+
+#ifdef NEVER /* Not currently needed, or effective */
+
+/* Set the current threads priority */
+int set_interactive_priority() {
+#ifdef __APPLE__
+#ifdef NEVER
+ int rv = 0;
+ struct task_category_policy tcatpolicy;
+
+ tcatpolicy.role = TASK_FOREGROUND_APPLICATION;
+
+ if (task_policy_set(mach_task_self(),
+ TASK_CATEGORY_POLICY, (thread_policy_t)&tcatpolicy,
+ TASK_CATEGORY_POLICY_COUNT) != KERN_SUCCESS)
+ rv = 1;
+// a1logd(g_log, 8, "set_interactive_priority: set to forground got %d\n",rv);
+ return rv;
+#else
+ int rv = 0;
+ struct thread_precedence_policy tppolicy;
+
+ tppolicy.importance = 500;
+
+ if (thread_policy_set(mach_thread_self(),
+ THREAD_PRECEDENCE_POLICY, (thread_policy_t)&tppolicy,
+ THREAD_PRECEDENCE_POLICY_COUNT) != KERN_SUCCESS)
+ rv = 1;
+// a1logd(g_log, 8, "set_interactive_priority: set to important got %d\n",rv);
+ return rv;
+#endif /* NEVER */
+#else /* !APPLE */
+ int rv;
+ struct sched_param param;
+ param.sched_priority = 32;
+
+ /* This doesn't work unless we're running as su :-( */
+ rv = pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);
+// a1logd(g_log, 8, "set_interactive_priority: set got %d\n",rv);
+ return rv;
+#endif /* !APPLE */
+}
+
+int set_normal_priority() {
+#ifdef __APPLE__
+#ifdef NEVER
+ int rv = 0;
+ struct task_category_policy tcatpolicy;
+
+ tcatpolicy.role = TASK_UNSPECIFIED;
+
+ if (task_policy_set(mach_task_self(),
+ TASK_CATEGORY_POLICY, (thread_policy_t)&tcatpolicy,
+ TASK_CATEGORY_POLICY_COUNT) != KERN_SUCCESS)
+ rev = 1;
+// a1logd(g_log, 8, "set_normal_priority: set to normal got %d\n",rv);
+#else
+ int rv = 0;
+ struct thread_precedence_policy tppolicy;
+
+ tppolicy.importance = 1;
+
+ if (thread_policy_set(mach_thread_self(),
+ THREAD_STANDARD_POLICY, (thread_policy_t)&tppolicy,
+ THREAD_STANDARD_POLICY_COUNT) != KERN_SUCCESS)
+ rv = 1;
+// a1logd(g_log, 8, "set_normal_priority: set to standard got %d\n",rv);
+ return rv;
+#endif /* NEVER */
+#else /* !APPLE */
+ struct sched_param param;
+ param.sched_priority = 0;
+ int rv;
+
+ rv = pthread_setschedparam(pthread_self(), SCHED_OTHER, &param);
+// a1logd(g_log, 8, "set_normal_priority: reset got %d\n",rv);
+ return rv;
+#endif /* !APPLE */
+}
+
+#endif /* NEVER */
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+static athread *beep_thread = NULL;
+static int beep_delay;
+static int beep_freq;
+static int beep_msec;
+
+/* Delayed beep handler */
+static int delayed_beep(void *pp) {
+ msec_sleep(beep_delay);
+#ifdef __APPLE__
+# if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050
+ AudioServicesPlayAlertSound(kUserPreferredAlert);
+# else
+ SysBeep((beep_msec * 60)/1000);
+# endif
+#else /* UNIX */
+ /* Linux is pretty lame in this regard... */
+ fprintf(stdout, "\a"); fflush(stdout);
+#endif
+ return 0;
+}
+
+/* Activate the system beeper */
+void msec_beep(int delay, int freq, int msec) {
+ if (delay > 0) {
+ if (beep_thread != NULL)
+ beep_thread->del(beep_thread);
+ beep_delay = delay;
+ beep_freq = freq;
+ beep_msec = msec;
+ if ((beep_thread = new_athread(delayed_beep, NULL)) == NULL)
+ a1logw(g_log, "msec_beep: Delayed beep failed to create thread\n");
+ } else {
+#ifdef __APPLE__
+# if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050
+ AudioServicesPlayAlertSound(kUserPreferredAlert);
+# else
+ SysBeep((msec * 60)/1000);
+# endif
+#else /* UNIX */
+ fprintf(stdout, "\a"); fflush(stdout);
+#endif
+ }
+}
+
+
+#ifdef NEVER
+/* If we're UNIX and running on X11, we could do this */
+/* sort of thing (from xset) to sound a beep, */
+/* IF we were linking with X11: */
+
+static void
+set_bell_vol(Display *dpy, int percent) {
+ XKeyboardControl values;
+ XKeyboardState kbstate;
+ values.bell_percent = percent;
+ if (percent == DEFAULT_ON)
+ values.bell_percent = SERVER_DEFAULT;
+ XChangeKeyboardControl(dpy, KBBellPercent, &values);
+ if (percent == DEFAULT_ON) {
+ XGetKeyboardControl(dpy, &kbstate);
+ if (!kbstate.bell_percent) {
+ values.bell_percent = -percent;
+ XChangeKeyboardControl(dpy, KBBellPercent, &values);
+ }
+ }
+ return;
+}
+
+static void
+set_bell_pitch(Display *dpy, int pitch) {
+ XKeyboardControl values;
+ values.bell_pitch = pitch;
+ XChangeKeyboardControl(dpy, KBBellPitch, &values);
+ return;
+}
+
+static void
+set_bell_dur(Display *dpy, int duration) {
+ XKeyboardControl values;
+ values.bell_duration = duration;
+ XChangeKeyboardControl(dpy, KBBellDuration, &values);
+ return;
+}
+
+XBell(..);
+
+#endif /* NEVER */
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Wait for the thread to exit. Return the result */
+static int athread_wait(struct _athread *p) {
+
+ if (p->finished)
+ return p->result;
+
+ pthread_join(p->thid, NULL);
+
+ return p->result;
+}
+
+/* Destroy the thread */
+static void athread_del(
+athread *p
+) {
+ DBG("athread_del called\n");
+
+ if (p == NULL)
+ return;
+
+ if (!p->finished) {
+ pthread_cancel(p->thid);
+ }
+ pthread_join(p->thid, NULL);
+ free(p);
+}
+
+static void *threadproc(
+ void *param
+) {
+ athread *p = (athread *)param;
+
+ /* Register this thread with the Objective-C garbage collector */
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
+ objc_registerThreadWithCollector();
+#endif
+
+ p->result = p->function(p->context);
+ p->finished = 1;
+
+ return 0;
+}
+
+
+athread *new_athread(
+ int (*function)(void *context),
+ void *context
+) {
+ int rv;
+ athread *p = NULL;
+
+ DBG("new_athread called\n");
+
+ if ((p = (athread *)calloc(sizeof(athread), 1)) == NULL) {
+ a1loge(g_log, 1, "new_athread: calloc failed\n");
+ return NULL;
+ }
+
+ p->function = function;
+ p->context = context;
+ p->wait = athread_wait;
+ p->del = athread_del;
+
+ /* Create a thread */
+ rv = pthread_create(&p->thid, NULL, threadproc, (void *)p);
+ if (rv != 0) {
+ a1loge(g_log, 1, "new_athread: pthread_create failed with %d\n",rv);
+ athread_del(p);
+ return NULL;
+ }
+
+ DBG("About to exit new_athread()\n");
+ return p;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Delete a file */
+void delete_file(char *fname) {
+ unlink(fname);
+}
+
+/* Given the path to a file, ensure that all the parent directories */
+/* are created. return nz on error */
+int create_parent_directories(char *path) {
+ struct stat sbuf;
+ char *pp = path;
+ mode_t mode = 0700; /* Default directory mode */
+
+ if (*pp == '/')
+ pp++; /* Skip root directory */
+ for (;pp != NULL && *pp != '\000';) {
+ if ((pp = strchr(pp, '/')) != NULL) {
+ *pp = '\000';
+ if (stat(path,&sbuf) != 0)
+ {
+ if (mkdir(path, mode) != 0)
+ return 1;
+ } else
+ mode = sbuf.st_mode;
+ *pp = '/';
+ pp++;
+ }
+ }
+ return 0;
+}
+
+#endif /* defined(UNIX) */
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - */
+#if defined(__APPLE__) || defined(NT)
+
+/* Thread to monitor and kill the named processes */
+static int th_kkill_nprocess(void *pp) {
+ kkill_nproc_ctx *ctx = (kkill_nproc_ctx *)pp;
+
+ while(ctx->stop == 0) {
+ kill_nprocess(ctx->pname, ctx->log);
+ msec_sleep(20); /* Don't hog the CPU */
+ }
+ ctx->done = 1;
+
+ return 0;
+}
+
+static void kkill_nprocess_del(kkill_nproc_ctx *p) {
+ int i;
+
+ p->stop = 1;
+
+ DBG("kkill_nprocess del called\n");
+ for (i = 0; p->done == 0 && i < 100; i++) {
+ msec_sleep(50);
+ }
+
+ if (p->done == 0) { /* Hmm */
+ a1logw(p->log,"kkill_nprocess del failed to stop - killing thread\n");
+ p->th->del(p->th);
+ }
+
+ del_a1log(p->log);
+ free(p);
+
+ DBG("kkill_nprocess del done\n");
+}
+
+/* Start a thread to constantly kill a process. */
+/* Call ctx->del() when done */
+kkill_nproc_ctx *kkill_nprocess(char **pname, a1log *log) {
+ kkill_nproc_ctx *p;
+
+ DBG("kkill_nprocess called\n");
+ if (log != NULL && log->debug >= 8) {
+ int i;
+ a1logv(log, 8, "kkill_nprocess called with");
+ for (i = 0; pname[i] != NULL; i++)
+ a1logv(log, 8, " '%s'",pname[i]);
+ a1logv(log, 8, "\n");
+ }
+
+ if ((p = (kkill_nproc_ctx *)calloc(sizeof(kkill_nproc_ctx), 1)) == NULL) {
+ a1loge(log, 1, "kkill_nprocess: calloc failed\n");
+ return NULL;
+ }
+
+ p->pname = pname;
+ p->log = new_a1log_d(log);
+ p->del = kkill_nprocess_del;
+
+ if ((p->th = new_athread(th_kkill_nprocess, p)) == NULL) {
+ del_a1log(p->log);
+ free(p);
+ return NULL;
+ }
+ return p;
+}
+
+#ifdef NT
+
+/* Kill a list of named process. */
+/* Kill the first process found, then return */
+/* return < 0 if this fails. */
+/* return 0 if there is no such process */
+/* return 1 if a process was killed */
+int kill_nprocess(char **pname, a1log *log) {
+ PROCESSENTRY32 entry;
+ HANDLE snapshot;
+ int j;
+
+ /* Get a snapshot of the current processes */
+ if ((snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0)) == NULL) {
+ return -1;
+ }
+
+ while(Process32Next(snapshot,&entry) != FALSE) {
+ HANDLE proc;
+
+ if (strcmp(entry.szExeFile, "spotread.exe") == 0
+ && (proc = OpenProcess(PROCESS_TERMINATE, FALSE, entry.th32ProcessID)) != NULL) {
+ if (TerminateProcess(proc,0) == 0) {
+ a1logv(log, 8, "kill_nprocess: Failed to kill '%s'\n",entry.szExeFile);
+ } else {
+ a1logv(log, 8, "kill_nprocess: Killed '%s'\n",entry.szExeFile);
+ }
+ CloseHandle(proc);
+ }
+ for (j = 0;; j++) {
+ if (pname[j] == NULL) /* End of list */
+ break;
+ a1logv(log, 8, "kill_nprocess: Checking process '%s' against list '%s'\n",
+ entry.szExeFile,pname[j]);
+ if (strcmp(entry.szExeFile,pname[j]) == 0) {
+ a1logv(log, 1, "kill_nprocess: killing process '%s' pid %d\n",
+ entry.szExeFile,entry.th32ProcessID);
+
+ if ((proc = OpenProcess(PROCESS_TERMINATE, FALSE, entry.th32ProcessID)) == NULL
+ || TerminateProcess(proc,0) == 0) {
+ a1logv(log, 1, "kill_nprocess: kill process '%s' failed with %d\n",
+ pname[j],GetLastError());
+ CloseHandle(proc);
+ CloseHandle(snapshot);
+ return -1;
+ }
+ CloseHandle(proc);
+ /* Stop on first one found ? */
+ CloseHandle(snapshot);
+ return 1;
+ }
+ }
+ }
+ CloseHandle(snapshot);
+ return 0;
+}
+
+#endif /* NT */
+
+#if defined(__APPLE__)
+
+/* Kill a list of named process. */
+/* Kill the first process found, then return */
+/* return < 0 if this fails. */
+/* return 0 if there is no such process */
+/* return 1 if a process was killed */
+int kill_nprocess(char **pname, a1log *log) {
+ struct kinfo_proc *procList = NULL;
+ size_t procCount = 0;
+ static int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
+ size_t length;
+ int rv = 0;
+ int i, j;
+
+ procList = NULL;
+ for (;;) {
+ int err;
+
+ /* Establish the amount of memory needed */
+ length = 0;
+ if (sysctl(name, (sizeof(name) / sizeof(*name)) - 1,
+ NULL, &length,
+ NULL, 0) == -1) {
+ DBGF((DBGA,"sysctl #1 failed with %d\n", errno));
+ return -1;
+ }
+
+ /* Add some more entries in case the number of processors changed */
+ length += 10 * sizeof(struct kinfo_proc);
+ if ((procList = malloc(length)) == NULL) {
+ DBGF((DBGA,"malloc failed for %d bytes\n", length));
+ return -1;
+ }
+
+ /* Call again with memory */
+ if ((err = sysctl(name, (sizeof(name) / sizeof(*name)) - 1,
+ procList, &length,
+ NULL, 0)) == -1) {
+ DBGF((DBGA,"sysctl #1 failed with %d\n", errno));
+ free(procList);
+ return -1;
+ }
+ if (err == 0) {
+ break;
+ } else if (err == ENOMEM) {
+ free(procList);
+ procList = NULL;
+ }
+ }
+
+ procCount = length / sizeof(struct kinfo_proc);
+
+ /* Locate the processes */
+ for (i = 0; i < procCount; i++) {
+ for (j = 0;; j++) {
+ if (pname[j] == NULL) /* End of list */
+ break;
+ a1logv(log, 8, "kill_nprocess: Checking process '%s' against list '%s'\n",
+ procList[i].kp_proc.p_comm,pname[j]);
+ if (strncmp(procList[i].kp_proc.p_comm,pname[j],MAXCOMLEN) == 0) {
+ a1logv(log, 1, "kill_nprocess: killing process '%s' pid %d\n",
+ pname[j],procList[i].kp_proc.p_pid);
+ if (kill(procList[i].kp_proc.p_pid, SIGTERM) != 0) {
+ a1logv(log, 1, "kill_nprocess: kill process '%s' failed with %d\n",
+ pname[j],errno);
+ free(procList);
+ return -1;
+ }
+ /* Stop on first one found ? */
+ free(procList);
+ return 1;
+ }
+ }
+ }
+ free(procList);
+ return rv;
+}
+
+#endif /* __APPLE__ */
+
+#endif /* __APPLE__ || NT */
+
+/* ============================================================= */
+
+/* A very small subset of icclib, copied to here. */
+/* This is just enough to support the standalone instruments */
+#ifdef SALONEINSTLIB
+
+sa_XYZNumber sa_D50 = {
+ 0.9642, 1.0000, 0.8249
+};
+
+void sa_SetUnity3x3(double mat[3][3]) {
+ int i, j;
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < 3; i++) {
+ if (i == j)
+ mat[j][i] = 1.0;
+ else
+ mat[j][i] = 0.0;
+ }
+ }
+
+}
+
+void sa_Cpy3x3(double dst[3][3], double src[3][3]) {
+ int i, j;
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < 3; i++)
+ dst[j][i] = src[j][i];
+ }
+}
+
+void sa_MulBy3x3(double out[3], double mat[3][3], double in[3]) {
+ double tt[3];
+
+ tt[0] = mat[0][0] * in[0] + mat[0][1] * in[1] + mat[0][2] * in[2];
+ tt[1] = mat[1][0] * in[0] + mat[1][1] * in[1] + mat[1][2] * in[2];
+ tt[2] = mat[2][0] * in[0] + mat[2][1] * in[1] + mat[2][2] * in[2];
+
+ out[0] = tt[0];
+ out[1] = tt[1];
+ out[2] = tt[2];
+}
+
+void sa_Mul3x3_2(double dst[3][3], double src1[3][3], double src2[3][3]) {
+ int i, j, k;
+ double td[3][3]; /* Temporary dest */
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < 3; i++) {
+ double tt = 0.0;
+ for (k = 0; k < 3; k++)
+ tt += src1[j][k] * src2[k][i];
+ td[j][i] = tt;
+ }
+ }
+
+ /* Copy result out */
+ for (j = 0; j < 3; j++)
+ for (i = 0; i < 3; i++)
+ dst[j][i] = td[j][i];
+}
+
+
+/* Matrix Inversion by Richard Carling from "Graphics Gems", Academic Press, 1990 */
+#define det2x2(a, b, c, d) (a * d - b * c)
+
+static void adjoint(
+double out[3][3],
+double in[3][3]
+) {
+ double a1, a2, a3, b1, b2, b3, c1, c2, c3;
+
+ /* assign to individual variable names to aid */
+ /* selecting correct values */
+
+ a1 = in[0][0]; b1 = in[0][1]; c1 = in[0][2];
+ a2 = in[1][0]; b2 = in[1][1]; c2 = in[1][2];
+ a3 = in[2][0]; b3 = in[2][1]; c3 = in[2][2];
+
+ /* row column labeling reversed since we transpose rows & columns */
+
+ out[0][0] = det2x2(b2, b3, c2, c3);
+ out[1][0] = - det2x2(a2, a3, c2, c3);
+ out[2][0] = det2x2(a2, a3, b2, b3);
+
+ out[0][1] = - det2x2(b1, b3, c1, c3);
+ out[1][1] = det2x2(a1, a3, c1, c3);
+ out[2][1] = - det2x2(a1, a3, b1, b3);
+
+ out[0][2] = det2x2(b1, b2, c1, c2);
+ out[1][2] = - det2x2(a1, a2, c1, c2);
+ out[2][2] = det2x2(a1, a2, b1, b2);
+}
+
+static double sa_Det3x3(double in[3][3]) {
+ double a1, a2, a3, b1, b2, b3, c1, c2, c3;
+ double ans;
+
+ a1 = in[0][0]; b1 = in[0][1]; c1 = in[0][2];
+ a2 = in[1][0]; b2 = in[1][1]; c2 = in[1][2];
+ a3 = in[2][0]; b3 = in[2][1]; c3 = in[2][2];
+
+ ans = a1 * det2x2(b2, b3, c2, c3)
+ - b1 * det2x2(a2, a3, c2, c3)
+ + c1 * det2x2(a2, a3, b2, b3);
+ return ans;
+}
+
+#define SA__SMALL_NUMBER 1.e-8
+
+int sa_Inverse3x3(double out[3][3], double in[3][3]) {
+ int i, j;
+ double det;
+
+ /* calculate the 3x3 determinant
+ * if the determinant is zero,
+ * then the inverse matrix is not unique.
+ */
+ det = sa_Det3x3(in);
+
+ if ( fabs(det) < SA__SMALL_NUMBER)
+ return 1;
+
+ /* calculate the adjoint matrix */
+ adjoint(out, in);
+
+ /* scale the adjoint matrix to get the inverse */
+ for (i = 0; i < 3; i++)
+ for(j = 0; j < 3; j++)
+ out[i][j] /= det;
+ return 0;
+}
+
+#undef SA__SMALL_NUMBER
+#undef det2x2
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Transpose a 3x3 matrix */
+void sa_Transpose3x3(double out[3][3], double in[3][3]) {
+ int i, j;
+ if (out != in) {
+ for (i = 0; i < 3; i++)
+ for (j = 0; j < 3; j++)
+ out[i][j] = in[j][i];
+ } else {
+ double tt[3][3];
+ for (i = 0; i < 3; i++)
+ for (j = 0; j < 3; j++)
+ tt[i][j] = in[j][i];
+ for (i = 0; i < 3; i++)
+ for (j = 0; j < 3; j++)
+ out[i][j] = tt[i][j];
+ }
+}
+
+/* Scale a 3 vector by the given ratio */
+void sa_Scale3(double out[3], double in[3], double rat) {
+ out[0] = in[0] * rat;
+ out[1] = in[1] * rat;
+ out[2] = in[2] * rat;
+}
+
+/* Clamp a 3 vector to be +ve */
+void sa_Clamp3(double out[3], double in[3]) {
+ int i;
+ for (i = 0; i < 3; i++)
+ out[i] = in[i] < 0.0 ? 0.0 : in[i];
+}
+
+/* Return the normal Delta E given two Lab values */
+double sa_LabDE(double *Lab0, double *Lab1) {
+ double rv = 0.0, tt;
+
+ tt = Lab0[0] - Lab1[0];
+ rv += tt * tt;
+ tt = Lab0[1] - Lab1[1];
+ rv += tt * tt;
+ tt = Lab0[2] - Lab1[2];
+ rv += tt * tt;
+
+ return sqrt(rv);
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - */
+/* A sub-set of ludecomp code from numlib */
+
+int sa_lu_decomp(double **a, int n, int *pivx, double *rip) {
+ int i, j;
+ double *rscale, RSCALE[10];
+
+ if (n <= 10)
+ rscale = RSCALE;
+ else
+ rscale = dvector(0, n-1);
+
+ for (i = 0; i < n; i++) {
+ double big;
+ for (big = 0.0, j=0; j < n; j++) {
+ double temp;
+ temp = fabs(a[i][j]);
+ if (temp > big)
+ big = temp;
+ }
+ if (fabs(big) <= DBL_MIN) {
+ if (rscale != RSCALE)
+ free_dvector(rscale, 0, n-1);
+ return 1;
+ }
+ rscale[i] = 1.0/big;
+ }
+
+ for (*rip = 1.0, j = 0; j < n; j++) {
+ double big;
+ int k, bigi = 0;
+
+ for (i = 0; i < j; i++) {
+ double sum;
+ sum = a[i][j];
+ for (k = 0; k < i; k++)
+ sum -= a[i][k] * a[k][j];
+ a[i][j] = sum;
+ }
+
+ for (big = 0.0, i = j; i < n; i++) {
+ double sum, temp;
+ sum = a[i][j];
+ for (k = 0; k < j; k++)
+ sum -= a[i][k] * a[k][j];
+ a[i][j] = sum;
+ temp = rscale[i] * fabs(sum);
+ if (temp >= big) {
+ big = temp;
+ bigi = i;
+ }
+ }
+
+ if (j != bigi) {
+ {
+ double *temp;
+ temp = a[bigi];
+ a[bigi] = a[j];
+ a[j] = temp;
+ }
+ *rip = -(*rip);
+ rscale[bigi] = rscale[j];
+ }
+
+ pivx[j] = bigi;
+ if (fabs(a[j][j]) <= DBL_MIN) {
+ if (rscale != RSCALE)
+ free_dvector(rscale, 0, n-1);
+ return 1;
+ }
+
+ if (j != (n-1)) {
+ double temp;
+ temp = 1.0/a[j][j];
+ for (i = j+1; i < n; i++)
+ a[i][j] *= temp;
+ }
+ }
+ if (rscale != RSCALE)
+ free_dvector(rscale, 0, n-1);
+ return 0;
+}
+
+void sa_lu_backsub(double **a, int n, int *pivx, double *b) {
+ int i, j;
+ int nvi;
+
+ for (nvi = -1, i = 0; i < n; i++) {
+ int px;
+ double sum;
+
+ px = pivx[i];
+ sum = b[px];
+ b[px] = b[i];
+ if (nvi >= 0) {
+ for (j = nvi; j < i; j++)
+ sum -= a[i][j] * b[j];
+ } else {
+ if (sum != 0.0)
+ nvi = i;
+ }
+ b[i] = sum;
+ }
+
+ for (i = (n-1); i >= 0; i--) {
+ double sum;
+ sum = b[i];
+ for (j = i+1; j < n; j++)
+ sum -= a[i][j] * b[j];
+ b[i] = sum/a[i][i];
+ }
+}
+
+int sa_lu_invert(double **a, int n) {
+ int i, j;
+ double rip;
+ int *pivx, PIVX[10];
+ double **y;
+
+ if (n <= 10)
+ pivx = PIVX;
+ else
+ pivx = ivector(0, n-1);
+
+ if (sa_lu_decomp(a, n, pivx, &rip)) {
+ if (pivx != PIVX)
+ free_ivector(pivx, 0, n-1);
+ return 1;
+ }
+
+ y = dmatrix(0, n-1, 0, n-1);
+ for (i = 0; i < n; i++) {
+ for (j = 0; j < n; j++) {
+ y[i][j] = a[i][j];
+ }
+ }
+
+ for (i = 0; i < n; i++) {
+ for (j = 0; j < n; j++)
+ a[i][j] = 0.0;
+ a[i][i] = 1.0;
+ sa_lu_backsub(y, n, pivx, a[i]);
+ }
+
+ free_dmatrix(y, 0, n-1, 0, n-1);
+ if (pivx != PIVX)
+ free_ivector(pivx, 0, n-1);
+
+ return 0;
+}
+
+int sa_lu_psinvert(double **out, double **in, int m, int n) {
+ int rv = 0;
+ double **tr;
+ double **sq;
+
+ tr = dmatrix(0, n-1, 0, m-1);
+ matrix_trans(tr, in, m, n);
+
+ if (m > n) {
+ sq = dmatrix(0, n-1, 0, n-1);
+ if ((rv = matrix_mult(sq, n, n, tr, n, m, in, m, n)) == 0) {
+ if ((rv = sa_lu_invert(sq, n)) == 0) {
+ rv = matrix_mult(out, n, m, sq, n, n, tr, n, m);
+ }
+ }
+ free_dmatrix(sq, 0, n-1, 0, n-1);
+ } else {
+ sq = dmatrix(0, m-1, 0, m-1);
+ if ((rv = matrix_mult(sq, m, m, in, m, n, tr, n, m)) == 0) {
+ if ((rv = sa_lu_invert(sq, m)) == 0) {
+ rv = matrix_mult(out, n, m, tr, n, m, sq, m, m);
+ }
+ }
+ free_dmatrix(sq, 0, m-1, 0, m-1);
+ }
+
+ free_dmatrix(tr, 0, n-1, 0, m-1);
+ return rv;
+}
+
+
+#endif /* SALONEINSTLIB */
+/* ============================================================= */
+
+
diff --git a/spectro/conv.h b/spectro/conv.h
new file mode 100644
index 0000000..ab5e850
--- /dev/null
+++ b/spectro/conv.h
@@ -0,0 +1,297 @@
+#ifndef CONV_H
+
+/*
+ * Some system dependent comvenience functions.
+ * Implemented in unixio.c and ntio.c
+ */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 2008/2/9
+ *
+ * Copyright 1996 - 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.
+ *
+ * Derived from icoms.h
+ */
+
+#if defined (NT)
+# if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0501
+# if defined _WIN32_WINNT
+# undef _WIN32_WINNT
+# endif
+# define _WIN32_WINNT 0x0501
+# endif
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+# include <io.h>
+#endif
+
+#if defined (UNIX) || defined(__APPLE__)
+# include <unistd.h>
+# include <glob.h>
+# include <pthread.h>
+#endif
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/* - - - - - - - - - - - - - - - - - - -- */
+/* System compatibility #defines */
+#if defined (NT)
+
+#ifndef sys_stat
+# define sys_stat _stat
+#endif
+#ifndef sys_mkdir
+# define sys_mkdir _mkdir
+#endif
+#ifndef sys_read
+# define sys_read _read
+#endif
+#ifndef sys_utime
+# define sys_utime _utime
+# define sys_utimbuf _utimbuf
+#endif
+#ifndef sys_access
+# define sys_access _access
+#endif
+
+#ifndef snprintf
+# define snprintf _snprintf
+# define vsnprintf _vsnprintf
+#endif
+#ifndef stricmp
+# define stricmp _stricmp
+#endif
+
+#endif /* NT */
+
+#if defined (UNIX)
+
+#ifndef sys_stat
+# define sys_stat stat
+#endif
+#ifndef sys_mkdir
+# define sys_mkdir mkdir
+#endif
+#ifndef sys_read
+# define sys_read read
+#endif
+#ifndef sys_utime
+# define sys_utime utime
+# define sys_utimbuf utimbuf
+#endif
+#ifndef sys_access
+# define sys_access access
+#endif
+
+#ifndef stricmp
+# define stricmp strcasecmp
+#endif
+
+#endif /* UNIX */
+
+/* - - - - - - - - - - - - - - - - - - -- */
+/* System dependent convenience functions */
+
+/* wait for and then return the next character from the keyboard */
+/* (If not_interactive, return getchar()) */
+int next_con_char(void);
+
+/* If there is one, return the next character from the keyboard, else return 0 */
+/* (If not_interactive, always returns 0) */
+int poll_con_char(void);
+
+/* Empty the console of any pending characters */
+/* (If not_interactive, does nothing) */
+void empty_con_chars(void);
+
+/* Sleep for the given number of msec */
+void msec_sleep(unsigned int msec);
+
+/* Return the current time in msec since */
+/* the first invokation of msec_time() */
+unsigned int msec_time();
+
+/* Return the current time in usec */
+/* the first invokation of usec_time() */
+double usec_time();
+
+/* Activate the system beeper after a delay */
+/* (Note frequancy and duration may not be honoured on all systems) */
+void msec_beep(int delay, int freq, int msec);
+
+void normal_beep(); /* Emit a "normal" beep */
+void good_beep(); /* Emit a "good" beep */
+void bad_beep(); /* Emit a "bad" double beep */
+
+/* - - - - - - - - - - - - - - - - - - -- */
+
+#ifdef NEVER /* Not currently needed, or effective */
+
+/* Set the current threads priority */
+/* return nz if this fails */
+int set_interactive_priority();
+
+int set_normal_priority();
+
+#endif /* NEVER */
+
+/* - - - - - - - - - - - - - - - - - - -- */
+/* An Argyll mutex */
+
+/* amutex_trylock() returns nz if it can't lock the mutex */
+
+#ifdef NT
+# define amutex CRITICAL_SECTION
+# define amutex_init(lock) InitializeCriticalSection(&(lock))
+# define amutex_del(lock) DeleteCriticalSection(&(lock))
+# define amutex_lock(lock) EnterCriticalSection(&(lock))
+# define amutex_trylock(lock) (!TryEnterCriticalSection(&(lock)))
+# define amutex_unlock(lock) LeaveCriticalSection(&(lock))
+#endif
+
+#ifdef UNIX
+# define amutex pthread_mutex_t
+# define amutex_init(lock) pthread_mutex_init(&(lock), NULL)
+# define amutex_del(lock) pthread_mutex_destroy(&(lock))
+# define amutex_lock(lock) pthread_mutex_lock(&(lock))
+# define amutex_trylock(lock) pthread_mutex_trylock(&(lock))
+# define amutex_unlock(lock) pthread_mutex_unlock(&(lock))
+#endif
+
+/* - - - - - - - - - - - - - - - - - - -- */
+
+/* An Argyll thread. */
+struct _athread {
+#if defined (NT)
+ HANDLE th; /* Thread */
+#endif
+#if defined (UNIX) || defined(__APPLE__)
+ pthread_t thid; /* Thread ID */
+#endif
+ int finished; /* Set when the thread returned */
+ int result; /* Return code from thread function */
+
+ /* Thread function to call */
+ int (*function)(void *context);
+
+ /* And the context to call it with */
+ void *context;
+
+ /* Wait for the thread to exit. Return the result */
+ int (*wait)(struct _athread *p);
+
+ /* Kill the thread and delete the object */
+ /* (Killing it may have side effects, so this is a last */
+ /* resort if the thread hasn't exited) */
+ void (*del)(struct _athread *p);
+
+}; typedef struct _athread athread;
+
+/* Create and start a thread */
+/* Thread function should only return on completion or error. */
+/* It should return 0 on completion or exit, nz on error. */
+athread *new_athread(int (*function)(void *context), void *context);
+
+
+/* - - - - - - - - - - - - - - - - - - -- */
+
+/* Delete a file */
+void delete_file(char *fname);
+
+/* Given the path to a file, ensure that all the parent directories */
+/* are created. return nz on error */
+int create_parent_directories(char *path);
+
+/* - - - - - - - - - - - - - - - - - - -- */
+
+struct _kkill_nproc_ctx {
+ athread *th;
+ char **pname;
+ a1log *log;
+ int stop;
+ int done;
+ void (*del)(struct _kkill_nproc_ctx *p);
+}; typedef struct _kkill_nproc_ctx kkill_nproc_ctx;
+
+#if defined(__APPLE__) || defined(NT)
+
+/* Kill a list of named processes. NULL for last */
+/* return < 0 if this fails. */
+/* return 0 if there is no such process */
+/* return 1 if a process was killed */
+int kill_nprocess(char **pname, a1log *log);
+
+/* Start a thread to constantly kill a process. */
+/* Call ctx->del() when done */
+kkill_nproc_ctx *kkill_nprocess(char **pname, a1log *log);
+
+#endif /* __APPLE__ || NT */
+
+#include "xdg_bds.h"
+
+/* - - - - - - - - - - - - - - - - - - -- */
+/* A very small subset of icclib */
+#ifdef SALONEINSTLIB
+
+typedef struct {
+ double X;
+ double Y;
+ double Z;
+} sa_XYZNumber;
+
+typedef enum {
+ sa_SigXYZData = 0x58595A20L, /* 'XYZ ' */
+ sa_SigLabData = 0x4C616220L /* 'Lab ' */
+} sa_ColorSpaceSignature;
+
+extern sa_XYZNumber sa_D50;
+void sa_SetUnity3x3(double mat[3][3]);
+void sa_Cpy3x3(double out[3][3], double mat[3][3]);
+void sa_MulBy3x3(double out[3], double mat[3][3], double in[3]);
+void sa_Mul3x3_2(double dst[3][3], double src1[3][3], double src2[3][3]);
+int sa_Inverse3x3(double out[3][3], double in[3][3]);
+void sa_Transpose3x3(double out[3][3], double in[3][3]);
+void sa_Scale3(double out[3], double in[3], double rat);
+double sa_LabDE(double *in0, double *in1);
+
+
+#define icmXYZNumber sa_XYZNumber
+#define icColorSpaceSignature sa_ColorSpaceSignature
+#define icSigXYZData sa_SigXYZData
+#define icSigLabData sa_SigLabData
+#define icmD50 sa_D50
+#define icmSetUnity3x3 sa_SetUnity3x3
+#define icmCpy3x3 sa_Cpy3x3
+#define icmMulBy3x3 sa_MulBy3x3
+#define icmMul3x3_2 sa_Mul3x3_2
+#define icmInverse3x3 sa_Inverse3x3
+#define icmTranspose3x3 sa_Transpose3x3
+#define icmScale3 sa_Scale3
+#define icmClamp3 sa_Clamp3
+#define icmLabDE sa_LabDE
+
+/* A subset of numlib */
+
+int sa_lu_psinvert(double **out, double **in, int m, int n);
+
+#define lu_psinvert sa_lu_psinvert
+
+#endif /* SALONEINSTLIB */
+/* - - - - - - - - - - - - - - - - - - -- */
+
+
+#ifdef __cplusplus
+ }
+#endif
+
+#define CONV_H
+#endif /* CONV_H */
diff --git a/spectro/dispcal.c b/spectro/dispcal.c
new file mode 100644
index 0000000..accb79a
--- /dev/null
+++ b/spectro/dispcal.c
@@ -0,0 +1,5356 @@
+
+/*
+ * Argyll Color Correction System
+ * Display callibrator.
+ *
+ * Author: Graeme W. Gill
+ * Date: 14/10/2005
+ *
+ * Copyright 1996 - 2013 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/* This program displays test patches, and takes readings from a display device */
+/* in order to create a RAMDAC calibration curve (usually stored in the ICC vcgt tag) */
+
+/* This is the third version of the program. */
+
+/* TTBD
+
+ Try to improve calibration speed by using adaptive
+ measurement set, rather than fixed resolution doubling ?
+ (ie. just measure at troublesome points using a "divide in half"
+ strategy ?. Estimate error between measurement points and
+ pick the next largest error.)
+
+ Add option to use L*u*v* DE's, as this is used in
+ some video standards. They sometime use u*v* as
+ a color tollerance too (see EBU TECH 3320).
+
+ Add a white point option that makes the target the
+ closest temperature to the native one of the display :-
+ ie. it moves the display to the closest point on the
+ chosen locus to RGB 1,1,1.
+ ie. should it do this if "-t" or "-T"
+ with no specific temperature is chosen ?
+
+ Change white point gamut clipping to be a measurement
+ search rather than computing from primary XYZ ?
+
+ Add bell at end of calibration ?
+
+
+ Add option to plot graph of native and calibrated RGB ?
+
+ Add a "delta E" number to the interactive adjustments,
+ so the significance of the error can be judged ?
+
+ Need to add flare measure/subtract, to improve
+ projector calibration ? - need to add to dispread too.
+
+ Instead of measuring/matching output at 50% device input as
+ measure of gamma, what about inverting it - measure/match device
+ values at 50% perceptual (18%) output value ?
+ [ Hmm. Current method is OK because a good perceptual
+ display gives about 18% output at 50% device input.]
+
+
+ The verify (-E) may not be being done correctly.
+ Like update, shouldn't it read the .cal file to set what's
+ being calibrated aganist ? (This would fix missing ambient value too!)
+
+ What about the "Read the base test set" - aren't
+ there numbers then used to tweak the black aim point
+ in "Figure out the black point target" - Yes they are !!
+ Verify probably shouldn't work this way.
+
+ */
+
+#ifdef __MINGW32__
+# define WINVER 0x0500
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/types.h>
+#include <time.h>
+#include <string.h>
+#include <stdarg.h>
+#if defined (NT)
+#include <conio.h>
+#endif
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "xicc.h"
+#include "xspect.h"
+#include "xcolorants.h"
+#include "ccmx.h"
+#include "ccss.h"
+#include "cgats.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "inst.h"
+#include "dispwin.h"
+#include "dispsup.h"
+#include "rspl.h"
+#include "moncurve.h"
+#include "targen.h"
+#include "ofps.h"
+#include "icc.h"
+#include "sort.h"
+#include "instappsup.h"
+#include "spyd2setup.h" /* Enable Spyder 2 access */
+
+#undef DEBUG
+#undef DEBUG_OFFSET /* Keep test window out of the way */
+#undef DEBUG_PLOT /* Plot curve each time around */
+#undef CHECK_MODEL /* Do readings to check the accuracy of our model */
+#undef SHOW_WINDOW_ONFAKE /* Display a test window up for a fake device */
+
+/* Invoke with -dfake for testing with a fake device. */
+/* Will use a fake.icm/.icc profile if present, or a built in fake */
+/* device behaviour if not. */
+
+#define COMPORT 1 /* Default com port 1..4 */
+#define OPTIMIZE_MODEL /* Adjust model for best fit */
+#define REFINE_GAIN 0.80 /* Refinement correction damping/gain */
+#define MAX_RPTS 12 /* Maximum tries at making a sample meet the current threshold */
+#define VER_RES 100 /* Verification resolution */
+#define NEUTRAL_BLEND_RATE 4.0 /* Default rate of transition for -k factor < 1.0 (power) */
+#define ADJ_JACOBIAN /* Adjust the Jacobian predictor matrix each time */
+#define JAC_COR_FACT 0.4 /* Amount to correct Jacobian by (to filter noise) */
+#define REMEAS_JACOBIAN /* Re-measure Jacobian */
+#define MOD_DIST_POW 1.6 /* Power used to distribute test samples for model building */
+#define REFN_DIST_POW 1.6 /* Power used to distribute test samples for grey axis refinement */
+#define CHECK_DIST_POW 1.6 /* Power used to distribute test samples for grey axis checking */
+#define THRESH_SCALE_POW 0.5 /* Amount to loosen threshold for first itterations */
+#define CAL_RES 256 /* Resolution of calibration table to produce. */
+#define CLIP /* Clip RGB during refinement */
+#define RDAC_SMOOTH 0.3 /* RAMDAC curve fitting smoothness */
+#define MEAS_RES /* Measure the RAMNDAC entry size */
+
+#ifdef DEBUG_PLOT
+#include "plot.h"
+#endif
+
+#if defined(DEBUG)
+
+#define DBG(xxx) fprintf xxx ;
+#define dbgo stderr
+#else
+#define DBG(xxx)
+#endif /* DEBUG */
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Sample points used in initial device model optimisation */
+
+typedef struct {
+ double dev[3]; /* Device values */
+ double lab[3]; /* Read value */
+ double w; /* Weighting */
+} optref;
+
+/* - - - - - - - - - - - - - - - - - - - */
+/* device RGB inverse solution code */
+
+/* Selected transfer curve */
+typedef enum {
+ gt_power = 0, /* A simple power */
+ gt_Lab = 1, /* The L* curve */
+ gt_sRGB = 2, /* The sRGB curve */
+ gt_Rec709 = 3, /* REC 709 video standard */
+ gt_SMPTE240M = 4 /* SMTPE 240M video standard */
+} gammatype;
+
+/* Context for calibration solution */
+typedef struct {
+ double wh[3]; /* White absolute XYZ value */
+ double bk[3]; /* Black absolute XYZ value */
+
+ /* Target model */
+ gammatype gammat; /* Transfer curve type */
+ double egamma; /* Effective Gamma target */
+ double oofff; /* proportion of output offset vs input offset (default 1.0) */
+ double gioff; /* Gamma curve input zero offset */
+ double gooff; /* Target output offset (normalised to Y max of 1.0) */
+ int nat; /* Flag - nz if native white target */
+ double nbrate; /* Neutral blend weight (power) */
+
+ /* Viewing conditions adjustment */
+ int vc; /* Flag, nz to enable viewing conditions adjustment */
+ icxcam *svc; /* Source viewing conditions */
+ icxcam *dvc; /* Destination viewing conditions */
+ double vn0, vn1; /* Normalisation values */
+
+ double nwh[3]; /* Target white normalised XYZ value (Y = 1.0) */
+ double twh[3]; /* Target white absolute XYZ value */
+ icmXYZNumber twN; /* Same as above as XYZNumber */
+
+ double tbk[3]; /* Target black point color */
+ icmXYZNumber tbN; /* Same as above as XYZNumber */
+
+ /* Device model */
+ double fm[3][3]; /* Forward, aprox. linear RGB -> XYZ */
+ double bm[3][3]; /* Backwards, aprox. XYZ -> linear RGB */
+ mcv *dcvs[3]; /* Device RGB channel to linearised RGB curves */
+ /* These are always normalized to map 1.0 to 1.0 */
+
+ /* Current state */
+ mcv *rdac[3]; /* Current RGB to RGB ramdac curves */
+
+ double xyz[3]; /* Target xyz value */
+
+ /* optimisation information */
+ int np; /* Total number of optimisation parameters */
+ int co[3]; /* Offset in the parameters to each curve offset */
+ int nc[3]; /* Number of for each curve */
+ int nrp; /* Total number of reference points */
+ optref *rp; /* reference points */
+ double *dtin_iv; /* Temporary array :- dp for input curves */
+} calx;
+
+/* - - - - - - - - - - - - - - - - - - - */
+/* Ideal target curve definitions */
+
+/* Convert ideal device (0..1) to target Y value (0..1) */
+static double dev2Y(calx *x, double egamma, double vv) {
+
+ switch(x->gammat) {
+ case gt_power: {
+ vv = pow(vv, egamma);
+ break;
+ }
+ case gt_Lab: {
+ vv = icmL2Y(vv * 100.0);
+ break;
+ }
+ case gt_sRGB: {
+ if (vv <= 0.03928)
+ vv = vv/12.92;
+ else
+ vv = pow((0.055 + vv)/1.055, 2.4);
+ break;
+ }
+ case gt_Rec709: {
+ if (vv <= 0.081)
+ vv = vv/4.5;
+ else
+ vv = pow((0.099 + vv)/1.099, 1.0/0.45);
+ break;
+ }
+ case gt_SMPTE240M: {
+ if (vv <= 0.0913)
+ vv = vv/4.0;
+ else
+ vv = pow((0.1115 + vv)/1.1115, 1.0/0.45);
+ break;
+ }
+ default:
+ error("Unknown gamma type");
+ }
+ return vv;
+}
+
+/* Convert target Y value (0..1) to ideal device (0..1) */
+static double Y2dev(calx *x, double egamma, double vv) {
+
+ switch(x->gammat) {
+ case gt_power: {
+ vv = pow(vv, 1.0/egamma);
+ break;
+ }
+ case gt_Lab: {
+ vv = icmY2L(vv) * 0.01;
+ break;
+ }
+ case gt_sRGB: {
+ if (vv <= 0.00304)
+ vv = vv * 12.92;
+ else
+ vv = pow(vv, 1.0/2.4) * 1.055 - 0.055;
+ break;
+ }
+ case gt_Rec709: {
+ if (vv <= 0.018)
+ vv = vv * 4.5;
+ else
+ vv = pow(vv, 0.45) * 1.099 - 0.099;
+ break;
+ }
+ case gt_SMPTE240M: {
+ if (vv <= 0.0228)
+ vv = vv * 4.0;
+ else
+ vv = pow(vv, 0.45) * 1.1115 - 0.1115;
+ break;
+ }
+ default:
+ error("Unknown gamma type");
+ }
+ return vv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - */
+/* Compute a viewing environment Y transform */
+
+static double view_xform(calx *x, double in) {
+ double out = in;
+
+ if (x->vc != 0) {
+ double xyz[3], Jab[3];
+
+ xyz[0] = in * x->nwh[0]; /* Compute value on neutral axis */
+ xyz[1] = in * x->nwh[1];
+ xyz[2] = in * x->nwh[2];
+ x->svc->XYZ_to_cam(x->svc, Jab, xyz);
+ x->dvc->cam_to_XYZ(x->dvc, xyz, Jab);
+
+ out = xyz[1] * x->vn1 + x->vn0; /* Apply scaling factors */
+ }
+ return out;
+}
+
+/* - - - - - - - - - - - - - - - - - - - */
+
+/* Info for optimization */
+typedef struct {
+ double thyr; /* 50% input target */
+ double roo; /* 0% input target */
+} gam_fits;
+
+/* gamma + input offset function handed to powell() */
+static double gam_fit(void *dd, double *v) {
+ gam_fits *gf = (gam_fits *)dd;
+ double gamma = v[0];
+ double ioff = v[1];
+ double rv = 0.0;
+ double tt;
+
+ if (gamma < 0.0) {
+ rv += 100.0 * -gamma;
+ gamma = 0.0;
+ }
+ if (ioff < 0.0) {
+ rv += 100.0 * -ioff;
+ ioff = 0.0;
+ } else if (ioff > 0.999) {
+ rv += 100.0 * (ioff - 0.999);
+ ioff = 0.999;
+ }
+ tt = gf->roo - pow(ioff, gamma);
+ rv += tt * tt;
+ tt = gf->thyr - pow(0.5 + (1.0 - 0.5) * ioff, gamma);
+ rv += tt * tt;
+
+//printf("~1 gam_fit %f %f returning %f\n",ioff,gamma,rv);
+ return rv;
+}
+
+
+/* Given the advertised gamma and the output offset, compute the */
+/* effective gamma and input offset needed. */
+/* Return the expected output value for 50% input. */
+/* (It's assumed that gooff is normalised the target brightness) */
+static double tech_gamma(
+ calx *x,
+ double *pegamma, /* return effective gamma needed */
+ double *pooff, /* return output offset needed */
+ double *pioff, /* return input offset needed */
+ double egamma, /* effective gamma needed (> 0.0 if valid, overrides gamma) */
+ double gamma, /* advertised gamma needed */
+ double tooff /* Total ouput offset needed */
+) {
+ int i;
+ double rv;
+ double gooff = 0.0; /* The output offset applied */
+ double gioff = 0.0; /* The input offset applied */
+ double roo; /* Remaining output offset accounted for by input offset */
+
+ /* Compute the output offset that will be applied */
+ gooff = tooff * x->oofff;
+ roo = (tooff - gooff)/(1.0 - gooff);
+
+//printf("~1 gooff = %f, roo = %f\n",gooff,roo);
+
+ /* Now compute the input offset that will be needed */
+ if (x->gammat == gt_power && egamma <= 0.0) {
+ gam_fits gf;
+ double op[2], sa[2], rv;
+
+ gf.thyr = pow(0.5, gamma); /* Advetised 50% target */
+ gf.thyr = (gf.thyr - gooff)/(1.0 - gooff); /* Target before gooff is added */
+ gf.roo = roo;
+
+ op[0] = gamma;
+ op[1] = pow(roo, 1.0/gamma);
+ sa[0] = 0.1;
+ sa[1] = 0.01;
+
+ if (powell(&rv, 2, op, sa, 1e-6, 500, gam_fit, (void *)&gf, NULL, NULL) != 0)
+ warning("Computing effective gamma and input offset is inaccurate");
+
+ if (rv > 1e-5) {
+ warning("Computing effective gamma and input offset is inaccurate (%f)",rv);
+ }
+ egamma = op[0];
+ gioff = op[1];
+
+//printf("~1 Result gioff %f, gooff %f, egamma %f\n",gioff, gooff, egamma);
+//printf("~1 Verify 0.0 in -> out = %f, tooff = %f\n",gooff + dev2Y(x, egamma, gioff) * (1.0 - gooff),tooff);
+//printf("~1 Verify 0.5 out = %f, target %f\n",gooff + dev2Y(x, egamma, gioff + 0.5 * (1.0 - gioff)) * (1.0 - gooff), pow(0.5, gamma));
+
+ } else {
+ gioff = Y2dev(x, egamma, roo);
+//printf("~1 Result gioff %f, gooff %f\n",gioff, gooff);
+//printf("~1 Verify 0.0 in -> out = %f, tooff = %f\n",gooff + dev2Y(x, egamma, gioff) * (1.0 - gooff),tooff);
+ }
+
+ /* Compute the 50% output value */
+ rv = gooff + dev2Y(x, egamma, gioff + 0.5 * (1.0 - gioff)) * (1.0 - gooff);
+
+ if (pegamma != NULL)
+ *pegamma = egamma;
+ if (pooff != NULL)
+ *pooff = gooff;
+ if (pioff != NULL)
+ *pioff = gioff;
+ return rv;
+}
+
+/* Compute approximate advertised gamma from black/50% grey/white readings, */
+/* (assumes a zero based gamma curve shape) */
+static double pop_gamma(double bY, double gY, double wY) {
+ int i;
+ double grat, brat, gioff, gvv, gamma;
+
+ grat = gY/wY;
+ brat = bY/wY;
+
+ gamma = log(grat) / log(0.5);
+ return gamma;
+}
+
+/* - - - - - - - - - - - - - - - - - - - */
+
+/* Return the xyz that is predicted by our aproximate device model */
+/* by the given device RGB. */
+static void fwddev(calx *x, double xyz[3], double rgb[3]) {
+ double lrgb[3];
+ int j;
+
+//printf("~1 fwddev called with rgb %f %f %f\n",rgb[0],rgb[1],rgb[2]);
+
+ /* Convert device RGB into linear light RGB via curves */
+ for (j = 0; j < 3; j++)
+ lrgb[j] = x->dcvs[j]->interp(x->dcvs[j], rgb[j]);
+
+//printf("~1 fwddev got linear RGB %f %f %f\n",lrgb[0],lrgb[1],lrgb[2]);
+
+ /* Convert linear light RGB into XYZ via the matrix */
+ icmMulBy3x3(xyz, x->fm, lrgb);
+
+//printf("~1 fwddev got final xyz %f %f %f\n",xyz[0],xyz[1],xyz[2]);
+}
+
+/* Return the closest device RGB predicted by our aprox. device model */
+/* to generate the given xyz. */
+static void invdev(calx *x, double rgb[3], double xyz[3]) {
+ double lrgb[3];
+ int j;
+
+//printf("~1 invdev called with xyz %f %f %f\n",xyz[0],xyz[1],xyz[2]);
+
+ /* Convert XYZ to linear light RGB via the inverse matrix */
+ icmMulBy3x3(lrgb, x->bm, xyz);
+//printf("~1 invdev; lin light rgb = %f %f %f\n",lrgb[0],lrgb[1],lrgb[2]);
+
+ /* Convert linear light RGB to device RGB via inverse curves */
+ for (j = 0; j < 3; j++) {
+ lrgb[j] = x->dcvs[j]->inv_interp(x->dcvs[j], lrgb[j]);
+ if (lrgb[j] < 0.0) {
+#ifdef CLIP
+ lrgb[j] = 0.0;
+#endif
+ } else if (lrgb[j] > 1.0) {
+#ifdef CLIP
+ lrgb[j] = 1.0;
+#endif
+ }
+ }
+//printf("~1 invdev; inverse curves rgb = %f %f %f\n",lrgb[0],lrgb[1],lrgb[2]);
+ if (rgb != NULL) {
+ rgb[0] = lrgb[0];
+ rgb[1] = lrgb[1];
+ rgb[2] = lrgb[2];
+ }
+}
+
+/* Return the closest linear device RGB predicted by our aprox. device matrix */
+/* to generate the given xyz. */
+/* Return > 0 if clipped */
+static double invlindev(calx *x, double rgb[3], double xyz[3]) {
+ double lrgb[3];
+ double clip = 0.0;
+ int j;
+
+//printf("~1 invlindev called with xyz %f %f %f\n",xyz[0],xyz[1],xyz[2]);
+
+ /* Convert XYZ to linear light RGB via the inverse matrix */
+ icmMulBy3x3(lrgb, x->bm, xyz);
+//printf("~1 invlindev; lin light rgb = %f %f %f\n",lrgb[0],lrgb[1],lrgb[2]);
+
+ /* Check for out of gamut */
+ for (j = 0; j < 3; j++) {
+ if (lrgb[j] < 0.0) {
+ if (-lrgb[j] > clip)
+ clip = -lrgb[j];
+ lrgb[j] = 0.0;
+ } else if (lrgb[j] > 1.0) {
+ if ((lrgb[j]-1.0) > clip)
+ clip = (lrgb[j]-1.0);
+ lrgb[j] = 1.0;
+ }
+ }
+//printf("~1 invlindev; clipped rgb = %f %f %f, clip = %f \n",lrgb[0],lrgb[1],lrgb[2],clip);
+ if (rgb != NULL) {
+ rgb[0] = lrgb[0];
+ rgb[1] = lrgb[1];
+ rgb[2] = lrgb[2];
+ }
+ return clip;
+}
+
+/* Overall optimisation support */
+
+/* Set the optimsation parameter number and offset values in calx, */
+/* and return an array filled in with the current parameters. */
+/* Allocate temporary arrays */
+static double *dev_get_params(calx *x) {
+ double *p, *tp;
+ int i, j;
+
+ x->np = 9;
+ for (i = 0; i < 3; i++)
+ x->np += x->dcvs[i]->luord;
+
+ if ((p = (double *)malloc(x->np * sizeof(double))) == NULL)
+ error("dev_params malloc failed");
+
+ tp = p;
+
+ for (i = 0; i < 3; i++)
+ for (j = 0; j < 3; j++)
+ *tp++ = x->fm[i][j];
+
+ for (i = 0; i < 3; i++) {
+ x->co[i] = tp - p; /* Offset to start */
+ for (j = 0; j < x->dcvs[i]->luord; j++)
+ *tp++ = x->dcvs[i]->pms[j];
+ x->nc[i] = (tp - p) - x->co[i]; /* Number */
+ }
+
+ if ((x->dtin_iv = (double *)malloc(x->np * sizeof(double))) == NULL)
+ error("dev_params malloc failed");
+
+ return p;
+}
+
+/* Given a set of parameters, put them back into the model */
+/* Normalize them so that the curve maximum is 1.0 too. */
+static void dev_put_params(calx *x, double *p) {
+ int i, j;
+ double scale[3];
+
+ for (i = 0; i < 3; i++)
+ for (j = 0; j < 3; j++)
+ x->fm[i][j] = *p++;
+ for (i = 0; i < 3; i++)
+ for (j = 0; j < x->dcvs[i]->luord; j++)
+ x->dcvs[i]->pms[j] = *p++;
+
+ /* Figure out how we have to scale the curves */
+ for (j = 0; j < 3; j++) {
+ scale[j] = x->dcvs[j]->interp(x->dcvs[j], 1.0);
+ x->dcvs[j]->force_scale(x->dcvs[j], 1.0);
+ }
+
+ /* Scale the matrix to compensate */
+ for (i = 0; i < 3; i++)
+ for (j = 0; j < 3; j++)
+ x->fm[i][j] *= scale[j];
+}
+
+/* Device model optimisation function handed to powell() */
+static double dev_opt_func(void *edata, double *v) {
+ calx *x = (calx *)edata;
+ int i, j;
+ double tw = 0.0;
+ double rv, smv;
+
+#ifdef NEVER
+ printf("params =");
+ for (i = 0; i < x->np; i++)
+ printf(" %f",v[i]);
+ printf("\n");
+#endif
+
+ /* For all our data points */
+ rv = 0.0;
+ for (i = 0; i < x->nrp; i++) {
+ double lrgb[3]; /* Linear light RGB */
+ double xyz[3], lab[3];
+ double de;
+
+ /* Convert through device curves */
+ for (j = 0; j < 3; j++)
+ lrgb[j] = x->dcvs[j]->interp_p(x->dcvs[j], v + x->co[j], x->rp[i].dev[j]);
+
+ /* Convert linear light RGB into XYZ via the matrix */
+ icxMulBy3x3Parm(xyz, v, lrgb);
+
+ /* Convert to Lab */
+ icmXYZ2Lab(&x->twN, lab, xyz);
+
+ /* Compute delta E squared */
+ de = icmCIE94sq(lab, x->rp[i].lab) * x->rp[i].w;
+#ifdef NEVER
+ printf("point %d DE %f, Lab is %f %f %f, should be %f %f %f\n",
+i, sqrt(de), lab[0], lab[1], lab[2], x->rp[i].lab[0], x->rp[i].lab[1], x->rp[i].lab[2]);
+#endif
+ rv += de;
+ tw += x->rp[i].w;
+ }
+
+ /* Normalise error to be a weighted average delta E squared and scale smoothing */
+ rv /= (tw * 5.0);
+
+ /* Sum with shaper parameters squared, to */
+ /* minimise unsconstrained "wiggles" */
+ smv = 0.0;
+ for (j = 0; j < 3; j++)
+ smv += x->dcvs[j]->shweight_p(x->dcvs[j], v + x->co[j], 1.0);
+ rv += smv;
+
+#ifdef NEVER
+ printf("rv = %f (%f)\n",rv, smv);
+#endif
+ return rv;
+}
+
+
+/* Device model optimisation function handed to conjgrad() */
+static double dev_dopt_func(void *edata, double *dv, double *v) {
+ calx *x = (calx *)edata;
+ int i, j, k;
+ int f, ee, ff, jj;
+ double tw = 0.0;
+ double rv, smv;
+
+ double dmato_mv[3][9]; /* Del in mat out due to del in matrix param vals */
+ double dmato_tin[3][3]; /* Del in mat out due to del in matrix input values */
+ double dout_lab[3][3]; /* Del in out due to XYZ to Lab conversion */
+ double de_dout[2][3]; /* Del in delta E due to input Lab values */
+
+#ifdef NEVER
+ printf("params =");
+ for (i = 0; i < x->np; i++)
+ printf(" %f",v[i]);
+ printf("\n");
+#endif
+
+ /* Zero the accumulated partial derivatives */
+ for (i = 0; i < x->np; i++)
+ dv[i] = 0.0;
+
+ /* For all our data points */
+ rv = 0.0;
+ for (i = 0; i < x->nrp; i++) {
+ double lrgb[3]; /* Linear light RGB */
+ double xyz[3], lab[3];
+
+ /* Apply the input channel curves */
+ for (j = 0; j < 3; j++)
+ lrgb[j] = x->dcvs[j]->dinterp_p(x->dcvs[j], v + x->co[j],
+ x->dtin_iv + x->co[j], x->rp[i].dev[j]);
+
+ /* Convert linear light RGB into XYZ via the matrix */
+ icxdpdiMulBy3x3Parm(xyz, dmato_mv, dmato_tin, v, lrgb);
+
+ /* Convert to Lab */
+ icxdXYZ2Lab(&x->twN, lab, dout_lab, xyz);
+
+ /* Compute delta E squared */
+//printf("~1 point %d: Lab is %f %f %f, should be %f %f %f\n",
+//i, lab[0], lab[1], lab[2], x->rp[i].lab[0], x->rp[i].lab[1], x->rp[i].lab[2]);
+ rv += icxdCIE94sq(de_dout, lab, x->rp[i].lab) * x->rp[i].w;
+ de_dout[0][0] *= x->rp[i].w;
+ de_dout[0][1] *= x->rp[i].w;
+ de_dout[0][2] *= x->rp[i].w;
+ tw += x->rp[i].w;
+
+ /* Compute and accumulate partial difference values for each parameter value */
+
+ /* Input channel curves */
+ for (ee = 0; ee < 3; ee++) { /* Parameter input chanel */
+ for (k = 0; k < x->nc[ee]; k++) { /* Param within channel */
+ double vv = 0.0;
+ jj = x->co[ee] + k; /* Overall input curve param */
+
+ for (ff = 0; ff < 3; ff++) { /* Lab channels */
+ for (f = 0; f < 3; f++) { /* XYZ channels */
+ vv += de_dout[0][ff] * dout_lab[ff][f]
+ * dmato_tin[f][ee] * x->dtin_iv[jj];
+ }
+ }
+ dv[jj] += vv;
+ }
+ }
+
+ /* Matrix parameters */
+ for (k = 0; k < 9; k++) { /* Matrix parameter */
+ double vv = 0.0;
+
+ for (ff = 0; ff < 3; ff++) { /* Lab channels */
+ for (f = 0; f < 3; f++) { /* XYZ channels */
+ vv += de_dout[0][ff] * dout_lab[ff][f]
+ * dmato_mv[f][k];
+ }
+ }
+ dv[k] += vv;
+ }
+ }
+
+ /* Normalise error to be a weighted average delta E squared and scale smoothing */
+ rv /= (tw * 1200.0);
+ for (i = 0; i < x->np; i++)
+ dv[i] /= (tw * 900.0);
+
+ /* Sum with shaper parameters squared, to */
+ /* minimise unsconstrained "wiggles" */
+ smv = 0.0;
+ for (j = 0; j < 3; j++)
+ smv += x->dcvs[j]->dshweight_p(x->dcvs[j], v + x->co[j], x->dtin_iv + x->co[j], 1.0);
+ rv += smv;
+
+#ifdef NEVER
+ printf("drv = %f (%f)\n",rv, smv);
+#endif
+ return rv;
+}
+
+#ifdef NEVER
+/* Check partial derivative function within dev_opt_func() using powell() */
+
+static double dev_opt_func(void *edata, double *v) {
+ calx *x = (calx *)edata;
+ int i;
+ double dv[2000];
+ double rv, drv;
+ double trv;
+
+ rv = dev_opt_func_(edata, v);
+ drv = dev_dopt_func(edata, dv, v);
+
+ if (fabs(rv - drv) > 1e-6) {
+ printf("######## RV MISMATCH is %f should be %f ########\n",drv, rv);
+ exit(0);
+ }
+
+ /* Check each parameter delta */
+ for (i = 0; i < x->np; i++) {
+ double del;
+
+ v[i] += 1e-7;
+ trv = dev_opt_func_(edata, v);
+ v[i] -= 1e-7;
+
+ /* Check that del is correct */
+ del = (trv - rv)/1e-7;
+ if (fabs(dv[i] - del) > 1.0) {
+//printf("~1 del = %f from (trv %f - rv %f)/0.1\n",del,trv,rv);
+ printf("######## EXCESSIVE at v[%d] is %f should be %f ########\n",i,dv[i],del);
+ exit(0);
+ }
+ }
+ return rv;
+}
+#endif
+
+/* =================================================================== */
+
+/* White point brightness optimization function handed to powell. */
+/* Maximize brigtness while staying within gamut */
+static double wp_opt_func(void *edata, double *v) {
+ calx *x = (calx *)edata;
+ double wxyz[3], rgb[3];
+ int j;
+ double rv = 0.0;
+
+ wxyz[0] = v[0] * x->twh[0];
+ wxyz[1] = v[0] * x->twh[1];
+ wxyz[2] = v[0] * x->twh[2];
+
+//printf("~1 wp_opt_func got scale %f, xyz = %f %f %f\n",
+//v[0],wxyz[0],wxyz[1],wxyz[2]);
+
+ if ((rv = invlindev(x, rgb, wxyz)) > 0.0) { /* Out of gamut */
+ rv *= 1e5;
+//printf("~1 out of gamut %f %f %f returning %f\n", rgb[0], rgb[1], rgb[2], rv);
+ return rv;
+ }
+ /* Maximize scale factor */
+ if (v[0] < 0.00001)
+ rv = 1.0/0.00001;
+ else
+ rv = 1.0/v[0];
+
+//printf("~1 %f %f %f returning %f\n", rgb[0], rgb[1], rgb[2], rv);
+ return rv;
+}
+
+/* =================================================================== */
+/* Structure to save aproximate model readings in */
+typedef struct {
+ double v; /* Input value */
+ double xyz[3]; /* Reading */
+} sxyz;
+
+/* ------------------------------------------------------------------- */
+#if defined(__APPLE__) && defined(__POWERPC__)
+
+/* Workaround for a ppc gcc 3.3 optimiser bug... */
+/* It seems to cause a segmentation fault instead of */
+/* converting an integer loop index into a float, */
+/* when there are sufficient variables in play. */
+static int gcc_bug_fix(int i) {
+ static int nn;
+ nn += i;
+ return nn;
+}
+#endif /* APPLE */
+
+
+/* =================================================================== */
+/* Calibration sample point support. This allows the successive */
+/* refinement of our neutral sample points */
+
+/* A sample point */
+typedef struct {
+ double v; /* Desired input value */
+ double rgb[3]; /* Input value through calibration curves */
+ double tXYZ[3]; /* Target XYZ */
+ double XYZ[3]; /* Read XYZ */
+ double deXYZ[3]; /* Delta XYZ wanted to target */
+ double de; /* Delta Lab to neutral target */
+ double dc; /* Delta XYZ to neutral target */
+ double peqde; /* Delta Lab to previous point value (last pass) */
+ double hde; /* Hybrid de composed of de and peqde */
+
+ double pXYZ[3]; /* Previous measured XYZ */
+ double pdXYZ[3]; /* Delta XYZ intended from previous measure */
+ double pdrgb[3]; /* Delta rgb made to previous */
+
+ double dXYZ[3]; /* Actual delta XYZ resulting from previous delta rgb */
+
+ double j[3][3]; /* Aproximate Jacobian (del RGB -> XYZ) */
+ double ij[3][3]; /* Aproximate inverse Jacobian (del XYZ-> del RGB) */
+ double fb_ij[3][3]; /* Copy of initial inverse Jacobian, used as a fallback */
+} csp;
+
+/* All the sample points */
+typedef struct {
+ int no; /* Number of samples */
+ int _no; /* Allocation */
+ csp *s; /* List of samples */
+} csamp;
+
+static void free_alloc_csamp(csamp *p) {
+ if (p->s != NULL)
+ free(p->s);
+ p->s = NULL;
+}
+
+/* Initialise v values */
+static void init_csamp_v(csamp *p, calx *x, int psrand) {
+ int i, j;
+ sobol *so = NULL;
+
+ if (psrand != 0) { /* Use pseudo random distribution for verification */
+ if ((so = new_sobol(1)) == NULL)
+ error("New sobol failed");
+ }
+
+ /* Generate the sample points */
+ for (i = 0; i < p->no; i++) {
+ double vv;
+
+#if defined(__APPLE__) && defined(__POWERPC__)
+ gcc_bug_fix(i);
+#endif
+ if (so != NULL) {
+ if (i == 0)
+ vv = 1.0;
+ else if (i == 1)
+ vv = 0.0;
+ else
+ so->next(so, &vv);
+ } else
+ vv = i/(p->no - 1.0);
+ vv = pow(vv, REFN_DIST_POW); /* Skew sample points to be slightly perceptual */
+ p->s[i].v = vv;
+ }
+
+ if (so != NULL) {
+ /* Sort it so white is last */
+#define HEAP_COMPARE(A,B) (A.v < B.v)
+ HEAPSORT(csp,p->s,p->no)
+#undef HEAP_COMPARE
+ so->del(so);
+ }
+}
+
+/* Initialise txyz values from v values */
+static void init_csamp_txyz(csamp *p, calx *x, int fixdev) {
+ int i, j;
+ double tbL[3]; /* tbk as Lab */
+
+ /* Convert target black from XYZ to Lab here, */
+ /* in case twN has changed at some point. */
+ icmXYZ2Lab(&x->twN, tbL, x->tbk);
+
+ /* Set the sample points targets */
+ for (i = 0; i < p->no; i++) {
+ double y, vv;
+ double XYZ[3]; /* Existing XYZ value */
+ double Lab[3];
+ double bl;
+
+ vv = p->s[i].v;
+
+ /* Compute target relative Y value for this device input. */
+ /* We allow for any input and/or output offset */
+ y = x->gooff + dev2Y(x, x->egamma, x->gioff + vv * (1.0 - x->gioff)) * (1.0 - x->gooff);
+
+ /* Add viewing environment transform */
+ y = view_xform(x, y);
+
+ /* Convert Y to L* */
+ Lab[0] = icmY2L(y);
+ Lab[1] = Lab[2] = 0.0; /* Target is neutral */
+
+ /* Compute blended neutral target a* b* */
+ bl = pow((1.0 - vv), x->nbrate); /* Crossover near the black */
+ Lab[1] = bl * tbL[1];
+ Lab[2] = bl * tbL[2];
+
+ icmAry2Ary(XYZ, p->s[i].tXYZ); /* Save the existing values */
+ icmLab2XYZ(&x->twN, p->s[i].tXYZ, Lab); /* New XYZ Value to aim for */
+
+#ifdef DEBUG
+ printf("%d: target XYZ %.2f %.2f %.2f, Lab %.2f %.2f %.2f\n",i, p->s[i].tXYZ[0],p->s[i].tXYZ[1],p->s[i].tXYZ[2], Lab[0],Lab[1],Lab[2]);
+#endif
+ }
+}
+
+
+/* Allocate the sample points and initialise them with the */
+/* target device and XYZ values, and first cut device values. */
+static void init_csamp(csamp *p, calx *x, int doupdate, int verify, int psrand, int no) {
+ int i, j;
+
+ p->_no = p->no = no;
+
+ if ((p->s = (csp *)malloc(p->_no * sizeof(csp))) == NULL)
+ error("csamp malloc failed");
+
+ /* Compute v and txyz */
+ init_csamp_v(p, x, psrand);
+ init_csamp_txyz(p, x, 0);
+
+ /* Generate the sample points */
+ for (i = 0; i < no; i++) {
+ double dd, vv;
+
+#if defined(__APPLE__) && defined(__POWERPC__)
+ gcc_bug_fix(i);
+#endif
+ vv = p->s[i].v;
+
+ if (verify == 2) { /* Verifying through installed curve */
+ /* Make RGB values the input value */
+ p->s[i].rgb[0] = p->s[i].rgb[1] = p->s[i].rgb[2] = vv;
+
+ } else if (doupdate) { /* Start or verify through current cal curves */
+ for (j = 0; j < 3; j++) {
+ p->s[i].rgb[j] = x->rdac[j]->interp(x->rdac[j], vv);
+#ifdef CLIP
+ if (p->s[i].rgb[j] < 0.0)
+ p->s[i].rgb[j] = 0.0;
+ else if (p->s[i].rgb[j] > 1.0)
+ p->s[i].rgb[j] = 1.0;
+#endif
+ }
+ } else { /* we have model */
+ /* Lookup an initial device RGB for that target by inverting */
+ /* the approximate forward device model */
+ p->s[i].rgb[0] = p->s[i].rgb[1] = p->s[i].rgb[2] = vv;
+ invdev(x, p->s[i].rgb, p->s[i].tXYZ);
+ }
+ /* Force white to be native if native flag set */
+ if (x->nat && i == (no-1)) {
+//printf("~1 Forcing white rgb to be 1,1,1\n");
+ p->s[i].rgb[0] = p->s[i].rgb[1] = p->s[i].rgb[2] = 1.0;
+ }
+
+//printf("~1 Inital point %d rgb %f %f %f\n",i,p->s[i].rgb[0],p->s[i].rgb[1],p->s[i].rgb[2]);
+
+ /* Compute the approximate inverse Jacobian at this point */
+ /* by taking the partial derivatives wrt to each device */
+ /* channel of our aproximate forward model */
+ if (verify != 2) {
+ double refXYZ[3], delXYZ[3];
+ fwddev(x, refXYZ, p->s[i].rgb);
+ if (vv < 0.5)
+ dd = 0.02;
+ else
+ dd = -0.02;
+ /* Matrix organization is J[XYZ][RGB] for del RGB->del XYZ*/
+ for (j = 0; j < 3; j++) {
+ p->s[i].rgb[j] += dd;
+ fwddev(x, delXYZ, p->s[i].rgb);
+ p->s[i].j[0][j] = (delXYZ[0] - refXYZ[0]) / dd;
+ p->s[i].j[1][j] = (delXYZ[1] - refXYZ[1]) / dd;
+ p->s[i].j[2][j] = (delXYZ[2] - refXYZ[2]) / dd;
+ p->s[i].rgb[j] -= dd;
+ }
+ if (icmInverse3x3(p->s[i].ij, p->s[i].j)) {
+ error("dispcal: inverting Jacobian failed (1)");
+ }
+ /* Make a copy of this Jacobian in case we get an invert failure later */
+ icmCpy3x3(p->s[i].fb_ij, p->s[i].ij);
+ }
+ }
+}
+
+/* Return a linear XYZ interpolation */
+static void csamp_interp(csamp *p, double xyz[3], double v) {
+ int i, j;
+ double b;
+
+ if (p->no < 2)
+ error("Calling csamp_interp with less than two existing samples");
+
+ /* Locate the pair surrounding our input value */
+ for (i = 0; i < (p->no-1); i++) {
+ if (v >= p->s[i].v && v <= p->s[i+1].v)
+ break;
+ }
+ if (i >= (p->no-1))
+ error("csamp_interp out of range");
+
+ b = (v - p->s[i].v)/(p->s[i+1].v - p->s[i].v);
+
+ for (j = 0; j < 3; j++) {
+ xyz[j] = b * p->s[i+1].XYZ[j] + (1.0 - b) * p->s[i].XYZ[j];
+ }
+}
+
+/* Re-initialise a CSP with a new number of points. */
+/* Interpolate the device values and jacobian. */
+/* Set the current rgb from the current RAMDAC curves if not verifying */
+static void reinit_csamp(csamp *p, calx *x, int verify, int psrand, int no) {
+ csp *os; /* Old list of samples */
+ int ono; /* Old number of samples */
+ int i, j, k, m;
+
+ if (no == p->no)
+ return; /* Nothing has changed */
+
+ os = p->s; /* Save the existing per point information */
+ ono = p->no;
+
+ init_csamp(p, x, 0, 2, psrand, no);
+
+ p->_no = p->no = no;
+
+ /* Interpolate the current device values */
+ for (i = 0; i < no; i++) {
+ double vv, b;
+
+ vv = p->s[i].v;
+
+ /* Locate the pair surrounding our target value */
+ for (j = 0; j < ono-1; j++) {
+ if (vv >= os[j].v && vv <= os[j+1].v)
+ break;
+ }
+ if (j >= (ono-1))
+ error("csamp interp. out of range");
+
+ b = (vv - os[j].v)/(os[j+1].v - os[j].v);
+
+ for (k = 0; k < 3; k++) {
+ if (verify == 2) {
+
+ p->s[i].rgb[k] = b * os[j+1].rgb[k] + (1.0 - b) * os[j].rgb[k];
+
+ } else { /* Lookup rgb from current calibration curves */
+ for (m = 0; m < 3; m++) {
+ p->s[i].rgb[m] = x->rdac[m]->interp(x->rdac[m], vv);
+#ifdef CLIP
+ if (p->s[i].rgb[m] < 0.0)
+ p->s[i].rgb[m] = 0.0;
+ else if (p->s[i].rgb[m] > 1.0)
+ p->s[i].rgb[m] = 1.0;
+#endif
+ }
+ }
+ p->s[i].XYZ[k] = b * os[j+1].XYZ[k] + (1.0 - b) * os[j].XYZ[k];
+ p->s[i].deXYZ[k] = b * os[j+1].deXYZ[k] + (1.0 - b) * os[j].deXYZ[k];
+ p->s[i].pXYZ[k] = b * os[j+1].pXYZ[k] + (1.0 - b) * os[j].pXYZ[k];
+ p->s[i].pdrgb[k] = b * os[j+1].pdrgb[k] + (1.0 - b) * os[j].pdrgb[k];
+ p->s[i].dXYZ[k] = b * os[j+1].dXYZ[k] + (1.0 - b) * os[j].dXYZ[k];
+#ifdef INTERP_JAC
+ for (m = 0; m < 3; m++)
+ p->s[i].j[k][m] = b * os[j+1].j[k][m] + (1.0 - b) * os[j].j[k][m];
+#endif
+
+ }
+#ifndef INTERP_JAC
+ /* Create a Jacobian at this location from our forward model */
+ {
+ double dd, refXYZ[3], delXYZ[3];
+ fwddev(x, refXYZ, p->s[i].rgb);
+ if (vv < 0.5)
+ dd = 0.02;
+ else
+ dd = -0.02;
+ /* Matrix organization is J[XYZ][RGB] for del RGB->del XYZ*/
+ for (j = 0; j < 3; j++) {
+ p->s[i].rgb[j] += dd;
+ fwddev(x, delXYZ, p->s[i].rgb);
+ p->s[i].j[0][j] = (delXYZ[0] - refXYZ[0]) / dd;
+ p->s[i].j[1][j] = (delXYZ[1] - refXYZ[1]) / dd;
+ p->s[i].j[2][j] = (delXYZ[2] - refXYZ[2]) / dd;
+ p->s[i].rgb[j] -= dd;
+ }
+ }
+#endif
+ if (icmInverse3x3(p->s[i].ij, p->s[i].j)) {
+ error("dispcal: inverting Jacobian failed (2)");
+ }
+ /* Make a copy of this Jacobian in case we get an invert failure later */
+ icmCpy3x3(p->s[i].fb_ij, p->s[i].ij);
+
+ /* Compute expected delta XYZ using new Jacobian */
+ icmMulBy3x3(p->s[i].pdXYZ, p->s[i].j, p->s[i].pdrgb);
+
+ p->s[i].de = b * os[j+1].de + (1.0 - b) * os[j].de;
+ p->s[i].dc = b * os[j+1].dc + (1.0 - b) * os[j].dc;
+ }
+
+ free(os);
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+#ifdef NEVER
+/* Do a linear interp of the ramdac */
+static void interp_ramdac(double cal[CAL_RES][3], double drgb[3], double rgb[3]) {
+ int i, j;
+ int gres = CAL_RES;
+ double w;
+
+ /* For r,g & b */
+ for (j = 0; j < 3; j++) {
+ int mi, gres_1 = gres-1;
+ double t, vv = rgb[j];
+ t = gres * vv;
+ mi = (int)floor(t); /* Grid coordinate */
+ if (mi < 0) /* Limit to valid cube base index range */
+ mi = 0;
+ else if (mi >= gres_1)
+ mi = gres_1-1;
+ w = t - (double)mi; /* 1.0 - weight */
+
+ drgb[j] = (1.0 - w) * cal[mi][j] + w * cal[mi+1][j];
+ }
+}
+#endif /* NEVER */
+
+/* Given an XYZ, compute the color temperature and the delta E 2K to the locus */
+static double comp_ct(
+ double *de, /* If non-NULL, return CIEDE2000 to locus */
+ double lxyz[3], /* If non-NULL, return normalised XYZ on locus */
+ int plank, /* NZ if Plankian locus, 0 if Daylight locus */
+ int dovct, /* NZ if visual match, 0 if traditional correlation */
+ icxObserverType obType, /* If not default, set a custom observer */
+ double xyz[3] /* Color to match */
+) {
+ double ct_xyz[3]; /* XYZ on locus */
+ double nxyz[3]; /* Normalised input color */
+ double ct, ctde; /* Color temperature & delta E to Black Body locus */
+ icmXYZNumber wN;
+
+
+ if (obType == icxOT_default)
+ obType = icxOT_CIE_1931_2;
+
+ if ((ct = icx_XYZ2ill_ct(ct_xyz, plank != 0 ? icxIT_Ptemp : icxIT_Dtemp,
+ obType, NULL, xyz, NULL, dovct)) < 0)
+ error ("Got bad color temperature conversion\n");
+
+ if (de != NULL) {
+ icmAry2XYZ(wN, ct_xyz);
+ icmAry2Ary(nxyz, xyz);
+ nxyz[0] /= xyz[1];
+ nxyz[2] /= xyz[1];
+ nxyz[1] /= xyz[1];
+ ctde = icmXYZCIE2K(&wN, nxyz, ct_xyz);
+ *de = ctde;
+ }
+ if (lxyz != NULL) {
+ icmAry2Ary(lxyz, ct_xyz);
+ }
+ return ct;
+}
+
+/* =================================================================== */
+
+/* Default gamma */
+double g_def_gamma = 2.4;
+
+/*
+
+ Flags used:
+
+ ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ upper .......... ....... . ...
+ lower ....... . ...... .... .
+
+*/
+
+void usage(char *diag, ...) {
+ int i;
+ disppath **dp;
+ icompaths *icmps;
+ inst2_capability cap2 = inst2_none;
+
+ fprintf(stderr,"Calibrate a Display, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ if (setup_spyd2() == 2)
+ fprintf(stderr,"WARNING: This file contains a proprietary firmware image, and may not be freely distributed !\n");
+ if (diag != NULL) {
+ va_list args;
+ fprintf(stderr,"Diagnostic: ");
+ va_start(args, diag);
+ vfprintf(stderr, diag, args);
+ va_end(args);
+ fprintf(stderr,"\n");
+ }
+ fprintf(stderr,"usage: dispcal [options] outfile\n");
+ fprintf(stderr," -v [n] Verbose mode\n");
+#if defined(UNIX_X11)
+ fprintf(stderr," -display displayname Choose X11 display name\n");
+ fprintf(stderr," -d n[,m] Choose the display n from the following list (default 1)\n");
+ fprintf(stderr," Optionally choose different display m for VideoLUT access\n");
+#else
+ fprintf(stderr," -d n Choose the display from the following list (default 1)\n");
+#endif
+ dp = get_displays();
+ if (dp == NULL || dp[0] == NULL)
+ fprintf(stderr," ** No displays found **\n");
+ else {
+ int i;
+ for (i = 0; ; i++) {
+ if (dp[i] == NULL)
+ break;
+ fprintf(stderr," %d = '%s'\n",i+1,dp[i]->description);
+ }
+ }
+ free_disppaths(dp);
+ fprintf(stderr," -dweb[:port] Display via a web server at port (default 8080)\n");
+// fprintf(stderr," -d fake Use a fake display device for testing, fake%s if present\n",ICC_FILE_EXT);
+ fprintf(stderr," -c listno Set communication port from the following list (default %d)\n",COMPORT);
+ if ((icmps = new_icompaths(g_log)) != NULL) {
+ icompath **paths;
+ if ((paths = icmps->paths) != NULL) {
+ int i;
+ for (i = 0; ; i++) {
+ if (paths[i] == NULL)
+ break;
+ if (paths[i]->itype == instSpyder2 && setup_spyd2() == 0)
+ fprintf(stderr," %d = '%s' !! Disabled - no firmware !!\n",i+1,paths[i]->name);
+ else
+ fprintf(stderr," %d = '%s'\n",i+1,paths[i]->name);
+ }
+ } else
+ fprintf(stderr," ** No ports found **\n");
+ }
+ fprintf(stderr," -r Report on the calibrated display then exit\n");
+ fprintf(stderr," -R Report on the uncalibrated display then exit\n");
+ fprintf(stderr," -m Skip adjustment of the monitor controls\n");
+ fprintf(stderr," -o [profile%s] Create fast matrix/shaper profile [different filename to outfile%s]\n",ICC_FILE_EXT,ICC_FILE_EXT);
+ fprintf(stderr," -O \"description\" Fast ICC Profile Description string (Default \"outfile\")\n");
+ fprintf(stderr," -u Update previous calibration and (if -o used) ICC profile VideoLUTs\n");
+ fprintf(stderr," -q [vlmh] Quality - Very Low, Low, Medium (def), High\n");
+// fprintf(stderr," -q [vfmsu] Speed - Very Fast, Fast, Medium (def), Slow, Ultra Slow\n");
+ fprintf(stderr," -p Use telephoto mode (ie. for a projector) (if available)\n");
+ cap2 = inst_show_disptype_options(stderr, " -y ", icmps, 0);
+ fprintf(stderr," -t [temp] White Daylight locus target, optional target temperaturee in deg. K (deflt.)\n");
+ fprintf(stderr," -T [temp] White Black Body locus target, optional target temperaturee in deg. K\n");
+ fprintf(stderr," -w x,y Set the target white point as chromaticity coordinates\n");
+#ifdef NEVER /* Not worth confusing people about this ? */
+ fprintf(stderr," -L Show CCT/CDT rather than VCT/VDT during native white point adjustment\n");
+#endif
+ fprintf(stderr," -b bright Set the target white brightness in cd/m^2\n");
+ fprintf(stderr," -g gamma Set the target response curve advertised gamma (Def. %3.1f)\n",g_def_gamma);
+ fprintf(stderr," Use \"-gl\" for L*a*b* curve\n");
+ fprintf(stderr," Use \"-gs\" for sRGB curve\n");
+ fprintf(stderr," Use \"-g709\" for REC 709 curve (should use -a as well!)\n");
+ fprintf(stderr," Use \"-g240\" for SMPTE 240M curve (should use -a as well!)\n");
+ fprintf(stderr," Use \"-G2.4 -f0\" for BT.1886\n");
+ fprintf(stderr," -G gamma Set the target response curve actual technical gamma\n");
+ fprintf(stderr," -f [degree] Amount of black level accounted for with output offset (default all output offset)\n");
+ fprintf(stderr," -a ambient Use viewing condition adjustment for ambient in Lux\n");
+ fprintf(stderr," -k factor Amount to correct black hue, 0 = none, 1 = full, Default = Automatic\n");
+ fprintf(stderr," -A rate Rate of blending from neutral to black point. Default %.1f\n",NEUTRAL_BLEND_RATE);
+ fprintf(stderr," -B blkbright Set the target black brightness in cd/m^2\n");
+ fprintf(stderr," -e [n] Run n verify passes on final curves\n");
+ fprintf(stderr," -E Run only verify pass on installed calibration curves\n");
+ fprintf(stderr," -P ho,vo,ss[,vs] Position test window and scale it\n");
+ fprintf(stderr," ho,vi: 0.0 = left/top, 0.5 = center, 1.0 = right/bottom etc.\n");
+ fprintf(stderr," ss: 0.5 = half, 1.0 = normal, 2.0 = double etc.\n");
+ fprintf(stderr," -F Fill whole screen with black background\n");
+#if defined(UNIX_X11)
+ fprintf(stderr," -n Don't set override redirect on test window\n");
+#endif
+ fprintf(stderr," -J Run instrument calibration first (used rarely)\n");
+ fprintf(stderr," -N Disable initial calibration of instrument if possible\n");
+ fprintf(stderr," -H Use high resolution spectrum mode (if available)\n");
+// fprintf(stderr," -V Use adaptive measurement mode (if available)\n");
+ if (cap2 & inst2_ccmx)
+ fprintf(stderr," -X file.ccmx Apply Colorimeter Correction Matrix\n");
+ if (cap2 & inst2_ccss) {
+ fprintf(stderr," -X file.ccss Use Colorimeter Calibration Spectral Samples for calibration\n");
+ fprintf(stderr," -Q observ Choose CIE Observer for spectrometer or CCSS colorimeter data:\n");
+ fprintf(stderr," 1931_2 (def), 1964_10, S&B 1955_2, shaw, J&V 1978_2, 1964_10c\n");
+ }
+ fprintf(stderr," -I b|w Drift compensation, Black: -Ib, White: -Iw, Both: -Ibw\n");
+ fprintf(stderr," -Y A Use non-adaptive integration time mode (if available).\n");
+ fprintf(stderr," -C \"command\" Invoke shell \"command\" each time a color is set\n");
+ fprintf(stderr," -M \"command\" Invoke shell \"command\" each time a color is measured\n");
+ fprintf(stderr," -W n|h|x Override serial port flow control: n = none, h = HW, x = Xon/Xoff\n");
+ fprintf(stderr," -D [level] Print debug diagnostics to stderr\n");
+ fprintf(stderr," inoutfile Base name for created or updated .cal and %s output files\n",ICC_FILE_EXT);
+ if (icmps != NULL)
+ icmps->del(icmps);
+ exit(1);
+}
+
+int main(int argc, char *argv[]) {
+ int i, j, k;
+ int fa, nfa, mfa; /* current argument we're looking at */
+ disppath *disp = NULL; /* Display being used */
+ double hpatscale = 1.0, vpatscale = 1.0; /* scale factor for test patch size */
+ double ho = 0.0, vo = 0.0; /* Test window offsets, -1.0 to 1.0 */
+ int blackbg = 0; /* NZ if whole screen should be filled with black */
+ int verb = 0;
+ int debug = 0;
+ int fake = 0; /* Use the fake device for testing */
+ int override = 1; /* Override redirect on X11 */
+ int docalib = 0; /* Do a manual instrument calibration */
+ int doreport = 0; /* 1 = Report the current uncalibrated display response */
+ /* 2 = Report the current calibrated display response */
+ int docontrols = 1; /* Do adjustment of the display controls */
+ int doprofile = 0; /* Create/update ICC profile */
+ char *profDesc = NULL; /* Created profile description string */
+ char *copyright = NULL; /* Copyright string */
+ char *deviceMfgDesc = NULL; /* Device manufacturer string */
+ char *modelDesc = NULL; /* Device model description string */
+ int doupdate = 0; /* Do an update rather than a fresh calbration */
+ int comport = COMPORT; /* COM port used */
+ icompaths *icmps = NULL;
+ icompath *ipath = NULL;
+ flow_control fc = fc_nc; /* Default flow control */
+ int dtype = 0; /* Display type selection charater */
+ int tele = 0; /* nz if telephoto mode */
+ int nocal = 0; /* Disable auto calibration */
+ int highres = 0; /* Use high res mode if available */
+ int nadaptive = 0; /* Use non-adaptive mode if available */
+ int bdrift = 0; /* Flag, nz for black drift compensation */
+ int wdrift = 0; /* Flag, nz for white drift compensation */
+ double temp = 0.0; /* Color temperature (0 = native) */
+ int planckian = 0; /* 0 = Daylight, 1 = Planckian color locus */
+ int dovct = 1; /* Show VXT rather than CXT for adjusting white point */
+ double wpx = 0.0, wpy = 0.0; /* White point xy (native) */
+ double tbright = 0.0; /* Target white brightness ( 0.0 == max) */
+ double gamma = 0.0; /* Advertised Gamma target */
+ double egamma = 0.0; /* Effective Gamma target, NZ if set */
+ double ambient = 0.0; /* NZ if viewing cond. adjustment to be used (cd/m^2) */
+ double bkcorrect = -1.0; /* Level of black point correction, < 0 = auto */
+ double bkbright = 0.0; /* Target black brightness ( 0.0 == min) */
+ int quality = -99; /* Quality level, -2 = v, -1 = l, 0 = m, 1 = h, 2 = u */
+ int isteps = 22; /* Initial measurement steps/3 (medium) */
+ int rsteps = 64; /* Refinement measurement steps (medium) */
+ double errthr = 1.5; /* Error threshold for refinement steps (medium) */
+ int thrfail = 0; /* Set to NZ if failed to meet threshold target */
+ double failerr = 0.0; /* Delta E of worst failed target */
+ int mxits = 3; /* maximum iterations (medium) */
+ int verify = 0; /* Do a verify after last refinement, 2 = do only verify. */
+ int nver = 0; /* Number of verify passes after refinement */
+ int webdisp = 0; /* NZ for web display, == port number */
+ char *ccallout = NULL; /* Change color Shell callout */
+ char *mcallout = NULL; /* Measure color Shell callout */
+ char outname[MAXNAMEL+1] = { 0 }; /* Output cgats file base name */
+ char iccoutname[MAXNAMEL+1] = { 0 };/* Output icc file base name */
+ char ccxxname[MAXNAMEL+1] = "\000"; /* CCMX or CCSS file name */
+ ccmx *cmx = NULL; /* Colorimeter Correction Matrix */
+ ccss *ccs = NULL; /* Colorimeter Calibration Spectral Samples */
+ int spec = 0; /* Want spectral data from instrument */
+ icxObserverType obType = icxOT_default;
+ disprd *dr = NULL; /* Display patch read object */
+ csamp asgrey; /* Main calibration loop test points */
+ double dispLum = 0.0; /* Display luminence reading */
+ int it; /* verify & refine iteration */
+ int rv;
+ int fitord = 30; /* More seems to make curves smoother */
+ int native = 1; /* 0 = use current or given calibration curve */
+ /* 1 = set native linear op and use ramdac high prec'n */
+ int noramdac = 0; /* Will be set to nz if can't set ramdac */
+ int errc; /* Return value from new_disprd() */
+ calx x; /* Context for calibration solution */
+
+ set_exe_path(argv[0]); /* Set global exe_path and error_program */
+ check_if_not_interactive();
+
+#if defined(__APPLE__)
+ {
+ SInt32 MacVers;
+
+ /* Hmm. Maybe this should actually be 1.72 ?? */
+ g_def_gamma = 1.8;
+
+ /* OS X 10.6 uses a nominal gamma of 2.2 */
+ if (Gestalt(gestaltSystemVersion, &MacVers) == noErr) {
+ if (MacVers >= 0x1060) {
+ g_def_gamma = 2.4;
+ }
+ }
+ }
+#else
+ g_def_gamma = 2.4; /* Typical CRT gamma */
+#endif
+ gamma = g_def_gamma;
+
+ setup_spyd2(); /* Load firware if available */
+
+ x.gammat = gt_power ; /* Default gamma type */
+ x.egamma = 0.0; /* Default effective gamma none */
+ x.oofff = 1.0; /* Default is all output ofset */
+ x.vc = 0; /* No viewing conditions adjustment */
+ x.svc = NULL;
+ x.dvc = NULL;
+ x.nbrate = NEUTRAL_BLEND_RATE; /* Rate of blending from black point to neutral axis */
+
+#ifdef DEBUG_OFFSET
+ ho = 0.8;
+ vo = -0.8;
+#endif
+
+#if defined(DEBUG) || defined(DEBUG_OFFSET) || defined(DEBUG_PLOT)
+ printf("!!!!!! Debug turned on !!!!!!\n");
+#endif
+
+ if (argc <= 1)
+ usage("Too few arguments");
+
+ /* Process the arguments */
+ mfa = 1; /* Minimum final arguments */
+ for(fa = 1;fa < argc;fa++) {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') /* Look for any flags */
+ {
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1+mfa) < argc) {
+ if (argv[fa+1][0] != '-')
+ {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?' || argv[fa][1] == '-') {
+ usage("Usage requested");
+
+ } else if (argv[fa][1] == 'v') {
+ verb = 1;
+ if (na != NULL && na[0] >= '0' && na[0] <= '9') {
+ verb = atoi(na);
+ fa = nfa;
+ }
+ g_log->verb = verb;
+
+ /* Display number */
+ } else if (argv[fa][1] == 'd') {
+ if (strncmp(na,"web",3) == 0
+ || strncmp(na,"WEB",3) == 0) {
+ webdisp = 8080;
+ if (na[3] == ':') {
+ webdisp = atoi(na+4);
+ if (webdisp == 0 || webdisp > 65535)
+ usage("Web port number must be in range 1..65535");
+ }
+ fa = nfa;
+ } else {
+#if defined(UNIX_X11)
+ int ix, iv;
+
+ if (strcmp(&argv[fa][2], "isplay") == 0 || strcmp(&argv[fa][2], "ISPLAY") == 0) {
+ if (++fa >= argc || argv[fa][0] == '-') usage("Parameter expected following -display");
+ setenv("DISPLAY", argv[fa], 1);
+ } else {
+ if (na == NULL) usage("Parameter expected following -d");
+ fa = nfa;
+ if (strcmp(na,"fake") == 0) {
+ fake = 1;
+ } else {
+ if (sscanf(na, "%d,%d",&ix,&iv) != 2) {
+ ix = atoi(na);
+ iv = 0;
+ }
+ if (disp != NULL)
+ free_a_disppath(disp);
+ if ((disp = get_a_display(ix-1)) == NULL)
+ usage("-d parameter %d out of range",ix);
+ if (iv > 0)
+ disp->rscreen = iv-1;
+ }
+ }
+#else
+ int ix;
+ if (na == NULL) usage("Parameter expected following -d");
+ fa = nfa;
+ if (strcmp(na,"fake") == 0) {
+ fake = 1;
+ } else {
+ ix = atoi(na);
+ if (disp != NULL)
+ free_a_disppath(disp);
+ if ((disp = get_a_display(ix-1)) == NULL)
+ usage("-d parameter %d out of range",ix);
+ }
+#endif
+ }
+
+ } else if (argv[fa][1] == 'J') {
+ docalib = 1;
+
+ } else if (argv[fa][1] == 'N') {
+ nocal = 1;
+
+ /* High res mode */
+ } else if (argv[fa][1] == 'H') {
+ highres = 1;
+
+ /* Adaptive mode - now default, so flag is deprecated */
+ } else if (argv[fa][1] == 'V') {
+ warning("dispcal -V flag is deprecated");
+
+ /* Colorimeter Correction Matrix */
+ /* or Colorimeter Calibration Spectral Samples */
+ } else if (argv[fa][1] == 'X') {
+ int ix;
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected following -X");
+ strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
+
+ /* Drift Compensation */
+ } else if (argv[fa][1] == 'I') {
+ fa = nfa;
+ if (na == NULL || na[0] == '\000') usage("Parameter expected after -I");
+ for (i=0; ; i++) {
+ if (na[i] == '\000')
+ break;
+ if (na[i] == 'b' || na[i] == 'B')
+ bdrift = 1;
+ else if (na[i] == 'w' || na[i] == 'W')
+ wdrift = 1;
+ else
+ usage("-I parameter '%c' not recognised",na[i]);
+ }
+
+ /* Spectral Observer type */
+ } else if (argv[fa][1] == 'Q') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expecte after -Q");
+ if (strcmp(na, "1931_2") == 0) { /* Classic 2 degree */
+ obType = icxOT_CIE_1931_2;
+ } else if (strcmp(na, "1964_10") == 0) { /* Classic 10 degree */
+ obType = icxOT_CIE_1964_10;
+ } else if (strcmp(na, "1964_10c") == 0) { /* 10 degree corrected */
+ obType = icxOT_CIE_1964_10c;
+ } else if (strcmp(na, "1955_2") == 0) { /* Stiles and Burch 1955 2 degree */
+ obType = icxOT_Stiles_Burch_2;
+ } else if (strcmp(na, "1978_2") == 0) { /* Judd and Voss 1978 2 degree */
+ obType = icxOT_Judd_Voss_2;
+ } else if (strcmp(na, "shaw") == 0) { /* Shaw and Fairchilds 1997 2 degree */
+ obType = icxOT_Shaw_Fairchild_2;
+ } else
+ usage("-Q parameter '%s' not recognised",na);
+
+ /* Change color callout */
+ } else if (argv[fa][1] == 'C') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -C");
+ ccallout = na;
+
+ /* Measure color callout */
+ } else if (argv[fa][1] == 'M') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -M");
+ mcallout = na;
+
+ /* Serial port flow control */
+ } else if (argv[fa][1] == 'W') {
+ fa = nfa;
+ if (na == NULL) usage("Paramater expected following -W");
+ if (na[0] == 'n' || na[0] == 'N')
+ fc = fc_none;
+ else if (na[0] == 'h' || na[0] == 'H')
+ fc = fc_Hardware;
+ else if (na[0] == 'x' || na[0] == 'X')
+ fc = fc_XonXOff;
+ else
+ usage("-W parameter '%c' not recognised",na[0]);
+
+ /* Debug coms */
+ } else if (argv[fa][1] == 'D') {
+ debug = 1;
+ if (na != NULL && na[0] >= '0' && na[0] <= '9') {
+ debug = atoi(na);
+ fa = nfa;
+ }
+ g_log->debug = debug;
+ callback_ddebug = 1; /* dispwin global */
+
+ /* Black point correction amount */
+ } else if (argv[fa][1] == 'k') {
+ fa = nfa;
+ if (na == NULL) usage("Paramater expected following -k");
+ bkcorrect = atof(na);
+ if (bkcorrect < 0.0 || bkcorrect > 1.0) usage("-k parameter must be between 0.0 and 1.0");
+ /* Neutral blend rate (power) */
+ } else if (argv[fa][1] == 'A') {
+ fa = nfa;
+ if (na == NULL) usage("Paramater expected following -A");
+ x.nbrate = atof(na);
+ if (x.nbrate < 0.05 || x.nbrate > 20.0) usage("-A parameter must be between 0.05 and 20.0");
+ /* Black brightness */
+ } else if (argv[fa][1] == 'B') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -B");
+ bkbright = atof(na);
+ if (bkbright <= 0.0 || bkbright > 100000.0) usage("-B parameter %f out of range",bkbright);
+
+ /* Number of verify passes */
+ } else if (argv[fa][1] == 'e') {
+ verify = 1;
+ nver = 1;
+ if (na != NULL && na[0] >= '0' && na[0] <= '9') {
+ nver = atoi(na);
+ fa = nfa;
+ }
+
+ } else if (argv[fa][1] == 'E') {
+ verify = 2;
+ mfa = 0;
+
+#if defined(UNIX_X11)
+ } else if (argv[fa][1] == 'n') {
+ override = 0;
+#endif /* UNIX */
+ /* COM port */
+ } else if (argv[fa][1] == 'c') {
+ fa = nfa;
+ if (na == NULL) usage("Paramater expected following -c");
+ comport = atoi(na);
+ if (comport < 1 || comport > 50) usage("-c parameter %d out of range",comport);
+
+ /* Telephoto */
+ } else if (argv[fa][1] == 'p') {
+ tele = 1;
+
+ } else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') {
+ if (argv[fa][1] == 'R')
+ doreport = 1; /* raw */
+ else
+ doreport = 2; /* Calibrated */
+ mfa = 0;
+
+ } else if (argv[fa][1] == 'm') {
+ docontrols = 0;
+
+ /* Output/update ICC profile [optional different name] */
+ } else if (argv[fa][1] == 'o') {
+ doprofile = 1;
+
+ if (na != NULL) { /* Found an optional icc profile name */
+ fa = nfa;
+ strncpy(iccoutname,na,MAXNAMEL); iccoutname[MAXNAMEL] = '\000';
+ }
+
+ /* Fast Profile Description */
+ } else if (argv[fa][1] == 'O') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to profile description flag -O");
+ profDesc = na;
+
+ /* Update calibration and (optionally) profile */
+ } else if (argv[fa][1] == 'u') {
+ doupdate = 1;
+ docontrols = 0;
+
+ /* Speed/Quality */
+ } else if (argv[fa][1] == 'q') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected following -q");
+ switch (na[0]) {
+ case 'L': /* Test value */
+ quality = -3;
+ break;
+ case 'v': /* very fast */
+ quality = -2;
+ break;
+ case 'f': /* fast */
+ case 'l':
+ quality = -1;
+ break;
+ case 'm': /* medium */
+ case 'M':
+ quality = 0;
+ break;
+ case 's': /* slow */
+ case 'h':
+ case 'H':
+ quality = 1;
+ break;
+ case 'u': /* ultra slow */
+ case 'U':
+ quality = 2;
+ break;
+ default:
+ usage("-q parameter '%c' not recognised",na[0]);
+ }
+
+ /* Display type */
+ } else if (argv[fa][1] == 'y') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -y");
+ dtype = na[0];
+
+ /* Daylight color temperature */
+ } else if (argv[fa][1] == 't' || argv[fa][1] == 'T') {
+ if (argv[fa][1] == 'T')
+ planckian = 1;
+ else
+ planckian = 0;
+ if (na != NULL) {
+ fa = nfa;
+ temp = atof(na);
+ if (temp < 1000.0 || temp > 15000.0) usage("-%c parameter %f out of range",argv[fa][1], temp);
+ }
+
+ /* White point as x, y */
+ } else if (argv[fa][1] == 'w') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -w");
+ if (sscanf(na, " %lf,%lf ", &wpx, &wpy) != 2)
+ usage("-w parameter '%s' not recognised",na);
+
+ /* Show CXT rather than VXT when adjusting native white point */
+ } else if (argv[fa][1] == 'L') {
+ dovct = 0;
+
+ /* White brightness */
+ } else if (argv[fa][1] == 'b') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -b");
+ tbright = atof(na);
+ if (tbright <= 0.0 || tbright > 100000.0) usage("-b parameter %f out of range",tbright);
+
+ /* Target transfer curve */
+ } else if (argv[fa][1] == 'g') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -g");
+ if ((na[0] == 'l' || na[0] == 'L') && na[1] == '\000')
+ x.gammat = gt_Lab;
+ else if ((na[0] == 's' || na[0] == 'S') && na[1] == '\000')
+ x.gammat = gt_sRGB;
+ else if (strcmp(na, "709") == 0)
+ x.gammat = gt_Rec709;
+ else if (strcmp(na, "240") == 0)
+ x.gammat = gt_SMPTE240M;
+ else {
+ gamma = atof(na);
+ if (gamma <= 0.0 || gamma > 10.0) usage("-g parameter %f out of range",gamma);
+ x.gammat = gt_power;
+ }
+
+ /* Effective gamma power */
+ } else if (argv[fa][1] == 'G') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -G");
+ egamma = atof(na);
+ if (egamma <= 0.0 || egamma > 10.0) usage("-G parameter %f out of range",egamma);
+ x.gammat = gt_power;
+
+ /* Degree of output offset */
+ } else if (argv[fa][1] == 'f') {
+ fa = nfa;
+ if (na == NULL) {
+ x.oofff = 0.0;
+ } else {
+ x.oofff = atof(na);
+ if (x.oofff < 0.0 || x.oofff > 1.0)
+ usage("-f parameter %f out of range",x.oofff);
+ }
+
+ /* Ambient light level */
+ } else if (argv[fa][1] == 'a') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -a");
+ ambient = atof(na);
+ if (ambient < 0.0)
+ usage("-a parameter %f out of range",ambient);
+ ambient /= 3.141592654; /* Convert from Lux to cd/m^2 */
+
+ /* Test patch offset and size */
+ } else if (argv[fa][1] == 'P') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -P");
+ if (sscanf(na, " %lf,%lf,%lf,%lf ", &ho, &vo, &hpatscale, &vpatscale) == 4) {
+ ;
+ } else if (sscanf(na, " %lf,%lf,%lf ", &ho, &vo, &hpatscale) == 3) {
+ vpatscale = hpatscale;
+ } else {
+ usage("-P parameter '%s' not recognised",na);
+ }
+ if (ho < 0.0 || ho > 1.0
+ || vo < 0.0 || vo > 1.0
+ || hpatscale <= 0.0 || hpatscale > 50.0
+ || vpatscale <= 0.0 || vpatscale > 50.0)
+ usage("-P parameters %f %f %f %f out of range",ho,vo,hpatscale,vpatscale);
+ ho = 2.0 * ho - 1.0;
+ vo = 2.0 * vo - 1.0;
+
+ /* Black background */
+ } else if (argv[fa][1] == 'F') {
+ blackbg = 1;
+
+ /* Extra flags */
+ } else if (argv[fa][1] == 'Y') {
+ if (na == NULL)
+ usage("Flag '-Y' expects extra flag");
+
+ if (na[0] == 'A') {
+ nadaptive = 1;
+ } else {
+ usage("Flag '-Y %c' not recognised",na[0]);
+ }
+
+ } else
+ usage("Flag '-%c' not recognised",argv[fa][1]);
+ } else
+ break;
+ }
+
+ /* No explicit display has been set */
+ if (
+#ifndef SHOW_WINDOW_ONFAKE
+ !fake &&
+#endif
+ disp == NULL) {
+ int ix = 0;
+#if defined(UNIX_X11)
+ char *dn, *pp;
+
+ if ((dn = getenv("DISPLAY")) != NULL) {
+ if ((pp = strrchr(dn, ':')) != NULL) {
+ if ((pp = strchr(pp, '.')) != NULL) {
+ if (pp[1] != '\000')
+ ix = atoi(pp+1);
+ }
+ }
+ }
+#endif
+ if ((disp = get_a_display(ix)) == NULL)
+ error("Unable to open the default display");
+ }
+
+ /* See if there is an environment variable ccxx */
+ if (ccxxname[0] == '\000') {
+ char *na;
+ if ((na = getenv("ARGYLL_COLMTER_CAL_SPEC_SET")) != NULL) {
+ strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
+
+ } else if ((na = getenv("ARGYLL_COLMTER_COR_MATRIX")) != NULL) {
+ strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
+ }
+ }
+
+ /* Load up CCMX or CCSS */
+ if (ccxxname[0] != '\000') {
+ if ((cmx = new_ccmx()) == NULL
+ || cmx->read_ccmx(cmx, ccxxname)) {
+ if (cmx != NULL) {
+ cmx->del(cmx);
+ cmx = NULL;
+ }
+
+ /* CCMX failed, try CCSS */
+ if ((ccs = new_ccss()) == NULL
+ || ccs->read_ccss(ccs, ccxxname)) {
+ if (ccs != NULL) {
+ ccs->del(ccs);
+ ccs = NULL;
+ error("Reading CCMX/CCSS File '%s' failed\n", ccxxname);
+ }
+ }
+ }
+ }
+
+ if (fake)
+ comport = -99;
+ if ((icmps = new_icompaths(g_log)) == NULL)
+ error("Finding instrument paths failed");
+ if ((ipath = icmps->get_path(icmps, comport)) == NULL)
+ error("No instrument at port %d",comport);
+
+ if (docalib) {
+ if ((rv = disprd_calibration(ipath, fc, dtype, 0, tele, nadaptive, nocal, disp,
+ webdisp, blackbg, override,
+ 100.0 * hpatscale, 100.0 * vpatscale,
+ ho, vo, g_log)) != 0) {
+ error("docalibration failed with return value %d\n",rv);
+ }
+ }
+
+ if (verify != 2 && doreport == 0) {
+ /* Get the file name argument */
+ if (fa >= argc || argv[fa][0] == '-') usage("Output filname parameter not found");
+ strncpy(outname,argv[fa],MAXNAMEL-4); outname[MAXNAMEL-4] = '\000';
+ strcat(outname,".cal");
+ if (iccoutname[0] == '\000') {
+ strncpy(iccoutname,argv[fa++],MAXNAMEL-4); iccoutname[MAXNAMEL-4] = '\000';
+ strcat(iccoutname,ICC_FILE_EXT);
+ }
+ }
+
+ if (verify == 2) {
+ if (doupdate)
+ warning("Update flag ignored because we're doing a verify only");
+ doupdate = 0;
+ docontrols = 0;
+ }
+
+ if (doreport != 0) {
+ if (verify == 2)
+ warning("Verify flag ignored because we're doing a report only");
+ verify = 0;
+ }
+
+ /* Normally calibrate against native response */
+ if (verify == 2 || doreport == 2)
+ native = 0; /* But measure calibrated response of verify or report calibrated */
+
+ /* Get ready to do some readings */
+ if ((dr = new_disprd(&errc, ipath, fc, dtype, 0, tele, nadaptive, nocal,
+ highres, native, &noramdac, NULL, 0, 0, disp, blackbg, override,
+ webdisp, ccallout, mcallout,
+ 100.0 * hpatscale, 100.0 * vpatscale, ho, vo,
+ cmx != NULL ? cmx->matrix : NULL,
+ ccs != NULL ? ccs->samples : NULL, ccs != NULL ? ccs->no_samp : 0,
+ spec, obType, NULL, bdrift, wdrift,
+ "fake" ICC_FILE_EXT, g_log)) == NULL)
+ error("new_disprd() failed with '%s'\n",disprd_err(errc));
+
+ if (native != 0 && noramdac) {
+ warning("No access to VideoLUTs, so calibrating display as-is rather than native");
+ if (doprofile)
+ warning("Profile will reflect the as-is display response and not contain a 'vcgt' tag");
+ native = 0;
+
+ if (doupdate && doprofile)
+ error("Can't update a profile that doesn't use the 'vcgt' tag for calibration");
+ }
+
+ if (icmps != NULL) {
+ icmps->del(icmps);
+ icmps = NULL;
+ }
+ if (cmx != NULL) {
+ cmx->del(cmx);
+ cmx = NULL;
+ }
+ if (ccs != NULL) {
+ ccs->del(ccs);
+ ccs = NULL;
+ }
+
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+ if (doreport) {
+ col tcols[3] = { /* Base set of test colors */
+ { 0.0, 0.0, 0.0 },
+ { 0.5, 0.5, 0.5 },
+ { 1.0, 1.0, 1.0 }
+ };
+ double cct, cct_de; /* Color temperatures and DE 2K */
+ double cdt, cdt_de;
+ double vct, vct_de;
+ double vdt, vdt_de;
+ double cgamma, w[3], wp[2];
+ int sigbits = 0; /* Number of significant bits in VideoLUT/display/instrument */
+
+ if ((rv = dr->read(dr, tcols, 3, 1, 3, 1, 0, instClamp)) != 0) {
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+
+//printf("~1 Got black = %f, half = %f, white = %f\n",tcols[0].XYZ[1],tcols[1].XYZ[1],tcols[2].XYZ[1]);
+ /* Normalised XYZ white point */
+ w[0] = tcols[2].XYZ[0]/tcols[2].XYZ[1];
+ w[1] = tcols[2].XYZ[1]/tcols[2].XYZ[1];
+ w[2] = tcols[2].XYZ[2]/tcols[2].XYZ[1];
+
+ /* White point chromaticity coordinates */
+ wp[0] = w[0]/(w[0] + w[1] + w[2]);
+ wp[1] = w[1]/(w[0] + w[1] + w[2]);
+
+ cct = comp_ct(&cct_de, NULL, 1, 0, obType, w); /* Compute CCT */
+ cdt = comp_ct(&cdt_de, NULL, 0, 0, obType, w); /* Compute CDT */
+ vct = comp_ct(&vct_de, NULL, 1, 1, obType, w); /* Compute VCT */
+ vdt = comp_ct(&vdt_de, NULL, 0, 1, obType, w); /* Compute VDT */
+
+ /* Compute advertised current gamma - use the gross curve shape for robustness */
+ cgamma = pop_gamma(tcols[0].XYZ[1], tcols[1].XYZ[1], tcols[2].XYZ[1]);
+
+#ifdef MEAS_RES
+ /* See if we can detect what sort of precision the LUT entries */
+ /* have. Our ability to detect this may be limited by the instrument */
+ /* (ie. Huey and Spyder 2) */
+ if (doreport == 1) {
+#define MAX_RES_SAMPS 24
+ col ttt[MAX_RES_SAMPS];
+ int res_samps = 6;
+ double a0, a1, a2, dd;
+ int n;
+ int issig = 0;
+
+ if (verb)
+ printf("Measuring VideoLUT table entry precision.\n");
+
+ /* Run a small state machine until we come to a conclusion */
+ sigbits = 8;
+ for (issig = 0; issig < 2; ) {
+
+ DBG((dbgo,"Trying %d bits\n",sigbits));
+ /* Do the test */
+ for (n = 0; n < res_samps; n++) {
+ double v;
+#if defined(__APPLE__) && defined(__POWERPC__)
+ gcc_bug_fix(sigbits);
+#endif
+ /* Notional test value */
+ v = (5 << (sigbits-3))/((1 << sigbits) - 1.0);
+ /* And -1, 0 , +1 bit test values */
+ if ((n % 3) == 2)
+ v += 1.0/((1 << sigbits) - 1.0);
+ else if ((n % 3) == 1)
+ v += 0.0/((1 << sigbits) - 1.0);
+ else
+ v += -1.0/((1 << sigbits) - 1.0);
+ ttt[n].r = ttt[n].g = ttt[n].b = v;
+ }
+ if ((rv = dr->read(dr, ttt, res_samps, 1, res_samps, 1, 0, instNoClamp)) != 0) {
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+ /* Average the readings for each test value */
+ a0 = a1 = a2 = 0.0;
+ for (n = 0; n < res_samps; n++) {
+ double v = ttt[n].XYZ[1];
+ if ((n % 3) == 2) {
+ a2 += v;
+ } else if ((n % 3) == 1) {
+ a1 += v;
+ } else {
+ a0 += v;
+ }
+ }
+ a0 /= (res_samps / 3.0);
+ a1 /= (res_samps / 3.0);
+ a2 /= (res_samps / 3.0);
+ /* Judge significance of any differences */
+ dd = 0.0;
+ for (n = 0; n < res_samps; n++) {
+ double tt;
+ if ((n % 3) == 2)
+ tt = fabs(a2 - ttt[n].XYZ[1]);
+ else if ((n % 3) == 1)
+ tt = fabs(a1 - ttt[n].XYZ[1]);
+ else
+ tt = fabs(a0 - ttt[n].XYZ[1]);
+ dd += tt * tt;
+ }
+ dd /= res_samps;
+ dd = sqrt(dd);
+ if (fabs(a1 - a0) > (2.0 * dd) && fabs(a2 - a1) > (2.0 * dd))
+ issig = 1; /* Noticable difference */
+ else
+ issig = 0; /* No noticable difference */
+ DBG((dbgo,"Bits %d: Between = %f, %f within = %f, sig = %s\n",sigbits, fabs(a1 - a0), fabs(a2 - a1),dd, issig ? "yes" : "no"));
+
+ switch(sigbits) {
+ case 8: /* Do another trial */
+ if (issig) {
+ sigbits = 10;
+ res_samps = 6;
+ } else {
+ sigbits = 6;
+ }
+ break;
+ case 6: /* Do another trial or give up */
+ if (issig) {
+ sigbits = 7;
+ } else {
+ sigbits = 0;
+ issig = 2; /* Give up */
+ }
+ break;
+ case 7: /* Terminal */
+ if (!issig)
+ sigbits = 6;
+ issig = 2; /* Stop here */
+ break;
+ case 10: /* Do another trial */
+ if (issig) {
+ sigbits = 12;
+ res_samps = 9;
+ } else {
+ sigbits = 9;
+ }
+ break;
+ case 12: /* Do another trial or give up */
+ if (issig) {
+ issig = 2; /* Stop here */
+ } else {
+ sigbits = 11;
+ }
+ break;
+ case 11: /* Terminal */
+ if (!issig)
+ sigbits = 10;
+ issig = 2; /* Stop here */
+ break;
+ case 9: /* Terminal */
+ if (!issig)
+ sigbits = 8;
+ issig = 2; /* Stop here */
+ break;
+
+ default:
+ error("Unexpected number of bits in depth test (bits)",sigbits);
+ }
+ }
+ }
+#endif /* MEAS_RES */
+
+ if (doreport == 2)
+ printf("Current calibration response:\n");
+ else
+ printf("Uncalibrated response:\n");
+ printf("Black level = %.2f cd/m^2\n",tcols[0].XYZ[1]);
+ printf("White level = %.2f cd/m^2\n",tcols[2].XYZ[1]);
+ printf("Aprox. gamma = %.2f\n",cgamma);
+ printf("Contrast ratio = %.0f:1\n",tcols[2].XYZ[1]/tcols[0].XYZ[1]);
+ printf("White chromaticity coordinates %.4f, %.4f\n",wp[0],wp[1]);
+ printf("White Correlated Color Temperature = %.0fK, DE 2K to locus = %4.1f\n",cct,cct_de);
+ printf("White Correlated Daylight Temperature = %.0fK, DE 2K to locus = %4.1f\n",cdt,cdt_de);
+ printf("White Visual Color Temperature = %.0fK, DE 2K to locus = %4.1f\n",vct,vct_de);
+ printf("White Visual Daylight Temperature = %.0fK, DE 2K to locus = %4.1f\n",vdt,vdt_de);
+#ifdef MEAS_RES
+ if (doreport == 1) {
+ if (sigbits == 0) {
+ warning("Unable to determine LUT entry bit depth - problems ?");
+ } else {
+ printf("Effective LUT entry depth seems to be %d bits\n",sigbits);
+ }
+ }
+#endif /* MEAS_RES */
+ dr->del(dr);
+ exit(0);
+ }
+
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+ /* If we're updating, retrieve the previously used settings, */
+ /* and device model */
+ if (doupdate) {
+ cgats *icg; /* output cgats structure */
+ int nsamp;
+ mcvco *rdv[3]; /* Scattered data for ramdac curves */
+ int fi; /* Field index */
+ int si[4]; /* Set fields */
+
+ if (verb) {
+ if (doprofile)
+ printf("Updating previous calibration and profile\n");
+ else
+ printf("Updating previous calibration\n");
+ }
+
+ icg = new_cgats(); /* Create a CGATS structure */
+ icg->add_other(icg, "CAL"); /* our special type is Calibration file */
+
+ if (icg->read_name(icg, outname)) {
+ dr->del(dr);
+ error("Can't update '%s' - read error : %s",outname, icg->err);
+ }
+
+ if (icg->ntables == 0
+ || icg->t[0].tt != tt_other || icg->t[0].oi != 0
+ || icg->t[1].tt != tt_other || icg->t[1].oi != 0) {
+ dr->del(dr);
+ error("Can't update '%s' - wrong type of file",outname);
+ }
+ if (icg->ntables < 2) {
+ dr->del(dr);
+ error("Can't update '%s' - there aren't two tables",outname);
+ }
+
+//printf("~1 reading previous cal, got 2 tables\n");
+
+ /* Read in the setup, user and model values */
+
+ if (dtype == 0) { /* If the use hasn't set anything */
+ if ((fi = icg->find_kword(icg, 0, "DEVICE_TYPE")) >= 0) {
+ if (strcmp(icg->t[0].kdata[fi], "CRT") == 0)
+ dtype = 'c';
+ else if (strcmp(icg->t[0].kdata[fi], "LCD") == 0)
+ dtype = 'l';
+ else
+ dtype = icg->t[0].kdata[fi][0];
+ }
+ }
+//printf("~1 dealt with device type\n");
+
+ if ((fi = icg->find_kword(icg, 0, "TARGET_WHITE_XYZ")) < 0) {
+ dr->del(dr);
+ error ("Can't update '%s' - can't find field 'TARGET_WHITE_XYZ'",outname);
+ }
+ if (sscanf(icg->t[0].kdata[fi], "%lf %lf %lf", &x.twh[0], &x.twh[1], &x.twh[2]) != 3) {
+ dr->del(dr);
+ error ("Can't update '%s' - reading field 'TARGET_WHITE_XYZ' failed",outname);
+ }
+ x.nwh[0] = x.twh[0] / x.twh[1];
+ x.nwh[1] = x.twh[1] / x.twh[1];
+ x.nwh[2] = x.twh[2] / x.twh[1];
+
+ if ((fi = icg->find_kword(icg, 0, "NATIVE_TARGET_WHITE")) >= 0) {
+ wpx = wpy = 0.0;
+ temp = 0.0;
+ tbright = 0.0;
+ } else {
+ wpx = wpy = 0.0001;
+ temp = 1.0;
+ tbright = 1.0;
+ }
+//printf("~1 dealt with target white\n");
+
+ if ((fi = icg->find_kword(icg, 0, "TARGET_GAMMA")) < 0) {
+ dr->del(dr);
+ error ("Can't update '%s' - can't find field 'TARGET_GAMMA'",outname);
+ }
+ if (strcmp(icg->t[0].kdata[fi], "L_STAR") == 0)
+ x.gammat = gt_Lab;
+ else if (strcmp(icg->t[0].kdata[fi], "sRGB") == 0)
+ x.gammat = gt_sRGB;
+ else if (strcmp(icg->t[0].kdata[fi], "REC709") == 0)
+ x.gammat = gt_Rec709;
+ else if (strcmp(icg->t[0].kdata[fi], "SMPTE240M") == 0)
+ x.gammat = gt_SMPTE240M;
+ else {
+ x.gammat = 0;
+ gamma = atof(icg->t[0].kdata[fi]);
+
+ if (fabs(gamma) < 0.1 || fabs(gamma) > 5.0) {
+ dr->del(dr);
+ error ("Can't update '%s' - field 'TARGET_GAMMA' has bad value %f",outname,fabs(gamma));
+ }
+ if (gamma < 0.0) { /* Effective gamma = actual power value */
+ egamma = -gamma;
+ }
+ }
+ if ((fi = icg->find_kword(icg, 0, "DEGREE_OF_BLACK_OUTPUT_OFFSET")) < 0) {
+ /* Backward compatibility if value is not present */
+ if (x.gammat == gt_Lab || x.gammat == gt_sRGB)
+ x.oofff = 1.0;
+ else
+ x.oofff = 0.0;
+ } else {
+ x.oofff = atof(icg->t[0].kdata[fi]);
+ }
+ if ((fi = icg->find_kword(icg, 0, "TARGET_BLACK_BRIGHTNESS")) < 0) {
+ bkbright = 0.0; /* Native */
+ } else {
+ bkbright = atof(icg->t[0].kdata[fi]);
+ }
+
+
+ if ((fi = icg->find_kword(icg, 0, "BLACK_POINT_CORRECTION")) < 0) {
+ dr->del(dr);
+ error ("Can't update '%s' - can't find field 'BLACK_POINT_CORRECTION'",outname);
+ }
+ bkcorrect = atof(icg->t[0].kdata[fi]);
+ if (bkcorrect < 0.0 || bkcorrect > 1.0) {
+ dr->del(dr);
+ error ("Can't update '%s' - field 'BLACK_POINT_CORRECTION' has bad value %f",outname,bkcorrect);
+ }
+
+ if ((fi = icg->find_kword(icg, 0, "BLACK_NEUTRAL_BLEND_RATE")) < 0) {
+ x.nbrate = 8.0; /* Backwards compatibility value */
+ } else {
+ x.nbrate = atof(icg->t[0].kdata[fi]);
+ if (x.nbrate < 0.05 || x.nbrate > 20.0) {
+ dr->del(dr);
+ error ("Can't update '%s' - field 'BLACK_NEUTRAL_BLEND_RATE' has bad value %f",outname,x.nbrate);
+ }
+ }
+
+ if ((fi = icg->find_kword(icg, 0, "QUALITY")) < 0) {
+ dr->del(dr);
+ error ("Can't update '%s' - can't find field 'QUALITY'",outname);
+ }
+ if (quality < -50) { /* User hasn't overridden quality */
+ if (strcmp(icg->t[0].kdata[fi], "ultra low") == 0)
+ quality = -3;
+ else if (strcmp(icg->t[0].kdata[fi], "very low") == 0)
+ quality = -2;
+ else if (strcmp(icg->t[0].kdata[fi], "low") == 0)
+ quality = -1;
+ else if (strcmp(icg->t[0].kdata[fi], "medium") == 0)
+ quality = 0;
+ else if (strcmp(icg->t[0].kdata[fi], "high") == 0)
+ quality = 1;
+ else if (strcmp(icg->t[0].kdata[fi], "ultra high") == 0)
+ quality = 2;
+ else {
+ dr->del(dr);
+ error ("Can't update '%s' - field 'QUALITY' has unrecognised value '%s'",
+ outname,icg->t[0].kdata[fi]);
+ }
+ }
+//printf("~1 dealt with quality\n");
+
+ /* Read in the last set of calibration curves used */
+ if ((nsamp = icg->t[0].nsets) < 2) {
+ dr->del(dr);
+ error("Can't update '%s' - %d not enough data points in calibration curves",
+ outname,nsamp);
+ }
+//printf("~1 got %d points in calibration curves\n",nsamp);
+
+ for (k = 0; k < 3; k++) {
+ if ((x.rdac[k] = new_mcv()) == NULL) {
+ dr->del(dr);
+ error("new_mcv x.rdac[%d] failed",k);
+ }
+ if ((rdv[k] = malloc(sizeof(mcvco) * nsamp)) == NULL) {
+ dr->del(dr);
+ error ("Malloc of scattered data points failed");
+ }
+ }
+//printf("~1 allocated calibration curve objects\n");
+
+ /* Read the current calibration curve points (usually CAL_RES of them) */
+ for (k = 0; k < 4; k++) {
+ char *fnames[4] = { "RGB_I", "RGB_R", "RGB_G", "RGB_B" };
+
+ if ((si[k] = icg->find_field(icg, 0, fnames[k])) < 0) {
+ dr->del(dr);
+ error ("Can't updata '%s' - can't find field '%s'",outname,fnames[k]);
+ }
+ if (icg->t[0].ftype[si[k]] != r_t) {
+ dr->del(dr);
+ error ("Can't updata '%s' - field '%s' is wrong type",outname,fnames[k]);
+ }
+ }
+//printf("~1 Found calibration curve fields\n");
+
+ for (i = 0; i < nsamp; i++) {
+ rdv[0][i].p =
+ rdv[1][i].p =
+ rdv[2][i].p =
+ *((double *)icg->t[0].fdata[i][si[0]]);
+ for (k = 0; k < 3; k++) { /* RGB */
+ rdv[k][i].v = *((double *)icg->t[0].fdata[i][si[k + 1]]);
+ }
+ rdv[0][i].w = rdv[1][i].w = rdv[2][i].w = 1.0;
+ }
+//printf("~1 Read calibration curve data points\n");
+ for (k = 0; k < 3; k++) {
+ x.rdac[k]->fit(x.rdac[k], 0, fitord, rdv[k], nsamp, RDAC_SMOOTH);
+ free (rdv[k]);
+ }
+//printf("~1 Fitted calibration curves\n");
+
+ /* Read in the per channel forward model curves */
+ for (k = 0; k < 3; k++) {
+ char *fnames[3] = { "R_P", "G_P", "B_P" };
+ double *pp;
+
+//printf("~1 Reading device curve channel %d\n",k);
+ if ((si[k] = icg->find_field(icg, 1, fnames[k])) < 0) {
+ dr->del(dr);
+ error ("Can't updata '%s' - can't find field '%s'",outname,fnames[k]);
+ }
+ if (icg->t[1].ftype[si[k]] != r_t) {
+ dr->del(dr);
+ error ("Can't updata '%s' - field '%s' is wrong type",outname,fnames[k]);
+ }
+ /* Create the model curves */
+ if ((pp = (double *)malloc(icg->t[1].nsets * sizeof(double))) == NULL) {
+ dr->del(dr);
+ error ("Malloc of device curve parameters");
+ }
+ for (i = 0; i < icg->t[1].nsets; i++)
+ pp[i] = *((double *)icg->t[1].fdata[i][si[k]]);
+
+ if ((x.dcvs[k] = new_mcv_p(pp, icg->t[1].nsets)) == NULL) {
+ dr->del(dr);
+ error("new_mcv x.dcvs[%d] failed",k);
+ }
+ free(pp);
+ }
+
+ icg->del(icg);
+//printf("~1 read in previous settings and device model\n");
+ }
+
+ /* Be nice - check we can read the iccprofile before calibrating the display */
+ if (verify != 2 && doupdate && doprofile) {
+ icmFile *ic_fp;
+ icc *icco;
+
+ if ((icco = new_icc()) == NULL) {
+ dr->del(dr);
+ error ("Creation of ICC object to read profile '%s' failed",iccoutname);
+ }
+
+ /* Open up the profile for reading */
+ if ((ic_fp = new_icmFileStd_name(iccoutname,"r")) == NULL) {
+ dr->del(dr);
+ error ("Can't open file '%s'",iccoutname);
+ }
+
+ /* Read header etc. */
+ if ((rv = icco->read(icco,ic_fp,0)) != 0) {
+ dr->del(dr);
+ error ("Reading profile '%s' failed with %d, %s",iccoutname, rv,icco->err);
+ }
+
+ ic_fp->del(ic_fp);
+
+ if (icco->find_tag(icco, icSigVideoCardGammaTag) != 0) {
+ dr->del(dr);
+ error("Can't find VideoCardGamma tag in file '%s': %d, %s",
+ iccoutname, icco->errc,icco->err);
+ }
+ icco->del(icco);
+ }
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+ /* Convert quality level to iterations etc. */
+ /* Note that final tolerance is often double the */
+ /* final errth, because one more corrections is always */
+ /* performed after the last reading. */
+ switch (quality) {
+ case -3: /* Test value */
+ isteps = 3;
+ rsteps = 9;
+ mxits = 1;
+ errthr = 2.0;
+ break;
+ case -2: /* Very low */
+ isteps = 10;
+ rsteps = 16;
+ errthr = 1.5;
+ if (doupdate)
+ mxits = 1;
+ else
+ mxits = 1;
+ break;
+ case -1: /* Low */
+ if (verify != 2 && doprofile && !doupdate)
+ isteps = 24; /* Use more steps if we're creating a profile */
+ else
+ isteps = 12;
+ rsteps = 32;
+ errthr = 0.9;
+ if (doupdate)
+ mxits = 1;
+ else
+ mxits = 2;
+ break;
+ default:
+ case 0: /* Medum */
+ quality = 0; /* In case it wasn't set */
+ if (verify != 2 && doprofile && !doupdate)
+ isteps = 32; /* Use more steps if we're creating a profile */
+ else
+ isteps = 16;
+ rsteps = 64;
+ errthr = 0.6;
+ if (doupdate)
+ mxits = 1;
+ else
+ mxits = 3;
+ break;
+ case 1: /* High */
+ if (verify != 2 && doprofile && !doupdate)
+ isteps = 40; /* Use more steps if we're creating a profile */
+ else
+ isteps = 20;
+ rsteps = 96;
+ errthr = 0.4;
+ if (doupdate)
+ mxits = 1;
+ else
+ mxits = 4;
+ break;
+ case 2: /* Ultra */
+ if (verify != 2 && doprofile && !doupdate)
+ isteps = 48; /* Use more steps if we're creating a profile */
+ else
+ isteps = 24;
+ rsteps = 128;
+ errthr = 0.25;
+ if (doupdate)
+ mxits = 1;
+ else
+ mxits = 5;
+ break;
+ }
+
+ /* Set native white target flag in calx so that other things can play the game.. */
+ if (wpx == 0.0 && wpy == 0.0 && temp == 0.0 && tbright == 0.0)
+ x.nat = 1;
+ else
+ x.nat = 0;
+
+ /* Say something about what we're doing */
+ if (verb) {
+ if (dtype > 0)
+ printf("Display type is '%c'\n",dtype);
+
+ if (doupdate) {
+ if (x.nat)
+ printf("Target white = native white point & brightness\n");
+ else
+ printf("Target white = XYZ %f %f %f\n",
+ x.twh[0], x.twh[1], x.twh[2]);
+ } else {
+ if (wpx > 0.0 || wpy > 0.0)
+ printf("Target white = xy %f %f\n",wpx,wpy);
+ else if (temp > 0.0) {
+ if (planckian)
+ printf("Target white = %f degrees kelvin Planckian (black body) spectrum\n",temp);
+ else
+ printf("Target white = %f degrees kelvin Daylight spectrum\n",temp);
+ } else
+ printf("Target white = native white point\n");
+
+ if (tbright > 0.0)
+ printf("Target white brightness = %f cd/m^2\n",tbright);
+ else
+ printf("Target white brightness = native brightness\n");
+ if (bkbright > 0.0)
+ printf("Target black brightness = %f cd/m^2\n",bkbright);
+ else
+ printf("Target black brightness = native brightness\n");
+ }
+
+ switch(x.gammat) {
+ case gt_power:
+ if (egamma > 0.0)
+ printf("Target effective gamma = %f\n",egamma);
+ else
+ printf("Target advertised gamma = %f\n",gamma);
+ break;
+ case gt_Lab:
+ printf("Target gamma = L* curve\n");
+ break;
+ case gt_sRGB:
+ printf("Target gamma = sRGB curve\n");
+ break;
+ case gt_Rec709:
+ printf("Target gamma = REC 709 curve\n");
+ break;
+ case gt_SMPTE240M:
+ printf("Target gamma = SMPTE 240M curve\n");
+ break;
+ default:
+ error("Unknown gamma type");
+ }
+ }
+
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+ /* Go through the procedure of adjusting monitor controls */
+ if (docontrols) {
+ int rgbch = 0; /* Got RBG Yxy ? */
+ double rgbXYZ[3][3]; /* The RGB XYZ */
+
+ /* Make sure drift comp. is off for interactive adjustment */
+ dr->change_drift_comp(dr, 0, 0);
+
+ /* Until the user is done */
+ printf("\nDisplay adjustment menu:");
+ for (;;) {
+ int c;
+
+ /* Print the menue of adjustments */
+ printf("\nPress 1 .. 7\n");
+ printf("1) Black level (CRT: Offset/Brightness)\n");
+ printf("2) White point (Color temperature, R,G,B, Gain/Contrast)\n");
+ printf("3) White level (CRT: Gain/Contrast, LCD: Brightness/Backlight)\n");
+ printf("4) Black point (R,G,B, Offset/Brightness)\n");
+ printf("5) Check all\n");
+ printf("6) Measure and set ambient for viewing condition adjustment\n");
+ printf("7) Continue on to calibration\n");
+ printf("8) Exit\n");
+
+ empty_con_chars();
+ c = next_con_char();
+
+ /* Black level adjustment */
+ /* Due to the possibility of the channel offsets not being even, */
+ /* we use the largest of the XYZ values after they have been */
+ /* scaled to be even acording to the white XYZ balance. */
+ /* It's safer to set the black level a bit low, and then the */
+ /* calibration curves can bump the low ones up. */
+ if (c == '1') {
+ col tcols[3] = { /* Base set of test colors */
+ { 0.0, 0.0, 0.0 },
+ { 0.5, 0.5, 0.5 }, /* And 1% values */
+ { 1.0, 1.0, 1.0 }
+ };
+ int ff;
+ double mgamma, tar1, dev1;
+
+ printf("Doing some initial measurements\n");
+ /* Do an initial set of readings to set 1% output mark */
+ if ((rv = dr->read(dr, tcols, 3, 0, 0, 1, 0, instClamp)) != 0) {
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+
+ if (verb) {
+ printf("Black = XYZ %6.2f %6.2f %6.2f\n",tcols[0].XYZ[0],
+ tcols[0].XYZ[1], tcols[0].XYZ[2]);
+ printf("Grey = XYZ %6.2f %6.2f %6.2f\n",tcols[1].XYZ[0],
+ tcols[1].XYZ[1], tcols[1].XYZ[2]);
+ printf("White = XYZ %6.2f %6.2f %6.2f\n",tcols[2].XYZ[0],
+ tcols[2].XYZ[1], tcols[2].XYZ[2]);
+ }
+
+ /* Advertised Gamma - Gross curve shape */
+ mgamma = pop_gamma(tcols[0].XYZ[1], tcols[1].XYZ[1], tcols[2].XYZ[1]);
+
+ dev1 = pow(0.01, 1.0/mgamma);
+//printf("~1 device level for 1%% output = %f\n",dev1);
+ tcols[1].r = tcols[1].g = tcols[1].b = dev1;
+ tar1 = 0.01 * tcols[2].XYZ[1];
+
+ printf("\nAdjust CRT brightness to get target level. Press space when done.\n");
+ printf(" Target %.2f\n",tar1);
+ for (ff = 0;; ff ^= 1) {
+ double dir; /* Direction to adjust brightness */
+ double sv[3], val1; /* Scaled values */
+ if ((rv = dr->read(dr, tcols+1, 1, 0, 0, 1, ' ',instClamp)) != 0) {
+ if (rv == 4)
+ break; /* User is done with this adjustment */
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+ /* Scale 1% values by ratio of Y to white XYZ */
+ sv[0] = tcols[1].XYZ[0] * tcols[2].XYZ[1]/tcols[2].XYZ[0];
+ sv[1] = tcols[1].XYZ[1];
+ sv[2] = tcols[1].XYZ[2] * tcols[2].XYZ[1]/tcols[2].XYZ[2];
+//printf("~1 scaled readings = %f %f %f\n",sv[0],sv[1],sv[2]);
+ val1 = sv[1];
+ if (sv[0] > val1)
+ val1 = sv[0];
+ if (sv[2] > val1)
+ val1 = sv[2];
+ dir = tar1 - val1;
+ if (fabs(dir) < 0.01)
+ dir = 0.0;
+ printf("%c%c Current %.2f %c",
+ cr_char,
+ ff == 0 ? '/' : '\\',
+ val1,
+ dir < 0.0 ? '-' : dir > 0.0 ? '+' : '=');
+ fflush(stdout);
+ }
+ printf("\n");
+
+ /* White point adjustment */
+ } else if (c == '2') {
+ int nat = 0; /* NZ if using native white as target */
+ col tcols[1] = { /* Base set of test colors */
+ { 1.0, 1.0, 1.0 }
+ };
+ int ff;
+ double tYxy[3]; /* Target white chromaticities */
+ icmXYZNumber tXYZ; /* Target white as XYZ */
+ double tLab[3]; /* Target white as Lab or UCS */
+ double tarw; /* Target brightness */
+ double Lab[3]; /* Last measured point Lab or UCS */
+ double ct = 0.0, ct_de; /* Color temperature & delta E to white locus */
+
+ printf("Doing some initial measurements\n");
+
+ if (rgbch == 0) { /* Figure the RGB chromaticities */
+ col ccols[3] = {
+ { 1.0, 0.0, 0.0 },
+ { 0.0, 1.0, 0.0 },
+ { 0.0, 0.0, 1.0 }
+ };
+ if ((rv = dr->read(dr, ccols, 3, 0, 0, 1, 0, instClamp)) != 0) {
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+ if (verb) {
+ printf("Red = XYZ %6.2f %6.2f %6.2f\n",ccols[0].XYZ[0],
+ ccols[0].XYZ[1], ccols[0].XYZ[2]);
+ printf("Green = XYZ %6.2f %6.2f %6.2f\n",ccols[1].XYZ[0],
+ ccols[1].XYZ[1], ccols[1].XYZ[2]);
+ printf("Blue = XYZ %6.2f %6.2f %6.2f\n",ccols[2].XYZ[0],
+ ccols[2].XYZ[1], ccols[2].XYZ[2]);
+ }
+ for (i = 0; i < 3; i++)
+ icmAry2Ary(rgbXYZ[i], ccols[i].XYZ);
+ rgbch = 1;
+ }
+ /* Do an initial set of readings to set full output mark */
+ if ((rv = dr->read(dr, tcols, 1, 0, 0, 1, 0, instClamp)) != 0) {
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+ if (verb) {
+ printf("White = XYZ %6.2f %6.2f %6.2f\n",tcols[0].XYZ[0],
+ tcols[0].XYZ[1], tcols[0].XYZ[2]);
+ }
+
+ /* Figure out the target white chromaticity */
+ if (wpx > 0.0 || wpy > 0.0) { /* xy coordinates */
+ tYxy[0] = 1.0;
+ tYxy[1] = wpx;
+ tYxy[2] = wpy;
+ } else if (temp > 0.0) { /* Daylight color temperature */
+ double XYZ[3];
+ if (planckian)
+ rv = icx_ill_sp2XYZ(XYZ, icxOT_default, NULL, icxIT_Ptemp, temp, NULL);
+ else
+ rv = icx_ill_sp2XYZ(XYZ, icxOT_default, NULL, icxIT_Dtemp, temp, NULL);
+ if (rv != 0)
+ error("Failed to compute XYZ of target color temperature %f\n",temp);
+ icmXYZ2Yxy(tYxy, XYZ);
+ } else { /* Native white */
+ icmXYZ2Yxy(tYxy, tcols[0].XYZ);
+ nat = 1;
+ }
+
+ /* Figure out the target white brightness */
+ /* Note we're not taking the device gamut into account here */
+ if (tbright > 0.0) { /* Given brightness */
+ tarw = tbright;
+//printf("~1 set tarw %f from tbright\n",tarw);
+ } else { /* Native/maximum brightness */
+ tarw = tcols[0].XYZ[1];
+//printf("~1 set tarw %f from tcols[1]\n",tarw);
+ }
+
+ if (!nat) { /* Target is a specified white */
+ printf("\nAdjust R,G & B gain to get target x,y. Press space when done.\n");
+ printf(" Target Br %.2f, x %.4f , y %.4f \n",
+ tarw, tYxy[1],tYxy[2]);
+
+ } else { /* Target is native white */
+ printf("\nAdjust R,G & B gain to desired white point. Press space when done.\n");
+ /* Compute the CT and delta E to white locus of target */
+ ct = comp_ct(&ct_de, NULL, planckian, dovct, obType, tcols[0].XYZ);
+ printf(" Initial Br %.2f, x %.4f , y %.4f , %c%cT %4.0fK DE 2K %4.1f\n",
+ tarw, tYxy[1],tYxy[2],
+ dovct ? 'V' : 'C', planckian ? 'C' : 'D', ct,ct_de);
+ }
+ for (ff = 0;; ff ^= 1) {
+ double dir; /* Direction to adjust brightness */
+ double Yxy[3]; /* Yxy of current reading */
+ double rgbdir[3]; /* Direction to adjust RGB */
+ double rgbxdir[3]; /* Biggest to move */
+ double bdir, terr;
+ int bx = 0;
+
+ if ((rv = dr->read(dr, tcols, 1, 0, 0, 1, ' ', instClamp)) != 0) {
+ if (rv == 4)
+ break; /* User is done with this adjustment */
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+ dir = tarw - tcols[0].XYZ[1];
+ if (fabs(dir) < 0.01)
+ dir = 0.0;
+
+ icmXYZ2Yxy(Yxy, tcols[0].XYZ);
+
+ if (!nat) { /* Target is a specified white */
+ /* Compute values we need for delta E and RGB direction */
+ icmYxy2XYZ(tLab, tYxy);
+ tLab[0] /= tLab[1];
+ tLab[2] /= tLab[1];
+ tLab[1] /= tLab[1];
+ icmAry2XYZ(tXYZ, tLab); /* Lab white reference */
+ icmXYZ2Lab(&tXYZ, tLab, tLab); /* Target Lab */
+
+ icmAry2Ary(Lab, tcols[0].XYZ);
+ Lab[0] /= Lab[1];
+ Lab[2] /= Lab[1];
+ Lab[1] /= Lab[1];
+ icmXYZ2Lab(&tXYZ, Lab, Lab); /* Current Lab */
+
+ } else { /* Target is native white */
+ double lxyz[3]; /* Locus XYZ */
+ ct = comp_ct(&ct_de, lxyz, planckian, dovct, obType, tcols[0].XYZ);
+
+ icmXYZ2Yxy(tYxy, lxyz);
+ /* lxyz is already normalised */
+ icmAry2XYZ(tXYZ, lxyz); /* Lab white reference */
+ if (dovct)
+ icmXYZ2Lab(&tXYZ, tLab, lxyz); /* Target Lab */
+ else
+ icmXYZ21960UCS(tLab, lxyz); /* Target UCS */
+
+ icmAry2Ary(Lab, tcols[0].XYZ);
+ Lab[0] /= Lab[1];
+ Lab[2] /= Lab[1];
+ Lab[1] /= Lab[1];
+ if (dovct)
+ icmXYZ2Lab(&tXYZ, Lab, Lab); /* Current Lab */
+ else
+ icmXYZ21960UCS(Lab, Lab); /* Current UCS */
+ }
+
+ /* Compute dot products */
+ bdir = 0.0;
+ for (i = 0; i < 3; i++) {
+ double rgbLab[3];
+
+ if (dovct)
+ icmXYZ2Lab(&tXYZ, rgbLab, rgbXYZ[i]);
+ else
+ icmXYZ21960UCS(rgbLab, rgbXYZ[i]);
+ rgbdir[i] = (tLab[1] - Lab[1]) * (rgbLab[1] - Lab[1])
+ + (tLab[2] - Lab[2]) * (rgbLab[2] - Lab[2]);
+ rgbxdir[i] = 0.0;
+ if (fabs(rgbdir[i]) > fabs(bdir)) {
+ bdir = rgbdir[i];
+ bx = i;
+ }
+ }
+
+ /* See how close to the target we are */
+ terr = sqrt((tLab[1] - Lab[1]) * (tLab[1] - Lab[1])
+ + (tLab[2] - Lab[2]) * (tLab[2] - Lab[2]));
+ if (terr < 0.1)
+ rgbdir[0] = rgbdir[1] = rgbdir[2] = 0.0;
+ rgbxdir[bx] = rgbdir[bx];
+
+
+ if (!nat) {
+ printf("%c%c Current Br %.2f, x %.4f%c, y %.4f%c DE %4.1f R%c%c G%c%c B%c%c ",
+ cr_char,
+ ff == 0 ? '/' : '\\',
+ tcols[0].XYZ[1],
+ Yxy[1],
+ Yxy[1] > tYxy[1] ? '-' : Yxy[1] < tYxy[1] ? '+' : '=',
+ Yxy[2],
+ Yxy[2] > tYxy[2] ? '-' : Yxy[2] < tYxy[2] ? '+' : '=',
+ icmCIE2K(tLab, Lab),
+ rgbdir[0] < 0.0 ? '-' : rgbdir[0] > 0.0 ? '+' : '=',
+ rgbxdir[0] < 0.0 ? '-' : rgbxdir[0] > 0.0 ? '+' : ' ',
+ rgbdir[1] < 0.0 ? '-' : rgbdir[1] > 0.0 ? '+' : '=',
+ rgbxdir[1] < 0.0 ? '-' : rgbxdir[1] > 0.0 ? '+' : ' ',
+ rgbdir[2] < 0.0 ? '-' : rgbdir[2] > 0.0 ? '+' : '=',
+ rgbxdir[2] < 0.0 ? '-' : rgbxdir[2] > 0.0 ? '+' : ' ');
+ } else {
+ printf("%c%c Current Br %.2f, x %.4f%c, y %.4f%c %c%cT %4.0fK DE 2K %4.1f R%c%c G%c%c B%c%c ",
+ cr_char,
+ ff == 0 ? '/' : '\\',
+ tcols[0].XYZ[1],
+ Yxy[1],
+ Yxy[1] > tYxy[1] ? '-': Yxy[1] < tYxy[1] ? '+' : '=',
+ Yxy[2],
+ Yxy[2] > tYxy[2] ? '-': Yxy[2] < tYxy[2] ? '+' : '=',
+ dovct ? 'V' : 'C', planckian ? 'C' : 'D', ct,ct_de,
+ rgbdir[0] < 0.0 ? '-' : rgbdir[0] > 0.0 ? '+' : '=',
+ rgbxdir[0] < 0.0 ? '-' : rgbxdir[0] > 0.0 ? '+' : ' ',
+ rgbdir[1] < 0.0 ? '-' : rgbdir[1] > 0.0 ? '+' : '=',
+ rgbxdir[1] < 0.0 ? '-' : rgbxdir[1] > 0.0 ? '+' : ' ',
+ rgbdir[2] < 0.0 ? '-' : rgbdir[2] > 0.0 ? '+' : '=',
+ rgbxdir[2] < 0.0 ? '-' : rgbxdir[2] > 0.0 ? '+' : ' ');
+ }
+ fflush(stdout);
+ }
+ printf("\n");
+
+ /* White level adjustment */
+ } else if (c == '3') {
+ col tcols[1] = { /* Base set of test colors */
+ { 1.0, 1.0, 1.0 }
+ };
+ int ff;
+ double tarw;
+
+ printf("Doing some initial measurements\n");
+ /* Do an initial set of readings to set full output mark */
+ if ((rv = dr->read(dr, tcols, 1, 0, 0, 1, 0, instClamp)) != 0) {
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+ if (verb) {
+ printf("White = XYZ %6.2f %6.2f %6.2f\n",tcols[0].XYZ[0],
+ tcols[0].XYZ[1], tcols[0].XYZ[2]);
+ }
+
+ /* Figure out the target white brightness */
+ /* Note we're not taking the device gamut into account here */
+ if (tbright > 0.0) /* Given brightness */
+ tarw = tbright;
+ else /* Native/maximum brightness */
+ tarw = tcols[0].XYZ[1];
+
+ if (tbright > 0.0) {
+ printf("\nAdjust CRT Contrast or LCD Brightness to get target level. Press space when done.\n");
+ printf(" Target %.2f\n", tarw);
+ } else {
+ printf("\nAdjust CRT Contrast or LCD Brightness to desired level. Press space when done.\n");
+ printf(" Initial %.2f\n", tarw);
+ }
+ for (ff = 0;; ff ^= 1) {
+ double dir; /* Direction to adjust brightness */
+
+ if ((rv = dr->read(dr, tcols, 1, 0, 0, 1, ' ', instClamp)) != 0) {
+ if (rv == 4)
+ break; /* User is done with this adjustment */
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+ dir = tarw - tcols[0].XYZ[1];
+ if (fabs(dir) < 0.01)
+ dir = 0.0;
+
+ if (tbright > 0.0)
+ printf("%c%c Current %.2f %c",
+ cr_char,
+ ff == 0 ? '/' : '\\',
+ tcols[0].XYZ[1],
+ dir < 0.0 ? '-' : dir > 0.0 ? '+' : '=');
+ else
+ printf("%c%c Current %.2f ",
+ cr_char,
+ ff == 0 ? '/' : '\\',
+ tcols[0].XYZ[1]);
+ fflush(stdout);
+ }
+ printf("\n");
+
+ /* Black point adjustment */
+ } else if (c == '4') {
+ col tcols[3] = { /* Base set of test colors */
+ { 0.0, 0.0, 0.0 },
+ { 0.5, 0.5, 0.5 }, /* And 1% values */
+ { 1.0, 1.0, 1.0 }
+ };
+ int ff;
+ double tYxy[3]; /* Target white chromaticities */
+ icmXYZNumber tXYZ; /* Target white as XYZ */
+ double tLab[3]; /* Target white as Lab or UCS */
+ double mgamma, tar1, dev1;
+ double Lab[3]; /* Last measured point Lab */
+
+ printf("Doing some initial measurements\n");
+
+ if (rgbch == 0) { /* Figure the RGB chromaticities */
+ col ccols[3] = {
+ { 1.0, 0.0, 0.0 },
+ { 0.0, 1.0, 0.0 },
+ { 0.0, 0.0, 1.0 }
+ };
+ if ((rv = dr->read(dr, ccols, 3, 0, 0, 1, 0, instClamp)) != 0) {
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+ if (verb) {
+ printf("Red = XYZ %6.2f %6.2f %6.2f\n",ccols[0].XYZ[0],
+ ccols[0].XYZ[1], ccols[0].XYZ[2]);
+ printf("Green = XYZ %6.2f %6.2f %6.2f\n",ccols[1].XYZ[0],
+ ccols[1].XYZ[1], ccols[1].XYZ[2]);
+ printf("Blue = XYZ %6.2f %6.2f %6.2f\n",ccols[2].XYZ[0],
+ ccols[2].XYZ[1], ccols[2].XYZ[2]);
+ }
+ for (i = 0; i < 3; i++)
+ icmAry2Ary(rgbXYZ[i], ccols[i].XYZ);
+ rgbch = 1;
+ }
+ /* Do an initial set of readings to set 1% output mark */
+ if ((rv = dr->read(dr, tcols, 3, 0, 0, 1, 0, instClamp)) != 0) {
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+ if (verb) {
+ printf("Black = XYZ %6.2f %6.2f %6.2f\n",tcols[0].XYZ[0],
+ tcols[0].XYZ[1], tcols[0].XYZ[2]);
+ printf("Grey = XYZ %6.2f %6.2f %6.2f\n",tcols[1].XYZ[0],
+ tcols[1].XYZ[1], tcols[1].XYZ[2]);
+ printf("White = XYZ %6.2f %6.2f %6.2f\n",tcols[2].XYZ[0],
+ tcols[2].XYZ[1], tcols[2].XYZ[2]);
+ }
+
+ /* Advertised Gamma - Gross curve shape */
+ mgamma = pop_gamma(tcols[0].XYZ[1], tcols[1].XYZ[1], tcols[2].XYZ[1]);
+
+ dev1 = pow(0.01, 1.0/mgamma);
+ tcols[1].r = tcols[1].g = tcols[1].b = dev1;
+ tar1 = 0.01 * tcols[2].XYZ[1];
+
+ /* Figure out the target white chromaticity */
+ if (wpx > 0.0 || wpy > 0.0) { /* xy coordinates */
+ tYxy[0] = 1.0;
+ tYxy[1] = wpx;
+ tYxy[2] = wpy;
+
+ } else if (temp > 0.0) { /* Daylight color temperature */
+ double XYZ[3];
+ if (planckian)
+ rv = icx_ill_sp2XYZ(XYZ, icxOT_default, NULL, icxIT_Ptemp, temp, NULL);
+ else
+ rv = icx_ill_sp2XYZ(XYZ, icxOT_default, NULL, icxIT_Dtemp, temp, NULL);
+ if (rv != 0)
+ error("Failed to compute XYZ of target color temperature %f\n",temp);
+ icmXYZ2Yxy(tYxy, XYZ);
+ } else { /* Native white */
+ icmXYZ2Yxy(tYxy, tcols[2].XYZ);
+ }
+
+ printf("\nAdjust R,G & B offsets to get target x,y. Press space when done.\n");
+ printf(" Target Br %.2f, x %.4f , y %.4f \n", tar1, tYxy[1],tYxy[2]);
+ for (ff = 0;; ff ^= 1) {
+ double dir; /* Direction to adjust brightness */
+ double sv[3], val1; /* Scaled values */
+ double Yxy[3]; /* Yxy of current reading */
+ double rgbdir[3]; /* Direction to adjust RGB */
+ double rgbxdir[3]; /* Biggest to move */
+ double bdir, terr;
+ int bx = 0;
+
+ if ((rv = dr->read(dr, tcols+1, 1, 0, 0, 1, ' ', instClamp)) != 0) {
+ if (rv == 4)
+ break; /* User is done with this adjustment */
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+
+ /* Scale 1% values by ratio of Y to white XYZ */
+ sv[0] = tcols[1].XYZ[0] * tcols[2].XYZ[1]/tcols[2].XYZ[0];
+ sv[1] = tcols[1].XYZ[1];
+ sv[2] = tcols[1].XYZ[2] * tcols[2].XYZ[1]/tcols[2].XYZ[2];
+ val1 = sv[1];
+ if (sv[0] > val1)
+ val1 = sv[0];
+ if (sv[2] > val1)
+ val1 = sv[2];
+
+ /* Compute 1% direction */
+ dir = tar1 - val1;
+ if (fabs(dir) < 0.01)
+ dir = 0.0;
+
+ /* Compute numbers for black point error and direction */
+ icmYxy2XYZ(tLab, tYxy);
+ tLab[0] /= tLab[1];
+ tLab[2] /= tLab[1];
+ tLab[1] /= tLab[1];
+ icmAry2XYZ(tXYZ, tLab); /* Lab white reference */
+ icmXYZ2Lab(&tXYZ, tLab, tLab);
+
+ icmXYZ2Yxy(Yxy, tcols[1].XYZ);
+ icmAry2Ary(Lab, tcols[1].XYZ);
+ Lab[0] /= Lab[1];
+ Lab[2] /= Lab[1];
+ Lab[1] /= Lab[1];
+ icmXYZ2Lab(&tXYZ, Lab, Lab);
+
+ /* Compute dot products */
+ bdir = 0.0;
+ for (i = 0; i < 3; i++) {
+ double rgbLab[3];
+
+ icmXYZ2Lab(&tXYZ, rgbLab, rgbXYZ[i]);
+ rgbdir[i] = (tLab[1] - Lab[1]) * (rgbLab[1] - Lab[1])
+ + (tLab[2] - Lab[2]) * (rgbLab[2] - Lab[2]);
+ rgbxdir[i] = 0.0;
+ if (fabs(rgbdir[i]) > fabs(bdir)) {
+ bdir = rgbdir[i];
+ bx = i;
+ }
+ }
+
+ /* See how close to the target we are */
+ terr = sqrt((tLab[1] - Lab[1]) * (tLab[1] - Lab[1])
+ + (tLab[2] - Lab[2]) * (tLab[2] - Lab[2]));
+ if (terr < 0.1)
+ rgbdir[0] = rgbdir[1] = rgbdir[2] = 0.0;
+ rgbxdir[bx] = rgbdir[bx];
+
+ printf("%c%c Current Br %.2f, x %.4f%c, y %.4f%c DE %4.1f R%c%c G%c%c B%c%c ",
+ cr_char,
+ ff == 0 ? '/' : '\\',
+ val1,
+ Yxy[1],
+ Yxy[1] > tYxy[1] ? '-': Yxy[1] < tYxy[1] ? '+' : '=',
+ Yxy[2],
+ Yxy[2] > tYxy[2] ? '-': Yxy[2] < tYxy[2] ? '+' : '=',
+ icmCIE2K(tLab, Lab),
+ rgbdir[0] < 0.0 ? '-' : rgbdir[0] > 0.0 ? '+' : '=',
+ rgbxdir[0] < 0.0 ? '-' : rgbxdir[0] > 0.0 ? '+' : ' ',
+ rgbdir[1] < 0.0 ? '-' : rgbdir[1] > 0.0 ? '+' : '=',
+ rgbxdir[1] < 0.0 ? '-' : rgbxdir[1] > 0.0 ? '+' : ' ',
+ rgbdir[2] < 0.0 ? '-' : rgbdir[2] > 0.0 ? '+' : '=',
+ rgbxdir[2] < 0.0 ? '-' : rgbxdir[2] > 0.0 ? '+' : ' ');
+ fflush(stdout);
+ }
+ printf("\n");
+
+ /* Report on how well we current meet the targets */
+ } else if (c == '5') {
+ int nat = 0; /* NZ if using native white as target */
+ col tcols[4] = { /* Set of test colors */
+ { 0.0, 0.0, 0.0 },
+ { 0.5, 0.5, 0.5 },
+ { 1.0, 1.0, 1.0 },
+ { 0.0, 0.0, 0.0 } /* 1% test value */
+ };
+ double tYxy[3]; /* Target white chromaticities */
+ double sv[3], val1; /* Scaled values */
+ double mgamma, tarw, tar1, dev1, tarh;
+ double gooff; /* Aproximate output offset needed */
+ icmXYZNumber tXYZ;
+ double tLab[3], wYxy[3], wLab[3], bYxy[3], bLab[3];
+ double ct, ct_de; /* Color temperature & delta E to white locus */
+
+ printf("Doing check measurements\n");
+
+ /* Do an initial set of readings to set 1% output mark */
+ if ((rv = dr->read(dr, tcols, 3, 0, 0, 1, 0, instClamp)) != 0) {
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+ if (verb) {
+ printf("Black = XYZ %6.2f %6.2f %6.2f\n",tcols[0].XYZ[0],
+ tcols[0].XYZ[1], tcols[0].XYZ[2]);
+ printf("Grey = XYZ %6.2f %6.2f %6.2f\n",tcols[1].XYZ[0],
+ tcols[1].XYZ[1], tcols[1].XYZ[2]);
+ printf("White = XYZ %6.2f %6.2f %6.2f\n",tcols[2].XYZ[0],
+ tcols[2].XYZ[1], tcols[2].XYZ[2]);
+ }
+
+ /* Approximate Gamma - use the gross curve shape for robustness */
+ mgamma = pop_gamma(tcols[0].XYZ[1], tcols[1].XYZ[1], tcols[2].XYZ[1]);
+
+ dev1 = pow(0.01, 1.0/mgamma);
+ tcols[3].r = tcols[3].g = tcols[3].b = dev1;
+ tar1 = 0.01 * tcols[2].XYZ[1];
+
+ /* Read the 1% value */
+ if ((rv = dr->read(dr, tcols+3, 1, 0, 0, 1, 0, instClamp)) != 0) {
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+ if (verb) {
+ printf("1%% = XYZ %6.2f %6.2f %6.2f\n",tcols[3].XYZ[0],
+ tcols[3].XYZ[1], tcols[3].XYZ[2]);
+ }
+
+ /* Scale 1% values by ratio of Y to white XYZ */
+ /* (Note we're assuming -k1 here, which may not be true...) */
+ sv[0] = tcols[3].XYZ[0] * tcols[2].XYZ[1]/tcols[2].XYZ[0];
+ sv[1] = tcols[3].XYZ[1];
+ sv[2] = tcols[3].XYZ[2] * tcols[2].XYZ[1]/tcols[2].XYZ[2];
+ val1 = sv[1];
+ if (sv[0] > val1)
+ val1 = sv[0];
+ if (sv[2] > val1)
+ val1 = sv[2];
+
+ /* Figure out the target white brightness */
+ /* Note we're not taking the device gamut into account here */
+ if (tbright > 0.0) /* Given brightness */
+ tarw = tbright;
+ else /* Native/maximum brightness */
+ tarw = tcols[2].XYZ[1];
+
+ /* Figure out the target white chromaticity */
+ if (wpx > 0.0 || wpy > 0.0) { /* xy coordinates */
+ tYxy[0] = 1.0;
+ tYxy[1] = wpx;
+ tYxy[2] = wpy;
+
+ } else if (temp > 0.0) { /* Daylight color temperature */
+ double XYZ[3];
+ if (planckian)
+ rv = icx_ill_sp2XYZ(XYZ, icxOT_default, NULL, icxIT_Ptemp, temp, NULL);
+ else
+ rv = icx_ill_sp2XYZ(XYZ, icxOT_default, NULL, icxIT_Dtemp, temp, NULL);
+ if (rv != 0)
+ error("Failed to compute XYZ of target color temperature %f\n",temp);
+ icmXYZ2Yxy(tYxy, XYZ);
+ } else { /* Native white */
+ icmXYZ2Yxy(tYxy, tcols[2].XYZ);
+ nat = 1;
+ }
+
+ /* Figure out the target 50% device output value */
+ gooff = tcols[0].XYZ[1]/tcols[2].XYZ[1]; /* Aprox. normed black output offset */
+
+ /* Use tech_gamma() to do the hard work */
+ tarh = tech_gamma(&x, NULL, NULL, NULL, egamma, gamma, gooff);
+
+ /* Convert from Y fraction to absolute Y */
+ tarh = tarh * tcols[2].XYZ[1];
+
+ /* Compute various white point values */
+ icmYxy2XYZ(tLab, tYxy);
+ tLab[0] /= tLab[1];
+ tLab[2] /= tLab[1];
+ tLab[1] /= tLab[1];
+ icmAry2XYZ(tXYZ, tLab);
+ icmXYZ2Lab(&tXYZ, tLab, tLab);
+
+ icmXYZ2Yxy(wYxy, tcols[2].XYZ);
+ icmAry2Ary(wLab, tcols[2].XYZ);
+ wLab[0] /= wLab[1];
+ wLab[2] /= wLab[1];
+ wLab[1] /= wLab[1];
+ icmXYZ2Lab(&tXYZ, wLab, wLab);
+
+ icmXYZ2Yxy(bYxy, tcols[3].XYZ);
+ icmAry2Ary(bLab, tcols[3].XYZ);
+ bLab[0] /= bLab[1];
+ bLab[2] /= bLab[1];
+ bLab[1] /= bLab[1];
+ icmXYZ2Lab(&tXYZ, bLab, bLab);
+
+ /* And color temperature */
+ ct = comp_ct(&ct_de, NULL, planckian, dovct, obType, tcols[2].XYZ);
+
+ printf("\n");
+
+ if (tbright > 0.0) /* Given brightness */
+ printf(" Target Brightness = %.2f, Current = %5.2f, error = % .1f%%\n",
+ tarw, tcols[2].XYZ[1],
+ 100.0 * (tcols[2].XYZ[1] - tarw)/tarw);
+ else
+ printf(" Current Brightness = %.2f\n", tcols[2].XYZ[1]);
+
+ printf(" Target 50%% Level = %.2f, Current = %5.2f, error = % .1f%%\n",
+ tarh, tcols[1].XYZ[1],
+ 100.0 * (tcols[1].XYZ[1] - tarh)/tarw);
+
+ printf(" Target Near Black = %5.2f, Current = %5.2f, error = % .1f%%\n",
+ tar1, val1,
+ 100.0 * (val1 - tar1)/tarw);
+
+ if (!nat)
+ printf(" Target white = x %.4f, y %.4f, Current = x %.4f, y %.4f, error = %5.2f DE\n",
+ tYxy[1], tYxy[2], wYxy[1], wYxy[2], icmCIE2K(tLab, wLab));
+ else
+ printf(" Current white = x %.4f, y %.4f, %c%cT %4.0fK DE 2K %4.1f\n",
+ wYxy[1], wYxy[2], dovct ? 'V' : 'C', planckian ? 'C' : 'D', ct,ct_de);
+
+ printf(" Target black = x %.4f, y %.4f, Current = x %.4f, y %.4f, error = %5.2f DE\n",
+ tYxy[1], tYxy[2], bYxy[1], bYxy[2], icmCIE2K(tLab, bLab));
+
+
+ /* Measure and set ambient for viewing condition adjustment */
+ } else if (c == '6') {
+ if ((rv = dr->ambient(dr, &ambient, 1)) != 0) {
+ if (rv == 8) {
+ printf("Instrument doesn't have an ambient reading capability\n");
+ } else {
+ dr->del(dr);
+ error("ambient measure failed with '%s'\n",disprd_err(rv));
+ }
+ } else {
+ printf("Measured ambient level = %.1f Lux\n",ambient * 3.141592654);
+ }
+
+ } else if (c == '7') {
+ if (!verb) { /* Tell user command has been accepted */
+ if (verify == 2)
+ printf("Commencing device verification\n");
+ else
+ printf("Commencing device calibration\n");
+ }
+ break;
+ } else if (c == '8' || c == 0x03 || c == 0x1b) {
+ printf("Exiting\n");
+ dr->del(dr);
+ exit(0);
+ }
+ }
+
+ /* Make sure drift comp. is set to the command line options */
+ dr->change_drift_comp(dr, bdrift, wdrift);
+ }
+
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+ /* Take a small number of readings, and compute basic */
+ /* informations such as black & white, white target, */
+ /* aproximate matrix based display forward and reverse model. */
+ /* If bkcorrect is auto, determine a level. */
+
+ /* Read the base test set */
+ {
+ icmXYZNumber mrd; /* Number for matrix */
+ icmXYZNumber mgn;
+ icmXYZNumber mbl;
+ icmXYZNumber mwh;
+ ramdac *or = NULL;
+
+ col base[6] = { /* Base set of test colors */
+ { 0.0, 0.0, 0.0 }, /* 0 - Black */
+ { 1.0, 0.0, 0.0 }, /* 1 - Red */
+ { 0.0, 1.0, 0.0 }, /* 2 - Green */
+ { 0.0, 0.0, 1.0 }, /* 3 - Blue */
+ { 1.0, 1.0, 1.0 }, /* 4 - White */
+ { 0.0, 0.0, 0.0 } /* 5 - Black */
+ };
+
+ if (verb) {
+ if (verify == 2)
+ printf("Commencing device verification\n");
+ else
+ printf("Commencing device calibration\n");
+ }
+
+ /* Switch to native for this, so the black calc is realistic. */
+ /* (Should we really get black aim from previous .cal though ???) */
+ if (verify == 2) {
+ if ((or = dr->dw->get_ramdac(dr->dw)) != NULL) {
+ ramdac *r;
+ if (verb) printf("Switching to native response for base measurements\n");
+ r = or->clone(or);
+ r->setlin(r);
+ dr->dw->set_ramdac(dr->dw, r, 0);
+ r->del(r);
+ }
+ }
+
+ /* Read the patches without clamping */
+ if ((rv = dr->read(dr, base, 6, 1, 6, 1, 0, instNoClamp)) != 0) {
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+
+ /* Restore the cal we're verifying */
+ if (verify == 2 && or != NULL) {
+ if (verb) printf("Switching back to calibration being verified\n");
+ dr->dw->set_ramdac(dr->dw, or, 0);
+ or->del(or);
+ }
+
+ if (base[0].XYZ_v == 0) {
+ dr->del(dr);
+ error("Failed to get an XYZ value from the instrument!\n");
+ }
+
+ /* Average black relative from 2 readings */
+ x.bk[0] = 0.5 * (base[0].XYZ[0] + base[5].XYZ[0]);
+ x.bk[1] = 0.5 * (base[0].XYZ[1] + base[5].XYZ[1]);
+ x.bk[2] = 0.5 * (base[0].XYZ[2] + base[5].XYZ[2]);
+ icmClamp3(x.bk, x.bk); /* And clamp them */
+ for (i = 1; i < 5; i++)
+ icmClamp3(base[i].XYZ, base[i].XYZ);
+
+ /* Copy other readings into place */
+ dispLum = base[4].XYZ[1]; /* White Y */
+ icmAry2Ary(x.wh, base[4].XYZ);
+ icmAry2XYZ(x.twN, x.wh); /* Use this as Lab reference white until we establish target */
+
+ icmAry2XYZ(mrd, base[1].XYZ);
+ icmAry2XYZ(mgn, base[2].XYZ);
+ icmAry2XYZ(mbl, base[3].XYZ);
+ icmAry2XYZ(mwh, base[4].XYZ);
+
+ if (verb) {
+ printf("Black = XYZ %6.2f %6.2f %6.2f\n",x.bk[0],x.bk[1],x.bk[2]);
+ printf("Red = XYZ %6.2f %6.2f %6.2f\n",base[1].XYZ[0], base[1].XYZ[1], base[1].XYZ[2]);
+ printf("Green = XYZ %6.2f %6.2f %6.2f\n",base[2].XYZ[0], base[2].XYZ[1], base[2].XYZ[2]);
+ printf("Blue = XYZ %6.2f %6.2f %6.2f\n",base[3].XYZ[0], base[3].XYZ[1], base[3].XYZ[2]);
+ printf("White = XYZ %6.2f %6.2f %6.2f\n",base[4].XYZ[0], base[4].XYZ[1], base[4].XYZ[2]);
+ }
+
+ /* Setup forward matrix */
+ if (icmRGBprim2matrix(mwh, mrd, mgn, mbl, x.fm)) {
+ dr->del(dr);
+ error("Aprox. fwd matrix unexpectedly singular\n");
+ }
+
+#ifdef DEBUG
+ if (verb) {
+ printf("Forward matrix is:\n");
+ printf("%f %f %f\n", x.fm[0][0], x.fm[0][1], x.fm[0][2]);
+ printf("%f %f %f\n", x.fm[1][0], x.fm[1][1], x.fm[1][2]);
+ printf("%f %f %f\n", x.fm[2][0], x.fm[2][1], x.fm[2][2]);
+ }
+#endif
+
+ /* Compute bwd matrix */
+ if (icmInverse3x3(x.bm, x.fm)) {
+ dr->del(dr);
+ error("Inverting aprox. fwd matrix failed");
+ }
+
+ /* Decide on the level of black correction. */
+ if (bkcorrect < 0.0) {
+ double rat;
+
+ /* rat is 0 for displays with a good black, */
+ /* and 1 for displays with a bad black level. */
+ /* (Not sure if this should be scaled by the white, */
+ /* making it contrast ratio sensitive?) */
+ rat = (x.bk[1] - 0.02)/(0.3 - 0.02);
+ if (rat < 0.0)
+ rat = 0.0;
+ else if (rat > 1.0)
+ rat = 1.0;
+ /* Make transition more perceptual */
+ rat = sqrt(rat);
+ bkcorrect = 1.0 - rat;
+ if (verb)
+ printf("Automatic black point hue correction level = %1.2f\n", bkcorrect);
+ }
+ }
+
+ /* Now do some more readings, to compute the basic per channel */
+ /* transfer characteristics, and then a device model. */
+ if (verify != 2 && !doupdate) {
+ col *cols; /* Read 4 x isteps patches from display */
+ sxyz *asrgb[4]; /* samples for r, g, b & w */
+
+ if ((cols = (col *)malloc(isteps * 4 * sizeof(col))) == NULL) {
+ dr->del(dr);
+ error ("Malloc of array of readings failed");
+ }
+ for (j = 0; j < 4; j++) {
+ if ((asrgb[j] = (sxyz *)malloc(isteps * sizeof(sxyz))) == NULL) {
+ free(cols);
+ dr->del(dr);
+ error ("Malloc of array of readings failed");
+ }
+ }
+
+ /* Set the device colors to read */
+ for (i = 0; i < isteps; i++) {
+ double vv;
+
+#if defined(__APPLE__) && defined(__POWERPC__)
+ gcc_bug_fix(i);
+#endif
+ vv = i/(isteps - 1.0);
+ vv = pow(vv, MOD_DIST_POW);
+ for (j = 0; j < 4; j++) {
+ cols[i * 4 + j].r = cols[i * 4 + j].g = cols[i * 4 + j].b = 0.0;
+ if (j == 0)
+ cols[i * 4 + j].r = vv;
+ else if (j == 1)
+ cols[i * 4 + j].g = vv;
+ else if (j == 2)
+ cols[i * 4 + j].b = vv;
+ else
+ cols[i * 4 + j].r = cols[i * 4 + j].g = cols[i * 4 + j].b = vv;
+ }
+ }
+
+ /* Read the patches */
+ if ((rv = dr->read(dr, cols, isteps * 4, 1, isteps * 4, 1, 0, instClamp)) != 0) {
+ free(cols); free(asrgb[0]); free(asrgb[1]); free(asrgb[2]); free(asrgb[3]);
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+
+ /* Transfer readings to asrgb[] */
+ for (i = 0; i < isteps; i++) {
+ double vv = cols[i * 4 + 0].r;
+ for (j = 0; j < 4; j++) {
+//printf("~1 R = %f, G = %f, B = %f, XYZ = %f %f %f\n",
+//cols[i * 4 + j].r, cols[i * 4 + j].g, cols[i * 4 + j].b, cols[i * 4 + j].XYZ[0], cols[i * 4 + j].XYZ[1], cols[i * 4 + j].XYZ[2]);
+ asrgb[j][i].v = vv;
+ asrgb[j][i].xyz[0] = cols[i * 4 + j].XYZ[0];
+ asrgb[j][i].xyz[1] = cols[i * 4 + j].XYZ[1];
+ asrgb[j][i].xyz[2] = cols[i * 4 + j].XYZ[2];
+ }
+ }
+
+ /* Convert RGB channel samples to curves */
+ {
+ mcvco *sdv; /* Points used to create cvs[], RGB */
+ double blrgb[3];
+ double *op; /* Parameters to optimise */
+ double *sa; /* Search area */
+ double re; /* Residual error */
+
+ /* Transform measured black back to linearised RGB values */
+ icmMulBy3x3(blrgb, x.bm, x.bk);
+//printf("~1 model black should be %f %f %f\n", x.bk[0], x.bk[1], x.bk[2]);
+//printf("~1 linearised RGB should be %f %f %f\n", blrgb[0], blrgb[1], blrgb[2]);
+
+ if ((sdv = malloc(sizeof(mcvco) * isteps)) == NULL) {
+ free(cols); free(asrgb[0]); free(asrgb[1]); free(asrgb[2]); free(asrgb[3]);
+ dr->del(dr);
+ error ("Malloc of scattered data points failed");
+ }
+ for (k = 0; k < 3; k++) { /* Create the model curves */
+ for (i = 0; i < isteps; i++) {
+ sdv[i].p = asrgb[k][i].v;
+ sdv[i].v = ICMNORM3(asrgb[k][i].xyz);
+ sdv[i].w = 1.0;
+//printf("~1 chan %d, entry %d, p = %f, v = %f from XYZ %f %f %f\n",
+//k,i,x.sdv[k][i].p,x.sdv[k][i].v, asrgb[k][i].xyz[0], asrgb[k][i].xyz[1], asrgb[k][i].xyz[2]);
+ }
+ if ((x.dcvs[k] = new_mcv()) == NULL) {
+ free(cols); free(asrgb[0]); free(asrgb[1]); free(asrgb[2]); free(asrgb[3]);
+ dr->del(dr);
+ error("new_mcv x.dcvs[%d] failed",k);
+ }
+ x.dcvs[k]->fit(x.dcvs[k], 0, fitord, sdv, isteps, 5.0);
+
+ /* Scale the whole curve so the output is scaled to 1.0 */
+ x.dcvs[k]->force_scale(x.dcvs[k], 1.0);
+
+ /* Force curves to produce this lrgb for 0.0 */
+ x.dcvs[k]->force_0(x.dcvs[k], blrgb[k]);
+ }
+ free(sdv);
+
+#ifdef OPTIMIZE_MODEL
+ /* Setup list of reference points ready for optimisation */
+ x.nrp = 4 * isteps;
+ if ((x.rp = (optref *)malloc(sizeof(optref) * x.nrp)) == NULL) {
+ free(cols); free(asrgb[0]); free(asrgb[1]); free(asrgb[2]); free(asrgb[3]);
+ dr->del(dr);
+ error ("Malloc of measurement reference points failed");
+ }
+ for (k = 0; k < 4; k++) {
+ for (i = 0; i < isteps; i++) {
+ int ii = k * isteps + i;
+ double v[3];
+
+ v[0] = v[1] = v[2] = 0.0;
+ if (k == 0)
+ v[k] = asrgb[k][i].v;
+ else if (k == 1)
+ v[k] = asrgb[k][i].v;
+ else if (k == 2)
+ v[k] = asrgb[k][i].v;
+ else
+ v[0] = v[1] = v[2] = asrgb[k][i].v;
+ icmAry2Ary(x.rp[ii].dev, v);
+ icmXYZ2Lab(&x.twN, x.rp[ii].lab, asrgb[k][i].xyz);
+ if (k == 3) /* White */
+ x.rp[ii].w = 0.5;
+ else
+ x.rp[ii].w = 0.16667;
+ }
+ }
+
+ /* Get parameters and setup for optimisation */
+ op = dev_get_params(&x);
+ if ((sa = malloc(x.np * sizeof(double))) == NULL) {
+ free(cols); free(asrgb[0]); free(asrgb[1]); free(asrgb[2]); free(asrgb[3]);
+ dr->del(dr);
+ error ("Malloc of scattered data points failed");
+ }
+
+ for (i = 0; i < x.np; i++)
+ sa[i] = 0.1;
+
+ /* Do optimisation */
+#ifdef NEVER
+ if (powell(&re, x.np, op, sa, 1e-5, 3000, dev_opt_func, (void *)&x, NULL, NULL) != 0) {
+ free(cols); free(asrgb[0]); free(asrgb[1]); free(asrgb[2]); free(asrgb[3]);
+ dr->del(dr);
+ error ("Model powell failed, re = %f",re);
+ }
+#else
+ if (conjgrad(&re, x.np, op, sa, 1e-5, 3000,
+ dev_opt_func, dev_dopt_func, (void *)&x, NULL, NULL) != 0) {
+ if (re > 1e-2) {
+ free(cols); free(asrgb[0]); free(asrgb[1]); free(asrgb[2]); free(asrgb[3]);
+ dr->del(dr);
+ error("Model conjgrad failed, residual error = %f",re);
+ } else
+ warning("Model conjgrad failed, residual error = %f",re);
+ }
+#endif
+
+ /* Put optimised parameters in place */
+ dev_put_params(&x, op);
+
+ free(x.rp);
+ x.rp = NULL;
+ x.nrp = 0;
+ free(x.dtin_iv); /* Free temporary arrays */
+ x.dtin_iv = NULL;
+ free(sa);
+ free(op);
+#endif /* OPTIMIZE_MODEL */
+ }
+
+#ifdef DEBUG_PLOT
+ /* Plot the current calc curves */
+ {
+ #define XRES 256
+ double xx[XRES];
+ double yy[3][XRES];
+ double xyz[3];
+ for (i = 0; i < XRES; i++) {
+ xx[i] = i/(XRES-1.0);
+ for (j = 0; j < 3; j++)
+ yy[j][i] = x.dcvs[j]->interp(x.dcvs[j], xx[i]);
+ }
+ printf("Channel curves\n");
+ do_plot(xx,yy[0],yy[1],yy[2],XRES);
+ #undef XRES
+ }
+#endif
+
+ /* We're done with cols[] and asrgb[] */
+ free(cols); free(asrgb[0]); free(asrgb[1]); free(asrgb[2]); free(asrgb[3]);
+ }
+
+#ifdef CHECK_MODEL
+ /* Check how well our fwd model agrees with the device */
+ if (verify != 2) {
+ col set[3]; /* Variable to read up to 3 values from the display */
+ int nn = 27;
+ double alab[3], mxyz[3], mlab[3]; /* Actual and model Lab */
+ double mnerr; /* Maximum neutral error */
+ double mnv; /* Value where maximum error is */
+ double anerr; /* Average neutral error */
+
+ mnerr = anerr = 0.0;
+ /* !!! Should change this to single batch to work better with drift comp. !!! */
+ for (i = 0; i < (nn + 3); i++) {
+ double vv, v[3];
+ double de;
+
+ if (i < nn) {
+ vv = i/(nn - 1.0);
+ vv = pow(vv, CHECK_DIST_POW);
+ v[0] = v[1] = v[2] = vv;
+ set[0].r = set[0].g = set[0].b = vv;
+
+ } else { /* Do R, G, B */
+ v[0] = v[1] = v[2] = 0.0;
+ v[i - nn] = 1.0;
+ set[0].r = v[0];
+ set[0].g = v[1];
+ set[0].b = v[2];
+ }
+
+ if ((rv = dr->read(dr, set, 1, i+1, nn+3, 1, 0, instClamp)) != 0) {
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+ icmXYZ2Lab(&x.twN, alab, set[0].XYZ);
+
+ fwddev(&x, mxyz, v);
+ icmXYZ2Lab(&x.twN, mlab, mxyz);
+
+ de = icmCIE2K(mlab, alab);
+ if (de > mnerr) {
+ mnerr = de;
+ mnv = vv;
+ }
+ anerr += de;
+
+ printf("RGB %.3f %.3f %.3f -> XYZ %.2f %.2f %.2f, model %.2f %.2f %.2f\n",
+ set[0].r, set[0].g, set[0].b,
+ set[0].XYZ[0], set[0].XYZ[1],
+ set[0].XYZ[2], mxyz[0], mxyz[1], mxyz[2]);
+
+ printf("RGB %.3f %.3f %.3f -> Lab %.2f %.2f %.2f, model %.2f %.2f %.2f, DE %f\n",
+ set[0].r, set[0].g, set[0].b, alab[0], alab[1], alab[2], mlab[0], mlab[1], mlab[2],de);
+ }
+ anerr /= (double)(nn+3);
+ printf("Model maximum error (@ %f) = %f deltaE\n",mnv, mnerr);
+ printf("Model average error = %f deltaE\n",anerr);
+ }
+#endif /* CHECK_MODEL */
+
+ /* Figure out our calibration curve parameter targets */
+ if (!doupdate) {
+
+ /* Figure out the target white point */
+ if (wpx > 0.0 || wpy > 0.0) { /* xy coordinates */
+ double Yxy[3];
+ Yxy[0] = 1.0;
+ Yxy[1] = wpx;
+ Yxy[2] = wpy;
+ icmYxy2XYZ(x.twh, Yxy);
+
+ } else if (temp > 0.0) { /* Daylight color temperature */
+ if (planckian)
+ rv = icx_ill_sp2XYZ(x.twh, icxOT_default, NULL, icxIT_Ptemp, temp, NULL);
+ else
+ rv = icx_ill_sp2XYZ(x.twh, icxOT_default, NULL, icxIT_Dtemp, temp, NULL);
+ if (rv != 0)
+ error("Failed to compute XYZ of target color temperature %f\n",temp);
+//printf("~1 Raw target from temp %f XYZ = %f %f %f\n",temp,x.twh[0],x.twh[1],x.twh[2]);
+ } else { /* Native white */
+ x.twh[0] = x.wh[0]/x.wh[1];
+ x.twh[1] = x.wh[1]/x.wh[1];
+ x.twh[2] = x.wh[2]/x.wh[1];
+ }
+ x.nwh[0] = x.twh[0];
+ x.nwh[1] = x.twh[1];
+ x.nwh[2] = x.twh[2];
+
+ /* Convert it to absolute white target */
+ if (tbright > 0.0) { /* Given brightness */
+ x.twh[0] *= tbright;
+ x.twh[1] *= tbright;
+ x.twh[2] *= tbright;
+ } else { /* Native/maximum brightness */
+ x.twh[0] *= x.wh[1];
+ x.twh[1] *= x.wh[1];
+ x.twh[2] *= x.wh[1];
+ if (verb)
+ printf("Initial native brightness target = %f cd/m^2\n", x.twh[1]);
+ }
+
+ /* Now make sure the target white will fit in gamut. */
+ if (verify != 2 &&
+ ((tbright > 0.0 && invlindev(&x, NULL, x.twh) > 0.0) /* Defined brightness and clips */
+ || (tbright <= 0.0 && x.nat == 0))) { /* Max non-native white */
+ double rgb[3];
+ double scale = 0.5;
+ double sa = 0.1;
+
+ if (powell(NULL, 1, &scale, &sa, 1e-7, 500, wp_opt_func, (void *)&x, NULL, NULL) != 0)
+ error ("WP scale powell failed");
+
+ x.twh[0] *= scale;
+ x.twh[1] *= scale;
+ x.twh[2] *= scale;
+ invdev(&x, rgb, x.twh);
+ if (verb) {
+ printf("Had to scale brightness from %f to %f to fit within gamut,\n",x.twh[1]/scale, x.twh[1]);
+ printf("corresponding to aprox. RGB %f %f %f\n",rgb[0],rgb[1],rgb[2]);
+ }
+ }
+
+ if (verb)
+ printf("Target white value is XYZ %f %f %f\n",x.twh[0],x.twh[1],x.twh[2]);
+ }
+
+ /* Need this for Lab conversions */
+ icmAry2XYZ(x.twN, x.twh);
+
+ /* Figure out the black point target */
+ {
+ double tbL[3];
+ double tbkLab[3];
+
+ icmXYZ2Lab(&x.twN, tbkLab, x.bk); /* Convert measured black to Lab */
+
+//printf("~1 black point Lab = %f %f %f\n", tbkLab[0], tbkLab[1], tbkLab[2]);
+
+ /* Now blend the a* b* with that of the target white point */
+ tbL[0] = tbkLab[0];
+ tbL[1] = bkcorrect * 0.0 + (1.0 - bkcorrect) * tbkLab[1];
+ tbL[2] = bkcorrect * 0.0 + (1.0 - bkcorrect) * tbkLab[2];
+
+//printf("~1 blended black Lab = %f %f %f\n", tbL[0], tbL[1], tbL[2]);
+
+ if (bkbright > 0.0 && (bkbright <= x.bk[1] || (2.0 * bkbright) >= x.twh[1]))
+ warning("Black brigtness %f ignored because it is out of range",bkbright);
+ else if (bkbright > 0.0) {
+ double bkbxyz[3], bkbLab[3], vv, bl;
+ /* Figure out the L value of the target brightness */
+ bkbxyz[0] = 0.0;
+ bkbxyz[1] = bkbright;
+ bkbxyz[2] = 0.0;
+ icmXYZ2Lab(&x.twN, bkbLab, bkbxyz);
+
+ /* Do crossover to white neutral */
+ vv = bkbLab[0] / (100.0 - tbL[0]);
+ bl = pow((1.0 - vv), x.nbrate); /* Crossover near the black */
+ tbL[0] = bkbLab[0];
+ tbL[1] = (1.0 - bl) * 0.0 + bl * tbL[1];
+ tbL[2] = (1.0 - bl) * 0.0 + bl * tbL[2];
+//printf("~1 brighted black Lab = %f %f %f\n", tbL[0], tbL[1], tbL[2]);
+ }
+
+ /* And make this the black hue to aim for */
+ icmLab2XYZ(&x.twN, x.tbk, tbL);
+ icmAry2XYZ(x.tbN, x.tbk);
+ if (verb)
+ printf("Adjusted target black XYZ %.2f %.2f %.2f, Lab %.2f %.2f %.2f\n",
+ x.tbk[0], x.tbk[1], x.tbk[2], tbL[0], tbL[1], tbL[2]);
+ }
+
+ /* Figure out the gamma curve black offset value */
+ /* that will give us the black level we actually have. */
+ {
+ double yy, tby; /* Target black y */
+
+ /* Make target black Y as high as necessary */
+ /* to get the black point hue */
+ /* ????? should do this by increasing L* until XYZ > x.bk ????? */
+ tby = x.bk[1];
+//printf("Target Y from Y = %f\n",tby);
+ yy = x.bk[0] * x.tbk[1]/x.tbk[0];
+//printf("Target Y from X = %f\n",yy);
+ if (yy > tby)
+ tby = yy;
+ yy = x.bk[2] * x.tbk[1]/x.tbk[2];
+//printf("Target Y from Z = %f\n",yy);
+ if (yy > tby)
+ tby = yy;
+
+ if (x.tbk[1] > tby) /* If target is already high enough */
+ tby = x.tbk[1];
+
+ if (verb) {
+ double tbp[3], tbplab[3];
+ tbp[0] = x.tbk[0] * tby/x.tbk[1];
+ tbp[1] = tby;
+ tbp[2] = x.tbk[2] * tby/x.tbk[1];
+ icmXYZ2Lab(&x.twN, tbplab, tbp);
+ printf("Target black after min adjust: XYZ %.3f %.3f %.3f, Lab %.3f %.3f %.3f\n",
+ tbp[0], tbp[1], tbp[2], tbplab[0], tbplab[1], tbplab[2]);
+ }
+
+ /* Figure out the x.gioff and egamma needed to get this x.gooff and gamma */
+ x.gooff = tby / x.twh[1]; /* Convert to relative */
+
+ /* tech_gamma() does the hard work */
+ tech_gamma(&x, &x.egamma, &x.gooff, &x.gioff, egamma, gamma, x.gooff);
+ }
+
+ if (verb)
+ printf("Gamma curve input offset = %f, output offset = %f, power = %f\n",x.gioff,x.gooff,x.egamma);
+
+ /* For ambient light compensation, we make use of CIECAM02 */
+ if (ambient > 0.0) {
+ double xyz[3], Jab[3];
+ double t1, t0, a1, a0;
+
+ /* Setup default source viewing conditions */
+ if ((x.svc = new_icxcam(cam_default)) == NULL
+ || (x.dvc = new_icxcam(cam_default)) == NULL) {
+ error("Failed to create source and destination CAMs");
+ }
+
+ switch(x.gammat) {
+ case gt_power: /* There's nothing obvious for these cases, */
+ case gt_Lab: /* So default to a computerish source viewing condition */
+
+ case gt_sRGB: /* sRGB standard viewing conditions */
+ x.svc->set_view(x.svc, vc_none,
+ x.nwh, /* Display normalised white point */
+ 0.2 * 80.0, /* Adapting luminence, 20% of display 80 cd/m^2 */
+ 0.2, /* Background relative to reference white */
+ 80.0, /* Display is 80 cd/m^2 */
+ 0.01, x.nwh, /* 1% flare same white point */
+ 0);
+ break;
+
+ case gt_Rec709:
+ case gt_SMPTE240M: /* Television studio conditions */
+ x.svc->set_view(x.svc, vc_none,
+ x.nwh, /* Display normalised white point */
+ 0.2 * 1000.0/3.1415, /* Adapting luminence, 20% of 1000 lux in cd/m^2 */
+ 0.2, /* Background relative to reference white */
+ 1000.0/3.1415, /* Luminance of white in the Image field (cd/m^2) */
+ 0.01, x.nwh, /* 1% flare same white point */
+ 0);
+ break;
+
+ default:
+ error("Unknown gamma type");
+ }
+ /* The display we're calibratings situation */
+ x.dvc->set_view(x.dvc, vc_none,
+ x.nwh, /* Display normalised white point */
+ 0.2 * ambient, /* Adapting luminence, 20% of ambient in cd/m^2 */
+ 0.2, /* Background relative to reference white */
+ x.twh[1], /* Target white level (cd/m^2) */
+ 0.01, x.nwh, /* 1% flare same white point */
+ 0);
+
+ /* Compute the normalisation values */
+ x.svc->XYZ_to_cam(x.svc, Jab, x.nwh); /* Relative white point */
+ x.dvc->cam_to_XYZ(x.dvc, xyz, Jab);
+ t1 = x.nwh[1];
+ a1 = xyz[1];
+
+ xyz[0] = x.tbk[1]/x.twh[1] * x.nwh[0];
+ xyz[1] = x.tbk[1]/x.twh[1] * x.nwh[1];
+ xyz[2] = x.tbk[1]/x.twh[1] * x.nwh[2];
+ t0 = xyz[1];
+ x.svc->XYZ_to_cam(x.svc, Jab, xyz); /* Relative black Y */
+ x.dvc->cam_to_XYZ(x.dvc, xyz, Jab);
+ a0 = xyz[1];
+
+//printf("~1 t1 = %f, t0 = %f\n",t1,t0);
+//printf("~1 a1 = %f, a0 = %f\n",a1,a0);
+ x.vn1 = (t1 - t0)/(a1 - a0); /* Scale factor */
+ x.vn0 = t0 - (a0 * x.vn1); /* Then offset */
+//printf("~1 vn1 = %f, vn0 = %f\n",x.vn1, x.vn0);
+//printf("~1 fix a1 = %f, should be = %f\n",a1 * x.vn1 + x.vn0, t1);
+//printf("~1 fix a0 = %f, should be = %f\n",a0 * x.vn1 + x.vn0, t0);
+
+ x.vc = 1;
+
+ /* Compute aproximate power of viewing transform */
+ if (verb) {
+ double v;
+ v = view_xform(&x, 0.5);
+ v = log(v) / log(0.5);
+ printf("Viewing conditions adjustment aprox. power = %f\n",v);
+ }
+#ifdef NEVER
+{
+ int i;
+
+ printf("~1 viewing xtranform:\n");
+ for (i = 0; i <= 10; i++) {
+ double w, v = i/10.0;
+
+ w = view_xform(&x, v);
+ printf("~1 in %f -> %f\n",v,w);
+ }
+}
+#endif /* NEVER */
+ }
+
+ /* - - - - - - - - - - - - - - - - - - - - - */
+ /* Start with a scaled down number of test points and refine threshold, */
+ /* and double/halve these on each iteration. */
+ if (verb && verify != 2)
+ printf("Total Iteration %d, Final Samples = %d Final Repeat threshold = %f\n",
+ mxits, rsteps, errthr);
+ if (verify == 2) {
+ rsteps = VER_RES;
+ errthr = 0.0;
+ } else {
+ rsteps /= (1 << (mxits-1));
+ errthr *= pow((double)(1 << (mxits-1)), THRESH_SCALE_POW);
+ }
+
+ /* Setup the initial calibration test point values */
+ init_csamp(&asgrey, &x, doupdate, verify, verify == 2 ? 1 : 0, rsteps);
+
+ /* Calculate the initial calibration curve values */
+ if (verify != 2 && !doupdate) {
+ int nsamp = 128;
+ mcvco *sdv[3]; /* Scattered data for creating mcv */
+
+ for (j = 0; j < 3; j++) {
+ if ((x.rdac[j] = new_mcv()) == NULL) {
+ dr->del(dr);
+ error("new_mcv x.rdac[%d] failed",j);
+ }
+ }
+
+ for (j = 0; j < 3; j++) {
+ if ((sdv[j] = malloc(sizeof(mcvco) * rsteps)) == NULL) {
+ dr->del(dr);
+ error ("Malloc of scattered data points failed");
+ }
+ }
+
+ if (verb)
+ printf("Creating initial calibration curves...\n");
+
+ /* Copy the sample points */
+ for (i = 0; i < rsteps; i++) {
+ for (j = 0; j < 3; j++) {
+ sdv[j][i].p = asgrey.s[i].v;
+ sdv[j][i].v = asgrey.s[i].rgb[j];
+ sdv[j][i].w = 1.0;
+ }
+ }
+ if (x.nat) /* Make curve go thought white if possible */
+ sdv[0][rsteps-1].w = sdv[1][rsteps-1].w = sdv[2][rsteps-1].w = 50.0;
+
+ /* Create an initial set of RAMDAC curves */
+ for (j = 0; j < 3; j++)
+ x.rdac[j]->fit(x.rdac[j], 0, fitord, sdv[j], rsteps, RDAC_SMOOTH);
+
+ /* Make sure that if we are using native brightness and white point, */
+ /* that the curves go to a perfect 1.0 ... */
+ if (x.nat) {
+ for (j = 0; j < 3; j++)
+ x.rdac[j]->force_1(x.rdac[j], 1.0);
+ }
+
+ for (j = 0; j < 3; j++)
+ free (sdv[j]);
+ }
+
+#ifdef DEBUG_PLOT
+ /* Plot the initial curves */
+ if (verify != 2) {
+ #define XRES 255
+ double xx[XRES];
+ double y1[XRES];
+ double y2[XRES];
+ double y3[XRES];
+ double rgb[3];
+ for (i = 0; i < XRES; i++) {
+ double drgb[3], rgb[3];
+ xx[i] = i/(XRES-1.0);
+ rgb[0] = rgb[1] = rgb[2] = xx[i];
+ for (j = 0; j < 3; j++)
+ drgb[j] = x.rdac[j]->interp(x.rdac[j], rgb[j]);
+ y1[i] = drgb[0];
+ y2[i] = drgb[1];
+ y3[i] = drgb[2];
+ }
+ printf("Initial ramdac curves\n");
+ do_plot(xx,y1,y2,y3,XRES);
+ #undef XRES
+ }
+#endif
+
+ dr->reset_targ_w(dr); /* Reset white drift target at start of main cal. */
+
+ /* Now we go into the main verify & refine loop */
+ for (it = verify == 2 ? mxits : 0; it < mxits || verify != 0;
+ rsteps *= 2, errthr /= (it < mxits) ? pow(2.0,THRESH_SCALE_POW) : 1.0, it++) {
+ int totmeas = 0; /* Total number of measurements in this pass */
+ col set[3]; /* Variable to read one to three values from the display */
+
+ /* Verify pass */
+ if (it >= mxits)
+ rsteps = VER_RES; /* Fixed verification resolution */
+ else
+ thrfail = 0; /* Not verify pass */
+
+
+ /* re-init asgrey if the number of test points has changed */
+ reinit_csamp(&asgrey, &x, verify, (verify == 2 || it >= mxits) ? 1 : 0, rsteps);
+
+ if (verb) {
+ if (it >= mxits)
+ printf("Doing verify pass with %d sample points\n",rsteps);
+ else
+ printf("Doing iteration %d with %d sample points and repeat threshold of %f DE\n",
+ it+1,rsteps, errthr);
+ }
+ /* Read and adjust each step */
+ /* Do this white to black to track drift in native white point */
+ for (i = rsteps-1; i >= 0; i--) {
+ double rpt;
+ double peqXYZ[3]; /* Previous steps equivalent aim point */
+ double bestrgb[3]; /* In case we fail */
+ double bestxyz[3];
+ double prevde = 1e7;
+ double bestde = 1e7;
+ double bestdc = 1e7;
+ double bestpeqde = 1e7;
+ double besthde = 1e7;
+ double rgain = REFINE_GAIN; /* Scale down if lots of repeats */
+ int mjac = 0; /* We measured the Jacobian */
+
+ /* Setup a second termination threshold chriteria based on */
+ /* the delta E to the previous step point for the last pass. */
+ if (i == (rsteps-1) || it < (mxits-1)) {
+ icmAry2Ary(peqXYZ, asgrey.s[i].tXYZ); /* Its own aim point */
+ } else {
+ double Lab1[3], Lab2[3], Lab3[3];
+ icmXYZ2Lab(&x.twN, Lab1, asgrey.s[i+1].XYZ);
+ icmXYZ2Lab(&x.twN, Lab2, asgrey.s[i].tXYZ);
+ Lab3[0] = Lab2[0];
+ Lab3[1] = 0.5 * (Lab1[1] + Lab2[1]); /* Compute aim point between actual target */
+ Lab3[2] = 0.5 * (Lab1[2] + Lab2[2]); /* and previous point. */
+ icmLab2XYZ(&x.twN, asgrey.s[i].tXYZ, Lab3);
+ Lab1[0] = Lab2[0]; /* L of current target with ab of previous as 2nd threshold */
+ icmLab2XYZ(&x.twN, peqXYZ, Lab1);
+ }
+
+ /* Until we meet the necessary accuracy or give up */
+ for (rpt = 0;rpt < MAX_RPTS; rpt++) {
+ int gworse = 0; /* information flag */
+ double wde; /* informational */
+
+ set[0].r = asgrey.s[i].rgb[0];
+ set[0].g = asgrey.s[i].rgb[1];
+ set[0].b = asgrey.s[i].rgb[2];
+ set[0].id = NULL;
+
+ /* Read patches (no auto cr in case we repeat last patch) */
+ if ((rv = dr->read(dr, set, 1, rsteps-i, rsteps, 0, 0, instClamp)) != 0) {
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+ totmeas++;
+
+ icmAry2Ary(asgrey.s[i].pXYZ, asgrey.s[i].XYZ); /* Remember previous XYZ */
+ icmAry2Ary(asgrey.s[i].XYZ, set[0].XYZ); /* Transfer current reading */
+
+ /* If native white and we've just measured it, */
+ /* and we're not doing a verification, */
+ /* adjust all the other point targets txyz to track the white. */
+ if (x.nat && i == (rsteps-1) && it < mxits && asgrey.s[i].v == 1.0) {
+
+ icmAry2Ary(x.twh, asgrey.s[i].XYZ); /* Set current white */
+ icmAry2XYZ(x.twN, x.twh); /* Need this for Lab conversions */
+ init_csamp_txyz(&asgrey, &x, 1); /* Recompute txyz's */
+ icmAry2Ary(peqXYZ, asgrey.s[i].tXYZ); /* Fix peqXYZ */
+//printf("~1 Just reset target white to native white\n");
+ if (wdrift) { /* Make sure white drift is reset on next read. */
+ dr->reset_targ_w(dr); /* Reset this */
+ }
+ }
+
+ /* Compute the current change wanted to hit target */
+ icmSub3(asgrey.s[i].deXYZ, asgrey.s[i].tXYZ, asgrey.s[i].XYZ);
+ asgrey.s[i].de = icmXYZLabDE(&x.twN, asgrey.s[i].tXYZ, asgrey.s[i].XYZ);
+ asgrey.s[i].peqde = icmXYZLabDE(&x.twN, peqXYZ, asgrey.s[i].XYZ);
+ asgrey.s[i].hde = 0.8 * asgrey.s[i].de + 0.2 * asgrey.s[i].peqde;
+ /* Eudclidian difference of XYZ, because this doesn't always track Lab */
+ asgrey.s[i].dc = icmLabDE(asgrey.s[i].tXYZ, asgrey.s[i].XYZ);
+
+ /* Compute change from last XYZ */
+ icmSub3(asgrey.s[i].dXYZ, asgrey.s[i].XYZ, asgrey.s[i].pXYZ);
+
+#ifdef DEBUG
+ printf("\n\nTest point %d, v = %f\n",rsteps - i,asgrey.s[i].v);
+ printf("Current rgb %f %f %f -> XYZ %f %f %f, de %f, dc %f\n",
+ asgrey.s[i].rgb[0], asgrey.s[i].rgb[1], asgrey.s[i].rgb[2],
+ asgrey.s[i].XYZ[0], asgrey.s[i].XYZ[1], asgrey.s[i].XYZ[2],
+ asgrey.s[i].de, asgrey.s[i].dc);
+ printf("Target XYZ %f %f %f, delta needed %f %f %f\n",
+ asgrey.s[i].tXYZ[0], asgrey.s[i].tXYZ[1], asgrey.s[i].tXYZ[2],
+ asgrey.s[i].deXYZ[0], asgrey.s[i].deXYZ[1], asgrey.s[i].deXYZ[2]);
+ if (rpt > 0) {
+ printf("Intended XYZ change %f %f %f, actual change %f %f %f\n",
+ asgrey.s[i].pdXYZ[0], asgrey.s[i].pdXYZ[1], asgrey.s[i].pdXYZ[2],
+ asgrey.s[i].dXYZ[0], asgrey.s[i].dXYZ[1], asgrey.s[i].dXYZ[2]);
+ }
+#endif
+
+ if (it < mxits) { /* Not verify, apply correction */
+ int impj = 0; /* We improved the Jacobian */
+ int dclip = 0; /* We clipped the new RGB */
+#ifdef ADJ_JACOBIAN
+ int isclipped = 0;
+
+#ifndef CLIP /* Check for cliping */
+ /* Don't try and update the Jacobian if the */
+ /* device values are going out of gamut, */
+ /* and being clipped without Jac correction being aware. */
+ for (j = 0; j < 3; j++) {
+ if (asgrey.s[i].rgb[j] <= 0.0 || asgrey.s[i].rgb[j] >= 1.0) {
+ isclipped = 1;
+ break;
+ }
+ }
+#endif /* !CLIP */
+
+#ifdef REMEAS_JACOBIAN
+ /* If the de hasn't improved, try and measure the Jacobian */
+ if (it < (rsteps-1) && mjac == 0 && asgrey.s[i].de > (0.8 * prevde)) {
+ double dd;
+ if (asgrey.s[i].v < 0.5)
+ dd = 0.05;
+ else
+ dd= -0.05;
+ set[0].r = asgrey.s[i].rgb[0] + dd;
+ set[0].g = asgrey.s[i].rgb[1];
+ set[0].b = asgrey.s[i].rgb[2];
+ set[0].id = NULL;
+ set[1].r = asgrey.s[i].rgb[0];
+ set[1].g = asgrey.s[i].rgb[1] + dd;
+ set[1].b = asgrey.s[i].rgb[2];
+ set[1].id = NULL;
+ set[2].r = asgrey.s[i].rgb[0];
+ set[2].g = asgrey.s[i].rgb[1];
+ set[2].b = asgrey.s[i].rgb[2] + dd;
+ set[2].id = NULL;
+
+ if ((rv = dr->read(dr, set, 1, rsteps-i, rsteps, 0, 0, instClamp)) != 0
+ || (rv = dr->read(dr, set+1, 1, rsteps-i, rsteps, 0, 0, instClamp)) != 0
+ || (rv = dr->read(dr, set+2, 1, rsteps-i, rsteps, 0, 0, instClamp)) != 0) {
+ dr->del(dr);
+ error("display read failed with '%s'\n",disprd_err(rv));
+ }
+ totmeas += 3;
+
+ /* Matrix organization is J[XYZ][RGB] for del RGB->del XYZ*/
+ for (j = 0; j < 3; j++) {
+ asgrey.s[i].j[0][j] = (set[j].XYZ[0] - asgrey.s[i].XYZ[0]) / dd;
+ asgrey.s[i].j[1][j] = (set[j].XYZ[1] - asgrey.s[i].XYZ[1]) / dd;
+ asgrey.s[i].j[2][j] = (set[j].XYZ[2] - asgrey.s[i].XYZ[2]) / dd;
+ }
+ if (icmInverse3x3(asgrey.s[i].ij, asgrey.s[i].j)) {
+ /* Should repeat with bigger dd ? */
+//printf("~1 matrix =\n");
+//printf("~1 %f %f %f\n", asgrey.s[i].j[0][0], asgrey.s[i].j[0][1], asgrey.s[i].j[0][2]);
+//printf("~1 %f %f %f\n", asgrey.s[i].j[1][0], asgrey.s[i].j[1][1], asgrey.s[i].j[1][2]);
+//printf("~1 %f %f %f\n", asgrey.s[i].j[2][0], asgrey.s[i].j[2][1], asgrey.s[i].j[2][2]);
+ if (verb)
+ printf("dispcal: inverting Jacobian failed (3) - falling back\n");
+
+ /* Revert to the initial Jacobian */
+ icmCpy3x3(asgrey.s[i].ij, asgrey.s[i].fb_ij);
+ }
+ /* Restart at the best we've had */
+ if (asgrey.s[i].hde > besthde) {
+ asgrey.s[i].de = bestde;
+ asgrey.s[i].dc = bestdc;
+ asgrey.s[i].peqde = bestpeqde;
+ asgrey.s[i].hde = besthde;
+ asgrey.s[i].rgb[0] = bestrgb[0];
+ asgrey.s[i].rgb[1] = bestrgb[1];
+ asgrey.s[i].rgb[2] = bestrgb[2];
+ asgrey.s[i].XYZ[0] = bestxyz[0];
+ asgrey.s[i].XYZ[1] = bestxyz[1];
+ asgrey.s[i].XYZ[2] = bestxyz[2];
+ icmSub3(asgrey.s[i].deXYZ, asgrey.s[i].tXYZ, asgrey.s[i].XYZ);
+ }
+ mjac = 1;
+ impj = 1;
+ }
+#endif /* REMEAS_JACOBIAN */
+
+ /* Compute a correction to the Jacobian if we can. */
+ /* (Don't do this unless we have a solid previous */
+ /* reading for this patch) */
+ if (impj == 0 && rpt > 0 && isclipped == 0) {
+ double nsdrgb; /* Norm squared of pdrgb */
+ double spdrgb[3]; /* Scaled previous delta rgb */
+ double dXYZerr[3]; /* Error in previous prediction */
+ double jadj[3][3]; /* Adjustment to Jacobian */
+ double tj[3][3]; /* Temp Jacobian */
+ double itj[3][3]; /* Temp inverse Jacobian */
+
+ /* Use Broyden's Formula */
+ icmSub3(dXYZerr, asgrey.s[i].dXYZ, asgrey.s[i].pdXYZ);
+//printf("~1 Jacobian error = %f %f %f\n", dXYZerr[0], dXYZerr[1], dXYZerr[2]);
+ nsdrgb = icmNorm3sq(asgrey.s[i].pdrgb);
+ /* If there was sufficient change in device values */
+ /* to be above any noise: */
+ if (nsdrgb >= (0.005 * 0.005)) {
+ icmScale3(spdrgb, asgrey.s[i].pdrgb, 1.0/nsdrgb);
+ icmTensMul3(jadj, dXYZerr, spdrgb);
+
+#ifdef DEBUG
+ /* Check that new Jacobian predicts previous delta XYZ */
+ {
+ double eXYZ[3];
+
+ /* Make a full adjustment to temporary Jac */
+ icmAdd3x3(tj, asgrey.s[i].j, jadj);
+ icmMulBy3x3(eXYZ, tj, asgrey.s[i].pdrgb);
+ icmSub3(eXYZ, eXYZ, asgrey.s[i].dXYZ);
+ printf("Jac check resid %f %f %f\n", eXYZ[0], eXYZ[1], eXYZ[2]);
+ }
+#endif /* DEBUG */
+
+ /* Add part of our correction to actual Jacobian */
+ icmScale3x3(jadj, jadj, JAC_COR_FACT);
+ icmAdd3x3(tj, asgrey.s[i].j, jadj);
+ if (icmInverse3x3(itj, tj) == 0) { /* Invert OK */
+ icmCpy3x3(asgrey.s[i].j, tj); /* Use adjusted */
+ icmCpy3x3(asgrey.s[i].ij, itj);
+ impj = 1;
+ }
+//else printf("~1 ij failed\n");
+ }
+//else printf("~1 nsdrgb was below threshold\n");
+ }
+//else if (isclipped) printf("~1 no j update: rgb is clipped\n");
+#endif /* ADJ_JACOBIAN */
+
+ /* Track the best solution we've found */
+ if (asgrey.s[i].hde <= besthde) {
+ bestde = asgrey.s[i].de;
+ bestdc = asgrey.s[i].dc;
+ bestpeqde = asgrey.s[i].peqde;
+ besthde = asgrey.s[i].hde;
+ bestrgb[0] = asgrey.s[i].rgb[0];
+ bestrgb[1] = asgrey.s[i].rgb[1];
+ bestrgb[2] = asgrey.s[i].rgb[2];
+ bestxyz[0] = asgrey.s[i].XYZ[0];
+ bestxyz[1] = asgrey.s[i].XYZ[1];
+ bestxyz[2] = asgrey.s[i].XYZ[2];
+
+ } else if (asgrey.s[i].dc > bestdc) {
+ /* we got worse in Lab and XYZ ! */
+
+ wde = asgrey.s[i].de;
+
+ /* If we've wandered too far, return to best we found */
+ if (asgrey.s[i].hde > (3.0 * besthde)) {
+ asgrey.s[i].de = bestde;
+ asgrey.s[i].dc = bestdc;
+ asgrey.s[i].peqde = bestpeqde;
+ asgrey.s[i].hde = besthde;
+ asgrey.s[i].rgb[0] = bestrgb[0];
+ asgrey.s[i].rgb[1] = bestrgb[1];
+ asgrey.s[i].rgb[2] = bestrgb[2];
+ asgrey.s[i].XYZ[0] = bestxyz[0];
+ asgrey.s[i].XYZ[1] = bestxyz[1];
+ asgrey.s[i].XYZ[2] = bestxyz[2];
+ icmSub3(asgrey.s[i].deXYZ, asgrey.s[i].tXYZ, asgrey.s[i].XYZ);
+ }
+
+ /* If the Jacobian hasn't changed, moderate the gain */
+ if (impj == 0)
+ rgain *= 0.8; /* We might be overshooting */
+ gworse = 1;
+ }
+
+ /* See if we need to repeat */
+ if (asgrey.s[i].de <= errthr && asgrey.s[i].peqde < errthr) {
+ if (verb > 1)
+ if (it < (mxits-1))
+ printf("Point %d Delta E %f, OK\n",rsteps - i,asgrey.s[i].de);
+ else
+ printf("Point %d Delta E %f, peqDE %f, OK\n",rsteps - i,asgrey.s[i].de, asgrey.s[i].peqde);
+ break; /* No more retries */
+ }
+ if ((rpt+1) >= MAX_RPTS) {
+ asgrey.s[i].de = bestde; /* Restore to best we found */
+ asgrey.s[i].dc = bestdc;
+ asgrey.s[i].peqde = bestpeqde; /* Restore to best we found */
+ asgrey.s[i].hde = besthde; /* Restore to best we found */
+ asgrey.s[i].rgb[0] = bestrgb[0];
+ asgrey.s[i].rgb[1] = bestrgb[1];
+ asgrey.s[i].rgb[2] = bestrgb[2];
+ asgrey.s[i].XYZ[0] = bestxyz[0];
+ asgrey.s[i].XYZ[1] = bestxyz[1];
+ asgrey.s[i].XYZ[2] = bestxyz[2];
+ if (verb > 1)
+ if (it < (mxits-1))
+ printf("Point %d Delta E %f, Fail\n",rsteps - i,asgrey.s[i].de);
+ else
+ printf("Point %d Delta E %f, peqDE %f, Fail\n",rsteps - i,asgrey.s[i].de,asgrey.s[i].peqde);
+ thrfail = 1; /* Failed to meet target */
+ if (bestde > failerr)
+ failerr = bestde; /* Worst failed delta E */
+ break; /* No more retries */
+ }
+ if (verb > 1) {
+ if (gworse)
+ if (it < (mxits-1))
+ printf("Point %d Delta E %f, Repeat (got worse)\n", rsteps - i, wde);
+ else
+ printf("Point %d Delta E %f, peqDE %f, Repeat (got worse)\n", rsteps - i, wde,asgrey.s[i].peqde);
+ else
+ if (it < (mxits-1))
+ printf("Point %d Delta E %f, Repeat\n", rsteps - i,asgrey.s[i].de);
+ else
+ printf("Point %d Delta E %f, peqDE %f, Repeat\n", rsteps - i,asgrey.s[i].de,asgrey.s[i].peqde);
+ }
+
+ /* Compute refinement of rgb */
+ icmMulBy3x3(asgrey.s[i].pdrgb, asgrey.s[i].ij, asgrey.s[i].deXYZ);
+//printf("~1 delta needed %f %f %f -> delta RGB %f %f %f\n",
+//asgrey.s[i].deXYZ[0], asgrey.s[i].deXYZ[1], asgrey.s[i].deXYZ[2],
+//asgrey.s[i].pdrgb[0], asgrey.s[i].pdrgb[1], asgrey.s[i].pdrgb[2]);
+
+ /* Gain scale */
+ icmScale3(asgrey.s[i].pdrgb, asgrey.s[i].pdrgb, rgain);
+//printf("~1 delta RGB after gain scale %f %f %f\n",
+//asgrey.s[i].pdrgb[0], asgrey.s[i].pdrgb[1], asgrey.s[i].pdrgb[2]);
+
+#ifdef CLIP
+ /* Component wise clip */
+ for (j = 0; j < 3; j++) { /* Check for clip */
+ if ((-asgrey.s[i].pdrgb[j]) > asgrey.s[i].rgb[j]) {
+ asgrey.s[i].pdrgb[j] = -asgrey.s[i].rgb[j];
+ dclip = 1;
+ }
+ if (asgrey.s[i].pdrgb[j] > (1.0 - asgrey.s[i].rgb[j])) {
+ asgrey.s[i].pdrgb[j] = (1.0 - asgrey.s[i].rgb[j]);
+ dclip = 1;
+ }
+ }
+#ifdef DEBUG
+ if (dclip) printf("delta RGB after clip %f %f %f\n",
+ asgrey.s[i].pdrgb[0], asgrey.s[i].pdrgb[1], asgrey.s[i].pdrgb[2]);
+#endif /* DEBUG */
+#endif /* CLIP */
+ /* Compute next on the basis of this one RGB */
+ icmAdd3(asgrey.s[i].rgb, asgrey.s[i].rgb, asgrey.s[i].pdrgb);
+
+ /* Save expected change in XYZ */
+ icmMulBy3x3(asgrey.s[i].pdXYZ, asgrey.s[i].j, asgrey.s[i].pdrgb);
+#ifdef DEBUG
+ printf("New rgb %f %f %f from expected del XYZ %f %f %f\n",
+ asgrey.s[i].rgb[0], asgrey.s[i].rgb[1], asgrey.s[i].rgb[2],
+ asgrey.s[i].pdXYZ[0], asgrey.s[i].pdXYZ[1], asgrey.s[i].pdXYZ[2]);
+#endif
+ } else { /* Verification, so no repeat */
+ break;
+ }
+
+ prevde = asgrey.s[i].de;
+ } /* Next repeat */
+ } /* Next resolution step */
+ if (verb)
+ printf("\n"); /* Final return for patch count */
+
+#ifdef DEBUG_PLOT
+ /* Plot the measured response XYZ */
+ {
+ #define XRES 256
+ double xx[XRES];
+ double yy[3][XRES];
+ double xyz[3];
+ for (i = 0; i < XRES; i++) {
+ xx[i] = i/(XRES-1.0);
+ csamp_interp(&asgrey, xyz, xx[i]);
+ for (j = 0; j < 3; j++)
+ yy[j][i] = xyz[j];
+ }
+ printf("Measured neutral axis XYZ\n",k);
+ do_plot(xx,yy[0],yy[1],yy[2],XRES);
+ #undef XRES
+ }
+#endif
+
+ /* Check out the accuracy of the results: */
+ {
+ double ctwh[3]; /* Current target white */
+ icmXYZNumber ctwN; /* Same as above as XYZNumber */
+ double brerr; /* Brightness error */
+ double cterr; /* Color temperature delta E */
+ double mnerr; /* Maximum neutral error */
+ double mnv = 0.0; /* Value where maximum error is */
+ double anerr; /* Average neutral error */
+ double lab1[3], lab2[3];
+
+ /* Brightness */
+ brerr = asgrey.s[asgrey.no-1].XYZ[1] - x.twh[1];
+
+ /* Compensate for brightness error */
+ for (j = 0; j < 3; j++)
+ ctwh[j] = x.twh[j] * asgrey.s[asgrey.no-1].XYZ[1]/x.twh[1];
+ icmAry2XYZ(ctwN, ctwh); /* Need this for Lab conversions */
+
+ /* Color temperature error */
+ icmXYZ2Lab(&ctwN, lab1, ctwh); /* Should be 100,0,0 */
+ icmXYZ2Lab(&ctwN, lab2, asgrey.s[asgrey.no-1].XYZ);
+ cterr = icmLabDE(lab1, lab2);
+
+ /* check delta E of all the sample points */
+ /* We're checking against our given brightness and */
+ /* white point target. */
+ mnerr = anerr = 0.0;
+ init_csamp_txyz(&asgrey, &x, 0); /* In case the targets were tweaked */
+ for (i = 0; i < asgrey.no; i++) {
+ double err;
+
+ /* Re-compute de in case last pass had tweaked targets */
+ asgrey.s[i].de = icmXYZLabDE(&x.twN, asgrey.s[i].tXYZ, asgrey.s[i].XYZ);
+ err = asgrey.s[i].de;
+//printf("RGB %.3f -> Lab %.2f %.2f %.2f, target %.2f %.2f %.2f, DE %f\n",
+//asgrey.s[i].v, lab2[0], lab2[1], lab2[2], lab1[0], lab1[1], lab1[2], err);
+ if (err > mnerr) {
+ mnerr = err;
+ mnv = asgrey.s[i].v;
+ }
+ anerr += err;
+ }
+ anerr /= (double)asgrey.no;
+
+ if (verb || it >= mxits) {
+ if (it >= mxits)
+ printf("Verification results:\n");
+ printf("Brightness error = %f cd/m^2 (is %f, should be %f)\n",brerr,asgrey.s[asgrey.no-1].XYZ[1],x.twh[1]);
+ printf("White point error = %f deltaE\n",cterr);
+ printf("Maximum neutral error (@ %f) = %f deltaE\n",mnv, mnerr);
+ printf("Average neutral error = %f deltaE\n",anerr);
+ if (it < mxits && thrfail)
+ printf("Failed to meet target %f delta E, got worst case %f\n",errthr,failerr);
+ printf("Number of measurements taken = %d\n",totmeas);
+ }
+ }
+
+ /* Verify loop exit */
+ if (it >= (mxits + nver -1)) {
+ break;
+ }
+
+ /* Convert our test points into calibration curves. */
+ /* The call to reinit_csamp() will then convert the */
+ /* curves back to current test point values. */
+ /* This applies some level of cohesion between the test points, */
+ /* as well as forcing monotomicity */
+ if (it < mxits) {
+ mcvco *sdv[3]; /* Scattered data for mcv */
+
+ for (j = 0; j < 3; j++) {
+ if ((sdv[j] = malloc(sizeof(mcvco) * asgrey.no)) == NULL) {
+ dr->del(dr);
+ error ("Malloc of scattered data points failed");
+ }
+ }
+
+ if (verb)
+ printf("Computing update to calibration curves...\n");
+
+ /* Use fixed rgb's */
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < asgrey.no; i++) {
+ sdv[j][i].p = asgrey.s[i].v;
+ sdv[j][i].v = asgrey.s[i].rgb[j];
+ sdv[j][i].w = 1.0;
+// ~~999
+#ifdef NEVER
+ printf("rdac %d point %d = %f, %f\n",j,i,sdv[j][i].p,sdv[j][i].v);
+#endif
+ }
+ }
+ if (x.nat) /* Make curve go thought white if possible */
+ sdv[0][rsteps-1].w = sdv[1][rsteps-1].w = sdv[2][rsteps-1].w = 10.0;
+
+ for (j = 0; j < 3; j++)
+ x.rdac[j]->fit(x.rdac[j], 0, fitord, sdv[j], asgrey.no, RDAC_SMOOTH);
+
+ /* Make sure that if we are using native brightness and white point, */
+ /* that the curves go to a perfect 1.0 ... */
+ if (x.nat) {
+ for (j = 0; j < 3; j++)
+ x.rdac[j]->force_1(x.rdac[j], 1.0);
+ }
+
+ for (j = 0; j < 3; j++)
+ free(sdv[j]);
+#ifdef DEBUG_PLOT
+ /* Plot the current curves */
+ {
+ #define XRES 255
+ double xx[XRES];
+ double y1[XRES];
+ double y2[XRES];
+ double y3[XRES];
+ double rgb[3];
+ for (i = 0; i < XRES; i++) {
+ double drgb[3], rgb[3];
+ xx[i] = i/(XRES-1.0);
+ rgb[0] = rgb[1] = rgb[2] = xx[i];
+ for (j = 0; j < 3; j++)
+ drgb[j] = x.rdac[j]->interp(x.rdac[j], rgb[j]);
+ y1[i] = drgb[0];
+ y2[i] = drgb[1];
+ y3[i] = drgb[2];
+ }
+ printf("Current ramdac curves\n");
+ do_plot(xx,y1,y2,y3,XRES);
+ #undef XRES
+ }
+#endif
+ }
+ } /* Next refine/verify loop */
+
+ free_alloc_csamp(&asgrey); /* We're done with test points */
+ dr->del(dr); /* Now we're done with test window */
+
+ /* Write out the resulting calibration file */
+ if (verify != 2) {
+ int calres = CAL_RES; /* steps in calibration table saved */
+ cgats *ocg; /* output cgats structure */
+ time_t clk = time(0);
+ struct tm *tsp = localtime(&clk);
+ char *atm = asctime(tsp); /* Ascii time */
+ cgats_set_elem *setel; /* Array of set value elements */
+ int ncps; /* Number of curve parameters */
+ double *cps[3]; /* Arrays of curve parameters */
+ char *bp = NULL, buf[100]; /* Buffer to sprintf into */
+
+ ocg = new_cgats(); /* Create a CGATS structure */
+ ocg->add_other(ocg, "CAL"); /* our special type is Calibration file */
+
+ ocg->add_table(ocg, tt_other, 0); /* Add a table for RAMDAC values */
+ ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Device Calibration Curves",NULL);
+ ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll dispcal", NULL);
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+ ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
+
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","DISPLAY", NULL);
+ ocg->add_kword(ocg, 0, "COLOR_REP","RGB", NULL);
+ /* Tell downstream whether they can expect that this calibration */
+ /* will be applied in hardware or not. */
+ ocg->add_kword(ocg, 0, "VIDEO_LUT_CALIBRATION_POSSIBLE",noramdac ? "NO" : "YES", NULL);
+
+ /* Put the target parameters in the CGATS file too */
+ if (dtype != 0) {
+ sprintf(buf,"%c",dtype);
+ ocg->add_kword(ocg, 0, "DEVICE_TYPE", buf, NULL);
+ }
+
+ if (wpx == 0.0 && wpy == 0.0 && temp == 0.0 && tbright == 0.0)
+ ocg->add_kword(ocg, 0, "NATIVE_TARGET_WHITE","", NULL);
+
+ sprintf(buf,"%f %f %f", x.twh[0], x.twh[1], x.twh[2]);
+ ocg->add_kword(ocg, 0, "TARGET_WHITE_XYZ",buf, NULL);
+
+ switch(x.gammat) {
+ case gt_power:
+ if (egamma > 0.0)
+ sprintf(buf,"%f", -egamma);
+ else
+ sprintf(buf,"%f", gamma);
+ break;
+ case gt_Lab:
+ strcpy(buf,"L_STAR");
+ break;
+ case gt_sRGB:
+ strcpy(buf,"sRGB");
+ break;
+ case gt_Rec709:
+ strcpy(buf,"REC709");
+ break;
+ case gt_SMPTE240M:
+ strcpy(buf,"SMPTE240M");
+ break;
+ default:
+ error("Unknown gamma type");
+ }
+ ocg->add_kword(ocg, 0, "TARGET_GAMMA",buf, NULL);
+
+ sprintf(buf,"%f", x.oofff);
+ ocg->add_kword(ocg, 0, "DEGREE_OF_BLACK_OUTPUT_OFFSET",buf, NULL);
+
+ sprintf(buf,"%f", bkcorrect);
+ ocg->add_kword(ocg, 0, "BLACK_POINT_CORRECTION", buf, NULL);
+
+ sprintf(buf,"%f", x.nbrate);
+ ocg->add_kword(ocg, 0, "BLACK_NEUTRAL_BLEND_RATE", buf, NULL);
+
+ if (bkbright > 0.0) {
+ sprintf(buf,"%f", bkbright);
+ ocg->add_kword(ocg, 0, "TARGET_BLACK_BRIGHTNESS",buf, NULL);
+ }
+
+ /* Write rest of setup */
+ switch (quality) {
+ case -3: /* Test value */
+ bp = "ultra low";
+ break;
+ case -2: /* Very low */
+ bp = "very low";
+ break;
+ case -1: /* Low */
+ bp = "low";
+ break;
+ case 0: /* Medum */
+ bp = "medium";
+ break;
+ case 1: /* High */
+ bp = "high";
+ break;
+ case 2: /* Ultra */
+ bp = "ultra high";
+ break;
+ default:
+ error("unknown quality level %d",quality);
+ }
+ ocg->add_kword(ocg, 0, "QUALITY",bp, NULL);
+
+ ocg->add_field(ocg, 0, "RGB_I", r_t);
+ ocg->add_field(ocg, 0, "RGB_R", r_t);
+ ocg->add_field(ocg, 0, "RGB_G", r_t);
+ ocg->add_field(ocg, 0, "RGB_B", r_t);
+
+ if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * 4)) == NULL)
+ error("Malloc failed!");
+
+ /* Write the video lut curve values */
+ for (i = 0; i < calres; i++) {
+ double vv, rgb[3];
+
+#if defined(__APPLE__) && defined(__POWERPC__)
+ gcc_bug_fix(i);
+#endif
+ vv = i/(calres-1.0);
+ for (j = 0; j < 3; j++) {
+ double cc;
+ cc = x.rdac[j]->interp(x.rdac[j], vv);
+ if (cc < 0.0)
+ cc = 0.0;
+ else if (cc > 1.0)
+ cc = 1.0;
+ rgb[j] = cc;
+ }
+
+ setel[0].d = vv;
+ setel[1].d = rgb[0];
+ setel[2].d = rgb[1];
+ setel[3].d = rgb[2];
+
+ ocg->add_setarr(ocg, 0, setel);
+ }
+
+ free(setel);
+
+ /* Write some of the device model information to a second */
+ /* table, so that we can update the calibration latter on without */
+ /* having to read R,G & B curves. */
+
+ ocg->add_table(ocg, tt_other, 0); /* Add a second table for setup and model */
+ ocg->add_kword(ocg, 1, "DESCRIPTOR", "Argyll Calibration options and model",NULL);
+ ocg->add_kword(ocg, 1, "ORIGINATOR", "Argyll dispcal", NULL);
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+ ocg->add_kword(ocg, 1, "CREATED",atm, NULL);
+
+
+ /* Write device model curves */
+ ocg->add_field(ocg, 1, "R_P", r_t);
+ ocg->add_field(ocg, 1, "G_P", r_t);
+ ocg->add_field(ocg, 1, "B_P", r_t);
+
+ if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * 3)) == NULL)
+ error("Malloc failed!");
+
+ ncps = -1;
+ for (i = 0; i < 3; i++) {
+ int nn;
+ nn = x.dcvs[i]->get_params(x.dcvs[i], &cps[i]);
+ if (ncps != -1 && ncps != nn)
+ error("Expect device model linearisation curves to have the same order");
+ ncps = nn;
+ }
+
+ for (i = 0; i < ncps; i++) {
+ setel[0].d = cps[0][i];
+ setel[1].d = cps[1][i];
+ setel[2].d = cps[2][i];
+ ocg->add_setarr(ocg, 1, setel);
+ }
+
+ for (i = 0; i < 3; i++)
+ free(cps[i]);
+ free(setel);
+
+ if (ocg->write_name(ocg, outname))
+ error("Write error : %s",ocg->err);
+
+ if (verb)
+ printf("Written calibration file '%s'\n",outname);
+
+ ocg->del(ocg); /* Clean up */
+
+ }
+
+ /* Update the ICC file with the new 'vcgt' curves */
+ if (verify != 2 && doupdate && doprofile) {
+ icmFile *ic_fp;
+ icc *icco;
+ int j, i;
+ icmVideoCardGamma *wo;
+
+ if ((icco = new_icc()) == NULL)
+ error ("Creation of ICC object to read profile '%s' failed",iccoutname);
+
+ /* Open up the profile for reading */
+ if ((ic_fp = new_icmFileStd_name(iccoutname,"r")) == NULL)
+ error ("Can't open file '%s'",iccoutname);
+
+ /* Read header etc. */
+ if ((rv = icco->read(icco,ic_fp,0)) != 0)
+ error ("Reading profile '%s' failed with %d, %s",iccoutname, rv,icco->err);
+
+ /* Read every tag */
+ if (icco->read_all_tags(icco) != 0) {
+ error("Unable to read all tags from '%s': %d, %s",iccoutname, icco->errc,icco->err);
+ }
+
+ ic_fp->del(ic_fp);
+
+ wo = (icmVideoCardGamma *)icco->read_tag(icco, icSigVideoCardGammaTag);
+ if (wo == NULL)
+ error("Can't find VideoCardGamma tag in file '%s': %d, %s",
+ iccoutname, icco->errc,icco->err);
+
+ wo->tagType = icmVideoCardGammaTableType;
+ wo->u.table.channels = 3; /* rgb */
+ wo->u.table.entryCount = CAL_RES; /* full lut */
+ wo->u.table.entrySize = 2; /* 16 bits */
+ wo->allocate((icmBase*)wo);
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < CAL_RES; i++) {
+ double cc, vv;
+#if defined(__APPLE__) && defined(__POWERPC__)
+ gcc_bug_fix(i);
+#endif
+ vv = i/(CAL_RES-1.0);
+
+ cc = x.rdac[j]->interp(x.rdac[j], vv);
+
+ if (cc < 0.0)
+ cc = 0.0;
+ else if (cc > 1.0)
+ cc = 1.0;
+ ((unsigned short*)wo->u.table.data)[CAL_RES * j + i] = (int)(cc * 65535.0 + 0.5);
+ }
+ }
+
+ /* Open up the profile again writing */
+ if ((ic_fp = new_icmFileStd_name(iccoutname,"w")) == NULL)
+ error ("Can't open file '%s' for writing",iccoutname);
+
+ if ((rv = icco->write(icco,ic_fp,0)) != 0)
+ error ("Write to file '%s' failed: %d, %s",iccoutname, rv,icco->err);
+
+ if (verb)
+ printf("Updated profile '%s'\n",iccoutname);
+
+ ic_fp->del(ic_fp);
+ icco->del(icco);
+
+ /* Create a fast matrix/shaper profile */
+ /*
+ [ Another way of doing this would be to run all the
+ measured points through the calibration curves, and
+ then re-fit the curve/matrix to the calibrated points.
+ This might be more accurate ?]
+
+ Ideally we should also re-measure primaries through calibration
+ rather than computing the calibrated values ?
+
+ */
+
+ } else if (verify != 2 && doprofile) {
+ icmFile *wr_fp;
+ icc *wr_icco;
+ double uwp[3]; /* Absolute Uncalibrated White point in XYZ */
+ double wp[3]; /* Absolute White point in XYZ */
+ double bp[3]; /* Absolute Black point in XYZ */
+ double mat[3][3]; /* Device to XYZ matrix */
+ double calrgb[3]; /* 1.0 through calibration curves */
+ double clrgb[3]; /* 1.0 through calibration and linearization */
+
+ /* Lookup white and black points */
+ {
+ int j;
+ double rgb[3];
+
+ calrgb[0] = calrgb[1] = calrgb[2] = 1.0;
+
+ fwddev(&x, uwp, calrgb); /* absolute uncalibrated WP (native white point) */
+
+//printf("~1 native abs white point XYZ %f %f %f\n", uwp[0], uwp[1], uwp[2]);
+
+ /* RGB 1.0 Through calibration */
+ for (j = 0; j < 3; j++) {
+ calrgb[j] = x.rdac[j]->interp(x.rdac[j], calrgb[j]);
+ if (calrgb[j] < 0.0)
+ calrgb[j] = 0.0;
+ else if (calrgb[j] > 1.0)
+ calrgb[j] = 1.0;
+ }
+ fwddev(&x, wp, calrgb); /* absolute calibrated WP */
+//printf("~1 calibrated rgb = %f %f %f\n", calrgb[0], calrgb[1], calrgb[2]);
+//printf("~1 calibrated abs white point XYZ %f %f %f\n", wp[0], wp[1], wp[2]);
+
+ for (j = 0; j < 3; j++)
+ clrgb[j] = x.dcvs[j]->interp(x.dcvs[j], calrgb[j]);
+//printf("~1 cal & lin rgb = %f %f %f\n", clrgb[0], clrgb[1], clrgb[2]);
+
+ rgb[0] = rgb[1] = rgb[2] = 0.0;
+
+ /* RGB 0.0 through calibration */
+ for (j = 0; j < 3; j++) {
+ rgb[j] = x.rdac[j]->interp(x.rdac[j], rgb[j]);
+ if (rgb[j] < 0.0)
+ rgb[j] = 0.0;
+ else if (rgb[j] > 1.0)
+ rgb[j] = 1.0;
+ }
+ fwddev(&x, bp, rgb); /* Absolute calibrated BP */
+ }
+
+ /* Apply calibration to matrix, and then adjust it to be */
+ /* relative to D50 white point, rather than absolute. */
+ {
+ double rgb[3];
+ icmXYZNumber swp;
+
+ /* Transfer from parameter to matrix */
+ icmCpy3x3(mat, x.fm);
+
+ /* Compute the calibrated matrix values so that the curves */
+ /* device curves end at 1.0. */
+
+ /* In the HW calibrated case this represents the lower XYZ due to */
+ /* the HW calibrated lower RGB values of white compared to the raw */
+ /* model response, so that the calibration curve concatentation with the */
+ /* device curves can be scaled up to end at 1.0. */
+ if (noramdac == 0) {
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < 3; i++)
+ rgb[i] = 0.0;
+ rgb[j] = clrgb[j];
+ icmMulBy3x3(rgb, x.fm, rgb); /* clrgb -> matrix -> RGB */
+ for (i = 0; i < 3; i++)
+ mat[i][j] = rgb[i];
+ }
+#ifdef NEVER
+ {
+ double rgb[3], xyz[3], lab[3];
+
+ rgb[0] = rgb[1] = rgb[2] = 1.0;
+ icmMulBy3x3(xyz, mat, rgb);
+ icmXYZ2Lab(&icmD50, lab, xyz);
+
+ printf("RGB 1 through matrix = XYZ %f %f %f, Lab %f %f %f\n", xyz[0], xyz[1], xyz[2], lab[0], lab[1], lab[2]);
+ }
+#endif
+ /* Adapt matrix */
+ icmAry2XYZ(swp, wp);
+ icmChromAdaptMatrix(ICM_CAM_MULMATRIX | ICM_CAM_BRADFORD, icmD50, swp, mat);
+#ifdef NEVER
+ {
+ double rgb[3], xyz[3], lab[3];
+
+ rgb[0] = rgb[1] = rgb[2] = 1.0;
+ icmMulBy3x3(xyz, mat, rgb);
+ icmXYZ2Lab(&icmD50, lab, xyz);
+
+ printf("RGB 1 through chrom matrix = XYZ %f %f %f, Lab %f %f %f\n", xyz[0], xyz[1], xyz[2], lab[0], lab[1], lab[2]);
+ }
+#endif
+
+ /* For the calibration incororated in profile case, we should boost the */
+ /* XYZ by 1/calrgb[] so that the lower calibrated RGB values results in the native */
+ /* white point, but we want to reduce it by callinrgb[] to move from the native */
+ /* white point to the calibrated white point. */
+ } else {
+ icmCpy3x3(mat, x.fm);
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < 3; i++)
+ rgb[i] = 0.0;
+ rgb[j] = clrgb[j]/calrgb[j];
+ icmMulBy3x3(rgb, x.fm, rgb); /* 1/calrgb -> matrix -> RGB */
+ for (i = 0; i < 3; i++)
+ mat[i][j] = rgb[i];
+ }
+#ifdef NEVER
+ {
+ double rgb[3], xyz[3], lab[3];
+
+ for (j = 0; j < 3; j++)
+ rgb[j] = calrgb[j];
+ icmMulBy3x3(xyz, mat, rgb);
+ icmXYZ2Lab(&icmD50, lab, xyz);
+
+ printf("RGB cal through matrix = XYZ %f %f %f, Lab %f %f %f\n", xyz[0], xyz[1], xyz[2], lab[0], lab[1], lab[2]);
+ }
+#endif
+ /* Adapt matrix */
+ icmAry2XYZ(swp, wp);
+ icmChromAdaptMatrix(ICM_CAM_MULMATRIX | ICM_CAM_BRADFORD, icmD50, swp, mat);
+#ifdef NEVER
+ {
+ double rgb[3], xyz[3], lab[3];
+
+ icmMulBy3x3(xyz, mat, calrgb);
+ icmXYZ2Lab(&icmD50, lab, xyz);
+
+ printf("RGB cal through chrom matrix = XYZ %f %f %f, Lab %f %f %f\n", xyz[0], xyz[1], xyz[2], lab[0], lab[1], lab[2]);
+ }
+#endif
+ }
+ }
+
+ /* Open up the file for writing */
+ if ((wr_fp = new_icmFileStd_name(iccoutname,"w")) == NULL)
+ error("Write: Can't open file '%s'",iccoutname);
+
+ if ((wr_icco = new_icc()) == NULL)
+ error("Write: Creation of ICC object failed");
+
+ /* Add all the tags required */
+
+ /* The header: */
+ {
+ icmHeader *wh = wr_icco->header;
+
+ /* Values that must be set before writing */
+ wh->deviceClass = icSigDisplayClass;
+ wh->colorSpace = icSigRgbData; /* Display is RGB */
+ wh->pcs = icSigXYZData; /* XYZ for matrix based profile */
+ wh->renderingIntent = icRelativeColorimetric; /* For want of something */
+
+ wh->manufacturer = icmSigUnknownType;
+ wh->model = icmSigUnknownType;
+#ifdef NT
+ wh->platform = icSigMicrosoft;
+#endif
+#ifdef __APPLE__
+ wh->platform = icSigMacintosh;
+#endif
+#if defined(UNIX_X11)
+ wh->platform = icmSig_nix;
+#endif
+ }
+
+ /* Profile Description Tag: */
+ {
+ icmTextDescription *wo;
+ char *dst, dstm[200]; /* description */
+
+ if (profDesc != NULL)
+ dst = profDesc;
+ else {
+ dst = iccoutname;
+ }
+
+ if ((wo = (icmTextDescription *)wr_icco->add_tag(
+ wr_icco, icSigProfileDescriptionTag, icSigTextDescriptionType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */
+ wo->allocate((icmBase *)wo);/* Allocate space */
+ strcpy(wo->desc, dst); /* Copy the string in */
+ }
+ /* Copyright Tag: */
+ {
+ icmText *wo;
+ char *crt;
+
+ if (copyright != NULL)
+ crt = copyright;
+ else
+ crt = "Copyright, the creator of this profile";
+
+ if ((wo = (icmText *)wr_icco->add_tag(
+ wr_icco, icSigCopyrightTag, icSigTextType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = strlen(crt)+1; /* Allocated and used size of text, inc null */
+ wo->allocate((icmBase *)wo);/* Allocate space */
+ strcpy(wo->data, crt); /* Copy the text in */
+ }
+ /* Device Manufacturers Description Tag: */
+ if (deviceMfgDesc != NULL) {
+ icmTextDescription *wo;
+ char *dst = deviceMfgDesc;
+
+ if ((wo = (icmTextDescription *)wr_icco->add_tag(
+ wr_icco, icSigDeviceMfgDescTag, icSigTextDescriptionType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */
+ wo->allocate((icmBase *)wo);/* Allocate space */
+ strcpy(wo->desc, dst); /* Copy the string in */
+ }
+ /* Model Description Tag: */
+ if (modelDesc != NULL) {
+ icmTextDescription *wo;
+ char *dst = modelDesc;
+
+ if ((wo = (icmTextDescription *)wr_icco->add_tag(
+ wr_icco, icSigDeviceModelDescTag, icSigTextDescriptionType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */
+ wo->allocate((icmBase *)wo);/* Allocate space */
+ strcpy(wo->desc, dst); /* Copy the string in */
+ }
+ /* Luminance tag */
+ {
+ icmXYZArray *wo;;
+
+ if ((wo = (icmXYZArray *)wr_icco->add_tag(
+ wr_icco, icSigLuminanceTag, icSigXYZArrayType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = 1;
+ wo->allocate((icmBase *)wo); /* Allocate space */
+ wo->data[0].X = 0.0;
+ wo->data[0].Y = dispLum * wp[1]/uwp[1]; /* Adjust for effect of calibration */
+ wo->data[0].Z = 0.0;
+
+ if (verb)
+ printf("Luminance XYZ = %f %f %f\n", wo->data[0].X, wo->data[0].Y, wo->data[0].Z);
+ }
+ /* White Point Tag: */
+ {
+ icmXYZArray *wo;
+ /* Note that tag types icSigXYZType and icSigXYZArrayType are identical */
+ if ((wo = (icmXYZArray *)wr_icco->add_tag(
+ wr_icco, icSigMediaWhitePointTag, icSigXYZArrayType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = 1;
+ wo->allocate((icmBase *)wo); /* Allocate space */
+ wo->data[0].X = wp[0] * 1.0/wp[1];
+ wo->data[0].Y = wp[1] * 1.0/wp[1];
+ wo->data[0].Z = wp[2] * 1.0/wp[1];
+
+ if (verb)
+ printf("White point XYZ = %f %f %f\n", wo->data[0].X, wo->data[0].Y, wo->data[0].Z);
+ }
+ /* Black Point Tag: */
+ {
+ icmXYZArray *wo;
+ if ((wo = (icmXYZArray *)wr_icco->add_tag(
+ wr_icco, icSigMediaBlackPointTag, icSigXYZArrayType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = 1;
+ wo->allocate((icmBase *)wo); /* Allocate space */
+ wo->data[0].X = bp[0] * 1.0/wp[1];
+ wo->data[0].Y = bp[1] * 1.0/wp[1];
+ wo->data[0].Z = bp[2] * 1.0/wp[1];
+
+ if (verb)
+ printf("Black point XYZ = %f %f %f\n", wo->data[0].X, wo->data[0].Y, wo->data[0].Z);
+ }
+
+ /* vcgt tag, if the display has an accessible VideoLUT */
+ if (noramdac == 0) {
+ int j, i;
+ icmVideoCardGamma *wo;
+ wo = (icmVideoCardGamma *)wr_icco->add_tag(wr_icco,
+ icSigVideoCardGammaTag, icSigVideoCardGammaType);
+ if (wo == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->tagType = icmVideoCardGammaTableType;
+ wo->u.table.channels = 3; /* rgb */
+ wo->u.table.entryCount = CAL_RES; /* full lut */
+ wo->u.table.entrySize = 2; /* 16 bits */
+ wo->allocate((icmBase*)wo);
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < CAL_RES; i++) {
+ double cc, vv = i/(CAL_RES-1.0);
+ cc = x.rdac[j]->interp(x.rdac[j], vv);
+ if (cc < 0.0)
+ cc = 0.0;
+ else if (cc > 1.0)
+ cc = 1.0;
+ ((unsigned short*)wo->u.table.data)[CAL_RES * j + i] = (int)(cc * 65535.0 + 0.5);
+ }
+ }
+ }
+
+ /* Red, Green and Blue Colorant Tags: */
+ {
+ icmXYZArray *wor, *wog, *wob;
+ if ((wor = (icmXYZArray *)wr_icco->add_tag(
+ wr_icco, icSigRedColorantTag, icSigXYZArrayType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+ if ((wog = (icmXYZArray *)wr_icco->add_tag(
+ wr_icco, icSigGreenColorantTag, icSigXYZArrayType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+ if ((wob = (icmXYZArray *)wr_icco->add_tag(
+ wr_icco, icSigBlueColorantTag, icSigXYZArrayType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+
+ wor->size = wog->size = wob->size = 1;
+ wor->allocate((icmBase *)wor); /* Allocate space */
+ wog->allocate((icmBase *)wog);
+ wob->allocate((icmBase *)wob);
+
+ wor->data[0].X = mat[0][0]; wor->data[0].Y = mat[1][0]; wor->data[0].Z = mat[2][0];
+ wog->data[0].X = mat[0][1]; wog->data[0].Y = mat[1][1]; wog->data[0].Z = mat[2][1];
+ wob->data[0].X = mat[0][2]; wob->data[0].Y = mat[1][2]; wob->data[0].Z = mat[2][2];
+ }
+
+ /* Red, Green and Blue Tone Reproduction Curve Tags: */
+ {
+ icmCurve *wor, *wog, *wob;
+ int ui;
+
+ if ((wor = (icmCurve *)wr_icco->add_tag(
+ wr_icco, icSigRedTRCTag, icSigCurveType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+ if ((wog = (icmCurve *)wr_icco->add_tag(
+ wr_icco, icSigGreenTRCTag, icSigCurveType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+ if ((wob = (icmCurve *)wr_icco->add_tag(
+ wr_icco, icSigBlueTRCTag, icSigCurveType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+
+ wor->flag = wog->flag = wob->flag = icmCurveSpec;
+ wor->size = wog->size = wob->size = 256; /* Number of entries */
+ wor->allocate((icmBase *)wor); /* Allocate space */
+ wog->allocate((icmBase *)wog);
+ wob->allocate((icmBase *)wob);
+
+ /* For the HW calibrated case, we have lowered the matrix */
+ /* values to reflect the calibrated RGB through the native */
+ /* device model, so now we can scale up the comcatenation */
+ /* of the calibration and linearisation curves so that */
+ /* 1.0 in maps to 1.0 out. */
+ if (noramdac == 0) {
+
+ for (ui = 0; ui < wor->size; ui++) {
+ double in, rgb[3];
+
+ for (j = 0; j < 3; j++) {
+#if defined(__APPLE__) && defined(__POWERPC__)
+ gcc_bug_fix(ui);
+#endif
+ in = (double)ui / (wor->size - 1.0);
+
+ /* Transform through calibration curve */
+ in = x.rdac[j]->interp(x.rdac[j], in);
+
+ if (in < 0.0)
+ in = 0.0;
+ else if (in > 1.0)
+ in = 1.0;
+
+ /* Trandform though device model linearisation */
+ in = x.dcvs[j]->interp(x.dcvs[j], in);
+
+ /* Scale back so that 1.0 in gets 1.0 out */
+ in /= clrgb[j];
+
+
+ if (in < 0.0)
+ in = 0.0;
+ else if (in > 1.0)
+ in = 1.0;
+ rgb[j] = in;
+//printf("Step %d, Chan %d, %f -> %f\n",ui,j,(double)ui / (wor->size - 1.0),in);
+ }
+ wor->data[ui] = rgb[0]; /* Curve values 0.0 - 1.0 */
+ wog->data[ui] = rgb[1];
+ wob->data[ui] = rgb[2];
+ }
+
+ /* For the calibration incororated in profile case, */
+ /* we bypass the inverse calibration curve if it would */
+ /* result in saturation, and then scale the overall output */
+ /* back by the calrgb[] value so that the overall curve */
+ /* maps 1.0 to 1.0. The scaled up values in the matrix */
+ /* then result in a calibrated RGB input mapping to the */
+ /* PCS white point. */
+ } else {
+
+ for (ui = 0; ui < wor->size; ui++) {
+ double in, rgb[3];
+
+ for (j = 0; j < 3; j++) {
+#if defined(__APPLE__) && defined(__POWERPC__)
+ gcc_bug_fix(ui);
+#endif
+ in = (double)ui / (wor->size - 1.0);
+
+ /* If within the inversion range, */
+ /* transform through the inverse calibration curve. */
+ if (in < calrgb[j]) {
+ in = x.rdac[j]->inv_interp(x.rdac[j], in);
+ if (in < 0.0)
+ in = 0.0;
+ else if (in > 1.0)
+ in = 1.0;
+ /* Pass through device model linearisation. */
+ in = x.dcvs[j]->interp(x.dcvs[j], in);
+
+ /* Linearly extrapolate when outside inv range */
+ } else {
+ in /= calrgb[j];
+ }
+
+ /* Scale it back again to 0.0 to 1.0, */
+ /* which is compensated for by matrix scale. */
+ in *= calrgb[j];
+
+ if (in < 0.0)
+ in = 0.0;
+ else if (in > 1.0)
+ in = 1.0;
+ rgb[j] = in;
+//printf("Step %d, Chan %d, %f -> %f\n",ui,j,(double)ui / (wor->size - 1.0),in);
+ }
+ wor->data[ui] = rgb[0]; /* Curve values 0.0 - 1.0 */
+ wog->data[ui] = rgb[1];
+ wob->data[ui] = rgb[2];
+ }
+ }
+ }
+
+ /* Write the file (including all tags) out */
+ if ((rv = wr_icco->write(wr_icco,wr_fp,0)) != 0) {
+ error("Write file: %d, %s",rv,wr_icco->err);
+ }
+
+ if (verb)
+ printf("Created fast shaper/matrix profile '%s'\n",iccoutname);
+
+ /* Close the file */
+ wr_icco->del(wr_icco);
+ wr_fp->del(wr_fp);
+ }
+
+ if (verify != 2) {
+ for (j = 0; j < 3; j++)
+ x.rdac[j]->del(x.rdac[j]);
+
+ for (k = 0; k < 3; k++)
+ x.dcvs[k]->del(x.dcvs[k]);
+ }
+
+ if (x.svc != NULL) {
+ x.svc->del(x.svc);
+ x.svc = NULL;
+ }
+ if (x.dvc != NULL) {
+ x.dvc->del(x.svc);
+ x.dvc = NULL;
+ }
+
+ free_a_disppath(disp);
+
+ return 0;
+}
+
+
diff --git a/spectro/dispread.c b/spectro/dispread.c
new file mode 100644
index 0000000..4a8d364
--- /dev/null
+++ b/spectro/dispread.c
@@ -0,0 +1,969 @@
+/*
+ * Argyll Color Correction System
+ * DTP92/Spectrolino display target reader
+ *
+ * Author: Graeme W. Gill
+ * Date: 4/10/96
+ *
+ * Copyright 1996 - 2013 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/* This program displays test patches, and takes readings from a display device */
+
+/* TTBD
+
+ Add bell at end of readings ?
+
+ Ideally this should be changed to always create non-normalized (absolute)
+ readings, since normalised readings are not natural, but everything
+ that deals with .ti3 data then needs fixing to deal with non-normalized
+ readings !
+ */
+
+#undef DEBUG
+#undef DEBUG_OFFSET /* Keep test window out of the way */
+
+/* Invoke with -dfake for testing with a fake device */
+/* Will use fake.icm/.icc if present */
+
+#define COMPORT 1 /* Default com port 1..4 */
+
+#ifdef __MINGW32__
+# define WINVER 0x0500
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <math.h>
+#include <sys/types.h>
+#include <time.h>
+#include <string.h>
+#if defined (NT)
+#include <conio.h>
+#endif
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "xspect.h"
+#include "ccmx.h"
+#include "ccss.h"
+#include "cgats.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "inst.h"
+#include "dispwin.h"
+#include "dispsup.h"
+#include "sort.h"
+#include "instappsup.h"
+#include "spyd2setup.h" /* Enable Spyder 2 access */
+
+/* ------------------------------------------------------------------- */
+#if defined(__APPLE__) && defined(__POWERPC__)
+
+/* Workaround for a PPC gcc 3.3 optimiser bug... */
+/* It seems to cause a segmentation fault instead of */
+/* converting an integer loop index into a float, */
+/* when there are sufficient variables in play. */
+static int gcc_bug_fix(int i) {
+ static int nn;
+ nn += i;
+ return nn;
+}
+#endif /* APPLE */
+
+/* ------------------------------------------------------------------- */
+
+/*
+
+ Flags used:
+
+ ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ upper .. . .... .. .. ...
+ lower .. . . . . .. .
+
+*/
+
+void usage(char *diag, ...) {
+ int i;
+ disppath **dp;
+ icompaths *icmps;
+ inst2_capability cap2 = inst2_none;
+
+ fprintf(stderr,"Read a Display, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ if (setup_spyd2() == 2)
+ fprintf(stderr,"WARNING: This file contains a proprietary firmware image, and may not be freely distributed !\n");
+ if (diag != NULL) {
+ va_list args;
+ fprintf(stderr,"Diagnostic: ");
+ va_start(args, diag);
+ vfprintf(stderr, diag, args);
+ va_end(args);
+ fprintf(stderr,"\n");
+ }
+ fprintf(stderr,"usage: dispread [options] outfile\n");
+ fprintf(stderr," -v Verbose mode\n");
+#if defined(UNIX_X11)
+ fprintf(stderr," -display displayname Choose X11 display name\n");
+ fprintf(stderr," -d n[,m] Choose the display n from the following list (default 1)\n");
+ fprintf(stderr," Optionally choose different display m for VideoLUT access\n");
+#else
+ fprintf(stderr," -d n Choose the display from the following list (default 1)\n");
+#endif
+ dp = get_displays();
+ if (dp == NULL || dp[0] == NULL)
+ fprintf(stderr," ** No displays found **\n");
+ else {
+ int i;
+ for (i = 0; ; i++) {
+ if (dp[i] == NULL)
+ break;
+ fprintf(stderr," %d = '%s'\n",i+1,dp[i]->description);
+ }
+ }
+ free_disppaths(dp);
+ fprintf(stderr," -dweb[:port] Display via a web server at port (default 8080)\n");
+// fprintf(stderr," -d fake Use a fake display device for testing, fake%s if present\n",ICC_FILE_EXT);
+ fprintf(stderr," -c listno Set communication port from the following list (default %d)\n",COMPORT);
+ if ((icmps = new_icompaths(g_log)) != NULL) {
+ icompath **paths;
+ if ((paths = icmps->paths) != NULL) {
+ int i;
+ for (i = 0; ; i++) {
+ if (paths[i] == NULL)
+ break;
+ if (paths[i]->itype == instSpyder2 && setup_spyd2() == 0)
+ fprintf(stderr," %d = '%s' !! Disabled - no firmware !!\n",i+1,paths[i]->name);
+ else
+ fprintf(stderr," %d = '%s'\n",i+1,paths[i]->name);
+ }
+ } else
+ fprintf(stderr," ** No ports found **\n");
+ }
+ fprintf(stderr," -p Use telephoto mode (ie. for a projector) (if available)\n");
+ cap2 = inst_show_disptype_options(stderr, " -y ", icmps, 0);
+ fprintf(stderr," -k file.cal Load calibration file into display while reading\n");
+ fprintf(stderr," -K file.cal Apply calibration file to test values while reading\n");
+ fprintf(stderr," -s Save spectral information (default don't save)\n");
+ fprintf(stderr," -P ho,vo,ss[,vs] Position test window and scale it\n");
+ fprintf(stderr," ho,vi: 0.0 = left/top, 0.5 = center, 1.0 = right/bottom etc.\n");
+ fprintf(stderr," ss: 0.5 = half, 1.0 = normal, 2.0 = double etc.\n");
+ fprintf(stderr," -F Fill whole screen with black background\n");
+#if defined(UNIX_X11)
+ fprintf(stderr," -n Don't set override redirect on test window\n");
+#endif
+ fprintf(stderr," -J Run instrument calibration first (used rarely)\n");
+ fprintf(stderr," -N Disable initial calibration of instrument if possible\n");
+ fprintf(stderr," -H Use high resolution spectrum mode (if available)\n");
+// fprintf(stderr," -V Use adaptive measurement mode (if available)\n");
+ fprintf(stderr," -w Disable normalisation of white to Y = 100\n");
+ if (cap2 & inst2_ccmx)
+ fprintf(stderr," -X file.ccmx Apply Colorimeter Correction Matrix\n");
+ if (cap2 & inst2_ccss) {
+ fprintf(stderr," -X file.ccss Use Colorimeter Calibration Spectral Samples for calibration\n");
+ fprintf(stderr," -Q observ Choose CIE Observer for spectrometer or CCSS colorimeter data:\n");
+ fprintf(stderr," 1931_2 (def), 1964_10, S&B 1955_2, shaw, J&V 1978_2, 1964_10c\n");
+ }
+ fprintf(stderr," -I b|w Drift compensation, Black: -Ib, White: -Iw, Both: -Ibw\n");
+ fprintf(stderr," -Y A Use non-adaptive integration time mode (if available).\n");
+ fprintf(stderr," -C \"command\" Invoke shell \"command\" each time a color is set\n");
+ fprintf(stderr," -M \"command\" Invoke shell \"command\" each time a color is measured\n");
+ fprintf(stderr," -W n|h|x Override serial port flow control: n = none, h = HW, x = Xon/Xoff\n");
+ fprintf(stderr," -D [level] Print debug diagnostics to stderr\n");
+ fprintf(stderr," outfile Base name for input[ti1]/output[ti3] file\n");
+ if (icmps != NULL)
+ icmps->del(icmps);
+ exit(1);
+}
+
+int main(int argc, char *argv[]) {
+ int i,j;
+ int fa, nfa, mfa; /* current argument we're looking at */
+ disppath *disp = NULL; /* Display being used */
+ double hpatscale = 1.0, vpatscale = 1.0; /* scale factor for test patch size */
+ double ho = 0.0, vo = 0.0; /* Test window offsets, -1.0 to 1.0 */
+ int blackbg = 0; /* NZ if whole screen should be filled with black */
+ int verb = 0;
+ int debug = 0;
+ int fake = 0; /* Use the fake device for testing */
+ int override = 1; /* Override redirect on X11 */
+ int comport = COMPORT; /* COM port used */
+ icompaths *icmps = NULL;
+ icompath *ipath = NULL;
+ flow_control fc = fc_nc; /* Default flow control */
+ int docalib = 0; /* Do a calibration */
+ int highres = 0; /* Use high res mode if available */
+ int nadaptive = 0; /* Use non-adaptive mode if available */
+ int bdrift = 0; /* Flag, nz for black drift compensation */
+ int wdrift = 0; /* Flag, nz for white drift compensation */
+ int dtype = 0; /* Display type selection charater */
+ int tele = 0; /* NZ if telephoto mode */
+ int noautocal = 0; /* Disable auto calibration */
+ int nonorm = 0; /* Disable normalisation */
+ char ccxxname[MAXNAMEL+1] = "\000"; /* Colorimeter Correction Matrix name */
+ ccmx *cmx = NULL; /* Colorimeter Correction Matrix */
+ ccss *ccs = NULL; /* Colorimeter Calibration Spectral Samples */
+ int spec = 0; /* Don't save spectral information */
+ icxObserverType obType = icxOT_default;
+ int webdisp = 0; /* NZ for web display, == port number */
+ char *ccallout = NULL; /* Change color Shell callout */
+ char *mcallout = NULL; /* Measure color Shell callout */
+ char inname[MAXNAMEL+1] = "\000"; /* Input cgats file base name */
+ char outname[MAXNAMEL+1] = "\000"; /* Output cgats file base name */
+ char calname[MAXNAMEL+1] = "\000"; /* Calibration file name (if any) */
+ int softcal = 0; /* nz if cal applied to values rather than hardware */
+ double cal[3][MAX_CAL_ENT]; /* Display calibration */
+ int ncal = 256; /* number of cal entries used */
+ cgats *icg; /* input cgats structure */
+ cgats *ocg; /* output cgats structure */
+ time_t clk = time(0);
+ struct tm *tsp = localtime(&clk);
+ char *atm = asctime(tsp); /* Ascii time */
+ col *cols; /* Internal storage of all the patch colors */
+ int dim = 0; /* Dimensionality - 1, 3, or 4 */
+ int npat; /* Number of patches/colors */
+ int xpat = 0; /* Set to number of extra patches */
+ int wpat; /* Set to index of white patch */
+ int si; /* Sample id index */
+ int ti; /* Temp index */
+ int fi; /* Colorspace index */
+ int nsetel = 0;
+ cgats_set_elem *setel; /* Array of set value elements */
+ disprd *dr; /* Display patch read object */
+ int noramdac = 0; /* Will be set to nz if can't set ramdac */
+ int errc; /* Return value from new_disprd() */
+ int rv;
+
+ set_exe_path(argv[0]); /* Set global exe_path and error_program */
+ check_if_not_interactive();
+ setup_spyd2(); /* Load firware if available */
+
+#ifdef DEBUG_OFFSET
+ ho = 0.8;
+ vo = -0.8;
+#endif
+
+#if defined(DEBUG) || defined(DEBUG_OFFSET)
+ printf("!!!!!! Debug turned on !!!!!!\n");
+#endif
+
+ if (argc <= 1)
+ usage("Too few arguments");
+
+ if (ncal > MAX_CAL_ENT)
+ error("Internal, ncal = %d > MAX_CAL_ENT %d\n",ncal,MAX_CAL_ENT);
+
+ /* Process the arguments */
+ mfa = 1; /* Minimum final arguments */
+ for (fa = 1;fa < argc;fa++) {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1+mfa) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?' || argv[fa][1] == '-') {
+ usage("Usage requested");
+
+ } else if (argv[fa][1] == 'v') {
+ verb = 1;
+ g_log->verb = verb;
+
+ /* Display number */
+ } else if (argv[fa][1] == 'd') {
+ if (strncmp(na,"web",3) == 0
+ || strncmp(na,"WEB",3) == 0) {
+ webdisp = 8080;
+ if (na[3] == ':') {
+ webdisp = atoi(na+4);
+ if (webdisp == 0 || webdisp > 65535)
+ usage("Web port number must be in range 1..65535");
+ }
+ fa = nfa;
+ } else {
+#if defined(UNIX_X11)
+ int ix, iv;
+
+ if (strcmp(&argv[fa][2], "isplay") == 0 || strcmp(&argv[fa][2], "ISPLAY") == 0) {
+ if (++fa >= argc || argv[fa][0] == '-') usage("Parameter expected following -display");
+ setenv("DISPLAY", argv[fa], 1);
+ } else {
+ if (na == NULL) usage("Parameter expected following -d");
+ fa = nfa;
+ if (strcmp(na,"fake") == 0) {
+ fake = 1;
+ } else {
+ if (sscanf(na, "%d,%d",&ix,&iv) != 2) {
+ ix = atoi(na);
+ iv = 0;
+ }
+ if (disp != NULL)
+ free_a_disppath(disp);
+ if ((disp = get_a_display(ix-1)) == NULL)
+ usage("-d parameter %d out of range",ix);
+ if (iv > 0)
+ disp->rscreen = iv-1;
+ }
+ }
+#else
+ int ix;
+ if (na == NULL) usage("Parameter expected following -d");
+ fa = nfa;
+ if (strcmp(na,"fake") == 0) {
+ fake = 1;
+ } else {
+ ix = atoi(na);
+ if (disp != NULL)
+ free_a_disppath(disp);
+ if ((disp = get_a_display(ix-1)) == NULL)
+ usage("-d parameter %d out of range",ix);
+ }
+#endif
+ }
+#if defined(UNIX_X11)
+ } else if (argv[fa][1] == 'n') {
+ override = 0;
+#endif /* UNIX */
+
+ /* COM port */
+ } else if (argv[fa][1] == 'c') {
+ fa = nfa;
+ if (na == NULL) usage("Paramater expected following -c");
+ comport = atoi(na);
+ if (comport < 1 || comport > 50) usage("-c parameter %d out of range",comport);
+
+ /* Telephoto */
+ } else if (argv[fa][1] == 'p') {
+ tele = 1;
+
+ /* Display type */
+ } else if (argv[fa][1] == 'y') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -y");
+ dtype = na[0];
+
+ /* Calibration file */
+ } else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -k/-K");
+ strncpy(calname,na,MAXNAMEL); calname[MAXNAMEL] = '\000';
+ softcal = 0;
+ if (argv[fa][1] == 'K')
+ softcal = 1;
+ }
+
+ /* Save spectral data */
+ else if (argv[fa][1] == 's') {
+ spec = 1;
+
+ /* Test patch offset and size */
+ } else if (argv[fa][1] == 'P') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -P");
+ if (sscanf(na, " %lf,%lf,%lf,%lf ", &ho, &vo, &hpatscale, &vpatscale) == 4) {
+ ;
+ } else if (sscanf(na, " %lf,%lf,%lf ", &ho, &vo, &hpatscale) == 3) {
+ vpatscale = hpatscale;
+ } else {
+ usage("-P parameter '%s' not recognised",na);
+ }
+ if (ho < 0.0 || ho > 1.0
+ || vo < 0.0 || vo > 1.0
+ || hpatscale <= 0.0 || hpatscale > 50.0
+ || vpatscale <= 0.0 || vpatscale > 50.0)
+ usage("-P parameters %f %f %f %f out of range",ho,vo,hpatscale,vpatscale);
+ ho = 2.0 * ho - 1.0;
+ vo = 2.0 * vo - 1.0;
+
+ /* Black background */
+ } else if (argv[fa][1] == 'F') {
+ blackbg = 1;
+
+ /* Force calibration */
+ } else if (argv[fa][1] == 'J') {
+ docalib = 1;
+
+ /* No auto-cal */
+ } else if (argv[fa][1] == 'N') {
+ noautocal = 1;
+
+ /* High res mode */
+ } else if (argv[fa][1] == 'H') {
+ highres = 1;
+
+ /* Adaptive mode - default, so flag is deprecated */
+ } else if (argv[fa][1] == 'V') {
+ warning("dispread -V flag is deprecated");
+
+ /* No normalisation */
+ } else if (argv[fa][1] == 'w') {
+ nonorm = 1;
+
+ /* Colorimeter Correction Matrix */
+ /* or Colorimeter Calibration Spectral Samples */
+ } else if (argv[fa][1] == 'X') {
+ int ix;
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected following -X");
+ strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
+
+ } else if (argv[fa][1] == 'I') {
+ fa = nfa;
+ if (na == NULL || na[0] == '\000') usage("Parameter expected after -I");
+ for (i=0; ; i++) {
+ if (na[i] == '\000')
+ break;
+ if (na[i] == 'b' || na[i] == 'B')
+ bdrift = 1;
+ else if (na[i] == 'w' || na[i] == 'W')
+ wdrift = 1;
+ else
+ usage("-I parameter '%c' not recognised",na[i]);
+ }
+
+ /* Spectral Observer type */
+ } else if (argv[fa][1] == 'Q') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expecte after -Q");
+ if (strcmp(na, "1931_2") == 0) { /* Classic 2 degree */
+ obType = icxOT_CIE_1931_2;
+ } else if (strcmp(na, "1964_10") == 0) { /* Classic 10 degree */
+ obType = icxOT_CIE_1964_10;
+ } else if (strcmp(na, "1964_10c") == 0) { /* 10 degree corrected */
+ obType = icxOT_CIE_1964_10c;
+ } else if (strcmp(na, "1955_2") == 0) { /* Stiles and Burch 1955 2 degree */
+ obType = icxOT_Stiles_Burch_2;
+ } else if (strcmp(na, "1978_2") == 0) { /* Judd and Voss 1978 2 degree */
+ obType = icxOT_Judd_Voss_2;
+ } else if (strcmp(na, "shaw") == 0) { /* Shaw and Fairchilds 1997 2 degree */
+ obType = icxOT_Shaw_Fairchild_2;
+ } else
+ usage("-Q parameter '%s' not recognised",na);
+
+
+ /* Change color callout */
+ } else if (argv[fa][1] == 'C') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -C");
+ ccallout = na;
+
+ /* Measure color callout */
+ } else if (argv[fa][1] == 'M') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -M");
+ mcallout = na;
+
+ /* Serial port flow control */
+ } else if (argv[fa][1] == 'W') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -W");
+ if (na[0] == 'n' || na[0] == 'N')
+ fc = fc_none;
+ else if (na[0] == 'h' || na[0] == 'H')
+ fc = fc_Hardware;
+ else if (na[0] == 'x' || na[0] == 'X')
+ fc = fc_XonXOff;
+ else
+ usage("-W parameter '%s' not recognised",na);
+
+ } else if (argv[fa][1] == 'D') {
+ debug = 1;
+ if (na != NULL && na[0] >= '0' && na[0] <= '9') {
+ debug = atoi(na);
+ fa = nfa;
+ }
+ g_log->debug = debug;
+ callback_ddebug = 1; /* dispwin global */
+
+ /* Extra flags */
+ } else if (argv[fa][1] == 'Y') {
+ if (na == NULL)
+ usage("Flag '-Y' expects extra flag");
+
+ if (na[0] == 'A') {
+ nadaptive = 1;
+ } else {
+ usage("Flag '-Y %c' not recognised",na[0]);
+ }
+
+ } else
+ usage("Flag '-%c' not recognised",argv[fa][1]);
+ }
+ else
+ break;
+ }
+
+ /* No explicit display has been set */
+ if (!fake && disp == NULL) {
+ int ix = 0;
+#if defined(UNIX_X11)
+ char *dn, *pp;
+
+ if ((dn = getenv("DISPLAY")) != NULL) {
+ if ((pp = strrchr(dn, ':')) != NULL) {
+ if ((pp = strchr(pp, '.')) != NULL) {
+ if (pp[1] != '\000')
+ ix = atoi(pp+1);
+ }
+ }
+ }
+#endif
+ if ((disp = get_a_display(ix)) == NULL)
+ error("Unable to open the default display");
+ }
+
+ /* See if there is an environment variable ccxx */
+ if (ccxxname[0] == '\000') {
+ char *na;
+ if ((na = getenv("ARGYLL_COLMTER_CAL_SPEC_SET")) != NULL) {
+ strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
+
+ } else if ((na = getenv("ARGYLL_COLMTER_COR_MATRIX")) != NULL) {
+ strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
+ }
+ }
+
+ /* Load up CCMX or CCSS */
+ if (ccxxname[0] != '\000') {
+ if ((cmx = new_ccmx()) == NULL
+ || cmx->read_ccmx(cmx, ccxxname)) {
+ if (cmx != NULL) {
+ cmx->del(cmx);
+ cmx = NULL;
+ }
+
+ /* CCMX failed, try CCSS */
+ if ((ccs = new_ccss()) == NULL
+ || ccs->read_ccss(ccs, ccxxname)) {
+ if (ccs != NULL) {
+ ccs->del(ccs);
+ ccs = NULL;
+ error("Reading CCMX/CCSS File '%s' failed\n", ccxxname);
+ }
+ }
+ }
+ }
+
+ if (fake)
+ comport = -99;
+ if ((icmps = new_icompaths(g_log)) == NULL)
+ error("Finding instrument paths failed");
+ if ((ipath = icmps->get_path(icmps, comport)) == NULL)
+ error("No instrument at port %d",comport);
+
+ if (docalib) {
+ if ((rv = disprd_calibration(ipath, fc, dtype, 0, tele, nadaptive, noautocal, disp,
+ webdisp, blackbg, override,
+ 100.0 * hpatscale, 100.0 * vpatscale, ho, vo,
+ g_log)) != 0) {
+ error("docalibration failed with return value %d\n",rv);
+ }
+ }
+
+ /* Get the file name argument */
+ if (fa >= argc || argv[fa][0] == '-') usage("Filname parameter not found");
+ strncpy(inname,argv[fa++],MAXNAMEL-4); inname[MAXNAMEL-4] = '\000';
+ strcpy(outname,inname);
+ strcat(inname,".ti1");
+ strcat(outname,".ti3");
+
+ icg = new_cgats(); /* Create a CGATS structure */
+ icg->add_other(icg, "CTI1"); /* our special input type is Calibration Target Information 1 */
+
+ if (icg->read_name(icg, inname))
+ error("CGATS file read error : %s",icg->err);
+
+ if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0)
+ error ("Input file '%s' isn't a CTI1 format file",inname);
+ if (icg->ntables < 1) /* We don't use second table at the moment */
+ error ("Input file '%s' doesn't contain at least one table",inname);
+
+ if ((npat = icg->t[0].nsets) <= 0)
+ error ("Input file '%s' has no sets of data",inname);
+
+ /* Setup output cgats file */
+ ocg = new_cgats(); /* Create a CGATS structure */
+ ocg->add_other(ocg, "CTI3"); /* our special type is Calibration Target Information 3 */
+ ocg->add_table(ocg, tt_other, 0); /* Start the first table */
+
+ ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 3",NULL);
+ ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll dispread", NULL);
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+ ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","DISPLAY", NULL); /* What sort of device this is */
+
+ if ((ti = icg->find_kword(icg, 0, "SINGLE_DIM_STEPS")) >= 0)
+ ocg->add_kword(ocg, 0, "SINGLE_DIM_STEPS",icg->t[0].kdata[ti], NULL);
+
+ if ((ti = icg->find_kword(icg, 0, "COMP_GREY_STEPS")) >= 0)
+ ocg->add_kword(ocg, 0, "COMP_GREY_STEPS",icg->t[0].kdata[ti], NULL);
+
+ if ((ti = icg->find_kword(icg, 0, "MULTI_DIM_STEPS")) >= 0)
+ ocg->add_kword(ocg, 0, "MULTI_DIM_STEPS",icg->t[0].kdata[ti], NULL);
+
+ if ((ti = icg->find_kword(icg, 0, "FULL_SPREAD_PATCHES")) >= 0)
+ ocg->add_kword(ocg, 0, "FULL_SPREAD_PATCHES",icg->t[0].kdata[ti], NULL);
+
+ if (verb) {
+ printf("Number of patches = %d\n",npat);
+ }
+
+ /* Fields we want */
+ ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t);
+
+ if ((si = icg->find_field(icg, 0, "SAMPLE_ID")) < 0)
+ error ("Input file '%s' doesn't contain field SAMPLE_ID",inname);
+ if (icg->t[0].ftype[si] != nqcs_t)
+ error ("Input file %s' field SAMPLE_ID is wrong type",inname);
+
+ if ((cols = (col *)malloc(sizeof(col) * (npat+1))) == NULL)
+ error("Malloc failed!");
+
+ /* Figure out the color space */
+ if ((fi = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
+ error ("Input file '%s' doesn't contain keyword COLOR_REP",inname);
+ if (strcmp(icg->t[0].kdata[fi],"RGB") == 0) {
+ int ri, gi, bi;
+ dim = 3;
+ if ((ri = icg->find_field(icg, 0, "RGB_R")) < 0)
+ error ("Input file '%s' doesn't contain field RGB_R",inname);
+ if (icg->t[0].ftype[ri] != r_t)
+ error ("Input file '%s' field RGB_R is wrong type",inname);
+ if ((gi = icg->find_field(icg, 0, "RGB_G")) < 0)
+ error ("Input file '%s' doesn't contain field RGB_G",inname);
+ if (icg->t[0].ftype[gi] != r_t)
+ error ("Input file '%s' field RGB_G is wrong type",inname);
+ if ((bi = icg->find_field(icg, 0, "RGB_B")) < 0)
+ error ("Input file '%s' doesn't contain field RGB_B",inname);
+ if (icg->t[0].ftype[bi] != r_t)
+ error ("Input file '%s' field RGB_B is wrong type",inname);
+ ocg->add_field(ocg, 0, "RGB_R", r_t);
+ ocg->add_field(ocg, 0, "RGB_G", r_t);
+ ocg->add_field(ocg, 0, "RGB_B", r_t);
+ ocg->add_kword(ocg, 0, "COLOR_REP","RGB_XYZ", NULL);
+ ocg->add_field(ocg, 0, "XYZ_X", r_t);
+ ocg->add_field(ocg, 0, "XYZ_Y", r_t);
+ ocg->add_field(ocg, 0, "XYZ_Z", r_t);
+ for (i = 0; i < npat; i++) {
+ cols[i].id = ((char *)icg->t[0].fdata[i][si]);
+ cols[i].r = *((double *)icg->t[0].fdata[i][ri]) / 100.0;
+ cols[i].g = *((double *)icg->t[0].fdata[i][gi]) / 100.0;
+ cols[i].b = *((double *)icg->t[0].fdata[i][bi]) / 100.0;
+ cols[i].XYZ[0] = cols[i].XYZ[1] = cols[i].XYZ[2] = -1.0;
+ }
+ } else
+ error ("Input file '%s' keyword COLOR_REP has illegal value (RGB colorspace expected)",inname);
+
+ /* Check that there is a white patch, and if not, add one, */
+ /* so that we can normalize the values to white. */
+ for (wpat = 0; wpat < npat; wpat++) {
+ if (cols[wpat].r > 0.9999999 &&
+ cols[wpat].g > 0.9999999 &&
+ cols[wpat].b > 0.9999999) {
+ break;
+ }
+ }
+ if (wpat >= npat) { /* Create a white patch */
+ if (verb)
+ printf("Adding one white patch\n");
+ xpat = 1;
+ cols[wpat].r = cols[wpat].g = cols[wpat].b = 1.0;
+ }
+
+ /* Setup a display calibration set if we are given one */
+ /* (Should switch to xcal ?) */
+ if (calname[0] != '\000') {
+ cgats *ccg; /* calibration cgats structure */
+ int ii, ri, gi, bi;
+
+ ccg = new_cgats(); /* Create a CGATS structure */
+ ccg->add_other(ccg, "CAL"); /* our special calibration type */
+
+ if (ccg->read_name(ccg, calname))
+ error("CGATS calibration file read error %s on file '%s'",ccg->err,calname);
+
+ if (ccg->ntables == 0 || ccg->t[0].tt != tt_other || ccg->t[0].oi != 0)
+ error ("Calibration file isn't a CAL format file");
+ if (ccg->ntables < 1)
+ error ("Calibration file '%s' doesn't contain at least one table",calname);
+
+ if ((ncal = ccg->t[0].nsets) <= 0)
+ error ("No data in set of file '%s'",calname);
+
+ if (ncal != 256)
+ error ("Expect 256 data sets in file '%s'",calname);
+ if (ncal > MAX_CAL_ENT)
+ error ("Cant handle %d data sets in file '%s', max is %d",ncal,calname,MAX_CAL_ENT);
+
+ if ((fi = ccg->find_kword(ccg, 0, "DEVICE_CLASS")) < 0)
+ error ("Calibration file '%s' doesn't contain keyword DEVICE_CLASS",calname);
+ if (strcmp(ccg->t[0].kdata[fi],"DISPLAY") != 0)
+ error ("Calibration file '%s' doesn't have DEVICE_CLASS of DISPLAY",calname);
+
+ if ((fi = ccg->find_kword(ccg, 0, "VIDEO_LUT_CALIBRATION_POSSIBLE")) >= 0) {
+ if (stricmp(ccg->t[0].kdata[fi],"NO") == 0) {
+ softcal = 1;
+ if (verb) printf("Switching to soft cal because there is no access to VideoLUTs\n");
+ }
+ }
+
+ if ((fi = ccg->find_kword(ccg, 0, "COLOR_REP")) < 0)
+ error ("Calibration file '%s' doesn't contain keyword COLOR_REP",calname);
+ if (strcmp(ccg->t[0].kdata[fi],"RGB") != 0)
+ error ("Calibration file '%s' doesn't have COLOR_REP of RGB",calname);
+
+ if ((ii = ccg->find_field(ccg, 0, "RGB_I")) < 0)
+ error ("Calibration file '%s' doesn't contain field RGB_I",calname);
+ if (ccg->t[0].ftype[ii] != r_t)
+ error ("Field RGB_R in file '%s' is wrong type",calname);
+ if ((ri = ccg->find_field(ccg, 0, "RGB_R")) < 0)
+ error ("Calibration file '%s' doesn't contain field RGB_R",calname);
+ if (ccg->t[0].ftype[ri] != r_t)
+ error ("Field RGB_R in file '%s' is wrong type",calname);
+ if ((gi = ccg->find_field(ccg, 0, "RGB_G")) < 0)
+ error ("Calibration file '%s' doesn't contain field RGB_G",calname);
+ if (ccg->t[0].ftype[gi] != r_t)
+ error ("Field RGB_G in file '%s' is wrong type",calname);
+ if ((bi = ccg->find_field(ccg, 0, "RGB_B")) < 0)
+ error ("Calibration file '%s' doesn't contain field RGB_B",calname);
+ if (ccg->t[0].ftype[bi] != r_t)
+ error ("Field RGB_B in file '%s' is wrong type",calname);
+ for (i = 0; i < ncal; i++) {
+ cal[0][i] = *((double *)ccg->t[0].fdata[i][ri]);
+ cal[1][i] = *((double *)ccg->t[0].fdata[i][gi]);
+ cal[2][i] = *((double *)ccg->t[0].fdata[i][bi]);
+ }
+ ccg->del(ccg);
+ } else {
+ cal[0][0] = -1.0; /* Not used */
+ }
+
+ if ((dr = new_disprd(&errc, ipath, fc, dtype, 0, tele, nadaptive, noautocal,
+ highres, 0, &noramdac, cal, ncal, softcal, disp, blackbg, override,
+ webdisp, ccallout, mcallout, 100.0 * hpatscale, 100.0 * vpatscale, ho, vo,
+ cmx != NULL ? cmx->matrix : NULL,
+ ccs != NULL ? ccs->samples : NULL, ccs != NULL ? ccs->no_samp : 0,
+ spec, obType, NULL, bdrift, wdrift,
+ "fake" ICC_FILE_EXT, g_log)) == NULL)
+ error("new_disprd failed with '%s'\n",disprd_err(errc));
+
+ if (cmx != NULL)
+ cmx->del(cmx);
+ if (ccs != NULL)
+ ccs->del(ccs);
+
+ /* Test the CRT with all of the test points */
+ if ((rv = dr->read(dr, cols, npat + xpat, 1, npat + xpat, 1, 0, instClamp)) != 0) {
+ dr->del(dr);
+ error("test_crt returned error code %d\n",rv);
+ }
+ /* Note what instrument the chart was read with */
+ if (dr->it != NULL) {
+ int refrmode, cbid;
+ ocg->add_kword(ocg, 0, "TARGET_INSTRUMENT", inst_name(dr->it->get_itype(dr->it)) , NULL);
+ dr->get_disptype(dr, &refrmode, &cbid);
+ if (refrmode >= 0)
+ ocg->add_kword(ocg, 0, "DISPLAY_TYPE_REFRESH", refrmode ? "YES" : "NO", NULL);
+ if (cbid != 0) {
+ char buf[100];
+ sprintf(buf, "%d", cbid);
+ ocg->add_kword(ocg, 0, "DISPLAY_TYPE_BASE_ID", buf, NULL);
+ }
+ } else {
+ ocg->add_kword(ocg, 0, "TARGET_INSTRUMENT", "Fake" , NULL);
+ }
+
+ /* Note if the instrument is natively spectral or not */
+ if (dr->it != NULL) {
+ inst_mode cap;
+ dr->it->capabilities(dr->it, &cap, NULL, NULL);
+
+ if (dr->it != NULL && cap & inst_mode_spectral)
+ ocg->add_kword(ocg, 0, "INSTRUMENT_TYPE_SPECTRAL", "YES" , NULL);
+ else
+ ocg->add_kword(ocg, 0, "INSTRUMENT_TYPE_SPECTRAL", "NO" , NULL);
+ } else {
+ ocg->add_kword(ocg, 0, "INSTRUMENT_TYPE_SPECTRAL", "NO" , NULL);
+ }
+
+ dr->del(dr);
+
+ /* And save the result: */
+
+ /* Convert from absolute XYZ to relative XYZ */
+ if (npat > 0) {
+ double nn;
+
+ /* Make sure there is a copy of the white patch beyond npat, */
+ /* so that it can be left absolute. */
+ if (wpat != npat) {
+ cols[npat].r = cols[npat].g = cols[npat].b = 1.0;
+ cols[npat].XYZ[0] = cols[wpat].XYZ[0];
+ cols[npat].XYZ[1] = cols[wpat].XYZ[1];
+ cols[npat].XYZ[2] = cols[wpat].XYZ[2];
+ cols[npat].XYZ_v = cols[wpat].XYZ_v;
+ wpat = npat;
+ }
+
+ if (nonorm)
+ nn = 1.0;
+ else {
+ if (cols[wpat].XYZ_v == 0)
+ error("XYZ of white patch is not valid!",i);
+
+ nn = 100.0 / cols[wpat].XYZ[1]; /* Normalise Y of white to 100 */
+ }
+
+ for (i = 0; i < npat; i++) {
+
+ if (cols[i].XYZ_v == 0)
+ error("XYZ %d is not valid!",i);
+
+ for (j = 0; j < 3; j++)
+ cols[i].XYZ[j] = nn * cols[i].XYZ[j];
+
+ /* Keep spectral aligned with normalised XYZ */
+ if (cols[i].sp.spec_n > 0) {
+ for (j = 0; j < cols[i].sp.spec_n; j++)
+ cols[i].sp.spec[j] *= nn;
+ }
+ }
+ }
+
+ nsetel += 1; /* For id */
+ nsetel += dim; /* For device values */
+ nsetel += 3; /* For XYZ */
+
+ /* If we have spectral information, output it too */
+ if (npat > 0 && cols[0].sp.spec_n > 0) {
+ char buf[100];
+
+ nsetel += cols[0].sp.spec_n; /* Spectral values */
+ sprintf(buf,"%d", cols[0].sp.spec_n);
+ ocg->add_kword(ocg, 0, "SPECTRAL_BANDS",buf, NULL);
+ sprintf(buf,"%f", cols[0].sp.spec_wl_short);
+ ocg->add_kword(ocg, 0, "SPECTRAL_START_NM",buf, NULL);
+ sprintf(buf,"%f", cols[0].sp.spec_wl_long);
+ ocg->add_kword(ocg, 0, "SPECTRAL_END_NM",buf, NULL);
+
+ /* Generate fields for spectral values */
+ for (i = 0; i < cols[0].sp.spec_n; i++) {
+ int nm;
+
+ /* Compute nearest integer wavelength */
+ nm = (int)(cols[0].sp.spec_wl_short + ((double)i/(cols[0].sp.spec_n-1.0))
+ * (cols[0].sp.spec_wl_long - cols[0].sp.spec_wl_short) + 0.5);
+
+ sprintf(buf,"SPEC_%03d",nm);
+ ocg->add_field(ocg, 0, buf, r_t);
+ }
+ }
+
+ if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL)
+ error("Malloc failed!");
+
+ /* Write out the patch info to the output CGATS file */
+ for (i = 0; i < npat; i++) {
+ int k = 0;
+
+ setel[k++].c = cols[i].id;
+ setel[k++].d = 100.0 * cols[i].r;
+ setel[k++].d = 100.0 * cols[i].g;
+ setel[k++].d = 100.0 * cols[i].b;
+
+ setel[k++].d = cols[i].XYZ[0];
+ setel[k++].d = cols[i].XYZ[1];
+ setel[k++].d = cols[i].XYZ[2];
+
+ for (j = 0; j < cols[i].sp.spec_n; j++) {
+ setel[k++].d = cols[i].sp.spec[j];
+ }
+
+ ocg->add_setarr(ocg, 0, setel);
+ }
+
+ free(setel);
+
+ /* If we have the absolute brightness of the display, record it */
+ if (cols[wpat].XYZ_v != 0) {
+ char buf[100];
+
+ sprintf(buf,"%f %f %f", cols[wpat].XYZ[0], cols[wpat].XYZ[1], cols[wpat].XYZ[2]);
+ ocg->add_kword(ocg, 0, "LUMINANCE_XYZ_CDM2",buf, NULL);
+ }
+
+ if (nonorm == 0)
+ ocg->add_kword(ocg, 0, "NORMALIZED_TO_Y_100","YES", NULL);
+ else
+ ocg->add_kword(ocg, 0, "NORMALIZED_TO_Y_100","NO", NULL);
+
+ /* Write out the calibration if we have it */
+ if (cal != NULL && cal[0][0] >= 0.0) {
+ ocg->add_other(ocg, "CAL"); /* our special type is Calibration file */
+ ocg->add_table(ocg, tt_other, 1); /* Add another table for RAMDAC values */
+ ocg->add_kword(ocg, 1, "DESCRIPTOR", "Argyll Device Calibration State",NULL);
+ ocg->add_kword(ocg, 1, "ORIGINATOR", "Argyll dispread", NULL);
+ ocg->add_kword(ocg, 1, "CREATED",atm, NULL);
+
+ ocg->add_kword(ocg, 1, "DEVICE_CLASS","DISPLAY", NULL);
+ ocg->add_kword(ocg, 1, "COLOR_REP","RGB", NULL);
+ ocg->add_kword(ocg, 0, "VIDEO_LUT_CALIBRATION_POSSIBLE",noramdac ? "NO" : "YES", NULL);
+
+ ocg->add_field(ocg, 1, "RGB_I", r_t);
+ ocg->add_field(ocg, 1, "RGB_R", r_t);
+ ocg->add_field(ocg, 1, "RGB_G", r_t);
+ ocg->add_field(ocg, 1, "RGB_B", r_t);
+
+ if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * 4)) == NULL)
+ error("Malloc failed!");
+
+ for (i = 0; i < ncal; i++) {
+ double vv;
+
+#if defined(__APPLE__) && defined(__POWERPC__)
+ gcc_bug_fix(i);
+#endif
+ vv = i/(ncal-1.0);
+
+ setel[0].d = vv;
+ setel[1].d = cal[0][i];
+ setel[2].d = cal[1][i];
+ setel[3].d = cal[2][i];
+
+ ocg->add_setarr(ocg, 1, setel);
+ }
+
+ free(setel);
+ }
+
+ if (ocg->write_name(ocg, outname))
+ error("Write error : %s",ocg->err);
+
+ if (verb)
+ printf("Written '%s'\n",outname);
+
+ icmps->del(icmps);
+ free(cols);
+ ocg->del(ocg); /* Clean up */
+ icg->del(icg); /* Clean up */
+ free_a_disppath(disp);
+
+ return 0;
+}
+
+
diff --git a/spectro/dispsup.c b/spectro/dispsup.c
new file mode 100644
index 0000000..0dc29ad
--- /dev/null
+++ b/spectro/dispsup.c
@@ -0,0 +1,2343 @@
+
+
+/*
+ * Argyll Color Correction System
+ * Common display patch reading support.
+ *
+ * Author: Graeme W. Gill
+ * Date: 2/11/2005
+ *
+ * Copyright 1998 - 2013 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/*
+ TTBD:
+
+ If verb, report white/black drift
+
+ Should add option to ask for a black cal every N readings, for spectro's
+ */
+
+#ifdef __MINGW32__
+# define WINVER 0x0500
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/types.h>
+#include <time.h>
+#include <string.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "inst.h"
+#include "spyd2.h"
+#include "dispwin.h"
+#include "dispsup.h"
+#include "webwin.h"
+#include "instappsup.h"
+
+#undef SIMPLE_MODEL /* Make fake device well behaved */
+ /* else has offsets, quantization, noise etc. */
+
+#define DRIFT_IPERIOD 40 /* Number of samples between drift interpolation measurements */
+#define DRIFT_EPERIOD 20 /* Number of samples between drift extrapolation measurements */
+//#define DRIFT_IPERIOD 6 /* Test values */
+//#define DRIFT_EPERIOD 3
+
+#ifdef SIMPLE_MODEL
+# undef FAKE_NOISE /* Add noise to _fake_ devices XYZ */
+# undef FAKE_BITS /* Number of bits of significance of fake device */
+#else
+# define FAKE_NOISE 0.01 /* Add noise to _fake_ devices XYZ */
+# define FAKE_BITS 9 /* Number of bits of significance of fake device */
+#endif
+
+
+/* -------------------------------------------------------- */
+/* A default callback that can be provided as an argument to */
+/* inst_handle_calibrate() to handle the display part of a */
+/* calibration callback. */
+/* Call this again with calc = inst_calc_none to cleanup afterwards. */
+inst_code setup_display_calibrate(
+ inst *p,
+ inst_cal_cond calc, /* Current condition. inst_calc_none for not setup */
+ disp_win_info *dwi /* Information to be able to open a display test patch */
+) {
+ inst_code rv = inst_ok, ev;
+ dispwin *dw; /* Display window to display test patches on, NULL if none. */
+ a1logd(p->log,1,"setup_display_calibrate called\n");
+
+ switch (calc) {
+ case inst_calc_none: /* Use this as a cleanup flag */
+ if (dwi->dw == NULL && dwi->_dw != NULL) {
+ dwi->_dw->del(dwi->_dw);
+ dwi->_dw = NULL;
+ }
+ break;
+
+ case inst_calc_emis_white:
+ if (dwi->dw == NULL) {
+ if (dwi->webdisp != 0) {
+ if ((dwi->_dw = new_webwin(dwi->webdisp, dwi->hpatsize, dwi->vpatsize,
+ dwi->ho, dwi->vo, 0, dwi->blackbg,
+ p->log->verb, p->log->debug)) == NULL) {
+ a1logd(p->log,1,"inst_handle_calibrate failed to create test window 0x%x\n",inst_other_error);
+ return inst_other_error;
+ }
+ } else {
+ if ((dwi->_dw = new_dispwin(dwi->disp, dwi->hpatsize, dwi->vpatsize,
+ dwi->ho, dwi->vo, 0, 0, NULL, dwi->blackbg,
+ dwi->override, p->log->debug)) == NULL) {
+ a1logd(p->log,1,"inst_handle_calibrate failed to create test window 0x%x\n",inst_other_error);
+ return inst_other_error;
+ }
+ }
+ printf("Frequency calibration, Place instrument on test window.\n");
+ printf(" Hit any key to continue,\n");
+ printf(" or hit Esc or Q to abort:");
+ } else {
+ dwi->_dw = dwi->dw;
+ }
+ p->cal_gy_level = 1.0;
+ dwi->_dw->set_color(dwi->_dw, 1.0, 1.0, 1.0);
+ break;
+
+ case inst_calc_emis_grey:
+ case inst_calc_emis_grey_darker:
+ case inst_calc_emis_grey_ligher:
+ if (dwi->dw == NULL) {
+ if (dwi->webdisp != 0) {
+ if ((dwi->_dw = new_webwin(dwi->webdisp, dwi->hpatsize, dwi->vpatsize,
+ dwi->ho, dwi->vo, 0, dwi->blackbg,
+ p->log->verb, p->log->debug)) == NULL) {
+ a1logd(p->log,1,"inst_handle_calibrate failed to create test window 0x%x\n",inst_other_error);
+ return inst_other_error;
+ }
+ } else {
+ if ((dwi->_dw = new_dispwin(dwi->disp, dwi->hpatsize, dwi->vpatsize,
+ dwi->ho, dwi->vo, 0, 0, NULL, dwi->blackbg,
+ dwi->override, p->log->debug)) == NULL) {
+ a1logd(p->log,1,"inst_handle_calibrate failed to create test window 0x%x\n",inst_other_error);
+ return inst_other_error;
+ }
+ }
+ printf("Cell ratio calibration, Place instrument on test window.\n");
+ printf(" Hit any key to continue,\n");
+ printf(" or hit Esc or Q to abort:");
+ } else {
+ dwi->_dw = dwi->dw;
+ }
+ if (calc == inst_calc_emis_grey) {
+ p->cal_gy_level = 0.6;
+ p->cal_gy_count = 0;
+ } else if (calc == inst_calc_emis_grey_darker) {
+ p->cal_gy_level *= 0.7;
+ p->cal_gy_count++;
+ } else if (calc == inst_calc_emis_grey_ligher) {
+ p->cal_gy_level *= 1.4;
+ if (p->cal_gy_level > 1.0)
+ p->cal_gy_level = 1.0;
+ p->cal_gy_count++;
+ }
+ if (p->cal_gy_count > 4) {
+ printf("Cell ratio calibration failed - too many tries at setting grey level.\n");
+ a1logd(p->log,1,"inst_handle_calibrate too many tries at setting grey level 0x%x\n",inst_internal_error);
+ return inst_internal_error;
+ }
+ dwi->_dw->set_color(dwi->_dw, p->cal_gy_level, p->cal_gy_level, p->cal_gy_level);
+ break;
+
+ default:
+ /* Something isn't being handled */
+ a1logd(p->log,1,"inst_handle_calibrate unhandled calc case 0x%x, err 0x%x\n",calc,inst_internal_error);
+ return inst_internal_error;
+ }
+ return inst_ok;
+}
+
+/* -------------------------------------------------------- */
+/* User requested calibration of the display instrument */
+/* Do all available non-deferrable calibrations */
+/* Should add an argument to be able to select type of calibration, */
+/* rather than guessing what the user wants ? */
+int disprd_calibration(
+icompath *ipath, /* Instrument path to open, &icomFakeDevice == fake */
+flow_control fc, /* Serial flow control */
+int dtype, /* Display type selection character */
+int docbid, /* NZ to only allow cbid dtypes */
+int tele, /* NZ for tele mode, falls back to spot mode */
+int nadaptive, /* NZ for non-adaptive mode */
+int noinitcal, /* NZ to disable initial instrument calibration */
+disppath *disp, /* display to calibrate. */
+int webdisp, /* If nz, port number for web display */
+int blackbg, /* NZ if whole screen should be filled with black */
+int override, /* Override_redirect on X11 */
+double hpatsize, /* Size of dispwin */
+double vpatsize,
+double ho, double vo, /* Position of dispwin */
+a1log *log /* Verb, debug & error log */
+) {
+ instType itype = instUnknown;
+ inst *p = NULL;
+ int c;
+ inst_code rv;
+ baud_rate br = baud_19200;
+ disp_win_info dwi;
+ inst_cal_type calt = inst_calt_needed;
+ inst_mode cap;
+ inst2_capability cap2;
+ inst3_capability cap3;
+ inst_mode mode = 0;
+
+ memset((void *)&dwi, 0, sizeof(disp_win_info));
+ dwi.webdisp = webdisp;
+ dwi.disp = disp;
+ dwi.blackbg = blackbg;
+ dwi.override = override;
+ dwi.hpatsize = hpatsize;
+ dwi.vpatsize = vpatsize;
+ dwi.ho = ho;
+ dwi.vo = vo;
+
+ p->log = new_a1log_d(log);
+
+ a1logv(log, 1, "Setting up the instrument\n");
+
+ if ((p = new_inst(ipath, 0, log, DUIH_FUNC_AND_CONTEXT)) == NULL) {
+ a1logd(log, 1, "new_inst failed\n");
+ return -1;
+ }
+
+ /* Establish communications */
+ if ((rv = p->init_coms(p, br, fc, 15.0)) != inst_ok) {
+ a1logd(p->log, 1, "init_coms returned '%s' (%s)\n",
+ p->inst_interp_error(p, rv), p->interp_error(p, rv));
+ p->del(p);
+ return -1;
+ }
+
+ /* Initialise the instrument */
+ if ((rv = p->init_inst(p)) != inst_ok) {
+ a1logd(log,1,"init_inst returned '%s' (%s)\n",
+ p->inst_interp_error(p, rv), p->interp_error(p, rv));
+ p->del(p);
+ return -1;
+ }
+
+ itype = p->get_itype(p); /* Actual type */
+ p->capabilities(p, &cap, &cap2, &cap3);
+
+ if (tele && !IMODETST(cap, inst_mode_emis_tele)) {
+ printf("Want telephoto measurement capability but instrument doesn't support it\n");
+ printf("so falling back to emissive spot mode.\n");
+ tele = 0;
+ }
+
+ /* Set to emission mode to read a display */
+ if (tele)
+ mode = inst_mode_emis_tele;
+ else
+ mode = inst_mode_emis_spot;
+ if (nadaptive)
+ mode |= inst_mode_emis_nonadaptive;
+
+ /* (We're assuming spectral doesn't affect calibration ?) */
+
+ if ((rv = p->set_mode(p, mode)) != inst_ok) {
+ a1logd(log,1,"Set_mode failed with '%s' (%s)\n",
+ p->inst_interp_error(p, rv), p->interp_error(p, rv));
+ return -1;
+ }
+ p->capabilities(p, &cap, &cap2, &cap3);
+
+ /* Set the display type */
+ if (dtype != 0) { /* Given selection character */
+ if (cap2 & inst2_disptype) {
+ int ix;
+ if ((ix = inst_get_disptype_index(p, dtype, docbid)) < 0) {
+ a1logd(log,1,"Display type selection '%c' is not valid for instrument\n",dtype);
+ p->del(p);
+ return -1;
+ }
+
+ if ((rv = p->set_disptype(p, ix)) != inst_ok) {
+ a1logd(log,1,"Setting display type failed failed with '%s' (%s)\n",
+ p->inst_interp_error(p, rv), p->interp_error(p, rv));
+ p->del(p);
+ return -1;
+ }
+ } else
+ printf("Display type ignored - instrument doesn't support display type\n");
+ }
+
+ /* Disable initial calibration of machine if selected */
+ if (noinitcal != 0) {
+ if ((rv = p->get_set_opt(p,inst_opt_noinitcalib, 0)) != inst_ok) {
+ a1logd(log,1,"Setting no-initail calibrate failed with '%s' (%s)\n",
+ p->inst_interp_error(p, rv), p->interp_error(p, rv));
+ printf("Disable initial-calibrate not supported\n");
+ }
+ }
+
+ /* Do the calibration */
+ rv = inst_handle_calibrate(p, inst_calt_available, inst_calc_none, setup_display_calibrate, &dwi);
+ setup_display_calibrate(p,inst_calc_none, &dwi);
+ if (rv == inst_unsupported) {
+ printf("No calibration available for instrument in this mode\n");
+ } else if (rv != inst_ok) { /* Abort or fatal error */
+ printf("Calibrate failed with '%s' (%s)\n",
+ p->inst_interp_error(p, rv), p->interp_error(p, rv));
+ p->del(p);
+ return -1;
+ }
+
+ /* clean up */
+ p->del(p);
+ a1logv(log, 1, "Finished setting up the instrument\n");
+
+ return 0;
+}
+
+/* Take a series of readings from the display - implementation */
+/* Return nz on fail/abort */
+/* 1 = user aborted */
+/* 2 = instrument access failed */
+/* 3 = window access failed */
+/* 4 = user hit terminate key */
+/* 5 = system error */
+/* Use disprd_err() to interpret it */
+static int disprd_read_imp(
+ disprd *p,
+ col *cols, /* Array of patch colors to be tested */
+ int npat, /* Number of patches to be tested */
+ int spat, /* Start patch index for "verb", 0 if not used */
+ int tpat, /* Total patch index for "verb", 0 if not used */
+ int acr, /* If nz, do automatic final carriage return */
+ int noinc, /* If nz, don't increment the count */
+ int tc, /* If nz, termination key */
+ instClamping clamp /* NZ if clamp XYZ/Lab to be +ve */
+) {
+ int j, rv;
+ int patch;
+ ipatch val; /* Return value */
+ int ch; /* Character */
+ int cal_type;
+ char id[CALIDLEN];
+ inst2_capability cap2;
+
+ /* Setup the keyboard trigger to return our commands */
+ inst_set_uih(0x0, 0xff, DUIH_TRIG);
+ inst_set_uih('q', 'q', DUIH_ABORT);
+ inst_set_uih('Q', 'Q', DUIH_ABORT);
+ inst_set_uih(0x03, 0x03, DUIH_ABORT); /* ^c */
+ inst_set_uih(0x1b, 0x1b, DUIH_ABORT); /* Esc */
+
+ /* Setup user termination character */
+ inst_set_uih(tc, tc, DUIH_TERM);
+
+ p->it->capabilities(p->it, NULL, &cap2, NULL);
+
+ /* See if we should calibrate the display update */
+ if (!p->update_delay_set && (cap2 & inst2_meas_disp_update) != 0) {
+ int cdelay, mdelay;
+
+ /* Set white with a normal delay */
+ if ((rv = p->dw->set_color(p->dw, 1.0, 1.0, 1.0)) != 0) {
+ a1logd(p->log,1,"set_color() returned %s\n",rv);
+ return 3;
+ }
+
+ /* Set a zero delay */
+ cdelay = p->dw->set_update_delay(p->dw, 0);
+
+ /* Set black with zero delay */
+ if ((rv = p->dw->set_color(p->dw, 0.0, 0.0, 0.0)) != 0) {
+ a1logd(p->log,1,"set_color() returned %s\n",rv);
+ return 3;
+ }
+
+ /* Measure the delay */
+ if ((rv = p->it->meas_delay(p->it, &mdelay)) != inst_ok) {
+ a1logd(p->log,1,"warning, measure display update delay failed with '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ p->dw->set_update_delay(p->dw, cdelay); /* Restore the default */
+ } else {
+ a1logv(p->log, 1, "Measured display update delay of %d msec",mdelay);
+ mdelay += mdelay/2 + 50;
+ p->dw->set_update_delay(p->dw, mdelay);
+ a1logv(p->log, 1, ", using delay of %d msec\n",mdelay);
+ }
+ p->update_delay_set = 1;
+ }
+
+ /* See if we should do a frequency calibration or display integration time cal. first */
+ if (p->it->needs_calibration(p->it) & inst_calt_ref_freq
+ && npat > 0
+ && (cols[0].r != 1.0 || cols[0].g != 1.0 || cols[0].b != 1.0)) {
+ col tc;
+ inst_cal_type calt = inst_calt_ref_freq;
+ inst_cal_cond calc = inst_calc_emis_white;
+
+ if ((rv = p->dw->set_color(p->dw, 1.0, 1.0, 1.0)) != 0) {
+ a1logd(p->log,1,"set_color() returned %s\n",rv);
+ return 3;
+ }
+ /* Do calibrate, but ignore return code. Press on regardless. */
+ if ((rv = p->it->calibrate(p->it, &calt, &calc, id)) != inst_ok) {
+ a1logd(p->log,1,"warning, frequency calibrate failed with '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ }
+ }
+
+ /* See if we should do a display integration calibration first */
+ if (p->it->needs_calibration(p->it) & inst_calt_emis_int_time) {
+ col tc;
+ inst_cal_type calt = inst_calt_emis_int_time;
+ inst_cal_cond calc = inst_calc_emis_white;
+
+ if ((rv = p->dw->set_color(p->dw, 1.0, 1.0, 1.0)) != 0) {
+ a1logd(p->log,1,"set_color() returned %s\n",rv);
+ return 3;
+ }
+ /* Do calibrate, but ignore return code. Press on regardless. */
+ if ((rv = p->it->calibrate(p->it, &calt, &calc, id)) != inst_ok) {
+ a1logd(p->log,1,"warning, display integration calibrate failed with '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ }
+ }
+
+ for (patch = 0; patch < npat; patch++) {
+ col *scb = &cols[patch];
+ double rgb[3];
+
+ scb->mtype = inst_mrt_none;
+ scb->XYZ_v = 0; /* No readings are valid */
+ scb->sp.spec_n = 0;
+ scb->duration = 0.0;
+
+ if (spat != 0 && tpat != 0)
+ a1logv(p->log, 1, "%cpatch %d of %d",cr_char,spat + (noinc != 0 ? 0 : patch),tpat);
+ a1logd(p->log,1,"About to read patch %d\n",patch);
+
+ rgb[0] = scb->r;
+ rgb[1] = scb->g;
+ rgb[2] = scb->b;
+
+ /* If we are doing a soft cal, apply it to the test color */
+ if (p->softcal && p->cal[0][0] >= 0.0) {
+ int j;
+ double inputEnt_1 = (double)(p->ncal-1);
+
+ for (j = 0; j < 3; j++) {
+ unsigned int ix;
+ double val, w;
+ val = rgb[j] * inputEnt_1;
+ if (val < 0.0) {
+ val = 0.0;
+ } else if (val > inputEnt_1) {
+ val = inputEnt_1;
+ }
+ ix = (unsigned int)floor(val); /* Coordinate */
+ if (ix > (p->ncal-2))
+ ix = (p->ncal-2);
+ w = val - (double)ix; /* weight */
+ val = p->cal[j][ix];
+ rgb[j] = val + w * (p->cal[j][ix+1] - val);
+ }
+ }
+ if ((rv = p->dw->set_color(p->dw, rgb[0], rgb[1], rgb[2])) != 0) {
+ a1logd(p->log,1,"set_color() returned %s\n",rv);
+ return 3;
+ }
+
+ /* Until we give up retrying */
+ for (;;) {
+ val.mtype = inst_mrt_none;
+ val.XYZ_v = 0; /* No readings are valid */
+ val.sp.spec_n = 0;
+ val.duration = 0.0;
+
+ if ((rv = p->it->read_sample(p->it, scb->id, &val, 1)) != inst_ok
+ && (rv & inst_mask) != inst_user_trig) {
+ a1logd(p->log,1,"read_sample returned '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+
+ /* deal with a user terminate or abort */
+ if ((rv & inst_mask) == inst_user_abort) {
+ int keyc = inst_get_uih_char();
+
+ /* Deal with a user terminate */
+ if (keyc & DUIH_TERM) {
+ return 4;
+
+ /* Deal with a user abort */
+ } else if (keyc & DUIH_ABORT) {
+ empty_con_chars();
+ printf("\nSample read stopped at user request!\n");
+ printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ return 1;
+ }
+ printf("\n");
+ continue;
+ }
+
+ /* Deal with needing calibration */
+ } else if ((rv & inst_mask) == inst_needs_cal) {
+ disp_win_info dwi;
+ dwi.dw = p->dw; /* Set window to use */
+ printf("\nSample read failed because instruments needs calibration\n");
+ rv = inst_handle_calibrate(p->it, inst_calt_needed, inst_calc_none,
+ setup_display_calibrate, &dwi);
+ setup_display_calibrate(p->it, inst_calc_none, &dwi);
+ if (rv != inst_ok) { /* Abort or fatal error */
+ return 1;
+ }
+ continue;
+
+ /* Deal with a bad sensor position */
+ } else if ((rv & inst_mask) == inst_wrong_config) {
+ empty_con_chars();
+ printf("\n\nSpot read failed due to the sensor being in the wrong position\n");
+ printf("(%s)\n",p->it->interp_error(p->it, rv));
+ printf("Correct position then hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ return 1;
+ }
+ printf("\n");
+ continue;
+
+ /* Deal with a misread */
+ } else if ((rv & inst_mask) == inst_misread) {
+ empty_con_chars();
+ printf("\nSample read failed due to misread\n");
+ printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ return 1;
+ }
+ printf("\n");
+ continue;
+
+ /* Deal with a communications error */
+ } else if ((rv & inst_mask) == inst_coms_fail) {
+ empty_con_chars();
+ printf("\nSample read failed due to communication problem.\n");
+ printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ return 1;
+ }
+ printf("\n");
+ if (p->it->icom->port_type(p->it->icom) == icomt_serial) {
+ /* Allow retrying at a lower baud rate */
+ int tt = p->it->last_scomerr(p->it);
+ if (tt & (ICOM_BRK | ICOM_FER | ICOM_PER | ICOM_OER)) {
+ if (p->br == baud_19200) p->br = baud_9600;
+ else if (p->br == baud_9600) p->br = baud_4800;
+ else if (p->br == baud_2400) p->br = baud_1200;
+ else p->br = baud_1200;
+ }
+ if ((rv = p->it->init_coms(p->it, p->br, p->fc, 15.0)) != inst_ok) {
+ a1logd(p->log,1,"init_coms returned '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ return 2;
+ }
+ }
+ continue;
+ }
+ } else {
+ break; /* Sucesful reading */
+ }
+ }
+ /* We only fall through with a valid reading */
+ scb->serno = p->serno++;
+ scb->msec = msec_time();
+
+ a1logd(p->log,1, "got reading %f %f %f, transfering to col\n",
+ val.XYZ[0], val.XYZ[1], val.XYZ[2]);
+
+ scb->mtype = val.mtype;
+
+ /* Copy XYZ */
+ if (val.XYZ_v != 0) {
+ for (j = 0; j < 3; j++)
+ scb->XYZ[j] = val.XYZ[j];
+ scb->XYZ_v = 1;
+ }
+
+ /* Copy spectral */
+ if (p->spectral && val.sp.spec_n > 0) {
+ scb->sp = val.sp;
+ }
+ a1logd(p->log,1,"on to next reading\n");
+ }
+ /* Final return. */
+ if (acr && spat != 0 && tpat != 0 && (spat+patch-1) == tpat)
+ a1logv(p->log, 1, "\n");
+ return 0;
+}
+
+/* A structure to hold info about drift samples */
+typedef struct {
+ col dcols[2]; /* Black and white readings */
+
+ int off, count; /* Offset and count into cols */
+
+} dsamples;
+
+/* Take a series of readings from the display - drift compensation */
+/* Return nz on fail/abort */
+/* 1 = user aborted */
+/* 2 = instrument access failed */
+/* 3 = window access failed */
+/* 4 = user hit terminate key */
+/* 5 = system error */
+/* Use disprd_err() to interpret it */
+static int disprd_read_drift(
+ disprd *p,
+ col *cols, /* Array of patch colors to be tested */
+ int npat, /* Number of patches to be tested */
+ int spat, /* Start patch index for "verb", 0 if not used */
+ int tpat, /* Total patch index for "verb", 0 if not used */
+ int acr, /* If nz, do automatic final carriage return */
+ int tc, /* If nz, termination key */
+ instClamping clamp /* NZ if clamp XYZ/Lab to be +ve */
+) {
+ int rv, i, j, e;
+ double fper, foff;
+ int off, poff;
+ int boff, eoff, dno; /* b&w offsets and count */
+
+//printf("~1 DRIFT_EPERIOD %d, DRIFT_IPERIOD %d, npat %d\n",DRIFT_EPERIOD,DRIFT_IPERIOD,npat);
+
+ /* Figure number and offset for b&w */
+ if (p->bdrift == 0) { /* Must be just wdrift */
+ boff = eoff = 1;
+ dno = 1;
+ } else if (p->bdrift == 0) { /* Must be just bdrift */
+ boff = eoff = 0;
+ dno = 1;
+ } else { /* Must be both */
+ boff = eoff = 0;
+ dno = 2;
+ }
+
+ /* If the last drift readings are invalid or too old, */
+ /* or if we will use interpolation, read b&w */
+#ifdef NEVER
+ printf("last_bw_v = %d (%d)\n",p->last_bw_v,p->last_bw_v == 0);
+ printf("npat = %d > %d, serno %d, last serno %d (%d)\n",npat, DRIFT_EPERIOD, p->serno,p->last_bw[eoff].serno,(npat > DRIFT_EPERIOD && p->serno > p->last_bw[eoff].serno));
+ printf("serno - lastserno %d > %d (%d)\n",p->serno - p->last_bw[eoff].serno, DRIFT_EPERIOD,(p->serno - p->last_bw[eoff].serno) > DRIFT_EPERIOD);
+ printf("msec %d - last %d = %d > 10000 (%d)\n",msec_time(),p->last_bw[boff].msec,msec_time() - p->last_bw[boff].msec,(msec_time() - p->last_bw[eoff].msec) > 10000);
+#endif /* NEVER */
+ if (p->last_bw_v == 0 /* There are none */
+ || (npat > DRIFT_EPERIOD && p->serno > p->last_bw[eoff].serno) /* We will interpolate */
+ || (p->serno - p->last_bw[eoff].serno - dno) > DRIFT_EPERIOD /* extrapolate would be too far */
+ || (msec_time() - p->last_bw[eoff].msec) > 10000) { /* The current is too long ago */
+
+//printf("~1 refreshing last bw\n");
+ /* Read the black and/or white drift patch */
+ p->last_bw[0].r =
+ p->last_bw[0].g =
+ p->last_bw[0].b = 0.0;
+ p->last_bw[1].r =
+ p->last_bw[1].g =
+ p->last_bw[1].b = 1.0;
+ if ((rv = disprd_read_imp(p, &p->last_bw[boff], dno, spat, tpat, 0, 1, tc, 0)) != 0) {
+ return rv;
+ }
+ p->last_bw_v = 1;
+
+ /* If there is no reference b&w, set them from this first b&w */
+ if (p->ref_bw_v == 0) {
+ p->ref_bw[0] = p->last_bw[0];
+ p->ref_bw[1] = p->last_bw[1];
+ p->ref_bw_v = 1;
+ }
+ }
+
+ /* If there are enough patches to bracket with drift readings */
+ if (npat > DRIFT_EPERIOD) {
+ int ndrift = 2; /* Number of drift records */
+ dsamples *dss;
+
+ /* Figure out the number of drift samples we need */
+ ndrift += (npat-1)/DRIFT_IPERIOD;
+//printf("~1 spat %d, npat = %d, tpat %d, ndrift = %d\n",spat,npat,tpat,ndrift);
+
+ if ((dss = (dsamples *)calloc(sizeof(dsamples), ndrift)) == NULL) {
+ a1logd(p->log,1, "malloc of %d dsamples failed\n",ndrift);
+ return 5;
+ }
+
+ /* Set up bookeeping */
+ fper = (double)npat/(ndrift-1.0);
+//printf("~1 fper = %f\n",fper);
+ foff = 0.0;
+ for (poff = off = i = 0; i < ndrift; i++) {
+ dss[i].off = off;
+ foff += fper;
+ off = (int)floor(foff + 0.5);
+ if (i < (ndrift-1))
+ dss[i].count = off - poff;
+ else
+ dss[i].count = 0;
+ poff = off;
+//printf("~1 dss[%d] off = %d, count = %d\n",i, dss[i].off,dss[i].count);
+
+ dss[i].dcols[0].r =
+ dss[i].dcols[0].g =
+ dss[i].dcols[0].b = 0.0;
+ dss[i].dcols[1].r =
+ dss[i].dcols[1].g =
+ dss[i].dcols[1].b = 1.0;
+ }
+
+ /* For each batch of patches bracketed by drift patch readings */
+ for (i = 0; i < (ndrift-1); i++) {
+
+ if (i == 0) { /* Already done very first drift patches, so use them */
+ /* Use last b&w */
+ dss[i].dcols[0] = p->last_bw[0];
+ dss[i].dcols[1] = p->last_bw[1];
+ } else {
+ /* Read the black and/or white drift patchs before next batch */
+ if ((rv = disprd_read_imp(p, &dss[i].dcols[boff], dno, spat+dss[i].off, tpat, 0, 1, tc, 0)) != 0) {
+ free(dss);
+ return rv;
+ }
+ }
+ /* Read this batch of patches */
+ if ((rv = disprd_read_imp(p, &cols[dss[i].off], dss[i].count,spat+dss[i].off,tpat,0,0,tc, 0)) != 0) {
+ free(dss);
+ return rv;
+ }
+ }
+ /* Read the black and/or white drift patchs after last batch */
+ if ((rv = disprd_read_imp(p, &dss[i].dcols[boff], dno, spat+dss[i].off-1, tpat, 0, 1, tc, 0)) != 0) {
+ free(dss);
+ return rv;
+ }
+ /* Remember the last for next time */
+ p->last_bw[0] = dss[i].dcols[0];
+ p->last_bw[1] = dss[i].dcols[1];
+ p->last_bw_v = 1;
+
+ /* Set the white drift target to be the last one for batch */
+ p->targ_w = p->last_bw[1];
+ p->targ_w_v = 1;
+
+//printf("~1 ref b = %f %f %f\n", p->ref_bw[0].XYZ[0], p->ref_bw[0].XYZ[1], p->ref_bw[0].XYZ[2]);
+//printf("~1 ref w = %f %f %f\n", p->ref_bw[1].XYZ[0], p->ref_bw[1].XYZ[1], p->ref_bw[1].XYZ[2]);
+//printf("~1 ndrift-1 b = %f %f %f\n", dss[ndrift-1].dcols[0].XYZ[0], dss[ndrift-1].dcols[0].XYZ[1], dss[ndrift-1].dcols[0].XYZ[2]);
+//printf("~1 ndrift-1 w = %f %f %f\n", dss[ndrift-1].dcols[1].XYZ[0], dss[ndrift-1].dcols[1].XYZ[1], dss[ndrift-1].dcols[1].XYZ[2]);
+
+ /* Apply the drift compensation using interpolation */
+ for (i = 0; i < (ndrift-1); i++) {
+
+ for (j = 0; j < dss[i].count; j++) {
+ int k = dss[i].off + j;
+ double we; /* Interpolation weight of eairlier value */
+ col bb, ww; /* Interpolated black and white */
+//double uXYZ[3];
+//icmCpy3(uXYZ, cols[k].XYZ);
+//printf("~1 patch %d = %f %f %f\n", k, cols[k].XYZ[0], cols[k].XYZ[1], cols[k].XYZ[2]);
+ if (p->bdrift) {
+ we = (double)(dss[i+1].dcols[0].msec - cols[k].msec)/
+ (double)(dss[i+1].dcols[0].msec - dss[i].dcols[0].msec);
+
+ if (cols[k].XYZ_v) {
+ for (e = 0; e < 3; e++) {
+ bb.XYZ[e] = we * dss[i].dcols[0].XYZ[e]
+ + (1.0 - we) * dss[i+1].dcols[0].XYZ[e];
+ }
+//printf("~1 bb = %f %f %f\n", bb.XYZ[0], bb.XYZ[1], bb.XYZ[2]);
+ }
+ if (cols[k].sp.spec_n > 0) {
+ for (e = 0; e < cols[k].sp.spec_n; e++) {
+ bb.sp.spec[e] = we * dss[i].dcols[0].sp.spec[e]
+ + (1.0 - we) * dss[i+1].dcols[0].sp.spec[e];
+ }
+ }
+ }
+ if (p->wdrift) {
+ we = (double)(dss[i+1].dcols[1].msec - cols[k].msec)/
+ (double)(dss[i+1].dcols[1].msec - dss[i].dcols[1].msec);
+
+ if (cols[k].XYZ_v) {
+ for (e = 0; e < 3; e++) {
+ ww.XYZ[e] = we * dss[i].dcols[1].XYZ[e]
+ + (1.0 - we) * dss[i+1].dcols[1].XYZ[e];
+ }
+//printf("~1 ww = %f %f %f\n", ww.XYZ[0], ww.XYZ[1], ww.XYZ[2]);
+ }
+ if (cols[k].sp.spec_n > 0) {
+ for (e = 0; e < cols[k].sp.spec_n; e++) {
+ ww.sp.spec[e] = we * dss[i].dcols[1].sp.spec[e]
+ + (1.0 - we) * dss[i+1].dcols[1].sp.spec[e];
+ }
+ }
+ }
+
+ if (p->bdrift) {
+ /* Compensate interpolated black for any white change from ref. */
+ if (p->wdrift) {
+ if (cols[k].XYZ_v) {
+ for (e = 0; e < 3; e++) {
+ bb.XYZ[e] *= p->ref_bw[1].XYZ[e]/ww.XYZ[e];
+ }
+//printf("~1 wcomp bb = %f %f %f\n", bb.XYZ[0], bb.XYZ[1], bb.XYZ[2]);
+ }
+ if (cols[k].sp.spec_n > 0) {
+ for (e = 0; e < cols[k].sp.spec_n; e++) {
+ bb.sp.spec[e] *= p->ref_bw[1].sp.spec[e]/ww.sp.spec[e];
+ }
+ }
+ }
+
+ /* Compensate the reading for any black drift from ref. */
+ if (cols[k].XYZ_v) {
+ for (e = 0; e < 3; e++) {
+ cols[k].XYZ[e] += p->ref_bw[0].XYZ[e] - bb.XYZ[e];
+ }
+//printf("~1 bcomp patch = %f %f %f\n", cols[k].XYZ[0], cols[k].XYZ[1], cols[k].XYZ[2]);
+ }
+ if (cols[k].sp.spec_n > 0) {
+ for (e = 0; e < cols[k].sp.spec_n; e++) {
+ cols[k].sp.spec[e] += p->ref_bw[0].sp.spec[e] - bb.sp.spec[e];
+ }
+ }
+ }
+ /* Compensate the reading for any white drift to target white */
+ if (p->wdrift) {
+ if (cols[k].XYZ_v) {
+ for (e = 0; e < 3; e++) {
+ cols[k].XYZ[e] *= p->targ_w.XYZ[e]/ww.XYZ[e];
+ }
+//printf("~1 wcomp patch = %f %f %f\n", cols[k].XYZ[0], cols[k].XYZ[1], cols[k].XYZ[2]);
+ }
+ if (cols[k].sp.spec_n > 0) {
+ for (e = 0; e < cols[k].sp.spec_n; e++) {
+ cols[k].sp.spec[e] *= p->targ_w.sp.spec[e]/ww.sp.spec[e];
+ }
+ }
+ }
+//printf("~1 %d: drift change %f DE\n",k,icmXYZLabDE(&icmD50, uXYZ, cols[k].XYZ));
+ }
+ }
+ free(dss);
+
+ /* Else too small a batch, use extrapolation from the last b&w */
+ } else {
+
+//printf("~1 doing small number of readings\n");
+ /* Read the small number of patches */
+ if ((rv = disprd_read_imp(p, cols,npat,spat,tpat,acr,0,tc,0)) != 0)
+ return rv;
+
+ if (p->targ_w_v == 0) {
+ /* Set the white drift reference to be the last one */
+ p->targ_w = p->last_bw[1];
+ p->targ_w_v = 1;
+//printf("~1 set white drift target\n");
+ }
+
+//printf("~1 ref b = %f %f %f\n", p->ref_bw[0].XYZ[0], p->ref_bw[0].XYZ[1], p->ref_bw[0].XYZ[2]);
+//printf("~1 ref w = %f %f %f\n", p->ref_bw[1].XYZ[0], p->ref_bw[1].XYZ[1], p->ref_bw[1].XYZ[2]);
+//printf("~1 last b = %f %f %f\n", p->last_bw[0].XYZ[0], p->last_bw[0].XYZ[1], p->last_bw[0].XYZ[2]);
+//printf("~1 last w = %f %f %f\n", p->last_bw[1].XYZ[0], p->last_bw[1].XYZ[1], p->last_bw[1].XYZ[2]);
+//printf("~1 target w = %f %f %f\n", p->targ_w.XYZ[0], p->targ_w.XYZ[1], p->targ_w.XYZ[2]);
+ /* Apply the drift compensation using extrapolation */
+ for (j = 0; j < npat; j++) {
+ double we; /* Interpolation weight of eairlier value */
+ col bb, ww; /* Interpolated black and white */
+
+//printf("~1 patch %d = %f %f %f\n", j, cols[j].XYZ[0], cols[j].XYZ[1], cols[j].XYZ[2]);
+//double uXYZ[3];
+//icmCpy3(uXYZ, cols[j].XYZ);
+
+ if (p->bdrift) {
+ bb = p->last_bw[0];
+//printf("~1 bb = %f %f %f\n", bb.XYZ[0], bb.XYZ[1], bb.XYZ[2]);
+ }
+ if (p->wdrift) {
+ ww = p->last_bw[1];
+//printf("~1 ww = %f %f %f\n", ww.XYZ[0], ww.XYZ[1], ww.XYZ[2]);
+ }
+
+ if (p->bdrift) {
+ /* Compensate interpolated black for any white change from ref. */
+ if (p->wdrift) {
+ if (cols[j].XYZ_v) {
+ for (e = 0; e < 3; e++) {
+ bb.XYZ[e] *= p->ref_bw[1].XYZ[e]/ww.XYZ[e];
+ }
+//printf("~1 wcomp bb = %f %f %f\n", bb.XYZ[0], bb.XYZ[1], bb.XYZ[2]);
+ }
+ if (cols[j].sp.spec_n > 0) {
+ for (e = 0; e < cols[j].sp.spec_n; e++) {
+ bb.sp.spec[e] *= p->ref_bw[1].sp.spec[e]/ww.sp.spec[e];
+ }
+ }
+ }
+
+ /* Compensate the reading for any black drift from ref. */
+ if (cols[j].XYZ_v) {
+ for (e = 0; e < 3; e++) {
+ cols[j].XYZ[e] += p->ref_bw[0].XYZ[e] - bb.XYZ[e];
+ }
+//printf("~1 bcomp patch = %f %f %f\n", cols[j].XYZ[0], cols[j].XYZ[1], cols[j].XYZ[2]);
+ }
+ if (cols[j].sp.spec_n > 0) {
+ for (e = 0; e < cols[j].sp.spec_n; e++) {
+ cols[j].sp.spec[e] += p->ref_bw[0].sp.spec[e] - bb.sp.spec[e];
+ }
+ }
+ }
+ /* Compensate the reading for any white drift to target white */
+ if (p->wdrift) {
+ if (cols[j].XYZ_v) {
+ for (e = 0; e < 3; e++) {
+ cols[j].XYZ[e] *= p->targ_w.XYZ[e]/ww.XYZ[e];
+ }
+//printf("~1 wcomp patch = %f %f %f\n", cols[j].XYZ[0], cols[j].XYZ[1], cols[j].XYZ[2]);
+ }
+ if (cols[j].sp.spec_n > 0) {
+ for (e = 0; e < cols[j].sp.spec_n; e++) {
+ cols[j].sp.spec[e] *= p->targ_w.sp.spec[e]/ww.sp.spec[e];
+ }
+ }
+ }
+//printf("~1 %d: drift change %f DE\n",j,icmXYZLabDE(&icmD50, uXYZ, cols[j].XYZ));
+ }
+ }
+
+ if (acr && spat != 0 && tpat != 0 && (spat+npat-1) == tpat)
+ a1logv(p->log, 1, "\n");
+
+ if (clamp) {
+ for (j = 0; j < npat; j++) {
+ if (cols[j].XYZ_v)
+ icmClamp3(cols[j].XYZ, cols[j].XYZ);
+ }
+ }
+ return rv;
+}
+
+/* Reset the white drift target white value, for non-batch */
+/* readings when white drift comp. is enabled */
+static void disprd_reset_targ_w(disprd *p) {
+ p->targ_w_v = 0;
+}
+
+/* Change the black/white drift compensation options. */
+/* Note that this simply invalidates any reference readings, */
+/* and therefore will not make for good black compensation */
+/* if it is done a long time since the instrument calibration. */
+static void disprd_change_drift_comp(disprd *p,
+ int bdrift, /* Flag, nz for black drift compensation */
+ int wdrift /* Flag, nz for white drift compensation */
+) {
+ if (p->bdrift && !bdrift) { /* Turning black drift off */
+ p->bdrift = 0;
+ p->ref_bw_v = 0;
+ p->last_bw_v = 0;
+ p->targ_w_v = 0;
+
+ } else if (!p->bdrift && bdrift) { /* Turning black drift on */
+ p->bdrift = 1;
+ p->ref_bw_v = 0;
+ p->last_bw_v = 0;
+ p->targ_w_v = 0;
+ }
+
+ if (p->wdrift && !wdrift) { /* Turning white drift off */
+ p->wdrift = 0;
+ p->ref_bw_v = 0;
+ p->last_bw_v = 0;
+ p->targ_w_v = 0;
+
+ } else if (!p->wdrift && wdrift) { /* Turning white drift on */
+ p->wdrift = 1;
+ p->ref_bw_v = 0;
+ p->last_bw_v = 0;
+ p->targ_w_v = 0;
+ }
+}
+
+/* Take a series of readings from the display */
+/* Return nz on fail/abort */
+/* 1 = user aborted */
+/* 2 = instrument access failed */
+/* 3 = window access failed */
+/* 4 = user hit terminate key */
+/* 5 = system error */
+/* Use disprd_err() to interpret it */
+static int disprd_read(
+ disprd *p,
+ col *cols, /* Array of patch colors to be tested */
+ int npat, /* Number of patches to be tested */
+ int spat, /* Start patch index for "verb", 0 if not used */
+ int tpat, /* Total patch index for "verb", 0 if not used */
+ int acr, /* If nz, do automatic final carriage return */
+ int tc, /* If nz, termination key */
+ instClamping clamp /* NZ if clamp XYZ/Lab to be +ve */
+) {
+ int rv, i;
+
+ if (p->bdrift || p->wdrift) {
+ if ((rv = disprd_read_drift(p, cols,npat,spat,tpat,acr,tc,clamp)) != 0)
+ return rv;
+ } else {
+ if ((rv = disprd_read_imp(p, cols,npat,spat,tpat,acr,0,tc,clamp)) != 0)
+ return rv;
+ }
+
+ /* Use spectral if available. */
+ /* Since this is a display, assume that this is absolute spectral */
+ if (p->sp2cie != NULL) {
+ for (i = 0; i < npat; i++) {
+ if (cols[i].sp.spec_n > 0) {
+ p->sp2cie->convert(p->sp2cie, cols[i].XYZ, &cols[i].sp);
+ if (clamp)
+ icmClamp3(cols[i].XYZ, cols[i].XYZ);
+ cols[i].XYZ_v = 1;
+ }
+ }
+ }
+
+ return rv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Return the refresh mode and cbid */
+static void disprd_get_disptype(disprd *p, int *refrmode, int *cbid) {
+ if (refrmode != NULL)
+ *refrmode = p->refrmode;
+ if (cbid != NULL)
+ *cbid = p->cbid;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+static int config_inst_displ(disprd *p);
+
+/* Take an ambient reading if the instrument has the capability. */
+/* return nz on fail/abort */
+/* 1 = user aborted */
+/* 2 = instrument access failed */
+/* 4 = user hit terminate key */
+/* 5 = system error */
+/* 8 = no ambient capability */
+/* Use disprd_err() to interpret it */
+int disprd_ambient(
+ struct _disprd *p,
+ double *ambient, /* return ambient in cd/m^2 */
+ int tc /* If nz, termination key */
+) {
+ inst_mode cap = 0;
+ inst2_capability cap2 = 0;
+ inst3_capability cap3 = 0;
+ inst_opt_type trigmode = inst_opt_unknown; /* Chosen trigger mode */
+ int uswitch = 0; /* Instrument switch is enabled */
+ ipatch val;
+ int rv;
+
+ if (p->it != NULL) { /* Not fake */
+ p->it->capabilities(p->it, &cap, &cap2, &cap3);
+ }
+
+ if (!IMODETST(cap, inst_mode_emis_ambient)) {
+ printf("Need ambient measurement capability,\n");
+ printf("but instrument doesn't support it\n");
+ return 8;
+ }
+
+ printf("\nPlease make sure the instrument is fitted with\n");
+ printf("the appropriate ambient light measuring head.\n");
+
+ if ((rv = p->it->set_mode(p->it, inst_mode_emis_ambient)) != inst_ok) {
+ a1logd(p->log,1,"set_mode returned '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ return 2;
+ }
+ if (p->it != NULL) { /* Not fake */
+ p->it->capabilities(p->it, &cap, &cap2, &cap3);
+ }
+
+ /* Select a reasonable trigger mode */
+ if (cap2 & inst2_user_switch_trig) {
+ trigmode = inst_opt_trig_user_switch;
+ uswitch = 1;
+
+ /* Or go for user via uicallback trigger */
+ } else if (cap2 & inst2_user_trig) {
+ trigmode = inst_opt_trig_user;
+
+ /* Or something is wrong with instrument capabilities */
+ } else {
+ printf("No reasonable trigger mode avilable for this instrument\n");
+ return 2;
+ }
+
+ if ((rv = p->it->get_set_opt(p->it, trigmode)) != inst_ok) {
+ printf("\nSetting trigger mode failed with error :'%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ return 2;
+ }
+
+ /* Setup the keyboard trigger to return our commands */
+ inst_set_uih(0x0, 0xff, DUIH_TRIG);
+ inst_set_uih('q', 'q', DUIH_ABORT);
+ inst_set_uih('Q', 'Q', DUIH_ABORT);
+ inst_set_uih(0x03, 0x03, DUIH_ABORT); /* ^c */
+ inst_set_uih(0x1b, 0x1b, DUIH_ABORT); /* Esc */
+
+ /* Setup user termination character */
+ inst_set_uih(tc, tc, DUIH_TERM);
+
+ /* Until we give up retrying */
+ for (;;) {
+ char ch;
+ val.mtype = inst_mrt_none;
+ val.XYZ_v = 0; /* No readings are valid */
+ val.sp.spec_n = 0;
+ val.duration = 0.0;
+
+ printf("\nPlace the instrument so as to measure ambient upwards, beside the display,\n");
+ if (uswitch)
+ printf("Hit ESC or Q to exit, instrument switch or any other key to take a reading: ");
+ else
+ printf("Hit ESC or Q to exit, any other key to take a reading: ");
+ fflush(stdout);
+
+ if ((rv = p->it->read_sample(p->it, "AMBIENT", &val, 1)) != inst_ok
+ && (rv & inst_mask) != inst_user_trig) {
+ a1logd(p->log,1,"read_sample returned '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+
+ /* deal with a user terminate or abort */
+ if ((rv & inst_mask) == inst_user_abort) {
+ int keyc = inst_get_uih_char();
+
+ /* Deal with a user terminate */
+ if (keyc & DUIH_TERM) {
+ return 4;
+
+ /* Deal with a user abort */
+ } else if (keyc & DUIH_ABORT) {
+ empty_con_chars();
+ printf("\nMeasure stopped at user request!\n");
+ printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ return 1;
+ }
+ printf("\n");
+ continue;
+ }
+
+ /* Deal with needs calibration */
+ } else if ((rv & inst_mask) == inst_needs_cal) {
+ disp_win_info dwi;
+ dwi.dw = p->dw; /* Set window to use */
+ printf("\nSample read failed because instruments needs calibration\n");
+ rv = inst_handle_calibrate(p->it, inst_calt_needed, inst_calc_none,
+ setup_display_calibrate, &dwi);
+ setup_display_calibrate(p->it,inst_calc_none, &dwi);
+ if (rv != inst_ok) { /* Abort or fatal error */
+ return 1;
+ }
+ continue;
+
+ /* Deal with a bad sensor position */
+ } else if ((rv & inst_mask) == inst_wrong_config) {
+ empty_con_chars();
+ printf("\n\nSpot read failed due to the sensor being in the wrong position\n");
+ printf("(%s)\n",p->it->interp_error(p->it, rv));
+ printf("Correct position then hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ return 1;
+ }
+ printf("\n");
+ continue;
+
+ /* Deal with a misread */
+ } else if ((rv & inst_mask) == inst_misread) {
+ empty_con_chars();
+ printf("\nMeasurement failed due to misread\n");
+ printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ return 1;
+ }
+ printf("\n");
+ continue;
+
+ /* Deal with a communications error */
+ } else if ((rv & inst_mask) == inst_coms_fail) {
+ empty_con_chars();
+ printf("\nMeasurement read failed due to communication problem.\n");
+ printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ return 1;
+ }
+ printf("\n");
+ if (p->it->icom->port_type(p->it->icom) == icomt_serial) {
+ /* Allow retrying at a lower baud rate */
+ int tt = p->it->last_scomerr(p->it);
+ if (tt & (ICOM_BRK | ICOM_FER | ICOM_PER | ICOM_OER)) {
+ if (p->br == baud_19200) p->br = baud_9600;
+ else if (p->br == baud_9600) p->br = baud_4800;
+ else if (p->br == baud_2400) p->br = baud_1200;
+ else p->br = baud_1200;
+ }
+ if ((rv = p->it->init_coms(p->it, p->br, p->fc, 15.0)) != inst_ok) {
+ a1logd(p->log,1,"init_coms returned '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ return 2;
+ }
+ }
+ continue;
+ }
+ } else {
+ break; /* Sucesful reading */
+ }
+ }
+ /* Convert spectral to XYZ */
+ if (p->sp2cie != NULL && val.sp.spec_n > 0) {
+ p->sp2cie->convert(p->sp2cie, val.XYZ, &val.sp);
+ icmClamp3(val.XYZ, val.XYZ);
+ val.XYZ_v = 1;
+ }
+
+ if (val.XYZ_v == 0) {
+ printf("Unexpected failure to get measurement\n");
+ return 2;
+ }
+
+ a1logd(p->log,1,"Measured ambient of %f\n",val.XYZ[1]);
+ if (ambient != NULL)
+ *ambient = val.XYZ[1];
+
+ /* Configure the instrument mode back to reading the display */
+ if ((rv = config_inst_displ(p)) != 0)
+ return rv;
+
+ printf("\nPlace the instrument back on the test window\n");
+ fflush(stdout);
+
+ return 0;
+}
+
+
+/* Test without a spectrometer using a fake device */
+/* Return nz on fail/abort */
+/* 1 = user aborted */
+/* 2 = instrument access failed */
+/* 3 = window access failed */
+/* 4 = user hit terminate key */
+/* 5 = system error */
+/* Use disprd_err() to interpret it */
+static int disprd_fake_read(
+ disprd *p,
+ col *cols, /* Array of patch colors to be tested */
+ int npat, /* Number of patches to be tested */
+ int spat, /* Start patch index for "verb", 0 if not used */
+ int tpat, /* Total patch index for "verb", 0 if not used */
+ int acr, /* If nz, do automatic final carriage return */
+ int tc, /* If nz, termination key */
+ instClamping clamp /* NZ if clamp XYZ/Lab to be +ve */
+) {
+ icmXYZNumber white; /* White point */
+ icmXYZNumber red; /* Red colorant */
+ icmXYZNumber green; /* Green colorant */
+ icmXYZNumber blue; /* Blue colorant */
+ double doff[3]; /* device offsets */
+ double mat[3][3]; /* Destination matrix */
+ double xmat[3][3]; /* Extra matrix */
+ double ooff[3]; /* XYZ offsets */
+ double rgb[3]; /* Given test values */
+ double crgb[3]; /* Calibrated test values */
+// double br = 35.4; /* Overall brightness */
+ double br = 120.0; /* Overall brightness */
+ int patch, j;
+ int ttpat = tpat;
+ inst_code (*uicallback)(void *, inst_ui_purp) = NULL;
+ void *uicontext = NULL;
+
+ if (ttpat < npat)
+ ttpat = npat;
+
+ /* Setup fake device */
+ white.X = br * 0.955; /* Somewhere between D50 and D65 */
+ white.Y = br * 1.00;
+ white.Z = br * 0.97;
+ red.X = br * 0.41;
+ red.Y = br * 0.21;
+ red.Z = br * 0.02;
+ green.X = br * 0.30;
+ green.Y = br * 0.55;
+ green.Z = br * 0.15;
+ blue.X = br * 0.15;
+ blue.Y = br * 0.10;
+ blue.Z = br * 0.97;
+#ifdef SIMPLE_MODEL
+ doff[0] = doff[1] = doff[2] = 0.0; /* Input offset */
+ ooff[0] = ooff[1] = ooff[2] = 0.0; /* Output offset */
+#else
+ /* Input offset, equivalent to RGB offsets having various values */
+ doff[0] = 0.10;
+ doff[1] = 0.06;
+ doff[2] = 0.08;
+ /* Output offset - equivalent to flare [range 0.0 - 1.0] */
+ ooff[0] = 0.03;
+ ooff[1] = 0.04;
+ ooff[2] = 0.09;
+#endif
+
+ if (icmRGBprim2matrix(white, red, green, blue, mat))
+ error("Fake read unexpectedly got singular matrix\n");
+
+ icmSetUnity3x3(xmat);
+
+ if (p->fake2 == 1) {
+ /* Target matrix */
+ xmat[0][0] = 1.0; xmat[0][1] = 0.01; xmat[0][2] = 0.02;
+ xmat[1][0] = 0.1; xmat[1][1] = 1.11; xmat[1][2] = 0.12;
+ xmat[2][0] = 0.2; xmat[2][1] = 0.2; xmat[2][2] = 1.22;
+ }
+
+ uicallback = inst_get_uicallback();
+ uicontext = inst_get_uicontext();
+
+ /* Setup the keyboard trigger to return our commands */
+ inst_set_uih(0x0, 0xff, DUIH_TRIG);
+ inst_set_uih('q', 'q', DUIH_ABORT);
+ inst_set_uih('Q', 'Q', DUIH_ABORT);
+ inst_set_uih(0x03, 0x03, DUIH_ABORT); /* ^c */
+ inst_set_uih(0x1b, 0x1b, DUIH_ABORT); /* Esc */
+
+ /* Setup user termination character */
+ inst_set_uih(tc, tc, DUIH_TERM);
+
+ for (patch = 0; patch < npat; patch++) {
+ if (spat != 0 && tpat != 0)
+ a1logv(p->log, 1, "%cpatch %d of %d",cr_char,spat+patch,tpat);
+ crgb[0] = rgb[0] = cols[patch].r;
+ crgb[1] = rgb[1] = cols[patch].g;
+ crgb[2] = rgb[2] = cols[patch].b;
+
+ /* deal with a user terminate or abort */
+ if (uicallback(uicontext, inst_measuring) == inst_user_abort) {
+ int ch, keyc = inst_get_uih_char();
+
+ /* Deal with a user terminate */
+ if (keyc & DUIH_TERM) {
+ return 4;
+
+ /* Deal with a user abort */
+ } else if (keyc & DUIH_ABORT) {
+ empty_con_chars();
+ printf("\nSample read stopped at user request!\n");
+ printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ return 1;
+ }
+ printf("\n");
+ }
+ }
+
+//printf("~1 patch %d RGB %f %f %f\n",patch,rgb[0], rgb[1], rgb[2]);
+ /* If we have a calibration, apply it to the color */
+ if (p->cal[0][0] >= 0.0) {
+ double inputEnt_1 = (double)(p->ncal-1);
+
+ for (j = 0; j < 3; j++) {
+ unsigned int ix;
+ double val, w;
+ val = rgb[j] * inputEnt_1;
+ if (val < 0.0) {
+ val = 0.0;
+ } else if (val > inputEnt_1) {
+ val = inputEnt_1;
+ }
+ ix = (unsigned int)floor(val); /* Coordinate */
+ if (ix > (p->ncal-2))
+ ix = (p->ncal-2);
+ w = val - (double)ix; /* weight */
+ val = p->cal[j][ix];
+ crgb[j] = val + w * (p->cal[j][ix+1] - val);
+ }
+//printf("~1 patch %d cal RGB %f %f %f\n",patch, crgb[0], crgb[1], crgb[2]);
+ }
+
+ /* If we have a test window, display the patch color */
+ if (p->dw) {
+ inst_code rv;
+ if (p->softcal) { /* Display value with calibration */
+ if ((rv = p->dw->set_color(p->dw, crgb[0], crgb[1], crgb[2])) != 0) {
+ a1logd(p->log,1,"set_color() returned %s\n",rv);
+ return 3;
+ }
+ } else { /* Hardware will apply calibration */
+ if ((rv = p->dw->set_color(p->dw, rgb[0], rgb[1], rgb[2])) != 0) {
+ a1logd(p->log,1,"set_color() returned %s\n",rv);
+ return 3;
+ }
+ }
+ }
+
+ /* Apply the fake devices level of quantization */
+#ifdef FAKE_BITS
+ if (p->fake2 == 0) {
+ for (j = 0; j < 3; j++) {
+ int vv;
+ vv = (int) (crgb[j] * ((1 << FAKE_BITS) - 1.0) + 0.5);
+ crgb[j] = vv/((1 << FAKE_BITS) - 1.0);
+ }
+ }
+#endif
+
+ /* Apply device offset */
+ for (j = 0; j < 3; j++) {
+ if (crgb[j] < 0.0)
+ crgb[j] = 0.0;
+ else if (crgb[j] > 1.0)
+ crgb[j] = 1.0;
+ crgb[j] = doff[j] + (1.0 - doff[j]) * crgb[j];
+ }
+
+ /* Apply gamma */
+ for (j = 0; j < 3; j++) {
+ if (crgb[j] >= 0.0)
+ crgb[j] = pow(crgb[j], 2.5);
+ else
+ crgb[j] = 0.0;
+ }
+
+ /* Convert to XYZ */
+ icmMulBy3x3(cols[patch].XYZ, mat, crgb);
+
+ /* Apply XYZ offset */
+ for (j = 0; j < 3; j++)
+ cols[patch].XYZ[j] += ooff[j];
+
+ /* Apply extra matrix */
+ icmMulBy3x3(cols[patch].XYZ, xmat, cols[patch].XYZ);
+
+#ifdef FAKE_NOISE
+ if (p->fake2 == 0) {
+ cols[patch].XYZ[0] += 2.0 * FAKE_NOISE * d_rand(-1.0, 1.0);
+ cols[patch].XYZ[1] += FAKE_NOISE * d_rand(-1.0, 1.0);
+ cols[patch].XYZ[2] += 4.0 * FAKE_NOISE * d_rand(-1.0, 1.0);
+ }
+#endif
+ if (clamp)
+ icmClamp3(cols[patch].XYZ, cols[patch].XYZ);
+//printf("~1 patch %d XYZ %f %f %f\n",patch,cols[patch].XYZ[0], cols[patch].XYZ[1], cols[patch].XYZ[2]);
+ cols[patch].XYZ_v = 1;
+ cols[patch].sp.spec_n = 0;
+ cols[patch].mtype = inst_mrt_emission;
+ }
+ if (acr && spat != 0 && tpat != 0 && (spat+patch-1) == tpat)
+ a1logv(p->log, 1, "\n");
+ return 0;
+}
+
+/* Test without a spectrometer using a fake ICC profile device */
+/* Return nz on fail/abort */
+/* 1 = user aborted */
+/* 2 = instrument access failed */
+/* 3 = window access failed */
+/* 4 = user hit terminate key */
+/* 5 = system error */
+/* Use disprd_err() to interpret it */
+static int disprd_fake_read_lu(
+ disprd *p,
+ col *cols, /* Array of patch colors to be tested */
+ int npat, /* Number of patches to be tested */
+ int spat, /* Start patch index for "verb", 0 if not used */
+ int tpat, /* Total patch index for "verb", 0 if not used */
+ int acr, /* If nz, do automatic final carriage return */
+ int tc, /* If nz, termination key */
+ instClamping clamp /* NZ if clamp XYZ/Lab to be +ve */
+) {
+ int patch, j;
+ int ttpat = tpat;
+ double br = 120.4; /* Overall brightness */
+ inst_code (*uicallback)(void *, inst_ui_purp) = NULL;
+ void *uicontext = NULL;
+
+ if (ttpat < npat)
+ ttpat = npat;
+
+ uicallback = inst_get_uicallback();
+ uicontext = inst_get_uicontext();
+
+ /* Setup the keyboard trigger to return our commands */
+ inst_set_uih(0x0, 0xff, DUIH_TRIG);
+ inst_set_uih('q', 'q', DUIH_ABORT);
+ inst_set_uih('Q', 'Q', DUIH_ABORT);
+ inst_set_uih(0x03, 0x03, DUIH_ABORT); /* ^c */
+ inst_set_uih(0x1b, 0x1b, DUIH_ABORT); /* Esc */
+
+ /* Setup user termination character */
+ inst_set_uih(tc, tc, DUIH_TERM);
+
+ for (patch = 0; patch < npat; patch++) {
+ double rgb[3];;
+ if (spat != 0 && tpat != 0)
+ a1logv(p->log, 1, "%cpatch %d of %d",cr_char,spat+patch,tpat);
+ rgb[0] = cols[patch].r;
+ rgb[1] = cols[patch].g;
+ rgb[2] = cols[patch].b;
+
+ /* deal with a user terminate or abort */
+ if (uicallback(uicontext, inst_measuring) == inst_user_abort) {
+ int ch, keyc = inst_get_uih_char();
+
+ /* Deal with a user terminate */
+ if (keyc & DUIH_TERM) {
+ return 4;
+
+ /* Deal with a user abort */
+ } else if (keyc & DUIH_ABORT) {
+ empty_con_chars();
+ printf("\nSample read stopped at user request!\n");
+ printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ return 1;
+ }
+ printf("\n");
+ }
+ }
+
+ /* If we have a test window, display the patch color */
+ if (p->dw) {
+ inst_code rv;
+ if ((rv = p->dw->set_color(p->dw, rgb[0], rgb[1], rgb[2])) != 0) {
+ a1logd(p->log,1,"set_color() returned %s\n",rv);
+ return 3;
+ }
+ }
+
+ /* If we have a RAMDAC, apply it to the color */
+ if (p->cal[0][0] >= 0.0) {
+ double inputEnt_1 = (double)(p->ncal-1);
+
+ for (j = 0; j < 3; j++) {
+ unsigned int ix;
+ double val, w;
+
+ val = rgb[j] * inputEnt_1;
+ if (val < 0.0) {
+ val = 0.0;
+ } else if (val > inputEnt_1) {
+ val = inputEnt_1;
+ }
+ ix = (unsigned int)floor(val); /* Coordinate */
+ if (ix > (p->ncal-2))
+ ix = (p->ncal-2);
+ w = val - (double)ix; /* weight */
+ val = p->cal[j][ix];
+ rgb[j] = val + w * (p->cal[j][ix+1] - val);
+ }
+ }
+
+ p->fake_lu->lookup(p->fake_lu, cols[patch].XYZ, rgb);
+ for (j = 0; j < 3; j++)
+ cols[patch].XYZ[j] *= br;
+#ifdef FAKE_NOISE
+ cols[patch].XYZ[0] += 2.0 * FAKE_NOISE * d_rand(-1.0, 1.0);
+ cols[patch].XYZ[1] += FAKE_NOISE * d_rand(-1.0, 1.0);
+ cols[patch].XYZ[2] += 4.0 * FAKE_NOISE * d_rand(-1.0, 1.0);
+#endif
+ if (clamp)
+ icmClamp3(cols[patch].XYZ, cols[patch].XYZ);
+ cols[patch].XYZ_v = 1;
+ cols[patch].sp.spec_n = 0;
+ cols[patch].mtype = inst_mrt_emission;
+ }
+ if (acr && spat != 0 && tpat != 0 && (spat+patch-1) == tpat)
+ a1logv(p->log, 1, "\n");
+ return 0;
+}
+
+/* Use without a direct connect spectrometer using shell callout */
+/* Return nz on fail/abort */
+/* 1 = user aborted */
+/* 2 = instrument access failed */
+/* 3 = window access failed */
+/* 4 = user hit terminate key */
+/* 5 = system error */
+/* Use disprd_err() to interpret it */
+static int disprd_fake_read_co(disprd *p,
+ col *cols, /* Array of patch colors to be tested */
+ int npat, /* Number of patches to be tested */
+ int spat, /* Start patch index for "verb", 0 if not used */
+ int tpat, /* Total patch index for "verb", 0 if not used */
+ int acr, /* If nz, do automatic final carriage return */
+ int tc, /* If nz, termination key */
+ instClamping clamp /* NZ if clamp XYZ/Lab to be +ve */
+) {
+ int patch, j;
+ int ttpat = tpat;
+ inst_code (*uicallback)(void *, inst_ui_purp) = NULL;
+ void *uicontext = NULL;
+
+ if (ttpat < npat)
+ ttpat = npat;
+
+ uicallback = inst_get_uicallback();
+ uicontext = inst_get_uicontext();
+
+ /* Setup the keyboard trigger to return our commands */
+ inst_set_uih(0x0, 0xff, DUIH_TRIG);
+ inst_set_uih('q', 'q', DUIH_ABORT);
+ inst_set_uih('Q', 'Q', DUIH_ABORT);
+ inst_set_uih(0x03, 0x03, DUIH_ABORT); /* ^c */
+ inst_set_uih(0x1b, 0x1b, DUIH_ABORT); /* Esc */
+
+ /* Setup user termination character */
+ inst_set_uih(tc, tc, DUIH_TERM);
+
+ for (patch = 0; patch < npat; patch++) {
+ double rgb[3];
+ int rv;
+ char *cmd;
+ FILE *fp;
+
+ /* deal with a user terminate or abort */
+ if (uicallback(uicontext, inst_measuring) == inst_user_abort) {
+ int ch, keyc = inst_get_uih_char();
+
+ /* Deal with a user terminate */
+ if (keyc & DUIH_TERM) {
+ return 4;
+
+ /* Deal with a user abort */
+ } else if (keyc & DUIH_ABORT) {
+ empty_con_chars();
+ printf("\nSample read stopped at user request!\n");
+ printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ return 1;
+ }
+ printf("\n");
+ }
+ }
+
+ if (spat != 0 && tpat != 0)
+ a1logv(p->log, 1, "%cpatch %d of %d",cr_char,spat+patch,tpat);
+ rgb[0] = cols[patch].r;
+ rgb[1] = cols[patch].g;
+ rgb[2] = cols[patch].b;
+
+ /* If we have a test window, display the patch color */
+ if (p->dw) {
+ inst_code rv;
+ if ((rv = p->dw->set_color(p->dw, rgb[0], rgb[1], rgb[2])) != 0) {
+ a1logd(p->log,1,"set_color() returned %s\n",rv);
+ return 3;
+ }
+ }
+
+ /* If we have a RAMDAC, apply it to the color */
+ if (p->cal[0][0] >= 0.0) {
+ double inputEnt_1 = (double)(p->ncal-1);
+
+ for (j = 0; j < 3; j++) {
+ unsigned int ix;
+ double val, w;
+
+ val = rgb[j] * inputEnt_1;
+ if (val < 0.0) {
+ val = 0.0;
+ } else if (val > inputEnt_1) {
+ val = inputEnt_1;
+ }
+ ix = (unsigned int)floor(val); /* Coordinate */
+ if (ix > (p->ncal-2))
+ ix = (p->ncal-2);
+ w = val - (double)ix; /* weight */
+ val = p->cal[j][ix];
+ rgb[j] = val + w * (p->cal[j][ix+1] - val);
+ }
+ }
+
+ if ((cmd = malloc(strlen(p->mcallout) + 200)) == NULL)
+ error("Malloc of command string failed");
+
+ sprintf(cmd, "%s %d %d %d %f %f %f",p->mcallout,
+ (int)(rgb[0] * 255.0 + 0.5),
+ (int)(rgb[1] * 255.0 + 0.5),
+ (int)(rgb[2] * 255.0 + 0.5), rgb[0], rgb[1], rgb[2]);
+ if ((rv = system(cmd)) != 0)
+ error("System command '%s' failed with %d",cmd,rv);
+
+ /* Now read the XYZ result from the mcallout.meas file */
+ sprintf(cmd, "%s.meas",p->mcallout);
+ if ((fp = fopen(cmd,"r")) == NULL)
+ error("Unable to open measurement value file '%s'",cmd);
+
+ if (fscanf(fp, " %lf %lf %lf", &cols[patch].XYZ[0], &cols[patch].XYZ[1],
+ &cols[patch].XYZ[2]) != 3)
+ error("Unable to parse measurement value file '%s'",cmd);
+ fclose(fp);
+ free(cmd);
+
+ if (clamp)
+ icmClamp3(cols[patch].XYZ, cols[patch].XYZ);
+ cols[patch].XYZ_v = 1;
+ cols[patch].mtype = inst_mrt_emission;
+
+ a1logv(p->log, 2, "Read XYZ %f %f %f from '%s'\n", cols[patch].XYZ[0],
+ cols[patch].XYZ[1],cols[patch].XYZ[2], cmd);
+
+ }
+ if (acr && spat != 0 && tpat != 0 && (spat+patch-1) == tpat)
+ a1logv(p->log, 1, "\n");
+ return 0;
+}
+
+/* Return a string describing the error code */
+char *disprd_err(int en) {
+ switch(en) {
+ case 1:
+ return "User Aborted";
+ case 2:
+ return "Instrument Access Failed";
+ case 22:
+ return "Instrument Access Failed (No PLD Pattern - have you run spyd2en ?)";
+ case 3:
+ return "Window Access Failed";
+ case 4:
+ return "VideoLUT Access Failed";
+ case 5:
+ return "User Terminated";
+ case 6:
+ return "System Error";
+ case 7:
+ return "Either CRT or LCD must be selected";
+ case 8:
+ return "Instrument has no ambient measurement capability";
+ case 9:
+ return "Creating spectral conversion object failed";
+ case 10:
+ return "Instrument has no CCMX capability";
+ case 11:
+ return "Instrument has no CCSS capability";
+ case 12:
+ return "Internal: trying to set calibration when not using calibration";
+ }
+ return "Unknown";
+}
+
+static void disprd_del(disprd *p) {
+
+ if (p->log->verb >= 1 && p->bdrift && p->ref_bw_v && p->last_bw_v) {
+ icmXYZNumber w;
+ double de;
+ icmAry2XYZ(w, p->ref_bw[1].XYZ);
+ de = icmXYZLabDE(&w, p->ref_bw[0].XYZ, p->last_bw[0].XYZ);
+ a1logv(p->log, 1, "Black drift was %f DE\n",de);
+ }
+ if (p->log->verb >= 1 && p->wdrift && p->ref_bw_v && p->last_bw_v) {
+ icmXYZNumber w;
+ double de;
+ icmAry2XYZ(w, p->ref_bw[1].XYZ);
+ de = icmXYZLabDE(&w, p->ref_bw[1].XYZ, p->last_bw[1].XYZ);
+ a1logv(p->log, 1, "White drift was %f DE\n",de);
+ }
+
+ /* The user may remove the instrument */
+ if (p->dw != NULL)
+ printf("The instrument can be removed from the screen.\n");
+
+ if (p->fake_lu != NULL)
+ p->fake_lu->del(p->fake_lu);
+ if (p->fake_icc != NULL)
+ p->fake_icc->del(p->fake_icc);
+ if (p->fake_fp != NULL)
+ p->fake_fp->del(p->fake_fp);
+
+ if (p->it != NULL)
+ p->it->del(p->it);
+ if (p->dw != NULL) {
+ if (p->or != NULL)
+ p->dw->set_ramdac(p->dw,p->or, 0);
+ p->dw->del(p->dw);
+ }
+ if (p->sp2cie != NULL)
+ p->sp2cie->del(p->sp2cie);
+ p->log = del_a1log(p->log);
+ free(p);
+}
+
+/* Helper to configure the instrument mode ready */
+/* for reading the display */
+/* return new_disprd() error code */
+static int config_inst_displ(disprd *p) {
+ inst_mode cap;
+ inst2_capability cap2;
+ inst3_capability cap3;
+ inst_mode mode = 0;
+ int rv;
+
+ p->it->capabilities(p->it, &cap, &cap2, &cap3);
+
+ if (p->tele && !IMODETST(cap, inst_mode_emis_tele)) {
+ printf("Want telephoto measurement capability but instrument doesn't support it\n");
+ printf("so falling back to spot mode.\n");
+ a1logd(p->log,1,"No telephoto mode so falling back to spot mode.\n");
+ p->tele = 0;
+ }
+
+ if (( p->tele && !IMODETST(cap, inst_mode_emis_tele))
+ || (!p->tele && !IMODETST(cap, inst_mode_emis_spot))) {
+ printf("Need %s emissive measurement capability,\n", p->tele ? "telephoto" : "spot");
+ printf("but instrument doesn't support it\n");
+ a1logd(p->log,1,"Need %s emissive measurement capability but device doesn't support it,\n",
+ p->tele ? "telephoto" : "spot");
+ return 2;
+ }
+
+ if (p->nadaptive && !IMODETST(cap, inst_mode_emis_nonadaptive)) {
+ printf("Need non-adaptives measurement mode, but instrument doesn't support it\n");
+ a1logd(p->log,1, "Need non-adaptives measurement mode, but instrument doesn't support it\n");
+ return 2;
+ }
+
+ if (p->obType != icxOT_none
+ && p->obType != icxOT_default) {
+ if (!IMODETST(cap, inst_mode_spectral) && (cap2 & inst2_ccss) == 0) {
+ printf("A non-standard observer was requested,\n");
+ printf("but instrument doesn't support spectral or CCSS\n");
+ a1logd(p->log,1,"A non-standard observer was requested,\n"
+ "but instrument doesn't support spectral or CCSS\n");
+ return 2;
+ }
+ /* Make sure spectral is turned on if an observer is requested */
+ if (!p->spectral && (cap2 & inst2_ccss) == 0)
+ p->spectral = 1;
+ }
+
+ if (p->spectral && !IMODETST(cap,inst_mode_spectral)) {
+ if (p->spectral != 2) { /* Not soft */
+ printf("Spectral information was requested,\n");
+ printf("but instrument doesn't support it\n");
+ a1logd(p->log,1,"Spectral information was requested,\nbut instrument doesn't support it\n");
+ return 2;
+ }
+ p->spectral = 0;
+ }
+
+ if (p->tele) {
+ mode = inst_mode_emis_tele;
+ } else {
+ mode = inst_mode_emis_spot;
+ }
+ if (p->nadaptive)
+ mode |= inst_mode_emis_nonadaptive;
+
+ if (p->spectral) {
+ mode |= inst_mode_spectral;
+ p->spectral = 1;
+ } else {
+ p->spectral = 0;
+ }
+
+ /* Set the display type */
+
+ if (p->dtype != 0) {
+ if (cap2 & inst2_disptype) {
+ int ix;
+ if ((ix = inst_get_disptype_index(p->it, p->dtype, p->docbid)) < 0) {
+ a1logd(p->log,1,"Display type selection '%c' is not valid for instrument\n",p->dtype);
+ return 2;
+ }
+ if ((rv = p->it->set_disptype(p->it, ix)) != inst_ok) {
+ a1logd(p->log,1,"Setting display type failed failed with '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ return 2;
+ }
+ } else
+ printf("Display type ignored - instrument doesn't support display type\n");
+ }
+
+ /* Get the refresh mode and cbid */
+ if (cap2 & inst2_disptype) {
+ p->it->get_set_opt(p->it, inst_opt_get_dtinfo, &p->refrmode, &p->cbid);
+ }
+
+ /* Disable initcalibration of machine if selected */
+ if (p->noinitcal != 0) {
+ if ((rv = p->it->get_set_opt(p->it,inst_opt_noinitcalib, 0)) != inst_ok) {
+ a1logd(p->log,1,"Setting no-initial calibrate failed failed with '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ printf("Disable initial-calibrate not supported\n");
+ }
+ }
+
+ if (p->highres) {
+ if (IMODETST(cap, inst_mode_highres)) {
+ inst_code ev;
+ if ((ev = p->it->get_set_opt(p->it, inst_opt_highres)) != inst_ok) {
+ a1logd(p->log,1,"\nSetting high res mode failed with error :'%s' (%s)\n",
+ p->it->inst_interp_error(p->it, ev), p->it->interp_error(p->it, ev));
+ return 2;
+ }
+ } else {
+ a1logv(p->log, 1, "high resolution ignored - instrument doesn't support high res. mode\n");
+ }
+ }
+ if ((rv = p->it->set_mode(p->it, mode)) != inst_ok) {
+ a1logd(p->log,1,"set_mode returned '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ return 2;
+ }
+ p->it->capabilities(p->it, &cap, &cap2, &cap3);
+
+ if (p->ccmtx != NULL) {
+ if ((cap2 & inst2_ccmx) == 0) {
+ a1logd(p->log,1,"Instrument doesn't support ccmx correction\n");
+ return 10;
+ }
+ if ((rv = p->it->col_cor_mat(p->it, p->ccmtx)) != inst_ok) {
+ a1logd(p->log,1,"col_cor_mat returned '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ return 2;
+ }
+ }
+
+ if (p->sets != NULL
+ || ((cap2 & inst2_ccss) != 0 && p->obType != icxOT_default)) {
+
+ if ((cap2 & inst2_ccss) == 0) {
+ a1logd(p->log,1,"Instrument doesn't support ccss calibration\n");
+ return 11;
+ }
+ if ((rv = p->it->get_set_opt(p->it, inst_opt_set_ccss_obs, p->obType, p->custObserver))
+ != inst_ok) {
+ a1logd(p->log,1,"inst_opt_set_ccss_obs returned '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ return 2;
+ }
+ if ((rv = p->it->col_cal_spec_set(p->it, p->sets, p->no_sets)) != inst_ok) {
+ a1logd(p->log,1,"col_cal_spec_set returned '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ return 2;
+ }
+ }
+
+ /* Set the trigger mode to program triggered */
+ if ((rv = p->it->get_set_opt(p->it,inst_opt_trig_prog)) != inst_ok) {
+ a1logd(p->log,1,"Setting program trigger mode failed failed with '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ return 2;
+ }
+
+ /* Reset key meanings */
+ inst_reset_uih();
+
+ a1logd(p->log,1,"config_inst_displ suceeded\n");
+ return 0;
+}
+
+/* Create a display reading object. */
+/* Return NULL if error */
+/* Set *errc to code: */
+/* 0 = no error */
+/* 1 = user aborted */
+/* 2 = instrument access failed */
+/* 22 = instrument access failed - no PLD pattern */
+/* 3 = window access failed */
+/* 4 = RAMDAC access failed */
+/* 5 = user hit terminate key */
+/* 6 = system error */
+/* 7 = CRT or LCD must be selected */
+/* 9 = spectral conversion failed */
+/* 10 = no ccmx support */
+/* 11 = no ccss support */
+/* 12 = cal to set but native != 0 */
+/* Use disprd_err() to interpret *errc */
+disprd *new_disprd(
+int *errc, /* Error code. May be NULL (could use log for this instead?) */
+icompath *ipath, /* Instrument path to open, &icomFakeDevice == fake */
+flow_control fc, /* Flow control */
+int dtype, /* Display type selection character */
+int docbid, /* NZ to only allow cbid dtypes */
+int tele, /* NZ for tele mode. Falls back to display mode */
+int nadaptive, /* NZ for non-adaptive mode */
+int noinitcal, /* No initial instrument calibration */
+int highres, /* Use high res mode if available */
+int native, /* 0 = use current current or given calibration curve */
+ /* 1 = use native linear out & high precision */
+int *noramdac, /* Return nz if no ramdac access. native is set to 0 */
+double cal[3][MAX_CAL_ENT], /* Calibration set/return (cal[0][0] < 0.0 or NULL if not used) */
+ /* native must be 0 if cal is set */
+int ncal, /* Number of cal[] entries */
+int softcal, /* NZ if apply cal to readings rather than hardware */
+disppath *disp, /* Display to calibrate. NULL if fake and no dispwin */
+int blackbg, /* NZ if whole screen should be filled with black */
+int override, /* Override_redirect on X11 */
+int webdisp, /* If nz, port number for web color display */
+char *ccallout, /* Shell callout on set color */
+char *mcallout, /* Shell callout on measure color (forced fake) */
+double hpatsize, /* Size of dispwin */
+double vpatsize,
+double ho, /* Horizontal offset */
+double vo, /* Vertical offset */
+double ccmtx[3][3], /* Colorimeter Correction matrix, NULL if none */
+xspect *sets, /* CCSS Set of sample spectra, NULL if none */
+int no_sets, /* CCSS Number on set, 0 if none */
+int spectral, /* Generate spectral info flag */
+icxObserverType obType, /* Use alternate observer if spectral or CCSS and != icxOT_none */
+xspect custObserver[3], /* Optional custom observer */
+int bdrift, /* Flag, nz for black drift compensation */
+int wdrift, /* Flag, nz for white drift compensation */
+char *fake_name, /* Name of profile to use as a fake device */
+a1log *log /* Verb, debug & error log */
+) {
+ disprd *p = NULL;
+ int ch;
+ inst_code rv;
+
+ if (errc != NULL) *errc = 0; /* default return code = no error */
+
+ if (cal != NULL && cal[0][0] >= 0.0 && native != 0) {
+ if (errc != NULL) *errc = 12;
+ return NULL;
+ }
+
+ /* Allocate a disprd */
+ if ((p = (disprd *)calloc(sizeof(disprd), 1)) == NULL) {
+ a1logd(log, 1, "new_disprd failed due to malloc failure\n");
+ if (errc != NULL) *errc = 6;
+ return NULL;
+ }
+ p->log = new_a1log_d(log);
+ p->del = disprd_del;
+ p->read = disprd_read;
+ p->get_disptype = disprd_get_disptype;
+ p->reset_targ_w = disprd_reset_targ_w;
+ p->change_drift_comp = disprd_change_drift_comp;
+ p->ambient = disprd_ambient;
+ p->fake_name = fake_name;
+
+ p->ccmtx = ccmtx;
+ p->sets = sets;
+ p->no_sets = no_sets; /* CCSS */
+ p->spectral = spectral;
+ p->obType = obType; /* CCSS or spectral */
+ p->custObserver = custObserver;
+ p->bdrift = bdrift;
+ p->wdrift = wdrift;
+ p->dtype = dtype;
+ p->docbid = docbid;
+ p->refrmode = -1; /* Unknown */
+ p->cbid = 0; /* Unknown */
+ p->tele = tele;
+ p->nadaptive = nadaptive;
+ p->noinitcal = noinitcal;
+ p->highres = highres;
+ if (mcallout != NULL)
+ ipath = &icomFakeDevice; /* Force fake device */
+ p->mcallout = mcallout;
+ p->ipath = ipath;
+ p->br = baud_19200;
+ p->fc = fc;
+
+ /* Save this in case we are using a fake device */
+ if (cal != NULL && cal[0][0] >= 0.0) {
+ int j, i;
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < ncal; i++) {
+ p->cal[j][i] = cal[j][i];
+ }
+ }
+ p->ncal = ncal;
+ p->softcal = softcal;
+ } else {
+ p->cal[0][0] = -1.0;
+ p->ncal = 0;
+ p->softcal = 0;
+ }
+
+ /* If non-real instrument */
+ if (ipath == &icomFakeDevice) {
+ p->fake = 1;
+
+ p->fake_fp = NULL;
+ p->fake_icc = NULL;
+ p->fake_lu = NULL;
+
+ /* See if there is a profile we should use as the fake device */
+ if (p->mcallout == NULL && p->fake_name != NULL
+ && (p->fake_fp = new_icmFileStd_name(p->fake_name,"r")) != NULL) {
+ if ((p->fake_icc = new_icc()) != NULL) {
+ if (p->fake_icc->read(p->fake_icc,p->fake_fp,0) == 0) {
+ icColorSpaceSignature ins;
+ p->fake_lu = p->fake_icc->get_luobj(p->fake_icc, icmFwd, icAbsoluteColorimetric,
+ icSigXYZData, icmLuOrdNorm);
+ p->fake_lu->spaces(p->fake_lu, &ins, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (ins != icSigRgbData) {
+ p->fake_lu->del(p->fake_lu);
+ p->fake_lu = NULL;
+ }
+ }
+ }
+ }
+
+ if (p->fake_lu != NULL) {
+ a1logv(p->log, 1, "Using profile '%s' rather than real device\n",p->fake_name);
+ p->read = disprd_fake_read_lu;
+ } else if (p->mcallout != NULL) {
+ a1logv(p->log, 1, "Using shell callout '%s' rather than real device\n",p->mcallout);
+ p->read = disprd_fake_read_co;
+ } else
+ p->read = disprd_fake_read;
+
+ if (disp == NULL) {
+ a1logd(log,1,"new_disprd returning fake device\n");
+ return p;
+ }
+
+ /* Setup the instrument */
+ } else {
+
+ a1logv(p->log, 1, "Setting up the instrument\n");
+
+ if ((p->it = new_inst(ipath, 0, log, DUIH_FUNC_AND_CONTEXT)) == NULL) {
+ a1logd(p->log,1,"new_disprd failed because new_inst failed\n");
+ p->del(p);
+ if (errc != NULL) *errc = 2;
+ return NULL;
+ }
+
+ /* Establish communications */
+ if ((rv = p->it->init_coms(p->it, p->br, p->fc, 15.0)) != inst_ok) {
+ a1logd(log,1,"init_coms returned '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ a1logd(log,1,"new_disprd failed because init_coms failed\n");
+ p->del(p);
+ if (errc != NULL) *errc = 2;
+ return NULL;
+ }
+
+ /* Initialise the instrument */
+ if ((rv = p->it->init_inst(p->it)) != inst_ok) {
+ a1logd(log,1,"init_inst returned '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ a1logd(log,1,"new_disprd failed because init_inst failed\n");
+ p->del(p);
+ if (errc != NULL) {
+ *errc = 2;
+ if ((rv & inst_imask) == SPYD2_NO_PLD_PATTERN)
+ *errc = 22;
+ }
+ return NULL;
+ }
+
+ /* Configure the instrument mode for reading the display */
+ if ((rv = config_inst_displ(p)) != 0) {
+ a1logd(log,1,"new_disprd failed because config_inst_displ failed\n");
+ p->del(p);
+ if (errc != NULL) *errc = rv;
+ return NULL;
+ }
+ }
+
+ /* Create a spectral conversion object if needed */
+ if (p->spectral && p->obType != icxOT_none) {
+ if ((p->sp2cie = new_xsp2cie(icxIT_none, NULL, p->obType, custObserver, icSigXYZData, icxNoClamp))
+ == NULL) {
+ a1logd(log,1,"new_disprd failed because creation of spectral conversion object failed\n");
+ p->del(p);
+ if (errc != NULL) *errc = 9;
+ return NULL;
+ }
+ }
+
+ if (webdisp != 0) {
+ /* Open web display */
+ if ((p->dw = new_webwin(webdisp, hpatsize, vpatsize, ho, vo, 0, 0,
+ p->log->verb, p->log->debug)) == NULL) {
+ a1logd(log,1,"new_disprd failed because new_webwin failed\n");
+ p->del(p);
+ if (errc != NULL) *errc = 3;
+ return NULL;
+ }
+ if (noramdac != NULL)
+ *noramdac = 1;
+ } else {
+ /* Open display window for positioning (no blackbg) */
+ if ((p->dw = new_dispwin(disp, hpatsize, vpatsize, ho, vo, 0, native, noramdac, 0,
+ override, p->log->debug)) == NULL) {
+ a1logd(log,1,"new_disprd failed because new_dispwin failed\n");
+ p->del(p);
+ if (errc != NULL) *errc = 3;
+ return NULL;
+ }
+ }
+
+ if (p->it != NULL) {
+ /* Do a calibration up front, so as not to get in the users way, */
+ /* but ignore a CRT frequency or display integration calibration, */
+ /* since these will be done automatically. */
+ if (p->it->needs_calibration(p->it) & inst_calt_n_dfrble_mask) {
+ disp_win_info dwi;
+ dwi.dw = p->dw; /* Set window to use */
+
+ rv = inst_handle_calibrate(p->it, inst_calt_needed, inst_calc_none,
+ setup_display_calibrate, &dwi);
+ setup_display_calibrate(p->it,inst_calc_none, &dwi);
+ printf("\n");
+ if (rv != inst_ok) { /* Abort or fatal error */
+ a1logd(log,1,"new_disprd failed because calibrate failed with '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ printf("Calibrate failed with '%s' (%s)\n",
+ p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
+ p->del(p);
+ if (errc != NULL) *errc = 2;
+ return NULL;
+ }
+ }
+ }
+
+ /* Ask user to put instrument on screen */
+ empty_con_chars();
+ printf("Place instrument on test window.\n");
+ printf("Hit Esc or Q to give up, any other key to continue:"); fflush(stdout);
+ if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ a1logd(log,1,"new_disprd failed because user aborted when placing device\n");
+ p->del(p);
+ if (errc != NULL) *errc = 1;
+ return NULL;
+ }
+ printf("\n");
+
+ if (webdisp == 0) {
+ /* Close the positioning window */
+ if (p->dw != NULL) {
+ if (p->or != NULL)
+ p->dw->set_ramdac(p->dw,p->or, 0);
+ p->dw->del(p->dw);
+ }
+
+ /* Open display window again for measurement */
+ if ((p->dw = new_dispwin(disp, hpatsize, vpatsize, ho, vo, 0, native, noramdac, blackbg,
+ override, p->log->debug)) == NULL) {
+ a1logd(log,1,"new_disprd failed new_dispwin failed\n");
+ p->del(p);
+ if (errc != NULL) *errc = 3;
+ return NULL;
+ }
+ }
+
+ /* Set color change callout */
+ if (ccallout) {
+ p->dw->set_callout(p->dw, ccallout);
+ }
+
+ /* If we have a calibration to set */
+ /* (This is only typically the case for disread) */
+ if (!p->softcal && cal != NULL && cal[0][0] >= 0.0) {
+
+ /* Save current RAMDAC so that we can restore it */
+ p->or = NULL;
+ if ((p->or = p->dw->get_ramdac(p->dw)) == NULL) {
+ warning("Unable to read or set display RAMDAC - switching to softcal");
+ p->softcal = softcal = 1;
+ }
+
+ /* Set the given RAMDAC so we can characterise through it */
+ if (p->or != NULL) {
+ ramdac *r;
+ int j, i;
+
+ r = p->or->clone(p->or);
+
+ /* Set the ramdac contents. */
+ /* We linearly interpolate from cal[ncal] to RAMDAC[nent] resolution */
+ for (i = 0; i < r->nent; i++) {
+ double val, w;
+ unsigned int ix;
+
+ val = (ncal-1.0) * i/(r->nent-1.0);
+ ix = (unsigned int)floor(val); /* Coordinate */
+ if (ix > (ncal-2))
+ ix = (ncal-2);
+ w = val - (double)ix; /* weight */
+ for (j = 0; j < 3; j++) {
+ val = cal[j][ix];
+ r->v[j][i] = val + w * (cal[j][ix+1] - val);
+ }
+ }
+ if (p->dw->set_ramdac(p->dw, r, 0)) {
+ a1logd(log,1,"new_disprd failed becayse set_ramdac failed\n");
+ a1logv(p->log, 1, "Failed to set RAMDAC to desired calibration.\n");
+ a1logv(p->log, 1, "Perhaps the operating system is being fussy ?\n");
+ r->del(r);
+ p->del(p);
+ if (errc != NULL) *errc = 4;
+ return NULL;
+ }
+ r->del(r);
+ }
+ }
+
+ /* Return the ramdac being used */
+ if (p->or != NULL && cal != NULL) {
+ ramdac *r;
+ int j, i;
+
+ if ((r = p->dw->get_ramdac(p->dw)) == NULL) {
+ a1logd(log,1,"new_disprd failed becayse get_ramdac failed\n");
+ a1logv(p->log, 1, "Failed to read current RAMDAC\n");
+ p->del(p);
+ if (errc != NULL) *errc = 4;
+ return NULL;
+ }
+ /* Get the ramdac contents. */
+ /* We linearly interpolate from RAMDAC[nent] to cal[ncal] resolution */
+ for (i = 0; i < ncal; i++) {
+ double val, w;
+ unsigned int ix;
+
+ val = (r->nent-1.0) * i/(ncal-1.0);
+ ix = (unsigned int)floor(val); /* Coordinate */
+ if (ix > (r->nent-2))
+ ix = (r->nent-2);
+ w = val - (double)ix; /* weight */
+ for (j = 0; j < 3; j++) {
+ val = r->v[j][ix];
+ cal[j][i] = val + w * (r->v[j][ix+1] - val);
+ }
+ }
+ r->del(r);
+ }
+
+ a1logd(log,1,"new_disprd succeeded\n");
+ return p;
+}
+
diff --git a/spectro/dispsup.h b/spectro/dispsup.h
new file mode 100644
index 0000000..837e16a
--- /dev/null
+++ b/spectro/dispsup.h
@@ -0,0 +1,247 @@
+
+#ifndef DISPSUP_H
+
+/*
+ * Argyll Color Correction System
+ * Common display patch reading support.
+ *
+ * Author: Graeme W. Gill
+ * Date: 2/11/2005
+ *
+ * Copyright 1998 - 2013 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/* A helper function to handle presenting a display test patch */
+struct _disp_win_info {
+ int webdisp; /* nz if web display is to be used */
+ disppath *disp; /* display to calibrate. */
+ int blackbg; /* NZ if whole screen should be filled with black */
+ int override; /* Override_redirect on X11 */
+ double hpatsize; /* Size of dispwin */
+ double vpatsize; /* Size of dispwin */
+ double ho, vo; /* Position of dispwin */
+ dispwin *dw; /* Display window if already open */
+ dispwin *_dw; /* Privare window if not already open */
+}; typedef struct _disp_win_info disp_win_info;
+
+/* A defauult callback that can be provided as an argument to */
+/* inst_handle_calibrate() to handle the display part of a */
+/* calibration callback. */
+/* Call this again with calc = inst_calc_none to cleanup afterwards. */
+inst_code setup_display_calibrate(
+ inst *p,
+ inst_cal_cond calc, /* Current condition. inst_calc_none for not setup */
+ disp_win_info *dwi /* Information to be able to open a display test patch */
+);
+
+/* User requested calibration of the display instrument */
+int disprd_calibration(
+icompath *ipath, /* Instrument path to open, &icomFakeDevice == fake */
+flow_control fc, /* Serial flow control */
+int dtype, /* Display type, 0 = unknown, 1 = CRT, 2 = LCD */
+int docbid, /* NZ to only allow cbid dtypes */
+int tele, /* NZ for tele mode */
+int nadaptive, /* NZ for non-adaptive mode */
+int noinitcal, /* NZ to disable initial instrument calibration */
+disppath *screen, /* Screen to calibrate. */
+int webdisp, /* If nz, port number for web display */
+int blackbg, /* NZ if whole screen should be filled with black */
+int override, /* Override_redirect on X11 */
+double hpatsize, /* Size of dispwin */
+double vpatsize,
+double ho, double vo, /* Position of dispwin */
+a1log *log /* Verb, debug & error log */
+);
+
+
+/* A color structure to return values with. */
+/* This can hold all representations simultaniously */
+typedef struct {
+ double r,g,b;
+ char *id; /* Id string */
+
+ inst_meas_type mtype; /* Measurement type */
+
+ int XYZ_v;
+ double XYZ[3]; /* Colorimeter readings */
+
+ xspect sp; /* Spectrum. sp.spec_n > 0 if valid */
+
+ double duration; /* Total duration in seconds (flash measurement) */
+
+ int serno; /* Reading serial number */
+ unsigned int msec; /* Timestamp */
+} col;
+
+
+/* Maximum number of entries to setup for calibration */
+#define MAX_CAL_ENT 4096
+
+/* Display reading context */
+struct _disprd {
+
+/* private: */
+ a1log *log; /* Verb, debug & error log */
+ int fake; /* Fake display/instrument flag */
+ int fake2; /* Flag to apply extra matrix to fake response */
+ char *fake_name; /* Fake profile name */
+ icmFile *fake_fp;
+ icc *fake_icc; /* NZ if ICC profile is being used for fake */
+ double cal[3][MAX_CAL_ENT]; /* Calibration being worked through (cal[0][0] < 0.0 or NULL if not used) */
+ int ncal; /* Number of entries used in cal[] */
+ int softcal; /* NZ if apply cal to readings rather than hardware */
+ icmLuBase *fake_lu;
+ char *mcallout; /* fake instrument shell callout */
+ icompath *ipath; /* Instrument path to open, &icomFakeDevice == fake */
+ baud_rate br;
+ flow_control fc;
+ inst *it; /* Instrument */
+ int dtype; /* Display type, 0 = unknown, 1 = CRT, 2 = LCD */
+ int docbid; /* NZ to only allow cbid dtypes */
+ int refrmode; /* Refresh display mode, -1 if unknow, 0 = if no, 1 if yes */
+ int cbid; /* The Calibration Base display mode ID, 0 if unknown */
+ int tele; /* NZ for tele mode */
+ int nadaptive; /* NZ for non-adaptive mode */
+ int highres; /* Use high res mode if available */
+ int update_delay_set; /* NZ if we've calibrated the disp. update delay, or tried and failed */
+ double (*ccmtx)[3]; /* Colorimeter Correction Matrix, NULL if none */
+ icxObserverType obType; /* CCSS Observer */
+ xspect *custObserver; /* CCSS Optional custom observer */
+ xspect *sets; /* CCSS Set of sample spectra, NULL if none */
+ int no_sets; /* CCSS Number on set, 0 if none */
+ int spectral; /* 1 = Generate spectral info flag, 2 = don't print error if not capable */
+ icxObserverType observ; /* Compute XYZ from spectral if spectral and != icxOT_none */
+ xsp2cie *sp2cie; /* Spectral to XYZ conversion */
+ int bdrift; /* Flag, nz for black drift compensation */
+ int wdrift; /* Flag, nz for white drift compensation */
+ int noinitcal; /* No initial instrument calibration */
+ dispwin *dw; /* Window */
+ ramdac *or; /* Original ramdac if we set one */
+
+ int serno; /* Reading serial number */
+ col ref_bw[2]; /* Reference black and white readings for drift comp. */
+ int ref_bw_v; /* Reference valid flag */
+ col last_bw[2]; /* Last black and white readings for drift comp. */
+ int last_bw_v; /* Last valid flag */
+ col targ_w; /* Target white to normalise to. last_bw[1] for batch, first white for */
+ /* non-batch, but latter can be reset. */
+ int targ_w_v; /* target_w valid flag */
+
+/* public: */
+
+ /* Destroy ourselves */
+ void (*del)(struct _disprd *p);
+
+ /* Take a series of readings from the display */
+ /* return nz on fail/abort */
+ /* 1 = user aborted */
+ /* 2 = instrument access failed */
+ /* 3 = window access failed */
+ /* 4 = user hit terminate key */
+ /* 5 = system error */
+ int (*read)(struct _disprd *p,
+ col *cols, /* Array of patch colors to be tested */
+ int npat, /* Number of patches to be tested */
+ int spat, /* Start patch index for "verb", 0 if not used */
+ int tpat, /* Total patch index for "verb", 0 if not used */
+ int acr, /* If nz, do automatic final carriage return */
+ int tc, /* If nz, termination key */
+ instClamping clamp /* NZ if clamp XYZ/Lab to be +ve */
+ );
+
+ /* Return the display type information */
+ void (*get_disptype)(struct _disprd *p, int *refrmode, int *cbid);
+
+ /* Reset the white drift target white value, for non-batch */
+ /* readings when white drift comp. is enabled */
+ void (*reset_targ_w)(struct _disprd *p);
+
+ /* Change the black/white drift compensation options */
+ /* Note that this simply invalidates any reference readings, */
+ /* and therefore will not make for good black compensation */
+ /* if it is done a long time since the instrument calibration. */
+ void (*change_drift_comp)(struct _disprd *p,
+ int bdrift, /* Flag, nz for black drift compensation */
+ int wdrift /* Flag, nz for white drift compensation */
+ );
+
+ /* Take an ambient reading if the instrument has the capability. */
+ /* return nz on fail/abort */
+ /* 1 = user aborted */
+ /* 2 = instrument access failed */
+ /* 3 = no ambient capability */
+ /* 4 = user hit terminate key */
+ /* 5 = system error */
+ /* 8 = no ambient capability */
+ int (*ambient)(struct _disprd *p,
+ double *ambient, /* return ambient in cd/m^2 */
+ int tc /* If nz, termination key */
+ );
+
+}; typedef struct _disprd disprd;
+
+
+/* Create a display reading object. */
+/* Return NULL if error */
+/* Set *errc to code: */
+/* 0 = no error */
+/* 1 = user aborted */
+/* 2 = instrument access failed */
+/* 3 = window access failed */
+/* 4 = user hit terminate key */
+/* 5 = system error */
+/* 6 = system error */
+/* 7 = CRT or LCD must be selected */
+/* 9 = spectral conversion failed */
+/* 10 = no ccmx support */
+/* 11 = no ccss support */
+/* 12 = cal to set but native != 0 */
+/* Use disprd_err() to interpret errc */
+disprd *new_disprd(
+int *errc, /* Error code. May be NULL */
+icompath *ipath, /* Instrument path to open, &icomFakeDevice == fake */
+flow_control fc, /* Serial flow control */
+int dtype, /* Display type, 0 = unknown, 1 = CRT, 2 = LCD */
+int docbid, /* NZ to only allow cbid dtypes */
+int tele, /* NZ for tele mode */
+int nadaptive, /* NZ for non-adaptive mode */
+int noinitcal, /* No initial instrument calibration */
+int highres, /* Use high res mode if available */
+int native, /* 0 = use current current or given calibration curve */
+ /* 1 = use native linear out & high precision */
+int *noramdac, /* Return nz if no ramdac access. native is set to 0 */
+double cal[3][MAX_CAL_ENT], /* Calibration set/return (cal[0][0] < 0.0 if can't/not to be used) */
+ /* native must be 0 if cal is set */
+int ncal, /* number of entries use in cal */
+int softcal, /* NZ if apply cal to readings rather than hardware */
+disppath *screen, /* Screen to calibrate. */
+int blackbg, /* NZ if whole screen should be filled with black */
+int override, /* Override_redirect on X11 */
+int webdisp, /* If nz, port number for web display */
+char *ccallout, /* Shell callout on set color */
+char *mcallout, /* Shell callout on measure color (forced fake) */
+double hpatsize, /* Size of dispwin */
+double vpatsize,
+double ho, /* Horizontal offset */
+double vo, /* Vertical offset */
+double ccmtx[3][3], /* Colorimeter Correction matrix, NULL if none */
+xspect *sets, /* CCSS Set of sample spectra, NULL if none */
+int no_sets, /* CCSS Number on set, 0 if none */
+int spectral, /* 1 = Generate spectral info flag, 2 = don't print error if not capable */
+icxObserverType obType, /* Use alternate observer if spectral or CCSS and != icxOT_none */
+xspect custObserver[3], /* Optional custom observer */
+int bdrift, /* Flag, nz for black drift compensation */
+int wdrift, /* Flag, nz for white drift compensation */
+char *fake_name, /* Name of profile to use as a fake device */
+a1log *log /* Verb, debug & error log */
+);
+/* Return a string describing the error code */
+char * disprd_err(int en);
+
+#define DISPSUP_H
+#endif /* DISPSUP_H */
+
diff --git a/spectro/dispwin.c b/spectro/dispwin.c
new file mode 100644
index 0000000..4a6acd6
--- /dev/null
+++ b/spectro/dispwin.c
@@ -0,0 +1,6321 @@
+
+/*
+ * Argyll Color Correction System
+ * Display target patch window
+ *
+ * Author: Graeme W. Gill
+ * Date: 4/10/96
+ *
+ * Copyright 1998 - 2013, Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/* This program displays test patches on a WinNT, MAC OSX or X11 windowing system. */
+
+/* TTBD
+ *
+ * Nice to have option to create non-square test window ?
+ *
+ * Should probably check the display attributes (like visual depth)
+ * and complain if we aren't using 24 bit color or better.
+ *
+ * Ideally should distinguish clearly between not having access to RAMDAC/VideoLuts
+ * (fail) vs. the display not having them at all.
+ *
+ * Is there a >8 bit way of setting frame buffer value on MSWin (see "Quantize")
+ * when using higher bit depth frame buffers ?
+ *
+ * Is there a >8 bit way of getting/setting RAMDAC indexes ?
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <math.h>
+#include <time.h>
+#include <signal.h>
+#ifndef NT
+#include <unistd.h>
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "icc.h"
+#include "numsup.h"
+#include "cgats.h"
+#include "conv.h"
+#include "dispwin.h"
+#include "webwin.h"
+#if defined(UNIX_X11) && defined(USE_UCMM)
+#include "ucmm.h"
+#endif
+
+#ifdef __APPLE__
+
+#include <Foundation/Foundation.h>
+
+#include <AppKit/AppKit.h>
+
+# if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
+# include <copyfile.h>
+# endif
+
+#ifndef CGFLOAT_DEFINED
+#ifdef __LP64__
+typedef double CGFloat;
+#else
+typedef float CGFloat;
+#endif /* defined(__LP64__) */
+#endif /* !CGFLOAT_DEFINED */
+
+#include <IOKit/Graphics/IOGraphicsLib.h>
+
+#if __MAC_OS_X_VERSION_MAX_ALLOWED <= 1060
+/* This wasn't declared in 10.6, although it is needed */
+CFUUIDRef CGDisplayCreateUUIDFromDisplayID (uint32_t displayID);
+#endif /* < 10.6 */
+
+#endif /* __APPLE__ */
+
+#define VERIFY_TOL (1.0/255.0)
+#undef DISABLE_RANDR /* Disable XRandR code */
+
+#undef DEBUG
+//#define STANDALONE_TEST
+
+#ifdef DEBUG
+#define errout stderr
+# define debug(xx) fprintf(errout, xx )
+# define debug2(xx) fprintf xx
+# define debugr(xx) fprintf(errout, xx )
+# define debugr2(xx) fprintf xx
+# define debugrr(xx) fprintf(errout, xx )
+# define debugrr2(xx) fprintf xx
+# define debugrr2l(lev, xx) fprintf xx
+#else
+#define errout stderr
+# define debug(xx)
+# define debug2(xx)
+# define debugr(xx) if (p->ddebug) fprintf(errout, xx )
+# define debugr2(xx) if (p->ddebug) fprintf xx
+# define debugrr(xx) if (callback_ddebug) fprintf(errout, xx )
+# define debugrr2(xx) if (callback_ddebug) fprintf xx
+# define debugrr2l(lev, xx) if (callback_ddebug >= lev) fprintf xx
+#endif
+
+/* ----------------------------------------------- */
+/* Dealing with locating displays */
+
+int callback_ddebug = 0; /* Diagnostic global for get_displays() and get_a_display() */
+ /* and events */
+
+#ifdef NT
+
+#define sleep(secs) Sleep((secs) * 1000)
+
+static BOOL CALLBACK MonitorEnumProc(
+ HMONITOR hMonitor, /* handle to display monitor */
+ HDC hdcMonitor, /* NULL, because EnumDisplayMonitors hdc is NULL */
+ LPRECT lprcMonitor, /* Virtual screen coordinates of this monitor */
+ LPARAM dwData /* Context data */
+) {
+ disppath ***pdisps = (disppath ***)dwData;
+ disppath **disps = *pdisps;
+ MONITORINFOEX pmi;
+ int ndisps = 0;
+
+ debugrr2((errout, "MonitorEnumProc() called with hMonitor = 0x%x\n",hMonitor));
+
+ /* Get some more information */
+ pmi.cbSize = sizeof(MONITORINFOEX);
+ if (GetMonitorInfo(hMonitor, (MONITORINFO *)&pmi) == 0) {
+ debugrr("get_displays failed GetMonitorInfo - ignoring display\n");
+ return TRUE;
+ }
+
+ /* See if it seems to be a pseudo-display */
+ if (strncmp(pmi.szDevice, "\\\\.\\DISPLAYV", 12) == 0) {
+ debugrr("Seems to be invisible pseudo-display - ignoring it\n");
+ return TRUE;
+ }
+
+ /* Add the display to the list */
+ if (disps == NULL) {
+ if ((disps = (disppath **)calloc(sizeof(disppath *), 1 + 1)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ return FALSE;
+ }
+ } else {
+ /* Count current number on list */
+ for (ndisps = 0; disps[ndisps] != NULL; ndisps++)
+ ;
+ if ((disps = (disppath **)realloc(disps,
+ sizeof(disppath *) * (ndisps + 2))) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ return FALSE;
+ }
+ disps[ndisps+1] = NULL; /* End marker */
+ }
+
+ if ((disps[ndisps] = calloc(sizeof(disppath),1)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ return FALSE;
+ }
+
+ if ((disps[ndisps]->name = strdup(pmi.szDevice)) == NULL) {
+ debugrr("malloc failed\n");
+ return FALSE;
+ }
+ disps[ndisps]->prim = (pmi.dwFlags & MONITORINFOF_PRIMARY) ? 1 : 0;
+
+ disps[ndisps]->sx = lprcMonitor->left;
+ disps[ndisps]->sy = lprcMonitor->top;
+ disps[ndisps]->sw = lprcMonitor->right - lprcMonitor->left;
+ disps[ndisps]->sh = lprcMonitor->bottom - lprcMonitor->top;
+
+ disps[ndisps]->description = NULL;
+
+ debugrr2((errout, "MonitorEnumProc() set initial monitor info: %d,%d %d,%d name '%s'\n",disps[ndisps]->sx,disps[ndisps]->sy,disps[ndisps]->sw,disps[ndisps]->sh, disps[ndisps]->name));
+
+ *pdisps = disps;
+ return TRUE;
+}
+
+/* Dynamically linked function support */
+
+BOOL (WINAPI* pEnumDisplayDevices)(PVOID,DWORD,PVOID,DWORD) = NULL;
+
+#if !defined(NTDDI_LONGHORN) || NTDDI_VERSION < NTDDI_LONGHORN
+
+typedef enum {
+ WCS_PROFILE_MANAGEMENT_SCOPE_SYSTEM_WIDE,
+ WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER
+} WCS_PROFILE_MANAGEMENT_SCOPE;
+
+BOOL (WINAPI* pWcsAssociateColorProfileWithDevice)(WCS_PROFILE_MANAGEMENT_SCOPE,PCWSTR,PCWSTR) = NULL;
+BOOL (WINAPI* pWcsDisassociateColorProfileFromDevice)(WCS_PROFILE_MANAGEMENT_SCOPE,PCWSTR,PCWSTR) = NULL;
+
+#endif /* NTDDI_VERSION < NTDDI_LONGHORN */
+
+/* See if we can get the wanted function calls */
+/* return nz if OK */
+static int setup_dyn_calls() {
+ static int dyn_inited = 0;
+
+ if (dyn_inited == 0) {
+ dyn_inited = 1;
+
+ /* EnumDisplayDevicesA was left out of lib32.lib on earlier SDK's ... */
+ pEnumDisplayDevices = (BOOL (WINAPI*)(PVOID,DWORD,PVOID,DWORD)) GetProcAddress(LoadLibrary("USER32"), "EnumDisplayDevicesA");
+ if (pEnumDisplayDevices == NULL)
+ dyn_inited = 0;
+
+ /* Vista calls */
+#if !defined(NTDDI_LONGHORN) || NTDDI_VERSION < NTDDI_LONGHORN
+ pWcsAssociateColorProfileWithDevice = (BOOL (WINAPI*)(WCS_PROFILE_MANAGEMENT_SCOPE,PCWSTR,PCWSTR)) GetProcAddress(LoadLibrary("mscms"), "WcsAssociateColorProfileWithDevice");
+ pWcsDisassociateColorProfileFromDevice = (BOOL (WINAPI*)(WCS_PROFILE_MANAGEMENT_SCOPE,PCWSTR,PCWSTR)) GetProcAddress(LoadLibrary("mscms"), "WcsDisassociateColorProfileFromDevice");
+ /* These are checked individually */
+#endif /* NTDDI_VERSION < NTDDI_LONGHORN */
+ }
+
+ return dyn_inited;
+}
+
+/* Simple up conversion from char string to wchar string */
+/* Return NULL if malloc fails */
+/* ~~~ Note, should probably replace this with mbstowcs() ???? */
+static unsigned short *char2wchar(char *s) {
+ unsigned char *cp;
+ unsigned short *w, *wp;
+
+ if ((w = malloc(sizeof(unsigned short) * (strlen(s) + 1))) == NULL)
+ return w;
+
+ for (cp = (unsigned char *)s, wp = w; ; cp++, wp++) {
+ *wp = *cp; /* Zero extend */
+ if (*cp == 0)
+ break;
+ }
+
+ return w;
+}
+
+#endif /* NT */
+
+
+#if defined(UNIX_X11)
+/* Hack to notice if the error handler has been triggered */
+/* when a function doesn't return a value. */
+
+int g_error_handler_triggered = 0;
+
+/* A noop X11 error handler */
+int null_error_handler(Display *disp, XErrorEvent *ev) {
+ g_error_handler_triggered = 1;
+ return 0;
+}
+#endif /* X11 */
+
+/* Return pointer to list of disppath. Last will be NULL. */
+/* Return NULL on failure. Call free_disppaths() to free up allocation */
+disppath **get_displays() {
+ disppath **disps = NULL;
+
+#ifdef NT
+ DISPLAY_DEVICE dd;
+ char buf[200];
+ int i, j;
+
+ if (setup_dyn_calls() == 0) {
+ debugrr("Dynamic linking to EnumDisplayDevices or Vista AssociateColorProfile failed\n");
+ free_disppaths(disps);
+ return NULL;
+ }
+
+ /* Create an initial list of monitors */
+ /* (It might be better to call pEnumDisplayDevices(NULL, i ..) instead ??, */
+ /* then we can use the StateFlags to distingish monitors not attached to the desktop etc.) */
+ if (EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM)&disps) == 0) {
+ debugrr("EnumDisplayMonitors failed\n");
+ free_disppaths(disps);
+ return NULL;
+ }
+
+ /* Now locate detailed information about displays */
+ for (i = 0; ; i++) {
+ if (disps == NULL || disps[i] == NULL)
+ break;
+
+ dd.cb = sizeof(dd);
+
+ debugrr2((errout, "get_displays about to get monitor information for %d\n",i));
+ /* Get monitor information */
+ for (j = 0; ;j++) {
+ if ((*pEnumDisplayDevices)(disps[i]->name, j, &dd, 0) == 0) {
+ debugrr2((errout,"EnumDisplayDevices failed on '%s' Mon = %d\n",disps[i]->name,j));
+ if (j == 0) {
+ strcpy(disps[i]->monid, ""); /* We won't be able to set a profile */
+ }
+ break;
+ }
+ if (callback_ddebug) {
+ fprintf(errout,"Mon %d, name '%s'\n",j,dd.DeviceName);
+ fprintf(errout,"Mon %d, string '%s'\n",j,dd.DeviceString);
+ fprintf(errout,"Mon %d, flags 0x%x\n",j,dd.StateFlags);
+ fprintf(errout,"Mon %d, id '%s'\n",j,dd.DeviceID);
+ fprintf(errout,"Mon %d, key '%s'\n",j,dd.DeviceKey);
+ }
+ if (j == 0) {
+ strcpy(disps[i]->monid, dd.DeviceID);
+ }
+ }
+
+ sprintf(buf,"%s, at %d, %d, width %d, height %d%s",disps[i]->name+4,
+ disps[i]->sx, disps[i]->sy, disps[i]->sw, disps[i]->sh,
+ disps[i]->prim ? " (Primary Display)" : "");
+
+ if ((disps[i]->description = strdup(buf)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ free_disppaths(disps);
+ return NULL;
+ }
+
+ debugrr2((errout, "get_displays added description '%s' to display %d\n",disps[i]->description,i));
+
+ /* Note that calling EnumDisplayDevices(NULL, j, ..) for the adapter can return other */
+ /* information, such as the graphics card name, and additional state flags. */
+ /* EnumDisplaySettings() can also be called to get information such as display depth etc. */
+ }
+
+#ifdef NEVER
+ /* Explore adapter information */
+ for (j = 0; ; j++) {
+ /* Get adapater information */
+ if ((*pEnumDisplayDevices)(NULL, j, &dd, 0) == 0)
+ break;
+ printf("Adapt %d, name '%s'\n",j,dd.DeviceName);
+ printf("Adapt %d, string '%s'\n",j,dd.DeviceString);
+ printf("Adapt %d, flags 0x%x\n",j,dd.StateFlags);
+ printf("Adapt %d, id '%s'\n",j,dd.DeviceID);
+ printf("Adapt %d, key '%s'\n",j,dd.DeviceKey);
+ }
+#endif /* NEVER */
+
+#endif /* NT */
+
+#ifdef __APPLE__
+ /* Note :- some recent releases of OS X have a feature which */
+ /* automatically adjusts the screen brigtness with ambient level. */
+ /* We may have to find a way of disabling this during calibration and profiling. */
+ /* See the "pset -g" command. */
+
+ /*
+ We could possibly use NSScreen instead of CG here,
+ but we'd need to have a an NSApp first, so perhaps not.
+
+ */
+
+ int i;
+ CGDisplayErr dstat;
+ CGDisplayCount dcount; /* Number of display IDs */
+ CGDirectDisplayID *dids; /* Array of display IDs */
+
+ if ((dstat = CGGetActiveDisplayList(0, NULL, &dcount)) != kCGErrorSuccess || dcount < 1) {
+ debugrr("CGGetActiveDisplayList #1 returned error\n");
+ return NULL;
+ }
+ if ((dids = (CGDirectDisplayID *)malloc(dcount * sizeof(CGDirectDisplayID))) == NULL) {
+ debugrr("malloc of CGDirectDisplayID's failed\n");
+ return NULL;
+ }
+ if ((dstat = CGGetActiveDisplayList(dcount, dids, &dcount)) != kCGErrorSuccess) {
+ debugrr("CGGetActiveDisplayList #2 returned error\n");
+ free(dids);
+ return NULL;
+ }
+
+ /* Found dcount displays */
+ debugrr2((errout,"Found %d screens\n",dcount));
+
+ /* Allocate our list */
+ if ((disps = (disppath **)calloc(sizeof(disppath *), dcount + 1)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ free(dids);
+ return NULL;
+ }
+ for (i = 0; i < dcount; i++) {
+ if ((disps[i] = calloc(sizeof(disppath), 1)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ free_disppaths(disps);
+ free(dids);
+ return NULL;
+ }
+ disps[i]->ddid = dids[i];
+ }
+
+ /* Got displays, now figure out a description for each one */
+ for (i = 0; i < dcount; i++) {
+ CGRect dbound; /* Bounding rectangle of chosen display */
+ io_service_t dport;
+ CFDictionaryRef ddr, pndr;
+ CFIndex dcount;
+ char *dp = NULL, desc[50];
+ char buf[200];
+
+ dbound = CGDisplayBounds(dids[i]);
+ disps[i]->sx = dbound.origin.x;
+ disps[i]->sy = dbound.origin.y;
+ disps[i]->sw = dbound.size.width;
+ disps[i]->sh = dbound.size.height;
+
+ /* Try and get some information about the display */
+ if ((dport = CGDisplayIOServicePort(dids[i])) == MACH_PORT_NULL) {
+ debugrr("CGDisplayIOServicePort returned error\n");
+ free_disppaths(disps);
+ free(dids);
+ return NULL;
+ }
+
+#ifdef NEVER
+ {
+ io_name_t name;
+ if (IORegistryEntryGetName(dport, name) != KERN_SUCCESS) {
+ debugrr("IORegistryEntryGetName returned error\n");
+ free_disppaths(disps);
+ free(dids);
+ return NULL;
+ }
+ printf("Driver %d name = '%s'\n",i,name);
+ }
+#endif
+ if ((ddr = IODisplayCreateInfoDictionary(dport, 0)) == NULL) {
+ debugrr("IODisplayCreateInfoDictionary returned NULL\n");
+ free_disppaths(disps);
+ free(dids);
+ return NULL;
+ }
+ if ((pndr = CFDictionaryGetValue(ddr, CFSTR(kDisplayProductName))) == NULL) {
+ debugrr("CFDictionaryGetValue returned NULL\n");
+ CFRelease(ddr);
+ free_disppaths(disps);
+ free(dids);
+ return NULL;
+ }
+ if ((dcount = CFDictionaryGetCount(pndr)) > 0) {
+ const void **keys;
+ const void **values;
+ int j;
+
+ keys = (const void **)calloc(sizeof(void *), dcount);
+ values = (const void **)calloc(sizeof(void *), dcount);
+ if (keys == NULL || values == NULL) {
+ if (keys != NULL)
+ free(keys);
+ if (values != NULL)
+ free(values);
+ debugrr("malloc failed\n");
+ CFRelease(ddr);
+ free_disppaths(disps);
+ free(dids);
+ return NULL;
+ }
+ CFDictionaryGetKeysAndValues(pndr, keys, values);
+ for (j = 0; j < dcount; j++) {
+ const char *k, *v;
+ char kbuf[50], vbuf[50];
+ k = CFStringGetCStringPtr(keys[j], kCFStringEncodingMacRoman);
+ if (k == NULL) {
+ if (CFStringGetCString(keys[j], kbuf, 50, kCFStringEncodingMacRoman))
+ k = kbuf;
+ }
+ v = CFStringGetCStringPtr(values[j], kCFStringEncodingMacRoman);
+ if (v == NULL) {
+ if (CFStringGetCString(values[j], vbuf, 50, kCFStringEncodingMacRoman))
+ v = vbuf;
+ }
+ /* We're only grabing the english description... */
+ if (k != NULL && v != NULL && strcmp(k, "en_US") == 0) {
+ strncpy(desc, v, 49);
+ desc[49] = '\000';
+ dp = desc;
+ }
+ }
+ free(keys);
+ free(values);
+ }
+ CFRelease(ddr);
+
+ if (dp == NULL) {
+ strcpy(desc, "(unknown)");
+ dp = desc;
+ }
+ sprintf(buf,"%s, at %d, %d, width %d, height %d%s",dp,
+ disps[i]->sx, disps[i]->sy, disps[i]->sw, disps[i]->sh,
+ CGDisplayIsMain(dids[i]) ? " (Primary Display)" : "");
+
+ if ((disps[i]->name = strdup(dp)) == NULL
+ || (disps[i]->description = strdup(buf)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ free_disppaths(disps);
+ free(dids);
+ return NULL;
+ }
+ }
+
+ free(dids);
+#endif /* __APPLE__ */
+
+#if defined(UNIX_X11)
+ int i, j, k;
+ int defsix = 0; /* default screen index */
+ int dcount; /* Number of screens */
+ char *dname;
+ char dnbuf[100];
+ int evb = 0, erb = 0;
+ int majv, minv; /* Version */
+ Display *mydisplay;
+ int ndisps = 0;
+ XineramaScreenInfo *xai = NULL;
+ char desc1[100], desc2[200];
+
+ /* There seems to be no way of getting the available displays */
+ /* on an X11 system. Attempting to open them in sequence */
+ /* takes too long. We just rely on the user supplying the */
+ /* right display. We can enumerate screens though. */
+
+ /* Open the base display, and then enumerate all the screens */
+ if ((dname = getenv("DISPLAY")) != NULL) {
+ char *pp;
+ strncpy(dnbuf,dname,99); dnbuf[99] = '\000';
+ if ((pp = strrchr(dnbuf, ':')) != NULL) {
+ if ((pp = strchr(pp, '.')) == NULL)
+ strcat(dnbuf,".0");
+ else {
+ if (pp[1] == '\000')
+ strcat(dnbuf,"0");
+ else {
+ pp[1] = '0';
+ pp[2] = '\000';
+ }
+ }
+ }
+ } else
+ strcpy(dnbuf,":0.0");
+
+ if ((mydisplay = XOpenDisplay(dnbuf)) == NULL) {
+ debugrr2((errout, "failed to open display '%s'\n",dnbuf));
+ return NULL;
+ }
+
+#if RANDR_MAJOR == 1 && RANDR_MINOR >= 2 && !defined(DISABLE_RANDR)
+ /* Use Xrandr 1.2 if it's available, and if it's not disabled */
+ if (getenv("ARGYLL_IGNORE_XRANDR1_2") == NULL
+ && XRRQueryExtension(mydisplay, &evb, &erb) != 0
+ && XRRQueryVersion(mydisplay, &majv, &minv)
+ && majv == 1 && minv >= 2) {
+
+ if (XSetErrorHandler(null_error_handler) == 0) {
+ debugrr("get_displays failed on XSetErrorHandler\n");
+ XCloseDisplay(mydisplay);
+ free_disppaths(disps);
+ return NULL;
+ }
+
+ dcount = ScreenCount(mydisplay);
+
+ /* Go through all the screens */
+ for (i = 0; i < dcount; i++) {
+ XRRScreenResources *scrnres;
+ int jj; /* Screen index */
+
+ if ((scrnres = XRRGetScreenResources(mydisplay, RootWindow(mydisplay,i))) == NULL) {
+ debugrr("XRRGetScreenResources failed\n");
+ XCloseDisplay(mydisplay);
+ free_disppaths(disps);
+ return NULL;
+ }
+
+ /* Look at all the screens outputs */
+ for (jj = j = 0; j < scrnres->noutput; j++) {
+ XRROutputInfo *outi;
+ XRRCrtcInfo *crtci;
+
+ if ((outi = XRRGetOutputInfo(mydisplay, scrnres, scrnres->outputs[j])) == NULL) {
+ debugrr("XRRGetOutputInfo failed\n");
+ XRRFreeScreenResources(scrnres);
+ XCloseDisplay(mydisplay);
+ free_disppaths(disps);
+ return NULL;
+ }
+
+ if (outi->connection == RR_Disconnected ||
+ outi->crtc == None) {
+ continue;
+ }
+
+ /* Check that the VideoLUT's are accessible */
+ {
+ XRRCrtcGamma *crtcgam;
+
+ debugrr("Checking XRandR 1.2 VideoLUT access\n");
+ if ((crtcgam = XRRGetCrtcGamma(mydisplay, outi->crtc)) == NULL
+ || crtcgam->size == 0) {
+ fprintf(stderr,"XRandR 1.2 is faulty - falling back to older extensions\n");
+ if (crtcgam != NULL)
+ XRRFreeGamma(crtcgam);
+ free_disppaths(disps);
+ disps = NULL;
+ j = scrnres->noutput;
+ i = dcount;
+ continue; /* Abort XRandR 1.2 */
+ }
+ }
+#ifdef NEVER
+ {
+ Atom *oprops;
+ int noprop;
+
+ /* Get a list of the properties of the output */
+ oprops = XRRListOutputProperties(mydisplay, scrnres->outputs[j], &noprop);
+
+ printf("num props = %d\n", noprop);
+ for (k = 0; k < noprop; k++) {
+ printf("%d: atom 0x%x, name = '%s'\n", k, oprops[k], XGetAtomName(mydisplay, oprops[k]));
+ }
+ }
+#endif /* NEVER */
+
+ if ((crtci = XRRGetCrtcInfo(mydisplay, scrnres, outi->crtc)) != NULL) {
+ char *pp;
+
+ /* Add the output to the list */
+ if (disps == NULL) {
+ if ((disps = (disppath **)calloc(sizeof(disppath *), 1 + 1)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ XRRFreeCrtcInfo(crtci);
+ XRRFreeScreenResources(scrnres);
+ XCloseDisplay(mydisplay);
+ return NULL;
+ }
+ } else {
+ if ((disps = (disppath **)realloc(disps,
+ sizeof(disppath *) * (ndisps + 2))) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ XRRFreeCrtcInfo(crtci);
+ XRRFreeScreenResources(scrnres);
+ XCloseDisplay(mydisplay);
+ return NULL;
+ }
+ disps[ndisps+1] = NULL; /* End marker */
+ }
+ /* ndisps is current display we're filling in */
+ if ((disps[ndisps] = calloc(sizeof(disppath),1)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ XRRFreeCrtcInfo(crtci);
+ XRRFreeScreenResources(scrnres);
+ XCloseDisplay(mydisplay);
+ free_disppaths(disps);
+ return NULL;
+ }
+
+ disps[ndisps]->screen = i;
+ disps[ndisps]->uscreen = i;
+ disps[ndisps]->rscreen = i;
+ disps[ndisps]->sx = crtci->x;
+ disps[ndisps]->sy = crtci->y;
+ disps[ndisps]->sw = crtci->width;
+ disps[ndisps]->sh = crtci->height;
+ disps[ndisps]->crtc = outi->crtc; /* XID of crtc */
+ disps[ndisps]->output = scrnres->outputs[j]; /* XID of output */
+
+ sprintf(desc1,"Screen %d, Output %s",ndisps+1,outi->name);
+ sprintf(desc2,"%s at %d, %d, width %d, height %d",desc1,
+ disps[ndisps]->sx, disps[ndisps]->sy, disps[ndisps]->sw, disps[ndisps]->sh);
+
+ /* See if it is a clone */
+ for (k = 0; k < ndisps; k++) {
+ if (disps[k]->crtc == disps[ndisps]->crtc) {
+ sprintf(desc1, "[ Clone of %d ]",k+1);
+ strcat(desc2, desc1);
+ }
+ }
+ if ((disps[ndisps]->description = strdup(desc2)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ XRRFreeCrtcInfo(crtci);
+ XRRFreeScreenResources(scrnres);
+ XCloseDisplay(mydisplay);
+ free_disppaths(disps);
+ return NULL;
+ }
+
+ /* Form the display name */
+ if ((pp = strrchr(dnbuf, ':')) != NULL) {
+ if ((pp = strchr(pp, '.')) != NULL) {
+ sprintf(pp,".%d",i);
+ }
+ }
+ if ((disps[ndisps]->name = strdup(dnbuf)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ XRRFreeCrtcInfo(crtci);
+ XRRFreeScreenResources(scrnres);
+ XCloseDisplay(mydisplay);
+ free_disppaths(disps);
+ return NULL;
+ }
+ debugrr2((errout, "Display %d name = '%s'\n",ndisps,disps[ndisps]->name));
+
+ /* Create the X11 root atom of the default screen */
+ /* that may contain the associated ICC profile */
+ /* (The _%d variant will probably break with non-Xrandr */
+ /* aware software if Xrandr is configured to have more than */
+ /* a single virtual screen.) */
+ if (jj == 0)
+ strcpy(desc1, "_ICC_PROFILE");
+ else
+ sprintf(desc1, "_ICC_PROFILE_%d",jj);
+
+ if ((disps[ndisps]->icc_atom = XInternAtom(mydisplay, desc1, False)) == None)
+ error("Unable to intern atom '%s'",desc1);
+
+ /* Create the atom of the output that may contain the associated ICC profile */
+ if ((disps[ndisps]->icc_out_atom = XInternAtom(mydisplay, "_ICC_PROFILE", False)) == None)
+ error("Unable to intern atom '%s'","_ICC_PROFILE");
+
+ /* Grab the EDID from the output */
+ {
+ Atom edid_atom, ret_type;
+ int ret_format;
+ long ret_len = 0, ret_togo;
+ unsigned char *atomv = NULL;
+ int ii;
+ char *keys[] = { /* Possible keys that may be used */
+ "EDID_DATA",
+ "EDID",
+ ""
+ };
+
+ /* Try each key in turn */
+ for (ii = 0; keys[ii][0] != '\000'; ii++) {
+ /* Get the atom for the EDID data */
+ if ((edid_atom = XInternAtom(mydisplay, keys[ii], True)) == None) {
+ debugrr2((errout, "Unable to intern atom '%s'\n",keys[ii]));
+ /* Try the next key */
+ } else {
+
+ /* Get the EDID_DATA */
+ if (XRRGetOutputProperty(mydisplay, scrnres->outputs[j], edid_atom,
+ 0, 0x7ffffff, False, False, XA_INTEGER,
+ &ret_type, &ret_format, &ret_len, &ret_togo, &atomv) == Success
+ && (ret_len == 128 || ret_len == 256)) {
+ if ((disps[ndisps]->edid = malloc(sizeof(unsigned char) * ret_len)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ XRRFreeCrtcInfo(crtci);
+ XRRFreeScreenResources(scrnres);
+ XCloseDisplay(mydisplay);
+ free_disppaths(disps);
+ return NULL;
+ }
+ memmove(disps[ndisps]->edid, atomv, ret_len);
+ disps[ndisps]->edid_len = ret_len;
+ XFree(atomv);
+ debugrr2((errout, "Got EDID for display\n"));
+ break;
+ }
+ /* Try the next key */
+ }
+ }
+ if (keys[ii][0] == '\000')
+ debugrr2((errout, "Failed to get EDID for display\n"));
+ }
+
+ jj++; /* Next enabled index */
+ ndisps++; /* Now it's number of displays */
+ XRRFreeCrtcInfo(crtci);
+ }
+ XRRFreeOutputInfo(outi);
+ }
+
+ XRRFreeScreenResources(scrnres);
+ }
+ XSetErrorHandler(NULL);
+ defsix = DefaultScreen(mydisplay);
+ }
+#endif /* randr >= V 1.2 */
+
+ if (disps == NULL) { /* Use Older style identification */
+ debugrr("get_displays checking for Xinerama\n");
+
+ if (XSetErrorHandler(null_error_handler) == 0) {
+ debugrr("get_displays failed on XSetErrorHandler\n");
+ XCloseDisplay(mydisplay);
+ return NULL;
+ }
+
+ if (XineramaQueryExtension(mydisplay, &evb, &erb) != 0
+ && XineramaIsActive(mydisplay)) {
+
+ xai = XineramaQueryScreens(mydisplay, &dcount);
+
+ if (xai == NULL || dcount == 0) {
+ debugrr("XineramaQueryScreens failed\n");
+ XCloseDisplay(mydisplay);
+ return NULL;
+ }
+ defsix = 0;
+ } else {
+ dcount = ScreenCount(mydisplay);
+ defsix = DefaultScreen(mydisplay);
+ }
+
+ /* Allocate our list */
+ if ((disps = (disppath **)calloc(sizeof(disppath *), dcount + 1)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ XCloseDisplay(mydisplay);
+ return NULL;
+ }
+ for (i = 0; i < dcount; i++) {
+ if ((disps[i] = calloc(sizeof(disppath), 1)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ free_disppaths(disps);
+ XCloseDisplay(mydisplay);
+ return NULL;
+ }
+ }
+
+ /* Create a description for each screen */
+ for (i = 0; i < dcount; i++) {
+ XF86VidModeMonitor monitor;
+ int evb = 0, erb = 0;
+ char *pp;
+
+ /* Form the display name */
+ if ((pp = strrchr(dnbuf, ':')) != NULL) {
+ if ((pp = strchr(pp, '.')) != NULL) {
+ sprintf(pp,".%d",i);
+ }
+ }
+ if ((disps[i]->name = strdup(dnbuf)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ free_disppaths(disps);
+ XCloseDisplay(mydisplay);
+ return NULL;
+ }
+
+ debugrr2((errout, "Display %d name = '%s'\n",i,disps[i]->name));
+ if (xai != NULL) { /* Xinerama */
+ disps[i]->screen = 0; /* We are asuming Xinerame creates a single virtual screen */
+ disps[i]->uscreen = i; /* We are assuming xinerama lists screens in the same order */
+ disps[i]->rscreen = i;
+ disps[i]->sx = xai[i].x_org;
+ disps[i]->sy = xai[i].y_org;
+ disps[i]->sw = xai[i].width;
+ disps[i]->sh = xai[i].height;
+ } else {
+ disps[i]->screen = i;
+ disps[i]->uscreen = i;
+ disps[i]->rscreen = i;
+ disps[i]->sx = 0; /* Must be 0 */
+ disps[i]->sy = 0;
+ disps[i]->sw = DisplayWidth(mydisplay, disps[i]->screen);
+ disps[i]->sh = DisplayHeight(mydisplay, disps[i]->screen);
+ }
+
+ /* Create the X11 root atom of the default screen */
+ /* that may contain the associated ICC profile */
+ if (disps[i]->uscreen == 0)
+ strcpy(desc1, "_ICC_PROFILE");
+ else
+ sprintf(desc1, "_ICC_PROFILE_%d",disps[i]->uscreen);
+
+ if ((disps[i]->icc_atom = XInternAtom(mydisplay, desc1, False)) == None)
+ error("Unable to intern atom '%s'",desc1);
+
+ /* See if we can locate the EDID of the monitor for this screen */
+ for (j = 0; j < 2; j++) {
+ char edid_name[50];
+ Atom edid_atom, ret_type;
+ int ret_format = 8;
+ long ret_len, ret_togo;
+ unsigned char *atomv = NULL;
+
+ if (disps[i]->uscreen == 0) {
+ if (j == 0)
+ strcpy(edid_name,"XFree86_DDC_EDID1_RAWDATA");
+ else
+ strcpy(edid_name,"XFree86_DDC_EDID2_RAWDATA");
+ } else {
+ if (j == 0)
+ sprintf(edid_name,"XFree86_DDC_EDID1_RAWDATA_%d",disps[i]->uscreen);
+ else
+ sprintf(edid_name,"XFree86_DDC_EDID2_RAWDATA_%d",disps[i]->uscreen);
+ }
+
+ if ((edid_atom = XInternAtom(mydisplay, edid_name, True)) == None)
+ continue;
+ if (XGetWindowProperty(mydisplay, RootWindow(mydisplay, disps[i]->uscreen), edid_atom,
+ 0, 0x7ffffff, False, XA_INTEGER,
+ &ret_type, &ret_format, &ret_len, &ret_togo, &atomv) == Success
+ && (ret_len == 128 || ret_len == 256)) {
+ if ((disps[i]->edid = malloc(sizeof(unsigned char) * ret_len)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ free_disppaths(disps);
+ XCloseDisplay(mydisplay);
+ return NULL;
+ }
+ memmove(disps[i]->edid, atomv, ret_len);
+ disps[i]->edid_len = ret_len;
+ XFree(atomv);
+ debugrr2((errout, "Got EDID for display\n"));
+ break;
+ } else {
+ debugrr2((errout, "Failed to get EDID for display\n"));
+ }
+ }
+
+ if (XF86VidModeQueryExtension(mydisplay, &evb, &erb) != 0) {
+ /* Some propietary multi-screen drivers (ie. TwinView & MergeFB) */
+ /* don't implement the XVidMode extension properly. */
+ monitor.model = NULL;
+ if (XF86VidModeGetMonitor(mydisplay, disps[i]->uscreen, &monitor) != 0
+ && monitor.model != NULL && monitor.model[0] != '\000')
+ sprintf(desc1, "%s",monitor.model);
+ else
+ sprintf(desc1,"Screen %d",i+1);
+ } else
+ sprintf(desc1,"Screen %d",i+1);
+
+ sprintf(desc2,"%s at %d, %d, width %d, height %d",desc1,
+ disps[i]->sx, disps[i]->sy, disps[i]->sw, disps[i]->sh);
+ if ((disps[i]->description = strdup(desc2)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ free_disppaths(disps);
+ XCloseDisplay(mydisplay);
+ return NULL;
+ }
+ }
+ XSetErrorHandler(NULL);
+ }
+
+ /* Put the screen given by the display name at the top */
+ {
+ disppath *tdispp;
+ tdispp = disps[defsix];
+ disps[defsix] = disps[0];
+ disps[0] = tdispp;
+ }
+
+ if (xai != NULL)
+ XFree(xai);
+
+ XCloseDisplay(mydisplay);
+
+#endif /* UNIX X11 */
+
+ return disps;
+}
+
+/* Free a whole list of display paths */
+void free_disppaths(disppath **disps) {
+ if (disps != NULL) {
+ int i;
+ for (i = 0; ; i++) {
+ if (disps[i] == NULL)
+ break;
+
+ if (disps[i]->name != NULL)
+ free(disps[i]->name);
+ if (disps[i]->description != NULL)
+ free(disps[i]->description);
+#if defined(UNIX_X11)
+ if (disps[i]->edid != NULL)
+ free(disps[i]->edid);
+#endif
+ free(disps[i]);
+ }
+ free(disps);
+ }
+}
+
+/* Delete a single display from the list of display paths */
+void del_disppath(disppath **disps, int ix) {
+ if (disps != NULL) {
+ int i, j, k;
+ for (i = 0; ; i++) {
+ if (disps[i] == NULL)
+ break;
+
+ if (i == ix) { /* One to delete */
+ if (disps[i]->name != NULL)
+ free(disps[i]->name);
+ if (disps[i]->description != NULL)
+ free(disps[i]->description);
+#if defined(UNIX_X11)
+ if (disps[i]->edid != NULL)
+ free(disps[i]->edid);
+#endif
+ free(disps[i]);
+
+ /* Shuffle the rest down */
+ for (j = i, k = i + 1; ;j++, k++) {
+ disps[j] = disps[k];
+ if (disps[k] == NULL)
+ break;
+ }
+ return;
+ }
+ }
+ }
+}
+
+/* ----------------------------------------------- */
+/* Deal with selecting a display */
+
+/* Return the given display given its index 0..n-1 */
+disppath *get_a_display(int ix) {
+ disppath **paths, *rv = NULL;
+ int i;
+
+ if ((paths = get_displays()) == NULL)
+ return NULL;
+
+ for (i = 0; ;i++) {
+ if (paths[i] == NULL) {
+ free_disppaths(paths);
+ return NULL;
+ }
+ if (i == ix)
+ break;
+ }
+ if ((rv = malloc(sizeof(disppath))) == NULL) {
+ debugrr("get_a_display failed malloc\n");
+ free_disppaths(paths);
+ return NULL;
+ }
+ *rv = *paths[i]; /* Structure copy */
+ if ((rv->name = strdup(paths[i]->name)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ free(rv->description);
+ free(rv);
+ free_disppaths(paths);
+ return NULL;
+ }
+ if ((rv->description = strdup(paths[i]->description)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ free(rv);
+ free_disppaths(paths);
+ return NULL;
+ }
+#if defined(UNIX_X11)
+ if (paths[i]->edid != NULL) {
+ if ((rv->edid = malloc(sizeof(unsigned char) * paths[i]->edid_len)) == NULL) {
+ debugrr("get_displays failed on malloc\n");
+ free(rv);
+ free_disppaths(paths);
+ return NULL;
+ }
+ rv->edid_len = paths[i]->edid_len;
+ memmove(rv->edid, paths[i]->edid, rv->edid_len );
+ }
+#endif
+ free_disppaths(paths);
+ return rv;
+}
+
+void free_a_disppath(disppath *path) {
+ if (path != NULL) {
+ if (path->name != NULL)
+ free(path->name);
+ if (path->description != NULL)
+ free(path->description);
+#if defined(UNIX_X11)
+ if (path->edid != NULL)
+ free(path->edid);
+#endif
+ free(path);
+ }
+}
+
+/* ----------------------------------------------- */
+
+static ramdac *dispwin_clone_ramdac(ramdac *r);
+static void dispwin_setlin_ramdac(ramdac *r);
+static void dispwin_del_ramdac(ramdac *r);
+
+/* For VideoLUT/RAMDAC use, we assume that the number of entries in the RAMDAC */
+/* meshes perfectly with the display raster depth, so that we can */
+/* figure out how to apportion device values. We fail if they don't */
+/* seem to mesh. */
+
+/* !!! Would be nice to add an error message return to dispwin and */
+/* !!! pass errors back to it so that the detail can be reported */
+/* !!! to the user. */
+
+/* Get RAMDAC values. ->del() when finished. */
+/* Return NULL if not possible */
+static ramdac *dispwin_get_ramdac(dispwin *p) {
+ ramdac *r = NULL;
+ int i, j;
+
+#ifdef NT
+ WORD vals[3][256]; /* 256 x 16 bit elements (Quantize) */
+
+ debugr("dispwin_get_ramdac called\n");
+
+#ifdef NEVER /* Doesn't seem to return correct information on win2K systems */
+ if ((GetDeviceCaps(p->hdc, COLORMGMTCAPS) & CM_GAMMA_RAMP) == 0) {
+ debugr("dispwin_get_ramdac failed on GetDeviceCaps(CM_GAMMA_RAMP)\n");
+ return NULL;
+ }
+#endif
+
+ /* Allocate a ramdac */
+ if ((r = (ramdac *)calloc(sizeof(ramdac), 1)) == NULL) {
+ debugr("dispwin_get_ramdac failed on malloc()\n");
+ return NULL;
+ }
+ r->pdepth = p->pdepth;
+ r->nent = (1 << p->pdepth);
+ r->clone = dispwin_clone_ramdac;
+ r->setlin = dispwin_setlin_ramdac;
+ r->del = dispwin_del_ramdac;
+
+ for (j = 0; j < 3; j++) {
+
+ if ((r->v[j] = (double *)calloc(sizeof(double), r->nent)) == NULL) {
+ for (j--; j >= 0; j--)
+ free(r->v[j]);
+ free(r);
+ debugr("dispwin_get_ramdac failed on malloc()\n");
+ return NULL;
+ }
+ }
+
+ /* GetDeviceGammaRamp() is hard coded for 3 x 256 entries (Quantize) */
+ if (r->nent != 256) {
+ debugr2((errout,"GetDeviceGammaRamp() is hard coded for nent == 256, and we've got nent = %d!\n",r->nent));
+ return NULL;
+ }
+
+ if (GetDeviceGammaRamp(p->hdc, vals) == 0) {
+ debugr("dispwin_get_ramdac failed on GetDeviceGammaRamp()\n");
+ return NULL;
+ }
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < r->nent; i++) {
+ r->v[j][i] = vals[j][i]/65535.0;
+ }
+ }
+#endif /* NT */
+
+#ifdef __APPLE__
+ unsigned int nent;
+ CGGammaValue vals[3][16385];
+
+ debugr("dispwin_get_ramdac called\n");
+
+ if (CGGetDisplayTransferByTable(p->ddid, 163845, vals[0], vals[1], vals[2], &nent) != 0) {
+ debugr("CGGetDisplayTransferByTable failed\n");
+ return NULL;
+ }
+
+ if (nent == 16385) { /* oops - we didn't provide enought space! */
+ debugr("CGGetDisplayTransferByTable has more entries than we can handle\n");
+ return NULL;
+ }
+
+ if (nent != (1 << p->pdepth)) {
+ debugr("CGGetDisplayTransferByTable number of entries mismatches screen depth\n");
+ return NULL;
+ }
+
+ /* Allocate a ramdac */
+ if ((r = (ramdac *)calloc(sizeof(ramdac), 1)) == NULL) {
+ debugr("dispwin_get_ramdac failed on malloc()\n");
+ return NULL;
+ }
+
+ r->pdepth = p->pdepth;
+ r->nent = (1 << p->pdepth);
+ r->clone = dispwin_clone_ramdac;
+ r->setlin = dispwin_setlin_ramdac;
+ r->del = dispwin_del_ramdac;
+ for (j = 0; j < 3; j++) {
+
+ if ((r->v[j] = (double *)calloc(sizeof(double), r->nent)) == NULL) {
+ for (j--; j >= 0; j--)
+ free(r->v[j]);
+ free(r);
+ debugr("dispwin_get_ramdac failed on malloc()\n");
+ return NULL;
+ }
+ }
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < r->nent; i++) {
+ r->v[j][i] = vals[j][i];
+ }
+ }
+#endif /* __APPLE__ */
+
+#if defined(UNIX_X11)
+ unsigned short vals[3][16384];
+ int nent = 0;
+ int evb = 0, erb = 0;
+
+ debugr("dispwin_get_ramdac called\n");
+
+#if RANDR_MAJOR == 1 && RANDR_MINOR >= 2 && !defined(DISABLE_RANDR)
+ if (p->crtc != 0) { /* Using Xrandr 1.2 */
+ XRRCrtcGamma *crtcgam;
+ int nz = 0;
+
+ debugr("Getting gamma using Randr 1.2\n");
+
+ if ((crtcgam = XRRGetCrtcGamma(p->mydisplay, p->crtc)) == NULL) {
+ debugr("XRRGetCrtcGamma failed\n");
+ return NULL;
+ }
+
+ nent = crtcgam->size;
+
+ if (nent > 16384) {
+ debugr("XRRGetCrtcGammaSize has more entries than we can handle\n");
+ return NULL;
+ }
+
+ if (nent != (1 << p->pdepth)) {
+ debugr2((errout,"XRRGetCrtcGammaSize number of entries %d mismatches screen depth %d\n",nent,(1 << p->pdepth)));
+ return NULL;
+ }
+
+ /* Check for XRandR 1.2 startup bug */
+ for (i = 0; i < nent; i++) {
+ vals[0][i] = crtcgam->red[i];
+ vals[1][i] = crtcgam->green[i];
+ vals[2][i] = crtcgam->blue[i];
+ nz = vals[0][i] | vals[1][i] | vals[2][i];
+ }
+
+ /* Compensate for XRandR 1.2 startup bug */
+ if (nz == 0) {
+ debugr("Detected XRandR 1.2 bug ? Assuming linear ramp!\n");
+ for (i = 0; i < nent; i++) {
+ for (j = 0; j < 3; j++)
+ vals[j][i] = (int)(65535.0 * i/(nent-1.0) + 0.5);
+ }
+ }
+
+ XRRFreeGamma(crtcgam);
+
+ } else
+#endif /* randr >= V 1.2 */
+ {
+
+ if (XF86VidModeQueryExtension(p->mydisplay, &evb, &erb) == 0) {
+ debugr("XF86VidModeQueryExtension failed\n");
+ return NULL;
+ }
+ /* Some propietary multi-screen drivers (ie. TwinView & MergedFB) */
+ /* don't implement the XVidMode extenstion properly. */
+ if (XSetErrorHandler(null_error_handler) == 0) {
+ debugr("get_displays failed on XSetErrorHandler\n");
+ return NULL;
+ }
+ nent = -1;
+ if (XF86VidModeGetGammaRampSize(p->mydisplay, p->myrscreen, &nent) == 0
+ || nent == -1) {
+ XSetErrorHandler(NULL);
+ debugr("XF86VidModeGetGammaRampSize failed\n");
+ return NULL;
+ }
+ XSetErrorHandler(NULL); /* Restore handler */
+ if (nent == 0) {
+ debugr("XF86VidModeGetGammaRampSize returned 0 size\n");
+ return NULL;
+ }
+
+ if (nent > 16384) {
+ debugr("XF86VidModeGetGammaRampSize has more entries than we can handle\n");
+ return NULL;
+ }
+
+ if (XF86VidModeGetGammaRamp(p->mydisplay, p->myrscreen, nent, vals[0], vals[1], vals[2]) == 0) {
+ debugr("XF86VidModeGetGammaRamp failed\n");
+ return NULL;
+ }
+
+ if (nent != (1 << p->pdepth)) {
+ debugr2((errout,"CGGetDisplayTransferByTable number of entries %d mismatches screen depth %d\n",nent,(1 << p->pdepth)));
+ return NULL;
+ }
+ }
+
+ /* Allocate a ramdac */
+ if ((r = (ramdac *)calloc(sizeof(ramdac), 1)) == NULL) {
+ debugr("dispwin_get_ramdac failed on malloc()\n");
+ return NULL;
+ }
+
+ r->pdepth = p->pdepth;
+ r->nent = (1 << p->pdepth);
+ r->clone = dispwin_clone_ramdac;
+ r->setlin = dispwin_setlin_ramdac;
+ r->del = dispwin_del_ramdac;
+ for (j = 0; j < 3; j++) {
+
+ if ((r->v[j] = (double *)calloc(sizeof(double), r->nent)) == NULL) {
+ for (j--; j >= 0; j--)
+ free(r->v[j]);
+ free(r);
+ debugr("dispwin_get_ramdac failed on malloc()\n");
+ return NULL;
+ }
+ }
+
+ for (i = 0; i < r->nent; i++) {
+ for (j = 0; j < 3; j++) {
+ r->v[j][i] = vals[j][i]/65535.0;
+ }
+ }
+#endif /* UNXI X11 */
+ debugr("dispwin_get_ramdac returning OK\n");
+ return r;
+}
+
+#ifdef __APPLE__
+ /* Various support functions */
+
+#if __MAC_OS_X_VERSION_MAX_ALLOWED < 1060
+
+/* Given a location, return a string for it's path */
+static char *plocpath(CMProfileLocation *ploc) {
+
+ if (ploc->locType == cmFileBasedProfile) {
+ FSRef newRef;
+ UInt8 path[256] = "";
+
+ /* Note that there is no non-deprecated equivalent to this. */
+ /* Apple need to remove the cmFileBasedProfile type from the */
+ /* CMProfileLocation to do away with it. */
+ if (FSpMakeFSRef(&ploc->u.fileLoc.spec, &newRef) == noErr) {
+ OSStatus stus;
+ if ((stus = FSRefMakePath(&newRef, path, 256)) == 0 || stus == fnfErr)
+ return strdup((char *)path);
+ return NULL;
+ }
+ } else if (ploc->locType == cmPathBasedProfile) {
+ return strdup(ploc->u.pathLoc.path);
+ }
+ return NULL;
+}
+
+/* Ugh! ColorSync doesn't take care of endian issues !! */
+static void cs_w32(unsigned long *p, unsigned long val) {
+ ((char *)p)[0] = (char)(val >> 24);
+ ((char *)p)[1] = (char)(val >> 16);
+ ((char *)p)[2] = (char)(val >> 8);
+ ((char *)p)[3] = (char)(val);
+}
+
+static void cs_w16(unsigned short *p, unsigned short val) {
+ ((char *)p)[0] = (char)(val >> 8);
+ ((char *)p)[1] = (char)(val);
+}
+
+#endif /* < 10.6 */
+
+#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
+
+/* There doesn't seem to be any means of determining the locations */
+/* of profiles using current OS X API's, so we simply hard code them. */
+/* This makes the older code easier too. */
+
+#define COLORSYNC_DIR_NETWORK "/Network/Library/ColorSync/Profiles/"
+#define COLORSYNC_DIR_SYSTEM "/System/Library/ColorSync/Profiles/"
+#define COLORSYNC_DIR_LOCAL "/Library/ColorSync/Profiles/"
+#define COLORSYNC_DIR_USER "/Library/ColorSync/Profiles/"
+
+/* Given a profile name and a scope, return the path to the */
+/* installed profile. free the returned string when done. */
+/* Returns NULL on error */
+static char *iprof_path(p_scope scope, char *fname) {
+ char *home = "", *dirname, *basename, *rv = NULL;
+ int tlen = 0;
+
+ /* Locate the base filename in the fname */
+ for (basename = fname + strlen(fname); ; basename--) {
+ if (basename <= fname || basename[-1] == '/')
+ break;
+ }
+
+ /* NSFileManager's URLForDirectory: etc. doesn't have ColorSync, */
+ /* so we have no choice but to hard code the paths */
+ if (scope == p_scope_network)
+ dirname = COLORSYNC_DIR_NETWORK;
+ else if (scope == p_scope_system)
+ dirname = COLORSYNC_DIR_SYSTEM;
+ else if (scope == p_scope_local)
+ dirname = COLORSYNC_DIR_LOCAL;
+ else {
+ dirname = COLORSYNC_DIR_USER;
+ if ((home = getenv("HOME")) == NULL){
+ return NULL;
+ }
+ }
+
+ tlen = strlen(home) + strlen(dirname) + strlen(basename) + 1;
+ if ((rv = malloc(tlen)) == NULL) {
+ return NULL;
+ }
+
+ strcpy(rv, home);
+ strcat(rv, dirname);
+ strcat(rv, basename);
+
+ return rv;
+}
+
+/* Callback */
+typedef struct {
+ CFUUIDRef dispuuid; /* UUID to match */
+ CFStringRef id; /* ProfileId */
+ CFURLRef url; /* URL to return */
+} diter_cntx_t;
+
+bool diter_callback(CFDictionaryRef dict, void *cntx) {
+ diter_cntx_t *cx = (diter_cntx_t *)cntx;
+ CFStringRef str;
+ CFUUIDRef uuid;
+ CFBooleanRef iscur;
+
+ if ((str = CFDictionaryGetValue(dict, kColorSyncDeviceClass)) == NULL) {
+ debugrr("Failed to get kColorSyncDeviceClass\n");
+ return true;
+ }
+ if (!CFEqual(str, kColorSyncDisplayDeviceClass)) {
+ return true;
+ }
+ if ((uuid = CFDictionaryGetValue(dict, kColorSyncDeviceID)) == NULL) {
+ debugrr("Failed to get kColorSyncDeviceID\n");
+ return true;
+ }
+ if (!CFEqual(uuid, cx->dispuuid)) {
+ return true;
+ }
+ if ((iscur = CFDictionaryGetValue(dict, kColorSyncDeviceProfileIsCurrent)) == NULL) {
+ debugrr("Failed to get kColorSyncDeviceProfileIsCurrent\n");
+ return true;
+ }
+ if (!CFBooleanGetValue(iscur)) {
+ return true;
+ }
+
+ /* get the URL */
+ if ((cx->id = CFDictionaryGetValue(dict, kColorSyncDeviceProfileID)) == NULL) {
+ debugrr("Failed to get current profile ID\n");
+ return true;
+ }
+ if ((cx->url = CFDictionaryGetValue(dict, kColorSyncDeviceProfileURL)) == NULL) {
+ debugrr("Failed to get current profile URL\n");
+ return true;
+ }
+ CFRetain(cx->id);
+ CFRetain(cx->url);
+
+ return false;
+}
+
+/* Return the url to the given displays current profile. */
+/* Return NULL on error. CFRelease when done. */
+/* Optionally return the ProfileID string */
+CFURLRef cur_profile_url(CFStringRef *idp, dispwin *p) {
+ diter_cntx_t cx;
+
+ if ((cx.dispuuid = CGDisplayCreateUUIDFromDisplayID(p->ddid)) == NULL) {
+ debugr2((errout,"CGDisplayCreateUUIDFromDisplayID() failed\n"));
+ return NULL;
+ }
+ cx.id = NULL;
+ cx.url = NULL;
+
+ ColorSyncIterateDeviceProfiles(diter_callback, (void *)&cx);
+
+ CFRelease(cx.dispuuid);
+
+ if (idp != NULL)
+ *idp = cx.id;
+ else
+ CFRelease(cx.id);
+ return cx.url;
+}
+
+/* Convert a URL into a local POSIX path string */
+/* Return NULL on error. Free returned string when done. */
+char *url_to_path(CFURLRef url) {
+ CFStringRef urlstr;
+ CFIndex bufSize;
+ char *dpath = NULL; /* return value */
+
+ urlstr = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
+ bufSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(urlstr),
+ kCFStringEncodingUTF8) + 1;
+ if ((dpath = malloc(bufSize)) == NULL) {
+ debugrr("url_to_path: malloc failed\n");
+ CFRelease(urlstr);
+ return NULL;
+ }
+ if (!CFStringGetCString(urlstr, dpath, bufSize, kCFStringEncodingUTF8)) {
+ debugrr("url_to_path: CFStringGetCString failed\n");
+ CFRelease(urlstr);
+ return NULL;
+ }
+ CFRelease(urlstr);
+
+ return dpath;
+}
+
+/* Return information about the given displays current profile */
+/* Return NULL on error. Free returned string when done. */
+char *cur_profile(dispwin *p) {
+ CFURLRef url;
+ char *dpath = NULL; /* return value */
+
+ if ((url = cur_profile_url(NULL, p)) == NULL) {
+ debugr2((errout,"cur_profile failed to find current profile\n"));
+ return NULL;
+ }
+
+ dpath = url_to_path(url);
+
+ CFRelease(url);
+
+ return dpath;
+}
+
+#endif /* >= 10.6 */
+
+#endif /* __APPLE__ */
+
+/* Set the RAMDAC values. */
+/* Return nz if not possible */
+/* Return 2 for OS X when the current profile is a system profile */
+static int dispwin_set_ramdac(dispwin *p, ramdac *r, int persist) {
+ int i, j;
+
+#ifdef NT
+ WORD vals[3][256]; /* 16 bit elements */
+
+ debugr("dispwin_set_ramdac called\n");
+
+#ifdef NEVER /* Doesn't seem to return correct information on win2K systems */
+ if ((GetDeviceCaps(p->hdc, COLORMGMTCAPS) & CM_GAMMA_RAMP) == 0) {
+ debugr("dispwin_set_ramdac failed on GetDeviceCaps(CM_GAMMA_RAMP)\n");
+ return 1;
+ }
+#endif
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < r->nent; i++) {
+ double vv = r->v[j][i];
+ if (vv < 0.0)
+ vv = 0.0;
+ else if (vv > 1.0)
+ vv = 1.0;
+ vals[j][i] = (int)(65535.0 * vv + 0.5);
+ }
+ }
+
+ if (SetDeviceGammaRamp(p->hdc, vals) == 0) {
+ debugr2((errout,"dispwin_set_ramdac failed on SetDeviceGammaRamp() with error %d\n",GetLastError()));
+ return 1;
+ }
+#endif /* NT */
+
+#ifdef __APPLE__
+ { /* Transient first */
+ CGGammaValue vals[3][16384];
+
+ debugr("dispwin_set_ramdac called\n");
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < r->nent; i++) {
+ double vv = r->v[j][i];
+ if (vv < 0.0)
+ vv = 0.0;
+ else if (vv > 1.0)
+ vv = 1.0;
+ vals[j][i] = vv;
+ }
+ }
+
+ if (CGSetDisplayTransferByTable(p->ddid, r->nent, vals[0], vals[1], vals[2]) != 0) {
+ debugr("CGSetDisplayTransferByTable failed\n");
+ return 1;
+ }
+
+ }
+
+ /* In theory IOFBSetGamma() might work - but maybe it needs root, and won't */
+ /* sync with OS X's view of what's loaded. */
+
+ /* By default the OSX RAMDAC access is transient, lasting only as long */
+ /* as the process setting it. To set a temporary but persistent beyond this process */
+ /* calibration, we fake up a profile and install it in such a way that it will disappear, */
+ /* restoring the previous display profile whenever the current ColorSync display profile */
+ /* is restored to the screen. NOTE that this trick will fail if it is not possible */
+ /* to rename the currently selected profile file, ie. because it is a system profile. */
+ /* [ Would a workaround be to use a link, or copy of the system file ? ] */
+ if (persist)
+#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
+ if (persist) { /* Persistent */
+ int rv = 0;
+ CFStringRef id;
+ CFURLRef url;
+ char *tpath; /* Temporary profiles/original profiles path */
+ char *ppath; /* Current/renamed profiles path */
+ icmFile *rd_fp, *wr_fp;
+ icc *icco;
+ CFUUIDRef dispuuid;
+ CFStringRef keys[1];
+ CFURLRef values[1];
+ CFDictionaryRef dict;
+
+ debugr("Set_ramdac persist\n");
+
+ /* Get the current installed profile */
+ if ((url = cur_profile_url(&id, p)) == NULL) {
+ debugr2((errout,"cur_profile_url failed for current profile\n"));
+ return 1;
+ }
+ if ((tpath = url_to_path(url)) == NULL) {
+ debugr2((errout,"url_to_path failed for current profile\n"));
+ CFRelease(id);
+ CFRelease(url);
+ return 1;
+ }
+
+ debugr2((errout, "Current profile path = '%s'\n",tpath));
+
+ /* Create a patched version with our calibration: */
+ /* (Hmm. I think icclib will cope with V4 OK) */
+
+ /* Open up the profile for reading */
+ if ((rd_fp = new_icmFileStd_name(tpath,"r")) == NULL) {
+ debugr2((errout,"Failed to open profile '%s'\n",tpath));
+ free(tpath);
+ CFRelease(id);
+ CFRelease(url);
+ return 1;
+ }
+
+ if ((icco = new_icc()) == NULL) {
+ debugr2((errout,"Creation of ICC object failed\n"));
+ rd_fp->del(rd_fp);
+ free(tpath);
+ CFRelease(id);
+ CFRelease(url);
+ return 1;
+ }
+
+ /* Read header etc. */
+ if ((rv = icco->read(icco,rd_fp,0)) != 0) {
+ debugr2((errout,"%d, %s",rv,icco->err));
+ icco->del(icco);
+ rd_fp->del(rd_fp);
+ free(tpath);
+ CFRelease(id);
+ CFRelease(url);
+ return 1;
+ }
+ /* Read every tag */
+ if (icco->read_all_tags(icco) != 0) {
+ debugr2((errout,"Unable to read all tags: %d, %s",icco->errc,icco->err));
+ icco->del(icco);
+ rd_fp->del(rd_fp);
+ free(tpath);
+ CFRelease(id);
+ CFRelease(url);
+ return 1;
+ }
+
+ rd_fp->del(rd_fp); rd_fp = NULL;
+
+ /* Replace the description */
+ {
+ icmTextDescription *wo;
+ char *dst = "Dispwin Temp";
+
+ if (icco->find_tag(icco, icSigProfileDescriptionTag) == 0) {
+ if (icco->delete_tag(icco, icSigProfileDescriptionTag) != 0) {
+ debugr2((errout,"Unable to delete Description tag: %d, %s",icco->errc,icco->err));
+ icco->del(icco);
+ free(tpath);
+ CFRelease(id);
+ CFRelease(url);
+ return 1;
+ }
+ }
+
+ if ((wo = (icmTextDescription *)icco->add_tag(
+ icco, icSigProfileDescriptionTag, icSigTextDescriptionType)) == NULL) {
+ debugr2((errout,"Unable to add Description tag: %d, %s",icco->errc,icco->err));
+ icco->del(icco);
+ free(tpath);
+ CFRelease(id);
+ CFRelease(url);
+ return 1;
+ }
+
+ wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */
+ wo->allocate((icmBase *)wo);/* Allocate space */
+ strcpy(wo->desc, dst); /* Copy the string in */
+ }
+ /* Replace the vcgt */
+ {
+ int c,i;
+ icmVideoCardGamma *wo;
+
+ if (icco->find_tag(icco, icSigVideoCardGammaTag) == 0) {
+ if (icco->delete_tag(icco, icSigVideoCardGammaTag) != 0) {
+ debugr2((errout,"Unable to delete VideoCardGamma tag: %d, %s",icco->errc,icco->err));
+ icco->del(icco);
+ free(tpath);
+ CFRelease(id);
+ CFRelease(url);
+ return 1;
+ }
+ }
+
+ if ((wo = (icmVideoCardGamma *)icco->add_tag(icco, icSigVideoCardGammaTag,
+ icSigVideoCardGammaType)) == NULL) {
+ debugr2((errout,"Unable to add VideoCardGamma tag: %d, %s",icco->errc,icco->err));
+ icco->del(icco);
+ free(tpath);
+ CFRelease(id);
+ CFRelease(url);
+ return 1;
+ }
+
+ wo->tagType = icmVideoCardGammaTableType;
+ wo->u.table.channels = 3; /* rgb */
+ wo->u.table.entryCount = r->nent; /* number of calibration entries */
+ wo->u.table.entrySize = 2; /* 16 bits */
+ wo->allocate((icmBase*)wo);
+
+ for (i = 0; i < r->nent; i++) {
+ for (j = 0; j < 3; j++) {
+ double vv = r->v[j][i];
+ int ivv;
+ if (vv < 0.0)
+ vv = 0.0;
+ else if (vv > 1.0)
+ vv = 1.0;
+ ((unsigned short*)wo->u.table.data)[r->nent * j + i] = (int)(vv * 65535.0 + 0.5);
+ }
+ }
+ }
+
+ if ((ppath = malloc(strlen(tpath) + 6)) == NULL) {
+ debugr2((errout,"malloc failed for display '%s'\n",p->name));
+ icco->del(icco);
+ free(tpath);
+ CFRelease(id);
+ CFRelease(url);
+ return 1;
+ }
+ strcpy(ppath, tpath);
+ strcat(ppath,".orig");
+
+ /* Rename the currently installed profile temporarily. */
+ /* This will fail if current profile is a system profile and not writable by the user. */
+ /* This could be worked around by cloning the system profile to the user */
+ /* area and installing it before modifiying it. */
+ if (rename(tpath, ppath) != 0) {
+ debugr2((errout,"Unable to rename '%s' to '%s'\n",tpath,ppath));
+ icco->del(icco);
+ free(ppath);
+ free(tpath);
+ CFRelease(id);
+ CFRelease(url);
+ return 2;
+ }
+
+ /* Rename worked */
+
+ debugr2((errout,"Renamed current profile '%s' to '%s'\n",tpath,ppath));
+
+ /* Save the modified profile to the original profile name */
+ if ((wr_fp = new_icmFileStd_name(tpath,"w")) == NULL) {
+ debugr2((errout,"Failed to open '%s' for writing\n",tpath));
+ free(ppath);
+ icco->del(icco);
+ free(tpath);
+ CFRelease(id);
+ CFRelease(url);
+ return 1;
+ }
+
+ if ((rv = icco->write(icco,wr_fp,0)) != 0) {
+ debugr2((errout,"Write file: %d, %s",rv,icco->err));
+ free(ppath);
+ wr_fp->del(wr_fp);
+ icco->del(icco);
+ free(tpath);
+ CFRelease(id);
+ CFRelease(url);
+ return 1;
+ }
+
+ icco->del(icco);
+ wr_fp->del(wr_fp);
+
+ /* Update to the "current" profile, which is actually the modified profile */
+ if ((dispuuid = CGDisplayCreateUUIDFromDisplayID(p->ddid)) == NULL) {
+ debugr2((errout,"CGDisplayCreateUUIDFromDisplayID() failed\n"));
+ free(ppath);
+ free(tpath);
+ CFRelease(id);
+ CFRelease(url);
+ return 1;
+ }
+
+ keys[0] = id;
+ values[0] = url;
+
+ if ((dict = CFDictionaryCreate(kCFAllocatorDefault, (const void **)&keys,
+ (const void **)&values, 1, NULL, NULL)) == NULL) {
+ debugr2((errout,"CFDictionaryCreate() failed\n"));
+ CFRelease(dispuuid);
+ free(ppath);
+ free(tpath);
+ CFRelease(id);
+ CFRelease(url);
+ return 1;
+ }
+ if (!ColorSyncDeviceSetCustomProfiles(kColorSyncDisplayDeviceClass, dispuuid, dict)) {
+ debugr2((errout,"ColorSyncDeviceSetCustomProfiles() failed\n"));
+ CFRelease(dict);
+ CFRelease(dispuuid);
+ free(ppath);
+ free(tpath);
+ CFRelease(id);
+ CFRelease(url);
+ return 1;
+ }
+ CFRelease(dict);
+ CFRelease(dispuuid);
+ CFRelease(id);
+ CFRelease(url);
+
+ /* Delete the temporary profile */
+ unlink(tpath);
+
+ /* Rename the current profile back to it's correct name */
+ if (rename(ppath, tpath) != 0) {
+ debugr2((errout,"Renaming existing profile '%s' failed\n",ppath));
+ return 1;
+ }
+ debugr2((errout,"Restored '%s' back to '%s'\n",ppath,tpath));
+ free(ppath);
+ free(tpath);
+ }
+#else /* < 10.6 */
+ if (persist) { /* Persistent */
+ CMError ev;
+ CMProfileRef prof; /* Current AVID profile */
+ CMProfileLocation ploc; /* Current profile location */
+ UInt32 plocsz = sizeof(CMProfileLocation);
+ char *ppath; /* Current/renamed profiles path */
+ char *tpath; /* Temporary profiles/original profiles path */
+ CMProfileRef tprof; /* Temporary profile */
+ CMProfileLocation tploc; /* Temporary profile */
+ CMVideoCardGammaType *vcgt = NULL; /* vcgt tag */
+ int size;
+ int i, j;
+
+ debugr("Set_ramdac persist\n");
+
+ /* Get the current installed profile */
+ if ((ev = CMGetProfileByAVID((CMDisplayIDType)p->ddid, &prof)) != noErr) {
+ debugr2((errout,"CMGetProfileByAVID() failed for display '%s' with error %d\n",p->name,ev));
+ return 1;
+ }
+
+ /* Get the current installed profile's location */
+ if ((ev = NCMGetProfileLocation(prof, &ploc, &plocsz)) != noErr) {
+ debugr2((errout,"NCMGetProfileLocation() failed for display '%s' with error %d\n",p->name,ev));
+ return 1;
+ }
+
+ debugr2((errout, "Current profile path = '%s'\n",plocpath(&ploc)));
+
+ if ((tpath = plocpath(&ploc)) == NULL) {
+ debugr2((errout,"plocpath failed for display '%s'\n",p->name));
+ return 1;
+ }
+
+ if (strlen(tpath) > 255) {
+ debugr2((errout,"current profile path is too long\n"));
+ return 1;
+ }
+ if ((ppath = malloc(strlen(tpath) + 6)) == NULL) {
+ debugr2((errout,"malloc failed for display '%s'\n",p->name));
+ free(tpath);
+ return 1;
+ }
+ strcpy(ppath,tpath);
+ strcat(ppath,".orig");
+
+ /* Rename the currently installed profile temporarily */
+ if (rename(tpath, ppath) != 0) {
+ debugr2((errout,"Renaming existing profile '%s' failed\n",ppath));
+ return 2;
+ }
+ debugr2((errout,"Renamed current profile '%s' to '%s'\n",tpath,ppath));
+
+ /* Make a copy of the renamed current profile back to it's true name */
+ tploc.locType = cmPathBasedProfile;
+ strncpy(tploc.u.pathLoc.path, tpath, 255);
+ tploc.u.pathLoc.path[255] = '\000';
+
+ /* Make the temporary copy */
+ if ((ev = CMCopyProfile(&tprof, &tploc, prof)) != noErr) {
+ debugr2((errout,"CMCopyProfile() failed for display '%s' with error %d\n",p->name,ev));
+ CMCloseProfile(prof);
+ unlink(tpath);
+ rename(ppath, tpath);
+ return 1;
+ }
+ CMCloseProfile(prof);
+
+ if ((ev = CMSetProfileDescriptions(tprof, "Dispwin Temp", 13, NULL, 0, NULL, 0)) != noErr) {
+ debugr2((errout,"cmVideoCardGammaTag`() failed for display '%s' with error %d\n",p->name,ev));
+ CMCloseProfile(tprof);
+ unlink(tpath);
+ rename(ppath, tpath);
+ return 1;
+ }
+
+ /* Change the description and set the vcgt tag to the calibration */
+ if ((vcgt = malloc(size = (sizeof(CMVideoCardGammaType) - 1 + 3 * 2 * r->nent))) == NULL) {
+ debugr2((errout,"malloc of vcgt tag failed for display '%s' with error %d\n",p->name,ev));
+ CMCloseProfile(tprof);
+ unlink(tpath);
+ rename(ppath, tpath);
+ return 1;
+ }
+ cs_w32(&vcgt->typeDescriptor, cmSigVideoCardGammaType);
+ cs_w32(&vcgt->gamma.tagType, cmVideoCardGammaTableType); /* Table, not formula */
+ cs_w16(&vcgt->gamma.u.table.channels, 3);
+ cs_w16(&vcgt->gamma.u.table.entryCount, r->nent);
+ cs_w16(&vcgt->gamma.u.table.entrySize, 2);
+
+ for (i = 0; i < r->nent; i++) {
+ for (j = 0; j < 3; j++) {
+ double vv = r->v[j][i];
+ int ivv;
+ if (vv < 0.0)
+ vv = 0.0;
+ else if (vv > 1.0)
+ vv = 1.0;
+ ivv = (int)(vv * 65535.0 + 0.5);
+ cs_w16(((unsigned short *)vcgt->gamma.u.table.data) + ((j * r->nent) + i), ivv);
+ }
+ }
+
+ /* Replace or add a vcgt tag */
+ if ((ev = CMSetProfileElement(tprof, cmVideoCardGammaTag, size, vcgt)) != noErr) {
+ debugr2((errout,"CMSetProfileElement vcgt tag failed with error %d\n",ev));
+ free(vcgt);
+ CMCloseProfile(tprof);
+ unlink(tpath);
+ rename(ppath, tpath);
+ return 1;
+ }
+ free(vcgt);
+
+ if ((ev = CMUpdateProfile(tprof)) != noErr) {
+ debugr2((errout,"CMUpdateProfile failed with error %d\n",ev));
+ CMCloseProfile(tprof);
+ unlink(tpath);
+ rename(ppath, tpath);
+ return 1;
+ }
+
+ /* Make temporary file the current profile - updates LUTs */
+ if ((ev = CMSetProfileByAVID((CMDisplayIDType)p->ddid, tprof)) != noErr) {
+ debugr2((errout,"CMSetProfileByAVID() failed for display '%s' with error %d\n",p->name,ev));
+ CMCloseProfile(tprof);
+ unlink(tpath);
+ rename(ppath, tpath);
+ return 1;
+ }
+ CMCloseProfile(tprof);
+ debugr2((errout,"Set display to use temporary profile '%s'\n",tpath));
+
+ /* Delete the temporary profile */
+ unlink(tpath);
+
+ /* Rename the current profile back to it's correct name */
+ if (rename(ppath, tpath) != 0) {
+ debugr2((errout,"Renaming existing profile '%s' failed\n",ppath));
+ return 1;
+ }
+ debugr2((errout,"Restored '%s' back to '%s'\n",ppath,tpath));
+ }
+#endif /* < 10.6 */
+
+#endif /* __APPLE__ */
+
+#if defined(UNIX_X11)
+ unsigned short vals[3][16384];
+
+ debugr("dispwin_set_ramdac called\n");
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < r->nent; i++) {
+ double vv = r->v[j][i];
+ if (vv < 0.0)
+ vv = 0.0;
+ else if (vv > 1.0)
+ vv = 1.0;
+ vals[j][i] = (int)(vv * 65535.0 + 0.5);
+ }
+ }
+
+#if RANDR_MAJOR == 1 && RANDR_MINOR >= 2 && !defined(DISABLE_RANDR)
+ if (p->crtc != 0) { /* Using Xrandr 1.2 */
+ XRRCrtcGamma *crtcgam;
+
+ debugr("Setting gamma using Randr 1.2\n");
+
+ if ((crtcgam = XRRAllocGamma(r->nent)) == NULL) {
+ debugr(" XRRAllocGamma failed\n");
+ return 1;
+ }
+
+ for (i = 0; i < r->nent; i++) {
+ crtcgam->red[i] = vals[0][i];
+ crtcgam->green[i] = vals[1][i];
+ crtcgam->blue[i] = vals[2][i];
+ }
+
+ XRRSetCrtcGamma(p->mydisplay, p->crtc, crtcgam);
+ XSync(p->mydisplay, False); /* Flush the change out */
+
+ XRRFreeGamma(crtcgam);
+
+ } else
+#endif /* randr >= V 1.2 */
+ {
+ /* Some propietary multi-screen drivers (ie. TwinView & MergedFB) */
+ /* don't implement the XVidMode extenstion properly. */
+ if (XSetErrorHandler(null_error_handler) == 0) {
+ debugr("get_displays failed on XSetErrorHandler\n");
+ return 1;
+ }
+ if (XF86VidModeSetGammaRamp(p->mydisplay, p->myrscreen, r->nent, vals[0], vals[1], vals[2]) == 0) {
+ XSetErrorHandler(NULL);
+ debugr("XF86VidModeSetGammaRamp failed\n");
+ return 1;
+ }
+ XSync(p->mydisplay, False); /* Flush the change out */
+ XSetErrorHandler(NULL);
+ }
+#endif /* UNXI X11 */
+
+ debugr("XF86VidModeSetGammaRamp returning OK\n");
+ return 0;
+}
+
+
+/* Clone ourselves */
+static ramdac *dispwin_clone_ramdac(ramdac *r) {
+ ramdac *nr;
+ int i, j;
+
+ debug("dispwin_clone_ramdac called\n");
+
+ /* Allocate a ramdac */
+ if ((nr = (ramdac *)calloc(sizeof(ramdac), 1)) == NULL) {
+ return NULL;
+ }
+
+ *nr = *r; /* Structrure copy */
+
+ for (j = 0; j < 3; j++) {
+
+ if ((nr->v[j] = (double *)calloc(sizeof(double), r->nent)) == NULL) {
+ for (j--; j >= 0; j--)
+ free(nr->v[j]);
+ free(nr);
+ return NULL;
+ }
+ }
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < r->nent; i++) {
+ nr->v[j][i] = r->v[j][i];
+ }
+ }
+
+ debug("clone is done\n");
+ return nr;
+}
+
+/* Set the ramdac values to linear */
+static void dispwin_setlin_ramdac(ramdac *r) {
+ int i, j;
+
+ debug("dispwin_setlin_ramdac called\n");
+
+ for (i = 0; i < r->nent; i++) {
+ double val = i/(r->nent - 1.0);
+ for (j = 0; j < 3; j++) {
+ r->v[j][i] = val;
+ }
+ }
+}
+
+/* We're done with a ramdac structure */
+static void dispwin_del_ramdac(ramdac *r) {
+ int j;
+
+ debug("dispwin_del_ramdac called\n");
+
+ for (j = 0; j < 3; j++) {
+ free(r->v[j]);
+ }
+
+ free(r);
+}
+
+/* ----------------------------------------------- */
+/* Useful function for X11 profie atom settings */
+
+#if defined(UNIX_X11)
+/* Return NZ on error */
+static int set_X11_atom(dispwin *p, char *fname) {
+ FILE *fp;
+ unsigned long psize, bread;
+ unsigned char *atomv;
+
+ debugr("Setting _ICC_PROFILE property\n");
+
+ /* Read in the ICC profile, then set the X11 atom value */
+#if !defined(O_CREAT) && !defined(_O_CREAT)
+# error "Need to #include fcntl.h!"
+#endif
+#if defined(O_BINARY) || defined(_O_BINARY)
+ if ((fp = fopen(fname,"rb")) == NULL)
+#else
+ if ((fp = fopen(fname,"r")) == NULL)
+#endif
+ {
+ debugr2((errout,"Can't open file '%s'\n",fname));
+ return 1;
+ }
+
+ /* Figure out how big it is */
+ if (fseek(fp, 0, SEEK_END)) {
+ debugr2((errout,"Seek '%s' to EOF failed\n",fname));
+ return 1;
+ }
+ psize = (unsigned long)ftell(fp);
+
+ if (fseek(fp, 0, SEEK_SET)) {
+ debugr2((errout,"Seek '%s' to SOF failed\n",fname));
+ return 1;
+ }
+
+ if ((atomv = (unsigned char *)malloc(psize)) == NULL) {
+ debugr2((errout,"Failed to allocate buffer for profile '%s'\n",fname));
+ return 1;
+ }
+
+ if ((bread = fread(atomv, 1, psize, fp)) != psize) {
+ debugr2((errout,"Failed to read profile '%s' into buffer\n",fname));
+ return 1;
+ }
+
+ fclose(fp);
+
+ XChangeProperty(p->mydisplay, RootWindow(p->mydisplay, 0), p->icc_atom,
+ XA_CARDINAL, 8, PropModeReplace, atomv, psize);
+
+#if RANDR_MAJOR == 1 && RANDR_MINOR >= 2 && !defined(DISABLE_RANDR)
+ if (p->icc_out_atom != 0) {
+ /* If Xrandr 1.2, set property on output */
+ /* This seems to fail on some servers. Ignore the error ? */
+ if (XSetErrorHandler(null_error_handler) == 0) {
+ debugr("get_displays failed on XSetErrorHandler\n");
+ return 1;
+ }
+ g_error_handler_triggered = 0;
+ XRRChangeOutputProperty(p->mydisplay, p->output, p->icc_out_atom,
+ XA_CARDINAL, 8, PropModeReplace, atomv, psize);
+ if (g_error_handler_triggered != 0) {
+ debugr("XRRChangeOutputProperty failed\n");
+ warning("Unable to set _ICC_PROFILE property on output");
+ }
+ XSync(p->mydisplay, False); /* Flush the change out */
+ XSetErrorHandler(NULL);
+ }
+#endif /* randr >= V 1.2 */
+ free(atomv);
+
+ return 0;
+}
+#endif /* UNXI X11 */
+
+/* ----------------------------------------------- */
+/* Install a display profile and make */
+/* it the default for this display. */
+/* Set the display to the calibration in the profile */
+/* (r == NULL if no calibration) */
+/* (We assume that the caller has checked that it's an ICC profile) */
+/* Return nz if failed */
+int dispwin_install_profile(dispwin *p, char *fname, ramdac *r, p_scope scope) {
+#ifdef NT
+ {
+ char *fullpath;
+ char *basename;
+ char colpath[MAX_PATH];
+ unsigned long colpathlen = MAX_PATH;
+ WCS_PROFILE_MANAGEMENT_SCOPE wcssc;
+ unsigned short *wpath, *wbname, *wmonid;
+
+ debugr2((errout,"dispwin_install_profile got '%s'\n",fname));
+
+ if (GetColorDirectory(NULL, colpath, &colpathlen) == 0) {
+ debugr2((errout,"Getting color directory failed\n"));
+ return 1;
+ }
+
+ if ((fullpath = _fullpath(NULL, fname, 0)) == NULL) {
+ debugr2((errout,"_fullpath() failed\n"));
+ return 1;
+ }
+
+ if ((basename = PathFindFileName(fullpath)) == NULL) {
+ debugr2((errout,"Locating base name in '%s' failed\n",fname));
+ free(fullpath);
+ return 1;
+ }
+
+ if ((strlen(colpath) + strlen(basename) + 2) > MAX_PATH) {
+ debugr2((errout,"Installed profile path too long\n"));
+ free(fullpath);
+ return 1;
+ }
+ strcat(colpath, "\\");
+ strcat(colpath, basename);
+
+ /* Setup in case we're on Vista */
+ if (scope == p_scope_user)
+ wcssc = WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER;
+ else
+ wcssc = WCS_PROFILE_MANAGEMENT_SCOPE_SYSTEM_WIDE;
+
+ if ((wpath = char2wchar(fullpath)) == NULL) {
+ debugr2((errout,"char2wchar failed\n"));
+ free(fullpath);
+ return 1;
+ }
+
+ if ((wbname = char2wchar(basename)) == NULL) {
+ debugr2((errout,"char2wchar failed\n"));
+ free(wpath);
+ free(fullpath);
+ return 1;
+ }
+
+ if ((wmonid = char2wchar(p->monid)) == NULL) {
+ debugr2((errout,"char2wchar failed\n"));
+ free(wbname);
+ free(wpath);
+ free(fullpath);
+ return 1;
+ }
+
+ debugr2((errout,"Installing '%s'\n",fname));
+
+ /* Install doesn't replace an existing installed profile, */
+ /* so we need to try and delete this profile first */
+ if (pWcsDisassociateColorProfileFromDevice != NULL) {
+ (*pWcsDisassociateColorProfileFromDevice)(wcssc, wbname, wmonid);
+ } else {
+ DisassociateColorProfileFromDevice(NULL, basename, p->monid);
+ }
+ if (UninstallColorProfile(NULL, basename, TRUE) == 0) {
+ /* UninstallColorProfile fails on Win2K */
+ _unlink(colpath);
+ }
+
+ if (InstallColorProfile(NULL, fullpath) == 0) {
+ debugr2((errout,"InstallColorProfile() failed for file '%s' with error %d\n",fname,GetLastError()));
+ free(wmonid);
+ free(wbname);
+ free(wpath);
+ free(fullpath);
+ return 1;
+ }
+
+ debugr2((errout,"Associating '%s' with '%s'\n",fullpath,p->monid));
+ if (pWcsAssociateColorProfileWithDevice != NULL) {
+ debugr("Using Vista Associate\n");
+ if ((*pWcsAssociateColorProfileWithDevice)(wcssc, wpath, wmonid) == 0) {
+ debugr2((errout,"WcsAssociateColorProfileWithDevice() failed for file '%s' with error %d\n",fullpath,GetLastError()));
+ free(wmonid);
+ free(wbname);
+ free(wpath);
+ free(fullpath);
+ return 1;
+ }
+ } else {
+ if (AssociateColorProfileWithDevice(NULL, fullpath, p->monid) == 0) {
+ debugr2((errout,"AssociateColorProfileWithDevice() failed for file '%s' with error %d\n",fullpath,GetLastError()));
+ free(wmonid);
+ free(wbname);
+ free(wpath);
+ free(fullpath);
+ return 1;
+ }
+ }
+
+ free(wmonid);
+ free(wbname);
+ free(wpath);
+ free(fullpath);
+ /* The default profile will be the last one associated */
+
+ /* MSWindows doesn't generally set the display to the current profile calibration, */
+ /* so we do it. */
+ if (p->set_ramdac(p,r,1))
+ error("Failed to set VideoLUT");
+
+ return 0;
+ }
+#endif /* NT */
+
+/* For Linux and OS X, make sure we don't create a file with the wrong owner */
+#if defined(UNIX)
+ /* If we're creating a user profile and running as root sudo */
+ if (scope == p_scope_user && geteuid() == 0) {
+ char *uids, *gids;
+ int uid, gid;
+
+ debugr("We're setting a user profile running as root - run as user\n");
+ if ((uids = getenv("SUDO_UID")) != NULL
+ && (gids = getenv("SUDO_GID")) != NULL) {
+ uid = atoi(uids);
+ gid = atoi(gids);
+ if (setegid(gid) || seteuid(uid)) {
+ debugr("seteuid or setegid failed\n");
+ }
+ debug2((errout,"Set euid %d and egid %d\n",uid,gid));
+ }
+ /* If setting local system profile and not effective root, but sudo */
+ } else if (scope != p_scope_user && getuid() == 0 && geteuid() != 0) {
+ if (getenv("SUDO_UID") != NULL
+ && getenv("SUDO_GID") != NULL) {
+
+ debugr("We're setting a system profile running as user - revert to root\n");
+ setegid(getgid());
+ seteuid(getuid());
+ }
+ }
+#endif /* OS X || Linux */
+
+#ifdef __APPLE__
+
+#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
+ {
+ char *dpath; /* Install file path */
+ CFUUIDRef dispuuid;
+ CFStringRef cfprofpath;
+ CFStringRef keys[1];
+ CFURLRef values[1];
+ CFDictionaryRef dict;
+
+ if ((dispuuid = CGDisplayCreateUUIDFromDisplayID(p->ddid)) == NULL) {
+ debugr2((errout,"CGDisplayCreateUUIDFromDisplayID() failed\n"));
+ return 1;
+ }
+
+ /* Determine the location the profile will be installed into */
+ if ((dpath = iprof_path(scope, fname)) == NULL) {
+ debugr2((errout,"iprof_path() failed\n"));
+ CFRelease(dispuuid);
+ return 1;
+ }
+
+ debugr2((errout,"Source profile '%s'\n",fname));
+ debugr2((errout,"Destination profile '%s'\n",dpath));
+
+ /* Copy the new profile to the destination */
+ if (copyfile(fname, dpath, NULL, COPYFILE_ALL) != 0) {
+ debugr2((errout,"copyfile failed\n"));
+ free(dpath);
+ CFRelease(dispuuid);
+ return 1;
+ }
+
+ /* Register it with the OS and make it the default */
+ if ((cfprofpath = CFStringCreateWithCString(kCFAllocatorDefault, dpath,
+ kCFStringEncodingUTF8)) == NULL) {
+ debugr2((errout,"CFStringCreateWithCString() failed\n"));
+ free(dpath);
+ CFRelease(dispuuid);
+ return 1;
+ }
+ free(dpath); dpath = NULL;
+
+ keys[0] = kColorSyncDeviceDefaultProfileID;
+ if ((values[0] = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, cfprofpath,
+ kCFURLPOSIXPathStyle, false)) == NULL) {
+ debugr2((errout,"CFURLCreateWithFileSystemPath() failed\n"));
+ CFRelease(cfprofpath);
+ CFRelease(dispuuid);
+ return 1;
+ }
+
+ if ((dict = CFDictionaryCreate(kCFAllocatorDefault, (const void **)&keys,
+ (const void **)&values, 1, NULL, NULL)) == NULL) {
+ debugr2((errout,"CFDictionaryCreate() failed\n"));
+ CFRelease(values[0]);
+ CFRelease(cfprofpath);
+ CFRelease(dispuuid);
+ return 1;
+ }
+ if (!ColorSyncDeviceSetCustomProfiles(kColorSyncDisplayDeviceClass, dispuuid, dict)) {
+ debugr2((errout,"ColorSyncDeviceSetCustomProfiles() failed\n"));
+ CFRelease(dict);
+ CFRelease(values[0]);
+ CFRelease(cfprofpath);
+ CFRelease(dispuuid);
+ return 1;
+ }
+ CFRelease(dict);
+ CFRelease(values[0]);
+ CFRelease(cfprofpath);
+ CFRelease(dispuuid);
+
+ return 0;
+ }
+#else /* 10.6 and prior */
+ // Switch to using iprof_path() to simplify ?
+ {
+ CMError ev;
+ short vref;
+ FSRef dirref;
+ char dpath[FILENAME_MAX];
+ char *basename;
+
+ CMProfileLocation ploc; /* Source profile location */
+ CMProfileRef prof; /* Source profile */
+ CMProfileLocation dploc; /* Destinaion profile location */
+ CMProfileRef dprof; /* Destinaion profile */
+
+ if (scope == p_scope_network)
+ vref = kNetworkDomain;
+ else if (scope == p_scope_system)
+ vref = kSystemDomain;
+ else if (scope == p_scope_local)
+ vref = kLocalDomain;
+ else
+ vref = kUserDomain;
+
+ /* Locate the appropriate ColorSync path */
+ if ((ev = FSFindFolder(vref, kColorSyncProfilesFolderType, kCreateFolder, &dirref)) != noErr) {
+ debugr2((errout,"FSFindFolder() failed with error %d\n",ev));
+ return 1;
+ }
+
+ /* Convert to POSIX path */
+ if ((ev = FSRefMakePath(&dirref, (unsigned char *)dpath, FILENAME_MAX)) != noErr) {
+ debugr2((errout,"FSRefMakePath failed with error %d\n",ev));
+ return 1;
+ }
+
+ /* Locate the base filename in the fname */
+ for (basename = fname + strlen(fname); ; basename--) {
+ if (basename <= fname || basename[-1] == '/')
+ break;
+ }
+
+ /* Append the basename to the ColorSync directory path */
+ if ((strlen(dpath) + strlen(basename) + 2) > FILENAME_MAX
+ || (strlen(dpath) + strlen(basename) + 2) > 256) {
+ debugr2((errout,"ColorSync dir + profile name too long\n"));
+ return 1;
+ }
+ strcat(dpath, "/");
+ strcat(dpath, basename);
+
+ debugr2((errout,"Source profile '%s'\n",fname));
+ debugr2((errout,"Destination profile '%s'\n",dpath));
+
+ /* Open the profile we want to install */
+ ploc.locType = cmPathBasedProfile;
+ strncpy(ploc.u.pathLoc.path, fname, 255);
+ ploc.u.pathLoc.path[255] = '\000';
+
+ if ((ev = CMOpenProfile(&prof, &ploc)) != noErr) {
+ debugr2((errout,"CMOpenProfile() failed for file '%s' with error %d\n",fname,ev));
+ return 1;
+ }
+
+ /* Delete any current profile */
+ unlink(dpath);
+
+ /* Make a copy of it to the ColorSync directory */
+ dploc.locType = cmPathBasedProfile;
+ strncpy(dploc.u.pathLoc.path, dpath, 255);
+ dploc.u.pathLoc.path[255] = '\000';
+
+ if ((ev = CMCopyProfile(&dprof, &dploc, prof)) != noErr) {
+ debugr2((errout,"CMCopyProfile() failed for file '%s' with error %d\n",dpath,ev));
+ return 1;
+ }
+
+ /* Make it the current profile - updates LUTs */
+ if ((ev = CMSetProfileByAVID((CMDisplayIDType)p->ddid, dprof)) != noErr) {
+ debugr2((errout,"CMSetProfileByAVID() failed for file '%s' with error %d\n",fname,ev));
+ return 1;
+ }
+ CMCloseProfile(prof);
+ CMCloseProfile(dprof);
+
+ return 0;
+ }
+#endif /* 10.6 and prior */
+#endif /* __APPLE__ */
+
+#if defined(UNIX_X11) && defined(USE_UCMM)
+ {
+ ucmm_error ev;
+ ucmm_scope sc;
+ FILE *fp;
+ unsigned long psize, bread;
+ unsigned char *atomv;
+ int rv;
+
+ if (scope == p_scope_network
+ || scope == p_scope_system
+ || scope == p_scope_local)
+ sc = ucmm_local_system;
+ else
+ sc = ucmm_user;
+
+ if ((ev = ucmm_install_monitor_profile(sc, p->edid, p->edid_len, p->name, fname)) != ucmm_ok) {
+ debugr2((errout,"Installing profile '%s' failed with error %d '%s'\n",fname,ev,ucmm_error_string(ev)));
+ return 1;
+ }
+
+ if ((rv = set_X11_atom(p, fname)) != 0) {
+ debugr2((errout,"Setting X11 atom failed"));
+ return 1;
+ }
+
+ /* X11 doesn't set the display to the current profile calibration, */
+ /* so we do it. */
+ if (p->set_ramdac(p,r,1)) {
+ debugr2((errout,"Failed to set VideoLUT"));
+ return 1;
+ }
+ return 0;
+ }
+#endif /* UNXI X11 */
+
+ return 1;
+}
+
+/* Un-Install a display profile */
+/* Return nz if failed, */
+/* 1 if not sucessfully deleted */
+/* 2 if profile not found */
+int dispwin_uninstall_profile(dispwin *p, char *fname, p_scope scope) {
+#ifdef NT
+ {
+ char *fullpath;
+ char *basename;
+ char colpath[MAX_PATH];
+ unsigned long colpathlen = MAX_PATH;
+ WCS_PROFILE_MANAGEMENT_SCOPE wcssc;
+ unsigned short *wbname, *wmonid;
+
+ debugr2((errout,"Uninstalling '%s'\n", fname));
+
+ if (GetColorDirectory(NULL, colpath, &colpathlen) == 0) {
+ debugr2((errout,"Getting color directory failed\n"));
+ return 1;
+ }
+
+ if ((fullpath = _fullpath(NULL, fname, 0)) == NULL) {
+ debugr2((errout,"_fullpath() failed\n"));
+ return 1;
+ }
+
+ if ((basename = PathFindFileName(fullpath)) == NULL) {
+ debugr2((errout,"Locating base name in '%s' failed\n",fname));
+ free(fullpath);
+ return 1;
+ }
+
+ if ((strlen(colpath) + strlen(basename) + 2) > MAX_PATH) {
+ debugr2((errout,"Installed profile path too long\n"));
+ free(fullpath);
+ return 1;
+ }
+ strcat(colpath, "\\");
+ strcat(colpath, basename);
+
+ /* Setup in case we're on Vista */
+ if (scope == p_scope_user)
+ wcssc = WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER;
+ else
+ wcssc = WCS_PROFILE_MANAGEMENT_SCOPE_SYSTEM_WIDE;
+
+ if ((wbname = char2wchar(basename)) == NULL) {
+ debugr2((errout,"char2wchar failed\n"));
+ free(fullpath);
+ return 1;
+ }
+
+ if ((wmonid = char2wchar(p->monid)) == NULL ) {
+ debugr2((errout,"char2wchar failed\n"));
+ free(wbname);
+ free(fullpath);
+ return 1;
+ }
+
+ debugr2((errout,"Disassociating '%s' from '%s'\n",basename,p->monid));
+
+ if (pWcsDisassociateColorProfileFromDevice != NULL) {
+ debugr("Using Vista Disassociate\n");
+ /* Ignore error if profile is already disasociated or doesn't exist */
+ if ((*pWcsDisassociateColorProfileFromDevice)(wcssc, wbname, wmonid) == 0
+ && GetLastError() != 2015 && GetLastError() != 2011) {
+ debugr2((errout,"WcsDisassociateColorProfileWithDevice() failed for file '%s' with error %d\n",basename,GetLastError()));
+ free(wmonid);
+ free(wbname);
+ free(fullpath);
+ return 1;
+ }
+ } else {
+ /* Ignore error if profile is already disasociated or doesn't exist */
+ if (DisassociateColorProfileFromDevice(NULL, basename, p->monid) == 0
+ && GetLastError() != 2015 && GetLastError() != 2011) {
+ debugr2((errout,"DisassociateColorProfileWithDevice() failed for file '%s' with error %d\n",basename,GetLastError()));
+ free(wmonid);
+ free(wbname);
+ free(fullpath);
+ return 1;
+ }
+ }
+
+ if (UninstallColorProfile(NULL, basename, TRUE) == 0) {
+ /* This can happen when some other program has the profile open */
+ int ev;
+ struct _stat sbuf;
+ debugr2((errout,"Warning, uninstallColorProfile() failed for file '%s' with error %d\n", basename,GetLastError()));
+ free(wmonid);
+ free(wbname);
+ free(fullpath);
+ return 2;
+ }
+
+ free(wmonid);
+ free(wbname);
+ free(fullpath);
+
+ return 0;
+ }
+#endif /* NT */
+
+/* For Linux and OS X, make sure we don't create a file with the wrong owner */
+#if defined(UNIX)
+ /* If we're creating a user profile and running as root sudo */
+ if (scope == p_scope_user && geteuid() == 0) {
+ char *uids, *gids;
+ int uid, gid;
+
+ debugr("We're setting a user profile running as root - run as user\n");
+ if ((uids = getenv("SUDO_UID")) != NULL
+ && (gids = getenv("SUDO_GID")) != NULL) {
+ uid = atoi(uids);
+ gid = atoi(gids);
+ if (setegid(gid) || seteuid(uid)) {
+ debugr("seteuid or setegid failed\n");
+ }
+ debug2((errout,"Set euid %d and egid %d\n",uid,gid));
+ }
+ /* If setting local system proile and not effective root, but sudo */
+ } else if (scope != p_scope_user && getuid() == 0 && geteuid() != 0) {
+ if (getenv("SUDO_UID") != NULL
+ && getenv("SUDO_GID") != NULL) {
+
+ debugr("We're setting a system profile running as user - revert to root\n");
+ setegid(getgid());
+ seteuid(getuid());
+ }
+ }
+#endif /* OS X || Linux */
+#ifdef __APPLE__
+#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
+ {
+ char *dpath; /* Un-install file path */
+ struct stat sbuf;
+ int ev;
+ CFStringRef keys[1];
+ CFURLRef values[1];
+ CFDictionaryRef dict;
+ CFUUIDRef dispuuid;
+
+ /* Determine the location the profile will be installed into */
+ if ((dpath = iprof_path(scope, fname)) == NULL) {
+ debugr2((errout,"iprof_path() failed\n"));
+ return 1;
+ }
+
+ debugr2((errout,"Profile to delete '%s'\n",dpath));
+
+ if (stat(dpath, &sbuf) != 0) {
+ debugr2((errout,"delete '%s' profile doesn't exist\n",dpath));
+ return 2;
+ }
+ if ((ev = unlink(dpath)) != 0) {
+ debugr2((errout,"delete '%s' failed with %d\n",dpath,ev));
+ return 1;
+ }
+
+ free(dpath); dpath = NULL;
+
+ /* Make ColorSync notice that it's gone */
+ /* (Works on 10.7, but not 10.6 ? */
+ if ((dispuuid = CGDisplayCreateUUIDFromDisplayID(p->ddid)) == NULL) {
+ debugr2((errout,"CGDisplayCreateUUIDFromDisplayID() failed\n"));
+ return 1;
+ }
+
+ keys[0] = kColorSyncDeviceDefaultProfileID;
+ values[0] = (CFURLRef)kCFNull;
+
+ if ((dict = CFDictionaryCreate(kCFAllocatorDefault, (const void **)&keys,
+ (const void **)&values, 1, NULL, NULL)) == NULL) {
+ debugr2((errout,"CFDictionaryCreate() failed\n"));
+ CFRelease(dispuuid);
+ return 1;
+ }
+
+ if (!ColorSyncDeviceSetCustomProfiles(kColorSyncDisplayDeviceClass, dispuuid, dict)) {
+ debugr2((errout,"ColorSyncDeviceSetCustomProfiles() failed\n"));
+ CFRelease(dict);
+ CFRelease(dispuuid);
+ return 1;
+ }
+ CFRelease(dict);
+ CFRelease(dispuuid);
+
+ return 0;
+ }
+#else /* 10.6 and prior */
+ // ~~~ can use above code
+ {
+ CMError cmev;
+ int ev;
+ short vref;
+ char dpath[FILENAME_MAX];
+ char *basename;
+ FSRef dirref;
+ struct stat sbuf;
+
+ if (scope == p_scope_network)
+ vref = kNetworkDomain;
+ else if (scope == p_scope_system)
+ vref = kSystemDomain;
+ else if (scope == p_scope_local)
+ vref = kLocalDomain;
+ else
+ vref = kUserDomain;
+
+ /* Locate the appropriate ColorSync path */
+ if ((cmev = FSFindFolder(vref, kColorSyncProfilesFolderType, kCreateFolder, &dirref)) != noErr) {
+ debugr2((errout,"FSFindFolder() failed with error %d\n",cmev));
+ return 1;
+ }
+
+ /* Convert to POSIX path */
+ if ((cmev = FSRefMakePath(&dirref, (unsigned char *)dpath, FILENAME_MAX)) != noErr) {
+ debugr2((errout,"FSRefMakePath failed with error %d\n",cmev));
+ return 1;
+ }
+
+ /* Locate the base filename in the fname */
+ for (basename = fname + strlen(fname); ; basename--) {
+ if (basename <= fname || basename[-1] == '/')
+ break;
+ }
+
+ /* Append the basename to the ColorSync directory path */
+ if ((strlen(dpath) + strlen(basename) + 2) > FILENAME_MAX
+ || (strlen(dpath) + strlen(basename) + 2) > 256) {
+ debugr2((errout,"ColorSync dir + profile name too long\n"));
+ return 1;
+ }
+ strcat(dpath, "/");
+ strcat(dpath, basename);
+ debugr2((errout,"Profile to delete '%s'\n",dpath));
+
+ if (stat(dpath, &sbuf) != 0) {
+ debugr2((errout,"delete '%s' profile doesn't exist\n",dpath));
+ return 2;
+ }
+ if ((ev = unlink(dpath)) != 0) {
+ debugr2((errout,"delete '%s' failed with %d\n",dpath,ev));
+ return 1;
+ }
+
+ return 0;
+ }
+#endif /* 10.6 and prior */
+#endif /* __APPLE__ */
+
+#if defined(UNIX_X11) && defined(USE_UCMM)
+ {
+ ucmm_error ev;
+ ucmm_scope sc;
+
+ if (scope == p_scope_network
+ || scope == p_scope_system
+ || scope == p_scope_local)
+ sc = ucmm_local_system;
+ else
+ sc = ucmm_user;
+
+ if ((ev = ucmm_uninstall_monitor_profile(sc, p->edid, p->edid_len, p->name, fname)) != ucmm_ok) {
+ debugr2((errout,"Installing profile '%s' failed with error %d '%s'\n",fname,ev,ucmm_error_string(ev)));
+ return 1;
+ }
+
+ XDeleteProperty(p->mydisplay, RootWindow(p->mydisplay, 0), p->icc_atom);
+
+#if RANDR_MAJOR == 1 && RANDR_MINOR >= 2 && !defined(DISABLE_RANDR)
+ /* If Xrandr 1.2, set property on output */
+ if (p->icc_out_atom != 0) {
+ XRRDeleteOutputProperty(p->mydisplay, p->output, p->icc_out_atom);
+ }
+#endif /* randr >= V 1.2 */
+ return 0;
+ }
+#endif /* UNXI X11 */
+
+ return 1;
+}
+
+/* Get the currently installed display profile and return it as an icmFile. */
+/* Return the name as well, up to mxlen chars, excluding nul. */
+/* Return NULL if failed. */
+icmFile *dispwin_get_profile(dispwin *p, char *name, int mxlen) {
+ icmFile *rd_fp = NULL;
+
+#ifdef NT
+ {
+ char buf[MAX_PATH];
+ DWORD blen = MAX_PATH;
+
+ if (GetICMProfile(p->hdc, &blen, buf) == 0) {
+ debugr2((errout, "GetICMProfile failed, lasterr = %d\n",GetLastError()));
+ return NULL;
+ }
+
+ debugr2((errout,"Loading default profile '%s'\n",buf));
+ if ((rd_fp = new_icmFileStd_name(buf,"r")) == NULL)
+ debugr2((errout,"Can't open file '%s'",buf));
+
+ return rd_fp;
+ }
+#endif /* NT */
+
+#ifdef __APPLE__
+#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
+ {
+ char *dpath; /* Read file path */
+ struct stat sbuf;
+ icmAlloc *al;
+ void *buf;
+ FILE *fp;
+
+ if ((dpath = cur_profile(p)) == NULL) {
+ debugr2((errout,"cur_profile() failed\n"));
+ return NULL;
+ }
+
+ /* Get the profile size */
+ if (stat(dpath, &sbuf) != 0) {
+ debugr2((errout,"Failed to open profile '%s'\n",dpath));
+ free(dpath);
+ return NULL;
+ }
+
+ if ((al = new_icmAllocStd()) == NULL) {
+ debugr("new_icmAllocStd failed\n");
+ free(dpath);
+ return NULL;
+ }
+ if ((buf = al->malloc(al, sbuf.st_size)) == NULL) {
+ debugr("malloc of profile buffer failed\n");
+ free(dpath);
+ return NULL;
+ }
+
+ if ((fp = fopen(dpath, "r")) == NULL) {
+ debugr2((errout,"opening '%s' failed\n",dpath));
+ al->free(al, buf);
+ free(dpath);
+ return NULL;
+ }
+ if (fread(buf, 1, sbuf.st_size, fp) != sbuf.st_size) {
+ debugr2((errout,"reading '%s' failed\n",dpath));
+ al->free(al, buf);
+ fclose(fp);
+ free(dpath);
+ return NULL;
+ }
+ fclose(fp);
+ free(dpath); dpath = NULL;
+
+ /* Memory File fp that will free the buffer when deleted: */
+ if ((rd_fp = new_icmFileMem_ad(buf, sbuf.st_size, al)) == NULL) {
+ debugr("Creating memory file profile failed");
+ al->free(al, buf);
+ al->del(al);
+ return NULL;
+ }
+
+ if (name != NULL) {
+ strncpy(name, "Display", mxlen);
+ name[mxlen] = '\000';
+ }
+
+ return rd_fp;
+ }
+
+#else /* 10.5 and prior */
+ {
+ CMError ev;
+ CMProfileRef prof, dprof; /* Source profile */
+ CMProfileLocation dploc; /* Destinaion profile location */
+ CMAppleProfileHeader hdr;
+ icmAlloc *al;
+
+#ifdef NEVER
+ /* Get the current display profile */
+ if ((ev = CMGetProfileByAVID((CMDisplayIDType)p->ddid, &prof)) != noErr) {
+ debugr2((errout,"CMGetProfileByAVID() failed with error %d\n",ev));
+ return NULL;
+ }
+#else
+ CMDeviceProfileID curID; /* Current Device Default profile ID */
+ CMProfileLocation cploc; /* Current profile location */
+
+ /* Get the default ID for the display */
+ if ((ev = CMGetDeviceDefaultProfileID(cmDisplayDeviceClass, (CMDeviceID)p->ddid, &curID)) != noErr) {
+ debugr2((errout,"CMGetDeviceDefaultProfileID() failed with error %d\n",ev));
+ return NULL;
+ }
+
+ /* Get the displays profile */
+ if ((ev = CMGetDeviceProfile(cmDisplayDeviceClass, (CMDeviceID)p->ddid, curID, &cploc)) != noErr) {
+ debugr2((errout,"CMGetDeviceDefaultProfileID() failed with error %d\n",ev));
+ return NULL;
+ }
+
+ if ((ev = CMOpenProfile(&prof, &cploc)) != noErr) {
+ debugr2((errout,"CMOpenProfile() failed with error %d\n",ev));
+ return NULL;
+ }
+#endif
+
+ /* Get the profile size */
+ if ((ev = CMGetProfileHeader(prof, &hdr)) != noErr) {
+ debugr2((errout,"CMGetProfileHeader() failed with error %d\n",ev));
+ return NULL;
+ }
+
+ /* Make a copy of the profile to a memory buffer */
+ dploc.locType = cmBufferBasedProfile;
+ dploc.u.bufferLoc.size = hdr.cm1.size;
+ if ((al = new_icmAllocStd()) == NULL) {
+ debugr("new_icmAllocStd failed\n");
+ return NULL;
+ }
+ if ((dploc.u.bufferLoc.buffer = al->malloc(al, dploc.u.bufferLoc.size)) == NULL) {
+ debugr("malloc of profile buffer failed\n");
+ return NULL;
+ }
+
+ if ((ev = CMCopyProfile(&dprof, &dploc, prof)) != noErr) {
+ debugr2((errout,"CMCopyProfile() failed for AVID to buffer with error %d\n",ev));
+ return NULL;
+ }
+
+ /* Memory File fp that will free the buffer when deleted: */
+ if ((rd_fp = new_icmFileMem_ad((void *)dploc.u.bufferLoc.buffer, dploc.u.bufferLoc.size, al)) == NULL) {
+ debugr("Creating memory file from CMProfileLocation failed");
+ al->free(al, dploc.u.bufferLoc.buffer);
+ al->del(al);
+ CMCloseProfile(prof);
+ CMCloseProfile(dprof);
+ return NULL;
+ }
+
+ if (name != NULL) {
+ strncpy(name, "Display", mxlen);
+ name[mxlen] = '\000';
+ }
+
+ CMCloseProfile(prof);
+ CMCloseProfile(dprof);
+
+ return rd_fp;
+ }
+#endif /* 10.5 and prior */
+#endif /* __APPLE__ */
+
+#if defined(UNIX_X11) && defined(USE_UCMM)
+ /* Try and get the currently installed profile from ucmm */
+ {
+ ucmm_error ev;
+ char *profile = NULL;
+
+ debugr2((errout,"dispwin_get_profile called\n"));
+
+ if ((ev = ucmm_get_monitor_profile(p->edid, p->edid_len, p->name, &profile)) == ucmm_ok) {
+
+ if (name != NULL) {
+ strncpy(name, profile, mxlen);
+ name[mxlen] = '\000';
+ }
+
+ debugr2((errout,"Loading current profile '%s'\n",profile));
+ if ((rd_fp = new_icmFileStd_name(profile,"r")) == NULL) {
+ debugr2((errout,"Can't open file '%s'",profile));
+ free(profile);
+ return NULL;
+ }
+
+ /* Implicitly we set the X11 atom to be the profile we just got */
+ debugr2((errout,"Setting X11 atom to current profile '%s'\n",profile));
+ if (set_X11_atom(p, profile) != 0) {
+ debugr2((errout,"Setting X11 atom to profile '%s' failed",profile));
+ /* Hmm. We ignore this error */
+ }
+ return rd_fp;
+ }
+ if (ev != ucmm_no_profile) {
+ debugr2((errout,"Got ucmm error %d '%s'\n",ev,ucmm_error_string(ev)));
+ return NULL;
+ }
+ debugr2((errout,"Failed to get configured profile, so use X11 atom\n"));
+ /* Drop through to using the X11 root window atom */
+ }
+ {
+ Atom ret_type;
+ int ret_format;
+ long ret_len, ret_togo;
+ char aname[30];
+ unsigned char *atomv = NULL; /* Profile loaded from/to atom */
+ unsigned char *buf;
+ icmAlloc *al;
+
+ atomv = NULL;
+
+ strcpy(aname, "_ICC_PROFILE");
+
+#if RANDR_MAJOR == 1 && RANDR_MINOR >= 2 && !defined(DISABLE_RANDR)
+ /* If Xrandr 1.2, get property on output */
+ if (p->icc_out_atom != 0) {
+
+ /* Get the ICC profile property */
+ if (XRRGetOutputProperty(p->mydisplay, p->output, p->icc_out_atom,
+ 0, 0x7ffffff, False, False, XA_CARDINAL,
+ &ret_type, &ret_format, &ret_len, &ret_togo, &atomv) != Success || ret_len == 0) {
+ debugr("Failed to read ICC_PROFILE property from Xranr output\n");
+ }
+
+ }
+#endif /* randr >= V 1.2 */
+
+ if (atomv == NULL) {
+ if (p->myuscreen != 0)
+ sprintf(aname, "_ICC_PROFILE_%d",p->myuscreen);
+
+ /* Get the ICC profile property */
+ if (XGetWindowProperty(p->mydisplay, RootWindow(p->mydisplay, 0), p->icc_atom,
+ 0, 0x7ffffff, False, XA_CARDINAL,
+ &ret_type, &ret_format, &ret_len, &ret_togo, &atomv) != Success || ret_len == 0) {
+ debugr2((errout,"Getting property '%s' from RootWindow\n", aname));
+ return NULL;
+ }
+ }
+
+ /* This is a bit of a fiddle to keep the memory allocations */
+ /* straight. (We can't assume that X11 and icc are using the */
+ /* same allocators) */
+ if ((al = new_icmAllocStd()) == NULL) {
+ debugr("new_icmAllocStd failed\n");
+ return NULL;
+ }
+ if ((buf = al->malloc(al, ret_len)) == NULL) {
+ debugr("malloc of profile buffer failed\n");
+ return NULL;
+ }
+ memmove(buf, atomv, ret_len);
+ XFree(atomv);
+
+ /* Memory File fp that will free the buffer when deleted: */
+ if ((rd_fp = new_icmFileMem_ad((void *)buf, ret_len, al)) == NULL) {
+ debugr("Creating memory file from X11 atom failed");
+ al->free(al, buf);
+ al->del(al);
+ return NULL;
+ }
+
+ if (name != NULL) {
+ strncpy(name, aname, mxlen);
+ name[mxlen] = '\000';
+ }
+ return rd_fp;
+ }
+#endif /* UNXI X11 */
+
+ return NULL;
+}
+
+/* ----------------------------------------------- */
+
+/* Restore the display state */
+static void restore_display(dispwin *p) {
+
+ /* Restore the ramdac */
+ if (p->or != NULL) {
+ p->set_ramdac(p, p->or, 0);
+ p->or->del(p->or);
+ p->or = NULL;
+ debugr("Restored original ramdac\n");
+ }
+ if (p->r != NULL) {
+ p->r->del(p->r);
+ p->r = NULL;
+ }
+
+#if defined(UNIX_X11)
+
+#if ScreenSaverMajorVersion >= 1 && ScreenSaverMinorVersion >= 1 /* X11R7.1 */
+ if (p->xsssuspend) {
+ XScreenSaverSuspend(p->mydisplay, False);
+ p->xsssuspend = 0;
+ }
+#endif
+ if (p->xssvalid) {
+ /* Restore the X11 screen saver state */
+ XSetScreenSaver(p->mydisplay, p->timeout, p->interval,
+ p->prefer_blanking, p->allow_exposures);
+ }
+
+ /* Restore the xscreensaver */
+ if (p->xssrunning) {
+ system("xscreensaver -nosplash 2>/dev/null >/dev/null&");
+ }
+
+ if (p->gnomessrunning && p->gnomepid != -1) {
+ kill(p->gnomepid, SIGKILL); /* Kill the process inhibiting the screen saver */
+ }
+
+ /* Restore the KDE screen saver state */
+ if (p->kdessrunning) {
+ system("dcop kdesktop KScreensaverIface enable true 2>&1 >/dev/null");
+ }
+
+ /* Restore DPMS */
+ if (p->dpmsenabled) {
+ DPMSEnable(p->mydisplay);
+ }
+
+ /* Flush any changes out */
+ XSync(p->mydisplay, False);
+#endif /* UNIX_X11 */
+}
+
+/* ----------------------------------------------- */
+/* On something killing our process, deal with Ramac & ScreenSaver cleanup */
+
+#ifdef NT
+void (__cdecl *dispwin_int)(int sig) = SIG_DFL;
+void (__cdecl *dispwin_term)(int sig) = SIG_DFL;
+#endif
+#ifdef UNIX
+void (*dispwin_hup)(int sig) = SIG_DFL;
+void (*dispwin_int)(int sig) = SIG_DFL;
+void (*dispwin_term)(int sig) = SIG_DFL;
+#endif
+
+/* List of dispwin's to clean up */
+static dispwin *signal_dispwin = NULL;
+
+static void dispwin_sighandler(int arg) {
+ dispwin *pp, *np;
+
+ /* Restore all dispwin's Ramdacs & screen savers */
+ for (pp = signal_dispwin; pp != NULL; pp = np) {
+ np = pp->next;
+ restore_display(pp);
+ }
+
+ /* Call through to previous handler */
+#ifdef UNIX
+ if (arg == SIGHUP && dispwin_hup != SIG_DFL && dispwin_hup != SIG_IGN)
+ dispwin_hup(arg);
+#endif
+ if (arg == SIGINT && dispwin_int != SIG_DFL && dispwin_int != SIG_IGN)
+ dispwin_int(arg);
+ if (arg == SIGTERM && dispwin_term != SIG_DFL && dispwin_term != SIG_IGN)
+ dispwin_term(arg);
+ exit(0);
+}
+
+static void dispwin_install_signal_handlers(dispwin *p) {
+
+ if (signal_dispwin == NULL) {
+ /* Install the signal handler to ensure cleanup */
+#ifdef UNIX
+ dispwin_hup = signal(SIGHUP, dispwin_sighandler);
+#endif /* UNIX */
+ dispwin_int = signal(SIGINT, dispwin_sighandler);
+ dispwin_term = signal(SIGTERM, dispwin_sighandler);
+ }
+
+ /* Add this one to the list */
+ p->next = signal_dispwin;
+ signal_dispwin = p;
+}
+
+static void dispwin_uninstall_signal_handlers(dispwin *p) {
+
+ /* Find it and delete it from our static cleanup list */
+ if (signal_dispwin != NULL) {
+ if (signal_dispwin == p) {
+ signal_dispwin = p->next;
+ if (signal_dispwin == NULL) {
+#if defined(UNIX)
+ signal(SIGHUP, dispwin_hup);
+#endif /* UNIX */
+ signal(SIGINT, dispwin_int);
+ signal(SIGTERM, dispwin_term);
+ }
+ } else {
+ dispwin *pp;
+ for (pp = signal_dispwin; pp != NULL; pp = pp->next) {
+ if (pp->next == p) {
+ pp->next = p->next;
+ break;
+ }
+ }
+ }
+ }
+ p->next = NULL;
+}
+
+/* ----------------------------------------------- */
+/* Test patch window specific declarations */
+
+#ifdef __APPLE__
+
+@class DWWin;
+@class DWView;
+
+/* Our OS X window specific information */
+typedef struct {
+ dispwin *p;
+ DWWin *window; /* Our NSWindow */
+ DWView *view; /* Our NSView */
+} osx_cntx_t;
+
+static void OSX_ProcessEvents(dispwin *p);
+
+// - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+@interface DWView : NSView {
+ osx_cntx_t *cntx;
+}
+- (void)setCntx:(osx_cntx_t *)cntx;
+@end
+
+@implementation DWView
+
+- (void)setCntx:(osx_cntx_t *)val {
+ cntx = val;
+}
+
+// A transparent 1x1 GIF: (43 bytes)
+unsigned char emptyCursor[43] = {
+ 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x21, 0xf9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3b };
+
+/* Make cursor invisible over our window */
+/*
+ * This doesn't work very well. The only way to work it properly
+ * is to create a CGEventTap and do a hide/unkid cursor when
+ * the mouse enters the window. This needs the main thread
+ * to be dedicated to running the event loop.
+ */
+- (void)resetCursorRects {
+ [super resetCursorRects];
+// [self addCursorRect:[self bounds] cursor:[NSCursor crosshairCursor]];
+ NSData *idata = [NSData dataWithBytes:(void *)emptyCursor length:43];
+ NSImage *img = [NSImage alloc];
+ img = [img initWithData:idata];
+ NSCursor *curs = [NSCursor alloc];
+ curs = [curs initWithImage:img hotSpot:NSMakePoint(0,0)];
+ [self addCursorRect:[self bounds] cursor:curs];
+}
+
+- (void)drawRect:(NSRect)rect {
+ osx_cntx_t *cx = cntx;
+ dispwin *p = cx->p;
+ NSRect frect;
+ NSBezierPath* aPath = [NSBezierPath bezierPath];
+
+ frect = NSMakeRect(p->tx, p->ty, (1.0 + p->tw), (1.0 + p->th));
+
+ [[NSColor colorWithDeviceRed: p->r_rgb[0]
+ green: p->r_rgb[1]
+ blue: p->r_rgb[2]
+ alpha: 1.0] setFill];
+ [aPath appendBezierPathWithRect:frect];
+ [aPath fill];
+}
+
+@end
+
+// - - - - - - - - - - - - - - - - - - - - - - - - -
+
+@interface DWWin : NSWindow {
+ osx_cntx_t *cntx;
+}
+- (void)setCntx:(osx_cntx_t *)cntx;
+@end
+
+@implementation DWWin
+
+- (void)setCntx:(osx_cntx_t *)val {
+ cntx = val;
+}
+
+- (BOOL)canBecomeMainWindow {
+ return NO;
+}
+
+/* So that we can change the cursor on a borderless window: */
+- (BOOL)canBecomeKeyWindow {
+ return YES;
+}
+
+- (BOOL)isMoveable {
+ return NO;
+}
+
+- (BOOL)windowShouldClose:(id)sender {
+// printf("Got Window windowShouldClose\n");
+
+// [NSApp terminate: nil];
+ return YES;
+}
+
+@end
+
+/* Create our window */
+static void create_my_win(NSRect rect, osx_cntx_t *cx) {
+ dispwin *p = cx->p;
+ int i;
+
+ /* We need to locate the NSScreen that corresponds to the */
+ /* CGDirectDisplayID - look through them for a match. */
+ NSScreen *screen = nil;
+ NSArray *screenArray = [NSScreen screens];
+ for (i = 0; i < [screenArray count]; i++) {
+ NSDictionary *screenDescription = [[screenArray objectAtIndex:i] deviceDescription];
+
+ // CFShow(screenDescription); // Dump it out
+ /* Hmm. Also has "NSDeviceBitsPerSample" entry with value 8 in dict. */
+
+ NSNumber* screenID = [screenDescription objectForKey:@"NSScreenNumber"];
+ CGDirectDisplayID ddid = (CGDirectDisplayID)[screenID unsignedIntValue];
+ if (ddid == p->ddid) {
+ screen = [screenArray objectAtIndex:i];
+ break;
+ }
+ }
+
+ /* Create Window */
+ cx->window = [[DWWin alloc] initWithContentRect: rect
+ styleMask: NSBorderlessWindowMask
+ backing: NSBackingStoreBuffered
+ defer: YES
+ screen: screen];
+
+ [cx->window setLevel: NSScreenSaverWindowLevel];
+
+ [cx->window setBackgroundColor: [NSColor blackColor]];
+
+ [cx->window setTitle: @"Argyll Window"];
+
+ /* Use our view for the whole window to draw plot */
+ cx->view = [DWView new];
+ [cx->view setCntx:(void *)cx];
+ [cx->window setContentView: cx->view];
+
+ [cx->window makeKeyAndOrderFront: nil];
+}
+
+#endif /* __APPLE__ */
+
+/* ----------------------------------------------- */
+
+/* Change the window color. */
+/* Return 1 on error, 2 on window being closed */
+static int dispwin_set_color(
+dispwin *p,
+double r, double g, double b /* Color values 0.0 - 1.0 */
+) {
+ int j;
+
+ debugr("dispwin_set_color called\n");
+
+ if (p->nowin)
+ return 1;
+
+ p->rgb[0] = r;
+ p->rgb[1] = g;
+ p->rgb[2] = b;
+
+ for (j = 0; j < 3; j++) {
+ if (p->rgb[j] < 0.0)
+ p->rgb[j] = 0.0;
+ else if (p->rgb[j] > 1.0)
+ p->rgb[j] = 1.0;
+ p->r_rgb[j] = p->rgb[j];
+ }
+
+ /* Use ramdac for high precision native output. */
+ /* The ramdac is used to hold the lsb that the frame buffer */
+ /* doesn't hold. */
+ if (p->native == 1) {
+ double prange = p->r->nent - 1.0;
+
+ for (j = 0; j < 3; j++) {
+ int tt;
+
+//printf("~1 %d: in %f, ",j,p->rgb[j]);
+ tt = (int)(p->rgb[j] * prange);
+ p->r->v[j][tt] = p->rgb[j];
+ p->r_rgb[j] = (double)tt/prange; /* RAMDAC output Quantized value */
+//printf(" cell[%d], val %f, rast val %f\n",tt, p->rgb[j], p->r_rgb[j]);
+ }
+ if (p->set_ramdac(p,p->r, 0)) {
+ debugr("set_ramdac() failed\n");
+ return 1;
+ }
+ }
+
+ /* - - - - - - - - - - - - - - */
+#ifdef NT
+ {
+ MSG msg;
+ INPUT fip;
+
+ /* Stop the system going to sleep */
+
+ /* This used to work OK in reseting the screen saver, but not in Vista :-( */
+ SetThreadExecutionState(ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
+
+ /* So we use a fake mouse non-movement reset the Vista screensaver. */
+ SystemParametersInfo(SPI_SETBLOCKSENDINPUTRESETS, FALSE, NULL, 0);
+ fip.type = INPUT_MOUSE;
+ fip.mi.dx = 0;
+ fip.mi.dy = 0;
+ fip.mi.dwFlags = MOUSEEVENTF_MOVE;
+ fip.mi.time = 0;
+ fip.mi.dwExtraInfo = 0;
+ SendInput(1, &fip, sizeof(INPUT));
+
+ p->colupd++;
+
+ /* Trigger a WM_PAINT */
+ if (!InvalidateRect(p->hwnd, NULL, FALSE)) {
+ debugr2((errout,"InvalidateRect failed, lasterr = %d\n",GetLastError()));
+ return 1;
+ }
+
+ /* Wait for WM_PAINT to be executed */
+ while (p->colupd != p->colupde) {
+ msec_sleep(20);
+ }
+ }
+#endif /* NT */
+
+ /* - - - - - - - - - - - - - - */
+
+#ifdef __APPLE__
+
+ if (p->winclose) {
+ return 2;
+ }
+
+ /* Stop the system going to sleep */
+ UpdateSystemActivity(OverallAct);
+
+ /* Make sure our window is brought to the front at least once, */
+ /* but not every time, in case the user wants to kill the application. */
+ if (p->btf == 0){
+ OSStatus stat;
+ ProcessSerialNumber cpsn;
+ if ((stat = GetCurrentProcess(&cpsn)) != noErr) {
+ debugr2((errout,"GetCurrentProcess returned error %d\n",stat));
+ } else {
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1030
+ if ((stat = TransformProcessType(&cpsn, kProcessTransformToForegroundApplication)) != noErr) {
+ debugr2((errout,"TransformProcessType returned error %d\n",stat));
+ }
+#endif /* OS X 10.3 */
+ if ((stat = SetFrontProcess(&cpsn)) != noErr) {
+ debugr2((errout,"SetFrontProcess returned error %d\n",stat));
+ }
+ }
+ p->btf = 1;
+ }
+
+ /* Trigger an update that fills window with r_rgb[] */
+ [((osx_cntx_t *)(p->osx_cntx))->view setNeedsDisplay: YES ];
+
+ /* Process events */
+ OSX_ProcessEvents(p);
+
+#endif /* __APPLE__ */
+
+ /* - - - - - - - - - - - - - - */
+
+#if defined(UNIX_X11)
+ {
+ Colormap mycmap;
+ XColor col;
+ int vali[3];
+
+ /* Indicate that we've got activity to the X11 Screensaver */
+ XResetScreenSaver(p->mydisplay);
+
+ /* Quantize to 16 bit color */
+ for (j = 0; j < 3; j++)
+ vali[j] = (int)(65535.0 * p->r_rgb[j] + 0.5);
+
+ mycmap = DefaultColormap(p->mydisplay, p->myscreen);
+ col.red = vali[0];
+ col.green = vali[1];
+ col.blue = vali[2];
+ XAllocColor(p->mydisplay, mycmap, &col);
+ XSetForeground(p->mydisplay, p->mygc, col.pixel);
+
+ XFillRectangle(p->mydisplay, p->mywindow, p->mygc,
+ p->tx, p->ty, p->tw, p->th);
+
+ XSync(p->mydisplay, False); /* Make sure it happens */
+ }
+#endif /* UNXI X11 */
+
+ if (p->callout != NULL) {
+ int rv;
+ char *cmd;
+
+ if ((cmd = malloc(strlen(p->callout) + 200)) == NULL)
+ error("Malloc of command string failed");
+
+ sprintf(cmd, "%s %d %d %d %f %f %f",p->callout,
+ (int)(r * 255.0 + 0.5),(int)(g * 255.0 + 0.5),(int)(b * 255.0 + 0.5), r, g, b);
+ if ((rv = system(cmd)) != 0)
+ warning("System command '%s' failed with %d",cmd,rv);
+ free(cmd);
+ }
+
+ /* Allow some time for the display to update before */
+ /* a measurement can take place. This allows for CRT */
+ /* refresh, or LCD processing/update time, + */
+ /* display settling time (quite long for smaller LCD changes). */
+ msec_sleep(p->update_delay);
+
+ return 0;
+}
+
+/* ----------------------------------------------- */
+/* Set an update delay, and return the previous value */
+/* Value can be set to zero, but othewise will be forced */
+/* to be >= min_update_delay */
+static int dispwin_set_update_delay(
+dispwin *p,
+int update_delay) {
+ int cval = p->update_delay;
+ p->update_delay = update_delay;
+ if (update_delay != 0 && p->update_delay < p->min_update_delay)
+ p->update_delay = p->min_update_delay;
+ return cval;
+}
+
+/* ----------------------------------------------- */
+/* Set the shell set color callout */
+void dispwin_set_callout(
+dispwin *p,
+char *callout
+) {
+ debugr2((errout,"dispwin_set_callout called with '%s'\n",callout));
+
+ p->callout = strdup(callout);
+}
+
+/* ----------------------------------------------- */
+/* Destroy ourselves */
+static void dispwin_del(
+dispwin *p
+) {
+
+ debugr("dispwin_del called\n");
+
+ if (p == NULL)
+ return;
+
+ /* Restore original RAMDAC if we were in native mode, */
+ /* and restore screensaver */
+ restore_display(p);
+ dispwin_uninstall_signal_handlers(p);
+
+ /* -------------------------------------------------- */
+#ifdef NT
+
+ if (p->hwnd != NULL) {
+
+ p->quit = 1;
+ if (PostMessage(p->hwnd, WM_CLOSE, (WPARAM)NULL, (LPARAM)NULL) != 0) {
+ while(p->hwnd != NULL)
+ msec_sleep(20);
+ } else {
+ debugr2((errout, "PostMessage(WM_GETICON failed, lasterr = %d\n",GetLastError()));
+ }
+// DestroyCursor(p->curs);
+
+ if (p->mth != NULL) { /* Message thread */
+ p->mth->del(p->mth);
+ }
+ }
+
+ if (p->hdc != NULL)
+ DeleteDC(p->hdc);
+
+#endif /* NT */
+ /* -------------------------------------------------- */
+
+ /* -------------------------------------------------- */
+#ifdef __APPLE__
+ if (p->nowin == 0) { /* We have a window up */
+ restore_display(p);
+ if (p->osx_cntx != NULL) { /* And we've allocated a context */
+ osx_cntx_t *cx = (osx_cntx_t *)p->osx_cntx;
+
+ p->winclose = 1;
+
+ [cx->window release];
+ free(p->osx_cntx);
+ p->osx_cntx = NULL;
+ }
+ }
+
+// ~~
+// CGDisplayShowCursor(p->ddid);
+
+#endif /* __APPLE__ */
+ /* -------------------------------------------------- */
+
+ /* -------------------------------------------------- */
+#if defined(UNIX_X11)
+ debugr("About to close display\n");
+
+ if (p->mydisplay != NULL) {
+ if (p->nowin == 0) { /* We have a window up */
+
+ XFreeGC(p->mydisplay, p->mygc);
+ XDestroyWindow(p->mydisplay, p->mywindow);
+ }
+ XCloseDisplay(p->mydisplay);
+ }
+ debugr("finished\n");
+
+#endif /* UNXI X11 */
+ /* -------------------------------------------------- */
+
+ if (p->name != NULL)
+ free(p->name);
+ if (p->description != NULL)
+ free(p->description);
+ if (p->callout != NULL)
+ free(p->callout);
+
+ free(p);
+}
+
+/* ----------------------------------------------- */
+/* Event handler callbacks */
+
+#ifdef NT
+
+/* Undocumented flag. Set when minimized/maximized etc. */
+#ifndef SWP_STATECHANGED
+#define SWP_STATECHANGED 0x8000
+#endif
+
+static LRESULT CALLBACK MainWndProc(
+ HWND hwnd,
+ UINT message,
+ WPARAM wParam,
+ LPARAM lParam
+) {
+ debugrr2l(4, (stderr, "Handling message type 0x%x\n",message));
+
+ if (message >= WM_APP) {
+ debugrr2l(4, (stderr, "Message ignored\n"));
+ return 0;
+ }
+
+ switch(message) {
+ case WM_PAINT: {
+ dispwin *p = NULL;
+ int vali[3];
+ HDC hdc;
+ PAINTSTRUCT ps;
+ RECT rect;
+ HBRUSH hbr;
+ int j;
+
+#ifdef _WIN64
+ if ((p = (dispwin *)GetWindowLongPtr(hwnd, GWLP_USERDATA)) == NULL)
+#else
+ if ((p = (dispwin *)GetWindowLong(hwnd, GWL_USERDATA)) == NULL)
+#endif
+ {
+ debugrr2l(4,(stderr, "GetWindowLongPtr failed, lasterr = %d\n",GetLastError()));
+ hdc = BeginPaint(hwnd, &ps);
+ /* Don't know where to paint */
+ EndPaint(hwnd, &ps);
+ return 0;
+ }
+
+ /* Check that there is something to paint */
+ if (GetUpdateRect(hwnd, NULL, FALSE) == 0) {
+ debugrr2l(4, (stderr,"The update region was empty\n"));
+ }
+
+ /* Frame buffer Quantize 8 bit color */
+ for (j = 0; j < 3; j++) {
+ vali[j] = (int)(255.0 * p->r_rgb[j] + 0.5);
+ }
+
+ hdc = BeginPaint(hwnd, &ps);
+
+ SaveDC(hdc);
+
+ /* Try and turn ICM off */
+#ifdef ICM_DONE_OUTSIDEDC
+ if (!SetICMMode(hdc, ICM_DONE_OUTSIDEDC)) {
+ /* This seems to fail with "invalid handle" under NT4 */
+ /* Does it work under Win98 or Win2K ? */
+ printf("SetICMMode failed, lasterr = %d\n",GetLastError());
+ }
+#endif
+
+ hbr = CreateSolidBrush(RGB(vali[0],vali[1],vali[2]));
+ SelectObject(hdc,hbr);
+
+ SetRect(&rect, p->tx, p->ty, p->tx + p->tw, p->ty + p->th);
+ FillRect(hdc, &rect, hbr);
+
+ RestoreDC(hdc,-1);
+ DeleteDC(hdc);
+
+ EndPaint(hwnd, &ps);
+
+ p->colupde = p->colupd; /* We're updated to this color */
+
+ return 0;
+ }
+
+ /* Prevent any changes in position of the window */
+ case WM_WINDOWPOSCHANGING: {
+ WINDOWPOS *wpos = (WINDOWPOS *)lParam;
+ debugrr2l(4,(stderr, "It's a windowposchange, flags = 0x%x, x,y %d %d, w,h %d %d\n",wpos->flags, wpos->x, wpos->y, wpos->cx, wpos->cy));
+ wpos->flags &= ~SWP_FRAMECHANGED & ~SWP_NOREDRAW;
+ wpos->flags |= SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER
+ | SWP_NOSENDCHANGING | SWP_NOSIZE | SWP_SHOWWINDOW;
+ debugrr2l(4,(stderr, "flags now = 0x%x\n",wpos->flags));
+ return DefWindowProc(hwnd, message, wParam, lParam);
+ }
+ case WM_WINDOWPOSCHANGED: {
+ WINDOWPOS *wpos = (WINDOWPOS *)lParam;
+ debugrr2l(4,(stderr, "It's a windowposchanged, flags = 0x%x, x,y %d %d, w,h %d %d\n",wpos->flags, wpos->x, wpos->y, wpos->cx, wpos->cy));
+ debugrr2l(4,(stderr, "It's a windowposchanged, flags = 0x%x\n",wpos->flags));
+ return 0;
+ }
+ case WM_CLOSE:
+ DestroyWindow(hwnd);
+ return 0;
+
+ case WM_DESTROY:
+ PostQuitMessage(0);
+ return 0;
+ }
+
+ debugrr2l(4,(stderr, "Handle message using DefWindowProc()\n"));
+
+ return DefWindowProc(hwnd, message, wParam, lParam);
+}
+
+#endif /* NT */
+
+#ifdef __APPLE__
+
+/* Not the event handler (because events are handled by the Cocoa objects) */
+/* but a function to call to process events after an update */
+static void OSX_ProcessEvents(dispwin *p) {
+ NSEvent *event;
+ NSDate *to;
+
+ /* We're creating and draining a pool here to ensure that all the */
+ /* auto release objects get drained when we're finished (?) */
+ NSAutoreleasePool *tpool = [NSAutoreleasePool new];
+
+ /* Wait until the events are done */
+ to = [NSDate dateWithTimeIntervalSinceNow:0.01]; /* autorelease ? */
+ for (;;) {
+ /* Hmm. Assume event is autorelease */
+ if ((event = [NSApp nextEventMatchingMask:NSAnyEventMask
+ untilDate:to inMode:NSDefaultRunLoopMode dequeue:YES]) != nil) {
+ [NSApp sendEvent:event];
+ } else {
+ break;
+ }
+ }
+ [tpool release];
+}
+
+#endif /* __APPLE__ */
+
+#if defined(UNIX_X11)
+ /* None */
+#endif /* UNXI X11 */
+
+/* ----------------------------------------------- */
+#ifdef NT
+
+/* Thread to handle message processing, so that there is no delay */
+/* when the main thread is doing other things. */
+int win_message_thread(void *pp) {
+ dispwin *p = (dispwin *)pp;
+ MSG msg;
+ WNDCLASS wc;
+
+ debugrr2l(4, (stderr, "win_message_thread started\n"));
+
+ /* Fill in window class structure with parameters that describe the */
+ /* main window. */
+ wc.style = 0 ; /* Class style(s). */
+ wc.lpfnWndProc = MainWndProc; /* Function to retrieve messages for windows of this class. */
+ wc.cbClsExtra = 0; /* No per-class extra data. */
+ wc.cbWndExtra = 0; /* No per-window extra data. */
+ wc.hInstance = NULL; /* Application that owns the class. */
+ wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
+ wc.hCursor = LoadCursor(NULL, IDC_CROSS);
+ wc.hbrBackground = GetStockObject(BLACK_BRUSH);
+ wc.lpszMenuName = NULL;
+ wc.lpszClassName = p->AppName;
+
+ /* Make the cursor disapear over our window */
+ /* (How does it know our window ??) */
+ ShowCursor(FALSE);
+
+ if ((p->arv = RegisterClass(&wc)) == 0) {
+ debugr2((errout, "RegisterClass failed, lasterr = %d\n",GetLastError()));
+ p->inited = 2;
+ return 0;
+ }
+
+#ifndef WS_EX_NOACTIVATE
+# define WS_EX_NOACTIVATE 0x08000000L
+#endif
+ p->hwnd = CreateWindowEx(
+ WS_EX_NOACTIVATE | WS_EX_TOPMOST,
+// 0,
+ p->AppName,
+ "Argyll Display Calibration Window",
+ WS_VISIBLE | WS_DISABLED | WS_POPUP,
+// WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+// WS_POPUPWINDOW | WS_VISIBLE,
+ p->xo, p->yo, /* Location */
+ p->wi, p->he, /* Size */
+ NULL, /* Handle to parent or owner */
+ NULL, /* Handle to menu or child window */
+ NULL, /* hInstance Handle to appication instance */
+ NULL); /* pointer to window creation data */
+
+ if (!p->hwnd) {
+ debugr2((errout, "CreateWindow failed, lasterr = %d\n",GetLastError()));
+ p->inited = 2;
+ return 0;
+ }
+
+ /* Associate the dispwin object with the window, */
+ /* so that the event callback can access it */
+#ifdef _WIN64
+ SetWindowLongPtr(p->hwnd, GWLP_USERDATA, (LONG_PTR)p);
+#else
+ SetWindowLong(p->hwnd, GWL_USERDATA, (LONG)p);
+#endif
+
+ /*
+ Should we call BOOL SystemParametersInfo()
+ to disable high contrast, powertimout and screensaver timeout ?
+
+ */
+
+ debugrr2l(4, (stderr, "win_message_thread initialized - about to process messages\n"));
+ p->inited = 1;
+
+ for (;;) {
+ if (GetMessage(&msg, NULL, 0, 0)) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+
+ if (p->quit != 0) {
+ /* Process any pending messages */
+ while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ break;
+ }
+ }
+ }
+
+ if (UnregisterClass(p->AppName, NULL) == 0) {
+ warning("UnregisterClass failed, lasterr = %d\n",GetLastError());
+ }
+
+ p->hwnd = NULL; /* Signal it's been deleted */
+
+ return 0;
+}
+
+#endif /* NT */
+
+/* Create a RAMDAC access and display test window, default grey */
+dispwin *new_dispwin(
+disppath *disp, /* Display to calibrate. */
+double width, double height, /* Width and height in mm */
+double hoff, double voff, /* Offset from center in fraction of screen, range -1.0 .. 1.0 */
+int nowin, /* NZ if no window should be created - RAMDAC access only */
+int native, /* 0 = use current current or given calibration curve */
+ /* 1 = use native linear out & high precision */
+int *noramdac, /* Return nz if no ramdac access. native is set to 0 */
+int blackbg, /* NZ if whole screen should be filled with black */
+int override, /* NZ if override_redirect is to be used on X11 */
+int ddebug /* >0 to print debug statements to stderr */
+) {
+ dispwin *p = NULL;
+ char *cp;
+
+ debug("new_dispwin called\n");
+
+ if ((p = (dispwin *)calloc(sizeof(dispwin), 1)) == NULL) {
+ if (ddebug) fprintf(stderr,"new_dispwin failed because malloc failed\n");
+ return NULL;
+ }
+
+ /* !!!! Make changes in webwin.c as well !!!! */
+ p->nowin = nowin;
+ p->native = native;
+ p->blackbg = blackbg;
+ p->ddebug = ddebug;
+ p->get_ramdac = dispwin_get_ramdac;
+ p->set_ramdac = dispwin_set_ramdac;
+ p->install_profile = dispwin_install_profile;
+ p->uninstall_profile = dispwin_uninstall_profile;
+ p->get_profile = dispwin_get_profile;
+ p->set_color = dispwin_set_color;
+ p->set_update_delay = dispwin_set_update_delay;
+ p->set_callout = dispwin_set_callout;
+ p->del = dispwin_del;
+
+ p->rgb[0] = p->rgb[1] = p->rgb[2] = 0.5; /* Set Grey as the initial test color */
+
+ p->min_update_delay = 20;
+
+ if ((cp = getenv("ARGYLL_MIN_DISPLAY_UPDATE_DELAY_MS")) != NULL) {
+ p->min_update_delay = atoi(cp);
+ if (p->min_update_delay < 20)
+ p->min_update_delay = 20;
+ if (p->min_update_delay > 60000)
+ p->min_update_delay = 60000;
+ debugr2((errout, "new_dispwin: Minimum display update delay set to %d msec\n",p->min_update_delay));
+ }
+
+ p->update_delay = DISPLAY_UPDATE_DELAY; /* Default update delay */
+ if (p->update_delay < p->min_update_delay)
+ p->update_delay = p->min_update_delay;
+
+ /* Basic object is initialised, so create a window */
+
+ /* -------------------------------------------------- */
+#ifdef NT
+ {
+ WNDCLASS wc;
+ int disp_hsz, disp_vsz; /* Display horizontal/vertical size in mm */
+ int disp_hrz, disp_vrz; /* Display horizontal/vertical resolution in pixels */
+ int wi, he; /* Width and height of window in pixels */
+ int xo, yo; /* Window location in pixels */
+ int bpp;
+
+ p->AppName = "Argyll Test Window";
+
+ debugr2((errout, "new_dispwin: About to open display '%s'\n",disp->name));
+
+ /* Get device context to main display */
+ /* (This is the recommended way of doing this, and works on Vista) */
+ if ((p->hdc = CreateDC(disp->name, NULL, NULL, NULL)) == NULL) {
+ debugr2((errout, "new_dispwin: CreateDC failed, lasterr = %d\n",GetLastError()));
+ dispwin_del(p);
+ return NULL;
+ }
+
+ if ((p->name = strdup(disp->name)) == NULL) {
+ debugr2((errout, "new_dispwin: Malloc failed\n"));
+ dispwin_del(p);
+ return NULL;
+ }
+ if ((p->description = strdup(disp->description)) == NULL) {
+ debugr2((errout, "new_dispwin: Malloc failed\n"));
+ dispwin_del(p);
+ return NULL;
+ }
+ strcpy(p->monid, disp->monid);
+
+ disp_hsz = GetDeviceCaps(p->hdc, HORZSIZE); /* mm */
+ disp_vsz = GetDeviceCaps(p->hdc, VERTSIZE);
+ disp_hrz = GetDeviceCaps(p->hdc, HORZRES); /* pixels */
+ disp_vrz = GetDeviceCaps(p->hdc, VERTRES);
+
+ wi = (int)(width * (double)disp_hrz/(double)disp_hsz + 0.5);
+ if (wi > disp_hrz)
+ wi = disp_hrz;
+ he = (int)(height * (double)disp_vrz/(double)disp_vsz + 0.5);
+ if (he > disp_vrz)
+ he = disp_vrz;
+
+ if (p->blackbg) { /* Window fills the screen, test area is within it */
+ p->tx = (int)((hoff * 0.5 + 0.5) * (disp->sw - wi) + 0.5);
+ p->ty = (int)((voff * 0.5 + 0.5) * (disp->sh - he) + 0.5);
+ p->tw = wi;
+ p->th = he;
+ wi = disp->sw;
+ he = disp->sh;
+ xo = disp->sx;
+ yo = disp->sy;
+ } else { /* Test area completely fills the window */
+ p->tx = 0;
+ p->ty = 0;
+ p->tw = wi;
+ p->th = he;
+ xo = disp->sx + (int)((hoff * 0.5 + 0.5) * (disp->sw - wi) + 0.5);
+ yo = disp->sy + (int)((voff * 0.5 + 0.5) * (disp->sh - he) + 0.5);
+ }
+ p->ww = wi;
+ p->wh = he;
+
+ /* It's a bit difficult to know how windows defines the display */
+ /* depth. Microsofts doco is fuzzy, and typical values */
+ /* for BITSPIXEL and PLANES are confusing (What does "32" and "1" */
+ /* mean ?) NUMCOLORS seems to be -1 on my box, and perhaps */
+ /* is only applicable to up to 256 paletized colors. The doco */
+ /* for COLORRES is also fuzzy, but it returns a meaningful number */
+ /* on my box (24) */
+ if (p->ddebug) {
+ fprintf(errout,"Windows display RASTERCAPS 0x%x, BITSPIXEL %d, PLANES %d, NUMCOLORS %d, COLORRES %d\n",
+ GetDeviceCaps(p->hdc, RASTERCAPS),
+ GetDeviceCaps(p->hdc, BITSPIXEL),GetDeviceCaps(p->hdc, PLANES),
+ GetDeviceCaps(p->hdc, NUMCOLORS),GetDeviceCaps(p->hdc, COLORRES));
+ }
+ if (GetDeviceCaps(p->hdc, RASTERCAPS) & RC_PALETTE) {
+ debugr2((errout, "new_dispwin: can't calibrate palette based device!\n"));
+ dispwin_del(p);
+ return NULL;
+ }
+ bpp = GetDeviceCaps(p->hdc, COLORRES);
+ if (bpp <= 0)
+ p->pdepth = 8; /* Assume this is so */
+ else
+ p->pdepth = bpp/3;
+
+ if (nowin == 0) {
+
+ /* We use a thread to process the window messages, so that */
+ /* Task Manager doesn't think it's not responding. */
+
+ /* Because messages only get delivered to same thread that created window, */
+ /* the thread needs to create window. :-( */
+
+ p->xo = xo; /* Pass info to thread */
+ p->yo = yo;
+ p->wi = wi;
+ p->he = he;
+
+ if ((p->mth = new_athread(win_message_thread, (void *)p)) == NULL) {
+ debugr2((errout, "new_dispwin: new_athread failed\n"));
+ dispwin_del(p);
+ return NULL;
+ }
+
+ /* Wait for thread to run */
+ while (p->inited == 0) {
+ msec_sleep(20);
+ }
+ /* If thread errored */
+ if (p->inited != 1) { /* Error */
+ debugr2((errout, "new_dispwin: new_athread returned error\n"));
+ dispwin_del(p);
+ return NULL;
+ }
+ }
+
+ /* Install the signal handler to ensure cleanup */
+ dispwin_install_signal_handlers(p);
+ }
+
+#endif /* NT */
+ /* -------------------------------------------------- */
+
+ /* -------------------------------------------------- */
+#ifdef __APPLE__
+
+ if ((p->name = strdup(disp->name)) == NULL) {
+ debugr2((errout,"new_dispwin: Malloc failed\n"));
+ dispwin_del(p);
+ return NULL;
+ }
+ p->ddid = disp->ddid; /* Display we're working on */
+
+#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
+ {
+ CGDisplayModeRef dispmode;
+ CFStringRef pixenc;
+
+ p->pdepth = 0;
+
+ dispmode = CGDisplayCopyDisplayMode(p->ddid);
+ pixenc = CGDisplayModeCopyPixelEncoding(dispmode);
+
+ /* Hmm. Don't know what to do with kIO16BitFloatPixels or kIO32BitFloatPixels */
+ if (CFStringCompare(pixenc, CFSTR(kIO64BitDirectPixels), kCFCompareCaseInsensitive)
+ == kCFCompareEqualTo)
+ p->pdepth = 16;
+ else if (CFStringCompare(pixenc, CFSTR(kIO30BitDirectPixels), kCFCompareCaseInsensitive)
+ == kCFCompareEqualTo)
+ p->pdepth = 10;
+ else if (CFStringCompare(pixenc, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive)
+ == kCFCompareEqualTo)
+ p->pdepth = 8;
+ else if (CFStringCompare(pixenc, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive)
+ == kCFCompareEqualTo)
+ p->pdepth = 5;
+ CFRelease(pixenc);
+ CGDisplayModeRelease(dispmode);
+ }
+#else
+ p->pdepth = CGDisplayBitsPerSample(p->ddid);
+#endif
+
+ if (nowin == 0) { /* Create a window */
+ osx_cntx_t *cx;
+ CGSize sz; /* Display size in mm */
+ int wi, he; /* Width and height in pixels */
+ int xo, yo; /* Window location */
+ NSRect wrect;
+
+ debugr2((errout, "new_dispwin: About to open display '%s'\n",disp->name));
+
+ /* If we don't have an application object, create one. */
+ /* (This should go in a common library) */
+ /* Note that we don't actually clean this up on exit - */
+ /* possibly we can't. */
+ if (NSApp == nil) {
+ static NSAutoreleasePool *pool; /* Pool used for NSApp */
+ pool = [NSAutoreleasePool new];
+ NSApp = [NSApplication sharedApplication]; /* Creates NSApp */
+ [NSApp finishLaunching];
+ /* We seem to need this, because otherwise we don't get focus automatically */
+ [NSApp activateIgnoringOtherApps: YES];
+ }
+
+ if ((cx = (osx_cntx_t *)calloc(sizeof(osx_cntx_t), 1)) == NULL) {
+ debugr2((errout,"new_dispwin: Malloc failed (osx_cntx_t)\n"));
+ dispwin_del(p);
+ return NULL;
+ }
+ cx->p = p;
+ p->osx_cntx = cx;
+
+ sz = CGDisplayScreenSize(p->ddid);
+ debugr2((errout," Display size = %f x %f mm\n",sz.width,sz.height));
+
+ wi = (int)(width * disp->sw/sz.width + 0.5);
+ if (wi > disp->sw)
+ wi = disp->sw;
+ he = (int)(height * disp->sh/sz.height + 0.5);
+ if (he > disp->sh)
+ he = disp->sh;
+
+ /* (Because Cocoa origin is botton left, we flip voff) */
+ /* (Cocoa doesn't use disp->sx/sy either - each screen origin is at 0,0) */
+ if (p->blackbg) { /* Window fills the screen, test area is within it */
+ p->tx = (int)((hoff * 0.5 + 0.5) * (disp->sw - wi) + 0.5);
+ p->ty = (int)((-voff * 0.5 + 0.5) * (disp->sh - he) + 0.5);
+ p->tw = wi;
+ p->th = he;
+ wi = disp->sw;
+ he = disp->sh;
+ xo = 0;
+ yo = 0;
+ } else { /* Test area completely fills the window */
+ p->tx = 0;
+ p->ty = 0;
+ p->tw = wi;
+ p->th = he;
+ xo = (int)((hoff * 0.5 + 0.5) * (disp->sw - wi) + 0.5);
+ yo = (int)((-voff * 0.5 + 0.5) * (disp->sh - he) + 0.5);
+ }
+ p->ww = wi;
+ p->wh = he;
+
+ wrect.origin.x = xo;
+ wrect.origin.y = yo;
+ wrect.size.width = wi;
+ wrect.size.height = he;
+
+ create_my_win(wrect, cx);
+
+ OSX_ProcessEvents(p);
+
+ p->winclose = 0;
+ }
+
+ /* Install the signal handler to ensure cleanup */
+ dispwin_install_signal_handlers(p);
+#endif /* __APPLE__ */
+ /* -------------------------------------------------- */
+
+ /* -------------------------------------------------- */
+#if defined(UNIX_X11)
+ {
+ /* NOTE: That we're not doing much to detect if the display/window
+ we open is unsuitable for high quality color (ie. at least
+ 24 bit etc.
+ */
+
+#ifdef NEVER // ?? is this for a specific reason ??
+ if (signal_dispwin != NULL) {
+ debugr2((errout,"new_dispwin: Attempting to open more than one dispwin!\n"));
+ dispwin_del(p);
+ return NULL;
+ }
+#endif
+
+ /* stuff for X windows */
+ char *pp, *bname; /* base display name */
+ Window rootwindow;
+ char *appname = "TestWin";
+ Visual *myvisual;
+ XSetWindowAttributes myattr;
+ XEvent myevent;
+ XTextProperty myappname;
+ XSizeHints mysizehints;
+ XWMHints mywmhints;
+ int evb = 0, erb = 0; /* Extension version */
+ unsigned long myforeground,mybackground;
+ int disp_hsz, disp_vsz; /* Display horizontal/vertical size in mm */
+ int disp_hrz, disp_vrz; /* Display horizontal/vertical resolution in pixels (virtual screen) */
+ int wi, he; /* Width and height of window in pixels */
+ int xo, yo; /* Window location in pixels */
+
+ /* Create the base display name (in case of Xinerama, XRandR) */
+ if ((bname = strdup(disp->name)) == NULL) {
+ debugr2((errout,"new_dispwin: Malloc failed\n"));
+ dispwin_del(p);
+ return NULL;
+ }
+ if ((pp = strrchr(bname, ':')) != NULL) {
+ if ((pp = strchr(pp, '.')) != NULL) {
+ sprintf(pp,".%d",disp->screen);
+ }
+ }
+
+ /* open the display */
+ p->mydisplay = XOpenDisplay(bname);
+ if(!p->mydisplay) {
+ debugr2((errout,"new_dispwin: Unable to open display '%s'\n",bname));
+ dispwin_del(p);
+ free(bname);
+ return NULL;
+ }
+ free(bname);
+ debugr("new_dispwin: Opened display OK\n");
+
+ if ((p->name = strdup(disp->name)) == NULL) {
+ debugr2((errout,"new_dispwin: Malloc failed\n"));
+ dispwin_del(p);
+ return NULL;
+ }
+ p->myscreen = disp->screen;
+ p->myuscreen = disp->uscreen;
+ p->myrscreen = disp->rscreen;
+
+#if RANDR_MAJOR == 1 && RANDR_MINOR >= 2 && !defined(DISABLE_RANDR)
+ /* These will be NULL otherwise */
+ p->icc_atom = disp->icc_atom;
+ p->crtc = disp->crtc;
+ p->output = disp->output;
+ p->icc_out_atom = disp->icc_out_atom;
+#endif /* randr >= V 1.2 */
+
+ if (disp->edid != NULL) {
+ if ((p->edid = malloc(sizeof(unsigned char) * disp->edid_len)) == NULL) {
+ debugr2((errout,"new_dispwin: Malloc failed\n"));
+ dispwin_del(p);
+ return NULL;
+ }
+ p->edid_len = disp->edid_len;
+ memmove(p->edid, disp->edid, p->edid_len);
+ }
+
+ //p->pdepth = DefaultDepth(p->mydisplay, p->myscreen)/3;
+ myvisual = DefaultVisual(p->mydisplay, p->myscreen);
+ p->pdepth = myvisual->bits_per_rgb;
+
+ if (nowin == 0) { /* Create a window */
+ rootwindow = RootWindow(p->mydisplay, p->myscreen);
+
+ myforeground = BlackPixel(p->mydisplay, p->myscreen);
+ mybackground = BlackPixel(p->mydisplay, p->myscreen);
+
+ /* Get device context to main display */
+ disp_hsz = DisplayWidthMM(p->mydisplay, p->myscreen);
+ disp_vsz = DisplayHeightMM(p->mydisplay, p->myscreen);
+ disp_hrz = DisplayWidth(p->mydisplay, p->myscreen);
+ disp_vrz = DisplayHeight(p->mydisplay, p->myscreen);
+
+ /* Compute width and offset from overal display in case Xinerama is active */
+ wi = (int)(width * (double)disp_hrz/(double)disp_hsz + 0.5);
+ if (wi > disp_hrz)
+ wi = disp_hrz;
+ he = (int)(height * (double)disp_vrz/(double)disp_vsz + 0.5);
+ if (he > disp_vrz)
+ he = disp_vrz;
+
+ if (p->blackbg) { /* Window fills the screen, test area is within it */
+ p->tx = (int)((hoff * 0.5 + 0.5) * (disp->sw - wi) + 0.5);
+ p->ty = (int)((voff * 0.5 + 0.5) * (disp->sh - he) + 0.5);
+ p->tw = wi;
+ p->th = he;
+ wi = disp->sw;
+ he = disp->sh;
+ xo = disp->sx;
+ yo = disp->sy;
+ } else { /* Test area completely fills the window */
+ p->tx = 0;
+ p->ty = 0;
+ p->tw = wi;
+ p->th = he;
+ xo = disp->sx + (int)((hoff * 0.5 + 0.5) * (disp->sw - wi) + 0.5);
+ yo = disp->sy + (int)((voff * 0.5 + 0.5) * (disp->sh - he) + 0.5);
+ }
+ p->ww = wi;
+ p->wh = he;
+
+ /* Setup Size Hints */
+ mysizehints.flags = PPosition | USSize;
+ mysizehints.x = xo;
+ mysizehints.y = yo;
+ mysizehints.width = wi;
+ mysizehints.height = he;
+
+ /* Setup Window Manager Hints */
+ mywmhints.flags = InputHint | StateHint;
+ mywmhints.input = 0;
+ mywmhints.initial_state = NormalState;
+
+ /* Setup Window Attributes */
+ myattr.background_pixel = mybackground;
+ myattr.bit_gravity = CenterGravity;
+ myattr.win_gravity = CenterGravity;
+ myattr.backing_store = WhenMapped; /* Since we aren't listning to events */
+ if (override)
+ myattr.override_redirect = True; /* Takes the WM out of the picture */
+ else
+ myattr.override_redirect = False;
+
+ debugr("Opening window\n");
+ p->mywindow = XCreateWindow(
+ p->mydisplay, rootwindow,
+ mysizehints.x,mysizehints.y,mysizehints.width,mysizehints.height,
+ 0, /* Border width */
+ CopyFromParent, /* Depth */
+ InputOutput, /* Class */
+ CopyFromParent, /* Visual */
+ CWBackPixel | CWBitGravity /* Attributes Valumask */
+ | CWWinGravity | CWBackingStore | CWOverrideRedirect,
+ &myattr /* Attribute details */
+ );
+
+#ifdef NEVER
+ XWindowAttributes mywattributes;
+
+ /* Get the windows attributes */
+ if (XGetWindowAttributes(
+ p->mydisplay, p->mywindow,
+ &mywattributes) == 0) {
+ debugr("new_dispwin: XGetWindowAttributes failed\n");
+ dispwin_del(p);
+ return NULL;
+ }
+ p->pdepth = mywattributes.depth/3;
+#endif
+
+ /* Setup TextProperty */
+ XStringListToTextProperty(&appname, 1, &myappname);
+
+ XSetWMProperties(
+ p->mydisplay, p->mywindow,
+ &myappname, /* Window name */
+ &myappname, /* Icon name */
+ NULL, 0, /* argv, argc */
+ &mysizehints,
+ &mywmhints,
+ NULL); /* No class hints */
+
+ /* Set aditional properties */
+ {
+ Atom optat;
+ unsigned int opaque = 0xffffffff;
+ unsigned int xid = (unsigned int)rootwindow; /* Hope this is 32 bit */
+ XChangeProperty(
+ p->mydisplay, p->mywindow,
+ XA_WM_TRANSIENT_FOR, /* Property */
+ XA_WINDOW, /* Type */
+ 32, /* format = bits in type of unsigned int */
+ PropModeReplace, /* Change mode */
+ (char *)(&xid), /* Data is Root Window XID */
+ 1 /* Number of elements of data */
+ );
+
+ /* Set hint for compositing WMs that the window must be opaque */
+ if ((optat = XInternAtom(p->mydisplay, "_NET_WM_WINDOW_OPACITY", False)) != None) {
+ XChangeProperty(p->mydisplay, p->mywindow, optat,
+ XA_CARDINAL, 32, PropModeReplace, (char *)(&opaque), 1);
+ }
+ if ((optat = XInternAtom(p->mydisplay, "_NET_WM_WINDOW_OPACITY_LOCKED", False)) != None) {
+ XChangeProperty(p->mydisplay, p->mywindow, optat,
+ XA_CARDINAL, 32, PropModeReplace, (char *)(&opaque), 1);
+ }
+ }
+
+ p->mygc = XCreateGC(p->mydisplay,p->mywindow,0,0);
+ XSetBackground(p->mydisplay,p->mygc,mybackground);
+ XSetForeground(p->mydisplay,p->mygc,myforeground);
+
+ /* Create an invisible cursor over our window */
+ {
+ Cursor mycursor;
+ Pixmap mypixmap;
+ Colormap mycmap;
+ XColor col;
+ char pmdata[1] = { 0 };
+
+ col.red = col.green = col.blue = 0;
+
+ mycmap = DefaultColormap(p->mydisplay, p->myscreen);
+ XAllocColor(p->mydisplay, mycmap, &col);
+ mypixmap = XCreatePixmapFromBitmapData(p->mydisplay, p->mywindow, pmdata, 1, 1, 0, 0, 1);
+ mycursor = XCreatePixmapCursor(p->mydisplay, mypixmap, mypixmap, &col, &col, 0,0);
+ XDefineCursor(p->mydisplay, p->mywindow, mycursor);
+ }
+
+ XSelectInput(p->mydisplay,p->mywindow, ExposureMask);
+
+ XMapRaised(p->mydisplay,p->mywindow);
+ debug("Raised window\n");
+
+ /* ------------------------------------------------------- */
+ /* Suspend any screensavers if we can */
+
+ /* Install the signal handler to ensure cleanup */
+ dispwin_install_signal_handlers(p);
+
+#if ScreenSaverMajorVersion >= 1 && ScreenSaverMinorVersion >= 1 /* X11R7.1 ??? */
+
+ /* Disable any screensavers that work properly with XScreenSaverSuspend() */
+ if (XScreenSaverQueryExtension (p->mydisplay, &evb, &erb) != 0) {
+ int majv, minv;
+ XScreenSaverSuspend(p->mydisplay, True);
+ p->xsssuspend = 1;
+
+ /* Else we'd have to register as a screensaver to */
+ /* prevent another one activating ?? */
+ }
+#endif /* X11R7.1 screensaver extension */
+
+ /* Disable the native X11 screensaver */
+ if (p->xsssuspend == 0) {
+
+ /* Save the screensaver state, and then disable it */
+ XGetScreenSaver(p->mydisplay, &p->timeout, &p->interval,
+ &p->prefer_blanking, &p->allow_exposures);
+ XSetScreenSaver(p->mydisplay, 0, 0, DefaultBlanking, DefaultExposures);
+ p->xssvalid = 1;
+ }
+
+ /* Disable xscreensaver if it is running */
+ if (p->xssrunning == 0) {
+ p->xssrunning = (system("xscreensaver-command -version 2>/dev/null >/dev/null") == 0);
+ if (p->xssrunning)
+ system("xscreensaver-command -exit 2>/dev/null >/dev/null");
+ }
+
+ /* Disable gnomescreensaver if it is running */
+ if (p->gnomessrunning == 0) {
+ p->gnomessrunning = (system("gnome-screensaver-command -q "
+ "2>/dev/null >/dev/null") == 0);
+ if (p->gnomessrunning) {
+ sigset_t nsm, osm;
+ /* Ensure that other process doesn't get the signals we want to catch */
+ sigemptyset(&nsm);
+ sigaddset(&nsm,SIGHUP);
+ sigaddset(&nsm,SIGINT);
+ sigaddset(&nsm,SIGTERM);
+ sigprocmask(SIG_BLOCK, &nsm, &osm);
+
+ if ((p->gnomepid = fork()) == 0) {
+ freopen("/dev/null", "r", stdin);
+ freopen("/dev/null", "a", stdout); /* Hide output */
+ freopen("/dev/null", "a", stderr);
+ execlp("gnome-screensaver-command", "gnome-screensaver-command","-i","-n","argyll","-r","measuring screen",NULL);
+
+ _exit(0);
+ }
+ sigprocmask(SIG_SETMASK, &osm, NULL); /* restore the signals */
+ }
+ }
+
+ /* kscreensaver > 3.5.9 obeys XResetScreenSaver(), but earlier versions don't. */
+ /* Disable any KDE screen saver if it's active */
+ if (p->kdessrunning == 0) {
+ /* dcop is very slow if we're not actually running kde. */
+ /* Check that kde is running first */
+ if (system("ps -e 2>/dev/null | grep kdesktop 2>/dev/null >/dev/null") == 0) {
+ p->kdessrunning = (system("dcop kdesktop KScreensaverIface isEnabled "
+ "2>/dev/null | grep true 2>/dev/null >/dev/null") == 0);
+ }
+ if (p->kdessrunning) {
+ system("dcop kdesktop KScreensaverIface enable false 2>&1 >/dev/null");
+ }
+ }
+
+ /* If DPMS is enabled, disable it */
+ if (DPMSQueryExtension(p->mydisplay, &evb, &erb) != 0) {
+ CARD16 power_level;
+ BOOL state;
+
+ if (DPMSInfo(p->mydisplay, &power_level, &state)) {
+ if ((p->dpmsenabled = state) != 0)
+ DPMSDisable(p->mydisplay);
+ }
+ }
+
+ /* Deal with any pending events */
+ debug("About to enter main loop\n");
+ while(XPending(p->mydisplay) > 0) {
+ XNextEvent(p->mydisplay, &myevent);
+ switch(myevent.type) {
+ case Expose:
+ if(myevent.xexpose.count == 0) { /* Repare the exposed region */
+ debug("Servicing final expose\n");
+ XFillRectangle(p->mydisplay, p->mywindow, p->mygc,
+ p->tx, p->ty, p->tw, p->th);
+ debug("Finished expose\n");
+ }
+ break;
+ }
+ }
+ } else {
+ /* Install the signal handler to ensure cleanup */
+ dispwin_install_signal_handlers(p);
+ }
+ }
+#endif /* UNIX X11 */
+ /* -------------------------------------------------- */
+
+ if (!p->nowin) {
+ /* Setup for native mode */
+ if (p->native) {
+ debug("About to setup native mode\n");
+ if ((p->or = p->get_ramdac(p)) == NULL
+ || (p->r = p->or->clone(p->or)) == NULL) {
+ if (noramdac != NULL)
+ *noramdac = 1;
+ debugr("new_dispwin: Accessing VideoLUT failed, so no way to guarantee that calibration is turned off!!\n");
+ warning("new_dispwin: Accessing VideoLUT failed, so no way to guarantee that calibration is turned off!!");
+ p->native = 0;
+ } else {
+ p->r->setlin(p->r);
+ if (noramdac != NULL)
+ *noramdac = 0;
+ debug("Saved original VideoLUT\n");
+ }
+ } else {
+ if (p->get_ramdac(p) == NULL) {
+ if (noramdac != NULL)
+ *noramdac = 1;
+ }
+ }
+
+ /* Make sure initial test color is displayed */
+ dispwin_set_color(p, p->rgb[0], p->rgb[1], p->rgb[2]);
+ }
+
+ debugr("new_dispwin: return sucessfully\n");
+ return p;
+}
+
+/* ================================================================ */
+#if defined(UNIX_X11)
+/* Process to continuously monitor XRandR events, */
+/* and load the appropriate calibration and profiles */
+/* for each monitor. */
+int x11_daemon_mode(disppath *disp, int verb, int ddebug) {
+
+#if RANDR_MAJOR == 1 && RANDR_MINOR >= 2 && !defined(DISABLE_RANDR)
+ char *dname;
+ char *pp;
+ char dnbuf[100];
+ Display *mydisplay;
+ int majv, minv; /* Version */
+ int evb = 0, erb = 0;
+ int dopoll = 1; /* Until XRandR is fixed */
+ XEvent myevent;
+ int update_profiles = 1; /* Do it on entry */
+
+ /* Open the base display */
+ strncpy(dnbuf,disp->name,99); dnbuf[99] = '\000';
+ if ((pp = strrchr(dnbuf, ':')) != NULL) {
+ if ((pp = strchr(pp, '.')) == NULL)
+ strcat(dnbuf,".0");
+ else {
+ if (pp[1] == '\000')
+ strcat(dnbuf,"0");
+ else {
+ pp[1] = '0';
+ pp[2] = '\000';
+ }
+ }
+ }
+
+ if ((mydisplay = XOpenDisplay(dnbuf)) == NULL) {
+ debug2((errout, "x11_daemon_mode: failed to open display '%s'\n",dnbuf));
+ return -1;
+ }
+
+ if (verb) printf("Opened display '%s'\n",dnbuf);
+
+ /* !!!! we want to create a test here, to see if we have to poll, */
+ /* !!!! or whether we spontainously get events when the EDID changes. */
+
+ /* Use Xrandr 1.2 if it's available and not disabled */
+ if (getenv("ARGYLL_IGNORE_XRANDR1_2") == NULL
+ && XRRQueryExtension(mydisplay, &evb, &erb) != 0
+ && XRRQueryVersion(mydisplay, &majv, &minv)
+ && majv == 1 && minv >= 2) {
+ if (verb) printf("Found XRandR 1.2 or latter\n");
+
+ XRRSelectInput(mydisplay,RootWindow(mydisplay,0),
+ RRScreenChangeNotifyMask
+ | RRCrtcChangeNotifyMask
+ | RROutputChangeNotifyMask
+ | RROutputPropertyNotifyMask
+ );
+
+ /* Deal with any pending events */
+ if (verb) printf("About to enter main loop waiting for XRandR changes\n");
+ for(;;) {
+
+ if (update_profiles == 0) {
+ if (dopoll) {
+ for (;;) {
+ XRRGetScreenResources(mydisplay, RootWindow(mydisplay,0));
+ if(XPending(mydisplay) > 0)
+ break;
+ sleep(2);
+ }
+ } else {
+ /* Sleep until there is an event */
+ XPeekEvent(mydisplay, &myevent);
+ }
+ }
+
+ /* Get all our events until we run out */
+ while (XPending(mydisplay) > 0) {
+ XNextEvent(mydisplay, &myevent);
+ if (myevent.type == evb + RRScreenChangeNotify) {
+// printf("~1 Got RRScreenChangeNotify\n");
+ update_profiles = 1;
+ } else if (myevent.type == evb + RRNotify) {
+ update_profiles = 1;
+ XRRNotifyEvent *rrne = (XRRNotifyEvent *)(&myevent);
+ if (rrne->subtype == RRNotify_CrtcChange) {
+// printf("~1 Got RRCrtcChangeNotify\n");
+ }
+ else if (rrne->subtype == RRNotify_OutputChange) {
+// printf("~1 Got RROutputChangeNotify\n");
+ }
+ else if (rrne->subtype == RRNotify_OutputProperty) {
+// printf("~1 Got RROutputPropertyNotify\n");
+ }
+ }
+ }
+
+ if (update_profiles) {
+ disppath **dp;
+ ramdac *r = NULL;
+
+ if (verb) printf("Updating profiles for display '%s'\n",dnbuf);
+
+ dp = get_displays();
+ if (dp == NULL || dp[0] == NULL) {
+ if (verb) printf("Failed to enumerate all the screens for display '%s'\n",dnbuf);
+ continue;
+ } else {
+ int i, j;
+ dispwin *dw;
+ char calname[MAXNAMEL+1] = "\000"; /* Calibration file name */
+ icmFile *rd_fp = NULL;
+ icc *icco = NULL;
+ icmVideoCardGamma *wo;
+ double iv;
+
+ for (i = 0; ; i++) {
+ if (dp[i] == NULL)
+ break;
+ if (verb) printf("Updating display %d = '%s'\n",i+1,dp[i]->description);
+
+ if ((dw = new_dispwin(dp[i], 0.0, 0.0, 0.0, 0.0, 1, 0, NULL, 0, 0, ddebug)) == NULL) {
+ if (verb) printf("Failed to access screen %d of display '%s'\n",i+1,dnbuf);
+ continue;
+ }
+ if ((r = dw->get_ramdac(dw)) == NULL) {
+ if (verb) printf("Failed to access VideoLUT of screen %d for display '%s'\n",i+1,dnbuf);
+ dw->del(dw);
+ continue;
+ }
+
+ /* Grab the installed profile from the ucmm */
+ if ((rd_fp = dw->get_profile(dw, calname, MAXNAMEL)) == NULL) {
+ if (verb) printf("Failed to find profile of screen %d for display '%s'\n",i+1,dnbuf);
+ r->del(r);
+ dw->del(dw);
+ continue;
+ }
+
+ if ((icco = new_icc()) == NULL) {
+ if (verb) printf("Failed to create profile object for screen %d for display '%s'\n",i+1,dnbuf);
+ rd_fp->del(rd_fp);
+ r->del(r);
+ dw->del(dw);
+ continue;
+ }
+
+ /* Read header etc. */
+ if (icco->read(icco, rd_fp,0) != 0) { /* Read ICC OK */
+ if (verb) printf("Failed to read profile for screen %d for display '%s'\n",i+1,dnbuf);
+ icco->del(icco);
+ rd_fp->del(rd_fp);
+ r->del(r);
+ dw->del(dw);
+ continue;
+ }
+
+ if ((wo = (icmVideoCardGamma *)icco->read_tag(icco, icSigVideoCardGammaTag)) == NULL) {
+ if (verb) printf("Failed to fined vcgt tagd in profile for screen %d for display '%s' so setting linear\n",i+1,dnbuf);
+ for (j = 0; j < r->nent; j++) {
+ iv = j/(r->nent-1.0);
+ r->v[0][j] = iv;
+ r->v[1][j] = iv;
+ r->v[2][j] = iv;
+ }
+ } else {
+ if (wo->u.table.channels == 3) {
+ for (j = 0; j < r->nent; j++) {
+ iv = j/(r->nent-1.0);
+ r->v[0][j] = wo->lookup(wo, 0, iv);
+ r->v[1][j] = wo->lookup(wo, 1, iv);
+ r->v[2][j] = wo->lookup(wo, 2, iv);
+ }
+ } else if (wo->u.table.channels == 1) {
+ for (j = 0; j < r->nent; j++) {
+ iv = j/(r->nent-1.0);
+ r->v[0][j] =
+ r->v[1][j] =
+ r->v[2][j] = wo->lookup(wo, 0, iv);
+ }
+ debug("Got monochrom vcgt calibration\n");
+ } else {
+ if (verb) printf("vcgt tag is unrecognized in profile for screen %d for display '%s'\n",i+1,dnbuf);
+ icco->del(icco);
+ rd_fp->del(rd_fp);
+ r->del(r);
+ dw->del(dw);
+ continue;
+ }
+ }
+ if (dw->set_ramdac(dw,r,1) != 0) {
+ if (verb) printf("Unable to set vcgt tag for screen %d for display '%s'\n",i+1,dnbuf);
+ icco->del(icco);
+ rd_fp->del(rd_fp);
+ r->del(r);
+ dw->del(dw);
+ continue;
+ }
+ if (verb) printf("Loaded profile and calibration for screen %d for display '%s'\n",i+1,dnbuf);
+ icco->del(icco);
+ rd_fp->del(rd_fp);
+ r->del(r);
+ dw->del(dw);
+ }
+ }
+ free_disppaths(dp);
+ update_profiles = 0;
+ }
+ }
+ } else
+#endif /* randr >= V 1.2 */
+
+ if (verb) printf("XRandR 1.2 is not available - quitting\n");
+ return -1;
+}
+
+#endif
+
+/* ================================================================ */
+#ifdef STANDALONE_TEST
+/* test/utility code */
+
+#if defined(__APPLE__) && defined(__POWERPC__)
+
+/* Workaround for a ppc gcc 3.3 optimiser bug... */
+/* It seems to cause a segmentation fault instead of */
+/* converting an integer loop index into a float, */
+/* when there are sufficient variables in play. */
+static int gcc_bug_fix(int i) {
+ static int nn;
+ nn += i;
+ return nn;
+}
+#endif /* APPLE */
+
+#include "numlib.h"
+
+static void usage(char *diag, ...) {
+ disppath **dp;
+ fprintf(stderr,"Test display patch window, Set Video LUTs, Install profiles, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ if (diag != NULL) {
+ va_list args;
+ fprintf(stderr,"Diagnostic: ");
+ va_start(args, diag);
+ vfprintf(stderr, diag, args);
+ va_end(args);
+ fprintf(stderr,"\n");
+ }
+ fprintf(stderr,"usage: dispwin [options] [calfile] \n");
+ fprintf(stderr," -v Verbose mode\n");
+#if defined(UNIX_X11)
+ fprintf(stderr," -display displayname Choose X11 display name\n");
+ fprintf(stderr," -d n[,m] Choose the display n from the following list (default 1)\n");
+ fprintf(stderr," Optionally choose different display m for Video LUT access\n");
+#else
+ fprintf(stderr," -d n Choose the display from the following list (default 1)\n");
+#endif
+ dp = get_displays();
+ if (dp == NULL || dp[0] == NULL) {
+ fprintf(stderr," ** No displays found **\n");
+ } else {
+ int i;
+ for (i = 0; ; i++) {
+ if (dp[i] == NULL)
+ break;
+ fprintf(stderr," %d = '%s'\n",i+1,dp[i]->description);
+ }
+ }
+ free_disppaths(dp);
+ fprintf(stderr," -dweb[:port] Display via a web server at port (default 8080)\n");
+ fprintf(stderr," -P ho,vo,ss[,vs] Position test window and scale it\n");
+ fprintf(stderr," -F Fill whole screen with black background\n");
+ fprintf(stderr," -i Run forever with random values\n");
+ fprintf(stderr," -G filename Display RGB colors from CGATS file\n");
+ fprintf(stderr," -m Manually cycle through values\n");
+ fprintf(stderr," -f Test grey ramp fade\n");
+ fprintf(stderr," -r Test just Video LUT loading & Beeps\n");
+ fprintf(stderr," -n Test native output (rather than through Video LUT)\n");
+ fprintf(stderr," -s filename Save the currently loaded Video LUT to 'filename'\n");
+ fprintf(stderr," -c Load a linear display calibration\n");
+ fprintf(stderr," -V Verify that calfile/profile cal. is currently loaded in LUT\n");
+ fprintf(stderr," -I Install profile for display and use it's calibration\n");
+ fprintf(stderr," -U Un-install profile for display\n");
+ fprintf(stderr," -S d Specify the install/uninstall scope for OS X [nlu] or X11/Vista [lu]\n");
+ fprintf(stderr," d is one of: n = network, l = local system, u = user (default)\n");
+ fprintf(stderr," -L Load installed profiles cal. into Video LUT\n");
+#if defined(UNIX_X11)
+ fprintf(stderr," -E Run in daemon loader mode for given X11 server\n");
+#endif /* X11 */
+ fprintf(stderr," -D [level] Print debug diagnostics to stderr\n");
+ fprintf(stderr," calfile Load calibration (.cal or %s) into Video LUT\n",ICC_FILE_EXT);
+ exit(1);
+}
+
+/* 32 bit pseudo random sequencer based on XOR feedback */
+/* generates number between 1 and 4294967295 */
+#define PSRAND32(S) (((S) & 0x80000000) ? (((S) << 1) ^ 0xa398655d) : ((S) << 1))
+
+int
+main(int argc, char *argv[]) {
+ int fa, nfa, mfa; /* current argument we're looking at */
+ int verb = 0; /* Verbose flag */
+ int ddebug = 0; /* debug level */
+ int webdisp = 0; /* NZ for web display, == port number */
+ disppath *disp = NULL; /* Display being used */
+ double hpatscale = 1.0, vpatscale = 1.0; /* scale factor for test patch size */
+ double ho = 0.0, vo = 0.0; /* Test window offsets, -1.0 to 1.0 */
+ int blackbg = 0; /* NZ if whole screen should be filled with black */
+ int nowin = 0; /* Don't create test window */
+ int ramd = 0; /* Just test ramdac */
+ int fade = 0; /* Test greyramp fade */
+ int native = 0; /* 0 = use current current or given calibration curve */
+ /* 1 = set native linear output and use ramdac high prec'n */
+ /* 2 = set native linear output */
+ int noramdac = 0; /* Set to nz if there is no VideoLUT access */
+ int inf = 0; /* Infnite/manual patches flag */
+ char pcname[MAXNAMEL+1] = "\000"; /* CGATS patch color name */
+ int clear = 0; /* Clear any display calibration (any calname is ignored) */
+ char sname[MAXNAMEL+1] = "\000"; /* Current cal save name */
+ int verify = 0; /* Verify that calname is currently loaded */
+ int installprofile = 0; /* Install (1) or uninstall (2) a profile for display */
+ int loadprofile = 0; /* Load displays profile calibration into LUT */
+ int loadfile = 0; /* Load given profile into LUT */
+ p_scope scope = p_scope_user; /* Scope of profile instalation/un-instalation */
+ int daemonmode = 0; /* X11 daemin loader mode */
+ char calname[MAXNAMEL+1] = "\000"; /* Calibration file name */
+ dispwin *dw;
+ unsigned int seed = 0x56781234;
+ int i, j;
+ ramdac *or = NULL, *r = NULL;
+ int is_ok_icc = 0; /* The profile is OK */
+
+ error_program = "Dispwin";
+ check_if_not_interactive();
+
+ /* Process the arguments */
+ mfa = 0; /* Minimum final arguments */
+ for(fa = 1;fa < argc;fa++) {
+
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1+mfa) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage("Usage requested");
+
+ else if (argv[fa][1] == 'v')
+ verb = 1;
+
+ /* Debug */
+ else if (argv[fa][1] == 'D') {
+ ddebug = 1;
+ if (na != NULL && na[0] >= '0' && na[0] <= '9') {
+ ddebug = atoi(na);
+ fa = nfa;
+ }
+ callback_ddebug = ddebug; /* dispwin global */
+ }
+
+ /* Display number */
+ else if (argv[fa][1] == 'd') {
+ if (strncmp(na,"web",3) == 0
+ || strncmp(na,"WEB",3) == 0) {
+ webdisp = 8080;
+ if (na[3] == ':') {
+ webdisp = atoi(na+4);
+ if (webdisp == 0 || webdisp > 65535)
+ usage("Web port number must be in range 1..65535");
+ }
+ fa = nfa;
+ } else {
+#if defined(UNIX_X11)
+ int ix, iv;
+
+ /* X11 type display name. */
+ if (strcmp(&argv[fa][2], "isplay") == 0 || strcmp(&argv[fa][2], "ISPLAY") == 0) {
+ if (++fa >= argc || argv[fa][0] == '-') usage("-DISPLAY parameter missing");
+ setenv("DISPLAY", argv[fa], 1);
+ } else {
+ if (na == NULL) usage("-d parameter missing");
+ fa = nfa;
+ if (sscanf(na, "%d,%d",&ix,&iv) != 2) {
+ ix = atoi(na);
+ iv = 0;
+ }
+ if (disp != NULL)
+ free_a_disppath(disp);
+ if ((disp = get_a_display(ix-1)) == NULL)
+ usage("-d parameter '%s' is out of range",na);
+ if (iv > 0)
+ disp->rscreen = iv-1;
+ }
+#else
+ int ix;
+ if (na == NULL) usage("-d parameter is missing");
+ fa = nfa;
+ ix = atoi(na);
+ if (disp != NULL)
+ free_a_disppath(disp);
+ if ((disp = get_a_display(ix-1)) == NULL)
+ usage("-d parameter '%s' is out of range",na);
+#endif
+ }
+ }
+
+ /* Test patch offset and size */
+ else if (argv[fa][1] == 'P') {
+ fa = nfa;
+ if (na == NULL) usage("-p parameters are missing");
+ if (sscanf(na, " %lf,%lf,%lf,%lf ", &ho, &vo, &hpatscale, &vpatscale) == 4) {
+ ;
+ } else if (sscanf(na, " %lf,%lf,%lf ", &ho, &vo, &hpatscale) == 3) {
+ vpatscale = hpatscale;
+ } else {
+ usage("-p parameters '%s' is badly formatted",na);
+ }
+ if (ho < 0.0 || ho > 1.0
+ || vo < 0.0 || vo > 1.0
+ || hpatscale <= 0.0 || hpatscale > 50.0
+ || vpatscale <= 0.0 || vpatscale > 50.0)
+ usage("-p parameters '%s' is out of range",na);
+ ho = 2.0 * ho - 1.0;
+ vo = 2.0 * vo - 1.0;
+
+ /* Black background */
+ } else if (argv[fa][1] == 'F') {
+ blackbg = 1;
+
+ } else if (argv[fa][1] == 'i')
+ inf = 1;
+
+ else if (argv[fa][1] == 'm' || argv[fa][1] == 'M')
+ inf = 2;
+
+ /* CGATS patch color file */
+ else if (argv[fa][1] == 'G') {
+ fa = nfa;
+ if (na == NULL) usage("-G parameter is missing");
+ strncpy(pcname,na,MAXNAMEL); pcname[MAXNAMEL] = '\000';
+ }
+ else if (argv[fa][1] == 'f')
+ fade = 1;
+
+ else if (argv[fa][1] == 'r' || argv[fa][1] == 'R')
+ ramd = 1;
+
+ else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') {
+ native = 1;
+ if (argv[fa][1] == 'N')
+ native = 2;
+ }
+
+ else if (argv[fa][1] == 's') {
+ fa = nfa;
+ if (na == NULL) usage("-s parameter is missing");
+ strncpy(sname,na,MAXNAMEL); sname[MAXNAMEL] = '\000';
+ }
+
+ else if (argv[fa][1] == 'c' || argv[fa][1] == 'C')
+ clear = 1;
+
+ else if (argv[fa][1] == 'V')
+ verify = 1;
+
+ else if (argv[fa][1] == 'I')
+ installprofile = 1;
+
+ else if (argv[fa][1] == 'U')
+ installprofile = 2;
+
+ else if (argv[fa][1] == 'L')
+ loadprofile = 1;
+
+ else if (argv[fa][1] == 'E')
+ daemonmode = 1;
+
+ else if (argv[fa][1] == 'S') {
+ fa = nfa;
+ if (na == NULL) usage("-S parameter is missing");
+ if (na[0] == 'n' || na[0] == 'N')
+ scope = p_scope_network;
+ else if (na[0] == 'l' || na[0] == 'L')
+ scope = p_scope_local;
+ else if (na[0] == 'u' || na[0] == 'U')
+ scope = p_scope_user;
+ }
+ else
+ usage("Unknown flag '%s'",argv[fa]);
+ }
+ else
+ break;
+ }
+
+ /* No explicit display has been set */
+ if (webdisp == 0 && disp == NULL) {
+ int ix = 0;
+#if defined(UNIX_X11)
+ char *dn, *pp;
+
+ if ((dn = getenv("DISPLAY")) != NULL) {
+ if ((pp = strrchr(dn, ':')) != NULL) {
+ if ((pp = strchr(pp, '.')) != NULL) {
+ if (pp[1] != '\000')
+ ix = atoi(pp+1);
+ }
+ }
+ }
+#endif
+ if ((disp = get_a_display(ix)) == NULL)
+ error("Unable to open the default display");
+ }
+
+ /* See if there's a calibration file */
+ if (fa < argc) {
+ strncpy(calname,argv[fa++],MAXNAMEL); calname[MAXNAMEL] = '\000';
+ if (installprofile == 0 && loadprofile == 0 && verify == 0)
+ loadfile = 1; /* Load the given profile into the videoLUT */
+ }
+
+#if defined(UNIX_X11)
+ if (webdisp == 0 && daemonmode) {
+ return x11_daemon_mode(disp, verb, ddebug);
+ }
+#endif
+
+ /* Bomb on bad combinations (not all are being detected) */
+ if (installprofile && calname[0] == '\000')
+ error("Can't install or uninstall a displays profile without profile argument");
+
+ if (verify && calname[0] == '\000' && loadprofile == 0)
+ error("No calibration/profile provided to verify against");
+
+ if (verify && installprofile == 1)
+ error("Can't verify and install a displays profile at the same time");
+
+ if (verify && installprofile == 2)
+ error("Can't verify and uninstall a displays profile at the same time");
+
+ /* Don't create a window if it won't be used */
+ if (ramd != 0 || sname[0] != '\000' || clear != 0 || verify != 0 || loadfile != 0 || installprofile != 0 || loadprofile != 0)
+ nowin = 1;
+
+
+ if (webdisp != 0) {
+ if ((dw = new_webwin(webdisp, 100.0 * hpatscale, 100.0 * vpatscale, ho, vo, nowin, blackbg, verb, ddebug)) == NULL) {
+ printf("Error - new_webpwin failed!\n");
+ return -1;
+ }
+ noramdac = 1;
+
+ } else {
+ if (verb) printf("About to open dispwin object on the display\n");
+ if ((dw = new_dispwin(disp, 100.0 * hpatscale, 100.0 * vpatscale, ho, vo, nowin, native, &noramdac, blackbg, 1, ddebug)) == NULL) {
+ printf("Error - new_dispwin failed!\n");
+ return -1;
+ }
+ }
+
+ if (native != 0 && noramdac) {
+ error("We don't have access to the VideoLUT so can't display native colors\n");
+ }
+
+ /* Save the current Video LUT to the calfile */
+ if (sname[0] != '\000') {
+ cgats *ocg; /* output cgats structure */
+ time_t clk = time(0);
+ struct tm *tsp = localtime(&clk);
+ char *atm = asctime(tsp); /* Ascii time */
+ cgats_set_elem *setel; /* Array of set value elements */
+ int nsetel = 0;
+
+ if (verb)
+ printf("About to save current loaded calibration to file '%s'\n",sname);
+
+ if ((r = dw->get_ramdac(dw)) == NULL) {
+ error("We don't have access to the VideoLUT");
+ }
+
+ ocg = new_cgats(); /* Create a CGATS structure */
+ ocg->add_other(ocg, "CAL"); /* our special type is Calibration file */
+
+ ocg->add_table(ocg, tt_other, 0); /* Add a table for RAMDAC values */
+ ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Device Calibration Curves",NULL);
+ ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll synthcal", NULL);
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+ ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
+
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","DISPLAY", NULL);
+ ocg->add_kword(ocg, 0, "COLOR_REP", "RGB", NULL);
+
+ ocg->add_field(ocg, 0, "RGB_I", r_t);
+ ocg->add_field(ocg, 0, "RGB_R", r_t);
+ ocg->add_field(ocg, 0, "RGB_G", r_t);
+ ocg->add_field(ocg, 0, "RGB_B", r_t);
+
+ if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * 4)) == NULL)
+ error("Malloc failed!");
+
+ /* Write the video lut curve values */
+ for (i = 0; i < r->nent; i++) {
+ double iv = i/(r->nent-1.0);
+
+#if defined(__APPLE__) && defined(__POWERPC__)
+ gcc_bug_fix(i);
+#endif
+ setel[0].d = iv;
+ setel[1].d = r->v[0][i];
+ setel[2].d = r->v[1][i];
+ setel[3].d = r->v[2][i];
+
+ ocg->add_setarr(ocg, 0, setel);
+ }
+
+ free(setel);
+
+ if (ocg->write_name(ocg, sname))
+ error("Write error to '%s' : %s",sname,ocg->err);
+
+ ocg->del(ocg); /* Clean up */
+ r->del(r);
+ r = NULL;
+
+ /* Fall through, as we may want to do other stuff too */
+ }
+
+ /* Clear the display calibration curve */
+ if (clear != 0) {
+ int rv;
+
+ if ((r = dw->get_ramdac(dw)) == NULL) {
+ error("We don't have access to the VideoLUT");
+ }
+
+ for (i = 0; i < r->nent; i++) {
+ double iv = i/(r->nent-1.0);
+ r->v[0][i] = iv;
+ r->v[1][i] = iv;
+ r->v[2][i] = iv;
+ }
+ if (verb)
+ printf("About to clear the calibration\n");
+ if ((rv = dw->set_ramdac(dw,r,1)) != 0) {
+ if (rv == 2)
+ warning("Failed to set VideoLUTs persistently because current System Profile can't be renamed");
+ else
+ error("Failed to set VideoLUTs");
+ }
+
+ r->del(r);
+ r = NULL;
+
+ /* Fall through, as we may want to do other stuff too */
+ }
+
+ /* Un-Install the profile from the display */
+ if (installprofile == 2) {
+ int rv;
+ if ((rv = dw->uninstall_profile(dw, calname, scope))) {
+ if (rv == 2)
+ warning("Profile '%s' not found to uninstall!",calname);
+ else
+ error("Error trying to uninstall profile '%s'!",calname);
+ }
+ if (verb) {
+ printf("Un-Installed '%s'\n",calname);
+ }
+ }
+
+ /* Get any calibration from the provided .cal file or .profile, */
+ /* or calibration from the current default display profile, */
+ /* and put it in r */
+ if (loadfile != 0 || verify != 0 || loadprofile != 0 || installprofile == 1) {
+ icmFile *rd_fp = NULL;
+ icc *icco = NULL;
+ cgats *ccg = NULL; /* calibration cgats structure */
+
+ /* Get a calibration that's compatible with the display. */
+ /* This can fail and return NULL - error if we later need it */
+ r = dw->get_ramdac(dw);
+
+ /* Should we load calfile instead of installed profile if it's present ??? */
+ if (loadprofile) {
+ if (calname[0] != '\000')
+ warning("Profile '%s' provided as argument is being ignored!\n",calname);
+
+ /* Get the current displays profile */
+ debug2((errout,"Loading calibration from display profile '%s'\n",dw->name));
+ if ((rd_fp = dw->get_profile(dw, calname, MAXNAMEL)) == NULL)
+ error("Failed to get the displays current ICC profile\n");
+
+ } else {
+ /* Open up the profile for reading */
+ debug2((errout,"Loading calibration from file '%s'\n",calname));
+ if ((rd_fp = new_icmFileStd_name(calname,"r")) == NULL)
+ error("Can't open file '%s'",calname);
+ }
+
+ if ((icco = new_icc()) == NULL)
+ error("Creation of ICC object failed");
+
+ /* Read header etc. */
+ if (icco->read(icco, rd_fp,0) == 0) { /* Read ICC OK */
+ icmVideoCardGamma *wo;
+ double iv;
+
+ is_ok_icc = 1; /* The profile is OK */
+
+ if ((wo = (icmVideoCardGamma *)icco->read_tag(icco, icSigVideoCardGammaTag)) == NULL) {
+ warning("No vcgt tag found in profile - assuming linear\n");
+ if (r != NULL) {
+ for (i = 0; i < r->nent; i++) {
+ iv = i/(r->nent-1.0);
+ r->v[0][i] = iv;
+ r->v[1][i] = iv;
+ r->v[2][i] = iv;
+ }
+ }
+ } else {
+
+ /* Hmm. Perhaps we should ignore this if the vcgt is linear ?? */
+ if (r == NULL)
+ error("We don't have access to the VideoLUT");
+
+ if (wo->u.table.channels == 3) {
+ for (i = 0; i < r->nent; i++) {
+ iv = i/(r->nent-1.0);
+ r->v[0][i] = wo->lookup(wo, 0, iv);
+ r->v[1][i] = wo->lookup(wo, 1, iv);
+ r->v[2][i] = wo->lookup(wo, 2, iv);
+//printf("~1 entry %d = %f %f %f\n",i,r->v[0][i],r->v[1][i],r->v[2][i]);
+ }
+ debug("Got color vcgt calibration\n");
+ } else if (wo->u.table.channels == 1) {
+ for (i = 0; i < r->nent; i++) {
+ iv = i/(r->nent-1.0);
+ r->v[0][i] =
+ r->v[1][i] =
+ r->v[2][i] = wo->lookup(wo, 0, iv);
+ }
+ debug("Got monochrom vcgt calibration\n");
+ } else {
+ r->del(r);
+ r = NULL;
+ }
+ }
+ } else { /* See if it's a .cal file */
+ int ncal;
+ int ii, fi, ri, gi, bi;
+ double cal[3][256];
+
+ icco->del(icco); /* Don't need these now */
+ icco = NULL;
+ rd_fp->del(rd_fp);
+ rd_fp = NULL;
+
+ ccg = new_cgats(); /* Create a CGATS structure */
+ ccg->add_other(ccg, "CAL"); /* our special calibration type */
+
+ if (ccg->read_name(ccg, calname)) {
+ ccg->del(ccg);
+ error("File '%s' is not a valid ICC profile or Argyll .cal file",calname);
+ }
+
+ if (ccg->ntables == 0 || ccg->t[0].tt != tt_other || ccg->t[0].oi != 0)
+ error("Calibration file isn't a CAL format file");
+ if (ccg->ntables < 1)
+ error("Calibration file '%s' doesn't contain at least one table",calname);
+
+ if ((ncal = ccg->t[0].nsets) <= 0)
+ error("No data in set of file '%s'",calname);
+
+ if (ncal != 256)
+ error("Expect 256 data sets in file '%s'",calname);
+
+ if ((fi = ccg->find_kword(ccg, 0, "DEVICE_CLASS")) < 0)
+ error("Calibration file '%s' doesn't contain keyword COLOR_REP",calname);
+ if (strcmp(ccg->t[0].kdata[fi],"DISPLAY") != 0)
+ error("Calibration file '%s' doesn't have DEVICE_CLASS of DISPLAY",calname);
+
+ if ((ii = ccg->find_field(ccg, 0, "RGB_I")) < 0)
+ error("Calibration file '%s' doesn't contain field RGB_I",calname);
+ if (ccg->t[0].ftype[ii] != r_t)
+ error("Field RGB_R in file '%s' is wrong type",calname);
+ if ((ri = ccg->find_field(ccg, 0, "RGB_R")) < 0)
+ error("Calibration file '%s' doesn't contain field RGB_R",calname);
+ if (ccg->t[0].ftype[ri] != r_t)
+ error("Field RGB_R in file '%s' is wrong type",calname);
+ if ((gi = ccg->find_field(ccg, 0, "RGB_G")) < 0)
+ error("Calibration file '%s' doesn't contain field RGB_G",calname);
+ if (ccg->t[0].ftype[gi] != r_t)
+ error("Field RGB_G in file '%s' is wrong type",calname);
+ if ((bi = ccg->find_field(ccg, 0, "RGB_B")) < 0)
+ error("Calibration file '%s' doesn't contain field RGB_B",calname);
+ if (ccg->t[0].ftype[bi] != r_t)
+ error("Field RGB_B in file '%s' is wrong type",calname);
+ for (i = 0; i < ncal; i++) {
+ cal[0][i] = *((double *)ccg->t[0].fdata[i][ri]);
+ cal[1][i] = *((double *)ccg->t[0].fdata[i][gi]);
+ cal[2][i] = *((double *)ccg->t[0].fdata[i][bi]);
+ }
+
+ if (r == NULL)
+ error("We don't have access to the VideoLUT");
+
+ /* Interpolate from cal value to RAMDAC entries */
+ for (i = 0; i < r->nent; i++) {
+ double val, w;
+ unsigned int ix;
+
+ val = (ncal-1.0) * i/(r->nent-1.0);
+ ix = (unsigned int)floor(val); /* Coordinate */
+ if (ix > (ncal-2))
+ ix = (ncal-2);
+ w = val - (double)ix; /* weight */
+ for (j = 0; j < 3; j++) {
+ val = cal[j][ix];
+ r->v[j][i] = val + w * (cal[j][ix+1] - val);
+ }
+ }
+ debug("Got cal file calibration\n");
+ }
+ if (ccg != NULL)
+ ccg->del(ccg);
+ if (icco != NULL)
+ icco->del(icco);
+ if (rd_fp != NULL)
+ rd_fp->del(rd_fp);
+ }
+
+ /* Install the profile into the display and set as the default */
+ if (installprofile == 1) {
+ if (is_ok_icc == 0)
+ error("File '%s' doesn't seem to be an ICC profile!",calname);
+
+ if (verb)
+ printf("About to install '%s' as display's default profile\n",calname);
+ if (dw->install_profile(dw, calname, r, scope)) {
+ error("Failed to install profile '%s'!",calname);
+ }
+ if (verb) {
+ printf("Installed '%s' and made it the default\n",calname);
+ }
+
+ /* load the LUT with the calibration from the given file or the current display profile. */
+ /* (But don't load profile calibration if we're verifying against it) */
+ } else if (loadfile != 0 || (loadprofile != 0 && verify == 0)) {
+ int rv;
+
+ /* r == NULL if no VideoLUT access and ICC profile without vcgt */
+ if (r == NULL) {
+ warning("No linear calibration loaded because there is no access to the VideoLUT");
+ } else {
+ if (verb)
+ printf("About to set display to given calibration\n");
+ if ((rv = dw->set_ramdac(dw,r,1)) != 0) {
+ if (rv == 2)
+ error("Failed to set VideoLUTs persistently because current System Profile can't be renamed");
+ else
+ error("Failed to set VideoLUTs");
+ }
+ if (verb)
+ printf("Calibration set\n");
+ }
+ }
+
+ if (verify != 0) {
+ int ver = 1;
+ double berr = 0.0;
+ if ((or = dw->get_ramdac(dw)) == NULL)
+ error("Unable to get current VideoLUT for verify");
+
+ if (r == NULL)
+ error("No calibration to verify against");
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < r->nent; i++) {
+ double err;
+ err = fabs(r->v[j][i] - or->v[j][i]);
+ if (err > berr)
+ berr = err;
+ if (err > VERIFY_TOL) {
+ ver = 0;
+ }
+ }
+ }
+ if (ver)
+ printf("Verify: '%s' IS loaded (discrepancy %.1f%%)\n", calname, berr * 100);
+ else
+ printf("Verify: '%s' is NOT loaded (discrepancy %.1f%%)\n", calname, berr * 100);
+ or->del(or);
+ or = NULL;
+ }
+ if (r != NULL) {
+ r->del(r);
+ r = NULL;
+ }
+
+ /* If no other command selected, do a Window or VideoLUT test */
+ if (sname[0] == '\000' && clear == 0 && installprofile == 0 && loadfile == 0 && verify == 0 && loadprofile == 0) {
+
+ if (ramd == 0) {
+
+ if (fade) {
+ int i;
+ int steps = 256;
+ for (i = 0; i < steps; i++) {
+ double tt;
+ tt = i/(steps - 1.0);
+ dw->set_color(dw, tt, tt, tt);
+ msec_sleep(20);
+ printf("Val = %f\n",tt);
+ }
+
+ /* Patch colors from a CGATS file */
+ } else if (pcname[0] != '\000') {
+ cgats *icg;
+ int i, npat;
+ int ri, gi, bi;
+ int si = -1;
+
+ if ((icg = new_cgats()) == NULL)
+ error("new_cgats() failed\n");
+ icg->add_other(icg, ""); /* Allow any signature file */
+
+ if (icg->read_name(icg, pcname))
+ error("File '%s' read error : %s",pcname, icg->err);
+
+ if (icg->ntables < 1) /* We don't use second table at the moment */
+ error ("Input file '%s' doesn't contain at least one table",pcname);
+
+ if ((npat = icg->t[0].nsets) <= 0)
+ error ("File '%s has no sets of data in the first table",pcname);
+
+ si = icg->find_field(icg, 0, "SAMPLE_ID");
+ if (si >= 0 && icg->t[0].ftype[si] != nqcs_t)
+ error("In file '%s' field SAMPLE_ID is wrong type",pcname);
+
+ if ((ri = icg->find_field(icg, 0, "RGB_R")) < 0)
+ error ("Input file '%s' doesn't contain field RGB_R",pcname);
+ if (icg->t[0].ftype[ri] != r_t)
+ error ("In file '%s' field RGB_R is wrong type",pcname);
+ if ((gi = icg->find_field(icg, 0, "RGB_G")) < 0)
+ error ("Input file '%s' doesn't contain field RGB_G",pcname);
+ if (icg->t[0].ftype[gi] != r_t)
+ error ("In file '%s' field RGB_G is wrong type",pcname);
+ if ((bi = icg->find_field(icg, 0, "RGB_B")) < 0)
+ error ("Input file '%s' doesn't contain field RGB_B",pcname);
+ if (icg->t[0].ftype[bi] != r_t)
+ error ("In file '%s' field RGB_B is wrong type",pcname);
+
+ if (inf == 2)
+ printf("\nHit return to advance each color\n");
+
+ if (inf == 2) {
+ printf("\nHit return to start\n");
+ getchar();
+ }
+ for (i = 0; i < npat; i++) {
+ double r, g, b;
+ r = *((double *)icg->t[0].fdata[i][ri]) / 100.0;
+ g = *((double *)icg->t[0].fdata[i][gi]) / 100.0;
+ b = *((double *)icg->t[0].fdata[i][bi]) / 100.0;
+
+ if (si >= 0)
+ printf("Patch id '%s'",((char *)icg->t[0].fdata[i][si]));
+ else
+ printf("Patch no %d",i+1);
+ printf(" color %f %f %f\n",r,g,b);
+
+ dw->set_color(dw, r, g, b);
+
+ if (inf == 2)
+ getchar();
+ else
+ sleep(2);
+ }
+ icg->del(icg);
+
+ /* Preset and random patch colors */
+ } else {
+
+ if (inf == 2)
+ printf("\nHit return to advance each color\n");
+
+ printf("Setting White\n");
+ dw->set_color(dw, 1.0, 1.0, 1.0); /* White */
+
+ if (inf == 2)
+ getchar();
+ else
+ sleep(2);
+
+ printf("Setting 75%% Grey\n");
+ dw->set_color(dw, 0.75, 0.75, 0.75); /* Grey */
+
+ if (inf == 2)
+ getchar();
+ else
+ sleep(2);
+
+ printf("Setting 50%% Grey\n");
+ dw->set_color(dw, 0.5, 0.5, 0.5); /* Grey */
+
+ if (inf == 2)
+ getchar();
+ else
+ sleep(2);
+
+ printf("Setting 25%% Grey\n");
+ dw->set_color(dw, 0.25, 0.25, 0.25); /* Grey */
+
+ if (inf == 2)
+ getchar();
+ else
+ sleep(2);
+
+ printf("Setting 12.5%% Grey\n");
+ dw->set_color(dw, 0.125, 0.125, 0.125); /* Grey */
+
+ if (inf == 2)
+ getchar();
+ else
+ sleep(2);
+
+ printf("Setting Black\n");
+ dw->set_color(dw, 0.0, 0.0, 0.0);
+
+ if (inf == 2)
+ getchar();
+ else
+ sleep(2);
+
+ printf("Setting Red\n");
+ dw->set_color(dw, 1.0, 0.0, 0.0); /* Red */
+
+ if (inf == 2)
+ getchar();
+ else
+ sleep(2);
+
+ printf("Setting Green\n");
+ dw->set_color(dw, 0.0, 1.0, 0.0); /* Green */
+
+ if (inf == 2)
+ getchar();
+ else
+ sleep(2);
+
+ printf("Setting Blue\n");
+ dw->set_color(dw, 0.0, 0.0, 1.0); /* Blue */
+
+ if (inf == 2)
+ getchar();
+ else
+ sleep(2);
+
+ printf("Setting Cyan\n");
+ dw->set_color(dw, 0.0, 1.0, 1.0); /* Cyan */
+
+ if (inf == 2)
+ getchar();
+ else
+ sleep(2);
+
+ printf("Setting Magenta\n");
+ dw->set_color(dw, 1.0, 0.0, 1.0); /* Magenta */
+
+ if (inf == 2)
+ getchar();
+ else
+ sleep(2);
+
+ printf("Setting Yellow\n");
+ dw->set_color(dw, 1.0, 1.0, 0.0); /* Yellow */
+
+ if (inf == 2)
+ getchar();
+ else
+ sleep(2);
+
+ printf("Setting 50%% Red\n");
+ dw->set_color(dw, 0.5, 0.0, 0.0); /* Red */
+
+ if (inf == 2)
+ getchar();
+ else
+ sleep(2);
+
+ printf("Setting 50%% Green\n");
+ dw->set_color(dw, 0.0, 0.5, 0.0); /* Green */
+
+ if (inf == 2)
+ getchar();
+ else
+ sleep(2);
+
+ printf("Setting 50%% Blue\n");
+ dw->set_color(dw, 0.0, 0.0, 0.5); /* Blue */
+
+ if (inf == 2)
+ getchar();
+ else
+ sleep(2);
+
+ if (inf == 1) {
+ for (;inf != 0;) {
+ double col[3];
+
+ for (i = 0; i < 3; i++) {
+ seed = PSRAND32(seed);
+ col[i] = seed/4294967295.0;
+ }
+
+ printf("Setting %f %f %f\n",col[0],col[1],col[2]);
+ dw->set_color(dw, col[0],col[1],col[2]);
+
+ if (inf == 2)
+ getchar();
+ else
+ sleep(2);
+
+ }
+ }
+ }
+ }
+
+ if (inf != 2) {
+ /* Test out the VideoLUT access */
+ if ((dw->or = dw->get_ramdac(dw)) != NULL) { /* Use dw->or so signal will restore */
+
+ r = dw->or->clone(dw->or);
+
+ /* Try darkening it */
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < r->nent; i++) {
+ r->v[j][i] = pow(dw->or->v[j][i], 2.0);
+ }
+ }
+ printf("Darkening screen\n");
+ if (dw->set_ramdac(dw,r,0)) {
+ dw->set_ramdac(dw,or,0);
+ error("Failed to set VideoLUTs");
+ }
+ sleep(1);
+
+ /* Try lightening it */
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < r->nent; i++) {
+ r->v[j][i] = pow(dw->or->v[j][i], 0.5);
+ }
+ }
+ printf("Lightening screen\n");
+ if (dw->set_ramdac(dw,r,0)) {
+ dw->set_ramdac(dw,or,0);
+ error("Failed to set VideoLUTs");
+ }
+ sleep(1);
+
+ /* restor it */
+ printf("Restoring screen\n");
+ if (dw->set_ramdac(dw,dw->or,0)) {
+ error("Failed to set VideoLUTs");
+ }
+
+ r->del(r);
+ dw->or->del(dw->or);
+ dw->or = NULL;
+
+ } else {
+ printf("We don't have access to the VideoLUT\n");
+ }
+
+ /* Test out the beeps */
+ printf("Normal beep\n");
+ normal_beep();
+
+ sleep(1);
+
+ printf("Good beep\n");
+ good_beep();
+
+ sleep(1);
+
+ printf("Bad double beep\n");
+ bad_beep();
+
+ sleep(2); /* Allow beep to complete */
+ }
+ }
+
+ if (disp != NULL)
+ free_a_disppath(disp);
+
+ if (verb)
+ printf("About to destroy dispwin object\n");
+
+ dw->del(dw);
+
+ return 0;
+}
+
+#endif /* STANDALONE_TEST */
+
+/* ================================================================ */
+/* Possible ThinkPad MSWin code to keep the main screen on */
+
+#ifdef NEVER
+
+Public Class Form1
+ Declare Sub XRCalibrationLidTurnOnNotification Lib "C:\Program Files (x86)\X-Rite\PANTONE Color Calibrator\XRLaptopIFSDK.dll" ()
+ Declare Sub XRCalibrationLidTurnOffNotification Lib "C:\Program Files (x86)\X-Rite\PANTONE Color Calibrator\XRLaptopIFSDK.dll" ()
+
+ Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
+
+ End Sub
+
+ Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
+ Call XRCalibrationLidTurnOnNotification()
+ End Sub
+
+ Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
+ Call XRCalibrationLidTurnOffNotification()
+ End Sub
+End Class
+
+#endif
+
+/* ================================================================ */
+/* Unused Apple code */
+
+#ifdef NEVER
+ /* Got displays, now have a look through them */
+ for (i = 0; i < dcount; i++) {
+ GDHandle gdh;
+ GDPtr gdp;
+
+ /* Dump display mode dictionary */
+ CFIndex nde, j;
+ CFDictionaryRef dr;
+ void **keys, **values;
+
+ dr = CGDisplayCurrentMode(dids[i]);
+ nde = CFDictionaryGetCount(dr);
+
+ printf("Dict contains %d entries \n", nde);
+ if ((keys = (void **)malloc(nde * sizeof(void *))) == NULL) {
+ debug("malloc failed for disp mode keys\n");
+ free_disppaths(disps);
+ free(dids);
+ return NULL;
+ }
+ if ((values = (void **)malloc(nde * sizeof(void *))) == NULL) {
+ debug("malloc failed for disp mode values\n");
+ free(keys);
+ free_disppaths(disps);
+ free(dids);
+ return NULL;
+ }
+ CFDictionaryGetKeysAndValues(dr, (const void **)keys, (const void **)values);
+ for (j = 0; j < nde; j++) {
+ printf("Entry %d key = %s\n", j, CFStringGetCStringPtr(keys[j], kCFStringEncodingMacRoman));
+ }
+ free(values);
+ free(keys);
+ }
+#endif
+
+
+#ifdef NEVER
+/* How to install profiles for devices. */
+
+/* Callback to locate a profile ID. */
+struct idp_rec {
+ CMDeviceID ddid; /* Device ID */
+// char *fname; /* Profile we're trying to find */
+ CMDeviceProfileID id; /* Corresponding ID */
+ CMDeviceScope dsc; /* Matching device scope */
+ int found; /* Flag indicating it's been found */
+};
+
+OSErr ItDevProfProc (
+const CMDeviceInfo *di,
+const NCMDeviceProfileInfo *pi,
+void *refCon)
+{
+ CMError ev;
+ struct idp_rec *r = (struct idp_rec *)refCon;
+ CMDeviceProfileID id;
+
+ if (di->deviceClass != cmDisplayDeviceClass
+ || di->deviceID != r->ddid) {
+ return noErr;
+ }
+
+ /* We'd qualify on the device mode too (deviceState ??), */
+ /* if we wanted to replace a profile for a particular mode. */
+
+ /* Assume this is a display with only one mode, and return */
+ /* the profile id and device scope */
+ r->id = pi->profileID;
+ r->dsc = di->deviceScope;
+ r->found = 1;
+ return noErr;
+//printf("~1 got match\n");
+
+/* Callback to locate a profile ID. */
+struct idp_rec {
+ CMDeviceID ddid; /* Device ID */
+ CMDeviceProfileID id; /* Corresponding ID */
+ CMDeviceScope dsc; /* Matching device scope */
+ int found; /* Flag indicating it's been found */
+};
+
+OSErr ItDevProfProc (
+const CMDeviceInfo *di,
+const NCMDeviceProfileInfo *pi,
+void *refCon)
+{
+ CMError ev;
+ struct idp_rec *r = (struct idp_rec *)refCon;
+ CMDeviceProfileID id;
+
+ if (di->deviceClass != cmDisplayDeviceClass
+ || di->deviceID != r->ddid) {
+ return noErr;
+ }
+
+ /* Assume this is a display with only one mode, and return */
+ /* the profile id and device scope */
+ r->id = pi->profileID;
+ r->dsc = di->deviceScope;
+ r->found = 1;
+ return noErr;
+}
+
+#ifndef NEVER
+
+/* Given a location, return a string for it's path */
+static char *plocpath(CMProfileLocation *ploc) {
+
+ if (ploc->locType == cmFileBasedProfile) {
+ FSRef newRef;
+ static UInt8 path[256] = "";
+//printf("~1 converted spec file location\n");
+
+ if (FSpMakeFSRef(&ploc->u.fileLoc.spec, &newRef) == noErr) {
+ OSStatus stus;
+ if ((stus = FSRefMakePath(&newRef, path, 256)) == 0 || stus == fnfErr) {
+ return path;
+ }
+ }
+ return strdup(path);
+ } else if (ploc->locType == cmPathBasedProfile) {
+ return strdup(ploc->u.pathLoc.path);
+ }
+ return NULL;
+}
+
+#else
+
+/* fss2path takes the FSSpec of a file, folder or volume and returns it's POSIX (?) path. */
+/* Return NULL on error. Free returned string */
+/* NOTE FSSpec is deprecated in 10.5. Replace with FSRef ?? */
+static char *fss2path(FSSpec *fss) {
+ int i, l; /* fss->name contains name of last item in path */
+ char *path;
+
+ l = fss->name[0];
+ if ((path = malloc(l + 1)) == NULL)
+ return NULL;
+ for (i = 0; i < l; i++) {
+ if (fss->name[i+1] == '/')
+ path[i] = ':';
+ else
+ path[i] = fss->name[i+1];
+ }
+ path[i] = '\000';
+//printf("~1 path = '%s', l = %d\n",path,l);
+
+ if(fss->parID != fsRtParID) { /* path is more than just a volume name */
+ FSSpec tfss;
+// CInfoPBRec pb;
+ FSRefParam pb;
+ int tl;
+ char *tpath;
+
+ memmove(&tfss, fss, sizeof(FSSpec)); /* Copy so we can modify */
+ memset(&pb, 0, sizeof(FSRefParam));
+ pb.ioNamePtr = tfss.name;
+ pb.ioVRefNum = tfss.vRefNum;
+ pb.ioDrParID = tfss.parID;
+ do {
+ pb.ioFDirIndex = -1; /* get parent directory name */
+ pb.ioDrDirID = pb.dirInfo.ioDrParID;
+ if(PBGetCatlogInfoSync(&pb) != noErr) {
+ free(path);
+ return NULL;
+ }
+
+ /* Pre-pend the directory name separated by '/' */
+ if (pb.dirInfo.ioDrDirID == fsRtDirID) {
+ tl = 0; /* Don't pre-pend volume name */
+ } else {
+ tl = tfss.name[0];
+ }
+ if ((tpath = malloc(tl + l + 1)) == NULL) {
+ free(path);
+ return NULL;
+ }
+ for (i = 0; i < tl; i++) {
+ if (tfss.name[i+1] == '/')
+ tpath[i] = ':';
+ else
+ tpath[i] = tfss.name[i+1];
+ }
+ tpath[i] = '/';
+ for (i = 0; i < l; i++)
+ tpath[tl+1+i] = path[i];
+ tpath[tl+1+i] = '\000';
+ free(path);
+ path = tpath;
+ l = tl + 1 + l;
+//printf("~1 path = '%s', l = %d\n",path,l);
+ } while(pb.dirInfo.ioDrDirID != fsRtDirID); /* while more directory levels */
+ }
+
+ return path;
+}
+
+/* Return a string containing the profiles path. */
+/* Return NULL on error. Free the string after use. */
+static char *plocpath(CMProfileLocation *ploc) {
+ if (ploc->locType == cmFileBasedProfile) {
+ return fss2path(&ploc->u.fileLoc.spec);
+ } else if (ploc->locType == cmPathBasedProfile) {
+ return strdup(ploc->u.pathLoc.path);
+ }
+ return NULL;
+}
+
+#endif
+
+/* Test code that checks what the current display default profile is, three ways */
+static void pcurpath(dispwin *p) {
+ CMProfileRef xprof; /* Current AVID profile */
+ CMProfileLocation xploc; /* Current profile location */
+ UInt32 xplocsz = sizeof(CMProfileLocation);
+ struct idp_rec cb;
+ CMError ev;
+
+ /* Get the current installed profile */
+ if ((ev = CMGetProfileByAVID((CMDisplayIDType)p->ddid, &xprof)) != noErr) {
+ debug2((errout,"CMGetProfileByAVID() failed with error %d\n",ev));
+ goto skip;
+ }
+
+ /* Get the current installed profile's location */
+ if ((ev = NCMGetProfileLocation(xprof, &xploc, &xplocsz)) != noErr) {
+ debug2((errout,"NCMGetProfileLocation() failed with error %d\n",ev));
+ goto skip;
+ }
+
+//printf("~1 Current profile by AVID = '%s'\n",plocpath(&xploc));
+
+ /* Get the current CMDeviceProfileID and device scope */
+ cb.ddid = (CMDeviceID)p->ddid; /* Display Device ID */
+ cb.found = 0;
+
+ if ((ev = CMIterateDeviceProfiles(ItDevProfProc, NULL, NULL, cmIterateAllDeviceProfiles, (void *)&cb)) != noErr) {
+ debug2((errout,"CMIterateDeviceProfiles() failed with error %d\n",ev));
+ goto skip;
+ }
+ if (cb.found == 0) {
+ debug2((errout,"Failed to find exsiting profiles ID\n"));
+ goto skip;
+ }
+ /* Got cb.id */
+
+ if ((ev = CMGetDeviceProfile(cmDisplayDeviceClass, (CMDeviceID)p->ddid, cb.id, &xploc)) != noErr) {
+ debug2((errout,"Failed to GetDeviceProfile\n"));
+ goto skip;
+ }
+//printf("~1 Current profile by Itterate = '%s'\n",plocpath(&xploc));
+
+ /* Get the default ID for the display */
+ if ((ev = CMGetDeviceDefaultProfileID(cmDisplayDeviceClass, (CMDeviceID)p->ddid, &cb.id)) != noErr) {
+ debug2((errout,"CMGetDeviceDefaultProfileID() failed with error %d\n",ev));
+ goto skip;
+ }
+
+ /* Get the displays default profile */
+ if ((ev = CMGetDeviceProfile(cmDisplayDeviceClass, (CMDeviceID)p->ddid, cb.id, &xploc)) != noErr) {
+ debug2((errout,"CMGetDeviceDefaultProfileID() failed with error %d\n",ev));
+ goto skip;
+ }
+//printf("~1 Current profile by get default = '%s'\n",plocpath(&xploc));
+
+ skip:;
+}
+ /* If we want the path to the profile, we'd do this: */
+
+ printf("id = 0x%x\n",pi->profileID);
+ if (pi->profileLoc.locType == cmFileBasedProfile) {
+ FSRef newRef;
+ UInt8 path[256] = "";
+
+ if (FSpMakeFSRef(&pi->profileLoc.u.fileLoc.spec, &newRef) == noErr) {
+ OSStatus stus;
+ if ((stus = FSRefMakePath(&newRef, path, 256)) == 0 || stus == fnfErr) {
+ printf("file = '%s'\n",path);
+ if (strcmp(r->fname, (char *)path) == 0) {
+ r->id = pi->profileID;
+ r->found = 1;
+ printf("got match\n");
+ }
+ }
+ }
+ } else if (pi->profileLoc.locType == cmPathBasedProfile) {
+ if (strcmp(r->fname, pi->profileLoc.u.pathLoc.path) == 0) {
+ r->id = pi->profileID;
+ r->dsc = di->deviceScope;
+ r->found = 1;
+ printf("got match\n");
+ }
+ }
+
+
+ {
+ struct idp_rec cb;
+
+ /* The CMDeviceProfileID wll always be 1 for a display, because displays have only one mode, */
+ /* so the Iterate could be avoided for it. */
+
+ cb.ddid = (CMDeviceID)p->ddid; /* Display Device ID */
+// cb.fname = dpath;
+ cb.found = 0;
+
+ if ((ev = CMIterateDeviceProfiles(ItDevProfProc, NULL, NULL, cmIterateAllDeviceProfiles, (void *)&cb)) != noErr) {
+ debug2((errout,"CMIterateDeviceProfiles() failed with error %d\n",dpath,ev));
+ return 1;
+ }
+ if (cb.found == 0) {
+ debug2((errout,"Failed to find exsiting profiles ID, so ca't set it as default\n"));
+ return 1;
+ }
+ if ((ev = CMSetDeviceProfile(cmDisplayDeviceClass, (CMDeviceID)p->ddid, &cb.dsc, cb.id, &dploc)) != noErr) {
+ debug2((errout,"CMSetDeviceProfile() failed for file '%s' with error %d\n",dpath,ev));
+ return 1;
+ }
+ /* There is no point in doing the following, because displays only have one mode. */
+ /* Make it the default for the display */
+ if ((ev = CMSetDeviceDefaultProfileID(cmDisplayDeviceClass, (CMDeviceID)p->ddid, cb.id)) != noErr) {
+ debug2((errout,"CMSetDeviceDefaultProfileID() failed for file '%s' with error %d\n",dpath,ev));
+ return 1;
+ }
+
+#endif /* NEVER */
+
+#ifdef NEVER
+ /* Unused 10.6+ code */
+ CFUUIDRef dispuuid;
+ CFDictionaryRef dict, sdict, pdict;
+ CFStringRef id, urlstr;
+ CFURLRef url;
+ CFIndex bufSize;
+ char *dpath = NULL; /* return value */
+
+ if ((dispuuid = CGDisplayCreateUUIDFromDisplayID(p->ddid)) == NULL) {
+ debugr2((errout,"CGDisplayCreateUUIDFromDisplayID() failed\n"));
+ return NULL;
+ }
+
+ if ((dict = ColorSyncDeviceCopyDeviceInfo(kColorSyncDisplayDeviceClass, dispuuid)) == NULL) {
+ debugr2((errout,"ColorSyncDeviceCopyDeviceInfo() failed\n"));
+ CFRelease(dispuuid);
+ return NULL;
+ }
+
+ /* Get the factory profile dictionary */
+ if ((sdict = CFDictionaryGetValue(dict, kColorSyncFactoryProfiles)) == NULL) {
+ debugrr("Failed to get kColorSyncFactoryProfiles\n");
+ CFRelease(dict);
+ CFRelease(dispuuid);
+ return NULL;
+ }
+ /* Lookup the default profile ID */
+ if ((id = CFDictionaryGetValue(sdict, kColorSyncDeviceDefaultProfileID)) == NULL) {
+ debugrr("Failed to get kColorSyncDeviceDefaultProfileID\n");
+ CFRelease(dict);
+ CFRelease(dispuuid);
+ return NULL;
+ }
+
+// printf("~1 got ProfileID '%s'\n",CFStringGetCStringPtr(id, kCFStringEncodingMacRoman));
+
+ /* See if this ProfileID is in the factory profile dictionary */
+ if ((pdict = CFDictionaryGetValue(sdict, id)) != NULL) {
+ } else {
+ debugrr("Failed to get factory profile with id \n");
+
+ /* Get the custom profile dictionary */
+ if ((sdict = CFDictionaryGetValue(dict, kColorSyncCustomProfiles)) == NULL) {
+ debugrr("Failed to get kColorSyncCustomProfiles\n");
+ CFRelease(dict);
+ CFRelease(dispuuid);
+ return NULL;
+ }
+ /* See if the default id is in the custom profile dictionary */
+ if ((pdict = CFDictionaryGetValue(sdict, id)) == NULL) {
+ debugrr("Failed to locate default profile in factory or custom profile list\n");
+ CFRelease(dict);
+ CFRelease(dispuuid);
+ return NULL;
+ }
+ }
+
+ /* get the URL */
+ if ((url = CFDictionaryGetValue(pdict, kColorSyncDeviceProfileURL)) == NULL) {
+ debugrr("Failed to get default profile URL\n");
+ CFRelease(dict);
+ CFRelease(dispuuid);
+ return NULL;
+ }
+
+// urlstr = CFURLGetString(url);
+// printf("~1 got URL '%s'\n",CFStringGetCStringPtr(urlstr, kCFStringEncodingMacRoman));
+
+ urlstr = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
+ bufSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(urlstr),
+ kCFStringEncodingUTF8) + 1;
+ if ((dpath = malloc(bufSize)) == NULL) {
+ debugrr("cur_profile malloc failed\n");
+ CFRelease(dict);
+ CFRelease(dispuuid);
+ return NULL;
+ }
+ if (!CFStringGetCString(urlstr, dpath, bufSize, kCFStringEncodingUTF8)) {
+ debugrr("cur_profile CFStringGetCString failed\n");
+ CFRelease(dict);
+ CFRelease(dispuuid);
+ return NULL;
+ }
+ printf("~1 got path '%s'\n",dpath);
+
+ CFRelease(dict);
+ CFRelease(dispuuid);
+
+// return dpath;
+ return NULL;
+#endif
+
diff --git a/spectro/dispwin.h b/spectro/dispwin.h
new file mode 100644
index 0000000..096547f
--- /dev/null
+++ b/spectro/dispwin.h
@@ -0,0 +1,327 @@
+
+#ifndef DISPWIN_H
+
+/*
+ * Argyll Color Correction System
+ * Display target patch window
+ *
+ * Author: Graeme W. Gill
+ * Date: 4/10/96
+ *
+ * Copyright 1998 - 2013 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+#define DISPLAY_UPDATE_DELAY 200 /* default display update delay allowance */
+
+int do_plot(double *x, double *y1, double *y2, double *y3, int n);
+
+#ifdef NT
+#define OEMRESOURCE
+#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0501
+#define _WIN32_WINNT 0x0501
+#endif
+#if !defined(WINVER) || WINVER < 0x0501
+#if defined(WINVER)
+# undef WINVER
+#endif
+#define WINVER 0x0501
+#endif
+#include <windows.h>
+#include <icm.h>
+
+#if(WINVER < 0x0500)
+#error Require WINVER >= 0x500 to compile (multi-monitor API needed)
+#endif
+
+#ifndef COLORMGMTCAPS /* In case SDK is out of date */
+
+#define COLORMGMTCAPS 121
+
+#define CM_NONE 0x00000000
+#define CM_DEVICE_ICM 0x00000001
+#define CM_GAMMA_RAMP 0x00000002
+#define CM_CMYK_COLOR 0x00000004
+
+#endif /* !COLORMGMTCAPS */
+
+/* Avoid shlwapi.h - there are problems in using it in latter SDKs */
+#ifndef WINSHLWAPI
+#define WINSHLWAPI DECLSPEC_IMPORT
+#endif
+
+WINSHLWAPI LPSTR WINAPI PathFindFileNameA(LPCSTR);
+WINSHLWAPI LPWSTR WINAPI PathFindFileNameW(LPCWSTR);
+
+#ifdef UNICODE
+#define PathFindFileName PathFindFileNameW
+#else
+#define PathFindFileName PathFindFileNameA
+#endif
+
+#endif /* NT */
+
+#ifdef __APPLE__ /* Assume OS X Cocoa */
+
+#include <Carbon/Carbon.h> /* To declare CGDirectDisplayID */
+
+#endif /* __APPLE__ */
+
+#if defined(UNIX_X11)
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/xf86vmode.h>
+#include <X11/extensions/dpms.h>
+#include <X11/extensions/Xinerama.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/extensions/scrnsaver.h>
+#include <X11/extensions/dpms.h>
+#endif /* UNIX_X11 */
+
+/* - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Profile instalation/association scope */
+typedef enum {
+ p_scope_user = 0, /* (user profiles) Linux, OS X & Vista */
+ p_scope_local = 1, /* (local system profiles) Linux, OS X & vista */
+ p_scope_system = 2, /* (system supplied profiles) OS X. [ Linux, Vista same as local ] */
+ p_scope_network = 3 /* (shared network profiles) [ OS X. Linux, Vista same as local ] */
+} p_scope;
+
+/* - - - - - - - - - - - - - - - - - - - - - - - */
+/* Enumerate and list all the available displays */
+
+/* Structure to store infomation about possible displays */
+typedef struct {
+ char *name; /* Display name */
+ char *description; /* Description of display */
+ int sx,sy; /* Displays offset in pixels */
+ int sw,sh; /* Displays width and height in pixels*/
+#ifdef NT
+ char monid[128]; /* Monitor ID */
+ int prim; /* NZ if primary display monitor */
+#endif /* NT */
+#ifdef __APPLE__
+ CGDirectDisplayID ddid;
+#endif /* __APPLE__ */
+#if defined(UNIX_X11)
+ int screen; /* Screen to select */
+ int uscreen; /* Underlying screen */
+ int rscreen; /* Underlying RAMDAC screen */
+ Atom icc_atom; /* ICC profile root atom for this display */
+ unsigned char *edid; /* 128 or 256 bytes of monitor EDID, NULL if none */
+ int edid_len; /* 128 or 256 */
+
+#if RANDR_MAJOR == 1 && RANDR_MINOR >= 2
+ /* Xrandr stuff - output is connected 1:1 to a display */
+ RRCrtc crtc; /* Associated crtc */
+ RROutput output; /* Associated output */
+ Atom icc_out_atom; /* ICC profile atom for this output */
+#endif /* randr >= V 1.2 */
+#endif /* UNIX_X11 */
+} disppath;
+
+/* Return pointer to list of disppath. Last will be NULL. */
+/* Return NULL on failure. */
+/* Call free_disppaths() to free up allocation */
+disppath **get_displays();
+
+void free_disppaths(disppath **paths);
+
+/* Delete the display at the given index from the paths */
+void del_disppath(disppath **paths, int ix);
+
+/* Return the given display given its index 0..n-1 */
+disppath *get_a_display(int ix);
+
+void free_a_disppath(disppath *path);
+
+extern int callback_ddebug; /* Diagnostic global for get_displays() and get_a_display() */
+
+/* - - - - - - - - - - - - - - - - - - - - - - - */
+/* Structure to handle RAMDAC values */
+struct _ramdac {
+ int pdepth; /* Plane depth, usually 8 */
+ int nent; /* Number of entries, = 2^pdepth */
+ double *v[3]; /* 2^pdepth entries for RGB, values 0.0 - 1.0 */
+
+ /* Clone ourselves */
+ struct _ramdac *(*clone)(struct _ramdac *p);
+
+ /* Set the curves to linear */
+ void (*setlin)(struct _ramdac *p);
+
+ /* Destroy ourselves */
+ void (*del)(struct _ramdac *p);
+}; typedef struct _ramdac ramdac;
+
+
+/* - - - - - - - - - - - - - - - - - - - - - - - */
+/* Dispwin object */
+
+/* !!!! Make changes in dispwin.c and webwin.c !!!! */
+
+struct _dispwin {
+
+/* private: */
+ char *name; /* Display path (ie. '\\.\DISPLAY1') */
+ /* or "10.0.0.1:0.0" */
+ char *description; /* Description of display */
+
+ /* Plot instance information */
+ int sx,sy; /* Screen offset in pixels */
+ int sw,sh; /* Screen width and height in pixels*/
+ int ww,wh; /* Window width and height */
+ int tx,ty; /* Test area within window offset in pixels */
+ int tw,th; /* Test area width and height in pixels */
+
+ double rgb[3]; /* Current color (full resolution) */
+ double r_rgb[3]; /* Current color (raster value) */
+ int update_delay; /* Update delay in msec, default 200 */
+ int min_update_delay; /* Minimum update delay, default 20, overriden by EV */
+ int nowin; /* Don't create a test window */
+ int native; /* 0 = use current current or given calibration curve */
+ /* 1 = set native linear output and use ramdac high precision */
+ ramdac *or; /* Original ramdac contents, NULL if none */
+ ramdac *r; /* Ramdac in use for native mode */
+ int blackbg; /* NZ if black full screen background */
+
+ char *callout; /* if not NULL - set color Shell callout routine */
+
+ /* Linked list to automate SIGKILL cleanup */
+ struct _dispwin *next;
+
+#ifdef NT
+ char monid[128]; /* Monitor ID (ie. 'Monitor\MEA1773\{4D36E96E-E325-11CE-BFC1-08002BE10318}\0015'*/
+ HDC hdc; /* Handle to display */
+ char *AppName;
+ HWND hwnd; /* Window handle */
+ HCURSOR curs; /* Invisible cursor */
+
+ MSG msg;
+ ATOM arv;
+
+ int xo, yo, wi, he; /* Window location & size */
+ athread *mth; /* Window message thread */
+ int inited;
+ int quit; /* Request to quit */
+
+ int colupd; /* Color update count */
+ int colupde; /* Color update count echo */
+
+#endif /* NT */
+
+#ifdef __APPLE__
+ CGDirectDisplayID ddid;
+ void *osx_cntx; /* OSX specific info */
+ int btf; /* Flag, nz if window has been brought to the front once */
+ int winclose; /* Flag, set to nz if window was closed */
+#endif /* __APPLE__ */
+
+#if defined(UNIX_X11)
+ Display *mydisplay;
+ int myscreen; /* Usual or virtual screen with Xinerama */
+ int myuscreen; /* Underlying screen */
+ int myrscreen; /* Underlying RAMDAC screen */
+ Atom icc_atom; /* ICC profile root atom for this display */
+ unsigned char *edid; /* 128 or 256 bytes of monitor EDID, NULL if none */
+ int edid_len; /* 128 or 256 */
+
+#if RANDR_MAJOR == 1 && RANDR_MINOR >= 2
+ /* Xrandr stuff - output is connected 1:1 to a display */
+ RRCrtc crtc; /* Associated crtc */
+ RROutput output; /* Associated output */
+ Atom icc_out_atom; /* ICC profile atom for this output */
+#endif /* randr >= V 1.2 */
+
+ /* Test windo access */
+ Window mywindow;
+ GC mygc;
+
+ /* Screensaver state */
+ int xsssuspend; /* Was able to suspend the screensaver using XScreenSaverSuspend */
+
+ int xssvalid; /* Was able to save & disable X screensaver using XSetScreenSaver */
+ int timeout, interval;
+ int prefer_blanking;
+ int allow_exposures;
+
+ int xssrunning; /* Disabled xscreensaver */
+
+ int gnomessrunning; /* Disabled gnome screensaver and is was enabled */
+ pid_t gnomepid; /* gnome-screensaver-command -i pid */
+
+ int kdessrunning; /* Disabled kde screensaver and is was enabled */
+
+ int dpmsenabled; /* DPMS is enabled */
+
+#endif /* UNIX_X11 */
+
+ void *pcntx; /* Private context (ie., webwin) */
+ volatile unsigned int ncix, ccix; /* Counters to trigger webwin colorchange */
+ volatile int mg_stop; /* Stop flag */
+
+ int ddebug; /* >0 to print debug to stderr */
+
+/* public: */
+ int pdepth; /* Plane depth of display */
+
+ /* Get RAMDAC values. ->del() when finished. */
+ /* Return NULL if not possible */
+ ramdac *(*get_ramdac)(struct _dispwin *p);
+
+ /* Set the RAMDAC values. */
+ /* Return nz if not possible */
+ int (*set_ramdac)(struct _dispwin *p, ramdac *r, int persist);
+
+ /* Install a display profile and make */
+ /* it the defult for this display. */
+ /* Return nz if failed */
+ int (*install_profile)(struct _dispwin *p, char *fname, ramdac *r, p_scope scope);
+
+ /* Un-install a display profile. */
+ /* Return nz if failed */
+ int (*uninstall_profile)(struct _dispwin *p, char *fname, p_scope scope);
+
+ /* Get the currently installed display profile and return it as an icmFile. */
+ /* Return the name as well, up to mxlen chars, excluding nul. */
+ /* Return NULL if failed */
+ icmFile *(*get_profile)(struct _dispwin *p, char *name, int mxlen);
+
+ /* Set a color (values 0.0 - 1.0) */
+ /* Return nz on error */
+ int (*set_color)(struct _dispwin *p, double r, double g, double b);
+
+ /* Set an update delay, and return the previous value */
+ int (*set_update_delay)(struct _dispwin *p, int update_delay);
+
+ /* Set a shell set color callout command line */
+ void (*set_callout)(struct _dispwin *p, char *callout);
+
+ /* Destroy ourselves */
+ void (*del)(struct _dispwin *p);
+
+}; typedef struct _dispwin dispwin;
+
+/* Create a RAMDAC access and display test window, default white */
+dispwin *new_dispwin(
+ disppath *screen, /* Screen to calibrate. */
+ double width, double height, /* Width and height in mm */
+ double hoff, double voff, /* Offset from c. in fraction of screen, range -1.0 .. 1.0 */
+ int nowin, /* NZ if no window should be created - RAMDAC access only */
+ int native, /* 0 = use current current or given calibration curve */
+ /* 1 = use native linear out & high precision */
+ int *noramdac, /* Return nz if no ramdac access. native is set to 0 */
+ int blackbg, /* NZ if whole screen should be filled with black */
+ int override, /* NZ if override_redirect is to be used on X11 */
+ int ddebug /* >0 to print debug statements to stderr */
+);
+
+
+#define DISPWIN_H
+#endif /* DISPWIN_H */
+
diff --git a/spectro/dtp20.c b/spectro/dtp20.c
new file mode 100644
index 0000000..84c4f2b
--- /dev/null
+++ b/spectro/dtp20.c
@@ -0,0 +1,1695 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * Xrite DTP20 related functions
+ *
+ * Author: Graeme W. Gill
+ * Date: 10/3/2001
+ *
+ * Copyright 1996 - 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.
+ *
+ * Derived from DTP41.c
+ *
+ * Thanks to Rolf Gierling for contributing to the devlopment of this driver.
+ */
+
+/*
+ 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdarg.h>
+#include <time.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#else /* SALONEINSTLIB */
+#include "sa_config.h"
+#endif /* SALONEINSTLIB */
+#include "numsup.h"
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "dtp20.h"
+
+static inst_code dtp20_interp_code(inst *pp, int ec);
+static inst_code activate_mode(dtp20 *p);
+
+#define MIN_MES_SIZE 10 /* Minimum normal message reply size */
+#define MAX_MES_SIZE 500 /* Maximum normal message reply size */
+#define MAX_RD_SIZE 100000 /* Maximum reading messagle reply size */
+
+/* Extract an error code from a reply string */
+/* Return -1 if no error code can be found */
+static int
+extract_ec(char *s) {
+ char *p;
+ char tt[3];
+ int rv;
+ p = s + strlen(s);
+ /* Find the trailing '>' */
+ for (p--; p >= s;p--) {
+ if (*p == '>')
+ break;
+ }
+ if ( (p-3) < s
+ || p[0] != '>'
+ || p[-3] != '<')
+ return -1;
+ tt[0] = p[-2];
+ tt[1] = p[-1];
+ tt[2] = '\000';
+ if (sscanf(tt,"%x",&rv) != 1)
+ return -1;
+ /* For some reason the top bit sometimes get set ? (ie. multiple errors ?)*/
+ rv &= 0x7f;
+ return rv;
+}
+
+/* Interpret an icoms error into a DTP20 error */
+static int icoms2dtp20_err(int se) {
+ if (se != ICOM_OK) {
+ if (se & ICOM_TO)
+ return DTP20_TIMEOUT;
+ return DTP20_COMS_FAIL;
+ }
+ return DTP20_OK;
+}
+
+/* Do a standard command/response echange with the dtp20 */
+/* Return the instrument error code */
+static inst_code
+dtp20_command(
+dtp20 *p,
+char *in, /* In string */
+char *out, /* Out string buffer */
+int bsize, /* Out buffer size */
+double to) { /* Timout in seconts */
+ char tc = '>'; /* Terminating character */
+ int ntc = 1; /* Number of terminating characters */
+ int rv, se, insize;
+
+ a1logd(p->log, 4, "dtp20: Sending '%s'",icoms_fix(in));
+
+ insize = strlen(in);
+ if (insize > 0) {
+ if ((se = p->icom->usb_control(p->icom, 0x41, 0x00, 0x00, 0x00, (unsigned char *)in, insize, to)) != ICOM_OK) {
+ a1logd(p->log, 1, "dtp20: send command failed ICOM err 0x%x\n",se);
+ return dtp20_interp_code((inst *)p, icoms2dtp20_err(se));
+ }
+ }
+
+ if ((se = p->icom->read(p->icom, out, bsize, tc, ntc, to)) != 0) {
+ a1logd(p->log, 1, "dtp20: read response failed ICOM err 0x%x\n",se);
+ return dtp20_interp_code((inst *)p, icoms2dtp20_err(se));
+ }
+
+ rv = DTP20_OK;
+ if (tc == '>' && ntc == 1) { /* Expecting DTP type error code */
+ rv = extract_ec(out);
+ if (rv > 0) {
+ rv &= inst_imask;
+ if (rv != DTP20_OK) { /* Clear the error */
+ char buf[MAX_MES_SIZE];
+ p->icom->usb_control(p->icom, 0x41, 0x00, 0x00, 0x00, (unsigned char *)"CE\r", 3, 0.5);
+ p->icom->read(p->icom, buf, MAX_MES_SIZE, tc, ntc, 0.5);
+ }
+ }
+ }
+
+ a1logd(p->log, 4, "dtp20: response '%s' ICOM err 0x%x\n",icoms_fix(out),rv);
+ return dtp20_interp_code((inst *)p, rv);
+}
+
+/* Do a command/binary response echange with the dtp20, */
+/* for reading binary spectral values. */
+/* This is for reading binary spectral data */
+/* Return the instrument error code */
+static inst_code
+dtp20_bin_command(
+dtp20 *p,
+char *in, /* In string */
+char *out, /* Out string buffer */
+int bsize, /* Out buffer size & bytes to read */
+double top) { /* Timout in seconds */
+ int rv, se, insize;
+ int bread = 0;
+ char *op;
+
+ a1logd(p->log, 4, "dtp20: Sending '%s'",icoms_fix(in));
+
+ insize = strlen(in);
+ if (insize > 0) {
+ if ((se = p->icom->usb_control(p->icom, 0x41, 0x00, 0x00, 0x00, (unsigned char *)in, insize, top)) != ICOM_OK) {
+ a1logd(p->log, 1, "dtp20: send failed ICOM err 0x%x\n",se);
+ return dtp20_interp_code((inst *)p, icoms2dtp20_err(se));
+ }
+ }
+
+ /* Read in 62 byte chunks */
+ for (op = out; bsize > 0;) {
+ int rsize = 62;
+
+ if (rsize > bsize)
+ rsize = bsize;
+
+ if ((se = p->icom->usb_read(p->icom, NULL, 0x81, (unsigned char *)op, rsize, &bread, top)) != ICOM_OK) {
+ if (se == ICOM_SHORT) {
+ a1logd(p->log, 1, "dtp20: response failed expected %d got %d ICOM err 0x%x\n",
+ rsize,bread,se);
+ } else {
+ a1logd(p->log, 1, "dtp20: response failed ICOM err 0x%x\n",se);
+ }
+ return dtp20_interp_code((inst *)p, icoms2dtp20_err(se));
+ }
+ bsize -= bread;
+ op += bread;
+ }
+
+ rv = DTP20_OK;
+
+ a1logd(p->log, 4, "dtp20: response '%s' ICOM err 0x%x\n",
+ icoms_tohex((unsigned char *)out, bread),rv);
+ return dtp20_interp_code((inst *)p, rv);
+}
+
+/* Establish communications with a DTP20 */
+/* Return DTP_COMS_FAIL on failure to establish communications */
+static inst_code
+dtp20_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ dtp20 *p = (dtp20 *)pp;
+ char buf[MAX_MES_SIZE];
+ int se;
+ inst_code ev = inst_ok;
+
+ if (p->icom->port_type(p->icom) == icomt_usb) {
+
+ a1logd(p->log, 4, "dtp20: About to init USB\n");
+
+ /* Set config, interface, write end point, read end point, read quanta */
+ if ((se = p->icom->set_usb_port(p->icom, 1, 0x00, 0x81, icomuf_none, 0, NULL)) != ICOM_OK) {
+ a1logd(p->log, 1, "dtp20: set_usbe_port failed ICOM err 0x%x\n",se);
+ return dtp20_interp_code((inst *)p, icoms2dtp20_err(se));
+ }
+
+ /* Blind reset it twice - it seems to sometimes hang up */
+ /* otherwise under OSX */
+ dtp20_command(p, "0PR\r", buf, MAX_MES_SIZE, 0.5);
+ dtp20_command(p, "0PR\r", buf, MAX_MES_SIZE, 0.5);
+
+ } else {
+ a1logd(p->log, 1, "dtp20: Failed to find connection to instrument\n");
+ return inst_coms_fail;
+ }
+
+ /* Check instrument is responding */
+ if ((ev = dtp20_command(p, "\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok) {
+ a1logd(p->log, 1, "dtp20: Failed to get a response from instrument\n");
+ return inst_coms_fail;
+ }
+
+ /* Print the general information returned by instrument */
+ if (p->log->verb) {
+ int i, j;
+ if ((ev = dtp20_command(p, "GI\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) {
+ a1logd(p->log, 1, "dtp20: GI command failed with ICOM err 0x%x\n",ev);
+ return ev;
+ }
+ for (j = i = 0; ;i++) {
+ if (buf[i] == '<' || buf[i] == '\000')
+ break;
+ if (buf[i] == '\r') {
+ buf[i] = '\000';
+ a1logv(p->log, 1, " %s\n",&buf[j]);
+ if (buf[i+1] == '\n')
+ i++;
+ j = i+1;
+ }
+ }
+ }
+
+ a1logd(p->log, 4, "dtp20: Got coms OK\n");
+
+ p->gotcoms = 1;
+ return inst_ok;
+}
+
+/* Build a strip definition as a set of passes, including DS command */
+static void
+build_strip(
+dtp20 *p,
+char *tp, /* pointer to string buffer */
+char *name, /* Strip name (7 chars) (not used) */
+int npatch, /* Number of patches in the pass */
+char *pname, /* Pass name (3 chars) (not used) */
+int sguide, /* Guide number (not used) */
+double pwid, /* Patch length in mm */
+double gwid, /* Gap length in mm */
+double twid /* Trailer length in mm (For DTP41T) */
+) {
+
+ /* Enable SID field but don't report it, */
+ /* since we expect to be reading DTP20 type charts. */
+ /* (Could set to '1' for reading DTP41 type charts) */
+ tp[0] = '0';
+
+ /* Gap width */
+ if((gwid <= 0.25))
+ tp[1] = '0';
+ else if(gwid <= 0.75)
+ tp[1] = '1';
+ else if(gwid <= 1.25)
+ tp[1] = '2';
+ else if(gwid <= 1.75)
+ tp[1] = '3';
+ else if(gwid <= 2.25)
+ tp[1] = '4';
+ else if(gwid <= 2.75)
+ tp[1] = '5';
+ else if(gwid <= 3.25)
+ tp[1] = '6';
+ else
+ tp[1] = '7';
+
+ /* Patch width in mm, dd.dd */
+ if(pwid <= 6.75)
+ tp[2] = '0';
+ else if(pwid <= 8.0)
+ tp[2] = '1';
+ else if(pwid <= 11.25)
+ tp[2] = '4';
+ else if(pwid <= 12.75)
+ tp[2] = '6';
+ else
+ tp[2] = '7';
+
+ /* Number of patches in strip */
+ tp += 3;
+ sprintf(tp, "%02d",npatch);
+ tp += 2;
+
+ *tp++ = 'D'; /* The DS command */
+ *tp++ = 'S';
+ *tp++ = '\r'; /* The CR */
+ *tp++ = '\000'; /* The end */
+
+}
+
+/* Initialise the DTP20. */
+/* return non-zero on an error, with instrument error code */
+static inst_code
+dtp20_init_inst(inst *pp) {
+ dtp20 *p = (dtp20 *)pp;
+ char buf[MAX_MES_SIZE];
+ inst_code rv = inst_ok;
+
+ a1logd(p->log, 2, "dtp20_init_inst: called\n");
+
+ if (p->gotcoms == 0)
+ return inst_no_coms; /* Must establish coms before calling init */
+
+ /* Reset it (without disconnecting USB or clearing stored data) */
+ if ((rv = dtp20_command(p, "0PR\r", buf, MAX_MES_SIZE, 2.0)) != inst_ok)
+ return rv;
+// if ((rv = dtp20_command(p, "RI\r", buf, MAX_MES_SIZE, 2.0)) != inst_ok)
+// return rv;
+
+ /* Set Response delimeter to CR */
+ if ((rv = dtp20_command(p, "0008CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return rv;
+
+ /* Get the model and version number */
+ if ((rv = dtp20_command(p, "SV\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return rv;
+
+ /* Check that it is a DTP20 */
+ if ( strlen(buf) < 12
+ || (strncmp(buf,"X-Rite DTP20",12) != 0))
+ return inst_unknown_model;
+
+// /* Set Beeper to off */
+// if ((rv = dtp20_command(p, "0001CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+// return rv;
+
+ /* Set Beeper to on */
+ if ((rv = dtp20_command(p, "0101CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return rv;
+
+ /* Set Automatic Transmit off */
+ if ((rv = dtp20_command(p, "0005CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return rv;
+
+ /* Set color data separator to TAB */
+ if ((rv = dtp20_command(p, "0207CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return rv;
+
+ /* Set 2 decimal digit resolution */
+ if ((rv = dtp20_command(p, "020ACF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return rv;
+
+ /* set default trigger mode */
+ p->trig = inst_opt_trig_user_switch;
+
+ /* - - - - - - - - - - - - - - - - - - - - - - - - */
+ /* Setup for the type of measurements we want to do */
+ /* Set instrument to XYZ mode */
+ if ((rv = dtp20_command(p, "0518CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return rv;
+
+ /* Disable multiple data output */
+ if ((rv = dtp20_command(p, "001ACF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return rv;
+
+ /* Set Illuminant to D50_2 */
+ if ((rv = dtp20_command(p, "0416CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return rv;
+
+ /* Reset retrieval of saved spot readings */
+ p->savix = 0;
+
+ a1logd(p->log, 2, "dtp20_init_inst: instrument inited OK\n");
+ p->inited = 1;
+
+ return inst_ok;
+}
+
+/* Read a full test chart composed of multiple sheets. */
+/* This only works if the sheets have been read off-line */
+/* DOESN'T use the trigger mode */
+/* Return the inst error code */
+static inst_code dtp20_read_chart(
+inst *pp,
+int npatch, /* Total patches/values in chart */
+int pich, /* Passes (rows) in chart */
+int sip, /* Steps in each pass (patches in each row) */
+int *pis, /* Passes in each strip (rows in each sheet) */
+int chid, /* Chart ID number */
+ipatch *vals) { /* Pointer to array of values */
+ dtp20 *p = (dtp20 *)pp;
+ inst_code ev = inst_ok;
+ char buf[MAX_RD_SIZE];
+ int cs, sl, ttlp;
+ double pw, gw;
+ int u[10];
+ int id = -1;
+ ipatch *tvals;
+ int six; /* strip index */
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (!IMODETST(p->mode,inst_mode_s_ref_chart))
+ return inst_unsupported;
+
+ /* Confirm that there is a chart ready to read */
+ if ((ev = dtp20_command(p, "CS\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if (sscanf(buf," %d ", &cs) != 1)
+ return inst_protocol_error;
+ if (cs != 3) {
+ /* Seems to be no chart saved, but double check, in case of old firmware ( < 1.03) */
+ if ((ev = dtp20_command(p, "00TS\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return inst_nonesaved;
+ if (sscanf(buf," %d ", &cs) != 1)
+ return inst_nonesaved;
+ if (cs == 0)
+ return inst_nonesaved;
+ }
+
+ /* Get the TID */
+ if ((ev = dtp20_command(p, "ST\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if (sscanf(buf,"Strip Length: %d Total Patches: %d Patch Width: %lf mm Gap Width: %lf mm"
+ " User 1: %d User 2: %d User 3: %d User 4: %d User 5: %d User 6: %d"
+ " User 7: %d User 8: %d User 9: %d User 10: %d ",
+ &sl, &ttlp, &pw, &gw, &u[0], &u[1], &u[2], &u[3], &u[4],
+ &u[5], &u[6], &u[7], &u[8], &u[9]) != 14) {
+ return inst_protocol_error;
+ }
+ /* Compute the user data/chart id */
+ if (u[0] == 0) { /* This seems to be a chart ID */
+ id = ((u[1] * 8 + u[2]) * 8 + u[3]) * 8 + u[4];
+ }
+
+ /* Check that the chart matches what we're reading */
+ /* (Should we have a way of ignoring a chart if mismatch ?) */
+ if (ttlp != npatch
+ || sl != sip
+ || (id != -1 && id != chid)) {
+ a1logd(p->log, 2, "dtp20: Got %d, xpt %d patches, got %d xpt %d strip lgth, "
+ "got %d xpt %d chart id\n",ttlp,npatch,sl,sip,id,chid);
+ return inst_nochmatch;
+ }
+
+ a1logv(p->log, 1, "Chart has %d patches, %d per strip, chart id %d\n",ttlp,sl,id);
+
+ /* Disable multiple data output */
+ if ((ev = dtp20_command(p, "001ACF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+
+ /* Read each strip's values in turn */
+ for (tvals = vals, six = 1; six <= pich; six++, tvals += sip) {
+ char *tp, cmd[10];
+ int i;
+
+ a1logv(p->log, 1, "Reading saved strip %d of %d\n",six,pich);
+
+ /* Select the strip to read */
+ sprintf(cmd, "%03d01TS\r",six);
+
+ /* Gather the results in D50_2 XYZ. This instruction seems unreliable! */
+ if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if ((ev = dtp20_command(p, cmd, buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return ev;
+
+ /* Parse the buffer */
+ /* Replace '\r' with '\000' */
+ for (tp = buf; *tp != '\000'; tp++) {
+ if (*tp == '\r')
+ *tp = '\000';
+ }
+ for (tp = buf, i = 0; i < sip; i++) {
+ if (*tp == '\000' || strlen(tp) > 40)
+ return inst_protocol_error;
+ if (sscanf(tp, " %lf %lf %lf ",
+ &tvals[i].XYZ[0], &tvals[i].XYZ[1], &tvals[i].XYZ[2]) != 3) {
+ if (sscanf(tp, " %lf %lf %lf ",
+ &tvals[i].XYZ[0], &tvals[i].XYZ[1], &tvals[i].XYZ[2]) != 3) {
+ return inst_protocol_error;
+ }
+ }
+ tvals[i].loc[0] = '\000';
+ tvals[i].mtype = inst_mrt_reflective;
+ tvals[i].XYZ_v = 1;
+ tvals[i].sp.spec_n = 0;
+ tvals[i].duration = 0.0;
+ tp += strlen(tp) + 1;
+ }
+
+ if (p->mode & inst_mode_spectral) {
+
+ /* Gather the results in Spectral reflectance */
+ if ((ev = dtp20_command(p, "0318CF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return ev;
+ /* Set to binary */
+ if ((ev = dtp20_command(p, "011BCF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return ev;
+ /* Read the strip */
+ if ((ev = dtp20_bin_command(p, cmd, buf, 62 * sip, 5.0)) != inst_ok)
+ return ev;
+
+ /* Get each patches spectra */
+ for (tp = buf, i = 0; i < sip; i++) {
+ int j;
+
+ /* Read the spectral value */
+ for (j = 0; j < 31; j++, tp += 2) {
+ int vv;
+ vv = (unsigned char)tp[0];
+ vv = vv * 256 + (unsigned char)tp[1];
+ tvals[i].sp.spec[j] = vv * 200.0/65535.0;
+ }
+
+ tvals[i].sp.spec_n = 31;
+ tvals[i].sp.spec_wl_short = 400.0;
+ tvals[i].sp.spec_wl_long = 700.0;
+ tvals[i].sp.norm = 100.0;
+ }
+
+ /* Set to ASCII */
+ if ((ev = dtp20_command(p, "001BCF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ /* Set back to in D50_2 XYZ. This instruction seems unreliable! */
+ if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return ev;
+ }
+ }
+
+ a1logv(p->log, 1, "All saved strips read\n");
+ return inst_ok;
+}
+
+/* Read a set of strips. This only works reading on-line */
+/* Return the instrument error code */
+static inst_code
+dtp20_read_strip(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+int npatch, /* Number of patches in the pass */
+char *pname, /* Pass name (3 chars) */
+int sguide, /* Guide number */
+double pwid, /* Patch length in mm (For DTP20/DTP41) */
+double gwid, /* Gap length in mm (For DTP20/DTP41) */
+double twid, /* Trailer length in mm (For DTP41T) */
+ipatch *vals) { /* Pointer to array of instrument patch values */
+ dtp20 *p = (dtp20 *)pp;
+ char tbuf[200], *tp;
+ char buf[MAX_RD_SIZE];
+ int i, cs, se;
+ inst_code ev = inst_ok;
+ int user_trig = 0;
+ int switch_trig = 0;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* This funtion isn't valid in saved data mode */
+ if (!IMODETST(p->mode, inst_mode_ref_strip))
+ return inst_wrong_setup;
+
+ /* Until we get the right status or give up */
+ for (i = 0;;i++) {
+ /* Confirm that we are ready to read strips */
+ if ((ev = dtp20_command(p, "CS\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if (sscanf(buf," %d ", &cs) != 1)
+ return inst_protocol_error;
+ if (cs == 1)
+ break; /* Read and empty of offline patches */
+ if (cs == 2 || cs == 3)
+ return (inst_misread | DTP20_NOT_EMPTY); /* Has onffline patches */
+ if (i < 20) {
+ if (cs == 0 || (cs >= 4 && cs <= 12)) { /* Ignore transient status */
+ msec_sleep(100);
+ continue;
+ }
+ }
+ return (inst_misread | DTP20_UNEXPECTED_STATUS);
+ }
+
+ /* Send strip definition */
+ build_strip(p, tbuf, name, npatch, pname, sguide, pwid, gwid, twid);
+
+ if ((ev = dtp20_command(p, tbuf, buf, MAX_MES_SIZE, 1.5)) != inst_ok) {
+ a1logv(p->log, 1, "Interactive strip reading won't work on Firmware earlier than V1.03 !\n");
+ return ev;
+ }
+
+ if (p->trig == inst_opt_trig_user_switch) {
+ int touts = 0;
+
+ /* Wait for a strip value to turn up, or a user command/abort */
+ for (;;) {
+
+ ev = dtp20_command(p, "CS\r", buf, MAX_MES_SIZE, 0.2);
+
+ if (ev == (inst_coms_fail | DTP20_TIMEOUT)) {
+ /* Ignore transient timeouts */
+ if (touts++ > 40)
+ return ev;
+ continue;
+
+ } else if (ev != inst_ok) {
+ if ((ev & inst_mask) == inst_needs_cal)
+ p->need_cal = 1;
+ return ev; /* Error */
+ } else {
+ int stat;
+ if (sscanf(buf, " %d ", &stat) != 1)
+ stat = 6;
+
+ /* Ignore benign status */
+ if (stat != 4 && stat != 6 && stat != 7) {
+ /* Not ready - Check for user trigger or command */
+ if (p->uicallback != NULL) {
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (ev == inst_user_abort) {
+ return ev; /* Error or user abort */
+ } else if (ev == inst_user_trig) {
+ user_trig = 1; /* User trigger */
+ break;
+ }
+ }
+ }
+ /* Keep waiting */
+ msec_sleep(200);
+ continue;
+
+ } else {
+ /* Ready - continue on */
+ switch_trig = 1;
+ break;
+ }
+ }
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ } else if (p->trig == inst_opt_trig_user) {
+ if (p->uicallback == NULL) {
+ a1logd(p->log, 1, "dtp20: inst_opt_trig_user but no uicallback function set!\n");
+ return inst_unsupported;
+ }
+
+ for (;;) {
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (ev == inst_user_abort)
+ return ev;
+ if (ev == inst_user_trig) {
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ msec_sleep(200);
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ 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)
+ return ev;
+ }
+
+ /* Trigger a read if the switch has not been used */
+ if (switch_trig == 0) {
+ /* Do a strip read */
+ if ((ev = dtp20_command(p, "RM\r", buf, MAX_MES_SIZE, 10.0)) != inst_ok) {
+ if ((ev & inst_mask) == inst_needs_cal)
+ p->need_cal = 1;
+ return ev;
+ }
+ }
+
+ /* Disable multiple data output */
+ if ((ev = dtp20_command(p, "001ACF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ /* Gather the results in D50_2 XYZ This instruction seems unreliable! */
+ if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if ((ev = dtp20_command(p, "01TS\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return ev;
+
+ /* Parse the buffer */
+ /* Replace '\r' with '\000' */
+ for (tp = buf; *tp != '\000'; tp++) {
+ if (*tp == '\r')
+ *tp = '\000';
+ }
+ for (tp = buf, i = 0; i < npatch; i++) {
+ if (*tp == '\000' || strlen(tp) > 40)
+ return inst_protocol_error;
+ if (sscanf(tp, " %lf %lf %lf ",
+ &vals[i].XYZ[0], &vals[i].XYZ[1], &vals[i].XYZ[2]) != 3) {
+ if (sscanf(tp, " %lf %lf %lf ",
+ &vals[i].XYZ[0], &vals[i].XYZ[1], &vals[i].XYZ[2]) != 3) {
+ return inst_protocol_error;
+ }
+ }
+ vals[i].loc[0] = '\000';
+ vals[i].mtype = inst_mrt_reflective;
+ vals[i].XYZ_v = 1;
+ vals[i].sp.spec_n = 0;
+ vals[i].duration = 0.0;
+ tp += strlen(tp) + 1;
+ }
+
+ if (p->mode & inst_mode_spectral) {
+
+ /* Gather the results in Spectral reflectance */
+ if ((ev = dtp20_command(p, "0318CF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return ev;
+ /* Set to binary */
+ if ((ev = dtp20_command(p, "011BCF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return ev;
+
+ /* Read the strip */
+ if ((ev = dtp20_bin_command(p, "01TS\r", buf, 62 * npatch, 5.0)) != inst_ok)
+ return ev;
+
+ /* Get each patches spectra */
+ for (tp = buf, i = 0; i < npatch; i++) {
+ int j;
+
+ /* Read the spectral value */
+ for (j = 0; j < 31; j++, tp += 2) {
+ int vv;
+ vv = (unsigned char)tp[0];
+ vv = vv * 256 + (unsigned char)tp[1];
+ vals[i].sp.spec[j] = vv * 200.0/65535.0;
+ }
+
+ vals[i].sp.spec_n = 31;
+ vals[i].sp.spec_wl_short = 400.0;
+ vals[i].sp.spec_wl_long = 700.0;
+ vals[i].sp.norm = 100.0;
+ }
+
+ /* Set to ASCII */
+ if ((ev = dtp20_command(p, "001BCF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ /* Set back to D50 2 degree */
+ if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return ev;
+ }
+
+ /* Wait for status to change */
+ for (;;) {
+ if ((ev = dtp20_command(p, "CS\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) {
+ return ev;
+ } else {
+ int stat = 4;
+ if (sscanf(buf, " %d ", &stat) != 1 || stat != 4)
+ break;
+ msec_sleep(200);
+ }
+ }
+
+ if (user_trig)
+ return inst_user_trig;
+ return inst_ok;
+}
+
+
+/* Read a single sample. */
+/* This works both on-line and off-line. */
+/* Return the instrument error code */
+static inst_code
+dtp20_read_sample(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+ipatch *val, /* Pointer to instrument patch value */
+instClamping clamp) { /* NZ if clamp XYZ/Lab to be +ve */
+ dtp20 *p = (dtp20 *)pp;
+ char buf[MAX_MES_SIZE], *tp;
+ int se;
+ inst_code ev = inst_ok;
+ int switch_trig = 0;
+ int user_trig = 0;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* This combination doesn't make any sense... */
+ if (IMODETST(p->mode, inst_mode_s_ref_spot) && p->trig == inst_opt_trig_user_switch) {
+ return inst_wrong_setup;
+ }
+
+ if (p->trig == inst_opt_trig_user_switch) {
+ int touts = 0;
+
+ /* Wait for a sample value to turn up, or a user abort */
+ for (;;) {
+
+ /* Check for user trigger */
+ if (p->uicallback != NULL) {
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (ev == inst_user_abort)
+ return ev; /* User abort */
+ if (ev == inst_user_trig) {
+ user_trig = 1;
+ break;
+ }
+ }
+ }
+
+ /* Check for an instrument switch trigger */
+ ev = dtp20_command(p, "CS\r", buf, MAX_MES_SIZE, 0.2);
+
+ if (ev == (inst_coms_fail | DTP20_TIMEOUT)) { /* Assume we're waiting for trigger */
+ if (touts++ > 20)
+ return ev;
+ continue;
+
+ } else if (ev != inst_ok) {
+ if ((ev & inst_mask) == inst_needs_cal)
+ p->need_cal = 1;
+ return ev; /* Error */
+
+ } else { /* Got a CS response */
+ int stat;
+ if (sscanf(buf, " %d ", &stat) != 1)
+ stat = 6;
+ /* Ingnore benign status */
+ if (stat != 4 && stat != 6 && stat != 7) {
+ msec_sleep(200);
+ continue;
+ }
+ switch_trig = 1;
+ break;
+ }
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ } else if (p->trig == inst_opt_trig_user) {
+ if (p->uicallback == NULL) {
+ a1logd(p->log, 1, "dtp20: inst_opt_trig_user but no uicallback function set!\n");
+ return inst_unsupported;
+ }
+
+ for (;;) {
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (ev == inst_user_abort)
+ return ev; /* Abort */
+ if (ev == inst_user_trig) {
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ msec_sleep(200);
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ 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)
+ return ev; /* Abort */
+ }
+
+ /* Read saved spot values */
+ if (IMODETST(p->mode, inst_mode_s_ref_spot)) {
+ char cmd[10];
+ int nsr;
+
+ /* See how many saved spot readings there are */
+ if ((ev = dtp20_command(p, "00GM\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if (sscanf(buf," %d ", &nsr) != 1)
+ return inst_protocol_error;
+
+ if (nsr <= p->savix)
+ return inst_nonesaved;
+
+ p->savix++;
+ sprintf(cmd, "%03d01GM\r",p->savix);
+ /* Disable multiple data output */
+ if ((ev = dtp20_command(p, "001ACF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ /* Gather the results in D50_2 XYZ This instruction seems unreliable! */
+ if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if ((ev = dtp20_command(p, cmd, buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+
+ /* Read real spot values */
+ } else {
+ /* Trigger a read if the switch has not been used */
+ if (switch_trig == 0) {
+ /* Do a spot read */
+ if ((ev = dtp20_command(p, "RM\r", buf, MAX_MES_SIZE, 5.0)) != inst_ok) {
+ if ((ev & inst_mask) == inst_needs_cal)
+ p->need_cal = 1;
+ return ev;
+ }
+ }
+
+ /* Disable multiple data output */
+ if ((ev = dtp20_command(p, "001ACF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ /* Gather the results in D50_2 XYZ */
+ if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if ((ev = dtp20_command(p, "01GM\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+
+ }
+
+ val->XYZ[0] = val->XYZ[1] = val->XYZ[2] = 0.0;
+
+ if (*buf == '\000' || strlen(buf) > 40)
+ return inst_protocol_error;
+
+ if (sscanf(buf, " %lf %lf %lf ", &val->XYZ[0], &val->XYZ[1], &val->XYZ[2]) != 3) {
+ return inst_protocol_error;
+ }
+ /* This may not change anything since instrument may clamp */
+ if (clamp)
+ icmClamp3(val->XYZ, val->XYZ);
+ val->loc[0] = '\000';
+ val->mtype = inst_mrt_reflective;
+ val->XYZ_v = 1;
+ val->sp.spec_n = 0;
+ val->duration = 0.0;
+
+ if (p->mode & inst_mode_spectral) {
+ int j;
+
+ /* Set to read spectral reflectance */
+ if ((ev = dtp20_command(p, "0318CF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ /* Set to binary */
+ if ((ev = dtp20_command(p, "011BCF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ /* Get measurement */
+ if ((ev = dtp20_bin_command(p, "01GM\r", buf, 62, 2.0)) != inst_ok)
+ return ev;
+
+ for (j = 0; j < 31; j++)
+ val->sp.spec[j] = 0.0;
+
+ /* Read the spectral value */
+ for (tp = buf, j = 0; j < 31; j++, tp += 2) {
+ int vv;
+ vv = (unsigned char)tp[0];
+ vv = vv * 256 + (unsigned char)tp[1];
+ val->sp.spec[j] = vv * 200.0/65535.0;
+ }
+
+ val->sp.spec_n = 31;
+ val->sp.spec_wl_short = 400.0;
+ val->sp.spec_wl_long = 700.0;
+ val->sp.norm = 100.0;
+
+ /* Set to ASCII */
+ if ((ev = dtp20_command(p, "001BCF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ /* Set to D50 2 degree */
+ if ((ev = dtp20_command(p, "0518CF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ }
+
+ if (!IMODETST(p->mode, inst_mode_s_ref_spot)) {
+
+ /* Clear the spot database so our reading doesn't appear as a stored reading */
+ if ((ev = dtp20_command(p, "02CD\r", buf, MAX_MES_SIZE, 1.0)) != inst_ok)
+ return ev;
+ p->savix = 0;
+
+ /* Wait for status to change */
+ for (;;) {
+ if ((ev = dtp20_command(p, "CS\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) {
+ return ev;
+ } else {
+ int stat = 4;
+ if (sscanf(buf, " %d ", &stat) != 1 || stat != 4)
+ break;
+ msec_sleep(200);
+ }
+ }
+ }
+
+ if (user_trig)
+ return inst_user_trig;
+ return inst_ok;
+}
+
+/* Return needed and available inst_cal_type's */
+static inst_code dtp20_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
+ dtp20 *p = (dtp20 *)pp;
+ inst_cal_type n_cals = inst_calt_none;
+ inst_cal_type a_cals = inst_calt_none;
+
+ if (p->need_cal)
+ n_cals |= inst_calt_ref_white;
+ a_cals |= inst_calt_ref_white;
+
+ if (pn_cals != NULL)
+ *pn_cals = n_cals;
+
+ if (pa_cals != NULL)
+ *pa_cals = a_cals;
+
+ return inst_ok;
+}
+
+/* Request an instrument calibration. */
+/* This is use if the user decides they want to do a calibration, */
+/* in anticipation of a calibration (needs_calibration()) to avoid */
+/* requiring one during measurement, or in response to measuring */
+/* returning inst_needs_cal. Initially us an inst_cal_cond of inst_calc_none, */
+/* and then be prepared to setup the right conditions, or ask the */
+/* user to do so, each time the error inst_cal_setup is returned. */
+inst_code dtp20_calibrate(
+inst *pp,
+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) */
+) {
+ dtp20 *p = (dtp20 *)pp;
+ char buf[MAX_MES_SIZE];
+ inst_code ev;
+ inst_cal_type needed, available;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ id[0] = '\000';
+
+ if ((ev = dtp20_get_n_a_cals((inst *)p, &needed, &available)) != inst_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,"dtp20_calibrate: doing calt 0x%x\n",calt);
+
+ if ((*calt & inst_calt_n_dfrble_mask) == 0) /* Nothing todo */
+ return inst_ok;
+ }
+
+ /* See if it's a calibration we understand */
+ if (*calt & ~available & inst_calt_all_mask) {
+ return inst_unsupported;
+ }
+
+ if (*calt & inst_calt_ref_white) {
+
+ if (*calc != inst_calc_man_ref_white) {
+ char *cp;
+ if ((ev = dtp20_command(p, "04SN\r", buf, MAX_MES_SIZE, 4.5)) != inst_ok)
+ return ev;
+ for (cp = buf; *cp >= '0' && *cp <= '9'; cp++)
+ ;
+ *cp = '\000';
+ strcpy(id, buf);
+ *calc = inst_calc_man_ref_white;
+ return inst_cal_setup;
+ }
+
+ if ((ev = dtp20_command(p, "CR\r", buf, MAX_MES_SIZE, 4.5)) != inst_ok)
+ return ev;
+
+ p->need_cal = 0;
+ *calt &= ~inst_calt_ref_white;
+ }
+
+ return inst_ok;
+}
+
+/* Error codes interpretation */
+static char *
+dtp20_interp_error(inst *pp, int ec) {
+// dtp20 *p = (dtp20 *)pp;
+ ec &= inst_imask;
+ switch (ec) {
+
+ case DTP20_INTERNAL_ERROR:
+ return "Internal software error";
+ case DTP20_COMS_FAIL:
+ return "Communications failure";
+ case DTP20_UNKNOWN_MODEL:
+ return "Not a DTP20";
+ case DTP20_DATA_PARSE_ERROR:
+ return "Data from DTP didn't parse as expected";
+
+ case DTP20_NOT_EMPTY:
+ return "Trying to read strips when there is already\n"
+ "an offline chart partially read. Clear the instrument and try again";
+ case DTP20_UNEXPECTED_STATUS:
+ return "Unexpected instrument status";
+
+ case DTP20_OK:
+ return "No device error";
+
+ case DTP20_MEASUREMENT_STATUS:
+ return "Measurement complete";
+
+ case DTP20_BAD_COMMAND:
+ return "Unrecognised command";
+ case DTP20_BAD_PARAMETERS:
+ return "Wrong number of parameters";
+ case DTP20_PRM_RANGE_ERROR:
+ return "One or more parameters are out of range";
+ case DTP20_BUSY:
+ return "Instrument is busy - command ignored";
+
+ case DTP20_MEASUREMENT_ERROR:
+ return "General measurement error";
+ case DTP20_TIMEOUT:
+ return "Receive timeout";
+ case DTP20_BAD_STRIP:
+ return "Bad strip";
+
+ case DTP20_NEEDS_CAL_ERROR:
+ return "Instrument needs calibration";
+ case DTP20_CAL_FAILURE_ERROR:
+ return "Calibration failed";
+
+ case DTP20_INSTRUMENT_ERROR:
+ return "General instrument error";
+ case DTP20_LAMP_ERROR:
+ return "Reflectance lamp error";
+
+ case DTP20_BAD_TID:
+ return "Invalid TID detected, Re-scan TID";
+ case DTP20_FLASH_ERASE_FAILURE:
+ return "Flash erase operation failed, Contact support";
+ case DTP20_FLASH_WRITE_FAILURE:
+ return "Flash write operation failed, Contact support";
+ case DTP20_FLASH_VERIFY_FAILURE:
+ return "Flash verify operation failed, Contact support";
+ case DTP20_MEMORY_ERROR:
+ return "Memory access failed, Contact support";
+ case DTP20_ADC_ERROR:
+ return "Analog to digital converter error, Contact support";
+ case DTP20_PROCESSOR_ERROR:
+ return "General processor error, Contact support";
+ case DTP20_BATTERY_ERROR:
+ return "General battery error occurred, Contact support";
+ case DTP20_BATTERY_LOW_ERROR:
+ return "Battery level too low to measure, Charge battery";
+ case DTP20_INPUT_POWER_ERROR:
+ return "Input power out of range, Contact support";
+
+ case DTP20_BATTERY_ABSENT_ERROR:
+ return "Battery could not be detected, Contact support";
+ case DTP20_BAD_CONFIGURATION:
+ return "Stored configuration data invalid, Set as desired";
+
+ case DTP20_BAD_SPOT:
+ return "Invalid spot reading was requested, Re-read or resend";
+ case DTP20_END_OF_DATA:
+ return "End of profile reached, None";
+ case DTP20_DBASE_PROFILE_NOT_EMPTY:
+ return "Profile database not empty, Clear profile data";
+ case DTP20_MEMORY_OVERFLOW_ERROR:
+ return "Memory overflow error, Contact support";
+ case DTP20_BAD_CALIBRATION:
+ return "Bad calibration data detected, Contact support";
+
+ case DTP20_CYAN_CAL_ERROR:
+ return "Failed cyan calibration during TID read, Re-scan TID";
+ case DTP20_MAGENTA_CAL_ERROR:
+ return "Failed magenta calibration during TID read, Re-scan TID";
+ case DTP20_YELLOW_CAL_ERROR:
+ return "Failed yellow calibration during TID read, Re-scan TID";
+ case DTP20_PATCH_SIZE_ERROR:
+ return "Invalid strip patch size was detected, Re-scan";
+ case DTP20_FAIL_PAPER_CHECK:
+ return "Failed to verify scan started/stopped on paper, Re-scan";
+ case DTP20_SHORT_SCAN_ERROR:
+ return "Less than minimum positional ticks detected, Re-scan";
+ case DTP20_STRIP_READ_ERROR:
+ return "General strip reading error, Re-scan";
+ case DTP20_SHORT_TID_ERROR:
+ return "Failed TID length verification, Re-scan TID";
+ case DTP20_SHORT_STRIP_ERROR:
+ return "Strip length invalid, Re-scan";
+ case DTP20_EDGE_COLOR_ERROR:
+ return "Strip edge color was measured invalid, Re-scan";
+ case DTP20_SPEED_ERROR:
+ return "Manual scan too fast to gather data, Re-scan";
+ case DTP20_UNDEFINED_SCAN_ERROR:
+ return "General scan error, Re-scan";
+ case DTP20_INVALID_STRIP_ID:
+ return "A strip ID field was out-of-range, Re-scan";
+ case DTP20_BAD_SERIAL_NUMBER:
+ return "A bad serial number has been detected, Contact support";
+ case DTP20_TID_ALREADY_SCANNED:
+ return "A TID has already been scanned, Scan strips";
+ case DTP20_PROFILE_DATABASE_FULL:
+ return "Profile database is full, Clear profile data";
+
+ case DTP20_SPOT_DATABASE_FULL:
+ return "Spot database is full, Clear spot data";
+ case DTP20_TID_STRIP_MIN_ERROR:
+ return "A TID was specified with fewer than 5 patches, Re-define TID";
+ case DTP20_REREAD_DATABASE_FULL:
+ return "Strip reread database is full (can't reread), Clear profile data";
+ case DTP20_STRIP_DEFINE_TOO_SHORT:
+ return "Strip definition contains too few patches, Re-define strip";
+ case DTP20_STRIP_DEFINE_TOO_LONG:
+ return "Strip definition contains too many patches, Re-define strip";
+ case DTP20_BAD_STRIP_DEFINE:
+ return "No valid strip defined, Define strip";
+
+ case DTP20_BOOTLOADER_MODE:
+ return "Instrument is in FW update mode, Reset or load FW";
+
+ default:
+ return "Unknown error code";
+ }
+}
+
+/* Convert a machine specific error code into an abstract dtp code */
+static inst_code
+dtp20_interp_code(inst *pp, int ec) {
+// dtp20 *p = (dtp20 *)pp;
+
+ switch (ec) {
+
+ case DTP20_OK:
+ return inst_ok;
+
+ case DTP20_MEASUREMENT_STATUS:
+ case DTP20_END_OF_DATA:
+ return inst_notify | ec;
+
+ case DTP20_INTERNAL_ERROR:
+ return inst_internal_error | ec;
+
+ case DTP20_TIMEOUT:
+ case DTP20_COMS_FAIL:
+ return inst_coms_fail | ec;
+
+ case DTP20_UNKNOWN_MODEL:
+ return inst_unknown_model | ec;
+
+ case DTP20_DATA_PARSE_ERROR:
+ case DTP20_BAD_COMMAND:
+ case DTP20_BAD_PARAMETERS:
+ case DTP20_PRM_RANGE_ERROR:
+ case DTP20_BUSY:
+ return inst_protocol_error | ec;
+
+ case DTP20_MEASUREMENT_ERROR:
+ case DTP20_BAD_STRIP:
+ case DTP20_BAD_SPOT:
+ case DTP20_CAL_FAILURE_ERROR:
+ case DTP20_BAD_TID:
+ case DTP20_CYAN_CAL_ERROR:
+ case DTP20_MAGENTA_CAL_ERROR:
+ case DTP20_YELLOW_CAL_ERROR:
+ case DTP20_PATCH_SIZE_ERROR:
+ case DTP20_FAIL_PAPER_CHECK:
+ case DTP20_SHORT_SCAN_ERROR:
+ case DTP20_STRIP_READ_ERROR:
+ case DTP20_SHORT_TID_ERROR:
+ case DTP20_SHORT_STRIP_ERROR:
+ case DTP20_EDGE_COLOR_ERROR:
+ case DTP20_SPEED_ERROR:
+ case DTP20_UNDEFINED_SCAN_ERROR:
+ case DTP20_INVALID_STRIP_ID:
+ case DTP20_TID_ALREADY_SCANNED:
+ case DTP20_NOT_EMPTY:
+ case DTP20_UNEXPECTED_STATUS:
+ return inst_misread | ec;
+
+ case DTP20_NEEDS_CAL_ERROR:
+ return inst_needs_cal | ec;
+
+ case DTP20_INSTRUMENT_ERROR:
+ case DTP20_LAMP_ERROR:
+ case DTP20_FLASH_ERASE_FAILURE:
+ case DTP20_FLASH_WRITE_FAILURE:
+ case DTP20_FLASH_VERIFY_FAILURE:
+ case DTP20_MEMORY_ERROR:
+ case DTP20_ADC_ERROR:
+ case DTP20_PROCESSOR_ERROR:
+ case DTP20_BATTERY_ERROR:
+ case DTP20_INPUT_POWER_ERROR:
+ case DTP20_BATTERY_LOW_ERROR:
+ case DTP20_BATTERY_ABSENT_ERROR:
+ case DTP20_MEMORY_OVERFLOW_ERROR:
+ case DTP20_BAD_CALIBRATION:
+ case DTP20_BAD_SERIAL_NUMBER:
+ return inst_hardware_fail | ec;
+
+ case DTP20_BAD_CONFIGURATION:
+ case DTP20_DBASE_PROFILE_NOT_EMPTY:
+ case DTP20_PROFILE_DATABASE_FULL:
+ case DTP20_SPOT_DATABASE_FULL:
+ case DTP20_TID_STRIP_MIN_ERROR:
+ case DTP20_REREAD_DATABASE_FULL:
+ case DTP20_STRIP_DEFINE_TOO_SHORT:
+ case DTP20_STRIP_DEFINE_TOO_LONG:
+ case DTP20_BAD_STRIP_DEFINE:
+ case DTP20_BOOTLOADER_MODE:
+ return inst_wrong_setup | ec;
+ }
+ return inst_other_error | ec;
+}
+
+/* Destroy ourselves */
+static void
+dtp20_del(inst *pp) {
+ dtp20 *p = (dtp20 *)pp;
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ free (p);
+}
+
+/* Set the instrument capabilities */
+static void set_capabilities(dtp20 *p) {
+
+ p->cap = inst_mode_ref_spot
+ | inst_mode_ref_strip
+ | inst_mode_s_ref_spot
+ | inst_mode_s_ref_chart
+ | inst_mode_colorimeter
+ | inst_mode_spectral
+ ;
+
+ p->cap2 = inst2_prog_trig
+ | inst2_user_switch_trig
+ | inst2_user_trig
+ | inst2_has_battery
+ ;
+
+ p->cap3 = inst3_none;
+}
+
+
+/* Return the instrument capabilities */
+void dtp20_capabilities(inst *pp,
+inst_mode *cap1,
+inst2_capability *cap2,
+inst3_capability *cap3) {
+ dtp20 *p = (dtp20 *)pp;
+
+ if (p->cap == inst_mode_none)
+ set_capabilities(p);
+
+ if (cap1 != NULL)
+ *cap1 = p->cap;
+ if (cap2 != NULL)
+ *cap2 = p->cap2;
+ if (cap3 != NULL)
+ *cap3 = p->cap3;
+}
+
+/*
+ * check measurement mode
+ */
+static inst_code
+dtp20_check_mode(inst *pp, inst_mode m) {
+ dtp20 *p = (dtp20 *)pp;
+ inst_mode cap;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ pp->capabilities(pp, &cap, NULL, NULL);
+
+ /* Simple test */
+ if (m & ~cap)
+ return inst_unsupported;
+
+ /* Check specific modes */
+ if (!IMODETST(m, inst_mode_ref_spot)
+ && !IMODETST(m, inst_mode_ref_strip)
+ && !IMODETST(m, inst_mode_s_ref_spot)
+ && !IMODETST(m, inst_mode_s_ref_chart)) {
+ return inst_unsupported;
+ }
+
+ return inst_ok;
+}
+
+/*
+ * set measurement mode
+ */
+static inst_code
+dtp20_set_mode(inst *pp, inst_mode m) {
+ dtp20 *p = (dtp20 *)pp;
+ inst_code ev;
+
+ if ((ev = dtp20_check_mode(pp, m)) != inst_ok)
+ return ev;
+
+ p->mode = m;
+
+ return inst_ok;
+}
+
+/* Get a status or get or set an option */
+static inst_code dtp20_get_set_opt(
+inst *pp,
+inst_opt_type m, /* Requested status type */
+...) { /* Status parameters */
+ dtp20 *p = (dtp20 *)pp;
+
+ /* Record the trigger mode */
+ if (m == inst_opt_trig_prog
+ || m == inst_opt_trig_user
+ || m == inst_opt_trig_user_switch) {
+ p->trig = m;
+
+ return inst_ok;
+ }
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (m == inst_stat_saved_readings) {
+ char buf[MAX_MES_SIZE];
+ int ev;
+ va_list args;
+ inst_stat_savdrd *fe;
+ int nsr, cs;
+
+ va_start(args, m);
+ fe = va_arg(args, inst_stat_savdrd *);
+ va_end(args);
+
+ *fe = inst_stat_savdrd_none;
+
+ /* See how many saved spot readings there are */
+ if ((ev = dtp20_command(p, "00GM\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if (sscanf(buf," %d ", &nsr) != 1)
+ return inst_protocol_error;
+ if (nsr > p->savix)
+ *fe |= inst_stat_savdrd_spot;
+
+ /* See if the instrument has read a partial or complete target */
+ if ((ev = dtp20_command(p, "CS\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if (sscanf(buf," %d ", &cs) != 1)
+ return inst_protocol_error;
+ if (0 && (cs == 2 || cs == 3)) { // ??? Is this unreliable ???
+ *fe |= inst_stat_savdrd_chart;
+ } else {
+ /* Seems to be no chart saved, but double check, in case of old firmware */
+ if ((ev = dtp20_command(p, "00TS\r", buf, MAX_MES_SIZE, 0.5)) == inst_ok) {
+ if (sscanf(buf," %d ", &cs) == 1) {
+ if (cs != 0) {
+ *fe |= inst_stat_savdrd_chart;
+ }
+ }
+ }
+ }
+ return inst_ok;
+ }
+
+ /* Return the number of saved spot readings */
+ if (m == inst_stat_s_spot) {
+ int ev;
+ char buf[MAX_MES_SIZE];
+ int *pnsr;
+ va_list args;
+
+ va_start(args, m);
+ pnsr = va_arg(args, int *);
+ va_end(args);
+
+ *pnsr = -1;
+
+ /* See how many saved spot readings there are */
+ if ((ev = dtp20_command(p, "00GM\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if (sscanf(buf," %d ", pnsr) != 1)
+ return inst_protocol_error;
+
+ if (*pnsr -= p->savix) /* ??? */
+
+ return inst_ok;
+ }
+
+ /* Return the saved chart details */
+ if (m == inst_stat_s_chart) {
+ inst_code ev = inst_ok;
+ char buf[MAX_RD_SIZE], *bp, *ep;
+ int u[10];
+ va_list args;
+ int *no_patches, *no_rows, *pat_per_row, *chart_id, *missing_row;
+ int i, cs;
+ double pw, gw;
+
+ va_start(args, m);
+ no_patches = va_arg(args, int *);
+ no_rows = va_arg(args, int *);
+ pat_per_row = va_arg(args, int *);
+ chart_id = va_arg(args, int *);
+ missing_row = va_arg(args, int *);
+ va_end(args);
+
+ *no_patches = *no_rows = *pat_per_row = *chart_id = *missing_row = -1;
+
+ /* Get the TID */
+ if ((ev = dtp20_command(p, "ST\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if (sscanf(buf,"Strip Length: %d Total Patches: %d Patch Width: %lf mm Gap Width: %lf mm"
+ " User 1: %d User 2: %d User 3: %d User 4: %d User 5: %d User 6: %d"
+ " User 7: %d User 8: %d User 9: %d User 10: %d ",
+ pat_per_row, no_patches, &pw, &gw, &u[0], &u[1], &u[2], &u[3], &u[4],
+ &u[5], &u[6], &u[7], &u[8], &u[9]) != 14) {
+ return inst_protocol_error;
+ }
+ /* Compute number of rows */
+ *no_rows = *no_patches / *pat_per_row;
+
+ /* Compute the user data/chart id */
+ if (u[0] == 0) /* This seems to be a chart ID */
+ *chart_id = ((u[1] * 8 + u[2]) * 8 + u[3]) * 8 + u[4];
+
+ /* See if any strips are missing */
+ if ((ev = dtp20_command(p, "CS\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if (sscanf(buf," %d ", &cs) != 1)
+ return inst_protocol_error;
+ if (cs == 2) {
+ if ((ev = dtp20_command(p, "01TT\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ bp = buf;
+ for (i = 1; i <= *no_rows; i++) {
+ int strno = 0;
+
+ /* Locate the next number */
+ while(*bp != '\000' && (*bp < '0' || *bp > '9'))
+ bp++;
+
+ /* Locate the end of the number */
+ ep = bp;
+ while (*ep != '\000' && *ep >= '0' && *ep <= '9')
+ ep++;
+ *ep = '\000';
+
+ if (ep > bp)
+ strno = atoi(bp);
+ if (strno != i) {
+ *missing_row = i; /* Assume no missing rows */
+ break;
+ }
+ bp = ep+1;
+ if (bp >= (buf + MAX_MES_SIZE))
+ return inst_protocol_error;
+ }
+ }
+
+ return inst_ok;
+ }
+
+ /* Return the charged status of the battery */
+ if (m == inst_stat_battery) {
+ int ev;
+ char buf[MAX_MES_SIZE];
+ double *pbchl;
+ va_list args;
+ int cs;
+
+ va_start(args, m);
+ pbchl = va_arg(args, double *);
+ va_end(args);
+
+ *pbchl = -1.0;
+
+ /* See how charged the battery is */
+ if ((ev = dtp20_command(p, "06BA\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ if (sscanf(buf," %d ", &cs) != 1)
+ return inst_protocol_error;
+
+ if (cs == 4)
+ *pbchl = 1.0;
+ else if (cs == 3)
+ *pbchl = 0.75;
+ else if (cs == 2)
+ *pbchl = 0.50;
+ else if (cs == 1)
+ *pbchl = 0.25;
+ else
+ *pbchl = 0.0;
+
+ return inst_ok;
+ }
+
+ /* !! It's not clear if there is a way of knowing */
+ /* whether the instrument has a UV filter. */
+
+ /* Use default implementation of other inst_opt_type's */
+ {
+ inst_code rv;
+ va_list args;
+
+ va_start(args, m);
+ rv = inst_get_set_opt_def(pp, m, args);
+ va_end(args);
+
+ return rv;
+ }
+}
+
+/* Constructor */
+extern dtp20 *new_dtp20(icoms *icom, instType itype) {
+ dtp20 *p;
+ if ((p = (dtp20 *)calloc(sizeof(dtp20),1)) == NULL) {
+ a1loge(icom->log, 1, "new_dtp20: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(icom->log);
+
+ p->init_coms = dtp20_init_coms;
+ p->init_inst = dtp20_init_inst;
+ p->capabilities = dtp20_capabilities;
+ p->check_mode = dtp20_check_mode;
+ p->set_mode = dtp20_set_mode;
+ p->get_set_opt = dtp20_get_set_opt;
+ p->read_chart = dtp20_read_chart;
+ p->read_strip = dtp20_read_strip;
+ p->read_sample = dtp20_read_sample;
+ p->get_n_a_cals = dtp20_get_n_a_cals;
+ p->calibrate = dtp20_calibrate;
+ p->interp_error = dtp20_interp_error;
+ p->del = dtp20_del;
+
+ p->icom = icom;
+ p->itype = icom->itype;
+ p->cap = inst_mode_none; /* Unknown until set */
+ p->mode = inst_mode_none; /* Not in a known mode yet */
+
+ return p;
+}
diff --git a/spectro/dtp20.h b/spectro/dtp20.h
new file mode 100644
index 0000000..f1e578a
--- /dev/null
+++ b/spectro/dtp20.h
@@ -0,0 +1,139 @@
+#ifndef DTP41_H
+
+/*
+ * Argyll Color Correction System
+ *
+ * Xrite DTP20 related defines
+ *
+ * Author: Graeme W. Gill
+ * Date: 10/1/2007
+ *
+ * Copyright 1996 - 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.
+ *
+ * Derived from DTP51.h
+ */
+
+/*
+ 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.
+ */
+
+#include "inst.h"
+
+/* Fake Error codes */
+#define DTP20_INTERNAL_ERROR 0x81 /* Internal software error */
+#define DTP20_COMS_FAIL 0x82 /* Communication failure */
+#define DTP20_UNKNOWN_MODEL 0x83 /* Not a DPT51 or DTP52 */
+#define DTP20_DATA_PARSE_ERROR 0x84 /* Read data parsing error */
+
+#define DTP20_NOT_EMPTY 0x89 /* Trying to read strips when not empty */
+#define DTP20_UNEXPECTED_STATUS 0x90 /* Instrument has unexpected status */
+
+/* Real error code */
+#define DTP20_OK 0x00
+
+#define DTP20_MEASUREMENT_STATUS 0x01
+
+#define DTP20_BAD_COMMAND 0x11
+#define DTP20_BAD_PARAMETERS 0x12
+#define DTP20_PRM_RANGE_ERROR 0x13
+#define DTP20_BUSY 0x14
+
+#define DTP20_MEASUREMENT_ERROR 0x20
+#define DTP20_TIMEOUT 0x21
+#define DTP20_BAD_STRIP 0x22
+
+#define DTP20_NEEDS_CAL_ERROR 0x28
+#define DTP20_CAL_FAILURE_ERROR 0x29
+
+#define DTP20_INSTRUMENT_ERROR 0x30
+#define DTP20_LAMP_ERROR 0x31
+
+#define DTP20_BAD_TID 0x33
+#define DTP20_FLASH_ERASE_FAILURE 0x34
+#define DTP20_FLASH_WRITE_FAILURE 0x35
+#define DTP20_FLASH_VERIFY_FAILURE 0x36
+#define DTP20_MEMORY_ERROR 0x37
+#define DTP20_ADC_ERROR 0x38
+#define DTP20_PROCESSOR_ERROR 0x39
+#define DTP20_BATTERY_ERROR 0x3A
+#define DTP20_BATTERY_LOW_ERROR 0x3B
+#define DTP20_INPUT_POWER_ERROR 0x3C
+
+#define DTP20_BATTERY_ABSENT_ERROR 0x3E
+#define DTP20_BAD_CONFIGURATION 0x3F
+
+#define DTP20_BAD_SPOT 0x41
+#define DTP20_END_OF_DATA 0x42
+#define DTP20_DBASE_PROFILE_NOT_EMPTY 0x43
+#define DTP20_MEMORY_OVERFLOW_ERROR 0x44
+#define DTP20_BAD_CALIBRATION 0x45
+
+#define DTP20_CYAN_CAL_ERROR 0x50
+#define DTP20_MAGENTA_CAL_ERROR 0x51
+#define DTP20_YELLOW_CAL_ERROR 0x52
+#define DTP20_PATCH_SIZE_ERROR 0x53
+#define DTP20_FAIL_PAPER_CHECK 0x54
+#define DTP20_SHORT_SCAN_ERROR 0x55
+#define DTP20_STRIP_READ_ERROR 0x56
+#define DTP20_SHORT_TID_ERROR 0x57
+#define DTP20_SHORT_STRIP_ERROR 0x58
+#define DTP20_EDGE_COLOR_ERROR 0x59
+#define DTP20_SPEED_ERROR 0x5A
+#define DTP20_UNDEFINED_SCAN_ERROR 0x5B
+#define DTP20_INVALID_STRIP_ID 0x5C
+#define DTP20_BAD_SERIAL_NUMBER 0x5D
+#define DTP20_TID_ALREADY_SCANNED 0x5E
+#define DTP20_PROFILE_DATABASE_FULL 0x5F
+
+#define DTP20_SPOT_DATABASE_FULL 0x60
+#define DTP20_TID_STRIP_MIN_ERROR 0x61
+#define DTP20_REREAD_DATABASE_FULL 0x62
+#define DTP20_STRIP_DEFINE_TOO_SHORT 0x63
+#define DTP20_STRIP_DEFINE_TOO_LONG 0x64
+#define DTP20_BAD_STRIP_DEFINE 0x65
+
+#define DTP20_BOOTLOADER_MODE 0x7F
+
+/* DTP20 communication object */
+struct _dtp20 {
+ /* **** base instrument class **** */
+ INST_OBJ_BASE
+
+ /* *** DTP20 private data **** */
+ inst_mode cap; /* Instrument mode capability */
+ inst2_capability cap2; /* Instrument capability 2 */
+ inst3_capability cap3; /* Instrument capability 3 */
+ inst_mode mode; /* Currently instrument mode */
+
+ int need_cal; /* Got a need_cal error */
+ inst_opt_type trig; /* Reading trigger mode */
+
+ int savix; /* Index of last saved spot reading read */
+
+}; typedef struct _dtp20 dtp20;
+
+/* Constructor */
+extern dtp20 *new_dtp20(icoms *icom, instType itype);
+
+
+
+#define DTP20_H
+#endif /* DTP20_H */
diff --git a/spectro/dtp22.c b/spectro/dtp22.c
new file mode 100644
index 0000000..5831a1d
--- /dev/null
+++ b/spectro/dtp22.c
@@ -0,0 +1,1110 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * Xrite DTP22 related functions
+ *
+ * Author: Graeme W. Gill
+ * Date: 17/11/2006
+ *
+ * Copyright 1996 - 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* !SALONEINSTLIB */
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* !SALONEINSTLIB */
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "dtp22.h"
+
+/* Default flow control (Instrument doesn't support HW flow control) */
+#define DEFFC fc_XonXOff
+
+static inst_code dtp22_interp_code(inst *pp, int ec);
+static int comp_password(char *out, char *in, unsigned char key[4]);
+static inst_code activate_mode(dtp22 *p);
+static inst_code dtp22_get_set_opt(inst *pp, inst_opt_type m, ...);
+
+#define MAX_MES_SIZE 500 /* Maximum normal message reply size */
+#define MAX_RD_SIZE 5000 /* Maximum reading messagle reply size */
+
+/* Known DTP22 challenge/response keys for each OEM */
+/* (This is a 24 bit key - only the xor of the middle 2 bytes is significant) */
+/* The keys seem to be base 6/36, using nibbles with 2+2 bits: 3 5 6 9 A C */
+/* The last digit corresponds to the OEM serial number (ie. 6C + 9base6 = A6) */
+/* Possibly each digit is offset by the oemsn if counted in the right sequence ? - */
+/* ie. base 40 sequence or so ? Need more examples of keys to tell. */
+struct {
+ int oemsn;
+ unsigned char key[4];
+} keys[] = {
+ { 0, { 0x39, 0xa6, 0x55, 0x6c }}, /* Standard DTP22 */
+ { 9, { 0x5a, 0x66, 0xcc, 0xa6 }}, /* ColorMark calibrator - MacDermid GRAPHICARTS ColorSpan */
+ { -1, } /* End marker */
+};
+
+/* Extract an error code from a reply string */
+/* Return -1 if no error code can be found */
+static int
+extract_ec(char *s) {
+ char *p;
+ char tt[3];
+ int rv;
+ p = s + strlen(s);
+ /* Find the trailing '>' */
+ for (p--; p >= s;p--) {
+ if (*p == '>')
+ break;
+ }
+ if ( (p-3) < s
+ || p[0] != '>'
+ || p[-3] != '<')
+ return -1;
+ tt[0] = p[-2];
+ tt[1] = p[-1];
+ tt[2] = '\000';
+ if (sscanf(tt,"%x",&rv) != 1)
+ return -1;
+ /* For some reason the top bit sometimes get set ? */
+ rv &= 0x7f;
+ return rv;
+}
+
+/* Interpret an icoms error into a DTP22 error */
+static int icoms2dtp22_err(int se) {
+ if (se != ICOM_OK) {
+ if (se & ICOM_TO)
+ return DTP22_TIMEOUT;
+ return DTP22_COMS_FAIL;
+ }
+ return DTP22_OK;
+}
+
+/* Do a full featured command/response echange with the dtp22 */
+/* Return the dtp error code. End on the specified number */
+/* of specified characters, or expiry if the specified timeout */
+/* Assume standard error code if tc = '>' and ntc = 1 */
+/* Return a DTP22 error code */
+static int
+dtp22_fcommand(
+ struct _dtp22 *p,
+ char *in, /* In string */
+ char *out, /* Out string buffer */
+ int bsize, /* Out buffer size */
+ char tc, /* Terminating character */
+ int ntc, /* Number of terminating characters */
+ double to) { /* Timout in seconds */
+ int se, rv = DTP22_OK;
+
+ if ((se = p->icom->write_read(p->icom, in, out, bsize, tc, ntc, to)) != 0) {
+ a1logd(p->log, 1, "dtp22_fcommand: serial i/o failure on write_read '%s'\n",icoms_fix(in));
+ return icoms2dtp22_err(se);
+ }
+ if (tc == '>' && ntc == 1) {
+ rv = extract_ec(out);
+#ifdef NEVER /* Simulate an error ?? */
+ if (strcmp(in, "0PR\r") == 0)
+ rv = 0x1b;
+#endif /* NEVER */
+ if (rv > 0) {
+ rv &= inst_imask;
+ if (rv != DTP22_OK) { /* Clear the error */
+ char buf[MAX_MES_SIZE];
+ p->icom->write_read(p->icom, "CE\r", buf, MAX_MES_SIZE, '>', 1, 0.5);
+ }
+ }
+ }
+ a1logd(p->log, 4, "dtp22_fcommand: command '%s' returned '%s', value 0x%x\n",
+ icoms_fix(in), icoms_fix(out),rv);
+ return rv;
+}
+
+/* Do a standard command/response echange with the dtp22 */
+/* Return the dtp error code */
+static inst_code
+dtp22_command(dtp22 *p, char *in, char *out, int bsize, double to) {
+ int rv = dtp22_fcommand(p, in, out, bsize, '>', 1, to);
+ return dtp22_interp_code((inst *)p, rv);
+}
+
+/* Establish communications with a DTP22 */
+/* If it's a serial port, use the baud rate given, and timeout in to secs */
+/* Return DTP_COMS_FAIL on failure to establish communications */
+static inst_code
+dtp22_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ dtp22 *p = (dtp22 *) pp;
+ char buf[MAX_MES_SIZE];
+ baud_rate brt[5] = { baud_9600, baud_19200, baud_4800, baud_2400, baud_1200 };
+ char *brc[5] = { "30BR\r", "60BR\r", "18BR\r", "0CBR\r", "06BR\r" };
+ char *fcc;
+ unsigned int etime;
+ int ci, bi, i, se;
+ inst_code ev = inst_ok;
+
+ a1logd(p->log, 2, "dtp22_init_coms: About to init Serial I/O\n");
+
+ /* Deal with flow control setting */
+ if (fc == fc_nc)
+ fc = DEFFC;
+ if (fc == fc_XonXOff) {
+ fcc = "0304CF\r";
+ } else if (fc == fc_Hardware) {
+ fcc = "0104CF\r";
+ } else {
+ fc = fc_none;
+ fcc = "0004CF\r";
+ }
+
+ /* Figure DTP22 baud rate being asked for */
+ for (bi = 0; bi < 5; bi++) {
+ if (brt[bi] == br)
+ break;
+ }
+ if (bi >= 5)
+ bi = 0;
+
+ /* Figure current icoms baud rate */
+ for (ci = 0; ci < 5; ci++) {
+ if (brt[ci] == p->icom->br)
+ break;
+ }
+ if (ci >= 5)
+ ci = bi;
+
+ /* The tick to give up on */
+ etime = msec_time() + (long)(1000.0 * tout + 0.5);
+
+ while (msec_time() < etime) {
+
+ a1logd(p->log, 4, "dtp22_init_coms: Trying different baud rates (%u msec to go)\n",
+ etime - msec_time());
+
+ /* Until we time out, find the correct baud rate */
+ for (i = ci; msec_time() < etime;) {
+
+ if ((se = p->icom->set_ser_port(p->icom, fc_none, brt[i], parity_none,
+ stop_1, length_8)) != ICOM_OK) {
+ a1logd(p->log, 1, "dtp22_init_coms: set_ser_port failed ICOM err 0x%x\n",se);
+ return dtp22_interp_code((inst *)p, icoms2dtp22_err(se));
+ }
+ if (((ev = dtp22_command(p, "\r", buf, MAX_MES_SIZE, 0.5)) & inst_mask)
+ != inst_coms_fail)
+ break; /* We've got coms */
+
+ /* Check for user abort */
+ if (p->uicallback != NULL) {
+ inst_code ev;
+ if ((ev = p->uicallback(p->uic_cntx, inst_negcoms)) == inst_user_abort) {
+ a1logd(p->log, 1, "dtp22_init_coms: user aborted\n");
+ return ev;
+ }
+ }
+ if (++i >= 5)
+ i = 0;
+ }
+ break; /* Got coms */
+ }
+
+ if (msec_time() >= etime) { /* We haven't established comms */
+ return inst_coms_fail;
+ }
+
+ /* Set the handshaking */
+ if ((ev = dtp22_command(p, fcc, buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Change the baud rate to the rate we've been told */
+ if ((se = p->icom->write_read(p->icom, brc[bi], buf, MAX_MES_SIZE, '>', 1, .2)) != 0) {
+ if (extract_ec(buf) != DTP22_OK)
+ return inst_coms_fail;
+ }
+
+ /* Configure our baud rate and handshaking as well */
+ if ((se = p->icom->set_ser_port(p->icom, fc, brt[bi], parity_none, stop_1, length_8)) != ICOM_OK) {
+ a1logd(p->log, 1, "dtp22_init_coms: set_ser_port failed ICOM err 0x%x\n",se);
+ return dtp22_interp_code((inst *)p, icoms2dtp22_err(se));
+ }
+
+ /* Loose a character (not sure why) */
+ p->icom->write_read(p->icom, "\r", buf, MAX_MES_SIZE, '>', 1, 0.1);
+
+ /* Check instrument is responding, and reset it again. */
+ if ((ev = dtp22_command(p, "\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok
+ || (ev = dtp22_command(p, "0PR\r", buf, MAX_MES_SIZE, 2.0)) != inst_ok) {
+
+ a1logd(p->log, 1, "dtp22_init_coms: failed with ICOM 0x%x\n",ev);
+
+ p->icom->del(p->icom); /* Since caller may not clean up */
+ p->icom = NULL;
+ return inst_coms_fail;
+ }
+
+ a1logd(p->log, 2, "dtp22_init_coms: init coms has suceeded\n");
+
+ p->gotcoms = 1;
+ return inst_ok;
+}
+
+/* Initialise the DTP22 */
+/* return non-zero on an error, with dtp error code */
+static inst_code
+dtp22_init_inst(inst *pp) {
+ dtp22 *p = (dtp22 *)pp;
+ char buf[MAX_MES_SIZE], *bp;
+ inst_code ev = inst_ok;
+ int i;
+
+ a1logd(p->log, 2, "dtp22_init_inst: called\n");
+
+ if (p->gotcoms == 0)
+ return inst_internal_error; /* Must establish coms before calling init */
+
+ /* Warm reset it */
+ if ((ev = dtp22_command(p, "0PR\r", buf, MAX_MES_SIZE, 2.0)) != inst_ok)
+ return ev;
+
+ /* Get the model and version number */
+ if ((ev = dtp22_command(p, "SV\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Check that it is a DTP22 */
+ if ( strlen(buf) < 12
+ || (strncmp(buf,"X-Rite DTP22",12) != 0))
+ return inst_unknown_model;
+
+ /* Factory reset */
+// if ((ev = dtp22_command(p, "5CRI\r", buf, MAX_MES_SIZE, 10.2)) != inst_ok)
+// return ev;
+
+ /* Turn echoing of characters off */
+ if ((ev = dtp22_command(p, "0EC\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Set decimal point on */
+ if ((ev = dtp22_command(p, "0106CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Set color data separator to TAB */
+ if ((ev = dtp22_command(p, "0207CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Set delimeter to CR */
+ if ((ev = dtp22_command(p, "0008CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Set extra digit resolution (X10) */
+ if ((ev = dtp22_command(p, "010ACF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* - - - - - - - - - - - - - - - - - - - - - - - - */
+ /* Get some information about the instrument */
+ if ((ev = dtp22_command(p, "GI\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) {
+ a1logd(p->log, 1, "dtp22: GI command failed with ICOM err 0x%x\n",ev);
+ return ev;
+ }
+
+ /* Extract some of these */
+ if ((bp = strstr(buf, "Serial Number:")) != NULL) {
+ bp += strlen("Serial Number:");
+ p->serno = atoi(bp);
+ } else {
+ p->serno = -1;
+ }
+ if ((bp = strstr(buf, "OEM Serial #:")) != NULL) {
+ bp += strlen("OEM Serial #:");
+ p->oemsn = atoi(bp);
+ } else {
+ p->oemsn = -1;
+ }
+ if ((bp = strstr(buf, "Cal Plaque Serial #:")) != NULL) {
+ bp += strlen("Cal Plaque Serial #:");
+ p->plaqueno = atoi(bp);
+ } else {
+ p->plaqueno = -1;
+ }
+ if (p->log->verb) {
+ int i, j;
+ for (j = i = 0; ;i++) {
+ if (buf[i] == '<' || buf[i] == '\000')
+ break;
+ if (buf[i] == '\r') {
+ buf[i] = '\000';
+ a1logv(p->log, 1, " %s\n",&buf[j]);
+ if (buf[i+1] == '\n')
+ i++;
+ j = i+1;
+ }
+ }
+ }
+
+ /* - - - - - - - - - - - - - - - - - - - - - - - - */
+ /* Setup for the default type of measurements we want to do */
+
+ /* Disable key codes */
+ if ((ev = dtp22_command(p, "0OK\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Disable the read microswitch by default */
+ if ((ev = dtp22_command(p, "0PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ p->trig = inst_opt_trig_user;
+
+ /* Set format to colorimetric */
+ if ((ev = dtp22_command(p, "0120CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ p->mode &= ~inst_mode_spectral;
+
+ /* Set colorimetric to XYZ */
+ if ((ev = dtp22_command(p, "0221CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Disable density */
+ if ((ev = dtp22_command(p, "0022CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Enable spectral */
+ if ((ev = dtp22_command(p, "0126CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Set Illuminant to D50_2 */
+ if ((ev = dtp22_command(p, "0427CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Read the current calibration values */
+// if ((ev = dtp22_command(p, "0LC\r", buf, MAX_MES_SIZE, 10.2)) != inst_ok)
+// return ev;
+
+ /* See that we have the correct challenge/response key */
+ for (i = 0; keys[i].oemsn >= 0; i++) {
+ if (keys[i].oemsn == p->oemsn) {
+ p->key[0] = keys[i].key[0];
+ p->key[1] = keys[i].key[1];
+ p->key[2] = keys[i].key[2];
+ p->key[3] = keys[i].key[3];
+ break;
+ }
+ }
+ if (keys[i].oemsn < 0)
+ return inst_unknown_model | DTP22_UNKN_OEM;
+
+ p->inited = 1;
+ a1logd(p->log, 2, "dtp22_init_inst: instrument inited OK\n");
+
+ return inst_ok;
+}
+
+/* Read a single sample */
+/* Return the instrument error code */
+static inst_code
+dtp22_read_sample(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+ipatch *val, /* Pointer to instrument patch value */
+instClamping clamp) { /* NZ if clamp XYZ/Lab to be +ve */
+ dtp22 *p = (dtp22 *)pp;
+ char *tp;
+ char buf[MAX_RD_SIZE];
+ char buf2[50];
+ int se;
+ inst_code ev = inst_ok;
+ int switch_trig = 0;
+ int user_trig = 0;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if ((ev = activate_mode(p)) != inst_ok)
+ return ev;
+
+ /* Signal a calibration is needed */
+ if (p->need_cal && p->noutocalib == 0) {
+ return inst_needs_cal; /* Get user to calibrate */
+ }
+
+ /* Request challenge, so that we can return the response */
+ if ((ev = dtp22_command(p, "GP\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ if (comp_password(buf2, buf, p->key))
+ return inst_internal_error | DTP22_INTERNAL_ERROR;
+
+ /* Validate the password */
+ strcat(buf2, "VD\r");
+ if ((ev = dtp22_command(p, buf2, buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ if (strncmp(buf,"PASS", 4) != 0)
+ return inst_unknown_model | DTP22_BAD_PASSWORD;
+
+ if (p->trig == inst_opt_trig_user_switch) {
+
+ /* Enable the read microswitch */
+ if ((ev = dtp22_command(p, "3PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Wait for the microswitch to be triggered, or the user to trigger */
+ for (;;) {
+ if ((se = p->icom->read(p->icom, buf, MAX_MES_SIZE, '>', 1, 1.0)) != 0) {
+ if ((se & ICOM_TO) == 0) { /* Some sort of read error */
+ /* Disable the read microswitch */
+ dtp22_command(p, "2PB\r", buf, MAX_MES_SIZE, 0.2);
+ return dtp22_interp_code((inst *)p, icoms2dtp22_err(se));
+ }
+ /* Timed out */
+ if (p->uicallback != NULL) { /* Check for user trigger */
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (ev == inst_user_abort) {
+ /* Disable the read microswitch */
+ dtp22_command(p, "2PB\r", buf, MAX_MES_SIZE, 0.2);
+ return ev; /* User abort */
+ }
+ if (ev == inst_user_trig)
+ break; /* Trigger */
+ }
+ }
+ } else { /* Inst error or switch activated */
+ if (strlen(buf) >= 4
+ && buf[0] == '<' && isdigit(buf[1]) && isdigit(buf[2]) && buf[3] == '>') {
+ if ((ev = dtp22_interp_code((inst *)p, extract_ec(buf))) != inst_ok) {
+ dtp22_command(p, "CE\r", buf, MAX_MES_SIZE, 0.5);
+ dtp22_command(p, "2PB\r", buf, MAX_MES_SIZE, 0.5);
+ return ev;
+ }
+ switch_trig = 1;
+ break; /* Measure triggered via inst switch */
+ }
+ }
+ }
+ /* Disable the read microswitch */
+ if ((ev = dtp22_command(p, "2PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ /* Notify of trigger */
+ if (p->uicallback)
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ } else if (p->trig == inst_opt_trig_user) {
+
+ if (p->uicallback == NULL) {
+ a1logd(p->log, 1, "dtp22: inst_opt_trig_user but no uicallback function set!\n");
+ return inst_unsupported;
+ }
+ for (;;) {
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (ev == inst_user_abort)
+ return ev; /* Abort */
+ if (ev == inst_user_trig)
+ break; /* Trigger */
+ }
+ msec_sleep(200);
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ 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)
+ return ev; /* Abort */
+ }
+
+ /* Trigger a read if the switch has not been used */
+ if (switch_trig == 0) {
+ if ((ev = dtp22_command(p, "RM\r", buf, MAX_RD_SIZE, 20.0)) != inst_ok) {
+ return ev; /* Misread */
+ }
+ }
+
+ /* Gather the results in D50_2 XYZ % reflectance */
+ if ((ev = dtp22_command(p, "0SR\r", buf, MAX_RD_SIZE, 5.0)) != inst_ok)
+ return ev; /* misread */
+
+ /* Parse the buffer */
+ /* Replace '\r' with '\000' */
+ for (tp = buf; *tp != '\000'; tp++) {
+ if (*tp == '\r')
+ *tp = '\000';
+ }
+
+ if (sscanf(buf, " X %lf Y %lf Z %lf ", &val->XYZ[0], &val->XYZ[1], &val->XYZ[2]) != 3) {
+ return inst_protocol_error;
+ }
+
+ /* This may not change anything since instrument may clamp */
+ if (clamp)
+ icmClamp3(val->XYZ, val->XYZ);
+ val->loc[0] = '\000';
+ val->mtype = inst_mrt_reflective;
+ val->XYZ_v = 1;
+ val->sp.spec_n = 0;
+ val->duration = 0.0;
+
+ if (p->mode & inst_mode_spectral) {
+ int j;
+ char *fmt;
+
+ /* Reset tp to point to start of spectral */
+ tp = buf + strlen(buf) + 1;
+
+ /* Different dialects spoken by DTP-22 */
+ if (strcmp(tp, "SPECTRAL DATA") == 0 ) {
+ tp += strlen(tp) + 1;
+ fmt = " w %*lf S %lf ";
+ } else {
+ fmt = " S %lf ";
+ }
+
+ /* Read the spectral value */
+ for (j = 0; j < 31; j++) {
+ if (sscanf(tp, fmt, &val->sp.spec[j]) != 1)
+ return inst_protocol_error;
+ tp += strlen(tp) + 1;
+ }
+
+ val->sp.spec_n = 31;
+ val->sp.spec_wl_short = 400.0;
+ val->sp.spec_wl_long = 700.0;
+ val->sp.norm = 100.0;
+ }
+
+ if (user_trig)
+ return inst_user_trig;
+ return inst_ok;
+}
+
+/* Return needed and available inst_cal_type's */
+static inst_code dtp22_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
+ dtp22 *p = (dtp22 *)pp;
+ inst_cal_type n_cals = inst_calt_none;
+ inst_cal_type a_cals = inst_calt_none;
+
+ if (p->need_cal && p->noutocalib == 0)
+ n_cals |= inst_calt_ref_white;
+ a_cals |= inst_calt_ref_white;
+
+ if (pn_cals != NULL)
+ *pn_cals = n_cals;
+
+ if (pa_cals != NULL)
+ *pa_cals = a_cals;
+
+ return inst_ok;
+}
+
+/* Request an instrument calibration. */
+/* This is use if the user decides they want to do a calibration, */
+/* in anticipation of a calibration (needs_calibration()) to avoid */
+/* requiring one during measurement, or in response to measuring */
+/* returning inst_needs_cal. Initially us an inst_cal_cond of inst_calc_none, */
+/* and then be prepared to setup the right conditions, or ask the */
+/* user to do so, each time the error inst_cal_setup is returned. */
+inst_code dtp22_calibrate(
+inst *pp,
+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) */
+) {
+ dtp22 *p = (dtp22 *)pp;
+ char buf[MAX_RD_SIZE];
+ int se;
+ inst_code tv, ev = inst_ok;
+ inst_cal_type needed, available;
+ int swen = 0;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ id[0] = '\000';
+
+ if ((ev = dtp22_get_n_a_cals((inst *)p, &needed, &available)) != inst_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,"dtp22_calibrate: doing calt 0x%x\n",calt);
+
+ if ((*calt & inst_calt_n_dfrble_mask) == 0) /* Nothing todo */
+ return inst_ok;
+ }
+
+ /* See if it's a calibration we understand */
+ if (*calt & ~available & inst_calt_all_mask) {
+ return inst_unsupported;
+ }
+
+ if (*calt & inst_calt_ref_white) { /* White calibration */
+
+ sprintf(id, "Serial no. %d",p->plaqueno);
+ if (*calc != inst_calc_man_ref_whitek) {
+ *calc = inst_calc_man_ref_whitek;
+ ev = inst_cal_setup;
+ goto do_exit;
+ }
+
+ /* Calibration only works when triggered by the read switch... */
+ if (!swen) {
+ if ((ev = dtp22_command(p, "3PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ swen = 1;
+ }
+
+ if ((ev = activate_mode(p)) != inst_ok)
+ goto do_exit;
+
+ /* Issue white calibration */
+ if ((se = p->icom->write(p->icom, "1CA\r", 0.5)) != ICOM_OK) {
+ ev = dtp22_interp_code((inst *)p, icoms2dtp22_err(se));
+ goto do_exit;
+ }
+
+ /* Wait for the microswitch to be triggered, or a user trigger via uicallback */
+ for (;;) {
+ if ((se = p->icom->read(p->icom, buf, MAX_MES_SIZE, '>', 1, 1.0)) != 0) {
+ if ((se & ICOM_TO) == 0) { /* Some sort of read error */
+ ev = dtp22_interp_code((inst *)p, icoms2dtp22_err(se));
+ goto do_exit;
+ }
+ /* Timed out - poll user */
+ if (p->uicallback != NULL) { /* Check for user trigger */
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (ev == inst_user_abort)
+ goto do_exit; /* User abort */
+ if (ev == inst_user_trig)
+ break; /* User trigger */
+ }
+ }
+ } else { /* Inst error or switch activated */
+ if (strlen(buf) >= 4
+ && buf[0] == '<' && isdigit(buf[1]) && isdigit(buf[2]) && buf[3] == '>') {
+ if ((ev = dtp22_interp_code((inst *)p, extract_ec(buf))) != inst_ok) {
+ dtp22_command(p, "CE\r", buf, MAX_MES_SIZE, 0.5);
+ if (ev != inst_ok)
+ goto do_exit; /* Error */
+ }
+ break; /* Switch trigger */
+ }
+ }
+ }
+
+ if (p->uicallback) /* Notify of trigger */
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ p->need_cal = 0;
+ *calt &= ~inst_calt_ref_white;
+
+ }
+ if (*calt & inst_calt_ref_dark) { /* Black calibration */
+
+ if (*calc != inst_calc_man_ref_dark) {
+ *calc = inst_calc_man_ref_dark;
+ ev = inst_cal_setup;
+ goto do_exit;
+ }
+
+ /* Check for abort */
+ if (p->uicallback != NULL
+ && (ev = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_abort) {
+ goto do_exit;
+ }
+
+ /* Calibration only works when triggered by the read switch... */
+ if (!swen) {
+ if ((ev = dtp22_command(p, "3PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ swen = 1;
+ }
+
+ if ((ev = activate_mode(p)) != inst_ok)
+ goto do_exit;
+
+ /* Do black calibration */
+ if ((ev = dtp22_command(p, "1CB\r", buf, MAX_RD_SIZE, 20)) != inst_ok)
+ goto do_exit;
+
+ /* Make calibration permanent */
+ if ((ev = dtp22_command(p, "MP\r", buf, MAX_RD_SIZE, 10.0)) != inst_ok)
+ goto do_exit;
+
+ *calt &= ~inst_calt_ref_dark;
+ }
+
+ do_exit:
+
+ if (swen) {
+ /* Disable the read microswitch */
+ if ((tv = dtp22_command(p, "3PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok && ev == inst_ok)
+ return tv;
+ swen = 0;
+ }
+
+ return ev;
+}
+
+/* Error codes interpretation */
+static char *
+dtp22_interp_error(inst *pp, int ec) {
+// dtp22 *p = (dtp22 *)pp;
+ ec &= inst_imask;
+ switch (ec) {
+ case DTP22_INTERNAL_ERROR:
+ return "Internal software error";
+ case DTP22_COMS_FAIL:
+ return "Communications failure";
+ case DTP22_UNKNOWN_MODEL:
+ return "Not a DTP22 or DTP52";
+ case DTP22_DATA_PARSE_ERROR:
+ return "Data from DTP didn't parse as expected";
+ case DTP22_UNKN_OEM:
+ return "Instrument is an unknown OEM version";
+ case DTP22_BAD_PASSWORD:
+ return "Instrument password was rejected";
+
+ case DTP22_OK:
+ return "No device error";
+
+ case DTP22_BAD_COMMAND:
+ return "Unrecognized command";
+ case DTP22_PRM_RANGE:
+ return "Command parameter out of range";
+ case DTP22_MEMORY_OVERFLOW:
+ return "Memory bounds error";
+ case DTP22_INVALID_BAUD_RATE:
+ return "Invalid baud rate";
+ case DTP22_TIMEOUT:
+ return "Receive timeout";
+ case DTP22_SYNTAX_ERROR:
+ return "Badly formed parameter";
+ case DTP22_INCORRECT_DATA_FORMAT:
+ return "Incorrect Data Format";
+ case DTP22_WEAK_LAMP:
+ return "Lamp is weak";
+ case DTP22_LAMP_FAILED:
+ return "Lamp has failed";
+ case DTP22_UNSTABLE_CAL:
+ return "Unstable calibration";
+ case DTP22_CAL_GAIN_ERROR:
+ return "Error setting gains during calibration";
+ case DTP22_SENSOR_FAILURE:
+ return "Sensing cell failure";
+ case DTP22_BLACK_CAL_TOO_HIGH:
+ return "Black calibration values are too high";
+ case DTP22_UNSTABLE_BLACK_CAL:
+ return "Unstable black calibration";
+ case DTP22_CAL_MEM_ERROR:
+ return "Memory error with calibration values";
+ case DTP22_FILTER_MOTOR:
+ return "Filter motor not working";
+ case DTP22_LAMP_FAILED_READING:
+ return "Lamp failed during reading";
+ case DTP22_POWER_INTR_READING:
+ return "Power failed during reading";
+ case DTP22_SIG_OFFSETS_READING:
+ return "Signal offsets exceeded limits during reading";
+ case DTP22_RD_SWITCH_TO_SOON:
+ return "Read switch released too soon";
+ case DTP22_OVERRANGE:
+ return "Overrange reading";
+ case DTP22_FILT_POS_ERROR:
+ return "Filter position sensor error";
+ case DTP22_FACT_TST_CONNECT:
+ return "Factory test connector error";
+ case DTP22_FACT_TST_LAMP_INH:
+ return "Factory test lamp inhibit error";
+
+ case DTP22_EEPROM_FAILURE:
+ return "EEprom write failure";
+ case DTP22_PROGRAM_WRITE_FAIL:
+ return "Loading new program error";
+ case DTP22_MEMORY_WRITE_FAIL:
+ return "Memory write error";
+ default:
+ return "Unknown error code";
+ }
+}
+
+
+/* Convert a machine specific error code into an abstract dtp code */
+static inst_code
+dtp22_interp_code(inst *pp, int ec) {
+// dtp22 *p = (dtp22 *)pp;
+
+ ec &= inst_imask;
+ switch (ec) {
+
+ case DTP22_OK:
+ return inst_ok;
+
+ case DTP22_INTERNAL_ERROR:
+ return inst_internal_error | ec;
+
+ case DTP22_COMS_FAIL:
+ return inst_coms_fail | ec;
+
+ case DTP22_UNKNOWN_MODEL:
+ case DTP22_UNKN_OEM:
+ case DTP22_BAD_PASSWORD:
+ return inst_unknown_model | ec;
+
+ case DTP22_DATA_PARSE_ERROR:
+ return inst_protocol_error | ec;
+
+ case DTP22_POWER_INTR_READING:
+ case DTP22_RD_SWITCH_TO_SOON:
+ case DTP22_OVERRANGE:
+ case DTP22_SIG_OFFSETS_READING:
+ case DTP22_UNSTABLE_CAL:
+ case DTP22_UNSTABLE_BLACK_CAL:
+ case DTP22_BLACK_CAL_TOO_HIGH:
+ case DTP22_CAL_GAIN_ERROR: /* Or H/W error ? */
+ return inst_misread | ec;
+
+ case DTP22_WEAK_LAMP:
+ case DTP22_LAMP_FAILED:
+ case DTP22_SENSOR_FAILURE:
+ case DTP22_CAL_MEM_ERROR:
+ case DTP22_FILTER_MOTOR:
+ case DTP22_LAMP_FAILED_READING:
+ case DTP22_FACT_TST_CONNECT:
+ case DTP22_FACT_TST_LAMP_INH:
+ case DTP22_EEPROM_FAILURE:
+ case DTP22_PROGRAM_WRITE_FAIL:
+ case DTP22_MEMORY_WRITE_FAIL:
+ case DTP22_FILT_POS_ERROR:
+ return inst_hardware_fail | ec;
+ }
+ return inst_other_error | ec;
+}
+
+/* Destroy ourselves */
+static void
+dtp22_del(inst *pp) {
+ dtp22 *p = (dtp22 *)pp;
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ free(p);
+}
+
+/* Return the instrument capabilities */
+void dtp22_capabilities(inst *pp,
+inst_mode *pcap1,
+inst2_capability *pcap2,
+inst3_capability *pcap3) {
+ inst_mode cap1 = 0;
+ inst2_capability cap2 = 0;
+
+ cap1 |= inst_mode_ref_spot
+ | inst_mode_colorimeter
+ | inst_mode_spectral
+ ;
+
+ cap2 |= inst2_prog_trig
+ | inst2_user_trig
+ | inst2_user_switch_trig
+ | inst2_cal_using_switch /* DTP22 special */
+ ;
+
+ if (pcap1 != NULL)
+ *pcap1 = cap1;
+ if (pcap2 != NULL)
+ *pcap2 = cap2;
+ if (pcap3 != NULL)
+ *pcap3 = inst3_none;
+}
+
+/* Activate the last set mode */
+static inst_code
+activate_mode(dtp22 *p) {
+ static char buf[MAX_MES_SIZE];
+ inst_code rv;
+
+ if (p->mode != p->lastmode) {
+
+ if ((p->lastmode & inst_mode_spectral) == inst_mode_spectral
+ && (p->mode & inst_mode_spectral) != inst_mode_spectral) {
+
+ /* Set format to colorimetric + spectral */
+ if ((rv = dtp22_command(p, "0020CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return rv;
+ }
+ if ((p->lastmode & inst_mode_spectral) != inst_mode_spectral
+ && (p->mode & inst_mode_spectral) == inst_mode_spectral) {
+
+ /* Set format to just colorimetric */
+ if ((rv = dtp22_command(p, "0120CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return rv;
+ }
+ p->mode = p->lastmode;
+ }
+ return inst_ok;
+}
+
+/*
+ * check measurement mode
+ */
+static inst_code
+dtp22_check_mode(inst *pp, inst_mode m) {
+ dtp22 *p = (dtp22 *)pp;
+ inst_mode cap;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ pp->capabilities(pp, &cap, NULL, NULL);
+
+ /* Simple test */
+ if (m & ~cap)
+ return inst_unsupported;
+
+ /* General check mode against specific capabilities logic: */
+ if (!IMODETST(m, inst_mode_ref_spot)) {
+ return inst_unsupported;
+ }
+
+ return inst_ok;
+}
+
+/*
+ * set measurement mode
+ */
+static inst_code
+dtp22_set_mode(inst *pp, inst_mode m)
+{
+ dtp22 *p = (dtp22 *)pp;
+ inst_code ev;
+
+ if ((ev = dtp22_check_mode(pp, m)) != inst_ok)
+ return ev;
+
+ p->lastmode = m;
+
+ return inst_ok;
+}
+
+/* !! It's not clear if there is a way of knowing */
+/* whether the instrument has a UV filter. */
+
+/*
+ * set or reset an optional mode
+ *
+ * Since there is no interaction with the instrument,
+ * was assume that all of these can be done before initialisation.
+ */
+static inst_code
+dtp22_get_set_opt(inst *pp, inst_opt_type m, ...)
+{
+ dtp22 *p = (dtp22 *)pp;
+
+ /* Record the trigger mode */
+ if (m == inst_opt_trig_prog
+ || m == inst_opt_trig_user
+ || m == inst_opt_trig_user_switch) {
+ p->trig = m;
+ return inst_ok;
+ }
+
+ return inst_unsupported;
+}
+
+/* Constructor */
+extern dtp22 *new_dtp22(icoms *icom, instType itype) {
+ dtp22 *p;
+ if ((p = (dtp22 *)calloc(sizeof(dtp22),1)) == NULL) {
+ a1loge(icom->log, 1, "new_dtp22: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(icom->log);
+
+ p->init_coms = dtp22_init_coms;
+ p->init_inst = dtp22_init_inst;
+ p->capabilities = dtp22_capabilities;
+ p->check_mode = dtp22_check_mode;
+ p->set_mode = dtp22_set_mode;
+ p->get_set_opt = dtp22_get_set_opt;
+ p->read_sample = dtp22_read_sample;
+ p->get_n_a_cals = dtp22_get_n_a_cals;
+ p->calibrate = dtp22_calibrate;
+ p->interp_error = dtp22_interp_error;
+ p->del = dtp22_del;
+
+ p->icom = icom;
+ p->itype = icom->itype;
+ p->mode = inst_mode_none;
+ p->need_cal = 1; /* Do a white calibration each time we open the device */
+
+ return p;
+}
+
+/* Compute the DTP22/Digital Swatchbook password response. */
+/* Return NZ if there was an error */
+static int comp_password(char *out, char *in, unsigned char key[4]) {
+ unsigned short inv[5];
+ unsigned short outv;
+
+ in[10] = '\000';
+
+ /* Convert the 10 hex chars of input to 5 unsigned chars */
+ if (sscanf(in, "%2hx%2hx%2hx%2hx%2hx", &inv[0], &inv[1], &inv[2], &inv[3], &inv[4]) != 5)
+ return 1;
+
+ /* X-Rite magic... */
+ inv[0] ^= key[0]; /* All seen to have 2 bits set in each nibble. */
+ inv[1] ^= key[1]; /* ie. taken from set 3,5,6,9,A,C ? */
+ inv[2] ^= key[2];
+ inv[4] ^= key[3];
+ outv = ((inv[0] * 256 + inv[2]) ^ (inv[4] * 256 + inv[1])) + inv[4];
+
+ sprintf(out, "%04x", outv);
+ return 0;
+}
diff --git a/spectro/dtp22.h b/spectro/dtp22.h
new file mode 100644
index 0000000..95dce16
--- /dev/null
+++ b/spectro/dtp22.h
@@ -0,0 +1,105 @@
+#ifndef DTP22_H
+
+/*
+ * Argyll Color Correction System
+ *
+ * Xrite DTP22 related defines
+ *
+ * Author: Graeme W. Gill
+ * Date: 17/11/2006
+ *
+ * Copyright 2001 - 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.
+ */
+
+#include "inst.h"
+
+/* Note: update dtp22_interp_error() and dtp22_interp_code() in dtp22.c */
+/* if anything of these #defines are added or subtracted */
+
+/* Fake Error codes */
+#define DTP22_INTERNAL_ERROR 0x61 /* Internal software error */
+#define DTP22_COMS_FAIL 0x62 /* Communication failure */
+#define DTP22_UNKNOWN_MODEL 0x63 /* Not a DPT22 */
+#define DTP22_DATA_PARSE_ERROR 0x64 /* Read data parsing error */
+#define DTP22_UNKN_OEM 0x69 /* Unrecognized OEM */
+#define DTP22_BAD_PASSWORD 0x6A /* Password wasn't accepted */
+
+/* Real error code */
+#define DTP22_OK 0x00
+
+#define DTP22_BAD_COMMAND 0x01
+#define DTP22_PRM_RANGE 0x02
+#define DTP22_MEMORY_OVERFLOW 0x04
+#define DTP22_INVALID_BAUD_RATE 0x05
+#define DTP22_TIMEOUT 0x07
+#define DTP22_SYNTAX_ERROR 0x08
+#define DTP22_INCORRECT_DATA_FORMAT 0x09
+#define DTP22_WEAK_LAMP 0x10
+#define DTP22_LAMP_FAILED 0x11
+#define DTP22_UNSTABLE_CAL 0x12
+#define DTP22_CAL_GAIN_ERROR 0x13
+#define DTP22_SENSOR_FAILURE 0x14
+#define DTP22_BLACK_CAL_TOO_HIGH 0x15
+#define DTP22_UNSTABLE_BLACK_CAL 0x16
+#define DTP22_CAL_MEM_ERROR 0x17
+#define DTP22_FILTER_MOTOR 0x21
+#define DTP22_LAMP_FAILED_READING 0x22
+#define DTP22_POWER_INTR_READING 0x23
+#define DTP22_SIG_OFFSETS_READING 0x24
+#define DTP22_RD_SWITCH_TO_SOON 0x25
+#define DTP22_OVERRANGE 0x26
+#define DTP22_FILT_POS_ERROR 0x28
+#define DTP22_FACT_TST_CONNECT 0x2A
+#define DTP22_FACT_TST_LAMP_INH 0x2B
+
+
+#define DTP22_EEPROM_FAILURE 0x70
+#define DTP22_PROGRAM_WRITE_FAIL 0x71
+#define DTP22_MEMORY_WRITE_FAIL 0x72
+
+/* DTP22 communication object */
+struct _dtp22 {
+ INST_OBJ_BASE
+
+ /* *** DTP41 private data **** */
+ unsigned char key[4]; /* Challenge/response key */
+ int keyvalid; /* nz if key is valid */
+ int serno; /* Serial number of instrument */
+ int oemsn; /* Serial number of OEM */
+ int plaqueno; /* Serial number of calibration plaque */
+ inst_mode mode; /* Currently instrument mode */
+ inst_mode lastmode; /* Last requested mode */
+ int need_cal; /* White calibration needed flag */
+ int noutocalib; /* Don't mode change or auto calibrate */
+ inst_opt_type trig; /* Reading trigger mode */
+
+ }; typedef struct _dtp22 dtp22;
+
+/* Constructor */
+extern dtp22 *new_dtp22(icoms *icom, instType itype);
+
+
+#define DTP22_H
+#endif /* DTP22_H */
diff --git a/spectro/dtp41.c b/spectro/dtp41.c
new file mode 100644
index 0000000..4487124
--- /dev/null
+++ b/spectro/dtp41.c
@@ -0,0 +1,1292 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * Xrite DTP41 related functions
+ *
+ * Author: Graeme W. Gill
+ * Date: 10/3/2001
+ *
+ * Copyright 1996 - 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.
+ *
+ * Derived from DTP51.c
+ */
+
+/*
+ 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdarg.h>
+#include <time.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* !SALONEINSTLIB */
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* !SALONEINSTLIB */
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "dtp41.h"
+
+/* Default flow control */
+#define DEFFC fc_XonXOff
+
+static inst_code dtp41_interp_code(inst *pp, int ec);
+static inst_code activate_mode(dtp41 *p);
+
+#define MAX_MES_SIZE 1000 /* Maximum normal message reply size */
+#define MAX_RD_SIZE 100000 /* Maximum reading messagle reply size */
+
+/* Extract an error code from a reply string */
+/* Return -1 if no error code can be found */
+static int
+extract_ec(char *s) {
+ char *p;
+ char tt[3];
+ int rv;
+ p = s + strlen(s);
+ /* Find the trailing '>' */
+ for (p--; p >= s;p--) {
+ if (*p == '>')
+ break;
+ }
+ if ( (p-3) < s
+ || p[0] != '>'
+ || p[-3] != '<')
+ return -1;
+ tt[0] = p[-2];
+ tt[1] = p[-1];
+ tt[2] = '\000';
+ if (sscanf(tt,"%x",&rv) != 1)
+ return -1;
+ rv &= 0x7f;
+ return rv;
+}
+
+/* Interpret an icoms error into a DTP41 error */
+static int icoms2dtp41_err(int se) {
+ if (se != ICOM_OK) {
+ if (se & ICOM_TO)
+ return DTP41_TIMEOUT;
+ return DTP41_COMS_FAIL;
+ }
+ return DTP41_OK;
+}
+
+/* Do a full featured command/response echange with the dtp41 */
+/* End on the specified number of characters, or expiry if */
+/* the specified timeout. */
+/* Assume standard error code if tc = '>' and ntc = 1 */
+/* Return a DTP41 error code */
+static int
+dtp41_fcommand(
+dtp41 *p,
+char *in, /* In string */
+char *out, /* Out string buffer */
+int bsize, /* Out buffer size */
+char tc, /* Terminating character */
+int ntc, /* Number of terminating characters */
+double to) { /* Timout in seconts */
+ int rv, se;
+
+ if ((se = p->icom->write_read(p->icom, in, out, bsize, tc, ntc, to)) != 0) {
+ a1logd(p->log, 1, "dtp41_fcommand: serial i/o failure 0x%x on write_read '%s'\n",se,icoms_fix(in));
+ return icoms2dtp41_err(se);
+ }
+ rv = DTP41_OK;
+ if (tc == '>' && ntc == 1) {
+ rv = extract_ec(out);
+ if (rv > 0) {
+ rv &= inst_imask;
+ if (rv != DTP41_OK) { /* Clear the error */
+ char buf[MAX_MES_SIZE];
+ p->icom->write_read(p->icom, "CE\r", buf, MAX_MES_SIZE, '>', 1, 0.5);
+ }
+ }
+ }
+ a1logd(p->log, 4, "dtp41_fcommand: command '%s' returned '%s', value 0x%x\n",
+ icoms_fix(in), icoms_fix(out),rv);
+ return rv;
+}
+
+/* Do a standard command/response echange with the dtp41 */
+/* Return the instrument error code */
+static inst_code
+dtp41_command(dtp41 *p, char *in, char *out, int bsize, double to) {
+ int rv = dtp41_fcommand(p, in, out, bsize, '>', 1, to);
+ return dtp41_interp_code((inst *)p, rv);
+}
+
+/* Establish communications with a DTP41 */
+/* Use the baud rate given, and timeout in to secs */
+/* Return DTP_COMS_FAIL on failure to establish communications */
+static inst_code
+dtp41_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ dtp41 *p = (dtp41 *)pp;
+ static char buf[MAX_MES_SIZE];
+ baud_rate brt[9] = { baud_9600, baud_19200, baud_38400, baud_57600,
+ baud_4800, baud_2400, baud_1200, baud_600, baud_300 };
+ char *brc[9] = { "9600BR\r", "19200BR\r", "38400BR\r", "57600BR\r",
+ "4800BR\r", "2400BR\r", "1200BR\r", "600BR\r", "300BR\r" };
+ char *fcc;
+ unsigned int etime;
+ int ci, bi, i, se;
+ inst_code ev = inst_ok;
+
+ a1logd(p->log, 2, "dtp41_init_coms: About to init Serial I/O\n");
+
+ /* Deal with flow control setting */
+ if (fc == fc_nc)
+ fc = DEFFC;
+ if (fc == fc_XonXOff) {
+ fcc = "0304CF\r";
+ } else if (fc == fc_Hardware) {
+ fcc = "0104CF\r";
+ } else {
+ fc = fc_none;
+ fcc = "0004CF\r";
+ }
+
+ /* Figure DTP41 baud rate being asked for */
+ for (bi = 0; bi < 9; bi++) {
+ if (brt[bi] == br)
+ break;
+ }
+ if (bi >= 9)
+ bi = 0;
+
+ /* Figure current icoms baud rate */
+ for (ci = 0; ci < 9; ci++) {
+ if (brt[ci] == p->icom->br)
+ break;
+ }
+ if (ci >= 9)
+ ci = bi;
+
+ /* The tick to give up on */
+ etime = msec_time() + (long)(1000.0 * tout + 0.5);
+
+ while (msec_time() < etime) {
+
+ /* Until we time out, find the correct baud rate */
+ for (i = ci; msec_time() < etime;) {
+ if ((se = p->icom->set_ser_port(p->icom, fc_none, brt[i], parity_none,
+ stop_1, length_8)) != ICOM_OK) {
+ a1logd(p->log, 1, "dtp41_init_coms: set_ser_port failed ICOM err 0x%x\n",se);
+ return dtp41_interp_code((inst *)p, icoms2dtp41_err(se));
+ }
+ if (((ev = dtp41_command(p, "\r", buf, MAX_MES_SIZE, 0.5)) & inst_mask)
+ != inst_coms_fail)
+ break; /* We've got coms */
+
+ /* Check for user abort */
+ if (p->uicallback != NULL) {
+ inst_code ev;
+ if ((ev = p->uicallback(p->uic_cntx, inst_negcoms)) == inst_user_abort) {
+ a1logd(p->log, 1, "dtp41_init_coms: user aborted\n");
+ return inst_user_abort;
+ }
+ }
+ if (++i >= 9)
+ i = 0;
+ }
+ break; /* Got coms */
+ }
+
+ if (msec_time() >= etime) { /* We haven't established comms */
+ return inst_coms_fail;
+ }
+
+ /* set the protocol to RCI */
+ if ((ev = dtp41_command(p, "0012CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set the handshaking (cope with coms breakdown) */
+ if ((se = p->icom->write_read(p->icom, fcc, buf, MAX_MES_SIZE, '>', 1, 1.5)) != 0) {
+ if (extract_ec(buf) != DTP41_OK)
+ return inst_coms_fail;
+ }
+
+ /* Change the baud rate to the rate we've been told (cope with coms breakdown) */
+ if ((se = p->icom->write_read(p->icom, brc[bi], buf, MAX_MES_SIZE, '>', 1, 1.5)) != 0) {
+ if (extract_ec(buf) != DTP41_OK)
+ return inst_coms_fail;
+ }
+
+ /* Configure our baud rate and handshaking as well */
+ if ((se = p->icom->set_ser_port(p->icom, fc, brt[bi], parity_none, stop_1, length_8))
+ != ICOM_OK) {
+ a1logd(p->log, 1, "dtp41_init_coms: set_ser_port failed ICOM err 0x%x\n",se);
+ return dtp41_interp_code((inst *)p, icoms2dtp41_err(se));
+ }
+
+ /* Loose a character (not sure why) */
+ p->icom->write_read(p->icom, "\r", buf, MAX_MES_SIZE, '>', 1, 0.5);
+
+ /* Check instrument is responding */
+ if ((ev = dtp41_command(p, "\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok) {
+ a1logd(p->log, 1, "dtp41_init_coms: instrument failed to respond\n");
+ return inst_coms_fail;
+ }
+
+ a1logd(p->log, 2, "dtp41_init_coms: init coms has suceeded\n");
+
+ p->gotcoms = 1;
+ return inst_ok;
+}
+
+/* Build a strip definition as a set of passes, including DS command */
+static void
+build_strip(
+dtp41 *p,
+char *tp, /* pointer to string buffer */
+char *name, /* Strip name (7 chars) (not used) */
+int npatch, /* Number of patches in the pass */
+char *pname, /* Pass name (3 chars) (not used) */
+int sguide, /* Guide number (not used) */
+double pwid, /* Patch length in mm */
+double gwid, /* Gap length in mm */
+double twid /* Trailer length in mm (DTP41T only) */
+) {
+
+ /* Number of patches in strip */
+ sprintf(tp, "%03d",npatch);
+ tp += 3;
+
+ /* Patch width in mm, dd.dd */
+ sprintf(tp, "%05.2f",pwid);
+ tp[2] = tp[3]; /* Remove point */
+ tp[3] = tp[4];
+ tp += 4;
+
+ /* Gap width in mm, dd.dd */
+ sprintf(tp, "%05.2f",gwid);
+ tp[2] = tp[3]; /* Remove point */
+ tp[3] = tp[4];
+ tp += 4;
+
+ *tp++ = '0'; /* Normal strip */
+
+ *tp++ = '8'; /* Auto type */
+
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_transmission) {
+
+ if (twid >= 9999.5) {
+ a1logw(p->log, "DTP41 build_strip given trailer length %f > 9999 mm\n",twid);
+ twid = 9999.0;
+ }
+ /* Trailer length in mm, dddd */
+ sprintf(tp, "%04.0f",twid);
+ tp += 4;
+ }
+
+ *tp++ = 'D'; /* The DS command */
+ *tp++ = 'S';
+ *tp++ = '\r'; /* The CR */
+ *tp++ = '\000'; /* The end */
+
+}
+
+/* Initialise the DTP41. */
+/* return non-zero on an error, with instrument error code */
+static inst_code
+dtp41_init_inst(inst *pp) {
+ dtp41 *p = (dtp41 *)pp;
+ static char tbuf[100], buf[MAX_MES_SIZE];
+ inst_code ev = inst_ok;
+
+ a1logd(p->log, 2, "dtp41_init_inst: called\n");
+
+ if (p->gotcoms == 0)
+ return inst_internal_error; /* Must establish coms before calling init */
+
+ /* Resetting instrument resets the baud rate, so do manual reset. */
+
+ /* Set emulation mode to DTP41 */
+ if ((ev = dtp41_command(p, "0010CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Turn echoing of characters off */
+ if ((ev = dtp41_command(p, "0009CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set Response delimeter to CR */
+ if ((ev = dtp41_command(p, "0008CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Get the model and version number */
+ if ((ev = dtp41_command(p, "SV\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Check that it is a DTP41 */
+ if ( strlen(buf) < 12
+ || strncmp(buf,"X-Rite DTP41",11) != 0
+ || (buf[11] != '1' && buf[11] != '2'))
+ return inst_unknown_model;
+
+ /* Set Language to English */
+ if ((ev = dtp41_command(p, "0000CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set Beeper to medium */
+ if ((ev = dtp41_command(p, "0201CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set Automatic Transmit off */
+ if ((ev = dtp41_command(p, "0005CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set decimal point on */
+ if ((ev = dtp41_command(p, "0106CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set color data separator to TAB */
+ if ((ev = dtp41_command(p, "0207CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set 2 decimal digit resolution */
+ if ((ev = dtp41_command(p, "020ACF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set Min/Max mode off */
+ if ((ev = dtp41_command(p, "000CCF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set persistent errors off */
+ if ((ev = dtp41_command(p, "000DCF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set show data labels mode off */
+ if ((ev = dtp41_command(p, "000FCF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set drive motor calibration at power up to off */
+ if ((ev = dtp41_command(p, "0011CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set Reflection calibration timeout to 24 Hrs */
+ if ((ev = dtp41_command(p, "181ECF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set Trailer timout to 2 seconds */
+ if ((ev = dtp41_command(p, "021FCF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set Transmission calibration timeout to 24 Hrs */
+ if ((ev = dtp41_command(p, "1820CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok) {
+ /* This may fail if the firmware version is < v8212 */
+ if ((ev & inst_imask) != DTP41_PRM_RANGE_ERROR)
+ return ev;
+ }
+
+ /* - - - - - - - - - - - - - - - - - - - - - - - - */
+ /* Setup for the type of measurements we want to do */
+ /* Enable the read microswitch */
+ if ((ev = dtp41_command(p, "01PB\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+ p->trig = inst_opt_trig_user_switch;
+
+ /* Set dynamic measurement mode */
+ if ((ev = dtp41_command(p, "0113CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set instrument to reflectance mode */
+ if ((ev = dtp41_command(p, "0019CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set data format to Reflectance, so TS can select. */
+ if ((ev = dtp41_command(p, "0318CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set density format to spectral */
+ if ((ev = dtp41_command(p, "0417CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set Illuminant to D50_2 */
+ if ((ev = dtp41_command(p, "0416CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set static samples to 10 */
+ if ((ev = dtp41_command(p, "0A14CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set static readings to configured number (usually 5) */
+ sprintf(tbuf, "%02x15CF\r", p->nstaticr);
+ if ((ev = dtp41_command(p, tbuf, buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+#ifdef NEVER
+ /* See what the transmission mode is up to */
+ dtp41_command(p, "DEVELOPERPW\r", buf, MAX_MES_SIZE, 1.5);
+ dtp41_command(p, "0119CF\r", buf, MAX_MES_SIZE, 1.5);
+ dtp41_command(p, "36OD\r", buf, MAX_MES_SIZE, 1.5);
+ dtp41_command(p, "1422OD\r", buf, MAX_MES_SIZE, 1.5);
+#endif
+
+ /* We are configured in this mode now */
+ p->mode = inst_mode_ref_strip;
+
+ if (p->lastmode != p->mode) {
+ if ((ev = activate_mode(p)) != inst_ok)
+ return ev;
+ }
+
+ p->inited = 1;
+
+ a1logd(p->log, 2, "dtp41_init_inst: instrument inited OK\n");
+
+ return inst_ok;
+}
+
+/* Read a set of strips */
+/* Return the instrument error code */
+static inst_code
+dtp41_read_strip(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+int npatch, /* Number of patches in the pass */
+char *pname, /* Pass name (3 chars) */
+int sguide, /* Guide number */
+double pwid, /* Patch length in mm (For DTP41) */
+double gwid, /* Gap length in mm (For DTP41) */
+double twid, /* Trailer length in mm (For DTP41T) */
+ipatch *vals) { /* Pointer to array of instrument patch values */
+ dtp41 *p = (dtp41 *)pp;
+ char tbuf[200], *tp;
+ static char buf[MAX_RD_SIZE];
+ int i, se;
+ inst_code ev = inst_ok;
+ int switch_trig = 0;
+ int user_trig = 0;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* Configure for dynamic mode */
+ p->lastmode = (p->lastmode & ~inst_mode_sub_mask) | inst_mode_strip;
+ activate_mode(p);
+
+ build_strip(p, tbuf, name, npatch, pname, sguide, pwid, gwid, twid);
+
+ /* Send strip definition */
+ if ((ev = dtp41_command(p, tbuf, buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ if (p->trig == inst_opt_trig_user_switch) {
+
+ /* Wait for the Read status, or a user trigger/abort */
+ for (;;) {
+ if ((ev = dtp41_command(p, "", buf, MAX_MES_SIZE, 0.5)) != inst_ok) {
+ if ((ev & inst_mask) == inst_needs_cal)
+ p->need_cal = 1;
+
+ if ((ev & inst_imask) != DTP41_TIMEOUT)
+ return ev; /* Instrument or comms error */
+
+ /* Timed out */
+ if (p->uicallback != NULL) { /* Check for user trigger */
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (ev == inst_user_abort)
+ return ev; /* User abort */
+ if (ev == inst_user_trig) {
+ user_trig = 1;
+ break;
+ }
+ }
+ }
+
+ } else { /* Got read status - assume triggered */
+ switch_trig = 1;
+ break; /* Switch activated */
+ }
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ } else if (p->trig == inst_opt_trig_user) {
+
+ if (p->uicallback == NULL) {
+ a1logd(p->log, 1, "dtp41: inst_opt_trig_user but no uicallback function set!\n");
+ return inst_unsupported;
+ }
+
+ for (;;) {
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (ev == inst_user_abort)
+ return ev; /* Abort */
+ if (ev == inst_user_trig) {
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ msec_sleep(200);
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ 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)
+ return ev; /* Abort */
+ }
+
+ /* Trigger a read if the switch has not been used */
+ if (switch_trig == 0) {
+ /* Do a strip read */
+ if ((ev = dtp41_command(p, "RM\r", buf, MAX_MES_SIZE, 30.0)) != inst_ok) {
+ if ((ev & inst_mask) == inst_needs_cal)
+ p->need_cal = 1;
+ return ev;
+ }
+ }
+
+ /* Gather the results in D50_2 XYZ */
+ if ((ev = dtp41_command(p, "0405TS\r", buf, MAX_RD_SIZE, 0.5 + npatch * 0.1)) != inst_ok)
+ return ev; /* Strip misread */
+
+ /* Parse the buffer */
+ /* Replace '\r' with '\000' */
+ for (tp = buf; *tp != '\000'; tp++) {
+ if (*tp == '\r')
+ *tp = '\000';
+ }
+ for (tp = buf, i = 0; i < npatch; i++) {
+ if (*tp == '\000')
+ return inst_protocol_error;
+ if (sscanf(tp, " %lf %lf %lf ",
+ &vals[i].XYZ[0], &vals[i].XYZ[1], &vals[i].XYZ[2]) != 3) {
+ if (sscanf(tp, " %lf %lf %lf ",
+ &vals[i].XYZ[0], &vals[i].XYZ[1], &vals[i].XYZ[2]) != 3) {
+ return inst_protocol_error;
+ }
+ }
+ vals[i].loc[0] = '\000';
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_transmission)
+ vals[i].mtype = inst_mrt_transmissive;
+ else
+ vals[i].mtype = inst_mrt_reflective;
+ vals[i].XYZ_v = 1;
+ vals[i].sp.spec_n = 0;
+ vals[i].duration = 0.0;
+ tp += strlen(tp) + 1;
+ }
+
+ if (p->mode & inst_mode_spectral) {
+
+ /* Gather the results in Spectral reflectance */
+ if ((ev = dtp41_command(p, "0403TS\r", buf, MAX_RD_SIZE, 0.5 + npatch * 0.1)) != inst_ok)
+ return ev; /* Strip misread */
+
+ /* Parse the buffer */
+ /* Replace '\r' with '\000' */
+ for (tp = buf; *tp != '\000'; tp++) {
+ if (*tp == '\r')
+ *tp = '\000';
+ }
+ /* Get each patches spetra */
+ for (tp = buf, i = 0; i < npatch; i++) {
+ int j;
+ char *tpp;
+ if (strlen(tp) < (31 * 8 - 1)) {
+ return inst_protocol_error;
+ }
+
+ /* Read the spectral value */
+ for (tpp = tp, j = 0; j < 31; j++, tpp += 8) {
+ char c;
+ c = tpp[7];
+ tpp[7] = '\000';
+ vals[i].sp.spec[j] = atof(tpp);
+ tpp[7] = c;
+ }
+
+ vals[i].sp.spec_n = 31;
+ vals[i].sp.spec_wl_short = 400.0;
+ vals[i].sp.spec_wl_long = 700.0;
+ vals[i].sp.norm = 100.0;
+ tp += strlen(tp) + 1;
+ }
+ }
+ if (user_trig)
+ return inst_user_trig;
+ return inst_ok;
+}
+
+/* Read a single sample */
+/* Return the instrument error code */
+static inst_code
+dtp41_read_sample(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+ipatch *val, /* Pointer to instrument patch value */
+instClamping clamp) { /* NZ if clamp XYZ/Lab to be +ve */
+ dtp41 *p = (dtp41 *)pp;
+ char *tp;
+ static char buf[MAX_RD_SIZE];
+ int i, se;
+ inst_code ev = inst_ok;
+ int switch_trig = 0;
+ int user_trig = 0;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* Configure for static mode */
+ p->lastmode = (p->lastmode & ~inst_mode_sub_mask) | inst_mode_spot;
+ activate_mode(p);
+
+ /* Set static measurement mode */
+ if ((ev = dtp41_command(p, "0013CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ if (p->trig == inst_opt_trig_user_switch) {
+
+ /* Wait for the Read status, or a user trigger/abort */
+ for (;;) {
+ if ((ev = dtp41_command(p, "", buf, MAX_MES_SIZE, 0.5)) != inst_ok) {
+ if ((ev & inst_mask) == inst_needs_cal)
+ p->need_cal = 1;
+
+ if ((ev & inst_imask) != DTP41_TIMEOUT)
+ return ev; /* Instrument or comms error */
+
+ /* Timed out */
+ if (p->uicallback != NULL) { /* Check for user trigger */
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (ev == inst_user_abort)
+ return ev; /* User abort */
+ if (ev == inst_user_trig) {
+ user_trig = 1;
+ break; /* User trigger */
+ }
+ }
+ }
+
+ } else { /* Assume read status and trigger */
+ switch_trig = 1;
+ break; /* Switch activated */
+ }
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ } else if (p->trig == inst_opt_trig_user) {
+
+ if (p->uicallback == NULL) {
+ a1logd(p->log, 1, "dtp41: inst_opt_trig_user but no uicallback function set!\n");
+ return inst_unsupported;
+ }
+
+ for (;;) {
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (ev == inst_user_abort)
+ return ev; /* Abort */
+ if (ev == inst_user_trig) {
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ msec_sleep(200);
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ 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)
+ return ev; /* Abort */
+ }
+
+ /* Trigger a read if the switch has not been used */
+ if (switch_trig == 0) {
+ /* Do a read */
+ if ((ev = dtp41_command(p, "RM\r", buf, MAX_MES_SIZE, 20.0)) != inst_ok) {
+ if ((ev & inst_mask) == inst_needs_cal)
+ p->need_cal = 1;
+ return ev;
+ }
+ }
+
+ /* Gather the results in D50_2 XYZ */
+ if ((ev = dtp41_command(p, "0405TS\r", buf, MAX_RD_SIZE, 0.5)) != inst_ok)
+ return ev; /* Strip misread */
+
+ /* Parse the buffer */
+ /* Replace '\r' with '\000' */
+ for (tp = buf; *tp != '\000'; tp++) {
+ if (*tp == '\r')
+ *tp = '\000';
+ }
+
+ val->XYZ[0] = val->XYZ[1] = val->XYZ[2] = 0.0;
+
+ /* for all the readings taken */
+ for (tp = buf, i = 0; i < p->nstaticr; i++) {
+ double XYZ[3];
+
+ if (*tp == '\000')
+ return inst_protocol_error;
+
+ if (sscanf(tp, " %lf %lf %lf ", &XYZ[0], &XYZ[1], &XYZ[2]) != 3) {
+ return inst_protocol_error;
+ }
+ val->XYZ[0] += XYZ[0];
+ val->XYZ[1] += XYZ[1];
+ val->XYZ[2] += XYZ[2];
+ tp += strlen(tp) + 1;
+ }
+
+ /* Average */
+ val->XYZ[0] /= (double)p->nstaticr;
+ val->XYZ[1] /= (double)p->nstaticr;
+ val->XYZ[2] /= (double)p->nstaticr;
+ /* This may not change anything since instrument may clamp */
+ if (clamp)
+ icmClamp3(val->XYZ, val->XYZ);
+ val->loc[0] = '\000';
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_transmission)
+ val->mtype = inst_mrt_transmissive;
+ else
+ val->mtype = inst_mrt_reflective;
+ val->XYZ_v = 1;
+ val->sp.spec_n = 0;
+ val->duration = 0.0;
+
+ if (p->mode & inst_mode_spectral) {
+ int j;
+
+ /* Gather the results in Spectral reflectance */
+ if ((ev = dtp41_command(p, "0403TS\r", buf, MAX_RD_SIZE, 0.5)) != DTP41_OK)
+ return ev; /* Strip misread */
+
+ /* Parse the buffer */
+ /* Replace '\r' with '\000' */
+ for (tp = buf; *tp != '\000'; tp++) {
+ if (*tp == '\r')
+ *tp = '\000';
+ }
+
+ for (j = 0; j < 31; j++)
+ val->sp.spec[j] = 0.0;
+
+ /* Get each readings spetra */
+ for (tp = buf, i = 0; i < p->nstaticr; i++) {
+ char *tpp;
+ if (strlen(tp) < (31 * 8 - 1)) {
+ return inst_protocol_error;
+ }
+
+ /* Read the spectral value */
+ for (tpp = tp, j = 0; j < 31; j++, tpp += 8) {
+ char c;
+ c = tpp[7];
+ tpp[7] = '\000';
+ val->sp.spec[j] += atof(tpp);
+ tpp[7] = c;
+ }
+
+ tp += strlen(tp) + 1;
+ }
+
+ /* Average the result */
+ for (j = 0; j < 31; j++)
+ val->sp.spec[j] /= (double)p->nstaticr;
+
+ val->sp.spec_n = 31;
+ val->sp.spec_wl_short = 400.0;
+ val->sp.spec_wl_long = 700.0;
+ val->sp.norm = 100.0;
+ }
+
+ /* Set back to dynamic measurement mode */
+ if ((ev = dtp41_command(p, "0113CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ if (user_trig)
+ return inst_user_trig;
+ return inst_ok;
+}
+
+/* Return needed and available inst_cal_type's */
+static inst_code dtp41_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
+ dtp41 *p = (dtp41 *)pp;
+ inst_cal_type n_cals = inst_calt_none;
+ inst_cal_type a_cals = inst_calt_none;
+
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_transmission) {
+ if (p->need_cal)
+ n_cals |= inst_calt_trans_white; /* ??? */
+ a_cals |= inst_calt_trans_white;
+ } else {
+ if (p->need_cal)
+ n_cals |= inst_calt_ref_white;
+ a_cals |= inst_calt_ref_white;
+ }
+
+ if (pn_cals != NULL)
+ *pn_cals = n_cals;
+
+ if (pa_cals != NULL)
+ *pa_cals = a_cals;
+
+ return inst_ok;
+}
+
+/* Request an instrument calibration. */
+/* This is use if the user decides they want to do a calibration, */
+/* in anticipation of a calibration (needs_calibration()) to avoid */
+/* requiring one during measurement, or in response to measuring */
+/* returning inst_needs_cal. Initially us an inst_cal_cond of inst_calc_none, */
+/* and then be prepared to setup the right conditions, or ask the */
+/* user to do so, each time the error inst_cal_setup is returned. */
+inst_code dtp41_calibrate(
+inst *pp,
+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) */
+) {
+ dtp41 *p = (dtp41 *)pp;
+ inst_code ev;
+ inst_cal_type needed, available;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ id[0] = '\000';
+
+ if ((ev = dtp41_get_n_a_cals((inst *)p, &needed, &available)) != inst_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,"dtp41_calibrate: doing calt 0x%x\n",calt);
+
+ if ((*calt & inst_calt_n_dfrble_mask) == 0) /* Nothing todo */
+ return inst_ok;
+ }
+
+ /* See if it's a calibration we understand */
+ if (*calt & ~available & inst_calt_all_mask) {
+ return inst_unsupported;
+ }
+
+
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_transmission) {
+ if (*calt & inst_calt_trans_white) {
+
+ if (*calc != inst_calc_uop_trans_white)
+ *calc = inst_calc_uop_trans_white; /* Ask user to do calibration */
+ return inst_cal_setup;
+ }
+
+ p->need_cal = 0;
+ *calt &= ~inst_calt_trans_white;
+
+ } else {
+ if (*calt & inst_calt_ref_white) {
+
+ if (*calc != inst_calc_uop_ref_white) {
+ *calc = inst_calc_uop_ref_white; /* Ask user to do calibration */
+ return inst_cal_setup;
+ }
+
+ p->need_cal = 0;
+ *calt &= ~inst_calt_ref_white;
+ }
+ }
+
+ return inst_ok; /* Calibration done */
+}
+
+/* Error codes interpretation */
+static char *
+dtp41_interp_error(inst *pp, int ec) {
+// dtp41 *p = (dtp41 *)pp;
+ ec &= inst_imask;
+ switch (ec) {
+
+ case DTP41_INTERNAL_ERROR:
+ return "Internal software error";
+ case DTP41_COMS_FAIL:
+ return "Communications failure";
+ case DTP41_UNKNOWN_MODEL:
+ return "Not a DTP41";
+ case DTP41_DATA_PARSE_ERROR:
+ return "Data from DTP41 didn't parse as expected";
+ case DTP41_OK:
+ return "No device error";
+ case DTP41_MEASUREMENT_STATUS:
+ return "Measurement complete";
+ case DTP41_CALIBRATION_STATUS:
+ return "Calibration complete";
+ case DTP41_KEYPRESS_STATUS:
+ return "A key was pressed";
+ case DTP41_DEFAULTS_LOADED_STATUS:
+ return "Default configuration values have been loaded";
+ case DTP41_BAD_COMMAND:
+ return "Unrecognised command";
+ case DTP41_BAD_PARAMETERS:
+ return "Wrong number of parameters";
+ case DTP41_PRM_RANGE_ERROR:
+ return "One or more parameters are out of range";
+ case DTP41_BUSY:
+ return "Instrument is busy - command ignored";
+ case DTP41_MEASUREMENT_ERROR:
+ return "General measurement error";
+ case DTP41_TIMEOUT:
+ return "Receive timeout";
+ case DTP41_BAD_STRIP:
+ return "Bad strip";
+ case DTP41_BAD_COLOR:
+ return "Bad color";
+ case DTP41_BAD_STEP:
+ return "Bad step";
+ case DTP41_BAD_PASS:
+ return "Bad pass";
+ case DTP41_BAD_PATCHES:
+ return "Bad patches";
+ case DTP41_BAD_READING:
+ return "Bad reading";
+ case DTP41_NEEDS_CAL_ERROR:
+ return "Instrument needs calibration";
+ case DTP41_CAL_FAILURE_ERROR:
+ return "Calibration failed";
+ case DTP41_INSTRUMENT_ERROR:
+ return "General instrument error";
+ case DTP41_LAMP_ERROR:
+ return "Reflectance lamp error";
+ case DTP41_FILTER_ERROR:
+ return "Filter error";
+ case DTP41_FILTER_MOTOR_ERROR:
+ return "Filter motor error";
+ case DTP41_DRIVE_MOTOR_ERROR:
+ return "Strip drive motor error";
+ case DTP41_KEYPAD_ERROR:
+ return "Keypad error";
+ case DTP41_DISPLAY_ERROR:
+ return "Display error";
+ case DTP41_MEMORY_ERROR:
+ return "Memory error";
+ case DTP41_ADC_ERROR:
+ return "ADC error";
+ case DTP41_PROCESSOR_ERROR:
+ return "Processor error";
+ case DTP41_BATTERY_ERROR:
+ return "Battery error";
+ case DTP41_BATTERY_LOW_ERROR:
+ return "Battery low error";
+ case DTP41_INPUT_POWER_ERROR:
+ return "Input power error";
+ case DTP41_TEMPERATURE_ERROR:
+ return "Temperature error";
+ case DTP41_BATTERY_ABSENT_ERROR:
+ return "Battery absent error";
+ case DTP41_TRAN_LAMP_ERROR:
+ return "Transmission lamp error";
+ case DTP41_INVALID_COMMAND_ERROR:
+ return "Invalid command";
+ default:
+ return "Unknown error code";
+ }
+}
+
+/* Convert a machine specific error code into an abstract dtp code */
+static inst_code
+dtp41_interp_code(inst *pp, int ec) {
+// dtp41 *p = (dtp41 *)pp;
+
+ ec &= inst_imask;
+ switch (ec) {
+
+ case DTP41_OK:
+ return inst_ok;
+
+ case DTP41_INTERNAL_ERROR:
+ return inst_internal_error | ec;
+
+ case DTP41_COMS_FAIL:
+ return inst_coms_fail | ec;
+
+ case DTP41_UNKNOWN_MODEL:
+ return inst_unknown_model | ec;
+
+ case DTP41_DATA_PARSE_ERROR:
+ return inst_protocol_error | ec;
+
+ case DTP41_BUSY:
+ case DTP41_TIMEOUT:
+ case DTP41_BAD_READING:
+ case DTP41_INSTRUMENT_ERROR:
+ case DTP41_DRIVE_MOTOR_ERROR:
+ case DTP41_ADC_ERROR:
+ case DTP41_TRAN_LAMP_ERROR:
+ return inst_misread | ec;
+
+ case DTP41_NEEDS_CAL_ERROR:
+ return inst_needs_cal | ec;
+ }
+ return inst_other_error | ec;
+}
+
+/* Destroy ourselves */
+static void
+dtp41_del(inst *pp) {
+ dtp41 *p = (dtp41 *)pp;
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ free (p);
+}
+
+/* Interogate the device to discover its capabilities */
+/* If we haven't initilalised the instrument, we don't */
+/* know if it supports transparency. */
+static void discover_capabilities(dtp41 *p) {
+ static char buf[MAX_MES_SIZE];
+ inst_code rv = inst_ok;
+
+ p->cap = inst_mode_ref_spot
+ | inst_mode_ref_strip
+ | inst_mode_colorimeter
+ | inst_mode_spectral
+ ;
+
+ p->cap2 = inst2_prog_trig
+ | inst2_user_trig
+ | inst2_user_switch_trig
+ ;
+
+ p->cap3 = inst3_none;
+
+ if (p->inited) {
+ /* Check whether we have transmission capability */
+ if ((rv = dtp41_command(p, "0119CF\r", buf, MAX_MES_SIZE, 1.5)) == inst_ok) {
+ p->cap |= inst_mode_trans_spot
+ | inst_mode_trans_strip
+ ;
+ }
+ /* Set back to reflectance mode */
+ rv = dtp41_command(p, "0019CF\r", buf, MAX_MES_SIZE, 1.5);
+ }
+}
+
+/* Return the instrument capabilities */
+void dtp41_capabilities(inst *pp,
+inst_mode *pcap1,
+inst2_capability *pcap2,
+inst3_capability *pcap3) {
+ dtp41 *p = (dtp41 *)pp;
+
+ if (p->cap == inst_mode_none)
+ discover_capabilities(p);
+
+ if (pcap1 != NULL)
+ *pcap1 = p->cap;
+ if (pcap2 != NULL)
+ *pcap2 = p->cap2;
+ if (pcap3 != NULL)
+ *pcap3 = p->cap3;
+}
+
+/* Activate the last set mode */
+static inst_code
+activate_mode(dtp41 *p)
+{
+ static char buf[MAX_MES_SIZE];
+ inst_code rv;
+
+ /* Setup for transmission or reflection */
+ if ((p->lastmode & inst_mode_illum_mask) == inst_mode_reflection
+ && (p->mode & inst_mode_illum_mask) != inst_mode_reflection) {
+ if ((rv = dtp41_command(p, "0019CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return rv;
+ }
+ if ((p->lastmode & inst_mode_illum_mask) == inst_mode_transmission
+ && (p->mode & inst_mode_illum_mask) != inst_mode_transmission) {
+ if ((rv = dtp41_command(p, "0119CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return rv;
+ }
+
+ /* Setup for static or dynamic reading */
+ if ((p->lastmode & inst_mode_sub_mask) == inst_mode_spot
+ && (p->mode & inst_mode_sub_mask) != inst_mode_spot) {
+ if ((rv = dtp41_command(p, "0013CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return rv;
+ }
+ if ((p->lastmode & inst_mode_sub_mask) == inst_mode_strip
+ && (p->mode & inst_mode_sub_mask) != inst_mode_strip) {
+ if ((rv = dtp41_command(p, "0113CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return rv;
+ }
+
+ p->mode = p->lastmode;
+
+ return inst_ok;
+}
+
+/*
+ * check measurement mode
+ * We assume that the instrument has been initialised.
+ */
+static inst_code
+dtp41_check_mode(inst *pp, inst_mode m) {
+ dtp41 *p = (dtp41 *)pp;
+ inst_mode cap;
+
+ pp->capabilities(pp, &cap, NULL, NULL);
+
+ if (m & ~cap) /* Simple elimination test */
+ return inst_unsupported;
+
+ /* Check specific modes */
+ if (!IMODETST(m, inst_mode_ref_spot)
+ && !IMODETST(m, inst_mode_ref_strip)
+ && !IMODETST2(cap, m, inst_mode_trans_spot)
+ && !IMODETST2(cap, m, inst_mode_trans_strip)) {
+ return inst_unsupported;
+ }
+
+ return inst_ok;
+}
+
+/*
+ * set measurement mode
+ * We assume that the instrument has been initialised.
+ */
+static inst_code
+dtp41_set_mode(inst *pp, inst_mode m) {
+ dtp41 *p = (dtp41 *)pp;
+ inst_code ev;
+
+ if ((ev = dtp41_check_mode(pp, m)) != inst_ok)
+ return ev;
+
+ p->lastmode = m;
+ if (p->lastmode != p->mode) {
+ return activate_mode(p);
+ }
+
+ return inst_ok;
+}
+
+/* !! It's not clear if there is a way of knowing */
+/* whether the instrument has a UV filter. */
+
+/*
+ * set or reset an optional mode
+ *
+ * Some options talk to the instrument, and these will
+ * error if it hasn't been initialised.
+ */
+static inst_code
+dtp41_get_set_opt(inst *pp, inst_opt_type m, ...)
+{
+ dtp41 *p = (dtp41 *)pp;
+ inst_code rv = inst_ok;
+ static char buf[MAX_MES_SIZE];
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* Set the trigger mode */
+ if (m == inst_opt_trig_prog
+ || m == inst_opt_trig_user
+ || m == inst_opt_trig_user_switch) {
+ p->trig = m;
+
+ if (m == inst_opt_trig_user_switch) {
+ /* Enable the read microswitch */
+ if ((rv = dtp41_command(p, "01PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return rv;
+ } else {
+ /* Disable the read microswitch */
+ if ((rv = dtp41_command(p, "00PB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return rv;
+ }
+ return inst_ok;
+ }
+
+ return inst_unsupported;
+}
+
+/* Constructor */
+extern dtp41 *new_dtp41(icoms *icom, instType itype) {
+ dtp41 *p;
+ if ((p = (dtp41 *)calloc(sizeof(dtp41),1)) == NULL) {
+ a1loge(icom->log, 1, "new_dtp41: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log(icom->log, 0, 0, NULL, NULL, NULL, NULL);
+
+ p->init_coms = dtp41_init_coms;
+ p->init_inst = dtp41_init_inst;
+ p->capabilities = dtp41_capabilities;
+ p->check_mode = dtp41_check_mode;
+ p->set_mode = dtp41_set_mode;
+ p->get_set_opt = dtp41_get_set_opt;
+ p->read_strip = dtp41_read_strip;
+ p->read_sample = dtp41_read_sample;
+ p->get_n_a_cals = dtp41_get_n_a_cals;
+ p->calibrate = dtp41_calibrate;
+ p->interp_error = dtp41_interp_error;
+ p->del = dtp41_del;
+
+ p->icom = icom;
+ p->itype = icom->itype;
+ p->cap = inst_mode_none; /* Unknown until set */
+ p->mode = inst_mode_none; /* Not in a known mode yet */
+ p->nstaticr = 5; /* Number of static readings */
+
+ return p;
+}
diff --git a/spectro/dtp41.h b/spectro/dtp41.h
new file mode 100644
index 0000000..529f94c
--- /dev/null
+++ b/spectro/dtp41.h
@@ -0,0 +1,110 @@
+#ifndef DTP41_H
+
+/*
+ * Argyll Color Correction System
+ *
+ * Xrite DTP41 related defines
+ *
+ * Author: Graeme W. Gill
+ * Date: 10/3/2001
+ *
+ * Copyright 1996 - 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.
+ *
+ * Derived from DTP51.h
+ */
+
+/*
+ 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.
+ */
+
+#include "inst.h"
+
+/* Fake Error codes */
+#define DTP41_INTERNAL_ERROR 0x61 /* Internal software error */
+#define DTP41_COMS_FAIL 0x62 /* Communication failure */
+#define DTP41_UNKNOWN_MODEL 0x63 /* Not a DPT51 or DTP52 */
+#define DTP41_DATA_PARSE_ERROR 0x64 /* Read data parsing error */
+
+/* Real error code */
+#define DTP41_OK 0x00
+
+#define DTP41_MEASUREMENT_STATUS 0x01
+#define DTP41_CALIBRATION_STATUS 0x02
+#define DTP41_KEYPRESS_STATUS 0x03
+#define DTP41_DEFAULTS_LOADED_STATUS 0x04
+
+#define DTP41_BAD_COMMAND 0x11
+#define DTP41_BAD_PARAMETERS 0x12
+#define DTP41_PRM_RANGE_ERROR 0x13
+#define DTP41_BUSY 0x14
+
+#define DTP41_MEASUREMENT_ERROR 0x20
+#define DTP41_TIMEOUT 0x21
+#define DTP41_BAD_STRIP 0x22
+#define DTP41_BAD_COLOR 0x23
+#define DTP41_BAD_STEP 0x24
+#define DTP41_BAD_PASS 0x25
+#define DTP41_BAD_PATCHES 0x26
+#define DTP41_BAD_READING 0x27
+#define DTP41_NEEDS_CAL_ERROR 0x28
+#define DTP41_CAL_FAILURE_ERROR 0x29
+
+#define DTP41_INSTRUMENT_ERROR 0x30
+#define DTP41_LAMP_ERROR 0x31
+#define DTP41_FILTER_ERROR 0x32
+#define DTP41_FILTER_MOTOR_ERROR 0x33
+#define DTP41_DRIVE_MOTOR_ERROR 0x34
+#define DTP41_KEYPAD_ERROR 0x35
+#define DTP41_DISPLAY_ERROR 0x36
+#define DTP41_MEMORY_ERROR 0x37
+#define DTP41_ADC_ERROR 0x38
+#define DTP41_PROCESSOR_ERROR 0x39
+#define DTP41_BATTERY_ERROR 0x3A
+#define DTP41_BATTERY_LOW_ERROR 0x3B
+#define DTP41_INPUT_POWER_ERROR 0x3C
+#define DTP41_TEMPERATURE_ERROR 0x3D
+#define DTP41_BATTERY_ABSENT_ERROR 0x3E
+#define DTP41_TRAN_LAMP_ERROR 0x3F
+#define DTP41_INVALID_COMMAND_ERROR 0x40
+
+/* DTP41 communication object */
+struct _dtp41 {
+ /* **** base instrument class **** */
+ INST_OBJ_BASE
+
+ /* *** DTP41 private data **** */
+ inst_mode cap; /* Instrument mode capability */
+ inst2_capability cap2; /* Instrument capability */
+ inst3_capability cap3; /* Instrument capability 3 */
+ inst_mode mode; /* Currently instrument mode */
+ inst_mode lastmode; /* Last requested mode */
+
+ int nstaticr; /* Number of static readings */
+ int need_cal; /* needs calibration */
+ inst_opt_type trig; /* Reading trigger mode */
+
+ }; typedef struct _dtp41 dtp41;
+
+/* Constructor */
+extern dtp41 *new_dtp41(icoms *icom, instType itype);
+
+#define DTP41_H
+#endif /* DTP41_H */
diff --git a/spectro/dtp51.c b/spectro/dtp51.c
new file mode 100644
index 0000000..7e49f3e
--- /dev/null
+++ b/spectro/dtp51.c
@@ -0,0 +1,898 @@
+/*
+ * Argyll Color Correction System
+ *
+ * Xrite DTP51 related functions
+ *
+ * Author: Graeme W. Gill
+ * Date: 5/10/96
+ *
+ * Copyright 1996 - 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:
+
+ Recovery from needing calibration may not work properly ?
+
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* !SALONEINSTLIB */
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* !SALONEINSTLIB */
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "dtp51.h"
+
+#undef DEBUG
+
+/* Default flow control */
+#define DEFFC fc_Hardware
+
+static inst_code dtp51_interp_code(inst *pp, int ec);
+
+#define MAX_MES_SIZE 500 /* Maximum normal message reply size */
+#define MAX_RD_SIZE 5000 /* Maximum reading messagle reply size */
+
+/* Extract an error code from a reply string */
+/* Return -1 if no error code can be found */
+static int
+extract_ec(char *s) {
+ char *p;
+ char tt[3];
+ int rv;
+ p = s + strlen(s);
+ /* Find the trailing '>' */
+ for (p--; p >= s;p--) {
+ if (*p == '>')
+ break;
+ }
+ if ( (p-3) < s
+ || p[0] != '>'
+ || p[-3] != '<')
+ return -1;
+ tt[0] = p[-2];
+ tt[1] = p[-1];
+ tt[2] = '\000';
+ if (sscanf(tt,"%x",&rv) != 1)
+ return -1;
+ return rv & 0x7f; /* Mask out MSB */
+}
+
+/* Interpret an icoms error into a DTP51 error */
+static int icoms2dtp51_err(int se) {
+ if (se != ICOM_OK) {
+ if (se & ICOM_TO)
+ return DTP51_TIMEOUT;
+ return DTP51_COMS_FAIL;
+ }
+ return DTP51_OK;
+}
+
+/* Do a full featured command/response echange with the dtp51 */
+/* Return the dtp error code. End on the specified number */
+/* of specified characters, or expiry if the specified timeout */
+/* Assume standard error code if tc = '>' and ntc = 1 */
+/* Return a DTP51 error code */
+static int
+dtp51_fcommand(
+ struct _dtp51 *p,
+ char *in, /* In string */
+ char *out, /* Out string buffer */
+ int bsize, /* Out buffer size */
+ char tc, /* Terminating character */
+ int ntc, /* Number of terminating characters */
+ double to) { /* Timout in seconts */
+ int rv, se;
+
+ if ((se = p->icom->write_read(p->icom, in, out, bsize, tc, ntc, to)) != 0) {
+ a1logd(p->log, 1, "dtp51_fcommand: serial i/o failure on write_read '%s'\n",icoms_fix(in));
+ return icoms2dtp51_err(se);
+ }
+ rv = DTP51_OK;
+ if (tc == '>' && ntc == 1) {
+ rv = extract_ec(out);
+ if (rv > 0) {
+ rv &= inst_imask;
+ if (rv != DTP51_OK) { /* Clear the error */
+ char buf[MAX_MES_SIZE];
+ p->icom->write_read(p->icom, "CE\r", buf, MAX_MES_SIZE, '>', 1, 0.5);
+ }
+ }
+ }
+ a1logd(p->log, 4, "dtp51_fcommand: command '%s' returned '%s', value 0x%x\n",
+ icoms_fix(in), icoms_fix(out),rv);
+ return rv;
+}
+
+/* Do a standard command/response echange with the dtp51 */
+/* Return the dtp error code */
+static inst_code
+dtp51_command(dtp51 *p, char *in, char *out, int bsize, double to) {
+ int rv = dtp51_fcommand(p, in, out, bsize, '>', 1, to);
+ return dtp51_interp_code((inst *)p, rv);
+}
+
+/* Just read a response from the dtp51 */
+/* Return the dtp error code. */
+static int
+dtp51_read(
+struct _dtp51 *p,
+char *out, /* Out string buffer */
+int bsize, /* Out buffer size */
+double to) { /* Timout in seconts */
+ char tc = '>'; /* Terminating character */
+ int ntc = 1; /* Number of terminating characters */
+ int rv, se;
+
+ if ((se = p->icom->read(p->icom, out, bsize, tc, ntc, to)) != 0) {
+ a1logd(p->log, 1, "dtp51_fcommand: serial i/o failure on read\n");
+ return icoms2dtp51_err(se);
+ }
+ rv = DTP51_OK;
+ if (tc == '>' && ntc == 1) {
+ rv = extract_ec(out);
+ if (rv > 0) {
+ rv &= inst_imask;
+ if (rv != DTP51_OK) { /* Clear the error */
+ char buf[MAX_MES_SIZE];
+ p->icom->write_read(p->icom, "CE\r", buf, MAX_MES_SIZE, '>', 1, 0.5);
+ }
+ }
+ }
+ a1logd(p->log, 4, "dtp51_read: returned '%s', value 0x%x\n", icoms_fix(out),rv);
+
+ return rv;
+}
+
+/* Establish communications with a DTP51 */
+/* Use the baud rate given, and timeout in to secs */
+/* Return DTP_COMS_FAIL on failure to establish communications */
+static inst_code
+dtp51_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ dtp51 *p = (dtp51 *)pp;
+ static char buf[MAX_MES_SIZE];
+ baud_rate brt[5] = { baud_9600, baud_19200, baud_4800, baud_2400, baud_1200 };
+ char *brc[5] = { "30BR\r", "60BR\r", "18BR\r", "0CBR\r", "06BR\r" };
+ char *fcc;
+ unsigned int etime;
+ int ci, bi, i, se;
+ inst_code ev = inst_ok;
+
+ a1logd(p->log, 2, "dtp51_init_coms: About to init Serial I/O\n");
+
+ /* Deal with flow control setting */
+ if (fc == fc_nc)
+ fc = DEFFC;
+ if (fc == fc_XonXOff) {
+ fcc = "0304CF\r";
+ } else if (fc == fc_Hardware) {
+ fcc = "0104CF\r";
+ } else {
+ fc = fc_none;
+ fcc = "0004CF\r";
+ }
+
+ /* Figure DTP51 baud rate being asked for */
+ for (bi = 0; bi < 5; bi++) {
+ if (brt[bi] == br)
+ break;
+ }
+ if (bi >= 5)
+ bi = 0;
+
+ /* Figure current icoms baud rate */
+ for (ci = 0; ci < 5; ci++) {
+ if (brt[ci] == p->icom->br)
+ break;
+ }
+ if (ci >= 5)
+ ci = bi;
+
+ /* The tick to give up on */
+ etime = msec_time() + (long)(1000.0 * tout + 0.5);
+
+ while (msec_time() < etime) {
+
+ /* Until we time out, find the correct baud rate */
+ for (i = ci; msec_time() < etime;) {
+ if ((se = p->icom->set_ser_port(p->icom, fc_none, brt[i], parity_none,
+ stop_1, length_8)) != ICOM_OK) {
+ a1logd(p->log, 1, "dtp51_init_coms: set_ser_port failed ICOM err 0x%x\n",se);
+ return dtp51_interp_code((inst *)p, icoms2dtp51_err(se));
+ }
+
+ if (((ev = dtp51_command(p, "\r", buf, MAX_MES_SIZE, 0.5)) & inst_mask)
+ != inst_coms_fail)
+ break; /* We've got coms */
+
+ /* Check for user abort */
+ if (p->uicallback != NULL) {
+ inst_code ev;
+ if ((ev = p->uicallback(p->uic_cntx, inst_negcoms)) == inst_user_abort) {
+ a1logd(p->log, 1, "dtp22_init_coms: user aborted\n");
+ return inst_user_abort;
+ }
+ }
+ if (++i >= 5)
+ i = 0;
+ }
+ break; /* Got coms */
+ }
+
+ if (msec_time() >= etime) { /* We haven't established comms */
+ return inst_coms_fail;
+ }
+
+ /* Set the handshaking */
+ if ((ev = dtp51_command(p, fcc, buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Change the baud rate to the rate we've been told */
+ if ((se = p->icom->write_read(p->icom, brc[bi], buf, MAX_MES_SIZE, '>', 1, 1.5)) != 0) {
+ if (extract_ec(buf) != DTP51_OK)
+ return inst_coms_fail;
+ }
+
+ /* Configure our baud rate and handshaking as well */
+ if ((se = p->icom->set_ser_port(p->icom, fc, brt[bi], parity_none, stop_1, length_8))
+ != ICOM_OK) {
+ a1logd(p->log, 1, "dtp51_init_coms: set_ser_port failed ICOM err 0x%x\n",se);
+ return dtp51_interp_code((inst *)p, icoms2dtp51_err(se));
+ }
+
+ /* Loose a character (not sure why) */
+ p->icom->write_read(p->icom, "\r", buf, MAX_MES_SIZE, '>', 1, 0.5);
+
+ /* Check instrument is responding */
+ if ((ev = dtp51_command(p, "\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return inst_coms_fail;
+
+ if (p->log->verb) {
+ int i, j;
+ if ((ev = dtp51_command(p, "GI\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok
+ && (ev & inst_imask) != DTP51_BAD_COMMAND)
+ return ev;
+
+ if ((ev & inst_imask) == DTP51_BAD_COMMAND) { /* Some fimware doesn't support GI */
+ a1logv(p->log, 1, "dtp51: Firware doesn't support GI command\n");
+ } else {
+ for (j = i = 0; ;i++) {
+ if (buf[i] == '<' || buf[i] == '\000')
+ break;
+ if (buf[i] == '\r') {
+ buf[i] = '\000';
+ a1logv(p->log, 1, " %s\n",&buf[j]);
+ if (buf[i+1] == '\n')
+ i++;
+ j = i+1;
+ }
+ }
+ }
+ }
+
+ a1logd(p->log, 4, "dtp51: Got coms OK\n");
+ p->gotcoms = 1;
+ return inst_ok;
+}
+
+/* Build a strip definition as a set of passes */
+static void
+build_strip(
+char *tp, /* pointer to string buffer */
+char *name, /* Strip name (7 chars) */
+int npatch, /* Number of patches in the pass */
+char *pname, /* Pass name (3 chars) */
+int sguide) { /* Guide number */
+ int i;
+
+ /* Per strip */
+ for (i = 0; i < 7 && *name != '\000'; i++)
+ *tp++ = *name++; /* Strip name padded to 7 characters */
+ for (; i < 7; i++)
+ *tp++ = ' ';
+ *tp++ = '1'; *tp++ = '0'; *tp++ = '0'; *tp++ = '0'; /* N factor */
+ *tp++ = 'F'; /* Forward order */
+ *tp++ = '1'; /* No min/max transmitted */
+ *tp++ = '0'; /* No absolute data, no extra steps */
+ *tp++ = '0'; *tp++ = '0'; *tp++ = '0'; *tp++ = '0'; *tp++ = '0'; /* Reserved */
+
+ /* Per pass */
+ for (i = 0; i < 3 && *pname != '\000'; i++)
+ *tp++ = *pname++; /* Pass name padded to 3 characters */
+ for (; i < 3; i++)
+ *tp++ = ' ';
+ /* *tp++ = '4'; */ /* Lab data */
+ *tp++ = '5'; /* XYZ data */
+ *tp++ = '8'; /* Auto color */
+ *tp++ = '0' + (char)(npatch/10); /* Number of patches MS */
+ *tp++ = '0' + (char)(npatch%10); /* Number of patches LS */
+ *tp++ = '0' + (char)(sguide/10); /* Guide location MS */
+ *tp++ = '0' + (char)(sguide%10); /* Guide location LS */
+ *tp++ = '0'; /* (Data output type) */
+ *tp++ = '0'; /* Extra steps */
+ *tp++ = '0'; /* Reserved */
+
+ *tp++ = '\r'; /* The CR */
+ *tp++ = '\000'; /* The end */
+}
+
+/* Initialise the DTP51. The spectral flag is ignored. */
+/* return non-zero on an error, with dtp error code */
+static inst_code
+dtp51_init_inst(inst *pp) {
+ dtp51 *p = (dtp51 *)pp;
+ static char tbuf[100], buf[MAX_MES_SIZE];
+ int rv;
+ inst_code ev = inst_ok;
+
+ a1logd(p->log, 2, "dtp51_init_inst: called\n");
+
+ if (p->gotcoms == 0)
+ return inst_internal_error; /* Must establish coms before calling init */
+
+ /* Reset it */
+ if ((ev = dtp51_command(p, "0PR\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+ sleep(2); /* Let it recover from reset */
+
+ /* Turn echoing of characters off */
+ if ((ev = dtp51_command(p, "EC\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Get the model and version number */
+ if ((ev = dtp51_command(p, "SV\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Check that it is a DTP51 or 52 */
+ if ( strlen(buf) < 12
+ || strncmp(buf,"X-Rite DTP5",11) != 0
+ || (buf[11] != '1' && buf[11] != '2'))
+ return inst_unknown_model;
+
+ /* Set the A/D Conversion rate to normal */
+ if ((ev = dtp51_command(p, "00AD\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Disable Bar code reading to save time */
+ if ((ev = dtp51_command(p, "0BC\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set the Calibration Check tolerance to default of 0.15D */
+ if ((ev = dtp51_command(p, "0FCC\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set Automatic Transmit off */
+ if ((ev = dtp51_command(p, "0005CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set Read Status on */
+ if ((ev = dtp51_command(p, "1RS\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set decimal pont on */
+ if ((ev = dtp51_command(p, "0106CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set color data separator to TAB */
+ if ((ev = dtp51_command(p, "0207CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set default strip format to off */
+ if ((ev = dtp51_command(p, "0009CF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set extra digit resolution */
+ if ((ev = dtp51_command(p, "010ACF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set data after pass to off */
+ if ((ev = dtp51_command(p, "000BCF\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+#ifdef NEVER /* Doesn't seem to work on DTP51 */
+ /* Set the patch detection window to default HRW = 3, LRW 0.122% */
+ if ((ev = dtp51_command(p, "2CW\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+#endif
+
+ /* Enable the LCD display */
+ if ((ev = dtp51_command(p, "EL\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Enable the read microswitch */
+ if ((ev = dtp51_command(p, "1SM\r", buf, MAX_MES_SIZE, 1.5)) != inst_ok)
+ return ev;
+
+ /* Set a strip length of 1, to ensure parsing is invalidated */
+ build_strip(tbuf, " ", 1, " ", 30);
+
+ if ((rv = dtp51_fcommand(p, "0105DS\r", buf, MAX_MES_SIZE, '*', 1, 0.5)) != DTP51_OK)
+ return dtp51_interp_code(pp, rv);
+
+ /* Expect '*' as response */
+ if (buf[0] != '*' || buf[1] != '\000')
+ return inst_coms_fail;
+
+ /* Send strip definition */
+ if ((ev = dtp51_command(p, tbuf, buf, MAX_MES_SIZE, 4.0)) != inst_ok)
+ return ev;
+
+ /* This is the only mode supported */
+ p->trig = inst_opt_trig_switch;
+
+ p->inited = 1;
+ a1logd(p->log, 2, "dtp51_init_inst: instrument inited OK\n");
+
+ return inst_ok;
+}
+
+/* Read a set of strips */
+/* Return the dtp error code */
+static inst_code
+dtp51_read_strip(
+inst *pp,
+char *name, /* Strip name (up to first 7 chars used) */
+int npatch, /* Number of patches in the pass */
+char *pname, /* Pass name (up to first 3 chars used) */
+int sguide, /* Guide number */
+double pwid, /* Patch length in mm (For DTP41) */
+double gwid, /* Gap length in mm (For DTP41) */
+double twid, /* Trailer length in mm (For DTP41T) */
+ipatch *vals) { /* Pointer to array of instrument patch values */
+ dtp51 *p = (dtp51 *)pp;
+ char tbuf[200], *tp;
+ static char buf[MAX_RD_SIZE];
+ int i, rv;
+ inst_code ev = inst_ok;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ build_strip(tbuf, name, npatch, pname, sguide);
+
+ if ((rv = dtp51_fcommand(p, "0105DS\r", buf, MAX_RD_SIZE, '*', 1, 0.5)) != DTP51_OK)
+ return dtp51_interp_code(pp, rv);
+
+ /* Expect '*' as response */
+ if (buf[0] != '*' || buf[1] != '\000')
+ return inst_coms_fail;
+
+ /* Send strip definition */
+ if ((ev = dtp51_command(p, tbuf, buf, MAX_RD_SIZE, 4.0)) != inst_ok)
+ return ev;
+
+ /* Do a strip read */
+ if ((ev = dtp51_command(p, "5SW\r", buf, MAX_RD_SIZE, 1.5)) != inst_ok) {
+ if ((ev & inst_mask) == inst_needs_cal)
+ p->need_cal = 1;
+ return ev;
+ }
+
+ /* Wait for the Read status, or a user abort (no user trigger!) */
+ for (;;) {
+ if ((ev = dtp51_read(p, buf, MAX_RD_SIZE, 0.5)) != inst_ok) {
+ if ((ev & inst_mask) == inst_needs_cal)
+ p->need_cal = 1;
+
+ if ((ev & inst_imask) == DTP51_TIMEOUT) { /* Timed out */
+ if (p->uicallback != NULL) { /* Check for user trigger */
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_abort) {
+ return ev; /* User abort */
+ }
+ }
+ } else
+ return ev; /* Instrument or comms error */
+
+ } else {
+ break; /* Switch activated */
+ }
+ }
+
+ if (p->uicallback) /* Notify of trigger */
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ /* Gather the results */
+ if ((ev = dtp51_command(p, "TS\r", buf, MAX_RD_SIZE, 0.5 + npatch * 0.1)) != inst_ok)
+ return ev;
+
+ /* Parse the buffer */
+ /* Replace '\r' with '\000' */
+ for (tp = buf; *tp != '\000'; tp++) {
+ if (*tp == '\r')
+ *tp = '\000';
+ }
+ for (tp = buf, i = 0; i < npatch; i++) {
+ if (*tp == '\000')
+ return inst_protocol_error;
+ if (sscanf(tp, " X %lf Y %lf Z %lf ",
+ &vals[i].XYZ[0], &vals[i].XYZ[1], &vals[i].XYZ[2]) != 3) {
+ if (sscanf(tp, " x %lf y %lf z %lf ",
+ &vals[i].XYZ[0], &vals[i].XYZ[1], &vals[i].XYZ[2]) != 3) {
+ return inst_protocol_error;
+ }
+ }
+ vals[i].mtype = inst_mrt_reflective;
+ vals[i].XYZ_v = 1;
+ vals[i].sp.spec_n = 0;
+ vals[i].duration = 0.0;
+ tp += strlen(tp) + 1;
+ }
+
+#ifdef NEVER
+ /* Disable the read microswitch */
+ if ((ev = dtp51_command(p, "0SM\r", buf, MAX_RD_SIZE)) != inst_ok)
+ return ev;
+#endif
+
+ return inst_ok;
+}
+
+/* Return needed and available inst_cal_type's */
+static inst_code dtp51_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
+ dtp51 *p = (dtp51 *)pp;
+ inst_cal_type n_cals = inst_calt_none;
+ inst_cal_type a_cals = inst_calt_none;
+
+ if (p->need_cal)
+ n_cals |= inst_calt_ref_white;
+ a_cals |= inst_calt_ref_white;
+
+ if (pn_cals != NULL)
+ *pn_cals = n_cals;
+
+ if (pa_cals != NULL)
+ *pa_cals = a_cals;
+
+ return inst_ok;
+}
+
+/* Request an instrument calibration. */
+/* This is used if the user decides they want to do a calibration, */
+/* in anticipation of a calibration (needs_calibration()) to avoid */
+/* requiring one during measurement, or in response to measuring */
+/* returning inst_needs_cal. Initially us an inst_cal_cond of inst_calc_none, */
+/* and then be prepared to setup the right conditions, or ask the */
+/* user to do so, each time the error inst_cal_setup is returned. */
+/* NOTE :- we can trigger a calibration ! */
+/* Perhaps we should do so ??? */
+inst_code dtp51_calibrate(
+inst *pp,
+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) */
+) {
+ dtp51 *p = (dtp51 *)pp;
+ inst_code ev;
+ inst_cal_type needed, available;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ id[0] = '\000';
+
+ if ((ev = dtp51_get_n_a_cals((inst *)p, &needed, &available)) != inst_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,"dtp51_calibrate: doing calt 0x%x\n",calt);
+
+ if ((*calt & inst_calt_n_dfrble_mask) == 0) /* Nothing todo */
+ return inst_ok;
+ }
+
+ /* See if it's a calibration we understand */
+ if (*calt & ~available & inst_calt_all_mask) {
+ return inst_unsupported;
+ }
+
+ if (*calt & inst_calt_ref_white) {
+
+ if (*calc != inst_calc_uop_ref_white) {
+ *calc = inst_calc_uop_ref_white; /* Ask user to do calibration */
+ return inst_cal_setup;
+ }
+ p->need_cal = 0;
+ *calt &= ~inst_calt_ref_white;
+ }
+ return inst_ok;
+}
+
+/* Error codes interpretation */
+static char *
+dtp51_interp_error(inst *pp, int ec) {
+// dtp51 *p = (dtp51 *)pp;
+ ec &= inst_imask;
+ switch (ec) {
+ case DTP51_INTERNAL_ERROR:
+ return "Internal software error";
+ case DTP51_COMS_FAIL:
+ return "Communications failure";
+ case DTP51_UNKNOWN_MODEL:
+ return "Not a DTP51 or DTP52";
+ case DTP51_DATA_PARSE_ERROR:
+ return "Data from DTP didn't parse as expected";
+ case DTP51_OK:
+ return "No device error";
+ case DTP51_BAD_COMMAND:
+ return "Unrecognized command";
+ case DTP51_PRM_RANGE:
+ return "Command parameter out of range";
+ case DTP51_DISPLAY_OVERFLOW:
+ return "Display overflow";
+ case DTP51_MEMORY_OVERFLOW:
+ return "Memory bounds error";
+ case DTP51_INVALID_BAUD_RATE:
+ return "Invalid baud rate";
+ case DTP51_TIMEOUT:
+ return "Receive timeout";
+ case DTP51_INVALID_PASS:
+ return "Invalid pass";
+ case DTP51_INVALID_STEP:
+ return "Invalid step";
+ case DTP51_NO_DATA_AVAILABLE:
+ return "No data availble";
+ case DTP51_LAMP_MARGINAL:
+ return "Lamp marginal";
+ case DTP51_LAMP_FAILURE:
+ return "Lamp failure";
+ case DTP51_STRIP_RESTRAINED:
+ return "Strip was restrained";
+ case DTP51_BAD_CAL_STRIP:
+ return "Bad calibration strip";
+ case DTP51_MOTOR_ERROR:
+ return "Motor error";
+ case DTP51_BAD_BARCODE:
+ return "Bad barcode on cal strip";
+ case DTP51_INVALID_READING:
+ return "Invalid strip reading";
+ case DTP51_WRONG_COLOR:
+ return "Wrong color strip";
+ case DTP51_BATTERY_TOO_LOW:
+ return "Battery too low";
+ case DTP51_NEEDS_CALIBRATION:
+ return "Needs calibration";
+ case DTP51_COMP_TABLE_MISMATCH:
+ return "Compensation table mismatch";
+ case DTP51_BAD_COMP_TABLE:
+ return "Bad compensation table";
+ case DTP51_NO_VALID_DATA:
+ return "No valid data found";
+ case DTP51_BAD_PATCH:
+ return "Bad patch in strip";
+ case DTP51_BAD_STRING_LENGTH:
+ return "Bad strip def. string length";
+ case DTP51_BAD_CHARACTER:
+ return "Bad chareter";
+ case DTP51_BAD_MEAS_TYPE:
+ return "Bad measure type field";
+ case DTP51_BAD_COLOR:
+ return "Bad color field";
+ case DTP51_BAD_STEPS:
+ return "Bad step field";
+ case DTP51_BAD_STOP_LOCATION:
+ return "Bad guide stop field";
+ case DTP51_BAD_OUTPUT_TYPE:
+ return "Bad output type field";
+ case DTP51_MEMORY_ERROR:
+ return "Memory error (need AC supply)";
+ case DTP51_BAD_N_FACTOR:
+ return "Bad N factore";
+ case DTP51_STRIP_DOESNT_EXIST:
+ return "Strip doesn't exist";
+ case DTP51_BAD_MIN_MAX_VALUE:
+ return "Bad min/max field value";
+ case DTP51_BAD_SERIAL_NUMBER:
+ return "Bad serial number";
+ default:
+ return "Unknown error code";
+ }
+}
+
+
+/* Convert a machine specific error code into an abstract dtp code */
+static inst_code
+dtp51_interp_code(inst *pp, int ec) {
+// dtp51 *p = (dtp51 *)pp;
+
+ ec &= inst_imask;
+ switch (ec) {
+
+ case DTP51_OK:
+ return inst_ok;
+
+ case DTP51_INTERNAL_ERROR:
+ return inst_internal_error | ec;
+
+ case DTP51_COMS_FAIL:
+ return inst_coms_fail | ec;
+
+ case DTP51_UNKNOWN_MODEL:
+ return inst_unknown_model | ec;
+
+ case DTP51_DATA_PARSE_ERROR:
+ return inst_protocol_error | ec;
+
+ case DTP51_NO_DATA_AVAILABLE:
+ case DTP51_LAMP_MARGINAL:
+ case DTP51_LAMP_FAILURE:
+ case DTP51_STRIP_RESTRAINED:
+ case DTP51_MOTOR_ERROR:
+ case DTP51_INVALID_READING:
+ case DTP51_WRONG_COLOR:
+ case DTP51_BATTERY_TOO_LOW:
+ case DTP51_COMP_TABLE_MISMATCH:
+ case DTP51_BAD_COMP_TABLE:
+ case DTP51_NO_VALID_DATA:
+ case DTP51_BAD_PATCH:
+ return inst_misread | ec;
+
+ case DTP51_NEEDS_CALIBRATION:
+ return inst_needs_cal | ec;
+ }
+ return inst_other_error | ec;
+}
+
+/* Destroy ourselves */
+static void
+dtp51_del(inst *pp) {
+ dtp51 *p = (dtp51 *)pp;
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ free(p);
+}
+
+/* Return the instrument mode capabilities */
+void dtp51_capabilities(inst *pp,
+inst_mode *pcap1,
+inst2_capability *pcap2,
+inst3_capability *pcap3) {
+ inst_mode cap1= 0;
+ inst2_capability cap2 = 0;
+
+ cap1 |= inst_mode_ref_strip
+ | inst_mode_colorimeter
+ ;
+
+ cap2 |= inst2_switch_trig /* Can only be triggered by microswitch */
+ ;
+
+ if (pcap1 != NULL)
+ *pcap1 = cap1;
+ if (pcap2 != NULL)
+ *pcap2 = cap2;
+ if (pcap3 != NULL)
+ *pcap3 = inst3_none;
+}
+
+/* Check device measurement mode */
+inst_code dtp51_check_mode(inst *pp, inst_mode m) {
+ inst_mode cap;
+
+ if (!pp->gotcoms)
+ return inst_no_coms;
+ if (!pp->inited)
+ return inst_no_init;
+
+ pp->capabilities(pp, &cap, NULL, NULL);
+
+ /* Simple test */
+ if (m & ~cap)
+ return inst_unsupported;
+
+ /* only reflection strip measurement mode suported */
+ if (!IMODETST(m, inst_mode_ref_strip)) {
+ return inst_unsupported;
+ }
+
+ return inst_ok;
+}
+
+/* Set device measurement mode */
+inst_code dtp51_set_mode(inst *pp, inst_mode m) {
+ inst_code ev;
+
+ if ((ev = dtp51_check_mode(pp, m)) != inst_ok)
+ return ev;
+
+ return inst_ok;
+}
+
+/* !! It's not clear if there is a way of knowing */
+/* whether the instrument has a UV filter. */
+
+/*
+ * set or reset an optional mode
+ *
+ * Since there is no interaction with the instrument,
+ * was assume that all of these can be done before initialisation.
+ */
+static inst_code
+dtp51_get_set_opt(inst *pp, inst_opt_type m, ...)
+{
+ dtp51 *p = (dtp51 *)pp;
+ static char buf[MAX_MES_SIZE];
+
+ /* Record the trigger mode */
+ if (m == inst_opt_trig_switch) { /* Can only be triggered this way */
+ p->trig = m;
+ return inst_ok;
+ }
+
+ return inst_unsupported;
+}
+
+/* Constructor */
+extern dtp51 *new_dtp51(icoms *icom, instType itype) {
+ dtp51 *p;
+ if ((p = (dtp51 *)calloc(sizeof(dtp51),1)) == NULL) {
+ a1loge(icom->log, 1, "new_dtp51: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(icom->log);
+
+ p->init_coms = dtp51_init_coms;
+ p->init_inst = dtp51_init_inst;
+ p->capabilities = dtp51_capabilities;
+ p->check_mode = dtp51_check_mode;
+ p->set_mode = dtp51_set_mode;
+ p->get_set_opt = dtp51_get_set_opt;
+ p->read_strip = dtp51_read_strip;
+ p->get_n_a_cals = dtp51_get_n_a_cals;
+ p->calibrate = dtp51_calibrate;
+ p->interp_error = dtp51_interp_error;
+ p->del = dtp51_del;
+
+ p->icom = icom;
+ p->itype = icom->itype;
+
+ return p;
+}
diff --git a/spectro/dtp51.h b/spectro/dtp51.h
new file mode 100644
index 0000000..329c5d6
--- /dev/null
+++ b/spectro/dtp51.h
@@ -0,0 +1,102 @@
+#ifndef DTP51_H
+
+/*
+ * Argyll Color Correction System
+ *
+ * Xrite DTP51 related defines
+ *
+ * Author: Graeme W. Gill
+ * Date: 5/10/1996
+ *
+ * Copyright 1996 - 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.
+ */
+
+#include "inst.h"
+
+/* Fake Error codes */
+#define DTP51_INTERNAL_ERROR 0x61 /* Internal software error */
+#define DTP51_COMS_FAIL 0x62 /* Communication failure */
+#define DTP51_UNKNOWN_MODEL 0x63 /* Not a DPT51 or DTP52 */
+#define DTP51_DATA_PARSE_ERROR 0x64 /* Read data parsing error */
+
+/* Real error code */
+#define DTP51_OK 0x00
+
+#define DTP51_BAD_COMMAND 0x01
+#define DTP51_PRM_RANGE 0x02
+#define DTP51_DISPLAY_OVERFLOW 0x03
+#define DTP51_MEMORY_OVERFLOW 0x04
+#define DTP51_INVALID_BAUD_RATE 0x05
+#define DTP51_TIMEOUT 0x07
+#define DTP51_INVALID_PASS 0x09
+#define DTP51_INVALID_STEP 0x0A
+#define DTP51_NO_DATA_AVAILABLE 0x0B
+
+#define DTP51_LAMP_MARGINAL 0x10
+#define DTP51_LAMP_FAILURE 0x11
+#define DTP51_STRIP_RESTRAINED 0x12
+#define DTP51_BAD_CAL_STRIP 0x13
+#define DTP51_MOTOR_ERROR 0x14
+#define DTP51_BAD_BARCODE 0x15
+
+#define DTP51_INVALID_READING 0x20
+#define DTP51_WRONG_COLOR 0x21
+#define DTP51_BATTERY_TOO_LOW 0x22
+#define DTP51_NEEDS_CALIBRATION 0x23
+#define DTP51_COMP_TABLE_MISMATCH 0x24
+#define DTP51_BAD_COMP_TABLE 0x25
+#define DTP51_NO_VALID_DATA 0x26
+#define DTP51_BAD_PATCH 0x27
+
+#define DTP51_BAD_STRING_LENGTH 0x30
+#define DTP51_BAD_CHARACTER 0x31
+#define DTP51_BAD_MEAS_TYPE 0x32
+#define DTP51_BAD_COLOR 0x33
+#define DTP51_BAD_STEPS 0x34
+#define DTP51_BAD_STOP_LOCATION 0x35
+#define DTP51_BAD_OUTPUT_TYPE 0x36
+#define DTP51_MEMORY_ERROR 0x37
+#define DTP51_BAD_N_FACTOR 0x38
+#define DTP51_STRIP_DOESNT_EXIST 0x39
+#define DTP51_BAD_MIN_MAX_VALUE 0x3A
+
+#define DTP51_BAD_SERIAL_NUMBER 0x40
+
+/* DTP51 communication object */
+struct _dtp51 {
+ INST_OBJ_BASE
+
+ int need_cal; /* needs calibration */
+ inst_opt_type trig; /* Reading trigger mode */
+
+ }; typedef struct _dtp51 dtp51;
+
+/* Constructor */
+extern dtp51 *new_dtp51(icoms *icom, instType itype);
+
+
+
+#define DTP51_H
+#endif /* DTP51_H */
diff --git a/spectro/dtp92.c b/spectro/dtp92.c
new file mode 100644
index 0000000..441c0eb
--- /dev/null
+++ b/spectro/dtp92.c
@@ -0,0 +1,1325 @@
+/*
+ * Argyll Color Correction System
+ *
+ * Xrite DTP92/94 related functions
+ *
+ * Author: Graeme W. Gill
+ * Date: 5/10/1996
+ *
+ * Copyright 1996 - 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:
+ Should make DTP92 switch trigger a reading.
+
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* !SALONEINSTLIB */
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* !SALONEINSTLIB */
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "dtp92.h"
+
+/* Default flow control */
+#define DEFFC fc_none
+
+#define IGNORE_NEEDS_OFFSET_DRIFT_CAL_ERR
+
+static inst_code dtp92_interp_code(inst *pp, int ec);
+
+#define MAX_MES_SIZE 500 /* Maximum normal message reply size */
+#define MAX_RD_SIZE 5000 /* Maximum reading messagle reply size */
+
+/* Extract an error code from a reply string */
+/* Return -1 if no error code can be found */
+static int
+extract_ec(char *s) {
+ char *p;
+ char tt[3];
+ int rv;
+ p = s + strlen(s);
+ /* Find the trailing '>' */
+ for (p--; p >= s;p--) {
+ if (*p == '>')
+ break;
+ }
+ if ( (p-3) < s
+ || p[0] != '>'
+ || p[-3] != '<')
+ return -1;
+ tt[0] = p[-2];
+ tt[1] = p[-1];
+ tt[2] = '\000';
+ if (sscanf(tt,"%x",&rv) != 1)
+ return -1;
+ return rv;
+}
+
+/* Interpret an icoms error into a DTP92 error */
+static int icoms2dtp92_err(int se) {
+ if (se != ICOM_OK) {
+ if (se & ICOM_TO)
+ return DTP92_TIMEOUT;
+ return DTP92_COMS_FAIL;
+ }
+ return DTP92_OK;
+}
+
+/* Do a full featured command/response echange with the dtp92 */
+/* Return the dtp error code. End on the specified number */
+/* of specified characters, or expiry if the specified timeout */
+/* Assume standard error code if tc = '>' and ntc = 1 */
+/* Return a DTP92 error code */
+static int
+dtp92_fcommand(
+ struct _dtp92 *p,
+ char *in, /* In string */
+ char *out, /* Out string buffer */
+ int bsize, /* Out buffer size */
+ char tc, /* Terminating character */
+ int ntc, /* Number of terminating characters */
+ double to) { /* Timout in seconds */
+ int rv, se;
+
+ if ((se = p->icom->write_read(p->icom, in, out, bsize, tc, ntc, to)) != 0) {
+ a1logd(p->log, 1, "dtp92_fcommand: serial i/o failure on write_read '%s'\n",icoms_fix(in));
+ return icoms2dtp92_err(se);
+ }
+ rv = DTP92_OK;
+ if (tc == '>' && ntc == 1) {
+ rv = extract_ec(out);
+
+#ifdef NEVER
+ if (strcmp(in, "0PR\r") == 0)
+ rv = DTP92_NEEDS_OFFSET_DRIFT_CAL; /* Emulate 1B error */
+#endif /* NEVER */
+
+ if (rv > 0) {
+ rv &= inst_imask;
+ if (rv != DTP92_OK) { /* Clear the error */
+ char buf[MAX_MES_SIZE];
+ p->icom->write_read(p->icom, "CE\r", buf, MAX_MES_SIZE, '>', 1, 0.5);
+ }
+ }
+ }
+ a1logd(p->log, 4, "dtp92_fcommand: command '%s' returned '%s', value 0x%x\n",
+ icoms_fix(in), icoms_fix(out),rv);
+
+#ifdef IGNORE_NEEDS_OFFSET_DRIFT_CAL_ERR
+ if (strcmp(in, "0PR\r") == 0 && rv == DTP92_NEEDS_OFFSET_DRIFT_CAL) {
+ static int warned = 0;
+ if (!warned) {
+ a1logw(p->log,"dtp92: Got error NEEDS_OFFSET_DRIFT_CAL on instrument reset - being ignored.");
+ warned = 1;
+ }
+ rv = 0;
+ }
+#endif
+ return rv;
+}
+
+/* Do a standard command/response echange with the dtp92 */
+/* Return the dtp error code */
+static inst_code
+dtp92_command(dtp92 *p, char *in, char *out, int bsize, double to) {
+ int rv = dtp92_fcommand(p, in, out, bsize, '>', 1, to);
+ return dtp92_interp_code((inst *)p, rv);
+}
+
+/* Establish communications with a DTP92 */
+/* If it's a serial port, use the baud rate given, and timeout in to secs */
+/* Return DTP_COMS_FAIL on failure to establish communications */
+static inst_code
+dtp92_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ dtp92 *p = (dtp92 *) pp;
+ static char buf[MAX_MES_SIZE];
+ baud_rate brt[5] = { baud_9600, baud_19200, baud_4800, baud_2400, baud_1200 };
+ char *brc[5] = { "30BR\r", "60BR\r", "18BR\r", "0CBR\r", "06BR\r" };
+ char *fcc;
+ unsigned int etime;
+ instType itype = pp->itype;
+ int ci, bi, i, se;
+ inst_code ev = inst_ok;
+
+ if (p->icom->port_type(p->icom) == icomt_usb) {
+#ifdef ENABLE_USB
+
+ a1logd(p->log, 2, "dtp92_init_coms: About to init USB\n");
+
+ /* Note that the 92Q and 94 have a different */
+ /* arrangement of end points: */
+ /* */
+ /* 92Q 94 */
+ /* --- -- */
+ /* */
+ /* 0x81 i Bulk 0x81 i Intr. */
+ /* 0x01 o Bulk */
+ /* 0x82 i Intr. */
+ /* 0x02 o Bulk 0x02 o Intr. */
+ /* */
+ /* Set config, interface, write end point, read end point, read quanta */
+ if (itype == instDTP94)
+ se = p->icom->set_usb_port(p->icom, 1, 0x02, 0x81, icomuf_none, 0, NULL);
+ else
+ se = p->icom->set_usb_port(p->icom, 1, 0x01, 0x81, icomuf_none, 0, NULL);
+
+ if (se != ICOM_OK) {
+ a1logd(p->log, 1, "dtp92_init_coms: set_usb_port failed ICOM err 0x%x\n",se);
+ return dtp92_interp_code((inst *)p, icoms2dtp92_err(se));
+ }
+
+ /* Blind reset it twice - it seems to sometimes hang up */
+ /* otherwise under OSX */
+ dtp92_command(p, "0PR\r", buf, MAX_MES_SIZE, 0.5);
+ dtp92_command(p, "0PR\r", buf, MAX_MES_SIZE, 0.5);
+#else /* !ENABLE_USB */
+ a1logd(p->log, 1, "dtp20: Failed to find USB connection to instrument\n");
+ return inst_coms_fail;
+#endif /* !ENABLE_USB */
+
+ } else {
+
+#ifdef ENABLE_SERIAL
+ a1logd(p->log, 2, "dtp92_init_coms: About to init Serial I/O\n");
+
+ /* Deal with flow control setting */
+ if (fc == fc_nc)
+ fc = DEFFC;
+ if (fc == fc_XonXOff) {
+ fcc = "0304CF\r";
+ } else if (fc == fc_Hardware) {
+ fcc = "0104CF\r";
+ } else {
+ fc = fc_none;
+ fcc = "0004CF\r";
+ }
+
+ /* Figure DTP92 baud rate being asked for */
+ for (bi = 0; bi < 5; bi++) {
+ if (brt[bi] == br)
+ break;
+ }
+ if (bi >= 5)
+ bi = 0;
+
+ /* Figure current icoms baud rate */
+ for (ci = 0; ci < 5; ci++) {
+ if (brt[ci] == p->icom->br)
+ break;
+ }
+ if (ci >= 5)
+ ci = bi;
+
+ /* The tick to give up on */
+ etime = msec_time() + (long)(1000.0 * tout + 0.5);
+
+ while (msec_time() < etime) {
+
+ a1logd(p->log, 4, "dtp92_init_coms: Trying different baud rates (%u msec to go)\n",
+ etime - msec_time());
+
+ /* Until we time out, find the correct baud rate */
+ for (i = ci; msec_time() < etime;) {
+ if ((se = p->icom->set_ser_port(p->icom, fc_none, brt[i], parity_none,
+ stop_1, length_8)) != ICOM_OK) {
+ a1logd(p->log, 1, "dtp92_init_coms: set_ser_port failed ICOM err 0x%x\n",se);
+ return dtp92_interp_code((inst *)p, icoms2dtp92_err(se));
+ }
+
+ if (((ev = dtp92_command(p, "\r", buf, MAX_MES_SIZE, 0.5)) & inst_mask)
+ != inst_coms_fail)
+ break; /* We've got coms or user abort */
+
+ /* Check for user abort */
+ if (p->uicallback != NULL) {
+ inst_code ev;
+ if ((ev = p->uicallback(p->uic_cntx, inst_negcoms)) == inst_user_abort) {
+ a1logd(p->log, 1, "dtp92_init_coms: user aborted\n");
+ return inst_user_abort;
+ }
+ }
+
+ if (++i >= 5)
+ i = 0;
+ }
+ break; /* Got coms */
+ }
+
+ if (msec_time() >= etime) { /* We haven't established comms */
+ return inst_coms_fail;
+ }
+
+ /* Set the handshaking */
+ if ((ev = dtp92_command(p, fcc, buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Change the baud rate to the rate we've been told */
+ if ((se = p->icom->write_read(p->icom, brc[bi], buf, MAX_MES_SIZE, '>', 1, .2)) != 0) {
+ if (extract_ec(buf) != DTP92_OK)
+ return inst_coms_fail;
+ }
+
+ /* Configure our baud rate and handshaking as well */
+ if ((se = p->icom->set_ser_port(p->icom, fc, brt[bi], parity_none,
+ stop_1, length_8)) != ICOM_OK) {
+ a1logd(p->log, 1, "dtp92_init_coms: set_ser_port failed ICOM err 0x%x\n",se);
+ return dtp92_interp_code((inst *)p, icoms2dtp92_err(se));
+ }
+
+ /* Loose a character (not sure why) */
+ p->icom->write_read(p->icom, "\r", buf, MAX_MES_SIZE, '>', 1, 0.1);
+#else /* !ENABLE_SERIAL */
+ a1logd(p->log, 1, "dtp20: Failed to find serial connection to instrument\n");
+ return inst_coms_fail;
+#endif /* !ENABLE_SERIAL */
+ }
+
+ /* Check instrument is responding, and reset it again. */
+ if ((ev = dtp92_command(p, "\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok
+ || (ev = dtp92_command(p, "0PR\r", buf, MAX_MES_SIZE, 2.0)) != inst_ok) {
+
+ a1logd(p->log, 1, "dtp92_init_coms: failed with ICOM 0x%x\n",ev);
+
+#ifdef NEVER /* Experimental code for fixing 0x1B "Offset Drift invalid" error */
+ if ((ev & inst_mask) == inst_hardware_fail) {
+ /* Could be a checksum type error. Try resetting to factory settings */
+ warning("Got error %s (%s) on attempting to communicate with instrument.",
+ p->inst_interp_error((inst *)p, ev), p->interp_error((inst *)p, ev));
+ if ((ev & inst_imask) == DTP92_NEEDS_OFFSET_DRIFT_CAL) {
+ char tbuf[100];
+ double odv = 0.0;
+ printf("Got Offset Drift Cal error. Will try re-writing it\n");
+ if ((ev = dtp92_command(p, "SD\r", buf, MAX_MES_SIZE, 2.0)) != inst_ok)
+ error("Reading current offset drift value failed");
+ if (sscanf(buf, "%lf<", &odv) != 1)
+ error("Unable to parse offset drift value");
+ printf("Read current offset drift value of %f\n", odv);
+ if (odv > 0.5 || odv < 0.05)
+ error("offset drift value seems like nonsense - aborting");
+ sprintf(tbuf,"%05dSD\r",(int)(odv * 100000));
+ printf("Command about to be sent is '%s', OK ? (Y/N)\n",icoms_fix(tbuf));
+ if (getchar() == 'Y') {
+ if ((ev = dtp92_command(p, tbuf, buf, MAX_MES_SIZE, 6.0)) != inst_ok)
+ error("Writing offset drift value failed");
+ else
+ printf("Writing offset drift value suceeded!\n");
+ } else {
+ printf("No command written\n");
+ }
+ printf("Now try re-running program\n");
+ }
+ }
+#endif /* NEVER */
+
+ p->icom->del(p->icom); /* Since caller may not clean up */
+ p->icom = NULL;
+ return inst_coms_fail;
+ }
+
+ a1logd(p->log, 2, "dtp92_init_coms: init coms has suceeded\n");
+
+ p->gotcoms = 1;
+ return inst_ok;
+}
+
+static inst_code set_default_disp_type(dtp92 *p);
+
+/* Initialise the DTP92 */
+/* return non-zero on an error, with dtp error code */
+static inst_code
+dtp92_init_inst(inst *pp) {
+ dtp92 *p = (dtp92 *)pp;
+ static char buf[MAX_MES_SIZE];
+ inst_code ev = inst_ok;
+
+ a1logd(p->log, 2, "dtp92_init_inst: called\n");
+
+ if (p->gotcoms == 0)
+ return inst_internal_error; /* Must establish coms before calling init */
+
+ /* Reset it ( without disconnecting USB or resetting baud rate etc.) */
+ if ((ev = dtp92_command(p, "0PR\r", buf, MAX_MES_SIZE, 2.0)) != inst_ok)
+ return ev;
+
+ /* Get the model and version number */
+ if ((ev = dtp92_command(p, "SV\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Check that it is a DTP92, DTP92Q or DTP94 */
+ if ( strlen(buf) < 12
+ || (strncmp(buf,"X-Rite DTP92",12) != 0
+ && strncmp(buf,"X-Rite DTP94",12) != 0))
+ return inst_unknown_model;
+
+ if (strncmp(buf,"X-Rite DTP94",12) == 0)
+ p->itype = instDTP94;
+ else
+ p->itype = instDTP92;
+
+ if (p->itype == instDTP92) {
+ /* Turn echoing of characters off */
+ if ((ev = dtp92_command(p, "DEC\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ }
+
+ if (p->itype == instDTP92) {
+ /* Set decimal point on */
+ if ((ev = dtp92_command(p, "0106CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ }
+
+ /* Set color data separator to TAB */
+ if ((ev = dtp92_command(p, "0207CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Set delimeter to CR */
+ if ((ev = dtp92_command(p, "0008CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Set extra digit resolution (X10) */
+ if ((ev = dtp92_command(p, "010ACF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ if (p->itype == instDTP92) {
+ /* Set absolute (luminance) calibration */
+ if ((ev = dtp92_command(p, "0118CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ }
+
+ /* Set no black point subtraction */
+ if ((ev = dtp92_command(p, "0019CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Set to factory calibration */
+ if ((ev = dtp92_command(p, "EFC\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+
+ if (p->itype == instDTP94) {
+ /* Compensate for offset drift */
+ if ((ev = dtp92_command(p, "0117CF\r", buf, MAX_MES_SIZE, 0.5)) != inst_ok)
+ return ev;
+ }
+
+ if (p->itype == instDTP92) {
+ /* Enable ABS mode (in case firmware doesn't default to this after EFC) */
+ if ((ev = dtp92_command(p, "0118CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Disable -BLK mode (in case firmware doesn't default to this after EFC) */
+ if ((ev = dtp92_command(p, "0019CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Set to transmit just the colorimetric data */
+ if ((ev = dtp92_command(p, "0120CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Transmit colorimetric data as XYZ */
+ if ((ev = dtp92_command(p, "0221CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Disable Luminance group */
+ if ((ev = dtp92_command(p, "0022CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Disable Frequency group */
+ if ((ev = dtp92_command(p, "0023CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Disable color temperature group */
+ if ((ev = dtp92_command(p, "0024CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Disable RGB group */
+ if ((ev = dtp92_command(p, "0025CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Disable pushbutton */
+ if ((ev = dtp92_command(p, "DPB\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+
+ /* Set sample size to 16 (default is 16) */
+ if ((ev = dtp92_command(p, "10SS\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ }
+ p->trig = inst_opt_trig_user;
+
+ /* Set the default display type */
+ if ((ev = set_default_disp_type(p)) != inst_ok)
+ return ev;
+
+ /* ??? Need to set CTYP appropriately ??? */
+
+#ifdef NEVER /* Debug code */
+ if (p->itype == instDTP94) {
+ int i, val;
+ char tb[50];
+
+ for (i = 0; i < 0x800; i++) {
+ sprintf(tb,"%04xRN\r",i);
+ if ((ev = dtp92_command(p, tb, buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ if (sscanf(buf,"%x",&val) != 1)
+ return inst_coms_fail;
+ tb[0] = val;
+ tb[1] = '\000';
+ a1logd(p->log, 0, "Mem location 0x%04x = 0x%02x '%s'\n",i,val,icoms_fix(tb));
+ }
+ }
+#endif /* NEVER */
+
+ if (p->log->verb) {
+ int i, j;
+ if ((ev = dtp92_command(p, "GI\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok) {
+ a1logd(p->log, 1, "dtp20: GI command failed with ICOM err 0x%x\n",ev);
+ return ev;
+ }
+ for (j = i = 0; ;i++) {
+ if (buf[i] == '<' || buf[i] == '\000')
+ break;
+ if (buf[i] == '\r') {
+ buf[i] = '\000';
+ a1logv(p->log, 1, " %s\n",&buf[j]);
+ if (buf[i+1] == '\n')
+ i++;
+ j = i+1;
+ }
+ }
+ }
+
+ p->inited = 1;
+ a1logd(p->log, 2, "dtp92_init_inst: instrument inited OK\n");
+
+ return inst_ok;
+}
+
+/* The DTP92 seems to have a bug whereby it adds a spurious */
+/* digit after the 'Z' of the Z value. Try and discard this. */
+
+/* Read a single sample */
+/* Return the dtp error code */
+static inst_code
+dtp92_read_sample(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+ipatch *val, /* Pointer to instrument patch value */
+instClamping clamp) { /* NZ if clamp XYZ/Lab to be +ve */
+ dtp92 *p = (dtp92 *)pp;
+ char buf[MAX_RD_SIZE];
+ int tries;
+ int user_trig = 0;
+ inst_code rv = inst_protocol_error;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* Could change SS to suite level expected. */
+#ifdef NEVER
+ if (p->itype == instDTP92) {
+ /* Set sample size to 31 (default is 16) for low level readings */
+ if ((rv = dtp92_command(p, "1fSS\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return rv;
+ }
+#endif
+
+ if (p->trig == inst_opt_trig_user) {
+
+ if (p->uicallback == NULL) {
+ a1logd(p->log, 1, "dtp92: inst_opt_trig_user but no uicallback function set!\n");
+ return inst_unsupported;
+ }
+
+ for (;;) {
+ if ((rv = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (rv == inst_user_abort)
+ return rv; /* Abort */
+ if (rv == inst_user_trig) {
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ msec_sleep(200);
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ /* Progromatic Trigger */
+ } else {
+ /* Check for abort */
+ if (p->uicallback != NULL
+ && (rv = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_abort)
+ return rv; /* Abort */
+ }
+
+ /* Until we get a valid return */
+ for (tries = 0; tries < 2; tries++) {
+
+ /* Take a reading */
+ /* (DTP94 has optional parameters, but the default is what we want, XYZ in cd/m^2) */
+ if ((rv = dtp92_command(p, "RM\r", buf, MAX_RD_SIZE, 10.0)) != inst_ok) {
+ if ((rv & inst_imask) == DTP92_NEEDS_OFFSET_CAL)
+ p->need_offset_cal = 1;
+ else if ((rv & inst_imask) == DTP92_NEEDS_RATIO_CAL) /* DTP92 only */
+ p->need_ratio_cal = 1;
+ return rv;
+ }
+
+ if (sscanf(buf, " X%*c %lf\t Y%*c %lf\t Z%*c %lf ",
+ &val->XYZ[0], &val->XYZ[1], &val->XYZ[2]) == 3) {
+
+ /* Apply the colorimeter correction matrix */
+ icmMulBy3x3(val->XYZ, p->ccmat, val->XYZ);
+
+ /* This may not change anything since instrument may clamp */
+ if (clamp)
+ icmClamp3(val->XYZ, val->XYZ);
+ val->loc[0] = '\000';
+ val->mtype = inst_mrt_emission;
+ val->XYZ_v = 1; /* These are absolute XYZ readings */
+ val->sp.spec_n = 0;
+ val->duration = 0.0;
+ rv = inst_ok;
+ break;
+ } else {
+ a1logd(p->log, 1, "dtp92_read_sample: failed to parse string '%s'\n",buf);
+ rv = inst_coms_fail;
+ }
+ }
+
+#ifdef NEVER
+ if (p->itype == instDTP92) {
+ /* Set sample size back to 16 */
+ if ((rv = dtp92_command(p, "10SS\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return rv;
+ }
+#endif
+
+ if (user_trig)
+ return inst_user_trig;
+ return rv;
+}
+
+/* Read an emissive refresh rate */
+static inst_code
+dtp92_read_refrate(
+inst *pp,
+double *ref_rate
+) {
+ dtp92 *p = (dtp92 *)pp;
+ char buf[MAX_RD_SIZE];
+ char buf2[MAX_RD_SIZE];
+ double refrate = 0.0;
+ inst_code rv;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* Measure the refresh rate */
+ rv = dtp92_command(p, "00103RM\r", buf, MAX_RD_SIZE, 5.0);
+
+ if (rv != inst_ok) {
+ if ((rv & inst_imask) == DTP92_NO_DATA_AVAILABLE)
+ rv = inst_misread;
+ return rv;
+ }
+
+ if (sscanf(buf, "Hz %lf ", &refrate) != 1) {
+ a1logd(p->log, 1, "dtp92_read_refrate rate: failed to parse string '%s'\n",buf);
+ *ref_rate = 0.0;
+ return inst_misread;
+ }
+ if (refrate == 0.0) {
+ return inst_misread;
+ }
+ *ref_rate = refrate;
+ return inst_ok;
+}
+
+/* Insert a colorimetric correction matrix in the instrument XYZ readings */
+/* This is only valid for colorimetric instruments. */
+/* To remove the matrix, pass NULL for the filter filename */
+static inst_code dtp92_col_cor_mat(
+inst *pp,
+double mtx[3][3]
+) {
+ dtp92 *p = (dtp92 *)pp;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (mtx == NULL)
+ icmSetUnity3x3(p->ccmat);
+ else
+ icmCpy3x3(p->ccmat, mtx);
+
+ return inst_ok;
+}
+
+/* Return needed and available inst_cal_type's */
+static inst_code dtp92_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
+ dtp92 *p = (dtp92 *)pp;
+ inst_cal_type n_cals = inst_calt_none;
+ inst_cal_type a_cals = inst_calt_none;
+
+ if (p->itype == instDTP92) {
+ if (p->need_ratio_cal)
+ n_cals |= inst_calt_emis_ratio;
+ a_cals |= inst_calt_emis_ratio;
+ }
+
+ if (p->need_offset_cal)
+ n_cals |= inst_calt_emis_offset;
+ a_cals |= inst_calt_emis_offset;
+
+ if (pn_cals != NULL)
+ *pn_cals = n_cals;
+
+ if (pa_cals != NULL)
+ *pa_cals = a_cals;
+
+ return inst_ok;
+}
+
+/* Request an instrument calibration. */
+/* This is use if the user decides they want to do a calibration */
+/* in anticipation of a calibration (needs_calibration()) to avoid */
+/* requiring one during measurement, or in response to measuring */
+/* returning inst_needs_cal. Initially use an inst_cal_cond of inst_calc_none, */
+/* and then be prepared to setup the right conditions, or ask the */
+/* user to do so, each time the error inst_cal_setup is returned. */
+static inst_code dtp92_calibrate(
+inst *pp,
+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) */
+) {
+ dtp92 *p = (dtp92 *)pp;
+ char buf[MAX_RD_SIZE];
+ inst_code ev = inst_ok;
+ inst_cal_type needed, available;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ id[0] = '\000';
+
+ if ((ev = dtp92_get_n_a_cals((inst *)p, &needed, &available)) != inst_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,"dtp92_calibrate: doing calt 0x%x\n",calt);
+
+ if ((*calt & inst_calt_n_dfrble_mask) == 0) /* Nothing todo */
+ return inst_ok;
+ }
+
+ /* See if it's a calibration we understand */
+ if (*calt & ~available & inst_calt_all_mask) {
+ return inst_unsupported;
+ }
+
+ if (*calt & inst_calt_emis_offset) { /* Dark offset calibration */
+
+ if (*calc != inst_calc_man_em_dark) {
+ *calc = inst_calc_man_em_dark;
+ return inst_cal_setup;
+ }
+
+ /* Do offset calibration */
+ if ((ev = dtp92_command(p, "CO\r", buf, MAX_RD_SIZE, 12)) != inst_ok)
+ return ev;
+
+ *calt &= inst_calt_emis_offset;
+ }
+
+ if (*calt & inst_calt_emis_ratio) { /* Cell ratio calibration */
+
+ if (*calc != inst_calc_emis_grey
+ && *calc != inst_calc_emis_grey_darker
+ && *calc != inst_calc_emis_grey_ligher) {
+ *calc = inst_calc_emis_grey;
+ return inst_cal_setup;
+ }
+
+ /* Do ratio calibration */
+ if ((ev = dtp92_command(p, "CR\r", buf, MAX_RD_SIZE, 25.0)) != inst_ok) {
+
+ if ((ev & inst_imask) == DTP92_TOO_MUCH_LIGHT) {
+ *calc = inst_calc_emis_grey_darker;
+ return inst_cal_setup;
+ } else if ((ev & inst_imask) == DTP92_NOT_ENOUGH_LIGHT) {
+ *calc = inst_calc_emis_grey_ligher;
+ return inst_cal_setup;
+ }
+ return ev; /* Error */
+ }
+ *calt &= inst_calt_emis_ratio;
+ }
+
+ return ev;
+}
+
+/* Return the last reading refresh rate in Hz. */
+static inst_code dtp92_get_refr_rate(inst *pp,
+double *ref_rate
+) {
+ dtp92 *p = (dtp92 *)pp;
+ char buf[MAX_RD_SIZE];
+ char buf2[MAX_RD_SIZE];
+ double refrate = 0.0;
+ inst_code rv;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* Get the last readings refresh rate */
+ rv = dtp92_command(p, "10103RM\r", buf, MAX_RD_SIZE, 5.0);
+
+ if (rv != inst_ok) {
+ if ((rv & inst_imask) == DTP92_NO_DATA_AVAILABLE)
+ rv = inst_misread;
+ return rv;
+ }
+
+ if (sscanf(buf, "Hz %lf ", &refrate) != 1) {
+ a1logd(p->log, 1, "dtp92_read_refresh rate: failed to parse string '%s'\n",buf);
+ *ref_rate = 0.0;
+ return inst_misread;
+ }
+ if (refrate == 0.0) {
+ return inst_misread;
+ }
+ *ref_rate = refrate;
+ return inst_ok;
+}
+
+/* Set the calibrated refresh rate in Hz. */
+/* Set refresh rate to 0.0 to mark it as invalid */
+/* Rates outside the range 5.0 to 150.0 Hz will return an error */
+static inst_code dtp92_set_refr_rate(inst *pp,
+double ref_rate
+) {
+ dtp92 *p = (dtp92 *)pp;
+
+ /* The DTP92 can't have a refresh rate set */
+ return inst_unsupported;
+}
+
+/* Error codes interpretation */
+static char *
+dtp92_interp_error(inst *pp, int ec) {
+// dtp92 *p = (dtp92 *)pp;
+ ec &= inst_imask;
+ switch (ec) {
+ case DTP92_INTERNAL_ERROR:
+ return "Internal software error";
+ case DTP92_COMS_FAIL:
+ return "Communications failure";
+ case DTP92_UNKNOWN_MODEL:
+ return "Not a DTP92 or DTP94";
+ case DTP92_DATA_PARSE_ERROR:
+ return "Data from DTP didn't parse as expected";
+
+ case DTP92_OK:
+ return "No device error";
+
+ case DTP92_BAD_COMMAND:
+ return "Unrecognized command";
+ case DTP92_PRM_RANGE:
+ return "Command parameter out of range";
+ case DTP92_MEMORY_OVERFLOW:
+ return "Memory bounds error";
+ case DTP92_INVALID_BAUD_RATE:
+ return "Invalid baud rate";
+ case DTP92_TIMEOUT:
+ return "Receive timeout";
+ case DTP92_SYNTAX_ERROR:
+ return "Badly formed parameter";
+ case DTP92_NO_DATA_AVAILABLE:
+ return "No data available";
+ case DTP92_MISSING_PARAMETER:
+ return "Paramter is missing";
+ case DTP92_CALIBRATION_DENIED:
+ return "Invalid calibration enable code";
+ case DTP92_NEEDS_OFFSET_CAL:
+ return "Offset calibration checksum failed";
+ case DTP92_NEEDS_RATIO_CAL:
+ return "Ratio calibration checksum failed";
+ case DTP92_NEEDS_LUMINANCE_CAL:
+ return "Luminance calibration checksum failed";
+ case DTP92_NEEDS_WHITE_POINT_CAL:
+ return "White point calibration checksum failed";
+ case DTP92_NEEDS_BLACK_POINT_CAL:
+ return "Black point calibration checksum failed";
+ case DTP92_NEEDS_OFFSET_DRIFT_CAL:
+ return "Offset drift calibration checksum failed";
+ case DTP92_INVALID_READING:
+ return "Unable to take a reading";
+ case DTP92_BAD_COMP_TABLE:
+ return "Bad compensation table";
+ case DTP92_TOO_MUCH_LIGHT:
+ return "Too much light entering instrument";
+ case DTP92_NOT_ENOUGH_LIGHT:
+ return "Not enough light to complete operation";
+
+ case DTP92_BAD_SERIAL_NUMBER:
+ return "New serial number is invalid";
+
+ case DTP92_NO_MODULATION:
+ return "No refresh modulation detected";
+
+ case DTP92_EEPROM_FAILURE:
+ return "EEprom write failure";
+ case DTP92_FLASH_WRITE_FAILURE:
+ return "Flash memory write failure";
+ case DTP92_BAD_CONFIGURATION:
+ return "Configuration data checksum failed";
+ case DTP92_INST_INTERNAL_ERROR:
+ return "Internal instrument error";
+ default:
+ return "Unknown error code";
+ }
+}
+
+
+/* Convert a machine specific error code into an abstract dtp code */
+static inst_code
+dtp92_interp_code(inst *pp, int ec) {
+
+ ec &= inst_imask;
+ switch (ec) {
+
+ case DTP92_OK:
+ return inst_ok;
+
+ case DTP92_INTERNAL_ERROR:
+ return inst_internal_error | ec;
+
+ case DTP92_COMS_FAIL:
+ return inst_coms_fail | ec;
+
+ case DTP92_UNKNOWN_MODEL:
+ return inst_unknown_model | ec;
+
+ case DTP92_DATA_PARSE_ERROR:
+ return inst_protocol_error | ec;
+
+ case DTP92_NO_MODULATION: /* ?? */
+ case DTP92_TOO_MUCH_LIGHT:
+ case DTP92_NOT_ENOUGH_LIGHT:
+ case DTP92_INVALID_READING:
+ return inst_misread | ec;
+
+ case DTP92_NEEDS_OFFSET_CAL:
+ return inst_needs_cal | ec;
+
+ case DTP92_NEEDS_RATIO_CAL:
+ return inst_needs_cal | ec;
+
+ case DTP92_NEEDS_LUMINANCE_CAL:
+ case DTP92_NEEDS_WHITE_POINT_CAL:
+ case DTP92_NEEDS_BLACK_POINT_CAL:
+ case DTP92_NEEDS_OFFSET_DRIFT_CAL:
+ return inst_hardware_fail | ec;
+ }
+ return inst_other_error | ec;
+}
+
+/* Destroy ourselves */
+static void
+dtp92_del(inst *pp) {
+ dtp92 *p = (dtp92 *)pp;
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ inst_del_disptype_list(p->dtlist, p->ndtlist);
+ free(p);
+}
+
+/* Return the instrument mode capabilities */
+void dtp92_capabilities(inst *pp,
+inst_mode *pcap1,
+inst2_capability *pcap2,
+inst3_capability *pcap3) {
+ dtp92 *p = (dtp92 *)pp;
+ inst_mode cap1 = 0;
+ inst2_capability cap2 = 0;
+
+ cap1 |= inst_mode_emis_spot
+ | inst_mode_colorimeter
+ ;
+
+ cap2 |= inst2_prog_trig
+ | inst2_user_trig
+ | inst2_ccmx
+ ; /* The '92 does have a switch, but we're not currently supporting it */
+
+ if (p->itype == instDTP94) {
+ cap2 |= inst2_disptype;
+ } else {
+ cap2 |= inst2_refresh_rate;
+ cap2 |= inst2_emis_refr_meas;
+ }
+
+ if (pcap1 != NULL)
+ *pcap1 = cap1;
+ if (pcap2 != NULL)
+ *pcap2 = cap2;
+ if (pcap3 != NULL)
+ *pcap3 = inst3_none;
+}
+
+/* Check device measurement mode */
+inst_code dtp92_check_mode(inst *pp, inst_mode m) {
+ inst_mode cap;
+
+ if (!pp->gotcoms)
+ return inst_no_coms;
+ if (!pp->inited)
+ return inst_no_init;
+
+ pp->capabilities(pp, &cap, NULL, NULL);
+
+ /* Simple test */
+ if (m & ~cap)
+ return inst_unsupported;
+
+ /* Only display emission mode supported */
+ if (!IMODETST(m, inst_mode_emis_spot)) {
+ return inst_unsupported;
+ }
+
+ return inst_ok;
+}
+
+/* Set device measurement mode */
+inst_code dtp92_set_mode(inst *pp, inst_mode m) {
+ inst_code ev;
+
+ if ((ev = dtp92_check_mode(pp, m)) != inst_ok)
+ return ev;
+
+ return inst_ok;
+}
+
+inst_disptypesel dtp92_disptypesel[2] = {
+ {
+ inst_dtflags_default, /* flags */
+ 2, /* cbid */
+ "c", /* sel */
+ "CRT display", /* desc */
+ 1, /* refr */
+ 0 /* ix */
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ 0
+ }
+};
+
+inst_disptypesel dtp94_disptypesel[4] = {
+ {
+ inst_dtflags_default,
+ 1,
+ "l",
+ "LCD display",
+ 0,
+ 2
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 2, /* cbid */
+ "c", /* sel */
+ "CRT display", /* desc */
+ 1, /* refr */
+ 1 /* ix */
+ },
+ {
+ inst_dtflags_none,
+ 3,
+ "g",
+ "Generic display",
+ 0, /* Might be auto refresh detect ? */
+ 0
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ 0
+ }
+};
+
+static void set_base_disptype_list(dtp92 *p) {
+ if (p->itype == instDTP94) {
+ p->_dtlist = dtp94_disptypesel;
+ } else {
+ p->_dtlist = dtp92_disptypesel;
+ }
+}
+
+/* Get mode and option details */
+static inst_code dtp92_get_disptypesel(
+inst *pp,
+int *pnsels, /* Return number of display types */
+inst_disptypesel **psels, /* Return the array of display types */
+int allconfig, /* nz to return list for all configs, not just current. */
+int recreate /* nz to re-check for new ccmx & ccss files */
+) {
+ dtp92 *p = (dtp92 *)pp;
+ inst_code rv = inst_ok;
+
+ /* Create/Re-create a current list of abailable display types */
+ if (p->dtlist == NULL || recreate) {
+ if ((rv = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
+ p->_dtlist, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return rv;
+ }
+
+ if (pnsels != NULL)
+ *pnsels = p->ndtlist;
+
+ if (psels != NULL)
+ *psels = p->dtlist;
+
+ return inst_ok;
+}
+
+/* Given a display type entry, setup for that type */
+static inst_code set_disp_type(dtp92 *p, inst_disptypesel *dentry) {
+
+ p->icx = dentry->ix;
+ p->cbid = dentry->cbid;
+ p->refrmode = dentry->refr;
+
+ if (p->itype == instDTP92) {
+ if (p->icx != 0)
+ return inst_unsupported;
+
+ } else { /* DTP94 */
+ static char buf[MAX_MES_SIZE];
+ inst_code ev;
+
+ if (p->icx == 0) { /* Generic/Non-specific */
+ if ((ev = dtp92_command(p, "0016CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ } else if (p->icx == 1) { /* CRT */
+ if ((ev = dtp92_command(p, "0116CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ } else if (p->icx == 2) { /* LCD */
+ if ((ev = dtp92_command(p, "0216CF\r", buf, MAX_MES_SIZE, 0.2)) != inst_ok)
+ return ev;
+ } else {
+ return inst_unsupported;
+ }
+ }
+
+ if (dentry->flags & inst_dtflags_ccmx) {
+ icmCpy3x3(p->ccmat, dentry->mat);
+ } else {
+ icmSetUnity3x3(p->ccmat);
+ }
+
+ return inst_ok;
+}
+
+/* Setup the default display type */
+static inst_code set_default_disp_type(dtp92 *p) {
+ inst_code ev;
+ int i;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list((inst *)p, &p->ndtlist, &p->dtlist,
+ p->_dtlist, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ for (i = 0; !(p->dtlist[i].flags & inst_dtflags_end); i++) {
+ if (p->dtlist[i].flags & inst_dtflags_default)
+ break;
+ }
+ if (p->dtlist[i].flags & inst_dtflags_end) {
+ a1loge(p->log, 1, "set_default_disp_type: failed to find type!\n");
+ return inst_internal_error;
+ }
+ if ((ev = set_disp_type(p, &p->dtlist[i])) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+/* Set the display type */
+static inst_code dtp92_set_disptype(inst *pp, int ix) {
+ dtp92 *p = (dtp92 *)pp;
+ inst_code ev;
+ inst_disptypesel *dentry;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
+ p->_dtlist, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ if (ix < 0 || ix >= p->ndtlist)
+ return inst_unsupported;
+
+ dentry = &p->dtlist[ix];
+
+ if ((ev = set_disp_type(p, dentry)) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+/*
+ * set or reset an optional mode
+ *
+ * Some options talk to the instrument, and these will
+ * error if it hasn't been initialised.
+ */
+static inst_code
+dtp92_get_set_opt(inst *pp, inst_opt_type m, ...)
+{
+ dtp92 *p = (dtp92 *)pp;
+ char buf[MAX_MES_SIZE];
+ inst_code ev = inst_ok;
+
+ /* Record the trigger mode */
+ if (m == inst_opt_trig_prog
+ || m == inst_opt_trig_user) {
+ p->trig = m;
+ return inst_ok;
+ }
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* Get the display type information */
+ if (m == inst_opt_get_dtinfo) {
+ va_list args;
+ int *refrmode, *cbid;
+
+ va_start(args, m);
+ refrmode = va_arg(args, int *);
+ cbid = va_arg(args, int *);
+ va_end(args);
+
+ if (refrmode != NULL)
+ *refrmode = p->refrmode;
+ if (cbid != NULL)
+ *cbid = p->cbid;
+
+ return inst_ok;
+ }
+
+ return inst_unsupported;
+}
+
+/* Constructor */
+extern dtp92 *new_dtp92(icoms *icom, instType itype) {
+ dtp92 *p;
+ if ((p = (dtp92 *)calloc(sizeof(dtp92),1)) == NULL) {
+ a1loge(icom->log, 1, "new_dtp92: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(icom->log);
+
+ p->init_coms = dtp92_init_coms;
+ p->init_inst = dtp92_init_inst;
+ p->capabilities = dtp92_capabilities;
+ p->check_mode = dtp92_check_mode;
+ p->set_mode = dtp92_set_mode;
+ p->get_disptypesel = dtp92_get_disptypesel;
+ p->set_disptype = dtp92_set_disptype;
+ p->get_set_opt = dtp92_get_set_opt;
+ p->read_sample = dtp92_read_sample;
+ p->read_refrate = dtp92_read_refrate;
+ p->get_n_a_cals = dtp92_get_n_a_cals;
+ p->calibrate = dtp92_calibrate;
+ p->col_cor_mat = dtp92_col_cor_mat;
+ p->get_refr_rate = dtp92_get_refr_rate;
+ p->set_refr_rate = dtp92_set_refr_rate;
+ p->interp_error = dtp92_interp_error;
+ p->del = dtp92_del;
+
+ p->icom = icom;
+ p->itype = icom->itype;
+
+ icmSetUnity3x3(p->ccmat); /* Set the colorimeter correction matrix to do nothing */
+ set_base_disptype_list(p);
+
+ return p;
+}
+
diff --git a/spectro/dtp92.h b/spectro/dtp92.h
new file mode 100644
index 0000000..7add3e0
--- /dev/null
+++ b/spectro/dtp92.h
@@ -0,0 +1,103 @@
+#ifndef DTP92_H
+
+/*
+ * Argyll Color Correction System
+ *
+ * Xrite DTP92/94 related defines
+ *
+ * Author: Graeme W. Gill
+ * Date: 5/6/2001
+ *
+ * Copyright 2001 - 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.
+ */
+
+#include "inst.h"
+
+/* Note: update dtp92_interp_error() and dtp92_interp_code() in dtp92.c */
+/* if anything of these #defines are added or subtracted */
+
+/* Fake Error codes */
+#define DTP92_INTERNAL_ERROR 0x61 /* Internal software error */
+#define DTP92_COMS_FAIL 0x62 /* Communication failure */
+#define DTP92_UNKNOWN_MODEL 0x63 /* Not a DPT92 */
+#define DTP92_DATA_PARSE_ERROR 0x64 /* Read data parsing error */
+
+/* Real error code */
+#define DTP92_OK 0x00
+
+#define DTP92_BAD_COMMAND 0x01
+#define DTP92_PRM_RANGE 0x02
+#define DTP92_MEMORY_OVERFLOW 0x04
+#define DTP92_INVALID_BAUD_RATE 0x05 /* 92 only */
+#define DTP92_TIMEOUT 0x07
+#define DTP92_SYNTAX_ERROR 0x08
+#define DTP92_NO_DATA_AVAILABLE 0x0B
+#define DTP92_MISSING_PARAMETER 0x0C
+#define DTP92_CALIBRATION_DENIED 0x0D
+#define DTP92_NEEDS_OFFSET_CAL 0x16
+#define DTP92_NEEDS_RATIO_CAL 0x17 /* 92 only */
+#define DTP92_NEEDS_LUMINANCE_CAL 0x18
+#define DTP92_NEEDS_WHITE_POINT_CAL 0x19
+#define DTP92_NEEDS_BLACK_POINT_CAL 0x1A
+#define DTP92_NEEDS_OFFSET_DRIFT_CAL 0x1B /* 94 only */
+#define DTP92_INVALID_READING 0x20
+#define DTP92_BAD_COMP_TABLE 0x25
+#define DTP92_TOO_MUCH_LIGHT 0x28
+#define DTP92_NOT_ENOUGH_LIGHT 0x29
+
+#define DTP92_BAD_SERIAL_NUMBER 0x40
+
+#define DTP92_NO_MODULATION 0x50 /* 92 only */
+
+#define DTP92_EEPROM_FAILURE 0x70
+#define DTP92_FLASH_WRITE_FAILURE 0x71
+#define DTP92_BAD_CONFIGURATION 0x72 /* 94 only */
+#define DTP92_INST_INTERNAL_ERROR 0x7F /* 92 only */
+
+
+/* DTP92 communication object */
+struct _dtp92 {
+ INST_OBJ_BASE
+
+ inst_disptypesel *_dtlist; /* Base list */
+ inst_disptypesel *dtlist; /* Display Type list */
+ int ndtlist; /* Number of valid dtlist entries */
+ int icx; /* Internal calibration index, 0 = CRT, 1 = LCD */
+ int cbid; /* calibration base ID, 0 if not a base */
+ int refrmode; /* 0 for constant, 1 for refresh display */
+ double ccmat[3][3]; /* Colorimeter correction matrix */
+
+ int need_offset_cal; /* Flags to indicate type of calibration needed */
+ int need_ratio_cal;
+ inst_opt_type trig; /* Reading trigger mode */
+
+ }; typedef struct _dtp92 dtp92;
+
+/* Constructor */
+extern dtp92 *new_dtp92(icoms *icom, instType itype);
+
+
+#define DTP92_H
+#endif /* DTP92_H */
diff --git a/spectro/fakeread.c b/spectro/fakeread.c
new file mode 100644
index 0000000..df77930
--- /dev/null
+++ b/spectro/fakeread.c
@@ -0,0 +1,1025 @@
+/*
+ * Argyll Color Correction System
+ * Fake print target chart reader - use ICC or MPP profile rather than instrument
+ *
+ * Author: Graeme W. Gill
+ * Date: 17/2/2002
+ *
+ * Copyright 2002 - 2013 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/*
+ * TTBD:
+
+ Do we need to deterct & mark display values normalized to Y = 100 ??
+ */
+
+
+#undef DEBUG
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/types.h>
+#include <time.h>
+#include <string.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "cgats.h"
+#include "xicc.h"
+#include "icc.h"
+
+void usage(char *diag, ...) {
+ fprintf(stderr,"Fake test chart reader - lookup values in ICC/MPP profile, Version %s\n",
+ ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ if (diag != NULL) {
+ va_list args;
+ fprintf(stderr," Diagnostic: ");
+ va_start(args, diag);
+ vfprintf(stderr, diag, args);
+ va_end(args);
+ fprintf(stderr,"\n");
+ }
+ fprintf(stderr,"usage: fakeread [-v] [-s] [separation.icm] profile.[%s|mpp|ti3] outfile\n",ICC_FILE_EXT_ND);
+ fprintf(stderr," -v Verbose mode\n");
+ fprintf(stderr," -s Lookup MPP spectral values\n");
+ fprintf(stderr," -p Use separation profile\n");
+ fprintf(stderr," -l Output Lab rather than XYZ\n");
+ fprintf(stderr," -k file.cal Apply calibration (after sep.) and include in .ti3\n");
+ fprintf(stderr," -i file.cal Include calibration in .ti3 (but don't apply it)\n");
+ fprintf(stderr," -r level Add average random deviation of <level>%% to input device values (after sep. & cal.)\n");
+ fprintf(stderr," -0 pow Apply power to input device chanel 0-9 (after sep. cal. & rand)\n");
+ fprintf(stderr," -R level Add average random deviation of <level>%% to output PCS values\n");
+ fprintf(stderr," -u Make random deviations have uniform distributions rather than normal\n");
+ fprintf(stderr," -S seed Set random seed\n");
+ fprintf(stderr," -b L,a,b Scale black point to target Lab value\n");
+ fprintf(stderr," -I intent r = relative colorimetric, a = absolute (default)\n");
+ fprintf(stderr," [separation.%s] Device link separation profile\n",ICC_FILE_EXT_ND);
+ fprintf(stderr," profile.[%s|mpp|ti3] ICC, MPP profile or TI3 to use\n",ICC_FILE_EXT_ND);
+ fprintf(stderr," outfile Base name for input[ti1]/output[ti3] file\n");
+ exit(1);
+ }
+
+int main(int argc, char *argv[])
+{
+ int j;
+ int fa,nfa; /* current argument we're looking at */
+ int verb = 0; /* Verbose flag */
+ int dosep = 0; /* Use separation before profile */
+ int dolab = 0; /* Output Lab rather than XYZ */
+ int gfudge = 0; /* Do grey fudge, 1 = W->RGB, 2 = K->xxxK */
+ double chpow[10] = { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 };
+ double rdlevel = 0.0; /* Random device average deviation level (0.0 - 1.0) */
+ double rplevel = 0.0; /* Random PCS average deviation level (0.0 - 1.0) */
+ int unidist = 0; /* Use uniform distribution of errors */
+ unsigned int seed = time(NULL); /* Random seed value */
+ double tbp[3] = { -1.0, 0.0, 0.0 }; /* Target black point */
+ int applycal = 0; /* NZ to apply calibration */
+ static char sepname[MAXNAMEL+1] = { 0 }; /* ICC separation profile */
+ static char calname[MAXNAMEL+1] = { 0 }; /* device calibration */
+ static char profname[MAXNAMEL+1] = { 0 }; /* ICC or MPP Profile name */
+ static char inname[MAXNAMEL+1] = { 0 }; /* Input cgats file base name */
+ static char outname[MAXNAMEL+1] = { 0 }; /* Output cgats file base name */
+ cgats *icg; /* input cgats structure */
+ cgats *ocg; /* output cgats structure */
+ int nmask = 0; /* Test chart device colorant mask */
+ int nchan = 0; /* Test chart number of device chanels */
+ int npat; /* Number of patches */
+ int si; /* Sample id index */
+ int ti; /* Temp index */
+ int fi; /* Colorspace index */
+ int inti3 = 0; /* Input is a renamed .ti3 file rather than .ti1 */
+
+ /* ICC separation device link profile */
+ icmFile *sep_fp = NULL; /* Color profile file */
+ icc *sep_icco = NULL; /* Profile object */
+ icmLuBase *sep_luo = NULL; /* Conversion object */
+ icColorSpaceSignature sep_ins, sep_outs; /* Type of input and output spaces */
+ int sep_inn; /* Number of input channels to separation */
+ inkmask sep_nmask = 0; /* Colorant mask for separation input */
+ double wp[3], bp[3]; /* ICC profile Lab white and black points */
+ double bpt[3][3]; /* Black point transform matrix (Lab->Lab) */
+
+ /* Calibration */
+ xcal *cal = NULL; /* calibration */
+
+ /* ICC profile based */
+ icRenderingIntent intent = icAbsoluteColorimetric;
+ icmFile *icc_fp = NULL; /* Color profile file */
+ icc *icc_icco = NULL; /* Profile object */
+ icmLuBase *icc_luo = NULL; /* Conversion object */
+
+ /* MPP profile based */
+ mpp *mlu = NULL; /* Conversion object */
+ instType itype = instUnknown; /* Type of instrument used */
+ int dospec = 0; /* Do spectral MPP lookup */
+ int spec_n = 0; /* Number of spectral bands */
+ double spec_wl_short;/* First reading wavelength in nm (shortest) */
+ double spec_wl_long; /* Last reading wavelength in nm (longest) */
+
+ /* TI3 based fake read */
+ cgats *ti3 = NULL; /* input cgats structure */
+ int ti3_npat = 0; /* Number of patches in reference file */
+ int ti3_chix[ICX_MXINKS]; /* Device chanel indexes */
+ int ti3_pcsix[3]; /* Device chanel indexes */
+ int ti3_spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */
+ int ti3_isLab = 0; /* Flag indicating PCS for TI3 file */
+
+ int rv = 0;
+ int inn, outn; /* Number of channels for conversion input, output */
+ icColorSpaceSignature ins, outs; /* Type of conversion input and output spaces */
+ inkmask cnv_nmask = 0; /* Conversion input nmask */
+ time_t clk = time(0);
+ struct tm *tsp = localtime(&clk);
+ char *atm = asctime(tsp); /* Ascii time */
+ char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" };
+ char *labfname[3] = { "LAB_L", "LAB_A", "LAB_B" };
+
+ error_program = "Fakeread";
+ if (argc < 3)
+ usage("Too few arguments");
+
+ /* Process the arguments */
+ for(fa = 1;fa < argc;fa++) {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else
+ {
+ if ((fa+1) < argc)
+ {
+ if (argv[fa+1][0] != '-')
+ {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage("Usage requested");
+
+ /* Verbose */
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V')
+ verb = 1;
+
+ /* Spectral MPP lookup */
+ else if (argv[fa][1] == 's')
+ dospec = 1;
+
+ /* Separation */
+ else if (argv[fa][1] == 'p' || argv[fa][1] == 'P')
+ dosep = 1;
+
+ /* Lab */
+ else if (argv[fa][1] == 'l' || argv[fa][1] == 'L')
+ dolab = 1;
+
+ /* Uniform distrivuted errors */
+ else if (argv[fa][1] == 'u' || argv[fa][1] == 'U')
+ unidist = 1;
+
+ /* Random seed value */
+ else if (argv[fa][1] == 'S') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to -S");
+ if (sscanf(na, " %u ",&seed) != 1)
+ usage("Couldn't parse argument to -S");
+ }
+
+ /* calibration to device values */
+ else if (argv[fa][1] == 'k' || argv[fa][1] == 'i') {
+ if (argv[fa][1] == 'k')
+ applycal = 1;
+ else
+ applycal = 0;
+ fa = nfa;
+ if (na == NULL) usage("Expected an argument to -%c",argv[fa][1]);
+ strncpy(calname,na,MAXNAMEL); calname[MAXNAMEL] = '\000';
+ }
+
+ /* Random addition to device levels */
+ else if (argv[fa][1] == 'r') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to -r");
+ rdlevel = atof(na) * 0.01;
+ }
+
+ /* Power applied to device channels */
+ else if (argv[fa][1] >= '0' && argv[fa][1] <= '9') {
+ int ch;
+ if (na == NULL) usage("Expect argument to -[0-9]");
+ ch = argv[fa][1]-'0';
+ if (ch < 0 || ch > 9)
+ usage("Channel no. %d is out of range\n",ch);
+ chpow[ch] = atof(na);
+ fa = nfa;
+ }
+
+ /* Random addition to PCS levels */
+ else if (argv[fa][1] == 'R') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to -R");
+ rplevel = atof(na) * 0.01;
+ }
+
+ /* Black point scale */
+ else if (argv[fa][1] == 'b' || argv[fa][1] == 'B') {
+ if (na == NULL) usage("Expect argument to -b");
+ fa = nfa;
+ if (sscanf(na, " %lf , %lf , %lf ",&tbp[0], &tbp[1], &tbp[2]) != 3)
+ usage("Couldn't parse argument to -b");
+ if (tbp[0] < 0.0 || tbp[0] > 100.0) usage("-b L* value out of range");
+ if (tbp[1] < -128.0 || tbp[1] > 128.0) usage("-b a* value out of range");
+ if (tbp[2] < -128.0 || tbp[2] > 128.0) usage("-b b* value out of range");
+ }
+
+ /* Intent (only applies to ICC profile) */
+ else if (argv[fa][1] == 'I') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to -I");
+ switch (na[0]) {
+ case 'r':
+ intent = icRelativeColorimetric;
+ break;
+ case 'a':
+ intent = icAbsoluteColorimetric;
+ break;
+ default:
+ usage("Unexpected argument value '%c' to -I optyion",na[0]);
+ }
+ }
+
+ else
+ usage("Unrecognised flag");
+ }
+ else
+ break;
+ }
+
+ /* Get the file name argument */
+ if (dosep) {
+ if (fa >= argc || argv[fa][0] == '-') usage("Missing separation profile filename argument");
+ strncpy(sepname,argv[fa++],MAXNAMEL); sepname[MAXNAMEL] = '\000';
+ }
+
+ if (fa >= argc || argv[fa][0] == '-') usage("Missing profile filename argument");
+ strncpy(profname,argv[fa++],MAXNAMEL); profname[MAXNAMEL] = '\000';
+
+ if (fa >= argc || argv[fa][0] == '-') usage("Missing basename argument");
+ strncpy(inname,argv[fa],MAXNAMEL-4); inname[MAXNAMEL-4] = '\000';
+ strcat(inname,".ti1");
+ strncpy(outname,argv[fa],MAXNAMEL-4); outname[MAXNAMEL-4] = '\000';
+ strcat(outname,".ti3");
+
+ rand32(seed); /* Init seed */
+ /* Deal with separation */
+ if (dosep) {
+ if ((sep_fp = new_icmFileStd_name(sepname,"r")) == NULL)
+ error ("Can't open file '%s'",sepname);
+
+ if ((sep_icco = new_icc()) == NULL)
+ error ("Creation of ICC object failed");
+
+ /* Deal with ICC separation */
+ if ((rv = sep_icco->read(sep_icco,sep_fp,0)) == 0) {
+
+ /* Get a conversion object */
+ if ((sep_luo = sep_icco->get_luobj(sep_icco, icmFwd, icmDefaultIntent, icmSigDefaultData, icmLuOrdNorm)) == NULL) {
+ error ("%d, %s",sep_icco->errc, sep_icco->err);
+ }
+
+ /* Get details of conversion */
+ sep_luo->spaces(sep_luo, &sep_ins, &sep_inn, &sep_outs, NULL, NULL, NULL, NULL, NULL, NULL);
+ sep_nmask = icx_icc_to_colorant_comb(sep_ins, sep_icco->header->deviceClass);
+ }
+ }
+
+ /* Deal with calibration */
+ if (calname[0] != '\000') {
+ if ((cal = new_xcal()) == NULL)
+ error("new_xcal failed");
+ if ((cal->read(cal, calname)) != 0)
+ error("%s",cal->err);
+ if (verb)
+ if (applycal)
+ printf("Applying calibration curves from '%s'\n",calname);
+ else
+ printf("Embedding calibration curves from '%s' in output\n",calname);
+ }
+
+ /* Deal with ICC profile */
+ if ((icc_fp = new_icmFileStd_name(profname,"r")) == NULL)
+ error ("Can't open file '%s'",profname);
+
+ if ((icc_icco = new_icc()) == NULL)
+ error ("Creation of ICC object failed");
+
+ /* Deal with ICC profile */
+ if ((rv = icc_icco->read(icc_icco,icc_fp,0)) == 0) {
+
+ /* Embed any calibration in the output if it's present */
+ if (cal == NULL) {
+ applycal = 0;
+ cal = xiccReadCalTag(icc_icco);
+
+ if (verb && cal != NULL)
+ printf("Embedding calibration curves from ICC profile in output\n");
+ }
+
+ /* Get a Device to PCS conversion object */
+ if ((icc_luo = icc_icco->get_luobj(icc_icco, icmFwd, intent,
+ dolab ? icSigLabData : icSigXYZData, icmLuOrdNorm)) == NULL) {
+ if ((icc_luo = icc_icco->get_luobj(icc_icco, icmFwd, icmDefaultIntent,
+ dolab ? icSigLabData : icSigXYZData, icmLuOrdNorm)) == NULL)
+ error ("%d, %s",icc_icco->errc, icc_icco->err);
+ }
+
+ /* Get details of conversion */
+ icc_luo->spaces(icc_luo, &ins, &inn, &outs, &outn, NULL, NULL, NULL, NULL, NULL);
+ cnv_nmask = icx_icc_to_colorant_comb(ins, icc_icco->header->deviceClass);
+
+ if (dospec)
+ error("Can't lookup spectral values for ICC profile");
+
+ if (tbp[0] >= 0.0) {
+ double ss[3], tt[3];
+
+ icc_luo->wh_bk_points(icc_luo, wp, bp);
+ if (dolab) {
+ icmXYZ2Lab(&icmD50, wp, wp);
+ icmXYZ2Lab(&icmD50, bp, bp);
+ } else {
+ icmLab2XYZ(&icmD50, tbp, tbp);
+ }
+
+ /* Create scaling matrix */
+ for (j = 0; j < 3; j++) {
+ tt[j] = tbp[j] - wp[j];
+ ss[j] = bp[j] - wp[j];
+ }
+ icmRotMat(bpt, ss, tt);
+
+ if (verb) {
+ printf("White point = %f %f %f (%s)\n",wp[0],wp[1],wp[2], dolab ? "Lab" : "XYZ");
+ printf("Black point = %f %f %f (%s)\n",bp[0],bp[1],bp[2], dolab ? "Lab" : "XYZ");
+ printf("Target Black point = %f %f %f (%s)\n",tbp[0],tbp[1],tbp[2], dolab ? "Lab" : "XYZ");
+ }
+ }
+
+ } else { /* Not a valid ICC */
+ /* Close out the ICC profile */
+ icc_icco->del(icc_icco);
+ icc_icco = NULL;
+ icc_fp->del(icc_fp);
+ icc_fp = NULL;
+ icc_luo = NULL;
+ }
+
+ /* If we don't have an ICC lookup object, look for an MPP */
+ if (icc_luo == NULL) {
+
+ if ((mlu = new_mpp()) == NULL)
+ error ("Creation of MPP object failed");
+
+ if ((rv = mlu->read_mpp(mlu, profname)) == 0) {
+
+ /* mlu defaults to absolute XYZ lookup */
+ if (dolab) {
+ mlu->set_ilob(mlu, icxIT_default, NULL, icxOT_default, NULL, icSigLabData, 0);
+ }
+
+ mlu->get_info(mlu, &cnv_nmask, &inn, NULL,
+ &spec_n, &spec_wl_short, &spec_wl_long, &itype, NULL);
+
+ outn = 3;
+ outs = dolab ? icSigLabData : icSigXYZData;
+
+ if ((ins = icx_colorant_comb_to_icc(cnv_nmask)) == 0)
+ error ("Couldn't match MPP mask to valid ICC colorspace");
+
+ if (dospec && spec_n <= 0)
+ error("Can't lookup spectral values for non-spectral MPP profile");
+ } else {
+ mlu->del(mlu);
+ mlu = NULL;
+ }
+ }
+
+ /* If we don't have an ICC or MPP lookup object, look for a TI3 */
+ if (icc_luo == NULL && mlu == NULL) {
+ char *rbuf, *outc;
+ char *ti3_bident;
+ int ti3_nchan;
+
+ ti3 = new_cgats(); /* Create a CGATS structure */
+ ti3->add_other(ti3, "CTI3");/* our special input type is Calibration Target Information 3 */
+
+ if (ti3->read_name(ti3, profname))
+ error("CGATS file read error : %s",ti3->err);
+
+ if (ti3->ntables == 0 || ti3->t[0].tt != tt_other || ti3->t[0].oi != 0)
+ error ("Profile file '%s' isn't a CTI3 format file",profname);
+ if (ti3->ntables != 1)
+ error ("Input file '%s' doesn't contain one table",profname);
+
+ if ((ti = ti3->find_kword(ti3, 0, "COLOR_REP")) < 0)
+ error("Input file doesn't contain keyword COLOR_REP");
+
+ if ((rbuf = strdup(ti3->t[0].kdata[ti])) == NULL)
+ error("Malloc failed");
+
+ /* Split COLOR_REP into device and PCS space */
+ if ((outc = strchr(rbuf, '_')) == NULL)
+ error("COLOR_REP '%s' invalid", ti3->t[0].kdata[ti]);
+ *outc++ = '\000';
+
+ outn = 3;
+ if (strcmp(outc, "XYZ") == 0) {
+ ti3_isLab = 0;
+ outs = icSigXYZData;
+ } else if (strcmp(outc, "LAB") == 0) {
+ ti3_isLab = 1;
+ outs = icSigLabData;
+ } else
+ error("COLOR_REP '%s' invalid (Neither XYZ nor LAB)", ti3->t[0].kdata[ti]);
+
+ if ((cnv_nmask = icx_char2inkmask(rbuf)) == 0) {
+ error ("File '%s' keyword COLOR_REP has unknown device value '%s'",profname,rbuf);
+ }
+
+ free(rbuf);
+
+ if ((ins = icx_colorant_comb_to_icc(cnv_nmask)) == 0)
+ error ("Couldn't match MPP mask to valid ICC colorspace");
+
+ if ((inn = icmCSSig2nchan(ins)) == 0)
+ error ("TI3 Colorspace with unknown number of channels");
+
+ if ((ti3_npat = ti3->t[0].nsets) <= 0)
+ error ("No sets of data in reference TI3 file");
+
+ ti3_nchan = icx_noofinks(cnv_nmask);
+ ti3_bident = icx_inkmask2char(cnv_nmask, 0);
+
+ /* Find device fields */
+ for (j = 0; j < ti3_nchan; j++) {
+ int ii, imask;
+ char fname[100];
+
+ imask = icx_index2ink(cnv_nmask, j);
+ sprintf(fname,"%s_%s",cnv_nmask == ICX_W || cnv_nmask == ICX_K ? "GRAY" : ti3_bident,
+ icx_ink2char(imask));
+
+ if ((ii = ti3->find_field(ti3, 0, fname)) < 0)
+ error ("Input file doesn't contain field %s",fname);
+ if (ti3->t[0].ftype[ii] != r_t)
+ error ("Field %s is wrong type",fname);
+ ti3_chix[j] = ii;
+ }
+
+ /* Find PCS fields */
+ for (j = 0; j < 3; j++) {
+ int ii;
+
+ if ((ii = ti3->find_field(ti3, 0, ti3_isLab ? labfname[j] : xyzfname[j])) < 0)
+ error ("Input file doesn't contain field %s",ti3_isLab ? labfname[j] : xyzfname[j]);
+ if (ti3->t[0].ftype[ii] != r_t)
+ error ("Field %s is wrong type",ti3_isLab ? labfname[j] : xyzfname[j]);
+ ti3_pcsix[j] = ii;
+ }
+
+ /* Find spectral fields */
+ if ((ti = ti3->find_kword(ti3, 0, "SPECTRAL_BANDS")) >= 0) {
+ char buf[100];
+
+ spec_n = atoi(ti3->t[0].kdata[ti]);
+ if ((ti = ti3->find_kword(ti3, 0, "SPECTRAL_START_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_START_NM");
+ spec_wl_short = atof(ti3->t[0].kdata[ti]);
+ if ((ti = ti3->find_kword(ti3, 0, "SPECTRAL_END_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_END_NM");
+ spec_wl_long = atof(ti3->t[0].kdata[ti]);
+
+ /* Find the fields for spectral values */
+ for (j = 0; j < spec_n; j++) {
+ int nm;
+
+ /* Compute nearest integer wavelength */
+ nm = (int)(spec_wl_short + ((double)j/(spec_n-1.0))
+ * (spec_wl_long - spec_wl_short) + 0.5);
+
+ sprintf(buf,"SPEC_%03d",nm);
+
+ if ((ti3_spi[j] = ti3->find_field(ti3, 0, buf)) < 0)
+ error("Input file doesn't contain field %s",buf);
+ }
+ }
+
+ if (dospec && spec_n <= 0)
+ error("Can't lookup spectral values for non-spectral TI3 file");
+
+ free(ti3_bident);
+ }
+
+ /* Some sanity checking */
+ if (tbp[0] >= 0.0 && icc_luo == NULL)
+ error("Black scaling only works with ICC profile");
+
+ if (verb) {
+// printf("Random seed is %u\n",seed);
+
+ if (rdlevel > 0.0)
+ printf("Adding %.3f%% average deviation %s noise to device values\n",rdlevel * 100.0,unidist ? "uniform" : "normal");
+ for (j = 0; j < inn && j < 10; j++) {
+ if (chpow[j] != 1.0)
+ printf("Applying chan %d power %f\n",j,chpow[j]);
+ }
+ if (rplevel > 0.0)
+ printf("Adding %.3f%% average deviation %s noise to %s PCS values\n",rplevel * 100.0,unidist ? "uniform" : "normal", dolab ? "L*a*b*": "XYZ");
+ fflush(stdout);
+ }
+
+ /* Deal with input CGATS files */
+ icg = new_cgats(); /* Create a CGATS structure */
+ icg->add_other(icg, "CTI1"); /* our special input type is Calibration Target Information 1 */
+ icg->add_other(icg, "CTI3"); /* also accept renamed .ti3 file */
+
+ if (icg->read_name(icg, inname))
+ error("CGATS file read error : %s",icg->err);
+
+ if (icg->ntables == 0 || icg->t[0].tt != tt_other || (icg->t[0].oi != 0 && icg->t[0].oi != 1))
+ error ("Input file isn't a CTI1 format file");
+ if (icg->t[0].oi == 1)
+ inti3 = 1; /* It's a renamed .ti3 file */
+ if (icg->ntables != 1 && icg->ntables != 2 && icg->ntables != 3)
+ error ("Input file doesn't contain one, two or three tables");
+
+ if ((npat = icg->t[0].nsets) <= 0)
+ error ("No sets of data");
+
+ /* Figure out the color space of the .ti1 */
+ if ((fi = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
+ error ("Input file doesn't contain keyword COLOR_REP");
+
+ if (inti3) {
+ char *rbuf, *outc;
+
+ if ((rbuf = strdup(icg->t[0].kdata[fi])) == NULL)
+ error("Malloc failed");
+
+ /* Split COLOR_REP into device and PCS space */
+ if ((outc = strchr(rbuf, '_')) == NULL)
+ error("Input file '%s' COLOR_REP '%s' invalid", inname, icg->t[0].kdata[fi]);
+ *outc++ = '\000';
+
+ if ((nmask = icx_char2inkmask(rbuf)) == 0) {
+ error ("Input file '%s' keyword COLOR_REP has unknown device value '%s'",inname,rbuf);
+ }
+
+ free(rbuf);
+ } else {
+ if ((nmask = icx_char2inkmask(icg->t[0].kdata[fi])) == 0)
+ error ("Input file '%s' keyword COLOR_REP has unknown value '%s'",inname, icg->t[0].kdata[fi]);
+ }
+
+ /* Setup output cgats file */
+ ocg = new_cgats(); /* Create a CGATS structure */
+ ocg->add_other(ocg, "CTI3"); /* our special type is Calibration Target Information 3 */
+ ocg->add_table(ocg, tt_other, 0); /* Start the first table */
+
+ ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 3",NULL);
+ ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll fakeread", NULL);
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+ ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
+
+ /* What sort of device this is */
+ if (ti3 != NULL) {
+ if ((ti = ti3->find_kword(ti3, 0, "DEVICE_CLASS")) < 0)
+ error("Input TI3 doesn't contain keyword DEVICE_CLASS");
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS",ti3->t[0].kdata[ti], NULL); /* Copy */
+ } else if (mlu != NULL) {
+ /* This is a guess. It may not be correct. */
+ if ((cnv_nmask & ICX_ADDITIVE) ^ (cnv_nmask & ICX_INVERTED)) {
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","DISPLAY", NULL);
+ if (nmask == ICX_IRGB)
+ warning("It's unusual to have an iRGB display device !");
+ } else {
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL);
+ if (nmask == ICX_RGB)
+ warning("It's unusual to have an RGB printing device !");
+ }
+ } else { /* Assume ICC */
+ if (icc_icco->header->deviceClass == icSigDisplayClass) {
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","DISPLAY", NULL);
+ if (nmask == ICX_IRGB)
+ warning("It's unusual to have an iRGB display device !");
+ } else {
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL);
+ if (nmask == ICX_RGB)
+ warning("It's unusual to have an RGB printing device !");
+ }
+ }
+
+ if ((ti = icg->find_kword(icg, 0, "SINGLE_DIM_STEPS")) >= 0)
+ ocg->add_kword(ocg, 0, "SINGLE_DIM_STEPS",icg->t[0].kdata[ti], NULL);
+
+ if ((ti = icg->find_kword(icg, 0, "COMP_GREY_STEPS")) >= 0)
+ ocg->add_kword(ocg, 0, "COMP_GREY_STEPS",icg->t[0].kdata[ti], NULL);
+
+ if ((ti = icg->find_kword(icg, 0, "MULTI_DIM_STEPS")) >= 0)
+ ocg->add_kword(ocg, 0, "MULTI_DIM_STEPS",icg->t[0].kdata[ti], NULL);
+
+ if ((ti = icg->find_kword(icg, 0, "FULL_SPREAD_PATCHES")) >= 0)
+ ocg->add_kword(ocg, 0, "FULL_SPREAD_PATCHES",icg->t[0].kdata[ti], NULL);
+
+ if (icc_luo == NULL) { /* If MPP profile, we know what the target instrument is */
+ ocg->add_kword(ocg, 0, "TARGET_INSTRUMENT", inst_name(itype) , NULL);
+ }
+
+ /* Fields we want */
+ ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t);
+
+ if ((si = icg->find_field(icg, 0, "SAMPLE_ID")) < 0)
+ error ("Input file doesn't contain field SAMPLE_ID");
+
+ {
+ int i, j, ii;
+ int chix[ICX_MXINKS]; /* Device chanel indexes */
+ char *ident, *bident;
+ int nsetel = 0;
+ cgats_set_elem *setel; /* Array of set value elements */
+
+ nchan = icx_noofinks(nmask);
+ ident = icx_inkmask2char(nmask, 1);
+ bident = icx_inkmask2char(nmask, 0);
+
+ /* Sanity check what we're going to do */
+ if (dosep) {
+
+ /* Check if sep ICC input is compatible with .ti1 */
+ if (nmask == ICX_W && sep_ins == icSigRgbData)
+ gfudge = 1;
+ else if (nmask == ICX_K && sep_ins == icSigCmykData)
+ gfudge = 2;
+ else if (icx_colorant_comb_match_icc(nmask, sep_ins) == 0) {
+ error("Separation ICC device space '%s' dosen't match TI1 '%s'",
+ icm2str(icmColorSpaceSignature, sep_ins),
+ ident); /* Should free(). */
+ }
+
+ /* Check if separation ICC output is compatible with ICC/MPP/TI3 conversion */
+ if (icc_luo != NULL) {
+ /* Check if icc is compatible with .ti1 */
+ if (sep_outs != ins)
+ error("ICC device space '%s' dosen't match Separation ICC '%s'",
+ icm2str(icmColorSpaceSignature, ins),
+ icm2str(icmColorSpaceSignature, sep_outs));
+ } else if (mlu != NULL) {
+ /* Check if mpp is compatible with .ti1 */
+ if (icx_colorant_comb_match_icc(cnv_nmask, sep_outs) == 0)
+ error("MPP device space '%s' doesn't match Separation ICC '%s'",
+ icx_inkmask2char(cnv_nmask, 1), /* Should free(). */
+ icm2str(icmColorSpaceSignature, sep_outs));
+ } else if (ti3 != NULL) {
+ /* Check if .ti3 is compatible with .ti1 */
+ if (icx_colorant_comb_match_icc(cnv_nmask, sep_outs) == 0)
+ error("TI3 device space '%s' doesn't match Separation ICC '%s'",
+ icx_inkmask2char(cnv_nmask, 1), /* Should free(). */
+ icm2str(icmColorSpaceSignature, sep_outs));
+ }
+ } else if (icc_luo != NULL) {
+ /* Check if icc is compatible with .ti1 */
+ if (nmask == ICX_W && ins == icSigRgbData)
+ gfudge = 1;
+ else if (nmask == ICX_K && ins == icSigCmykData)
+ gfudge = 2; /* Should allow for other colorant combo's that include black */
+ else if (icx_colorant_comb_match_icc(nmask, ins) == 0) {
+ error("ICC device space '%s' dosen't match TI1 '%s'",
+ icm2str(icmColorSpaceSignature, ins),
+ ident); // Should free().
+ }
+ } else if (mlu != NULL) {
+ /* Check if mpp is compatible with .ti1 */
+ if (nmask == ICX_W && (cnv_nmask == ICX_RGB || cnv_nmask == ICX_IRGB))
+ gfudge = 1;
+ else if (nmask == ICX_K && (cnv_nmask & ICX_BLACK))
+ gfudge = 2;
+ else if (cnv_nmask != nmask)
+ error("MPP device space '%s' doesn't match TI1 '%s'",
+ icx_inkmask2char(cnv_nmask, 1), ident); // Should free().
+ } else if (ti3 != NULL) {
+ /* Check if .ti3 is compatible with .ti1 */
+ if (nmask == ICX_W && (cnv_nmask == ICX_RGB || cnv_nmask == ICX_IRGB))
+ gfudge = 1;
+ else if (nmask == ICX_K && (cnv_nmask & ICX_BLACK))
+ gfudge = 2;
+ else if (cnv_nmask != nmask)
+ error("TI3 device space '%s' doesn't match TI1 '%s'",
+ icx_inkmask2char(cnv_nmask, 1), ident); // Should free().
+ }
+
+ if ((ii = icg->find_kword(icg, 0, "TOTAL_INK_LIMIT")) >= 0)
+ ocg->add_kword(ocg, 0, "TOTAL_INK_LIMIT",icg->t[0].kdata[ii], NULL);
+
+ nsetel += 1; /* For id */
+ nsetel += nchan; /* For device values */
+ nsetel += 3; /* For XYZ/Lab */
+
+ for (j = 0; j < nchan; j++) {
+ int imask;
+ char fname[100];
+
+ imask = icx_index2ink(nmask, j);
+ sprintf(fname,"%s_%s",nmask == ICX_W || nmask == ICX_K ? "GRAY" : bident,
+ icx_ink2char(imask));
+
+ if ((ii = icg->find_field(icg, 0, fname)) < 0)
+ error ("Input file doesn't contain field %s",fname);
+ if (icg->t[0].ftype[ii] != r_t)
+ error ("Field %s is wrong type",fname);
+
+ ocg->add_field(ocg, 0, fname, r_t);
+ chix[j] = ii;
+ }
+
+ /* Add PCS fields */
+ for (j = 0; j < 3; j++) {
+ ocg->add_field(ocg, 0, dolab ? labfname[j] : xyzfname[j], r_t);
+ }
+
+ /* If we have spectral information, output it too */
+ if (dospec > 0 && spec_n > 0) {
+ char buf[100];
+
+ nsetel += spec_n; /* Spectral values */
+ sprintf(buf,"%d", spec_n);
+ ocg->add_kword(ocg, 0, "SPECTRAL_BANDS",buf, NULL);
+ sprintf(buf,"%f", spec_wl_short);
+ ocg->add_kword(ocg, 0, "SPECTRAL_START_NM",buf, NULL);
+ sprintf(buf,"%f", spec_wl_long);
+ ocg->add_kword(ocg, 0, "SPECTRAL_END_NM",buf, NULL);
+
+ /* Generate fields for spectral values */
+ for (i = 0; i < spec_n; i++) {
+ int nm;
+
+ /* Compute nearest integer wavelength */
+ nm = (int)(spec_wl_short + ((double)i/(spec_n-1.0))
+ * (spec_wl_long - spec_wl_short) + 0.5);
+
+ sprintf(buf,"SPEC_%03d",nm);
+ ocg->add_field(ocg, 0, buf, r_t);
+ }
+ }
+
+ {
+ char fname[100];
+ sprintf(fname, dolab ? "%s_LAB" : "%s_XYZ", ident);
+ ocg->add_kword(ocg, 0, "COLOR_REP", fname, NULL);
+ }
+
+ if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL)
+ error("Malloc failed!");
+
+ /* Read all the test patches in, convert them, */
+ /* and write them out. */
+ for (i = 0; i < npat; i++) {
+ int k = 0;
+ char *id;
+ double odev[ICX_MXINKS], dev[ICX_MXINKS], sep[ICX_MXINKS], PCS[3];
+ xspect out;
+
+ id = ((char *)icg->t[0].fdata[i][si]);
+ for (j = 0; j < nchan; j++) {
+ double dv = *((double *)icg->t[0].fdata[i][chix[j]]) / 100.0;
+ odev[j] = dev[j] = sep[j] = dv;
+ }
+
+ if (gfudge) {
+ int nch;
+
+ if (dosep) /* Figure number of channels into conversion */
+ nch = sep_inn;
+ else
+ nch = inn;
+
+ if (gfudge == 1) { /* Convert W -> RGB */
+ double wval = dev[0];
+ for (j = 0; j < nch; j++)
+ dev[j] = sep[j] = wval;
+
+ } else { /* Convert K->xxxK */
+ int kch;
+ int inmask;
+ double kval = dev[0];
+
+ if (dosep) /* Figure number of channels into conversion */
+ inmask = sep_nmask;
+ else
+ inmask = cnv_nmask;
+
+ if (inmask == 0)
+ error("Input colorspace ambiguous - can't determine if it has black");
+
+ if ((kch = icx_ink2index(inmask, ICX_BLACK)) == -1)
+ error("Can't find black colorant for K fudge");
+ for (j = 0; j < nch; j++) {
+ if (j == kch)
+ dev[j] = sep[j] = kval;
+ else
+ dev[j] = sep[j] = 0.0;
+ }
+ }
+ }
+
+ if (dosep)
+ if (sep_luo->lookup(sep_luo, sep, dev) > 1)
+ error ("%d, %s",icc_icco->errc,icc_icco->err);
+
+ /* Do calibration */
+ if (applycal && cal != NULL)
+ cal->interp(cal, sep, sep);
+
+ /* Add randomness and non-linearity to device values. */
+ /* rdlevel = avg. dev. */
+ /* Note dev/sep is 0-1.0 at this stage */
+ for (j = 0; j < inn; j++) {
+ double dv = sep[j];
+ if (rdlevel > 0.0) {
+ double rr;
+ if (unidist)
+ rr = d_rand(-2.0 * rdlevel, 2.0 * rdlevel);
+ else
+ rr = 1.2533 * rdlevel * norm_rand();
+ dv += rr;
+ if (dv < 0.0)
+ dv = 0.0;
+ else if (dv > 1.0)
+ dv = 1.0;
+ }
+ if (j < 10 && chpow[j] != 1.0) {
+ dv = pow(dv, chpow[j]);
+ }
+ sep[j] = dv;
+ }
+
+ /* Do color conversion */
+ if (icc_luo != NULL) {
+ if (icc_luo->lookup(icc_luo, PCS, sep) > 1)
+ error ("%d, %s",icc_icco->errc,icc_icco->err);
+
+ if (tbp[0] >= 0) { /* Doing black point scaling */
+
+ for (j = 0; j < 3; j++)
+ PCS[j] -= wp[j];
+ icmMulBy3x3(PCS, bpt, PCS);
+ for (j = 0; j < 3; j++)
+ PCS[j] += wp[j];
+ }
+
+ } else if (mlu != NULL) {
+ mlu->lookup(mlu, PCS, sep);
+ if (dospec && spec_n > 0) {
+ mlu->lookup_spec(mlu, &out, sep);
+ }
+ } else if (ti3 != NULL) {
+ int m;
+ double bdif = 1e6;
+ int bix = -1;
+
+ /* Search for the closest device values in TI3 file */
+ for (m = 0; m < ti3_npat; m++) {
+ double dif;
+
+ for (dif = 0.0, j = 0; j < nchan; j++) {
+ double xx;
+
+ xx = (*((double *)ti3->t[0].fdata[m][ti3_chix[j]]) / 100.0) - sep[j];
+ dif += xx * xx;
+ }
+ if (dif < bdif) {
+ bdif = dif;
+ bix = m;
+ }
+ }
+ /* Copy best value over */
+ if (!dosep) /* Doesn't make sense for separation */
+ for (j = 0; j < nchan; j++) {
+ dev[j] = *((double *)ti3->t[0].fdata[bix][ti3_chix[j]]) / 100.0;
+ }
+ for (j = 0; j < 3; j++) {
+ PCS[j] = *((double *)ti3->t[0].fdata[bix][ti3_pcsix[j]]);
+ }
+ if (ti3_isLab && !dolab) { /* Convert Lab to XYZ */
+ icmLab2XYZ(&icmD50, PCS, PCS);
+ } else if (!ti3_isLab && dolab) { /* Convert XYZ to Lab */
+ icmXYZ2Lab(&icmD50, PCS, PCS);
+ } else if (!ti3_isLab) { /* Convert XYZ100 to XYZ1 */
+ PCS[0] /= 100.0;
+ PCS[1] /= 100.0;
+ PCS[2] /= 100.0;
+ }
+ if (dospec && spec_n > 0) {
+ for (j = 0; j < spec_n; j++) {
+ out.spec[j] = *((double *)ti3->t[0].fdata[bix][ti3_spi[j]]);
+ }
+ }
+ }
+
+ setel[k++].c = id;
+
+ for (j = 0; j < nchan; j++)
+ setel[k++].d = 100.0 * odev[j];
+
+ if (dolab == 0) {
+ PCS[0] *= 100.0;
+ PCS[1] *= 100.0;
+ PCS[2] *= 100.0;
+ }
+
+ /* Add randomness. rplevel is avg. dev. */
+ /* Note PCS is 0..100 XYZ or Lab at this point */
+ if (rplevel > 0.0) {
+ double opcs[3];
+ for (j = 0; j < 3; j++) {
+ double dv = PCS[j];
+ double rr;
+ opcs[j] = dv;
+ if (unidist)
+ rr = 100.0 * d_rand(-2.0 * rplevel, 2.0 * rplevel);
+ else
+ rr = 100.0 * 1.2533 * rplevel * norm_rand();
+ dv += rr;
+
+ /* Don't let L*, X, Y or Z go negative */
+ if ((!dolab || j == 0) && dv < 0.0)
+ dv = 0.0;
+ PCS[j] = dv;
+ }
+//printf("~1 pcs %f %f %f -> %f %f %f\n", opcs[0], opcs[1], opcs[2], PCS[0], PCS[1], PCS[2]);
+ }
+
+ setel[k++].d = PCS[0];
+ setel[k++].d = PCS[1];
+ setel[k++].d = PCS[2];
+
+ if (dospec && spec_n > 0) {
+ for (j = 0; j < spec_n; j++) {
+ setel[k++].d = 100.0 * out.spec[j];
+ }
+ }
+
+ ocg->add_setarr(ocg, 0, setel);
+ }
+
+ free(setel);
+ free(ident);
+ free(bident);
+ }
+
+ if (sep_luo != NULL) {
+ sep_luo->del(sep_luo);
+ sep_icco->del(sep_icco);
+ sep_fp->del(sep_fp);
+ }
+
+ /* If there is a calibration, append it to the .ti3 file */
+ if (cal != NULL) {
+ if (cal->write_cgats(cal, ocg) != 0)
+ error("Writing cal error : %s",cal->err);
+ }
+
+ if (cal != NULL)
+ cal->del(cal);
+
+ if (icc_luo != NULL) {
+ /* Cleanup ICC profile */
+ icc_luo->del(icc_luo);
+ icc_icco->del(icc_icco);
+ icc_fp->del(icc_fp);
+ } else if (mlu != NULL) {
+ mlu->del(mlu);
+ } else if (ti3 != NULL) {
+ ti3->del(ti3);
+ }
+
+ if (ocg->write_name(ocg, outname))
+ error("Write error : %s",ocg->err);
+
+ ocg->del(ocg); /* Clean up */
+ icg->del(icg); /* Clean up */
+
+ return 0;
+}
+
+
diff --git a/spectro/hcfr.c b/spectro/hcfr.c
new file mode 100644
index 0000000..9ec6be9
--- /dev/null
+++ b/spectro/hcfr.c
@@ -0,0 +1,878 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * HCFR Association HCFR sensor related functions
+ *
+ * Author: Graeme W. Gill
+ * Date: 20/1/2007
+ *
+ * Copyright 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* SALONEINSTLIB */
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* SALONEINSTLIB */
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "hcfr.h"
+
+static inst_code hcfr_interp_code(inst *pp, int ec);
+
+#define MAX_MES_SIZE 500 /* Maximum normal message reply size */
+#define MAX_RD_SIZE 5000 /* Maximum reading messagle reply size */
+
+/* ==================================================================== */
+
+/* Interpret an icoms error into a HCFR error */
+static int icoms2hcfr_err(int se) {
+ if (se != ICOM_OK)
+ return HCFR_COMS_FAIL;
+ return HCFR_OK;
+}
+
+/* Do a standard command/response echange with the hcfr */
+/* Return the dtp error code */
+static inst_code
+hcfr_command(
+ hcfr *p,
+ char *in, /* In string */
+ char *out, /* Out string */
+ int bsize, /* Out buffer size */
+ double to /* Timeout in seconds */
+) {
+ int rv, se;
+
+ if ((se = p->icom->write_read(p->icom, in, out, bsize, '\n', 1, to)) != 0) {
+ int ec;
+ a1logd(p->log, 1, "hcfr_command: serial i/o failure on write_read '%s'\n",icoms_fix(in));
+ return hcfr_interp_code((inst *)p, icoms2hcfr_err(se));
+ }
+ a1logd(p->log, 4, "hcfr_command: command '%s' returned '%s', value 0x%x\n",
+ icoms_fix(in), icoms_fix(out),HCFR_OK);
+ return hcfr_interp_code((inst *)p, HCFR_OK);
+}
+
+/* Do a break to check coms is working */
+inst_code
+hcfr_break(
+ hcfr *p
+) {
+ int rwbytes; /* Data bytes read or written */
+ int se, rv = inst_ok;
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_CLASS | IUSB_REQ_RECIP_INTERFACE,
+ 0x22, 0, 0, NULL, 0, 1.0);
+
+ rv = hcfr_interp_code((inst *)p, icoms2hcfr_err(se));
+
+ a1logd(p->log, 4, "hcfr_break: done, ICOM err 0x%x\n",se);
+
+ return rv;
+}
+
+/* Flush an pending messages from the device */
+inst_code
+hcfr_flush(
+ hcfr *p
+) {
+ icoms *c = p->icom;
+ char buf[MAX_MES_SIZE];
+ inst_code ev = inst_ok;
+ int rv;
+
+ for (rv = ICOM_OK;;) {
+ rv = c->read(c, buf, MAX_MES_SIZE, '\000', 100000, 0.05);
+ if (rv != ICOM_OK)
+ break; /* Expect timeout with nothing to read */
+ }
+ a1logd(p->log, 5, "hcfr_flush: done\n");
+
+ return inst_ok;
+}
+
+/* Get and check the firmware version */
+inst_code
+hcfr_get_check_version(
+ hcfr *p,
+ int *pmaj,
+ int *pmin
+) {
+ char ibuf[2];
+ char buf[MAX_MES_SIZE];
+ inst_code ev = inst_ok;
+ int maj, min;
+
+ a1logd(p->log, 4, "hcfr_get_check_version: called\n");
+
+ if (p->gotcoms == 0)
+ return inst_internal_error;
+
+ ibuf[0] = HCFR_GET_VERS;
+ ibuf[1] = 0x00;
+
+ if ((ev = hcfr_command(p, ibuf, buf, MAX_MES_SIZE, 1.0)) != inst_ok)
+ return ev;
+
+ if (strlen(buf) < 6) {
+ a1logd(p->log, 1, "hcfr_get_check_version: version string too short\n");
+ return hcfr_interp_code((inst *)p, HCFR_BAD_FIRMWARE);
+ }
+
+ if (sscanf(buf, "v%d.%d", &maj,&min) != 2) {
+ a1logd(p->log, 1, "hcfr_get_check_version: version string doesn't match format\n");
+ return hcfr_interp_code((inst *)p, HCFR_BAD_FIRMWARE);
+ }
+
+ if (maj != HCFR_FIRMWARE_MAJOR_VERSION || min < HCFR_FIRMWARE_MINOR_VERSION) {
+ a1logd(p->log, 1, "hcfr_get_check_version: version string out of range\n");
+ return hcfr_interp_code((inst *)p, HCFR_BAD_FIRMWARE);
+ }
+
+ a1logd(p->log, 4, "hcfr_get_check_version: got firmare version %d.%d\n",maj,min);
+ if (pmaj != NULL)
+ *pmaj = maj;
+ if (pmin != NULL)
+ *pmin = min;
+
+ return inst_ok;
+}
+
+/* Get a raw measurement value */
+inst_code
+hcfr_get_rgb(
+ hcfr *p,
+ double rgb[3] /* return value */
+) {
+ char ibuf[2];
+ char buf[MAX_MES_SIZE], *bp;
+ char vbuf[4];
+ inst_code ev = inst_ok;
+ double mul, div;
+ double vals[8];
+ int onesens = 0;
+ int i;
+
+ a1logd(p->log, 3, "hcfr_get_rgb: called\n");
+
+ if (p->gotcoms == 0)
+ return inst_internal_error;
+
+ ibuf[0] = HCFR_MEAS_RGB /* Read RGB */
+ | HCFR_MEAS_SENS0 /* Use one sensors because it's faster */
+// | HCFR_MEAS_SENS1
+ | HCFR_INTERLACE_0
+// | HCFR_FAST_MEASURE
+ ;
+
+ ibuf[1] = 0x00;
+
+ if ((ev = hcfr_command(p, ibuf, buf, MAX_MES_SIZE, 60.0)) != inst_ok) {
+ a1logd(p->log, 1, "hcfr_get_rgb: hcfr_command failed\n");
+ return ev;
+ }
+
+ if (strlen(buf) < 156) {
+ a1logd(p->log, 1, "hcfr_get_rgb: not enough bytes returned = expected %d, got %d\n",156,strlen(buf));
+ return hcfr_interp_code((inst *)p, HCFR_BAD_READING);
+ }
+
+ if (strncmp(buf, "RGB_1:", 6) == 0)
+ onesens = 1;
+ else if (strncmp(buf, "RGB_2:", 6) != 0) {
+ a1logd(p->log, 1, "hcfr_get_rgb: RGB_1 or RGB_2 not founde\n");
+ return hcfr_interp_code((inst *)p, HCFR_BAD_READING);
+ }
+
+ vbuf[3] = 0x00;
+ bp = buf + 6;
+
+ strncpy(vbuf, bp, 3); div = (double)atoi(vbuf); bp += 3;
+
+ strncpy(vbuf, bp, 3); mul = (double)atoi(vbuf); bp += 3;
+
+ /* Compute all the possible values for 4 colors and 2 sensors */
+ for (i = 0; i < 8; i++) {
+ unsigned int num, den;
+
+ strncpy(vbuf, bp, 3); den = atoi(vbuf); bp += 3;
+ strncpy(vbuf, bp, 3); den = (den << 8) + atoi(vbuf); bp += 3;
+ strncpy(vbuf, bp, 3); den = (den << 8) + atoi(vbuf); bp += 3;
+ strncpy(vbuf, bp, 3); den = (den << 8) + atoi(vbuf); bp += 3;
+
+ strncpy(vbuf, bp, 3); num = atoi(vbuf); bp += 3;
+ strncpy(vbuf, bp, 3); num = (num << 8) + atoi(vbuf); bp += 3;
+
+ if (den == 0) /* Hmm. */
+ vals[i] = -1.0;
+ else
+ vals[i] = 1e4 * (double)num * mul * div / (double)den;
+// a1logd(p->log, 6,"vals[%d] = %f = num %d * mul %f * div %f / den %d\n", i, vals[i], num,mul,div,den);
+ }
+ if (onesens) {
+ rgb[0] = vals[0];
+ rgb[1] = vals[1];
+ rgb[2] = vals[2];
+ } else {
+ rgb[0] = 0.5 * (vals[0] + vals[4]);
+ rgb[1] = 0.5 * (vals[1] + vals[5]);
+ rgb[2] = 0.5 * (vals[2] + vals[6]);
+ }
+ a1logd(p->log, 3, "hcfr_get_rgb: returning value %f %f %f\n",rgb[0],rgb[1],rgb[2]);
+
+ return inst_ok;
+}
+
+/* Compute the calibration matricies. */
+/* The basic calibration data is from my particular HCFR, measured */
+/* against one of my CRT and LCD displays, with the reference XYZ */
+/* derived from my i1pro. */
+inst_code
+hcfr_comp_matrix(
+ hcfr *p
+) {
+ double tmat[3][3];
+ double xmat[3][3];
+ double itmat[3][3];
+
+ /* CRT */
+
+ /* Red test patch, sensor then reference */
+ tmat[0][0] = 71.71880890;
+ tmat[1][0] = 8.53740337;
+ tmat[2][0] = 3.08216218;
+
+ xmat[0][0] = 21.988601;
+ xmat[1][0] = 12.131219;
+ xmat[2][0] = 1.312786;
+
+ /* Green test patch, sensor then reference */
+ tmat[0][1] = 6.26299108;
+ tmat[1][1] = 37.49843127;
+ tmat[2][1] = 15.91104086;
+
+ xmat[0][1] = 13.677691;
+ xmat[1][1] = 28.870823;
+ xmat[2][1] = 5.636190;
+
+ /* Blue test patch, sensor then reference */
+ tmat[0][2] = 1.30620298;
+ tmat[1][2] = 4.62894673;
+ tmat[2][2] = 27.57654019;
+
+ xmat[0][2] = 6.387302;
+ xmat[1][2] = 2.755360;
+ xmat[2][2] = 33.588242;
+
+ /* Compute the inverse */
+ if (icmInverse3x3(itmat, tmat))
+ return hcfr_interp_code((inst *)p, HCFR_CALIB_CALC);
+
+ /* Multiply by target values */
+ icmMul3x3_2(p->crt, xmat, itmat);
+
+ /* LCD */
+
+ /* Red test patch, sensor then reference */
+ tmat[0][0] = 39.94356609;
+ tmat[1][0] = 11.59679928;
+ tmat[2][0] = 8.18430397;
+
+ xmat[0][0] = 51.875052;
+ xmat[1][0] = 30.640815;
+ xmat[2][0] = 4.712397;
+
+ /* Green test patch, sensor then reference */
+ tmat[0][1] = 14.45920285;
+ tmat[1][1] = 33.82116329;
+ tmat[2][1] = 17.64558523;
+
+ xmat[0][1] = 37.482638;
+ xmat[1][1] = 64.670821;
+ xmat[2][1] = 14.554874;
+
+ /* Blue test patch, sensor then reference */
+ tmat[0][2] = 8.29727493;
+ tmat[1][2] = 17.95182031;
+ tmat[2][2] = 38.20123872;
+
+ xmat[0][2] = 25.098392;
+ xmat[1][2] = 23.719352;
+ xmat[2][2] = 108.134087;
+
+ /* Compute the inverse */
+ if (icmInverse3x3(itmat, tmat))
+ return hcfr_interp_code((inst *)p, HCFR_CALIB_CALC);
+
+ /* Multiply by target values */
+ icmMul3x3_2(p->lcd, xmat, itmat);
+
+ return inst_ok;
+}
+
+/* ==================================================================== */
+
+/* Establish communications with a HCFR */
+/* Return HCFR_COMS_FAIL on failure to establish communications */
+static inst_code
+hcfr_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ hcfr *p = (hcfr *) pp;
+ int rsize;
+ long etime;
+ int bi, i, se;
+ inst_code ev = inst_ok;
+ icomuflags usbflags = icomuf_no_open_clear | icomuf_detach;
+
+#if defined(__APPLE__) && defined(__i386__)
+ /* Except on Intel OS X 10.4/5 for some reasone. */
+ /* It would be good if the HCFR had a better USB implementation... */
+ usbflags &= ~icomuf_no_open_clear;
+#endif
+
+ a1logd(p->log, 2, "hcfr_init_coms: About to init USB\n");
+
+ if (p->icom->port_type(p->icom) != icomt_usb) {
+ a1logd(p->log, 1, "hcfr_init_coms: expect hcfr to be USB\n");
+ return hcfr_interp_code((inst *)p, HCFR_UNKNOWN_MODEL);
+ }
+
+ /* Set config, interface, "Serial" write & read end points */
+ /* Note if we clear halt the interface hangs */
+ if ((se = p->icom->set_usb_port(p->icom, 1, 0x03, 0x83, usbflags, 0, NULL)) != ICOM_OK) {
+ a1logd(p->log, 1, "hcfr_init_coms: set_usb_port failed ICOM err 0x%x\n",se);
+ return hcfr_interp_code((inst *)p, icoms2hcfr_err(se));
+ }
+
+ if ((ev = hcfr_break(p)) != inst_ok) {
+ a1logd(p->log, 1, "hcfr_init_coms: break failed\n");
+ return ev;
+ }
+ p->gotcoms = 1;
+
+ a1logd(p->log, 2, "hcfr_init_coms: inited coms OK\n");
+
+ return inst_ok;
+}
+
+static inst_code set_default_disp_type(hcfr *p);
+
+/* Initialise the HCFR */
+/* return non-zero on an error, with dtp error code */
+static inst_code
+hcfr_init_inst(inst *pp) {
+ hcfr *p = (hcfr *)pp;
+ static char buf[MAX_MES_SIZE];
+ inst_code ev = inst_ok;
+
+ a1logd(p->log, 2, "hcfr_init_inst: called\n");
+
+ if (p->gotcoms == 0)
+ return inst_internal_error; /* Must establish coms before calling init */
+
+#ifndef NEVER /* Doesn't work with OSX ? */
+// hcfr_flush(p);
+#endif
+
+ if ((ev = hcfr_get_check_version(p, &p->maj, &p->min)) != inst_ok) {
+ a1logd(p->log, 1, "hcfr_init_inst: check_version failed\n");
+ return ev;
+ }
+
+ if ((ev = hcfr_comp_matrix(p)) != inst_ok) {
+ return ev;
+ }
+
+ p->trig = inst_opt_trig_user;
+
+ /* Setup the default display type */
+ if ((ev = set_default_disp_type(p)) != inst_ok) {
+ return ev;
+ }
+
+ p->inited = 1;
+ a1logd(p->log, 2, "hcfr_init_inst: instrument inited OK\n");
+
+ return inst_ok;
+}
+
+/* Read a single sample */
+/* Return the dtp error code */
+static inst_code
+hcfr_read_sample(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+ipatch *val, /* Pointer to instrument patch value */
+instClamping clamp) { /* NZ if clamp XYZ/Lab to be +ve */
+ hcfr *p = (hcfr *)pp;
+ inst_code ev;
+ double rgb[3];
+ int user_trig = 0;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->trig == inst_opt_trig_user) {
+
+ if (p->uicallback == NULL) {
+ a1logd(p->log, 1, "hcfr: inst_opt_trig_user but no uicallback function set!\n");
+ return inst_unsupported;
+ }
+
+ for (;;) {
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (ev == inst_user_abort)
+ return ev; /* Abort */
+ if (ev == inst_user_trig) {
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ msec_sleep(200);
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ 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)
+ return ev; /* Abort */
+ }
+
+ if ((ev = hcfr_get_rgb(p, rgb)) != inst_ok)
+ return ev;
+
+ if (p->ix == 0) { /* LCD */
+ icmMulBy3x3(val->XYZ, p->lcd, rgb);
+
+ } else if (p->ix == 1) { /* CRT */
+ icmMulBy3x3(val->XYZ, p->crt, rgb);
+
+ } else { /* Raw */
+ val->XYZ[0] = rgb[0];
+ val->XYZ[1] = rgb[1];
+ val->XYZ[2] = rgb[2];
+ }
+
+ /* Apply the colorimeter correction matrix */
+ icmMulBy3x3(val->XYZ, p->ccmat, val->XYZ);
+
+ /* This may not change anything since instrument may clamp */
+ if (clamp)
+ icmClamp3(val->XYZ, val->XYZ);
+
+ val->loc[0] = '\000';
+ val->mtype = inst_mrt_emission;
+ val->XYZ_v = 1; /* These are absolute XYZ readings */
+ val->sp.spec_n = 0;
+ val->duration = 0.0;
+
+ if (user_trig)
+ return inst_user_trig;
+ return inst_ok;
+}
+
+/* Insert a colorimetric correction matrix in the instrument XYZ readings */
+/* This is only valid for colorimetric instruments. */
+/* To remove the matrix, pass NULL for the filter filename */
+inst_code hcfr_col_cor_mat(
+inst *pp,
+double mtx[3][3]
+) {
+ hcfr *p = (hcfr *)pp;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (mtx == NULL) {
+ icmSetUnity3x3(p->ccmat);
+ } else {
+ if (p->cbid == 0) {
+ a1loge(p->log, 1, "hcfr: can't set col_cor_mat over non base display type\n");
+ inst_wrong_setup;
+ }
+ icmCpy3x3(p->ccmat, mtx);
+ }
+
+ return inst_ok;
+}
+
+/* Error codes interpretation */
+static char *
+hcfr_interp_error(inst *pp, int ec) {
+// hcfr *p = (hcfr *)pp;
+ ec &= inst_imask;
+ switch (ec) {
+ case HCFR_INTERNAL_ERROR:
+ return "Internal software error";
+ case HCFR_COMS_FAIL:
+ return "Communications failure";
+ case HCFR_UNKNOWN_MODEL:
+ return "Not a HCFR or DTP52";
+ case HCFR_DATA_PARSE_ERROR:
+ return "Data from DTP didn't parse as expected";
+
+ case HCFR_OK:
+ return "No device error";
+
+ case HCFR_BAD_READING:
+ return "Invalid reading";
+
+ case HCFR_BAD_FIRMWARE:
+ return "Bad firmware version";
+
+ case HCFR_CALIB_CALC:
+ return "Error computing calibration matrix";
+
+ default:
+ return "Unknown error code";
+ }
+}
+
+
+/* Convert a machine specific error code into an abstract dtp code */
+static inst_code
+hcfr_interp_code(inst *pp, int ec) {
+ hcfr *p = (hcfr *)pp;
+
+ ec &= inst_imask;
+ switch (ec) {
+
+ case HCFR_OK:
+ return inst_ok;
+
+ case HCFR_CALIB_CALC:
+ return inst_internal_error | ec;
+
+ case HCFR_COMS_FAIL:
+ return inst_coms_fail | ec;
+
+ case HCFR_UNKNOWN_MODEL:
+ return inst_unknown_model | ec;
+
+ case HCFR_DATA_PARSE_ERROR:
+ return inst_protocol_error | ec;
+
+ case HCFR_BAD_READING:
+ return inst_misread | ec;
+
+// case HCFR_NEEDS_OFFSET_CAL:
+// return inst_needs_cal | ec;
+
+ case HCFR_BAD_FIRMWARE:
+ return inst_hardware_fail | ec;
+ }
+ return inst_other_error | ec;
+}
+
+/* Destroy ourselves */
+static void
+hcfr_del(inst *pp) {
+ hcfr *p = (hcfr *)pp;
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ inst_del_disptype_list(p->dtlist, p->ndtlist);
+ free(p);
+}
+
+/* Return the instrument mode capabilities */
+void hcfr_capabilities(inst *pp,
+inst_mode *pcap1,
+inst2_capability *pcap2,
+inst3_capability *pcap3) {
+ hcfr *p = (hcfr *)pp;
+ inst_mode cap = 0;
+ inst2_capability cap2 = 0;
+
+ cap |= inst_mode_emis_spot
+ | inst_mode_colorimeter
+ ;
+
+ cap2 |= inst2_prog_trig
+ | inst2_user_trig
+ | inst2_disptype
+ | inst2_ccmx
+ ;
+
+ if (pcap1 != NULL)
+ *pcap1 = cap;
+ if (pcap2 != NULL)
+ *pcap2 = cap2;
+ if (pcap3 != NULL)
+ *pcap3 = inst3_none;
+}
+
+/* Check device measurement mode */
+inst_code hcfr_check_mode(inst *pp, inst_mode m) {
+ inst_mode cap;
+
+ if (!pp->gotcoms)
+ return inst_no_coms;
+ if (!pp->inited)
+ return inst_no_init;
+
+ pp->capabilities(pp, &cap, NULL, NULL);
+
+ /* Simple test */
+ if (m & ~cap)
+ return inst_unsupported;
+
+ /* only display emission mode supported */
+ if (!IMODETST(m, inst_mode_emis_spot)) {
+ return inst_unsupported;
+ }
+
+ return inst_ok;
+}
+
+/* Set device measurement mode */
+inst_code hcfr_set_mode(inst *pp, inst_mode m) {
+ inst_code ev;
+
+ if ((ev = hcfr_check_mode(pp, m)) != inst_ok)
+ return ev;
+
+ return inst_ok;
+}
+
+inst_disptypesel hcfr_disptypesel[4] = {
+ {
+ inst_dtflags_default,
+ 0,
+ "l",
+ "LCD display",
+ 0,
+ 0
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 0, /* cbix */
+ "c", /* sel */
+ "CRT display", /* desc */
+ 0, /* refr */
+ 1 /* ix */
+ },
+ {
+ inst_dtflags_none,
+ 1,
+ "R",
+ "Raw Reading",
+ 0,
+ 2
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ 0
+ }
+};
+
+/* Get mode and option details */
+static inst_code hcfr_get_disptypesel(
+inst *pp,
+int *pnsels, /* Return number of display types */
+inst_disptypesel **psels, /* Return the array of display types */
+int allconfig, /* nz to return list for all configs, not just current. */
+int recreate /* nz to re-check for new ccmx & ccss files */
+) {
+ hcfr *p = (hcfr *)pp;
+ inst_code rv = inst_ok;
+
+ /* Create/Re-create a current list of abailable display types */
+ if (p->dtlist == NULL || recreate) {
+ if ((rv = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
+ hcfr_disptypesel, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return rv;
+ }
+
+ if (pnsels != NULL)
+ *pnsels = p->ndtlist;
+
+ if (psels != NULL)
+ *psels = p->dtlist;
+
+ return inst_ok;
+}
+
+/* Given a display type entry, setup for that type */
+static inst_code set_disp_type(hcfr *p, inst_disptypesel *dentry) {
+
+ p->ix = dentry->ix;
+ p->refrmode = dentry->refr;
+ p->cbid = dentry->cbid;
+
+ if (dentry->flags & inst_dtflags_ccmx) {
+ icmCpy3x3(p->ccmat, dentry->mat);
+ } else {
+ icmSetUnity3x3(p->ccmat);
+ }
+
+ return inst_ok;
+}
+
+/* Setup the default display type */
+static inst_code set_default_disp_type(hcfr *p) {
+ inst_code ev;
+ int i;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list((inst *)p, &p->ndtlist, &p->dtlist,
+ hcfr_disptypesel, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ for (i = 0; !(p->dtlist[i].flags & inst_dtflags_end); i++) {
+ if (p->dtlist[i].flags & inst_dtflags_default)
+ break;
+ }
+ if (p->dtlist[i].flags & inst_dtflags_end) {
+ a1loge(p->log, 1, "set_default_disp_type: failed to find type!\n");
+ return inst_internal_error;
+ }
+ if ((ev = set_disp_type(p, &p->dtlist[i])) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+/* Set the display type */
+static inst_code hcfr_set_disptype(inst *pp, int ix) {
+ hcfr *p = (hcfr *)pp;
+ inst_code ev;
+ inst_disptypesel *dentry;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
+ hcfr_disptypesel, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ if (ix < 0 || ix >= p->ndtlist)
+ return inst_unsupported;
+
+ dentry = &p->dtlist[ix];
+
+ if ((ev = set_disp_type(p, dentry)) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+/*
+ * set or reset an optional mode
+ *
+ * Since there is no interaction with the instrument,
+ * was assume that all of these can be done before initialisation.
+ */
+static inst_code
+hcfr_get_set_opt(inst *pp, inst_opt_type m, ...) {
+ hcfr *p = (hcfr *)pp;
+ inst_code ev = inst_ok;
+
+ /* Record the trigger mode */
+ if (m == inst_opt_trig_prog
+ || m == inst_opt_trig_user) {
+ p->trig = m;
+ return inst_ok;
+ }
+
+ /* Get the display type information */
+ if (m == inst_opt_get_dtinfo) {
+ va_list args;
+ int *refrmode, *cbid;
+
+ va_start(args, m);
+ refrmode = va_arg(args, int *);
+ cbid = va_arg(args, int *);
+ va_end(args);
+
+ if (refrmode != NULL)
+ *refrmode = p->refrmode;
+ if (cbid != NULL)
+ *cbid = p->cbid;
+
+ return inst_ok;
+ }
+
+ return inst_unsupported;
+}
+
+/* Constructor */
+extern hcfr *new_hcfr(icoms *icom, instType itype) {
+ hcfr *p;
+ if ((p = (hcfr *)calloc(sizeof(hcfr),1)) == NULL) {
+ a1loge(icom->log, 1, "new_hcfr: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(icom->log);
+
+ p->init_coms = hcfr_init_coms;
+ p->init_inst = hcfr_init_inst;
+ p->capabilities = hcfr_capabilities;
+ p->check_mode = hcfr_check_mode;
+ p->set_mode = hcfr_set_mode;
+ p->get_disptypesel = hcfr_get_disptypesel;
+ p->set_disptype = hcfr_set_disptype;
+ p->get_set_opt = hcfr_get_set_opt;
+ p->read_sample = hcfr_read_sample;
+ p->col_cor_mat = hcfr_col_cor_mat;
+ p->interp_error = hcfr_interp_error;
+ p->del = hcfr_del;
+
+ p->icom = icom;
+ p->itype = icom->itype;
+
+ icmSetUnity3x3(p->ccmat); /* Set the colorimeter correction matrix to do nothing */
+
+ return p;
+}
+
+
diff --git a/spectro/hcfr.h b/spectro/hcfr.h
new file mode 100644
index 0000000..efbbd6c
--- /dev/null
+++ b/spectro/hcfr.h
@@ -0,0 +1,102 @@
+#ifndef HCFR_H
+
+/*
+ * Argyll Color Correction System
+ *
+ * Colorimtre HCFR related defines
+ *
+ * Author: Graeme W. Gill
+ * Date: 20/1/2007
+ *
+ * Copyright 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.
+ */
+
+#include "inst.h"
+
+/* Required minimum firmware version */
+#define HCFR_FIRMWARE_MAJOR_VERSION 5
+#define HCFR_FIRMWARE_MINOR_VERSION 0
+
+
+/* Command byte contents. (A value of 0x00 won't be tranmsitted properly) */
+/* 0xff = get firmware version */
+
+
+#define HCFR_MEAS_RGB 0x01 /* Enable reading RGB sensor values */
+#define HCFR_MEAS_WHITE 0x02 /* Enable reading White values */
+#define HCFR_MEAS_SENS0 0x04 /* Read sensor 0 */
+#define HCFR_MEAS_SENS1 0x08 /* Read sensor 1 */
+#define HCFR_INTERLACE_0 0x00 /* No interlace */
+#define HCFR_INTERLACE_1 0x10 /* 2 way interlace ? */
+#define HCFR_INTERLACE_2 0x20 /* 4 way interlace ?? */
+#define HCFR_INTERLACE_3 0x30 /* ? way interlace ??? */
+#define HCFR_FAST_MEASURE 0x40 /* Fast measure */
+
+#define HCFR_GET_VERS 0xFF /* Get the firmware version number */
+
+/* Note: update hcfr_interp_error() and hcfr_interp_code() in hcfr.c */
+/* if anything of these #defines are added or subtracted */
+
+/* Fake Error codes */
+#define HCFR_INTERNAL_ERROR 0x61 /* Internal software error */
+#define HCFR_COMS_FAIL 0x62 /* Communication failure */
+#define HCFR_UNKNOWN_MODEL 0x63 /* Not an HCFR */
+#define HCFR_DATA_PARSE_ERROR 0x64 /* Read data parsing error */
+
+/* Real error code */
+#define HCFR_OK 0x00
+
+#define HCFR_BAD_FIRMWARE 0x01 /* Bad firmware version */
+
+#define HCFR_BAD_READING 0x30 /* Error doing or parsing reading */
+
+#define HCFR_CALIB_CALC 0x40 /* Error computing calibration matrix */
+
+/* HCFR communication object */
+struct _hcfr {
+ INST_OBJ_BASE
+
+ int maj, min; /* Firmware version */
+
+ double lcd[3][3]; /* CRT RGB->XYZ transformation matrix */
+ double crt[3][3]; /* CRT RGB->XYZ transformation matrix */
+
+ inst_disptypesel *dtlist; /* Display Type list */
+ int ndtlist; /* Number of valid dtlist entries */
+ int ix; /* 0 = CRT, 1 = LCD, 2 = raw RGB from sensors */
+ int cbid; /* calibration base ID, 0 if not a base */
+ int refrmode; /* Refresh mode (always 0) */
+ double ccmat[3][3]; /* Colorimeter correction matrix */
+
+ inst_opt_type trig; /* Reading trigger mode */
+
+ }; typedef struct _hcfr hcfr;
+
+/* Constructor */
+extern hcfr *new_hcfr(icoms *icom, instType itype);
+
+
+#define HCFR_H
+#endif /* HCFR_H */
diff --git a/spectro/hidio.c b/spectro/hidio.c
new file mode 100644
index 0000000..595b707
--- /dev/null
+++ b/spectro/hidio.c
@@ -0,0 +1,866 @@
+
+ /* General USB HID I/O support */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 2007/10/10
+ *
+ * Copyright 2006 - 2013 Graeme W. Gill
+ * All rights reserved.
+ *
+ * (Based on usbio.c)
+ *
+ * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
+ * see the License2.txt file for licencing details.
+ */
+
+/*
+ * TTBD:
+ * As usual, Apple seem to have deprecated the routines used here.
+ * See IOHIDDeviceGetReportWithCallback & IOHIDDeviceSetReportWithCallback
+ * for info on the replacements.
+ */
+
+/* These routines supliement the class code in ntio.c and unixio.c */
+/* with HID specific access routines for devices running on operating */
+/* systems where this is desirable. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <signal.h>
+#if defined(UNIX)
+#include <termios.h>
+#include <errno.h>
+#include <dirent.h>
+#endif
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#else
+#include "sa_config.h"
+#endif
+#include "numsup.h"
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+
+#if defined(NT)
+#include <setupapi.h>
+#endif
+
+#if defined(UNIX_X11)
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#include <sys/types.h>
+#include <usbhid.h>
+#else /* assume Linux */
+# include <asm/types.h>
+# include <linux/hiddev.h>
+#endif
+#ifndef HID_MAX_USAGES /* Workaround Linux Bug ? */
+# define HID_MAX_USAGES 1024
+#endif
+#endif
+
+#if defined(NT)
+
+/* Declartions to enable HID access without using the DDK */
+
+typedef struct _HIDD_ATTRIBUTES {
+ ULONG Size;
+ USHORT VendorID;
+ USHORT ProductID;
+ USHORT VersionNumber;
+} HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES;
+
+void (WINAPI *pHidD_GetHidGuid)(OUT LPGUID HidGuid) = NULL;
+BOOL (WINAPI *pHidD_GetAttributes)(IN HANDLE HidDeviceObject, OUT PHIDD_ATTRIBUTES Attributes) = NULL;
+
+/* See if we can get the wanted function calls */
+/* Return nz if OK */
+static int setup_dyn_calls() {
+ static int dyn_inited = 0;
+
+ if (dyn_inited == 0) {
+ dyn_inited = 1;
+
+ pHidD_GetHidGuid = (void (WINAPI*)(LPGUID))
+ GetProcAddress(LoadLibrary("HID"), "HidD_GetHidGuid");
+ pHidD_GetAttributes = (BOOL (WINAPI*)(HANDLE, PHIDD_ATTRIBUTES))
+ GetProcAddress(LoadLibrary("HID"), "HidD_GetAttributes");
+
+ if (pHidD_GetHidGuid == NULL
+ || pHidD_GetAttributes == NULL)
+ dyn_inited = 0;
+ }
+ return dyn_inited;
+}
+
+#endif
+
+
+/* Add paths to USB connected instruments, to the existing */
+/* icompath paths in the icoms structure. */
+/* return com error */
+int hid_get_paths(icompaths *p) {
+
+ a1logd(p->log, 8, "hid_get_paths: called\n");
+
+#if defined(NT)
+ {
+ GUID HidGuid;
+ HDEVINFO hdinfo;
+ SP_DEVICE_INTERFACE_DATA did;
+#define DIDD_BUFSIZE sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + (sizeof(TCHAR)*MAX_PATH)
+ char *buf[DIDD_BUFSIZE];
+ PSP_DEVICE_INTERFACE_DETAIL_DATA pdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)buf;
+ SP_DEVINFO_DATA dinfod;
+ int i;
+ unsigned int VendorID = 0, ProductID = 0;
+
+ /* Make sure we've dynamically linked */
+ if (setup_dyn_calls() == 0) {
+ a1loge(p->log, ICOM_SYS, "hid_get_paths() Dynamic linking to hid.dll failed\n");
+ return ICOM_SYS;
+ }
+
+ /* Get the device interface GUID for HIDClass devices */
+ (*pHidD_GetHidGuid)(&HidGuid);
+
+ /* Return device information for all devices of the HID class */
+ hdinfo = SetupDiGetClassDevs(&HidGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+ if (hdinfo == INVALID_HANDLE_VALUE) {
+ a1loge(p->log, ICOM_SYS, "hid_get_paths() SetupDiGetClassDevs failed\n");
+ return ICOM_SYS;
+ }
+
+ /* Get each devices interface data in turn */
+ did.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
+ pdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)buf;
+ pdidd->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
+ dinfod.cbSize = sizeof(SP_DEVINFO_DATA);
+ for (i = 0; ; i++) {
+ instType itype;
+
+ if (SetupDiEnumDeviceInterfaces(hdinfo, NULL, &HidGuid, i, &did) == 0) {
+ if (GetLastError() == ERROR_NO_MORE_ITEMS) {
+ break;
+ }
+ a1loge(p->log, ICOM_SYS, "hid_get_paths() SetupDiEnumDeviceInterfaces failed\n");
+ return ICOM_SYS;
+ }
+ if (SetupDiGetDeviceInterfaceDetail(hdinfo, &did, pdidd, DIDD_BUFSIZE, NULL, &dinfod)
+ == 0) {
+ a1loge(p->log, ICOM_SYS, "hid_get_paths() SetupDiGetDeviceInterfaceDetail failed\n");
+ return ICOM_SYS;
+ }
+
+ /* Extract the vid and pid from the device path */
+ {
+ int gotid;
+ char *cp, buf[20];
+
+ for(gotid = 0;;) {
+ if ((cp = strchr(pdidd->DevicePath, 'v')) == NULL)
+ break;
+ if (strlen(cp) < 8)
+ break;
+ if (cp[1] != 'i' || cp[2] != 'd' || cp[3] != '_')
+ break;
+ memcpy(buf, cp + 4, 4);
+ buf[4] = '\000';
+ if (sscanf(buf, "%x", &VendorID) != 1)
+ break;
+ if ((cp = strchr(pdidd->DevicePath, 'p')) == NULL)
+ break;
+ if (strlen(cp) < 8)
+ break;
+ if (cp[1] != 'i' || cp[2] != 'd' || cp[3] != '_')
+ break;
+ memcpy(buf, cp + 4, 4);
+ buf[4] = '\000';
+ if (sscanf(buf, "%x", &ProductID) != 1)
+ break;
+ gotid = 1;
+ break;
+ }
+ if (!gotid) {
+ a1logd(p->log, 1, "found HID device '%s', inst %d but unable get PID and VID\n",pdidd->DevicePath, dinfod.DevInst);
+ continue;
+ }
+ }
+
+ /* If it's a device we're looking for */
+ if ((itype = inst_usb_match(VendorID, ProductID, 0)) != instUnknown) {
+ struct hid_idevice *hidd;
+ char pname[400];
+
+ /* Create human readable path/identification */
+ sprintf(pname,"hid:/%d (%s)", dinfod.DevInst, inst_name(itype));
+
+ a1logd(p->log, 2, "found HID device '%s', inst %d that we're looking for\n",pdidd->DevicePath, dinfod.DevInst);
+
+ if ((hidd = (struct hid_idevice *) calloc(sizeof(struct hid_idevice), 1)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "hid_get_paths() calloc failed!\n");
+ return ICOM_SYS;
+ }
+ if ((hidd->dpath = strdup(pdidd->DevicePath)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "hid_get_paths() calloc failed!\n");
+ return ICOM_SYS;
+ }
+
+ /* Add the path to the list */
+ p->add_hid(p, pname, VendorID, ProductID, 0, hidd, itype);
+
+ } else {
+ a1logd(p->log, 6, "found HID device '%s', inst %d but not one we're looking for\n",pdidd->DevicePath, dinfod.DevInst);
+ }
+ }
+
+ /* Now we're done with the hdifo */
+ if (SetupDiDestroyDeviceInfoList(hdinfo) == 0) {
+ a1loge(p->log, ICOM_SYS, "SetupDiDestroyDeviceInfoList failed\n");
+ return ICOM_SYS;
+ }
+ }
+#endif /* NT */
+
+#ifdef __APPLE__
+ {
+ kern_return_t kstat;
+ CFMutableDictionaryRef sdict; /* HID Device dictionary */
+ io_iterator_t mit; /* Matching itterator */
+
+ /* Get dictionary of HID devices */
+ if ((sdict = IOServiceMatching(kIOHIDDeviceKey)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "hid_get_paths() IOServiceMatching returned a NULL dictionary\n");
+ return ICOM_SYS;
+ }
+
+ /* Init itterator to find matching types. Consumes sdict reference */
+ if ((kstat = IOServiceGetMatchingServices(kIOMasterPortDefault, sdict, &mit))
+ != KERN_SUCCESS) {
+ a1loge(p->log, ICOM_SYS, "hid_get_paths() IOServiceGetMatchingServices returned %d\n", kstat);
+ return ICOM_SYS;
+ }
+
+ /* Find all the matching HID devices */
+ for (;;) {
+ io_object_t ioob; /* HID object found */
+ CFNumberRef vref, pref; /* HID Vendor and Product ID propeties */
+ CFNumberRef lidpref; /* Location ID properties */
+ unsigned int vid = 0, pid = 0, lid = 0;
+ instType itype;
+
+ if ((ioob = IOIteratorNext(mit)) == 0)
+ break;
+
+ /* Get the two properies we need. [ Doing IORegistryEntryCreateCFProperty() is much faster */
+ /* than IORegistryEntryCreateCFProperties() in some cases.] */
+ if ((vref = IORegistryEntryCreateCFProperty(ioob, CFSTR(kIOHIDVendorIDKey),
+ kCFAllocatorDefault,kNilOptions)) != 0) {
+ CFNumberGetValue(vref, kCFNumberIntType, &vid);
+ CFRelease(vref);
+ }
+ if ((pref = IORegistryEntryCreateCFProperty(ioob, CFSTR(kIOHIDProductIDKey),
+ kCFAllocatorDefault,kNilOptions)) != 0) {
+ CFNumberGetValue(pref, kCFNumberIntType, &pid);
+ CFRelease(pref);
+ }
+ if ((lidpref = IORegistryEntryCreateCFProperty(ioob, CFSTR("LocationID"),
+ kCFAllocatorDefault,kNilOptions)) != 0) {
+ CFNumberGetValue(lidpref, kCFNumberIntType, &lid);
+ CFRelease(lidpref);
+ }
+
+ /* If it's a device we're looking for */
+ if ((itype = inst_usb_match(vid, pid, 0)) != instUnknown) {
+ struct hid_idevice *hidd;
+ char pname[400];
+
+ a1logd(p->log, 2, "found HID device '%s' lid 0x%x that we're looking for\n",inst_name(itype), lid);
+
+ /* Create human readable path/identification */
+ sprintf(pname,"hid%d: (%s)", lid >> 20, inst_name(itype));
+
+ if ((hidd = (struct hid_idevice *)calloc(sizeof(struct hid_idevice), 1)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "hid_get_paths calloc failed!\n");
+ return ICOM_SYS;
+ }
+ hidd->lid = lid;
+ hidd->ioob = ioob;
+ ioob = 0; /* Don't release it */
+
+ /* Add the path to the list */
+ p->add_hid(p, pname, vid, pid, 0, hidd, itype);
+ }
+ if (ioob != 0) /* If we haven't kept it */
+ IOObjectRelease(ioob); /* Release found object */
+ }
+ IOObjectRelease(mit); /* Release the itterator */
+ }
+#endif /* __APPLE__ */
+
+#if defined(UNIX_X11)
+
+ /* This is how we'd go about adding HID support for Linux, IF it */
+ /* was actually capable of communicating application composed reports - */
+ /* which it is not, so HID seems pretty busted on Linux.. */
+#ifdef NEVER
+ /* We need to scan for /dev/hiddev* or /dev/usb/hiddev* device names, */
+ /* and then read their vid & pid */
+ {
+ int i;
+ char *devds[] = { /* Typical locations hiddev will appear under */
+ "/dev",
+ "/dev/usb",
+ ""
+ };
+
+ /* For each possible device directory */
+ for (i = 0;;i++) {
+ DIR *dir;
+ struct dirent *dentry;
+
+ if (devds[i][0] == '\000')
+ break;
+ if ((dir = opendir(devds[i])) == NULL)
+ continue;
+ while ((dentry = readdir(dir)) != NULL) {
+ if (strncmp(dentry->d_name, "hiddev", 6) == 0) {
+ char dpath[PATH_MAX];
+ int fd;
+
+ strcpy(dpath, devds[i]);
+ strcat(dpath, "/");
+ strcat(dpath, dentry->d_name);
+// a1logd(p->log, 8, "found hid device '%s'\n",dpath);
+ /* Open it and see what VID and PID it is */
+ if ((fd = open(dpath, O_RDONLY)) >= 0) {
+ struct hiddev_devinfo hidinfo;
+ if (ioctl(fd, HIDIOCGDEVINFO, &hidinfo) < 0)
+ a1logd(p->log, 1, "Unable to get HID info for '%s'\n",dpath);
+ else {
+// a1logd(p->log, 8,"busnum = %d, devnum = %d, vid = 0x%x, pid = 0x%x\n",
+// hidinfo.busnum, hidinfo.devnum, hidinfo.vendor, hidinfo.product);
+ }
+ close(fd);
+ }
+// More hotplug/udev magic needed to make the device accesible !
+//else a1logd(p->log, 8,"failed to open '%s' err %d\n",dpath,fd);
+ }
+ }
+ closedir(dir);
+ }
+ }
+#endif /* NEVER */
+#endif /* UNIX_X11 */
+
+ a1logd(p->log, 8, "icoms_get_paths: returning %d paths and ICOM_OK\n",p->npaths);
+
+ return ICOM_OK;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Close an open HID port */
+/* If we don't do this, the port and/or the device may be left in an unusable state. */
+void hid_close_port(icoms *p) {
+
+ a1logd(p->log, 8, "hid_close_port: called\n");
+
+ if (p->is_open && p->hidd != NULL) {
+
+#if defined(NT)
+ CloseHandle(p->hidd->ols.hEvent);
+ CloseHandle(p->hidd->fh);
+#endif /* NT */
+
+#ifdef __APPLE__
+ IOObjectRelease(p->hidd->port);
+ p->hidd->port = 0;
+
+ if (p->hidd->evsrc != NULL)
+ CFRelease(p->hidd->evsrc);
+ p->hidd->evsrc = NULL;
+
+ p->hidd->rlr = NULL;
+
+ if ((*p->hidd->device)->close(p->hidd->device) != kIOReturnSuccess) {
+ a1loge(p->log, ICOM_SYS, "hid_close_port: closing HID port '%s' failed",p->name);
+ return;
+ }
+
+ if ((*p->hidd->device)->Release(p->hidd->device) != kIOReturnSuccess) {
+ a1loge(p->log, ICOM_SYS, "hid_close_port: Releasing HID port '%s' failed",p->name);
+ }
+ p->hidd->device = NULL;
+#endif /* __APPLE__ */
+
+ p->is_open = 0;
+ a1logd(p->log, 8, "hid_close_port: has been released and closed\n");
+ }
+
+ /* Find it and delete it from our static cleanup list */
+ usb_delete_from_cleanup_list(p);
+}
+
+
+/* Open an HID port for all our uses. */
+/* This always re-opens the port */
+/* return icom error */
+static int hid_open_port(
+icoms *p,
+icomuflags hidflags, /* Any special handling flags */
+int retries, /* > 0 if we should retry set_configuration (100msec) */
+char **pnames /* List of process names to try and kill before opening */
+) {
+ /* Make sure the port is open */
+ if (!p->is_open) {
+ a1logd(p->log, 8, "hid_open_port: about to open HID port '%s'\n",p->name);
+
+ p->uflags = hidflags;
+
+#if defined(NT)
+ {
+ int tries = 0;
+
+ for (tries = 0; retries >= 0; retries--, tries++) {
+ /* Open the device */
+ if ((p->hidd->fh = CreateFile(p->hidd->dpath, GENERIC_READ|GENERIC_WRITE,
+ 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL))
+ != INVALID_HANDLE_VALUE) {
+ memset(&p->hidd->ols,0,sizeof(OVERLAPPED));
+ if ((p->hidd->ols.hEvent = CreateEvent(NULL, 0, 0, NULL)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "hid_open_port: Failed to create HID "
+ "Event with %d'\n",GetLastError());
+ return ICOM_SYS;
+ }
+ break;
+ }
+ if (tries > 0 && pnames != NULL) {
+ /* Open failed. This could be the i1ProfileTray.exe */
+ kill_nprocess(pnames, p->log);
+ msec_sleep(100);
+ }
+ }
+ if (p->hidd->fh == INVALID_HANDLE_VALUE) {
+ a1loge(p->log, ICOM_SYS, "hid_open_port: Failed to open "
+ "HID '%s' with %d\n",GetLastError());
+ return ICOM_SYS;
+ }
+ }
+#endif /* NT */
+
+#ifdef __APPLE__
+ {
+ IOCFPlugInInterface **piif = NULL;
+ IOReturn result;
+ SInt32 score;
+
+ if ((result = IOCreatePlugInInterfaceForService(p->hidd->ioob, kIOHIDDeviceUserClientTypeID,
+ kIOCFPlugInInterfaceID, &piif, &score) != kIOReturnSuccess || piif == NULL)) {
+ a1loge(p->log, ICOM_SYS, "hid_open_port: Failed to get piif for "
+ "HID device '%s', result 0x%x, piif 0x%x\n",p->name,result,piif);
+ return ICOM_SYS;
+ }
+
+ p->hidd->device = NULL;
+ if ((*piif)->QueryInterface(piif,
+ CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID122), (LPVOID)&p->hidd->device)
+ != kIOReturnSuccess || p->hidd->device == NULL) {
+ a1loge(p->log, ICOM_SYS, "hid_open_port: Getting HID device '%s' failed",p->name);
+ return ICOM_SYS;
+ }
+ (*piif)->Release(piif); /* delete intermediate object */
+
+ if ((*p->hidd->device)->open(p->hidd->device, kIOHIDOptionsTypeSeizeDevice)
+ != kIOReturnSuccess) {
+ a1loge(p->log, ICOM_SYS, "hid_open_port: Opening HID device '%s' failed",p->name);
+ return ICOM_SYS;
+ }
+
+ /* Setup to handle interrupt read callbacks */
+ p->hidd->port = 0;
+ if ((*(p->hidd->device))->createAsyncPort(p->hidd->device, &p->hidd->port)
+ != kIOReturnSuccess || p->hidd->port == 0) {
+ a1loge(p->log, ICOM_SYS, "hid_open_port: Creating port on "
+ "HID device '%s' failed",p->name);
+ return ICOM_SYS;
+ }
+
+ p->hidd->evsrc = NULL;
+ if ((*(p->hidd->device))->createAsyncEventSource(p->hidd->device, &p->hidd->evsrc)
+ != kIOReturnSuccess || p->hidd->evsrc == NULL) {
+ a1loge(p->log, ICOM_SYS, "hid_open_port: Creating event source on "
+ "HID device '%s' failed",p->name);
+ return ICOM_SYS;
+ }
+
+ }
+#endif /* __APPLE__ */
+
+ p->is_open = 1;
+ a1logd(p->log, 8, "hid_open_port: HID port is now open\n");
+ }
+
+ /* Install the cleanup signal handlers, and add to our cleanup list */
+ usb_install_signal_handlers(p);
+
+ return ICOM_OK;
+}
+
+/* ========================================================= */
+
+#ifdef __APPLE__
+
+/* HID Interrupt callback for OS X */
+static void hid_read_callback(
+void *target,
+IOReturn result,
+void *refcon,
+void *sender,
+#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050
+uint32_t size
+#else
+UInt32 size
+#endif
+) {
+ icoms *p = (icoms *)target;
+
+ a1logd(p->log, 8, "HID callback called with size %d, result 0x%x\n",size,result);
+ p->hidd->result = result;
+ p->hidd->bread = size;
+ CFRunLoopStop(p->hidd->rlr); /* We're done */
+}
+
+#endif /* __APPLE__ */
+
+/* HID Interrupt pipe Read */
+/* Don't retry on a short read, return ICOM_SHORT. */
+/* [Probably uses the control pipe. We need a different */
+/* call for reading messages from the interrupt pipe] */
+static int
+icoms_hid_read(icoms *p,
+ unsigned char *rbuf, /* Read buffer */
+ int bsize, /* Bytes to read */
+ int *breadp, /* Bytes read */
+ double tout /* Timeout in seconds */
+) {
+ int retrv = ICOM_OK; /* Returned error value */
+ int bread = 0;
+
+ if (!p->is_open) {
+ a1loge(p->log, ICOM_SYS, "icoms_hid_read: device not initialised\n");
+ return ICOM_SYS;
+ }
+
+#if defined(NT)
+ {
+ unsigned char *rbuf2;
+
+ /* Create a copy of the data recieved with one more byte */
+ if ((rbuf2 = malloc(bsize + 1)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "icoms_hid_read: malloc failed\n");
+ return ICOM_SYS;
+ }
+ rbuf2[0] = 0;
+ if (ReadFile(p->hidd->fh, rbuf2, bsize+1, (LPDWORD)&bread, &p->hidd->ols) == 0) {
+ if (GetLastError() != ERROR_IO_PENDING) {
+ retrv = ICOM_USBR;
+ } else {
+ int res;
+ res = WaitForSingleObject(p->hidd->ols.hEvent, (int)(tout * 1000.0 + 0.5));
+ if (res == WAIT_FAILED) {
+ a1loge(p->log, ICOM_SYS, "icoms_hid_read: HID wait on read failed\n");
+ return ICOM_SYS;
+ } else if (res == WAIT_TIMEOUT) {
+ CancelIo(p->hidd->fh);
+ retrv = ICOM_TO;
+ bread = 0;
+ } else {
+ bread = p->hidd->ols.InternalHigh;
+ }
+ }
+ }
+ if (bread > 0)
+ bread--;
+ memmove(rbuf,rbuf2+1,bsize);
+ free(rbuf2);
+ }
+#endif /* NT */
+
+#ifdef __APPLE__
+#ifndef NEVER
+ {
+ IOReturn result;
+
+ /* Setup for callback */
+ p->hidd->result = -1;
+ p->hidd->bread = 0;
+
+ if ((*(p->hidd->device))->setInterruptReportHandlerCallback(p->hidd->device,
+ rbuf, bsize, hid_read_callback, (void *)p, NULL) != kIOReturnSuccess) {
+ a1loge(p->log, ICOM_SYS, "icoms_hid_read: Setting callback handler for "
+ "HID '%s' failed\n", p->name);
+ return ICOM_SYS;
+ }
+ /* Call runloop, but exit after handling one callback */
+ p->hidd->rlr = CFRunLoopGetCurrent();
+ CFRunLoopAddSource(p->hidd->rlr, p->hidd->evsrc, kCFRunLoopDefaultMode);
+
+ if ((*(p->hidd->device))->startAllQueues(p->hidd->device) != kIOReturnSuccess) {
+ a1loge(p->log, ICOM_SYS, "icoms_hid_read: Starting queues for "
+ "HID '%s' failed\n", p->name);
+ return ICOM_SYS;
+ }
+
+ result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, tout, false);
+
+ if ((*(p->hidd->device))->stopAllQueues(p->hidd->device) != kIOReturnSuccess) {
+ a1loge(p->log, ICOM_SYS, "icoms_hid_read: Stopping queues for "
+ "HID '%s' failed\n", p->name);
+ return ICOM_SYS;
+ }
+ if (result == kCFRunLoopRunTimedOut) {
+ retrv = ICOM_TO;
+ } else if (result != kCFRunLoopRunStopped) {
+ retrv = ICOM_USBR;
+ }
+ CFRunLoopRemoveSource(p->hidd->rlr, p->hidd->evsrc, kCFRunLoopDefaultMode);
+
+ if (p->hidd->result == -1) { /* Callback wasn't called */
+ retrv = ICOM_TO;
+ } else if (p->hidd->result != kIOReturnSuccess) {
+ a1loge(p->log, ICOM_SYS, "icoms_hid_read: Callback for "
+ "HID '%s' got unexpected return value\n", p->name);
+ return ICOM_SYS;
+ }
+ bread = p->hidd->bread;
+ }
+#else // NEVER
+ /* This doesn't work. Don't know why */
+ /* Returns 0xe000404f = kIOUSBPipeStalled, Pipe has stalled, error needs to be cleared */
+ {
+ IOReturn result;
+ if ((result = (*p->hidd->device)->getReport(
+ p->hidd->device,
+ kIOHIDReportTypeInput,
+ 9, /* Bulk or Interrupt transfer */
+ (void *)rbuf,
+ (UInt32 *)&bsize,
+ (unsigned int)(tout * 1000.0 + 0.5),
+ NULL, NULL, NULL)
+ ) != kIOReturnSuccess) {
+ /* (We could detect other error codes and translate them to ICOM) */
+ if (result == kIOReturnTimeout)
+ retrv = ICOM_TO;
+ else
+ retrv = ICOM_USBW;
+ } else {
+ bread = bsize;
+ }
+ }
+#endif // NEVER
+#endif /* __APPLE__ */
+
+ if (breadp != NULL)
+ *breadp = bread;
+
+ a1logd(p->log, 8, "icoms_hid_read: About to return hid read %d bytes, ICOM err 0x%x\n",
+ bread, retrv);
+
+ return retrv;
+}
+
+/* - - - - - - - - - - - - - */
+
+/* HID Command Pipe Write */
+/* Don't retry on a short read, return ICOM_SHORT. */
+static int
+icoms_hid_write(icoms *p,
+ unsigned char *wbuf, /* Write buffer */
+ int bsize, /* Bytes to write */
+ int *bwrittenp, /* Bytes written */
+ double tout /* Timeout in seconds */
+) {
+ int retrv = ICOM_OK; /* Returned error value */
+ int bwritten = 0;
+
+ if (!p->is_open) {
+ a1loge(p->log, ICOM_SYS, "icoms_hid_write: device not initialised\n");
+ return ICOM_SYS;
+ }
+
+#if defined(NT)
+ {
+ unsigned char *wbuf2;
+
+ /* Create a copy of the data to send with one more byte */
+ if ((wbuf2 = malloc(bsize + 1)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "icoms_hid_write: malloc failed\n");
+ return ICOM_SYS;
+ }
+ memmove(wbuf2+1,wbuf,bsize);
+ wbuf2[0] = 0; /* Extra report ID byte (why ?) */
+ if (WriteFile(p->hidd->fh, wbuf2, bsize+1, (LPDWORD)&bwritten, &p->hidd->ols) == 0) {
+ if (GetLastError() != ERROR_IO_PENDING) {
+ retrv = ICOM_USBW;
+ } else {
+ int res;
+ res = WaitForSingleObject(p->hidd->ols.hEvent, (int)(tout * 1000.0 + 0.5));
+ if (res == WAIT_FAILED) {
+ a1loge(p->log, ICOM_SYS, "icoms_hid_write: HID wait on write failed\n");
+ return ICOM_SYS;
+ }
+ else if (res == WAIT_TIMEOUT) {
+ CancelIo(p->hidd->fh);
+ retrv = ICOM_TO;
+ bwritten = 0;
+ } else {
+ bwritten = p->hidd->ols.InternalHigh;
+ }
+ }
+ }
+ if (bwritten > 0)
+ bwritten--;
+
+ free(wbuf2);
+ }
+#endif /* NT */
+
+#ifdef __APPLE__
+ {
+ IOReturn result;
+ if ((result = (*p->hidd->device)->setReport(
+ p->hidd->device,
+ kIOHIDReportTypeOutput,
+ 9, /* Bulk or Interrupt transfer */
+ (void *)wbuf,
+ bsize,
+ (unsigned int)(tout * 1000.0 + 0.5),
+ NULL, NULL, NULL)) != kIOReturnSuccess) {
+ /* (We could detect other error codes and translate them to ICOM) */
+ if (result == kIOReturnTimeout)
+ retrv = ICOM_TO;
+ else
+ retrv = ICOM_USBW;
+ } else {
+ bwritten = bsize;
+ }
+ }
+#endif /* __APPLE__ */
+
+ if (bwrittenp != NULL)
+ *bwrittenp = bwritten;
+
+ a1logd(p->log, 8, "icoms_hid_write: wrote %d bytes, ICOM err 0x%x\n",bwritten, retrv);
+
+ return retrv;
+}
+
+/* ------------------------------------------------- */
+/* Set the hid port number and characteristics. */
+/* This may be called to re-establish a connection that has failed */
+/* return icom error */
+static int
+icoms_set_hid_port(
+icoms *p,
+icomuflags hidflags, /* Any special handling flags */
+int retries, /* > 0 if we should retry set_configuration (100msec) */
+char **pnames /* List of process names to try and kill before opening */
+) {
+ int rv = ICOM_OK;
+
+ a1logd(p->log, 8, "icoms_set_hid_port: About to set HID port characteristics\n");
+
+ if (p->is_open)
+ p->close_port(p);
+
+ if (p->port_type(p) == icomt_hid) {
+
+ if ((rv = hid_open_port(p, hidflags, retries, pnames)) != ICOM_OK)
+ return rv;
+
+ p->write = NULL;
+ p->read = NULL;
+
+ }
+ a1logd(p->log, 8, "icoms_set_hid_port: HID port characteristics set ok\n");
+
+ return rv;
+}
+
+/* Copy hid_idevice contents from icompaths to icom */
+/* return icom error */
+int hid_copy_hid_idevice(icoms *d, icompath *s) {
+
+ if (s->hidd == NULL) {
+ d->hidd = NULL;
+ return ICOM_OK;
+ }
+
+ if ((d->hidd = calloc(sizeof(struct hid_idevice), 1)) == NULL) {
+ a1loge(d->log, ICOM_SYS, "hid_copy_hid_idevice: malloc failed\n");
+ return ICOM_SYS;
+ }
+
+#if defined(NT)
+ if ((d->hidd->dpath = strdup(s->hidd->dpath)) == NULL) {
+ a1loge(d->log, ICOM_SYS, "hid_copy_hid_idevice: malloc\n");
+ return ICOM_SYS;
+ }
+#endif
+#if defined(__APPLE__)
+ d->hidd->ioob = s->hidd->ioob;
+ IOObjectRetain(d->hidd->ioob);
+#endif
+#if defined (UNIX_X11)
+#endif
+ return ICOM_OK;
+}
+
+/* Cleanup and then free a hid_del_hid_idevice */
+void hid_del_hid_idevice(struct hid_idevice *hidd) {
+ if (hidd == NULL)
+ return;
+
+#if defined(NT)
+ if (hidd->dpath != NULL)
+ free(hidd->dpath);
+#endif
+#if defined(__APPLE__)
+ if (hidd->ioob != 0)
+ IOObjectRelease(hidd->ioob);
+#endif
+#if defined (UNIX_X11)
+#endif
+ free(hidd);
+}
+
+/* Cleanup any HID specific icoms info */
+void hid_del_hid(icoms *p) {
+ hid_del_hid_idevice(p->hidd);
+}
+
+
+/* ---------------------------------------------------------------------------------*/
+
+/* Set the HID specific icoms methods */
+void hid_set_hid_methods(
+icoms *p
+) {
+ p->set_hid_port = icoms_set_hid_port;
+ p->hid_read = icoms_hid_read;
+ p->hid_write = icoms_hid_write;
+}
+
+/* ---------------------------------------------------------------------------------*/
diff --git a/spectro/hidio.h b/spectro/hidio.h
new file mode 100644
index 0000000..b25fe03
--- /dev/null
+++ b/spectro/hidio.h
@@ -0,0 +1,87 @@
+
+#ifndef HIDIO_H
+
+ /* General USB HID I/O support */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 2007/10/10
+ *
+ * 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.
+ */
+
+/* These routines supliement the class code in ntio.c and unixio.c */
+
+#ifdef __APPLE__
+#include <sys/param.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/IOCFPlugIn.h>
+#include <IOKit/hid/IOHIDLib.h>
+#include <IOKit/hid/IOHIDKeys.h>
+#include <CoreFoundation/CoreFoundation.h>
+#endif /* __APPLE__ */
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/* Add or modify paths to USB HID connected instruments, to the existing */
+/* icompath paths in the icoms structure, if HID access is supported. */
+/* return icom error */
+int hid_get_paths(struct _icompaths *p);
+
+void hid_close_port(icoms *p);
+
+/* Set the HID specific icoms methods */
+void hid_set_hid_methods(icoms *p);
+
+/* Copy hid_idevice contents */
+/* return icom error */
+int hid_copy_hid_idevice(icoms *d, icompath *s);
+
+/* Cleanup and then free a hid_del_hid_idevice */
+void hid_del_hid_idevice(struct hid_idevice *dev);
+
+/* Cleanup any HID specific icoms info */
+void hid_del_hid(icoms *p);
+
+/* Opaque structure to hid OS dependent HID details */
+struct hid_idevice {
+#if defined(NT)
+ char *dpath; /* Device path */
+ /* Stuff setup when device is open: */
+ HANDLE fh; /* File handle for write/read */
+ OVERLAPPED ols; /* Overlapped structure for write/read */
+#endif
+#if defined(__APPLE__)
+ int lid; /* Location ID */
+ io_object_t ioob; /* Object to open */
+ /* Stuff setup when device is open: */
+ IOHIDDeviceInterface122 **device; /* OS X HID device we've opened */
+ mach_port_t port; /* Other stuff present when opened */
+ CFRunLoopSourceRef evsrc;
+ CFRunLoopRef rlr;
+ IOReturn result;
+ int bread; /* Bytes read by callback */
+#endif
+#if defined (UNIX) && !defined(__APPLE__)
+ int temp; /* Shut the compiler up */
+#endif
+};
+
+/* Cleanup and then free an hidd entry */
+void hid_del_hid_idevice(struct hid_idevice *hidd);
+
+#ifdef __cplusplus
+ }
+#endif
+
+#define HIDIO_H
+#endif /* HIDIO_H */
+
diff --git a/spectro/huey.c b/spectro/huey.c
new file mode 100644
index 0000000..f8d8308
--- /dev/null
+++ b/spectro/huey.c
@@ -0,0 +1,1663 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * GretagMacbeth Huey related functions
+ *
+ * Author: Graeme W. Gill
+ * Date: 18/10/2006
+ *
+ * Copyright 2006 - 2013, Graeme W. Gill
+ * All rights reserved.
+ *
+ * (Based on i1disp.c)
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#include <math.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* SALONEINSTLIB */
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* SALONEINSTLIB */
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "huey.h"
+
+#define dbgo stderr
+
+static inst_code huey_interp_code(inst *pp, int ec);
+static inst_code huey_check_unlock(huey *p);
+
+#define CALFACTOR 3.428 /* Emissive magic calibration factor */
+#define AMB_SCALE_FACTOR 5.772e-3 /* Ambient mode scale factor */
+ /* This is only approximate, and were derived */
+ /* by matching readings from the i1pro. */
+
+/* ------------------------------------------------------------------------ */
+/* Implementation */
+
+/* Interpret an icoms error into a HUEY error */
+/* If torc is nz, then a trigger or command is OK, */
+/* othewise they are treated as an abort. */
+static int icoms2huey_err(int se, int torc) {
+ if (se != ICOM_OK)
+ return HUEY_COMS_FAIL;
+ return HUEY_OK;
+}
+
+/* i1Display command codes */
+/* B = byte (8bit), S = short (16bit), W = word (32bit), A = string */
+/* U = unused byte, - = no arguments/results */
+/* The is a 7 byte command buffer and 6 response recieve buffer. */
+/* :2 means the read is from a second 8 byte ep x81 read. */
+/* cbuf[-] is command byte */
+/* rbuf[-2] is continuation byte */
+/* rbuf[-1] is echo of command byte */
+/* rbuf2[-2] is an error byte if nz */
+typedef enum {
+ i1d_status = 0x00, /* -:A Get status string starting at 0, 1sec */
+ i1d_rd_green = 0x02, /* -:W Read the green channel, 1sec */
+ i1d_rd_blue = 0x03, /* -:W Read the blue channel, 1sec */
+ i1d_setintgt = 0x05, /* W:- Set the integration time, 1sec */
+ i1d_getintgt = 0x06, /* -:W Get the integration time, 1sec */
+ i1d_wrreg = 0x07, /* BB:? Write a register value, 1sec */
+ i1d_rdreg = 0x08, /* B:B Read a register value, 1sec */
+ i1d_unlock = 0x0e, /* BBBB:- Unlock the interface */
+ i1d_m_red_2 = 0x13, /* B:2:W Measure the red channel in freq mode, 1,10sec */
+ /* B = sync mode, typically 2 */
+ i1d_m_rgb_edge_2 = 0x16, /* SSS:2:WB Measure RGB edge/period mode, 1.70sec, ret red */
+ /* 2nd return value is not used ? */
+ i1d_rdambient = 0x17, /* BB:2:BWB Read Ambient, 1,10sec */
+ /* Returns first B param as first response */
+ /* Returns W as value read */
+ /* Returns aditional byte at end */
+
+ i1d_set_leds = 0x18, /* BB:B Set 4 LEDs state, 1sec */
+ /* 1st B is always 0 in practice */
+ /* 2nd B bits 0-4, 0 == on */
+ /* Echo's led state with returned B */
+ i1d_rgb_edge_3 = 0x19 /* SB:2:WB Unknown measurement command 1,10sec */
+ /* S is number of edges ?? */
+ /* B is the channel */
+ /* W is the reading */
+ /* B is not used ? */
+
+} i1DispCC;
+
+/* Diagnostic - return a description given the instruction code */
+static char *inst_desc(int cc) {
+ static char buf[40]; /* Fallback string */
+ switch(cc) {
+ case 0x00:
+ return "GetStatus";
+ case 0x02:
+ return "RdGreen";
+ case 0x03:
+ return "RdBlue";
+ case 0x05:
+ return "SetIntTime";
+ case 0x06:
+ return "GetIntTime";
+ case 0x07:
+ return "WrReg";
+ case 0x08:
+ return "RdReg";
+ case 0x09:
+ return "GetMeasPeriod";
+ case 0x0e:
+ return "Unlock";
+ case 0x13:
+ return "RdRedFreqMode";
+ case 0x16:
+ return "MeasRGBPeriMode";
+ case 0x17:
+ return "RdAmbient";
+ case 0x18:
+ return "SetLEDs";
+ case 0x19:
+ return "MeasRGBPeriMode2";
+ }
+ sprintf(buf,"Unknown %02x",cc);
+ return buf;
+}
+
+/* Do a command/response exchange with the huey. */
+/* Return the error code */
+/* The Huey is set up as an HID device, which can ease the need */
+/* for providing a kernel driver on MSWindows systems, */
+/* but it doesn't seem to actually be used as an HID device. */
+/* We allow for communicating via libusb, or an HID driver. */
+static inst_code
+huey_command(
+ huey *p, /* huey object */
+ i1DispCC cc, /* Command code */
+ unsigned char *in, /* 7 Command bytes to send */
+ unsigned char *out, /* 6 Response bytes returned */
+ double to, /* Timeout in seconds */
+ double to2 /* Timeout in seconds for 2nd read */
+) {
+ int i;
+ unsigned char buf[8]; /* 8 bytes to write/read */
+ int wbytes; /* bytes written */
+ int rbytes; /* bytes read from ep */
+ int se, ua = 0, rv = inst_ok;
+ int ishid = p->icom->port_type(p->icom) == icomt_hid;
+
+ a1logd(p->log,5,"huey_command: Sending '%s' args '%s'\n",inst_desc(cc), icoms_tohex(in, 7));
+
+ /* Send the command using the control interface */
+ buf[0] = cc; /* Construct the command == HID report number */
+ memmove(buf + 1, in, 7);
+
+ if (ishid) {
+ se = p->icom->hid_write(p->icom, buf, 8, &wbytes, to);
+ } else {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_CLASS | IUSB_REQ_RECIP_INTERFACE, 0x9, 0x200, 0, buf, 8, to);
+ wbytes = 8;
+ }
+ if (se != 0) {
+ a1logd(p->log,1,"huey_command: command send failed with ICOM err 0x%x\n",se);
+ return huey_interp_code((inst *)p, HUEY_COMS_FAIL);
+ }
+ rv = huey_interp_code((inst *)p, icoms2huey_err(ua, 0));
+
+ if (rv == inst_ok && wbytes != 8)
+ rv = huey_interp_code((inst *)p, HUEY_BAD_WR_LENGTH);
+
+ a1logd(p->log,6,"huey_command: get inst code\n",rv);
+
+ if (rv != inst_ok) {
+ /* Flush any response if write failed */
+ if (ishid)
+ p->icom->hid_read(p->icom, buf, 8, &rbytes, to);
+ else
+ p->icom->usb_read(p->icom, NULL, 0x81, buf, 8, &rbytes, to);
+ return rv;
+ }
+
+ /* Now fetch the response */
+ a1logd(p->log,6,"huey_command: Reading response\n");
+
+ if (ishid) {
+ se = p->icom->hid_read(p->icom, buf, 8, &rbytes, to);
+ } else {
+ se = p->icom->usb_read(p->icom, NULL, 0x81, buf, 8, &rbytes, to);
+ }
+ if (se != 0) {
+ a1logd(p->log,1,"huey_command: read failed with ICOM err 0x%x\n",se);
+ return huey_interp_code((inst *)p, HUEY_COMS_FAIL);
+ }
+ rv = huey_interp_code((inst *)p, icoms2huey_err(ua, 0));
+ if (rv == inst_ok && rbytes != 8)
+ rv = huey_interp_code((inst *)p, HUEY_BAD_RD_LENGTH);
+ if (rv == inst_ok && buf[1] != cc)
+ rv = huey_interp_code((inst *)p, HUEY_BAD_RET_CMD);
+
+ /* Some commands don't use the first response, but need to */
+ /* fetch a second response, with a longer timeout. */
+ /* This seems to be all of the measurement trigger commands */
+ if (rv == inst_ok && buf[0] == 0x90) { /* there is more */
+ a1logd(p->log,6,"huey_command: Reading extended response\n");
+
+ if (ishid) {
+ se = p->icom->hid_read(p->icom, buf, 8, &rbytes, to2);
+ } else {
+ se = p->icom->usb_read(p->icom, NULL, 0x81, buf, 8, &rbytes, to2);
+ }
+ if (se != 0) {
+ a1logd(p->log,1,"huey_command: read failed with ICOM err 0x%x\n",se);
+ return huey_interp_code((inst *)p, HUEY_COMS_FAIL);
+ }
+ rv = huey_interp_code((inst *)p, icoms2huey_err(ua, 0));
+ if (rv == inst_ok && rbytes != 8)
+ rv = huey_interp_code((inst *)p, HUEY_BAD_RD_LENGTH);
+ if (rv == inst_ok && buf[1] != cc) {
+ rv = huey_interp_code((inst *)p, HUEY_BAD_RET_CMD);
+ }
+ }
+
+ /* The first byte returned seems to be a command result error code. */
+ /* Not all codes are known, but it seems that the 6 byte payload */
+ /* is an error message, for instance 0x80 "NoCmd". */
+ /* The second byte is always the command code being echo'd back. */
+ if (rv == inst_ok && cc != 0x00 && buf[0] != 0x00) {
+ ua = HUEY_BAD_RET_STAT;
+ if (buf[0] == 0x80)
+ ua = HUEY_BAD_COMMAND;
+ rv = huey_interp_code((inst *)p, ua);
+ }
+
+ if (rv == inst_ok) {
+ memmove(out, buf + 2, 6);
+ } else {
+ memset(out, 0, 6);
+ }
+ a1logd(p->log,5,"huey_command: returning '%s' ICOM err 0x%x\n",icoms_tohex(out, 6),ua);
+
+ return rv;
+}
+
+/* Take an int, and convert it into a byte buffer */
+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 */
+static void short2buf(unsigned char *buf, int inv) {
+ buf[0] = (inv >> 8) & 0xff;
+ buf[1] = (inv >> 0) & 0xff;
+}
+
+/* Take a word sized return buffer, and convert it to an int */
+static int buf2int(unsigned char *buf) {
+ int val;
+ val = buf[0];
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[3]));
+ return val;
+}
+
+/* Read a byte from a register */
+static inst_code
+huey_rdreg_byte(
+ huey *p, /* Object */
+ int *outp, /* Where to write value */
+ int addr /* Register Address, 0 - 255 */
+) {
+ unsigned char buf[8];
+ int rsize;
+ inst_code ev;
+
+ if (addr < 0 || addr > 255)
+ return huey_interp_code((inst *)p, HUEY_BAD_REG_ADDRESS);
+
+ /* Read a byte */
+ memset(buf, 0, 7);
+ buf[0] = addr;
+ if ((ev = huey_command(p, i1d_rdreg, buf, buf, 1.0, 1.0)) != inst_ok)
+ return ev;
+
+ /* We expect the address to be returned */
+ if ((buf[0] & 0xff) != addr)
+ return huey_interp_code((inst *)p, HUEY_UNEXPECTED_RET_VAL);
+
+ *outp = (int)(buf[1] & 0xff);
+
+ return inst_ok;
+}
+
+/* Read a short from a register */
+static inst_code
+huey_rdreg_short(
+ huey *p, /* Object */
+ int *outp, /* Where to write value */
+ int addr /* Register Address, 0 - 126 */
+) {
+ inst_code ev;
+ int v, val;
+
+ if ((ev = huey_rdreg_byte(p, &v, addr)) != inst_ok)
+ return ev;
+ val = v;
+
+ if ((ev = huey_rdreg_byte(p, &v, addr+1)) != inst_ok)
+ return ev;
+ val = ((val << 8) + (0xff & v));
+
+ *outp = val;
+
+ return inst_ok;
+}
+
+/* Read a word from a register */
+static inst_code
+huey_rdreg_word(
+ huey *p, /* Object */
+ int *outp, /* Where to write value */
+ int addr /* Register Address, 0 - 124 */
+) {
+ inst_code ev;
+ int v, val;
+
+ if ((ev = huey_rdreg_byte(p, &v, addr)) != inst_ok)
+ return ev;
+ val = v;
+
+ if ((ev = huey_rdreg_byte(p, &v, addr+1)) != inst_ok)
+ return ev;
+ val = ((val << 8) + (0xff & v));
+
+ if ((ev = huey_rdreg_byte(p, &v, addr+2)) != inst_ok)
+ return ev;
+ val = ((val << 8) + (0xff & v));
+
+ if ((ev = huey_rdreg_byte(p, &v, addr+3)) != inst_ok)
+ return ev;
+ val = ((val << 8) + (0xff & v));
+
+ *outp = val;
+
+ return inst_ok;
+}
+
+
+/* Read a float from a register */
+/* Will return HUEY_FLOAT_NOT_SET if the float value was 0xffffffff */
+static inst_code
+huey_rdreg_float(
+ huey *p, /* Object */
+ double *outp, /* Where to write value */
+ int addr /* Register Address, 0 - 124 */
+) {
+ inst_code ev;
+ int val;
+
+ if ((ev = huey_rdreg_word(p, &val, addr)) != inst_ok)
+ return ev;
+
+ if (ev == 0xffffffff) {
+ return inst_ok;
+ }
+
+ *outp = IEEE754todouble((unsigned int)val);
+ return inst_ok;
+}
+
+
+/* Write a byte to a register */
+static inst_code
+huey_wrreg_byte(
+ huey *p, /* Object */
+ int inv, /* Input value */
+ int addr /* Register Address, 0 - 127 */
+) {
+ int cval;
+ unsigned char ibuf[8], obuf[8];
+ int rsize;
+ inst_code ev;
+
+ ibuf[0] = addr;
+ ibuf[1] = inv;
+
+ /* Write a byte */
+ if ((ev = huey_command(p, i1d_wrreg, ibuf, obuf, 1.0, 1.0)) != inst_ok)
+ return ev;
+
+ return inst_ok;
+}
+
+/* Write a word to a register */
+static inst_code
+huey_wrreg_word(
+ huey *p, /* Object */
+ int inv, /* Where to write value */
+ int addr /* Register Address, 0 - 124 */
+) {
+ inst_code ev;
+ int v;
+
+ v = (inv >> 24) & 0xff;
+ if ((ev = huey_wrreg_byte(p, v, addr) ) != inst_ok)
+ return ev;
+
+ v = (inv >> 16) & 0xff;
+ if ((ev = huey_wrreg_byte(p, v, addr+1) ) != inst_ok)
+ return ev;
+
+ v = (inv >> 8) & 0xff;
+ if ((ev = huey_wrreg_byte(p, v, addr+2) ) != inst_ok)
+ return ev;
+
+ v = (inv >> 0) & 0xff;
+ if ((ev = huey_wrreg_byte(p, v, addr+3) ) != inst_ok)
+ return ev;
+
+ return inst_ok;
+}
+
+/* Write a float to a register */
+static inst_code
+huey_wrreg_float(
+ huey *p, /* Object */
+ double inv, /* Value to write */
+ int addr /* Register Address, 0 - 124 */
+) {
+ inst_code ev;
+ int val;
+
+ val = (int)doubletoIEEE754(inv);
+
+ if ((ev = huey_wrreg_word(p, val, addr)) != inst_ok)
+ return ev;
+ return inst_ok;
+}
+
+/* Read the integration time */
+static inst_code
+huey_rd_int_time(
+ huey *p, /* Object */
+ int *outp /* Where to write value */
+) {
+ unsigned char buf[8];
+ int rsize;
+ inst_code ev;
+
+ if ((ev = huey_command(p, i1d_getintgt, buf, buf, 1.0, 1.0)) != inst_ok)
+ return ev;
+
+ *outp = buf2int(buf);
+
+ return inst_ok;
+}
+
+/* Set the integration time */
+/* (Not used for Huey ?) */
+static inst_code
+huey_wr_int_time(
+ huey *p, /* Object */
+ int inv /* Value to write */
+) {
+ unsigned char buf[16];
+ int rsize;
+ inst_code ev;
+
+ int2buf(buf, inv);
+ if ((ev = huey_command(p, i1d_setintgt, buf, buf, 1.0, 1.0)) != inst_ok)
+ return ev;
+
+ return inst_ok;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Take a raw measurement using a given integration time. */
+/* The measureent is the count of (both) edges from the L2V */
+/* over the integration time */
+static inst_code
+huey_freq_measure(
+ huey *p, /* Object */
+ double rgb[3] /* Return the RGB edge count values */
+) {
+ int i;
+ unsigned char ibuf[8];
+ unsigned char obuf[8];
+ inst_code ev;
+
+ /* Do the measurement, and return the Red value */
+ ibuf[0] = 2; /* Sync mode 2 for CRT */
+ if ((ev = huey_command(p, i1d_m_red_2, ibuf, obuf, 1.0, 10.0)) != inst_ok)
+ return ev;
+ rgb[0] = (double)buf2int(obuf);
+
+ /* Get the green value */
+ if ((ev = huey_command(p, i1d_rd_green, ibuf, obuf, 1.0, 1.0)) != inst_ok)
+ return ev;
+ rgb[1] = (double)buf2int(obuf);
+
+ /* Get the blue value */
+ if ((ev = huey_command(p, i1d_rd_blue, ibuf, obuf, 1.0, 1.0)) != inst_ok)
+ return ev;
+ rgb[2] = (double)buf2int(obuf);
+
+ return inst_ok;
+}
+
+/* Take a raw measurement that returns the number of clocks */
+/* between and initial edge and edgec[] subsequent edges of the L2F. */
+/* Both edges are counted. */
+static inst_code
+huey_period_measure(
+ huey *p, /* Object */
+ int edgec[3], /* Measurement edge count for each channel */
+ double rgb[3] /* Return the RGB values */
+) {
+ int i;
+ unsigned char ibuf[16];
+ unsigned char obuf[16];
+ int rsize;
+ inst_code ev;
+
+ /* Set the edge count */
+ short2buf(ibuf + 0, edgec[0]);
+ short2buf(ibuf + 2, edgec[1]);
+ short2buf(ibuf + 4, edgec[2]);
+
+ /* Do the measurement, and return the Red value */
+ if ((ev = huey_command(p, i1d_m_rgb_edge_2, ibuf, obuf, 1.0, 70.0)) != inst_ok)
+ return ev;
+ rgb[0] = (double)buf2int(obuf);
+
+ /* Get the green value */
+ if ((ev = huey_command(p, i1d_rd_green, ibuf, obuf, 1.0, 1.0)) != inst_ok)
+ return ev;
+ rgb[1] = (double)buf2int(obuf);
+
+ /* Get the blue value */
+ if ((ev = huey_command(p, i1d_rd_blue, ibuf, obuf, 1.0, 1.0)) != inst_ok)
+ return ev;
+ rgb[2] = (double)buf2int(obuf);
+
+ return inst_ok;
+}
+
+/* Take a another mode raw mesurement from the device for */
+/* (not currently used ?) */
+static inst_code
+huey_take_raw_measurement_3(
+ huey *p, /* Object */
+ int edgec[3], /* Measurement edge count for each channel */
+ double rgb[3] /* Return the RGB values */
+) {
+ int i;
+ unsigned char ibuf[16];
+ unsigned char obuf[16];
+ int rsize;
+ inst_code ev;
+
+ /* Do the measurement, and return the Red value */
+ short2buf(ibuf + 0, edgec[0]);
+ ibuf[2] = 0; /* Channel */
+ if ((ev = huey_command(p, i1d_m_rgb_edge_2, ibuf, obuf, 1.0, 10.0)) != inst_ok)
+ return ev;
+ rgb[0] = (double)buf2int(obuf);
+
+ /* Do the measurement, and return the Green value */
+ short2buf(ibuf + 0, edgec[1]);
+ ibuf[2] = 1; /* Channel */
+ if ((ev = huey_command(p, i1d_m_rgb_edge_2, ibuf, obuf, 1.0, 10.0)) != inst_ok)
+ return ev;
+ rgb[1] = (double)buf2int(obuf);
+
+ /* Do the measurement, and return the Blue value */
+ short2buf(ibuf + 0, edgec[2]);
+ ibuf[2] = 2; /* Channel */
+ if ((ev = huey_command(p, i1d_m_rgb_edge_2, ibuf, obuf, 1.0, 10.0)) != inst_ok)
+ return ev;
+ rgb[2] = (double)buf2int(obuf);
+
+ return inst_ok;
+}
+
+/* Take a cooked measurement from the device for Huey. */
+/* The sensors are likely to be light to frequency converters */
+/* such as the TAOS TSL237, and there are only so many ways */
+/* the output can be measured. If the light level is high enough, */
+/* you could simply count the number of output transitions (edges) */
+/* in a fixed period of time. If the frequency is low though, */
+/* this limits precision due to quantization of the count. */
+/* Another way is to time how long it takes to count a certain */
+/* number of edges. This has higher precision at low frequencies, */
+/* but has the problem of having an unknown measurement duration, */
+/* and seems to be the main method chosen for the Huey. */
+static inst_code
+huey_take_measurement_2(
+ huey *p, /* Object */
+ int refr, /* nz if crt mode */
+ double rgb[3] /* Return the rgb values */
+) {
+ int i, j;
+ int edgec[3] = {1,1,1}; /* Measurement edge count for each channel */
+ int rem[3] = {1,1,1}; /* remeasure flags */
+ inst_code ev;
+
+ if (p->inited == 0)
+ return huey_interp_code((inst *)p, HUEY_NOT_INITED);
+
+ a1logd(p->log,4,"take_measurement_2 called with refr = %d\n",refr);
+
+ /* For Refresh mode, do an initial set of measurements over a fixed period */
+ if (refr) {
+
+ if ((ev = huey_freq_measure(p, rgb)) != inst_ok)
+ return ev;
+
+ a1logd(p->log,4,"Raw initial CRT RGB = %f %f %f\n",rgb[0],rgb[1],rgb[2]);
+
+ /* Decide whether any channels need re-measuring, */
+ /* and computed cooked values. Threshold is typically 75 */
+ for (i = 0; i < 3; i++) {
+ rem[i] = (rgb[i] <= (0.75 * (double)p->sampno)) ? 1 : 0;
+ rgb[i] = 0.5 * rgb[i] * 1e6/(double)p->int_clocks;
+ }
+ a1logd(p->log,4,"Re-measure flags = %d %d %d\n",rem[0],rem[1],rem[2]);
+ }
+
+ /* If any need re-measuring */
+ if (rem[0] || rem[1] || rem[2]) {
+ double rgb2[3];
+
+ /* Do a first or second set of measurements */
+ if ((ev = huey_period_measure(p, edgec, rgb2)) != inst_ok)
+ return ev;
+ a1logd(p->log,4,"Raw initial/subsequent ecount %d %d %d RGB = %f %f %f\n",
+ edgec[0], edgec[1], edgec[2], rgb2[0], rgb2[1], rgb2[2]);
+
+ /* Compute adjusted edge count for channels we're remeasuring, */
+ /* aiming for count values of clk_freq (~1e6). */
+ for (i = 0; i < 3; i++) {
+ double ns;
+ if (rem[i]) {
+ if (p->clk_freq > ((2000.0 - 0.5) * rgb2[i]))
+ ns = 2000.0;
+ else {
+ ns = floor(p->clk_freq/rgb2[i]) + 0.5;
+ if (ns < 1.0)
+ ns = 1.0;
+ }
+ edgec[i] = (int)ns;
+ }
+ }
+
+ /* If we compute a different edge count, read again */
+ if (edgec[0] > 1 || edgec[1] > 1 || edgec[2] > 1) {
+ double rgb3[3]; /* 2nd RGB Readings */
+
+ if ((ev = huey_period_measure(p, edgec, rgb3)) != inst_ok)
+ return ev;
+
+ a1logd(p->log,4,"Raw subsequent2 ecount %d %d %d RGB = %f %f %f\n",
+ edgec[0], edgec[1], edgec[2], rgb3[0], rgb3[1], rgb3[2]);
+
+ /* Average readings if we repeated a measurement with the same threshold */
+ /* (Minor advantage, but may as well use it) */
+ for (i = 0; i < 3; i++) {
+ if (edgec[i] == 1)
+ rgb2[i] = 0.5 * (rgb2[i] + rgb3[i]);
+ else
+ rgb2[i] = rgb3[i];
+ }
+ }
+
+ /* Compute adjusted readings, ovewritting initial cooked values */
+ for (i = 0; i < 3; i++) {
+ if (rem[i]) {
+ rgb[i] = ((double)edgec[i])/(rgb2[i] * 2.0 * p->clk_prd);
+ a1logd(p->log,4,"%d after scale = %f\n",i,rgb[i]);
+
+ rgb[i] -= p->dark_cal[i]; /* Subtract black level */
+ a1logd(p->log,4,"%d after sub black = %f\n",i,rgb[i]);
+
+ if (rgb[i] < 0.0001)
+ rgb[i] = 0.0001;
+ a1logd(p->log,4,"%d after limit min = %f\n",i,rgb[i]);
+ }
+ }
+ }
+
+ a1logd(p->log,4,"Cooked RGB = %f %f %f\n",rgb[0],rgb[1],rgb[2]);
+
+ return inst_ok;
+}
+
+/* Take a raw ambient measurement */
+static inst_code
+huey_take_amb_measurement_1(
+ huey *p, /* Object */
+ int a1, /* 8 bit argument - unknown */
+ int syncmode, /* 8 bit argument - sync mode */
+ double *amb, /* Return the raw ambient value */
+ int *rb /* Returned byte */
+) {
+ int i;
+ unsigned char ibuf[16];
+ unsigned char obuf[16];
+ int rsize;
+ inst_code ev;
+
+ a1 &= 0xff;
+ ibuf[0] = a1;
+ ibuf[1] = syncmode;
+
+ /* Do the measurement */
+ if ((ev = huey_command(p, i1d_rdambient, ibuf, obuf, 1.0, 10.0)) != inst_ok)
+ return ev;
+
+ /* Expect first argument to be returned */
+ if (obuf[0] != a1)
+ return huey_interp_code((inst *)p, HUEY_UNEXPECTED_RET_VAL);
+
+ *amb = (double)buf2int(obuf+1);
+ *rb = 0xff & obuf[5];
+
+ return inst_ok;
+}
+
+/* Take a cooked ambient measurement */
+static inst_code
+huey_take_amb_measurement(
+ huey *p, /* Object */
+ int refr, /* nz if refresh mode */
+ double *amb /* Return the ambient value */
+) {
+ int rb; /* Returned byte - not used */
+ inst_code ev;
+
+ if (p->inited == 0)
+ return huey_interp_code((inst *)p, HUEY_NOT_INITED);
+
+ a1logd(p->log,4,"take_amb_measurement_2 called with refr = %d\n",refr);
+
+ /* First param is always 3, second is sync mode */
+ if ((ev = huey_take_amb_measurement_1(p, 3, refr ? 2 : 0, amb, &rb)) != inst_ok)
+ return ev;
+ a1logd(p->log,4,"Raw ambient = %f\n",*amb);
+ return inst_ok;
+}
+
+/* Set the indicator LED's state. */
+/* The bottom 4 bits set the LED state from bottom (0) */
+/* to top (3), 0 = off, 1 = on */
+static inst_code
+huey_set_LEDs(
+ huey *p, /* Object */
+ int mask /* 8 bit LED mask */
+) {
+ int i;
+ unsigned char ibuf[8];
+ unsigned char obuf[8];
+ inst_code ev;
+
+ mask &= 0xf;
+ p->led_state = mask;
+
+ ibuf[0] = 0;
+ ibuf[1] = 0xf & (~mask);
+
+ /* Do command */
+ if ((ev = huey_command(p, i1d_set_leds, ibuf, obuf, 1.0, 1.0)) != inst_ok)
+ return ev;
+
+ return inst_ok;
+}
+
+/* . . . . . . . . . . . . . . . . . . . . . . . . */
+
+/* Take a XYZ measurement from the device */
+static inst_code
+huey_take_XYZ_measurement(
+ huey *p, /* Object */
+ double XYZ[3] /* Return the XYZ values */
+) {
+ int i, j;
+ double rgb[3]; /* RGB Readings */
+ inst_code ev;
+ double *mat; /* Pointer to matrix */
+
+ if (IMODETST(p->mode, inst_mode_emis_ambient)) {
+ if ((ev = huey_take_amb_measurement(p, 0, &XYZ[1])) != inst_ok)
+ return ev;
+ XYZ[1] *= AMB_SCALE_FACTOR; /* Times aproximate fudge factor */
+ XYZ[0] = icmD50.X * XYZ[1]; /* Convert to D50 neutral */
+ XYZ[2] = icmD50.Z * XYZ[1];
+ } else {
+ if ((ev = huey_take_measurement_2(p, p->refrmode, rgb)) != inst_ok)
+ return ev;
+
+ if (p->icx)
+ mat = p->CRT_cal; /* CRT/factory matrix */
+ else
+ mat = p->LCD_cal; /* LCD/user matrix */
+
+ for (i = 0; i < 3; i++) {
+ XYZ[i] = 0.0;
+ for (j = 0; j < 3; j++) {
+ XYZ[i] += mat[i * 3 + j] * rgb[j];
+ }
+ XYZ[i] *= CALFACTOR; /* Times magic scale factor */
+ }
+
+ /* Apply the colorimeter correction matrix */
+ icmMulBy3x3(XYZ, p->ccmat, XYZ);
+ }
+ a1logd(p->log,3,"huey_take_XYZ_measurement: XYZ = %f %f %f\n",XYZ[0],XYZ[1],XYZ[2]);
+ return inst_ok;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Check the device is responding, and unlock if necessary */
+static inst_code
+huey_check_unlock(
+ huey *p /* Object */
+) {
+ unsigned char buf[8];
+ int rsize;
+ inst_code ev;
+ int vv;
+ double ver;
+
+ a1logd(p->log,2,"huey_check_unlock: called\n");
+
+ /* Check the instrument status */
+ if ((ev = huey_command(p, i1d_status, buf, buf, 1.0,1.0)) != inst_ok)
+ return ev;
+
+ if (strncmp((char *)buf, "Locked", 6) == 0) {
+ if (p->lenovo)
+ strcpy((char *)buf,"huyL");
+ else
+ strcpy((char *)buf,"GrMb");
+
+ if ((ev = huey_command(p, i1d_unlock, buf, buf, 1.0,1.0)) != inst_ok)
+ return ev;
+
+ if ((ev = huey_command(p, i1d_status, buf, buf, 1.0,1.0)) != inst_ok)
+ return ev;
+ }
+
+ if (strncmp((char *)buf, "huL002", 6) != 0 /* Lenovo Huey ? */
+ && strncmp((char *)buf, "Cir001", 6) != 0) { /* Huey */
+ a1logd(p->log,1,"huey_check_unlock: unknown model '%s'\n",buf);
+ return huey_interp_code((inst *)p, HUEY_UNKNOWN_MODEL);
+ }
+
+ a1logd(p->log,2,"huey_check_unlock: instrument is responding, unlocked, and right type\n");
+
+ return inst_ok;
+}
+
+/* Read all the relevant register values */
+static inst_code
+huey_read_all_regs(
+ huey *p /* Object */
+) {
+ inst_code ev;
+ int i;
+
+ a1logd(p->log,2,"huey_check_unlock: about to read all the registers\n");
+
+ /* Serial number */
+ if ((ev = huey_rdreg_word(p, &p->ser_no, 0) ) != inst_ok)
+ return ev;
+ a1logd(p->log,4,"serial number = %d\n",p->ser_no);
+
+
+ /* LCD/user calibration values */
+ for (i = 0; i < 9; i++) {
+ if ((ev = huey_rdreg_float(p, &p->LCD_cal[i], 4 + 4 * i) ) != inst_ok)
+ return ev;
+ a1logd(p->log,4,"LCD/user cal[%d] = %f\n",i,p->LCD_cal[i]);
+ }
+ /* LCD/user calibration time */
+ if ((ev = huey_rdreg_word(p, &p->LCD_caltime, 50) ) != inst_ok)
+ return ev;
+ a1logd(p->log,2,"LCD/user calibration time = 0x%x = %s\n",p->LCD_caltime, ctime_32(&p->LCD_caltime));
+
+ /* CRT/factory calibration values */
+ for (i = 0; i < 9; i++) {
+ if ((ev = huey_rdreg_float(p, &p->CRT_cal[i], 54 + 4 * i) ) != inst_ok)
+ return ev;
+ a1logd(p->log,3,"CRT/factory cal[%d] = %f\n",i,p->CRT_cal[i]);
+ }
+ /* CRT/factory calibration flag */
+ if ((ev = huey_rdreg_word(p, &p->CRT_caltime, 90) ) != inst_ok)
+ return ev;
+ a1logd(p->log,3,"CRT/factory flag = 0x%x = %s\n",p->CRT_caltime, ctime_32(&p->CRT_caltime));
+
+
+ /* Hard coded in Huey */
+ p->clk_prd = 1e-6;
+ a1logd(p->log,3,"Clock period = %f\n",p->clk_prd);
+
+ /* Dark current calibration values */
+ for (i = 0; i < 3; i++) {
+ if ((ev = huey_rdreg_float(p, &p->dark_cal[i], 103 + 4 * i)) != inst_ok) {
+ if ((ev & inst_imask) != HUEY_FLOAT_NOT_SET)
+ return ev;
+ p->dark_cal[i] = 0.0;
+ }
+ a1logd(p->log,3,"darkcal[%d] = %f\n",i,p->dark_cal[i]);
+ }
+
+ /* Ambient darkcurrent calibration value ? */
+ if ((ev = huey_rdreg_float(p, &p->amb_cal, 148)) != inst_ok) {
+ if ((ev & inst_imask) != HUEY_FLOAT_NOT_SET)
+ return ev;
+ p->amb_cal = 0.0;
+ }
+ a1logd(p->log,3,"Ambient cal = %f\n",p->amb_cal);
+
+ /* Unlock string */
+ for (i = 0; i < 4; i++) {
+ int vv;
+ if ((ev = huey_rdreg_byte(p, &vv, 122 + i) ) != inst_ok)
+ return ev;
+ p->unlk_string[i] = (char)vv;
+ }
+ p->unlk_string[i] = '\000';
+ a1logd(p->log,3,"unlock string = '%s'\n",p->unlk_string);
+
+ /* Read the integration time */
+ if ((ev = huey_rd_int_time(p, &p->int_clocks) ) != inst_ok)
+ return ev;
+ a1logd(p->log,3,"Integration time = %d\n",p->int_clocks);
+
+ a1logd(p->log,2,"huey_check_unlock: all registers read OK\n");
+
+ return inst_ok;
+}
+
+/* Compute factors that depend on the register values */
+static inst_code
+huey_compute_factors(
+ huey *p /* Object */
+) {
+ int i;
+
+ /* Check that certain value are valid */
+ if (p->ser_no == 0xffffffff) /* (It appears that some instruments have no serial number!) */
+ a1logw(p->log,"huey: bad instrument serial number\n");
+
+ if (p->LCD_caltime == 0xffffffff)
+ return huey_interp_code((inst *)p, HUEY_BAD_LCD_CALIBRATION);
+
+ if (p->CRT_caltime == 0xffffffff)
+ return huey_interp_code((inst *)p, HUEY_BAD_CRT_CALIBRATION);
+
+ /* clk_prd inversion */
+ p->clk_freq = 1.0/p->clk_prd;
+ a1logd(p->log,3,"clk_freq = %f\n",p->clk_freq);
+
+ /* Set some defaults */
+ p->sampno = 100; /* Minimum sampling count */
+
+ return inst_ok;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static inst_code set_default_disp_type(huey *p);
+
+/* Establish communications with a HUEY */
+/* If it's a serial port, use the baud rate given, and timeout in to secs */
+/* Return DTP_COMS_FAIL on failure to establish communications */
+static inst_code
+huey_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ huey *p = (huey *) pp;
+ unsigned char buf[8];
+ int rsize;
+ long etime;
+ int bi, i, se, rv;
+ inst_code ev = inst_ok;
+ char **pnames = NULL;
+ int retries = 0;
+
+ a1logd(p->log, 2, "huey_init_coms: About to init coms\n");
+
+ /* Open as an HID if available */
+ if (p->icom->port_type(p->icom) == icomt_hid) {
+
+ a1logd(p->log, 3, "huey_init_coms: About to init HID\n");
+
+ /* Set config, interface */
+ if ((se = p->icom->set_hid_port(p->icom, icomuf_none, retries, pnames)) != ICOM_OK) {
+ a1logd(p->log, 1, "huey_init_coms: set_hid_port failed ICOM err 0x%x\n",se);
+ return huey_interp_code((inst *)p, icoms2huey_err(se, 0));
+ }
+
+ } else if (p->icom->port_type(p->icom) == icomt_usb) {
+
+ a1logd(p->log, 3, "huey_init_coms: About to init USB\n");
+
+ /* Set config, interface, write end point, read end point */
+ /* ("serial" end points aren't used - the huey uses USB control messages) */
+ /* We need to detatch the HID driver on Linux */
+
+ if ((se = p->icom->set_usb_port(p->icom, 1, 0x00, 0x00, icomuf_detach, 0, NULL))
+ != ICOM_OK) {
+ a1logd(p->log, 1, "huey_init_coms: set_usb_port failed ICOM err 0x%x\n",se);
+ return huey_interp_code((inst *)p, icoms2huey_err(se, 0));
+ }
+
+ } else {
+ a1logd(p->log, 1, "huey_init_coms: wrong communications type for device!\n");
+ return huey_interp_code((inst *)p, HUEY_UNKNOWN_MODEL);
+ }
+
+ if ((p->icom->vid == 0x0765 && p->icom->pid == 0x5001)
+ || (p->icom->vid == 0x0765 && p->icom->pid == 0x5010)) {
+ a1logd(p->log, 2, "huey_init_coms: Lenovo version\n");
+ p->lenovo = 1;
+ }
+
+ /* Check instrument is responding */
+ if ((ev = huey_command(p, i1d_status, buf, buf, 1.0, 1.0)) != inst_ok) {
+ a1logd(p->log, 1, "huey_init_coms: instrument didn't respond 0x%x\n",ev);
+ return ev;
+ }
+
+ /* Setup the default display type */
+ if ((ev = set_default_disp_type(p)) != inst_ok) {
+ return ev;
+ }
+
+ a1logd(p->log, 2, "huey_init_coms: inited coms OK\n");
+
+ p->gotcoms = 1;
+ return inst_ok;
+}
+
+/* Initialise the HUEY */
+/* return non-zero on an error, with dtp error code */
+static inst_code
+huey_init_inst(inst *pp) {
+ huey *p = (huey *)pp;
+ inst_code ev = inst_ok;
+
+ a1logd(p->log, 2, "huey_init_inst: called\n");
+
+ if (p->gotcoms == 0)
+ return huey_interp_code((inst *)p, HUEY_NO_COMS); /* Must establish coms first */
+
+ /* Check instrument is responding, and right type */
+ if ((ev = huey_check_unlock(p)) != inst_ok)
+ return ev;
+
+ /* Turn the LEDs off */
+ if ((ev = huey_set_LEDs(p, 0x0)) != inst_ok)
+ return ev;
+
+ /* Read all the registers and store their contents */
+ if ((ev = huey_read_all_regs(p)) != inst_ok)
+ return ev;
+
+ if ((ev = huey_compute_factors(p)) != inst_ok)
+ return ev;
+
+ p->trig = inst_opt_trig_user;
+
+ p->inited = 1;
+ a1logd(p->log, 2, "huey_init_inst: inited OK\n");
+
+ /* Flash the LEDs, just cos we can! */
+ if ((ev = huey_set_LEDs(p, 0x1)) != inst_ok)
+ return ev;
+ msec_sleep(50);
+ if ((ev = huey_set_LEDs(p, 0x2)) != inst_ok)
+ return ev;
+ msec_sleep(50);
+ if ((ev = huey_set_LEDs(p, 0x4)) != inst_ok)
+ return ev;
+ msec_sleep(50);
+ if ((ev = huey_set_LEDs(p, 0x8)) != inst_ok)
+ return ev;
+ msec_sleep(50);
+ if ((ev = huey_set_LEDs(p, 0x4)) != inst_ok)
+ return ev;
+ msec_sleep(50);
+ if ((ev = huey_set_LEDs(p, 0x2)) != inst_ok)
+ return ev;
+ msec_sleep(50);
+ if ((ev = huey_set_LEDs(p, 0x1)) != inst_ok)
+ return ev;
+ msec_sleep(50);
+ if ((ev = huey_set_LEDs(p, 0x0)) != inst_ok)
+ return ev;
+
+ return inst_ok;
+}
+
+/* Read a single sample */
+/* Return the dtp error code */
+static inst_code
+huey_read_sample(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+ipatch *val, /* Pointer to instrument patch value */
+instClamping clamp) { /* NZ if clamp XYZ/Lab to be +ve */
+ huey *p = (huey *)pp;
+ int user_trig = 0;
+ int rv = inst_protocol_error;
+
+a1logd(p->log, 1, "huey: huey_read_sample called\n");
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->trig == inst_opt_trig_user) {
+
+ if (p->uicallback == NULL) {
+ a1logd(p->log, 1, "huey: inst_opt_trig_user but no uicallback function set!\n");
+ return inst_unsupported;
+ }
+
+a1logd(p->log, 1, "huey: about to wait for user trigger\n");
+ for (;;) {
+ if ((rv = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (rv == inst_user_abort)
+ return rv; /* Abort */
+ if (rv == inst_user_trig) {
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ msec_sleep(200);
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ /* Progromatic Trigger */
+ } else {
+a1logd(p->log, 1, "huey: checking for abort\n");
+ /* Check for abort */
+ if (p->uicallback != NULL
+ && (rv = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_abort)
+ return rv; /* Abort */
+ }
+
+a1logd(p->log, 1, "huey: about to call huey_take_XYZ_measurement\n");
+ /* Read the XYZ value */
+ if ((rv = huey_take_XYZ_measurement(p, val->XYZ)) != inst_ok) {
+ return rv;
+ }
+ /* This may not change anything since instrument may clamp */
+ if (clamp)
+ icmClamp3(val->XYZ, val->XYZ);
+
+ val->loc[0] = '\000';
+ if (IMODETST(p->mode, inst_mode_emis_ambient))
+ val->mtype = inst_mrt_ambient;
+ else
+ val->mtype = inst_mrt_none;
+ val->XYZ_v = 1; /* These are absolute XYZ readings ? */
+ val->sp.spec_n = 0;
+ val->duration = 0.0;
+
+ if (user_trig)
+ return inst_user_trig;
+ return rv;
+}
+
+/* Insert a colorimetric correction matrix in the instrument XYZ readings */
+/* This is only valid for colorimetric instruments. */
+/* To remove the matrix, pass NULL for the filter filename */
+inst_code huey_col_cor_mat(
+inst *pp,
+double mtx[3][3]
+) {
+ huey *p = (huey *)pp;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (mtx == NULL) {
+ icmSetUnity3x3(p->ccmat);
+ } else {
+ if (p->cbid == 0) {
+ a1loge(p->log, 1, "huey: can't set col_cor_mat over non base display type\n");
+ inst_wrong_setup;
+ }
+ icmCpy3x3(p->ccmat, mtx);
+ }
+
+ return inst_ok;
+}
+
+/* Error codes interpretation */
+static char *
+huey_interp_error(inst *pp, int ec) {
+// huey *p = (huey *)pp;
+ ec &= inst_imask;
+ switch (ec) {
+ case HUEY_INTERNAL_ERROR:
+ return "Internal software error";
+ case HUEY_COMS_FAIL:
+ return "Communications failure";
+ case HUEY_UNKNOWN_MODEL:
+ return "Not a known Huey Model";
+ case HUEY_DATA_PARSE_ERROR:
+ return "Data from i1 Display didn't parse as expected";
+
+ case HUEY_OK:
+ return "No device error";
+
+ case HUEY_FLOAT_NOT_SET:
+ return "Float value is not set in EEPROM";
+ case HUEY_NOT_READY:
+ return "Command didn't return command code - not ready ?";
+
+ case HUEY_BAD_SERIAL_NUMBER:
+ return "Serial number isn't set";
+ case HUEY_BAD_LCD_CALIBRATION:
+ return "LCD calibration values aren't set";
+ case HUEY_BAD_CRT_CALIBRATION:
+ return "CRT calibration values aren't set";
+ case HUEY_EEPROM_WRITE_FAIL:
+ return "Write to EEPROM failed to verify";
+
+ case HUEY_BAD_WR_LENGTH:
+ return "Unable to write full message to instrument";
+ case HUEY_BAD_RD_LENGTH:
+ return "Unable to read full message to instrument";
+ case HUEY_BAD_RET_CMD:
+ return "Message from instrument didn't echo command code";
+ case HUEY_BAD_RET_STAT:
+ return "Message from instrument had bad status code";
+ case HUEY_UNEXPECTED_RET_VAL:
+ return "Message from instrument has unexpected value";
+
+ case HUEY_BAD_STATUS:
+ return "Instrument status is unrecognised format";
+ case HUEY_UNKNOWN_VERS_ID:
+ return "Instrument version number or ID byte not recognised";
+ case HUEY_BAD_COMMAND:
+ return "Instrument didn't recognise the command";
+
+ /* Internal errors */
+ case HUEY_BAD_REG_ADDRESS:
+ return "Out of range register address";
+ case HUEY_BAD_INT_THRESH:
+ return "Out of range integration threshold";
+ case HUEY_NO_COMS:
+ return "Communications hasn't been established";
+ case HUEY_NOT_INITED:
+ return "Insrument hasn't been initialised";
+ case HUEY_CANT_BLACK_CALIB:
+ return "Device doesn't support black calibration";
+ case HUEY_CANT_MEASP_CALIB:
+ return "Device doesn't support measurment period calibration";
+ case HUEY_WRONG_DEVICE:
+ return "Wrong type of device for called function";
+ default:
+ return "Unknown error code";
+ }
+}
+
+
+/* Convert a machine specific error code into an abstract dtp code */
+static inst_code
+huey_interp_code(inst *pp, int ec) {
+// huey *p = (huey *)pp;
+
+ ec &= inst_imask;
+ switch (ec) {
+
+ case HUEY_OK:
+ case HUEY_FLOAT_NOT_SET: /* Internal indication */
+ case HUEY_NOT_READY: /* Internal indication */
+ return inst_ok;
+
+ case HUEY_INTERNAL_ERROR:
+ case HUEY_BAD_REG_ADDRESS:
+ case HUEY_BAD_INT_THRESH:
+ case HUEY_NO_COMS:
+ case HUEY_NOT_INITED:
+ case HUEY_CANT_BLACK_CALIB:
+ case HUEY_CANT_MEASP_CALIB:
+ case HUEY_WRONG_DEVICE:
+ return inst_internal_error | ec;
+
+ case HUEY_COMS_FAIL:
+ return inst_coms_fail | ec;
+
+ case HUEY_UNKNOWN_MODEL:
+ case HUEY_BAD_STATUS:
+ case HUEY_UNKNOWN_VERS_ID:
+ return inst_unknown_model | ec;
+
+ case HUEY_DATA_PARSE_ERROR:
+ case HUEY_BAD_WR_LENGTH:
+ case HUEY_BAD_RD_LENGTH:
+ case HUEY_BAD_RET_CMD:
+ case HUEY_BAD_RET_STAT:
+ case HUEY_UNEXPECTED_RET_VAL:
+ case HUEY_BAD_COMMAND:
+ return inst_protocol_error | ec;
+
+ case HUEY_BAD_SERIAL_NUMBER:
+ case HUEY_BAD_LCD_CALIBRATION:
+ case HUEY_BAD_CRT_CALIBRATION:
+ case HUEY_EEPROM_WRITE_FAIL:
+ return inst_hardware_fail | ec;
+
+ /* return inst_misread | ec; */
+ /* return inst_needs_cal_2 | ec; */
+ }
+ return inst_other_error | ec;
+}
+
+/* Destroy ourselves */
+static void
+huey_del(inst *pp) {
+ huey *p = (huey *)pp;
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ inst_del_disptype_list(p->dtlist, p->ndtlist);
+ free(p);
+}
+
+/* Return the instrument mode capabilities */
+void huey_capabilities(inst *pp,
+inst_mode *pcap1,
+inst2_capability *pcap2,
+inst3_capability *pcap3) {
+ huey *p = (huey *)pp;
+ inst_mode cap1 = 0;
+ inst2_capability cap2 = 0;
+
+ cap1 |= inst_mode_emis_spot
+ | inst_mode_emis_ambient
+ | inst_mode_colorimeter
+ ;
+
+ cap2 |= inst2_prog_trig
+ | inst2_user_trig
+ | inst2_has_leds
+ | inst2_disptype
+ | inst2_ambient_mono
+ | inst2_ccmx
+ ;
+
+ if (pcap1 != NULL)
+ *pcap1 = cap1;
+ if (pcap2 != NULL)
+ *pcap2 = cap2;
+ if (pcap3 != NULL)
+ *pcap3 = inst3_none;
+}
+
+/* Check device measurement mode */
+inst_code huey_check_mode(inst *pp, inst_mode m) {
+ huey *p = (huey *)pp;
+ inst_mode cap;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ pp->capabilities(pp, &cap, NULL, NULL);
+
+ /* Simple test */
+ if (m & ~cap)
+ return inst_unsupported;
+
+ /* only display emission mode and ambient supported */
+ if (!IMODETST(m, inst_mode_emis_spot)
+ && !IMODETST(m, inst_mode_emis_ambient)) {
+ return inst_unsupported;
+ }
+
+ return inst_ok;
+}
+
+/* Set device measurement mode */
+inst_code huey_set_mode(inst *pp, inst_mode m) {
+ huey *p = (huey *)pp;
+ inst_code ev;
+
+ if ((ev = huey_check_mode(pp, m)) != inst_ok)
+ return ev;
+
+ p->mode = m;
+
+ return inst_ok;
+}
+
+inst_disptypesel huey_disptypesel[3] = {
+ {
+ inst_dtflags_default,
+ 1,
+ "l",
+ "LCD display",
+ 0,
+ 0
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 2, /* cbix */
+ "c", /* sel */
+ "CRT display", /* desc */
+ 1, /* refr */
+ 1 /* ix */
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ 0
+ }
+};
+
+/* Get mode and option details */
+static inst_code huey_get_disptypesel(
+inst *pp,
+int *pnsels, /* Return number of display types */
+inst_disptypesel **psels, /* Return the array of display types */
+int allconfig, /* nz to return list for all configs, not just current. */
+int recreate /* nz to re-check for new ccmx & ccss files */
+) {
+ huey *p = (huey *)pp;
+ inst_code rv = inst_ok;
+
+ /* Create/Re-create a current list of abailable display types */
+ if (p->dtlist == NULL || recreate) {
+ if ((rv = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
+ huey_disptypesel, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return rv;
+ }
+
+ if (pnsels != NULL)
+ *pnsels = p->ndtlist;
+
+ if (psels != NULL)
+ *psels = p->dtlist;
+
+ return inst_ok;
+}
+
+/* Given a display type entry, setup for that type */
+static inst_code set_disp_type(huey *p, inst_disptypesel *dentry) {
+
+ p->icx = dentry->ix;
+ p->refrmode = dentry->refr;
+ p->cbid = dentry->cbid;
+
+ if (dentry->flags & inst_dtflags_ccmx) {
+ icmCpy3x3(p->ccmat, dentry->mat);
+ } else {
+ icmSetUnity3x3(p->ccmat);
+ }
+
+ return inst_ok;
+}
+
+/* Setup the default display type */
+static inst_code set_default_disp_type(huey *p) {
+ inst_code ev;
+ int i;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list((inst *)p, &p->ndtlist, &p->dtlist,
+ huey_disptypesel, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ for (i = 0; !(p->dtlist[i].flags & inst_dtflags_end); i++) {
+ if (p->dtlist[i].flags & inst_dtflags_default)
+ break;
+ }
+ if (p->dtlist[i].flags & inst_dtflags_end) {
+ a1loge(p->log, 1, "set_default_disp_type: failed to find type!\n");
+ return inst_internal_error;
+ }
+ if ((ev = set_disp_type(p, &p->dtlist[i])) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+/* Set the display type */
+static inst_code huey_set_disptype(inst *pp, int ix) {
+ huey *p = (huey *)pp;
+ inst_code ev;
+ inst_disptypesel *dentry;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
+ huey_disptypesel, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ if (ix < 0 || ix >= p->ndtlist)
+ return inst_unsupported;
+
+ dentry = &p->dtlist[ix];
+
+ if ((ev = set_disp_type(p, dentry)) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+/*
+ * set or reset an optional mode
+ *
+ * Some options talk to the instrument, and these will
+ * error if it hasn't been initialised.
+ */
+static inst_code
+huey_get_set_opt(inst *pp, inst_opt_type m, ...)
+{
+ huey *p = (huey *)pp;
+ inst_code ev = inst_ok;
+
+ /* Record the trigger mode */
+ if (m == inst_opt_trig_prog
+ || m == inst_opt_trig_user) {
+ p->trig = m;
+ return inst_ok;
+ }
+
+ /* Get the display type information */
+ if (m == inst_opt_get_dtinfo) {
+ va_list args;
+ int *refrmode, *cbid;
+
+ va_start(args, m);
+ refrmode = va_arg(args, int *);
+ cbid = va_arg(args, int *);
+ va_end(args);
+
+ if (refrmode != NULL)
+ *refrmode = p->refrmode;
+ if (cbid != NULL)
+ *cbid = p->cbid;
+
+ return inst_ok;
+ }
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* Operate the LEDS */
+ if (m == inst_opt_get_gen_ledmask) {
+ va_list args;
+ int *mask = NULL;
+
+ va_start(args, m);
+ mask = va_arg(args, int *);
+ va_end(args);
+ *mask = 0xf; /* Four general LEDs */
+ return inst_ok;
+ } else if (m == inst_opt_get_led_state) {
+ va_list args;
+ int *mask = NULL;
+
+ va_start(args, m);
+ mask = va_arg(args, int *);
+ va_end(args);
+ *mask = p->led_state;
+ return inst_ok;
+ } else if (m == inst_opt_set_led_state) {
+ va_list args;
+ int mask = 0;
+
+ va_start(args, m);
+ mask = va_arg(args, int);
+ va_end(args);
+ return huey_set_LEDs(p, mask);
+ }
+
+ return inst_unsupported;
+}
+
+/* Constructor */
+extern huey *new_huey(icoms *icom, instType itype) {
+ huey *p;
+ if ((p = (huey *)calloc(sizeof(huey),1)) == NULL) {
+ a1loge(icom->log, 1, "new_huey: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(icom->log);
+
+ p->init_coms = huey_init_coms;
+ p->init_inst = huey_init_inst;
+ p->capabilities = huey_capabilities;
+ p->check_mode = huey_check_mode;
+ p->set_mode = huey_set_mode;
+ p->get_disptypesel = huey_get_disptypesel;
+ p->set_disptype = huey_set_disptype;
+ p->get_set_opt = huey_get_set_opt;
+ p->read_sample = huey_read_sample;
+ p->col_cor_mat = huey_col_cor_mat;
+ p->interp_error = huey_interp_error;
+ p->del = huey_del;
+
+ p->icom = icom;
+ p->itype = icom->itype;
+
+ icmSetUnity3x3(p->ccmat); /* Set the colorimeter correction matrix to do nothing */
+
+ return p;
+}
+
diff --git a/spectro/huey.h b/spectro/huey.h
new file mode 100644
index 0000000..6b8858a
--- /dev/null
+++ b/spectro/huey.h
@@ -0,0 +1,134 @@
+#ifndef HUEY_H
+
+/*
+ * Argyll Color Correction System
+ *
+ * GretagMacbeth Huey related defines
+ *
+ * Author: Graeme W. Gill
+ * Date: 7/10/2007
+ *
+ * Copyright 2006 - 2013, Graeme W. Gill
+ * All rights reserved.
+ *
+ * (Based on i1disp.h)
+ *
+ * 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.
+ */
+
+#include "inst.h"
+
+/* Note: update huey_interp_error() and huey_interp_code() in huey.c */
+/* if anything of these #defines are added or subtracted */
+
+/* Fake Error codes */
+#define HUEY_INTERNAL_ERROR 0x61 /* Internal software error */
+#define HUEY_COMS_FAIL 0x62 /* Communication failure */
+#define HUEY_UNKNOWN_MODEL 0x63 /* Not an huey */
+#define HUEY_DATA_PARSE_ERROR 0x64 /* Read data parsing error */
+
+/* Real error code */
+#define HUEY_OK 0x00
+
+/* Sub codes for device specific reasoning */
+#define HUEY_FLOAT_NOT_SET 0x01
+#define HUEY_NOT_READY 0x02
+
+#define HUEY_BAD_SERIAL_NUMBER 0x03
+#define HUEY_BAD_LCD_CALIBRATION 0x04
+#define HUEY_BAD_CRT_CALIBRATION 0x05
+#define HUEY_EEPROM_WRITE_FAIL 0x06
+
+#define HUEY_BAD_WR_LENGTH 0x07
+#define HUEY_BAD_RD_LENGTH 0x08
+#define HUEY_BAD_RET_CMD 0x09
+#define HUEY_BAD_RET_STAT 0x0A
+#define HUEY_UNEXPECTED_RET_VAL 0x0B
+
+#define HUEY_BAD_STATUS 0x0C
+#define HUEY_UNKNOWN_VERS_ID 0x0D
+
+#define HUEY_BAD_COMMAND 0x0E /* Command isn't recognised by instrument */
+
+/* Internal errors */
+#define HUEY_BAD_REG_ADDRESS 0x20
+#define HUEY_BAD_INT_THRESH 0x21
+#define HUEY_NO_COMS 0x22
+#define HUEY_NOT_INITED 0x23
+#define HUEY_CANT_BLACK_CALIB 0x24
+#define HUEY_CANT_MEASP_CALIB 0x25
+#define HUEY_WRONG_DEVICE 0x26
+
+
+/* HUEY communication object */
+struct _huey {
+ INST_OBJ_BASE
+
+ int lenovo; /* 0 = normal, 1 = 'huyL' */
+
+ inst_mode mode; /* Currently selected mode */
+
+ inst_opt_type trig; /* Reading trigger mode */
+
+ /* EEPROM registers */
+ /* Number is the register address, and B, S, W, F indicate the type/size */
+ int ser_no; /* Serial number */
+
+ double LCD_cal[9]; /* LCD 3x3 calibration matrix */
+ int LCD_caltime; /* Calibration time in secs from January 1, 1970, UTC */
+
+ double CRT_cal[9]; /* CRT 3x3 calibration matrix */
+ int CRT_caltime; /* cal valid/time flag. 0xffffffff = invalid ?, */
+ /* time in secs from January 1, 1970, UTC */
+
+ double clk_prd; /* Master clock period, hard coded to 1e-6 */
+
+ double dark_cal[3]; /* Dark current calibration values */
+
+ char unlk_string[5]; /* Unlock string */
+
+ double amb_cal; /* Ambient calibration value */
+
+ /* Computed factors and state */
+ double clk_freq; /* Inverted clk_prd, ie master clock frequency, typically apx 1e6 */
+
+ int sampno; /* Number of CRT samples we're aiming to take, def 100 */
+ int int_clocks; /* Integration time in clocks */
+
+ inst_disptypesel *dtlist; /* Display Type list */
+ int ndtlist; /* Number of valid dtlist entries */
+ int icx; /* 0 = LCD, 1 = CRT matrix */
+ int cbid; /* calibration base ID, 0 if not a base */
+ int refrmode; /* Refresh mode (always 0) */
+ double ccmat[3][3]; /* Colorimeter correction matrix */
+
+ /* Other state */
+ int led_state; /* Current LED state */
+
+}; typedef struct _huey huey;
+
+/* Constructor */
+extern huey *new_huey(icoms *icom, instType itype);
+
+
+#define HUEY_H
+#endif /* HUEY_H */
diff --git a/spectro/i1d3.c b/spectro/i1d3.c
new file mode 100644
index 0000000..53b2f0c
--- /dev/null
+++ b/spectro/i1d3.c
@@ -0,0 +1,3637 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * GretagMacbeth Huey related functions
+ *
+ * Author: Graeme W. Gill
+ * Date: 28/7/2011
+ *
+ * Copyright 2006 - 2013, Graeme W. Gill
+ * All rights reserved.
+ *
+ * (Based on huey.c)
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#include <math.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* SALONEINSTLIB */
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* SALONEINSTLIB */
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "i1d3.h"
+
+#undef PLOT_SPECTRA /* Plot the sensor senitivity spectra */
+#undef SAVE_SPECTRA /* Save the sensor senitivity spectra to "sensors.cmf" */
+#undef PLOT_REFRESH /* Plot data used to determine refresh rate */
+
+static inst_code i1d3_interp_code(inst *pp, int ec);
+static inst_code i1d3_check_unlock(i1d3 *p);
+
+/* ------------------------------------------------------------------- */
+#if defined(__APPLE__) && defined(__POWERPC__)
+
+/* Workaround for a PPC gcc 3.3 optimiser bug... */
+/* It seems to cause a segmentation fault instead of */
+/* converting an integer loop index into a float, */
+/* when there are sufficient variables in play. */
+static int gcc_bug_fix(int i) {
+ static int nn;
+ nn += i;
+ return nn;
+}
+#endif /* APPLE */
+
+/* ------------------------------------------------------------------------ */
+/* Implementation */
+
+/* Interpret an icoms error into a I1D3 error */
+/* If torc is nz, then a trigger or command is OK, */
+/* othewise they are treated as an abort. */
+static int icoms2i1d3_err(int se, int torc) {
+ if (se != ICOM_OK)
+ return I1D3_COMS_FAIL;
+ return I1D3_OK;
+}
+
+/* i1d3 command codes. */
+/* A 64 bit command/response buffer is always used, communicating */
+/* over EP 0x81 and 0x01. The command byte 0 is the major code, */
+/* and byte 1 is the sub code for command 0x00 . The response is byte 0 */
+/* error code, byte 1 echoing the major command number. */
+/* Major code 00 works when locked ? */
+typedef enum {
+ i1d3_getinfo = 0x0000, /* Product name + Firmware version + Firmware Date string */
+ i1d3_status = 0x0001, /* status number ?? */
+ i1d3_prodname = 0x0010, /* Product name string */
+ i1d3_prodtype = 0x0011, /* Product type number */
+ i1d3_firmver = 0x0012, /* Firmware version string */
+ i1d3_firmdate = 0x0013, /* Firmware date string */
+ i1d3_locked = 0x0020, /* Get locked status */
+ i1d3_measure1 = 0x0100, /* Used by all measure */
+ i1d3_measure2 = 0x0200, /* Used by all measure except ambient */
+ i1d3_readintee = 0x0800, /* Read internal EEPROM */
+ i1d3_readextee = 0x1200, /* Read external EEPROM */
+ i1d3_setled = 0x2100, /* Set the LED state */
+ i1d3_rd_sensor = 0x9300, /* Read the analog sensor */
+ i1d3_get_diff = 0x9400, /* Get the diffuser position */
+ i1d3_lockchal = 0x9900, /* Request lock challenge */
+ i1d3_lockresp = 0x9a00, /* Unlock response */
+ i1d3_relock = 0x9b00 /* Close device - relock ? */
+} i1Disp3CC;
+
+/* Diagnostic - return a description given the instruction code */
+static char *inst_desc(i1Disp3CC cc) {
+ static char buf[40]; /* Fallback string */
+ switch(cc) {
+ case i1d3_getinfo:
+ return "GetInfo";
+ case i1d3_status:
+ return "GetStatus";
+ case i1d3_prodname:
+ return "GetProductName";
+ case i1d3_prodtype:
+ return "GetProductType";
+ case i1d3_firmver:
+ return "GetFirmwareVersion";
+ case i1d3_firmdate:
+ return "GetFirmwareDate";
+ case i1d3_locked:
+ return "GetLockedStatus";
+ case i1d3_measure1:
+ return "Measure1";
+ case i1d3_measure2:
+ return "Measure2";
+ case i1d3_readintee:
+ return "ReadInternalEEPROM";
+ case i1d3_readextee:
+ return "ReadExternalEEPROM";
+ case i1d3_setled:
+ return "SetLED";
+ case i1d3_rd_sensor:
+ return "ReadAnalogSensor";
+ case i1d3_get_diff:
+ return "GetDiffuserPositio";
+ case i1d3_lockchal:
+ return "GetLockChallenge";
+ case i1d3_lockresp:
+ return "SendLockResponse";
+ case i1d3_relock:
+ return "ReLock";
+ }
+ sprintf(buf,"Unknown %04x",cc);
+ return buf;
+}
+
+/* Do a command/response exchange with the i1d3. */
+/* Return the error code */
+/* This is protected by a mutex, so it is multi-thread safe. */
+/* The i1d3 is set up as an HID device, which can ease the need */
+/* for providing a kernel driver on MSWindows systems, */
+/* but it doesn't seem to actually be used as an HID device. */
+/* We allow for communicating via libusb, or an HID driver. */
+static inst_code
+i1d3_command(
+ i1d3 *p, /* i1d3 object */
+ i1Disp3CC cc, /* Command code */
+ unsigned char *send, /* 64 Command bytes to send */
+ unsigned char *recv, /* 64 Response bytes returned */
+ double to, /* Timeout in seconds */
+ int nd /* nz to disable debug messages */
+) {
+ unsigned char cmd; /* Major command code */
+ int wbytes; /* bytes written */
+ int rbytes; /* bytes read from ep */
+ int se, ua = 0, rv = inst_ok;
+ int ishid = p->icom->port_type(p->icom) == icomt_hid;
+
+ amutex_lock(p->lock);
+
+ /* Send the command using interrupt transfer to EP 0x01 */
+ send[0] = cmd = (cc >> 8) & 0xff; /* Major command == HID report number */
+ if (cmd == 0x00)
+ send[1] = (cc & 0xff); /* Minor command */
+
+ if (!nd) a1logd(p->log, 4, "i1d3_command: Sending cmd '%s' args '%s'\n",
+ inst_desc(cc), icoms_tohex(send, 8));
+
+ if (p->icom->port_type(p->icom) == icomt_hid) {
+ se = p->icom->hid_write(p->icom, send, 64, &wbytes, to);
+ } else {
+ se = p->icom->usb_write(p->icom, NULL, 0x01, send, 64, &wbytes, to);
+ }
+ if (se != 0) {
+ if (!nd) a1logd(p->log, 1, "i1d3_command: Command send failed with ICOM err 0x%x\n",se);
+ amutex_unlock(p->lock);
+ return i1d3_interp_code((inst *)p, I1D3_COMS_FAIL);
+ }
+ rv = i1d3_interp_code((inst *)p, icoms2i1d3_err(ua, 0));
+ if (!nd) a1logd(p->log, 5, "i1d3_command: ICOM err 0x%x\n",ua);
+
+ if (rv == inst_ok && wbytes != 64) {
+ if (!nd) a1logd(p->log, 1, "i1d3_command: wbytes = %d != 64\n",wbytes);
+ rv = i1d3_interp_code((inst *)p, I1D3_BAD_WR_LENGTH);
+ }
+
+ if (rv != inst_ok) {
+ /* Flush any response */
+ if (ishid) {
+ p->icom->hid_read(p->icom, recv, 64, &rbytes, to);
+ } else {
+ p->icom->usb_read(p->icom, NULL, 0x81, recv, 64, &rbytes, to);
+ }
+ amutex_unlock(p->lock);
+ return rv;
+ }
+
+ /* Now fetch the response */
+ if (!nd) a1logd(p->log, 5, "i1d3_command: Reading response\n");
+
+ if (ishid) {
+ se = p->icom->hid_read(p->icom, recv, 64, &rbytes, to);
+ } else {
+ se = p->icom->usb_read(p->icom, NULL, 0x81, recv, 64, &rbytes, to);
+ }
+ if (se != 0) {
+ if (!nd) a1logd(p->log, 1, "i1d3_command: response read failed with ICOM err 0x%x\n",se);
+ amutex_unlock(p->lock);
+ return i1d3_interp_code((inst *)p, I1D3_COMS_FAIL);
+ }
+ if (rv == inst_ok && rbytes != 64) {
+ if (!nd) a1logd(p->log, 1, "i1d3_command: rbytes = %d != 64\n",rbytes);
+ rv = i1d3_interp_code((inst *)p, I1D3_BAD_RD_LENGTH);
+ }
+
+ /* The first byte returned seems to be a command result error code. */
+ /* The second byte is usually the command code being echo'd back, but not always. */
+ if (rv == inst_ok && recv[0] != 0x00) {
+ if (!nd) a1logd(p->log, 1, "i1d3_command: status byte != 00 = 0x%x\n",recv[0]);
+ rv = i1d3_interp_code((inst *)p, I1D3_BAD_RET_STAT);
+ }
+
+ if (rv == inst_ok) {
+ if (cc != i1d3_get_diff) {
+ if (recv[1] != cmd) {
+ if (!nd) a1logd(p->log, 1, "i1d3_command: major cmd not echo'd != 0x%02x = 0x%02x\n",
+ cmd,recv[1]);
+ rv = i1d3_interp_code((inst *)p, I1D3_BAD_RET_CMD);
+ }
+ }
+ }
+
+ if (!nd) a1logd(p->log, 4, "i1d3_command: got '%s' ICOM err 0x%x\n",icoms_tohex(recv, 14),ua);
+
+ amutex_unlock(p->lock);
+ return rv;
+}
+
+/* Read a packet and time out or throw it away */
+static inst_code
+i1d3_dummy_read(
+ i1d3 *p /* i1d3 object */
+) {
+ unsigned char buf[64];
+ int rbytes; /* bytes read from ep */
+ int se, rv = inst_ok;
+ int ishid = p->icom->port_type(p->icom) == icomt_hid;
+
+ if (ishid) {
+ se = p->icom->hid_read(p->icom, buf, 64, &rbytes, 0.1);
+ } else {
+ se = p->icom->usb_read(p->icom, NULL, 0x81, buf, 64, &rbytes, 0.1);
+ }
+
+ return rv;
+}
+
+/* Byte to int conversion. Most things seem to be little endian... */
+
+/* Take an int, and convert it into a byte buffer */
+static void int2buf(unsigned char *buf, int inv) {
+ buf[0] = (inv >> 0) & 0xff;
+ buf[1] = (inv >> 8) & 0xff;
+ buf[2] = (inv >> 16) & 0xff;
+ buf[3] = (inv >> 24) & 0xff;
+}
+
+/* Take a short, and convert it into a byte buffer */
+static void short2buf(unsigned char *buf, int inv) {
+ buf[0] = (inv >> 0) & 0xff;
+ buf[1] = (inv >> 8) & 0xff;
+}
+
+/* Take a short, and convert it into a byte buffer (Big Endian) */
+static void short2bufBE(unsigned char *buf, int inv) {
+ buf[0] = (inv >> 8) & 0xff;
+ buf[1] = (inv >> 0) & 0xff;
+}
+
+
+/* Take a 64 sized return buffer, and convert it to an ORD64 */
+static ORD64 buf2ord64(unsigned char *buf) {
+ ORD64 val;
+ val = buf[7];
+ val = ((val << 8) + (0xff & buf[6]));
+ val = ((val << 8) + (0xff & buf[5]));
+ val = ((val << 8) + (0xff & buf[4]));
+ val = ((val << 8) + (0xff & buf[3]));
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* Take a word sized return buffer, and convert it to an unsigned int */
+static unsigned int buf2uint(unsigned char *buf) {
+ unsigned int val;
+ val = buf[3];
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* Take a word sized return buffer, and convert it to an int */
+static int buf2int(unsigned char *buf) {
+ int val;
+ val = buf[3];
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* Take a short sized return buffer, and convert it to an int */
+static int buf2short(unsigned char *buf) {
+ int val;
+ val = buf[1];
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Get Product name + Firmware version + Firmware Date string */
+static inst_code
+i1d3_get_info(
+ i1d3 *p, /* Object */
+ char *rv /* 64 byte buffer */
+) {
+ unsigned char todev[64];
+ unsigned char fromdev[64];
+ inst_code ev;
+
+ memset(todev, 0, 64);
+ memset(fromdev, 0, 64);
+
+ if ((ev = i1d3_command(p, i1d3_getinfo, todev, fromdev, 1.0, 0)) != inst_ok)
+ return ev;
+
+ strncpy((char *)rv, (char *)fromdev + 2, 63);
+
+ a1logd(p->log, 3, "i1d3_get_info: got '%s'\n",rv);
+
+ return inst_ok;
+}
+
+/* Check the status. 0 = OK, 1 = BAD */
+/* Not sure what sort of status this is. The result changes some */
+/* other command parameter treatment. Could it be somthing like */
+/* "factory calibrated" status ? */
+static inst_code
+i1d3_check_status(
+ i1d3 *p, /* Object */
+ int *stat /* Status - 0 if OK, 1 if not OK */
+) {
+ unsigned char todev[64];
+ unsigned char fromdev[64];
+ inst_code ev;
+
+ memset(todev, 0, 64);
+ memset(fromdev, 0, 64);
+
+ if ((ev = i1d3_command(p, i1d3_status, todev, fromdev, 1.0, 0)) != inst_ok)
+ return ev;
+
+ *stat = 1; /* Bad */
+ if (fromdev[2] != 0 || (buf2short(fromdev + 3) >= 5))
+ *stat = 0; /* OK */
+
+ a1logd(p->log, 3, "i1d3_check_status: got %s\n",*stat == 0 ? "OK" : "Bad");
+
+ return inst_ok;
+}
+
+/* Get Product name */
+static inst_code
+i1d3_get_prodname(
+ i1d3 *p, /* Object */
+ char *rv /* 32 byte buffer */
+) {
+ unsigned char todev[64];
+ unsigned char fromdev[64];
+ inst_code ev;
+
+ memset(todev, 0, 64);
+ memset(fromdev, 0, 64);
+
+ if ((ev = i1d3_command(p, i1d3_prodname, todev, fromdev, 1.0, 0)) != inst_ok)
+ return ev;
+
+ strncpy((char *)rv, (char *)fromdev + 2, 31);
+
+ a1logd(p->log, 3, "i1d3_get_prodname: got '%s'\n",rv);
+
+ return inst_ok;
+}
+
+/* Get Product type number */
+static inst_code
+i1d3_get_prodtype(
+ i1d3 *p, /* Object */
+ int *stat /* 16 bit version number */
+) {
+ unsigned char todev[64];
+ unsigned char fromdev[64];
+ inst_code ev;
+
+ memset(todev, 0, 64);
+ memset(fromdev, 0, 64);
+
+ if ((ev = i1d3_command(p, i1d3_prodtype, todev, fromdev, 1.0, 0)) != inst_ok)
+ return ev;
+
+ *stat = buf2short(fromdev + 3);
+
+ a1logd(p->log, 3, "i1d3_get_prodtype: got 0x%x\n",*stat);
+
+ return inst_ok;
+}
+
+/* Get firmware version */
+static inst_code
+i1d3_get_firmver(
+ i1d3 *p, /* Object */
+ char *rv /* 32 byte buffer */
+) {
+ unsigned char todev[64];
+ unsigned char fromdev[64];
+ inst_code ev;
+
+ memset(todev, 0, 64);
+ memset(fromdev, 0, 64);
+
+ if ((ev = i1d3_command(p, i1d3_firmver, todev, fromdev, 1.0, 0)) != inst_ok)
+ return ev;
+
+ strncpy((char *)rv, (char *)fromdev + 2, 31);
+
+ a1logd(p->log, 3, "i1d3_get_firmver: got '%s'\n",rv);
+
+ return inst_ok;
+}
+
+/* Get firmware date name */
+static inst_code
+i1d3_get_firmdate(
+ i1d3 *p, /* Object */
+ char *rv /* 32 byte buffer */
+) {
+ unsigned char todev[64];
+ unsigned char fromdev[64];
+ inst_code ev;
+
+ memset(todev, 0, 64);
+ memset(fromdev, 0, 64);
+
+ if ((ev = i1d3_command(p, i1d3_firmdate, todev, fromdev, 1.0, 0)) != inst_ok)
+ return ev;
+
+ strncpy((char *)rv, (char *)fromdev + 2, 31);
+
+ a1logd(p->log, 3, "i1d3_get_firmdate: got '%s'\n",rv);
+
+ return inst_ok;
+}
+
+/* Check the lock status */
+static inst_code
+i1d3_lock_status(
+ i1d3 *p, /* Object */
+ int *stat /* Status - 0 if Unlocked, 1 if locked */
+) {
+ unsigned char todev[64];
+ unsigned char fromdev[64];
+ inst_code ev;
+
+ memset(todev, 0, 64);
+ memset(fromdev, 0, 64);
+
+ if ((ev = i1d3_command(p, i1d3_locked, todev, fromdev, 1.0, 0)) != inst_ok)
+ return ev;
+
+ *stat = 1; /* Locked */
+ if (fromdev[2] != 0 || fromdev[3] == 0)
+ *stat = 0; /* Not Locked */
+
+ a1logd(p->log, 3, "i1d3_lock_status: got %s\n",*stat == 1 ? "Locked" : "Unlocked");
+
+ return inst_ok;
+}
+
+static void create_unlock_response(unsigned int *k, unsigned char *c, unsigned char *r);
+
+
+/* Unlock the device */
+static inst_code
+i1d3_unlock(
+ i1d3 *p /* Object */
+) {
+ unsigned char todev[64];
+ unsigned char fromdev[64];
+ struct {
+ char *pname; /* Product name */
+ unsigned int key[2]; /* Unlock code */
+ i1d3_dtype dtype; /* Base type enumerator */
+ i1d3_dtype stype; /* Sub type enumerator */
+ } codes[] = {
+ { "i1Display3 ", { 0xe9622e9f, 0x8d63e133 }, i1d3_disppro, i1d3_disppro },
+ { "Colormunki Display ", { 0xe01e6e0a, 0x257462de }, i1d3_munkdisp, i1d3_munkdisp },
+ { "i1Display3 ", { 0xcaa62b2c, 0x30815b61 }, i1d3_disppro, i1d3_oem },
+ { "i1Display3 ", { 0xa9119479, 0x5b168761 }, i1d3_disppro, i1d3_nec_ssp },
+ { "i1Display3 ", { 0x160eb6ae, 0x14440e70 }, i1d3_disppro, i1d3_quato_sh3 },
+ { NULL }
+ };
+ inst_code ev;
+ int ix;
+
+ a1logd(p->log, 2, "i1d3_unlock: called\n");
+
+ /* Until we give up */
+ for (ix = 0;;ix++) {
+
+ /* If we've run out of unlock keys */
+ if (codes[ix].pname == NULL) {
+ a1logd(p->log, 1, "i1d3: Unknown lock code. Please contact ArgyllCMS for help\n");
+ return i1d3_interp_code((inst *)p, I1D3_UNKNOWN_UNLOCK);
+ }
+
+// return i1d3_interp_code((inst *)p, I1D3_UNLOCK_FAIL);
+
+ /* Skip any keys that don't match the product name */
+ if (strcmp(p->prod_name, codes[ix].pname) != 0) {
+ continue;
+ }
+
+// a1logd(p->log, 3, "i1d3_unlock: Trying unlock key 0x%08x 0x%08x\n",
+// codes[ix].key[0], codes[ix].key[1]);
+
+ p->dtype = codes[ix].dtype;
+ p->stype = codes[ix].stype;
+
+ /* Attempt unlock */
+ memset(todev, 0, 64);
+ memset(fromdev, 0, 64);
+
+ /* Get a challenge */
+ if ((ev = i1d3_command(p, i1d3_lockchal, todev, fromdev, 1.0, 0)) != inst_ok)
+ return ev;
+
+ /* Convert challenge to response */
+ create_unlock_response(codes[ix].key, fromdev, todev);
+
+ /* Send the response */
+ if ((ev = i1d3_command(p, i1d3_lockresp, todev, fromdev, 1.0, 0)) != inst_ok)
+ return ev;
+
+ if (fromdev[2] == 0x77) { /* Sucess */
+ break;
+ }
+
+ a1logd(p->log, 3, "i1d3_unlock: Trying next unlock key\n");
+ /* Try the next key */
+ }
+
+ return inst_ok;
+}
+
+/* Get the ambient diffuser position */
+static inst_code
+i1d3_get_diffpos(
+ i1d3 *p, /* Object */
+ int *pos, /* 0 = display, 1 = ambient */
+ int nd /* nz = no debug message */
+) {
+ unsigned char todev[64];
+ unsigned char fromdev[64];
+ inst_code ev;
+
+ memset(todev, 0, 64);
+ memset(fromdev, 0, 64);
+
+ if ((ev = i1d3_command(p, i1d3_get_diff, todev, fromdev, 1.0, nd)) != inst_ok)
+ return ev;
+
+ *pos = fromdev[1];
+
+ if (nd == 0)
+ a1logd(p->log, 3, "i1d3_get_diffpos: got %d\n",*pos);
+
+ return inst_ok;
+}
+
+/* Read bytes from the internal EEPROM */
+static inst_code
+i1d3_read_internal_eeprom(
+ i1d3 *p, /* Object */
+ int addr, /* address, 0 .. 255 */
+ int len, /* length, 0 .. 255 */
+ unsigned char *bytes /* return bytes here */
+) {
+ inst_code ev;
+ unsigned char todev[64];
+ unsigned char fromdev[64];
+ int ll;
+
+ if (addr < 0 || addr > 255)
+ return i1d3_interp_code((inst *)p, I1D3_BAD_MEM_ADDRESS);
+
+ if (len < 0 || (addr + len) > 256)
+ return i1d3_interp_code((inst *)p, I1D3_BAD_MEM_LENGTH);
+
+ memset(todev, 0, 64);
+ memset(fromdev, 0, 64);
+
+ /* Bread read up into 60 bytes packets */
+ for (; len > 0; addr += ll, bytes += ll, len -= ll) {
+ ll = len;
+ if (ll > 60)
+ ll = 60;
+
+ /* OEM driver retries several times after a 10msec sleep on failure. */
+ /* Can a failure actually happen though ? */
+ todev[1] = (unsigned char)addr;
+ todev[2] = (unsigned char)ll;
+
+ if ((ev = i1d3_command(p, i1d3_readintee, todev, fromdev, 1.0, 0)) != inst_ok) {
+ return ev;
+ }
+
+ memmove(bytes, fromdev + 4, ll);
+ }
+
+ return inst_ok;
+}
+
+/* Read bytes from the external EEPROM */
+static inst_code
+i1d3_read_external_eeprom(
+ i1d3 *p, /* Object */
+ int addr, /* address, 0 .. 8191 */
+ int len, /* length, 0 .. 8192 */
+ unsigned char *bytes /* return bytes here */
+) {
+ inst_code ev;
+ unsigned char todev[64];
+ unsigned char fromdev[64];
+ int ll;
+ int sdebug;
+
+ if (addr < 0 || addr > 8191)
+ return i1d3_interp_code((inst *)p, I1D3_BAD_MEM_ADDRESS);
+
+ if (len < 0 || (addr + len) > 8192)
+ return i1d3_interp_code((inst *)p, I1D3_BAD_MEM_LENGTH);
+
+ memset(todev, 0, 64);
+ memset(fromdev, 0, 64);
+
+ /* Bread read up into 59 bytes packets */
+ sdebug = p->log->debug;
+ p->log->debug = p->log->debug >= 2 ? p->log->debug - 2 : 0; /* Supress command traces */
+ for (; len > 0; addr += ll, bytes += ll, len -= ll) {
+ ll = len;
+ if (ll > 59)
+ ll = 59;
+
+ /* OEM driver retries several times after a 10msec sleep on failure. */
+ /* Can a failure actually happen though ? */
+ short2bufBE(todev + 1, addr);
+ todev[3] = (unsigned char)ll;
+
+ if ((ev = i1d3_command(p, i1d3_readextee, todev, fromdev, 1.0, 0)) != inst_ok) {
+ p->log->debug = sdebug;
+ return ev;
+ }
+
+ memmove(bytes, fromdev + 5, ll);
+ }
+ p->log->debug = sdebug;
+
+ return inst_ok;
+}
+
+
+/* Take a raw measurement using a given integration time. */
+/* The measureent is the count of (both) edges from the L2V */
+/* over the integration time */
+static inst_code
+i1d3_freq_measure(
+ i1d3 *p, /* Object */
+ double *inttime, /* Integration time in seconds. (Return clock rounded) */
+ double rgb[3] /* Return the RGB values */
+) {
+ int intclks;
+ unsigned char todev[64];
+ unsigned char fromdev[64];
+ inst_code ev;
+
+ memset(todev, 0, 64);
+ memset(fromdev, 0, 64);
+
+ if (*inttime > 20.0) /* Hmm */
+ *inttime = 20.0;
+
+ /* Max = 357.9 seconds ? */
+ intclks = (int)(*inttime * p->clk_freq + 0.5);
+ *inttime = (double)intclks / p->clk_freq;
+
+ int2buf(todev + 1, intclks);
+
+ todev[23] = 0; /* Unknown parameter, always 0 */
+
+ if ((ev = i1d3_command(p, i1d3_measure1, todev, fromdev, 20.0, 0)) != inst_ok)
+ return ev;
+
+ rgb[0] = (double)buf2uint(fromdev + 2);
+ rgb[1] = (double)buf2uint(fromdev + 6);
+ rgb[2] = (double)buf2uint(fromdev + 10);
+
+ return inst_ok;
+}
+
+/* Take a raw measurement that returns the number of clocks */
+/* between and initial edge and edgec[] subsequent edges of the L2F. */
+/* The edge count must be between 1 and 65535 inclusive. */
+/* Both edges are counted. It's advisable to use and even edgec[], */
+/* because the L2F output may not be symetric. */
+/* If there are no edges within 10 seconds, return a count of 0 */
+static inst_code
+i1d3_period_measure(
+ i1d3 *p, /* Object */
+ int edgec[3], /* Measurement edge count for each channel */
+ int mask, /* Bit mask to enable channels */
+ double rgb[3] /* Return the RGB values */
+) {
+ unsigned char todev[64];
+ unsigned char fromdev[64];
+ inst_code ev;
+
+ memset(todev, 0, 64);
+ memset(fromdev, 0, 64);
+
+ short2buf(todev + 1, edgec[0]);
+ short2buf(todev + 3, edgec[1]);
+ short2buf(todev + 5, edgec[2]);
+
+ todev[7] = (unsigned char)mask;
+ todev[8] = 0; /* Unknown parameter, always 0 */
+
+ if ((ev = i1d3_command(p, i1d3_measure2, todev, fromdev, 20.0, 0)) != inst_ok)
+ return ev;
+
+ rgb[0] = (double)buf2uint(fromdev + 2);
+ rgb[1] = (double)buf2uint(fromdev + 6);
+ rgb[2] = (double)buf2uint(fromdev + 10);
+
+ return inst_ok;
+}
+
+typedef enum {
+ i1d3_flash = 1,
+ i1d3_fade = 3,
+} i1d3_ledmode;
+
+static inst_code
+i1d3_set_LEDs(
+ i1d3 *p, /* Object */
+ i1d3_ledmode mode, /* 1 = off & on, 3 = off & fade on */
+ double offtime, /* Off time */
+ double ontime, /* On time. Fade is included in this */
+ int count /* Pulse count. 0x80 = infinity ? */
+) {
+ unsigned char todev[64];
+ unsigned char fromdev[64];
+ inst_code ev;
+ double mul1, mul2;
+ int ftime, ntime;
+
+ memset(todev, 0, 64);
+ memset(fromdev, 0, 64);
+
+ mul1 = p->clk_freq/(1 << 23);
+ mul2 = p->clk_freq/(1 << 19);
+
+ ftime = (int)(0.5 + offtime * mul2);
+ if (ftime < 0)
+ ftime = 0;
+ else if (ftime > 255)
+ ftime = 255;
+
+ if (mode == 1)
+ ntime = (int)(0.5 + ontime * mul2);
+ else if (mode == 3)
+ ntime = (int)(0.5 + ontime * mul1);
+ else
+ return i1d3_interp_code((inst *)p, I1D3_BAD_LED_MODE);
+
+ if (ntime < 0)
+ ntime = 0;
+ else if (ntime > 255)
+ ntime = 255;
+
+ if (count < 0)
+ count = 0;
+ else if (count > 0x80)
+ count = 0x80;
+
+ todev[1] = (unsigned char)mode;
+ todev[2] = (unsigned char)ftime;
+ todev[3] = (unsigned char)ntime;
+ todev[4] = (unsigned char)count;
+
+ if ((ev = i1d3_command(p, i1d3_setled, todev, fromdev, 1.0, 0)) != inst_ok)
+ return ev;
+
+ return inst_ok;
+}
+
+
+
+/* - - - - - - - - - - - - - - - - - - - - - - */
+/*
+
+ determining the refresh rate for a refresh type display;
+
+ Read 1300 .5 msec samples as fast as possible, and
+ timestamp them.
+ Interpolate values up to .05 msec regular samples.
+ Do an auto-correlation on the samples.
+ Pick the longest peak between 10 andf 40Hz as the best sample period,
+ and halve this to use as the quantization value (ie. make
+ it lie between 20 and 80 Hz).
+
+ If there was no error, return refresh quanization period it.
+
+ If there is no aparent refresh, or the refresh rate is not determinable,
+ return a period of 0.0 and inst_ok;
+
+ To break up the USB synchronization, the integration time
+ is randomized slightly.
+*/
+
+#ifndef PSRAND32L
+# define PSRAND32L(S) ((S) * 1664525L + 1013904223L)
+#endif
+#undef FREQ_SLOW_PRECISE /* [und] Interpolate then autocorrelate, else autc & filter */
+#define NFSAMPS 1300 /* 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)/5) /* 5 Hz*/
+#define NPER (PERMAX - PERMIN + 1)
+//#define PWIDTH (3 * PBPMS) /* 3 msec bin spread to look for peak in */
+#define PWIDTH (8 * PBPMS) /* 3 msec bin spread to look for peak in */
+#define MAXPKS 20 /* Number of peaks to find */
+
+/* Set refperiod, refrate if possible */
+static inst_code
+i1d3_imp_measure_refresh(
+ i1d3 *p, /* Object */
+ double *prefrate, /* Return value, 0.0 if none */
+ double *ppval /* Return period value, 0.0 if none */
+) {
+ inst_code ev;
+ int i, j, k;
+ double ucalf = 1.0; /* usec_time calibration factor */
+ double inttimel = 0.0003;
+ double inttimeh = 0.0040;
+ double sutime, putime, cutime, eutime;
+ static unsigned int randn = 0x12345678;
+ struct {
+ double itime; /* Integration time */
+ double sec;
+ double rgb[3];
+ } samp[NFSAMPS];
+ int nfsamps; /* Actual samples read */
+ double maxt; /* Time range */
+ double rms[3]; /* RMS value of each channel */
+ double trms; /* Total RMS */
+ int nbins;
+ double *bins[3]; /* PBPMS sample bins */
+ double tcorr[NPER]; /* Temp for initial autocorrelation */
+ double corr[NPER]; /* Filtered correlation for each period value */
+ double mincv, maxcv; /* Max and min correlation values */
+ double crange; /* Correlation range */
+ double peaks[MAXPKS]; /* Each peak from longest to shortest */
+ int npeaks = 0; /* Number of peaks */
+ double pval; /* Period value */
+ int isdeb;
+
+ if (prefrate != NULL)
+ *prefrate = 0.0;
+ if (ppval != NULL)
+ *ppval = 0.0;
+
+ if (usec_time() < 0.0) {
+ a1loge(p->log, inst_internal_error, "i1d3_measure_refresh: No high resolution timers\n");
+ return inst_internal_error;
+ }
+
+ /* Turn debug off so that it doesn't intefere with measurement timing */
+ isdeb = p->log->debug;
+ p->icom->log->debug = 0;
+
+ /* Do some measurement and throw them away, to make sure the code is in cache. */
+ for (i = 0; i < 5; i++) {
+ if ((ev = i1d3_freq_measure(p, &inttimeh, samp[i].rgb)) != inst_ok) {
+ p->log->debug = isdeb;
+ return ev;
+ }
+ }
+
+#ifdef NEVER /* This appears to be unnecessary */
+ /* Calibrate the usec timer against the instrument */
+ {
+ double inttime1, inttime2;
+ inttime1 = 0.001;
+ inttime2 = 0.501;
+
+ sutime = usec_time();
+
+ if ((ev = i1d3_freq_measure(p, &inttime1, samp[0].rgb)) != inst_ok) {
+ p->log->debug = isdeb;
+ return ev;
+ }
+
+ putime = usec_time();
+
+ if ((ev = i1d3_freq_measure(p, &inttime2, samp[0].rgb)) != inst_ok) {
+ p->log->debug = isdeb;
+ return ev;
+ }
+
+ cutime = usec_time();
+
+ ucalf = 1000000.0 * (inttime2 - inttime1)/(cutime - 2.0 * putime + sutime);
+
+ a1logd(p->log, 3, "i1d3_measure_refresh: Clock calibration factor = %f\n",ucalf);
+ }
+#endif
+
+ /* Read the samples */
+ sutime = usec_time();
+ putime = (usec_time() - sutime) / 1000000.0;
+ for (i = 0; i < NFSAMPS; i++) {
+ double rval;
+
+ randn = PSRAND32L(randn);
+ rval = (double)randn/4294967295.0;
+ rval *= rval;
+ rval *= rval; /* Sharpen random time up */
+ samp[i].itime = (inttimeh - inttimel) * rval + inttimel;
+
+ if ((ev = i1d3_freq_measure(p, &samp[i].itime, samp[i].rgb)) != inst_ok) {
+ p->log->debug = isdeb;
+ return ev;
+ }
+ cutime = (usec_time() - sutime) / 1000000.0;
+// ~~999
+ samp[i].sec = 0.5 * (putime + cutime); /* Mean of before and after stamp */
+//samp[i].sec *= 85.0/20.0; /* Test 20 Hz */
+//samp[i].sec *= 85.0/100.0; /* Test 100 Hz */
+ putime = cutime;
+ if (cutime > NFMXTIME)
+ break;
+ }
+ p->log->debug = isdeb;
+
+ nfsamps = i;
+ if (nfsamps < 100) {
+ a1logv(p->log, 1, "No distict refresh period\n");
+ a1logd(p->log, 3, "i1d3_measure_refresh: Couldn't find a distinct refresh frequency\n");
+ return inst_ok;
+ }
+
+ a1logd(p->log, 3, "i1d3_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
+
+ /* Re-zero the sample times, normalise int time, and calibrate it. */
+ maxt = -1e6;
+ rms[0] = rms[1] = rms[2] = 0.0;
+ 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] /= samp[i].itime;
+ rms[j] += samp[i].rgb[j] * samp[i].rgb[j];
+ }
+ }
+ trms = 0.0;
+ for (j = 0; j < 3; j++) {
+ rms[j] /= (double)nfsamps;
+ trms += rms[j];
+ rms[j] = sqrt(rms[j]);
+ }
+ trms = sqrt(trms);
+ a1logd(p->log, 4, "RMS = %f %f %f, total %f\n", rms[0], rms[1], rms[2], trms);
+
+#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, "i1d3_measure_refresh: malloc failed\n");
+ return inst_internal_error;
+ }
+ }
+
+ /* 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, "i1d3_measure_refresh: malloc failed\n");
+ for (j = 0; j < 3; j++)
+ free(bins[j]);
+ return inst_internal_error;
+ }
+ 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)\n");
+ 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.0;
+
+ for (k = 0; (k + poff) < nbins; k++)
+ corr[i] += bins[0][k] * bins[0][k + poff];
+ for (k = 0; (k + poff) < nbins; k++)
+ corr[i] += bins[1][k] * bins[1][k + poff];
+ for (k = 0; (k + poff) < nbins; k++)
+ corr[i] += 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];
+ }
+ for (j = 0; j < 3; j++)
+ free(bins[j]);
+
+#else /* !FREQ_SLOW_PRECISE Fast - autocorrellate then filter */
+
+ /* Do point by point correllation of samples */
+ for (i = 0; i < NPER; i++)
+ tcorr[i] = 0.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];
+
+ cor = samp[j].rgb[1] * samp[k].rgb[1];
+
+ tcorr[bix] += cor;
+ }
+ }
+
+#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 5 msec gausian filter */
+#define FWIDTH 6
+ {
+ double gaus_[2 * FWIDTH * PBPMS + 1];
+ double *gaus = &gaus_[FWIDTH * PBPMS];
+ double bb = 1.0/pow(2, 5.0);
+
+ for (j = (-FWIDTH * PBPMS); j <= (FWIDTH * PBPMS); j++) {
+ double tt;
+ tt = (double)j/(FWIDTH * PBPMS);
+ gaus[j] = 1.0/pow(2, 5.0 * tt * tt) - bb;
+ }
+ for (k = 0; k < 1; k++) {
+ for (i = 0; i < NPER; i++) {
+ double sum = 0.0;
+ double wght = 0.0;
+
+ for (j = (-FWIDTH * PBPMS); j <= (FWIDTH * PBPMS); j++) {
+ double w;
+ int ix = i + j;
+ if (ix < 0)
+ continue;
+ if (ix > (NPER-1))
+ break;
+ w = gaus[j];
+ sum += w * tcorr[ix];
+ wght += w;
+ }
+ 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,4,"Correlation value range %f - %f = %f = %f%%\n",mincv, maxcv,crange, 100.0 * (maxcv-mincv)/maxcv);
+
+#ifdef PLOT_REFRESH
+ /* Plot 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 */
+
+ /* If there is sufficient level and distict correlations */
+ if (trms >= 1000 && (maxcv-mincv)/maxcv >= 0.10) {
+
+ /* 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];
+ v3 = corr[i + PWIDTH];
+
+ if (fabs(v3 - v1) < (0.05 * crange)
+ && (v2 - v1) > (0.025 * crange)
+ && (v2 - v3) > (0.025 * crange)) {
+ double pkv; /* Peak value */
+ int pki; /* Peak index */
+ double ii, bl;
+
+ a1logd(p->log,4,"Max between %f and %f msec\n",
+ (i + PERMIN)/(double)PBPMS,(i + PWIDTH + PERMIN)/(double)PBPMS);
+
+ /* 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;
+ }
+ }
+ a1logd(p->log,4,"Peak is at %f msec, %f corr\n", (pki + PERMIN)/(double)PBPMS, pkv);
+
+ /* 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;
+
+ a1logd(p->log,4,"Interpolated peak is at %f msec\n", pval);
+
+ peaks[npeaks++] = pval;
+
+ i -= PWIDTH;
+ }
+ }
+ }
+
+ a1logd(p->log,3,"Number of peaks located = %d\n",npeaks);
+ if (npeaks == 0) {
+ a1logd(p->log, 2, "i1d3: Couldn't find a distinct refresh frequency\n");
+ a1logv(p->log, 1, "No distict refresh period\n");
+ return inst_ok;
+ }
+
+ if (npeaks == 1) {
+ a1logd(p->log,3,"Only one peak\n");
+ pval = peaks[0] / 2000.0; /* Scale by half and convert to seconds */
+
+ a1logd(p->log, 1, "Quantizing to %f msec\n",pval);
+ a1logv(p->log, 1, "Quantizing to %f msec\n",pval);
+
+ if (ppval != NULL)
+ *ppval = pval;
+
+ } else {
+ 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 < 20; j++) {
+ avg = ano = 0.0;
+ div = peaks[k]/(double)j;
+ if (div < 9.0)
+ continue; /* Skip anything over 100Hz */
+ for (nfails = i = 0; i < npeaks; i++) {
+ double rem, cnt;
+
+ rem = peaks[i]/div;
+ cnt = floor(rem + 0.5);
+ rem = fabs(rem - cnt);
+
+ a1logd(p->log, 1, "remainder for peak %d = %f\n",i,rem);
+ if (rem > 0.06) {
+ if (++nfails > 2)
+ break; /* Fail this divisor */
+ }
+ avg += peaks[i]; /* Already weighted by cnt */
+ ano += cnt;
+ }
+
+// if (i >= npeaks)
+ if (nfails == 0 || (nfails <= 2 && npeaks >= 6))
+ break; /* Sucess */
+ /* else go and try a different divisor */
+ }
+ if (j < 20)
+ break; /* Found common divisor */
+ }
+ if (k >= npeaks) {
+ a1logd(p->log,3,"Failed to locate common divisor\n");
+ pval = peaks[0] / 2000.0; /* Scale by half and convert to seconds */
+
+ if (ppval != NULL)
+ *ppval = pval;
+
+
+ a1logd(p->log, 1, "Quantizing to %f msec\n",pval);
+ a1logv(p->log, 1, "Quantizing to %f msec\n",pval);
+
+ } else {
+ int mul;
+ double refrate;
+
+ pval = avg/ano;
+ pval /= 1000.0; /* Convert to seconds */
+ refrate = 1.0/pval;
+
+ if (prefrate != NULL)
+ *prefrate = refrate; /* Save it for get_refr_rate() */
+
+ /* Error against my 85Hz CRT - GWG */
+// a1logd(p->log, 1, "Refresh rate error = %.4f%%\n",100.0 * fabs(refrate - 85.0)/(85.0));
+
+ /* Scale to just above 20 Hz */
+ mul = floor((1.0/20) / pval);
+ if (mul > 1)
+ pval *= mul;
+
+ a1logd(p->log, 1, "Refresh rate = %f Hz, quantizing to %f msec\n",refrate,pval);
+ a1logv(p->log, 1, "Refresh rate = %f Hz, quantizing to %f msec\n",refrate,pval);
+
+ if (ppval != NULL)
+ *ppval = pval;
+ }
+ }
+
+ return inst_ok;
+}
+#undef NFSAMPS
+#undef PBPMS
+#undef PERMIN
+#undef PERMAX
+#undef NPER
+#undef PWIDTH
+
+/* Measure and then set refperiod, refrate if possible */
+static inst_code
+i1d3_measure_set_refresh(
+ i1d3 *p /* Object */
+) {
+ inst_code rv;
+ double refrate = 0.0;
+ int mul;
+ double pval;
+
+ if ((rv = i1d3_imp_measure_refresh(p, &refrate, &pval)) != inst_ok) {
+ return rv;
+ }
+
+ p->refrate = refrate;
+ p->refrvalid = refrate != 0.0 ? 1 : 0;
+ p->refperiod = pval;
+ p->rrset = 1;
+
+ return inst_ok;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Take an ambient measurement and return the cooked reading */
+/* The cooked reading is the frequency of the L2V */
+static inst_code
+i1d3_take_amb_measurement(
+ i1d3 *p, /* Object */
+ double *rgb /* Return the ambient RGB values */
+) {
+ int i; /* Returned byte - not used */
+ int pos;
+ inst_code ev;
+
+ if (p->inited == 0)
+ return i1d3_interp_code((inst *)p, I1D3_NOT_INITED);
+
+ a1logd(p->log,3,"take_amb_measurement called\n");
+
+ /* Check that the ambient filter is in place */
+ if ((ev = i1d3_get_diffpos(p, &pos, 0)) != inst_ok)
+ return ev;
+
+ if (pos != 1)
+ return i1d3_interp_code((inst *)p, I1D3_SPOS_AMB);
+
+ if ((ev = i1d3_freq_measure(p, &p->inttime, rgb)) != inst_ok)
+ return ev;
+
+ /* Scale to account for counting both edges (?) over integration time */
+ /* and subtract black level */
+ for (i = 0; i < 3; i++) {
+ rgb[i] *= 0.5/p->inttime;
+ rgb[i] -= p->black[i];
+ if (rgb[i] < 0.0)
+ rgb[i] = 0.0;
+ }
+
+ a1logd(p->log,3,"take_amb_measurement returned %f %f %f\n",rgb[0],rgb[1],rgb[2]);
+
+ return inst_ok;
+}
+
+
+
+/* - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Take an display measurement and return the cooked reading */
+/* The cooked reading is the frequency of the L2V */
+static inst_code
+i1d3_take_emis_measurement(
+ i1d3 *p, /* Object */
+ i1d3_mmode mode, /* Measurement mode */
+ double *rgb /* Return the cooked emsissive RGB values */
+) {
+ int i, k;
+ int pos;
+ inst_code ev;
+ double rmeas[3] = { -1.0, -1.0, -1.0 }; /* raw measurement */
+ int edgec[3] = {2,2,2}; /* Measurement edge count for each channel (not counting start edge) */
+ int mask = 0x7; /* Period measure mask */
+ int msecstart = msec_time(); /* Debug */
+ double rgb2[3] = { 0.0, 0.0, 0.0 }; /* Trial measurement RGB values */
+
+ if (p->inited == 0)
+ return i1d3_interp_code((inst *)p, I1D3_NOT_INITED);
+
+ a1logd(p->log,3,"\ntake_emis_measurement called\n");
+
+ /* Check that the ambient filter is not in place */
+ if ((ev = i1d3_get_diffpos(p, &pos, 0)) != inst_ok)
+ return ev;
+
+ if (pos != 0)
+ return i1d3_interp_code((inst *)p, I1D3_SPOS_EMIS);
+
+
+ /* If we should take a frequency measurement first */
+ if (mode == i1d3_adaptive || mode == i1d3_frequency) {
+
+ /* Typically this is 200msec for non-refresh, 400msec for refresh. */
+ a1logd(p->log,3,"Doing fixed period frequency measurement over %f secs\n",p->inttime);
+
+ /* Take a frequency measurement over a fixed period */
+ if ((ev = i1d3_freq_measure(p, &p->inttime, rmeas)) != inst_ok)
+ return ev;
+
+ /* Convert to frequency (assume raw meas is both edges count over integration time) */
+ for (i = 0; i < 3; i++) {
+ rgb[i] = (0.5 * rmeas[i])/p->inttime;
+ }
+
+ a1logd(p->log,3,"Got %f %f %f raw, %f %f %f Hz\n",rmeas[0],rmeas[1],rmeas[2],rgb[0],rgb[1],rgb[2]);
+ }
+
+ /* If some period measurement will be done */
+ if (mode != i1d3_frequency) {
+
+ /* Decide if a period measurement is needed */
+ if (mode == i1d3_adaptive) {
+
+ mask = 0x0;
+
+ for (i = 0; i < 3; i++) {
+ /* Not measured or count is too small for desired precision. */
+ /* (We're being twice as critical as the OEM driver here) */
+ if (rmeas[i] < 200.0) { /* Could be 0.25% quantization error */
+ a1logd(p->log,3,"chan %d needs re-reading\n",i);
+ mask |= 1 << i;
+ } else {
+ a1logd(p->log,3,"chan %d has sufficient frequeny count\n",i);
+ }
+ }
+ }
+
+ if (mask != 0x0) { /* Some measurement wasn't accurate enough, so use period */
+ /* or longer frequency measurement */
+ int mask2 = mask;
+ double tintt[3]; /* Per channel re-measure target int. times */
+ double tinttime; /* Maximum re-measure target integration time */
+
+ /* See if we need to do some pre-measurement to compute how many */
+ /* edges to count. */
+ for (i = 0; i < 3; i++) {
+ if ((mask & (1 << i)) == 0)
+ continue;
+
+ if (rmeas[i] < 10.0
+ || (p->dtype != i1d3_munkdisp && rmeas[i] < 20.0)) {
+ a1logd(p->log,3,"chan %d needs pre-measurement\n",i);
+ mask2 |= 1 << i;
+ } else {
+ double freq;
+ mask2 &= ~(1 << i);
+ /* Convert rmeas[i] from frequency to period equivalent */
+ /* for subsequent calculations */
+ freq = (rmeas[i] * 0.5)/p->inttime;
+ rmeas[i] = (0.5 * edgec[i] * p->clk_freq)/freq;
+ a1logd(p->log,3,"chan %d has sufficient frequeny count to avoid pre-measure (rmeas_p %f)\n",i,rmeas[i]);
+ }
+ }
+ if (mask2 != 0x0) {
+ int mask3 = 0x0;
+ double rmeas2[3];
+
+ a1logd(p->log,3,"Doing 1st period pre-measurement mask 0x%x, edgec %d %d %d\n",mask2,edgec[0],edgec[1],edgec[2]);
+ /* Take an initial period pre-measurement over 2 edges */
+ if ((ev = i1d3_period_measure(p, edgec, mask2, rmeas2)) != inst_ok)
+ return ev;
+
+ a1logd(p->log,3,"Got %f %f %f raw %f %f %f Hz\n",rmeas2[0],rmeas2[1],rmeas2[2],
+ 0.5 * edgec[0] * p->clk_freq/rmeas2[0],
+ 0.5 * edgec[1] * p->clk_freq/rmeas2[1],
+ 0.5 * edgec[2] * p->clk_freq/rmeas2[2]);
+
+ /* Transfer updated counts from 1st initial measurement */
+ for (i = 0; i < 3; i++) {
+ if ((mask2 & (1 << i)) != 0) {
+ rmeas[i] = rmeas2[i];
+
+ /* Compute trial RGB in case we need it later */
+ if (rmeas[i] >= 0.5) {
+ rgb2[i] = (p->clk_freq * 0.5 * edgec[i])/rmeas[i];
+ }
+ }
+ }
+
+ /* Do 2nd initial measurement if the count is small, in case */
+ /* we are measuring a CRT with a refresh rate which adds innacuracy, */
+ /* and could result in a unecessarily long re-reading. */
+ /* Don't do this for Munki Display, because of its slow measurements. */
+ if (p->dtype != i1d3_munkdisp) {
+ for (i = 0; i < 3; i++) {
+ if ((mask2 & (1 << i)) == 0)
+ continue;
+
+ if (rmeas2[i] > 0.5) {
+ double pintt, nedgec;
+ int inedgec;
+
+ /* Compute number of edges needed for a clock count */
+ /* of 0.100 seconds */
+
+ pintt = 0.1;
+
+ if (p->refperiod > 0.0) { /* If we have a refresh period */
+ int n;
+ n = (int)ceil(pintt/p->refperiod); /* Quantize */
+ pintt = n * p->refperiod;
+ }
+
+ nedgec = edgec[i] * pintt * p->clk_freq/rmeas2[i];
+
+ a1logd(p->log,3,"chan %d target edges %f\n",i,nedgec);
+
+ /* Limit to a legal range */
+ if (nedgec > 65534.0)
+ nedgec = 65534.0;
+ else if (nedgec < 2.0)
+ nedgec = 2.0;
+
+ /* Round down to nearest even edge count */
+ inedgec = 2.0 * (int)floor(nedgec/2.0);
+
+ a1logd(p->log,3,"chan %d set edgec to %d\n",i,inedgec);
+
+ /* Don't do 2nd initial measure if we have fewer number of edges */
+ if (inedgec > edgec[i]) {
+ mask3 |= (1 << i);
+ edgec[i] = (int)inedgec;
+ }
+ } else {
+ a1logd(p->log,3,"chan %d had no reading, so skipping period measurement\n",i);
+ }
+ }
+ if (mask3 != 0x0) {
+
+ a1logd(p->log,3,"Doing 2nd initial period measurement mask 0x%x, edgec %d %d %d\n",mask2,edgec[0],edgec[1],edgec[2]);
+ /* Take a 2nd initial period measurement */
+ if ((ev = i1d3_period_measure(p, edgec, mask3, rmeas2)) != inst_ok)
+ return ev;
+
+ a1logd(p->log,3,"Got %f %f %f raw %f %f %f Hz\n",rmeas2[0],rmeas2[1],rmeas2[2],
+ 0.5 * edgec[0] * p->clk_freq/rmeas2[0],
+ 0.5 * edgec[1] * p->clk_freq/rmeas2[1],
+ 0.5 * edgec[2] * p->clk_freq/rmeas2[2]);
+
+ /* Transfer updated counts from 2nd initial measurement */
+ for (i = 0; i < 3; i++) {
+ if ((mask3 & (1 << i)) != 0)
+ rmeas[i] = rmeas2[i];
+
+ /* Compute trial RGB in case we need it later */
+ if (rmeas[i] >= 0.5) {
+ rgb2[i] = (p->clk_freq * 0.5 * edgec[i])/rmeas[i];
+ }
+ }
+ }
+ }
+ }
+
+ /* Now setup for re-measure, aiming for longer freq/full period measurement. */
+ /* Compute a target integration time for this re-measurement */
+ tinttime = tintt[0] = tintt[1] = tintt[2] = p->inttime;
+ for (i = 0; i < 3; i++) {
+ double nedgec;
+
+ if ((mask & (1 << i)) == 0 || rmeas[i] <= 0.5)
+ continue;
+
+ /* Compute number of edges needed for a clock count */
+ /* of p->inttime (0.2 secs) */
+ nedgec = edgec[i] * p->inttime * p->clk_freq/rmeas[i];
+
+ /* If we will get less than 200 edges, raise the target integration */
+ /* time in a curve to aim at a higher edge count up to 200 */
+ if (nedgec < 200.0) {
+ double bl, tedges;
+ double mint;
+
+ /* Blend down from target of 200 to minimum target of 1 edge over 8 sec. */
+ /* (Allow margine away from max integration time of 10 secs) */
+ mint = p->inttime/6.0;
+ bl = (nedgec - mint)/(200.0 - mint);
+ if (bl < 0.0)
+ bl = 0.0;
+ else {
+ /* This power sets how fast the int. time rises */
+ bl = pow(bl, 0.5); /* Use longer int. times to increase ecount */
+ }
+ tedges = bl * (200.0 - mint) + mint;
+
+ tintt[i] = tedges/(edgec[i] * p->clk_freq/rmeas[i]);
+
+ if (tintt[i] > 6.0) /* Maximum possible is 10 seconds */
+ tintt[i] = 6.0;
+
+ if (p->refperiod > 0.0) { /* If we have a refresh period */
+ int n;
+ n = (int)ceil(tintt[i]/p->refperiod); /* Quantize */
+ tintt[i] = n * p->refperiod;
+ }
+ if (tintt[i] > tinttime) /* New overal max. int. time */
+ tinttime = tintt[i];
+ }
+ }
+ a1logd(p->log,3,"target re-measure inttime %f\n",tinttime);
+
+ /* Now compute the number of edges to measure */
+ for (i = 0; i < 3; i++) {
+ if ((mask & (1 << i)) == 0)
+ continue;
+
+ if (rmeas[i] > 0.5) {
+ double nedgec, onedgec, atintt;
+
+ /* Compute number of edges needed for a clock count */
+ /* of tintt[i], the individual channels goal */
+ nedgec = edgec[i] * tintt[i] * p->clk_freq/rmeas[i];
+
+ /* Limit to a legal range */
+ if (nedgec > 65534.0)
+ nedgec = 65534.0;
+ else if (nedgec < 2.0)
+ nedgec = 2.0;
+
+ /* Round down to the nearest even edge count */
+ nedgec = 2.0 * (int)floor(nedgec/2.0);
+
+ /* Compute number of edges needed for the overall goal */
+ /* of clock count of tinttime */
+ onedgec = edgec[i] * tinttime * p->clk_freq/rmeas[i];
+
+ /* Limit to a legal range */
+ if (onedgec > 65534.0)
+ onedgec = 65534.0;
+ else if (onedgec < 2.0)
+ onedgec = 2.0;
+
+ /* Round down to nearest even edge count */
+ onedgec = 2.0 * (int)floor(onedgec/2.0);
+
+ /* Use this overall edge count goal, as long as */
+ /* it doesn't excessively increase the overall integration time */
+ atintt = onedgec * rmeas[i]/(edgec[i] * p->clk_freq);
+
+ if (atintt < (1.1 * tinttime))
+ nedgec = onedgec;
+
+ a1logd(p->log,3,"chan %d set edgec to %d\n",i,(int)nedgec);
+
+ /* Don't measure again if we have same number of edges as last time */
+ if (edgec[i] == (int)nedgec) {
+
+ /* Use previous measurement */
+ rgb[i] = (p->clk_freq * 0.5 * edgec[i])/rmeas[i];
+ mask &= ~(1 << i);
+ edgec[i] = 0;
+
+ a1logd(p->log,3,"chan %d skipping re-measure, frequency %f\n",i,rgb[i]);
+
+ } else {
+ edgec[i] = (int)nedgec;
+ }
+ } else {
+ /* Don't measure again, we failed to see any edges */
+ rgb[i] = 0.0;
+ mask &= ~(1 << i);
+ edgec[i] = 0;
+ }
+ }
+
+ if (mask != 0x0) {
+ int minedgec = 1000;
+
+ for (i = 0; i < 3; i++) {
+ if ((mask & (1 << i)) == 0)
+ continue;
+ if (edgec[i] < minedgec)
+ minedgec = edgec[i];
+ }
+ a1logd(p->log,3,"Minedgec = %d\n",minedgec);
+
+ /* Use frequency measurement over the fixed period if refresh display */
+ /* This compromises quantization error for improved stability */
+ if (p->refperiod > 0.0 /* If we have a refresh period */
+ && minedgec >= 100) { /* and the expected edge count is sufficient */
+ int n;
+
+ a1logd(p->log,3,"Doing freq re-measure inttime %f\n",tinttime);
+
+ /* Take a frequency measurement over a fixed period */
+ if ((ev = i1d3_freq_measure(p, &tinttime, rmeas)) != inst_ok)
+ return ev;
+
+ /* Convert raw measurement to frequency */
+ for (i = 0; i < 3; i++) {
+ rgb[i] = (0.5 * rmeas[i])/tinttime;
+ }
+
+ a1logd(p->log,3,"Got %f %f %f raw, %f %f %f Hz after re-measure\n",rmeas[0],rmeas[1],rmeas[2],rgb[0],rgb[1],rgb[2]);
+
+ } else {
+ /* Use period measurement of the target number of edges */
+ /* (Note that if the patch isn't constant and drops compared to */
+ /* the trial measurement used to set the target number of edges, */
+ /* that the measurement may time out and return 0. In this case */
+ /* we fall back on the trial value rather than return 0.) */
+
+ a1logd(p->log,3,"Doing period re-measure mask 0x%x, edgec %d %d %d\n",mask,edgec[0],edgec[1],edgec[2]);
+ /* Measure again with desired precision, taking up to 0.4/0.8 secs */
+ if ((ev = i1d3_period_measure(p, edgec, mask, rmeas)) != inst_ok)
+ return ev;
+
+ for (i = 0; i < 3; i++) {
+ double tt;
+ if ((mask & (1 << i)) == 0)
+ continue;
+
+ /* Compute the frequency from period measurement */
+ if (rmeas[i] < 0.5) /* Number of edges wasn't counted */
+ rgb[i] = rgb2[i]; /* Trial value, since it may be more realistic */
+ else
+ rgb[i] = (p->clk_freq * 0.5 * edgec[i])/rmeas[i];
+ a1logd(p->log,3,"chan %d raw %f frequency %f (%f Sec)\n",i,rmeas[i],rgb[i],
+ rmeas[i]/p->clk_freq);
+ }
+ a1logd(p->log,3,"Got %f %f %f Hz after period measure\n",rgb[0],rgb[1],rgb[2]);
+ }
+ }
+ }
+ }
+
+ a1logd(p->log,3,"Took %d msec to measure\n", msec_time() - msecstart);
+
+ /* Subtract black level */
+ for (i = 0; i < 3; i++) {
+ rgb[i] -= p->black[i];
+ if (rgb[i] < 0.0)
+ rgb[i] = 0.0;
+ }
+
+ a1logd(p->log,3,"Cooked RGB = %f %f %f\n",rgb[0],rgb[1],rgb[2]);
+
+ return inst_ok;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Take a XYZ measurement from the device */
+static inst_code
+i1d3_take_XYZ_measurement(
+ i1d3 *p, /* Object */
+ double XYZ[3] /* Return the XYZ values */
+) {
+ inst_code ev;
+
+ if (IMODETST(p->mode, inst_mode_emis_ambient)) {
+ if ((ev = i1d3_take_amb_measurement(p, XYZ)) != inst_ok)
+ return ev;
+
+ /* Multiply by ambient calibration matrix */
+ icmMulBy3x3(XYZ, p->ambi_cal, XYZ);
+ icmScale3(XYZ, XYZ, 1/3.141592654); /* Convert from Lux to cd/m^2 */
+
+ } else {
+
+ /* Constant fast speed, poor accuracy for black */
+// if ((ev = i1d3_take_emis_measurement(p, i1d3_frequency, XYZ)) != inst_ok)
+// return ev;
+
+ /* Most accurate ? */
+// if ((ev = i1d3_take_emis_measurement(p, i1d3_period, XYZ)) != inst_ok)
+// return ev;
+
+ /* Best combination */
+ if ((ev = i1d3_take_emis_measurement(p, i1d3_adaptive, XYZ)) != inst_ok)
+ return ev;
+
+ /* Multiply by current emissive calibration matrix */
+ icmMulBy3x3(XYZ, p->emis_cal, XYZ);
+
+ /* Apply the (optional) colorimeter correction matrix */
+ icmMulBy3x3(XYZ, p->ccmat, XYZ);
+
+ }
+ a1logd(p->log,3,"returning XYZ = %f %f %f\n",XYZ[0],XYZ[1],XYZ[2]);
+ return inst_ok;
+}
+
+// ============================================================
+
+/* Decode the Internal EEPROM */
+static inst_code i1d3_decode_intEE(
+ i1d3 *p,
+ unsigned char *buf /* Buffer holding 256 bytes from Internal EEProm */
+) {
+ int i;
+ unsigned int t1;
+
+ /* Read the serial number */
+ strncpy(p->serial_no, (char *)buf + 0x10, 20);
+ p->serial_no[20] = '\000';
+
+ /* Read the black level offset */
+ for (i = 0; i < 3; i++) {
+ t1 = buf2uint(buf + 0x0004 + 4 * i);
+
+ if (t1 == 0xffffffff)
+ p->black[0] = 0.0;
+ else
+ p->black[0] = (double)t1/6e6;
+ }
+
+ return inst_ok;
+}
+
+/* Decode the External EEPRom */
+static inst_code i1d3_decode_extEE(
+ i1d3 *p,
+ unsigned char *buf /* Buffer holding 8192 bytes from External EEProm */
+) {
+ int i, j;
+ unsigned int off;
+ unsigned int chsum, rchsum;
+ xspect tmp;
+
+ for (chsum = 0, i = 4; i < 6042; i++)
+ chsum += buf[i];
+
+ chsum &= 0xffff; /* 16 bit sum */
+
+ rchsum = buf2short(buf + 2);
+
+ if (rchsum != chsum) {
+ a1logd(p->log, 3, "i1d3_decode_extEE: checksum failed\n");
+ return i1d3_interp_code((inst *)p, I1D3_BAD_EX_CHSUM);
+ }
+
+ /* Read 3 x sensor spectral sensitivits */
+ /* These seem to be in Hz per W/nm @ 1nm spacing, */
+ /* so convert to Hz per mW/nm which is our default assumption. */
+ p->cal_date = buf2ord64(buf + 0x001E);
+
+ for (j = 0; j < 3; j++) {
+ p->sens[j].spec_n = 351;
+ p->sens[j].spec_wl_short = 380.0;
+ p->sens[j].spec_wl_long = 730.0;
+ p->sens[j].norm = 1.0;
+ for (i = 0, off = 0x0026 + j * 351 * 4; i < 351; i++, off += 4) {
+ unsigned int val;
+ val = buf2uint(buf + off);
+ p->sens[j].spec[i] = IEEE754todouble(val);
+ p->sens[j].spec[i] /= 1000;
+ }
+ p->ambi[j] = p->sens[j]; /* Structure copy */
+ }
+
+#ifdef SAVE_SPECTRA
+ write_cmf("sensors.cmf", p->sens);
+#endif
+
+ /* Read ambient filter spectrum */
+ tmp.spec_n = 351;
+ tmp.spec_wl_short = 380.0;
+ tmp.spec_wl_long = 730.0;
+ tmp.norm = 1.0;
+ for (i = 0, off = 0x10bc; i < 351; i++, off += 4) {
+ unsigned int val;
+ val = buf2uint(buf + off);
+ tmp.spec[i] = IEEE754todouble(val);
+ }
+
+ /* Compute ambient sensor sensitivity by multiplying filter in */
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < 351; i++)
+ p->ambi[j].spec[i] *= tmp.spec[i];
+ }
+#ifdef PLOT_SPECTRA
+ /* Plot the spectra */
+ {
+ double xx[351];
+ double y1[351];
+ double y2[351];
+ double y3[351];
+ double y4[351];
+ double y5[351];
+ double y6[351];
+
+ for (i = 0; i < 351; i++) {
+ xx[i] = XSPECT_XWL(&tmp, i);
+ y1[i] = p->sens[0].spec[i];
+ y2[i] = p->sens[1].spec[i];
+ y3[i] = p->sens[2].spec[i];
+ y4[i] = p->ambi[0].spec[i];
+ y5[i] = p->ambi[1].spec[i];
+ y6[i] = p->ambi[2].spec[i];
+ }
+ printf("The sensor and ambient sensor sensitivy curves\n");
+ do_plot6(xx, y1, y2, y3, y4, y5, y6, 351);
+ }
+#endif /* PLOT_SPECTRA */
+
+ // Should try and read 4 factory 3x3 matricies too,
+ // even though they are not usually set.
+
+ return inst_ok;
+}
+
+/* ------------------------------------------------------------------------ */
+/* Calibration code */
+
+/* Maximum Ignorance by Least Squares regression (MIbLSr) Calibration. */
+/* This makes no assumption about the spectral distribution of */
+/* typical samples or their underlying dimensionality. */
+/* We use this as a default means of calibration, and as */
+/* a means of calibrating the Ambient readings. */
+/* (This matches the OEM default calibrations.) */
+/* We could weight this towards minimizing white error */
+/* by synthesizing a white patch to add to the "sample" set */
+/* (but this might make the result worse!), or we could */
+/* add or use spectral shape target (ie. analogous to */
+/* one sample per spectral wavelength, weighted by CMF's) */
+
+/* The more general calibration uses a set of spectral samples, */
+/* and a least squares matrix is computed to map the sensor RGB */
+/* to the computed XYZ values. This allows better accuracy for */
+/* a typical display that has only 3 degrees of freedom, and */
+/* allows weigting towards a distribution of actual spectral samples. */
+/* (The OEM driver supplies .edr files with this information. We use */
+/* .ccss files) */
+/* To allow less than 3 samples, extra secondary constraints could be added, */
+/* such as CMF's as pseudo-samples or a spectral shape target. */
+
+static inst_code
+i1d3_comp_calmat(
+ i1d3 *p,
+ double mat[3][3], /* Return calibration matrix from RGB to XYZ */
+ icxObserverType obType, /* XYZ Observer type */
+ xspect custObserver[3], /* Optional custom observer */ \
+ xspect *RGBcmfs, /* Array of 3 sensor CMFs, either emissive or ambient */
+ xspect *samples, /* Array of nsamp spectral samples, or RGBcmfs for MIbLSr */
+ /* (~~~ weighting array ? ~~~) */
+ int nsamp /* Number of samples */
+) {
+ int i, j, k;
+ double **sampXYZ; /* Sample XYZ values */
+ double **sampRGB; /* Sample RGB values */
+ double XYZ[3][3];
+ double RGB[3][3];
+ double iRGB[3][3];
+ xsp2cie *conv;
+
+ if (nsamp < 3)
+ return i1d3_interp_code((inst *)p, I1D3_TOO_FEW_CALIBSAMP);
+
+ sampXYZ = dmatrix(0, nsamp-1, 0, 3-1);
+ sampRGB = dmatrix(0, nsamp-1, 0, 3-1);
+
+ /* Compute XYZ of the sample array */
+ if ((conv = new_xsp2cie(icxIT_none, NULL, obType, custObserver, icSigXYZData, icxClamp)) == NULL)
+ return i1d3_interp_code((inst *)p, I1D3_INT_CIECONVFAIL);
+ for (i = 0; i < nsamp; i++) {
+ conv->convert(conv, sampXYZ[i], &samples[i]);
+ }
+ conv->del(conv);
+
+ /* Compute sensor RGB of the sample array */
+ if ((conv = new_xsp2cie(icxIT_none, NULL, icxOT_custom, RGBcmfs, icSigXYZData, icxClamp)) == NULL) {
+ free_dmatrix(sampXYZ, 0, nsamp-1, 0, 3-1);
+ free_dmatrix(sampRGB, 0, nsamp-1, 0, 3-1);
+ return i1d3_interp_code((inst *)p, I1D3_INT_CIECONVFAIL);
+ }
+ for (i = 0; i < nsamp; i++) {
+ conv->convert(conv, sampRGB[i], &samples[i]);
+ /* But we need to undo lumens scaling, because it doesn't apply to RGB sensor values */
+ for (j = 0; j < 3; j++)
+ sampRGB[i][j] /= 0.683002;
+ }
+ conv->del(conv);
+
+ /* If there are exactly 3 samples, we can directly compute the */
+ /* correction matrix, since the problem is not over-determined. */
+ if (nsamp == 3) {
+ copy_dmatrix_to3x3(XYZ, sampXYZ, 0, 2, 0, 2);
+ copy_dmatrix_to3x3(RGB, sampRGB, 0, 2, 0, 2);
+ if (icmInverse3x3(iRGB, RGB)) {
+ free_dmatrix(sampXYZ, 0, nsamp-1, 0, 3-1);
+ free_dmatrix(sampRGB, 0, nsamp-1, 0, 3-1);
+ return i1d3_interp_code((inst *)p, I1D3_TOO_FEW_CALIBSAMP);
+ }
+
+ icmMul3x3_2(mat, iRGB, XYZ);
+ icmTranspose3x3(mat, mat);
+
+ /* Otherwise we compute the least squares calibration matrix. */
+ } else {
+ /* Multiply the [3 x nsamp] XYZ matrix by the [nsamp x 3] RGB */
+ /* matrix to produce the [3 x 3] design matrix. */
+ for (i = 0; i < 3; i++) {
+ for (j = 0; j < 3; j++) {
+ XYZ[j][i] = 0.0;
+ for (k = 0; k < nsamp; k++)
+ XYZ[j][i] += sampXYZ[k][i] * sampRGB[k][j];
+ }
+ }
+ for (i = 0; i < 3; i++) {
+ for (j = 0; j < 3; j++) {
+ RGB[j][i] = 0.0;
+ for (k = 0; k < nsamp; k++)
+ RGB[j][i] += sampRGB[k][i] * sampRGB[k][j];
+ }
+ }
+ if (icmInverse3x3(iRGB, RGB)) {
+ free_dmatrix(sampXYZ, 0, nsamp-1, 0, 3-1);
+ free_dmatrix(sampRGB, 0, nsamp-1, 0, 3-1);
+ return i1d3_interp_code((inst *)p, I1D3_TOO_FEW_CALIBSAMP);
+ }
+
+ icmMul3x3_2(mat, iRGB, XYZ);
+ icmTranspose3x3(mat, mat);
+ }
+ free_dmatrix(sampXYZ, 0, nsamp-1, 0, 3-1);
+ free_dmatrix(sampRGB, 0, nsamp-1, 0, 3-1);
+
+ return inst_ok;
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+/* Establish communications with a I1D3 */
+/* Return DTP_COMS_FAIL on failure to establish communications */
+static inst_code
+i1d3_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ i1d3 *p = (i1d3 *) pp;
+ int stat, se;
+ inst_code ev = inst_ok;
+ icomuflags usbflags = icomuf_none;
+
+#ifdef NT
+ /* If the X-Rite software has been installed, then there may */
+ /* be a utility that has the device open. Kill that process off */
+ /* so that we can open it here. */
+ char *pnames[] = {
+ "i1ProfilerTray.exe",
+ NULL
+ };
+ int retries = 2;
+#else /* !NT */
+ char **pnames = NULL;
+ int retries = 0;
+#endif /* !NT */
+
+ a1logd(p->log, 2, "i1d3_init_coms: called\n");
+
+ /* On Linux, the i1d3 doesn't seem to close properly, and won't re-open - */
+ /* something to do with detaching the default HID driver ?? */
+#if defined(UNIX_X11)
+ usbflags |= icomuf_reset_before_close;
+#endif
+ /* Open as an HID if available */
+ if (p->icom->port_type(p->icom) == icomt_hid) {
+
+ a1logd(p->log, 2, "i1d3_init_coms: About to init HID\n");
+
+ /* Set config, interface */
+ if ((se = p->icom->set_hid_port(p->icom, icomuf_none, retries, pnames))
+ != ICOM_OK) {
+ a1logd(p->log, 1, "i1d3_init_coms: set_hid_port failed ICOM err 0x%x\n",se);
+ return i1d3_interp_code((inst *)p, icoms2i1d3_err(se, 0));
+ }
+
+ } else if (p->icom->port_type(p->icom) == icomt_usb) {
+
+ a1logd(p->log, 2, "i1d3_init_coms: About to init USB\n");
+
+ /* Set config, interface, write end point, read end point */
+ /* ("serial" end points aren't used - the i1d3 uses USB control messages) */
+ /* We need to detatch the HID driver on Linux */
+ if ((se = p->icom->set_usb_port(p->icom, 1, 0x00, 0x00, usbflags | icomuf_detach, 0, NULL))
+ != ICOM_OK) {
+ a1logd(p->log, 1, "i1d3_init_coms: set_usb_port failed ICOM err 0x%x\n",se);
+ return i1d3_interp_code((inst *)p, icoms2i1d3_err(se, 0));
+ }
+
+ } else {
+ a1logd(p->log, 1, "i1d3_init_coms: wrong sort of coms!\n");
+ return i1d3_interp_code((inst *)p, I1D3_UNKNOWN_MODEL);
+ }
+
+#if defined(__APPLE__)
+ /* We seem to have to clear any pending messages for OS X HID */
+ i1d3_dummy_read(p);
+#endif
+
+ /* Check instrument is responding */
+ if ((ev = i1d3_check_status(p,&stat)) != inst_ok) {
+ a1logd(p->log, 1, "i1d3_init_coms: failed with rv = 0x%x\n",ev);
+ return ev;
+ }
+ a1logd(p->log, 2, "i1d3_init_coms: suceeded\n");
+
+ p->gotcoms = 1;
+ return inst_ok;
+}
+
+// Print bytes as hex to debug log */
+static void dump_bytes(a1log *log, char *pfx, unsigned char *buf, int len) {
+ int i, j, ii;
+ char oline[200] = { '\000' }, *bp = oline;
+ for (i = j = 0; i < len; i++) {
+ if ((i % 16) == 0)
+ bp += sprintf(bp,"%s%04x:",pfx,i);
+ bp += sprintf(bp," %02x",buf[i]);
+ if ((i+1) >= len || ((i+1) % 16) == 0) {
+ for (ii = i; ((ii+1) % 16) != 0; ii++)
+ bp += sprintf(bp," ");
+ bp += sprintf(bp," ");
+ for (; j <= i; j++) {
+ if (!(buf[j] & 0x80) && isprint(buf[j]))
+ bp += sprintf(bp,"%c",buf[j]);
+ else
+ bp += sprintf(bp,".");
+ }
+ bp += sprintf(bp,"\n");
+ a1logd(log,0,oline);
+ bp = oline;
+ }
+ }
+}
+
+/* Diffuser position thread. */
+/* Poll the instrument at 100msec intervals */
+int i1d3_diff_thread(void *pp) {
+ int nfailed = 0;
+ i1d3 *p = (i1d3 *)pp;
+ inst_code rv = inst_ok;
+ a1logd(p->log,3,"Diffuser thread started\n");
+ for (nfailed = 0; nfailed < 5;) {
+ int pos;
+
+ rv = i1d3_get_diffpos(p, &pos, 1);
+ if (p->th_term) {
+ p->th_termed = 1;
+ break;
+ }
+ if (rv != inst_ok) {
+ nfailed++;
+ a1logd(p->log,3,"Diffuser thread failed with 0x%x\n",rv);
+ continue;
+ }
+ if (pos != p->dpos) {
+ p->dpos = pos;
+ if (p->eventcallback != NULL) {
+ p->eventcallback(p->event_cntx, inst_event_mconf);
+ }
+ }
+ msec_sleep(100);
+ }
+ a1logd(p->log,3,"Diffuser thread returning\n");
+ return rv;
+}
+
+static inst_code set_default_disp_type(i1d3 *p);
+
+/* Initialise the I1D3 */
+static inst_code
+i1d3_init_inst(inst *pp) {
+ i1d3 *p = (i1d3 *)pp;
+ inst_code ev = inst_ok;
+ int i, stat;
+ unsigned char buf[8192];
+
+ a1logd(p->log, 2, "i1d3_init_inst: called\n");
+
+ p->rrset = 0;
+
+ if (p->gotcoms == 0)
+ return i1d3_interp_code((inst *)p, I1D3_NO_COMS); /* Must establish coms first */
+
+ // Get instrument information */
+ if ((ev = i1d3_check_status(p, &p->status)) != inst_ok)
+ return ev;
+ if (p->status != 0) {
+ a1logd(p->log, 1, "i1d3_init_inst: bad device status\n");
+ return i1d3_interp_code((inst *)p, I1D3_BAD_STATUS);
+ }
+
+ if ((ev = i1d3_get_prodname(p, p->prod_name)) != inst_ok)
+ return ev;
+ if ((ev = i1d3_get_prodtype(p, &p->prod_type)) != inst_ok)
+ return ev;
+ if (p->prod_type == 0x0002) { /* If ColorMunki Display */
+ /* Set this in case it doesn't need unlocking */
+ p->dtype = p->stype = i1d3_munkdisp;
+ }
+ if ((ev = i1d3_get_firmver(p, p->firm_ver)) != inst_ok)
+ return ev;
+ if ((ev = i1d3_get_firmdate(p, p->firm_date)) != inst_ok)
+ return ev;
+
+ /* Unlock instrument */
+ if ((ev = i1d3_lock_status(p,&stat)) != inst_ok)
+ return ev;
+
+ if (stat != 0) { /* Locked, so unlock it */
+ a1logd(p->log, 3, "i1d3_init_inst: unlocking the instrument\n");
+
+ if ((ev = i1d3_unlock(p)) != inst_ok)
+ return ev;
+ if ((ev = i1d3_lock_status(p,&stat)) != inst_ok)
+ return ev;
+ if (stat != 0) {
+ a1logd(p->log, 1, "i1d3_init_inst: failed to unlock instrument\n");
+ return i1d3_interp_code((inst *)p, I1D3_UNLOCK_FAIL);
+ }
+ }
+
+ /* Read the instrument information and calibration */
+ if ((ev = i1d3_read_internal_eeprom(p,0,256,buf)) != inst_ok)
+ return ev;
+ if (p->log->debug >= 8) {
+ a1logd(p->log, 8, "Internal EEPROM:\n");
+ dump_bytes(p->log, " ", buf, 256);
+ }
+ /* Decode the Internal EEPRom */
+ if ((ev = i1d3_decode_intEE(p, buf)) != inst_ok)
+ return ev;
+
+ if ((ev = i1d3_read_external_eeprom(p,0,8192,buf)) != inst_ok)
+ return ev;
+ if (p->log->debug >= 8) {
+ a1logd(p->log, 8, "External EEPROM:\n");
+ dump_bytes(p->log, " ", buf, 8192);
+ }
+ /* Decode the External EEPRom */
+ if ((ev = i1d3_decode_extEE(p, buf)) != inst_ok)
+ return ev;
+
+ /* Set known constants */
+ p->clk_freq = 12e6; /* 12 Mhz */
+ p->dinttime = 0.2; /* 0.2 second integration time default */
+ p->inttime = p->dinttime; /* Start in non-refresh mode */
+
+ /* Create the default calibrations */
+
+ p->obType = icxOT_CIE_1931_2; /* Set the default ccss observer */
+
+ /* Setup the default display type */
+ if ((ev = set_default_disp_type(p)) != inst_ok) {
+ return ev;
+ }
+
+ p->inited = 1;
+ a1logd(p->log, 2, "i1d3_init_inst: inited OK\n");
+
+ a1logv(p->log,1,"Product Name: %s\n"
+ "Serial Number: %s\n"
+ "Firmware Version: %s\n"
+ "Firmware Date: %s\n"
+ ,p->prod_name,p->serial_no,p->firm_ver,p->firm_date);
+
+ if (p->log->debug >= 4) {
+ a1logd(p->log,4,"Default calibration:\n");
+ a1logd(p->log,4,"Ambient matrix = %f %f %f\n",
+ p->ambi_cal[0][0], p->ambi_cal[0][1], p->ambi_cal[0][2]);
+ a1logd(p->log,4," %f %f %f\n",
+ p->ambi_cal[1][0], p->ambi_cal[1][1], p->ambi_cal[1][2]);
+ a1logd(p->log,4," %f %f %f\n\n",
+ p->ambi_cal[2][0], p->ambi_cal[2][1], p->ambi_cal[2][2]);
+ a1logd(p->log,4,"Emissive matrix = %f %f %f\n",
+ p->emis_cal[0][0], p->emis_cal[0][1], p->emis_cal[0][2]);
+ a1logd(p->log,4," %f %f %f\n",
+ p->emis_cal[1][0], p->emis_cal[1][1], p->emis_cal[1][2]);
+ a1logd(p->log,4," %f %f %f\n",
+ p->emis_cal[2][0], p->emis_cal[2][1], p->emis_cal[2][2]);
+ a1logd(p->log,4,"\n");
+ }
+
+ /* Start the diffuser monitoring thread */
+ if ((p->th = new_athread(i1d3_diff_thread, (void *)p)) == NULL)
+ return I1D3_INT_THREADFAILED;
+
+ /* Flash the LED, just cos we can! */
+ if ((ev = i1d3_set_LEDs(p, i1d3_flash, 0.2, 0.05, 2)) != inst_ok)
+ return ev;
+
+ return ev;
+}
+
+/* Read a single sample */
+/* Return the dtp error code */
+static inst_code
+i1d3_read_sample(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+ipatch *val, /* Pointer to instrument patch value */
+instClamping clamp) { /* NZ if clamp XYZ/Lab to be +ve */
+ i1d3 *p = (i1d3 *)pp;
+ int user_trig = 0;
+ int rv = inst_protocol_error;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->trig == inst_opt_trig_user) {
+
+ if (p->uicallback == NULL) {
+ a1logd(p->log, 1, "i1d3: inst_opt_trig_user but no uicallback function set!\n");
+ return inst_unsupported;
+ }
+
+ for (;;) {
+ if ((rv = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (rv == inst_user_abort)
+ return rv; /* Abort */
+ if (rv == inst_user_trig) {
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ msec_sleep(200);
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ /* Progromatic Trigger */
+ } else {
+ /* Check for abort */
+ if (p->uicallback != NULL
+ && (rv = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_abort)
+ return rv; /* Abort */
+ }
+
+ /* Attempt a refresh display frame rate calibration if needed */
+ if (p->dtype != i1d3_munkdisp && p->refrmode != 0 && p->rrset == 0) {
+ inst_code ev = inst_ok;
+
+ if ((ev = i1d3_measure_set_refresh(p)) != inst_ok)
+ return ev;
+
+ /* Quantize the sample time */
+ if (p->refperiod > 0.0) { /* If we have a refresh period */
+ int n;
+ n = (int)ceil(p->dinttime/p->refperiod);
+ p->inttime = 2.0 * n * p->refperiod;
+ a1logd(p->log, 3, "i1d3: integration time quantize to %f secs\n",p->inttime);
+
+ } else { /* We don't have a period, so simply double the default */
+ p->inttime = 2.0 * p->dinttime;
+ a1logd(p->log, 3, "i1d3: integration time integration time doubled to %f secs\n",p->inttime);
+ }
+ }
+
+ /* Read the XYZ value */
+ if ((rv = i1d3_take_XYZ_measurement(p, val->XYZ)) != inst_ok)
+ return rv;
+
+ /* This may not change anything since instrument may clamp */
+ if (clamp)
+ icmClamp3(val->XYZ, val->XYZ);
+
+ val->loc[0] = '\000';
+ if (IMODETST(p->mode, inst_mode_emis_ambient))
+ val->mtype = inst_mrt_ambient;
+ else
+ val->mtype = inst_mrt_emission;
+ val->XYZ_v = 1;
+ val->sp.spec_n = 0;
+ val->duration = 0.0;
+
+ if (user_trig)
+ return inst_user_trig;
+
+ return rv;
+}
+
+/* Read an emissive refresh rate */
+static inst_code
+i1d3_read_refrate(
+inst *pp,
+double *ref_rate) {
+ i1d3 *p = (i1d3 *)pp;
+ inst_code rv;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->dtype == i1d3_munkdisp)
+ return inst_unsupported;
+
+ if ((rv = i1d3_imp_measure_refresh(p, ref_rate, NULL)) != inst_ok)
+ return rv;
+
+ if (*ref_rate == 0.0)
+ return inst_misread;
+
+ return inst_ok;
+}
+
+/* Insert a colorimetric correction matrix in the instrument XYZ readings */
+/* This is only valid for colorimetric instruments. */
+/* To remove the matrix, pass NULL for the matrix. */
+inst_code i1d3_col_cor_mat(
+inst *pp,
+double mtx[3][3]
+) {
+ i1d3 *p = (i1d3 *)pp;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (mtx == NULL)
+ icmSetUnity3x3(p->ccmat);
+ else {
+ if (p->cbid == 0) {
+ a1loge(p->log, 1, "i1d3: can't set col_cor_mat over non base display type\n");
+ inst_wrong_setup;
+ }
+ icmCpy3x3(p->ccmat, mtx);
+ }
+
+ return inst_ok;
+}
+
+/* Use a Colorimeter Calibration Spectral Set to set the */
+/* instrumen calibration. */
+/* This is only valid for colorimetric instruments. */
+/* To set calibration back to default, pass NULL for sets. */
+inst_code i1d3_col_cal_spec_set(
+inst *pp,
+xspect *sets,
+int no_sets
+) {
+ i1d3 *p = (i1d3 *)pp;
+ inst_code ev = inst_ok;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (sets == NULL || no_sets <= 0) {
+ if ((ev = set_default_disp_type(p)) != inst_ok) {
+ return ev;
+ }
+ } else {
+ /* Use given spectral samples */
+ if ((ev = i1d3_comp_calmat(p, p->emis_cal, p->obType, p->custObserver, p->sens,
+ sets, no_sets)) != inst_ok)
+ return ev;
+ /* Use MIbLSr */
+ if ((ev = i1d3_comp_calmat(p, p->ambi_cal, p->obType, p->custObserver, p->ambi,
+ p->ambi, 3)) != inst_ok)
+ return ev;
+ }
+ if (p->log->debug >= 4) {
+ a1logd(p->log,4,"CCSS update calibration:\n");
+ a1logd(p->log,4,"Ambient matrix = %f %f %f\n",
+ p->ambi_cal[0][0], p->ambi_cal[0][1], p->ambi_cal[0][2]);
+ a1logd(p->log,4," %f %f %f\n",
+ p->ambi_cal[1][0], p->ambi_cal[1][1], p->ambi_cal[1][2]);
+ a1logd(p->log,4," %f %f %f\n\n",
+ p->ambi_cal[2][0], p->ambi_cal[2][1], p->ambi_cal[2][2]);
+ a1logd(p->log,4,"Emissive matrix = %f %f %f\n",
+ p->emis_cal[0][0], p->emis_cal[0][1], p->emis_cal[0][2]);
+ a1logd(p->log,4," %f %f %f\n",
+ p->emis_cal[1][0], p->emis_cal[1][1], p->emis_cal[1][2]);
+ a1logd(p->log,4," %f %f %f\n",
+ p->emis_cal[2][0], p->emis_cal[2][1], p->emis_cal[2][2]);
+ a1logd(p->log,4,"\n");
+ }
+ return ev;
+}
+
+/* Return needed and available inst_cal_type's */
+static inst_code i1d3_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
+ i1d3 *p = (i1d3 *)pp;
+ inst_cal_type n_cals = inst_calt_none;
+ inst_cal_type a_cals = inst_calt_none;
+
+ if (p->dtype != i1d3_munkdisp && p->refrmode != 0) {
+ if (p->rrset == 0)
+ n_cals |= inst_calt_ref_freq;
+ a_cals |= inst_calt_ref_freq;
+ }
+
+ if (pn_cals != NULL)
+ *pn_cals = n_cals;
+
+ if (pa_cals != NULL)
+ *pa_cals = a_cals;
+
+ return inst_ok;
+}
+
+/* Request an instrument calibration. */
+inst_code i1d3_calibrate(
+inst *pp,
+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) */
+) {
+ i1d3 *p = (i1d3 *)pp;
+ inst_code ev;
+ inst_cal_type needed, available;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ id[0] = '\000';
+
+ if ((ev = i1d3_get_n_a_cals((inst *)p, &needed, &available)) != inst_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,"i1d3_calibrate: doing calt 0x%x\n",calt);
+
+ if ((*calt & inst_calt_n_dfrble_mask) == 0) /* Nothing todo */
+ return inst_ok;
+ }
+
+ /* See if it's a calibration we understand */
+ if (*calt & ~available & inst_calt_all_mask) {
+ return inst_unsupported;
+ }
+
+ if ((*calt & inst_calt_ref_freq) && p->dtype != i1d3_munkdisp && p->refrmode != 0) {
+ inst_code ev = inst_ok;
+
+ if (*calc != inst_calc_emis_white) {
+ *calc = inst_calc_emis_white;
+ return inst_cal_setup;
+ }
+
+ /* Do refresh display rate calibration */
+ if ((ev = i1d3_measure_set_refresh(p)) != inst_ok)
+ return ev;
+
+ /* Quantize the sample time */
+ if (p->refperiod > 0.0) {
+ int n;
+ n = (int)ceil(p->dinttime/p->refperiod);
+ p->inttime = 2.0 * n * p->refperiod;
+ a1logd(p->log, 3, "i1d3: integration time quantize to %f secs\n",p->inttime);
+ } else {
+ p->inttime = 2.0 * p->dinttime; /* Double default integration time */
+ a1logd(p->log, 3, "i1d3: integration time integration time doubled to %f secs\n",p->inttime);
+ }
+ *calt &= ~inst_calt_ref_freq;
+ }
+ return inst_ok;
+}
+
+/* Measure a display update delay. It is assumed that a */
+/* white to black change has been made to the displayed color, */
+/* and this will measure the time it took for the update to */
+/* be noticed by the instrument, up to 0.5 seconds. */
+/* inst_misread will be returned on failure to find a transition to black. */
+#define NDSAMPS 60
+#define DINTT 0.010
+#define NDMXTIME 0.6 /* Maximum time to take */
+
+inst_code i1d3_meas_delay(
+inst *pp,
+int *msecdelay) { /* Return the number of msec */
+ i1d3 *p = (i1d3 *)pp;
+ inst_code ev;
+ int i, j, k;
+ double sutime, putime, cutime, eutime;
+ struct {
+ double sec;
+ double rgb[3];
+ } samp[NDSAMPS];
+ int ndsamps;
+ double inttime = DINTT;
+ double rgb[3];
+ double etime;
+ int isdeb;
+
+ if (usec_time() < 0.0) {
+ a1loge(p->log, inst_internal_error, "i1d3_meas_delay: No high resolution timers\n");
+ return inst_internal_error;
+ }
+
+ /* Turn debug off so that it doesn't intefere with measurement timing */
+ isdeb = p->log->debug;
+ p->icom->log->debug = 0;
+
+ /* Read the samples */
+ sutime = usec_time();
+ putime = (usec_time() - sutime) / 1000000.0;
+ for (i = 0; i < NDSAMPS; i++) {
+ if ((ev = i1d3_freq_measure(p, &inttime, samp[i].rgb)) != inst_ok) {
+ a1logd(p->log, 1, "i1d3_meas_delay: measurement failed\n");
+ p->log->debug = isdeb;
+ return ev;
+ }
+ cutime = (usec_time() - sutime) / 1000000.0;
+ samp[i].sec = 0.5 * (putime + cutime); /* Mean of before and after stamp */
+ putime = cutime;
+ if (cutime > NDMXTIME)
+ break;
+ }
+ ndsamps = i;
+
+ /* Restore debugging */
+ p->log->debug = isdeb;
+
+ if (ndsamps == 0) {
+ a1logd(p->log, 1, "i1d3_meas_delay: No measurement samples returned in time\n");
+ return inst_internal_error;
+ }
+
+#ifdef NEVER
+ /* Plot the raw sensor values */
+ {
+ double xx[NDSAMPS];
+ double y1[NDSAMPS];
+ double y2[NDSAMPS];
+ double y3[NDSAMPS];
+
+ for (i = 0; i < ndsamps; 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("Display update delay measure sensor values and time (sec)\n");
+ do_plot6(xx, y1, y2, y3, NULL, NULL, NULL, ndsamps);
+ }
+#endif
+
+ /* Over the last 100msec, locate the maximum value */
+ etime = samp[ndsamps-1].sec;
+ for (j = 0; j < 3; j++)
+ rgb[j] = 0.0;
+ for (i = ndsamps-1; i >= 0; i--) {
+ for (j = 0; j < 3; j++) {
+ if (samp[i].rgb[j] > rgb[j])
+ rgb[j] = samp[i].rgb[j];
+ }
+ if ((etime - samp[i].sec) > 0.1)
+ break;
+ }
+
+// a1logd(p->log, 3, "i1d3_meas_delay: end rgb = %f %f %f stopped at %d\n", rgb[0], rgb[1], rgb[2], i);
+
+ if (rgb[0] > 10.0 || rgb[1] > 10.0 || rgb[2] > 10.0) {
+ a1logd(p->log, 1, "i1d3_meas_delay: measurement delay doesn't seem to be black\n");
+ return inst_misread;
+ }
+
+ /* Locate the time at which the values are above this */
+ for (i = ndsamps-1; i >= 0; i--) {
+ for (j = 0; j < 3; j++) {
+ if (samp[i].rgb[j] > (1.5 * rgb[j]))
+ break;
+ }
+ if (j < 3)
+ break;
+ }
+ if (i < 0) /* Assume the update was so fast that we missed it */
+ i = 0;
+
+ a1logd(p->log, 2, "i1d3_meas_delay: stoped at sample %d time %f\n",i,samp[i].sec);
+
+ *msecdelay = (int)(samp[i].sec * 1000.0 + 0.5);
+
+ return inst_ok;
+}
+#undef NDSAMPS
+#undef DINTT
+#undef NDMXTIME
+
+/* Return the last calibrated refresh rate in Hz. Returns: */
+static inst_code i1d3_get_refr_rate(inst *pp,
+double *ref_rate
+) {
+ i1d3 *p = (i1d3 *)pp;
+ if (p->refrvalid) {
+ *ref_rate = p->refrate;
+ return inst_ok;
+ } else if (p->rrset) {
+ *ref_rate = 0.0;
+ return inst_misread;
+ }
+ return inst_needs_cal;
+}
+
+/* Set the calibrated refresh rate in Hz. */
+/* Set refresh rate to 0.0 to mark it as invalid */
+/* Rates outside the range 5.0 to 150.0 Hz will return an error */
+/* Note that it's possible to set a ColorMunki Display to use */
+/* synchronised measurement this way. */
+static inst_code i1d3_set_refr_rate(inst *pp,
+double ref_rate
+) {
+ i1d3 *p = (i1d3 *)pp;
+
+ if (ref_rate != 0.0 && (ref_rate < 5.0 || ref_rate > 150.0))
+ return inst_bad_parameter;
+
+ p->refrate = ref_rate;
+ if (ref_rate == 0.0)
+ p->refrvalid = 0;
+ else {
+ int mul;
+ double pval;
+
+ /* Scale to just above 20 Hz */
+ pval = 1.0/ref_rate;
+ mul = floor((1.0/20) / pval);
+ if (mul > 1)
+ pval *= mul;
+ p->refperiod = pval;
+
+ p->refrvalid = 1;
+ }
+ p->rrset = 1;
+
+ return inst_ok;
+}
+
+/* Error codes interpretation */
+static char *
+i1d3_interp_error(inst *pp, int ec) {
+// i1d3 *p = (i1d3 *)pp;
+ ec &= inst_imask;
+ switch (ec) {
+ case I1D3_INTERNAL_ERROR:
+ return "Internal software error";
+ case I1D3_COMS_FAIL:
+ return "Communications failure";
+ case I1D3_UNKNOWN_MODEL:
+ return "Not a known Huey Model";
+ case I1D3_DATA_PARSE_ERROR:
+ return "Data from i1 Display didn't parse as expected";
+
+ case I1D3_OK:
+ return "No device error";
+
+ case I1D3_UNKNOWN_UNLOCK:
+ return "Don't know unlock code for device";
+ case I1D3_UNLOCK_FAIL:
+ return "Device unlock command failed";
+ case I1D3_BAD_EX_CHSUM:
+ return "External EEPRrom checksum doesn't match";
+
+ case I1D3_SPOS_EMIS:
+ return "Ambient filter should be removed";
+ case I1D3_SPOS_AMB:
+ return "Ambient filter should be used";
+
+ case I1D3_BAD_WR_LENGTH:
+ return "Unable to write full message to instrument";
+ case I1D3_BAD_RD_LENGTH:
+ return "Unable to read full message to instrument";
+ case I1D3_BAD_RET_STAT:
+ return "Message from instrument had bad status code";
+ case I1D3_BAD_RET_CMD:
+ return "Message from instrument didn't echo command code";
+ case I1D3_BAD_STATUS:
+ return "Instrument status is unrecognised format";
+ case I1D3_INT_THREADFAILED:
+ return "Starting diffuser position thread failed";
+
+ case I1D3_NO_COMS:
+ return "Communications hasn't been established";;
+ case I1D3_NOT_INITED:
+ return "Instrument hasn't been initialized";
+ case I1D3_BAD_MEM_ADDRESS:
+ return "Out of range EEPROM address";
+ case I1D3_BAD_MEM_LENGTH:
+ return "Out of range EEPROM length";
+ case I1D3_INT_CIECONVFAIL:
+ return "Creating spectral to CIE converted failed";
+ case I1D3_TOO_FEW_CALIBSAMP:
+ return "There are too few spectral calibration samples - need at least 3";
+ case I1D3_INT_MATINV_FAIL:
+ return "Calibration matrix inversion failed";
+ case I1D3_BAD_LED_MODE:
+ return "Parameters to set LED are incorrect";
+
+ default:
+ return "Unknown error code";
+ }
+}
+
+
+/* Convert a machine specific error code into an abstract dtp code */
+static inst_code
+i1d3_interp_code(inst *pp, int ec) {
+// i1d3 *p = (i1d3 *)pp;
+
+ ec &= inst_imask;
+ switch (ec) {
+
+ case I1D3_OK:
+ return inst_ok;
+
+ case I1D3_BAD_MEM_ADDRESS:
+ case I1D3_BAD_MEM_LENGTH:
+ case I1D3_INT_CIECONVFAIL:
+ case I1D3_TOO_FEW_CALIBSAMP:
+ case I1D3_INT_MATINV_FAIL:
+ case I1D3_BAD_LED_MODE:
+ case I1D3_NO_COMS:
+ case I1D3_NOT_INITED:
+ case I1D3_INT_THREADFAILED:
+ return inst_internal_error | ec;
+
+ case I1D3_COMS_FAIL:
+ return inst_coms_fail | ec;
+
+ case I1D3_UNKNOWN_UNLOCK:
+ case I1D3_UNLOCK_FAIL:
+ case I1D3_DATA_PARSE_ERROR:
+ case I1D3_BAD_WR_LENGTH:
+ case I1D3_BAD_RD_LENGTH:
+ case I1D3_BAD_RET_STAT:
+ case I1D3_BAD_RET_CMD:
+ case I1D3_BAD_STATUS:
+ return inst_protocol_error | ec;
+
+ case I1D3_SPOS_EMIS:
+ case I1D3_SPOS_AMB:
+ return inst_wrong_config | ec;
+
+ case I1D3_BAD_EX_CHSUM:
+ return inst_hardware_fail | ec;
+
+/* Unused:
+ inst_notify
+ inst_warning
+ inst_unknown_model
+ inst_misread
+ inst_nonesaved
+ inst_nochmatch
+ inst_needs_cal
+ inst_cal_setup
+ inst_unsupported
+ inst_unexpected_reply
+ inst_wrong_setup
+ inst_bad_parameter
+ */
+ }
+ return inst_other_error | ec;
+}
+
+/* Convert instrument specific inst_wrong_config error to inst_config enum */
+static inst_config i1d3_config_enum(inst *pp, int ec) {
+// i1d3 *p = (i1d3 *)pp;
+
+ ec &= inst_imask;
+ switch (ec) {
+
+ case I1D3_SPOS_EMIS:
+ return inst_conf_emission;
+
+ case I1D3_SPOS_AMB:
+ return inst_conf_ambient;
+ }
+ return inst_conf_unknown;
+}
+
+/* Destroy ourselves */
+static void
+i1d3_del(inst *pp) {
+ if (pp != NULL) {
+ i1d3 *p = (i1d3 *)pp;
+
+ if (p->th != NULL) { /* Terminate diffuser monitor thread */
+ int i;
+ p->th_term = 1; /* Tell thread to exit on error */
+ for (i = 0; p->th_termed == 0 && i < 5; i++)
+ msec_sleep(50); /* Wait for thread to terminate */
+ if (i >= 5) {
+ a1logd(p->log,3,"i1d3 diffuser thread termination failed\n");
+ }
+ p->th->del(p->th);
+ }
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ inst_del_disptype_list(p->dtlist, p->ndtlist);
+ amutex_del(p->lock);
+ free(p);
+ }
+}
+
+/* Return the instrument capabilities */
+void i1d3_capabilities(inst *pp,
+inst_mode *pcap1,
+inst2_capability *pcap2,
+inst3_capability *pcap3) {
+ i1d3 *p = (i1d3 *)pp;
+ inst_mode cap1 = 0;
+ inst2_capability cap2 = 0;
+
+ cap1 |= inst_mode_emis_spot
+ | inst_mode_emis_tele
+ | inst_mode_emis_ambient
+ | inst_mode_emis_refresh_ovd
+ | inst_mode_emis_norefresh_ovd
+ | inst_mode_colorimeter
+ ;
+
+ cap2 |= inst2_has_sensmode
+ | inst2_prog_trig
+ | inst2_user_trig
+ | inst2_has_leds
+ | inst2_disptype
+ | inst2_ccmx
+ | inst2_ccss
+ ;
+
+ if (p->dtype != i1d3_munkdisp) {
+ cap2 |= inst2_meas_disp_update;
+ cap2 |= inst2_refresh_rate;
+ cap2 |= inst2_emis_refr_meas;
+ }
+ if (pcap1 != NULL)
+ *pcap1 = cap1;
+ if (pcap2 != NULL)
+ *pcap2 = cap2;
+ if (pcap3 != NULL)
+ *pcap3 = inst3_none;
+}
+
+/* Return current or given configuration available measurement modes. */
+static inst_code i1d3_meas_config(
+inst *pp,
+inst_mode *mmodes,
+inst_cal_cond *cconds,
+int *conf_ix
+) {
+ i1d3 *p = (i1d3 *)pp;
+ inst_code ev;
+ inst_mode mval;
+ int pos;
+
+ if (mmodes != NULL)
+ *mmodes = inst_mode_none;
+ if (cconds != NULL)
+ *cconds = inst_calc_unknown;
+
+ if (conf_ix == NULL
+ || *conf_ix < 0
+ || *conf_ix > 1) {
+ /* Return current configuration measrement modes */
+ if ((ev = i1d3_get_diffpos(p, &pos, 0)) != inst_ok)
+ return ev;
+ } else {
+ /* Return given configuration measurement modes */
+ pos = *conf_ix;
+ }
+
+ if (pos == 1) {
+ mval = inst_mode_emis_ambient;
+ } else {
+ mval = inst_mode_emis_spot
+ | inst_mode_emis_tele;
+ }
+
+ /* Add the extra dependent and independent modes */
+ mval |= inst_mode_emis_refresh_ovd
+ | inst_mode_emis_norefresh_ovd
+ | inst_mode_colorimeter;
+
+ if (mmodes != NULL)
+ *mmodes = mval;
+
+ /* Return configuration index returned */
+ if (conf_ix != NULL)
+ *conf_ix = pos;
+
+ return inst_ok;
+}
+
+/* Check device measurement mode */
+inst_code i1d3_check_mode(inst *pp, inst_mode m) {
+ i1d3 *p = (i1d3 *)pp;
+ inst_mode cap;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ pp->capabilities(pp, &cap, NULL, NULL);
+
+ /* Simple test */
+ if (m & ~cap)
+ return inst_unsupported;
+
+ /* only display emission mode and ambient supported */
+ if (!IMODETST(m, inst_mode_emis_spot)
+ && !IMODETST(m, inst_mode_emis_tele)
+ && !IMODETST(m, inst_mode_emis_ambient)) {
+ return inst_unsupported;
+ }
+
+ return inst_ok;
+}
+
+/* Set device measurement mode */
+inst_code i1d3_set_mode(inst *pp, inst_mode m) {
+ i1d3 *p = (i1d3 *)pp;
+ int refrmode;
+ inst_code ev;
+
+ if ((ev = i1d3_check_mode(pp, m)) != inst_ok)
+ return ev;
+
+ p->mode = m;
+
+ /* Effective refresh mode may change */
+ refrmode = p->refrmode;
+ if ( IMODETST(p->mode, inst_mode_emis_norefresh_ovd)) { /* Must test this first! */
+ refrmode = 0;
+ } else if (IMODETST(p->mode, inst_mode_emis_refresh_ovd)) {
+ refrmode = 1;
+ }
+
+ if (p->refrmode != refrmode) {
+ p->rrset = 0; /* This is a hint we may have swapped displays */
+ p->refrvalid = 0;
+ }
+ p->refrmode = refrmode;
+
+ if (p->refrmode) {
+ p->inttime = 2.0 * p->dinttime; /* Double default integration time */
+ } else {
+ p->inttime = p->dinttime; /* Normal integration time */
+ }
+
+ return inst_ok;
+}
+
+inst_disptypesel i1d3_disptypesel[3] = {
+ {
+ inst_dtflags_default,
+ 1,
+ "n",
+ "Non-Refresh display",
+ 0,
+ 0
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 2, /* cbid */
+ "r", /* sel */
+ "Refresh display", /* desc */
+ 1, /* refr */
+ 1 /* ix */
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ 0
+ }
+};
+
+/* Get mode and option details */
+static inst_code i1d3_get_disptypesel(
+inst *pp,
+int *pnsels, /* Return number of display types */
+inst_disptypesel **psels, /* Return the array of display types */
+int allconfig, /* nz to return list for all configs, not just current. */
+int recreate /* nz to re-check for new ccmx & ccss files */
+) {
+ i1d3 *p = (i1d3 *)pp;
+ inst_code rv = inst_ok;
+
+ if (!allconfig && p->dpos) { /* If set to Ambient */
+
+ if (pnsels != NULL)
+ *pnsels = 0;
+
+ if (psels != NULL)
+ *psels = NULL;
+
+ return inst_ok;
+ }
+
+ /* Create/Re-create a current list of abailable display types */
+ if (p->dtlist == NULL || recreate) {
+ if ((rv = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
+ i1d3_disptypesel, 1 /* doccss*/, 1 /* doccmx */)) != inst_ok) {
+ return rv;
+ }
+ }
+
+ if (pnsels != NULL)
+ *pnsels = p->ndtlist;
+
+ if (psels != NULL)
+ *psels = p->dtlist;
+
+ return inst_ok;
+}
+
+/* Given a display type entry, setup for that type */
+static inst_code set_disp_type(i1d3 *p, inst_disptypesel *dentry) {
+ inst_code ev;
+ int refrmode;
+
+ p->icx = dentry->ix;
+ p->cbid = dentry->cbid;
+ refrmode = dentry->refr;
+
+ if ( IMODETST(p->mode, inst_mode_emis_norefresh_ovd)) { /* Must test this first! */
+ refrmode = 0;
+ } else if (IMODETST(p->mode, inst_mode_emis_refresh_ovd)) {
+ refrmode = 1;
+ }
+
+ if (p->refrmode != refrmode)
+ p->rrset = 0; /* This is a hint we may have swapped displays */
+ p->refrmode = refrmode;
+
+// if (p->refrmode && p->dtype == i1d3_munkdisp) {
+ if (p->refrmode) {
+ p->inttime = 2.0 * p->dinttime; /* Double integration time */
+ } else {
+ p->inttime = p->dinttime; /* Normal integration time */
+ }
+
+ if (dentry->flags & inst_dtflags_ccss) {
+
+ if ((ev = i1d3_comp_calmat(p, p->emis_cal, p->obType, p->custObserver,
+ p->sens, dentry->sets, dentry->no_sets)) != inst_ok) {
+ a1logd(p->log, 1, "i1d3_set_disp_type: comp_calmat ccss failed with rv = 0x%x\n",ev);
+ return ev;
+ }
+ /* Use MIbLSr for ambient */
+ if ((ev = i1d3_comp_calmat(p, p->ambi_cal, p->obType, p->custObserver, p->ambi,
+ p->ambi, 3)) != inst_ok)
+ return ev;
+
+ icmSetUnity3x3(p->ccmat);
+
+ if (p->log->debug >= 4) {
+ a1logd(p->log,4,"Display type set CCSS:\n");
+ a1logd(p->log,4,"Emissive matrix = %f %f %f\n",
+ p->emis_cal[0][0], p->emis_cal[0][1], p->emis_cal[0][2]);
+ a1logd(p->log,4," %f %f %f\n",
+ p->emis_cal[1][0], p->emis_cal[1][1], p->emis_cal[1][2]);
+ a1logd(p->log,4," %f %f %f\n",
+ p->emis_cal[2][0], p->emis_cal[2][1], p->emis_cal[2][2]);
+ a1logd(p->log,4,"\n");
+ }
+
+ } else { /* Assume matrix */
+
+ /* Create the default calibration matrix */
+ if ((ev = i1d3_comp_calmat(p, p->emis_cal, p->obType, p->custObserver,
+ p->sens, p->sens, 3)) != inst_ok) {
+ a1logd(p->log, 1, "i1d3_set_disp_type: comp_calmat dflt failed with rv = 0x%x\n",ev);
+ return ev;
+ }
+ /* Use MIbLSr for ambient */
+ if ((ev = i1d3_comp_calmat(p, p->ambi_cal, p->obType, p->custObserver, p->ambi,
+ p->ambi, 3)) != inst_ok)
+ return ev;
+
+ if (dentry->flags & inst_dtflags_ccmx) {
+ icmCpy3x3(p->ccmat, dentry->mat);
+ } else {
+ icmSetUnity3x3(p->ccmat);
+ }
+ }
+
+ return inst_ok;
+}
+
+/* Setup the default display type */
+static inst_code set_default_disp_type(i1d3 *p) {
+ inst_code ev;
+ int i;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list((inst *)p, &p->ndtlist, &p->dtlist,
+ i1d3_disptypesel, 1 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ for (i = 0; !(p->dtlist[i].flags & inst_dtflags_end); i++) {
+ if (p->dtlist[i].flags & inst_dtflags_default)
+ break;
+ }
+ if (p->dtlist[i].flags & inst_dtflags_end) {
+ a1loge(p->log, 1, "set_default_disp_type: failed to find type!\n");
+ return inst_internal_error;
+ }
+ if ((ev = set_disp_type(p, &p->dtlist[i])) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+/* Set the display type */
+static inst_code i1d3_set_disptype(inst *pp, int ix) {
+ i1d3 *p = (i1d3 *)pp;
+ inst_code ev;
+ inst_disptypesel *dentry;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
+ i1d3_disptypesel, 1 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ if (ix < 0 || ix >= p->ndtlist)
+ return inst_unsupported;
+
+ dentry = &p->dtlist[ix];
+
+ if ((ev = set_disp_type(p, dentry)) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+
+/*
+ * set or reset an optional mode
+ *
+ * Some options talk to the instrument, and these will
+ * error if it hasn't been initialised.
+ */
+static inst_code
+i1d3_get_set_opt(inst *pp, inst_opt_type m, ...)
+{
+ i1d3 *p = (i1d3 *)pp;
+ inst_code ev;
+
+ /* Record the trigger mode */
+ if (m == inst_opt_trig_prog
+ || m == inst_opt_trig_user) {
+ p->trig = m;
+ return inst_ok;
+ }
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* Get the display type information */
+ if (m == inst_opt_get_dtinfo) {
+ va_list args;
+ int *refrmode, *cbid;
+
+ va_start(args, m);
+ refrmode = va_arg(args, int *);
+ cbid = va_arg(args, int *);
+ va_end(args);
+
+ if (refrmode != NULL)
+ *refrmode = p->refrmode;
+ if (cbid != NULL)
+ *cbid = p->cbid;
+
+ return inst_ok;
+ }
+
+ /* Set the ccss observer type */
+ if (m == inst_opt_set_ccss_obs) {
+ va_list args;
+ icxObserverType obType;
+ xspect *custObserver;
+
+ va_start(args, m);
+ obType = va_arg(args, icxObserverType);
+ custObserver = va_arg(args, xspect *);
+ va_end(args);
+
+ if (obType == icxOT_default)
+ obType = icxOT_CIE_1931_2;
+ p->obType = obType;
+ if (obType == icxOT_custom) {
+ p->custObserver[0] = custObserver[0];
+ p->custObserver[1] = custObserver[1];
+ p->custObserver[2] = custObserver[2];
+ }
+
+ return inst_ok;
+ }
+
+ /* Operate the LEDS */
+ if (m == inst_opt_get_gen_ledmask) {
+ va_list args;
+ int *mask = NULL;
+
+ va_start(args, m);
+ mask = va_arg(args, int *);
+ va_end(args);
+ *mask = 0x1; /* One general LEDs */
+ return inst_ok;
+ } else if (m == inst_opt_get_led_state) {
+ va_list args;
+ int *mask = NULL;
+
+ va_start(args, m);
+ mask = va_arg(args, int *);
+ va_end(args);
+ *mask = p->led_state;
+ return inst_ok;
+ } else if (m == inst_opt_set_led_state) {
+ va_list args;
+ int mask = 0;
+
+ va_start(args, m);
+ mask = va_arg(args, int);
+ va_end(args);
+
+ if (p->led_state & 0x1)
+ return i1d3_set_LEDs(p, i1d3_flash, 0.0, 100.0, 0x80);
+ else
+ return i1d3_set_LEDs(p, i1d3_flash, 100.0, 0.0, 0x80);
+ }
+
+ if (m == inst_opt_get_pulse_ledmask) {
+ va_list args;
+ int *mask = NULL;
+
+ va_start(args, m);
+ mask = va_arg(args, int *);
+ va_end(args);
+ *mask = 0x1; /* General LED is pulsable */
+ return inst_ok;
+ } else if (m == inst_opt_set_led_pulse_state) {
+ va_list args;
+ double period, on_time_prop, trans_time_prop;
+ double offtime, ontime;
+ i1d3_ledmode mode;
+ int nopulses;
+
+ va_start(args, m);
+ period = va_arg(args, double);
+ on_time_prop = va_arg(args, double);
+ trans_time_prop = va_arg(args, double);
+ va_end(args);
+ if (period < 0.0
+ || on_time_prop < 0.0 || on_time_prop > 1.0
+ || trans_time_prop < 0.0 || trans_time_prop > 1.0
+ || trans_time_prop > on_time_prop || trans_time_prop > (1.0 - on_time_prop))
+ return inst_bad_parameter;
+
+ p->led_period = period;
+ p->led_on_time_prop = on_time_prop;
+ p->led_trans_time_prop = trans_time_prop;
+
+ /* i1d3 doesn't have controllable fade time, so any = fade */
+ if (trans_time_prop > 0.0) {
+ mode = i1d3_fade;
+ offtime = period * (1.0 - on_time_prop - trans_time_prop);
+ ontime = period * (on_time_prop + trans_time_prop);
+ } else {
+ mode = i1d3_flash;
+ offtime = period * (1.0 - on_time_prop);
+ ontime = period * on_time_prop;
+ }
+ nopulses = 0x80;
+ if (period == 0.0 || on_time_prop == 0.0) {
+ mode = i1d3_flash;
+ offtime = 100.0;
+ ontime = 0.0;
+ p->led_state = 0;
+ } else {
+ p->led_state = 1;
+ }
+ return i1d3_set_LEDs(p, mode, offtime, ontime, nopulses);
+
+ } else if (m == inst_opt_get_led_state) {
+ va_list args;
+ double *period, *on_time_prop, *trans_time_prop;
+
+ va_start(args, m);
+ period = va_arg(args, double *);
+ on_time_prop = va_arg(args, double *);
+ trans_time_prop = va_arg(args, double *);
+ va_end(args);
+ if (period != NULL) *period = p->led_period;
+ if (on_time_prop != NULL) *on_time_prop = p->led_on_time_prop;
+ if (trans_time_prop != NULL) *trans_time_prop = p->led_trans_time_prop;
+ return inst_ok;
+ }
+ return inst_unsupported;
+}
+
+/* Constructor */
+extern i1d3 *new_i1d3(icoms *icom, instType itype) {
+ i1d3 *p;
+
+ if ((p = (i1d3 *)calloc(sizeof(i1d3),1)) == NULL) {
+ a1loge(icom->log, 1, "new_i1d3: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(icom->log);
+
+ p->init_coms = i1d3_init_coms;
+ p->init_inst = i1d3_init_inst;
+ p->capabilities = i1d3_capabilities;
+ p->meas_config = i1d3_meas_config;
+ p->check_mode = i1d3_check_mode;
+ p->set_mode = i1d3_set_mode;
+ p->get_disptypesel = i1d3_get_disptypesel;
+ p->set_disptype = i1d3_set_disptype;
+ p->get_set_opt = i1d3_get_set_opt;
+ p->read_sample = i1d3_read_sample;
+ p->read_refrate = i1d3_read_refrate;
+ p->col_cor_mat = i1d3_col_cor_mat;
+ p->col_cal_spec_set = i1d3_col_cal_spec_set;
+ p->get_n_a_cals = i1d3_get_n_a_cals;
+ p->calibrate = i1d3_calibrate;
+ p->meas_delay = i1d3_meas_delay;
+ p->get_refr_rate = i1d3_get_refr_rate;
+ p->set_refr_rate = i1d3_set_refr_rate;
+ p->interp_error = i1d3_interp_error;
+ p->config_enum = i1d3_config_enum;
+ p->del = i1d3_del;
+
+ p->icom = icom;
+ p->itype = icom->itype;
+
+ amutex_init(p->lock);
+ icmSetUnity3x3(p->ccmat);
+
+ return p;
+}
+
+
+
+/* Combine the 2 word key and 64 byte challenge into a 64 bit response. */
+static void create_unlock_response(unsigned int *k, unsigned char *c, unsigned char *r) {
+ int i;
+ unsigned char sc[8], sr[16]; /* Sub-challeng and response */
+
+ /* Only 8 bytes is used out of challenge buffer starting at */
+ /* offset 35. Bytes are decoded with xor of byte 3 value. */
+ for (i = 0; i < 8; i++)
+ sc[i] = c[3] ^ c[35 + i];
+
+ /* Combine key with 16 byte challenge to create core 16 byte response */
+ {
+ unsigned int ci[2]; /* challenge as 4 ints */
+ unsigned int co[4]; /* product, difference of 4 ints */
+ unsigned int sum; /* Sum of all input bytes */
+ unsigned char s0, s1; /* Byte components of sum. */
+
+ /* Shuffle bytes into 32 bit ints to be able to use 32 bit computation. */
+ ci[0] = (sc[3] << 24)
+ + (sc[0] << 16)
+ + (sc[4] << 8)
+ + (sc[6]);
+
+ ci[1] = (sc[1] << 24)
+ + (sc[7] << 16)
+ + (sc[2] << 8)
+ + (sc[5]);
+
+ /* Computation on the ints */
+ co[0] = -k[0] - ci[1];
+ co[1] = -k[1] - ci[0];
+ co[2] = ci[1] * -k[0];
+ co[3] = ci[0] * -k[1];
+
+ /* Sum of challenge bytes */
+ for (sum = 0, i = 0; i < 8; i++)
+ sum += sc[i];
+
+ /* Minus the two key values as bytes */
+ sum += (0xff & -k[0]) + (0xff & (-k[0] >> 8))
+ + (0xff & (-k[0] >> 16)) + (0xff & (-k[0] >> 24));
+ sum += (0xff & -k[1]) + (0xff & (-k[1] >> 8))
+ + (0xff & (-k[1] >> 16)) + (0xff & (-k[1] >> 24));
+
+ /* Convert sum to bytes. Only need 2, because sum of 16 bytes can't exceed 16 bits. */
+ s0 = sum & 0xff;
+ s1 = (sum >> 8) & 0xff;
+
+ /* Final computation of bytes from 4 ints + sum bytes */
+ sr[0] = ((co[0] >> 16) & 0xff) + s0;
+ sr[1] = ((co[2] >> 8) & 0xff) - s1;
+ sr[2] = ( co[3] & 0xff) + s1;
+ sr[3] = ((co[1] >> 16) & 0xff) + s0;
+ sr[4] = ((co[2] >> 16) & 0xff) - s1;
+ sr[5] = ((co[3] >> 16) & 0xff) - s0;
+ sr[6] = ((co[1] >> 24) & 0xff) - s0;
+ sr[7] = ( co[0] & 0xff) - s1;
+ sr[8] = ((co[3] >> 8) & 0xff) + s0;
+ sr[9] = ((co[2] >> 24) & 0xff) - s1;
+ sr[10] = ((co[0] >> 8) & 0xff) + s0;
+ sr[11] = ((co[1] >> 8) & 0xff) - s1;
+ sr[12] = ( co[1] & 0xff) + s1;
+ sr[13] = ((co[3] >> 24) & 0xff) + s1;
+ sr[14] = ( co[2] & 0xff) + s0;
+ sr[15] = ((co[0] >> 24) & 0xff) - s0;
+ }
+
+ /* The OEM driver sets the resonse to random bytes, */
+ /* but we don't need to do this, since the device doesn't */
+ /* look at them. */
+ for (i = 0; i < 64; i++)
+ r[i] = 0;
+
+ /* The actual resonse is 16 bytes at offset 24 in the response buffer. */
+ /* The OEM driver xor's challeng byte 2 with response bytes 4..63, but */
+ /* since the instrument doesn't look at them, we only do this to the actual */
+ /* response. */
+ for (i = 0; i < 16; i++)
+ r[24 + i] = c[2] ^ sr[i];
+}
diff --git a/spectro/i1d3.h b/spectro/i1d3.h
new file mode 100644
index 0000000..96de478
--- /dev/null
+++ b/spectro/i1d3.h
@@ -0,0 +1,161 @@
+#ifndef I1D3_H
+
+/*
+ * Argyll Color Correction System
+ *
+ * X-Rite i1d3 related defines
+ *
+ * Author: Graeme W. Gill
+ * Date: 7/10/2007
+ *
+ * Copyright 2006 - 2013, Graeme W. Gill
+ * All rights reserved.
+ *
+ * (Based on huey.h)
+ *
+ * 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.
+ */
+
+#include "inst.h"
+
+/* Note: update huey_interp_error() and huey_interp_code() in huey.c */
+/* if anything of these #defines are added or subtracted */
+
+/* Fake Error codes */
+#define I1D3_INTERNAL_ERROR 0x61 /* Internal software error */
+#define I1D3_COMS_FAIL 0x62 /* Communication failure */
+#define I1D3_UNKNOWN_MODEL 0x63 /* Not an i1d3 */
+#define I1D3_DATA_PARSE_ERROR 0x64 /* Read data parsing error */
+
+/* Real error code */
+#define I1D3_OK 0x00
+
+#define I1D3_UNKNOWN_UNLOCK 0x01
+#define I1D3_UNLOCK_FAIL 0x02
+#define I1D3_BAD_EX_CHSUM 0x03
+
+#define I1D3_SPOS_EMIS 0x05 /* Needs to be in emsissive configuration */
+#define I1D3_SPOS_AMB 0x06 /* Needs to be in ambient configuration */
+
+#define I1D3_TOO_FEW_CALIBSAMP 0x10
+
+#define I1D3_BAD_WR_LENGTH 0x11
+#define I1D3_BAD_RD_LENGTH 0x12
+#define I1D3_BAD_RET_STAT 0x13
+#define I1D3_BAD_RET_CMD 0x14
+#define I1D3_NOT_INITED 0x15
+
+/* Internal errors */
+#define I1D3_BAD_MEM_ADDRESS 0x20
+#define I1D3_BAD_MEM_LENGTH 0x21
+#define I1D3_INT_CIECONVFAIL 0x22
+#define I1D3_INT_MATINV_FAIL 0x23
+#define I1D3_BAD_LED_MODE 0x24
+#define I1D3_NO_COMS 0x25
+#define I1D3_BAD_STATUS 0x26
+#define I1D3_INT_THREADFAILED 0x27
+
+/* Sub-type of instrument */
+typedef enum {
+ i1d3_disppro = 0, /* i1 DisplayPro */
+ i1d3_munkdisp = 1, /* ColorMunki Display */
+ i1d3_oem = 2, /* OEM */
+ i1d3_nec_ssp = 3, /* NEC SpectraSensor Pro */
+ i1d3_quato_sh3 = 4 /* Quato Silver Haze 3 */
+} i1d3_dtype;
+
+/* Measurement mode */
+typedef enum {
+ i1d3_adaptive = 0, /* Frequency over fixed period then adaptive period measurement */
+ i1d3_frequency = 1, /* Frequency over fixed period measurement */
+ i1d3_period = 2 /* Adaptive period measurement */
+} i1d3_mmode;
+
+/* I1D3 communication object */
+struct _i1d3 {
+ INST_OBJ_BASE
+
+ amutex lock; /* Command lock */
+
+ /* Modes */
+ inst_mode mode; /* Currently selected mode */
+ inst_opt_type trig; /* Reading trigger mode */
+
+ /* Information and EEPROM values */
+ i1d3_dtype dtype; /* Base type of instrument, ie i1d3_disppro or i1d3_munkdisp */
+ i1d3_dtype stype; /* Sub type of instrument, ie. any of i1d3_dtype. */
+ /* (Only accurate if it needed unlocking). */
+ int status; /* 0 if status is ok (not sure what this is) */
+ char serial_no[21]; /* "I1-11.A-01.100999.02" or "CM-11.A-01.100999.02" */
+ char prod_name[32]; /* "i1Display3 " or "ColorMunki Display" */
+ int prod_type; /* 16 bit product type number. i1d3_disppro = 0x0001, */
+ /* i1d3_munkdisp = 0x0002 */
+ char firm_ver[32]; /* Firmwar version string. ie. "v1.0 " */
+ char firm_date[32]; /* Firmwar date string. ie. "11Jan11" */
+
+ /* Calibration information */
+ ORD64 cal_date; /* Calibration date */
+ xspect sens[3]; /* RGB Sensor spectral sensitivities in Hz per mW/nm */
+ xspect ambi[3]; /* RGB Sensor with ambient filter spectral sensitivities */
+
+ double black[3]; /* Black level to subtract */
+ double emis_cal[3][3]; /* Current emssion calibration matrix */
+ double ambi_cal[3][3]; /* Current ambient calibration matrix */
+
+ inst_disptypesel *dtlist; /* Display Type list */
+ int ndtlist; /* Number of valid dtlist entries */
+ int icx; /* Internal calibration matrix index, 11 = Raw */
+ int cbid; /* calibration base ID, 0 if not a base */
+ int refrmode; /* nz if in refresh display mode/double int. time */
+ double ccmat[3][3]; /* Optional colorimeter correction matrix */
+ icxObserverType obType; /* ccss observer to use */
+ xspect custObserver[3]; /* Custom ccss observer to use */
+
+ /* Computed factors and state */
+ int rrset; /* Flag, nz if the refresh rate has been determined */
+ double refperiod; /* if > 0.0 in refmode, target int time quantization */
+ double refrate; /* Measured refresh rate in Hz */
+ int refrvalid; /* nz if refrate is valid */
+ double clk_freq; /* Clock frequency (12Mhz) */
+ double dinttime; /* default integration time = 0.2 seconds */
+ double inttime; /* current integration time = 0.2 seconds */
+
+ double transblend; /* Blend between fixed and adaptive integration */
+ /* at low light levels */
+
+ /* Other state */
+ int led_state; /* : Current LED on/off state */
+ double led_period, led_on_time_prop, led_trans_time_prop; /* Pulse state */
+
+ athread *th; /* Diffuser position monitoring thread */
+ volatile int th_term; /* nz to terminate thread */
+ volatile int th_termed; /* nz when thread terminated */
+ int dpos; /* Diffuser position, 0 = display, 1 = ambient */
+
+}; typedef struct _i1d3 i1d3;
+
+/* Constructor */
+extern i1d3 *new_i1d3(icoms *icom, instType itype);
+
+
+#define I1D3_H
+#endif /* I1D3_H */
diff --git a/spectro/i1disp.c b/spectro/i1disp.c
new file mode 100644
index 0000000..b9da1e1
--- /dev/null
+++ b/spectro/i1disp.c
@@ -0,0 +1,2591 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * Gretag i1Display related functions
+ *
+ * Author: Graeme W. Gill
+ * Date: 18/10/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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#include <math.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* !SALONEINSTLIB */
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* !SALONEINSTLIB */
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "i1disp.h"
+
+static void dump_bytes(a1log *log, char *pfx, unsigned char *buf, int base, int len);
+static inst_code i1disp_interp_code(inst *pp, int ec);
+static inst_code i1disp_do_fcal_setit(i1disp *p);
+static inst_code i1disp_check_unlock(i1disp *p);
+
+#define MAX_MES_SIZE 500 /* Maximum normal message reply size */
+#define MAX_RD_SIZE 5000 /* Maximum reading messagle reply size */
+
+#define CALFACTOR 3.428 /* Emissive magic calibration factor */
+
+/* ------------------------------------------------------------------------ */
+/* Implementation */
+
+/* Interpret an icoms error into a I1DISP error */
+static int icoms2i1disp_err(int se) {
+ if (se != ICOM_OK)
+ return I1DISP_COMS_FAIL;
+ return I1DISP_OK;
+}
+
+/* i1Display command codes - number:X is argument count:return type */
+/* B = byte (8bit), S = short (16bit), W = word (32bit), */
+/* A = string (5 bytes total max) , - = none */
+typedef enum {
+ i1d_status = 0x00, /* -:A Get status string */
+ i1d_rd_red = 0x01, /* -:W Read the red channel clk count (and trig ?) */
+ i1d_rd_green = 0x02, /* -:W Read the green channel clk count */
+ i1d_rd_blue = 0x03, /* -:W Read the blue channel clk count */
+ i1d_getmeas_p = 0x04, /* -:W Read the measure refresh period */
+ i1d_setintgt = 0x05, /* W:- Set the integration time */
+ i1d_getintgt = 0x06, /* -:W Get the integration time */
+ i1d_wrreg = 0x07, /* BB:- Write a register value */
+ i1d_rdreg = 0x08, /* B:B Read a register value */
+ i1d_getmeas_p2 = 0x09, /* -:W Read the measure refresh period (finer ?) */
+ i1d_m_red_p = 0x0a, /* B:W Measure the red period for given edge count */
+ i1d_m_green_p = 0x0b, /* B:W Measure the green period for given edge count */
+ i1d_m_blue_p = 0x0c, /* B:W Measure the blue period for given edge count */
+ i1d_m_rgb_p = 0x0d, /* BBB:W Measure the RGB period for given edge count */
+ i1d_unlock = 0x0e, /* BBBB:- Unlock the interface */
+
+ i1d_m_red_p2 = 0x10, /* S:W Measure the red period (16 bit) */
+ i1d_m_green_p2 = 0x11, /* S:W Measure the green period (16 bit) */
+ i1d_m_blue_p2 = 0x12, /* S:W Measure the blue period (16 bit) */
+ /* S = edge count */
+
+ i1d_m_red_2 = 0x13, /* B:W Measure the red channel (16 bit) */
+ /* B = sync mode, typically 1 */
+
+ i1d_setmedges2 = 0x14, /* SB:- Set number of edges used for measurment 16 bit */
+ /* B = channel */
+ i1d_getmedges2 = 0x15, /* B:S Get number of edges used for measurment 16 bit */
+ /* B = channel */
+
+ i1d_m_rgb_edge_2 = 0x16, /* -:W Measure RGB Edge (16 bit) */
+
+ i1d_set_pll_p = 0x11, /* SS:- Set PLL period */
+ i1d_get_pll_p = 0x12, /* -:W Get PLL period */
+
+ i1d_m_rgb_edge_3 = 0x10, /* BBBB:W Measure RGB Edge & return red. */
+ /* BBBB = edge counts ??? */
+ i1d_g_green_3 = 0x11, /* -:W Get green data */
+ i1d_g_blue_3 = 0x12, /* -:W Get blue data */
+
+ i1d_wrxreg = 0x13, /* SB:- Write an extra register value */
+ i1d_rdxreg = 0x14, /* S:B Read an extra register value */
+
+ i1d_rdexreg = 0x19 /* BS:B Smile: Read an extended register value */
+ /* The address range overlapps i1d_rdreg */
+} i1DispCC;
+
+/* Do a command/response exchange with the i1disp. */
+/* Return the error code */
+/* The i1 display uses a rather convoluted means of communication (historical?). */
+/* Messages from the host are conveyed one byte per USB control message, */
+/* with the byte conveyed in bRequest, with each such control message reading */
+/* 8 bytes of data from the device. The last 8 bytes read in an overall */
+/* message contains up to 5 response bytes, the last always being nul (?). */
+/* Muti-byte quantities are transmitted in big-endian order. */
+/* (Instructions 1,2,3,8 & 9 seem to be retried up to 5 times. We're */
+/* not doing this, as it may just be historical) */
+static inst_code
+i1disp_command_1(
+ i1disp *p, /* i1display object */
+ i1DispCC cc, /* Command code */
+ unsigned char *in, int insize, /* Parameter to send */
+ unsigned char *out, int bsize, int *rsize, /* Parameter returned */
+ double to /* Timeout in seconds */
+) {
+ int requesttype; /* 8 bit request type (USB bmRequestType) */
+ int request; /* 8 bit request code (USB bRequest) */
+ int value; /* 16 bit value (USB wValue, sent little endian) */
+ int index; /* 16 bit index (USB wIndex, sent little endian) */
+ int rwsize; /* 16 bit data size (USB wLength, send little endian) */
+ int rcc = 0; /* Return cc code from instruction */
+ int i, tsize;
+ unsigned char buf[8]; /* 8 bytes to read */
+ int se, ua = 0, rv = inst_ok;
+
+ tsize = insize + 2;
+ *rsize = 0;
+
+ a1logd(p->log, 4, "i1disp: Sending cmd %02x args '%s'\n",cc, icoms_tohex(in, insize));
+
+ /* For each byte to be sent */
+ for (i = 0; i < tsize; i++) {
+ unsigned int smsec;
+
+ /* Control message to read 8 bytes */
+ requesttype = IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_ENDPOINT;
+ if (i == 0) /* Count */
+ request = insize + 1;
+ else if (i == 1) /* Command */
+ request = (int)cc;
+ else /* Data */
+ request = (int)in[i-2];
+ value = i; /* Incrementing count */
+ index = (tsize - i - 1); /* Decrementing count */
+ rwsize = 8;
+
+ smsec = msec_time();
+ if ((se = p->icom->usb_control(p->icom, requesttype, request, value, index,
+ buf, rwsize, to)) != 0) {
+ a1logd(p->log, 1, "i1disp: Message send failed with ICOM err 0x%x\n",se);
+ p->last_com_err = se;
+ return i1disp_interp_code((inst *)p, I1DISP_COMS_FAIL);
+ }
+
+ /* We could check the return data. This seems to be what we sent, */
+ /* unless it's the last exchange in the message sequence. */
+ /* I don't currently know how or if the device signals an error. */
+
+ /* If this is the last exchange, copy return value out */
+ if (i == (tsize-1)) {
+ *rsize = buf[1];
+ if (*rsize > bsize)
+ *rsize = bsize;
+ if (*rsize > 5)
+ *rsize = 5;
+ memmove(out, buf + 3, *rsize);
+
+ /* buf[2] is usually the cc, except for i1d_unlock. */
+ /* If it is not the cc, this may indicate that the command */
+ /* should be retried up to a total of 5 times, before */
+ /* assuming it has suceeded. */
+ rcc = buf[2] & 0xff;
+ }
+ }
+ rv = i1disp_interp_code((inst *)p, icoms2i1disp_err(ua));
+
+ if (rv == inst_ok && rcc != cc)
+ rv = i1disp_interp_code((inst *)p, I1DISP_NOT_READY);
+
+ /* Instrument returns "LOCK" to any instruction if it is locked */
+ if (rv == inst_ok && *rsize == 5 && strncmp((char *)out,"LOCK",4) == 0) {
+ rv = i1disp_interp_code((inst *)p, I1DISP_LOCKED);
+ }
+
+ a1logd(p->log, 4, "i1disp: response '%s' ICOM err 0x%x\n",icoms_tohex(out, *rsize),ua);
+
+ return rv;
+}
+
+/* Do a command/response exchange with the i1disp, taking care of */
+/* a LOCK error */
+static inst_code
+i1disp_command(
+ i1disp *p, /* i1display object */
+ i1DispCC cc, /* Command code */
+ unsigned char *in, int insize, /* Parameter to send */
+ unsigned char *out, int bsize, int *rsize, /* Parameter returned */
+ double to /* Timeout in seconds */
+) {
+ inst_code rv;
+
+ if ((rv = i1disp_command_1(p, cc, in, insize, out, bsize, rsize, to)) == inst_ok)
+ return rv;
+
+ /* Unlock and try again */
+ if ((rv & inst_imask) == I1DISP_LOCKED) {
+ if ((rv = i1disp_check_unlock(p)) != inst_ok)
+ return rv;
+ rv = i1disp_command_1(p, cc, in, insize, out, bsize, rsize, to);
+ }
+ return rv;
+}
+
+/* Take an int, and convert it into a byte buffer */
+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 */
+static void short2buf(unsigned char *buf, int inv) {
+ buf[0] = (inv >> 8) & 0xff;
+ buf[1] = (inv >> 0) & 0xff;
+}
+
+/* Take a word sized return buffer, and convert it to an int */
+static int buf2int(unsigned char *buf) {
+ int val;
+ val = (signed char)buf[0];
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[3]));
+ return val;
+}
+
+/* Read a byte from a register */
+static inst_code
+i1disp_rdreg_byte(
+ i1disp *p, /* Object */
+ int *outp, /* Where to write value */
+ int addr /* Register Address, 0 - 127, 159 */
+) {
+ unsigned char c, buf[16];
+ int rsize;
+ inst_code ev;
+
+ if (p->dtype == 0) {
+ if (addr < 0 || addr > 127)
+ return i1disp_interp_code((inst *)p, I1DISP_BAD_REG_ADDRESS);
+ } else {
+ if (addr < 0 || addr > 159)
+ return i1disp_interp_code((inst *)p, I1DISP_BAD_REG_ADDRESS);
+ }
+ c = (unsigned char)addr;
+
+ /* Read a byte */
+ if ((ev = i1disp_command(p, i1d_rdreg, &c, 1,
+ buf, 8, &rsize, 0.5)) != inst_ok)
+ return ev;
+
+ if (rsize != 3)
+ return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE);
+
+ if (buf[0] != c)
+ return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_VAL);
+
+ *outp = (int)buf[1];
+
+ return inst_ok;
+}
+
+/* Read a short from a register */
+static inst_code
+i1disp_rdreg_short(
+ i1disp *p, /* Object */
+ int *outp, /* Where to write value */
+ int addr /* Register Address, 0 - 126 */
+) {
+ inst_code ev;
+ int v, val;
+
+ if ((ev = i1disp_rdreg_byte(p, &v, addr)) != inst_ok)
+ return ev;
+ val = v;
+
+ if ((ev = i1disp_rdreg_byte(p, &v, addr+1)) != inst_ok)
+ return ev;
+ val = ((val << 8) + (0xff & v));
+
+ *outp = val;
+
+ return inst_ok;
+}
+
+/* Read a word from a register */
+static inst_code
+i1disp_rdreg_word(
+ i1disp *p, /* Object */
+ int *outp, /* Where to write value */
+ int addr /* Register Address, 0 - 124 */
+) {
+ inst_code ev;
+ int v, val;
+
+ if ((ev = i1disp_rdreg_byte(p, &v, addr)) != inst_ok)
+ return ev;
+ val = v;
+
+ if ((ev = i1disp_rdreg_byte(p, &v, addr+1)) != inst_ok)
+ return ev;
+ val = ((val << 8) + (0xff & v));
+
+ if ((ev = i1disp_rdreg_byte(p, &v, addr+2)) != inst_ok)
+ return ev;
+ val = ((val << 8) + (0xff & v));
+
+ if ((ev = i1disp_rdreg_byte(p, &v, addr+3)) != inst_ok)
+ return ev;
+ val = ((val << 8) + (0xff & v));
+
+ *outp = val;
+
+ return inst_ok;
+}
+
+
+/* Read a float from a register */
+/* Will return I1DISP_FLOAT_NOT_SET if the float value was 0xffffffff */
+static inst_code
+i1disp_rdreg_float(
+ i1disp *p, /* Object */
+ double *outp, /* Where to write value */
+ int addr /* Register Address, 0 - 124 */
+) {
+ inst_code ev;
+ int val;
+
+ if ((ev = i1disp_rdreg_word(p, &val, addr)) != inst_ok)
+ return ev;
+
+ if (ev == 0xffffffff) {
+ return I1DISP_FLOAT_NOT_SET;
+ }
+
+ *outp = IEEE754todouble((unsigned int)val);
+ return inst_ok;
+}
+
+
+/* Write a byte to a register */
+static inst_code
+i1disp_wrreg_byte(
+ i1disp *p, /* Object */
+ int inv, /* Input value */
+ int addr /* Register Address, 0 - 127 */
+) {
+ int cval;
+ unsigned char ibuf[16], obuf[16];
+ int rsize;
+ inst_code ev;
+
+ inv &= 0xff;
+
+ /* Read it first, to see if it needs writing */
+ if ((ev = i1disp_rdreg_byte(p, &cval, addr) ) != inst_ok)
+ return ev;
+
+ if (cval == inv) /* No need to write */
+ return inst_ok;
+
+ ibuf[0] = (unsigned char)addr;
+ ibuf[1] = (unsigned char)inv;
+
+ /* Write a byte */
+ if ((ev = i1disp_command(p, i1d_wrreg, ibuf, 2,
+ obuf, 8, &rsize, 0.5)) != inst_ok)
+ return ev;
+
+ if (rsize != 2)
+ return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE);
+
+ if (obuf[0] != addr)
+ return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_VAL);
+
+ /* Check it got written properly */
+ if ((ev = i1disp_rdreg_byte(p, &cval, addr) ) != inst_ok)
+ return ev;
+
+ cval &= 0xff;
+ if (cval != inv) /* No need to write */
+ return i1disp_interp_code((inst *)p, I1DISP_EEPROM_WRITE_FAIL);
+
+ return inst_ok;
+}
+
+/* Write a word to a register */
+static inst_code
+i1disp_wrreg_word(
+ i1disp *p, /* Object */
+ int inv, /* Where to write value */
+ int addr /* Register Address, 0 - 124 */
+) {
+ inst_code ev;
+ int v;
+
+ v = (inv >> 24) & 0xff;
+ if ((ev = i1disp_wrreg_byte(p, v, addr) ) != inst_ok)
+ return ev;
+
+ v = (inv >> 16) & 0xff;
+ if ((ev = i1disp_wrreg_byte(p, v, addr+1) ) != inst_ok)
+ return ev;
+
+ v = (inv >> 8) & 0xff;
+ if ((ev = i1disp_wrreg_byte(p, v, addr+2) ) != inst_ok)
+ return ev;
+
+ v = (inv >> 0) & 0xff;
+ if ((ev = i1disp_wrreg_byte(p, v, addr+3) ) != inst_ok)
+ return ev;
+
+ return inst_ok;
+}
+
+/* Write a float to a register */
+static inst_code
+i1disp_wrreg_float(
+ i1disp *p, /* Object */
+ double inv, /* Value to write */
+ int addr /* Register Address, 0 - 124 */
+) {
+ inst_code ev;
+ int val;
+
+ val = (int)doubletoIEEE754(inv);
+
+ if ((ev = i1disp_wrreg_word(p, val, addr)) != inst_ok)
+ return ev;
+ return inst_ok;
+}
+
+/* ColorMunki Smile: Read a byte from an extended register range */
+static inst_code
+i1disp_rdexreg_bytes(
+ i1disp *p, /* Object */
+ unsigned char *outp, /* Where to write values */
+ int addr, /* Register Address, 16 bit */
+ int len /* Number of bytes, 8 bits */
+) {
+ unsigned char ibuf[16];
+ unsigned char obuf[16];
+ int rsize;
+ inst_code ev;
+
+ if (p->dtype != 2) /* Only ColorMunki Smile ? */
+ return i1disp_interp_code((inst *)p, I1DISP_WRONG_DEVICE);
+
+ if (addr < 0 || addr > 0x0200)
+ return i1disp_interp_code((inst *)p, I1DISP_BAD_REG_ADDRESS);
+
+ if (len < 0 || (addr + len) > 0x0200)
+ return i1disp_interp_code((inst *)p, I1DISP_BAD_REG_ADDRESS);
+
+ for (; len > 0; ) {
+ int rlen = len;
+ if (rlen > 4)
+ rlen = 4;
+
+ /* Read up to 4 bytes at a time */
+ short2buf(ibuf+0, addr); /* Address to read from */
+ ibuf[2] = rlen; /* Number of bytes to read */
+ if ((ev = i1disp_command(p, i1d_rdexreg, ibuf, 3,
+ obuf, 16, &rsize, 0.5)) != inst_ok)
+ return ev;
+
+ if ((rsize < 4 && (rsize != (2 + rlen)))
+ || (rsize >= 4 && (rsize != (1 + rlen))))
+ return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE);
+
+ if (obuf[0] != rlen) /* Number of bytes returned */
+ return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_VAL);
+
+ memcpy(outp + addr, obuf + 1, rlen);
+ addr += rlen;
+ len -= rlen;
+ }
+ return inst_ok;
+}
+
+/* Read the integration time */
+static inst_code
+i1disp_rd_int_time(
+ i1disp *p, /* Object */
+ int *outp /* Where to write value */
+) {
+ unsigned char buf[16];
+ int rsize;
+ inst_code ev;
+
+ if ((ev = i1disp_command(p, i1d_getintgt, NULL, 0,
+ buf, 8, &rsize, 0.5)) != inst_ok)
+ return ev;
+
+ if (rsize != 5)
+ return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE);
+
+ *outp = buf2int(buf);
+
+ return inst_ok;
+}
+
+/* Set the integration time */
+static inst_code
+i1disp_wr_int_time(
+ i1disp *p, /* Object */
+ int inv /* Value to write */
+) {
+ unsigned char buf[16];
+ int rsize;
+ inst_code ev;
+
+ int2buf(buf, inv);
+ if ((ev = i1disp_command(p, i1d_setintgt, buf, 4,
+ buf, 8, &rsize, 0.5)) != inst_ok)
+ return ev;
+
+ return inst_ok;
+}
+
+/* Read the refresh period */
+static inst_code
+i1disp_rd_meas_ref_period(
+ i1disp *p, /* Object */
+ int *outp /* Where to write value */
+) {
+ unsigned char buf[16];
+ int rsize;
+ inst_code ev;
+
+ if ((ev = i1disp_command(p, i1d_getmeas_p, NULL, 0,
+ buf, 8, &rsize, 1.5)) != inst_ok)
+ return ev;
+
+ if (rsize != 5)
+ return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE);
+
+ *outp = buf2int(buf);
+
+ return inst_ok;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Take a raw RGB period measurement from the device for an i1d1. */
+/* The time taken to count the given number of L2F clock edges (+ve & -ve) */
+/* is measured in clk clk_freq counts. */
+static inst_code
+i1d1_period_measure(
+ i1disp *p, /* Object */
+ int edgec[3], /* Number of clock edges to count */
+ double rgb[3] /* Return the number of clk's */
+) {
+ int i;
+ unsigned char ibuf[16];
+ unsigned char obuf[16];
+ int rsize;
+ inst_code ev;
+
+ /* Sanity check the number of edges */
+ for (i = 0; i < 3; i++) {
+ if (edgec[i] < 1 || edgec[i] > 255)
+ return i1disp_interp_code((inst *)p, I1DISP_BAD_INT_THRESH);
+ ibuf[i] = (char)edgec[i];
+ }
+
+ /* Do the measurement, and return the Red value */
+ if ((ev = i1disp_command(p, i1d_m_rgb_p, ibuf, 3,
+ obuf, 8, &rsize, 60.0)) != inst_ok)
+ return ev;
+ if (rsize != 5)
+ return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE);
+ rgb[0] = (double)buf2int(obuf);
+
+ /* Get the green value */
+ if ((ev = i1disp_command(p, i1d_rd_green, NULL, 0,
+ obuf, 8, &rsize, 0.5)) != inst_ok)
+ return ev;
+ if (rsize != 5)
+ return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE);
+ rgb[1] = (double)buf2int(obuf);
+
+ /* Get the blue value */
+ if ((ev = i1disp_command(p, i1d_rd_blue, NULL, 0,
+ obuf, 8, &rsize, 0.5)) != inst_ok)
+ return ev;
+ if (rsize != 5)
+ return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE);
+ rgb[2] = (double)buf2int(obuf);
+
+ return inst_ok;
+}
+
+/* Take a cooked period measurement from the device for the i1d1 */
+/* and return the frequency for each sensor. */
+static inst_code
+i1d1_take_measurement(
+ i1disp *p, /* Object */
+ int cal, /* nz if black is not to be subtracted */
+ double rgb[3] /* Return the rgb frequency values */
+) {
+ int i;
+ int edgec[3]; /* Edge count 1..255 for each channel */
+ inst_code ev;
+
+ if (p->inited == 0)
+ return i1disp_interp_code((inst *)p, I1DISP_NOT_INITED);
+
+ if (p->dtype != 0)
+ return i1disp_interp_code((inst *)p, I1DISP_WRONG_DEVICE);
+
+ /* Do an initial measurement with minimum edge count of 1 */
+ edgec[0] = edgec[1] = edgec[2] = 1;
+
+ if ((ev = i1d1_period_measure(p, edgec, rgb)) != inst_ok)
+ return ev;
+
+ a1logd(p->log, 3, "Initial RGB = %f %f %f\n",rgb[0],rgb[1],rgb[2]);
+
+ /* Compute adjusted edge count, aiming */
+ /* for count values of clk_freq = 1 second (~1e6). */
+ for (i = 0; i < 3; i++) {
+ double ns;
+ if (p->clk_freq > ((255.0 - 0.5) * rgb[i]))
+ ns = 255.0;
+ else {
+ ns = floor(p->clk_freq/rgb[i]) + 0.5;
+ if (ns < 1.0)
+ ns = 1.0;
+ }
+ edgec[i] = (int)ns;
+ }
+
+ /* Only if we compute a different edge count, read again */
+ if (edgec[0] > 1 || edgec[1] > 1 || edgec[2] > 1) {
+ double rgb2[3]; /* 2nd RGB Readings */
+
+ if ((ev = i1d1_period_measure(p, edgec, rgb2)) != inst_ok)
+ return ev;
+
+ /* Average readings if we repeated a measurement with the same edge count */
+ /* (Minor advantage, but may as well use it) */
+ for (i = 0; i < 3; i++) {
+ if (edgec[i] == 1)
+ rgb[i] = 0.5 * (rgb[i] + rgb2[i]);
+ else
+ rgb[i] = rgb2[i];
+ }
+ }
+
+ a1logd(p->log, 3, "scaled %d %d %d gives RGB = %f %f %f\n", edgec[0],edgec[1],edgec[2], rgb[0],rgb[1],rgb[2]);
+
+ /* Compute adjusted readings as a frequency. */
+ /* We need to divide the number of edges/2 by the period in seconds */
+ for (i = 0; i < 3; i++) {
+ rgb[i] = (p->rgbadj[i] * 0.5 * (double)edgec[i] * p->clk_freq)/rgb[i];
+ a1logd(p->log, 3, "%d sensor frequency = %f\n",i,rgb[i]);
+
+ /* If we're not calibrating the black */
+ if (cal == 0) {
+ rgb[i] -= p->reg103_F[i]; /* Subtract black level */
+ a1logd(p->log, 3, "%d after sub black = %f\n",i,rgb[i]);
+
+ if (rgb[i] < 0.0001)
+ rgb[i] = 0.0001;
+ a1logd(p->log, 3, "%d after limit min = %f\n",i,rgb[i]);
+ }
+ }
+ a1logd(p->log, 3, "Adjusted RGB = %f %f %f\n",rgb[0],rgb[1],rgb[2]);
+
+ return inst_ok;
+}
+
+/* . . . . . . . . . . . . . . . . . . . . . . . . */
+
+/* Take a fixed period frequency measurement from the device for an i1d2. */
+/* This measures edge count over the set integration period. */
+
+/* Take a raw measurement using a given integration time. */
+/* The measureent is the count of (both) edges from the L2V */
+/* over the integration time */
+static inst_code
+i1d2_freq_measure(
+ i1disp *p, /* Object */
+ double *inttime, /* Integration time in seconds. (Return clock rounded) */
+ double rgb[3] /* Return the RGB edge count values */
+) {
+ unsigned char ibuf[16];
+ unsigned char obuf[16];
+ int intclks;
+ int rsize;
+ inst_code ev;
+
+ if (*inttime > 20.0) /* Hmm */
+ *inttime = 20.0;
+
+ intclks = (int)(*inttime * p->iclk_freq + 0.5);
+ *inttime = (double)intclks / p->iclk_freq;
+ if (intclks != p->int_clocks) {
+ if ((ev = i1disp_wr_int_time(p, intclks)) != inst_ok)
+ return ev;
+ if ((ev = i1disp_rd_int_time(p, &intclks) ) != inst_ok)
+ return ev;
+ p->int_clocks = intclks;
+ *inttime = (double)p->int_clocks/p->iclk_freq;
+ }
+
+ /* Do the measurement, and return the Red value */
+ ibuf[0] = 1; /* Sync mode 1 */
+ if ((ev = i1disp_command(p, i1d_m_red_2, ibuf, 1,
+ obuf, 8, &rsize, p->inttime + 1.0)) != inst_ok)
+ return ev;
+ if (rsize != 5)
+ return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE);
+ rgb[0] = (double)buf2int(obuf);
+
+ /* Get the green value */
+ if ((ev = i1disp_command(p, i1d_rd_green, NULL, 0,
+ obuf, 8, &rsize, 0.5)) != inst_ok)
+ return ev;
+ if (rsize != 5)
+ return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE);
+ rgb[1] = (double)buf2int(obuf);
+
+ /* Get the blue value */
+ if ((ev = i1disp_command(p, i1d_rd_blue, NULL, 0,
+ obuf, 8, &rsize, 0.5)) != inst_ok)
+ return ev;
+ if (rsize != 5)
+ return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE);
+ rgb[2] = (double)buf2int(obuf);
+
+ return inst_ok;
+}
+
+/* Take a raw measurement that returns the number of clocks */
+/* between and initial edge and edgec[] subsequent edges of the L2F. */
+/* Both edges are counted. */
+static inst_code
+i1d2_period_measure(
+ i1disp *p, /* Object */
+ int edgec[3], /* Measurement edge count for each channel */
+ double rgb[3] /* Return the RGB clock count values */
+) {
+ int i;
+ unsigned char ibuf[16];
+ unsigned char obuf[16];
+ int rsize;
+ inst_code ev;
+
+ /* Set the edge count */
+ for (i = 0; i < 3; i++) {
+ short2buf(ibuf, edgec[i]); /* Edge count */
+ ibuf[2] = (unsigned char)i; /* Channel number */
+ if ((ev = i1disp_command(p, i1d_setmedges2, ibuf, 3,
+ obuf, 8, &rsize, 1.0)) != inst_ok)
+ return ev;
+ }
+
+ /* Do the measurement, and return the Red value */
+ if ((ev = i1disp_command(p, i1d_m_rgb_edge_2, ibuf, 0,
+ obuf, 8, &rsize, 120.0)) != inst_ok) {
+ return ev;
+ }
+ if (rsize != 5)
+ return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE);
+ rgb[0] = (double)buf2int(obuf);
+
+ /* Get the green value */
+ if ((ev = i1disp_command(p, i1d_rd_green, NULL, 0,
+ obuf, 8, &rsize, 0.5)) != inst_ok)
+ return ev;
+ if (rsize != 5)
+ return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE);
+ rgb[1] = (double)buf2int(obuf);
+
+ /* Get the blue value */
+ if ((ev = i1disp_command(p, i1d_rd_blue, NULL, 0,
+ obuf, 8, &rsize, 0.5)) != inst_ok)
+ return ev;
+ if (rsize != 5)
+ return i1disp_interp_code((inst *)p, I1DISP_UNEXPECTED_RET_SIZE);
+ rgb[2] = (double)buf2int(obuf);
+
+ return inst_ok;
+}
+
+#define ME 1 /* One edge initially (should this be 2 ?) */
+
+#ifndef NEVER
+
+/* ### Quick and precise for low levels, but subject to long */
+/* ### delays if the light level drops during measurement, and */
+/* ### may be less accurate at low levels due to dark noise */
+
+/* Take a cooked measurement from the device for the i1d2 */
+static inst_code
+i1d2_take_measurement(
+ i1disp *p, /* Object */
+ int refreshm, /* Measure in refresh mode flag */
+ double rgb[3] /* Return the rgb values */
+) {
+ int i;
+ double rmeas[3]; /* Raw measurement */
+ int edgec[3] = {ME,ME,ME}; /* Measurement edge count for each channel */
+ int cdgec[3] = {ME,ME,ME}; /* CRT computed edge count for re-measure */
+ int mask = 0x0; /* Period measure mask */
+ inst_code ev;
+
+ if (p->inited == 0)
+ return i1disp_interp_code((inst *)p, I1DISP_NOT_INITED);
+
+ if (p->dtype == 0)
+ return i1disp_interp_code((inst *)p, I1DISP_WRONG_DEVICE);
+
+ a1logd(p->log, 3, "i1d2_take_measurement called with refreshm = %d\n",refreshm);
+
+ /* Do refresh period measurement */
+ if (p->dtype == 1 && refreshm && p->rrset == 0) {
+ if ((ev = i1disp_do_fcal_setit(p)) != inst_ok)
+ return ev;
+
+ /* Quantize the sample time */
+ if (p->refperiod > 0.0) {
+ int n;
+ n = (int)ceil(p->dinttime/p->refperiod);
+ p->inttime = n * p->refperiod;
+ a1logd(p->log, 3, "i1disp: integration time quantize to %f secs\n",p->inttime);
+ } else {
+ p->inttime = p->dinttime;
+ a1logd(p->log, 3, "i1disp: integration time set to %f secs\n",p->inttime);
+ }
+ }
+
+// Do a frequency measurement first, to reduces chances of a light change causing delays.
+// This makes LCD same as CRT as far as quantization errors in high level readings.
+// if (refreshm) {
+ /* Do an initial fixed integration time frequency measurement. */
+ a1logd(p->log, 3, "Doing fixed period frequency measurement over %f secs\n",p->inttime);
+
+ if ((ev = i1d2_freq_measure(p, &p->inttime, rmeas)) != inst_ok)
+ return ev;
+
+ for (i = 0; i < 3; i++)
+ rgb[i] = p->rgbadj[i] * 0.5 * rmeas[i]/p->inttime;
+
+ a1logd(p->log, 3, "Got %f %f %f raw, %f %f %f Hz\n",
+ rmeas[0], rmeas[1], rmeas[2], rgb[0], rgb[1], rgb[2]);
+
+ /* Decide whether any channels need re-measuring, */
+ /* and computed cooked values. Threshold is a count of 75 */
+ for (i = 0; i < 3; i++) {
+ if (rmeas[i] <= 75.0) {
+ mask |= (1 << i); /* Yes */
+ if (rmeas[i] >= 10.0) { /* Compute target edges */
+ cdgec[i] = (int)(2.0 * rgb[i] * p->inttime + 0.5);
+ if (cdgec[i] > 2000)
+ cdgec[i] = 2000;
+ else if (cdgec[i] < ME)
+ cdgec[i] = ME;
+ }
+ }
+ }
+// } else {
+// mask = 0x7;
+// }
+ a1logd(p->log, 3, "Re-measure mask = 0x%x\n",mask);
+ a1logd(p->log, 3, "cdgec = %d %d %d\n",cdgec[0],cdgec[1],cdgec[2]);
+
+ /* If any need re-measuring */
+ if (mask != 0) {
+
+ /* See if we need to compute a target edge count */
+ for (i = 0; i < 3; i++) {
+ if ((mask & (1 << i)) && cdgec[i] == ME)
+ break;
+ }
+
+ /* Yes we do */
+ if (i < 3) {
+
+ a1logd(p->log, 3, "Doing 1st period pre-measurement mask 0x%x, edgec %d %d %d\n",
+ mask, edgec[0], edgec[1], edgec[2]);
+
+ /* Do an initial measurement of 1 edge to estimate the */
+ /* number of edges needed for the whole integration time. */
+ if ((ev = i1d2_period_measure(p, edgec, rmeas)) != inst_ok)
+ return ev;
+
+ a1logd(p->log, 3, "Got %f %f %f raw %f %f %f Hz\n",
+ rmeas[0], rmeas[1], rmeas[2],
+ (p->rgbadj[0] * 0.5 * (double)edgec[0] * p->clk_freq)/rmeas[0],
+ (p->rgbadj[1] * 0.5 * (double)edgec[1] * p->clk_freq)/rmeas[1],
+ (p->rgbadj[2] * 0.5 * (double)edgec[2] * p->clk_freq)/rmeas[2]);
+
+ /* Compute adjusted edge count for channels we're remeasuring, */
+ /* aiming for a values of int_clocks. */
+ for (i = 0; i < 3; i++) {
+ double ns;
+ if ((mask & (1 << i)) == 0)
+ continue;
+ if (p->int_clocks > ((2000.0 - 0.5) * rmeas[i]))
+ ns = 2000.0; /* Maximum edge count */
+ else {
+ ns = floor(p->inttime * edgec[i] * p->clk_freq/rmeas[i] + 0.5);
+ if (ns < ME) /* Minimum edge count */
+ ns = ME;
+ }
+ edgec[i] = (int)ns;
+ }
+ }
+
+ /* Use frequency computed edge count if available */
+ for (i = 0; i < 3; i++) {
+ if ((mask & (1 << i)) == 0)
+ continue;
+ if (cdgec[i] != ME)
+ edgec[i] = cdgec[i];
+ }
+
+ /* If we compute a different edge count, read again */
+ if (edgec[0] > ME || edgec[1] > ME || edgec[2] > ME) {
+ double rmeas2[3]; /* 2nd RGB Readings */
+
+ a1logd(p->log, 3, "Doing period re-measurement mask 0x%x, edgec %d %d %d\n",
+ mask, edgec[0], edgec[1], edgec[2]);
+
+ if ((ev = i1d2_period_measure(p, edgec, rmeas2)) != inst_ok)
+ return ev;
+
+ a1logd(p->log, 3, "Got %f %f %f raw %f %f %f Hz\n",
+ rmeas[0], rmeas[1], rmeas[2],
+ (p->rgbadj[0] * 0.5 * (double)edgec[0] * p->clk_freq)/rmeas2[0],
+ (p->rgbadj[1] * 0.5 * (double)edgec[1] * p->clk_freq)/rmeas2[1],
+ (p->rgbadj[2] * 0.5 * (double)edgec[2] * p->clk_freq)/rmeas2[2]);
+
+ a1logd(p->log, 3, "Int period %f %f %f secs\n",
+ rmeas2[0]/p->clk_freq, rmeas2[1]/p->clk_freq, rmeas2[2]/p->clk_freq);
+
+ /* Average readings if we repeated a measurement with the same count */
+ /* (Minor advantage, but may as well use it) */
+ for (i = 0; i < 3; i++) {
+ if (edgec[i] == ME)
+ rmeas[i] = 0.5 * (rmeas[i] + rmeas2[i]);
+ else
+ rmeas[i] = rmeas2[i];
+ }
+ }
+
+ /* Compute adjusted readings, ovewritting initial cooked values */
+ for (i = 0; i < 3; i++) {
+ if ((mask & (1 << i)) == 0)
+ continue;
+ rgb[i] = (p->rgbadj[i] * 0.5 * (double)edgec[i] * p->clk_freq)/rmeas[i];
+ a1logd(p->log, 3, "%d after scale = %f\n",i,rgb[i]);
+
+ rgb[i] -= p->reg103_F[i]; /* Subtract black level */
+ a1logd(p->log, 3, "%d after sub black = %f\n",i,rgb[i]);
+
+ if (rgb[i] < 0.0001)
+ rgb[i] = 0.0001;
+ a1logd(p->log, 3, "%d after limit min = %f\n",i,rgb[i]);
+ }
+ }
+
+ a1logd(p->log, 3, "Cooked RGB Hz = %f %f %f\n",rgb[0],rgb[1],rgb[2]);
+
+ return inst_ok;
+}
+
+#else
+/* Less precise (worse quatization errors), but more robust */
+/* against excessive delays if the light level drops during measurement. */
+/* Limits period measurement to an edge count < 35, but that can */
+/* still take a long time in the dark. */
+
+/* Take a cooked measurement from the device for the i1d2 */
+static inst_code
+i1d2_take_measurement(
+ i1disp *p, /* Object */
+ int refreshm, /* Measure in crt mode flag */
+ double rgb[3] /* Return the rgb values */
+) {
+ int i;
+ double rmeas[3]; /* Raw measurement */
+ int edgec[3] = {ME,ME,ME}; /* Measurement edge count for each channel */
+ int cdgec[3] = {ME,ME,ME}; /* CRT computed edge count for re-measure */
+ int fmask = 0x0; /* Freq re-measure mask */
+ int mask = 0x0; /* Period measure mask */
+ inst_code ev;
+
+ if (p->inited == 0)
+ return i1disp_interp_code((inst *)p, I1DISP_NOT_INITED);
+
+ if (p->dtype == 0)
+ return i1disp_interp_code((inst *)p, I1DISP_WRONG_DEVICE);
+
+ a1logd(p->log, 3, "i1d2_take_measurement called with refreshm = %d\n",refreshm);
+
+ /* Do refresh period measurement */
+ if (p->dtype == 1 && refreshm && p->rrset == 0) {
+ if ((ev = i1disp_do_fcal_setit(p)) != inst_ok)
+ return ev;
+
+ /* Quantize the sample time */
+ if (p->refperiod > 0.0) {
+ int n;
+ n = (int)ceil(p->dinttime/p->refperiod);
+ p->inttime = n * p->refperiod;
+ a1logd(p->log, 3, "i1disp: integration time quantize to %f secs\n",p->inttime);
+ } else {
+ p->inttime = p->dinttime;
+ a1logd(p->log, 3, "i1disp: ntegration time set to %f secs\n",p->inttime);
+ }
+ }
+
+ /* Do an initial fixed integration time frequency measurement. */
+ a1logd(p->log, 3, "Doing fixed period frequency measurement over %f secs\n",p->inttime)
+
+ if ((ev = i1d2_freq_measure(p, &p->inttime, rmeas)) != inst_ok)
+ return ev;
+
+ for (i = 0; i < 3; i++)
+ rgb[i] = p->rgbadj[i] * 0.5 * rmeas[i]/p->inttime;
+
+ a1logd(p->log, 3, "Got %f %f %f raw, %f %f %f Hz\n",
+ rmeas[0], rmeas[1], rmeas[2], rgb[0], rgb[1], rgb[2]);
+
+ /* Decide whether any channels need re-measuring. */
+ /* Threshold is a count of 75, and switch to period */
+ /* measurement mode on count less than 37. */
+ fmask = 0x0;
+ for (i = 0; i < 3; i++) {
+
+ if (rmeas[i] <= 75.0) {
+ fmask |= (1 << i); /* Yes, do another freq re-measure */
+
+ if (rmeas[i] <= 37.5) {
+ mask |= (1 << i); /* Do a period re-measure */
+ fmask = 0; /* Don't bother with freq re-measure */
+ }
+ if (rmeas[i] >= 10.0) { /* Compute target edges */
+ cdgec[i] = (int)(2.0 * rgb[i] * p->inttime + 0.5);
+ if (cdgec[i] > 2000)
+ cdgec[i] = 2000;
+ else if (cdgec[i] < ME)
+ cdgec[i] = ME;
+ }
+ }
+ }
+ a1logd(p->log, 3, "Freq mask = 0x%x, Period mask 0x%x\n",fmask, mask);
+ a1logd(p->log, 3, "cdgec = %d %d %d\n",cdgec[0],cdgec[1],cdgec[2]);
+
+ /* If there is a frequency re-measure */
+ /* ** This doesn't actually work. The quantization error */
+ /* for each read is 0.5, but averaging 2 reads it drops */
+ /* to 0.354, not the desired 0.25 that would have been */
+ /* acheived with double the integration time. ** */
+ if (fmask != 0) {
+ a1logd(p->log, 3, "Doing frequency re-measurement over %f secs\n",p->inttime);
+ if ((ev = i1d2_freq_measure(p, &p->inttime, rmeas)) != inst_ok)
+ return ev;
+
+ for (i = 0; i < 3; i++) {
+ rgb[i] += p->rgbadj[i] * 0.5 * rmeas[i]/p->inttime;
+ rgb[i] /= 2.0;
+ }
+
+ a1logd(p->log, 3, "Got %f %f %f raw, %f %f %f Avg. Hz\n",
+ rmeas[0], rmeas[1], rmeas[2], rgb[0], rgb[1], rgb[2]);
+
+ /* If there is a period re-measure */
+ } else if (mask != 0) {
+
+ /* See if we need to compute a target edge count */
+ for (i = 0; i < 3; i++) {
+ if ((mask & (1 << i)) && cdgec[i] == ME)
+ break;
+ }
+
+ /* Yes we do */
+ if (i < 3) {
+
+ a1logd(p->log, 3, "Doing 1st period pre-measurement mask 0x%x, edgec %d %d %d\n",
+ mask, edgec[0], edgec[1], edgec[2]);
+
+ /* Do an initial measurement of 1 edge to estimate the */
+ /* number of edges needed for the whole integration time. */
+ if ((ev = i1d2_period_measure(p, edgec, rmeas)) != inst_ok)
+ return ev;
+
+ a1logd(p->log, 3, "Got %f %f %f raw %f %f %f Hz\n",
+ rmeas[0], rmeas[1], rmeas[2],
+ (p->rgbadj[0] * 0.5 * (double)edgec[0] * p->clk_freq)/rmeas[0],
+ (p->rgbadj[1] * 0.5 * (double)edgec[1] * p->clk_freq)/rmeas[1],
+ (p->rgbadj[2] * 0.5 * (double)edgec[2] * p->clk_freq)/rmeas[2]);
+
+ /* Compute adjusted edge count for channels we're remeasuring, */
+ /* aiming for a values of int_clocks. */
+ for (i = 0; i < 3; i++) {
+ double ns;
+ if ((mask & (1 << i)) == 0)
+ continue;
+ if (p->int_clocks > ((2000.0 - 0.5) * rmeas[i]))
+ ns = 2000.0; /* Maximum edge count */
+ else {
+ ns = floor(p->inttime * edgec[i] * p->clk_freq/rmeas[i] + 0.5);
+ if (ns < ME) /* Minimum edge count */
+ ns = ME;
+ }
+ edgec[i] = (int)ns;
+
+ /* Sanity check cdgec value, in case light level has changed */
+ if ((edgec[i] * 3) < (cdgec[i] * 2)) {
+ cdgec[i] = edgec[i];
+ }
+ }
+ }
+
+ /* Use frequency computed edge count if available */
+ for (i = 0; i < 3; i++) {
+ if ((mask & (1 << i)) == 0)
+ continue;
+ if (cdgec[i] != ME)
+ edgec[i] = cdgec[i];
+ }
+
+ /* If we compute a different edge count, read again */
+ if (edgec[0] > ME || edgec[1] > ME || edgec[2] > ME) {
+ double rmeas2[3]; /* 2nd RGB Readings */
+
+ a1logd(p->log, 3, "Doing period re-measurement mask 0x%x, edgec %d %d %d\n",
+ mask, edgec[0], edgec[1], edgec[2]);
+
+ if ((ev = i1d2_period_measure(p, edgec, rmeas2)) != inst_ok)
+ return ev;
+
+ a1logd(p->log, 3, "Got %f %f %f raw %f %f %f Hz\n",
+ rmeas[0], rmeas[1], rmeas[2],
+ (p->rgbadj[0] * 0.5 * (double)edgec[0] * p->clk_freq)/rmeas2[0],
+ (p->rgbadj[1] * 0.5 * (double)edgec[1] * p->clk_freq)/rmeas2[1],
+ (p->rgbadj[2] * 0.5 * (double)edgec[2] * p->clk_freq)/rmeas2[2]);
+
+ a1logd(p->log, 3, "Int period %f %f %f secs\n",
+ rmeas2[0]/p->clk_freq, rmeas2[1]/p->clk_freq, rmeas2[2]/p->clk_freq);
+
+ /* Average readings if we repeated a measurement with the same count */
+ /* (Minor advantage, but may as well use it) */
+ for (i = 0; i < 3; i++) {
+ if (edgec[i] == ME)
+ rmeas[i] = 0.5 * (rmeas[i] + rmeas2[i]);
+ else
+ rmeas[i] = rmeas2[i];
+ }
+ }
+
+ /* Compute adjusted readings, ovewritting initial cooked values */
+ for (i = 0; i < 3; i++) {
+ if ((mask & (1 << i)) == 0)
+ continue;
+ rgb[i] = (p->rgbadj[i] * 0.5 * (double)edgec[i] * p->clk_freq)/rmeas[i];
+ a1logd(p->log, 3, "%d after scale = %f\n",i,rgb[i]);
+
+ rgb[i] -= p->reg103_F[i]; /* Subtract black level */
+ a1logd(p->log, 3, "%d after sub black = %f\n",i,rgb[i]);
+
+ if (rgb[i] < 0.0001)
+ rgb[i] = 0.0001;
+ a1logd(p->log, 3, "%d after limit min = %f\n",i,rgb[i]);
+ }
+ }
+
+ a1logd(p->log, 3, "Cooked RGB Hz = %f %f %f\n",rgb[0],rgb[1],rgb[2]);
+
+ return inst_ok;
+}
+#endif
+#undef ME
+
+/* . . . . . . . . . . . . . . . . . . . . . . . . */
+
+/* Take a XYZ measurement from the device */
+static inst_code
+i1disp_take_XYZ_measurement(
+ i1disp *p, /* Object */
+ double XYZ[3] /* Return the XYZ values */
+) {
+ int i, j;
+ double rgb[3]; /* RGB Readings */
+ inst_code ev;
+ double *mat; /* Pointer to matrix */
+
+ if (p->dtype == 0) { /* i1 disp 1 */
+ if ((ev = i1d1_take_measurement(p, 0, rgb)) != inst_ok)
+ return ev;
+ } else { /* i1 disp 2 or ColorMunki Smile */
+ int refreshm = 0; /* Assume non-refresh mode */
+
+ if (p->refrmode && !IMODETST(p->mode, inst_mode_emis_ambient)) {
+ refreshm = 1;
+ }
+
+ if ((ev = i1d2_take_measurement(p, refreshm, rgb)) != inst_ok)
+ return ev;
+ }
+
+ /* Multiply by calibration matrix to arrive at XYZ */
+ if (IMODETST(p->mode, inst_mode_emis_ambient)) {
+ mat = p->amb; /* Ambient matrix */
+ } else {
+ if (p->icx)
+ mat = p->reg54_F; /* CRT/factory matrix/CCFL */
+ else
+ mat = p->reg4_F; /* LCD/user matrix/LED */
+ }
+ for (i = 0; i < 3; i++) {
+ XYZ[i] = 0.0;
+ for (j = 0; j < 3; j++) {
+ XYZ[i] += mat[i * 3 + j] * rgb[j];
+ }
+ XYZ[i] *= CALFACTOR; /* Times magic scale factor */
+
+#ifdef NEVER
+ if (p->chroma4)
+ XYZ[i] *= 4.0/3.0; /* (Not sure about this factor!) */
+#endif
+ }
+
+ if (!IMODETST(p->mode, inst_mode_emis_ambient)) {
+
+ /* Apply the colorimeter correction matrix */
+ icmMulBy3x3(XYZ, p->ccmat, XYZ);
+ }
+
+ a1logd(p->log, 3, "returning XYZ = %f %f %f\n",XYZ[0],XYZ[1],XYZ[2]);
+ return inst_ok;
+}
+
+/* Do a black calibration (i1display 1) */
+static inst_code
+i1disp_do_black_cal(
+ i1disp *p /* Object */
+) {
+ int i;
+ double rgb1[3], rgb2[3]; /* RGB Readings */
+ inst_code ev;
+
+ if (p->dtype != 0)
+ return i1disp_interp_code((inst *)p, I1DISP_CANT_BLACK_CALIB);
+
+ /* Do a couple of readings without subtracting the black */
+ if ((ev = i1d1_take_measurement(p, 1, rgb1)) != inst_ok)
+ return ev;
+ if ((ev = i1d1_take_measurement(p, 1, rgb2)) != inst_ok)
+ return ev;
+
+ /* Average the results */
+ for (i = 0; i < 3; i++) {
+ rgb1[i] = 0.5 * (rgb1[i] + rgb2[i]);
+
+ /* Since the readings are clamped to 0.0001, */
+ /* aim for a black level of 0.0001 */
+ rgb1[i] -= 0.0001;
+ }
+
+ a1logd(p->log, 3, "Black rgb = %f %f %f\n",rgb1[0],rgb1[1],rgb1[2]);
+
+ /* Save it to the EEPROM */
+ for (i = 0; i < 3; i++) {
+ if ((ev = i1disp_wrreg_float(p, rgb1[i], 103 + 4 * i)) != inst_ok)
+ return ev;
+ p->reg103_F[i] = rgb1[i];
+ }
+ return inst_ok;
+}
+
+/* Measure the refresh rate */
+static inst_code
+i1disp_read_refrate(
+ inst *pp,
+ double *ref_rate /* Return value, 0.0 if fails */
+) {
+ i1disp *p = (i1disp *)pp;
+ int i;
+ inst_code ev;
+ double measp = 0.0;
+
+ a1logd(p->log, 3, "Frequency calibration called\n");
+
+ if (p->dtype != 1)
+ return inst_unsupported;
+
+ /* Average a few refresh period readings */
+ for (i = 0; i < p->nmeasprds; i++) {
+ int mp;
+
+ /* Measures period in clocks */
+ if ((ev = i1disp_rd_meas_ref_period(p, &mp)) != inst_ok)
+ return ev;
+ if (mp == 0) {
+ measp = 0.0;
+ break; /* Too dark to measure */
+ }
+ measp += (double)mp;
+ }
+
+ /* Compute the measurement frequency */
+ if (measp != 0.0) {
+ double rrate = (p->clk_freq * (double)p->nmeasprds)/measp;
+ a1logd(p->log, 3, "Sample frequency measured = %f\n",rrate);
+ if (ref_rate != NULL)
+ *ref_rate = rrate;
+ return inst_ok;
+ } else {
+ a1logd(p->log, 3, "No discernable refresh frequency measured\n");
+ if (ref_rate != NULL)
+ *ref_rate = 0.0;
+ return inst_misread;
+ }
+}
+
+/* Do a refresh period cailbration */
+static inst_code
+i1disp_do_fcal_setit(
+ i1disp *p /* Object */
+) {
+ int i;
+ inst_code ev;
+
+ a1logd(p->log, 3, "Frequency calibration called\n");
+
+ if (p->dtype != 1)
+ return i1disp_interp_code((inst *)p, I1DISP_CANT_MEASP_CALIB);
+
+ if ((ev = i1disp_read_refrate((inst *)p, &p->refrate)) != inst_ok
+ && ev != inst_misread)
+ return ev;
+
+ if (p->refrate != 0.0) {
+ p->refperiod = 1.0/p->refrate;
+ p->refrvalid = 1;
+ } else {
+ p->refrvalid = 0;
+ }
+ p->rrset = 1;
+
+ return inst_ok;
+}
+
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Check the device is responding, and unlock if necessary */
+static inst_code
+i1disp_check_unlock(
+ i1disp *p /* Object */
+) {
+ unsigned char buf[16];
+ int rsize;
+ inst_code ev;
+ int i, vv;
+ double ver;
+
+ struct {
+ unsigned char code[4];
+ int *flag;
+ } codes[] = {
+ { { 'G','r','M','b' }, NULL }, /* "GrMb" i1 Display */
+ { { 'L','i','t','e' }, &p->lite }, /* "Lite" i1 Display LT */
+ { { 'M','u','n','k' }, &p->munki }, /* "Munk" ColorMunki Create */
+ { { 'O','b','i','W' }, &p->hpdream }, /* "ObiW" HP DreamColor */
+ { { 'O','b','i','w' }, &p->hpdream }, /* "Obiw" HP DreamColor */
+ { { 'C','M','X','2' }, &p->calmanx2 }, /* "CMX2" Calman X2 */
+ { { 0x24,0xb6,0xb5,0x13 }, NULL }, /* ColorMunki Smile */
+ { { 'R','G','B','c' }, NULL }, /* */
+ { { 'C','E','C','5' }, NULL }, /* */
+ { { 'C','M','C','5' }, NULL }, /* */
+ { { 'C','M','G','5' }, NULL }, /* */
+ { { 0x00,0x00,0x01,0x00 }, NULL }, /* */
+ { { 0x09,0x0b,0x0c,0x0d }, NULL }, /* */
+ { { 0x0e,0x0e,0x0e,0x0e }, NULL }, /* */
+ { { 0x11,0x02,0xde,0xf0 }, NULL }, /* Barco Chroma 5 ? */
+ { { ' ',' ',' ',' ' }, (int *)-1 }
+ };
+
+ a1logd(p->log, 3, "i1disp: about to check response and unlock instrument if needed\n");
+
+ /* Get status */
+ if ((ev = i1disp_command_1(p, i1d_status, NULL, 0,
+ buf, 8, &rsize, 0.5)) != inst_ok && (ev & inst_imask) != I1DISP_LOCKED)
+ return ev; /* An error other than being locked */
+
+ /* Try and unlock it if it is locked */
+ if ((ev & inst_imask) == I1DISP_LOCKED) {
+
+ /* Reset any flags */
+ for (i = 0; ;i++) {
+ if (codes[i].flag == (int *)-1)
+ break;
+ if (codes[i].flag != NULL)
+ *codes[i].flag = 0;
+ }
+
+ /* Try each code in turn */
+ for (i = 0; ;i++) {
+ if (codes[i].flag == (int *)-1) {
+ a1logd(p->log, 3, "Failed to find correct unlock code\n");
+ return i1disp_interp_code((inst *)p, I1DISP_UNKNOWN_MODEL);
+ }
+
+ /* Try unlock code. Ignore I1DISP_NOT_READY status. */
+ if (((ev = i1disp_command_1(p, i1d_unlock, codes[i].code, 4,
+ buf, 8, &rsize, 0.5)) & inst_mask) != inst_ok
+ && (ev & inst_imask) != I1DISP_LOCKED)
+ return ev; /* Some other sort of failure */
+
+ /* Get status */
+ if ((ev = i1disp_command_1(p, i1d_status, NULL, 0,
+ buf, 8, &rsize, 0.5)) != inst_ok
+ && (ev & inst_imask) != I1DISP_LOCKED)
+ return ev; /* An error other than being locked */
+
+ if (ev == inst_ok) { /* Correct code */
+ if (codes[i].flag != NULL)
+ *codes[i].flag = 1;
+ break;
+ }
+ }
+ }
+
+ if (rsize != 5 || !isdigit(buf[0]) || buf[1] != '.'
+ || !isdigit(buf[2]) || !isdigit(buf[3])) {
+ return i1disp_interp_code((inst *)p, I1DISP_BAD_STATUS);
+ }
+
+ buf[4] = '\000';
+ ver = atof((char *)buf);
+ a1logd(p->log, 3, "Version string = %5.3f\n",ver);
+
+ /* Read register 0x79 for the model identifier */
+ if ((ev = i1disp_rdreg_byte(p, &vv, 121) ) != inst_ok) {
+ return ev;
+ }
+ vv &= 0xff;
+
+ a1logd(p->log, 3, "Version character = 0x%02x = '%c'\n",vv,vv);
+
+ /* Sequel Chroma 4 with vv == 0xff ? */
+ /* Barco Chroma 5 with ver = 5.01 and vv = '5' */
+ if (ver >= 4.0 && ver < 5.1
+ && (vv == 0xff || vv == 0x35)) {
+ p->dtype = 0; /* Sequel Chroma 4 ?? */
+ p->chroma4 = 1; /* Treat like an Eye-One Display 1 */
+ /* !!! Not fully tested !!! */
+ } else if (ver >= 5.1 && ver <= 5.3 && vv == 'L') {
+ p->dtype = 0; /* Eye-One Display 1 */
+ } else if (ver >= 6.0 && ver <= 6.29 && vv == 'L') {
+ p->dtype = 1; /* Eye-One Display 2 */
+
+ } else if (ver >= 6.0 && ver <= 6.29 && vv == 'M') {
+ /* ColorMunki Create ? */
+ /* ColorMunki Smile */
+ if (p->dtype == 0) /* Not sure if this is set by Create */
+ p->dtype = 1;
+ } else {
+ /* Reject any version or model we don't know about */
+ a1logv(p->log, 1, "Version string = %3.1f\nID character = 0x%02x = '%c'\n",ver,vv,vv);
+ return i1disp_interp_code((inst *)p, I1DISP_UNKNOWN_VERS_ID);
+ }
+
+ a1logd(p->log, 2, "i1disp: instrument is responding, unlocked, and right type\n");
+
+ return inst_ok;
+}
+
+/* Read all the relevant register values */
+static inst_code
+i1disp_read_all_regs(
+ i1disp *p /* Object */
+) {
+ inst_code ev;
+ int i;
+
+ a1logd(p->log, 3, "i1disp: about to read all the registers\n");
+
+ /* Serial number */
+ if ((ev = i1disp_rdreg_word(p, &p->reg0_W, 0) ) != inst_ok)
+ return ev;
+ a1logd(p->log, 3, "serial number = %d\n",p->reg0_W);
+
+ /* LCD/user calibration values */
+ for (i = 0; i < 9; i++) {
+ if ((ev = i1disp_rdreg_float(p, &p->reg4_F[i], 4 + 4 * i) ) != inst_ok)
+ return ev;
+ a1logd(p->log, 3, "LCD/user cal[%d] = %f\n",i,p->reg4_F[i]);
+ }
+ /* LCD/user calibration time */
+ if ((ev = i1disp_rdreg_word(p, &p->reg50_W, 50) ) != inst_ok)
+ return ev;
+ a1logd(p->log, 3, "LCD/user calibration time = 0x%x = %s\n",p->reg50_W, ctime_32(&p->reg50_W));
+
+ /* LCD/user calibration flag */
+ if ((ev = i1disp_rdreg_short(p, &p->reg126_S, 126) ) != inst_ok)
+ return ev;
+ a1logd(p->log, 3, "User cal flag = 0x%x\n",p->reg126_S);
+
+
+ /* CRT/factory calibration values */
+ for (i = 0; i < 9; i++) {
+ if ((ev = i1disp_rdreg_float(p, &p->reg54_F[i], 54 + 4 * i) ) != inst_ok)
+ return ev;
+ a1logd(p->log, 3, "CRT/factory cal[%d] = %f\n",i,p->reg54_F[i]);
+ }
+ /* CRT/factory calibration flag */
+ if ((ev = i1disp_rdreg_word(p, &p->reg90_W, 90) ) != inst_ok)
+ return ev;
+ a1logd(p->log, 3, "CRT/factory flag = 0x%x = %s\n",p->reg90_W, ctime_32(&p->reg90_W));
+
+
+ /* Integration clock period in nsec */
+ if ((ev = i1disp_rdreg_short(p, &p->reg40_S, 40) ) != inst_ok)
+ return ev;
+ a1logd(p->log, 3, "Reg40 = %d\n",p->reg40_S);
+
+ /* Calibration factor */
+ if ((ev = i1disp_rdreg_short(p, &p->reg42_S, 42) ) != inst_ok)
+ return ev;
+ a1logd(p->log, 3, "Reg42 = %d\n",p->reg42_S);
+
+ /* Calibration factors */
+ for (i = 0; i < 3; i++) {
+ if ((ev = i1disp_rdreg_short(p, &p->reg44_S[i], 44 + 2 * i) ) != inst_ok)
+ return ev;
+ a1logd(p->log, 3, "reg44[%d] = %d\n",i,p->reg44_S[i]);
+ }
+
+ /* Measurement/master clock period */
+ if ((ev = i1disp_rdreg_float(p, &p->clk_prd, 94) ) != inst_ok)
+ return ev;
+ a1logd(p->log, 3, "Master clock Frequency = %e\n",1/p->clk_prd);
+
+ /* unknown */
+ if ((ev = i1disp_rdreg_word(p, &p->reg98_W, 98) ) != inst_ok)
+ return ev;
+ a1logd(p->log, 3, "reg98 = 0x%x = %s\n",p->reg98_W,ctime_32(&p->reg98_W));
+
+ /* unknown */
+ if ((ev = i1disp_rdreg_byte(p, &p->reg102_B, 102) ) != inst_ok)
+ return ev;
+ a1logd(p->log, 3, "reg102 = 0x%x\n",p->reg102_B);
+
+ /* Dark current calibration values */
+ /* Should we set to a default 0.0 if reg126_S < 0xd ?? */
+ for (i = 0; i < 3; i++) {
+ if ((ev = i1disp_rdreg_float(p, &p->reg103_F[i], 103 + 4 * i)) != inst_ok) {
+ if ((ev & inst_imask) != I1DISP_FLOAT_NOT_SET)
+ return ev;
+ p->reg103_F[i] = 0.0;
+ }
+ a1logd(p->log, 3, "darkcal[%d] = %f\n",i,p->reg103_F[i]);
+ }
+
+ /* Unknown byte */
+ if ((ev = i1disp_rdreg_byte(p, &p->reg115_B, 115) ) != inst_ok)
+ return ev;
+ a1logd(p->log, 3, "Unknown 115 byte = 0x%x\n",p->reg115_B);
+
+ /* device ID byte */
+ if ((ev = i1disp_rdreg_byte(p, &p->reg121_B, 121) ) != inst_ok)
+ return ev;
+ a1logd(p->log, 3, "device type byte = 0x%x\n",p->reg121_B);
+
+ /* Unlock string */
+ for (i = 0; i < 4; i++) {
+ int vv;
+ if ((ev = i1disp_rdreg_byte(p, &vv, 122 + i) ) != inst_ok)
+ return ev;
+ p->reg122_B[i] = (char)vv;
+ }
+ p->reg122_B[i] = '\000';
+ a1logd(p->log, 3, "unlock string = '%s'\n",p->reg122_B);
+
+ p->serno[0] = '\000';
+
+ /* Read extra registers */
+ if (p->dtype == 1) {
+
+#ifdef NEVER /* Not used, so don't bother */
+ for (i = 0; i < 3; i++) {
+ if ((ev = i1disp_rdreg_float(p, &p->reg128_F[i], 128 + 4 * i) ) != inst_ok)
+ return ev;
+ a1logd(p->log, 3, "reg128_F[%d] = %f\n",i,p->reg128_F[i]);
+ }
+#endif /* NEVER */
+
+ for (i = 0; i < 3; i++) {
+ if ((ev = i1disp_rdreg_float(p, &p->reg144_F[i], 144 + 4 * i) ) != inst_ok) {
+ if ((ev & inst_imask) != I1DISP_FLOAT_NOT_SET)
+ return ev;
+ p->reg144_F[i] = 1.0;
+ }
+ a1logd(p->log, 3, "Ambient scale factor [%d] = %f\n",i,p->reg144_F[i]);
+ }
+
+ /* Read the integration time */
+ if ((ev = i1disp_rd_int_time(p, &p->int_clocks) ) != inst_ok)
+ return ev;
+ a1logd(p->log, 3, "Integration time = %d\n",p->int_clocks);
+
+ /* ColorMunki Smile extra information */
+ /* (Colormunki Create too ????) */
+ } else if (p->dtype == 2) {
+ int i, v;
+
+ /* Smile doesn't have ambient - reg144 seems to contain LCD cal type, */
+ /* ie. "CCFLWLED" */
+
+ /* Serial Number */
+ if ((ev = i1disp_rdexreg_bytes(p, (unsigned char *)p->serno, 0x104, 20)) != inst_ok)
+ return ev;
+ p->serno[19] = '\000';
+ }
+ a1logd(p->log, 2, "i1disp: all registers read OK\n");
+
+ return inst_ok;
+}
+
+/* Compute factors that depend on the register values */
+static inst_code
+i1disp_compute_factors(
+ i1disp *p /* Object */
+) {
+ int i;
+
+ /* Check that certain value are valid */
+ if (p->reg0_W == 0xffffffff)
+ return i1disp_interp_code((inst *)p, I1DISP_BAD_SERIAL_NUMBER);
+
+ /* LCD calibration date valid ? */
+ if (p->reg50_W == 0xffffffff)
+ return i1disp_interp_code((inst *)p, I1DISP_BAD_LCD_CALIBRATION);
+
+ /* The value stored in reg126_S ("user cal flag") seems hard to interpret. */
+ /* For the i1display 1&2, it has a value of 0x0d. */
+ /* Value 0x07 seems to be for a "user calibration" */
+ /* Values 3 & 6 seem to always "errors" as does a value */
+ /* < 7 in most circumstances. But the Heidelberg Viewmaker */
+ /* (from Sequel Imaging) and Lacie Blue Eye colorimeter seems to have a value of 2. */
+ /* The Barco sensor seems to have a value of 0x20 */
+ /* The ColorMunki Smile has a value of 0x21 */
+ if (p->reg126_S == 0xffffffff || (p->reg126_S < 7 && p->reg126_S != 2))
+ return i1disp_interp_code((inst *)p, I1DISP_BAD_LCD_CALIBRATION);
+
+ /* Not quite sure about this, but we're assuming this */
+ /* is set to 2 or 0xd if reg4-36 hold the LCD calibration, */
+ /* and some other number if they are not set, or set */
+ /* to a custom user calibration. */
+ if (p->reg126_S != 0x02
+ && p->reg126_S != 0x0d
+ && p->reg126_S != 0x20
+ && p->reg126_S != 0x21)
+ return i1disp_interp_code((inst *)p, I1DISP_BAD_LCD_CALIBRATION);
+
+ if (p->reg90_W == 0xffffffff)
+ return i1disp_interp_code((inst *)p, I1DISP_BAD_CRT_CALIBRATION);
+
+ /* Compute ambient matrix */
+ for (i = 0; i < 9; i++)
+ p->amb[i] = p->reg144_F[i % 3] * 0.5 * (p->reg4_F[i] + p->reg54_F[i]);
+
+ /* Integration clock frequency */
+ p->iclk_freq = 1.0/(p->reg40_S * 1e-9);
+
+ /* Master/Measurement clock frequency */
+ p->clk_freq = 1.0/p->clk_prd;
+
+ /* RGB channel calibration factors */
+ for (i = 0; i < 3; i++) {
+
+ /* Individual channel calibration factors, typically 1.0 */
+ p->rgbadj[i] = (double)p->reg44_S[i] * 100.0/(double)p->reg42_S;
+ a1logd(p->log, 3, "reg44+%dcalc2 = %f\n",i,p->rgbadj[i]);
+ }
+
+ /* Set some defaults */
+ p->nmeasprds = 5; /* Number of disp refresh period measurments to average */
+ /* in doing frequency calibration */
+ p->dinttime = 1.0; /* 1.0 second integration time default */
+ p->inttime = p->dinttime; /* Current integration time */
+
+ return inst_ok;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* Establish communications with a I1DISP */
+/* If it's a serial port, use the baud rate given, and timeout in to secs */
+/* Return DTP_COMS_FAIL on failure to establish communications */
+static inst_code
+i1disp_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ i1disp *p = (i1disp *) pp;
+ unsigned char buf[16];
+ int rsize;
+ int se;
+ inst_code ev = inst_ok;
+
+ a1logd(p->log, 2, "i1disp: About to init coms\n");
+
+ if (p->icom->port_type(p->icom) != icomt_usb) {
+ a1logd(p->log, 1, "i1disp_init_coms: coms is not the right type!\n");
+ return i1disp_interp_code((inst *)p, I1DISP_UNKNOWN_MODEL);
+ }
+
+ /* Set config, interface, write end point, read end point */
+ /* ("serial" end points aren't used - the i1display uses USB control messages) */
+
+ /* Set config, interface, write end point, read end point, read quanta */
+ if ((se = p->icom->set_usb_port(p->icom, 1, 0x00, 0x00, icomuf_none, 0, NULL)) != ICOM_OK) {
+ a1logd(p->log, 1, "i1disp_init_coms: set_usbe_port failed ICOM err 0x%x\n",se);
+ return i1disp_interp_code((inst *)p, icoms2i1disp_err(se));
+ }
+
+ /* Check instrument is responding */
+ if ((ev = i1disp_command_1(p, i1d_status, NULL, 0, buf, 8, &rsize, 0.5)) != inst_ok
+ && (ev & inst_imask) != I1DISP_LOCKED) {
+ a1logd(p->log, 1, "i1disp_init_coms: failed with rv = 0x%x\n",ev);
+ return ev;
+ }
+
+ a1logd(p->log, 2, "i1disp: init coms OK\n");
+
+ p->gotcoms = 1;
+ return inst_ok;
+}
+
+static inst_code set_default_disp_type(i1disp *p);
+
+/* Initialise the I1DISP */
+/* return non-zero on an error, with dtp error code */
+static inst_code
+i1disp_init_inst(inst *pp) {
+ i1disp *p = (i1disp *)pp;
+ inst_code ev = inst_ok;
+
+ a1logd(p->log, 2, "i1disp_init_inst: called\n");
+
+ if (p->gotcoms == 0)
+ return i1disp_interp_code((inst *)p, I1DISP_NO_COMS); /* Must establish coms first */
+
+ /* Check instrument is responding, and right type */
+ if ((ev = i1disp_check_unlock(p)) != inst_ok)
+ return ev;
+
+ /* Read all the registers and store their contents */
+ if ((ev = i1disp_read_all_regs(p)) != inst_ok)
+ return ev;
+
+#ifdef NEVER
+ /* Dump all the register space */
+ if (p->dtype < 2) {
+ unsigned char buf[0x200];
+ int i, len;
+
+ len = 128;
+ if (p->dtype != 0)
+ len = 160;
+
+ for (i = 0; i < len; i++) {
+ int v;
+ if ((ev = i1disp_rdreg_byte(p, &v, i)) != inst_ok) {
+ return ev;
+ }
+ buf[i] = v;
+ }
+ dump_bytes(p->log, "dump:", buf, 0, len);
+
+ /* Dump ColorMunki Smile extended range */
+ /* Main difference is Ascii serial number + other minor unknown */
+ } else {
+ unsigned char buf[0x200];
+
+
+ if ((ev = i1disp_rdexreg_bytes(p, buf, 0, 0x200)) != inst_ok) {
+ return ev;
+ }
+ dump_bytes(p->log, "dump:", buf, 0, 0x200);
+ }
+#endif /* NEVER */
+
+ if ((ev = i1disp_compute_factors(p)) != inst_ok)
+ return ev;
+
+ p->trig = inst_opt_trig_user;
+
+ /* Set a default calibration */
+ if ((ev = set_default_disp_type(p)) != inst_ok) {
+ return ev;
+ }
+
+ p->inited = 1;
+ a1logd(p->log, 2, "i1disp_init_inst: inited OK\n");
+
+ return inst_ok;
+}
+
+/* Read a single sample */
+/* Return the dtp error code */
+static inst_code
+i1disp_read_sample(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+ipatch *val, /* Pointer to instrument patch value */
+instClamping clamp) { /* NZ if clamp XYZ/Lab to be +ve */
+ i1disp *p = (i1disp *)pp;
+ int user_trig = 0;
+ int rv = inst_protocol_error;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->trig == inst_opt_trig_user) {
+
+ if (p->uicallback == NULL) {
+ a1logd(p->log, 1, "i1disp: inst_opt_trig_user but no uicallback function set!\n");
+ return inst_unsupported;
+ }
+
+ for (;;) {
+ if ((rv = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (rv == inst_user_abort)
+ return rv; /* Abort */
+ if (rv == inst_user_trig) {
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ msec_sleep(200);
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ /* Progromatic Trigger */
+ } else {
+ /* Check for abort */
+ if (p->uicallback != NULL
+ && (rv = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_abort)
+ return rv; /* Abort */
+ }
+
+ /* Read the XYZ value */
+ if ((rv = i1disp_take_XYZ_measurement(p, val->XYZ)) != inst_ok)
+ return rv;
+ /* This may not change anything since instrument may clamp */
+ if (clamp)
+ icmClamp3(val->XYZ, val->XYZ);
+ val->loc[0] = '\000';
+ if (IMODETST(p->mode, inst_mode_emis_ambient))
+ val->mtype = inst_mrt_ambient;
+ else
+ val->mtype = inst_mrt_emission;
+ val->XYZ_v = 1; /* These are absolute XYZ readings ? */
+ val->sp.spec_n = 0;
+ val->duration = 0.0;
+
+ if (user_trig)
+ return inst_user_trig;
+ return rv;
+}
+
+/* Insert a colorimetric correction matrix in the instrument XYZ readings */
+/* This is only valid for colorimetric instruments. */
+/* To remove the matrix, pass NULL for the filter filename */
+inst_code i1disp_col_cor_mat(
+inst *pp,
+double mtx[3][3]
+) {
+ i1disp *p = (i1disp *)pp;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (mtx == NULL) {
+ icmSetUnity3x3(p->ccmat);
+ } else {
+ if (p->cbid == 0) {
+ a1loge(p->log, 1, "spyd2: can't set col_cor_mat over non base display type\n");
+ inst_wrong_setup;
+ }
+ icmCpy3x3(p->ccmat, mtx);
+ }
+
+ return inst_ok;
+}
+
+/* Return needed and available inst_cal_type's */
+static inst_code i1disp_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
+ i1disp *p = (i1disp *)pp;
+ inst_cal_type n_cals = inst_calt_none;
+ inst_cal_type a_cals = inst_calt_none;
+
+ if (p->dtype == 0) { /* Eye-One Display 1 */
+ a_cals |= inst_calt_emis_offset;
+ }
+
+ if (p->dtype == 1 && p->refrmode != 0) {
+ if (p->rrset == 0)
+ n_cals |= inst_calt_ref_freq;
+ a_cals |= inst_calt_ref_freq;
+ }
+
+ if (pn_cals != NULL)
+ *pn_cals = n_cals;
+
+ if (pa_cals != NULL)
+ *pa_cals = a_cals;
+
+ return inst_ok;
+}
+
+/* Request an instrument calibration. */
+/* This is use if the user decides they want to do a calibration, */
+/* in anticipation of a calibration (needs_calibration()) to avoid */
+/* requiring one during measurement, or in response to measuring */
+/* returning inst_needs_cal. Initially us an inst_cal_cond of inst_calc_none, */
+/* and then be prepared to setup the right conditions, or ask the */
+/* user to do so, each time the error inst_cal_setup is returned. */
+inst_code i1disp_calibrate(
+inst *pp,
+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) */
+) {
+ i1disp *p = (i1disp *)pp;
+ inst_code ev;
+ inst_cal_type needed, available;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ id[0] = '\000';
+
+ if ((ev = i1disp_get_n_a_cals((inst *)p, &needed, &available)) != inst_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,"i1disp_calibrate: doing calt 0x%x\n",calt);
+
+ if ((*calt & inst_calt_n_dfrble_mask) == 0) /* Nothing todo */
+ return inst_ok;
+ }
+
+ /* See if it's a calibration we understand */
+ if (*calt & ~available & inst_calt_all_mask) {
+ return inst_unsupported;
+ }
+
+ /* Do the appropriate calibration */
+ if (p->dtype == 0) { /* Eye-One Display 1 */
+ if (*calt & inst_calt_emis_offset) {
+
+ if (*calc != inst_calc_man_ref_dark) {
+ *calc = inst_calc_man_ref_dark;
+ return inst_cal_setup;
+ }
+
+ /* Do black offset calibration */
+ if ((ev = i1disp_do_black_cal(p)) != inst_ok)
+ return ev;
+
+ *calt &= ~inst_calt_emis_offset;
+ }
+
+ } else { /* Eye-One Display 2 */
+ if ((*calt & inst_calt_ref_freq) && p->refrmode != 0) {
+
+ if (*calc != inst_calc_emis_white) {
+ *calc = inst_calc_emis_white;
+ return inst_cal_setup;
+ }
+
+ /* Do CRT frequency calibration and set integration time */
+ if ((ev = i1disp_do_fcal_setit(p)) != inst_ok)
+ return ev;
+
+ /* Quantize the sample time */
+ if (p->refperiod > 0.0) {
+ int n;
+ n = (int)ceil(p->dinttime/p->refperiod);
+ p->inttime = n * p->refperiod;
+ a1logd(p->log, 3, "i1disp: integration time quantize to %f secs\n",p->inttime);
+ } else {
+ p->inttime = p->dinttime;
+ a1logd(p->log, 3, "i1disp: integration time set to %f secs\n",p->inttime);
+ }
+
+ *calt &= ~inst_calt_ref_freq;
+ }
+ }
+
+ return inst_ok;
+}
+
+/* Return the last calibrated refresh rate in Hz. Returns: */
+static inst_code i1disp_get_refr_rate(inst *pp,
+double *ref_rate
+) {
+ i1disp *p = (i1disp *)pp;
+ if (p->refrvalid) {
+ *ref_rate = p->refrate;
+ return inst_ok;
+ } else if (p->rrset) {
+ *ref_rate = 0.0;
+ return inst_misread;
+ }
+ return inst_needs_cal;
+}
+
+/* Set the calibrated refresh rate in Hz. */
+/* Set refresh rate to 0.0 to mark it as invalid */
+/* Rates outside the range 5.0 to 150.0 Hz will return an error */
+static inst_code i1disp_set_refr_rate(inst *pp,
+double ref_rate
+) {
+ i1disp *p = (i1disp *)pp;
+
+ if (ref_rate != 0.0 && (ref_rate < 5.0 || ref_rate > 150.0))
+ return inst_bad_parameter;
+
+ p->refrate = ref_rate;
+ if (ref_rate == 0.0)
+ p->refrvalid = 0;
+ else {
+ p->refperiod = 1.0/p->refrate;
+ p->refrvalid = 1;
+ }
+ p->rrset = 1;
+
+ return inst_ok;
+}
+
+/* Error codes interpretation */
+static char *
+i1disp_interp_error(inst *pp, int ec) {
+// i1disp *p = (i1disp *)pp;
+ ec &= inst_imask;
+ switch (ec) {
+ case I1DISP_INTERNAL_ERROR:
+ return "Internal software error";
+ case I1DISP_COMS_FAIL:
+ return "Communications failure";
+ case I1DISP_UNKNOWN_MODEL:
+ return "Not a i1 Display";
+ case I1DISP_DATA_PARSE_ERROR:
+ return "Data from i1 Display didn't parse as expected";
+
+ case I1DISP_OK:
+ return "No device error";
+
+ case I1DISP_FLOAT_NOT_SET:
+ return "Float value is not set in EEPROM";
+ case I1DISP_NOT_READY:
+ return "Command didn't return command code - not ready ?";
+
+ case I1DISP_BAD_SERIAL_NUMBER:
+ return "Serial number isn't set";
+ case I1DISP_BAD_LCD_CALIBRATION:
+ return "LCD calibration values aren't set";
+ case I1DISP_BAD_CRT_CALIBRATION:
+ return "CRT calibration values aren't set";
+ case I1DISP_EEPROM_WRITE_FAIL:
+ return "Write to EEPROM failed to verify";
+
+ case I1DISP_UNEXPECTED_RET_SIZE:
+ return "Message from instrument has unexpected size";
+ case I1DISP_UNEXPECTED_RET_VAL:
+ return "Message from instrument has unexpected value";
+
+ case I1DISP_BAD_STATUS:
+ return "Instrument status is unrecognised format";
+ case I1DISP_UNKNOWN_VERS_ID:
+ return "Instrument version number or ID byte not recognised";
+
+ /* Internal errors */
+ case I1DISP_BAD_REG_ADDRESS:
+ return "Out of range register address";
+ case I1DISP_BAD_INT_THRESH:
+ return "Out of range integration threshold";
+ case I1DISP_NO_COMS:
+ return "Communications hasn't been established";
+ case I1DISP_NOT_INITED:
+ return "Insrument hasn't been initialised";
+ case I1DISP_CANT_BLACK_CALIB:
+ return "Device doesn't support black calibration";
+ case I1DISP_CANT_MEASP_CALIB:
+ return "Device doesn't support measurment period calibration";
+ case I1DISP_WRONG_DEVICE:
+ return "Wrong type of device for called function";
+ default:
+ return "Unknown error code";
+ }
+}
+
+
+/* Convert a machine specific error code into an abstract dtp code */
+static inst_code
+i1disp_interp_code(inst *pp, int ec) {
+// i1disp *p = (i1disp *)pp;
+
+ ec &= inst_imask;
+ switch (ec) {
+
+ case I1DISP_OK:
+ case I1DISP_FLOAT_NOT_SET: /* Internal indication */
+ case I1DISP_NOT_READY: /* Internal indication */
+ return inst_ok;
+
+ case I1DISP_INTERNAL_ERROR:
+ case I1DISP_BAD_REG_ADDRESS:
+ case I1DISP_BAD_INT_THRESH:
+ case I1DISP_NO_COMS:
+ case I1DISP_NOT_INITED:
+ case I1DISP_CANT_BLACK_CALIB:
+ case I1DISP_CANT_MEASP_CALIB:
+ case I1DISP_WRONG_DEVICE:
+ case I1DISP_LOCKED:
+ return inst_internal_error | ec;
+
+ case I1DISP_COMS_FAIL:
+ return inst_coms_fail | ec;
+
+ case I1DISP_UNKNOWN_MODEL:
+ case I1DISP_BAD_STATUS:
+ case I1DISP_UNKNOWN_VERS_ID:
+ return inst_unknown_model | ec;
+
+ case I1DISP_DATA_PARSE_ERROR:
+ case I1DISP_UNEXPECTED_RET_SIZE:
+ case I1DISP_UNEXPECTED_RET_VAL:
+ return inst_protocol_error | ec;
+
+ case I1DISP_BAD_SERIAL_NUMBER:
+ case I1DISP_BAD_LCD_CALIBRATION:
+ case I1DISP_BAD_CRT_CALIBRATION:
+ case I1DISP_EEPROM_WRITE_FAIL:
+ return inst_hardware_fail | ec;
+
+ /* return inst_misread | ec; */
+ /* return inst_needs_cal_2 | ec; */
+ }
+ return inst_other_error | ec;
+}
+
+/* Destroy ourselves */
+static void
+i1disp_del(inst *pp) {
+ i1disp *p = (i1disp *)pp;
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ inst_del_disptype_list(p->dtlist, p->ndtlist);
+ free(p);
+}
+
+/* Return the instrument capabilities */
+void i1disp_capabilities(inst *pp,
+inst_mode *pcap1,
+inst2_capability *pcap2,
+inst3_capability *pcap3) {
+ i1disp *p = (i1disp *)pp;
+ inst_mode cap1 = 0;
+ inst2_capability cap2 = 0;
+
+ cap1 |= inst_mode_emis_spot
+ | inst_mode_colorimeter
+ ;
+
+ cap2 |= inst2_prog_trig
+ | inst2_user_trig
+ | inst2_disptype
+ | inst2_ccmx
+ ;
+
+ /* i1D2 has refresh display & ambient capability */
+ /* but i1D1 & ColorMunki Smile don't */
+ if (p->dtype == 1) {
+ cap1 |= inst_mode_emis_ambient
+ | inst_mode_emis_refresh_ovd
+ | inst_mode_emis_norefresh_ovd
+ ;
+
+ cap2 |= inst2_refresh_rate
+ | inst2_emis_refr_meas
+ ;
+ }
+
+ if (pcap1 != NULL)
+ *pcap1 = cap1;
+ if (pcap2 != NULL)
+ *pcap2 = cap2;
+ if (pcap3 != NULL)
+ *pcap3 = inst3_none;
+}
+
+/* Check device measurement mode */
+inst_code i1disp_check_mode(inst *pp, inst_mode m) {
+ i1disp *p = (i1disp *)pp;
+ inst_mode cap;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ pp->capabilities(pp, &cap, NULL, NULL);
+
+ /* Simple filter for most modes */
+ if (m & ~cap)
+ return inst_unsupported;
+
+ /* Only display emission mode supported */
+ if (!IMODETST(m, inst_mode_emis_spot)
+ && !(p->dtype == 1 && IMODETST(m, inst_mode_emis_ambient))) {
+ return inst_unsupported;
+ }
+
+ return inst_ok;
+}
+
+/* Set device measurement mode */
+inst_code i1disp_set_mode(inst *pp, inst_mode m) {
+ i1disp *p = (i1disp *)pp;
+ inst_code ev;
+
+ if ((ev = i1disp_check_mode(pp, m)) != inst_ok)
+ return ev;
+
+ p->mode = m;
+
+ if ( IMODETST(p->mode, inst_mode_emis_norefresh_ovd)) /* Must test this first! */
+ p->refrmode = 0;
+ else if (IMODETST(p->mode, inst_mode_emis_refresh_ovd))
+ p->refrmode = 1;
+
+ return inst_ok;
+}
+
+inst_disptypesel i1disp_disptypesel[3] = {
+ {
+ inst_dtflags_default,
+ 1,
+ "l",
+ "LCD display",
+ 0,
+ 0
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 2, /* cbix */
+ "c", /* sel */
+ "CRT display", /* desc */
+ 1, /* refr */
+ 1 /* ix */
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ 0
+ }
+};
+
+
+inst_disptypesel smile_disptypesel[3] = {
+ {
+ inst_dtflags_default, /* flags */
+ 1, /* cbix */
+ "f", /* sel */
+ "LCD with CCFL backlight", /* desc */
+ 0, /* refr */
+ 1 /* ix */
+ },
+ {
+ inst_dtflags_none,
+ 0,
+ "e",
+ "LCD with White LED backlight",
+ 0,
+ 0
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ 0
+ }
+};
+
+static void set_base_disptype_list(i1disp *p) {
+ /* set the base display type list */
+ if (p->itype == instSmile) {
+ p->_dtlist = smile_disptypesel;
+ } else {
+ p->_dtlist = i1disp_disptypesel;
+ }
+}
+
+/* Get mode and option details */
+static inst_code i1disp_get_disptypesel(
+inst *pp,
+int *pnsels, /* Return number of display types */
+inst_disptypesel **psels, /* Return the array of display types */
+int allconfig, /* nz to return list for all configs, not just current. */
+int recreate /* nz to re-check for new ccmx & ccss files */
+) {
+ i1disp *p = (i1disp *)pp;
+ inst_code rv = inst_ok;
+
+ /* Create/Re-create a current list of abailable display types */
+ if (p->dtlist == NULL || recreate) {
+ if ((rv = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
+ p->_dtlist, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return rv;
+ }
+
+ if (pnsels != NULL)
+ *pnsels = p->ndtlist;
+
+ if (psels != NULL)
+ *psels = p->dtlist;
+
+ return inst_ok;
+}
+
+/* Given a display type entry, setup for that type */
+static inst_code set_disp_type(i1disp *p, inst_disptypesel *dentry) {
+ int refrmode;
+
+ p->icx = dentry->ix;
+ p->cbid = dentry->cbid;
+ refrmode = dentry->refr;
+
+ if ( IMODETST(p->mode, inst_mode_emis_norefresh_ovd)) { /* Must test this first! */
+ refrmode = 0;
+ } else if (IMODETST(p->mode, inst_mode_emis_refresh_ovd)) {
+ refrmode = 1;
+ }
+
+ if (p->refrmode != refrmode) {
+ p->rrset = 0; /* This is a hint we may have swapped displays */
+ p->refrvalid = 0;
+ }
+ p->refrmode = refrmode;
+
+ if (dentry->flags & inst_dtflags_ccmx) {
+ icmCpy3x3(p->ccmat, dentry->mat);
+ } else {
+ icmSetUnity3x3(p->ccmat);
+ }
+
+ return inst_ok;
+}
+
+/* Setup the default display type */
+static inst_code set_default_disp_type(i1disp *p) {
+ inst_code ev;
+ int i;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list((inst *)p, &p->ndtlist, &p->dtlist,
+ p->_dtlist, 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ for (i = 0; !(p->dtlist[i].flags & inst_dtflags_end); i++) {
+ if (p->dtlist[i].flags & inst_dtflags_default)
+ break;
+ }
+ if (p->dtlist[i].flags & inst_dtflags_end) {
+ a1loge(p->log, 1, "set_default_disp_type: failed to find type!\n");
+ return inst_internal_error;
+ }
+ if ((ev = set_disp_type(p, &p->dtlist[i])) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+/* Set the display type */
+static inst_code i1disp_set_disptype(inst *pp, int ix) {
+ i1disp *p = (i1disp *)pp;
+ inst_code ev;
+ inst_disptypesel *dentry;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
+ p->_dtlist, 1 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ if (ix < 0 || ix >= p->ndtlist)
+ return inst_unsupported;
+
+ dentry = &p->dtlist[ix];
+
+ if ((ev = set_disp_type(p, dentry)) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+
+/*
+ * set or reset an optional mode
+ *
+ * Since there is no interaction with the instrument,
+ * was assume that all of these can be done before initialisation.
+ */
+static inst_code
+i1disp_get_set_opt(inst *pp, inst_opt_type m, ...)
+{
+ i1disp *p = (i1disp *)pp;
+ inst_code ev;
+
+ /* Get the display type information */
+ if (m == inst_opt_get_dtinfo) {
+ va_list args;
+ int *refrmode, *cbid;
+
+ va_start(args, m);
+ refrmode = va_arg(args, int *);
+ cbid = va_arg(args, int *);
+ va_end(args);
+
+ if (refrmode != NULL)
+ *refrmode = p->refrmode;
+ if (cbid != NULL)
+ *cbid = p->cbid;
+
+ return inst_ok;
+ }
+
+ /* Record the trigger mode */
+ if (m == inst_opt_trig_prog
+ || m == inst_opt_trig_user) {
+ p->trig = m;
+ return inst_ok;
+ }
+
+ return inst_unsupported;
+}
+
+/* Constructor */
+extern i1disp *new_i1disp(icoms *icom, instType itype) {
+ i1disp *p;
+ if ((p = (i1disp *)calloc(sizeof(i1disp),1)) == NULL) {
+ a1loge(icom->log, 1, "new_i1disp: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(icom->log);
+
+ p->init_coms = i1disp_init_coms;
+ p->init_inst = i1disp_init_inst;
+ p->capabilities = i1disp_capabilities;
+ p->check_mode = i1disp_check_mode;
+ p->set_mode = i1disp_set_mode;
+ p->get_disptypesel = i1disp_get_disptypesel;
+ p->set_disptype = i1disp_set_disptype;
+ p->get_set_opt = i1disp_get_set_opt;
+ p->read_sample = i1disp_read_sample;
+ p->read_refrate = i1disp_read_refrate;
+ p->get_n_a_cals = i1disp_get_n_a_cals;
+ p->calibrate = i1disp_calibrate;
+ p->col_cor_mat = i1disp_col_cor_mat;
+ p->get_refr_rate = i1disp_get_refr_rate;
+ p->set_refr_rate = i1disp_set_refr_rate;
+ p->interp_error = i1disp_interp_error;
+ p->del = i1disp_del;
+
+ p->icom = icom;
+ p->itype = icom->itype;
+
+ if (p->itype == instI1Disp2)
+ p->dtype = 1; /* i1Display2 */
+
+ else if (p->itype == instSmile) {
+ p->dtype = 2; /* Smile */
+ }
+
+ icmSetUnity3x3(p->ccmat); /* Set the colorimeter correction matrix to do nothing */
+ set_base_disptype_list(p);
+
+ return p;
+}
+
+/* ---------------------------------------------------------------- */
+
+// Print bytes as hex to debug log */
+static void dump_bytes(a1log *log, char *pfx, unsigned char *buf, int base, int len) {
+ int i, j, ii;
+ char oline[200] = { '\000' }, *bp = oline;
+ for (i = j = 0; i < len; i++) {
+ if ((i % 16) == 0)
+ bp += sprintf(bp,"%s%04x:",pfx,base+i);
+ bp += sprintf(bp," %02x",buf[i]);
+ if ((i+1) >= len || ((i+1) % 16) == 0) {
+ for (ii = i; ((ii+1) % 16) != 0; ii++)
+ bp += sprintf(bp," ");
+ bp += sprintf(bp," ");
+ for (; j <= i; j++) {
+ if (!(buf[j] & 0x80) && isprint(buf[j]))
+ bp += sprintf(bp,"%c",buf[j]);
+ else
+ bp += sprintf(bp,".");
+ }
+ bp += sprintf(bp,"\n");
+ a1logd(log,0,oline);
+ bp = oline;
+ }
+ }
+}
+
+
+
+
diff --git a/spectro/i1disp.h b/spectro/i1disp.h
new file mode 100644
index 0000000..893cad2
--- /dev/null
+++ b/spectro/i1disp.h
@@ -0,0 +1,172 @@
+#ifndef I1DISP_H
+
+/*
+ * Argyll Color Correction System
+ *
+ * Gretag i1Display related defines
+ *
+ * Author: Graeme W. Gill
+ * Date: 19/10/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.
+ */
+
+#include "inst.h"
+
+/* Note: update i1disp_interp_error() and i1disp_interp_code() in i1disp.c */
+/* if anything of these #defines are added or subtracted */
+
+/* Fake Error codes */
+#define I1DISP_INTERNAL_ERROR 0x61 /* Internal software error */
+#define I1DISP_COMS_FAIL 0x62 /* Communication failure */
+#define I1DISP_UNKNOWN_MODEL 0x63 /* Not an i1display */
+#define I1DISP_DATA_PARSE_ERROR 0x64 /* Read data parsing error */
+
+/* Real error code */
+#define I1DISP_OK 0x00
+
+/* Sub codes for device specific reasoning */
+#define I1DISP_FLOAT_NOT_SET 0x01
+#define I1DISP_NOT_READY 0x02
+
+#define I1DISP_BAD_SERIAL_NUMBER 0x03
+#define I1DISP_BAD_LCD_CALIBRATION 0x04
+#define I1DISP_BAD_CRT_CALIBRATION 0x05
+#define I1DISP_EEPROM_WRITE_FAIL 0x06
+
+#define I1DISP_UNEXPECTED_RET_SIZE 0x07
+#define I1DISP_UNEXPECTED_RET_VAL 0x08
+
+#define I1DISP_BAD_STATUS 0x09
+#define I1DISP_UNKNOWN_VERS_ID 0x10
+
+/* Internal errors */
+#define I1DISP_BAD_REG_ADDRESS 0x20
+#define I1DISP_BAD_INT_THRESH 0x21
+#define I1DISP_NO_COMS 0x22
+#define I1DISP_NOT_INITED 0x23
+#define I1DISP_CANT_BLACK_CALIB 0x24
+#define I1DISP_CANT_MEASP_CALIB 0x25
+#define I1DISP_WRONG_DEVICE 0x26
+#define I1DISP_LOCKED 0x27
+
+
+/* I1DISP communication object */
+struct _i1disp {
+ INST_OBJ_BASE
+
+ int dtype; /* Device type: 0 = i1D1, 1 = i1D2, 2 = Smile */
+ int lite; /* i1D2: 0 = normal, 1 = "Lite" */
+ int munki; /* i1D2: 0 = normal, 1 = "Munk" */
+ int hpdream; /* i1D2: 0 = normal, 1 = "ObiW" */
+ int calmanx2; /* i1D2: 0 = normal, 1 = "CMX2" */
+ int chroma4; /* 0 = other, 1 = Sequel Chroma 4 (i1D1 based) */
+ inst_mode mode; /* Currently selected mode */
+
+ inst_opt_type trig; /* Reading trigger mode */
+
+ /* EEPROM registers */
+ /* Number is the register address, and B, S, W, F indicate the type/size */
+ int reg0_W; /* Serial number */
+
+ double reg4_F[9]; /* LCD 3x3 calibration matrix (also known as "user") */
+ /* Smile LED backlight */
+ int reg50_W; /* Calibration time in secs from January 1, 1970, UTC */
+ int reg126_S; /* LCD cal valid/state flag. For the i1disp this is 0xd, */
+ /* perhaps meaning that it is the LCD matrix. */
+ /* It's set to 7 after storing a user calibration. */
+ /* A value of 0xffff or < 7 means that it's invalid */
+ /* A value of 2 seems valid for OEM instruments */
+ /* (Heidelberg Viewmaker & Lacie Blue Eye) */
+
+ double reg54_F[9]; /* CRT 3x3 calibration matrix (also known as "factory") */
+ /* Smile CCFL backlight */
+ int reg90_W; /* CRT cal valid/time flag. 0xffffffff = invalid, */
+ /* time in secs from January 1, 1970, UTC */
+
+ int reg40_S; /* Integration clock perod in nsec reg40S, typically 1000 */
+ int reg42_S; /* Int cal. factor denominator, typically 10000 */
+ int reg44_S[3]; /* Int cal. factors numerator/100, typically 100 */
+
+ double clk_prd; /* Master clock period, reg94F, typically 1e-6 */
+
+ int reg98_W; /* A time value. Date of manufacture ? */
+ /* Reg 40-44 write date ? */
+
+ int reg102_B; /* Not used ? */
+
+ double reg103_F[3]; /* Dark current calibration values */
+ /* Not valid if reg126_S < 0xd ?? */
+
+ int reg115_B; /* Unknown */
+ int reg121_B; /* Device ID character */
+
+ char reg122_B[5]; /* Unlock string */
+
+ /* Extra registers for dtype == 1 (Eye-One Display2) */
+// double reg128_F[3]; /* Not used at all */
+ double reg144_F[3]; /* Ambient matrix adjustment values */
+ /* ??? Default to 1.0 if not set in EEPROM */
+
+ /* Computed factors and state */
+ double iclk_freq; /* Integration clock (from reg40_S), typically 1e6 */
+ double clk_freq; /* Measurement clock (from reg94_F), typically 1e6 */
+ double rgbadj[3]; /* RGB adjustment values for period meas., typically 1.0 */
+ double amb[9]; /* Ambient measurement matrix = ref144[] * average of LCD & CRT */
+
+ inst_disptypesel *_dtlist; /* Base list */
+ inst_disptypesel *dtlist; /* Display Type list */
+ int ndtlist; /* Number of valid dtlist entries */
+ int icx; /* 0 = LCD, 1 = CRT/CCFL matrix */
+ int cbid; /* calibration base ID, 0 if not a base */
+ double ccmat[3][3]; /* Colorimeter correction matrix */
+
+ /* For dtype == 1 (Eye-One Display2) */
+ int nmeasprds; /* Number of disp refresh period measurments to average, deflt 5 */
+ int refrmode; /* 0 for constant, 1 for refresh display */
+ int rrset; /* Flag, nz if the refresh rate has been determined */
+ double refperiod; /* if > 0.0 in refmode, target int time quantization */
+ double refrate; /* Measured refresh rate in Hz */
+ int refrvalid; /* nz if refrate is valid */
+
+ double dinttime; /* default integration time = 1.1 seconds */
+ double inttime; /* current integration time = 1.0 seconds */
+
+ int int_clocks; /* Currently set integration time in clocks */
+
+ /* For dtype == 2 (ColorMunki Smile) */
+ char serno[20]; /* Ascii serial number */
+
+ /* misc */
+ int last_com_err; /* Last icoms error code */
+
+}; typedef struct _i1disp i1disp;
+
+/* Constructor */
+extern i1disp *new_i1disp(icoms *icom, instType itype);
+
+
+#define I1DISP_H
+#endif /* I1DISP_H */
diff --git a/spectro/i1pro.c b/spectro/i1pro.c
new file mode 100644
index 0000000..99dd61b
--- /dev/null
+++ b/spectro/i1pro.c
@@ -0,0 +1,809 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * Gretag i1Monitor & i1Pro related 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
+
+
+ Should add extra filter compensation support.
+
+ Should alias projector mode to display mode ??
+
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#include <math.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "rspl.h"
+#else /* SALONEINSTLIB */
+#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 "i1pro.h"
+#include "i1pro_imp.h"
+
+#define MAX_MES_SIZE 500 /* Maximum normal message reply size */
+#define MAX_RD_SIZE 5000 /* Maximum reading messagle reply size */
+
+/* Convert a machine specific error code into an abstract inst code */
+static inst_code i1pro_interp_code(i1pro *p, i1pro_code ec);
+
+/* ------------------------------------------------------------------------ */
+
+/* Establish communications with a I1DISP */
+/* If it's a serial port, use the baud rate given, and timeout in to secs */
+/* Return DTP_COMS_FAIL on failure to establish communications */
+static inst_code
+i1pro_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ i1pro *p = (i1pro *) pp;
+ int rsize, se;
+ icomuflags usbflags = icomuf_none;
+#ifdef __APPLE__
+ /* If the X-Rite software has been installed, then there may */
+ /* be a daemon process that has the device open. Kill that process off */
+ /* so that we can open it here, before it re-spawns. */
+ char *pnames[] = {
+// "i1iSisDeviceService",
+ "i1ProDeviceService",
+ NULL
+ };
+ int retries = 20;
+#else /* !__APPLE__ */
+ char **pnames = NULL;
+ int retries = 0;
+#endif /* !__APPLE__ */
+
+ a1logd(p->log, 2, "i1pro_init_coms: called\n");
+
+ if (p->icom->port_type(p->icom) != icomt_usb) {
+ a1logd(p->log, 1, "i1pro_init_coms: wrong sort of coms!\n");
+ return i1pro_interp_code(p, I1PRO_UNKNOWN_MODEL);
+ }
+
+ a1logd(p->log, 2, "i1pro_init_coms: about to init USB\n");
+
+ /* Set config, interface, write end point, read end point, read quanta */
+ /* ("serial" end points aren't used - the i1display uses USB control messages) */
+ if ((se = p->icom->set_usb_port(p->icom, 1, 0x00, 0x00, usbflags, retries, pnames))
+ != ICOM_OK) {
+ a1logd(p->log, 1, "i1pro_init_coms: failed ICOM err 0x%x\n",se);
+ return i1pro_interp_code(p, icoms2i1pro_err(se));
+ }
+
+ a1logd(p->log, 2, "i1pro_init_coms: init coms has suceeded\n");
+
+ p->gotcoms = 1;
+ return inst_ok;
+}
+
+static inst_code
+i1pro_determine_capabilities(i1pro *p) {
+
+ /* Set the base Monitor/Pro capabilities mask */
+ p->cap = inst_mode_emis_spot
+ | inst_mode_emis_tele
+ | inst_mode_emis_strip /* Also likely to be light table reading */
+ | inst_mode_trans_spot /* Support this manually using a light table */
+ | inst_mode_trans_strip
+ | inst_mode_emis_nonadaptive
+ | inst_mode_colorimeter
+ | inst_mode_spectral
+ ;
+
+ /* Set the Pro capabilities mask */
+ if (p->itype == instI1Pro
+ || p->itype == instI1Pro2) {
+ p->cap |= inst_mode_ref_spot
+ | inst_mode_ref_strip
+ ;
+ }
+
+ /* Set the Pro2 capabilities mask */
+ if (p->itype == instI1Pro2) {
+ p->cap |= inst_mode_ref_uv
+ ;
+ }
+
+ if (i1pro_imp_highres(p)) /* This is static */
+ p->cap |= inst_mode_highres;
+
+ if (i1pro_imp_ambient(p)) { /* This depends on the instrument */
+ p->cap |= inst_mode_emis_ambient
+ | inst_mode_emis_ambient_flash
+ ;
+ }
+
+ p->cap2 = inst2_prog_trig
+ | inst2_user_trig
+ | inst2_user_switch_trig
+ | inst2_bidi_scan
+ | inst2_has_scan_toll
+ | inst2_no_feedback
+ ;
+
+ if (p->m != NULL) {
+ i1proimp *m = (i1proimp *)p->m;
+ i1pro_state *s = &m->ms[m->mmode];
+ if (s->emiss)
+ p->cap2 |= inst2_emis_refr_meas;
+ }
+
+ p->cap3 = inst3_none;
+
+ return inst_ok;
+}
+
+/* Initialise the I1PRO */
+/* return non-zero on an error, with inst error code */
+static inst_code
+i1pro_init_inst(inst *pp) {
+ i1pro *p = (i1pro *)pp;
+ i1pro_code ev = I1PRO_OK;
+
+ a1logd(p->log, 2, "i1pro_init_inst: called\n");
+
+ if (p->gotcoms == 0)
+ return i1pro_interp_code(p, I1PRO_INT_NO_COMS); /* Must establish coms before calling init */
+ if ((ev = i1pro_imp_init(p)) != I1PRO_OK) {
+ a1logd(p->log, 1, "i1pro_init_inst: failed with 0x%x\n",ev);
+ return i1pro_interp_code(p, ev);
+ }
+
+ p->inited = 1;
+ a1logd(p->log, 2, "i1pro_init_inst: instrument inited OK\n");
+
+ /* Now it's initied, we can get true capabilities */
+ i1pro_determine_capabilities(p);
+
+ return i1pro_interp_code(p, ev);
+}
+
+static char *i1pro_get_serial_no(inst *pp) {
+ i1pro *p = (i1pro *)pp;
+
+ if (!pp->gotcoms)
+ return "";
+ if (!pp->inited)
+ return "";
+
+ return i1pro_imp_get_serial_no(p);
+}
+
+/* Read a set of strips */
+static inst_code
+i1pro_read_strip(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+int npatch, /* Number of patches in the pass */
+char *pname, /* Pass name (3 chars) */
+int sguide, /* Guide number */
+double pwid, /* Patch length in mm (DTP41) */
+double gwid, /* Gap length in mm (DTP41) */
+double twid, /* Trailer length in mm (DTP41T) */
+ipatch *vals) { /* Pointer to array of instrument patch values */
+ i1pro *p = (i1pro *)pp;
+ i1pro_code rv;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ rv = i1pro_imp_measure(p, vals, npatch, 1);
+
+ return i1pro_interp_code(p, rv);
+}
+
+/* Read a single sample */
+static inst_code
+i1pro_read_sample(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+ipatch *val, /* Pointer to instrument patch value */
+instClamping clamp) { /* Clamp XYZ/Lab to be +ve */
+ i1pro *p = (i1pro *)pp;
+ i1pro_code rv;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ rv = i1pro_imp_measure(p, val, 1, clamp);
+
+ return i1pro_interp_code(p, rv);
+}
+
+/* Read an emissive refresh rate */
+static inst_code
+i1pro_read_refrate(
+inst *pp,
+double *ref_rate) {
+ i1pro *p = (i1pro *)pp;
+ i1pro_code rv;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ rv = i1pro_imp_meas_refrate(p, ref_rate);
+
+ return i1pro_interp_code(p, rv);
+}
+
+/* Return needed and available inst_cal_type's */
+static inst_code i1pro_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
+ i1pro *p = (i1pro *)pp;
+ i1pro_code rv;
+
+ rv = i1pro_imp_get_n_a_cals(p, pn_cals, pa_cals);
+ return i1pro_interp_code(p, rv);
+}
+
+/* Request an instrument calibration. */
+inst_code i1pro_calibrate(
+inst *pp,
+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 *p = (i1pro *)pp;
+ i1pro_code rv;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ rv = i1pro_imp_calibrate(p, calt, calc, id);
+ return i1pro_interp_code(p, rv);
+}
+
+/* Instrument specific error codes interpretation */
+static char *
+i1pro_interp_error(inst *pp, i1pro_code ec) {
+// i1pro *p = (i1pro *)pp;
+ ec &= inst_imask;
+ switch (ec) {
+ case I1PRO_INTERNAL_ERROR:
+ return "Internal software error";
+ case I1PRO_COMS_FAIL:
+ return "Communications failure";
+ case I1PRO_UNKNOWN_MODEL:
+ return "Not an i1 Pro";
+ case I1PRO_DATA_PARSE_ERROR:
+ return "Data from i1 Display didn't parse as expected";
+
+ case I1PRO_USER_ABORT:
+ return "User abort";
+
+ case I1PRO_USER_TRIG:
+ return "User trigger";
+
+ case I1PRO_UNSUPPORTED:
+ return "Unsupported function";
+ case I1PRO_CAL_SETUP:
+ return "Calibration retry with correct setup is needed";
+
+ case I1PRO_OK:
+ return "No device error";
+
+ case I1PRO_DATA_COUNT:
+ return "EEProm data count unexpectedly small";
+ case I1PRO_DATA_BUFSIZE:
+ return "EEProm data buffer too small";
+ case I1PRO_DATA_MAKE_KEY:
+ return "EEProm data creating key failed";
+ case I1PRO_DATA_MEMORY:
+ return "EEProm memory alloc failure";
+ case I1PRO_DATA_KEYNOTFOUND:
+ return "EEProm key value wasn't found";
+ case I1PRO_DATA_WRONGTYPE:
+ return "EEProm key is the wrong type";
+ case I1PRO_DATA_KEY_CORRUPT:
+ return "EEProm key table seems to be corrupted";
+ case I1PRO_DATA_KEY_COUNT:
+ return "EEProm key table count is too big or small";
+ case I1PRO_DATA_KEY_UNKNOWN:
+ return "EEProm unknown key type";
+ case I1PRO_DATA_KEY_MEMRANGE:
+ return "EEProm key data is out of range of EEProm";
+ case I1PRO_DATA_KEY_ENDMARK:
+ return "EEProm end section marker was missing";
+
+ case I1PRO_HW_HIGHPOWERFAIL:
+ return "Failed to switch to high power mode";
+ case I1PRO_HW_EE_SIZE:
+ return "EEProm size is too small";
+ case I1PRO_HW_EE_SHORTREAD:
+ return "Read less bytes for EEProm read than expected";
+ case I1PRO_HW_EE_SHORTWRITE:
+ return "Wrote less bytes for EEProm write than expected";
+ case I1PRO_HW_ME_SHORTREAD:
+ return "Read less bytes for measurement read than expected";
+ case I1PRO_HW_ME_ODDREAD:
+ return "Read a number of bytes not a multiple of 256";
+ case I1PRO_HW_SW_SHORTREAD:
+ return "Read less bytes for Switch read than expected";
+ case I1PRO_HW_LED_SHORTWRITE:
+ return "Wrote fewer LED sequence bytes than expected";
+ case I1PRO_HW_UNEX_SPECPARMS:
+ return "Instrument has unexpected spectral parameters";
+ case I1PRO_HW_CALIBINFO:
+ return "Instrument calibration info is missing or corrupted";
+ case I1PRO_WL_TOOLOW:
+ return "Wavelength calibration reading is too low";
+ case I1PRO_WL_SHAPE:
+ return "Wavelength calibration reading shape is incorrect";
+ case I1PRO_WL_ERR2BIG:
+ return "Wavelength calibration correction is excessive";
+
+ case I1PRO_RD_DARKREADINCONS:
+ return "Dark calibration reading is inconsistent";
+ case I1PRO_RD_SENSORSATURATED:
+ return "Sensor is saturated";
+ case I1PRO_RD_DARKNOTVALID:
+ return "Dark reading is not valid (too light)";
+ case I1PRO_RD_NEEDS_CAL:
+ return "Mode needs calibration";
+ case I1PRO_RD_WHITEREADINCONS:
+ return "White calibration reading is inconsistent";
+ case I1PRO_RD_WHITEREFERROR:
+ return "White reference reading error";
+ case I1PRO_RD_LIGHTTOOLOW:
+ return "Light level is too low";
+ case I1PRO_RD_LIGHTTOOHIGH:
+ return "Light level is too high";
+ case I1PRO_RD_SHORTMEAS:
+ return "Reading is too short";
+ case I1PRO_RD_READINCONS:
+ return "Reading is inconsistent";
+ case I1PRO_RD_TRANSWHITERANGE:
+ return "Transmission white reference is out of range";
+ case I1PRO_RD_NOTENOUGHPATCHES:
+ return "Not enough patches";
+ case I1PRO_RD_TOOMANYPATCHES:
+ return "Too many patches";
+ case I1PRO_RD_NOTENOUGHSAMPLES:
+ return "Not enough samples per patch";
+ case I1PRO_RD_NOFLASHES:
+ return "No flashes recognized";
+ case I1PRO_RD_NOAMBB4FLASHES:
+ return "No ambient found before first flash";
+ case I1PRO_RD_NOREFR_FOUND:
+ return "No refresh rate detected or failed to measure it";
+
+ case I1PRO_INT_NO_COMS:
+ return "Communications hasn't been established";
+ case I1PRO_INT_EETOOBIG:
+ return "Read of EEProm is too big (> 65536)";
+ case I1PRO_INT_ODDREADBUF:
+ return "Measurement read buffer is not a multiple of reading size";
+ case I1PRO_INT_SMALLREADBUF:
+ return "Measurement read buffer is too small for initial measurement";
+ case I1PRO_INT_INTTOOBIG:
+ return "Integration time is too big";
+ case I1PRO_INT_INTTOOSMALL:
+ return "Integration time is too small";
+ case I1PRO_INT_ILLEGALMODE:
+ return "Illegal measurement mode selected";
+ case I1PRO_INT_ZEROMEASURES:
+ return "Number of measurements requested is zero";
+ case I1PRO_INT_WRONGPATCHES:
+ return "Number of patches to match is wrong";
+ case I1PRO_INT_MEASBUFFTOOSMALL:
+ return "Measurement exceeded read buffer";
+ case I1PRO_INT_NOTIMPLEMENTED:
+ return "Support not implemented";
+ case I1PRO_INT_NOTCALIBRATED:
+ return "Unexpectedely invalid calibration";
+ case I1PRO_INT_NOINTERPDARK:
+ return "Need interpolated dark and don't have it";
+ case I1PRO_INT_THREADFAILED:
+ return "Creation of thread failed";
+ case I1PRO_INT_BUTTONTIMEOUT:
+ return "Button status read timed out";
+ case I1PRO_INT_CIECONVFAIL:
+ return "Creating spectral to CIE converted failed";
+ case I1PRO_INT_PREP_LOG_DATA:
+ return "Error in preparing log data";
+ case I1PRO_INT_MALLOC:
+ return "Error in allocating memory";
+ case I1PRO_INT_CREATE_EEPROM_STORE:
+ return "Error in creating EEProm store";
+ case I1PRO_INT_SAVE_SUBT_MODE:
+ return "Can't save calibration if in subt mode";
+ case I1PRO_INT_NO_CAL_TO_SAVE:
+ return "No calibration data to save";
+ case I1PRO_INT_EEPROM_DATA_MISSING:
+ return "EEProm data is missing";
+ case I1PRO_INT_NEW_RSPL_FAILED:
+ return "Creating RSPL object faild";
+ case I1PRO_INT_CAL_SAVE:
+ return "Unable to save calibration to file";
+ case I1PRO_INT_CAL_RESTORE:
+ return "Unable to restore calibration from file";
+ case I1PRO_INT_CAL_TOUCH:
+ return "Unable to update calibration file modification time";
+ case I1PRO_INT_ADARK_INVALID:
+ return "Adaptive dark calibration is invalid";
+ case I1PRO_INT_NO_HIGH_GAIN:
+ return "Rev E mode doesn't have a high gain mode";
+ case I1PRO_INT_ASSERT:
+ return "Assert fail";
+
+ default:
+ return "Unknown error code";
+ }
+}
+
+
+/* Convert a machine specific error code into an abstract inst code */
+static inst_code
+i1pro_interp_code(i1pro *p, i1pro_code ec) {
+
+ ec &= inst_imask;
+ switch (ec) {
+
+ case I1PRO_OK:
+
+ return inst_ok;
+
+ case I1PRO_COMS_FAIL:
+ return inst_coms_fail | ec;
+
+ case I1PRO_UNKNOWN_MODEL:
+ return inst_unknown_model | ec;
+
+ case I1PRO_DATA_PARSE_ERROR:
+ return inst_protocol_error | ec;
+
+ case I1PRO_USER_ABORT:
+ return inst_user_abort;
+
+ case I1PRO_USER_TRIG:
+ return inst_user_trig;
+
+ case I1PRO_UNSUPPORTED:
+ return inst_unsupported | ec;
+
+ case I1PRO_CAL_SETUP:
+ return inst_cal_setup | ec;
+
+ case I1PRO_HW_HIGHPOWERFAIL:
+ case I1PRO_HW_EE_SHORTREAD:
+ case I1PRO_HW_ME_SHORTREAD:
+ case I1PRO_HW_ME_ODDREAD:
+ case I1PRO_HW_CALIBINFO:
+ return inst_hardware_fail | ec;
+
+ case I1PRO_RD_DARKREADINCONS:
+ case I1PRO_RD_SENSORSATURATED:
+ case I1PRO_RD_DARKNOTVALID:
+ case I1PRO_RD_WHITEREADINCONS:
+ case I1PRO_RD_WHITEREFERROR:
+ case I1PRO_RD_LIGHTTOOLOW:
+ case I1PRO_RD_LIGHTTOOHIGH:
+ case I1PRO_RD_SHORTMEAS:
+ case I1PRO_RD_READINCONS:
+ case I1PRO_RD_TRANSWHITERANGE:
+ case I1PRO_RD_NOTENOUGHPATCHES:
+ case I1PRO_RD_TOOMANYPATCHES:
+ case I1PRO_RD_NOTENOUGHSAMPLES:
+ case I1PRO_RD_NOFLASHES:
+ case I1PRO_RD_NOAMBB4FLASHES:
+ case I1PRO_RD_NOREFR_FOUND:
+ return inst_misread | ec;
+
+ case I1PRO_RD_NEEDS_CAL:
+ return inst_needs_cal | ec;
+
+ case I1PRO_INT_NO_COMS:
+ case I1PRO_INT_EETOOBIG:
+ case I1PRO_INT_ODDREADBUF:
+ case I1PRO_INT_SMALLREADBUF:
+ case I1PRO_INT_INTTOOBIG:
+ case I1PRO_INT_INTTOOSMALL:
+ case I1PRO_INT_ILLEGALMODE:
+ case I1PRO_INT_ZEROMEASURES:
+ case I1PRO_INT_MEASBUFFTOOSMALL:
+ case I1PRO_INT_NOTIMPLEMENTED:
+ case I1PRO_INT_NOTCALIBRATED:
+ case I1PRO_INT_NOINTERPDARK:
+ case I1PRO_INT_THREADFAILED:
+ case I1PRO_INT_BUTTONTIMEOUT:
+ case I1PRO_INT_CIECONVFAIL:
+ case I1PRO_INT_PREP_LOG_DATA:
+ case I1PRO_INT_MALLOC:
+ case I1PRO_INT_CREATE_EEPROM_STORE:
+ case I1PRO_INT_SAVE_SUBT_MODE:
+ case I1PRO_INT_NO_CAL_TO_SAVE:
+ case I1PRO_INT_EEPROM_DATA_MISSING:
+ case I1PRO_INT_NEW_RSPL_FAILED:
+ case I1PRO_INT_CAL_SAVE:
+ case I1PRO_INT_CAL_RESTORE:
+ case I1PRO_INT_CAL_TOUCH:
+ case I1PRO_INT_ADARK_INVALID:
+ case I1PRO_INT_NO_HIGH_GAIN:
+ case I1PRO_INT_ASSERT:
+ return inst_internal_error | ec;
+ }
+ return inst_other_error | ec;
+}
+
+/* Return the instrument capabilities */
+void i1pro_capabilities(inst *pp,
+inst_mode *pcap1,
+inst2_capability *pcap2,
+inst3_capability *pcap3) {
+ i1pro *p = (i1pro *)pp;
+
+ if (pcap1 != NULL)
+ *pcap1 = p->cap;
+ if (pcap2 != NULL)
+ *pcap2 = p->cap2;
+ if (pcap3 != NULL)
+ *pcap3 = p->cap3;
+}
+
+/* Return the corresponding i1pro measurement mode, */
+/* or i1p_no_modes if invalid */
+static i1p_mode i1pro_convert_mode(i1pro *p, inst_mode m) {
+ i1p_mode mmode = 0; /* Instrument measurement mode */
+
+ /* Simple test */
+ if (m & ~p->cap) {
+ return i1p_no_modes;
+ }
+
+ if (IMODETST(m, inst_mode_ref_spot)) {
+ mmode = i1p_refl_spot;
+ } else if (IMODETST(m, inst_mode_ref_strip)) {
+ mmode = i1p_refl_scan;
+ } else if (IMODETST(m, inst_mode_trans_spot)) {
+ mmode = i1p_trans_spot;
+ } else if (IMODETST(m, inst_mode_trans_strip)) {
+ mmode = i1p_trans_scan;
+ } else if (IMODETST(m, inst_mode_emis_spot)
+ || IMODETST(m, inst_mode_emis_tele)) {
+ if (IMODETST(m, inst_mode_emis_nonadaptive))
+ mmode = i1p_emiss_spot_na;
+ else
+ mmode = i1p_emiss_spot;
+ } else if (IMODETST(m, inst_mode_emis_strip)) {
+ mmode = i1p_emiss_scan;
+ } else if (IMODETST(m, inst_mode_emis_ambient)
+ && (p->cap & inst_mode_emis_ambient)) {
+ mmode = i1p_amb_spot;
+ } else if (IMODETST(m, inst_mode_emis_ambient_flash)
+ && (p->cap & inst_mode_emis_ambient_flash)) {
+ mmode = i1p_amb_flash;
+ } else {
+ return i1p_no_modes;
+ }
+
+ return mmode;
+}
+
+/* Check device measurement mode */
+inst_code i1pro_check_mode(inst *pp, inst_mode m) {
+ i1pro *p = (i1pro *)pp;
+ i1p_mode mmode = 0; /* Instrument measurement mode */
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (i1pro_convert_mode(p, m) == i1p_no_modes)
+ return inst_unsupported;
+
+ return inst_ok;
+}
+
+/* Set device measurement mode */
+inst_code i1pro_set_mode(inst *pp, inst_mode m) {
+ i1pro *p = (i1pro *)pp;
+ i1p_mode mmode; /* Instrument measurement mode */
+ inst_code rv;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if ((mmode = i1pro_convert_mode(p, m)) == i1p_no_modes)
+ return inst_unsupported;
+
+ if ((rv = i1pro_interp_code(p, i1pro_imp_set_mode(p, mmode, m))) != inst_ok)
+ return rv;
+
+ i1pro_determine_capabilities(p);
+
+ return inst_ok;
+}
+
+/*
+ * set or reset an optional mode
+ *
+ * Some options talk to the instrument, and these will
+ * error if it hasn't been initialised.
+ */
+static inst_code
+i1pro_get_set_opt(inst *pp, inst_opt_type m, ...)
+{
+ i1pro *p = (i1pro *)pp;
+
+ if (m == inst_opt_noinitcalib) {
+ va_list args;
+ int losecs = 0;
+
+ va_start(args, m);
+ losecs = va_arg(args, int);
+ va_end(args);
+
+ i1pro_set_noinitcalib(p, 1, losecs);
+ return inst_ok;
+
+ } else if (m == inst_opt_initcalib) {
+ i1pro_set_noinitcalib(p, 0, 0);
+ return inst_ok;
+
+ /* Record the trigger mode */
+ } else if (m == inst_opt_trig_prog
+ || m == inst_opt_trig_user
+ || m == inst_opt_trig_user_switch) {
+ i1pro_set_trig(p, m);
+ return inst_ok;
+ }
+
+ if (m == inst_opt_scan_toll) {
+ va_list args;
+ double toll_ratio = 1.0;
+
+ va_start(args, m);
+ toll_ratio = va_arg(args, double);
+ va_end(args);
+ return i1pro_interp_code(p, i1pro_set_scan_toll(p, toll_ratio));
+ }
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* Not sure if this can be set before init */
+ if (m == inst_opt_highres) {
+ return i1pro_interp_code(p, i1pro_set_highres(p));
+ } else if (m == inst_opt_stdres) {
+ return i1pro_interp_code(p, i1pro_set_stdres(p));
+ }
+
+ /* Return the filter */
+ if (m == inst_stat_get_filter) {
+ i1proimp *imp = (i1proimp *)p->m;
+ inst_opt_filter *filt;
+ va_list args;
+
+ va_start(args, m);
+ filt = va_arg(args, inst_opt_filter *);
+ va_end(args);
+
+ *filt = inst_opt_filter_none;
+
+ if (imp->physfilt == 0x82)
+ *filt = inst_opt_filter_UVCut;
+
+ return inst_ok;
+ }
+
+ /* Use default implementation of other inst_opt_type's */
+ {
+ inst_code rv;
+ va_list args;
+
+ va_start(args, m);
+ rv = inst_get_set_opt_def(pp, m, args);
+ va_end(args);
+
+ return rv;
+ }
+}
+
+/* Destroy ourselves */
+static void
+i1pro_del(inst *pp) {
+ i1pro *p = (i1pro *)pp;
+
+ del_i1proimp(p);
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ free(p);
+}
+
+/* Constructor */
+extern i1pro *new_i1pro(icoms *icom, instType itype) {
+ i1pro *p;
+ int rv;
+ if ((p = (i1pro *)calloc(sizeof(i1pro),1)) == NULL) {
+ a1loge(icom->log, 1, "new_i1pro: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(icom->log);
+
+ /* Inst methods */
+ p->init_coms = i1pro_init_coms;
+ p->init_inst = i1pro_init_inst;
+ p->capabilities = i1pro_capabilities;
+ p->get_serial_no = i1pro_get_serial_no;
+ p->check_mode = i1pro_check_mode;
+ p->set_mode = i1pro_set_mode;
+ p->get_set_opt = i1pro_get_set_opt;
+ p->read_strip = i1pro_read_strip;
+ p->read_sample = i1pro_read_sample;
+ p->read_refrate = i1pro_read_refrate;
+ p->get_n_a_cals = i1pro_get_n_a_cals;
+ p->calibrate = i1pro_calibrate;
+ p->interp_error = i1pro_interp_error;
+ p->del = i1pro_del;
+
+ p->icom = icom;
+ p->itype = icom->itype;
+
+ i1pro_determine_capabilities(p);
+
+ if ((rv = add_i1proimp(p)) != I1PRO_OK) {
+ free(p);
+ a1loge(icom->log, 1, "new_i1pro: error %d creating i1proimp\n",rv);
+ return NULL;
+ }
+
+ return p;
+}
+
diff --git a/spectro/i1pro.h b/spectro/i1pro.h
new file mode 100644
index 0000000..4366e4f
--- /dev/null
+++ b/spectro/i1pro.h
@@ -0,0 +1,56 @@
+#ifndef I1PRO_H
+
+/*
+ * Argyll Color Correction System
+ *
+ * Gretag i1Pro related defines
+ *
+ * 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.
+ */
+
+#include "inst.h"
+
+/* I1PRO communication object */
+struct _i1pro {
+ INST_OBJ_BASE
+
+ int dtype; /* Device type: 0 = ?? */
+
+ /* *** i1pro private data **** */
+ inst_mode cap; /* Instrument mode capability */
+ inst2_capability cap2; /* Instrument capability 2 */
+ inst3_capability cap3; /* Instrument capability 3 */
+
+ void *m; /* Implementation - i1proimp type */
+}; typedef struct _i1pro i1pro;
+
+/* Constructor */
+extern i1pro *new_i1pro(icoms *icom, instType itype);
+
+#define I1PRO_H
+#endif /* I1PRO_H */
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;
+}
+
+/* ----------------------------------------------------------------- */
diff --git a/spectro/i1pro_imp.h b/spectro/i1pro_imp.h
new file mode 100644
index 0000000..64296b2
--- /dev/null
+++ b/spectro/i1pro_imp.h
@@ -0,0 +1,1376 @@
+#ifndef I1PRO_IMP_H
+
+/*
+ * Argyll Color Correction System
+ *
+ * Gretag i1Pro implementation defines
+ *
+ * Author: Graeme W. Gill
+ * Date: 20/12/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.
+ */
+
+/* Implementation resources for i1pro driver */
+
+/* -------------------------------------------------- */
+/* Implementation class */
+
+typedef int i1pro_code; /* Type to use for error codes */
+
+/* I1PRO mode state. This is implementation data that */
+/* depends on the mode the instrument is in. */
+/* Each mode has a separate calibration, and configured instrument state. */
+
+typedef enum {
+ i1p_refl_spot = 0,
+ i1p_refl_scan = 1,
+ i1p_emiss_spot_na = 2,
+ i1p_emiss_spot = 3,
+ i1p_emiss_scan = 4,
+ i1p_amb_spot = 5,
+ i1p_amb_flash = 6,
+ i1p_trans_spot = 7,
+ i1p_trans_scan = 8,
+ i1p_no_modes = 9 /* Number of modes */
+} i1p_mode;
+
+struct _i1pro_state {
+ i1p_mode mode; /* Mode number */
+
+ /* Just one of the following 3 must always be set */
+ int emiss; /* flag - Emissive mode */
+ int trans; /* flag - Transmissive mode */
+ int reflective; /* flag - Reflective mode */
+
+ /* The following can be added to emiss */
+ int ambient; /* flag - Ambient mode */
+
+ /* The following can be added to any of the 3: */
+ int scan; /* flag - Scanning mode */
+ int adaptive; /* flag - adaptive mode */
+
+ /* The following can be added to scan: */
+ int flash; /* flag - Flash detection from scan mode */
+
+ /* Configuration & state information */
+ double targoscale; /* Optimal reading scale factor <= 1.0 */
+ /* Would determine scan sample rate, except we're not doing it that way! */
+ double targmaxitime;/* maximum integration time to aim for (ie. 2.0 sec) */
+ double targoscale2;/* Proportion of targoscale allowed to meed targmaxitime */
+ int gainmode; /* Gain mode, 0 = normal, 1 = high */
+ double inttime; /* Integration time */
+ double lamptime; /* Lamp turn on time */
+
+ double dadaptime; /* Target adaptive dark read time - sets number of readings */
+ double wadaptime; /* Target adaptive white/sample read time - sets number of readings */
+
+ double dcaltime; /* Target dark calibration time - sets number of readings */
+ double wcaltime; /* Target white calibration time - sets number of readings */
+
+ double dreadtime; /* Target dark on-the-fly cal time - sets number of readings */
+ double wreadtime; /* Target white/sample reading time - sets number of readings */
+
+ double maxscantime; /* Maximum scan time sets buffer size allocated */
+
+ double min_wl; /* Minimum wavelegth to report for this mode */
+
+ /* calibration information for this mode */
+ int wl_valid; /* wavelength calibration factor valid */
+ time_t wldate; /* Date/time of last wavelength calibration */
+ double wl_led_off; /* Wavelength LED reference spectrum current offset */
+
+ int dark_valid; /* dark calibration factor valid */
+ time_t ddate; /* Date/time of last dark calibration */
+ double dark_int_time; /* Integration time used for dark data */
+ double *dark_data; /* [-1 nraw] of dark level to subtract. Note that the dark value */
+ /* depends on integration time. */
+ int dark_gain_mode; /* Gain mode used for dark data */
+
+ int cal_valid; /* calibration factor valid */
+ time_t cfdate; /* Date/time of last cal factor calibration */
+ double *cal_factor[2]; /* [low res, high res][nwav] calibration scale factor for this mode */
+ double *white_data; /* [-1 nraw] linear absolute dark subtracted white data */
+ /* used to compute cal_factor */
+// double *cal_factor1, *cal_factor2; /* (Underlying tables for two resolutions) */
+
+ /* Adaptive emission/transparency black data */
+ int idark_valid; /* idark calibration factors valid */
+ time_t iddate; /* Date/time of last dark idark calibration */
+ double idark_int_time[4];
+ double **idark_data; /* [4][-1 nraw] of dark level for inttime/gains of : */
+ /* 0.01 norm, 4.0 norm, 0.01 high, 2.0 high */
+
+ int want_calib; /* Want White calibration at start */
+ int want_dcalib; /* Want Dark Calibration at start */
+
+ /* Display mode calibration state (emmis && !scan && !adaptive) */
+ int dispswap; /* 0 = default time, 1 = dark_int_time2, 2 = dark_int_time3 */
+ double done_dintsel; /* A display integration time selection has been done */
+ time_t diseldate; /* Date/time of last display integration time selection */
+ double dcaltime2; /* Target dark calibration time - sets number of readings */
+ double dark_int_time2; /* Integration time used for dark data 2 */
+ double *dark_data2; /* [-1 nraw] of dark level to subtract for dark_int_time2. */
+ double dcaltime3; /* Target dark calibration time - sets number of readings */
+ double dark_int_time3; /* Integration time used for dark data 3 */
+ double *dark_data3; /* [-1 nraw] of dark level to subtract for dark_int_time3. */
+
+}; typedef struct _i1pro_state i1pro_state;
+
+/* Pointers to the three tables that allow a raw to wave filter conversion */
+typedef struct {
+ int *index; /* [nwav] Matrix CCD sample starting index for each out wavelength */
+ int *nocoef; /* [nwav] Number of matrix cooeficients for each out wavelength */
+ double *coef; /* [nwav * mtx_nocoef] Matrix cooeficients to compute each wavelength */
+} i1pro_r2wtab;
+
+/* RevE capability bits */
+#define I1PRO_CAP2_AMBIENT 0x01 /* Has ambient measurement capability */
+#define I1PRO_CAP2_WL_LED 0x02 /* Has wavelenght LED */
+#define I1PRO_CAP2_UV_LED 0x04 /* Has Ultra Violet LED */
+#define I1PRO_CAP2_ZEB_RUL 0x08 /* Has zerbra ruler sensor */
+#define I1PRO_CAP2_IND_LED 0x10 /* Has indicator LEDs */
+#define I1PRO_CAP2_UV_FILT 0x20 /* Has Ultra Violet Filter */
+
+/* I1PRO implementation class */
+struct _i1proimp {
+ i1pro *p;
+
+ /* Misc. and top level */
+ struct _i1data *data; /* EEProm data container */
+ athread *th; /* Switch monitoring thread (NULL if not used) */
+ volatile int switch_count; /* Incremented in thread */
+ volatile int hide_switch; /* Set to supress switch event during read */
+ usb_cancelt cancelt; /* Token to allow cancelling an outstanding I/O */
+ volatile int th_term; /* Terminate thread on next return */
+ volatile int th_termed; /* Thread has terminated */
+ inst_opt_type trig; /* Reading trigger mode */
+ int noinitcalib; /* Disable initial calibration if not essential */
+ int highres; /* High resolution mode */
+ int hr_inited; /* High resolution has been initialized */
+
+ /* Current settings */
+ i1p_mode mmode; /* Current measurement mode selected */
+ i1pro_state ms[i1p_no_modes]; /* Mode state */
+ int spec_en; /* NZ to enable reporting of spectral data */
+ int uv_en; /* NZ to do UV reflective measurement */
+ /* ~~ change this to uv_mode of none, uv, strip1, 2pass */
+
+ double intclkp; /* Integration clock period (typically 68 usec) */
+ int subclkdiv; /* Sub clock divider ratio */
+ int subtmode; /* Reading 127 subtract mode (version 301 or greater) */
+
+ /* Current state of hardware, to avoid uncessary operations */
+ double c_inttime; /* Integration time */
+ double l_inttime; /* Last Integration time (for Rev A+/B quirk fix) */
+ double c_lamptime; /* Lamp turn on time */
+ int c_mcmode; /* special clock mode we're in (if rev >= 301) */
+ int c_intclocks; /* Number of integration clocks (set using setmeasparams() */
+ int c_lampclocks; /* Number of integration clocks (set using setmeasparams() */
+ int c_nummeas; /* Number of measurements (set using setmeasparams() */
+ int c_measmodeflags; /* Measurement mode flags (set using setmeasparams() */
+ int c_measmodeflags2; /* Measurement mode flags Rev E (set using setmeasparams() */
+ unsigned int slamponoff; /* The second last time the lamp was switched from on to off */
+ unsigned int llampoffon; /* The last time the lamp was switched from off to on, in msec */
+ unsigned int llamponoff; /* The last time the lamp was switched from on to off, in msec */
+
+
+ /* Values read from GetMisc() */
+ int fwrev; /* int - Firmware revision number, from getmisc() */
+ /* Used for internal switching ?? */
+ /* 101 = Rev A, 202 = Rev A update, 302 = Rev B, 502 = Rev D */
+ /* 629 = Rev E (i1pro2) */
+
+ int cpldrev; /* int - CPLD revision number in EEProm */
+ /* Not used internaly ???? */
+ /* 101 = Rev A, 2 = Rev A update, 301 = Rev B, 999 = Rev D */
+
+ unsigned char chipid[8]; /* HW serial number - Rev E */
+
+ int eesize; /* EEProm size in bytes */
+ int maxpve; /* Maximum +ve value of Sensor Data + 1 */
+ int powmode; /* Power mode status, 0 = high, 8 = low */
+
+ /* Values from i1pro2_getmeaschar() */
+ double intclkp2; /* Rev E Integration clock period (typically 36 usec) */
+ int subclkdiv2; /* Sub clock divider ratio (typically 136) */
+
+ /* Values read from GetMeasureParameters() - are these needed ? */
+ int r_intclocks; /* Number of integration clocks (read from instrument) */
+ int r_lampclocks; /* Number of lamp turn on sub-clocks (read from instrument) */
+ int r_nummeas; /* Number of measurements (read from instrument) */
+ int r_measmodeflags; /* Measurement mode flags (read from instrument) */
+
+
+ /* Information about the instrument from the EEprom */
+ int serno; /* serial number */
+ char sserno[14]; /* serial number as string */
+ int dom; /* Date of manufacture DDMMYYYY ? */
+ int capabilities; /* Capabilities flag */
+ /* Ambient capability if val & 0x6000 != 0 */
+ int physfilt; /* int - physical filter */
+ /* 0x80 == no filter */
+ /* 0x81 == emission only ?? */
+ /* 0x82 == UV filter */
+ int capabilities2; /* Rev E capabilities - set #defines above */
+ /* Also set for RevA-D */
+
+ /* Underlying calibration information */
+ int nsen; /* Raw + extra sample bands read = 128 for i1pro, 136 for Rev E */
+ /* Rev <= D have exactly 128 */
+ /* Rev E has 134, of which 128 are measurements. */
+ /* 5 are skipped at the start, and 1 at the end */
+ /* The first 4 are used as a dark consistency check. */
+ /* ie. 4 + 1 + 128 + 1 */
+ int nraw; /* Raw sample bands stored = 128 (Must be signed!) */
+ unsigned int nwav[2]; /* [low res, high res] cooked spectrum bands stored, ie = 36 */
+ double wl_short[2]; /* [low res, high res] cooked spectrum bands short wavelength, ie 380 */
+ double wl_long[2]; /* [low res, high res] cooked spectrum bands short wavelength, ie 730 */
+
+ unsigned int nlin0; /* Number in array */
+ double *lin0; /* Array of linearisation polinomial factors, normal gain. */
+
+ unsigned int nlin1; /* Number in array */
+ double *lin1; /* Array of linearisation polinomial factors, high gain. */
+
+ double min_int_time; /* Minimum integration time (secs) */
+ double max_int_time; /* Maximum integration time (secs) */
+
+ i1pro_r2wtab mtx[2][2]; /* Raw to wav filters [normal res, high res][emis/trans, reflective] */
+ /* These are all pointers to tables allocated below */
+
+ i1pro_r2wtab mtx_o; /* Underlying original filters from EEProm calibration info. */
+ i1pro_r2wtab mtx_c[2][2]; /* Underlying allocated for RevE wavelength and hi-res calibrated */
+
+ double *white_ref[2]; /* [low res, high res][nwav] White cal tile reflectance values */
+ double *emis_coef[2]; /* [low res, high res][nwav] Emission cal coefficients */
+ double *amb_coef[2]; /* [low res, high res][nwav] Ambient light cal values */
+ /* (compound with Emission), NULL if ambient not supported */
+
+ double **straylight[2]; /* [nwav][nwav] Stray light convolution matrix (Rev E) */
+
+ double highgain; /* High gain mode gain */
+ double scan_toll_ratio; /* Modifier of scan tollerance */
+
+ int sens_target; /* sensor optimal target value */
+ int sens_dark; /* sensor dark reference threshold */
+ int sens_sat0; /* Normal gain sensor saturated threshold */
+ int sens_sat1; /* High gain sensor saturated threshold */
+
+ /* RevA-D alternative to RevE calibration information */
+ rspl *raw2wav; /* Lookup from CCD index to wavelength, NULL until highres inited */
+
+ /* Rev E calibration information */
+ double wl_cal_inttime; /* Wavelength calibration integration time */
+ double wl_cal_min_level; /* Normalized wavelength calibration minumum peak level */
+ double wl_cal_fwhm; /* Wavelength cal expected FWHM (nm) */
+ double wl_cal_fwhm_tol; /* Wavelength cal expected FWHM tollerance (nm) */
+ double *wl_led_spec; /* Wavelength LED reference spectrum */
+ unsigned int wl_led_count; /* Wavelength LED reference spectrum number of entries */
+ double wl_led_ref_off; /* Wavelength LED reference spectrum ref. offset */
+ double wl_err_max; /* Wavelength error maximum value (ie. 5.0) */
+ double *wlpoly1, *wlpoly2; /* CCD bin to wavelength polinomial equations */
+ /* for reflective and emissive/transmissuce modes respectively. */
+
+ /* log variables */
+ int meascount; /* Total Measure (Emis/Remis/Ambient/Trans/Cal) count */
+ /* but not the pre-Remission dark calibration. */
+ time_t caldate; /* Remspotcal last calibration date */
+ int calcount; /* Remission spot measure count at last Remspotcal. */
+ double rpinttime; /* Last remision spot reading integration time */
+ int rpcount; /* Remission spot measure count */
+ int acount; /* Remission scan measure count (Or all scan ??) */
+ double lampage; /* Total lamp usage time in seconds (??) */
+
+ /* Trigger houskeeping & diagnostics */
+ int transwarn; /* Transmission calibration warning state */
+ int lo_secs; /* Seconds since last opened (from calibration file mod time) */
+ int msec; /* msec_time() at creation */
+ athread *trig_thread; /* Delayed trigger thread */
+ int trig_delay; /* Trigger delay in msec */
+ int tr_t1, tr_t2, tr_t3, tr_t4, tr_t5, tr_t6, tr_t7; /* Trigger/read timing diagnostics */
+ /* 1->2 = time to execute trigger */
+ /* 2->3 = time to between end trigger and start of first read */
+ /* 3->4 = time to exectute first read */
+ /* 6->5 = time between end of second last read and start of last read */
+ int trig_se; /* Delayed trigger icoms error */
+ i1pro_code trig_rv; /* Delayed trigger result */
+
+}; typedef struct _i1proimp i1proimp;
+
+/* Add an implementation structure */
+i1pro_code add_i1proimp(i1pro *p);
+
+/* Destroy implementation structure */
+void del_i1proimp(i1pro *p);
+
+/* ============================================================ */
+/* Error codes returned from i1pro_imp */
+
+/* Note: update i1pro_interp_error() and i1pro_interp_code() in i1pro.c */
+/* if anything of these #defines are added or subtracted */
+
+/* Fake Error codes */
+#define I1PRO_INTERNAL_ERROR 0x71 /* Internal software error */
+#define I1PRO_COMS_FAIL 0x72 /* Communication failure */
+#define I1PRO_UNKNOWN_MODEL 0x73 /* Not an i1pro */
+#define I1PRO_DATA_PARSE_ERROR 0x74 /* Read data parsing error */
+
+#define I1PRO_USER_ABORT 0x75 /* uicallback returned abort */
+#define I1PRO_USER_TRIG 0x76 /* uicallback retuned trigger */
+
+#define I1PRO_UNSUPPORTED 0x79 /* Unsupported function */
+#define I1PRO_CAL_SETUP 0x7A /* Cal. retry with correct setup is needed */
+
+/* Real error code */
+#define I1PRO_OK 0x00
+
+#define I1PRO_DATA_COUNT 0x01 /* count unexpectedly small */
+#define I1PRO_DATA_BUFSIZE 0x02 /* buffer too small */
+#define I1PRO_DATA_MAKE_KEY 0x03 /* creating key failed */
+#define I1PRO_DATA_MEMORY 0x04 /* memory alloc failure */
+#define I1PRO_DATA_KEYNOTFOUND 0x05 /* a key value wasn't found */
+#define I1PRO_DATA_WRONGTYPE 0x06 /* a key is the wrong type */
+#define I1PRO_DATA_KEY_CORRUPT 0x07 /* key table seems to be corrupted */
+#define I1PRO_DATA_KEY_COUNT 0x08 /* key table count is too big or small */
+#define I1PRO_DATA_KEY_UNKNOWN 0x09 /* unknown key type */
+#define I1PRO_DATA_KEY_MEMRANGE 0x0a /* key data is out of range of EEProm */
+#define I1PRO_DATA_KEY_ENDMARK 0x0b /* And end section marker was missing */
+
+/* HW errors */
+#define I1PRO_HW_HIGHPOWERFAIL 0x10 /* Switch to high power mode failed */
+#define I1PRO_HW_EE_SIZE 0x11 /* EEProm is too small */
+#define I1PRO_HW_EE_SHORTREAD 0x12 /* Read fewer EEProm bytes than expected */
+#define I1PRO_HW_EE_SHORTWRITE 0x13 /* Read fewer EEProm bytes than expected */
+#define I1PRO_HW_ME_SHORTREAD 0x14 /* Read measurement bytes than expected */
+#define I1PRO_HW_ME_ODDREAD 0x15 /* Read measurement bytes was not mult 256 */
+#define I1PRO_HW_SW_SHORTREAD 0x16 /* Read less bytes for Switch read than expected */
+#define I1PRO_HW_LED_SHORTWRITE 0x17 /* Wrote fewer LED sequence bytes than expected */
+#define I1PRO_HW_UNEX_SPECPARMS 0x18 /* Unexpacted spectral parameter values */
+#define I1PRO_HW_CALIBINFO 0x19 /* calibration info is missing or corrupted */
+#define I1PRO_WL_TOOLOW 0x1A /* WL calibration measurement too low */
+#define I1PRO_WL_SHAPE 0x1B /* WL calibration measurement shape is wrong */
+#define I1PRO_WL_ERR2BIG 0x1C /* WL calibration correction is too big */
+
+/* Sample read operation errors */
+#define I1PRO_RD_DARKREADINCONS 0x30 /* Dark calibration reading inconsistent */
+#define I1PRO_RD_SENSORSATURATED 0x31 /* Sensor is saturated */
+#define I1PRO_RD_DARKNOTVALID 0x32 /* Dark reading is not valid (too light) */
+#define I1PRO_RD_NEEDS_CAL 0x33 /* Mode needs calibration */
+#define I1PRO_RD_WHITEREADINCONS 0x34 /* White reference readings are inconsistent */
+#define I1PRO_RD_WHITEREFERROR 0x35 /* White reference reading error */
+#define I1PRO_RD_LIGHTTOOLOW 0x36 /* Light level is too low */
+#define I1PRO_RD_LIGHTTOOHIGH 0x37 /* Light level is too high */
+#define I1PRO_RD_SHORTMEAS 0x38 /* Measurment was too short */
+#define I1PRO_RD_READINCONS 0x39 /* Reading is inconsistent */
+#define I1PRO_RD_TRANSWHITERANGE 0x3A /* Transmission white reference is out of range */
+#define I1PRO_RD_NOTENOUGHPATCHES 0x3B /* Not enough patches */
+#define I1PRO_RD_TOOMANYPATCHES 0x3C /* Too many patches */
+#define I1PRO_RD_NOTENOUGHSAMPLES 0x3D /* Not enough samples per patch */
+#define I1PRO_RD_NOFLASHES 0x3E /* No flashes recognized */
+#define I1PRO_RD_NOAMBB4FLASHES 0x3F /* No ambient before flashes found */
+#define I1PRO_RD_NOREFR_FOUND 0x40 /* Unable to measure refresh rate */
+
+/* Internal errors */
+#define I1PRO_INT_NO_COMS 0x50
+#define I1PRO_INT_EETOOBIG 0x51 /* EEProm read size is too big */
+#define I1PRO_INT_ODDREADBUF 0x52 /* Measurment read buffer is not mult 256 */
+#define I1PRO_INT_SMALLREADBUF 0x53 /* Measurment read buffer too small */
+#define I1PRO_INT_INTTOOBIG 0x55 /* Integration time is too big */
+#define I1PRO_INT_INTTOOSMALL 0x56 /* Integration time is too small */
+#define I1PRO_INT_ILLEGALMODE 0x57 /* Illegal measurement mode selected */
+#define I1PRO_INT_WRONGMODE 0x58 /* In wrong mode for request */
+#define I1PRO_INT_ZEROMEASURES 0x59 /* Number of measurements requested is zero */
+#define I1PRO_INT_WRONGPATCHES 0x5A /* Number of patches to match is wrong */
+#define I1PRO_INT_MEASBUFFTOOSMALL 0x5B /* Measurement read buffer is too small */
+#define I1PRO_INT_NOTIMPLEMENTED 0x5C /* Support not implemented */
+#define I1PRO_INT_NOTCALIBRATED 0x5D /* Unexpectedely invalid calibration */
+#define I1PRO_INT_NOINTERPDARK 0x5E /* Need interpolated dark and don't have it */
+#define I1PRO_INT_THREADFAILED 0x5F /* Creation of thread failed */
+#define I1PRO_INT_BUTTONTIMEOUT 0x60 /* Switch status read timed out */
+#define I1PRO_INT_CIECONVFAIL 0x61 /* Creating spectral to CIE converted failed */
+#define I1PRO_INT_PREP_LOG_DATA 0x62 /* Error in preparing log data */
+#define I1PRO_INT_MALLOC 0x63 /* Error in mallocing memory */
+#define I1PRO_INT_CREATE_EEPROM_STORE 0x64 /* Error in creating EEProm store */
+#define I1PRO_INT_SAVE_SUBT_MODE 0x65 /* Can't save calibration if in subt mode */
+#define I1PRO_INT_NO_CAL_TO_SAVE 0x66 /* No calibration data to save */
+#define I1PRO_INT_EEPROM_DATA_MISSING 0x67 /* EEProm data is missing */
+#define I1PRO_INT_NEW_RSPL_FAILED 0x68 /* Creating RSPL object faild */
+#define I1PRO_INT_CAL_SAVE 0x69 /* Unable to save calibration to file */
+#define I1PRO_INT_CAL_RESTORE 0x6A /* Unable to restore calibration from file */
+#define I1PRO_INT_CAL_TOUCH 0x6B /* Unable to touch calibration file */
+#define I1PRO_INT_ADARK_INVALID 0x6C /* Adaptive dark calibration is invalid */
+#define I1PRO_INT_NO_HIGH_GAIN 0x6D /* Rev E mode doesn't support high gain mode */
+#define I1PRO_INT_ASSERT 0x6F /* Internal assert */
+
+int icoms2i1pro_err(int se);
+
+/* ============================================================ */
+/* High level implementatation */
+
+/* Initialise our software state from the hardware */
+i1pro_code i1pro_imp_init(i1pro *p);
+
+/* Return a pointer to the serial number */
+char *i1pro_imp_get_serial_no(i1pro *p);
+
+/* Return non-zero if capable of ambient mode */
+int i1pro_imp_ambient(i1pro *p);
+
+/* Set the measurement mode. It may need calibrating */
+i1pro_code i1pro_imp_set_mode(
+ i1pro *p,
+ i1p_mode mmode, /* i1pro mode to use */
+ inst_mode m); /* full mode mask */
+
+/* Implement get_n_a_cals */
+i1pro_code i1pro_imp_get_n_a_cals(i1pro *p, inst_cal_type *pn_cals, inst_cal_type *pa_cals);
+
+/* 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[100] /* Condition identifier (ie. white reference ID) */
+);
+
+/* Measure a patch or strip in the current mode. */
+i1pro_code i1pro_imp_measure(
+ i1pro *p,
+ ipatch *val, /* Pointer to array of instrument patch value */
+ int nvals, /* Number of values */
+ instClamping clamp /* Clamp XYZ/Lab to be +ve */
+);
+
+/* Measure the emissive refresh rate */
+i1pro_code i1pro_imp_meas_refrate(
+ i1pro *p,
+ double *ref_rate
+);
+
+/* 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);
+
+/* Compute standard res downsampling filters */
+/* mtx_index1, mtx_nocoef1, mtx_coef1 given the */
+/* current wl_led_off */
+i1pro_code i1pro2_compute_wav_filters(i1pro *p, int reflective);
+
+/* return nz if high res is supported */
+int i1pro_imp_highres(i1pro *p);
+
+/* Set to high resolution mode */
+i1pro_code i1pro_set_highres(i1pro *p);
+
+/* Set to standard resolution mode */
+i1pro_code i1pro_set_stdres(i1pro *p);
+
+/* Modify the scan consistency tollerance */
+i1pro_code i1pro_set_scan_toll(i1pro *p, double toll_ratio);
+
+
+/* Update the single remission calibration and instrument usage log */
+i1pro_code i1pro_update_log(i1pro *p);
+
+/* Save the reflective spot calibration information to the EEPRom data object. */
+/* Note we don't actually write to the EEProm here! */
+static i1pro_code i1pro_set_log_data(i1pro *p);
+
+/* Restore the reflective spot calibration information from the EEPRom */
+/* Always returns success, even if the restore fails */
+i1pro_code i1pro_restore_refspot_cal(i1pro *p);
+
+
+/* Save the calibration for all modes, stored on local system */
+i1pro_code i1pro_save_calibration(i1pro *p);
+
+/* Restore the all modes calibration from the local system */
+i1pro_code i1pro_restore_calibration(i1pro *p);
+
+/* Update the modification time on the file, so we can */
+/* track when the instrument was last open. */
+i1pro_code i1pro_touch_calibration(i1pro *p);
+
+/* ============================================================ */
+/* Intermediate routines - composite commands/processing */
+
+i1pro_code i1pro_establish_high_power(i1pro *p);
+
+/* 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 */
+);
+
+/* 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 */
+);
+
+/* Take a dark measurement */
+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 */
+);
+
+/* Take a white reference measurement - part 3 */
+/* Average, check, and convert to output wavelengths */
+i1pro_code i1pro_whitemeasure_3(
+ i1pro *p,
+ double *abswav1, /* Return array [nwav1] of abswav values (may be NULL) */
+ double *abswav2, /* Return array [nwav2] 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 target scale factor */
+ double **multimes, /* Multiple measurement results */
+ double darkthresh /* Raw dark threshold */
+);
+
+/* Take a white reference measurement */
+/* (Subtracts black and processes into wavelenths) */
+i1pro_code i1pro_whitemeasure(
+ i1pro *p,
+ double *abswav1, /* Return array [nwav1] of abswav values (may be NULL) */
+ double *abswav2, /* Return array [nwav2] 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 */
+);
+
+/* Process a single raw white reference measurement */
+/* (Subtracts black and processes into wavelenths) */
+i1pro_code i1pro_whitemeasure_buf(
+ i1pro *p,
+ double *abswav1, /* Return array [nwav1] of abswav values (may be NULL) */
+ double *abswav2, /* Return array [nwav2] 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 */
+);
+
+/* 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 */
+);
+
+/* Take a measurement reading using the current mode, part 1 */
+/* Converts to completely processed output readings. */
+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
+);
+
+/* 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 (secs) */
+ 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
+);
+
+/* Take a measurement reading using the current mode. */
+/* Converts to completely processed output readings. */
+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 */
+);
+
+/* Take a trial measurement reading using the current mode. */
+/* Used to determine if sensor is saturated, or not optimal */
+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 */
+ 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 */
+);
+
+/* Measurement modifier. Modifes the default current measurement mode */
+/* for the measurement. Bit 0x10 indicates that incandescent illumination */
+/* is possible, bit 0x20 indicates that any scan mode is to be ignored */
+typedef enum {
+ i1p_norm = 0x10, /* Normal measurement for current mode */
+ i1p2_UV = 0x01, /* Measurement using UV LED instead of incandescent (Rev E) */
+ i1p_cal = 0x32, /* No scan, with current mode illumination */
+ i1p_dark_cal = 0x23, /* No scan, no illumination */
+ i1p2_wl_cal = 0x24 /* No scan, wavelength reference LED illumination (Rev E) */
+} i1p_mmodif;
+
+/* Trigger a single measurement cycle. This could be a dark calibration, */
+/* a calibration, or a real measurement. Used to create the higher */
+/* level "calibrate" and "take reading" functions. */
+/* The setup for the operation is in the current mode state. */
+/* The called then needs to call i1pro_readmeasurement() */
+i1pro_code
+i1pro_trigger_one_measure(
+ i1pro *p,
+ int nummeas, /* 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 */
+);
+
+/* ============================================================ */
+/* lower level reading processing */
+
+/* Take a buffer full of sensor readings, and convert them to */
+/* absolute raw values. Linearise if Rev A..D */
+/* 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 256 * 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 dark threshold value (Rev E) */
+);
+
+/* 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 */
+);
+
+/* Take a single set of absolute linearised sensor values and */
+/* convert them back into i1pro Rev A..D raw reading values. */
+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 */
+);
+
+/* 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, /* Return number of readings measured */
+ 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) */
+);
+
+/* Recognise the required number of ref/trans patch locations, */
+/* and average the measurements within each patch. */
+/* Return flags zero if readings are consistent and not saturated. */
+/* Return flags nz with bit 1 set if the readings are not consistent */
+/* Return flags nz with bit 2 set if the readings are saturated */
+/* Return the highest individual element. */
+i1pro_code i1pro_extract_patches_multimeas(
+ i1pro *p,
+ int *flags, /* return flags */
+ double **pavg, /* return patch average [naptch][-1 nraw] */
+ int npatch, /* number of patches to recognise */
+ double **multimeas, /* Array of [nummeas][-1 nraw] value to extract from */
+ int nummeas, /* number of readings to recognise them from */
+ 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) */
+);
+
+/* Recognise any flashes in the readings, and */
+/* and average their values together as well as summing their duration. */
+/* Return nz on an error */
+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) */
+);
+
+/* Subtract one absraw array from another */
+/* 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] */
+);
+
+/* Convert an absraw array from raw wavelengths to output wavelenths */
+/* for the current resolution */
+void i1pro_absraw_to_abswav(
+ i1pro *p,
+ int highres,
+ int reflective,
+ int nummeas, /* Return number of readings measured */
+ double **abswav, /* Desination array [nwav] */
+ double **absraw /* Source array [-1 nraw] */
+);
+
+/* 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 */
+);
+
+/* 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 */
+);
+
+/* Check a reflective white measurement, and check that */
+/* it seems reasonable. Return inst_ok if it is, error if not. */
+i1pro_code i1pro_check_white_reference1(
+ i1pro *p,
+ double *abswav /* Measurement to check */
+);
+
+/* Compute a calibration factor given the reading of the white reference. */
+/* Return nz if any of the transmission wavelengths are low */
+int i1pro_compute_white_cal(
+ i1pro *p,
+ double *cal_factor0, /* [nwav0] Calibration factor to compute */
+ double *white_ref0, /* [nwav0] White reference to aim for, NULL for 1.0 */
+ double *white_read0, /* [nwav0] The white that was read */
+ double *cal_factor1, /* [nwav1] Calibration factor to compute */
+ double *white_ref1, /* [nwav1] White reference to aim for, NULL for 1.0 */
+ double *white_read1 /* [nwav1] The white that was read */
+);
+
+/* For adaptive mode, compute a new integration time and gain mode */
+/* in order to optimise the sensor values. */
+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 */
+);
+
+/* Compute the number of measurements needed, given the target */
+/* time and integration time. Will return 0 if target time is 0 */
+int i1pro_comp_nummeas(
+ i1pro *p,
+ double meas_time,
+ double int_time
+);
+
+/* Convert the dark interpolation data to a useful state */
+void i1pro_prepare_idark(i1pro *p);
+
+/* 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
+);
+
+/* Create or re-create high resolution mode references */
+i1pro_code i1pro_create_hr(i1pro *p);
+
+/* Set the noinitcalib mode */
+void i1pro_set_noinitcalib(i1pro *p, int v, int losecs);
+
+/* Set the trigger config */
+void i1pro_set_trig(i1pro *p, inst_opt_type trig);
+
+/* Return the trigger config */
+inst_opt_type i1pro_get_trig(i1pro *p);
+
+/* Set the trigger return */
+void i1pro_set_trigret(i1pro *p, int val);
+
+/* Switch thread handler */
+int i1pro_switch_thread(void *pp);
+
+/* ============================================================ */
+/* Low level i1pro commands */
+
+/* USB Commands */
+
+/* Reset the instrument */
+i1pro_code
+i1pro_reset(
+ struct _i1pro *p,
+ int mask /* reset mask ?. Known values ar 0x1f, 0x07, 0x01 */
+);
+
+/* Read from the EEProm */
+i1pro_code
+i1pro_readEEProm(
+ struct _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) */
+);
+
+/* 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) */
+);
+
+/* Get the miscelanious 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 */
+);
+
+/* 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 (Up to 65535) */
+ int *lampclocks, /* Number of lamp turn on sub-clocks (Up to 65535) */
+ int *nummeas, /* Number of measurements (Up to 65535) */
+ int *measmodeflags /* Measurement mode flags (4 bits, see below) */
+);
+
+/* These bits correspond with the instruction flags */
+#define I1PRO_MMF_SCAN 0x01 /* Scan mode bit, else spot mode */
+#define I1PRO_MMF_NOLAMP 0x02 /* No lamp mode, else use illumination lamp */
+#define I1PRO_MMF_LOWGAIN 0x04 /* Normal gain mode, else high gain */
+#define I1PRO_MMF_UNKN 0x08 /* Unknown. Not usually set */
+
+/* Scan mode continues measuring until the user releases the button. */
+/* (Does scan mode do the given number of readings as a minimum ???) */
+/* Spot mode does the given number of readings. */
+
+/* Set the measurement parameters */
+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 to make */
+ int measmodeflags /* Measurement mode flags */
+);
+
+/* Trigger a measurement after the delay in msec. */
+/* The actual return code will be in m->trig_rv after the delay */
+i1pro_code
+i1pro_triggermeasure(i1pro *p, int delay);
+
+
+/* Read a measurements results */
+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 */
+);
+
+
+/* Set the measurement clock mode */
+/* Version >= 301 only */
+i1pro_code
+i1pro_setmcmode(
+ i1pro *p,
+ int mcmode /* Measurement clock mode, 1..mxmcmode */
+);
+
+
+/* Get the current measurement clock mode */
+/* Return pointers may be NULL if not needed. */
+/* 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 */
+);
+
+/* ============================================================ */
+/* Low level Rev E commands */
+
+/* Get the EEProm size */
+i1pro_code
+i1pro2_geteesize(
+ i1pro *p,
+ int *eesize
+);
+
+/* Get the Chip ID (Also valid for Rev D) */
+/* Only returns a valid result after reading the EEProm ! */
+i1pro_code
+i1pro2_getchipid(
+ i1pro *p,
+ unsigned char chipid[8]
+);
+
+/* Get Extra Parameters */
+i1pro_code
+i1pro2_getmeaschar(
+ i1pro *p,
+ int *clkusec,
+ int *xraw,
+ int *nraw,
+ int *subdiv
+);
+
+/* These bits correspond with the instruction flags */
+#define I1PRO2_MMF_LAMP 0x0100 /* Use the Incandescent Lamp as the illuminant */
+#define I1PRO2_MMF_UV_LED 0x0200 /* Use the Ultra Violet LED as the illuminant */
+#define I1PRO2_MMF_WL_LED 0x0300 /* Use the Wavelength Reference LED as the illuminant */
+//#define I1PRO2_MMF_HIGHGAIN 0x0000 /* Rev E mode has no high gain mode ? */
+#define I1PRO2_MMF_SCAN 0x0001 /* Scan mode bit, else spot mode */
+#define I1PRO2_MMF_RULER_START 0x0004 /* Start ruler tracking in scan mode */
+#define I1PRO2_MMF_RULER_END 0x0008 /* End ruler tracking in scan mode */
+
+/* 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);
+
+/* 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);
+
+
+/* Get the UV before and after measurement voltage drop */
+i1pro_code
+i1pro2_getUVvolts(
+ i1pro *p,
+ int *before,
+ int *after
+);
+
+/* Terminate Ruler tracking (???) */
+/* The parameter seems to be always 0 ? */
+static int
+i1pro2_stop_ruler(void *pp, int parm);
+
+
+/* Send a LED sequence */
+static int
+i1pro2_indLEDseq(void *pp, unsigned char *buf, int size);
+
+/* Turn indicator LEDs off */
+static int
+i1pro2_indLEDoff(void *pp);
+
+// ~~~~9999
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Wait for a reply triggered by a button press */
+i1pro_code i1pro_waitfor_switch(i1pro *p, double top);
+
+/* Wait for a reply triggered by a button press (thread version) */
+i1pro_code i1pro_waitfor_switch_th(i1pro *p, double top);
+
+/* Terminate button handling ? */
+i1pro_code i1pro_terminate_switch(i1pro *p);
+
+/* -------------------------------------------------- */
+/* Key/Value storage */
+
+/* Calibration data storage class */
+/* The i1pro stores all it's calibration information */
+/* using a key/values arrangement. */
+/* We provide a place to store and retrieve that information here. */
+
+/* We haven't implemented a full set of functions - it's not possible */
+/* to create the store from scratch, re-allocate key/value entries, */
+/* resize entries or anything else of this sort. */
+
+
+/* Data Key identifiers */
+
+/* Note that array sizes are nominal. They could change with */
+/* driver and instrument changes. */
+
+/* "Log" data is keys 2710-2715, 271a-271d, 2724-2725 */
+
+/* The log data seems largly devoted to the last remission spot calibration */
+/* or reading, and some general statistics. */
+
+typedef enum {
+
+// Note 0x2710 = 10000
+ key_meascount = 0x2715, /* int, Total Measure (Emis/Remis/Ambient/Trans/Cal) count */
+ /* but not the pre-Remission dark calibration. */
+ key_darkreading = 0x271a, /* int[128] Remspotcal Dark data */
+ key_whitereading= 0x271b, /* int[128] Remspotcal White data */
+ key_gainmode = 0x271c, /* int - Remspotcal gain mode, Values 1 (normal) or 0 (high) */
+ key_inttime = 0x271d, /* double - Remspotcal integration time */
+ key_caldate = 0x2724, /* int date - Remspotcal last calibration date */
+ key_calcount = 0x2725, /* int - Remission spot measure Count at last Remspotcal. */
+ key_checksum = 0x2710, /* int - Log checksum */
+ key_rpinttime = 0x2711, /* double - Last remision spot reading integration time */
+ key_rpcount = 0x2712, /* int - Remission spot measure Count */
+ key_acount = 0x2713, /* int - Remission scan measure Count (??) */
+ key_lampage = 0x2714, /* double - Total lamp usage time (??) */
+
+/* Duplicate of above, keys += 0x3E8 (+1000) */
+// (0x2af8 = 11000)
+
+ key_2logoff = 0x03e8, /* Offset from first to second copy of log keys */
+
+
+/* Calibration parameters are 3e8-3ec, 44c-44e, 4b4-4b5, 4b7-4b8, 4bb-4bd, */
+/* 4c5-4c6, bb9-bba, bbf-bc6, fa0 */
+
+// Note 0x3e8 = 1000
+// 0x44c = 1100
+// 0x4b0 = 1200
+// 0xbb8 = 3000
+// 0xfa0 = 4000
+
+/* Linearisation uses Polinomial equation, ie: y = c0 + c1 * x + c2 * x^2 + c3 * x^3 etc. */
+/* and is applied to the raw (integer) sensor data. */
+
+ key_ng_lin = 0x03e8, /* double[4] */
+ /* Normal gain polinomial linearisation coefficients */
+
+ key_hg_lin = 0x03e9, /* double[4] */
+ /* High gain polinomial linearisation coefficients */
+
+ key_min_int_time= 0x04c5, /* double - Minumum integration time */
+ /* default 8.84000025689601900e-003 in EEProm */
+ /* Overwritten in MinilinoLowLevelDriver constructor: */
+ /* Default to 8.84000025689601900e-003 if cpldrev == 101 Ver A */
+ /* Default to 4.71600005403161050e-003 if cpldrev == 301 Ver B+ */
+
+ key_max_int_time= 0x04c6, /* double - Maximum integration time */
+ /* Typically 4.4563798904418945 */
+
+ key_mtx_index = 0x03ea, /* int[36] */
+ /* Matrix CCD sample index for each out wavelength */
+ /* 380 - 730nm */
+
+ key_mtx_nocoef = 0x03eb, /* int[36] */
+ /* Number of matrix cooeficients for each out wavelength */
+
+ key_mtx_coef = 0x03ec, /* double[36 x 16] */
+ /* Matrix cooeficients to compute each wavelength */
+
+ key_0bb9 = 0x0bb9, /* int - value typically -1*/
+ key_0bba = 0x0bba, /* int - value typically -1 */
+
+ key_white_ref = 0x044c, /* double[36] */
+ /* White calibration tile reflectance values */
+
+ key_emis_coef = 0x044d, /* double[36] */
+ /* Emission calibration coefficients */
+
+ key_amb_coef = 0x044e, /* double[36] */
+ /* Ambient light calibration values (compound with Emission) */
+ /* May be < 36, values -1.0 if Ambient is not supported */
+
+ key_0fa0 = 0x0fa0, /* int */
+ key_0bbf = 0x0bbf, /* int */
+
+ key_cpldrev = 0x0bc0, /* int - Firmware revision number */
+
+ key_0bc1 = 0x0bc1, /* int[5] */
+
+ key_capabilities= 0x0bc2, /* int */
+ /* Capabilities flag ? */
+ /* ie. has Ambient capability if val & 0x6000 != 0 */
+
+ key_0bc3 = 0x0bc3, /* int */
+
+ key_physfilt = 0x0bc4, /* int - physical filter */
+ /* 0x80 == no filter */
+ /* 0x82 == UV filter */
+
+ key_0bc5 = 0x0bc5, /* int */
+
+ key_0bc6 = 0x0bc6, /* double */
+
+ key_sens_target = 0x04b4, /* int - sensor optimal target value */
+ /* typical value 37000 */
+
+ key_sens_dark = 0x04b5, /* int - sensor dark reference threshold */
+ /* typically value 150 */
+
+ key_ng_sens_sat = 0x04b7, /* int */
+ /* Normal gain sensor saturated threshold */
+ /* typically value 45000 */
+
+ key_hg_sens_sat = 0x04b8, /* int */
+ /* High gain sensor saturated threshold */
+ /* typically value 45000 */
+
+ key_serno = 0x04bb, /* int - serial number */
+
+ key_dom = 0x04bc, /* int - unknown */
+ /* Possibly date of manufacture DDMMYYYY ? */
+ /* ie., decimal 10072002 would be 10/7/2002 ? */
+
+ key_hg_factor = 0x04bd, /* double */
+ /* High gain mode gain factor, ie 9.5572.. */
+
+
+ key2_chip_id = 0x2ee1, /* uchar[8], chip id */
+
+ key2_capabilities = 0x2ee2, /* int, capabilities bits */
+
+ key2_sens_target = 0x2eeb, /* int - sensor optimal target value ? */
+ /* typical value 30000 */
+
+ key2_sens_sat = 0x2eec, /* int - sensor saturation value ? */
+ /* typical value 55000 */
+
+ key2_uvcal_intt = 0x2ef9, /* double, UV calibration initial integration time */
+
+ key2_wlcal_intt = 0x2efa, /* double, wavelength calibration initial integration time */
+
+ key2_wlcal_minlev = 0x2efe, /* int, wavelength calibration normalized minimum peak level */
+
+ key2_wlcal_spec = 0x2f44, /* double[50], wavelength calibration reference spectrum */
+
+ key2_wlcal_ooff = 0x2f45, /* int, Reference WL Led spectral offset */
+
+ key2_wlcal_fwhm = 0x2f4e, /* double, wavelength calibration nominal fwhm (nm) */
+ key2_wlcal_fwhm_tol = 0x2f4f, /* double, wavelength calibration fwhm tollerance (nm) */
+
+ key2_wlcal_max = 0x2f46, /* double, wavelength calibration error limit, ie. 5.0 */
+
+ key2_wlpoly_1 = 0x2f62, /* double[4], CCD bin to wavelength polinomial #1 (normal) */
+ key2_wlpoly_2 = 0x2f63, /* double[4], CCD bin to wavelength polinomial #2 ??? */
+
+ key2_straylight = 0x2f58, /* int16[36][6] signed stray light values */
+ key2_straylight_scale = 0x2f59 /* double stray light scale factor */
+
+} i1key;
+
+
+/* Data type */
+typedef enum {
+ i1_dtype_unknown = 0,
+ i1_dtype_char = 1, /* Array of bytes */
+ i1_dtype_short = 2, /* 16 bit int, date */
+ i1_dtype_int = 3, /* 32 bit int, date */
+ i1_dtype_double = 4, /* 64 bit double, serialized as 32 bit float */
+ i1_dtype_section = 5 /* End of section marker */
+} i1_dtype;
+
+/* A key/value entry */
+struct _i1keyv {
+ void *data; /* Array of data */
+ unsigned int count; /* Count of data */
+ i1_dtype type; /* Type of data */
+ int addr; /* EEProm address */
+ int size; /* Size in bytes */
+ int key; /* 16 bit key */
+ struct _i1keyv *next; /* Link to next keyv */
+}; typedef struct _i1keyv i1keyv;
+
+struct _i1data {
+ /* private: */
+ i1pro *p;
+ i1proimp *m;
+
+ a1log *log; /* reference to instrument log */
+ i1keyv *head; /* Pointer to first in chain of keyv */
+ i1keyv *last; /* Pointer to last in chain of keyv */
+
+ /* public: */
+
+ /* Search the linked list for the given key */
+ /* Return NULL if not found */
+ i1keyv *(* find_key)(struct _i1data *d, i1key key);
+
+ /* Search the linked list for the given key and */
+ /* return it, or create the key if it doesn't exist. */
+ /* Return NULL on error */
+ i1keyv *(* make_key)(struct _i1data *d, i1key key);
+
+ /* Return type of data associated with key. Return i1_dtype_unknown if not found */
+ i1_dtype (*get_type)(struct _i1data *d, i1key key);
+
+ /* Return the number of data items in a keyv. Return 0 if not found */
+ unsigned int (*get_count)(struct _i1data *d, i1key key);
+
+ /* Return a int pointer to the 16 bit int data for the key. */
+ /* Optionally return the number of items too. */
+ /* Return NULL if not found or wrong type */
+ int *(*get_shorts)(struct _i1data *d, unsigned int *count, i1key key);
+
+ /* Return a pointer to the 32 bit int data for the key. */
+ /* Optionally return the number of items too. */
+ /* Return NULL if not found or wrong type */
+ int *(*get_ints)(struct _i1data *d, unsigned int *count, i1key key);
+
+ /* Return a pointer to the double data for the key. */
+ /* Optionally return the number of items too. */
+ /* Return NULL if not found or wrong type */
+ double *(*get_doubles)(struct _i1data *d, unsigned int *count, i1key key);
+
+
+ /* Return pointer to one of the int data for the key. */
+ /* Return NULL if not found or wrong type or out of range index. */
+ int *(*get_int)(struct _i1data *d, i1key key, unsigned int 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. */
+ double *(*get_double)(struct _i1data *d, i1key key, double *data, unsigned int index);
+
+
+ /* Un-serialize a char buffer into an i1key keyv */
+ /* (Don't change addr if its is -1) */
+ i1pro_code (*unser_ints)(struct _i1data *d, i1key key, int addr,
+ unsigned char *buf, unsigned int size);
+
+ /* Un-serialize a char buffer of floats into a double keyv */
+ /* (Don't change addr if its is -1) */
+ i1pro_code (*unser_doubles)(struct _i1data *d, i1key key, int addr,
+ unsigned char *buf, unsigned int size);
+
+
+ /* Serialize an i1key keyv into a char buffer. Error if it is outside the buffer */
+ i1pro_code (*ser_ints)(struct _i1data *d, i1keyv *k, unsigned char *buf, unsigned int size);
+
+ /* Serialize a double keyv as floats into a char buffer. Error if the buf is not big enough */
+ i1pro_code (*ser_doubles)(struct _i1data *d, i1keyv *k, unsigned char *buf, unsigned int size);
+
+ /* Initialise the data from the EEprom contents */
+ i1pro_code (*parse_eeprom)(struct _i1data *d, unsigned char *buf, unsigned int len, int extra);
+
+
+ /* Serialise all the keys up to the first marker into a buffer. */
+ i1pro_code (*prep_section1)(struct _i1data *d, unsigned char **buf, unsigned int *len);
+
+ /* Copy an array full of ints to the key (must be same size as existing) */
+ i1pro_code (*add_ints)(struct _i1data *d, i1key key, int *data, unsigned int count);
+
+ /* Copy an array full of doubles to the key (must be same size as existing) */
+ i1pro_code (*add_doubles)(struct _i1data *d, i1key key, double *data, unsigned int count);
+
+
+ /* Destroy ourselves */
+ void (*del)(struct _i1data *d);
+
+ /* Other utility methods */
+
+ /* Return the data type for the given key identifier */
+ i1_dtype (*det_type)(struct _i1data *d, i1key key);
+
+ /* Given an index starting at 0, return the matching key code */
+ /* for keys that get checksummed. Return 0 if outside range. */
+ i1key (*chsum_keys)(struct _i1data *d, int index);
+
+ /* Compute a checksum. */
+ int (*checksum)(struct _i1data *d, i1key keyoffset);
+
+}; typedef struct _i1data i1data;
+
+/* Constructor. Construct from the EEprom contents */
+extern i1data *new_i1data(i1proimp *m);
+
+#define I1PRO_IMP
+#endif /* I1PRO_IMP */
diff --git a/spectro/icoms.c b/spectro/icoms.c
new file mode 100644
index 0000000..0dc815c
--- /dev/null
+++ b/spectro/icoms.c
@@ -0,0 +1,484 @@
+
+ /* General instrument + serial I/O support */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 28/9/97
+ *
+ * Copyright 1997 - 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.
+ */
+
+/* These routines supliement the class code in ntio.c and unixio.c */
+/* with common and USB specific routines */
+/* Rename to icoms.c ?? */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <time.h>
+#include <signal.h>
+#if defined(UNIX)
+#include <termios.h>
+#include <errno.h>
+#include <unistd.h>
+#endif
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#else
+#include "sa_config.h"
+#endif
+#include "numsup.h"
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+
+
+/* ----------------------------------------------------- */
+
+/* Fake device */
+icompath icomFakeDevice = { "Fake Display Device" };
+
+/* Return the path corresponding to the port number, or NULL if out of range */
+static icompath *icompaths_get_path(
+ icompaths *p,
+ int port /* Enumerated port number, 1..n */
+) {
+ if (port == -99)
+ return &icomFakeDevice;
+
+ if (port <= 0 || port > p->npaths)
+ return NULL;
+
+ return p->paths[port-1];
+}
+
+static void icompaths_clear(icompaths *p) {
+
+ /* Free any old list */
+ if (p->paths != NULL) {
+ int i;
+ for (i = 0; i < p->npaths; i++) {
+ if (p->paths[i]->name != NULL)
+ free(p->paths[i]->name);
+#ifdef ENABLE_SERIAL
+ if (p->paths[i]->spath != NULL)
+ free(p->paths[i]->spath);
+#endif /* ENABLE_SERIAL */
+#ifdef ENABLE_USB
+ usb_del_usb_idevice(p->paths[i]->usbd);
+ hid_del_hid_idevice(p->paths[i]->hidd);
+#endif /* ENABLE_USB */
+ free(p->paths[i]);
+ }
+ free(p->paths);
+ p->npaths = 0;
+ p->paths = NULL;
+ }
+}
+
+/* Add an empty new path. */
+/* Return icom error */
+static int icompaths_add_path(icompaths *p) {
+ if (p->paths == NULL) {
+ if ((p->paths = (icompath **)calloc(sizeof(icompath *), 1 + 1)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "icompaths: calloc failed!\n");
+ return ICOM_SYS;
+ }
+ } else {
+ icompath **npaths;
+ if ((npaths = (icompath **)realloc(p->paths,
+ sizeof(icompath *) * (p->npaths + 2))) == NULL) {
+ a1loge(p->log, ICOM_SYS, "icompaths: realloc failed!\n");
+ return ICOM_SYS;
+ }
+ p->paths = npaths;
+ p->paths[p->npaths+1] = NULL;
+ }
+ if ((p->paths[p->npaths] = calloc(sizeof(icompath), 1)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "icompaths: malloc failed!\n");
+ return ICOM_SYS;
+ }
+ p->npaths++;
+ p->paths[p->npaths] = NULL;
+ return ICOM_OK;
+}
+
+#ifdef ENABLE_SERIAL
+
+/* Add a serial path */
+/* return icom error */
+static int icompaths_add_serial(icompaths *p, char *name, char *spath) {
+ int rv;
+
+ if ((rv = icompaths_add_path(p)) != ICOM_OK)
+ return rv;
+
+ if ((p->paths[p->npaths-1]->name = strdup(name)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "icompaths: strdup failed!\n");
+ return ICOM_SYS;
+ }
+ if ((p->paths[p->npaths-1]->spath = strdup(spath)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "icompaths: strdup failed!\n");
+ return ICOM_SYS;
+ }
+ return ICOM_OK;
+}
+
+#endif /* ENABLE_SERIAL */
+
+#ifdef ENABLE_USB
+
+/* Set an icompath details */
+/* return icom error */
+int icompath_set_usb(a1log *log, icompath *p, char *name, unsigned int vid, unsigned int pid,
+ int nep, struct usb_idevice *usbd, instType itype) {
+ int rv;
+
+ if ((p->name = strdup(name)) == NULL) {
+ a1loge(log, ICOM_SYS, "icompath: strdup failed!\n");
+ return ICOM_SYS;
+ }
+
+ p->nep = nep;
+ p->vid = vid;
+ p->pid = pid;
+ p->usbd = usbd;
+ p->itype = itype;
+
+ return ICOM_OK;
+}
+
+/* Add a usb path. usbd is taken, others are copied. */
+/* return icom error */
+static int icompaths_add_usb(icompaths *p, char *name, unsigned int vid, unsigned int pid,
+ int nep, struct usb_idevice *usbd, instType itype) {
+ int rv;
+
+ if ((rv = icompaths_add_path(p)) != ICOM_OK)
+ return rv;
+
+ return icompath_set_usb(p->log, p->paths[p->npaths-1], name, vid, pid, nep, usbd, itype);
+
+ return ICOM_OK;
+}
+
+/* Add an hid path. hidd is taken, others are copied. */
+/* return icom error */
+static int icompaths_add_hid(icompaths *p, char *name, unsigned int vid, unsigned int pid,
+ int nep, struct hid_idevice *hidd, instType itype) {
+ int rv;
+
+ if ((rv = icompaths_add_path(p)) != ICOM_OK)
+ return rv;
+
+ if ((p->paths[p->npaths-1]->name = strdup(name)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "icompaths: strdup failed!\n");
+ return ICOM_SYS;
+ }
+
+ p->paths[p->npaths-1]->nep = nep;
+ p->paths[p->npaths-1]->vid = vid;
+ p->paths[p->npaths-1]->pid = pid;
+ p->paths[p->npaths-1]->hidd = hidd;
+ p->paths[p->npaths-1]->itype = itype;
+
+ return ICOM_OK;
+}
+#endif /* ENABLE_USB */
+
+static void icompaths_del(icompaths *p) {
+ icompaths_clear(p);
+
+ p->log = del_a1log(p->log); /* Unreference it */
+ free(p);
+}
+
+int icompaths_refresh_paths(icompaths *p); /* Defined in platform specific */
+
+/* Allocate an icom paths and set it to the list of available devices */
+/* Return NULL on error */
+icompaths *new_icompaths(a1log *log) {
+ icompaths *p;
+ if ((p = (icompaths *)calloc(sizeof(icompaths), 1)) == NULL) {
+ a1loge(log, ICOM_SYS, "new_icompath: calloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(log);
+
+ p->clear = icompaths_clear;
+ p->refresh = icompaths_refresh_paths;
+ p->get_path = icompaths_get_path;
+#ifdef ENABLE_SERIAL
+ p->add_serial = icompaths_add_serial;
+#endif /* ENABLE_SERIAL */
+#ifdef ENABLE_USB
+ p->add_usb = icompaths_add_usb;
+ p->add_hid = icompaths_add_hid;
+#endif /* ENABLE_USB */
+ p->del = icompaths_del;
+
+ if (icompaths_refresh_paths(p)) {
+ a1loge(log, ICOM_SYS, "new_icompaths: icompaths_refresh_paths failed!\n");
+ return NULL;
+ }
+
+ return p;
+}
+
+
+/* ----------------------------------------------------- */
+
+/* Copy an icompath into an icom */
+/* return icom error */
+static int icom_copy_path_to_icom(icoms *p, icompath *pp) {
+ int rv;
+
+ if ((p->name = strdup(pp->name)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "copy_path_to_icom: malloc name failed\n");
+ return ICOM_SYS;
+ }
+#ifdef ENABLE_SERIAL
+ if (pp->spath != NULL) {
+ if ((p->spath = strdup(pp->spath)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "copy_path_to_icom: malloc spath failed\n");
+ return ICOM_SYS;
+ }
+ } else {
+ p->spath = NULL;
+ }
+#endif /* ENABLE_SERIAL */
+#ifdef ENABLE_USB
+ p->nep = pp->nep;
+ p->vid = pp->vid;
+ p->pid = pp->pid;
+ if ((rv = usb_copy_usb_idevice(p, pp)) != ICOM_OK)
+ return rv;
+ if ((rv = hid_copy_hid_idevice(p, pp)) != ICOM_OK)
+ return rv;
+#endif
+ p->itype = pp->itype;
+
+ return ICOM_OK;
+}
+
+/* Return the port type */
+static icom_type icoms_port_type(
+icoms *p
+) {
+#ifdef ENABLE_USB
+ if (p->hidd != NULL)
+ return icomt_hid;
+ if (p->usbd != NULL)
+ return icomt_usb;
+#endif
+ return icomt_serial;
+}
+
+/* ----------------------------------------------------- */
+
+/* Include the icoms implementation dependent function implementations */
+#ifdef NT
+# include "icoms_nt.c"
+#endif
+#if defined(UNIX)
+# include "icoms_ux.c"
+#endif
+
+/* write and read convenience function */
+/* return icom error */
+static int
+icoms_write_read(
+icoms *p,
+char *wbuf, /* Write puffer */
+char *rbuf, /* Read buffer */
+int bsize, /* Buffer size */
+char tc, /* Terminating characer */
+int ntc, /* Number of terminating characters */
+double tout
+) {
+ int rv = ICOM_OK;
+
+ a1logd(p->log, 8, "icoms_write_read: called with '%s'\n",icoms_fix(wbuf));
+
+ if (p->write == NULL || p->read == NULL) { /* Neither serial nor USB ? */
+ a1loge(p->log, ICOM_NOTS, "icoms_write_read: Neither serial nor USB device!\n");
+ return ICOM_NOTS;
+ }
+
+#ifdef ENABLE_SERIAL
+ /* Flush any stray chars if serial */
+ if (p->usbd == NULL && p->hidd == NULL) {
+ int debug = p->log->debug;
+ if (debug < 8)
+ p->log->debug = 0;
+ for (; rv == ICOM_OK;) /* Until we get a timeout */
+ rv = p->read(p, rbuf, bsize, '\000', 100000, 0.01);
+ p->log->debug = debug;
+ rv = ICOM_OK;
+ }
+#endif
+
+ /* Write the write data */
+ rv = p->write(p, wbuf, tout);
+
+ /* Return error if coms */
+ if (rv != ICOM_OK) {
+ a1logd(p->log, 8, "icoms_write_read: failed - returning 0x%x\n",rv);
+ return rv;
+ }
+
+ /* Read response */
+ rv = p->read(p, rbuf, bsize, tc, ntc, tout);
+
+ a1logd(p->log, 8, "icoms_write_read: returning 0x%x\n",rv);
+
+ return rv;
+}
+
+/* icoms Constructor */
+/* Return NULL on error */
+icoms *new_icoms(
+ icompath *ipath, /* instrument to open */
+ a1log *log /* log to take reference from, NULL for default */
+) {
+ icoms *p;
+ if ((p = (icoms *)calloc(sizeof(icoms), 1)) == NULL) {
+ a1loge(log, ICOM_SYS, "new_icoms: calloc failed!\n");
+ return NULL;
+ }
+
+ if ((p->name = strdup(ipath->name)) == NULL) {
+ a1loge(log, ICOM_SYS, "new_icoms: strdup failed!\n");
+ return NULL;
+ }
+ p->itype = ipath->itype;
+
+ /* Copy ipath info to this icoms */
+ if (icom_copy_path_to_icom(p, ipath)) {
+ free(p);
+ return NULL;
+ }
+
+#ifdef ENABLE_SERIAL
+#ifdef NT
+ p->phandle = NULL;
+#endif
+#ifdef UNIX
+ p->fd = -1;
+#endif
+ p->fc = fc_nc;
+ p->br = baud_nc;
+ p->py = parity_nc;
+ p->sb = stop_nc;
+ p->wl = length_nc;
+#endif /* ENABLE_SERIAL */
+
+ p->lserr = 0;
+ p->tc = -1;
+
+ p->log = new_a1log_d(log);
+ p->debug = p->log->debug; /* Legacy code */
+
+ p->close_port = icoms_close_port;
+
+ p->port_type = icoms_port_type;
+#ifdef ENABLE_SERIAL
+ p->set_ser_port = icoms_set_ser_port;
+#endif /* ENABLE_SERIAL */
+
+ p->write = NULL; /* Serial open or set_methods will set */
+ p->read = NULL;
+ p->write_read = icoms_write_read;
+
+ p->del = icoms_del;
+
+#ifdef ENABLE_USB
+ usb_set_usb_methods(p);
+ hid_set_hid_methods(p);
+#endif /* ENABLE_USB */
+
+ return p;
+}
+
+/* ---------------------------------------------------------------------------------*/
+/* utilities */
+
+/* Emit a "normal" beep */
+void normal_beep() {
+ /* 0msec delay, 1.0KHz for 200 msec */
+ msec_beep(0, 1000, 200);
+}
+
+/* Emit a "good" beep */
+void good_beep() {
+ /* 0msec delay, 1.2KHz for 200 msec */
+ msec_beep(0, 1200, 200);
+}
+
+/* Emit a "bad" double beep */
+void bad_beep() {
+ /* 0msec delay, 800Hz for 200 msec */
+ msec_beep(0, 800, 200);
+ /* 500msec delay, 800Hz for 200 msec */
+ msec_beep(350, 800, 200);
+}
+
+/* Convert control chars to ^[A-Z] notation in a string */
+/* Can be called 5 times without reusing the static buffer */
+char *
+icoms_fix(char *ss) {
+ static unsigned char buf[5][1005];
+ static int ix = 0;
+ unsigned char *d;
+ unsigned char *s = (unsigned char *)ss;
+ if (++ix >= 5)
+ ix = 0;
+ for(d = buf[ix]; (d - buf[ix]) < 1000;) {
+ if (*s < ' ' && *s > '\000') {
+ *d++ = '^';
+ *d++ = *s++ + '@';
+ } else if (*s >= 0x80) {
+ *d++ = '\\';
+ *d++ = '0' + ((*s >> 6) & 0x3);
+ *d++ = '0' + ((*s >> 3) & 0x7);
+ *d++ = '0' + ((*s++ >> 0) & 0x7);
+ } else {
+ *d++ = *s++;
+ }
+ if (s[-1] == '\000')
+ break;
+ }
+ *d++ = '.';
+ *d++ = '.';
+ *d++ = '.';
+ *d++ = '\000';
+ return (char *)buf[ix];
+}
+
+/* Convert a limited binary buffer to a list of hex */
+char *
+icoms_tohex(unsigned char *s, int len) {
+ static char buf[64 * 3 + 10];
+ int i;
+ char *d;
+
+ buf[0] = '\000';
+ for(i = 0, d = buf; i < 64 && i < len; i++, s++) {
+ sprintf(d, "%s%02x", i > 0 ? " " : "", *s);
+ d += strlen(d);
+ }
+ if (i < len)
+ sprintf(d, " ...");
+
+ return buf;
+}
+
diff --git a/spectro/icoms.h b/spectro/icoms.h
new file mode 100644
index 0000000..aac351a
--- /dev/null
+++ b/spectro/icoms.h
@@ -0,0 +1,484 @@
+
+#ifndef ICOMS_H
+
+ /* An abstracted instrument serial and USB communication class. */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 2006/4/20
+ *
+ * Copyright 1996 - 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.
+ *
+ * Derived from serio.h
+ */
+
+/*
+
+ Notes: The user keyboard/interrupt handling is really broken.
+ It's not noticed most of the time because mostly keys are only
+ hit at the expected times. Serial instruments (X-Rite) are more
+ forgiving in interrupting coms to/from the instrument, as well
+ as being long winded and needing abort. For USB insruments it's
+ not necessarily robust to interrupt or terminate after a give
+ USB transaction. The handling of user commands and aborts
+ is not consistent either, possibly leaving some instruments
+ suseptable to bodgy results due to an unrecognised aborted
+ command. Really, the instrument driver should deterimine
+ at what points an operation can be aborted, and how to recover.
+
+ Because the instrument itself can be a source of commands,
+ some way of waiting for instrument or user input is needed.
+ Could a threaded approach with instrument abort work ?
+
+*/
+
+/* Some MSWin specific stuff is in icoms, and used by usbio & hidio */
+#if defined (NT)
+#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0501
+# if defined(_WIN32_WINNT)
+# undef _WIN32_WINNT
+# endif
+# define _WIN32_WINNT 0x0501
+#endif
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif
+
+#if defined(__APPLE__)
+#include <IOKit/usb/IOUSBLib.h>
+#endif
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#undef QUIET_MEMCHECKERS /* #define to memset coms read buffers before reading */
+
+typedef struct _icompath icompath;
+typedef struct _icompaths icompaths;
+typedef struct _icoms icoms;
+
+#ifdef ENABLE_USB
+
+struct usb_idevice; /* Forward declarations */
+struct hid_idevice;
+
+/* Information about each end point */
+typedef struct {
+ int valid; /* Flag, nz if this endpoint is valid */
+ int addr; /* Address of end point */
+ int packetsize; /* The max packet size */
+ int type; /* 2 = bulk, 3 = interrupt */
+ int interface; /* interface number */
+ int pipe; /* pipe number (1..N, OS X only) */
+} usb_ep;
+
+#define ICOM_EP_TYPE_BULK 2
+#define ICOM_EP_TYPE_INTERRUPT 3
+
+#include "usbio.h"
+#include "hidio.h"
+
+#endif /* ENABLE_USB */
+
+/* - - - - - - - - - - - - - - - - - - - - */
+
+/* Store information about a possible instrument communication path */
+/* (Note a path doesn't have a reference to icompaths or its' log) */
+struct _icompath{
+ char *name; /* instance description */
+#ifdef ENABLE_SERIAL
+ char *spath; /* Serial device path */
+#endif
+#ifdef ENABLE_USB
+ int nep; /* Number of end points */
+ unsigned int vid, pid; /* USB vendor and product id's */
+ struct usb_idevice *usbd; /* USB port, NULL if not USB */
+ struct hid_idevice *hidd; /* HID port, NULL if not HID */
+#endif /* ENABLE_USB */
+ instType itype; /* Type of instrument if known */
+};
+
+extern icompath icomFakeDevice; /* Declare fake device */
+
+/* The available instrument communication paths */
+struct _icompaths {
+ int npaths; /* Number of paths */
+ icompath **paths; /* Paths if any */
+ a1log *log; /* Verbose, debuge & error logging */
+
+ /* Re-populate the available instrument list */
+ /* return icom error */
+ int (*refresh)(struct _icompaths *p);
+
+#ifdef ENABLE_SERIAL
+ /* Add a serial path. path is copied. Return icom error */
+ int (*add_serial)(struct _icompaths *p, char *name, char *spath);
+#endif /* ENABLE_SERIAL */
+
+#ifdef ENABLE_USB
+ /* Add a usb path. usbd is taken, others are copied. Return icom error */
+ int (*add_usb)(struct _icompaths *p, char *name, unsigned int vid, unsigned int pid,
+ int nep, struct usb_idevice *usbd, instType itype);
+
+ /* Add an hid path. hidd is taken, others are copied. Return icom error */
+ int (*add_hid)(struct _icompaths *p, char *name, unsigned int vid, unsigned int pid,
+ int nep, struct hid_idevice *hidd, instType itype);
+#endif /* ENABLE_USB */
+
+ /* Return the path corresponding to the port number, or NULL if out of range */
+ icompath *(*get_path)(
+ struct _icompaths *p,
+ int port); /* Enumerated port number, 1..n */
+
+ /* Clear all the paths */
+ void (*clear)(struct _icompaths *p);
+
+ /* We're done */
+ void (*del)(struct _icompaths *p);
+
+};
+
+/* Allocate an icom paths and set it to the list of available devices */
+/* Return NULL on error */
+icompaths *new_icompaths(a1log *log);
+
+
+/* - - - - - - - - - - - */
+/* Serial related stuff */
+
+/* Flow control */
+typedef enum {
+ fc_nc = 0, /* not configured/default */
+ fc_none,
+ fc_XonXOff,
+ fc_Hardware
+} flow_control;
+
+/* baud rate available on all systems */
+typedef enum {
+ baud_nc = 0, /* Not Configured */
+ baud_110 = 1,
+ baud_300 = 2,
+ baud_600 = 3,
+ baud_1200 = 4,
+ baud_2400 = 5,
+ baud_4800 = 6,
+ baud_9600 = 7,
+ baud_14400 = 8,
+ baud_19200 = 9,
+ baud_38400 = 10,
+ baud_57600 = 11,
+ baud_115200 = 12
+} baud_rate;
+
+/* Possible parity */
+typedef enum {
+ parity_nc,
+ parity_none,
+ parity_odd,
+ parity_even
+} parity;
+
+/* Possible stop bits */
+typedef enum {
+ stop_nc,
+ stop_1,
+ stop_2
+} stop_bits;
+
+/* Possible word length */
+typedef enum {
+ length_nc,
+ length_5,
+ length_6,
+ length_7,
+ length_8
+} word_length;
+
+/* USB open/handling/buf workaround flags */
+typedef enum {
+ icomuf_none = 0x0000,
+ icomuf_detach = 0x0001, /* Attempt to detach from system driver */
+ icomuf_no_open_clear = 0x0001, /* Don't send a clear_halt after opening the port */
+ icomuf_reset_before_close = 0x0004, /* Reset port before closing it */
+ icomuf_resetep_before_read = 0x0008 /* Do a usb_resetep before each ep read */
+} icomuflags;
+
+/* Type of port */
+typedef enum {
+ icomt_serial, /* Serial port */
+ icomt_usb, /* USB port */
+ icomt_hid /* HID (USB) port */
+} icom_type;
+
+/* Status bits/return values */
+#define ICOM_OK 0x000000 /* No error */
+
+#define ICOM_NOTS 0x001000 /* Not supported */
+#define ICOM_SIZE 0x002000 /* Request/response size exceeded limits */
+#define ICOM_TO 0x004000 /* Timed out */
+#define ICOM_SHORT 0x008000 /* Number of bytes wasn't read/written */
+#define ICOM_CANC 0x010000 /* Was cancelled */
+#define ICOM_SYS 0x020000 /* System error (ie. malloc, system call fail) */
+#define ICOM_VER 0x040000 /* Version error - need up to date kernel driver */
+
+#define ICOM_USBR 0x000100 /* Unspecified USB read error */
+#define ICOM_USBW 0x000200 /* Unspecified USB write error */
+#define ICOM_SERR 0x000400 /* Unspecified Serial read error */
+#define ICOM_SERW 0x000800 /* Unspecified Serial write error */
+
+#define ICOM_XRE 0x000040 /* Xmit shift reg empty */
+#define ICOM_XHE 0x000020 /* Xmit hold reg empty */
+#define ICOM_BRK 0x000010 /* Break detected */
+#define ICOM_FER 0x000008 /* Framing error */
+#define ICOM_PER 0x000004 /* Parity error */
+#define ICOM_OER 0x000002 /* Overun error */
+#define ICOM_DRY 0x000001 /* Recv data ready */
+
+
+
+/* Cancelation token. */
+typedef struct _usb_cancelt usb_cancelt;
+
+#ifdef ENABLE_USB
+void usb_init_cancel(usb_cancelt *p);
+void usb_uninit_cancel(usb_cancelt *p);
+
+#endif
+
+struct _icoms {
+ /* Private: */
+
+ /* Copy of some of icompath contents: */
+ char *name; /* Device description */
+ instType itype; /* Type of instrument if known */
+
+ int is_open; /* Flag, NZ if this port is open */
+
+#ifdef ENABLE_SERIAL
+
+ /* Serial port parameters */
+ char *spath; /* Serial port path */
+#if defined (MSDOS)
+ unsigned short porta; /* Hardware port address */
+#endif
+#if defined (NT)
+ HANDLE phandle; /* NT handle */
+#endif
+#if defined (UNIX) || defined(__APPLE__)
+ int fd; /* Unix file descriptor */
+#endif
+ flow_control fc;
+ baud_rate br;
+ parity py;
+ stop_bits sb;
+ word_length wl;
+
+#endif /* ENABLE_SERIAL */
+
+#ifdef ENABLE_USB
+
+ /* USB port parameters */
+ struct usb_idevice *usbd; /* USB port - copy of ppath->usbd */
+#ifndef NATIVE_USB /* Hmm. could put all this within usb_idevice if not #defined ? */
+# ifdef USE_LIBUSB1
+ libusb_device_handle *usbh;
+# else
+ struct usb_dev_handle *usbh;
+# endif
+#endif
+ icomuflags uflags; /* Bug workaround flags */
+
+ unsigned int vid, pid; /* USB vendor and product id's, used to distiguish instruments */
+ int nconfig; /* Number of configurations */
+ int config; /* Config this info applies to (always 1 ?) */
+ int cconfig; /* Current configuration (0 or 1 ?) */
+ int nifce; /* Number of interfaces */
+ int nep; /* Number of end points */
+ int wr_ep, rd_ep; /* Default end points to use for "serial" read/write */
+ int rd_qa; /* Read quanta size */
+
+ usb_ep ep[32]; /* Information about each end point for general usb i/o */
+
+/* Macro to access end point information */
+#define EPINFO(addr) ep[((addr >> 3) & 0x10) + (addr & 0x0f)]
+
+ /* HID port parameters */
+ struct hid_idevice *hidd; /* HID port - copy of ppath->hidd */
+
+#endif /* ENABLE_USB */
+
+ /* General parameters */
+ int lserr; /* Last serial communication error code */
+ int tc; /* Current serial parser termination character (-1 if not set) */
+ a1log *log; /* Debug & Error log */
+ int debug; /* legacy - Flag, nz to print instrument io info to stderr */
+
+ /* Linked list to automate SIGKILL cleanup */
+ struct _icoms *next;
+
+ /* Public: */
+
+ /* Return the port type */
+ icom_type (*port_type)(struct _icoms *p);
+
+#ifdef ENABLE_SERIAL
+ /* Select the serial communications port and characteristics */
+ /* return icom error */
+ int (*set_ser_port)(
+ struct _icoms *p,
+ flow_control fc, /* Flow control */
+ baud_rate baud,
+ parity parity,
+ stop_bits stop_bits,
+ word_length word_length);
+#endif /* ENABLE_SERIAL */
+
+#ifdef ENABLE_USB
+ /* Select the USB communications port and characteristics */
+ /* return icom error */
+ int (*set_usb_port)(
+ struct _icoms *p,
+ int config, /* Configuration */
+ int wr_ep, /* "serial" write end point */
+ int rd_ep, /* "serial" read end point */
+ icomuflags usbflags, /* Any special handling flags */
+ int retries, /* > 0 if we should retry set_configuration (100msec) */
+ char **pnames /* List of process names to try and kill before opening */
+ );
+
+ /* Select the HID communications port and characteristics */
+ /* return icom error */
+ int (*set_hid_port)(
+ struct _icoms *p,
+ icomuflags usbflags, /* Any special handling flags */
+ int retries, /* > 0 if we should retry set_configuration (100msec) */
+ char **pnames /* List of process names to try and kill before opening */
+ );
+#endif /* ENABLE_USB */
+
+ /* Close the port */
+ void (*close_port)(struct _icoms *p);
+
+ /* "Serial" write the characters in the buffer out */
+ /* Data will be written up to the terminating nul. */
+ /* return icom error */
+ int (*write)(
+ struct _icoms *p,
+ char *buf,
+ double tout); /* Timeout in seconds */
+
+ /* "Serial" read characters into the buffer */
+ /* The returned data will be terminated by a nul. */
+ /* return icom error */
+ int (*read)(
+ struct _icoms *p,
+ char *buf, /* Buffer to store characters read */
+ int bsize, /* Buffer size */
+ char tc, /* Terminating character */
+ int ntc, /* Number of terminating characters */
+ double tout); /* Timeout in seconds */
+
+ /* "Serial" write and read */
+ /* return icom error */
+ int (*write_read)(
+ struct _icoms *p,
+ char *wbuf, /* Write puffer */
+ char *rbuf, /* Read buffer */
+ int bsize, /* Buffer size */
+ char tc, /* Terminating characer */
+ int ntc, /* Number of terminating characters */
+ double tout); /* Timeout in seconds */
+
+ /* For a USB device, do a control message */
+ /* return icom error */
+ int (*usb_control)(struct _icoms *p,
+ int requesttype, /* 8 bit request type (USB bmRequestType) */
+ int request, /* 8 bit request code (USB bRequest) */
+ int value, /* 16 bit value (USB wValue, sent little endian) */
+ int index, /* 16 bit index (USB wIndex, sent little endian) */
+ unsigned char *rwbuf, /* Read or write buffer */
+ int rwsize, /* Bytes to read or write */
+ double tout); /* Timeout in seconds */
+
+ /* For a USB device, do a bulk or interrupt read from an end point */
+ /* return icom error */
+ int (*usb_read)(struct _icoms *p,
+ usb_cancelt *cancelt, /* Optionaly tokem that can be used to cancel */
+ int ep, /* End point address */
+ unsigned char *buf, /* Read buffer */
+ int bsize, /* Bytes to read or write */
+ int *bread, /* Bytes read */
+ double tout); /* Timeout in seconds */
+
+ /* For a USB device, do a bulk or interrupt write to an end point */
+ /* return icom error */
+ int (*usb_write)(struct _icoms *p,
+ usb_cancelt *cancelt, /* Optionaly tokem that can be used to cancel */
+ int ep, /* End point address */
+ unsigned char *wbuf, /* Write buffer */
+ int wsize, /* Bytes to or write */
+ int *bwritten, /* Bytes written */
+ double tout); /* Timeout in seconds */
+
+ /* Cancel a read/write in another thread */
+ /* return icom error */
+ int (*usb_cancel_io)(struct _icoms *p,
+ usb_cancelt *cantelt); /* Cancel handle */
+
+ /* Reset and end point toggle state to 0 */
+ /* return icom error */
+ int (*usb_resetep)(struct _icoms *p,
+ int ep); /* End point address */
+
+ /* Clear an end point halt */
+ /* return icom error */
+ int (*usb_clearhalt)(struct _icoms *p,
+ int ep); /* End point address */
+
+ /* For an HID device, read a message from the device. */
+ /* return icom error */
+ int (*hid_read)(struct _icoms *p,
+ unsigned char *buf, /* Read buffer */
+ int bsize, /* Bytes to read or write */
+ int *bread, /* Bytes read */
+ double tout); /* Timeout in seconds */
+
+ /* For an HID device, write a message to the device */
+ /* return icom error */
+ int (*hid_write)(struct _icoms *p,
+ unsigned char *wbuf, /* Write buffer */
+ int wsize, /* Bytes to or write */
+ int *bwritten, /* Bytes written */
+ double tout); /* Timeout in seconds */
+
+ /* Destroy ourselves */
+ void (*del)(struct _icoms *p);
+
+};
+
+/* Constructor */
+extern icoms *new_icoms(icompath *ipath, a1log *log);
+
+/* - - - - - - - - - - - - - - - - - - -- */
+/* Utilities */
+
+/* Convert control chars to ^[A-Z] notation in a string */
+char *icoms_fix(char *s);
+
+/* Convert a limited binary buffer to a list of hex */
+char *icoms_tohex(unsigned char *s, int len);
+
+#ifdef __cplusplus
+ }
+#endif
+
+#define ICOMS_H
+#endif /* ICOMS_H */
diff --git a/spectro/icoms_nt.c b/spectro/icoms_nt.c
new file mode 100644
index 0000000..ef364c4
--- /dev/null
+++ b/spectro/icoms_nt.c
@@ -0,0 +1,563 @@
+
+ /* Windows NT serial I/O class */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 28/9/97
+ *
+ * Copyright 1997 - 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.
+ */
+
+#include <conio.h>
+
+/* Create and return a list of available serial ports or USB instruments for this system. */
+/* We look at the registry key "HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM" */
+/* to determine serial ports, and use libusb to discover USB instruments. */
+/* Create and return a list of available serial ports or USB instruments for this system */
+/* return icom error */
+int icompaths_refresh_paths(icompaths *p) {
+ int rv, usbend = 0;
+ int i,j;
+ LONG stat;
+ HKEY sch; /* Serial coms handle */
+
+ a1logd(p->log, 8, "icoms_get_paths: called\n");
+
+ /* Clear any existing paths */
+ p->clear(p);
+
+#ifdef ENABLE_USB
+ if ((rv = hid_get_paths(p)) != ICOM_OK)
+ return rv;
+ if ((rv = usb_get_paths(p)) != ICOM_OK)
+ return rv;
+#endif /* ENABLE_USB */
+ usbend = p->npaths;
+
+ a1logd(p->log, 6, "icoms_get_paths: got %d paths, looking up the registry for serial ports\n",p->npaths);
+
+#ifdef ENABLE_SERIAL
+ /* Look in the registry for serial ports */
+ if ((stat = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\SERIALCOMM",
+ 0, KEY_READ, &sch)) != ERROR_SUCCESS) {
+ a1logd(p->log, 1, "icoms_get_paths: There don't appear to be any serial ports\n");
+ return ICOM_OK; /* Maybe they have USB ports */
+ }
+
+ /* Look at all the values in this key */
+ a1logd(p->log, 8, "icoms_get_paths: looking through all the values in the SERIALCOMM key\n");
+
+ for (i = 0; ; i++) {
+ char valname[500];
+ DWORD vnsize = 500;
+ DWORD vtype;
+ char value[500];
+ DWORD vsize = 500;
+
+ stat = RegEnumValue(
+ sch, /* handle to key to enumerate */
+ i, /* index of subkey to enumerate */
+ valname, /* address of buffer for value name */
+ &vnsize, /* address for size of value name buffer */
+ NULL, /* reserved */
+ &vtype, /* Address of value type */
+ value, /* Address of value buffer */
+ &vsize /* Address of value buffer size */
+ );
+ if (stat == ERROR_NO_MORE_ITEMS) {
+ a1logd(p->log, 8, "icoms_get_paths: got ERROR_NO_MORE_ITEMS\n");
+ break;
+ }
+ if (stat == ERROR_MORE_DATA /* Hmm. Should expand buffer size */
+ || stat != ERROR_SUCCESS) {
+ a1logw(p->log, "icoms_get_paths: RegEnumValue failed with %d\n",stat);
+ break;
+ }
+ valname[500-1] = '\000';
+ value[500-1] = '\000';
+
+ if (vtype != REG_SZ) {
+ a1logw(p->log, "icoms_get_paths: RegEnumValue didn't return stringz type\n");
+ continue;
+ }
+
+ /* Add the port to the list */
+ p->add_serial(p, value, value);
+ a1logd(p->log, 8, "icoms_get_paths: Added path '%s'\n",value);
+ }
+ if ((stat = RegCloseKey(sch)) != ERROR_SUCCESS) {
+ a1logw(p->log, "icoms_get_paths: RegCloseKey failed with %d\n",stat);
+ }
+#endif /* ENABLE_SERIAL */
+
+ /* Sort the COM keys so people don't get confused... */
+ a1logd(p->log, 6, "icoms_get_paths: we now have %d entries and are about to sort them\n",p->npaths);
+ for (i = usbend; i < (p->npaths-1); i++) {
+ for (j = i+1; j < p->npaths; j++) {
+ if (strcmp(p->paths[i]->name, p->paths[j]->name) > 0) {
+ icompath *tt = p->paths[i];
+ p->paths[i] = p->paths[j];
+ p->paths[j] = tt;
+ }
+ }
+ }
+
+ a1logd(p->log, 8, "icoms_get_paths: returning %d paths and ICOM_OK\n",p->npaths);
+
+ return ICOM_OK;
+}
+
+
+/* Close the port */
+static void icoms_close_port(icoms *p) {
+ if (p->is_open) {
+#ifdef ENABLE_USB
+ if (p->usbd) {
+ usb_close_port(p);
+ } else if (p->hidd) {
+ hid_close_port(p);
+ }
+#endif
+#ifdef ENABLE_SERIAL
+ if (p->phandle != NULL) {
+ CloseHandle(p->phandle);
+ }
+#endif /* ENABLE_SERIAL */
+ p->is_open = 0;
+ }
+}
+
+#ifdef ENABLE_SERIAL
+
+static int icoms_ser_write(icoms *p, char *wbuf, double tout);
+static int icoms_ser_read(icoms *p, char *rbuf, int bsize, char tc, int ntc, double tout);
+
+/* Set the serial port characteristics */
+/* This always re-opens the port */
+/* return an icom error */
+static int
+icoms_set_ser_port(
+icoms *p,
+flow_control fc,
+baud_rate baud,
+parity parity,
+stop_bits stop,
+word_length word)
+{
+ a1logd(p->log, 8, "icoms_set_ser_port: About to set port characteristics:\n"
+ " Port name = %s\n"
+ " Flow control = %d\n"
+ " Baud Rate = %d\n"
+ " Parity = %d\n"
+ " Stop bits = %d\n"
+ " Word length = %d\n"
+ ,p->name ,fc ,baud ,parity ,stop ,word);
+
+ if (p->is_open)
+ p->close_port(p);
+
+ if (p->port_type(p) == icomt_serial) {
+ DCB dcb;
+
+ a1logd(p->log, 8, "icoms_set_ser_port: Make sure serial port is open\n");
+
+ if (fc != fc_nc)
+ p->fc = fc;
+ if (baud != baud_nc)
+ p->br = baud;
+ if (parity != parity_nc)
+ p->py = parity;
+ if (stop != stop_nc)
+ p->sb = stop;
+ if (word != length_nc)
+ p->wl = word;
+
+ /* Make sure the port is open */
+ if (!p->is_open) {
+ a1logd(p->log, 8, "icoms_set_ser_port: about to open serial port '%s'\n",p->spath);
+
+ if ((p->phandle = CreateFile(
+ p->spath,
+ GENERIC_READ|GENERIC_WRITE,
+ 0, /* Exclusive access */
+ NULL, /* No security */
+ OPEN_EXISTING, /* Does not make sense to create */
+ 0, /* No overlapped I/O */
+ NULL) /* NULL template */
+ ) == INVALID_HANDLE_VALUE) {
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: open port '%s' failed with LastError %d\n",p->spath,GetLastError());
+ return ICOM_SYS;
+ }
+ p->is_open = 1;
+ }
+
+ if (GetCommState(p->phandle, &dcb) == FALSE) {
+ CloseHandle(p->phandle);
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: reading state '%s' failed with LastError %d\n",p->spath,GetLastError());
+ return ICOM_SYS;
+ }
+
+ /* Set misc stuff to default */
+ dcb.fBinary = TRUE; /* Binary mode is only one that works */
+ dcb.fOutxCtsFlow = FALSE; /* Not using Cts flow control */
+ dcb.fOutxDsrFlow = FALSE; /* Not using Dsr Flow control */
+ dcb.fDtrControl = DTR_CONTROL_ENABLE; /* Enable DTR during connection */
+ dcb.fDsrSensitivity = FALSE; /* Not using Dsr Flow control */
+ dcb.fTXContinueOnXoff = TRUE; /* */
+ dcb.fOutX = FALSE; /* No default Xon/Xoff flow control */
+ dcb.fInX = FALSE; /* No default Xon/Xoff flow control */
+ dcb.fErrorChar = FALSE;
+ dcb.fNull = FALSE;
+ dcb.fRtsControl = RTS_CONTROL_ENABLE; /* Turn RTS on during connection */
+ dcb.fAbortOnError = TRUE;
+
+ switch (p->fc) {
+ case fc_nc:
+ CloseHandle(p->phandle);
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: illegal flow control %d\n",p->fc);
+ return ICOM_SYS;
+ case fc_XonXOff:
+ /* Use Xon/Xoff bi-directional flow control */
+ dcb.fOutX = TRUE;
+ dcb.fInX = TRUE;
+ dcb.XonChar = 0x11; /* ^Q */
+ dcb.XoffChar = 0x13; /* ^S */
+ // dcb.fTXContinueOnXoff = FALSE; /* Stop transmitting if input is full */
+ break;
+ case fc_Hardware:
+ /* Use RTS/CTS bi-directional flow control */
+ dcb.fOutxCtsFlow = TRUE;
+ dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
+ break;
+ default:
+ break;
+ }
+
+ switch (p->py) {
+ case parity_nc:
+ CloseHandle(p->phandle);
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: illegal parity setting %d\n",p->py);
+ return ICOM_SYS;
+ case parity_none:
+ dcb.fParity = FALSE;
+ dcb.Parity = NOPARITY;
+ break;
+ case parity_odd:
+ dcb.fParity = TRUE;
+ dcb.Parity = ODDPARITY;
+ break;
+ case parity_even:
+ dcb.fParity = TRUE;
+ dcb.Parity = EVENPARITY;
+ break;
+ default:
+ break;
+ }
+
+ switch (p->sb) {
+ case stop_nc:
+ CloseHandle(p->phandle);
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: illegal stop bits %d\n",p->sb);
+ return ICOM_SYS;
+ case stop_1:
+ dcb.StopBits = ONESTOPBIT;
+ break;
+ case stop_2:
+ dcb.StopBits = TWOSTOPBITS;
+ break;
+ }
+
+ switch (p->wl) {
+ case length_nc:
+ CloseHandle(p->phandle);
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: illegal word length %d\n",p->wl);
+ return ICOM_SYS;
+ case length_5:
+ dcb.ByteSize = 5;
+ break;
+ case length_6:
+ dcb.ByteSize = 6;
+ break;
+ case length_7:
+ dcb.ByteSize = 7;
+ break;
+ case length_8:
+ dcb.ByteSize = 8;
+ break;
+ }
+
+ switch (p->br) {
+ case baud_110:
+ dcb.BaudRate = CBR_110;
+ break;
+ case baud_300:
+ dcb.BaudRate = CBR_300;
+ break;
+ case baud_600:
+ dcb.BaudRate = CBR_600;
+ break;
+ case baud_1200:
+ dcb.BaudRate = CBR_1200;
+ break;
+ case baud_2400:
+ dcb.BaudRate = CBR_2400;
+ break;
+ case baud_4800:
+ dcb.BaudRate = CBR_4800;
+ break;
+ case baud_9600:
+ dcb.BaudRate = CBR_9600;
+ break;
+ case baud_14400:
+ dcb.BaudRate = CBR_14400;
+ break;
+ case baud_19200:
+ dcb.BaudRate = CBR_19200;
+ break;
+ case baud_38400:
+ dcb.BaudRate = CBR_38400;
+ break;
+ case baud_57600:
+ dcb.BaudRate = CBR_57600;
+ break;
+ case baud_115200:
+ dcb.BaudRate = CBR_115200;
+ break;
+ default:
+ CloseHandle(p->phandle);
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: illegal baud rate! (0x%x)\n",p->br);
+ return ICOM_SYS;
+ }
+
+ PurgeComm(p->phandle, PURGE_TXCLEAR | PURGE_RXCLEAR);
+
+ if (!SetCommState(p->phandle, &dcb)) {
+ CloseHandle(p->phandle);
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: SetCommState failed with LastError %d\n",
+ GetLastError());
+ return ICOM_SYS;
+ }
+
+ PurgeComm(p->phandle, PURGE_TXCLEAR | PURGE_RXCLEAR);
+
+ p->write = icoms_ser_write;
+ p->read = icoms_ser_read;
+
+ }
+ a1logd(p->log, 8, "icoms_set_ser_port: port characteristics set ok\n");
+
+ return ICOM_OK;
+}
+
+/* ---------------------------------------------------------------------------------*/
+/* Serial write/read */
+
+/* Write the characters in the buffer out */
+/* Data will be written up to the terminating nul */
+/* Return relevant error status bits */
+/* Set the icoms lserr value */
+static int
+icoms_ser_write(
+icoms *p,
+char *wbuf,
+double tout)
+{
+ COMMTIMEOUTS tmo;
+ DWORD wbytes;
+ int c, len;
+ long toc, i, top; /* Timout count, counter, timeout period */
+ int rv = ICOM_OK;
+
+ a1logd(p->log, 8, "icoms_ser_write: About to write '%s' ",icoms_fix(wbuf));
+ if (!p->is_open) {
+ a1loge(p->log, ICOM_SYS, "icoms_ser_write: device not initialised\n");
+ p->lserr = rv = ICOM_SYS;
+ return rv;
+ }
+
+ len = strlen(wbuf);
+ tout *= 1000.0; /* Timout in msec */
+
+ top = 100; /* Timeout period in msecs */
+ toc = (int)(tout/top + 0.5); /* Number of timout periods in timeout */
+ if (toc < 1)
+ toc = 1;
+
+ /* Set the timout value */
+ tmo.ReadIntervalTimeout = top;
+ tmo.ReadTotalTimeoutMultiplier = 0;
+ tmo.ReadTotalTimeoutConstant = top;
+ tmo.WriteTotalTimeoutMultiplier = top;
+ tmo.WriteTotalTimeoutConstant = 0;
+ if (!SetCommTimeouts(p->phandle, &tmo)) {
+ a1loge(p->log, ICOM_SYS, "icoms_ser_write: SetCommTimeouts failed with %d\n",GetLastError());
+ p->lserr = rv = ICOM_SYS;
+ return rv;
+ }
+
+ /* Until data is all written or we time out */
+ for (i = toc; i > 0 && len > 0;) {
+ if (!WriteFile(p->phandle, wbuf, len, &wbytes, NULL)) {
+ DWORD errs;
+ if (!ClearCommError(p->phandle,&errs,NULL))
+ error("Write to COM port failed, and Clear error failed");
+ if (errs & CE_BREAK)
+ rv |= ICOM_BRK;
+ if (errs & CE_FRAME)
+ rv |= ICOM_FER;
+ if (errs & CE_RXPARITY)
+ rv |= ICOM_PER;
+ if (errs & CE_RXOVER)
+ rv |= ICOM_OER;
+ break;
+ } else if (wbytes == 0) {
+ i--; /* Timeout */
+ } else if (wbytes > 0) { /* Account for bytes done */
+ i = toc;
+ len -= wbytes;
+ wbuf += len;
+ }
+ }
+ if (i <= 0) { /* Timed out */
+ rv |= ICOM_TO;
+ }
+ a1logd(p->log, 8, "icoms_ser_write: returning ICOM err 0x%x\n",rv);
+
+ p->lserr = rv;
+ return rv;
+}
+
+
+/* Read characters into the buffer */
+/* Return string will be terminated with a nul */
+static int
+icoms_ser_read(
+icoms *p,
+char *rbuf, /* Buffer to store characters read */
+int bsize, /* Buffer size */
+char tc, /* Terminating characer */
+int ntc, /* Number of terminating characters */
+double tout /* Time out in seconds */
+) {
+ COMMTIMEOUTS tmo;
+ DWORD rbytes;
+ int c, j;
+ long toc, i, top; /* Timout count, counter, timeout period */
+ char *rrbuf = rbuf; /* Start of return buffer */
+ DCB dcb;
+ int rv = ICOM_OK;
+
+ if (p->phandle == NULL) {
+ a1loge(p->log, ICOM_SYS, "icoms_read: device not initialised\n");
+ p->lserr = rv = ICOM_SYS;
+ return rv;
+ }
+
+ if (bsize < 3) {
+ a1loge(p->log, ICOM_SYS, "icoms_read: given too small a buffer\n");
+ p->lserr = rv = ICOM_SYS;
+ return rv;
+ }
+
+#ifdef NEVER
+ /* The Prolific 2303 USB<->serial seems to choke on this, */
+ /* so we just put up with a 100msec delay at the end of each */
+ /* reply. */
+ if (GetCommState(p->phandle, &dcb) == FALSE) {
+ a1loge(p->log, ICOM_SYS, "icoms_ser_read: GetCommState failed with %d\n",GetLastError());
+ p->lserr = rv = ICOM_SYS;
+ return rv;
+ }
+
+ dcb.EofChar = tc;
+
+ if (!SetCommState(p->phandle, &dcb)) {
+ a1loge(p->log, ICOM_SYS, "icoms_ser_read: SetCommState failed %d\n",GetLastError());
+ p->lserr = rv = ICOM_SYS;
+ return rv;
+ }
+#endif
+ p->tc = tc;
+
+ tout *= 1000.0; /* Timout in msec */
+ bsize--; /* Allow space for null */
+
+ top = 100; /* Timeout period in msecs */
+ toc = (int)(tout/top + 0.5); /* Number of timout periods in timeout */
+ if (toc < 1)
+ toc = 1;
+
+ /* Set the timout value */
+ tmo.ReadIntervalTimeout = top;
+ tmo.ReadTotalTimeoutMultiplier = 0;
+ tmo.ReadTotalTimeoutConstant = top;
+ tmo.WriteTotalTimeoutMultiplier = top;
+ tmo.WriteTotalTimeoutConstant = 0;
+ if (!SetCommTimeouts(p->phandle, &tmo)) {
+ a1loge(p->log, ICOM_SYS, "icoms_ser_read: SetCommTimeouts failed with %d\n",GetLastError());
+ p->lserr = rv = ICOM_SYS;
+ return rv;
+ }
+
+ /* Until data is all read or we time out */
+ for (i = toc, j = 0; i > 0 && bsize > 1 && j < ntc ;) {
+ if (!ReadFile(p->phandle, rbuf, bsize, &rbytes, NULL)) {
+ DWORD errs;
+ if (!ClearCommError(p->phandle,&errs,NULL))
+ error("Read from COM port failed, and Clear error failed");
+ if (errs & CE_BREAK)
+ rv |= ICOM_BRK;
+ if (errs & CE_FRAME)
+ rv |= ICOM_FER;
+ if (errs & CE_RXPARITY)
+ rv |= ICOM_PER;
+ if (errs & CE_RXOVER)
+ rv |= ICOM_OER;
+ break;
+ } else if (rbytes == 0) {
+ i--; /* Timeout */
+ } else if (rbytes > 0) { /* Account for bytes done */
+ i = toc;
+ bsize -= rbytes;
+ while(rbytes--) { /* Count termination characters */
+ if (*rbuf++ == tc)
+ j++;
+ }
+ }
+ }
+ if (i <= 0) { /* timed out */
+ rv |= ICOM_TO;
+ }
+ *rbuf = '\000';
+ a1logd(p->log, 8, "icoms_ser_read: returning '%s' ICOM err 0x%x\n",icoms_fix(rrbuf),rv);
+
+ p->lserr = rv;
+ return rv;
+}
+
+#endif /* ENABLE_SERIAL */
+/* ---------------------------------------------------------------------------------*/
+
+
+/* Destroy ourselves */
+static void
+icoms_del(icoms *p) {
+ a1logd(p->log, 8, "icoms_del: called\n");
+ if (p->is_open) {
+ a1logd(p->log, 8, "icoms_del: closing port\n");
+ p->close_port(p);
+ }
+#ifdef ENABLE_USB
+ usb_del_usb(p);
+ hid_del_hid(p);
+#endif
+ p->log = del_a1log(p->log); /* unref */
+ free (p);
+}
+
diff --git a/spectro/icoms_ux.c b/spectro/icoms_ux.c
new file mode 100644
index 0000000..722492e
--- /dev/null
+++ b/spectro/icoms_ux.c
@@ -0,0 +1,739 @@
+
+ /* Unix icoms and serial I/O class */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 18/11/2000
+ *
+ * Copyright 1997 - 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.
+ */
+
+/*
+ TTBD:
+ */
+
+#include <sys/types.h> /* Include sys/select.h ? */
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <dirent.h>
+#include <string.h>
+
+/* select() defined, but not poll(), so emulate poll() */
+#if defined(FD_CLR) && !defined(POLLIN)
+#include "pollem.h"
+#define poll_x pollem
+#else
+#include <sys/poll.h> /* Else assume poll() is native */
+#define poll_x poll
+#endif
+
+#ifdef __APPLE__
+//#include <stdbool.h>
+#include <sys/sysctl.h>
+#include <sys/param.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/serial/IOSerialKeys.h>
+#include <IOKit/IOBSD.h>
+#include <mach/mach_init.h>
+#include <mach/task_policy.h>
+#endif /* __APPLE__ */
+
+/* Create and return a list of available serial ports or USB instruments for this system */
+/* return icom error */
+int icompaths_refresh_paths(icompaths *p) {
+ int rv, usbend = 0;
+ int i,j;
+
+ a1logd(p->log, 8, "icoms_get_paths: called\n");
+
+ /* Clear any existing paths */
+ p->clear(p);
+
+#ifdef ENABLE_USB
+ if ((rv = hid_get_paths(p)) != ICOM_OK)
+ return rv;
+ if ((rv = usb_get_paths(p)) != ICOM_OK)
+ return rv;
+#endif /* ENABLE_USB */
+ usbend = p->npaths;
+
+#ifdef ENABLE_SERIAL
+#ifdef __APPLE__
+ /* Search the OSX registry for serial ports */
+ {
+ kern_return_t kstat;
+ mach_port_t mp; /* Master IO port */
+ CFMutableDictionaryRef sdict; /* Serial Port dictionary */
+ io_iterator_t mit; /* Matching itterator */
+ io_object_t ioob; /* Serial object found */
+
+ /* Get dictionary of serial ports */
+ if ((sdict = IOServiceMatching(kIOSerialBSDServiceValue)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "IOServiceMatching returned a NULL dictionary\n");
+ return ICOM_OK;
+ }
+
+ /* Set value to match to RS232 type serial */
+ CFDictionarySetValue(sdict, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDRS232Type));
+
+ /* Init itterator to find matching types. Consumes sdict reference */
+ if ((kstat = IOServiceGetMatchingServices(kIOMasterPortDefault, sdict, &mit))
+ != KERN_SUCCESS) {
+ a1loge(p->log, ICOM_SYS, "IOServiceGetMatchingServices returned %d\n", kstat);
+ return ICOM_SYS;
+ }
+
+ /* Find all the matching serial ports */
+ for (;;) {
+ char pname[100];
+
+ CFTypeRef dfp; /* Device file path */
+
+ if ((ioob = IOIteratorNext(mit)) == 0)
+ break;
+
+ /* Get the callout device's path (/dev/cu.xxxxx). */
+ if ((dfp = IORegistryEntryCreateCFProperty(ioob, CFSTR(kIOCalloutDeviceKey),
+ kCFAllocatorDefault, 0)) == NULL)
+ goto continue1;
+
+ /* Convert from CF string to C string */
+ if (!CFStringGetCString(dfp, pname, 100, kCFStringEncodingASCII))
+ goto continue2;
+
+ /* Ignore infra red port or Bluetooth, or any other noise */
+ if (strstr(pname, "IrDA") != NULL
+ || strstr(pname, "Dialup") != NULL
+ || strstr(pname, "Bluetooth") != NULL)
+ goto continue2;
+
+ /* Add the port to the list */
+ p->add_serial(p, pname, pname);
+ a1logd(p->log, 8, "icoms_get_paths: Added path '%s'\n",pname);
+
+ continue2:
+ CFRelease(dfp);
+ continue1:
+ IOObjectRelease(ioob); /* Release found object */
+ }
+ IOObjectRelease(mit); /* Release the itterator */
+ }
+#else
+ /* Other UNIX like systems */
+ /* Many are crude and list every available device name, whether */
+ /* it's usable or not. Do any UNIX systems have a mechanism for listing */
+ /* serial ports ?? */
+
+ /* On Linux, the list in /proc/tty/driver/serial may indicate */
+ /* which are real or not (if "uart:unknown" then not real) */
+ /* e.g.:
+
+ 0: uart:16550A port:000003F8 irq:4 tx:3 rx:1755 brk:1 RTS|DTR
+ 1: uart:16550A port:000002F8 irq:3 tx:11 rx:3 brk:3
+ 2: uart:unknown port:000003E8 irq:4
+ 3: uart:unknown port:000002E8 irq:3
+ 4: uart:unknown port:00000000 irq:0
+ 5: uart:unknown port:00000000 irq:0
+ 6: uart:unknown port:00000000 irq:0
+ 7: uart:unknown port:00000000 irq:0
+
+ but the permissions don't allow looking at this.
+ */
+ /* (This info is similar to what is returned by "setserial -g /dev/ttyS*", */
+ /* and "setserial -gb /dev/ttyS*" returns just the real ports.) */
+ /* None of this can distinguish if one is the mouse. */
+
+ /* From "QTSerialPort": */
+ /*
+ Constant Used By Naming Convention
+ ---------- ------------- ------------------------
+ _TTY_WIN_ Windows COM1, COM2
+ _TTY_IRIX_ SGI/IRIX /dev/ttyf1, /dev/ttyf2
+ _TTY_HPUX_ HP-UX /dev/tty1p0, /dev/tty2p0
+ _TTY_SUN_ SunOS/Solaris /dev/ttya, /dev/ttyb
+ _TTY_DIGITAL_ Digital UNIX /dev/tty01, /dev/tty02
+ _TTY_FREEBSD_ FreeBSD /dev/ttyd0, /dev/ttyd1
+ _TTY_LINUX_ Linux /dev/ttyS0, /dev/ttyS1
+ <none> Linux /dev/ttyS0, /dev/ttyS1
+ Linux /dev/ttyUSB0, /dev/ttyUSB1
+ */
+
+ /*
+ "Most program set a lock in /var/lock/LCK..tty<XX> on Linux ?
+ <http://sunsite.ualberta.ca/LDP/LDP/nag2/x-087-2-serial.devices.html>
+ <http://docs.freebsd.org/info/uucp/uucp.info.UUCP_Lock_Files.html>
+
+ We should really use the lock files to avoid treading on
+ other programs toes. We assume at the moment that the user
+ only picks a serial port with an instrument on it.
+ */
+
+ /* Search for devices that match the pattern /dev/ttyS[0-9]* and /dev/ttyUSB* */
+ {
+ DIR *dd;
+ struct dirent *de;
+ char *dirn = "/dev/";
+
+ if ((dd = opendir(dirn)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "failed to open directory \"%s\"\n",dirn);
+ return ICOM_OK;
+ }
+
+ for (;;) {
+ int fd;
+ char *dpath;
+
+ if ((de = readdir(dd)) == NULL)
+ break;
+
+ if (!(
+#ifdef __FreeBSD__
+ /* This should match uart & USB devs. */
+ ( strncmp (de->d_name, "cua", 3) == 0
+ && strlen (de->d_name) < 7)
+#else
+ /* Presumably Linux.. */
+ ( strncmp(de->d_name, "ttyS", 4) == 0
+ && de->d_name[4] >= '0' && de->d_name[4] <= '9')
+ || ( strncmp(de->d_name, "ttyUSB", 5) == 0)
+#endif
+ ))
+ continue;
+
+ if ((dpath = (char *)malloc(strlen(dirn) + strlen(de->d_name) + 1)) == NULL) {
+ closedir(dd);
+ a1loge(p->log, ICOM_SYS, "icompaths_refresh_paths() malloc failed!\n");
+ return ICOM_SYS;
+ }
+ strcpy(dpath, dirn);
+ strcat(dpath, de->d_name);
+
+ /* See if the serial port is real */
+ if (strncmp(de->d_name, "ttyUSB", 5) != 0) {
+
+ /* Hmm. This is probably a bad idea - it can upset other */
+ /* programs that use the serial ports ? */
+ if ((fd = open(dpath, O_RDONLY | O_NOCTTY | O_NONBLOCK)) < 0) {
+ a1logd(p->log, 8, "icoms_get_paths: failed to open serial \"%s\" - not real\n",dpath);
+ free(dpath);
+ continue;
+ }
+ /* On linux we could do a
+ struct serial_struct serinfo;
+
+ serinfo.reserved_char[0] = 0;
+
+ if (ioctl(fd, TIOCGSERIAL, &serinfo) < 0
+ || serinfo.type == PORT_UNKNOWN) {
+ free(dpath);
+ continue;
+ }
+
+ */
+ close(fd);
+ }
+ a1logd(p->log, 8, "icoms_get_paths: open'd serial \"%s\" - assume real\n",dpath);
+
+ /* Add the path to the list */
+ p->add_serial(p, dpath, dpath);
+ a1logd(p->log, 8, "icoms_get_paths: Added path '%s'\n",dpath);
+ }
+ closedir(dd);
+ }
+#endif /* ! __APPLE__ */
+#endif /* ENABLE_SERIAL */
+
+ /* Sort the serial /dev keys so people don't get confused... */
+ /* Sort USB serial ports ahead of normal serial ports. */
+ for (i = usbend; i < (p->npaths-1); i++) {
+ for (j = i+1; j < p->npaths; j++) {
+ if ((strncmp(p->paths[i]->name, "/dev/ttyUSB", 11) ||
+ strncmp(p->paths[j]->name, "/dev/ttyS", 9)) &&
+ strcmp(p->paths[i]->name, p->paths[j]->name) > 0) {
+ icompath *tt = p->paths[i];
+ p->paths[i] = p->paths[j];
+ p->paths[j] = tt;
+ }
+ }
+ }
+ return ICOM_OK;
+}
+
+/* -------------------------------------------------------------------- */
+
+/* Close the port */
+static void icoms_close_port(icoms *p) {
+ if (p->is_open) {
+#ifdef ENABLE_USB
+ if (p->usbd) {
+ usb_close_port(p);
+ } else if (p->hidd) {
+ hid_close_port(p);
+ }
+#endif
+#ifdef ENABLE_SERIAL
+ if (p->fd != -1) {
+ close(p->fd);
+ p->fd = -1;
+ }
+#endif /* ENABLE_SERIAL */
+ p->is_open = 0;
+ }
+}
+
+#ifdef ENABLE_SERIAL
+
+static int icoms_ser_write(icoms *p, char *wbuf, double tout);
+static int icoms_ser_read(icoms *p, char *rbuf, int bsize, char tc, int ntc, double tout);
+
+/* Set the serial port number and characteristics */
+/* This always re-opens the port */
+/* return icom error */
+static int
+icoms_set_ser_port(
+icoms *p,
+flow_control fc,
+baud_rate baud,
+parity parity,
+stop_bits stop,
+word_length word
+) {
+ int rv;
+ struct termios tio;
+ speed_t speed = 0;
+
+ a1logd(p->log, 8, "icoms_set_ser_port: About to set port characteristics:\n"
+ " Port name = %s\n"
+ " Flow control = %d\n"
+ " Baud Rate = %d\n"
+ " Parity = %d\n"
+ " Stop bits = %d\n"
+ " Word length = %d\n"
+ ,p->name ,fc ,baud ,parity ,stop ,word);
+
+
+ if (p->is_open) /* Close it and re-open it */
+ p->close_port(p);
+
+ if (p->port_type(p) == icomt_serial) {
+
+ a1logd(p->log, 8, "icoms_set_ser_port: Make sure serial port is open\n");
+
+ if (fc != fc_nc)
+ p->fc = fc;
+ if (baud != baud_nc)
+ p->br = baud;
+ if (parity != parity_nc)
+ p->py = parity;
+ if (stop != stop_nc)
+ p->sb = stop;
+ if (word != length_nc)
+ p->wl = word;
+
+ /* Make sure the port is open */
+ if (!p->is_open) {
+
+ a1logd(p->log, 8, "icoms_set_ser_port: about to open serial port '%s'\n",p->spath);
+
+ if ((p->fd = open(p->spath, O_RDWR | O_NOCTTY )) < 0) {
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: open port '%s' failed with %d (%s)\n",p->spath,p->fd,strerror(errno));
+ return ICOM_SYS;
+ }
+ /* O_NONBLOCK O_SYNC */
+ p->is_open = 1;
+ }
+
+ if ((rv = tcgetattr(p->fd, &tio)) < 0) {
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: tcgetattr on '%s' failed with %d (%s)\n",p->spath,p->fd,strerror(errno));
+ return ICOM_SYS;
+ }
+
+ /* Clear everything in the tio, and just set what we want */
+ memset(&tio, 0, sizeof(struct termios));
+
+ /* Turn on basic configuration: */
+ tio.c_iflag |= (
+ IGNBRK /* Ignore Break */
+ );
+
+ tio.c_oflag |= ( 0 );
+
+ tio.c_cflag |= (
+ CREAD /* Enable the receiver */
+ | CLOCAL /* Ignore modem control lines */
+ );
+
+ tio.c_lflag |= (
+ 0 /* Non-canonical input mode */
+ );
+
+ /* And configure: */
+ tio.c_cc[VTIME] = 1; /* 0.1 second timeout */
+ tio.c_cc[VMIN] = 64; /* Comfortably less than _PF_MAX_INPUT */
+
+ switch (p->fc) {
+ case fc_nc:
+ close(p->fd);
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: illegal flow control %d\n",p->fc);
+ return ICOM_SYS;
+ case fc_XonXOff:
+ /* Use Xon/Xoff bi-directional flow control */
+ tio.c_iflag |= IXON; /* Enable XON/XOFF flow control on output */
+ tio.c_iflag |= IXOFF; /* Enable XON/XOFF flow control on input */
+ tio.c_cc[VSTART] = 0x11; /* ^Q */
+ tio.c_cc[VSTOP] = 0x13; /* ^S */
+ break;
+ case fc_Hardware:
+ /* Use RTS/CTS bi-directional flow control */
+#ifdef __APPLE__
+ tio.c_cflag |= CCTS_OFLOW;
+ tio.c_cflag |= CRTS_IFLOW;
+#else
+ tio.c_cflag |= CRTSCTS;
+#endif
+ break;
+ default:
+ break;
+ }
+
+ switch (p->py) {
+ case parity_nc:
+ close(p->fd);
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: illegal parity setting %d\n",p->py);
+ return ICOM_SYS;
+ break;
+ case parity_none:
+ tio.c_iflag &= ~INPCK; /* Disable input parity checking */
+ break;
+ case parity_odd:
+ tio.c_iflag |= INPCK; /* Enable input parity checking */
+ tio.c_cflag |= PARENB; /* Enable input and output parity checking */
+ tio.c_cflag |= PARODD; /* Input and output parity is odd */
+ break;
+ case parity_even:
+ tio.c_iflag |= INPCK; /* Enable input parity checking */
+ tio.c_cflag |= PARENB; /* Enable input and output parity checking */
+ break;
+ }
+
+ switch (p->sb) {
+ case stop_nc:
+ close(p->fd);
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: illegal stop bits %d\n",p->sb);
+ return ICOM_SYS;
+ case stop_1:
+ break; /* defaults to 1 */
+ case stop_2:
+ tio.c_cflag |= CSTOPB;
+ break;
+ }
+
+ switch (p->wl) {
+ case length_nc:
+ close(p->fd);
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: illegal word length %d\n",p->wl);
+ return ICOM_SYS;
+ case length_5:
+ tio.c_cflag |= CS5;
+ break;
+ case length_6:
+ tio.c_cflag |= CS6;
+ break;
+ case length_7:
+ tio.c_cflag |= CS7;
+ break;
+ case length_8:
+ tio.c_cflag |= CS8;
+ break;
+ }
+
+ /* Set the baud rate */
+ switch (p->br) {
+ case baud_110:
+ speed = B110;
+ break;
+ case baud_300:
+ speed = B300;
+ break;
+ case baud_600:
+ speed = B600;
+ break;
+ case baud_1200:
+ speed = B1200;
+ break;
+ case baud_2400:
+ speed = B2400;
+ break;
+ case baud_4800:
+ speed = B4800;
+ break;
+ case baud_9600:
+ speed = B9600;
+ break;
+ case baud_19200:
+ speed = B19200;
+ break;
+ case baud_38400:
+ speed = B38400;
+ break;
+ case baud_57600:
+ speed = B57600;
+ break;
+ case baud_115200:
+ speed = B115200;
+ break;
+ default:
+ close(p->fd);
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: illegal baud rate! (0x%x)\n",p->br);
+ return ICOM_SYS;
+ }
+
+ tcflush(p->fd, TCIOFLUSH); /* Discard any current in/out data */
+
+ if ((rv = cfsetispeed(&tio, speed)) < 0) {
+ close(p->fd);
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: cfsetispeed failed with '%s'\n", strerror(errno));
+ return ICOM_SYS;
+ }
+ if ((rv = cfsetospeed(&tio, speed)) < 0) {
+ close(p->fd);
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: cfsetospeed failed with '%s'\n", strerror(errno));
+ return ICOM_SYS;
+ }
+
+ /* Make change immediately */
+ if ((rv = tcsetattr(p->fd, TCSANOW, &tio)) < 0) {
+ close(p->fd);
+ a1loge(p->log, ICOM_SYS, "icoms_set_ser_port: tcsetattr failed with '%s'\n", strerror(errno));
+ return ICOM_SYS;
+ }
+
+ tcflush(p->fd, TCIOFLUSH); /* Discard any current in/out data */
+
+ p->write = icoms_ser_write;
+ p->read = icoms_ser_read;
+
+ }
+ a1logd(p->log, 8, "icoms_set_ser_port: port characteristics set ok\n");
+
+ return ICOM_OK;
+}
+
+/* ---------------------------------------------------------------------------------*/
+/* Serial write/read */
+
+/* Write the characters in the buffer out */
+/* Data will be written up to the terminating nul */
+/* Return relevant error status bits */
+/* Set the icoms lserr value */
+static int
+icoms_ser_write(
+icoms *p,
+char *wbuf,
+double tout
+) {
+ int rv, retrv = ICOM_OK;
+ int len, wbytes;
+ long toc, i, top; /* Timout count, counter, timeout period */
+ struct pollfd pa[1]; /* Poll array to monitor serial write and stdin */
+ int nfd = 1; /* Number of fd's to poll */
+ struct termios origs, news;
+
+ a1logd(p->log, 8, "icoms_ser_write: About to write '%s' ",icoms_fix(wbuf));
+ if (!p->is_open) {
+ a1loge(p->log, ICOM_SYS, "icoms_ser_write: device not initialised\n");
+ p->lserr = rv = ICOM_SYS;
+ return rv;
+ }
+
+ /* Setup to wait for serial output not block */
+ pa[0].fd = p->fd;
+ pa[0].events = POLLOUT;
+ pa[0].revents = 0;
+
+ /* Until timed out, aborted, or transmitted */
+ len = strlen(wbuf);
+ tout *= 1000.0; /* Timout in msec */
+
+ top = 100; /* Timeout period in msecs */
+ toc = (int)(tout/top + 0.5); /* Number of timout periods in timeout */
+ if (toc < 1)
+ toc = 1;
+
+ /* Until data is all written, we time out, or the user aborts */
+ for(i = toc; i > 0 && len > 0;) {
+ if (poll_x(pa, nfd, top) > 0) {
+ if (pa[0].revents != 0) {
+ if (pa[0].revents != POLLOUT) {
+ a1loge(p->log, ICOM_SYS, "icoms_ser_write: poll returned "
+ "unexpected value 0x%x",pa[0].revents);
+ p->lserr = rv = ICOM_SYS;
+ return rv;
+ }
+
+ /* We can write it without blocking */
+ if ((wbytes = write(p->fd, wbuf, len)) < 0) {
+ retrv |= ICOM_SERW;
+ break;
+ } else if (wbytes > 0) {
+ i = toc;
+ len -= wbytes;
+ wbuf += wbytes;
+ }
+ }
+ } else {
+ i--; /* timeout (or error!) */
+ }
+ }
+ if (i <= 0) { /* Timed out */
+ retrv |= ICOM_TO;
+ }
+
+ a1logd(p->log, 8, "icoms_ser_write: returning ICOM err 0x%x\n",retrv);
+
+ p->lserr = retrv;
+ return retrv;
+}
+
+/* Read characters into the buffer */
+/* Return string will be terminated with a nul */
+/* return icom error */
+static int
+icoms_ser_read(
+icoms *p,
+char *rbuf, /* Buffer to store characters read */
+int bsize, /* Buffer size */
+char tc, /* Terminating characer */
+int ntc, /* Number of terminating characters */
+double tout /* Time out in seconds */
+) {
+ int rv, retrv = ICOM_OK;
+ int rbytes;
+ long j, toc, i, top; /* Timout count, counter, timeout period */
+ struct pollfd pa[1]; /* Poll array to monitor serial read and stdin */
+ int nfd = 1; /* Number of fd's to poll */
+ struct termios origs, news;
+ char *rrbuf = rbuf; /* Start of return buffer */
+
+ if (!p->is_open) {
+ a1loge(p->log, ICOM_SYS, "icoms_ser_read: device not initialised\n");
+ p->lserr = rv = ICOM_SYS;
+ return rv;
+ }
+
+ if (bsize < 3) {
+ a1loge(p->log, ICOM_SYS, "icoms_ser_read: given too small a buffer\n");
+ p->lserr = rv = ICOM_SYS;
+ return rv;
+ }
+
+#ifdef NEVER
+ /* The Prolific 2303 USB<->serial seems to choke on this, */
+ /* so we just put up with the 100msec delay at the end of each reply. */
+ if (tc != p->tc) { /* Set the termination char */
+ struct termios tio;
+
+ if (tcgetattr(p->fd, &tio) < 0) {
+ a1loge(p->log, ICOM_SYS, "icoms_ser_read: tcgetattr failed with '%s' on '%s'\n",
+ strerror(errno),p->spath);
+ p->lserr = rv = ICOM_SYS;
+ return rv;
+ }
+
+ tio.c_cc[VEOL] = tc;
+
+ /* Make change immediately */
+ tcflush(p->fd, TCIFLUSH);
+ if (tcsetattr(p->fd, TCSANOW, &tio) < 0) {
+ a1loge(p->log, ICOM_SYS, "icoms_ser_read: tcsetattr failed with '%s' on '%s'\n",
+ strerror(errno),p->spath);
+ p->lserr = rv = ICOM_SYS;
+ return rv;
+ }
+
+ p->tc = tc;
+ }
+#endif
+
+ /* Wait for serial input to have data */
+ pa[0].fd = p->fd;
+ pa[0].events = POLLIN | POLLPRI;
+ pa[0].revents = 0;
+
+ bsize--; /* Allow space for null */
+ tout *= 1000.0; /* Timout in msec */
+
+ top = 100; /* Timeout period in msecs */
+ toc = (int)(tout/top + 0.5); /* Number of timout periods in timeout */
+ if (toc < 1)
+ toc = 1;
+
+ /* Until data is all read, we time out, or the user aborts */
+ for(i = toc, j = 0; i > 0 && bsize > 1 && j < ntc ;) {
+
+ if (poll_x(pa, nfd, top) > 0) {
+ if (pa[0].revents != 0) {
+ if (pa[0].revents != POLLIN && pa[0].revents != POLLPRI) {
+ a1loge(p->log, ICOM_SYS, "icoms_ser_read: poll on serin returned "
+ "unexpected value 0x%x",pa[0].revents);
+ p->lserr = rv = ICOM_SYS;
+ return rv;
+ }
+
+ /* We have data to read from input */
+ if ((rbytes = read(p->fd, rbuf, bsize)) < 0) {
+ retrv |= ICOM_SERR;
+ break;
+ } else if (rbytes > 0) {
+ i = toc; /* Reset time */
+ bsize -= rbytes;
+ while(rbytes--) { /* Count termination characters */
+ if (*rbuf++ == tc)
+ j++;
+ }
+ }
+ }
+ } else {
+ i--; /* We timed out (or error!) */
+ }
+ }
+
+ *rbuf = '\000';
+ if (i <= 0) { /* timed out */
+ retrv |= ICOM_TO;
+ }
+
+ a1logd(p->log, 8, "icoms_ser_read: returning '%s' ICOM err 0x%x\n",icoms_fix(rrbuf),retrv);
+
+ p->lserr = retrv;
+ return retrv;
+}
+
+#endif /* ENABLE_SERIAL */
+
+/* ---------------------------------------------------------------------------------*/
+
+/* Destroy ourselves */
+static void
+icoms_del(icoms *p) {
+ a1logd(p->log, 8, "icoms_del: called\n");
+ if (p->is_open) {
+ a1logd(p->log, 8, "icoms_del: closing port\n");
+ p->close_port(p);
+ }
+#ifdef ENABLE_USB
+ usb_del_usb(p);
+ hid_del_hid(p);
+#endif
+ p->log = del_a1log(p->log);
+ free (p);
+}
+
diff --git a/spectro/ifiles b/spectro/ifiles
new file mode 100644
index 0000000..6e73997
--- /dev/null
+++ b/spectro/ifiles
@@ -0,0 +1,14 @@
+dtp20.c
+dtp22.c
+dtp41.c
+dtp51.c
+dtp92.c
+i1disp.c
+huey.c
+i1d3.c
+spyd2.c
+ss.c
+i1pro.c
+munki.c
+hcfr.c
+colorhug.c
diff --git a/spectro/illumread.c b/spectro/illumread.c
new file mode 100644
index 0000000..282ec2f
--- /dev/null
+++ b/spectro/illumread.c
@@ -0,0 +1,1232 @@
+
+ /* Illuminant measurement utility */
+
+/*
+ * Argyll Color Correction System
+ * Author: Graeme W. Gill
+ * Date: 3/10/2001
+ *
+ * Derived from printread.c/chartread.c
+ * Was called printspot.
+ *
+ * Copyright 2001 - 2013 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/* This program uses an instrument capable of spectral illuminant/emissive */
+/* and reflective (non-UV filtered) measurement, to measure an illuminant */
+/* spectrum that includes a UV estimate. */
+
+/* Based on spotread.c */
+
+/*
+ TTBD:
+
+ Should add support for converting Spyder correction readings to ccmx.
+ (see SpyderDeviceCorrections.txt)
+
+ */
+
+#undef DEBUG /* Save measurements and restore them to outname_X.sp */
+ /*
+
+ outname_i.sp Illuminant spectrum
+ outname_r.sp Illuminant off paper spectrum
+ outname_p.sp Instrument measured paper reflectance spectrum
+ outname_mpir.sp Measured paper under illuminant spectrum
+ outname_cpir.sp Computed paper under illuminant spectrum
+
+ */
+
+#define SHOWDXX /* Plot the best matched daylight as well */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/types.h>
+#include <time.h>
+#include <string.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "cgats.h"
+#include "xicc.h"
+#include "conv.h"
+#include "inst.h"
+#include "icoms.h"
+#include "instappsup.h"
+#include "plot.h"
+#include "spyd2setup.h" /* Enable Spyder 2 access */
+
+#include <stdarg.h>
+
+#if defined (NT)
+#include <conio.h>
+#endif
+
+/* Deal with an instrument error. */
+/* Return 0 to retry, 1 to abort */
+static int ierror(inst *it, inst_code ic) {
+ int ch;
+ empty_con_chars();
+ printf("Got '%s' (%s) error.\nHit Esc or Q to give up, any other key to retry:",
+ it->inst_interp_error(it, ic), it->interp_error(it, ic));
+ fflush(stdout);
+ ch = next_con_char();
+ printf("\n");
+ if (ch == 0x03 || ch == 0x1b || ch == 'q' || ch == 'Q') /* Escape, ^C or Q */
+ return 1;
+ return 0;
+}
+
+#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;
+}
+
+#define GCC_BUGFIX(XX) gcc_bug_fix(XX);
+#else /* !APPLE */
+#define GCC_BUGFIX(XX)
+#endif /* !APPLE */
+
+
+/* ============================================================== */
+/* optimizer callback */
+
+/* Structure to hold optimisation information */
+typedef struct {
+ xspect *i_sp; /* Illuminiant measurement */
+ xspect *r_sp; /* Illuminant off paper measurement */
+ xspect *p_sp; /* Paper reflectance measurement */
+
+ xsp2cie *pap; /* Estimated illuminant + UV on paper lookup inc FWA */
+ xsp2cie *ref; /* Measured illuminant on paper lookup */
+
+ xspect ill; /* Illuminant with UV added */
+ xspect cpsp; /* FWA corrected calculated paper reflectance */
+ xspect srop; /* Scaled measured paper reflectance */
+
+ double lab0[3], lab1[3]; /* Conversion of calculated vs. measured */
+} bfinds;
+
+/* Optimize the UV content that minimizes the (weighted) Delta E */
+static double bfindfunc(void *adata, double pv[]) {
+ bfinds *b = (bfinds *)adata;
+ int i;
+ double rv = 0.0;
+
+ /* Add UV level to illuminant */
+ b->ill = *b->i_sp;
+ xsp_setUV(&b->ill, b->i_sp, pv[0]);
+
+ /* Update the conversion */
+ if (b->pap->update_fwa_custillum(b->pap, NULL, &b->ill) != 0)
+ error ("Updating FWA compensation failed");
+
+ /* Apply FWA compensation to the paper reflectance */
+ b->pap->sconvert(b->pap, &b->cpsp, b->lab0, b->p_sp);
+
+ /* Adjust gain factor of measured illuminant off paper */
+ /* and divide by illuminant measurement to give reflectance */
+ b->srop = *b->r_sp;
+ for (i = 0; i < b->r_sp->spec_n; i++) {
+ double ww = XSPECT_XWL(b->r_sp, i);
+ double ival = value_xspect(b->i_sp, ww);
+ if (ival < 0.05) /* Stop noise sending it wild */
+ ival = 0.05;
+ b->srop.spec[i] *= pv[1]/ival;
+ if (b->srop.spec[i] < 0.0)
+ b->srop.spec[i] = 0.0;
+ }
+
+ /* Compute Lab of the computed reflectance under flat spectrum */
+ b->ref->convert(b->ref, b->lab0, &b->cpsp);
+
+ /* Compute Lab value of illuminant measured reflectance */
+ b->ref->convert(b->ref, b->lab1, &b->srop);
+
+ /* Weighted Delta E (low weight on a* error) */
+ rv = sqrt( (b->lab0[0] - b->lab1[0]) * (b->lab0[0] - b->lab1[0])
+ + 0.1 * (b->lab0[1] - b->lab1[1]) * (b->lab0[1] - b->lab1[1])
+ + (b->lab0[2] - b->lab1[2]) * (b->lab0[2] - b->lab1[2]));
+
+ /* Add a slight weight of the UV level, to minimize it */
+ /* if the case is unconstrained. */
+ rv += 0.1 * fabs(pv[0]);
+
+//printf("~1 rev = %f (%f %f %f - %f %f %f) from %f %f\n",rv, b->lab0[0], b->lab0[1], b->lab0[2], b->lab1[0], b->lab1[1], b->lab1[2], pv[0], pv[1]);
+ return rv;
+}
+
+/* ============================================================== */
+#ifdef SHOWDXX /* Plot the best matched daylight as well */
+
+/* optimizer callback for computing daylight match */
+
+/* Structure to hold optimisation information */
+typedef struct {
+ xspect ill; /* Illuminant with UV added - target */
+ xspect dxx; /* Daylight spectrum */
+
+ xsp2cie *ref; /* reference Lab conversion */
+
+ double lab0[3], lab1[3]; /* Conversion of target vs. Daylight */
+} cfinds;
+
+/* Optimize the daylight color temperature and level to minimizes the Delta E */
+static double cfindfunc(void *adata, double pv[]) {
+ cfinds *b = (cfinds *)adata;
+ int i;
+ double rv = 0.0;
+
+ /* Compute daylight with the given color temperature */
+ standardIlluminant( &b->dxx, icxIT_Dtemp, pv[0]);
+ b->dxx.norm = 1.0;
+
+ /* Adjust the level of daylight spectra */
+ for (i = 0; i < b->dxx.spec_n; i++) {
+ b->dxx.spec[i] *= pv[1];
+ }
+
+ /* Compute Lab of target */
+ b->ref->convert(b->ref, b->lab0, &b->ill);
+
+ /* Compute Lab of Dxx */
+ b->ref->convert(b->ref, b->lab1, &b->dxx);
+
+ /* Weighted Delta E (low weight on a* error) */
+ rv = icmLabDEsq(b->lab0, b->lab1);
+
+//printf("~1 rev = %f (%f %f %f - %f %f %f) from %f %f\n",rv, b->lab0[0], b->lab0[1], b->lab0[2], b->lab1[0], b->lab1[1], b->lab1[2], pv[0], pv[1]);
+ return rv;
+}
+
+#endif /* SHOWDXX */
+
+/* ============================================================== */
+void
+usage() {
+ icompaths *icmps;
+ fprintf(stderr,"Measure an illuminant, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ if (setup_spyd2() == 2)
+ fprintf(stderr,"WARNING: This file contains a proprietary firmware image, and may not be freely distributed !\n");
+ fprintf(stderr,"usage: illumread [-options] output.sp\n");
+ fprintf(stderr," -v Verbose mode\n");
+ fprintf(stderr," -S Plot spectrum for each reading\n");
+ fprintf(stderr," -c listno Choose instrument from the following list (default 1)\n");
+ if ((icmps = new_icompaths(g_log)) != NULL) {
+ icompath **paths;
+ if ((paths = icmps->paths) != NULL) {
+ int i;
+ for (i = 0; ; i++) {
+ if (paths[i] == NULL)
+ break;
+ if (paths[i]->itype == instSpyder2 && setup_spyd2() == 0)
+ fprintf(stderr," %d = '%s' !! Disabled - no firmware !!\n",i+1,paths[i]->name);
+ else
+ fprintf(stderr," %d = '%s'\n",i+1,paths[i]->name);
+ }
+ } else
+ fprintf(stderr," ** No ports found **\n");
+ }
+ fprintf(stderr," -N Disable initial calibration of instrument if possible\n");
+ fprintf(stderr," -H Use high resolution spectrum mode (if available)\n");
+ fprintf(stderr," -W n|h|x Override serial port flow control: n = none, h = HW, x = Xon/Xoff\n");
+ fprintf(stderr," -D [level] Print debug diagnostics to stderr\n");
+ fprintf(stderr," illuminant.sp File to save measurement to\n");
+
+ if (icmps != NULL)
+ icmps->del(icmps);
+ exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+ int i,j;
+ int fa, nfa, mfa; /* current argument we're looking at */
+ int verb = 0;
+ int debug = 0;
+ int nocal = 0; /* Disable initial calibration */
+ int pspec = 0; /* 2 = Plot out the spectrum for each reading */
+ int highres = 0; /* Use high res mode if available */
+ char outname[MAXNAMEL+1] = "\000"; /* Spectral output file name */
+#ifdef DEBUG
+ char tname[MAXNAMEL+11] = "\000", *tnp;
+ int rd_i = 0, rd_r = 0, rd_p = 0;
+#endif
+
+ icompaths *icmps = NULL; /* Ports to choose from */
+ int comno = 1; /* Specific port suggested by user */
+ inst *it = NULL; /* Instrument object, NULL if none */
+ inst_mode mode = 0; /* Instrument reading mode */
+ inst_opt_type trigmode = inst_opt_unknown; /* Chosen trigger mode */
+ instType itype = instUnknown; /* No default target instrument */
+ inst_mode cap = inst_mode_none; /* Instrument mode capabilities */
+ inst2_capability cap2 = inst2_none; /* Instrument capabilities 2 */
+ inst3_capability cap3 = inst3_none; /* Instrument capabilities 3 */
+ baud_rate br = baud_38400; /* Target baud rate */
+ flow_control fc = fc_nc; /* Default flow control */
+
+ inst_code rv;
+ int uswitch = 0; /* Instrument switch is enabled */
+ xspect i_sp; /* Illuminant emsission spectrum */
+ xspect r_sp; /* Illuminant reflected from paper emission spectrum */
+ xspect p_sp; /* Paper reflectance spectrum */
+ xspect insp; /* Instrument illuminant (for FWA) */
+ xspect ill; /* Estimated illuminant */
+ xspect aill; /* Accumulated result */
+ int nacc = 0; /* Number accumulated */
+ ipatch val; /* Value read */
+
+ set_exe_path(argv[0]); /* Set global exe_path and error_program */
+ check_if_not_interactive();
+ setup_spyd2(); /* Load firware if available */
+
+ i_sp.spec_n = 0;
+ r_sp.spec_n = 0;
+ p_sp.spec_n = 0;
+ insp.spec_n = 0;
+ ill.spec_n = 0;
+ aill.spec_n = 0;
+
+ /* Process the arguments */
+ mfa = 0; /* Minimum final arguments */
+ for(fa = 1;fa < argc;fa++) {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1+mfa) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?') {
+ usage();
+
+ } else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') {
+ verb = 1;
+ g_log->verb = verb;
+
+ } else if (argv[fa][1] == 'S') {
+ pspec = 2;
+
+ /* COM port */
+ } else if (argv[fa][1] == 'c') {
+ fa = nfa;
+ if (na == NULL) usage();
+ comno = atoi(na);
+ if (comno < 1 || comno > 99) usage();
+
+ /* No initial calibration */
+ } else if (argv[fa][1] == 'N') {
+ nocal = 1;
+
+ /* High res mode */
+ } else if (argv[fa][1] == 'H') {
+ highres = 1;
+
+ /* Serial port flow control */
+ } else if (argv[fa][1] == 'W') {
+ fa = nfa;
+ if (na == NULL) usage();
+ if (na[0] == 'n' || na[0] == 'N')
+ fc = fc_none;
+ else if (na[0] == 'h' || na[0] == 'H')
+ fc = fc_Hardware;
+ else if (na[0] == 'x' || na[0] == 'X')
+ fc = fc_XonXOff;
+ else
+ usage();
+
+ } else if (argv[fa][1] == 'D') {
+ debug = 1;
+ if (na != NULL && na[0] >= '0' && na[0] <= '9') {
+ debug = atoi(na);
+ fa = nfa;
+ }
+ g_log->debug = debug;
+ } else
+ usage();
+ }
+ else
+ break;
+ }
+
+ /* Get the output spectrum file name argument */
+ if (fa >= argc)
+ usage();
+
+ strncpy(outname,argv[fa++],MAXNAMEL-1); outname[MAXNAMEL-1] = '\000';
+
+#ifdef DEBUG
+ strcpy(tname, outname);
+ if ((tnp = strrchr(tname, '.')) == NULL)
+ tnp = tname + strlen(tname);
+
+ /* Special debug */
+ strcpy(tnp, "_i.sp");
+ if (read_xspect(&i_sp, tname) == 0) {
+ rd_i = 1;
+ printf("(Found '%s' file and loaded it)\n",tname);
+ }
+ strcpy(tnp, "_r.sp");
+ if (read_xspect(&r_sp, tname) == 0) {
+ rd_r = 1;
+ printf("(Found '%s' file and loaded it)\n",tname);
+ }
+ strcpy(tnp, "_p.sp");
+ if (read_xspect(&p_sp, tname) == 0) {
+ rd_p = 1;
+ /* Should read instrument type from debug_p.sp !! */
+ if (inst_illuminant(&insp, instI1Pro) != 0) /* Hack !! */
+ error ("Instrument doesn't have an FWA illuminent");
+ printf("(Found '%s' file and loaded it)\n",tname);
+ }
+#endif /* DEBUG */
+
+ /* Until the measurements are done, or we give up */
+ for (;;) {
+ int c;
+
+ /* Print the menue of adjustments */
+ printf("\nPress 1 .. 7\n");
+ printf("1) Measure direct illuminant%s\n",i_sp.spec_n != 0 ? " (measured)":"");
+ printf("2) Measure illuminant reflected from paper%s\n", r_sp.spec_n != 0 ? " (measured)":"");
+ printf("3) Measure paper%s\n", p_sp.spec_n != 0 ? " (measured)":"");
+ if (it == NULL) {
+ printf("4) Select another instrument, Currently %d (", comno);
+ if (icmps == NULL)
+ icmps = new_icompaths(g_log);
+ else
+ icmps->refresh(icmps);
+ if (icmps != NULL) {
+ icompath **paths;
+ if ((paths = icmps->paths) != NULL) {
+ int i;
+ for (i = 0; ; i++) {
+ if (paths[i] == NULL)
+ break;
+ if ((i+1) == comno) {
+ printf(" '%s'",paths[i]->name);
+ break;
+ }
+ }
+ }
+ }
+ printf(")\n");
+ } else
+ printf("4) Select another instrument, Currently %s\n", inst_name(itype));
+ printf("5) Compute illuminant spectrum, average result with %d previous readings & save it\n",nacc);
+ printf("6) Compute illuminant spectrum from this reading & save result\n");
+ printf("7) Exit\n");
+
+ empty_con_chars();
+ c = next_con_char();
+ printf("'%c'\n",c);
+
+ /* Deal with doing a measurement */
+ if (c == '1' || c == '2' || c == '3') {
+ int ch;
+
+ if (it == NULL) {
+ if (icmps == NULL)
+ icmps = new_icompaths(g_log);
+
+ /* Open the instrument */
+ if ((it = new_inst(icmps->get_path(icmps, comno), 0, g_log, DUIH_FUNC_AND_CONTEXT)) == NULL) {
+ printf("!!! Unknown, inappropriate or no instrument detected !!!\n");
+ continue;
+ }
+
+ if (verb)
+ printf("Connecting to the instrument ..\n");
+
+#ifdef DEBUG
+ printf("About to init the comms\n");
+#endif
+
+ /* Establish communications */
+ if ((rv = it->init_coms(it, br, fc, 15.0)) != inst_ok) {
+ printf("!!! Failed to initialise communications with instrument\n"
+ " or wrong instrument or bad configuration !!!\n"
+ " ('%s' + '%s')\n", it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ it = NULL;
+ itype = instUnknown;
+ continue;
+ }
+
+#ifdef DEBUG
+ printf("Established comms & initing the instrument\n");
+#endif
+
+ /* Initialise the instrument */
+ if ((rv = it->init_inst(it)) != inst_ok) {
+ printf("!!! Instrument initialisation failed with '%s' (%s) !!!\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ it = NULL;
+ itype = instUnknown;
+ continue;
+ }
+
+ itype = it->get_itype(it); /* get actual type of instrument */
+
+ /* Check the instrument has the necessary capabilities */
+ it->capabilities(it, &cap, &cap2, &cap3);
+
+ /* Need spectral */
+ if (!IMODETST(cap, inst_mode_spectral)) {
+ printf("Instrument lacks spectral measurement capability");
+ }
+
+ /* Disable iniial calibration of machine if selected */
+ if (nocal != 0){
+ if ((rv = it->get_set_opt(it,inst_opt_noinitcalib, 0)) != inst_ok) {
+ printf("Setting no-initial calibrate failed failed with '%s' (%s) !!!\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ printf("Disable initial-calibrate not supported\n");
+ }
+ }
+ if (highres) {
+ if (IMODETST(cap, inst_mode_highres)) {
+ inst_code ev;
+ if ((ev = it->get_set_opt(it, inst_opt_highres)) != inst_ok) {
+ printf("!!! Setting high res mode failed with error :'%s' (%s) !!!\n",
+ it->inst_interp_error(it, ev), it->interp_error(it, ev));
+ }
+ } else if (verb) {
+ printf("!!! High resolution ignored - instrument doesn't support it !!!\n");
+ }
+ }
+ }
+
+ /* Check the instrument has the necessary capabilities for this measurement */
+ it->capabilities(it, &cap, &cap2, &cap3);
+
+ if (c == '1') {
+ if (IMODETST(cap, inst_mode_emis_ambient)) {
+ mode = inst_mode_emis_ambient;
+ } else if (IMODETST(cap, inst_mode_emis_spot)) {
+ mode = inst_mode_emis_spot;
+ } else {
+ printf("!!! Instrument doesn't have ambient or emissive capability !!!\n");
+ continue;
+ }
+ }
+ if (c == '2') {
+ if (IMODETST(cap, inst_mode_emis_tele)) {
+ mode = inst_mode_emis_tele;
+ } else if (IMODETST(cap, inst_mode_emis_spot)) {
+ mode = inst_mode_emis_spot;
+ } else {
+ printf("!!! Instrument doesn't have telephoto or emissive capability !!!\n");
+ continue;
+ }
+ }
+ if (c == '3') {
+ inst_opt_filter filt;
+
+ if (IMODETST(cap, inst_mode_ref_spot)) {
+ mode = inst_mode_ref_spot;
+ } else {
+ printf("!!! Instrument lacks reflective spot measurement capability !!!\n");
+ continue;
+ }
+
+ if ((rv = it->get_set_opt(it, inst_stat_get_filter, &filt)) == inst_ok) {
+ if (filt & inst_opt_filter_UVCut) {
+ printf("!!! Instrument has UV filter - can't measure FWA !!!\n");
+ continue;
+ }
+ }
+ }
+ mode |= inst_mode_spectral;
+
+ if ((rv = it->set_mode(it, mode)) != inst_ok) {
+ printf("!!! Setting instrument mode failed with error :'%s' (%s) !!!\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ continue;
+ }
+ it->capabilities(it, &cap, &cap2, &cap3);
+
+ /* If it batter powered, show the status of the battery */
+ if ((cap2 & inst2_has_battery)) {
+ double batstat = 0.0;
+ if ((rv = it->get_set_opt(it, inst_stat_battery, &batstat)) == inst_ok)
+ printf("The battery charged level is %.0f%%\n",batstat * 100.0);
+ }
+
+ /* If it's an instrument that need positioning do trigger using user via uicallback */
+ /* in illumread, else enable switch or keyboard trigger if possible. */
+ if ((cap2 & inst2_xy_locate) && (cap2 & inst2_xy_position)) {
+ trigmode = inst_opt_trig_prog;
+
+ } else if (cap2 & inst2_user_switch_trig) {
+ trigmode = inst_opt_trig_user_switch;
+ uswitch = 1;
+
+ /* Or go for user via uicallback trigger */
+ } else if (cap2 & inst2_user_trig) {
+ trigmode = inst_opt_trig_user;
+
+ /* Or something is wrong with instrument capabilities */
+ } else {
+ printf("!!! No reasonable trigger mode avilable for this instrument !!!\n");
+ continue;
+ }
+ if ((rv = it->get_set_opt(it, trigmode)) != inst_ok) {
+ printf("!!! Setting trigger mode failed with error :'%s' (%s) !!!\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ continue;
+ }
+
+ /* Setup the keyboard trigger to return our commands */
+ inst_set_uih(0x0, 0xff, DUIH_TRIG);
+ inst_set_uih('q', 'q', DUIH_ABORT);
+ inst_set_uih('Q', 'Q', DUIH_ABORT);
+ inst_set_uih(0x03, 0x03, DUIH_ABORT); /* ^c */
+ inst_set_uih(0x1b, 0x1b, DUIH_ABORT); /* Esc */
+
+ /* Hold table */
+ if (cap2 & inst2_xy_holdrel) {
+ for (;;) { /* retry loop */
+ if ((rv = it->xy_sheet_hold(it)) == inst_ok)
+ break;
+
+ if (ierror(it, rv)) {
+ printf("!!! Setting paper hold failed with error :'%s' (%s) !!!\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->xy_clear(it);
+ continue;
+ }
+ }
+ }
+
+ /* Do any needed calibration before the user places the instrument on a desired spot */
+ if (it->needs_calibration(it) != inst_calt_none) {
+ double lx, ly;
+ inst_code ev;
+
+ printf("\nInstrument needs a calibration before continuing\n");
+
+ /* save current location */
+ if ((cap2 & inst2_xy_locate) && (cap2 & inst2_xy_position)) {
+ for (;;) { /* retry loop */
+ if ((ev = it->xy_get_location(it, &lx, &ly)) == inst_ok)
+ break;
+ if (ierror(it, ev) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (ev != inst_ok) {
+ printf("!!! Setting calibration location failed with error :'%s' (%s) !!!\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ continue;
+ }
+ }
+
+ ev = inst_handle_calibrate(it, inst_calt_needed, inst_calc_none, NULL, NULL);
+ if (ev != inst_ok) { /* Abort or fatal error */
+ printf("!!! Calibration failed with error :'%s' (%s) !!!\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ continue;
+ }
+
+ /* restore location */
+ if ((cap2 & inst2_xy_locate) && (cap2 & inst2_xy_position)) {
+ for (;;) { /* retry loop */
+ if ((ev = it->xy_position(it, 0, lx, ly)) == inst_ok)
+ break;
+ if (ierror(it, ev) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (ev != inst_ok) {
+ printf("!!! Restoring location failed with error :'%s' (%s) !!!\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ continue;
+ }
+ }
+ }
+
+ /* Now do the measurement: */
+
+ /* If this is an xy instrument: */
+ if ((cap2 & inst2_xy_locate) && (cap2 & inst2_xy_position)) {
+
+ /* Allow the user to position the instrument */
+ for (;;) { /* retry loop */
+ if ((rv = it->xy_locate_start(it)) == inst_ok)
+ break;
+ if (ierror(it, rv) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (rv != inst_ok)
+ break; /* Abort */
+
+ if (c != 3) {
+ printf("!!! Unexpected: XY instrument used for emissive measurements !!!\n");
+ continue;
+ }
+
+ printf("Using the XY table controls, position the instrument sight\n");
+ printf("so as to read the paper,\n");
+
+ /* Purely manual instrument */
+ } else {
+
+ if (c == '1') {
+ if (IMODETST(cap, inst_mode_emis_ambient)) {
+ printf("\n(If applicable) set instrument to ambient measurenent mode, or place\n");
+ printf("ambient adapter on it, and position it so as to measure the illuminant directly.\n");
+ } else {
+ printf("\n(If applicable) set instrument to emissive measurenent mode,\n");
+ printf("and position it so as to measure the illuminant directly.\n");
+ }
+ } else if (c == '2') {
+ if (IMODETST(cap, inst_mode_emis_tele)) {
+ printf("\n(If applicable) set instrument to telephoto measurenent mode,\n");
+ printf("position it so as to measure the illuminant reflected from the paper.\n");
+ } else {
+ printf("\n(If applicable) set instrument to emsissive measurenent mode,\n");
+ printf("position it so as to measure the illuminant reflected from the paper.\n");
+ }
+ } else if (c == '3') {
+ printf("\n(If applicable) set instrument to reflective measurenent mode,\n");
+ printf("position it so as to measure the paper.");
+ } else
+ error("Unexpected choice");
+ }
+ if (uswitch)
+ printf("Hit ESC or Q to abort, or instrument switch or any other key to take a reading: ");
+ else
+ printf("Hit ESC or Q to abort, any any other key to take a reading: ");
+ fflush(stdout);
+
+ if ((cap2 & inst2_xy_locate) && (cap2 & inst2_xy_position)) {
+
+ /* Wait for the user to hit a key */
+ for (;;) {
+ if ((rv = inst_get_uicallback()(inst_get_uicontext(), inst_armed)) != inst_ok)
+ break;
+ }
+
+ if (rv == inst_user_abort) {
+ break; /* Abort */
+
+ } else if (rv == inst_user_trig) {
+ double lx, ly;
+ inst_code ev;
+
+ /* Take the location set on the sight, and move the instrument */
+ /* to take the measurement there. */
+ if ((cap2 & inst2_xy_locate) && (cap2 & inst2_xy_position)) {
+ for (;;) { /* retry loop */
+ if ((rv = it->xy_get_location(it, &lx, &ly)) == inst_ok)
+ break;
+ if (ierror(it, rv) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (rv != inst_ok)
+ break; /* Abort */
+
+ for (;;) { /* retry loop */
+ if ((rv = it->xy_locate_end(it)) == inst_ok)
+ break;
+ if (ierror(it, rv) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (rv != inst_ok)
+ break; /* Abort */
+
+ for (;;) { /* retry loop */
+ if ((rv = it->xy_position(it, 1, lx, ly)) == inst_ok)
+ break;
+ if (ierror(it, rv) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (rv != inst_ok)
+ break; /* Abort */
+ }
+ rv = it->read_sample(it, "SPOT", &val, 1);
+
+ /* Restore the location the instrument to have the location */
+ /* sight over the selected patch. */
+ for (;;) { /* retry loop */
+ if ((ev = it->xy_position(it, 0, lx, ly)) == inst_ok)
+ break;
+ if (ierror(it, ev) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (ev != inst_ok)
+ break; /* Abort */
+ }
+ /* else what ? */
+
+ /* Not an XY instrument */
+ } else {
+ rv = it->read_sample(it, "SPOT", &val, 1);
+ }
+
+ /* Release paper */
+ if (cap2 & inst2_xy_holdrel) {
+ it->xy_clear(it);
+ }
+
+#ifdef DEBUG
+ printf("read_sample returned '%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+#endif /* DEBUG */
+
+ /* Deal with a trigger or command */
+ if ((rv & inst_mask) == inst_user_trig) {
+ ch = inst_get_uih_char() & 0xff;
+
+ /* Deal with a command or abort */
+ } else if ((rv & inst_mask) == inst_user_abort) {
+ ch = inst_get_uih_char();
+
+ if (ch & DUIH_CMND) {
+ ch &= 0xff;
+ } else if (ch & DUIH_ABORT) {
+ printf("\nIlluminant measure aborted at user request!\n");
+ continue;
+ }
+
+ /* Deal with a needs calibration */
+ } else if ((rv & inst_mask) == inst_needs_cal) {
+ inst_code ev;
+ printf("\nIlluminant measure failed because instruments needs calibration.\n");
+ ev = inst_handle_calibrate(it, inst_calt_needed, inst_calc_none, NULL, NULL);
+ continue;
+
+ /* Deal with a bad sensor position */
+ } else if ((rv & inst_mask) == inst_wrong_config) {
+ printf("\nIlluminant measure failed due to the sensor being in the wrong position\n(%s)\n",it->interp_error(it, rv));
+ continue;
+
+ /* Deal with a misread */
+ } else if ((rv & inst_mask) == inst_misread) {
+ printf("\nIlluminant measure failed due to misread (%s)\n",it->interp_error(it, rv));
+ continue;
+
+ /* Deal with a communications error */
+ } else if ((rv & inst_mask) == inst_coms_fail) {
+ empty_con_chars();
+ printf("\nIlluminant measure failed due to communication problem.\n");
+ printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
+ continue;
+
+ /* Some other fatal error */
+ } else if (rv != inst_ok) {
+ printf("\nGot fatal error '%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ continue;
+ }
+
+ if (c == '1') {
+ i_sp = val.sp;
+#ifdef DEBUG
+ if (rd_i == 0) {
+ strcpy(tnp, "_i.sp");
+ write_xspect(tname,&i_sp);
+ }
+#endif
+ } else if (c == '2') {
+ r_sp = val.sp;
+#ifdef DEBUG
+ if (rd_r == 0) {
+ strcpy(tnp, "_r.sp");
+ write_xspect(tname,&r_sp);
+ }
+#endif
+ } else if (c == '3') {
+ p_sp = val.sp;
+
+ /* Get the illuminant spectrum too */
+ if (inst_illuminant(&insp, itype) != 0)
+ error ("Instrument doesn't have an FWA illuminent");
+
+#ifdef DEBUG
+ if (rd_p == 0) {
+ /* Should save instrument type/instrument illuminant spectrum !!! */
+ strcpy(tnp, "_p.sp");
+ write_xspect(tname,&p_sp);
+ }
+#endif
+ }
+
+ if (pspec) {
+ double xx[XSPECT_MAX_BANDS];
+ double y1[XSPECT_MAX_BANDS];
+ double xmin, xmax, ymin, ymax;
+ int nn;
+
+ if (val.sp.spec_n <= 0)
+ error("Instrument didn't return spectral data");
+
+ printf("Spectrum from %f to %f nm in %d steps\n",
+ val.sp.spec_wl_short, val.sp.spec_wl_long, val.sp.spec_n);
+
+ if (val.sp.spec_n > XSPECT_MAX_BANDS)
+ error("Got > %d spectral values (%d)",XSPECT_MAX_BANDS,val.sp.spec_n);
+
+ for (j = 0; j < val.sp.spec_n; j++) {
+ GCC_BUGFIX(j)
+ xx[j] = val.sp.spec_wl_short
+ + j * (val.sp.spec_wl_long - val.sp.spec_wl_short)/(val.sp.spec_n-1);
+
+ y1[j] = value_xspect(&val.sp, xx[j]);
+ }
+
+ xmax = val.sp.spec_wl_long;
+ xmin = val.sp.spec_wl_short;
+ ymin = ymax = 0.0; /* let it scale */
+ do_plot_x(xx, y1, NULL, NULL, val.sp.spec_n, 1,
+ xmin, xmax, ymin, ymax, 2.0);
+ }
+
+ continue;
+ } /* End of take a measurement */
+
+ /* Deal with selecting the instrument */
+ if (c == '4') {
+
+ if (it != NULL)
+ it->del(it);
+ it = NULL;
+ itype = instUnknown;
+
+ if (icmps == NULL)
+ icmps = new_icompaths(g_log);
+ else
+ icmps->refresh(icmps);
+ if (icmps != NULL) {
+ icompath **paths;
+ if ((paths = icmps->paths) != NULL) {
+ int i;
+ for (i = 0; ; i++) {
+ if (paths[i] == NULL)
+ break;
+ if (paths[i]->itype == instSpyder2 && setup_spyd2() == 0)
+ fprintf(stderr," %d = '%s' !! Disabled - no firmware !!\n",i+1,paths[i]->name);
+ else
+ fprintf(stderr," %d = '%s'\n",i+1,paths[i]->name);
+ }
+ printf("Select device 1 - %d: \n",i);
+ empty_con_chars();
+ c = next_con_char();
+
+ if (c < '1' || c > ('0' + i)) {
+ printf("'%c' is out of range - ignored !\n",c);
+ } else {
+ comno = c - '0';
+ }
+
+ } else {
+ fprintf(stderr,"No ports to select from!\n");
+ }
+ }
+ continue;
+ }
+ if (c == '5' || c == '6') { /* Compute result */
+ xspect cpisp; /* FWA corrected calculated initial paper reflectance */
+ double gain;
+ bfinds bf; /* Optimization context */
+ double xyz0[3], xyz1[3];
+ double tt[2], sr[2];
+ double rv;
+
+ if (i_sp.spec_n == 0) {
+ printf("Need to measure the direct illuminant\n");
+ continue;
+ }
+ if (r_sp.spec_n == 0) {
+ printf("Need to measure the illuminant reflected off paper\n");
+ continue;
+ }
+ if (p_sp.spec_n == 0) {
+ printf("Need to measure the paper\n");
+ continue;
+ }
+
+ /* Normalize direct illumination */
+ for (gain = 0.0, i = 0; i < i_sp.spec_n; i++)
+ gain += i_sp.spec[i];
+ gain /= i_sp.spec_n;
+ for (i = 0; i < i_sp.spec_n; i++)
+ i_sp.spec[i] /= gain;
+
+ /* Normalize indirect illumination */
+ for (gain = 0.0, i = 0; i < r_sp.spec_n; i++)
+ gain += r_sp.spec[i];
+ gain /= r_sp.spec_n;
+ for (i = 0; i < r_sp.spec_n; i++)
+ r_sp.spec[i] /= gain;
+
+ /* Normalize paper reflectance to 1.0 */
+ xspect_denorm(&p_sp);
+
+ bf.i_sp = &i_sp;
+ bf.r_sp = &r_sp;
+ bf.p_sp = &p_sp;
+
+ if ((bf.pap = new_xsp2cie(icxIT_custom, &i_sp, icxOT_CIE_1931_2, NULL, icSigLabData, icxClamp)) == NULL)
+ error("new_xsp2cie pap failed");
+
+ if (bf.pap->set_fwa(bf.pap, &insp, NULL, &p_sp) != 0)
+ error ("Setting FWA compensation failed");
+
+ /* Setup the equal energy to Lab conversion */
+ if ((bf.ref = new_xsp2cie(icxIT_E, NULL, icxOT_CIE_1931_2, NULL, icSigLabData, icxClamp)) == NULL)
+ error("new_xsp2cie ref failed");
+
+ /* Estimate an initial gain match */
+ tt[0] = 0.0;
+ tt[1] = 1.0;
+ bfindfunc((void *)&bf, tt);
+ icmLab2XYZ(&icmD50, xyz0, bf.lab0);
+ icmLab2XYZ(&icmD50, xyz1, bf.lab1);
+
+ gain = xyz0[1] / xyz1[1];
+//printf("~1 Target XYZ %f %f %f, is %f %f %f, gain needed = %f\n",xyz0[0],xyz0[1],xyz0[2],xyz1[0],xyz1[1],xyz1[2],gain);
+
+ for (i = 0; i < r_sp.spec_n; i++)
+ r_sp.spec[i] *= gain;
+
+ /* Check initial gain match is OK */
+ bfindfunc((void *)&bf, tt);
+ cpisp = bf.cpsp; /* Copy initial match */
+ icmLab2XYZ(&icmD50, xyz0, bf.lab0);
+ icmLab2XYZ(&icmD50, xyz1, bf.lab1);
+#ifdef NEVER
+ printf("~1 Target XYZ %f %f %f, now %f %f %f\n",xyz0[0],xyz0[1],xyz0[2],xyz1[0],xyz1[1],xyz1[2]);
+#endif
+
+ tt[0] = 0.1, tt[1] = 1.0; /* Search parameter starting values */
+ sr[0] = sr[1] = 0.1; /* Search parameter search radiuses */
+
+ if (powell(&rv, 2, tt, sr, 0.0001, 1000,
+ bfindfunc, (void *)&bf, NULL, NULL) != 0) {
+ printf("Optimization search failed\n");
+ continue;
+ }
+ printf("(Best match DE %f, UV content = %f (gain match %f))\n", rv, tt[0], tt[1]);
+
+ /* Compute the result */
+ bfindfunc((void *)&bf, tt);
+ ill = bf.ill;
+
+ if (c == '5' && nacc > 0) {
+ for (i = 0; i < ill.spec_n; i++)
+ aill.spec[i] += ill.spec[i];
+ nacc++;
+ } else {
+ aill = ill;
+ nacc = 1;
+ }
+
+ /* Save the result */
+ if (aill.spec_n == 0) {
+ printf("Nothing to save!\n");
+ } else {
+ for (i = 0; i < ill.spec_n; i++)
+ ill.spec[i] = aill.spec[i]/nacc;
+
+ if(write_xspect(outname, &ill))
+ printf("\nWriting file '%s' failed\n",outname);
+ else
+ printf("\nWriting file '%s' succeeded\n",outname);
+#ifdef DEBUG
+ strcpy(tnp, "_mpir.sp"); // Measured paper under illuminant spectrum
+ write_xspect(tname,&bf.srop);
+ strcpy(tnp, "_cpir.sp"); // Computed paper under illuminant spectrum
+ write_xspect(tname,&bf.cpsp);
+#endif
+ }
+
+ /* Plot the result */
+ if (pspec) {
+ double xx[XSPECT_MAX_BANDS];
+ double y1[XSPECT_MAX_BANDS];
+ double y2[XSPECT_MAX_BANDS];
+ double y3[XSPECT_MAX_BANDS];
+ double xmin, xmax, ymin, ymax;
+ int nn;
+
+#ifdef SHOWDXX
+ cfinds cf; /* Optimization context */
+ double tt[2], sr[2];
+ double rv;
+ xspect cpdsp; /* FWA corrected calculated daylight paper reflectance */
+
+ /* Setup the referencec comversion */
+ if ((cf.ref = new_xsp2cie(icxIT_E, NULL, icxOT_CIE_1931_2, NULL, icSigLabData, icxClamp)) == NULL)
+ error("new_xsp2cie ref failed");
+
+ cf.ill = bf.ill;
+
+ /* Set starting values */
+ tt[0] = 5000.0; /* degrees */
+ tt[1] = 1.0;
+ cfindfunc((void *)&cf, tt);
+ icmLab2XYZ(&icmD50, xyz0, cf.lab0);
+ icmLab2XYZ(&icmD50, xyz1, cf.lab1);
+
+ tt[1] = xyz0[1] / xyz1[1];
+
+ sr[0] = 10.0;
+ sr[1] = 0.1; /* Search parameter search radiuses */
+
+ if (powell(&rv, 2, tt, sr, 0.0001, 1000,
+ cfindfunc, (void *)&cf, NULL, NULL) != 0) {
+ error("Optimization search failed\n");
+ }
+ printf("(Best daylight match DE %f, temp = %f (gain match %f))\n", rv, tt[0], tt[1]);
+
+ /* Compute the result */
+ cfindfunc((void *)&cf, tt);
+
+ printf("Illuminant: Black - Measured, Red - with estimated UV, Green - daylight\n");
+
+ if (bf.ill.spec_n > XSPECT_MAX_BANDS)
+ error("Got > %d spectral values (%d)",XSPECT_MAX_BANDS,bf.ill.spec_n);
+
+ for (j = 0; j < bf.ill.spec_n; j++) {
+ GCC_BUGFIX(j)
+ xx[j] = XSPECT_XWL(&bf.ill, j);
+ y1[j] = value_xspect(bf.i_sp, xx[j]); /* Measured (black) */
+ y2[j] = value_xspect(&bf.ill, xx[j]); /* Computed (red)*/
+ y3[j] = value_xspect(&cf.dxx, xx[j]); /* Daylight match (green)*/
+ }
+
+ xmax = bf.ill.spec_wl_long;
+ xmin = bf.ill.spec_wl_short;
+ ymin = ymax = 0.0; /* let it scale */
+ do_plot_x(xx, y1, y2, y3, bf.ill.spec_n, 1,
+ xmin, xmax, ymin, ymax, 2.0);
+
+ /* Update the conversion to the matched Dayligh */
+ if (bf.pap->update_fwa_custillum(bf.pap, NULL, &cf.dxx) != 0)
+ error ("Updating FWA compensation to daylight failed");
+
+ /* Apply FWA compensation to the paper reflectance */
+ bf.pap->sconvert(bf.pap, &cpdsp, NULL, bf.p_sp);
+
+ printf("Paper Reflectance: Black - Measured, Red - Initial FWA model, Green - FWA Final FWA model\n");
+// printf("Paper Reflectance: Black - Measured, Red - FWA Modelled, Green - Daylight modelled\n");
+
+ for (j = 0; j < bf.cpsp.spec_n; j++) {
+ GCC_BUGFIX(j)
+ xx[j] = XSPECT_XWL(&bf.cpsp, j);
+ y1[j] = value_xspect(&bf.srop, xx[j]); /* Measured reflectance (black) */
+ y2[j] = value_xspect(&cpisp, xx[j]); /* Computed initial reflectance (red) */
+ y3[j] = value_xspect(&bf.cpsp, xx[j]); /* Computed final reflectance (green) */
+// y3[j] = value_xspect(&cpdsp, xx[j]); /* Computed daylight reflectance (green) */
+ }
+
+ xmax = bf.cpsp.spec_wl_long;
+ xmin = bf.cpsp.spec_wl_short;
+ ymin = ymax = 0.0; /* let it scale */
+ do_plot_x(xx, y1, y2, y3, bf.cpsp.spec_n, 1,
+ xmin, xmax, ymin, ymax, 2.0);
+
+#else // !SHOWDXX
+ printf("Illuminant: Black - Measured, Red - with estimated UV\n");
+
+ if (bf.ill.spec_n > XSPECT_MAX_BANDS)
+ error("Got > %d spectral values (%d)",XSPECT_MAX_BANDS,bf.ill.spec_n);
+
+ for (j = 0; j < bf.ill.spec_n; j++) {
+ GCC_BUGFIX(j)
+ xx[j] = XSPECT_XWL(&bf.ill, j);
+ y1[j] = value_xspect(bf.i_sp, xx[j]); /* Measured (black) */
+ y2[j] = value_xspect(&bf.ill, xx[j]); /* Computed (red)*/
+ }
+
+ xmax = bf.ill.spec_wl_long;
+ xmin = bf.ill.spec_wl_short;
+ ymin = ymax = 0.0; /* let it scale */
+ do_plot_x(xx, y1, y2, NULL, bf.ill.spec_n, 1,
+ xmin, xmax, ymin, ymax, 2.0);
+
+ printf("Paper Reflectance: Black - Measured, Red - Initial FWA model, Green - FWA Final FWA model\n");
+
+ for (j = 0; j < bf.cpsp.spec_n; j++) {
+ GCC_BUGFIX(j)
+ xx[j] = XSPECT_XWL(&bf.cpsp, j);
+ y1[j] = value_xspect(&bf.srop, xx[j]); /* Measured reflectance (black) */
+ y2[j] = value_xspect(&cpisp, xx[j]); /* Computed initial reflectance (red) */
+ y3[j] = value_xspect(&bf.cpsp, xx[j]); /* Computed final reflectance (green) */
+ }
+
+ xmax = bf.cpsp.spec_wl_long;
+ xmin = bf.cpsp.spec_wl_short;
+ ymin = ymax = 0.0; /* let it scale */
+ do_plot_x(xx, y1, y2, y3, bf.cpsp.spec_n, 1,
+ xmin, xmax, ymin, ymax, 2.0);
+
+#endif // !SHOWDXX
+ printf("%s illuminant with UV:\n",c == '5' ? "Averaged" : "Computed");
+
+ for (j = 0; j < ill.spec_n; j++) {
+ GCC_BUGFIX(j)
+ xx[j] = XSPECT_XWL(&ill, j);
+ y1[j] = value_xspect(&ill, xx[j]);
+ }
+
+ xmax = ill.spec_wl_long;
+ xmin = ill.spec_wl_short;
+ ymin = ymax = 0.0; /* let it scale */
+ do_plot_x(xx, y1, NULL, NULL, ill.spec_n, 1,
+ xmin, xmax, ymin, ymax, 2.0);
+ }
+
+ /* Make sure that the illuminant is re-measured for another reading */
+ i_sp.spec_n = 0;
+ r_sp.spec_n = 0;
+
+ continue;
+ }
+
+ if (c == '7' || c == 0x3) { /* Exit */
+ break;
+ }
+
+ } /* Next command */
+
+#ifdef DEBUG
+ printf("About to exit\n");
+#endif
+
+ /* Free instrument */
+ if (it != NULL)
+ it->del(it);
+
+ return 0;
+}
+
+
+
+
diff --git a/spectro/inflate.c b/spectro/inflate.c
new file mode 100644
index 0000000..55d8e65
--- /dev/null
+++ b/spectro/inflate.c
@@ -0,0 +1,922 @@
+
+/*
+ inflate.c -- Not copyrighted 1992 by Mark Adler
+ version c10p1, 10 January 1993
+
+ Copied from gzip version 1.2.4 source.
+
+ Adapted for use in i1d3ccss
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/types.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* SALONEINSTLIB */
+#include <fcntl.h>
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* SALONEINSTLIB */
+
+#undef PKZIP_BUG_WORKAROUND
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(text) printf text ;
+#else
+#define DBG(text)
+#endif
+
+typedef unsigned char uch;
+typedef unsigned short ush;
+typedef unsigned int ulg;
+
+#define memzero(s, n) memset((void *)(s), 0, (n))
+
+#define slide window
+
+/* Huffman code lookup table entry--this entry is four bytes for machines
+ that have 16-bit pointers (e.g. PC's in the small or medium model).
+ Valid extra bits are 0..13. e == 15 is EOB (end of block), e == 16
+ means that v is a literal, 16 < e < 32 means that v is a pointer to
+ the next table, which codes e - 16 bits, and lastly e == 99 indicates
+ an unused code. If a code with e == 99 is looked up, this implies an
+ error in the data. */
+struct huft {
+ uch e; /* number of extra bits or operation */
+ uch b; /* number of bits in this code or subcode */
+ union {
+ ush n; /* literal, length base, or distance base */
+ struct huft *t; /* pointer to next level of table */
+ } v;
+};
+
+
+/* Interface to i1d3ccss.c */
+extern unsigned int inflate_get_byte();
+extern void inflate_unget_byte();
+extern int inflate_write_output(unsigned char *buf, unsigned int len);
+
+
+/* Function prototypes */
+static int huft_build(unsigned *, unsigned, unsigned, ush *, ush *,
+ struct huft **, int *);
+static int huft_free(struct huft *);
+static int inflate_codes(struct huft *, struct huft *, int, int);
+static int inflate_stored(void);
+static int inflate_fixed(void);
+static int inflate_dynamic(void);
+static int inflate_block(int *);
+int inflate(void);
+
+/* The inflate algorithm uses a sliding 32K byte window on the uncompressed
+ stream to find repeated byte strings. This is implemented here as a
+ circular buffer. The index is updated simply by incrementing and then
+ and'ing with 0x7fff (32K-1). */
+/* It is left to other modules to supply the 32K area. It is assumed
+ to be usable as if it were declared "uch slide[32768];" or as just
+ "uch *slide;" and then malloc'ed in the latter case. The definition
+ must be in unzip.h, included above. */
+/* unsigned wp; current position in slide */
+
+#define WSIZE 0x8000
+unsigned int wp; /* current position in slide */
+uch slide[32768];
+
+static int flush_output(unsigned int w) {
+ wp = w;
+
+ if (wp == 0)
+ return 0;
+ if (inflate_write_output(slide, wp))
+ return 1;
+ wp = 0;
+ return 0;
+}
+
+/* Tables for deflate from PKZIP's appnote.txt. */
+static unsigned border[] = { /* Order of the bit length code lengths */
+ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+
+static ush cplens[] = { /* Copy lengths for literal codes 257..285 */
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
+ 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0};
+ /* note: see note #13 above about the 258 in this list. */
+
+static ush cplext[] = { /* Extra bits for literal codes 257..285 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
+ 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99}; /* 99==invalid */
+
+static ush cpdist[] = { /* Copy offsets for distance codes 0..29 */
+ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
+ 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
+ 8193, 12289, 16385, 24577};
+
+static ush cpdext[] = { /* Extra bits for distance codes */
+ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
+ 7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
+ 12, 12, 13, 13};
+
+
+/* Macros for inflate() bit peeking and grabbing.
+ The usage is:
+
+ NEEDBITS(j)
+ x = b & mask_bits[j];
+ DUMPBITS(j)
+
+ where NEEDBITS makes sure that b has at least j bits in it, and
+ DUMPBITS removes the bits from b. The macros use the variable k
+ for the number of bits in b. Normally, b and k are register
+ variables for speed, and are initialized at the beginning of a
+ routine that uses these macros from a global bit buffer and count.
+
+ If we assume that EOB will be the longest code, then we will never
+ ask for bits with NEEDBITS that are beyond the end of the stream.
+ So, NEEDBITS should not read any more bytes than are needed to
+ meet the request. Then no bytes need to be "returned" to the buffer
+ at the end of the last block.
+
+ However, this assumption is not true for fixed blocks--the EOB code
+ is 7 bits, but the other literal/length codes can be 8 or 9 bits.
+ (The EOB code is shorter than other codes because fixed blocks are
+ generally short. So, while a block always has an EOB, many other
+ literal/length codes have a significantly lower probability of
+ showing up at all.) However, by making the first table have a
+ lookup of seven bits, the EOB code will be found in that first
+ lookup, and so will not require that too many bits be pulled from
+ the stream.
+ */
+
+ulg bb; /* bit buffer */
+unsigned bk; /* bits in bit buffer */
+
+ush mask_bits[] = {
+ 0x0000,
+ 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff,
+ 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff
+};
+
+#define NEXTBYTE() (uch)inflate_get_byte()
+#define NEEDBITS(n) {while(k<(n)){b|=((ulg)NEXTBYTE())<<k;k+=8;}}
+#define DUMPBITS(n) {b>>=(n);k-=(n);}
+
+/*
+ Huffman code decoding is performed using a multi-level table lookup.
+ The fastest way to decode is to simply build a lookup table whose
+ size is determined by the longest code. However, the time it takes
+ to build this table can also be a factor if the data being decoded
+ is not very long. The most common codes are necessarily the
+ shortest codes, so those codes dominate the decoding time, and hence
+ the speed. The idea is you can have a shorter table that decodes the
+ shorter, more probable codes, and then point to subsidiary tables for
+ the longer codes. The time it costs to decode the longer codes is
+ then traded against the time it takes to make longer tables.
+
+ This results of this trade are in the variables lbits and dbits
+ below. lbits is the number of bits the first level table for literal/
+ length codes can decode in one step, and dbits is the same thing for
+ the distance codes. Subsequent tables are also less than or equal to
+ those sizes. These values may be adjusted either when all of the
+ codes are shorter than that, in which case the longest code length in
+ bits is used, or when the shortest code is *longer* than the requested
+ table size, in which case the length of the shortest code in bits is
+ used.
+
+ There are two different values for the two tables, since they code a
+ different number of possibilities each. The literal/length table
+ codes 286 possible values, or in a flat code, a little over eight
+ bits. The distance table codes 30 possible values, or a little less
+ than five bits, flat. The optimum values for speed end up being
+ about one bit more than those, so lbits is 8+1 and dbits is 5+1.
+ The optimum values may differ though from machine to machine, and
+ possibly even between compilers. Your mileage may vary.
+ */
+
+
+int lbits = 9; /* bits in base literal/length lookup table */
+int dbits = 6; /* bits in base distance lookup table */
+
+
+/* If BMAX needs to be larger than 16, then h and x[] should be ulg. */
+#define BMAX 16 /* maximum bit length of any code (16 for explode) */
+#define N_MAX 288 /* maximum number of codes in any set */
+
+
+unsigned hufts; /* track memory usage */
+
+
+static int huft_build(b, n, s, d, e, t, m)
+unsigned *b; /* code lengths in bits (all assumed <= BMAX) */
+unsigned n; /* number of codes (assumed <= N_MAX) */
+unsigned s; /* number of simple-valued codes (0..s-1) */
+ush *d; /* list of base values for non-simple codes */
+ush *e; /* list of extra bits for non-simple codes */
+struct huft **t; /* result: starting table */
+int *m; /* maximum lookup bits, returns actual */
+/* Given a list of code lengths and a maximum table size, make a set of
+ tables to decode that set of codes. Return zero on success, one if
+ the given code set is incomplete (the tables are still built in this
+ case), two if the input is invalid (all zero length codes or an
+ oversubscribed set of lengths), and three if not enough memory. */
+{
+ unsigned a; /* counter for codes of length k */
+ unsigned c[BMAX+1]; /* bit length count table */
+ unsigned f; /* i repeats in table every f entries */
+ int g; /* maximum code length */
+ int h; /* table level */
+ register unsigned i; /* counter, current code */
+ register unsigned j; /* counter */
+ register int k; /* number of bits in current code */
+ int l; /* bits per table (returned in m) */
+ register unsigned *p; /* pointer into c[], b[], or v[] */
+ register struct huft *q; /* points to current table */
+ struct huft r; /* table entry for structure assignment */
+ struct huft *u[BMAX]; /* table stack */
+ unsigned v[N_MAX]; /* values in order of bit length */
+ register int w; /* bits before this table == (l * h) */
+ unsigned x[BMAX+1]; /* bit offsets, then code stack */
+ unsigned *xp; /* pointer into x */
+ int y; /* number of dummy codes added */
+ unsigned z; /* number of entries in current table */
+
+
+ /* Generate counts for each bit length */
+ memzero(c, sizeof(c));
+ p = b; i = n;
+ do {
+// Tracecv(*p, (stderr, (n-i >= ' ' && n-i <= '~' ? "%c %d\n" : "0x%x %d\n"), n-i, *p));
+ c[*p]++; /* assume all entries <= BMAX */
+ p++; /* Can't combine with above line (Solaris bug) */
+ } while (--i);
+ if (c[0] == n) /* null input--all zero length codes */
+ {
+ *t = (struct huft *)NULL;
+ *m = 0;
+ return 0;
+ }
+
+
+ /* Find minimum and maximum length, bound *m by those */
+ l = *m;
+ for (j = 1; j <= BMAX; j++)
+ if (c[j])
+ break;
+ k = j; /* minimum code length */
+ if ((unsigned)l < j)
+ l = j;
+ for (i = BMAX; i; i--)
+ if (c[i])
+ break;
+ g = i; /* maximum code length */
+ if ((unsigned)l > i)
+ l = i;
+ *m = l;
+
+
+ /* Adjust last length count to fill out codes, if needed */
+ for (y = 1 << j; j < i; j++, y <<= 1)
+ if ((y -= c[j]) < 0) {
+ DBG(("huft_buildd, bad input: more codes than bits\n"))
+ return 2; /* bad input: more codes than bits */
+ }
+ if ((y -= c[i]) < 0) {
+ DBG(("huft_buildd, less than zero\n"))
+ return 2;
+ }
+ c[i] += y;
+
+
+ /* Generate starting offsets into the value table for each length */
+ x[1] = j = 0;
+ p = c + 1; xp = x + 2;
+ while (--i) { /* note that i == g from above */
+ *xp++ = (j += *p++);
+ }
+
+
+ /* Make a table of values in order of bit lengths */
+ p = b; i = 0;
+ do {
+ if ((j = *p++) != 0)
+ v[x[j]++] = i;
+ } while (++i < n);
+
+
+ /* Generate the Huffman codes and for each, make the table entries */
+ x[0] = i = 0; /* first Huffman code is zero */
+ p = v; /* grab values in bit order */
+ h = -1; /* no tables yet--level -1 */
+ w = -l; /* bits decoded == (l * h) */
+ u[0] = (struct huft *)NULL; /* just to keep compilers happy */
+ q = (struct huft *)NULL; /* ditto */
+ z = 0; /* ditto */
+
+ /* go through the bit lengths (k already is bits in shortest code) */
+ for (; k <= g; k++)
+ {
+ a = c[k];
+ while (a--)
+ {
+ /* here i is the Huffman code of length k bits for value *p */
+ /* make tables up to required level */
+ while (k > w + l)
+ {
+ h++;
+ w += l; /* previous table always l bits */
+
+ /* compute minimum size table less than or equal to l bits */
+ z = (z = g - w) > (unsigned)l ? l : z; /* upper limit on table size */
+ if ((f = 1 << (j = k - w)) > a + 1) /* try a k-w bit table */
+ { /* too few codes for k-w bit table */
+ f -= a + 1; /* deduct codes from patterns left */
+ xp = c + k;
+ while (++j < z) /* try smaller tables up to z bits */
+ {
+ if ((f <<= 1) <= *++xp)
+ break; /* enough codes to use up j bits */
+ f -= *xp; /* else deduct codes from patterns */
+ }
+ }
+ z = 1 << j; /* table entries for j-bit table */
+
+ /* allocate and link in new table */
+ if ((q = (struct huft *)malloc((z + 1)*sizeof(struct huft))) ==
+ (struct huft *)NULL)
+ {
+ if (h)
+ huft_free(u[0]);
+ DBG(("huft_buildd, not enough memory\n"))
+ return 3; /* not enough memory */
+ }
+ hufts += z + 1; /* track memory usage */
+ *t = q + 1; /* link to list for huft_free() */
+ *(t = &(q->v.t)) = (struct huft *)NULL;
+ u[h] = ++q; /* table starts after link */
+
+ /* connect to last table, if there is one */
+ if (h)
+ {
+ x[h] = i; /* save pattern for backing up */
+ r.b = (uch)l; /* bits to dump before this table */
+ r.e = (uch)(16 + j); /* bits in this table */
+ r.v.t = q; /* pointer to this table */
+ j = i >> (w - l); /* (get around Turbo C bug) */
+ u[h-1][j] = r; /* connect to last table */
+ }
+ }
+
+ /* set up table entry in r */
+ r.b = (uch)(k - w);
+ if (p >= v + n)
+ r.e = 99; /* out of values--invalid code */
+ else if (*p < s)
+ {
+ r.e = (uch)(*p < 256 ? 16 : 15); /* 256 is end-of-block code */
+ r.v.n = (ush)(*p); /* simple code is just the value */
+ p++; /* one compiler does not like *p++ */
+ }
+ else
+ {
+ r.e = (uch)e[*p - s]; /* non-simple--look up in lists */
+ r.v.n = d[*p++ - s];
+ }
+
+ /* fill code-like entries with r */
+ f = 1 << (k - w);
+ for (j = i >> w; j < z; j += f)
+ q[j] = r;
+
+ /* backwards increment the k-bit code i */
+ for (j = 1 << (k - 1); i & j; j >>= 1)
+ i ^= j;
+ i ^= j;
+
+ /* backup over finished tables */
+ while ((i & ((1 << w) - 1)) != x[h])
+ {
+ h--; /* don't need to update q */
+ w -= l;
+ }
+ }
+ }
+
+
+ /* Return true (1) if we were given an incomplete table */
+ return y != 0 && g != 1;
+}
+
+
+
+static int huft_free(t)
+struct huft *t; /* table to free */
+/* Free the malloc'ed tables built by huft_build(), which makes a linked
+ list of the tables it made, with the links in a dummy first entry of
+ each table. */
+{
+ register struct huft *p, *q;
+
+
+ /* Go through linked list, freeing from the malloced (t[-1]) address. */
+ p = t;
+ while (p != (struct huft *)NULL)
+ {
+ q = (--p)->v.t;
+ free((char*)p);
+ p = q;
+ }
+ return 0;
+}
+
+
+static int inflate_codes(tl, td, bl, bd)
+struct huft *tl, *td; /* literal/length and distance decoder tables */
+int bl, bd; /* number of bits decoded by tl[] and td[] */
+/* inflate (decompress) the codes in a deflated (compressed) block.
+ Return an error code or zero if it all goes ok. */
+{
+ register unsigned e; /* table entry flag/number of extra bits */
+ unsigned n, d; /* length and index for copy */
+ unsigned w; /* current window position */
+ struct huft *t; /* pointer to table entry */
+ unsigned ml, md; /* masks for bl and bd bits */
+ register ulg b; /* bit buffer */
+ register unsigned k; /* number of bits in bit buffer */
+
+
+ /* make local copies of globals */
+ b = bb; /* initialize bit buffer */
+ k = bk;
+ w = wp; /* initialize window position */
+
+ /* inflate the coded data */
+ ml = mask_bits[bl]; /* precompute masks for speed */
+ md = mask_bits[bd];
+ for (;;) /* do until end of block */
+ {
+ NEEDBITS((unsigned)bl)
+ if ((e = (t = tl + ((unsigned)b & ml))->e) > 16)
+ do {
+ if (e == 99)
+ return 1;
+ DUMPBITS(t->b)
+ e -= 16;
+ NEEDBITS(e)
+ } while ((e = (t = t->v.t + ((unsigned)b & mask_bits[e]))->e) > 16);
+ DUMPBITS(t->b)
+ if (e == 16) /* then it's a literal */
+ {
+ slide[w++] = (uch)t->v.n;
+// Tracevv((stderr, "%c", slide[w-1]));
+ if (w == WSIZE)
+ {
+ flush_output(w);
+ w = 0;
+ }
+ }
+ else /* it's an EOB or a length */
+ {
+ /* exit if end of block */
+ if (e == 15)
+ break;
+
+ /* get length of block to copy */
+ NEEDBITS(e)
+ n = t->v.n + ((unsigned)b & mask_bits[e]);
+ DUMPBITS(e);
+
+ /* decode distance of block to copy */
+ NEEDBITS((unsigned)bd)
+ if ((e = (t = td + ((unsigned)b & md))->e) > 16)
+ do {
+ if (e == 99)
+ return 1;
+ DUMPBITS(t->b)
+ e -= 16;
+ NEEDBITS(e)
+ } while ((e = (t = t->v.t + ((unsigned)b & mask_bits[e]))->e) > 16);
+ DUMPBITS(t->b)
+ NEEDBITS(e)
+ d = w - t->v.n - ((unsigned)b & mask_bits[e]);
+ DUMPBITS(e)
+// Tracevv((stderr,"\\[%d,%d]", w-d, n));
+
+ /* do the copy */
+ do {
+ n -= (e = (e = WSIZE - ((d &= WSIZE-1) > w ? d : w)) > n ? n : e);
+#if !defined(NOMEMCPY) && !defined(DEBUG)
+ if (w - d >= e) /* (this test assumes unsigned comparison) */
+ {
+ memcpy(slide + w, slide + d, e);
+ w += e;
+ d += e;
+ }
+ else /* do it slow to avoid memcpy() overlap */
+#endif /* !NOMEMCPY */
+ do {
+ slide[w++] = slide[d++];
+// Tracevv((stderr, "%c", slide[w-1]));
+ } while (--e);
+ if (w == WSIZE)
+ {
+ flush_output(w);
+ w = 0;
+ }
+ } while (n);
+ }
+ }
+
+
+ /* restore the globals from the locals */
+ wp = w; /* restore global window pointer */
+ bb = b; /* restore global bit buffer */
+ bk = k;
+
+ /* done */
+ return 0;
+}
+
+
+
+static int inflate_stored()
+/* "decompress" an inflated type 0 (stored) block. */
+{
+ unsigned n; /* number of bytes in block */
+ unsigned w; /* current window position */
+ register ulg b; /* bit buffer */
+ register unsigned k; /* number of bits in bit buffer */
+
+
+ /* make local copies of globals */
+ b = bb; /* initialize bit buffer */
+ k = bk;
+ w = wp; /* initialize window position */
+
+
+ /* go to byte boundary */
+ n = k & 7;
+ DUMPBITS(n);
+
+
+ /* get the length and its complement */
+ NEEDBITS(16)
+ n = ((unsigned)b & 0xffff);
+ DUMPBITS(16)
+ NEEDBITS(16)
+ if (n != (unsigned)((~b) & 0xffff)) {
+ DBG(("stored, error in compressed data\n"))
+ return 1; /* error in compressed data */
+ }
+ DUMPBITS(16)
+
+
+ /* read and output the compressed data */
+ while (n--)
+ {
+ NEEDBITS(8)
+ slide[w++] = (uch)b;
+ if (w == WSIZE)
+ {
+ flush_output(w);
+ w = 0;
+ }
+ DUMPBITS(8)
+ }
+
+
+ /* restore the globals from the locals */
+ wp = w; /* restore global window pointer */
+ bb = b; /* restore global bit buffer */
+ bk = k;
+ return 0;
+}
+
+
+
+static int inflate_fixed()
+/* decompress an inflated type 1 (fixed Huffman codes) block. We should
+ either replace this with a custom decoder, or at least precompute the
+ Huffman tables. */
+{
+ int i; /* temporary variable */
+ struct huft *tl; /* literal/length code table */
+ struct huft *td; /* distance code table */
+ int bl; /* lookup bits for tl */
+ int bd; /* lookup bits for td */
+ unsigned l[288]; /* length list for huft_build */
+
+
+ /* set up literal table */
+ for (i = 0; i < 144; i++)
+ l[i] = 8;
+ for (; i < 256; i++)
+ l[i] = 9;
+ for (; i < 280; i++)
+ l[i] = 7;
+ for (; i < 288; i++) /* make a complete, but wrong code set */
+ l[i] = 8;
+ bl = 7;
+ if ((i = huft_build(l, 288, 257, cplens, cplext, &tl, &bl)) != 0) {
+ DBG(("fixed, huft_build return nz\n"))
+ return i;
+ }
+
+
+ /* set up distance table */
+ for (i = 0; i < 30; i++) /* make an incomplete code set */
+ l[i] = 5;
+ bd = 5;
+ if ((i = huft_build(l, 30, 0, cpdist, cpdext, &td, &bd)) > 1)
+ {
+ huft_free(tl);
+ DBG(("fixed, huft_build return > 1\n"))
+ return i;
+ }
+
+
+ /* decompress until an end-of-block code */
+ if (inflate_codes(tl, td, bl, bd)) {
+ DBG(("fixed, inflate_codes return nz\n"))
+ return 1;
+ }
+
+
+ /* free the decoding tables, return */
+ huft_free(tl);
+ huft_free(td);
+ return 0;
+}
+
+
+
+static int inflate_dynamic()
+/* decompress an inflated type 2 (dynamic Huffman codes) block. */
+{
+ int i; /* temporary variables */
+ unsigned j;
+ unsigned l; /* last length */
+ unsigned m; /* mask for bit lengths table */
+ unsigned n; /* number of lengths to get */
+ struct huft *tl; /* literal/length code table */
+ struct huft *td; /* distance code table */
+ int bl; /* lookup bits for tl */
+ int bd; /* lookup bits for td */
+ unsigned nb; /* number of bit length codes */
+ unsigned nl; /* number of literal/length codes */
+ unsigned nd; /* number of distance codes */
+#ifdef PKZIP_BUG_WORKAROUND
+ unsigned ll[288+32]; /* literal/length and distance code lengths */
+#else
+ unsigned ll[286+30]; /* literal/length and distance code lengths */
+#endif
+ register ulg b; /* bit buffer */
+ register unsigned k; /* number of bits in bit buffer */
+
+
+ /* make local bit buffer */
+ b = bb;
+ k = bk;
+
+
+ /* read in table lengths */
+ NEEDBITS(5)
+ nl = 257 + ((unsigned)b & 0x1f); /* number of literal/length codes */
+ DUMPBITS(5)
+ NEEDBITS(5)
+ nd = 1 + ((unsigned)b & 0x1f); /* number of distance codes */
+ DUMPBITS(5)
+ NEEDBITS(4)
+ nb = 4 + ((unsigned)b & 0xf); /* number of bit length codes */
+ DUMPBITS(4)
+#ifdef PKZIP_BUG_WORKAROUND
+ if (nl > 288 || nd > 32)
+#else
+ if (nl > 286 || nd > 30)
+#endif
+ {
+ DBG(("dynamic, bad length\n"))
+ return 1; /* bad lengths */
+ }
+
+
+ /* read in bit-length-code lengths */
+ for (j = 0; j < nb; j++)
+ {
+ NEEDBITS(3)
+ ll[border[j]] = (unsigned)b & 7;
+ DUMPBITS(3)
+ }
+ for (; j < 19; j++)
+ ll[border[j]] = 0;
+
+
+ /* build decoding table for trees--single level, 7 bit lookup */
+ bl = 7;
+ if ((i = huft_build(ll, 19, 19, NULL, NULL, &tl, &bl)) != 0)
+ {
+ if (i == 1)
+ huft_free(tl);
+ DBG(("dynamic, huft_build failed\n"))
+ return i; /* incomplete code set */
+ }
+
+
+ /* read in literal and distance code lengths */
+ n = nl + nd;
+ m = mask_bits[bl];
+ i = l = 0;
+ while ((unsigned)i < n)
+ {
+ NEEDBITS((unsigned)bl)
+ j = (td = tl + ((unsigned)b & m))->b;
+ DUMPBITS(j)
+ j = td->v.n;
+ if (j < 16) /* length of code in bits (0..15) */
+ ll[i++] = l = j; /* save last length in l */
+ else if (j == 16) /* repeat last length 3 to 6 times */
+ {
+ NEEDBITS(2)
+ j = 3 + ((unsigned)b & 3);
+ DUMPBITS(2)
+ if ((unsigned)i + j > n) {
+ DBG(("dynamic, i + j > n\n"))
+ return 1;
+ }
+ while (j--)
+ ll[i++] = l;
+ }
+ else if (j == 17) /* 3 to 10 zero length codes */
+ {
+ NEEDBITS(3)
+ j = 3 + ((unsigned)b & 7);
+ DUMPBITS(3)
+ if ((unsigned)i + j > n) {
+ DBG(("dynamic, i + j > n\n"))
+ return 1;
+ } while (j--)
+ ll[i++] = 0;
+ l = 0;
+ }
+ else /* j == 18: 11 to 138 zero length codes */
+ {
+ NEEDBITS(7)
+ j = 11 + ((unsigned)b & 0x7f);
+ DUMPBITS(7)
+ if ((unsigned)i + j > n) {
+ DBG(("dynamic, i + j > n\n"))
+ return 1;
+ }
+ while (j--)
+ ll[i++] = 0;
+ l = 0;
+ }
+ }
+
+
+ /* free decoding table for trees */
+ huft_free(tl);
+
+
+ /* restore the global bit buffer */
+ bb = b;
+ bk = k;
+
+
+ /* build the decoding tables for literal/length and distance codes */
+ bl = lbits;
+ if ((i = huft_build(ll, nl, 257, cplens, cplext, &tl, &bl)) != 0)
+ {
+ if (i == 1) {
+ fprintf(stderr, " incomplete literal tree\n");
+ huft_free(tl);
+ }
+ DBG(("dynamic, incomplete code set\n"))
+ return i; /* incomplete code set */
+ }
+ bd = dbits;
+ if ((i = huft_build(ll + nl, nd, 0, cpdist, cpdext, &td, &bd)) != 0)
+ {
+ if (i == 1) {
+ fprintf(stderr, " incomplete distance tree\n");
+#ifdef PKZIP_BUG_WORKAROUND
+ i = 0;
+ }
+#else
+ huft_free(td);
+ }
+ huft_free(tl);
+ DBG(("dynamic, incomplete distance tree\n"))
+ return i; /* incomplete code set */
+#endif
+ }
+
+
+ /* decompress until an end-of-block code */
+ if (inflate_codes(tl, td, bl, bd)) {
+ DBG(("dynamic, an end-of-block code\n"))
+ return 1;
+ }
+
+ /* free the decoding tables, return */
+ huft_free(tl);
+ huft_free(td);
+ return 0;
+}
+
+
+
+static int inflate_block(e)
+int *e; /* last block flag */
+/* decompress an inflated block */
+{
+ unsigned t; /* block type */
+ register ulg b; /* bit buffer */
+ register unsigned k; /* number of bits in bit buffer */
+
+
+ /* make local bit buffer */
+ b = bb;
+ k = bk;
+
+
+ /* read in last block bit */
+ NEEDBITS(1)
+ *e = (int)b & 1;
+ DUMPBITS(1)
+
+
+ /* read in block type */
+ NEEDBITS(2)
+ t = (unsigned)b & 3;
+ DUMPBITS(2)
+
+
+ /* restore the global bit buffer */
+ bb = b;
+ bk = k;
+
+
+ /* inflate that block type */
+ if (t == 2)
+ return inflate_dynamic();
+ if (t == 0)
+ return inflate_stored();
+ if (t == 1)
+ return inflate_fixed();
+
+
+ /* bad block type */
+ return 2;
+}
+
+
+
+int inflate()
+/* decompress an inflated entry */
+{
+ int e; /* last block flag */
+ int r; /* result code */
+ unsigned h; /* maximum struct huft's malloc'ed */
+
+
+ /* initialize window, bit buffer */
+ wp = 0;
+ bk = 0;
+ bb = 0;
+
+
+ /* decompress until the last block */
+ h = 0;
+ do {
+ hufts = 0;
+ if ((r = inflate_block(&e)) != 0)
+ return r;
+ if (hufts > h)
+ h = hufts;
+ } while (!e);
+
+ /* Undo too much lookahead. The next read will be byte aligned so we
+ * can discard unused bits in the last meaningful byte.
+ */
+ while (bk >= 8) {
+ bk -= 8;
+ inflate_unget_byte();
+ }
+
+ /* flush out slide */
+ flush_output(wp);
+
+
+ /* return success */
+#ifdef DEBUG
+ fprintf(stderr, "<%u> ", h);
+#endif /* DEBUG */
+ return 0;
+}
diff --git a/spectro/inst.c b/spectro/inst.c
new file mode 100644
index 0000000..d57a84a
--- /dev/null
+++ b/spectro/inst.c
@@ -0,0 +1,1359 @@
+
+ /* Abstract instrument class implemenation */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 10/3/2001
+ *
+ * Copyright 2001 - 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#else
+#include "sa_config.h"
+#endif /* !SALONEINSTLIB */
+#include "numsup.h"
+#include "xspect.h"
+#include "ccmx.h"
+#include "ccss.h"
+#include "conv.h"
+#include "insttypes.h"
+
+#include "icoms.h"
+#include "inst.h"
+#include "insttypeinst.h"
+
+#include "sort.h"
+
+#ifdef ENABLE_SERIAL
+static instType ser_inst_type(icoms *p,
+inst_code (*uicallback)(void *cntx, inst_ui_purp purp), void *cntx);
+#endif /* ENABLE_SERIAL */
+
+/* ------------------------------------ */
+/* Default methods for instrument class */
+
+/* Establish communications at the indicated baud rate. */
+/* Timout in to seconds, and return non-zero error code */
+static inst_code init_coms(
+inst *p,
+baud_rate br, /* Baud rate */
+flow_control fc, /* Flow control */
+double tout) { /* Timeout */
+ return inst_unsupported;
+}
+
+/* Initialise or re-initialise the INST */
+/* return non-zero on an error, with inst error code */
+static inst_code init_inst(
+inst *p) {
+ return inst_unsupported;
+}
+
+/* Return the instrument type */
+static instType get_itype(inst *p) {
+ if (p != NULL)
+ return p->itype;
+ return instUnknown;
+}
+
+/* Return the instrument serial number. */
+/* (This will be an empty string if there is no serial no) */
+static char *get_serial_no(inst *p) {
+ return "";
+}
+
+/* Return the instrument mode capabilities */
+static void capabilities(inst *p, inst_mode *cap1,
+ inst2_capability *cap2, inst3_capability *cap3) {
+ if (cap1 != NULL)
+ *cap1 = inst_mode_none;
+ if (cap2 != NULL)
+ *cap2 = inst2_none;
+ if (cap3 != NULL)
+ *cap3 = inst3_none;
+}
+
+/* Return current or given configuration available measurement modes. */
+/* This default routine assumed one confiration which will be */
+/* all the measurement modes returned by capabilities(). */
+static inst_code meas_config(inst *p,
+inst_mode *mmodes, /* Return all the valid measurement modes */
+ /* for the current configuration */
+inst_cal_cond *cconds, /* Return the valid calibration conditions */
+int *conf_ix) { /* Request mode for given configuration, and */
+ /* return the index of the configuration returned */
+ if (mmodes != NULL) {
+ *mmodes = inst_mode_none;
+
+ /* Available measurement modes is current capabilities */
+ p->capabilities(p, mmodes, NULL, NULL);
+ }
+
+ if (cconds != NULL)
+ *cconds = inst_calc_unknown;
+
+ if (conf_ix != NULL)
+ *conf_ix = 0;
+
+ return inst_ok;
+}
+
+/* Check the device measurement mode */
+static inst_code check_mode(
+inst *p,
+inst_mode m) { /* Requested mode */
+ return inst_unsupported;
+}
+
+/* Set the device measurement mode */
+static inst_code set_mode(
+inst *p,
+inst_mode m) { /* Requested mode */
+ return inst_unsupported;
+}
+
+/* Return array of display type selectors */
+static inst_code get_disptypesel(
+inst *pp,
+int *pnsels, /* Return number of display types */
+inst_disptypesel **psels, /* Return the array of display types */
+int allconfig, /* nz to return list for all configs, not just current. */
+int recreate /* nz to re-check for new ccmx & ccss files */
+) {
+ return inst_unsupported;
+}
+
+/* Set the display type */
+static inst_code set_disptype(
+inst *pp,
+int ix /* index into the inst_disptypesel[] */
+) {
+ return inst_unsupported;
+}
+
+/* Get a status or set or get an option (default implementation) */
+inst_code inst_get_set_opt_def(
+inst *p,
+inst_opt_type m, /* Requested option type */
+va_list args) { /* Option parameters */
+
+ return inst_unsupported;
+}
+
+/* Get a status or set or get an option */
+static inst_code get_set_opt(
+inst *p,
+inst_opt_type m, /* Requested status type */
+...) { /* Status parameters */
+ inst_code rv;
+ va_list args;
+
+ va_start(args, m);
+ rv = inst_get_set_opt_def(p, m, args); /* Call the above */
+ va_end(args);
+
+ return rv;
+}
+
+/* Read a full test chart composed of multiple sheets */
+/* DOESN'T use the trigger mode */
+/* Return the inst error code */
+static inst_code read_chart(
+inst *p,
+int npatch, /* Total patches/values in chart */
+int pich, /* Passes (rows) in chart */
+int sip, /* Steps in each pass (patches in each row) */
+int *pis, /* Passes in each strip (rows in each sheet) */
+int chid, /* Chart ID number */
+ipatch *vals) { /* Pointer to array of values */
+
+ return inst_unsupported;
+}
+
+/* For an xy instrument, release the paper */
+/* (if cap has inst_xy_holdrel) */
+/* Return the inst error code */
+static inst_code xy_sheet_release(
+inst *p) {
+ return inst_unsupported;
+}
+
+/* For an xy instrument, hold the paper */
+/* (if cap has inst_xy_holdrel) */
+/* Return the inst error code */
+static inst_code xy_sheet_hold(
+inst *p) {
+ return inst_unsupported;
+}
+
+/* For an xy instrument, allow the user to locate a point */
+/* (if cap has inst_xy_locate) */
+/* Return the inst error code */
+static inst_code xy_locate_start(
+inst *p) {
+ return inst_unsupported;
+}
+
+/* For an xy instrument, read back the location */
+/* (if cap has inst_xy_locate) */
+/* Return the inst error code */
+static inst_code xy_get_location(
+inst *p,
+double *x, double *y) {
+ return inst_unsupported;
+}
+
+/* For an xy instrument, end allowing the user to locate a point */
+/* (if cap has inst_xy_locate) */
+/* Return the inst error code */
+static inst_code xy_locate_end(
+inst *p) {
+ return inst_unsupported;
+}
+
+/* For an xy instrument, move the measurement point */
+/* (if cap has inst_xy_position) */
+/* Return the inst error code */
+static inst_code xy_position(
+inst *p,
+int measure, /* nz for measure point, z for locate point */
+double x, double y) {
+ return inst_unsupported;
+}
+
+/* For an xy instrument, try and clear the table after an abort */
+/* Return the inst error code */
+static inst_code xy_clear(
+inst *p) {
+ return inst_unsupported;
+}
+
+/* Read a sheet full of patches using xy mode */
+/* DOESN'T use the trigger mode */
+/* Return the inst error code */
+static inst_code read_xy(
+inst *p,
+int pis, /* Passes in strips (letters in sheet) */
+int sip, /* Steps in pass (numbers in sheet) */
+int npatch, /* Total patches in strip (skip in last pass) */
+char *pname, /* Starting pass name (' A' to 'ZZ') */
+char *sname, /* Starting step name (' 1' to '99') */
+double ox, double oy, /* Origin of first patch */
+double ax, double ay, /* pass increment */
+double aax, double aay, /* pass offset for odd patches */
+double px, double py, /* step/patch increment */
+ipatch *vals) { /* Pointer to array of values */
+ return inst_unsupported;
+}
+
+/* Read a set of strips (applicable to strip reader) */
+/* Obeys the trigger mode set */
+/* Return the inst error code */
+static inst_code read_strip(
+inst *p,
+char *name, /* Strip name (up to 7 chars) */
+int npatch, /* Number of patches in each pass */
+char *pname, /* Pass name (3 chars) */
+int sguide, /* Guide number (decrements by 5) */
+double pwid, /* Patch width in mm (For DTP20/DTP41) */
+double gwid, /* Gap width in mm (For DTP20/DTP41) */
+double twid, /* Trailer width in mm (For DTP41T) */
+ipatch *vals) { /* Pointer to array of values */
+ return inst_unsupported;
+}
+
+/* Read a single sample (applicable to spot instruments) */
+/* Obeys the trigger mode set */
+/* Return the inst error code */
+static inst_code read_sample(
+inst *p,
+char *name, /* Patch identifier (up to 7 chars) */
+ipatch *val, /* Pointer to value to be returned */
+instClamping clamp) { /* NZ to clamp XYZ to be +ve */
+ return inst_unsupported;
+}
+
+/* Measure the emissive refresh rate in Hz. */
+static inst_code read_refrate(
+inst *p,
+double *ref_rate) {
+ if (ref_rate != NULL)
+ *ref_rate = 0.0;
+ return inst_unsupported;
+}
+
+/* Determine if a calibration is needed. Returns inst_calt_none if not */
+/* or unknown, or a mask of the needed calibrations. */
+/* Default implementation - call through to get_n_a_cals() */
+static inst_cal_type needs_calibration(
+inst *p) {
+ inst_cal_type n_cals;
+
+ if (p->get_n_a_cals(p, &n_cals, NULL) != inst_ok)
+ return inst_calt_none;
+
+ return n_cals;
+}
+
+/* Return combined mask of of needed or available inst_cal_type's */
+/* for the current mode. */
+inst_code inst_get_n_a_cals(
+struct _inst *p,
+inst_cal_type *n_cals,
+inst_cal_type *a_cals) {
+
+ if (n_cals != NULL)
+ *n_cals = inst_calc_none;
+
+ if (a_cals != NULL)
+ *a_cals = inst_mode_none;
+
+ return inst_ok;
+}
+
+/* Request an instrument calibration. */
+/* DOESN'T use the trigger mode */
+/* Return inst_unsupported if calibration type is not appropriate. */
+static inst_code calibrate(
+inst *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, filter ID) */
+ return inst_unsupported;
+}
+
+/* Measure a display update delay. It is assumed that a */
+/* White to black change has been made to the displayed color, */
+/* and this will measure the time it took for the update to */
+/* be noticed by the instrument, up to 1.0 seconds. */
+/* inst_misread will be returned on failure to find a transition. */
+static inst_code meas_delay(
+inst *p,
+int *msecdelay) { /* Return the number of msec */
+ return inst_unsupported;
+}
+
+/* Return the last calibrated refresh rate in Hz. Returns: */
+/* inst_unsupported - if this instrument doesn't suport a refresh mode */
+/* or is unable to retrieve the refresh rate */
+/* inst_needs_cal - if the refresh rate value is not valid */
+/* inst_misread - if no refresh rate could be determined */
+/* inst_ok - on returning a valid reading */
+static inst_code get_refr_rate(
+struct _inst *p,
+double *ref_rate) {
+ if (ref_rate != NULL)
+ *ref_rate = 0.0;
+ return inst_unsupported;
+}
+
+/* Set the calibrated refresh rate in Hz. */
+/* Set refresh rate to 0.0 to mark it as invalid */
+/* Rates outside the range 5.0 to 150.0 Hz will return an error */
+static inst_code set_refr_rate(
+struct _inst *p,
+double ref_rate) {
+ return inst_unsupported;
+}
+
+/* Insert a compensation filter in the instrument readings */
+/* This is typically needed if an adapter is being used, that alters */
+/* the spectrum of the light reaching the instrument */
+/* To remove the filter, pass NULL for the filter filename */
+static inst_code comp_filter(
+inst *p,
+char *filtername) { /* File containing compensating filter */
+ return inst_unsupported;
+}
+
+/* Insert a colorimetric correction matrix in the instrument XYZ readings */
+/* This is only valid for colorimetric instruments. */
+/* To remove the matrix, pass NULL for the matrix */
+static inst_code col_cor_mat(
+struct _inst *p,
+double mtx[3][3]) { /* XYZ matrix */
+ return inst_unsupported;
+}
+
+/* Use a Colorimeter Calibration Spectral Set to set the */
+/* instrumen calibration. */
+/* This is only valid for colorimetric instruments. */
+/* To set calibration back to default, pass NULL for ccss. */
+static inst_code col_cal_spec_set(
+inst *pp,
+xspect *sets,
+int no_sets) {
+ return inst_unsupported;
+}
+
+/* Supply a user interaction callback function. */
+static void set_uicallback(
+inst *pp,
+inst_code (*uicallback)(void *cntx, inst_ui_purp purp),
+void *cntx) {
+ pp->uicallback = uicallback;
+ pp->uic_cntx = cntx;
+}
+
+/* Supply an asynchronous event callback function. */
+static void set_event_callback(
+inst *pp,
+void (*eventcallback)(void *cntx, inst_event_type event),
+void *cntx) {
+ pp->eventcallback = eventcallback;
+ pp->event_cntx = cntx;
+}
+
+/* Generic inst error codes interpretation */
+static char *inst_interp_error(inst *p, inst_code ec) {
+ switch(ec & inst_mask) {
+ case inst_ok:
+ return "No error";
+ case inst_notify:
+ return "Notification";
+ case inst_warning:
+ return "Warning";
+ case inst_no_coms:
+ return "Internal error - communications needed but not established";
+ case inst_no_init:
+ return "Internal error - initialisation needed but not done";
+ case inst_internal_error:
+ return "Internal software error";
+ case inst_coms_fail:
+ return "Communications failure";
+ case inst_unknown_model:
+ return "Not expected instrument model";
+ case inst_protocol_error:
+ return "Communication protocol breakdown";
+ case inst_user_abort:
+ return "User hit Abort Key";
+ case inst_user_trig:
+ return "User hit Trigger Key";
+ case inst_misread:
+ return "Measurement misread";
+ case inst_nonesaved:
+ return "No saved data to read";
+ case inst_nochmatch:
+ return "Chart being read doesn't match chart expected";
+ case inst_needs_cal:
+ return "Instrument needs calibration";
+ case inst_cal_setup:
+ return "Instrument needs to be setup for calibration";
+ case inst_unsupported:
+ return "Unsupported function";
+ case inst_unexpected_reply:
+ return "Unexpected Reply";
+ case inst_wrong_config:
+ return "Wrong Sensor Position";
+ case inst_wrong_setup:
+ return "Wrong or conflicting setup";
+ case inst_bad_parameter:
+ return "Bad Parameter Value";
+ case inst_hardware_fail:
+ return "Hardware Failure";
+ case inst_other_error:
+ return "Non-specific error";
+ }
+ return "Unknown inst error code";
+}
+
+/* Instrument specific error codes interpretation */
+static char *interp_error(inst *p, int ec) {
+ return "interp_error is uninplemented in base class!";
+}
+
+/* Return the last serial communication error code */
+/* (This is used for deciding fallback/retry strategies) */
+static int last_scomerr(inst *p) {
+ return p->icom->lserr;
+}
+
+/* Convert instrument specific inst_wrong_config error to inst_config enum */
+static inst_config config_enum(inst *p, int ec) {
+ return inst_conf_unknown;
+}
+
+/* ---------------------------------------------- */
+/* Virtual constructor. */
+/* Return NULL for unknown instrument, */
+/* or serial instrument if nocoms == 0. */
+extern inst *new_inst(
+icompath *path, /* Device path to instrument */
+int nocoms, /* Don't open if communications are needed to establish inst type */
+a1log *log, /* log to use */
+inst_code (*uicallback)(void *cntx, inst_ui_purp purp), /* optional uicallback */
+void *cntx /* Context for callback */
+) {
+ instType itype = instUnknown; /* Actual type */
+ icoms *icom;
+ inst *p = NULL;
+
+ if ((icom = new_icoms(path, log)) == NULL) {
+ return NULL;
+ }
+
+ /* Set instrument type from USB port, if not specified */
+ itype = icom->itype; /* Instrument type if its known from usb/hid */
+#ifdef ENABLE_SERIAL
+ if (itype == instUnknown && !nocoms)
+ itype = ser_inst_type(icom, uicallback, cntx); /* Else type from serial */
+#endif /* ENABLE_SERIAL */
+
+
+#ifdef ENABLE_SERIAL
+ if (itype == instDTP22)
+ p = (inst *)new_dtp22(icom, itype);
+ else if (itype == instDTP41)
+ p = (inst *)new_dtp41(icom, itype);
+ else if (itype == instDTP51)
+ p = (inst *)new_dtp51(icom, itype);
+ else if (itype == instDTP92)
+ p = (inst *)new_dtp92(icom, itype);
+ else if ((itype == instSpectrolino ) ||
+ (itype == instSpectroScan ) ||
+ (itype == instSpectroScanT))
+ p = (inst *)new_ss(icom, itype);
+/* NYI
+ else if (itype == instSpectrocam)
+ p = (inst *)new_spc(icom, itype);
+*/
+#endif /* ENABLE_SERIAL */
+
+#ifdef ENABLE_USB
+ if (itype == instDTP94)
+ p = (inst *)new_dtp92(icom, itype);
+ else if (itype == instDTP20)
+ p = (inst *)new_dtp20(icom, itype);
+ else if (itype == instI1Disp1 ||
+ itype == instI1Disp2)
+ p = (inst *)new_i1disp(icom, itype);
+ else if (itype == instI1Disp3)
+ p = (inst *)new_i1d3(icom, itype);
+ else if (itype == instI1Monitor)
+ p = (inst *)new_i1pro(icom, itype);
+ else if ((itype == instI1Pro) ||
+ (itype == instI1Pro2))
+ p = (inst *)new_i1pro(icom, itype);
+ else if (itype == instColorMunki)
+ p = (inst *)new_munki(icom, itype);
+ else if (itype == instHCFR)
+ p = (inst *)new_hcfr(icom, itype);
+ else if (itype == instSpyder2)
+ p = (inst *)new_spyd2(icom, itype);
+ else if (itype == instSpyder3)
+ p = (inst *)new_spyd2(icom, itype);
+ else if (itype == instSpyder4)
+ p = (inst *)new_spyd2(icom, itype);
+ else if (itype == instHuey)
+ p = (inst *)new_huey(icom, itype);
+ else if (itype == instSmile)
+ p = (inst *)new_i1disp(icom, itype);
+ else if (itype == instColorHug)
+ p = (inst *)new_colorhug(icom, itype);
+#endif /* ENABLE_USB */
+
+ /* Nothing matched */
+ if (p == NULL)
+ return NULL;
+
+ /* Add default methods if constructor did not supply them */
+ if (p->init_coms == NULL)
+ p->init_coms = init_coms;
+ if (p->init_inst == NULL)
+ p->init_inst = init_inst;
+ if (p->get_itype == NULL)
+ p->get_itype = get_itype;
+ if (p->get_serial_no == NULL)
+ p->get_serial_no = get_serial_no;
+ if (p->capabilities == NULL)
+ p->capabilities = capabilities;
+ if (p->meas_config == NULL)
+ p->meas_config = meas_config;
+ if (p->set_mode == NULL)
+ p->set_mode = set_mode;
+ if (p->get_disptypesel == NULL)
+ p->get_disptypesel = get_disptypesel;
+ if (p->set_disptype == NULL)
+ p->set_disptype = set_disptype;
+ if (p->get_set_opt == NULL)
+ p->get_set_opt = get_set_opt;
+ if (p->read_chart == NULL)
+ p->read_chart = read_chart;
+ if (p->xy_sheet_release == NULL)
+ p->xy_sheet_release = xy_sheet_release;
+ if (p->xy_sheet_hold == NULL)
+ p->xy_sheet_hold = xy_sheet_hold;
+ if (p->xy_locate_start == NULL)
+ p->xy_locate_start = xy_locate_start;
+ if (p->xy_get_location == NULL)
+ p->xy_get_location = xy_get_location;
+ if (p->xy_locate_end == NULL)
+ p->xy_locate_end = xy_locate_end;
+ if (p->xy_position == NULL)
+ p->xy_position = xy_position;
+ if (p->xy_clear == NULL)
+ p->xy_clear = xy_clear;
+ if (p->read_xy == NULL)
+ p->read_xy = read_xy;
+ if (p->read_strip == NULL)
+ p->read_strip = read_strip;
+ if (p->read_sample == NULL)
+ p->read_sample = read_sample;
+ if (p->read_refrate == NULL)
+ p->read_refrate = read_refrate;
+ if (p->needs_calibration == NULL)
+ p->needs_calibration = needs_calibration;
+ if (p->get_n_a_cals == NULL)
+ p->get_n_a_cals = inst_get_n_a_cals;
+ if (p->calibrate == NULL)
+ p->calibrate = calibrate;
+ if (p->meas_delay == NULL)
+ p->meas_delay = meas_delay;
+ if (p->get_refr_rate == NULL)
+ p->get_refr_rate = get_refr_rate;
+ if (p->set_refr_rate == NULL)
+ p->set_refr_rate = set_refr_rate;
+ if (p->comp_filter == NULL)
+ p->comp_filter = comp_filter;
+ if (p->col_cor_mat == NULL)
+ p->col_cor_mat = col_cor_mat;
+ if (p->col_cal_spec_set == NULL)
+ p->col_cal_spec_set = col_cal_spec_set;
+ if (p->set_uicallback == NULL)
+ p->set_uicallback = set_uicallback;
+ if (p->set_event_callback == NULL)
+ p->set_event_callback = set_event_callback;
+ if (p->inst_interp_error == NULL)
+ p->inst_interp_error = inst_interp_error;
+ if (p->interp_error == NULL)
+ p->interp_error = interp_error;
+ if (p->last_scomerr == NULL)
+ p->last_scomerr = last_scomerr;
+ if (p->config_enum == NULL)
+ p->config_enum = config_enum;
+
+ /* Set the provided user interaction callback */
+ p->set_uicallback(p, uicallback, cntx);
+
+ return p;
+}
+
+/* --------------------------------------------------- */
+/* Display type list support */
+
+/* Default display type UI selector characters:
+
+ g Generic
+ n Non-refresh (Generic)
+ r Refresh (Generic)
+
+ c CRT
+ l LCD (Generic or CCFL Backlight)
+ f LCD, CCFL Backlight
+ b LCD, RGB LED Backlight
+ L LCD, Wide Gamut, CCFL Backlight
+ B LCD, Wide Gamut, RGB LED Backlight
+ e LCD, White LED Backlight
+ o OLED
+ a AMOLED
+ p Projector (Generic)
+ m Plasma
+
+ F Factory base calibration
+ R Raw sensor values
+ */
+
+
+/* Free a display type list */
+void inst_del_disptype_list(inst_disptypesel *list, int no) {
+
+ if (list != NULL) {
+ int i;
+ for (i = 0; i < no; i++) {
+ if (list[i].path != NULL)
+ free(list[i].path);
+ if (list[i].sets != NULL)
+ free(list[i].sets);
+ }
+ free(list);
+ }
+}
+
+/* Ensure that the list is large enough for n+1 entries (+1 == end marker) */
+/* Return NULL on malloc error */
+static inst_disptypesel *expand_dlist(inst_disptypesel *list, int nlist, int *nalist) {
+
+ if ((nlist+1) > *nalist) {
+ int xnalist = (2 * nlist + 6);
+ inst_disptypesel *xlist;
+
+ if ((xlist = realloc(list, xnalist * sizeof(inst_disptypesel))) == NULL) {
+ inst_del_disptype_list(list, nlist);
+ *nalist = 0;
+ return NULL;
+ }
+ *nalist = xnalist;
+ list = xlist;
+ }
+
+ /* Set end marker */
+ list[nlist].flags = inst_dtflags_end;
+ list[nlist].cbid = 0;
+ list[nlist].sel[0] = '\000';
+ list[nlist].desc[0] = '\000';
+ list[nlist].refr = 0;
+ list[nlist].ix = 0;
+ list[nlist].path = NULL;
+ list[nlist].sets = NULL;
+ list[nlist].no_sets = 0;
+
+ return list;
+}
+
+/* For each selector we need to:
+
+ check each selector char
+ if already used,
+ remove it.
+ if no selector remain,
+ allocate a free one.
+ mark all used selectors
+*/
+
+/* Set the selection characters */
+/* return NZ if we have run out */
+static int set_sel(char *sel, char *usels, int *k, char *asels) {
+ char *d, *s;
+
+ /* First remove any used chars from selector */
+ for (d = s = sel; *s != '\000'; s++) {
+ if (usels[*s] == 0)
+ *d++ = *s;
+ }
+ *d = '\000';
+
+ /* Add a selector if we need one */
+ if (sel[0] == '\000') {
+
+ /* Locate the next unused selector */
+ for (;;) {
+ if (asels[*k] == '\000') /* Run out of selectors */
+ return 1;
+ if (usels[*k] == 0)
+ break;
+ (*k)++;
+ }
+ sel[0] = asels[*k];
+ sel[1] = '\000';
+ usels[sel[0]] = 1;
+ (*k)++;
+ }
+
+ return 0;
+}
+
+/* Create the display type list */
+inst_code inst_creat_disptype_list(inst *p,
+int *pndtlist, /* Number in returned list */
+inst_disptypesel **pdtlist, /* Returned list */
+inst_disptypesel *sdtlist, /* Static list */
+int doccss, /* Add installed ccss files */
+int doccmx /* Add matching installed ccmx files */
+) {
+ inst_disptypesel *list = NULL;
+ int i, j, k, nlist = 0, nalist = 0;
+ char usels[256]; /* Used selectors */
+ static char *asels = "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+ /* free the old list */
+ inst_del_disptype_list(*pdtlist, *pndtlist);
+ *pdtlist = NULL;
+ *pndtlist = 0;
+
+ for (i = 0; i < 256; i++)
+ usels[i] = 0;
+ k = 0;
+
+ /* Add entries from the static list */
+ /* Count the number in the static list */
+ for (i = 0; !(sdtlist[i].flags & inst_dtflags_end); i++) {
+
+ if ((list = expand_dlist(list, ++nlist, &nalist)) == NULL)
+ return inst_internal_error;
+
+ list[nlist-1] = sdtlist[i]; /* Struct copy */
+
+ if (set_sel(list[nlist-1].sel, usels, &k, asels)) {
+ a1loge(p->log, 1, "inst_creat_disptype_list run out of selectors\n");
+ break;
+ }
+ }
+
+ k = 0; /* Next selector index */
+
+ /* Add any ccss's */
+ if (doccss) {
+ iccss *ss_list;
+ if ((ss_list = list_iccss(NULL)) == NULL) {
+ free(list);
+ return inst_internal_error;
+ }
+
+ for (i = 0; ss_list[i].path != NULL; i++) {
+
+ if ((list = expand_dlist(list, ++nlist, &nalist)) == NULL)
+ return inst_internal_error;
+
+ list[nlist-1].flags = inst_dtflags_ccss;
+
+ if (ss_list[i].sel != NULL) {
+ strncpy(list[nlist-1].sel, ss_list[i].sel, INST_DTYPE_SEL_LEN);
+ list[nlist-1].sel[INST_DTYPE_SEL_LEN-1] = '\000';
+ }
+ if (set_sel(list[nlist-1].sel, usels, &k, asels)) {
+ a1loge(p->log, 1, "inst_creat_disptype_list run out of selectors\n");
+ break;
+ }
+
+ strncpy(list[nlist-1].desc, ss_list[i].desc, INST_DTYPE_DESC_LEN);
+ list[nlist-1].desc[INST_DTYPE_DESC_LEN-1] = '\000';
+ list[nlist-1].refr = ss_list[i].refr;
+ list[nlist-1].ix = 0;
+ list[nlist-1].path = ss_list[i].path; ss_list[i].path = NULL;
+ list[nlist-1].sets = ss_list[i].sets; ss_list[i].sets = NULL;
+ list[nlist-1].no_sets = ss_list[i].no_sets; ss_list[i].no_sets = 0;
+ }
+ }
+
+ /* Add any ccmx's */
+ if (doccmx) {
+ iccmx *ss_list;
+
+ /* Just ccmx's for this instrument */
+ if ((ss_list = list_iccmx(inst_name(p->itype), NULL)) == NULL) {
+ free(list);
+ return inst_internal_error;
+ }
+
+ for (i = 0; ss_list[i].path != NULL; i++) {
+
+ /* Check that we can find the matching base calibation */
+ for (j = 0; j < nlist; j++) {
+ if (list[j].cbid == ss_list[i].cbid)
+ break;
+ }
+ if (j >= nlist) {
+ a1loge(p->log, 1, "inst_creat_disptype_list can't find cbid %d for '%s'\n",list[j].cbid, list[j].path);
+ continue;
+ }
+
+ if ((list = expand_dlist(list, ++nlist, &nalist)) == NULL)
+ return inst_internal_error;
+
+ list[nlist-1].flags = inst_dtflags_ccmx;
+
+ if (ss_list[i].sel != NULL) {
+ strncpy(list[nlist-1].sel, ss_list[i].sel, INST_DTYPE_SEL_LEN);
+ list[nlist-1].sel[INST_DTYPE_SEL_LEN-1] = '\000';
+ }
+ if (set_sel(list[nlist-1].sel, usels, &k, asels)) {
+ a1loge(p->log, 1, "inst_creat_disptype_list run out of selectors\n");
+ break;
+ }
+ strncpy(list[nlist-1].desc, ss_list[i].desc, INST_DTYPE_DESC_LEN);
+ list[nlist-1].desc[INST_DTYPE_DESC_LEN-1] = '\000';
+ list[nlist-1].refr = ss_list[i].refr;
+ list[nlist-1].ix = list[j].ix; /* Copy underlying cal selection from base */
+ list[nlist-1].path = ss_list[i].path; ss_list[i].path = NULL;
+ icmCpy3x3(list[nlist-1].mat, ss_list[i].mat);
+ }
+ }
+
+ if (pndtlist != NULL)
+ *pndtlist = nlist;
+ if (pdtlist != NULL)
+ *pdtlist = list;
+
+ return inst_ok;
+}
+
+/* ============================================================= */
+/* CCMX location support */
+
+/* return a list of installed ccmx files. */
+/* if inst != NULL, return those that match the given instrument. */
+/* The list is sorted by description and terminated by a NULL entry. */
+/* If no is != NULL, return the number in the list */
+/* Return NULL and -1 if there is a malloc error */
+iccmx *list_iccmx(char *inst, int *no) {
+ int i, j;
+ iccmx *rv;
+
+ char **paths = NULL;
+ int npaths = 0;
+
+
+ npaths = xdg_bds(NULL, &paths, xdg_data, xdg_read, xdg_user,
+ "ArgyllCMS/\052.ccmx" XDG_FUDGE "color/\052.ccmx"
+ );
+
+ if ((rv = malloc(sizeof(iccmx) * (npaths + 1))) == NULL) {
+ a1loge(g_log, 1, "list_iccmx: malloc of paths failed\n");
+ xdg_free(paths, npaths);
+ if (no != NULL) *no = -1;
+ return NULL;
+ }
+
+ for (i = j = 0; i < npaths; i++) {
+ ccmx *cs;
+ int len;
+ char *pp;
+ char *tech, *disp;
+ int cbid, refr;
+
+ if ((cs = new_ccmx()) == NULL) {
+ a1loge(g_log, 1, "list_iccmx: new_ccmx failed\n");
+ for (--j; j>= 0; j--) {
+ free(rv[j].path);
+ free(rv[j].desc);
+ }
+ xdg_free(paths, npaths);
+ if (no != NULL) *no = -1;
+ return NULL;
+ }
+ if (cs->read_ccmx(cs, paths[i])) {
+ cs->del(cs);
+ continue; /* Skip any unreadable ccmx's */
+ }
+
+ /* Skip any that don't match */
+ if (inst != NULL && cs->inst != NULL && strcmp(inst, cs->inst) != 0)
+ continue;
+
+ if ((tech = cs->tech) == NULL)
+ tech = "";
+ if ((disp = cs->disp) == NULL)
+ disp = "";
+ cbid = cs->cbid;
+ refr = cs->refrmode;
+ len = strlen(tech) + strlen(disp) + 4;
+ if ((pp = malloc(len)) == NULL) {
+ a1loge(g_log, 1, "list_iccmx: malloc failed\n");
+ for (--j; j >= 0; j--) {
+ free(rv[j].path);
+ free(rv[j].desc);
+ }
+ cs->del(cs);
+ free(rv);
+ xdg_free(paths, npaths);
+ if (no != NULL) *no = -1;
+ return NULL;
+ }
+ if ((rv[j].path = strdup(paths[i])) == NULL) {
+ a1loge(g_log, 1, "list_iccmx: strdup failed\n");
+ for (--j; j >= 0; j--) {
+ free(rv[j].path);
+ free(rv[j].desc);
+ }
+ cs->del(cs);
+ free(rv);
+ free(pp);
+ xdg_free(paths, npaths);
+ if (no != NULL) *no = -1;
+ return NULL;
+ }
+ strcpy(pp, tech);
+ strcat(pp, " (");
+ strcat(pp, disp);
+ strcat(pp, ")");
+ rv[j].desc = pp;
+ rv[j].cbid = cbid;
+ rv[j].refr = refr;
+ rv[j].sel = cs->sel; cs->sel = NULL;
+ icmCpy3x3(rv[j].mat, cs->matrix);
+ cs->del(cs);
+ j++;
+ }
+ xdg_free(paths, npaths);
+ rv[j].path = NULL;
+ rv[j].desc = NULL;
+ rv[j].cbid = 0;
+ rv[j].refr = -1;
+ rv[j].sel = NULL;
+ if (no != NULL)
+ *no = j;
+
+ /* Sort the list */
+#define HEAP_COMPARE(A,B) (strcmp(A.desc, B.desc) < 0)
+ HEAPSORT(iccmx, rv, j)
+#undef HEAP_COMPARE
+
+ return rv;
+}
+
+/* Free up a iccmx list */
+void free_iccmx(iccmx *list) {
+ int i;
+
+ if (list != NULL) {
+ for (i = 0; list[i].path != NULL || list[i].desc != NULL; i++) {
+ if (list[i].path != NULL)
+ free(list[i].path);
+ if (list[i].desc != NULL)
+ free(list[i].desc);
+ if (list[i].sel != NULL)
+ free(list[i].sel);
+ }
+ free(list);
+ }
+}
+
+/* ============================================================= */
+/* CCSS location support */
+
+/* return a list of installed ccss files. */
+/* The list is sorted by description and terminated by a NULL entry. */
+/* If no is != NULL, return the number in the list */
+/* Return NULL and -1 if there is a malloc error */
+iccss *list_iccss(int *no) {
+ int i, j;
+ iccss *rv;
+
+ char **paths = NULL;
+ int npaths = 0;
+
+
+ npaths = xdg_bds(NULL, &paths, xdg_data, xdg_read, xdg_user,
+ "ArgyllCMS/\052.ccss" XDG_FUDGE "color/\052.ccss"
+ );
+
+ if ((rv = malloc(sizeof(iccss) * (npaths + 1))) == NULL) {
+ a1loge(g_log, 1, "list_iccss: malloc of paths failed\n");
+ xdg_free(paths, npaths);
+ if (no != NULL) *no = -1;
+ return NULL;
+ }
+
+ for (i = j = 0; i < npaths; i++) {
+ ccss *cs;
+ int len;
+ char *pp;
+ char *tech, *disp;
+ int refr;
+
+ if ((cs = new_ccss()) == NULL) {
+ a1loge(g_log, 1, "list_iccss: new_ccss failed\n");
+ for (--j; j>= 0; j--) {
+ free(rv[j].path);
+ free(rv[j].desc);
+ }
+ xdg_free(paths, npaths);
+ if (no != NULL) *no = -1;
+ return NULL;
+ }
+ if (cs->read_ccss(cs, paths[i])) {
+ cs->del(cs);
+ continue; /* Skip any unreadable ccss's */
+ }
+
+ if ((tech = cs->tech) == NULL)
+ tech = "";
+ if ((disp = cs->disp) == NULL)
+ disp = "";
+ refr = cs->refrmode;
+ len = strlen(tech) + strlen(disp) + 4;
+ if ((pp = malloc(len)) == NULL) {
+ a1loge(g_log, 1, "list_iccss: malloc failed\n");
+ for (--j; j >= 0; j--) {
+ free(rv[j].path);
+ free(rv[j].desc);
+ }
+ cs->del(cs);
+ free(rv);
+ xdg_free(paths, npaths);
+ if (no != NULL) *no = -1;
+ return NULL;
+ }
+ if ((rv[j].path = strdup(paths[i])) == NULL) {
+ a1loge(g_log, 1, "list_iccss: strdup failed\n");
+ for (--j; j >= 0; j--) {
+ free(rv[j].path);
+ free(rv[j].desc);
+ }
+ cs->del(cs);
+ free(rv);
+ free(pp);
+ xdg_free(paths, npaths);
+ if (no != NULL) *no = -1;
+ return NULL;
+ }
+ strcpy(pp, tech);
+ strcat(pp, " (");
+ strcat(pp, disp);
+ strcat(pp, ")");
+ rv[j].desc = pp;
+ rv[j].refr = refr;
+ rv[j].sel = cs->sel; cs->sel = NULL;
+ rv[j].sets = cs->samples; cs->samples = NULL;
+ rv[j].no_sets = cs->no_samp; cs->no_samp = 0;
+ cs->del(cs);
+ j++;
+ }
+ xdg_free(paths, npaths);
+ rv[j].path = NULL;
+ rv[j].desc = NULL;
+ rv[j].refr = -1;
+ rv[j].sel = NULL;
+ rv[j].sets = NULL;
+ rv[j].no_sets = 0;
+ if (no != NULL)
+ *no = j;
+
+ /* Sort the list */
+#define HEAP_COMPARE(A,B) (strcmp(A.desc, B.desc) < 0)
+ HEAPSORT(iccss, rv, j)
+#undef HEAP_COMPARE
+
+ return rv;
+}
+
+/* Free up a iccss list */
+void free_iccss(iccss *list) {
+ int i;
+
+ if (list != NULL) {
+ for (i = 0; list[i].path != NULL || list[i].desc != NULL; i++) {
+ if (list[i].path != NULL)
+ free(list[i].path);
+ if (list[i].desc != NULL)
+ free(list[i].desc);
+ if (list[i].sel != NULL)
+ free(list[i].sel);
+ if (list[i].sets != NULL)
+ free(list[i].sets);
+ }
+ free(list);
+ }
+}
+
+/* ============================================================= */
+
+#ifdef ENABLE_SERIAL
+static void hex2bin(char *buf, int len);
+
+/* Heuristicly determine the instrument type for */
+/* a serial connection, and instUnknown if not serial. */
+/* Set it in icoms and also return it. */
+static instType ser_inst_type(
+ icoms *p,
+ inst_code (*uicallback)(void *cntx, inst_ui_purp purp), /* optional uicallback */
+ void *cntx /* Context for callback */
+) {
+ instType rv = instUnknown;
+ char buf[100];
+ baud_rate brt[] = { baud_9600, baud_19200, baud_4800, baud_2400,
+ baud_1200, baud_38400, baud_57600, baud_115200,
+ baud_600, baud_300, baud_110, baud_nc };
+ unsigned int etime;
+ unsigned int bi, i;
+ int se, len;
+ int xrite = 0;
+ int ss = 0;
+ int so = 0;
+
+#ifdef ENABLE_USB
+ if (p->usbd != NULL || p->hidd != NULL)
+ return p->itype;
+#endif /* ENABLE_USB */
+
+ bi = 0;
+
+ /* The tick to give up on */
+ etime = msec_time() + (long)(1000.0 * 20.0 + 0.5);
+
+ a1logd(p->log, 1, "ser_inst_type: Trying different baud rates (%u msec to go)\n",etime - msec_time());
+
+ /* Until we time out, find the correct baud rate */
+ for (i = bi; msec_time() < etime; i++) {
+ if (brt[i] == baud_nc)
+ i = 0;
+ if ((se = p->set_ser_port(p, fc_none, brt[i], parity_none,
+ stop_1, length_8)) != ICOM_OK) {
+ a1logd(p->log, 5, "ser_inst_type: set_ser_port failed with 0x%x\n",se);
+ return instUnknown; /* Give up */
+ }
+
+// a1logd(p->log, 5, "brt = %d\n",brt[i]);
+ if ((se = p->write_read(p, ";D024\r\n", buf, 100, '\r', 1, 0.5)) != inst_ok) {
+ /* Check for user abort */
+ if (uicallback != NULL) {
+ inst_code ev;
+ if ((ev = uicallback(cntx, inst_negcoms)) == inst_user_abort) {
+ a1logd(p->log, 5, "ser_inst_type: User aborted\n");
+ return instUnknown;
+ }
+ }
+ }
+ len = strlen(buf);
+
+// a1logd(p->log, 5, "len = %d\n",len);
+ if (len < 4)
+ continue;
+
+ /* Is this an X-Rite error value such as "<01>" ? */
+ if (buf[0] == '<' && isdigit(buf[1]) && isdigit(buf[2]) && buf[3] == '>') {
+// a1logd(p->log, 5, "xrite\n");
+ xrite = 1;
+ break;
+ }
+
+ /* Is this a Spectrolino error resonse ? */
+ if (len >= 5 && strncmp(buf, ":26", 3) == 0) {
+// a1logd(p->log, 5, "spectrolino\n");
+ so = 1;
+ break;
+ }
+ /* Is this a SpectroScan response ? */
+ if (len >= 7 && strncmp(buf, ":D183", 5) == 0) {
+// a1logd(p->log, 5, "spectroscan\n");
+ ss = 1;
+ break;
+ }
+ }
+
+ if (msec_time() >= etime) { /* We haven't established comms */
+ a1logd(p->log, 5, "ser_inst_type: Failed to establish coms\n");
+ return instUnknown;
+ }
+
+ a1logd(p->log, 5, "ser_inst_type: Got coms with instrument\n");
+
+ /* Spectrolino */
+ if (so) {
+ rv = instSpectrolino;
+ }
+
+ /* SpectroScan */
+ if (ss) {
+ rv = instSpectroScan;
+ if ((se = p->write_read(p, ";D030\r\n", buf, 100, '\n', 1, 1.5)) == 0) {
+ if (strlen(buf) >= 41) {
+ hex2bin(&buf[5], 12);
+// a1logd(p->log, 5, "spectroscan type = '%s'\n",buf);
+ if (strncmp(buf, ":D190SpectroScanT", 17) == 0)
+ rv = instSpectroScanT;
+ }
+ }
+ }
+ if (xrite) {
+
+ /* Get the X-Rite model and version number */
+ if ((se = p->write_read(p, "SV\r\n", buf, 100, '>', 1, 2.5)) != 0)
+ return instUnknown;
+
+ if (strlen(buf) >= 12) {
+ if (strncmp(buf,"X-Rite DTP22",12) == 0)
+ rv = instDTP22;
+ if (strncmp(buf,"X-Rite DTP41",12) == 0)
+ rv = instDTP41;
+ if (strncmp(buf,"X-Rite DTP42",12) == 0)
+ rv = instDTP41;
+ if (strncmp(buf,"X-Rite DTP51",12) == 0)
+ rv = instDTP51;
+ if (strncmp(buf,"X-Rite DTP52",12) == 0)
+ rv = instDTP51;
+ if (strncmp(buf,"X-Rite DTP92",12) == 0)
+ rv = instDTP92;
+ if (strncmp(buf,"X-Rite DTP94",12) == 0)
+ rv = instDTP94;
+ }
+ }
+
+ a1logd(p->log, 5, "ser_inst_type: Instrument type is '%s'\n", inst_name(rv));
+
+ p->close_port(p); /* Or should we leave it open ?? */
+
+ p->itype = rv;
+
+ return rv;
+}
+
+/* Convert an ASCII Hex character to an integer. */
+static int h2b(char c) {
+ if (c >= '0' && c <= '9')
+ return (c-(int)'0');
+ if (c >= 'A' && c <= 'F')
+ return (10 + c-(int)'A');
+ if (c >= 'a' && c <= 'f')
+ return (10 + c-(int)'a');
+ return 0;
+}
+
+/* Convert a Hex encoded buffer into binary. */
+/* len is number of bytes out */
+static void hex2bin(char *buf, int len) {
+ int i;
+
+ for (i = 0; i < len; i++) {
+ buf[i] = (char)((h2b(buf[2 * i + 0]) << 4)
+ | (h2b(buf[2 * i + 1]) << 0));
+ }
+}
+
+#endif /* ENABLE_SERIAL */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spectro/inst.h b/spectro/inst.h
new file mode 100644
index 0000000..5b5d710
--- /dev/null
+++ b/spectro/inst.h
@@ -0,0 +1,999 @@
+
+#ifndef INST_H
+
+ /* instlib API definition. */
+
+ /* See spotread.c, chartread.c, illumread.c & ccxxmake.c for examples of */
+ /* the API usage. */
+
+ /* Abstract base class for common color instrument interface */
+ /* and other common instrument stuff. */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 15/3/2001
+ *
+ * Copyright 2001 - 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.
+ */
+
+#include "insttypes.h" /* libinst Includes this functionality */
+#include "icoms.h" /* libinst Includes this functionality */
+#include "conv.h"
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/* ------------------------------------------------- */
+/* aprox. debug level guide:
+
+ 1,2 Applications, internal errors
+ 2,3 High level instrument drivers
+ 4,5 High level instrument communications
+ 6,7 High level serial/USB communications
+ 8,9 Low level serial/USB communications
+
+*/
+
+/* ------------------------------------------------- */
+/* Structure for holding an instrument patch reading */
+
+#define ICOM_MAX_LOC_LEN 10
+
+/* Type of measurement result */
+typedef enum { /* XYZ units, spectral units */
+ inst_mrt_none = 0, /* Not set */
+ inst_mrt_emission = 1, /* cd/m^2, mW/m^2/nm */
+ inst_mrt_ambient = 2, /* Lux/3.1415926 ?? */
+ inst_mrt_emission_flash = 3, /* cd/m^2.s, mW/m^2/nm.s*/
+ inst_mrt_ambient_flash = 4, /* Lux/3.1415926/s ?? */
+ inst_mrt_reflective = 5, /* %, %/nm */
+ inst_mrt_transmissive = 6 /* %, %/nm */
+} inst_meas_type;
+
+struct _ipatch {
+ char loc[ICOM_MAX_LOC_LEN]; /* patch location */
+
+ inst_meas_type mtype; /* Measurement type */
+
+ int XYZ_v; /* XYZ valid */
+ double XYZ[3]; /* XYZ values */
+
+ xspect sp; /* Spectrum. sp.spec_n > 0 if valid */
+ /* Reflectance/Transmittance 0.0 .. 100.0%, norm = 100.0 */
+ /* or mW/nm/m^2, norm = 1.0, etc. */
+
+ double duration; /* Apparent total duration in seconds (flash measurement) */
+ /* Typicall limited to sampling rate of instrument. */
+
+}; typedef struct _ipatch ipatch;
+
+/* ---------------------------------------- */
+/* Instrument interface abstract base class */
+
+/* Abstract return codes in ms byte. */
+/* Instrument dependant codes in ls byte. */
+/* Note :- update inst_interp_error() in inst.c if anything here is changed. */
+/* and also check all the instrument specific XXX_interp_code() routines too. */
+typedef enum {
+ inst_ok = 0x0000,
+ inst_notify = 0x0100, /* A Notification */
+ inst_warning = 0x0200, /* A Warning */
+ inst_no_coms = 0x0300, /* init_coms() hasn't been called yet */
+ inst_no_init = 0x0400, /* init_inst() hasn't been called yet */
+ inst_unsupported = 0x0500, /* Unsupported function */
+ inst_internal_error = 0x0600, /* Internal software error */
+ inst_coms_fail = 0x0700, /* Communication failure */
+ inst_unknown_model = 0x0800, /* Not the expected instrument */
+ inst_protocol_error = 0x0900, /* Read or Write protocol error */
+ inst_user_abort = 0x0A00, /* User hit escape */
+ inst_user_trig = 0x0C00, /* User hit trigger key */
+ inst_misread = 0x0E00, /* Bad reading, or strip misread */
+ inst_nonesaved = 0x0F00, /* No saved data to read */
+ inst_nochmatch = 0x1000, /* Chart doesn't match */
+ inst_needs_cal = 0x1100, /* Instrument needs calibration, and read retried */
+ inst_cal_setup = 0x1200, /* Calibration retry with correct setup is needed */
+ inst_wrong_config = 0x1300, /* Retry with correct inst. config./sensor posn. needed */
+ inst_unexpected_reply = 0x1400, /* Unexpected Reply */
+ inst_wrong_setup = 0x1500, /* Setup is wrong or conflicting */
+ inst_hardware_fail = 0x1600, /* Hardware failure */
+ inst_bad_parameter = 0x1700, /* Bad parameter value */
+ inst_other_error = 0x1800, /* Some other error */
+ inst_mask = 0xff00, /* inst_code mask value */
+ inst_imask = 0x00ff /* instrument specific mask value */
+} inst_code;
+
+/* Instrument capabilities & modes */
+/* Note that due to the binary combinations, capabilities is not definititive */
+/* as to valid modes. check_mode() is definitive. */
+typedef enum {
+ inst_mode_none = 0x00000000, /* No capability or mode */
+
+ /* Mode of light measurement */
+ inst_mode_reflection = 0x00000001, /* General reflection mode */
+ inst_mode_s_reflection = 0x00000002, /* General saved reflection mode */
+ inst_mode_transmission = 0x00000004, /* General transmission mode */
+ inst_mode_emission = 0x00000008, /* General emission mode */
+ inst_mode_illum_mask = 0x0000000f, /* Mask of sample illumination sub mode */
+
+ /* Access mode of measurement */
+ inst_mode_spot = 0x00000010, /* General spot measurement mode */
+ inst_mode_strip = 0x00000020, /* General strip measurement mode */
+ inst_mode_xy = 0x00000040, /* General X-Y measurement mode */
+ inst_mode_chart = 0x00000080, /* General chart measurement mode */
+ inst_mode_ambient = 0x00000100, /* General ambient measurement mode */
+ inst_mode_ambient_flash = 0x00000200, /* General ambient flash measurement mode */
+ inst_mode_tele = 0x00000400, /* General telephoto measurement mode */
+ // Hmm. Should there be a tele_flash mode ????
+ inst_mode_sub_mask = 0x000007f0, /* Mask of sub-mode */
+
+ /* Basic mode */
+ inst_mode_basic_mask = inst_mode_illum_mask | inst_mode_sub_mask,
+
+/*
+ possible UV modes:
+
+ Do the reflective measurement with UV rather than normal illuminant
+ [ Should this be reflectivity against 'A', or absolute ?? ]
+ (ie. spot, strip, xy or chart). inst_mode_ref_uv
+
+ Do a white & UV measurement at the start of each strip reading.
+ Return result with special call after each strip read.
+ inst_mode_ref_uv_strip_1
+
+ Do a dual white & UV measurement
+ [ Can do in one hit for spot, but how should two strip passes be handled ?
+ ie. two separate strip reads of phase 1 & then 2 ? ]
+ (ie. spot, strip, xy or chart). inst_mode_ref_uv_2pass
+
+ Get normal illuminant spectrum.
+
+ Get UV spectrum.
+
+ get_meas_illum_spectrum(mode);
+ */
+
+ /* Extra dependent modes */
+ inst_mode_emis_nonadaptive = 0x00000800, /* Emissom Non-adaptive mode */
+ inst_mode_ref_uv = 0x00001000, /* Ultra Violet measurement mode */
+ inst_mode_emis_refresh_ovd = 0x00002000, /* Emissom Refresh mode override */
+ inst_mode_emis_norefresh_ovd = 0x00006000, /* Emissom Non-refresh mode override */
+ inst_mode_dep_extra_mask = 0x00007800, /* Mask of measurement modifiers */
+
+ /* Extra independent modes */
+ inst_mode_colorimeter = 0x00004000, /* Colorimetric mode */
+ inst_mode_spectral = 0x00008000, /* Spectral mode */
+ inst_mode_highres = 0x00010000, /* High Resolution Spectral mode */
+ inst_mode_extra_mask = 0x0001c000, /* Mask of extra modes */
+
+ /* Configured for calibration & capable of returning it from inst_mode_calibration */
+ inst_mode_calibration = 0x80000000, /* Configured for calibration */
+
+ /* Combined operating modes (from above): */
+ /* These mode capabilities are also use to set the mode */
+ /* Test for a mode should be IMODETST(flags, mode) */
+ inst_mode_ref_spot = inst_mode_spot /* Reflection spot measurement mode */
+ | inst_mode_reflection,
+ inst_mode_ref_strip = inst_mode_strip /* Reflection strip measurement mode */
+ | inst_mode_reflection,
+ inst_mode_ref_xy = inst_mode_xy /* Reflection X-Y measurement mode */
+ | inst_mode_reflection,
+ inst_mode_ref_chart = inst_mode_chart /* Reflection Chart measurement mode */
+ | inst_mode_reflection,
+
+ inst_mode_s_ref_spot = inst_mode_spot /* Saved reflection spot measurement mode */
+ | inst_mode_s_reflection,
+ inst_mode_s_ref_strip = inst_mode_strip /* Saved reflection strip measurement mode */
+ | inst_mode_s_reflection,
+ inst_mode_s_ref_xy = inst_mode_xy /* Saved reflection X-Y measurement mode */
+ | inst_mode_s_reflection,
+ inst_mode_s_ref_chart = inst_mode_chart /* Saved reflection Chart measurement mode */
+ | inst_mode_s_reflection,
+
+ inst_mode_trans_spot = inst_mode_spot /* Transmission spot measurement mode */
+ | inst_mode_transmission,
+ inst_mode_trans_strip = inst_mode_strip /* Transmission strip measurement mode */
+ | inst_mode_transmission,
+ inst_mode_trans_xy = inst_mode_xy /* Transmission X-Y measurement mode */
+ | inst_mode_transmission,
+ inst_mode_trans_chart = inst_mode_chart /* Transmission chart measurement mode */
+ | inst_mode_transmission,
+
+ inst_mode_emis_spot = inst_mode_spot /* Spot emission measurement mode */
+ | inst_mode_emission,
+ inst_mode_emis_tele = inst_mode_tele /* Telephoto emission measurement mode */
+ | inst_mode_emission,
+ inst_mode_emis_ambient = inst_mode_ambient /* Ambient emission measurement mode */
+ | inst_mode_emission,
+ inst_mode_emis_ambient_flash = inst_mode_ambient_flash /* Ambient emission flash measurement */
+ | inst_mode_emission,
+
+ inst_mode_emis_strip = inst_mode_strip /* Strip emission measurement mode */
+ | inst_mode_emission,
+
+ inst_mode_measurement_mask = inst_mode_illum_mask /* Mask of exclusive measurement modes */
+ | inst_mode_sub_mask
+ | inst_mode_dep_extra_mask
+} inst_mode;
+
+/* Test for a specific mode */
+#define IMODETST(mbits, mode) (((mbits) & (mode)) == (mode))
+
+/* Test for a specific mode in capability and mode */
+#define IMODETST2(mcap, mbits, mode) (IMODETST(mcap, mode) && IMODETST(mbits, mode))
+
+/* Instrument capabilities 2 */
+/* (Available capabilities may be mode dependent) */
+typedef enum {
+ inst2_none = 0x00000000, /* No capabilities */
+
+ inst2_xy_holdrel = 0x00000001, /* Needs paper hold/release between each sheet */
+ inst2_xy_locate = 0x00000002, /* Needs user to locate patch locations */
+ inst2_xy_position = 0x00000004, /* Can be positioned at a given location */
+
+ inst2_meas_disp_update = 0x00000010, /* Is able to measure display update delay */
+ inst2_refresh_rate = 0x00000020, /* Is able to retrieve the calibrated refresh rate */
+
+ inst2_prog_trig = 0x00000100, /* Progromatic trigger measure capability */
+ inst2_user_trig = 0x00000200, /* User trigger measure capability */
+ inst2_switch_trig = 0x00000400, /* Inst. switch trigger measure capability */
+ inst2_user_switch_trig = 0x00000800, /* User or switch trigger measure capability */
+
+ inst2_bidi_scan = 0x00001000, /* Try and recognise patches scanned from either dir. */
+ inst2_cal_using_switch = 0x00002000, /* DTP22 special - use switch triggered calibration */
+ inst2_has_scan_toll = 0x00004000, /* Instrument will honour modified scan tollerance */
+ inst2_no_feedback = 0x00008000, /* Instrument doesn't give any user feedback */
+
+ inst2_has_leds = 0x00200000, /* Instrument has some user viewable indicator LEDs */
+ inst2_has_sensmode = 0x00400000, /* Instrument can report it's sensors mode */
+
+ inst2_has_battery = 0x00800000, /* Instrument is battery powered */
+
+ inst2_disptype = 0x01000000, /* Has a display type selector */
+ inst2_ccmx = 0x02000000, /* Colorimeter Correction Matrix capability */
+ inst2_ccss = 0x04000000, /* Colorimeter Cal. Spectral Set capability */
+
+ inst2_ambient_mono = 0x08000000, /* The ambient measurement is monochrome */
+
+ inst2_emis_refr_meas = 0x10000000, /* Has an emissive refresh rate measurement func. */
+
+} inst2_capability;
+
+/* Instrument capabilities 3 (room for expansion) */
+/* (Available capabilities may be mode dependent) */
+typedef enum {
+ inst3_none = 0x00000000, /* No capabilities */
+
+} inst3_capability;
+
+typedef enum {
+ inst_dtflags_none = 0x0000, /* no flags */
+ inst_dtflags_default = 0x0001, /* default display type */
+ inst_dtflags_ccss = 0x0002, /* ccss */
+ inst_dtflags_ccmx = 0x0004, /* ccmx */
+ inst_dtflags_end = 0x8000 /* end marker */
+
+} inst_dtflags;
+
+#define INST_DTYPE_SEL_LEN 10
+#define INST_DTYPE_DESC_LEN 100
+
+/* Structure used to return display type selection information */
+typedef struct _inst_disptypesel {
+
+ /* Public: */
+ inst_dtflags flags; /* Attribute flags */
+ int cbid; /* Calibration base ID. NZ if valid ccmx calibration base. */
+ /* Should remain constant between releases */
+ char sel[INST_DTYPE_SEL_LEN]; /* String of selector characters */
+ char desc[INST_DTYPE_DESC_LEN]; /* Textural description */
+ int refr; /* Refresh mode flag */
+
+ /* Private: */
+ int ix; /* Internal index, */
+
+ // Stuff for ccss & ccmx
+ char *path; /* Path to ccss or ccmx */
+ double mat[3][3]; /* ccmx matrix */
+ xspect *sets; /* ccss set of sample spectra */
+ int no_sets; /* ccs number of sets */
+
+} inst_disptypesel;
+
+/* Instrument options for get_set_opt() */
+typedef enum {
+ inst_opt_unknown = 0x0000, /* Option not specified */
+
+ inst_stat_saved_readings = 0x0001, /* Return status of saved reading values */
+ /* [1 argument type *inst_stat_savdrd] */
+ inst_stat_s_spot = 0x0002, /* Return number of saved spot readings */
+ /* [1 argument type *int] */
+ inst_stat_s_strip = 0x0003, /* Return saved strip details */
+ /* [args: int *no_patches, int *no_rows */
+ /* int *pat_per_row] */
+ inst_stat_s_xy = 0x0004, /* Return saved strip details */
+ /* [args: int *no_sheets, int *no_patches, */
+ /* int *no_rows, int *pat_per_row] */
+ inst_stat_s_chart = 0x0005, /* Return number saved chart details */
+ /* [args: int *no_patches, int *no_rows */
+ /* int *pat_per_row, int *chart_id, */
+ /* int *missing_row ] */
+
+ inst_stat_battery = 0x0006, /* Return charged status of battery */
+ /* [1 argument type *double : range 0.0 - 1.0 ] */
+
+ inst_stat_get_filter = 0x0007, /* Get a filter configuration */
+ /* [1 argument type *inst_opt_filter ] */
+
+ inst_opt_initcalib = 0x0008, /* Enable initial calibration (default) [No args] */
+ inst_opt_noinitcalib = 0x0009, /* Disable initial calibration if < losecs since last */ /* opened, or losecs == 0 [int losecs] */
+
+ inst_opt_set_ccss_obs = 0x000A, /* Set the observer used with ccss device types */
+ /* Only takes effect after inst_opt_set_disp_type */
+ /* or col_cal_spec_set() */
+ /* [args: icxObserverType obType,*/
+ /* xspect custObserver[3] */
+
+ inst_opt_get_dtinfo = 0x000C, /* Get current display type information */
+ /* [args: int *refrmode,*/
+ /* int *cbid] */
+
+ inst_opt_set_filter = 0x000D, /* Set a filter configuration */
+ /* [1 argument type inst_opt_filter] */
+
+ inst_opt_trig_prog = 0x000E, /* Trigger progromatically [No args] */
+ inst_opt_trig_user = 0x000F, /* Trigger from user via uicallback [No args] */
+ inst_opt_trig_switch = 0x0010, /* Trigger using instrument switch [No args] */
+ inst_opt_trig_user_switch = 0x0011, /* Trigger from user via uicallback or switch (def) [No args] */
+
+ inst_opt_highres = 0x0012, /* Enable high resolution spectral mode */
+ inst_opt_stdres = 0x0013, /* Revert to standard resolution spectral mode */
+
+ inst_opt_scan_toll = 0x0014, /* Modify the patch scan recognition tollnce [double] */
+
+ inst_opt_get_gen_ledmask = 0x0015, /* Get the bitmask for general indication LEDs [*int] */
+ /* (More specialized indicator masks go here) */
+ inst_opt_set_led_state = 0x0016, /* Set the current LED state. 0 = off, 1 == on [int] */
+ inst_opt_get_led_state = 0x0017, /* Get the current LED state. 0 = off, 1 == on [*int] */
+ inst_opt_get_pulse_ledmask = 0x0018, /* Get the bitmask for pulseable ind. LEDs [*int] */
+ inst_opt_set_led_pulse_state= 0x0019, /* Set the current LED state. [double period_in_secs, */
+ /* double on_time_prop, double trans_time_prop] */
+ inst_opt_get_led_pulse_state= 0x001A /* Get the current pulse LED state. [*double period, */
+
+} inst_opt_type;
+
+/* Optional filter fitted to instrument (for inst_opt_set_filter) */
+typedef enum {
+ inst_opt_filter_unknown = 0xffff, /* Unspecified filter */
+ inst_opt_filter_none = 0x0000, /* No filters fitted */
+ inst_opt_filter_pol = 0x0001, /* Polarising filter */
+ inst_opt_filter_D65 = 0x0002, /* D65 Illuminant filter */
+ inst_opt_filter_UVCut = 0x0004, /* U.V. Cut filter */
+ inst_opt_filter_Custom = 0x0008 /* Custom Filter */
+} inst_opt_filter;
+
+/* Off-line pending readings available (status) */
+typedef enum {
+ inst_stat_savdrd_none = 0x00, /* No saved readings */
+ inst_stat_savdrd_spot = 0x01, /* There are saved spot readings available */
+ inst_stat_savdrd_strip = 0x02, /* There are saved strip readings available */
+ inst_stat_savdrd_xy = 0x04, /* There are saved page readings available */
+ inst_stat_savdrd_chart = 0x08 /* There are saved chart readings available */
+} inst_stat_savdrd;
+
+/* Type of calibration needed/available/requested - corresponds to capabilities */
+/* [ inst_calt_trans_vwhite is "variable" white transmission calibration, needed */
+/* where transmission mode is being emulated. ] */
+typedef enum {
+ /* Response to needs_calibration() */
+ inst_calt_none = 0x00000000, /* No calibration or unknown */
+
+ /* Psudo-calibration types */
+ inst_calt_all = 0x00000001, /* Do required non-deferable cals for mode, but also */
+ /* do all possible calibrations for all other modes */
+ /* using the calibration conditions. This may be slow */
+ /* Hmm. We don't have an "calt_all_needed" - do all needed cals of all possible modes. */
+ /* This might be more useful than inst_calt_all ? */
+ inst_calt_needed = 0x00000002, /* Do all required non-deferable cals for c.m. */
+ inst_calt_available = 0x00000003, /* Do all available non-deferable cals for c.m. */
+
+ /* Specific type of calibration - corresponds to capabilities */
+ inst_calt_wavelength = 0x00000010, /* Wavelength calibration using refl. cal. surface */
+ inst_calt_ref_white = 0x00000020, /* Reflective white/emissive dark calibration */
+ inst_calt_ref_dark = 0x00000040, /* Reflective dark calibration (in dark) */
+ inst_calt_emis_offset = 0x00000080, /* Emissive offset/black calibration (dark surface) */
+ inst_calt_emis_ratio = 0x00000100, /* Emissive ratio calibration */
+ inst_calt_em_dark = 0x00000200, /* Emissive dark calibration (in dark) */
+ inst_calt_trans_white = 0x00000400, /* Transmissive white reference calibration */
+ inst_calt_trans_vwhite = 0x00000800, /* Transmissive variable white reference calibration */
+ inst_calt_trans_dark = 0x00001000, /* Transmissive dark reference calibration */
+
+ inst_calt_n_dfrble_mask = 0x0000fff0, /* Mask of non-deferrable calibrations */
+
+ /* Calibrations that might be deferred until measurement */
+ inst_calt_emis_int_time = 0x00100000, /* Emissive measurement range (integration time) */
+ inst_calt_ref_freq = 0x00200000, /* Display refresh frequency calibration */
+
+ inst_calt_dfrble_mask = 0x00f00000, /* Mask of deferrable calibrations */
+
+ inst_calt_all_mask = 0x00f0fff0, /* Mask of all specific calibrations */
+
+ inst_calt_ap_flag = 0x80000000 /* Implementation flag indicating do all possible */
+
+} inst_cal_type;
+
+/* Calibration conditions. */
+/* This is how the instrument communicates to the calling program */
+/* about how to facilitate a calibration, or what it's current measurement */
+/* configuration provides. */
+/* [There is no provission for explictly indicating calibrations that can be */
+/* performed automatically and transparently by the instrument - for instance */
+/* in the case of the spectroscan, since the required condition can be obtained */
+/* without the users interaction. ] */
+typedef enum {
+ inst_calc_none = 0x00000000, /* Not suitable for calibration */
+ inst_calc_unknown = 0xffffffff, /* Unknown calibration setup */
+
+ /* uop means that user has to trigger the within instrument */
+ /* calibration using its "front panel" or other direct keys */
+ inst_calc_uop_ref_white = 0x00000001, /* user operated reflective white calibration */
+ inst_calc_uop_trans_white = 0x00000002, /* user operated tranmissive white calibration */
+ inst_calc_uop_trans_dark = 0x00000003, /* user operated tranmissive dark calibration */
+ inst_calc_uop_mask = 0x0000000F, /* user operated calibration mask */
+
+ /* Man means that the user has to manualy configure the instrument */
+ /* to be on the correct reference for the software triggered cal. */
+ inst_calc_man_ref_white = 0x00000010, /* place instrument on reflective white reference */
+ inst_calc_man_ref_whitek = 0x00000020, /* click instrument on reflective white reference */
+ inst_calc_man_ref_dark = 0x00000030, /* place instrument in dark, not close to anything */
+ inst_calc_man_em_dark = 0x00000040, /* place cap on instrument, put on dark surface or white ref. */
+ inst_calc_man_am_dark = 0x00000050, /* Place cap over ambient sensor (wl calib capable) */
+ inst_calc_man_cal_smode = 0x00000060, /* Put instrument sensor in calibration position */
+
+ inst_calc_man_trans_white = 0x00000070, /* place instrument on transmissive white reference */
+ inst_calc_man_trans_dark = 0x00000080, /* place instrument on transmissive dark reference */
+ inst_calc_man_man_mask = 0x000000F0, /* user configured calibration mask */
+
+ inst_calc_emis_white = 0x00000100, /* Provide a white test patch */
+ inst_calc_emis_grey = 0x00000200, /* Provide a grey test patch */
+ inst_calc_emis_grey_darker = 0x00000300, /* Provide a darker grey test patch */
+ inst_calc_emis_grey_ligher = 0x00000400, /* Provide a darker grey test patch */
+ inst_calc_emis_mask = 0x00000F00, /* Emmissive/display provided reference patch */
+
+ inst_calc_change_filter = 0x00010000, /* Filter needs changing on device - see id[] */
+ inst_calc_message = 0x00020000 /* Issue a message. - see id[] */
+} inst_cal_cond;
+
+/* Clamping state */
+typedef enum {
+ instNoClamp = 0, /* Don't clamp XYZ/Lab to +ve */
+ instClamp = 1, /* Clamp XYZ/Lab to +ve */
+} instClamping;
+
+/* User interaction callback function purpose */
+typedef enum {
+ inst_negcoms, /* Negotiating communications */
+ inst_triggered, /* Measurement has been triggered by switch or user (not progromatic) */
+ inst_armed, /* Armed and waiting for a measurement trigger */
+ inst_measuring /* Busy measuring */
+} inst_ui_purp;
+
+/* Asynchronous event callback type */
+typedef enum {
+ inst_event_switch, /* Instrument measure/calibrate switch pressed */
+ inst_event_mconf /* Change in measurement configuration (ie. sensor position) */
+} inst_event_type;
+
+/* Instrument configuration/sensor position*/
+typedef enum {
+ inst_conf_unknown,
+ inst_conf_projector,
+ inst_conf_surface,
+ inst_conf_emission,
+ inst_conf_calibration,
+ inst_conf_ambient
+} inst_config;
+
+/* Off-line pending readings available (status) */
+#define CALIDLEN 200 /* Maxumum length of calibration tile ID string */
+
+/* Color instrument interface base object */
+/* Note that some methods work after creation, while many */
+/* will return an error if communications hasn't been established and */
+/* the instrument initialised. Some may change their response before and */
+/* after initialisation. */
+#define INST_OBJ_BASE \
+ \
+ a1log *log; /* Pointer to debug & error logging class */ \
+ instType itype; /* Instrument type determined by driver */ \
+ icoms *icom; /* Instrument coms object */ \
+ int gotcoms; /* Coms established flag */ \
+ int inited; /* Instrument open and initialized flag */ \
+ double cal_gy_level; /* Display calibration test window state */ \
+ int cal_gy_count; /* Display calibration test window state */ \
+ inst_code (*uicallback)(void *cntx, inst_ui_purp purp); \
+ void *uic_cntx; /* User interaction callback function */ \
+ void (*eventcallback)(void *cntx, inst_event_type event); \
+ void *event_cntx; /* Event callback function */ \
+ \
+ /* Establish communications at the indicated baud rate. */ \
+ /* (Serial parameters are ignored for USB instrument) */ \
+ /* Timout in to seconds, and return non-zero error code */ \
+ inst_code (*init_coms)( \
+ struct _inst *p, \
+ baud_rate br, /* Baud rate */ \
+ flow_control fc, /* Flow control */ \
+ double tout); /* Timeout */ \
+ \
+ /* Initialise or re-initialise the INST */ \
+ /* return non-zero on an error, with inst error code */ \
+ inst_code (*init_inst)( \
+ struct _inst *p); \
+ \
+ /* Return the instrument type */ \
+ /* (this could concievably change after init_inst()) */ \
+ /* Can be called before init */ \
+ instType (*get_itype)( \
+ struct _inst *p); \
+ \
+ /* Return the instrument serial number. */ \
+ /* (This will be an empty string if there is no serial no) */ \
+ char *(*get_serial_no)( \
+ struct _inst *p); \
+ \
+ /* Return the avilable instrument modes and capabilities. */ \
+ /* Can be called before init, but may be different to */ \
+ /* what's returned after initilisation. */ \
+ /* Note that these may change with the mode. */ \
+ /* Arguments may be NULL */ \
+ void (*capabilities)(struct _inst *p, \
+ inst_mode *cap1, \
+ inst2_capability *cap2, \
+ inst3_capability *cap3); \
+ \
+ /* Return current or given configuration available measurement modes. */ \
+ /* Most instruments have only one detectable configuration. */ \
+ /* If conf_ix == NULL or *conf_ix is an invalid configuration index, */ \
+ /* then the current configuration modes are returned. */ \
+ /* Otherwise the given configuration index is returned. */ \
+ /* The i1d3 has 2, the Munki has 4, one being calibration. */ \
+ /* *cconds is valid if *mmodes = inst_mode_calibration */ \
+ inst_code (*meas_config)(struct _inst *p, \
+ inst_mode *mmodes, /* Return all the valid measurement modes */ \
+ /* for the current configuration */ \
+ inst_cal_cond *cconds, /* Return the valid calibration conditions */ \
+ int *conf_ix); /* Request mode for given configuration, and */ \
+ /* return the index of the configuration returned */ \
+ \
+ /* Check that the particular device measurement mode is valid, */ \
+ /* since it's not possible to be 100% sure from capabilities */ \
+ inst_code (*check_mode)( \
+ struct _inst *p, \
+ inst_mode m); /* Requested mode */ \
+ \
+ /* Set the device measurement mode */ \
+ /* Note that this may change the capabilities. */ \
+ inst_code (*set_mode)( \
+ struct _inst *p, \
+ inst_mode m); /* Requested mode */ \
+ \
+ /* Return array of display type selectors */ \
+ inst_code (*get_disptypesel)( \
+ struct _inst *p, \
+ int *no_selectors, /* Return number of display types */ \
+ inst_disptypesel **sels,/* Return the array of display types */ \
+ int allconfig, /* nz to return list for all configs, not just current. */ \
+ int recreate); /* nz to re-check for new ccmx & ccss files */ \
+ \
+ /* Set the display type. index is into the inst_disptypesel[] returned */ \
+ /* returned by get_disptypesel(). clears col_cor_mat() */ \
+ inst_code (*set_disptype)( \
+ struct _inst *p, \
+ int index); \
+ \
+ /* Get a status or get or set an option */ \
+ /* option state. */ \
+ /* Some options can be set before init */ \
+ /* See inst_opt_type typedef for list of mode types */ \
+ inst_code (*get_set_opt)( \
+ struct _inst *p, \
+ inst_opt_type m, /* Requested option mode */ \
+ ...); /* Option parameters */ \
+ \
+ /* Read a full test chart composed of multiple sheets */ \
+ /* DOESN'T use the trigger mode */ \
+ /* Return the inst error code */ \
+ inst_code (*read_chart)( \
+ struct _inst *p, \
+ int npatch, /* Total patches/values in chart */ \
+ int pich, /* Passes (rows) in chart */ \
+ int sip, /* Steps in each pass (patches in each row) */ \
+ int *pis, /* Passes in each strip (rows in each sheet) */ \
+ int chid, /* Chart ID number */ \
+ ipatch *vals); /* Pointer to array of values */ \
+ \
+ \
+ /* For an xy instrument, release the paper */ \
+ /* (if cap has inst_xy_holdrel) */ \
+ /* Return the inst error code */ \
+ inst_code (*xy_sheet_release)( \
+ struct _inst *p); \
+ \
+ /* For an xy instrument, hold the paper */ \
+ /* (if cap has inst_xy_holdrel) */ \
+ /* Return the inst error code */ \
+ inst_code (*xy_sheet_hold)( \
+ struct _inst *p); \
+ \
+ /* For an xy instrument, allow the user to locate a point */ \
+ /* (if cap has inst_xy_locate) */ \
+ /* Return the inst error code */ \
+ inst_code (*xy_locate_start)( \
+ struct _inst *p); \
+ \
+ /* For an xy instrument, read back the location */ \
+ /* (if cap has inst_xy_locate) */ \
+ /* Return the inst error code */ \
+ inst_code (*xy_get_location)( \
+ struct _inst *p, \
+ double *x, double *y); \
+ \
+ /* For an xy instrument, end allowing the user to locate a point */ \
+ /* (if cap has inst_xy_locate) */ \
+ /* Return the inst error code */ \
+ inst_code (*xy_locate_end)( \
+ struct _inst *p); \
+ \
+ /* For an xy instrument, move the measurement point */ \
+ /* (if cap has inst_xy_position) */ \
+ /* Return the inst error code */ \
+ inst_code (*xy_position)( \
+ struct _inst *p, \
+ int measure, /* nz for measure point, z for locate point */ \
+ double x, double y); \
+ \
+ /* For an xy instrument, try and clear the table after an abort */ \
+ /* Return the inst error code */ \
+ inst_code (*xy_clear)( \
+ struct _inst *p); \
+ \
+ /* Read a sheet full of patches using xy mode */ \
+ /* DOESN'T use the trigger mode */ \
+ /* Return the inst error code */ \
+ inst_code (*read_xy)( \
+ struct _inst *p, \
+ int pis, /* Passes in strips (letters in sheet) */ \
+ int sip, /* Steps in pass (numbers in sheet) */ \
+ int npatch, /* Total patches in strip (skip in last pass) */ \
+ char *pname, /* Starting pass name (' A' to 'ZZ') */ \
+ char *sname, /* Starting step name (' 1' to '99') */ \
+ double ox, double oy, /* Origin of first patch */ \
+ double ax, double ay, /* pass increment */ \
+ double aax, double aay, /* pass offset for odd patches */ \
+ double px, double py, /* step/patch increment */ \
+ ipatch *vals); /* Pointer to array of values */ \
+ \
+ \
+ /* Read a set of strips (applicable to strip reader) */ \
+ /* Obeys the trigger mode set, and may return user trigger code */ \
+ /* (to hint that a user command may be available) */ \
+ /* Return the inst error code */ \
+ inst_code (*read_strip)( \
+ struct _inst *p, \
+ char *name, /* Strip name (up to 7 chars) */ \
+ int npatch, /* Number of patches in each pass */ \
+ char *pname, /* Pass name (3 chars) */ \
+ int sguide, /* Guide number (decrements by 5) */ \
+ double pwid, /* Patch width in mm (For DTP20/DTP41) */ \
+ double gwid, /* Gap width in mm (For DTP20/DTP41) */ \
+ double twid, /* Trailer width in mm (For DTP41T) */ \
+ ipatch *vals); /* Pointer to array of values */ \
+ \
+ \
+ /* Read a single sample (applicable to spot instruments) */ \
+ /* Obeys the trigger mode set, and may return user trigger code */ \
+ /* Values are in XYZ 0..100 for reflective transmissive, */ \
+ /* aXYZ in cd/m^2 for emissive, amd Lux/3.1415926 for ambient. */ \
+ /* Spectral will be analogous to the XYZ. */ \
+ /* By default values may be -ve due to noise (depending on instrument) */ \
+ /* Return the inst error code */ \
+ inst_code (*read_sample)( \
+ struct _inst *p, \
+ char *name, /* Patch identifier (up to 7 chars) */ \
+ ipatch *val, /* Pointer to value to be returned */ \
+ instClamping clamp); /* NZ to clamp XYZ to be +ve */ \
+ \
+ /* Measure the emissive refresh rate in Hz. */ \
+ /* (Available if cap2 & inst2_emis_refr_meas) */ \
+ /* Returns: */ \
+ /* inst_unsupported - if this instrument doesn't suport this measuremet */ \
+ /* or not in an emissive measurement mode */ \
+ /* inst_misread - if no refresh rate could be determined */ \
+ /* inst_ok - on returning a valid reading */ \
+ inst_code (*read_refrate)( \
+ struct _inst *p, \
+ double *ref_rate); /* Return the Hz */ \
+ \
+ /* Determine if a calibration is needed. Returns inst_calt_none if not */ \
+ /* or unknown, or a mask of the needed calibrations. */ \
+ /* This call checks if calibrations are invalid or have timed out. */ \
+ /* With the exception of instruments with automated calibration */ \
+ /* (ie. SpectroScan), an instrument will typically */ \
+ /* not check for calibration timout any other way. */ \
+ /* [What's returned is the same as get_n_a_cals() [ needed_calibrations.] */\
+ inst_cal_type (*needs_calibration)( \
+ struct _inst *p); \
+ \
+ /* Return combined mask of needed and available inst_cal_type's */ \
+ /* for the current mode. */ \
+ inst_code (*get_n_a_cals)( \
+ struct _inst *p, \
+ inst_cal_type *needed_calibrations, \
+ inst_cal_type *available_calibrations); \
+ \
+ /* Request an instrument calibration. */ \
+ /* This is use if the user decides they want to do a calibration */ \
+ /* in anticipation of a calibration (needs_calibration()) to avoid */ \
+ /* requiring one during measurement, or in response to measuring */ \
+ /* returning inst_needs_cal before retrying the measurement, */ \
+ /* or to do one or more re-calibrations. */ \
+ \
+ /* *calt should contain the mask of calibrations to be performed, */ \
+ /* with *calc set to the current calibration condition. */ \
+ /* Alternately, one of the psudo-calibration types inst_calt_all, */ \
+ /* inst_calt_needed or inst_calt_available can be used, */ \
+ /* and/or the *calc of inst_calc_none to get calibrate() */ \
+ /* to determine the required calibration types and conditions. */ \
+ /* (The corresponding calibration types will be used & returned. */ \
+ \
+ /* If no error is returned to the first call to calibrate() with */ \
+ /* then the instrument was capable of calibrating without user or */ \
+ /* application intervention. If on the other hand calibrate() returns */ \
+ /* inst_cal_setup, then the appropriate action indicated by the value */ \
+ /* returned in *calc should be taken by the user or application, */ \
+ /* before retrying calibration() with the current setup indicated */ \
+ /* by *calc. If more than one calibration type is requested, then */ \
+ /* several retries may be needed with different calibration conditions. */ \
+ /* Each call to calibrate() will update *calt to reflect the remaining */ \
+ /* calibration to be performed. calibrate() returns inst_ok when no */ \
+ /* more calibrations remain. */ \
+ \
+ /* DOESN'T use the trigger mode */ \
+ /* Return inst_unsupported if *calt is not appropriate, */ \
+ /* inst_cal_setup if *calc is not appropriate. */ \
+ inst_code (*calibrate)( \
+ struct _inst *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, filter ID) */ \
+ \
+ /* Measure a display update delay. It is assumed that a */ \
+ /* White to black change has been made to the displayed color, */ \
+ /* and this will measure the time it took for the update to */ \
+ /* be noticed by the instrument, up to 1.0 seconds. */ \
+ /* inst_misread will be returned on failure to find a transition to black. */ \
+ inst_code (*meas_delay)( \
+ struct _inst *p, \
+ int *msecdelay); /* Return the number of msec */ \
+ \
+ /* Return the last calibrated refresh rate in Hz. Returns: */ \
+ /* inst_unsupported - if this instrument doesn't suport a refresh mode */ \
+ /* or is unable to retrieve the refresh rate */ \
+ /* inst_needs_cal - if the refresh rate value is not valid */ \
+ /* inst_misread - if no refresh rate could be determined */ \
+ /* inst_ok - on returning a valid reading */ \
+ inst_code (*get_refr_rate)( \
+ struct _inst *p, \
+ double *ref_rate); /* Return the Hz */ \
+ \
+ /* Set the calibrated refresh rate in Hz. */ \
+ /* Set refresh rate to 0.0 to mark it as invalid */ \
+ /* Rates outside the range 5.0 to 150.0 Hz will return an error */ \
+ /* Note that not all instruments that can return a refresh rate, */ \
+ /* will permit one to be set (ie., DTP92) */ \
+ inst_code (*set_refr_rate)( \
+ struct _inst *p, \
+ double ref_rate); /* Rate in Hz */ \
+ \
+ /* Insert a compensation filter in the instrument readings */ \
+ /* This is typically needed if an adapter is being used, that alters */ \
+ /* the spectrum of the light reaching the instrument */ \
+ /* To remove the filter, pass NULL for the filter filename */ \
+ inst_code (*comp_filter)( \
+ struct _inst *p, \
+ char *filtername); /* File containing compensating filter */ \
+ \
+ /* Insert a colorimetric correction matrix in the instrument XYZ readings */ \
+ /* This is only valid for colorimetric instruments, and can only be */ \
+ /* applied over a base calibration display type. Setting a display */ \
+ /* type will clear the matrix. */ \
+ /* To clear the matrix, pass NULL for the matrix */ \
+ inst_code (*col_cor_mat)( \
+ struct _inst *p, \
+ double mtx[3][3]); /* XYZ matrix */ \
+ \
+ /* Use a Colorimeter Calibration Spectral Set (ccss) to set the */ \
+ /* instrumen calibration. This will affect emissive readings. */ \
+ /* An alternate observer may also be set, and this will affect both */ \
+ /* emissive and ambient readings. */ \
+ /* This is only valid for colorimetric instruments. */ \
+ /* To set calibration back to default, pass NULL for sets, and */ \
+ /* icxOT_default for the observer. */ \
+ inst_code (*col_cal_spec_set)( \
+ struct _inst *p, \
+ xspect *sets, /* Set of sample spectra */ \
+ int no_sets); /* Number on set */ \
+ \
+ /* Supply a user interaction callback function. \
+ * This is called for one of three different purposes: \
+ * To signal that the instrument measurement has been triggered. \
+ * To poll for a abort while waiting to trigger. \
+ * To poll for a user abort during measurement. \
+ * \
+ * The callback function will have the purpose paramater appropriately. \
+ * \
+ * For inst_negcoms, the return value of inst_user_abort \
+ * will abort the communication negotiation \
+ * \
+ * For inst_triggered, the return value of the callback is ignored. \
+ * \
+ * For inst_armed return value should be one of: \
+ * inst_ok to do nothing, inst_user_abort to abort the measurement, \
+ * or inst_user_trig to trigger the measurement. \
+ * \
+ * For inst_measuring the return value should be one of: \
+ * inst_ok to do nothing, inst_user_abort to abort the measurement. \
+ * \
+ * NULL can be set to disable the callback. \
+ */ \
+ void (*set_uicallback)(struct _inst *p, \
+ inst_code (*uicallback)(void *cntx, inst_ui_purp purp), \
+ void *cntx); \
+ \
+ /* Supply an aynchronous event callback function. \
+ * This is called from a thread with the following possible events: \
+ * \
+ * inst_event_switch: Instrument measure/calibrate switch pressed \
+ * inst_event_mconf: The measurement configuration has changed (ie. sensor position) \
+ * \
+ * NULL can be set to disable the callback. \
+ */ \
+ void (*set_event_callback)(struct _inst *p, \
+ void (*eventcallback)(void *cntx, inst_event_type event), \
+ void *cntx); \
+ \
+ /* Generic inst error codes interpretation */ \
+ char * (*inst_interp_error)(struct _inst *p, inst_code ec); \
+ \
+ /* Instrument specific error codes interpretation */ \
+ char * (*interp_error)(struct _inst *p, int ec); \
+ \
+ /* Convert instrument specific inst_wrong_config error to inst_config enum */ \
+ inst_config (*config_enum)(struct _inst *p, int ec); \
+ \
+ /* Return the last serial communication error code */ \
+ /* (This is used for deciding fallback/retry strategies) */ \
+ int (*last_scomerr)(struct _inst *p); \
+ \
+ /* Destroy ourselves */ \
+ void (*del)(struct _inst *p); \
+
+/* The base object type */
+struct _inst {
+ INST_OBJ_BASE
+ }; typedef struct _inst inst;
+
+/* Virtual constructor. */
+/* Return NULL for unknown instrument, */
+/* or serial instrument if nocoms == 0. */
+/* (Doesn't copy icompaths log!) */
+/* If uicallback is provided, it will be set in the resulting inst */
+extern inst *new_inst(
+ icompath *path, /* Device path this instrument */
+ int nocoms, /* Don't open if communications are needed to establish inst type */
+ a1log *log, /* Log to use */
+ inst_code (*uicallback)(void *cntx, inst_ui_purp purp), /* optional uicallback */
+ void *cntx /* Context for callback */
+);
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Implementation functions used by drivers */
+
+/* Get a status or set or get an option (default implementation) */
+inst_code inst_get_set_opt_def(
+inst *p,
+inst_opt_type m, /* Option type */
+va_list args); /* Option parameters */
+
+/* - - - - - - - - - - - - - - - - - - -- */
+
+/* Create the display type list */
+inst_code inst_creat_disptype_list(inst *p,
+int *pndtlist, /* Number in returned list */
+inst_disptypesel **pdtlist, /* Returned list */
+inst_disptypesel *sdtlist, /* Static list */
+int doccss, /* Add installed ccss files */
+int doccmx /* Add matching installed ccmx files */
+);
+
+/* Free a display type list */
+void inst_del_disptype_list(inst_disptypesel *list, int no);
+
+
+/* - - - - - - - - - - - - - - - - - - -- */
+/* CCMX support */
+
+typedef struct {
+ char *path; /* Path to the file */
+ char *desc; /* Technology + display description */
+ int cbid; /* Calibration display type base ID */
+ int refr; /* Refresh mode flag */
+ char *sel; /* UI selector characters (may be NULL) */
+ double mat[3][3]; /* The matrix values */
+} iccmx;
+
+/* return a list of installed ccmx files. */
+/* if inst != NULL, return those that match the given instrument. */
+/* The list is sorted by description and terminated by a NULL entry. */
+/* If no is != NULL, return the number in the list */
+/* Return NULL and -1 if there is a malloc error */
+iccmx *list_iccmx(char *inst, int *no);
+
+/* Free up a iccmx list */
+void free_iccmx(iccmx *list);
+
+/* - - - - - - - - - - - - - - - - - - -- */
+/* CCSS support */
+
+typedef struct {
+ char *path; /* Path to the file */
+ char *desc; /* Technology + display description */
+ int refr; /* Refresh mode flag */
+ char *sel; /* UI selector characters (may be NULL) */
+ xspect *sets; /* Set of sample spectra */
+ int no_sets; /* Number on set */
+} iccss;
+
+/* return a list of installed ccss files. */
+/* The list is sorted by description and terminated by a NULL entry. */
+/* If no is != NULL, return the number in the list */
+/* Return NULL and -1 if there is a malloc error */
+iccss *list_iccss(int *no);
+
+/* Free up a iccss list */
+void free_iccss(iccss *list);
+
+/* - - - - - - - - - - - - - - - - - - -- */
+
+#ifdef __cplusplus
+ }
+#endif
+
+#define INST_H
+#endif /* INST_H */
diff --git a/spectro/instappsup.c b/spectro/instappsup.c
new file mode 100644
index 0000000..67849c7
--- /dev/null
+++ b/spectro/instappsup.c
@@ -0,0 +1,556 @@
+
+ /* Instrument command line application support functions */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 10/3/2001
+ *
+ * Copyright 2001 - 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#else
+#include "sa_config.h"
+#endif /* !SALONEINSTLIB */
+#include "numsup.h"
+#include "xspect.h"
+#include "conv.h"
+#include "insttypes.h"
+
+#include "icoms.h"
+#include "inst.h"
+#include "insttypeinst.h"
+#include "instappsup.h"
+
+/* ================================================================= */
+/* a default user interaction handler */
+
+typedef struct _uicontext {
+ int emit_ret; /* Emit \n on inst_triggered */
+ int cut; /* The character that caused the termination */
+ int uih[256]; /* User interrupt handling key table. Value can be: */
+ /* DUIH_OK, DUIH_ABORT, DUIH_TERM, DUIH_TRIG, DUIH_CMND */
+} uicontext;
+
+static uicontext def_uicntx = { 1, 0, { 0 } };
+
+static inst_code def_uicallback(void *cntx, inst_ui_purp purp) {
+ uicontext *p = (uicontext *)cntx;
+
+ if (purp == inst_triggered) {
+ if (p->emit_ret)
+ printf("\n");
+ return inst_ok;
+
+ } else if (purp == inst_negcoms
+ || purp == inst_armed
+ || purp == inst_measuring) {
+ int c;
+
+ c = poll_con_char();
+ if (c != 0) {
+ p->cut = c;
+ c = p->uih[c];
+ if (c & (DUIH_ABORT | DUIH_TERM | DUIH_CMND))
+ return inst_user_abort;
+ if (c & DUIH_TRIG)
+ return inst_user_trig;
+ }
+
+ /* Change in measurement configuration */
+ } else if (purp == inst_measuring) {
+ return inst_ok;
+ }
+ return inst_ok;
+}
+
+/* Return the default uicallback function */
+inst_code (*inst_get_uicallback())(void *, inst_ui_purp) {
+ return &def_uicallback;
+}
+
+/* Return the default uicallback context */
+void *inst_get_uicontext() {
+ return (void *)&def_uicntx;
+}
+
+/* Install the default uicallback function in the given inst */
+void inst_set_uicallback(inst *p) {
+ p->set_uicallback(p, def_uicallback, (void *)&def_uicntx);
+}
+
+/* Set the return on trigger to true or false */
+void inst_set_uicb_trigret(int set) {
+ uicontext *p = &def_uicntx;
+ p->emit_ret = set;
+}
+
+/* Reset user interaction handling to default (Esc, ^C, q or 'Q' = Abort) */
+void inst_reset_uih() {
+ uicontext *p = &def_uicntx;
+ int i;
+
+ for (i = 0; i < 255; i++)
+ p->uih[i] = DUIH_NONE;
+
+ p->uih[0x1b] = DUIH_ABORT; /* Escape */
+ p->uih['q'] = DUIH_ABORT; /* q */
+ p->uih['Q'] = DUIH_ABORT; /* Q */
+ p->uih[0x03] = DUIH_ABORT; /* ^C */
+}
+
+/* Set a key range to the given handling type */
+/* min & max are between 0 and 255, status is one of */
+/* DUIH_OK, DUIH_USER, DUIH_TERM, DUIH_TRIG, DUIH_CMND */
+void inst_set_uih(
+int min, /* Start key code */
+int max, /* End key code (inclusive) */
+int status /* ICOM_OK, ICOM_USER, ICOM_TERM, ICOM_TRIG, ICOM_CMND */
+) {
+ uicontext *p = &def_uicntx;
+ int i;
+
+ if (min < 0)
+ min = 0;
+ else if (min > 255)
+ min = 255;
+ if (max < 0)
+ max = 0;
+ else if (max > 255)
+ max = 255;
+
+ if (status != DUIH_NONE
+ && status != DUIH_ABORT
+ && status != DUIH_TERM
+ && status != DUIH_CMND
+ && status != DUIH_TRIG)
+ status = DUIH_NONE;
+
+ for (i = min; i <= max; i++) {
+ p->uih[i] = status;
+ }
+}
+
+/* Get the character that caused the user interrupt */
+/* + its key type in the upper 8 bits. */
+/* Clear it to 0x00 after reading it. */
+int inst_get_uih_char() {
+ uicontext *p = &def_uicntx;
+ int c = p->cut;
+ c |= p->uih[c];
+ p->cut = 0;
+ return c;
+}
+
+/* ================================================================= */
+
+/* A default calibration user interaction handler using the console. */
+/* This handles both normal and display based calibration interaction */
+/* with the instrument, if a disp_setup function and pointer to disp_win_info */
+/* is provided. */
+inst_code inst_handle_calibrate(
+ inst *p,
+ inst_cal_type calt, /* Calibration type to do */
+ inst_cal_cond calc, /* Current current condition */
+ inst_code (*disp_setup) (inst *p,inst_cal_cond calc, disp_win_info *dwi),
+ /* Callback for handling a display calibration - May be NULL */
+ disp_win_info *dwi /* Information to be able to open a display test patch - May be NULL */
+) {
+ inst_code rv = inst_ok, ev;
+ int usermes = 0; /* User was given a message */
+ char id[200]; /* Condition identifier */
+ int ch;
+
+ a1logd(p->log,1,"inst_handle_calibrate called\n");
+
+ /* Untill we're done with the calibration */
+ for (;;) {
+
+ a1logd(p->log,1,"About to call calibrate at top of loop\n");
+ ev = p->calibrate(p, &calt, &calc, id);
+ a1logd(p->log,1,"Calibrate returned calt 0x%x, calc 0x%x, ev 0x%x\n",calt,calc,ev);
+
+ /* We're done */
+ if ((ev & inst_mask) == inst_ok) {
+ if (calc == inst_calc_message)
+ printf("%s\n",id);
+ if (usermes)
+ printf("Calibration complete\n");
+ fflush(stdout);
+ a1logd(p->log,1,"inst_handle_calibrate done 0x%x\n",ev);
+ return ev;
+ }
+
+ /* User aborted */
+ if ((ev & inst_mask) == inst_user_abort) {
+ a1logd(p->log,1,"inst_handle_calibrate user aborted 0x%x\n",ev);
+ return ev;
+ }
+
+ /* Retry on an error */
+ if ((ev & inst_mask) != inst_cal_setup) {
+ if ((ev & inst_mask) == inst_unsupported) {
+ a1logd(p->log,1,"inst_handle_calibrate err 0x%x, calibration type 0x%x not supported\n",ev, calt);
+ return inst_unsupported;
+ }
+
+ printf("Calibration failed with '%s' (%s)\n",
+ p->inst_interp_error(p, ev), p->interp_error(p, ev));
+ printf("Hit any key to retry, or Esc or Q to abort:\n");
+
+ empty_con_chars();
+ ch = next_con_char();
+ printf("\n");
+ if (ch == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ a1logd(p->log,1,"inst_handle_calibrate user aborted 0x%x\n",inst_user_abort);
+ fflush(stdout);
+ return inst_user_abort;
+ }
+
+ /* Get user to do/setup calibration */
+ } else {
+
+ switch (calc) {
+ case inst_calc_uop_ref_white:
+ printf("Do a reflective white calibration,\n");
+ printf(" and then hit any key to continue,\n");
+ printf(" or hit Esc or Q to abort: ");
+ break;
+
+ case inst_calc_uop_trans_white:
+ printf("Do a transmissive white calibration,\n");
+ printf(" and then hit any key to continue,\n");
+ printf(" or hit Esc or Q to abort: ");
+ break;
+
+ case inst_calc_uop_trans_dark:
+ printf("Do a transmissive dark calibration,\n");
+ printf(" and then hit any key to continue,\n");
+ printf(" or hit Esc or Q to abort: ");
+ break;
+
+ case inst_calc_man_ref_white:
+ printf("Place the instrument on its reflective white reference %s,\n",id);
+ printf(" and then hit any key to continue,\n");
+ printf(" or hit Esc or Q to abort: ");
+ break;
+
+ case inst_calc_man_ref_whitek:
+ printf("Click the instrument on its reflective white reference %s,\n",id);
+ printf(" or hit Esc or Q to abort: ");
+ break;
+
+ case inst_calc_man_ref_dark:
+ printf("Place the instrument in the dark, not in contact with any surface,\n");
+ printf(" and then hit any key to continue,\n");
+ printf(" or hit Esc or Q to abort: ");
+ break;
+
+ case inst_calc_man_em_dark:
+ printf("Place cap on the instrument, or place on a dark surface,\n");
+ printf("or place on the white calibration reference,\n");
+ printf(" and then hit any key to continue,\n");
+ printf(" or hit Esc or Q to abort: ");
+ break;
+
+ case inst_calc_man_am_dark:
+ printf("Place ambient adapter and cap on the instrument,\n");
+ printf("or place on the white calibration reference,\n");
+ printf(" and then hit any key to continue,\n");
+ printf(" or hit Esc or Q to abort: ");
+ break;
+
+ case inst_calc_man_cal_smode:
+ printf("Set instrument sensor to calibration position,\n");
+ printf(" and then hit any key to continue,\n");
+ printf(" or hit Esc or Q to abort: ");
+ break;
+
+ case inst_calc_man_trans_white:
+ printf("Place the instrument on its transmissive white source,\n");
+ printf(" and then hit any key to continue,\n");
+ printf(" or hit Esc or Q to abort: ");
+ break;
+
+ case inst_calc_man_trans_dark:
+ printf("Use the appropriate tramissive blocking to block the transmission path,\n");
+ printf(" and then hit any key to continue,\n");
+ printf(" or hit Esc or Q to abort: ");
+ break;
+
+ case inst_calc_change_filter:
+ printf("Change filter on instrument to %s,\n",id);
+ printf(" and then hit any key to continue,\n");
+ printf(" or hit Esc or Q to abort: ");
+ break;
+
+ case inst_calc_message:
+ printf("%s\n",id);
+ printf(" Hit any key to continue,\n");
+ printf(" or hit Esc or Q to abort: ");
+ break;
+
+ case inst_calc_emis_white:
+ if (disp_setup == NULL || dwi == NULL) { /* No way of creating a test window */
+ printf("Place the instrument on a 100%% white test patch,\n");
+ printf(" and then hit any key to continue,\n");
+ printf(" or hit Esc or Q to abort: ");
+ } else {
+ /* We need to display a 100% white patch to proceed with this */
+ /* type of calibration */
+ if ((rv = disp_setup(p, calc, dwi)) != inst_ok)
+ return rv;
+ }
+ break;
+
+ case inst_calc_emis_grey:
+ case inst_calc_emis_grey_darker:
+ case inst_calc_emis_grey_ligher:
+ if (dwi == NULL) { /* No way of creating a test window */
+ if (calc == inst_calc_emis_grey) {
+ p->cal_gy_level = 0.6;
+ p->cal_gy_count = 0;
+ } else if (calc == inst_calc_emis_grey_darker) {
+ p->cal_gy_level *= 0.7;
+ p->cal_gy_count++;
+ } else if (calc == inst_calc_emis_grey_ligher) {
+ p->cal_gy_level *= 1.4;
+ if (p->cal_gy_level > 1.0)
+ p->cal_gy_level = 1.0;
+ p->cal_gy_count++;
+ }
+ if (p->cal_gy_count > 4) {
+ printf("Cell ratio calibration failed - too many tries at setting grey level.\n");
+ a1logd(p->log,1,"inst_handle_calibrate too many tries at setting grey level 0x%x\n",inst_internal_error);
+ return inst_internal_error;
+ } else {
+ printf("Place the instrument on a %d%% white test patch,\n", (int)(p->cal_gy_level * 100.0 + 0.5));
+ printf(" and then hit any key to continue,\n");
+ printf(" or hit Esc or Q to abort: ");
+ }
+ } else {
+
+ /* We need to display a test patch to proceed with this
+ * type of calibration. Typically this will be:
+ *
+ * inst_calc_xxxx_grey:
+ * set p->cal_gy_level = 0.6
+ * set p->cal_gy_count = 0;
+ *
+ * inst_calc_xxxx_grey_darker:
+ * set p->cal_gy_level *= 0.7
+ * set p->cal_gy_count++
+ *
+ * inst_calc_xxxx_grey_ligher:
+ * set p->cal_gy_level *= 1.4
+ * set p->cal_gy_count++
+ *
+ * and return failure if p->cal_gy_count > 4
+ */
+
+ if ((rv = disp_setup(p, calc, dwi)) != inst_ok)
+ return rv;
+ }
+ break;
+
+ default:
+ /* Something isn't being handled */
+ a1logd(p->log,1,"inst_handle_calibrate unhandled calc case 0x%x, err 0x%x\n",calc,inst_internal_error);
+ return inst_internal_error;
+ }
+ fflush(stdout);
+
+ usermes = 1;
+
+ if (calc != inst_calc_man_ref_whitek) {
+ empty_con_chars();
+ ch = next_con_char();
+ printf("\n");
+ if (ch == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
+ a1logd(p->log,1,"inst_handle_calibrate user aborted 0x%x\n",inst_user_abort);
+ return inst_user_abort;
+ }
+ }
+ }
+ }
+}
+
+/* ============================================================================= */
+
+/* A helper function to display -y flag usage for each instrument type available */
+/* Return accumulated capabilities2 of all the instruments */
+/* Return all possible capabilities if there are no instruments */
+/* If docbib is nz, then only display the base calibration display types */
+inst2_capability inst_show_disptype_options(FILE *fp, char *oline, icompaths *icmps, int docbib) {
+ int i, j;
+ char buf[200], *bp;
+ char extra[40];
+ int olen, pstart;
+ int notall = 0; /* Not all instruments are USB */
+ int gotone = 0; /* Found at least one USB instrument */
+ inst2_capability acap = 0; /* Accumulate capabilities */
+
+ if (icmps == NULL)
+ return 0;
+
+ /* Locate the end of the option */
+ for (bp = oline; *bp != '\000' && *bp == ' '; bp++)
+ ;
+ for (; *bp != '\000' && *bp != ' '; bp++)
+ ;
+ pstart = bp - oline;
+ if (pstart > 10)
+ pstart = 10;
+ strncpy(buf, oline, pstart);
+ buf[pstart++] = ' ';
+
+ olen = strlen(oline); /* lenth of option part of line */
+
+ for (i = 0; icmps != NULL && i < icmps->npaths; i++) {
+ inst *it;
+ inst2_capability cap;
+ int k;
+
+ if ((it = new_inst(icmps->paths[i], 1, g_log, NULL, NULL)) == NULL) {
+ notall = 1;
+ continue;
+ }
+ gotone = 1;
+
+ it->capabilities(it, NULL, &cap, NULL);
+ acap |= cap;
+
+ if (cap & inst2_disptype) {
+ int nsel;
+ inst_disptypesel *sels;
+
+ if (it->get_disptypesel(it, &nsel, &sels, 1, 0) != inst_ok) {
+ it->del(it);
+ continue;
+ }
+ for (j = 0; j < nsel; j++) {
+ int m;
+
+ if (docbib && sels[j].cbid == 0)
+ continue; /* Skip non cbid type */
+
+ m = pstart;
+ for (k = 0; k < (INST_DTYPE_SEL_LEN-1); k++) {
+ if (sels[j].sel[k] == '\000')
+ break;
+ if (m > pstart)
+ buf[m++] = '|';
+ buf[m++] = sels[j].sel[k];
+ }
+ while (m < (olen+1)) /* Indent it by 1 */
+ buf[m++] = ' ';
+ buf[m++] = '\000';
+
+ extra[0] = '\000';
+ if ((sels[j].flags & inst_dtflags_default) || sels[j].cbid != 0) {
+ strcat(extra, " [");
+ if (sels[j].flags & inst_dtflags_default) {
+ strcat(extra, "Default");
+ if (sels[j].cbid != 0)
+ strcat(extra, ",");
+ }
+ if (sels[j].cbid != 0) {
+ sprintf(extra + strlen(extra), "CB%d",sels[j].cbid);
+ }
+ strcat(extra, "]");
+ }
+
+ fprintf(fp, "%s%s: %s%s\n",buf, inst_sname(it->itype), sels[j].desc, extra);
+
+ if (j == 0) {
+ for (m = 0; m < pstart; m++)
+ buf[m] = ' ';
+ }
+ }
+ }
+ it->del(it);
+ }
+ /* Output a default desciption if not all instruments are USB */
+ if (notall) {
+ int m = pstart;
+ buf[m++] = 'l';
+ buf[m++] = '|';
+ buf[m++] = 'c';
+ while (m < olen)
+ buf[m++] = ' ';
+ buf[m++] = '\000';
+ fprintf(fp, "%s%s\n",buf, " Other: l = LCD, c = CRT");
+ }
+ if (!gotone)
+ acap = ~0;
+
+ return acap;
+}
+
+/* A helper function to turn a -y flag into a selection index */
+/* If docbib is nz, then only allow base calibration display types */
+/* Return -1 on error */
+int inst_get_disptype_index(inst *it, int c, int docbib) {
+ inst2_capability cap;
+ int j, k;
+
+ it->capabilities(it, NULL, &cap, NULL);
+
+ if (cap & inst2_disptype) {
+ int nsel;
+ inst_disptypesel *sels;
+
+ if (it->get_disptypesel(it, &nsel, &sels, 1, 0) != inst_ok) {
+ return -1;
+ }
+ for (j = 0; j < nsel; j++) {
+ if (docbib && sels[j].cbid == 0)
+ continue; /* Skip non cbid type */
+
+ for (k = 0; k < (INST_DTYPE_SEL_LEN-1); k++) {
+ if (sels[j].sel[k] == '\000')
+ break;
+ if (sels[j].sel[k] == c) {
+ return j;
+ }
+ }
+ }
+ }
+ return -1;
+}
+
+/* ================================================================= */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spectro/instappsup.h b/spectro/instappsup.h
new file mode 100644
index 0000000..24da6db
--- /dev/null
+++ b/spectro/instappsup.h
@@ -0,0 +1,100 @@
+
+#ifndef INSTAPPSUP_H
+
+/* Instrument command line application support functions */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 15/3/2001
+ *
+ * Copyright 2001 - 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.
+ *
+ */
+
+//#include "insttypes.h" /* libinst Includes this functionality */
+//#include "icoms.h"
+//#include "conv.h"
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/* ============================================================================= */
+/* a default user interaction callback handler */
+
+/* User key types */
+#define DUIH_NONE 0x0000 /* No meaning */
+#define DUIH_ABORT 0x0100 /* User abort operation */
+#define DUIH_TERM 0x0200 /* User terminated operation */
+#define DUIH_CMND 0x0400 /* User command */
+#define DUIH_TRIG 0x0800 /* User trigger */
+
+/* The default uicallback function and context. resets uih too. */
+#define DUIH_FUNC_AND_CONTEXT (inst_reset_uih(), inst_get_uicallback()), inst_get_uicontext()
+
+/* Return the default uicallback function */
+inst_code (*inst_get_uicallback())(void *, inst_ui_purp);
+
+/* Return the default uicallback context */
+void *inst_get_uicontext();
+
+/* Install the default uicallback function in the given inst */
+void inst_set_uicallback(inst *p);
+
+/* Reset user interaction handling to default (Esc, ^C, q or 'Q' = Abort) */
+void inst_reset_uih();
+
+/* Set a key range to the given handling type */
+/* min & max are between 0 and 255, status is one of */
+/* DUIH_OK, DUIH_USER, DUIH_TERM, DUIH_TRIG, DUIH_CMND */
+void inst_set_uih(int min, int max, int status);
+
+/* Get the character that caused the user trigger or abort */
+/* + its key type in the upper 8 bits. */
+/* Clear it to 0x00 after reading it. */
+int inst_get_uih_char();
+
+/* ============================================================================= */
+
+#ifndef DISPSUP_H
+/* Opaque type as far as inst.h is concerned. */
+typedef struct _disp_win_info disp_win_info;
+#endif
+
+/* A default calibration user interaction handler using the console. */
+/* This handles both normal and display based calibration interaction */
+/* with the instrument, if a disp_setup function and pointer to disp_win_info */
+/* is provided. */
+inst_code inst_handle_calibrate(
+ inst *p,
+ inst_cal_type calt, /* Calibration type to do */
+ inst_cal_cond calc, /* Current calibration condition */
+ inst_code (*disp_setup) (inst *p, inst_cal_cond calc, disp_win_info *dwi),
+ /* Callback for handling a display calibration - May be NULL */
+ disp_win_info *dwi /* Information to be able to open a display test patch - May be NULL */
+);
+
+/* ============================================================================= */
+
+/* A helper function to display -y flag usage for each instrument type available */
+/* Return accumulated capabilities2 of all the instruments. */
+/* If docbib is nz, then only display the base calibration display types */
+inst2_capability inst_show_disptype_options(FILE *fp, char *oline, icompaths *icmps, int docbib);
+
+/* A helper function to turn a -y flag into a list index */
+/* If docbib is nz, then only allow base calibration display types */
+/* Return 0 on error */
+int inst_get_disptype_index(inst *it, int c, int docbib);
+
+#ifdef __cplusplus
+ }
+#endif
+
+#define INSTAPPSUP_H
+#endif /* INSTAPPSUP_H */
diff --git a/spectro/instlib.ksh b/spectro/instlib.ksh
new file mode 100644
index 0000000..88fcba5
--- /dev/null
+++ b/spectro/instlib.ksh
@@ -0,0 +1,191 @@
+#!/bin/sh
+
+# Package up the source and other files for the standalone GPLv2 Instrument library.
+
+# Copyright 2007 - 2013 Graeme W. Gill
+# This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
+# see the License2.txt file for licencing details.
+
+echo "Making standalone GPLv2 instrument archive instlib.zip "
+
+H_FILES="
+ ../h/sort.h
+ "
+
+NUMLIB_FILES="
+ ../numlib/numsup.h
+ ../numlib/numsup.c
+ "
+
+CGATS_FILES="
+ ../cgats/pars.h
+ ../cgats/pars.c
+ ../cgats/parsstd.c
+ ../cgats/cgats.h
+ ../cgats/cgats.c
+ ../cgats/cgatsstd.c
+ "
+
+XICC_FILES="
+ ../xicc/xspect.h
+ ../xicc/xspect.c
+ ../xicc/ccss.h
+ ../xicc/ccss.c
+ ../xicc/ccmx.h
+ ../xicc/ccmx.c
+ "
+
+RSPL_FILES="
+ ../rspl/rspl1.h
+ ../rspl/rspl1.c
+ "
+
+SPECTRO_FILES="
+ License2.txt
+ Makefile.OSX
+ Makefile.UNIX
+ Makefile.WNT
+ pollem.h
+ pollem.c
+ conv.h
+ conv.c
+ aglob.c
+ aglob.h
+ hidio.h
+ hidio.c
+ icoms.h
+ inst.h
+ inst.c
+ insttypes.c
+ insttypes.h
+ insttypeinst.h
+ instappsup.c
+ instappsup.h
+ dtp20.c
+ dtp20.h
+ dtp22.c
+ dtp22.h
+ dtp41.c
+ dtp41.h
+ dtp51.c
+ dtp51.h
+ dtp92.c
+ dtp92.h
+ ss.h
+ ss.c
+ ss_imp.h
+ ss_imp.c
+ i1disp.c
+ i1disp.h
+ i1d3.h
+ i1d3.c
+ i1pro.h
+ i1pro.c
+ i1pro_imp.h
+ i1pro_imp.c
+ munki.h
+ munki.c
+ munki_imp.h
+ munki_imp.c
+ hcfr.c
+ hcfr.h
+ huey.c
+ huey.h
+ colorhug.c
+ colorhug.h
+ spyd2.c
+ spyd2.h
+ spyd2setup.h
+ spyd2PLD.h
+ oemarch.c
+ oemarch.h
+ oeminst.c
+ vinflate.c
+ inflate.c
+ LzmaDec.c
+ LzmaDec.h
+ LzmaTypes.h
+ icoms.c
+ icoms_nt.c
+ icoms_ux.c
+ iusb.h
+ usbio.h
+ usbio.c
+ usbio_lusb.c
+ usbio_nt.c
+ usbio_ox.c
+ usbio_lx.c
+ xdg_bds.c
+ xdg_bds.h
+ spotread.c
+ "
+
+FILES=" $H_FILES $CGATS_FILES $NUMLIB_FILES $RSPL_FILES $XICC_FILES $SPECTRO_FILES "
+
+rm -f instlib.zip
+rm -rf _zipdir
+rm -f _ziplist
+mkdir _zipdir
+mkdir _zipdir/instlib
+
+
+# Archive the Argyll files needed
+for j in $FILES
+do
+ if [ ! -e ${j} ] ; then
+ echo "!!!!!!!!!!!!!!!!!!!!!!!!!!! Can't find file ${j} !!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
+ NOTFOUND="$NOTFOUND ${tt}"
+ else
+ cp ${j} _zipdir/instlib/${j##*/}
+ echo instlib/${j##*/} >> _ziplist
+ fi
+done
+
+# Plus renamed files
+cp IntsLib_Readme.txt _zipdir/instlib/Readme.txt
+echo instlib/Readme.txt >> _ziplist
+cp Makefile.SA _zipdir/instlib/Makefile
+echo instlib/Makefile >> _ziplist
+cp ../h/aconfig.h _zipdir/instlib/sa_config.h
+echo instlib/sa_config.h >> _ziplist
+
+# Create usb archive
+
+for j in `cat ../usb/afiles | grep -E -v 'afiles|binfiles.msw|binfiles.osx|binfiles.lx|Jamfile|ArgyllCMS.inf.t|ArgyllCMS.inf.d'`
+do
+ echo "File ${j}"
+
+ # Create any needed temporary directories
+
+ tt=usb/${j}
+ path=${tt%/*} # extract path without filename
+
+ echo "path ${path}"
+
+ if [ ! -e _zipdir/instlib/${path} ] ; then # if not been created
+ echo "Creating directory _zipdir/instlib/${path}"
+ mkdir -p _zipdir/instlib/${path}
+ fi
+
+ tt=../${tt}
+
+ if [ ! -e ${tt} ] ; then
+ echo "!!!!!!!!!!!!!!!!!!!!!!!!!!! Can't find file ${tt} !!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
+ NOTFOUND="$NOTFOUND ${tt}"
+ else
+ cp ${tt} _zipdir/instlib/usb/${j}
+ echo instlib/usb/${j} >> _ziplist
+ fi
+done
+
+cd _zipdir
+zip -9 -m ../instlib.zip `cat ../_ziplist`
+cd ..
+rm -rf _zipdir
+rm -f _ziplist
+
+if [ "X$NOTFOUND" != "X" ] ; then
+ echo "!!!!!! Didn't find $NOTFOUND !!!!!!"
+fi
+
+echo "Created instlib.zip"
diff --git a/spectro/instlib.txt b/spectro/instlib.txt
new file mode 100644
index 0000000..661584d
--- /dev/null
+++ b/spectro/instlib.txt
@@ -0,0 +1,11 @@
+
+To create a standalone instrument library, you need a subset of
+files from this directory and other selected files from Argyll.
+
+If you are on MSWin you need to build argyll first using Jam.
+
+You can run the instlib.ksh script which will copy the necessary
+files into the file instlib.zip.
+
+This can be extracted, the Makefile modified to suite the system,
+and then built. (See the Readme.txt within instlib.zip file)
diff --git a/spectro/insttypeinst.h b/spectro/insttypeinst.h
new file mode 100644
index 0000000..c61a5c4
--- /dev/null
+++ b/spectro/insttypeinst.h
@@ -0,0 +1,25 @@
+
+/* Add instrument instance headers here */
+
+/*
+ * Copyright 2001 - 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.
+ */
+
+#include "dtp20.h"
+#include "dtp22.h"
+#include "dtp41.h"
+#include "dtp51.h"
+#include "dtp92.h"
+#include "ss.h"
+#include "i1disp.h"
+#include "i1d3.h"
+#include "i1pro.h"
+#include "munki.h"
+#include "hcfr.h"
+#include "spyd2.h"
+#include "huey.h"
+#include "colorhug.h"
diff --git a/spectro/insttypes.c b/spectro/insttypes.c
new file mode 100644
index 0000000..e0863d0
--- /dev/null
+++ b/spectro/insttypes.c
@@ -0,0 +1,384 @@
+
+ /* Instrument supported types utilities */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 10/3/2001
+ *
+ * Copyright 2001 - 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#else
+#include "sa_config.h"
+#endif /* !SALONEINSTLIB */
+#include "numsup.h"
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+
+/* NOTE NOTE NOTE: */
+/* Need to add a new instrument to new_inst() in */
+/* inst.c as well !!! */
+
+/* Utility functions */
+
+/* Return the short instrument identification name (static string) */
+char *inst_sname(instType itype) {
+ switch (itype) {
+ case instDTP20:
+ return "DTP20";
+ case instDTP22:
+ return "DTP22";
+ case instDTP41:
+ return "DTP41";
+ case instDTP51:
+ return "DTP51";
+ case instDTP92:
+ return "DTP92";
+ case instDTP94:
+ return "DTP94";
+ case instSpectrolino:
+ return "Spectrolino";
+ case instSpectroScan:
+ return "SpectroScan";
+ case instSpectroScanT:
+ return "SpectroScanT";
+ case instSpectrocam:
+ return "Spectrocam";
+ case instI1Disp1:
+ return "i1D1";
+ case instI1Disp2:
+ return "i1D2";
+ case instI1Disp3:
+ return "i1D3";
+ case instI1Monitor:
+ return "i1 Monitor";
+ case instI1Pro:
+ return "i1 Pro";
+ case instI1Pro2:
+ return "i1 Pro 2";
+ case instColorMunki:
+ return "ColorMunki";
+ case instHCFR:
+ return "HCFR";
+ case instSpyder2:
+ return "Spyder2";
+ case instSpyder3:
+ return "Spyder3";
+ case instSpyder4:
+ return "Spyder4";
+ case instHuey:
+ return "Huey";
+ case instSmile:
+ return "Smile";
+ case instColorHug:
+ return "ColorHug";
+ default:
+ break;
+ }
+ return "Unknown";
+}
+
+/* Return the long instrument identification name (static string) */
+char *inst_name(instType itype) {
+ switch (itype) {
+ case instDTP20:
+ return "Xrite DTP20";
+ case instDTP22:
+ return "Xrite DTP22";
+ case instDTP41:
+ return "Xrite DTP41";
+ case instDTP51:
+ return "Xrite DTP51";
+ case instDTP92:
+ return "Xrite DTP92";
+ case instDTP94:
+ return "Xrite DTP94";
+ case instSpectrolino:
+ return "GretagMacbeth Spectrolino";
+ case instSpectroScan:
+ return "GretagMacbeth SpectroScan";
+ case instSpectroScanT:
+ return "GretagMacbeth SpectroScanT";
+ case instSpectrocam:
+ return "Spectrocam";
+ case instI1Disp1:
+ return "GretagMacbeth i1 Display 1";
+ case instI1Disp2:
+ return "GretagMacbeth i1 Display 2";
+ case instI1Disp3:
+ return "Xrite i1 DisplayPro, ColorMunki Display";
+ case instI1Monitor:
+ return "GretagMacbeth i1 Monitor";
+ case instI1Pro:
+ return "GretagMacbeth i1 Pro";
+ case instI1Pro2:
+ return "X-Rite i1 Pro 2";
+ case instColorMunki:
+ return "X-Rite ColorMunki";
+ case instHCFR:
+ return "Colorimtre HCFR";
+ case instSpyder2:
+ return "ColorVision Spyder2";
+ case instSpyder3:
+ return "Datacolor Spyder3";
+ case instSpyder4:
+ return "Datacolor Spyder4";
+ case instHuey:
+ return "GretagMacbeth Huey";
+ case instSmile:
+ return "ColorMunki Smile";
+ case instColorHug:
+ return "Hughski ColorHug";
+ default:
+ break;
+ }
+ return "Unknown Instrument";
+}
+
+/* Given a long instrument identification name, return the matching */
+/* instType, or instUnknown if not matched */
+instType inst_enum(char *name) {
+
+ if (strcmp(name, "Xrite DTP20") == 0)
+ return instDTP20;
+ else if (strcmp(name, "Xrite DTP22") == 0)
+ return instDTP22;
+ else if (strcmp(name, "Xrite DTP41") == 0)
+ return instDTP41;
+ else if (strcmp(name, "Xrite DTP51") == 0)
+ return instDTP51;
+ else if (strcmp(name, "Xrite DTP92") == 0)
+ return instDTP92;
+ else if (strcmp(name, "Xrite DTP94") == 0)
+ return instDTP94;
+ else if (strcmp(name, "GretagMacbeth Spectrolino") == 0)
+ return instSpectrolino;
+ else if (strcmp(name, "GretagMacbeth SpectroScan") == 0)
+ return instSpectroScan;
+ else if (strcmp(name, "GretagMacbeth SpectroScanT") == 0)
+ return instSpectroScanT;
+ else if (strcmp(name, "Spectrocam") == 0)
+ return instSpectrocam;
+ else if (strcmp(name, "GretagMacbeth i1 Display 1") == 0)
+ return instI1Disp1;
+ else if (strcmp(name, "GretagMacbeth i1 Display 2") == 0
+ || strcmp(name, "GretagMacbeth i1 Display") == 0
+ || strcmp(name, "Xrite i1 Display") == 0)
+ return instI1Disp2;
+ else if (strcmp(name, "Xrite i1 DisplayPro") == 0
+ || strcmp(name, "ColorMunki Display") == 0)
+ return instI1Disp3;
+ else if (strcmp(name, "GretagMacbeth i1 Monitor") == 0)
+ return instI1Monitor;
+ else if (strcmp(name, "GretagMacbeth i1 Pro") == 0
+ || strcmp(name, "Xrite i1 Pro") == 0)
+ return instI1Pro;
+ else if (strcmp(name, "Xrite i1 Pro 2") == 0)
+ return instI1Pro2;
+ else if (strcmp(name, "X-Rite ColorMunki") == 0)
+ return instColorMunki;
+ else if (strcmp(name, "Colorimtre HCFR") == 0)
+ return instHCFR;
+ else if (strcmp(name, "ColorVision Spyder2") == 0)
+ return instSpyder2;
+ else if (strcmp(name, "Datacolor Spyder3") == 0)
+ return instSpyder3;
+ else if (strcmp(name, "Datacolor Spyder4") == 0)
+ return instSpyder4;
+ else if (strcmp(name, "GretagMacbeth Huey") == 0)
+ return instHuey;
+ else if (strcmp(name, "ColorMunki Smile") == 0)
+ return instSmile;
+ else if (strcmp(name, "Hughski ColorHug") == 0)
+ return instColorHug;
+
+ return instUnknown;
+}
+
+#ifdef ENABLE_USB
+
+/* Given a USB vendor and product ID, */
+/* return the matching instrument type, or */
+/* instUnknown if none match. */
+/* If nep == 0, do preliminary match just on vid & pid */
+instType inst_usb_match(
+unsigned int idVendor,
+unsigned int idProduct,
+int nep) { /* Number of end points */
+
+ if (idVendor == 0x04DB) {
+ if (idProduct == 0x005B) /* Colorimtre HCFR */
+ return instHCFR;
+ }
+
+ if (idVendor == 0x0670) { /* Sequel Imaging */
+ if (idProduct == 0x0001) /* Monaco Optix / i1 Display 1 */
+ /* Sequel Chroma 4 / i1 Display 1 */
+ return instI1Disp1;
+ }
+
+ if (idVendor == 0x0765) { /* X-Rite */
+ if (idProduct == 0x5001) /* HueyL (Lenovo W70DS Laptop ?) */
+ return instHuey;
+ if (idProduct == 0x5010) /* HueyL (Lenovo W530 Laptop ?) */
+ return instHuey;
+ if (idProduct == 0x5020) /* i1DisplayPro, ColorMunki Display (HID) */
+ return instI1Disp3;
+ if (idProduct == 0x6003) /* ColorMinki Smile (aka. ColorMunki Display Lite) */
+ return instSmile;
+ if (idProduct == 0xD020) /* DTP20 */
+ return instDTP20;
+ if (idProduct == 0xD092) /* DTP92Q */
+ return instDTP92;
+ if (idProduct == 0xD094) /* DTP94 */
+ return instDTP94;
+ }
+
+ if (idVendor == 0x085C) { /* ColorVision */
+ if (idProduct == 0x0100) /* ColorVision Spyder1 */
+ return instSpyder2; /* Alias to Spyder 2 */
+ if (idProduct == 0x0200) /* ColorVision Spyder2 */
+ return instSpyder2;
+ if (idProduct == 0x0300) /* ColorVision Spyder3 */
+ return instSpyder3;
+ if (idProduct == 0x0400) /* ColorVision Spyder4 */
+ return instSpyder4;
+ }
+
+ if (idVendor == 0x0971) { /* Gretag Macbeth */
+ if (idProduct == 0x2000) { /* i1 Pro or i1 Pro 2*/
+
+ /* The i1pro2/rev E has 5 pipes - it has EP 85 */
+ if (nep >= 5)
+ return instI1Pro2;
+ else
+ return instI1Pro;
+ }
+ if (idProduct == 0x2001) /* i1 Monitor */
+ return instI1Monitor;
+ if (idProduct == 0x2003) /* i1 Display 2 */
+ return instI1Disp2;
+ if (idProduct == 0x2005) /* Huey (HID) */
+ return instHuey;
+ if (idProduct == 0x2007) /* ColorMunki */
+ return instColorMunki;
+ }
+
+ if ((idVendor == 0x04d8 && idProduct == 0xf8da) /* Microchip & Hughski ColorHug (old) */
+ || (idVendor == 0x273f && idProduct == 0x1001)) { /* Hughski & ColorHug Fmw. >= 0.1.20 */
+ return instColorHug;
+ }
+ /* Add other instruments here */
+
+ return instUnknown;
+}
+
+#endif /* ENABLE_USB */
+
+/* Should deprecate the following. It should be replaced with a */
+/* method in the instrument class that returns its configured spectrum, */
+/* and the spectrum should be embedded in the .ti3 file, not the instrument */
+/* name. */
+
+/* Fill in an instruments illuminant spectrum. */
+/* Return 0 on sucess, 1 if not not applicable. */
+int inst_illuminant(xspect *sp, instType itype) {
+
+ switch (itype) {
+ case instDTP20:
+ return standardIlluminant(sp, icxIT_A, 0); /* 2850K */
+
+ case instDTP22:
+ return standardIlluminant(sp, icxIT_A, 0); /* 2850K */
+
+ case instDTP41:
+ return standardIlluminant(sp, icxIT_A, 0); /* 2850K */
+
+ case instDTP51:
+ return standardIlluminant(sp, icxIT_A, 0); /* 2850K */
+
+ case instDTP92:
+ case instDTP94:
+ return 1; /* Not applicable */
+
+ /* Strictly the Spectrolino could have the UV or D65 filter on, */
+ /* but since we're not currently passing this inform through, we */
+ /* are simply assuming the default A type illuminant. */
+
+ case instSpectrolino:
+ return standardIlluminant(sp, icxIT_A, 0); /* Standard A type assumed */
+
+ case instSpectroScan:
+ return standardIlluminant(sp, icxIT_A, 0); /* Standard A type assumed */
+
+ case instSpectroScanT:
+ return standardIlluminant(sp, icxIT_A, 0); /* Standard A type assumed */
+
+#ifndef SALONEINSTLIB
+ case instSpectrocam:
+ return standardIlluminant(sp, icxIT_Spectrocam, 0); /* Spectrocam Xenon Lamp */
+#endif
+ case instI1Disp1:
+ return 1; /* Not applicable */
+
+ case instI1Disp2:
+ return 1; /* Not applicable */
+
+ case instI1Disp3:
+ return 1; /* Not applicable */
+
+ case instI1Monitor:
+ return 1; /* Not applicable */
+
+ case instI1Pro:
+ case instI1Pro2:
+ return standardIlluminant(sp, icxIT_A, 0); /* Standard A type assumed */
+
+ case instColorMunki:
+ return 1; /* No U.V. */
+
+ case instHCFR:
+ return 1; /* Not applicable */
+
+ case instSpyder2:
+ return 1; /* Not applicable */
+
+ case instSpyder3:
+ return 1; /* Not applicable */
+
+ case instSpyder4:
+ return 1; /* Not applicable */
+
+ case instHuey:
+ return 1; /* Not applicable */
+
+ case instSmile:
+ return 1; /* Not applicable */
+
+ case instColorHug:
+ return 1; /* Not applicable */
+
+
+ default:
+ break;
+ }
+ return 1;
+}
+
+
diff --git a/spectro/insttypes.h b/spectro/insttypes.h
new file mode 100644
index 0000000..88e800b
--- /dev/null
+++ b/spectro/insttypes.h
@@ -0,0 +1,100 @@
+
+#ifndef INSTTYPES_H
+
+ /* Instrument suported types utilities. */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 15/3/2001
+ *
+ * Copyright 2001 - 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.
+ *
+ */
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/* ----------------------------- */
+/* Possible types of instruments */
+typedef enum {
+ instUnknown = 0, /* Undefined Instrument */
+ instDTP20, /* Xrite DTP20 (Pulse) */
+ instDTP22, /* Xrite DTP22 (Digital Swatchbook) */
+ instDTP41, /* Xrite DTP41 */
+ instDTP51, /* Xrite DTP51 */
+ instDTP92, /* Xrite DTP92 */
+ instDTP94, /* Xrite DTP94 (Optix) */
+ instSpectrolino, /* GretagMacbeth Spectrolino */
+ instSpectroScan, /* GretagMacbeth SpectroScan */
+ instSpectroScanT, /* GretagMacbeth SpectroScanT */
+ instSpectrocam, /* Avantes Spectrocam */
+ instI1Disp1, /* GretagMacbeth i1 Display 1 */
+ instI1Disp2, /* GretagMacbeth i1 Display 2 */
+ instI1Disp3, /* Xrite i1 DisplayPro, ColorMunki Display */
+ instI1Monitor, /* GretagMacbeth i1 Monitor */
+ instI1Pro, /* GretagMacbeth i1 Pro */
+ instI1Pro2, /* X-Rite i1 Pro2 */
+ instColorMunki, /* X-Rite ColorMunki */
+ instHCFR, /* Colorimtre HCFR */
+ instSpyder2, /* Datacolor/ColorVision Spyder2 */
+ instSpyder3, /* Datacolor Spyder3 */
+ instSpyder4, /* Datacolor Spyder4 */
+ instHuey, /* GretagMacbeth Huey */
+ instSmile, /* X-rite Colormunki Smile */
+ instColorHug, /* Hughski ColorHug */
+
+} instType;
+
+struct _icoms; /* Forward declaration */
+
+/* Utility functions in libinsttypes */
+
+/* Given its instrument type, return the matching */
+/* short instrument name (static string), */
+extern char *inst_sname(instType itype);
+
+/* Given its instrument type, return the matching */
+/* long instrument identification name (static string), */
+extern char *inst_name(instType itype);
+
+
+/* Given an instrument long identification name, return the matching */
+/* instType, or instUnknown if not matched */
+extern instType inst_enum(char *name);
+
+
+#ifdef ENABLE_USB
+/* Given a USB vendor and product ID, */
+/* return the matching instrument type, or */
+/* instUnknown if none match. */
+extern instType inst_usb_match(
+unsigned int idVendor,
+unsigned int idProduct,
+int nep); /* Number of end points (0 for prelim match) */
+#endif /* ENABLE_USB */
+
+
+/* Should deprecate the following. It should be replaced with a */
+/* method in the instrument class that returns its configured spectrum, */
+/* and the spectrum should be embedded in the .ti3 file, not the instrument */
+/* name. */
+
+/* Fill in an instruments illuminant spectrum. */
+/* Return 0 on sucess, 1 if not not applicable. */
+extern int inst_illuminant(xspect *sp, instType itype);
+
+
+#ifdef __cplusplus
+ }
+#endif
+
+#define INSTTYPES_H
+#endif /* INSTTYPES_H */
+
diff --git a/spectro/iusb.h b/spectro/iusb.h
new file mode 100644
index 0000000..a254ec0
--- /dev/null
+++ b/spectro/iusb.h
@@ -0,0 +1,131 @@
+#ifndef _IUSB_H_
+
+/* Standard USB protocol defines */
+
+/*
+ * Copyright 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.
+ */
+
+/* Device and/or Interface Class codes */
+#define IUSB_CLASS_PER_INTERFACE 0x00 /* for DeviceClass */
+#define IUSB_CLASS_AUDIO 0x01
+#define IUSB_CLASS_COMM 0x02
+#define IUSB_CLASS_HID 0x03
+#define IUSB_CLASS_PHYSICAL 0x05
+#define IUSB_CLASS_STILL_IMAGE 0x06
+#define IUSB_CLASS_PRINTER 0x07
+#define IUSB_CLASS_MASS_STORAGE 0x08
+#define IUSB_CLASS_HUB 0x09
+#define IUSB_CLASS_CDC_DATA 0x0a
+#define IUSB_CLASS_SMART_CARD 0x0b
+#define IUSB_CLASS_CONT_SECURITY 0x0d
+#define IUSB_CLASS_VIDEO 0x0e
+#define IUSB_CLASS_DIAGNOSTIC 0xdc
+#define IUSB_CLASS_WIRELESS 0xe0
+#define IUSB_CLASS_MISC 0xef
+#define IUSB_CLASS_APP_SPEC 0xfe
+#define IUSB_CLASS_VENDOR_SPEC 0xff
+
+
+/* Standard Descriptor types */
+#define IUSB_DESC_TYPE_DEVICE 0x01
+#define IUSB_DESC_TYPE_CONFIG 0x02
+#define IUSB_DESC_TYPE_STRING 0x03
+#define IUSB_DESC_TYPE_INTERFACE 0x04
+#define IUSB_DESC_TYPE_ENDPOINT 0x05
+#define IUSB_DESC_TYPE_DEVICE_QUALIFIER 0x06
+#define IUSB_DESC_TYPE_OTHER_SPEED_CONFIG 0x07
+#define IUSB_DESC_TYPE_INTERFACE_POWER 0x08
+#define IUSB_DESC_TYPE_OTG 0x09
+#define IUSB_DESC_TYPE_DEBUG 0x0a
+#define IUSB_DESC_TYPE_INTERFACE_ASSOCIATION 0x0b
+
+/* Descriptor sizes per descriptor type */
+#define IUSB_DESC_TYPE_DEVICE_SIZE 18
+#define IUSB_DESC_TYPE_CONFIG_SIZE 9
+#define IUSB_DESC_TYPE_INTERFACE_SIZE 9
+#define IUSB_DESC_TYPE_ENDPOINT_SIZE 7
+
+/* Endpoint descriptor bEndpointAddress */
+#define IUSB_ENDPOINT_NUM_MASK 0x0f
+#define IUSB_ENDPOINT_DIR_MASK 0x80
+#define IUSB_ENDPOINT_DIR_SHIFT 7
+#define IUSB_ENDPOINT_IN 0x80
+#define IUSB_ENDPOINT_OUT 0x00
+
+/* Endpoint descriptor bmAttributes */
+#define IUSB_ENDPOINT_TYPE_MASK 0x03
+#define IUSB_ENDPOINT_TYPE_CONTROL 0x00
+#define IUSB_ENDPOINT_TYPE_ISOCHRONOUS 0x01
+#define IUSB_ENDPOINT_TYPE_BULK 0x02
+#define IUSB_ENDPOINT_TYPE_INTERRUPT 0x03
+
+#define IUSB_ENDPOINT_SYNC_MASK 0x0c
+#define IUSB_ENDPOINT_SYNC_NONE 0x00
+#define IUSB_ENDPOINT_SYNC_ASYNC 0x04
+#define IUSB_ENDPOINT_SYNC_ADPT 0x08
+#define IUSB_ENDPOINT_SYNC_SYNC 0x0c
+
+#define IUSB_ENDPOINT_USAGE_MASK 0x30
+#define IUSB_ENDPOINT_USAGE_DATA 0x00
+#define IUSB_ENDPOINT_USAGE_FEED 0x10
+#define IUSB_ENDPOINT_USAGE_IMPL_FEED 0x20
+
+/* Endpoint descriptor wMaxPacketSize */
+#define IUSB_ENDPOINT_MAX_PKTSZ_MASK 0x03ff
+#define IUSB_ENDPOINT_MAX_XACTS_MASK 0x0c00
+#define IUSB_ENDPOINT_MAX_XACTS_SHIFT 11
+
+/* OTG descriptor bmAttributes */
+#define IUSB_OTG_SRP 0x00
+#define IUSB_OTG_HNP 0x01
+
+/* Control request */
+#define IUSB_REQ_HEADER_SIZE 8
+
+/* Request bmRequestType */
+#define IUSB_REQ_HOST_TO_DEV 0x00
+#define IUSB_REQ_DEV_TO_HOST 0x80
+#define IUSB_REQ_DIR_MASK 0x80
+
+#define IUSB_REQ_TYPE_SHIFT 5
+#define IUSB_REQ_TYPE_STANDARD (0x00 << IUSB_REQ_TYPE_SHIFT)
+#define IUSB_REQ_TYPE_CLASS (0x01 << IUSB_REQ_TYPE_SHIFT)
+#define IUSB_REQ_TYPE_VENDOR (0x02 << IUSB_REQ_TYPE_SHIFT)
+#define IUSB_REQ_TYPE_RESERVED (0x03 << IUSB_REQ_TYPE_SHIFT)
+#define IUSB_REQ_TYPE_MASK (0x03 << IUSB_REQ_TYPE_SHIFT)
+
+#define IUSB_REQ_RECIP_DEVICE 0x00
+#define IUSB_REQ_RECIP_INTERFACE 0x01
+#define IUSB_REQ_RECIP_ENDPOINT 0x02
+#define IUSB_REQ_RECIP_OTHER 0x03
+#define IUSB_REQ_RECIP_MASK 0x1f
+
+/* Standard bRequest values */
+#define IUSB_REQ_GET_STATUS 0x00
+#define IUSB_REQ_CLEAR_FEATURE 0x01
+#define IUSB_REQ_SET_FEATURE 0x03
+#define IUSB_REQ_SET_ADDRESS 0x05
+#define IUSB_REQ_GET_DESCRIPTOR 0x06
+#define IUSB_REQ_SET_DESCRIPTOR 0x07
+#define IUSB_REQ_GET_CONFIGURATION 0x08
+#define IUSB_REQ_SET_CONFIGURATION 0x09
+#define IUSB_REQ_GET_INTERFACE 0x0a
+#define IUSB_REQ_SET_INTERFACE 0x0b
+#define IUSB_REQ_SYNCH_FRAME 0x0c
+
+/* Feature selector */
+#define IUSB_FEATURE_EP_HALT 0
+#define IUSB_FEATURE_DEV_REMOTE_WAKEUP 1
+
+/* REQ_GET_STATUS return values */
+#define IUSB_DEVICE_STATUS_SELFPWR 0x0001
+#define IUSB_DEVICE_STATUS_REMOTE_WAKEUP 0x0002
+#define IUSB_ENDPOINT_STATUS_HALT 0x0001
+
+#define _IUSB_H_
+#endif /* _IUSB_H_ */
diff --git a/spectro/linear.cal b/spectro/linear.cal
new file mode 100644
index 0000000..30da145
--- /dev/null
+++ b/spectro/linear.cal
@@ -0,0 +1,275 @@
+CAL
+
+DESCRIPTOR "Argyll Device Calibration Curves"
+ORIGINATOR "Argyll synthcal"
+CREATED "Sat Mar 09 18:33:22 2013"
+KEYWORD "DEVICE_CLASS"
+DEVICE_CLASS "DISPLAY"
+KEYWORD "COLOR_REP"
+COLOR_REP "RGB"
+
+KEYWORD "RGB_I"
+NUMBER_OF_FIELDS 4
+BEGIN_DATA_FORMAT
+RGB_I RGB_R RGB_G RGB_B
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 256
+BEGIN_DATA
+0.0000 0.0000 0.0000 0.0000
+3.9216e-003 3.9216e-003 3.9216e-003 3.9216e-003
+7.8431e-003 7.8431e-003 7.8431e-003 7.8431e-003
+0.011765 0.011765 0.011765 0.011765
+0.015686 0.015686 0.015686 0.015686
+0.019608 0.019608 0.019608 0.019608
+0.023529 0.023529 0.023529 0.023529
+0.027451 0.027451 0.027451 0.027451
+0.031373 0.031373 0.031373 0.031373
+0.035294 0.035294 0.035294 0.035294
+0.039216 0.039216 0.039216 0.039216
+0.043137 0.043137 0.043137 0.043137
+0.047059 0.047059 0.047059 0.047059
+0.050980 0.050980 0.050980 0.050980
+0.054902 0.054902 0.054902 0.054902
+0.058824 0.058824 0.058824 0.058824
+0.062745 0.062745 0.062745 0.062745
+0.066667 0.066667 0.066667 0.066667
+0.070588 0.070588 0.070588 0.070588
+0.074510 0.074510 0.074510 0.074510
+0.078431 0.078431 0.078431 0.078431
+0.082353 0.082353 0.082353 0.082353
+0.086275 0.086275 0.086275 0.086275
+0.090196 0.090196 0.090196 0.090196
+0.094118 0.094118 0.094118 0.094118
+0.098039 0.098039 0.098039 0.098039
+0.10196 0.10196 0.10196 0.10196
+0.10588 0.10588 0.10588 0.10588
+0.10980 0.10980 0.10980 0.10980
+0.11373 0.11373 0.11373 0.11373
+0.11765 0.11765 0.11765 0.11765
+0.12157 0.12157 0.12157 0.12157
+0.12549 0.12549 0.12549 0.12549
+0.12941 0.12941 0.12941 0.12941
+0.13333 0.13333 0.13333 0.13333
+0.13725 0.13725 0.13725 0.13725
+0.14118 0.14118 0.14118 0.14118
+0.14510 0.14510 0.14510 0.14510
+0.14902 0.14902 0.14902 0.14902
+0.15294 0.15294 0.15294 0.15294
+0.15686 0.15686 0.15686 0.15686
+0.16078 0.16078 0.16078 0.16078
+0.16471 0.16471 0.16471 0.16471
+0.16863 0.16863 0.16863 0.16863
+0.17255 0.17255 0.17255 0.17255
+0.17647 0.17647 0.17647 0.17647
+0.18039 0.18039 0.18039 0.18039
+0.18431 0.18431 0.18431 0.18431
+0.18824 0.18824 0.18824 0.18824
+0.19216 0.19216 0.19216 0.19216
+0.19608 0.19608 0.19608 0.19608
+0.20000 0.20000 0.20000 0.20000
+0.20392 0.20392 0.20392 0.20392
+0.20784 0.20784 0.20784 0.20784
+0.21176 0.21176 0.21176 0.21176
+0.21569 0.21569 0.21569 0.21569
+0.21961 0.21961 0.21961 0.21961
+0.22353 0.22353 0.22353 0.22353
+0.22745 0.22745 0.22745 0.22745
+0.23137 0.23137 0.23137 0.23137
+0.23529 0.23529 0.23529 0.23529
+0.23922 0.23922 0.23922 0.23922
+0.24314 0.24314 0.24314 0.24314
+0.24706 0.24706 0.24706 0.24706
+0.25098 0.25098 0.25098 0.25098
+0.25490 0.25490 0.25490 0.25490
+0.25882 0.25882 0.25882 0.25882
+0.26275 0.26275 0.26275 0.26275
+0.26667 0.26667 0.26667 0.26667
+0.27059 0.27059 0.27059 0.27059
+0.27451 0.27451 0.27451 0.27451
+0.27843 0.27843 0.27843 0.27843
+0.28235 0.28235 0.28235 0.28235
+0.28627 0.28627 0.28627 0.28627
+0.29020 0.29020 0.29020 0.29020
+0.29412 0.29412 0.29412 0.29412
+0.29804 0.29804 0.29804 0.29804
+0.30196 0.30196 0.30196 0.30196
+0.30588 0.30588 0.30588 0.30588
+0.30980 0.30980 0.30980 0.30980
+0.31373 0.31373 0.31373 0.31373
+0.31765 0.31765 0.31765 0.31765
+0.32157 0.32157 0.32157 0.32157
+0.32549 0.32549 0.32549 0.32549
+0.32941 0.32941 0.32941 0.32941
+0.33333 0.33333 0.33333 0.33333
+0.33725 0.33725 0.33725 0.33725
+0.34118 0.34118 0.34118 0.34118
+0.34510 0.34510 0.34510 0.34510
+0.34902 0.34902 0.34902 0.34902
+0.35294 0.35294 0.35294 0.35294
+0.35686 0.35686 0.35686 0.35686
+0.36078 0.36078 0.36078 0.36078
+0.36471 0.36471 0.36471 0.36471
+0.36863 0.36863 0.36863 0.36863
+0.37255 0.37255 0.37255 0.37255
+0.37647 0.37647 0.37647 0.37647
+0.38039 0.38039 0.38039 0.38039
+0.38431 0.38431 0.38431 0.38431
+0.38824 0.38824 0.38824 0.38824
+0.39216 0.39216 0.39216 0.39216
+0.39608 0.39608 0.39608 0.39608
+0.40000 0.40000 0.40000 0.40000
+0.40392 0.40392 0.40392 0.40392
+0.40784 0.40784 0.40784 0.40784
+0.41176 0.41176 0.41176 0.41176
+0.41569 0.41569 0.41569 0.41569
+0.41961 0.41961 0.41961 0.41961
+0.42353 0.42353 0.42353 0.42353
+0.42745 0.42745 0.42745 0.42745
+0.43137 0.43137 0.43137 0.43137
+0.43529 0.43529 0.43529 0.43529
+0.43922 0.43922 0.43922 0.43922
+0.44314 0.44314 0.44314 0.44314
+0.44706 0.44706 0.44706 0.44706
+0.45098 0.45098 0.45098 0.45098
+0.45490 0.45490 0.45490 0.45490
+0.45882 0.45882 0.45882 0.45882
+0.46275 0.46275 0.46275 0.46275
+0.46667 0.46667 0.46667 0.46667
+0.47059 0.47059 0.47059 0.47059
+0.47451 0.47451 0.47451 0.47451
+0.47843 0.47843 0.47843 0.47843
+0.48235 0.48235 0.48235 0.48235
+0.48627 0.48627 0.48627 0.48627
+0.49020 0.49020 0.49020 0.49020
+0.49412 0.49412 0.49412 0.49412
+0.49804 0.49804 0.49804 0.49804
+0.50196 0.50196 0.50196 0.50196
+0.50588 0.50588 0.50588 0.50588
+0.50980 0.50980 0.50980 0.50980
+0.51373 0.51373 0.51373 0.51373
+0.51765 0.51765 0.51765 0.51765
+0.52157 0.52157 0.52157 0.52157
+0.52549 0.52549 0.52549 0.52549
+0.52941 0.52941 0.52941 0.52941
+0.53333 0.53333 0.53333 0.53333
+0.53725 0.53725 0.53725 0.53725
+0.54118 0.54118 0.54118 0.54118
+0.54510 0.54510 0.54510 0.54510
+0.54902 0.54902 0.54902 0.54902
+0.55294 0.55294 0.55294 0.55294
+0.55686 0.55686 0.55686 0.55686
+0.56078 0.56078 0.56078 0.56078
+0.56471 0.56471 0.56471 0.56471
+0.56863 0.56863 0.56863 0.56863
+0.57255 0.57255 0.57255 0.57255
+0.57647 0.57647 0.57647 0.57647
+0.58039 0.58039 0.58039 0.58039
+0.58431 0.58431 0.58431 0.58431
+0.58824 0.58824 0.58824 0.58824
+0.59216 0.59216 0.59216 0.59216
+0.59608 0.59608 0.59608 0.59608
+0.60000 0.60000 0.60000 0.60000
+0.60392 0.60392 0.60392 0.60392
+0.60784 0.60784 0.60784 0.60784
+0.61176 0.61176 0.61176 0.61176
+0.61569 0.61569 0.61569 0.61569
+0.61961 0.61961 0.61961 0.61961
+0.62353 0.62353 0.62353 0.62353
+0.62745 0.62745 0.62745 0.62745
+0.63137 0.63137 0.63137 0.63137
+0.63529 0.63529 0.63529 0.63529
+0.63922 0.63922 0.63922 0.63922
+0.64314 0.64314 0.64314 0.64314
+0.64706 0.64706 0.64706 0.64706
+0.65098 0.65098 0.65098 0.65098
+0.65490 0.65490 0.65490 0.65490
+0.65882 0.65882 0.65882 0.65882
+0.66275 0.66275 0.66275 0.66275
+0.66667 0.66667 0.66667 0.66667
+0.67059 0.67059 0.67059 0.67059
+0.67451 0.67451 0.67451 0.67451
+0.67843 0.67843 0.67843 0.67843
+0.68235 0.68235 0.68235 0.68235
+0.68627 0.68627 0.68627 0.68627
+0.69020 0.69020 0.69020 0.69020
+0.69412 0.69412 0.69412 0.69412
+0.69804 0.69804 0.69804 0.69804
+0.70196 0.70196 0.70196 0.70196
+0.70588 0.70588 0.70588 0.70588
+0.70980 0.70980 0.70980 0.70980
+0.71373 0.71373 0.71373 0.71373
+0.71765 0.71765 0.71765 0.71765
+0.72157 0.72157 0.72157 0.72157
+0.72549 0.72549 0.72549 0.72549
+0.72941 0.72941 0.72941 0.72941
+0.73333 0.73333 0.73333 0.73333
+0.73725 0.73725 0.73725 0.73725
+0.74118 0.74118 0.74118 0.74118
+0.74510 0.74510 0.74510 0.74510
+0.74902 0.74902 0.74902 0.74902
+0.75294 0.75294 0.75294 0.75294
+0.75686 0.75686 0.75686 0.75686
+0.76078 0.76078 0.76078 0.76078
+0.76471 0.76471 0.76471 0.76471
+0.76863 0.76863 0.76863 0.76863
+0.77255 0.77255 0.77255 0.77255
+0.77647 0.77647 0.77647 0.77647
+0.78039 0.78039 0.78039 0.78039
+0.78431 0.78431 0.78431 0.78431
+0.78824 0.78824 0.78824 0.78824
+0.79216 0.79216 0.79216 0.79216
+0.79608 0.79608 0.79608 0.79608
+0.80000 0.80000 0.80000 0.80000
+0.80392 0.80392 0.80392 0.80392
+0.80784 0.80784 0.80784 0.80784
+0.81176 0.81176 0.81176 0.81176
+0.81569 0.81569 0.81569 0.81569
+0.81961 0.81961 0.81961 0.81961
+0.82353 0.82353 0.82353 0.82353
+0.82745 0.82745 0.82745 0.82745
+0.83137 0.83137 0.83137 0.83137
+0.83529 0.83529 0.83529 0.83529
+0.83922 0.83922 0.83922 0.83922
+0.84314 0.84314 0.84314 0.84314
+0.84706 0.84706 0.84706 0.84706
+0.85098 0.85098 0.85098 0.85098
+0.85490 0.85490 0.85490 0.85490
+0.85882 0.85882 0.85882 0.85882
+0.86275 0.86275 0.86275 0.86275
+0.86667 0.86667 0.86667 0.86667
+0.87059 0.87059 0.87059 0.87059
+0.87451 0.87451 0.87451 0.87451
+0.87843 0.87843 0.87843 0.87843
+0.88235 0.88235 0.88235 0.88235
+0.88627 0.88627 0.88627 0.88627
+0.89020 0.89020 0.89020 0.89020
+0.89412 0.89412 0.89412 0.89412
+0.89804 0.89804 0.89804 0.89804
+0.90196 0.90196 0.90196 0.90196
+0.90588 0.90588 0.90588 0.90588
+0.90980 0.90980 0.90980 0.90980
+0.91373 0.91373 0.91373 0.91373
+0.91765 0.91765 0.91765 0.91765
+0.92157 0.92157 0.92157 0.92157
+0.92549 0.92549 0.92549 0.92549
+0.92941 0.92941 0.92941 0.92941
+0.93333 0.93333 0.93333 0.93333
+0.93725 0.93725 0.93725 0.93725
+0.94118 0.94118 0.94118 0.94118
+0.94510 0.94510 0.94510 0.94510
+0.94902 0.94902 0.94902 0.94902
+0.95294 0.95294 0.95294 0.95294
+0.95686 0.95686 0.95686 0.95686
+0.96078 0.96078 0.96078 0.96078
+0.96471 0.96471 0.96471 0.96471
+0.96863 0.96863 0.96863 0.96863
+0.97255 0.97255 0.97255 0.97255
+0.97647 0.97647 0.97647 0.97647
+0.98039 0.98039 0.98039 0.98039
+0.98431 0.98431 0.98431 0.98431
+0.98824 0.98824 0.98824 0.98824
+0.99216 0.99216 0.99216 0.99216
+0.99608 0.99608 0.99608 0.99608
+1.0000 1.0000 1.0000 1.0000
+END_DATA
diff --git a/spectro/linear.sp b/spectro/linear.sp
new file mode 100644
index 0000000..8851462
--- /dev/null
+++ b/spectro/linear.sp
@@ -0,0 +1,23 @@
+SPECT
+
+DESCRIPTOR "Argyll Spectrolino tele adapter compensation filter - linear version"
+ORIGINATOR "Argyll CMS"
+CREATED "Wed Jun 21 17:49:57 2006"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "36"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "380.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "730.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "1.000000"
+
+NUMBER_OF_FIELDS 36
+BEGIN_DATA_FORMAT
+SPEC_380 SPEC_390 SPEC_400 SPEC_410 SPEC_420 SPEC_430 SPEC_440 SPEC_450 SPEC_460 SPEC_470 SPEC_480 SPEC_490 SPEC_500 SPEC_510 SPEC_520 SPEC_530 SPEC_540 SPEC_550 SPEC_560 SPEC_570 SPEC_580 SPEC_590 SPEC_600 SPEC_610 SPEC_620 SPEC_630 SPEC_640 SPEC_650 SPEC_660 SPEC_670 SPEC_680 SPEC_690 SPEC_700 SPEC_710 SPEC_720 SPEC_730
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
+END_DATA
diff --git a/spectro/mongoose.c b/spectro/mongoose.c
new file mode 100644
index 0000000..e1df94a
--- /dev/null
+++ b/spectro/mongoose.c
@@ -0,0 +1,4819 @@
+// Copyright (c) 2004-2012 Sergey Lyubka
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#if defined(_WIN32)
+#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005
+#else
+#ifdef __linux__
+#define _XOPEN_SOURCE 600 // For flockfile() on Linux
+#endif
+#define _LARGEFILE_SOURCE // Enable 64-bit file offsets
+#define __STDC_FORMAT_MACROS // <inttypes.h> wants this for C++
+#define __STDC_LIMIT_MACROS // C++ wants that for INT64_MAX
+#endif
+
+//#ifdef WIN32_LEAN_AND_MEAN
+//#undef WIN32_LEAN_AND_MEAN // Disable WIN32_LEAN_AND_MEAN, if necessary
+//#endif
+
+#define WIN32_LEAN_AND_MEAN // Set WIN32_LEAN_AND_MEAN to prevent winsock2.h problem
+
+#if defined(__SYMBIAN32__)
+#define NO_SSL // SSL is not supported
+#define NO_CGI // CGI is not supported
+#define PATH_MAX FILENAME_MAX
+#endif // __SYMBIAN32__
+
+#ifndef _WIN32_WCE // Some ANSI #includes are not available on Windows CE
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+#endif // !_WIN32_WCE
+
+#include <time.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#if defined(_WIN32) && !defined(__SYMBIAN32__) // Windows specific
+#if _WIN32_WINNT < 0x0400
+# define _WIN32_WINNT 0x0400 // To make it link in VS2005
+#endif
+#ifndef WIN32
+# define WIN32
+#endif
+#include <winsock2.h>
+#include <windows.h>
+#include <ws2tcpip.h>
+
+#ifndef PATH_MAX
+# define PATH_MAX MAX_PATH
+#endif
+
+#ifndef _WIN32_WCE
+# include <process.h>
+# include <direct.h>
+# include <io.h>
+#else // _WIN32_WCE
+# define NO_CGI // WinCE has no pipes
+
+typedef long off_t;
+
+# define errno GetLastError()
+# define strerror(x) _ultoa(x, (char *) _alloca(sizeof(x) *3 ), 10)
+#endif // _WIN32_WCE
+
+#define MAKEUQUAD(lo, hi) ((uint64_t)(((uint32_t)(lo)) | \
+ ((uint64_t)((uint32_t)(hi))) << 32))
+#define RATE_DIFF 10000000 // 100 nsecs
+#define EPOCH_DIFF MAKEUQUAD(0xd53e8000, 0x019db1de)
+#define SYS2UNIX_TIME(lo, hi) \
+ (time_t) ((MAKEUQUAD((lo), (hi)) - EPOCH_DIFF) / RATE_DIFF)
+
+// Visual Studio 6 does not know __func__ or __FUNCTION__
+// The rest of MS compilers use __FUNCTION__, not C99 __func__
+// Also use _strtoui64 on modern M$ compilers
+#if defined(_MSC_VER) && _MSC_VER < 1300
+# define STRX(x) #x
+# define STR(x) STRX(x)
+# define __func__ "line " STR(__LINE__)
+# define strtoull(x, y, z) strtoul(x, y, z)
+# define strtoll(x, y, z) strtol(x, y, z)
+#else
+# define __func__ __FUNCTION__
+# define strtoull(x, y, z) _strtoui64(x, y, z)
+# define strtoll(x, y, z) _strtoi64(x, y, z)
+#endif // _MSC_VER
+
+#define ERRNO GetLastError()
+#define NO_SOCKLEN_T
+#define SSL_LIB "ssleay32.dll"
+#define CRYPTO_LIB "libeay32.dll"
+#define O_NONBLOCK 0
+#if !defined(EWOULDBLOCK)
+# define EWOULDBLOCK WSAEWOULDBLOCK
+#endif // !EWOULDBLOCK
+#define _POSIX_
+#define INT64_FMT "I64d"
+
+#define WINCDECL __cdecl
+#define SHUT_WR 1
+#define snprintf _snprintf
+#define vsnprintf _vsnprintf
+#define mg_sleep(x) Sleep(x)
+
+#define pipe(x) _pipe(x, MG_BUF_LEN, _O_BINARY)
+#ifndef popen
+# define popen(x, y) _popen(x, y)
+# define pclose(x) _pclose(x)
+#endif
+#define close(x) _close(x)
+#define dlsym(x,y) GetProcAddress((HINSTANCE) (x), (y))
+#define RTLD_LAZY 0
+#define fseeko(x, y, z) _lseeki64(_fileno(x), (y), (z))
+#define fdopen(x, y) _fdopen((x), (y))
+#define write(x, y, z) _write((x), (y), (unsigned) z)
+#define read(x, y, z) _read((x), (y), (unsigned) z)
+#define flockfile(x) EnterCriticalSection(&global_log_file_lock)
+#define funlockfile(x) LeaveCriticalSection(&global_log_file_lock)
+#define sleep(x) Sleep((x) * 1000)
+
+#if !defined(fileno)
+#define fileno(x) _fileno(x)
+#endif // !fileno MINGW #defines fileno
+
+typedef HANDLE pthread_mutex_t;
+typedef struct {HANDLE signal, broadcast;} pthread_cond_t;
+typedef DWORD pthread_t;
+#define pid_t HANDLE // MINGW typedefs pid_t to int. Using #define here.
+
+static int pthread_mutex_lock(pthread_mutex_t *);
+static int pthread_mutex_unlock(pthread_mutex_t *);
+static FILE *mg_fopen(const char *path, const char *mode);
+
+#if defined(HAVE_STDINT) || defined(INT64_MAX)
+#include <stdint.h>
+#else
+typedef unsigned int uint32_t;
+typedef unsigned short uint16_t;
+typedef unsigned __int64 uint64_t;
+typedef __int64 int64_t;
+#define INT64_MAX 9223372036854775807
+#endif // HAVE_STDINT
+
+// POSIX dirent interface
+struct dirent {
+ char d_name[PATH_MAX];
+};
+
+typedef struct DIR {
+ HANDLE handle;
+ WIN32_FIND_DATAW info;
+ struct dirent result;
+} DIR;
+
+// Mark required libraries
+#pragma comment(lib, "Ws2_32.lib")
+
+#else // UNIX specific
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/time.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <netdb.h>
+
+#include <pwd.h>
+#include <unistd.h>
+#include <dirent.h>
+#if !defined(NO_SSL_DL) && !defined(NO_SSL)
+#include <dlfcn.h>
+#endif
+#include <pthread.h>
+#if defined(__MACH__)
+#define SSL_LIB "libssl.dylib"
+#define CRYPTO_LIB "libcrypto.dylib"
+#else
+#if !defined(SSL_LIB)
+#define SSL_LIB "libssl.so"
+#endif
+#if !defined(CRYPTO_LIB)
+#define CRYPTO_LIB "libcrypto.so"
+#endif
+#endif
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif // O_BINARY
+#define closesocket(a) close(a)
+#define mg_fopen(x, y) fopen(x, y)
+#define mg_mkdir(x, y) mkdir(x, y)
+#define mg_remove(x) remove(x)
+#define mg_rename(x, y) rename(x, y)
+#define mg_sleep(x) usleep((x) * 1000)
+#define ERRNO errno
+#define INVALID_SOCKET (-1)
+#define INT64_FMT PRId64
+typedef int SOCKET;
+#define WINCDECL
+
+#endif // End of Windows and UNIX specific includes
+
+#include "mongoose.h"
+
+#define MONGOOSE_VERSION "3.3"
+#define PASSWORDS_FILE_NAME ".htpasswd"
+#define CGI_ENVIRONMENT_SIZE 4096
+#define MAX_CGI_ENVIR_VARS 64
+#define MG_BUF_LEN 8192
+#define MAX_REQUEST_SIZE 16384
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
+
+#ifdef _WIN32
+static CRITICAL_SECTION global_log_file_lock;
+static pthread_t pthread_self(void) {
+ return GetCurrentThreadId();
+}
+#endif // _WIN32
+
+#ifdef DEBUG_TRACE
+#undef DEBUG_TRACE
+#define DEBUG_TRACE(x)
+#else
+#if defined(DEBUG)
+#define DEBUG_TRACE(x) do { \
+ flockfile(stdout); \
+ printf("*** %lu.%p.%s.%d: ", \
+ (unsigned long) time(NULL), (void *) pthread_self(), \
+ __func__, __LINE__); \
+ printf x; \
+ putchar('\n'); \
+ fflush(stdout); \
+ funlockfile(stdout); \
+} while (0)
+#else
+#define DEBUG_TRACE(x)
+#endif // DEBUG
+#endif // DEBUG_TRACE
+
+// Darwin prior to 7.0 and Win32 do not have socklen_t
+#ifdef NO_SOCKLEN_T
+typedef int socklen_t;
+#endif // NO_SOCKLEN_T
+#define _DARWIN_UNLIMITED_SELECT
+
+#if !defined(MSG_NOSIGNAL)
+#define MSG_NOSIGNAL 0
+#endif
+
+#if !defined(SOMAXCONN)
+#define SOMAXCONN 100
+#endif
+
+#if !defined(PATH_MAX)
+#define PATH_MAX 4096
+#endif
+
+static const char *http_500_error = "Internal Server Error";
+
+// Snatched from OpenSSL includes. I put the prototypes here to be independent
+// from the OpenSSL source installation. Having this, mongoose + SSL can be
+// built on any system with binary SSL libraries installed.
+typedef struct ssl_st SSL;
+typedef struct ssl_method_st SSL_METHOD;
+typedef struct ssl_ctx_st SSL_CTX;
+
+#define SSL_ERROR_WANT_READ 2
+#define SSL_ERROR_WANT_WRITE 3
+#define SSL_FILETYPE_PEM 1
+#define CRYPTO_LOCK 1
+
+#if defined(NO_SSL_DL)
+extern void SSL_free(SSL *);
+extern int SSL_accept(SSL *);
+extern int SSL_connect(SSL *);
+extern int SSL_read(SSL *, void *, int);
+extern int SSL_write(SSL *, const void *, int);
+extern int SSL_get_error(const SSL *, int);
+extern int SSL_set_fd(SSL *, int);
+extern SSL *SSL_new(SSL_CTX *);
+extern SSL_CTX *SSL_CTX_new(SSL_METHOD *);
+extern SSL_METHOD *SSLv23_server_method(void);
+extern SSL_METHOD *SSLv23_client_method(void);
+extern int SSL_library_init(void);
+extern void SSL_load_error_strings(void);
+extern int SSL_CTX_use_PrivateKey_file(SSL_CTX *, const char *, int);
+extern int SSL_CTX_use_certificate_file(SSL_CTX *, const char *, int);
+extern int SSL_CTX_use_certificate_chain_file(SSL_CTX *, const char *);
+extern void SSL_CTX_set_default_passwd_cb(SSL_CTX *, mg_callback_t);
+extern void SSL_CTX_free(SSL_CTX *);
+extern unsigned long ERR_get_error(void);
+extern char *ERR_error_string(unsigned long, char *);
+extern int CRYPTO_num_locks(void);
+extern void CRYPTO_set_locking_callback(void (*)(int, int, const char *, int));
+extern void CRYPTO_set_id_callback(unsigned long (*)(void));
+#else
+// Dynamically loaded SSL functionality
+struct ssl_func {
+ const char *name; // SSL function name
+ void (*ptr)(void); // Function pointer
+};
+
+#define SSL_free (* (void (*)(SSL *)) ssl_sw[0].ptr)
+#define SSL_accept (* (int (*)(SSL *)) ssl_sw[1].ptr)
+#define SSL_connect (* (int (*)(SSL *)) ssl_sw[2].ptr)
+#define SSL_read (* (int (*)(SSL *, void *, int)) ssl_sw[3].ptr)
+#define SSL_write (* (int (*)(SSL *, const void *,int)) ssl_sw[4].ptr)
+#define SSL_get_error (* (int (*)(SSL *, int)) ssl_sw[5].ptr)
+#define SSL_set_fd (* (int (*)(SSL *, SOCKET)) ssl_sw[6].ptr)
+#define SSL_new (* (SSL * (*)(SSL_CTX *)) ssl_sw[7].ptr)
+#define SSL_CTX_new (* (SSL_CTX * (*)(SSL_METHOD *)) ssl_sw[8].ptr)
+#define SSLv23_server_method (* (SSL_METHOD * (*)(void)) ssl_sw[9].ptr)
+#define SSL_library_init (* (int (*)(void)) ssl_sw[10].ptr)
+#define SSL_CTX_use_PrivateKey_file (* (int (*)(SSL_CTX *, \
+ const char *, int)) ssl_sw[11].ptr)
+#define SSL_CTX_use_certificate_file (* (int (*)(SSL_CTX *, \
+ const char *, int)) ssl_sw[12].ptr)
+#define SSL_CTX_set_default_passwd_cb \
+ (* (void (*)(SSL_CTX *, mg_callback_t)) ssl_sw[13].ptr)
+#define SSL_CTX_free (* (void (*)(SSL_CTX *)) ssl_sw[14].ptr)
+#define SSL_load_error_strings (* (void (*)(void)) ssl_sw[15].ptr)
+#define SSL_CTX_use_certificate_chain_file \
+ (* (int (*)(SSL_CTX *, const char *)) ssl_sw[16].ptr)
+#define SSLv23_client_method (* (SSL_METHOD * (*)(void)) ssl_sw[17].ptr)
+
+#define CRYPTO_num_locks (* (int (*)(void)) crypto_sw[0].ptr)
+#define CRYPTO_set_locking_callback \
+ (* (void (*)(void (*)(int, int, const char *, int))) crypto_sw[1].ptr)
+#define CRYPTO_set_id_callback \
+ (* (void (*)(unsigned long (*)(void))) crypto_sw[2].ptr)
+#define ERR_get_error (* (unsigned long (*)(void)) crypto_sw[3].ptr)
+#define ERR_error_string (* (char * (*)(unsigned long,char *)) crypto_sw[4].ptr)
+
+// set_ssl_option() function updates this array.
+// It loads SSL library dynamically and changes NULLs to the actual addresses
+// of respective functions. The macros above (like SSL_connect()) are really
+// just calling these functions indirectly via the pointer.
+static struct ssl_func ssl_sw[] = {
+ {"SSL_free", NULL},
+ {"SSL_accept", NULL},
+ {"SSL_connect", NULL},
+ {"SSL_read", NULL},
+ {"SSL_write", NULL},
+ {"SSL_get_error", NULL},
+ {"SSL_set_fd", NULL},
+ {"SSL_new", NULL},
+ {"SSL_CTX_new", NULL},
+ {"SSLv23_server_method", NULL},
+ {"SSL_library_init", NULL},
+ {"SSL_CTX_use_PrivateKey_file", NULL},
+ {"SSL_CTX_use_certificate_file",NULL},
+ {"SSL_CTX_set_default_passwd_cb",NULL},
+ {"SSL_CTX_free", NULL},
+ {"SSL_load_error_strings", NULL},
+ {"SSL_CTX_use_certificate_chain_file", NULL},
+ {"SSLv23_client_method", NULL},
+ {NULL, NULL}
+};
+
+// Similar array as ssl_sw. These functions could be located in different lib.
+#if !defined(NO_SSL)
+static struct ssl_func crypto_sw[] = {
+ {"CRYPTO_num_locks", NULL},
+ {"CRYPTO_set_locking_callback", NULL},
+ {"CRYPTO_set_id_callback", NULL},
+ {"ERR_get_error", NULL},
+ {"ERR_error_string", NULL},
+ {NULL, NULL}
+};
+#endif // NO_SSL
+#endif // NO_SSL_DL
+
+static const char *month_names[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+// Unified socket address. For IPv6 support, add IPv6 address structure
+// in the union u.
+union usa {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+#if defined(USE_IPV6)
+ struct sockaddr_in6 sin6;
+#endif
+};
+
+// Describes a string (chunk of memory).
+struct vec {
+ const char *ptr;
+ size_t len;
+};
+
+// Structure used by mg_stat() function. Uses 64 bit file length.
+struct mgstat {
+ int is_directory; // Directory marker
+ int64_t size; // File size
+ time_t mtime; // Modification time
+};
+
+// Describes listening socket, or socket which was accept()-ed by the master
+// thread and queued for future handling by the worker thread.
+struct socket {
+ struct socket *next; // Linkage
+ SOCKET sock; // Listening socket
+ union usa lsa; // Local socket address
+ union usa rsa; // Remote socket address
+ int is_ssl; // Is socket SSL-ed
+};
+
+// NOTE(lsm): this enum shoulds be in sync with the config_options below.
+enum {
+ CGI_EXTENSIONS, CGI_ENVIRONMENT, PUT_DELETE_PASSWORDS_FILE, CGI_INTERPRETER,
+ PROTECT_URI, AUTHENTICATION_DOMAIN, SSI_EXTENSIONS, THROTTLE,
+ ACCESS_LOG_FILE, ENABLE_DIRECTORY_LISTING, ERROR_LOG_FILE,
+ GLOBAL_PASSWORDS_FILE, INDEX_FILES, ENABLE_KEEP_ALIVE, ACCESS_CONTROL_LIST,
+ EXTRA_MIME_TYPES, LISTENING_PORTS, DOCUMENT_ROOT, SSL_CERTIFICATE,
+ NUM_THREADS, RUN_AS_USER, REWRITE, HIDE_FILES,
+ NUM_OPTIONS
+};
+
+static const char *config_options[] = {
+ "C", "cgi_pattern", "**.cgi$|**.pl$|**.php$",
+ "E", "cgi_environment", NULL,
+ "G", "put_delete_passwords_file", NULL,
+ "I", "cgi_interpreter", NULL,
+ "P", "protect_uri", NULL,
+ "R", "authentication_domain", "mydomain.com",
+ "S", "ssi_pattern", "**.shtml$|**.shtm$",
+ "T", "throttle", NULL,
+ "a", "access_log_file", NULL,
+ "d", "enable_directory_listing", "yes",
+ "e", "error_log_file", NULL,
+ "g", "global_passwords_file", NULL,
+ "i", "index_files", "index.html,index.htm,index.cgi,index.shtml,index.php",
+ "k", "enable_keep_alive", "no",
+ "l", "access_control_list", NULL,
+ "m", "extra_mime_types", NULL,
+ "p", "listening_ports", "8080",
+ "r", "document_root", ".",
+ "s", "ssl_certificate", NULL,
+ "t", "num_threads", "20",
+ "u", "run_as_user", NULL,
+ "w", "url_rewrite_patterns", NULL,
+ "x", "hide_files_patterns", NULL,
+ NULL
+};
+#define ENTRIES_PER_CONFIG_OPTION 3
+
+struct mg_context {
+ volatile int stop_flag; // Should we stop event loop
+ SSL_CTX *ssl_ctx; // SSL context
+ SSL_CTX *client_ssl_ctx; // Client SSL context
+ char *config[NUM_OPTIONS]; // Mongoose configuration parameters
+ mg_callback_t user_callback; // User-defined callback function
+ void *user_data; // User-defined data
+
+ struct socket *listening_sockets;
+
+ volatile int num_threads; // Number of threads
+ pthread_mutex_t mutex; // Protects (max|num)_threads
+ pthread_cond_t cond; // Condvar for tracking workers terminations
+
+ struct socket queue[20]; // Accepted sockets
+ volatile int sq_head; // Head of the socket queue
+ volatile int sq_tail; // Tail of the socket queue
+ pthread_cond_t sq_full; // Signaled when socket is produced
+ pthread_cond_t sq_empty; // Signaled when socket is consumed
+};
+
+struct mg_connection {
+ struct mg_request_info request_info;
+ struct mg_context *ctx;
+ SSL *ssl; // SSL descriptor
+ struct socket client; // Connected client
+ time_t birth_time; // Time when request was received
+ int64_t num_bytes_sent; // Total bytes sent to client
+ int64_t content_len; // Content-Length header value
+ int64_t consumed_content; // How many bytes of content have been read
+ char *buf; // Buffer for received data
+ char *path_info; // PATH_INFO part of the URL
+ char *log_message; // Placeholder for the mongoose error log message
+ int must_close; // 1 if connection must be closed
+ int buf_size; // Buffer size
+ int request_len; // Size of the request + headers in a buffer
+ int data_len; // Total size of data in a buffer
+ int status_code; // HTTP reply status code, e.g. 200
+ int throttle; // Throttling, bytes/sec. <= 0 means no throttle
+ time_t last_throttle_time; // Last time throttled data was sent
+ int64_t last_throttle_bytes;// Bytes sent this second
+};
+
+const char **mg_get_valid_option_names(void) {
+ return config_options;
+}
+
+static void *call_user(struct mg_connection *conn, enum mg_event event) {
+ return conn == NULL || conn->ctx == NULL || conn->ctx->user_callback == NULL ?
+ NULL : conn->ctx->user_callback(event, conn);
+}
+
+void *mg_get_user_data(struct mg_connection *conn) {
+ return conn != NULL && conn->ctx != NULL ? conn->ctx->user_data : NULL;
+}
+
+const char *mg_get_log_message(const struct mg_connection *conn) {
+ return conn == NULL ? NULL : conn->log_message;
+}
+
+int mg_get_reply_status_code(const struct mg_connection *conn) {
+ return conn == NULL ? -1 : conn->status_code;
+}
+
+void *mg_get_ssl_context(const struct mg_connection *conn) {
+ return conn == NULL || conn->ctx == NULL ? NULL : conn->ctx->ssl_ctx;
+}
+
+static int get_option_index(const char *name) {
+ int i;
+
+ for (i = 0; config_options[i] != NULL; i += ENTRIES_PER_CONFIG_OPTION) {
+ if (strcmp(config_options[i], name) == 0 ||
+ strcmp(config_options[i + 1], name) == 0) {
+ return i / ENTRIES_PER_CONFIG_OPTION;
+ }
+ }
+ return -1;
+}
+
+const char *mg_get_option(const struct mg_context *ctx, const char *name) {
+ int i;
+ if ((i = get_option_index(name)) == -1) {
+ return NULL;
+ } else if (ctx->config[i] == NULL) {
+ return "";
+ } else {
+ return ctx->config[i];
+ }
+}
+
+static void sockaddr_to_string(char *buf, size_t len,
+ const union usa *usa) {
+ buf[0] = '\0';
+#if defined(USE_IPV6)
+ inet_ntop(usa->sa.sa_family, usa->sa.sa_family == AF_INET ?
+ (void *) &usa->sin.sin_addr :
+ (void *) &usa->sin6.sin6_addr, buf, len);
+#elif defined(_WIN32)
+ // Only Windoze Vista (and newer) have inet_ntop()
+ strncpy(buf, inet_ntoa(usa->sin.sin_addr), len);
+#else
+ inet_ntop(usa->sa.sa_family, (void *) &usa->sin.sin_addr, buf, len);
+#endif
+}
+
+static void cry(struct mg_connection *conn,
+ PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(2, 3);
+
+// Print error message to the opened error log stream.
+static void cry(struct mg_connection *conn, const char *fmt, ...) {
+ char buf[MG_BUF_LEN], src_addr[20];
+ va_list ap;
+ FILE *fp;
+ time_t timestamp;
+
+ va_start(ap, fmt);
+ (void) vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ // Do not lock when getting the callback value, here and below.
+ // I suppose this is fine, since function cannot disappear in the
+ // same way string option can.
+ conn->log_message = buf;
+ if (call_user(conn, MG_EVENT_LOG) == NULL) {
+ fp = conn->ctx == NULL || conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL :
+ mg_fopen(conn->ctx->config[ERROR_LOG_FILE], "a+");
+
+ if (fp != NULL) {
+ flockfile(fp);
+ timestamp = time(NULL);
+
+ sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
+ fprintf(fp, "[%010lu] [error] [client %s] ", (unsigned long) timestamp,
+ src_addr);
+
+ if (conn->request_info.request_method != NULL) {
+ fprintf(fp, "%s %s: ", conn->request_info.request_method,
+ conn->request_info.uri);
+ }
+
+ (void) fprintf(fp, "%s", buf);
+ fputc('\n', fp);
+ funlockfile(fp);
+ if (fp != stderr) {
+ fclose(fp);
+ }
+ }
+ }
+ conn->log_message = NULL;
+}
+
+// Return fake connection structure. Used for logging, if connection
+// is not applicable at the moment of logging.
+static struct mg_connection *fc(struct mg_context *ctx) {
+ static struct mg_connection fake_connection;
+ fake_connection.ctx = ctx;
+ return &fake_connection;
+}
+
+const char *mg_version(void) {
+ return MONGOOSE_VERSION;
+}
+
+const struct mg_request_info *
+mg_get_request_info(const struct mg_connection *conn) {
+ return &conn->request_info;
+}
+
+static void mg_strlcpy(register char *dst, register const char *src, size_t n) {
+ for (; *src != '\0' && n > 1; n--) {
+ *dst++ = *src++;
+ }
+ *dst = '\0';
+}
+
+static int lowercase(const char *s) {
+ return tolower(* (const unsigned char *) s);
+}
+
+static int mg_strncasecmp(const char *s1, const char *s2, size_t len) {
+ int diff = 0;
+
+ if (len > 0)
+ do {
+ diff = lowercase(s1++) - lowercase(s2++);
+ } while (diff == 0 && s1[-1] != '\0' && --len > 0);
+
+ return diff;
+}
+
+static int mg_strcasecmp(const char *s1, const char *s2) {
+ int diff;
+
+ do {
+ diff = lowercase(s1++) - lowercase(s2++);
+ } while (diff == 0 && s1[-1] != '\0');
+
+ return diff;
+}
+
+static char * mg_strndup(const char *ptr, size_t len) {
+ char *p;
+
+ if ((p = (char *) malloc(len + 1)) != NULL) {
+ mg_strlcpy(p, ptr, len + 1);
+ }
+
+ return p;
+}
+
+static char * mg_strdup(const char *str) {
+ return mg_strndup(str, strlen(str));
+}
+
+// Like snprintf(), but never returns negative value, or a value
+// that is larger than a supplied buffer.
+// Thanks to Adam Zeldis to pointing snprintf()-caused vulnerability
+// in his audit report.
+static int mg_vsnprintf(struct mg_connection *conn, char *buf, size_t buflen,
+ const char *fmt, va_list ap) {
+ int n;
+
+ if (buflen == 0)
+ return 0;
+
+ n = vsnprintf(buf, buflen, fmt, ap);
+
+ if (n < 0) {
+ cry(conn, "vsnprintf error");
+ n = 0;
+ } else if (n >= (int) buflen) {
+ cry(conn, "truncating vsnprintf buffer: [%.*s]",
+ n > 200 ? 200 : n, buf);
+ n = (int) buflen - 1;
+ }
+ buf[n] = '\0';
+
+ return n;
+}
+
+static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen,
+ PRINTF_FORMAT_STRING(const char *fmt), ...)
+ PRINTF_ARGS(4, 5);
+
+static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen,
+ const char *fmt, ...) {
+ va_list ap;
+ int n;
+
+ va_start(ap, fmt);
+ n = mg_vsnprintf(conn, buf, buflen, fmt, ap);
+ va_end(ap);
+
+ return n;
+}
+
+// Skip the characters until one of the delimiters characters found.
+// 0-terminate resulting word. Skip the delimiter and following whitespaces if any.
+// Advance pointer to buffer to the next word. Return found 0-terminated word.
+// Delimiters can be quoted with quotechar.
+static char *skip_quoted(char **buf, const char *delimiters,
+ const char *whitespace, char quotechar) {
+ char *p, *begin_word, *end_word, *end_whitespace;
+
+ begin_word = *buf;
+ end_word = begin_word + strcspn(begin_word, delimiters);
+
+ // Check for quotechar
+ if (end_word > begin_word) {
+ p = end_word - 1;
+ while (*p == quotechar) {
+ // If there is anything beyond end_word, copy it
+ if (*end_word == '\0') {
+ *p = '\0';
+ break;
+ } else {
+ size_t end_off = strcspn(end_word + 1, delimiters);
+ memmove (p, end_word, end_off + 1);
+ p += end_off; // p must correspond to end_word - 1
+ end_word += end_off + 1;
+ }
+ }
+ for (p++; p < end_word; p++) {
+ *p = '\0';
+ }
+ }
+
+ if (*end_word == '\0') {
+ *buf = end_word;
+ } else {
+ end_whitespace = end_word + 1 + strspn(end_word + 1, whitespace);
+
+ for (p = end_word; p < end_whitespace; p++) {
+ *p = '\0';
+ }
+
+ *buf = end_whitespace;
+ }
+
+ return begin_word;
+}
+
+// Simplified version of skip_quoted without quote char
+// and whitespace == delimiters
+static char *skip(char **buf, const char *delimiters) {
+ return skip_quoted(buf, delimiters, delimiters, 0);
+}
+
+
+// Return HTTP header value, or NULL if not found.
+static const char *get_header(const struct mg_request_info *ri,
+ const char *name) {
+ int i;
+
+ for (i = 0; i < ri->num_headers; i++)
+ if (!mg_strcasecmp(name, ri->http_headers[i].name))
+ return ri->http_headers[i].value;
+
+ return NULL;
+}
+
+const char *mg_get_header(const struct mg_connection *conn, const char *name) {
+ return get_header(&conn->request_info, name);
+}
+
+// A helper function for traversing a comma separated list of values.
+// It returns a list pointer shifted to the next value, or NULL if the end
+// of the list found.
+// Value is stored in val vector. If value has form "x=y", then eq_val
+// vector is initialized to point to the "y" part, and val vector length
+// is adjusted to point only to "x".
+static const char *next_option(const char *list, struct vec *val,
+ struct vec *eq_val) {
+ if (list == NULL || *list == '\0') {
+ // End of the list
+ list = NULL;
+ } else {
+ val->ptr = list;
+ if ((list = strchr(val->ptr, ',')) != NULL) {
+ // Comma found. Store length and shift the list ptr
+ val->len = list - val->ptr;
+ list++;
+ } else {
+ // This value is the last one
+ list = val->ptr + strlen(val->ptr);
+ val->len = list - val->ptr;
+ }
+
+ if (eq_val != NULL) {
+ // Value has form "x=y", adjust pointers and lengths
+ // so that val points to "x", and eq_val points to "y".
+ eq_val->len = 0;
+ eq_val->ptr = (const char *) memchr(val->ptr, '=', val->len);
+ if (eq_val->ptr != NULL) {
+ eq_val->ptr++; // Skip over '=' character
+ eq_val->len = val->ptr + val->len - eq_val->ptr;
+ val->len = (eq_val->ptr - val->ptr) - 1;
+ }
+ }
+ }
+
+ return list;
+}
+
+static int match_prefix(const char *pattern, int pattern_len, const char *str) {
+ const char *or_str;
+ int i, j, len, res;
+
+ if ((or_str = (const char *) memchr(pattern, '|', pattern_len)) != NULL) {
+ res = match_prefix(pattern, or_str - pattern, str);
+ return res > 0 ? res :
+ match_prefix(or_str + 1, (pattern + pattern_len) - (or_str + 1), str);
+ }
+
+ i = j = 0;
+ res = -1;
+ for (; i < pattern_len; i++, j++) {
+ if (pattern[i] == '?' && str[j] != '\0') {
+ continue;
+ } else if (pattern[i] == '$') {
+ return str[j] == '\0' ? j : -1;
+ } else if (pattern[i] == '*') {
+ i++;
+ if (pattern[i] == '*') {
+ i++;
+ len = (int) strlen(str + j);
+ } else {
+ len = (int) strcspn(str + j, "/");
+ }
+ if (i == pattern_len) {
+ return j + len;
+ }
+ do {
+ res = match_prefix(pattern + i, pattern_len - i, str + j + len);
+ } while (res == -1 && len-- > 0);
+ return res == -1 ? -1 : j + res + len;
+ } else if (pattern[i] != str[j]) {
+ return -1;
+ }
+ }
+ return j;
+}
+
+// HTTP 1.1 assumes keep alive if "Connection:" header is not set
+// This function must tolerate situations when connection info is not
+// set up, for example if request parsing failed.
+static int should_keep_alive(const struct mg_connection *conn) {
+ const char *http_version = conn->request_info.http_version;
+ const char *header = mg_get_header(conn, "Connection");
+ if (conn->must_close ||
+ conn->status_code == 401 ||
+ mg_strcasecmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes") != 0 ||
+ (header != NULL && mg_strcasecmp(header, "keep-alive") != 0) ||
+ (header == NULL && http_version && strcmp(http_version, "1.1"))) {
+ return 0;
+ }
+ return 1;
+}
+
+static const char *suggest_connection_header(const struct mg_connection *conn) {
+ return should_keep_alive(conn) ? "keep-alive" : "close";
+}
+
+static void send_http_error(struct mg_connection *, int, const char *,
+ PRINTF_FORMAT_STRING(const char *fmt), ...)
+ PRINTF_ARGS(4, 5);
+
+
+static void send_http_error(struct mg_connection *conn, int status,
+ const char *reason, const char *fmt, ...) {
+ char buf[MG_BUF_LEN];
+ va_list ap;
+ int len;
+
+ conn->status_code = status;
+ if (call_user(conn, MG_HTTP_ERROR) == NULL) {
+ buf[0] = '\0';
+ len = 0;
+
+ // Errors 1xx, 204 and 304 MUST NOT send a body
+ if (status > 199 && status != 204 && status != 304) {
+ len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason);
+ buf[len++] = '\n';
+
+ va_start(ap, fmt);
+ len += mg_vsnprintf(conn, buf + len, sizeof(buf) - len, fmt, ap);
+ va_end(ap);
+ }
+ DEBUG_TRACE(("[%s]", buf));
+
+ mg_printf(conn, "HTTP/1.1 %d %s\r\n"
+ "Content-Length: %d\r\n"
+ "Connection: %s\r\n\r\n", status, reason, len,
+ suggest_connection_header(conn));
+ conn->num_bytes_sent += mg_printf(conn, "%s", buf);
+ }
+}
+
+#if defined(_WIN32) && !defined(__SYMBIAN32__)
+static int pthread_mutex_init(pthread_mutex_t *mutex, void *unused) {
+ unused = NULL;
+ *mutex = CreateMutex(NULL, FALSE, NULL);
+ return *mutex == NULL ? -1 : 0;
+}
+
+static int pthread_mutex_destroy(pthread_mutex_t *mutex) {
+ return CloseHandle(*mutex) == 0 ? -1 : 0;
+}
+
+static int pthread_mutex_lock(pthread_mutex_t *mutex) {
+ return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1;
+}
+
+static int pthread_mutex_unlock(pthread_mutex_t *mutex) {
+ return ReleaseMutex(*mutex) == 0 ? -1 : 0;
+}
+
+static int pthread_cond_init(pthread_cond_t *cv, const void *unused) {
+ unused = NULL;
+ cv->signal = CreateEvent(NULL, FALSE, FALSE, NULL);
+ cv->broadcast = CreateEvent(NULL, TRUE, FALSE, NULL);
+ return cv->signal != NULL && cv->broadcast != NULL ? 0 : -1;
+}
+
+static int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex) {
+ HANDLE handles[] = {cv->signal, cv->broadcast};
+ ReleaseMutex(*mutex);
+ WaitForMultipleObjects(2, handles, FALSE, INFINITE);
+ return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1;
+}
+
+static int pthread_cond_signal(pthread_cond_t *cv) {
+ return SetEvent(cv->signal) == 0 ? -1 : 0;
+}
+
+static int pthread_cond_broadcast(pthread_cond_t *cv) {
+ // Implementation with PulseEvent() has race condition, see
+ // http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
+ return PulseEvent(cv->broadcast) == 0 ? -1 : 0;
+}
+
+static int pthread_cond_destroy(pthread_cond_t *cv) {
+ return CloseHandle(cv->signal) && CloseHandle(cv->broadcast) ? 0 : -1;
+}
+
+// For Windows, change all slashes to backslashes in path names.
+static void change_slashes_to_backslashes(char *path) {
+ int i;
+
+ for (i = 0; path[i] != '\0'; i++) {
+ if (path[i] == '/')
+ path[i] = '\\';
+ // i > 0 check is to preserve UNC paths, like \\server\file.txt
+ if (path[i] == '\\' && i > 0)
+ while (path[i + 1] == '\\' || path[i + 1] == '/')
+ (void) memmove(path + i + 1,
+ path + i + 2, strlen(path + i + 1));
+ }
+}
+
+// Encode 'path' which is assumed UTF-8 string, into UNICODE string.
+// wbuf and wbuf_len is a target buffer and its length.
+static void to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len) {
+ char buf[PATH_MAX], buf2[PATH_MAX], *p;
+
+ mg_strlcpy(buf, path, sizeof(buf));
+ change_slashes_to_backslashes(buf);
+
+ // Point p to the end of the file name
+ p = buf + strlen(buf) - 1;
+
+ // Trim trailing backslash character
+ while (p > buf && *p == '\\' && p[-1] != ':') {
+ *p-- = '\0';
+ }
+
+ // Protect from CGI code disclosure.
+ // This is very nasty hole. Windows happily opens files with
+ // some garbage in the end of file name. So fopen("a.cgi ", "r")
+ // actually opens "a.cgi", and does not return an error!
+ if (*p == 0x20 || // No space at the end
+ (*p == 0x2e && p > buf) || // No '.' but allow '.' as full path
+ *p == 0x2b || // No '+'
+ (*p & ~0x7f)) { // And generally no non-ASCII chars
+ (void) fprintf(stderr, "Rejecting suspicious path: [%s]", buf);
+ wbuf[0] = L'\0';
+ } else {
+ // Convert to Unicode and back. If doubly-converted string does not
+ // match the original, something is fishy, reject.
+ memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
+ MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
+ WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
+ NULL, NULL);
+ if (strcmp(buf, buf2) != 0) {
+ wbuf[0] = L'\0';
+ }
+ }
+}
+
+#if defined(_WIN32_WCE)
+static time_t time(time_t *ptime) {
+ time_t t;
+ SYSTEMTIME st;
+ FILETIME ft;
+
+ GetSystemTime(&st);
+ SystemTimeToFileTime(&st, &ft);
+ t = SYS2UNIX_TIME(ft.dwLowDateTime, ft.dwHighDateTime);
+
+ if (ptime != NULL) {
+ *ptime = t;
+ }
+
+ return t;
+}
+
+static struct tm *localtime(const time_t *ptime, struct tm *ptm) {
+ int64_t t = ((int64_t) *ptime) * RATE_DIFF + EPOCH_DIFF;
+ FILETIME ft, lft;
+ SYSTEMTIME st;
+ TIME_ZONE_INFORMATION tzinfo;
+
+ if (ptm == NULL) {
+ return NULL;
+ }
+
+ * (int64_t *) &ft = t;
+ FileTimeToLocalFileTime(&ft, &lft);
+ FileTimeToSystemTime(&lft, &st);
+ ptm->tm_year = st.wYear - 1900;
+ ptm->tm_mon = st.wMonth - 1;
+ ptm->tm_wday = st.wDayOfWeek;
+ ptm->tm_mday = st.wDay;
+ ptm->tm_hour = st.wHour;
+ ptm->tm_min = st.wMinute;
+ ptm->tm_sec = st.wSecond;
+ ptm->tm_yday = 0; // hope nobody uses this
+ ptm->tm_isdst =
+ GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_DAYLIGHT ? 1 : 0;
+
+ return ptm;
+}
+
+static struct tm *gmtime(const time_t *ptime, struct tm *ptm) {
+ // FIXME(lsm): fix this.
+ return localtime(ptime, ptm);
+}
+
+static size_t strftime(char *dst, size_t dst_size, const char *fmt,
+ const struct tm *tm) {
+ (void) snprintf(dst, dst_size, "implement strftime() for WinCE");
+ return 0;
+}
+#endif
+
+static int mg_rename(const char* oldname, const char* newname) {
+ wchar_t woldbuf[PATH_MAX];
+ wchar_t wnewbuf[PATH_MAX];
+
+ to_unicode(oldname, woldbuf, ARRAY_SIZE(woldbuf));
+ to_unicode(newname, wnewbuf, ARRAY_SIZE(wnewbuf));
+
+ return MoveFileW(woldbuf, wnewbuf) ? 0 : -1;
+}
+
+
+static FILE *mg_fopen(const char *path, const char *mode) {
+ wchar_t wbuf[PATH_MAX], wmode[20];
+
+ to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
+ MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, ARRAY_SIZE(wmode));
+
+ return _wfopen(wbuf, wmode);
+}
+
+static int mg_stat(const char *path, struct mgstat *stp) {
+ int ok = -1; // Error
+ wchar_t wbuf[PATH_MAX];
+ WIN32_FILE_ATTRIBUTE_DATA info;
+
+ to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
+
+ if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0) {
+ stp->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh);
+ stp->mtime = SYS2UNIX_TIME(info.ftLastWriteTime.dwLowDateTime,
+ info.ftLastWriteTime.dwHighDateTime);
+ stp->is_directory =
+ info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
+ ok = 0; // Success
+ }
+
+ return ok;
+}
+
+static int mg_remove(const char *path) {
+ wchar_t wbuf[PATH_MAX];
+ to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
+ return DeleteFileW(wbuf) ? 0 : -1;
+}
+
+static int mg_mkdir(const char *path, int mode) {
+ char buf[PATH_MAX];
+ wchar_t wbuf[PATH_MAX];
+
+ mode = 0; // Unused
+ mg_strlcpy(buf, path, sizeof(buf));
+ change_slashes_to_backslashes(buf);
+
+ (void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
+
+ return CreateDirectoryW(wbuf, NULL) ? 0 : -1;
+}
+
+// Implementation of POSIX opendir/closedir/readdir for Windows.
+static DIR * opendir(const char *name) {
+ DIR *dir = NULL;
+ wchar_t wpath[PATH_MAX];
+ DWORD attrs;
+
+ if (name == NULL) {
+ SetLastError(ERROR_BAD_ARGUMENTS);
+ } else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ } else {
+ to_unicode(name, wpath, ARRAY_SIZE(wpath));
+ attrs = GetFileAttributesW(wpath);
+ if (attrs != 0xFFFFFFFF &&
+ ((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) {
+ (void) wcscat(wpath, L"\\*");
+ dir->handle = FindFirstFileW(wpath, &dir->info);
+ dir->result.d_name[0] = '\0';
+ } else {
+ free(dir);
+ dir = NULL;
+ }
+ }
+
+ return dir;
+}
+
+static int closedir(DIR *dir) {
+ int result = 0;
+
+ if (dir != NULL) {
+ if (dir->handle != INVALID_HANDLE_VALUE)
+ result = FindClose(dir->handle) ? 0 : -1;
+
+ free(dir);
+ } else {
+ result = -1;
+ SetLastError(ERROR_BAD_ARGUMENTS);
+ }
+
+ return result;
+}
+
+static struct dirent *readdir(DIR *dir) {
+ struct dirent *result = 0;
+
+ if (dir) {
+ if (dir->handle != INVALID_HANDLE_VALUE) {
+ result = &dir->result;
+ (void) WideCharToMultiByte(CP_UTF8, 0,
+ dir->info.cFileName, -1, result->d_name,
+ sizeof(result->d_name), NULL, NULL);
+
+ if (!FindNextFileW(dir->handle, &dir->info)) {
+ (void) FindClose(dir->handle);
+ dir->handle = INVALID_HANDLE_VALUE;
+ }
+
+ } else {
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ }
+ } else {
+ SetLastError(ERROR_BAD_ARGUMENTS);
+ }
+
+ return result;
+}
+
+#define set_close_on_exec(fd) // No FD_CLOEXEC on Windows
+
+int mg_start_thread(mg_thread_func_t f, void *p) {
+ return _beginthread((void (__cdecl *)(void *)) f, 0, p) == -1L ? -1 : 0;
+}
+
+static HANDLE dlopen(const char *dll_name, int flags) {
+ wchar_t wbuf[PATH_MAX];
+ flags = 0; // Unused
+ to_unicode(dll_name, wbuf, ARRAY_SIZE(wbuf));
+ return LoadLibraryW(wbuf);
+}
+
+#if !defined(NO_CGI)
+#define SIGKILL 0
+static int kill(pid_t pid, int sig_num) {
+ (void) TerminateProcess(pid, sig_num);
+ (void) CloseHandle(pid);
+ return 0;
+}
+
+static pid_t spawn_process(struct mg_connection *conn, const char *prog,
+ char *envblk, char *envp[], int fd_stdin,
+ int fd_stdout, const char *dir) {
+ HANDLE me;
+ char *p, *interp, full_interp[PATH_MAX], cmdline[PATH_MAX], buf[PATH_MAX];
+ FILE *fp;
+ STARTUPINFOA si = { sizeof(si) };
+ PROCESS_INFORMATION pi = { 0 };
+
+ envp = NULL; // Unused
+
+ // TODO(lsm): redirect CGI errors to the error log file
+ si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_HIDE;
+
+ me = GetCurrentProcess();
+ DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdin), me,
+ &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdout), me,
+ &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
+
+ // If CGI file is a script, try to read the interpreter line
+ interp = conn->ctx->config[CGI_INTERPRETER];
+ if (interp == NULL) {
+ buf[0] = buf[2] = '\0';
+
+ // Read the first line of the script into the buffer
+ snprintf(cmdline, sizeof(cmdline), "%s%c%s", dir, '/', prog);
+ if ((fp = mg_fopen(cmdline, "r")) != NULL) {
+ fgets(buf, sizeof(buf), fp);
+ fclose(fp);
+ buf[sizeof(buf) - 1] = '\0';
+ }
+
+ if (buf[0] == '#' && buf[1] == '!') {
+ // Trim whitespace in interpreter name
+ for (p = buf + 2; *p != '\0' && isspace(* (unsigned char *) p); )
+ p++;
+ *p = '\0';
+ }
+ interp = buf + 2;
+ }
+
+ if (interp[0] != '\0') {
+ GetFullPathName(interp, sizeof(full_interp), full_interp, NULL);
+ interp = full_interp;
+ }
+
+ mg_snprintf(conn, cmdline, sizeof(cmdline), "%s%s%s",
+ interp, interp[0] == '\0' ? "" : " ", prog);
+
+ DEBUG_TRACE(("Running [%s]", cmdline));
+ if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
+ CREATE_NEW_PROCESS_GROUP, envblk, dir, &si, &pi) == 0) {
+ cry(conn, "%s: CreateProcess(%s): %d",
+ __func__, cmdline, ERRNO);
+ pi.hProcess = (pid_t) -1;
+ }
+
+ // Always close these to prevent handle leakage.
+ (void) close(fd_stdin);
+ (void) close(fd_stdout);
+
+ (void) CloseHandle(si.hStdOutput);
+ (void) CloseHandle(si.hStdInput);
+ (void) CloseHandle(pi.hThread);
+
+ return (pid_t) pi.hProcess;
+}
+#endif // !NO_CGI
+
+static int set_non_blocking_mode(SOCKET sock) {
+ unsigned long on = 1;
+ return ioctlsocket(sock, FIONBIO, &on);
+}
+
+#else
+static int mg_stat(const char *path, struct mgstat *stp) {
+ struct stat st;
+ int ok;
+
+ if (stat(path, &st) == 0) {
+ ok = 0;
+ stp->size = st.st_size;
+ stp->mtime = st.st_mtime;
+ stp->is_directory = S_ISDIR(st.st_mode);
+ } else {
+ ok = -1;
+ }
+
+ return ok;
+}
+
+static void set_close_on_exec(int fd) {
+ (void) fcntl(fd, F_SETFD, FD_CLOEXEC);
+}
+
+int mg_start_thread(mg_thread_func_t func, void *param) {
+ pthread_t thread_id;
+ pthread_attr_t attr;
+
+ (void) pthread_attr_init(&attr);
+ (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ // TODO(lsm): figure out why mongoose dies on Linux if next line is enabled
+ // (void) pthread_attr_setstacksize(&attr, sizeof(struct mg_connection) * 5);
+
+ return pthread_create(&thread_id, &attr, func, param);
+}
+
+#ifndef NO_CGI
+static pid_t spawn_process(struct mg_connection *conn, const char *prog,
+ char *envblk, char *envp[], int fd_stdin,
+ int fd_stdout, const char *dir) {
+ pid_t pid;
+ const char *interp;
+
+ envblk = NULL; // Unused
+
+ if ((pid = fork()) == -1) {
+ // Parent
+ send_http_error(conn, 500, http_500_error, "fork(): %s", strerror(ERRNO));
+ } else if (pid == 0) {
+ // Child
+ if (chdir(dir) != 0) {
+ cry(conn, "%s: chdir(%s): %s", __func__, dir, strerror(ERRNO));
+ } else if (dup2(fd_stdin, 0) == -1) {
+ cry(conn, "%s: dup2(%d, 0): %s", __func__, fd_stdin, strerror(ERRNO));
+ } else if (dup2(fd_stdout, 1) == -1) {
+ cry(conn, "%s: dup2(%d, 1): %s", __func__, fd_stdout, strerror(ERRNO));
+ } else {
+ (void) dup2(fd_stdout, 2);
+ (void) close(fd_stdin);
+ (void) close(fd_stdout);
+
+ interp = conn->ctx->config[CGI_INTERPRETER];
+ if (interp == NULL) {
+ (void) execle(prog, prog, NULL, envp);
+ cry(conn, "%s: execle(%s): %s", __func__, prog, strerror(ERRNO));
+ } else {
+ (void) execle(interp, interp, prog, NULL, envp);
+ cry(conn, "%s: execle(%s %s): %s", __func__, interp, prog,
+ strerror(ERRNO));
+ }
+ }
+ exit(EXIT_FAILURE);
+ }
+
+ // Parent. Close stdio descriptors
+ (void) close(fd_stdin);
+ (void) close(fd_stdout);
+
+ return pid;
+}
+#endif // !NO_CGI
+
+static int set_non_blocking_mode(SOCKET sock) {
+ int flags;
+
+ flags = fcntl(sock, F_GETFL, 0);
+ (void) fcntl(sock, F_SETFL, flags | O_NONBLOCK);
+
+ return 0;
+}
+#endif // _WIN32
+
+// Write data to the IO channel - opened file descriptor, socket or SSL
+// descriptor. Return number of bytes written.
+static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
+ int64_t len) {
+ int64_t sent;
+ int n, k;
+
+ sent = 0;
+ while (sent < len) {
+
+ // How many bytes we send in this iteration
+ k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);
+
+ if (ssl != NULL) {
+ n = SSL_write(ssl, buf + sent, k);
+ } else if (fp != NULL) {
+ n = (int) fwrite(buf + sent, 1, (size_t) k, fp);
+ if (ferror(fp))
+ n = -1;
+ } else {
+ n = send(sock, buf + sent, (size_t) k, MSG_NOSIGNAL);
+ }
+
+ if (n < 0)
+ break;
+
+ sent += n;
+ }
+
+ return sent;
+}
+
+// This function is needed to prevent Mongoose to be stuck in a blocking
+// socket read when user requested exit. To do that, we sleep in select
+// with a timeout, and when returned, check the context for the stop flag.
+// If it is set, we return 0, and this means that we must not continue
+// reading, must give up and close the connection and exit serving thread.
+static int wait_until_socket_is_readable(struct mg_connection *conn) {
+ int result;
+ struct timeval tv;
+ fd_set set;
+
+ do {
+ tv.tv_sec = 0;
+ tv.tv_usec = 300 * 1000;
+ FD_ZERO(&set);
+ FD_SET(conn->client.sock, &set);
+ result = select(conn->client.sock + 1, &set, NULL, NULL, &tv);
+ } while ((result == 0 || (result < 0 && ERRNO == EINTR)) &&
+ conn->ctx->stop_flag == 0);
+
+ return conn->ctx->stop_flag || result < 0 ? 0 : 1;
+}
+
+// Read from IO channel - opened file descriptor, socket, or SSL descriptor.
+// Return negative value on error, or number of bytes read on success.
+static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) {
+ int nread;
+
+ if (fp != NULL) {
+ // Use read() instead of fread(), because if we're reading from the CGI
+ // pipe, fread() may block until IO buffer is filled up. We cannot afford
+ // to block and must pass all read bytes immediately to the client.
+ nread = read(fileno(fp), buf, (size_t) len);
+ } else if (!wait_until_socket_is_readable(conn)) {
+ nread = -1;
+ } else if (conn->ssl != NULL) {
+ nread = SSL_read(conn->ssl, buf, len);
+ } else {
+ nread = recv(conn->client.sock, buf, (size_t) len, 0);
+ }
+
+ return conn->ctx->stop_flag ? -1 : nread;
+}
+
+int mg_read(struct mg_connection *conn, void *buf, size_t len) {
+ int n, buffered_len, nread;
+ const char *body;
+
+ nread = 0;
+ if (conn->consumed_content < conn->content_len) {
+ // Adjust number of bytes to read.
+ int64_t to_read = conn->content_len - conn->consumed_content;
+ if (to_read < (int64_t) len) {
+ len = (size_t) to_read;
+ }
+
+ // Return buffered data
+ body = conn->buf + conn->request_len + conn->consumed_content;
+ buffered_len = &conn->buf[conn->data_len] - body;
+ if (buffered_len > 0) {
+ if (len < (size_t) buffered_len) {
+ buffered_len = (int) len;
+ }
+ memcpy(buf, body, (size_t) buffered_len);
+ len -= buffered_len;
+ conn->consumed_content += buffered_len;
+ nread += buffered_len;
+ buf = (char *) buf + buffered_len;
+ }
+
+ // We have returned all buffered data. Read new data from the remote socket.
+ while (len > 0) {
+ n = pull(NULL, conn, (char *) buf, (int) len);
+ if (n < 0) {
+ nread = n; // Propagate the error
+ break;
+ } else if (n == 0) {
+ break; // No more data to read
+ } else {
+ buf = (char *) buf + n;
+ conn->consumed_content += n;
+ nread += n;
+ len -= n;
+ }
+ }
+ }
+ return nread;
+}
+
+int mg_write(struct mg_connection *conn, const void *buf, size_t len) {
+ time_t now;
+ int64_t n, total, allowed;
+
+ if (conn->throttle > 0) {
+ if ((now = time(NULL)) != conn->last_throttle_time) {
+ conn->last_throttle_time = now;
+ conn->last_throttle_bytes = 0;
+ }
+ allowed = conn->throttle - conn->last_throttle_bytes;
+ if (allowed > (int64_t) len) {
+ allowed = len;
+ }
+ if ((total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
+ (int64_t) allowed)) == allowed) {
+ buf = (char *) buf + total;
+ conn->last_throttle_bytes += total;
+ while (total < (int64_t) len && conn->ctx->stop_flag == 0) {
+ allowed = conn->throttle > (int64_t) len - total ?
+ len - total : conn->throttle;
+ if ((n = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
+ (int64_t) allowed)) != allowed) {
+ break;
+ }
+ sleep(1);
+ conn->last_throttle_bytes = allowed;
+ conn->last_throttle_time = time(NULL);
+ buf = (char *) buf + n;
+ total += n;
+ }
+ }
+ } else {
+ total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
+ (int64_t) len);
+ }
+ return (int) total;
+}
+
+int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
+ char mem[MG_BUF_LEN], *buf = mem;
+ int len;
+ va_list ap;
+
+ // Print in a local buffer first, hoping that it is large enough to
+ // hold the whole message
+ va_start(ap, fmt);
+ len = vsnprintf(mem, sizeof(mem), fmt, ap);
+ va_end(ap);
+
+ if (len == 0) {
+ // Do nothing. mg_printf(conn, "%s", "") was called.
+ } else if (len < 0) {
+ // vsnprintf() error, give up
+ len = -1;
+ cry(conn, "%s(%s, ...): vsnprintf() error", __func__, fmt);
+ } else if (len > (int) sizeof(mem) && (buf = (char *) malloc(len + 1)) != NULL) {
+ // Local buffer is not large enough, allocate big buffer on heap
+ va_start(ap, fmt);
+ vsnprintf(buf, len + 1, fmt, ap);
+ va_end(ap);
+ len = mg_write(conn, buf, (size_t) len);
+ free(buf);
+ } else if (len > (int) sizeof(mem)) {
+ // Failed to allocate large enough buffer, give up
+ cry(conn, "%s(%s, ...): Can't allocate %d bytes, not printing anything",
+ __func__, fmt, len);
+ len = -1;
+ } else {
+ // Copy to the local buffer succeeded
+ len = mg_write(conn, buf, (size_t) len);
+ }
+
+ return len;
+}
+
+// URL-decode input buffer into destination buffer.
+// 0-terminate the destination buffer. Return the length of decoded data.
+// form-url-encoded data differs from URI encoding in a way that it
+// uses '+' as character for space, see RFC 1866 section 8.2.1
+// http://ftp.ics.uci.edu/pub/ietf/html/rfc1866.txt
+static size_t url_decode(const char *src, size_t src_len, char *dst,
+ size_t dst_len, int is_form_url_encoded) {
+ size_t i, j;
+ int a, b;
+#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
+
+ for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
+ if (src[i] == '%' &&
+ isxdigit(* (const unsigned char *) (src + i + 1)) &&
+ isxdigit(* (const unsigned char *) (src + i + 2))) {
+ a = tolower(* (const unsigned char *) (src + i + 1));
+ b = tolower(* (const unsigned char *) (src + i + 2));
+ dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
+ i += 2;
+ } else if (is_form_url_encoded && src[i] == '+') {
+ dst[j] = ' ';
+ } else {
+ dst[j] = src[i];
+ }
+ }
+
+ dst[j] = '\0'; // Null-terminate the destination
+
+ return j;
+}
+
+// Scan given buffer and fetch the value of the given variable.
+// It can be specified in query string, or in the POST data.
+// Return -1 if the variable not found, or length of the URL-decoded value
+// stored in dst. The dst buffer is guaranteed to be NUL-terminated if it
+// is not NULL or zero-length. If dst is NULL or zero-length, then
+// -2 is returned.
+int mg_get_var(const char *buf, size_t buf_len, const char *name,
+ char *dst, size_t dst_len) {
+ const char *p, *e, *s;
+ size_t name_len;
+ int len;
+
+ if (dst == NULL || dst_len == 0) {
+ len = -2;
+ } else if (buf == NULL || name == NULL || buf_len == 0) {
+ len = -1;
+ dst[0] = '\0';
+ } else {
+ name_len = strlen(name);
+ e = buf + buf_len;
+ len = -1;
+ dst[0] = '\0';
+
+ // buf is "var1=val1&var2=val2...". Find variable first
+ for (p = buf; p + name_len < e; p++) {
+ if ((p == buf || p[-1] == '&') && p[name_len] == '=' &&
+ !mg_strncasecmp(name, p, name_len)) {
+
+ // Point p to variable value
+ p += name_len + 1;
+
+ // Point s to the end of the value
+ s = (const char *) memchr(p, '&', (size_t)(e - p));
+ if (s == NULL) {
+ s = e;
+ }
+ assert(s >= p);
+
+ // Decode variable into destination buffer
+ if ((size_t) (s - p) < dst_len) {
+ len = (int) url_decode(p, (size_t)(s - p), dst, dst_len, 1);
+ }
+ break;
+ }
+ }
+ }
+
+ return len;
+}
+
+int mg_get_cookie(const struct mg_connection *conn, const char *cookie_name,
+ char *dst, size_t dst_size) {
+ const char *s, *p, *end;
+ int name_len, len = -1;
+
+ dst[0] = '\0';
+ if ((s = mg_get_header(conn, "Cookie")) == NULL) {
+ return -1;
+ }
+
+ name_len = (int) strlen(cookie_name);
+ end = s + strlen(s);
+
+ for (; (s = strstr(s, cookie_name)) != NULL; s += name_len)
+ if (s[name_len] == '=') {
+ s += name_len + 1;
+ if ((p = strchr(s, ' ')) == NULL)
+ p = end;
+ if (p[-1] == ';')
+ p--;
+ if (*s == '"' && p[-1] == '"' && p > s + 1) {
+ s++;
+ p--;
+ }
+ if ((size_t) (p - s) < dst_size) {
+ len = p - s;
+ mg_strlcpy(dst, s, (size_t) len + 1);
+ }
+ break;
+ }
+
+ return len;
+}
+
+static int convert_uri_to_file_name(struct mg_connection *conn, char *buf,
+ size_t buf_len, struct mgstat *st) {
+ struct vec a, b;
+ const char *rewrite, *uri = conn->request_info.uri;
+ char *p;
+ int match_len, stat_result;
+
+ buf_len--; // This is because memmove() for PATH_INFO may shift part
+ // of the path one byte on the right.
+ mg_snprintf(conn, buf, buf_len, "%s%s", conn->ctx->config[DOCUMENT_ROOT],
+ uri);
+
+ rewrite = conn->ctx->config[REWRITE];
+ while ((rewrite = next_option(rewrite, &a, &b)) != NULL) {
+ if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) {
+ mg_snprintf(conn, buf, buf_len, "%.*s%s", (int) b.len, b.ptr,
+ uri + match_len);
+ break;
+ }
+ }
+
+ if ((stat_result = mg_stat(buf, st)) != 0) {
+ // Support PATH_INFO for CGI scripts.
+ for (p = buf + strlen(buf); p > buf + 1; p--) {
+ if (*p == '/') {
+ *p = '\0';
+ if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
+ strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0 &&
+ (stat_result = mg_stat(buf, st)) == 0) {
+ // Shift PATH_INFO block one character right, e.g.
+ // "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
+ // conn->path_info is pointing to the local variable "path" declared
+ // in handle_request(), so PATH_INFO is not valid after
+ // handle_request returns.
+ conn->path_info = p + 1;
+ memmove(p + 2, p + 1, strlen(p + 1) + 1); // +1 is for trailing \0
+ p[1] = '/';
+ break;
+ } else {
+ *p = '/';
+ stat_result = -1;
+ }
+ }
+ }
+ }
+
+ return stat_result;
+}
+
+static int sslize(struct mg_connection *conn, SSL_CTX *s, int (*func)(SSL *)) {
+ return (conn->ssl = SSL_new(s)) != NULL &&
+ SSL_set_fd(conn->ssl, conn->client.sock) == 1 &&
+ func(conn->ssl) == 1;
+}
+
+// Check whether full request is buffered. Return:
+// -1 if request is malformed
+// 0 if request is not yet fully buffered
+// >0 actual request length, including last \r\n\r\n
+static int get_request_len(const char *buf, int buflen) {
+ const char *s, *e;
+ int len = 0;
+
+ for (s = buf, e = s + buflen - 1; len <= 0 && s < e; s++)
+ // Control characters are not allowed but >=128 is.
+ if (!isprint(* (const unsigned char *) s) && *s != '\r' &&
+ *s != '\n' && * (const unsigned char *) s < 128) {
+ len = -1;
+ break; // [i_a] abort scan as soon as one malformed character is found; don't let subsequent \r\n\r\n win us over anyhow
+ } else if (s[0] == '\n' && s[1] == '\n') {
+ len = (int) (s - buf) + 2;
+ } else if (s[0] == '\n' && &s[1] < e &&
+ s[1] == '\r' && s[2] == '\n') {
+ len = (int) (s - buf) + 3;
+ }
+
+ return len;
+}
+
+// Convert month to the month number. Return -1 on error, or month number
+static int get_month_index(const char *s) {
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(month_names); i++)
+ if (!strcmp(s, month_names[i]))
+ return (int) i;
+
+ return -1;
+}
+
+static int num_leap_years(int year) {
+ return year / 4 - year / 100 + year / 400;
+}
+
+// Parse UTC date-time string, and return the corresponding time_t value.
+static time_t parse_date_string(const char *datetime) {
+ static const unsigned short days_before_month[] = {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
+ };
+ char month_str[32];
+ int second, minute, hour, day, month, year, leap_days, days;
+ time_t result = (time_t) 0;
+
+ if (((sscanf(datetime, "%d/%3s/%d %d:%d:%d",
+ &day, month_str, &year, &hour, &minute, &second) == 6) ||
+ (sscanf(datetime, "%d %3s %d %d:%d:%d",
+ &day, month_str, &year, &hour, &minute, &second) == 6) ||
+ (sscanf(datetime, "%*3s, %d %3s %d %d:%d:%d",
+ &day, month_str, &year, &hour, &minute, &second) == 6) ||
+ (sscanf(datetime, "%d-%3s-%d %d:%d:%d",
+ &day, month_str, &year, &hour, &minute, &second) == 6)) &&
+ year > 1970 &&
+ (month = get_month_index(month_str)) != -1) {
+ leap_days = num_leap_years(year) - num_leap_years(1970);
+ year -= 1970;
+ days = year * 365 + days_before_month[month] + (day - 1) + leap_days;
+ result = days * 24 * 3600 + hour * 3600 + minute * 60 + second;
+ }
+
+ return result;
+}
+
+// Protect against directory disclosure attack by removing '..',
+// excessive '/' and '\' characters
+static void remove_double_dots_and_double_slashes(char *s) {
+ char *p = s;
+
+ while (*s != '\0') {
+ *p++ = *s++;
+ if (s[-1] == '/' || s[-1] == '\\') {
+ // Skip all following slashes, backslashes and double-dots
+ while (s[0] != '\0') {
+ if (s[0] == '/' || s[0] == '\\') {
+ s++;
+ } else if (s[0] == '.' && s[1] == '.') {
+ s += 2;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ *p = '\0';
+}
+
+static const struct {
+ const char *extension;
+ size_t ext_len;
+ const char *mime_type;
+} builtin_mime_types[] = {
+ {".html", 5, "text/html"},
+ {".htm", 4, "text/html"},
+ {".shtm", 5, "text/html"},
+ {".shtml", 6, "text/html"},
+ {".css", 4, "text/css"},
+ {".js", 3, "application/x-javascript"},
+ {".ico", 4, "image/x-icon"},
+ {".gif", 4, "image/gif"},
+ {".jpg", 4, "image/jpeg"},
+ {".jpeg", 5, "image/jpeg"},
+ {".png", 4, "image/png"},
+ {".svg", 4, "image/svg+xml"},
+ {".txt", 4, "text/plain"},
+ {".torrent", 8, "application/x-bittorrent"},
+ {".wav", 4, "audio/x-wav"},
+ {".mp3", 4, "audio/x-mp3"},
+ {".mid", 4, "audio/mid"},
+ {".m3u", 4, "audio/x-mpegurl"},
+ {".ram", 4, "audio/x-pn-realaudio"},
+ {".xml", 4, "text/xml"},
+ {".json", 5, "text/json"},
+ {".xslt", 5, "application/xml"},
+ {".ra", 3, "audio/x-pn-realaudio"},
+ {".doc", 4, "application/msword"},
+ {".exe", 4, "application/octet-stream"},
+ {".zip", 4, "application/x-zip-compressed"},
+ {".xls", 4, "application/excel"},
+ {".tgz", 4, "application/x-tar-gz"},
+ {".tar", 4, "application/x-tar"},
+ {".gz", 3, "application/x-gunzip"},
+ {".arj", 4, "application/x-arj-compressed"},
+ {".rar", 4, "application/x-arj-compressed"},
+ {".rtf", 4, "application/rtf"},
+ {".pdf", 4, "application/pdf"},
+ {".swf", 4, "application/x-shockwave-flash"},
+ {".mpg", 4, "video/mpeg"},
+ {".webm", 5, "video/webm"},
+ {".mpeg", 5, "video/mpeg"},
+ {".mp4", 4, "video/mp4"},
+ {".m4v", 4, "video/x-m4v"},
+ {".asf", 4, "video/x-ms-asf"},
+ {".avi", 4, "video/x-msvideo"},
+ {".bmp", 4, "image/bmp"},
+ {NULL, 0, NULL}
+};
+
+const char *mg_get_builtin_mime_type(const char *path) {
+ const char *ext;
+ size_t i, path_len;
+
+ path_len = strlen(path);
+
+ for (i = 0; builtin_mime_types[i].extension != NULL; i++) {
+ ext = path + (path_len - builtin_mime_types[i].ext_len);
+ if (path_len > builtin_mime_types[i].ext_len &&
+ mg_strcasecmp(ext, builtin_mime_types[i].extension) == 0) {
+ return builtin_mime_types[i].mime_type;
+ }
+ }
+
+ return "text/plain";
+}
+
+// Look at the "path" extension and figure what mime type it has.
+// Store mime type in the vector.
+static void get_mime_type(struct mg_context *ctx, const char *path,
+ struct vec *vec) {
+ struct vec ext_vec, mime_vec;
+ const char *list, *ext;
+ size_t path_len;
+
+ path_len = strlen(path);
+
+ // Scan user-defined mime types first, in case user wants to
+ // override default mime types.
+ list = ctx->config[EXTRA_MIME_TYPES];
+ while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) {
+ // ext now points to the path suffix
+ ext = path + path_len - ext_vec.len;
+ if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) {
+ *vec = mime_vec;
+ return;
+ }
+ }
+
+ vec->ptr = mg_get_builtin_mime_type(path);
+ vec->len = strlen(vec->ptr);
+}
+
+#ifndef HAVE_MD5
+typedef struct MD5Context {
+ uint32_t buf[4];
+ uint32_t bits[2];
+ unsigned char in[64];
+} MD5_CTX;
+
+#if defined(__BYTE_ORDER) && (__BYTE_ORDER == 1234)
+#define byteReverse(buf, len) // Do nothing
+#else
+static void byteReverse(unsigned char *buf, unsigned longs) {
+ uint32_t t;
+ do {
+ t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+ ((unsigned) buf[1] << 8 | buf[0]);
+ *(uint32_t *) buf = t;
+ buf += 4;
+ } while (--longs);
+}
+#endif
+
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+#define MD5STEP(f, w, x, y, z, data, s) \
+ ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
+
+// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+// initialization constants.
+static void MD5Init(MD5_CTX *ctx) {
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+ ctx->buf[3] = 0x10325476;
+
+ ctx->bits[0] = 0;
+ ctx->bits[1] = 0;
+}
+
+static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
+ register uint32_t a, b, c, d;
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+
+static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
+ uint32_t t;
+
+ t = ctx->bits[0];
+ if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
+ ctx->bits[1]++;
+ ctx->bits[1] += len >> 29;
+
+ t = (t >> 3) & 0x3f;
+
+ if (t) {
+ unsigned char *p = (unsigned char *) ctx->in + t;
+
+ t = 64 - t;
+ if (len < t) {
+ memcpy(p, buf, len);
+ return;
+ }
+ memcpy(p, buf, t);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ buf += t;
+ len -= t;
+ }
+
+ while (len >= 64) {
+ memcpy(ctx->in, buf, 64);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ buf += 64;
+ len -= 64;
+ }
+
+ memcpy(ctx->in, buf, len);
+}
+
+static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
+ unsigned count;
+ unsigned char *p;
+
+ count = (ctx->bits[0] >> 3) & 0x3F;
+
+ p = ctx->in + count;
+ *p++ = 0x80;
+ count = 64 - 1 - count;
+ if (count < 8) {
+ memset(p, 0, count);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ memset(ctx->in, 0, 56);
+ } else {
+ memset(p, 0, count - 8);
+ }
+ byteReverse(ctx->in, 14);
+
+ ((uint32_t *) ctx->in)[14] = ctx->bits[0];
+ ((uint32_t *) ctx->in)[15] = ctx->bits[1];
+
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ byteReverse((unsigned char *) ctx->buf, 4);
+ memcpy(digest, ctx->buf, 16);
+ memset((char *) ctx, 0, sizeof(*ctx));
+}
+#endif // !HAVE_MD5
+
+// Stringify binary data. Output buffer must be twice as big as input,
+// because each byte takes 2 bytes in string representation
+static void bin2str(char *to, const unsigned char *p, size_t len) {
+ static const char *hex = "0123456789abcdef";
+
+ for (; len--; p++) {
+ *to++ = hex[p[0] >> 4];
+ *to++ = hex[p[0] & 0x0f];
+ }
+ *to = '\0';
+}
+
+// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
+void mg_md5(char buf[33], ...) {
+ unsigned char hash[16];
+ const char *p;
+ va_list ap;
+ MD5_CTX ctx;
+
+ MD5Init(&ctx);
+
+ va_start(ap, buf);
+ while ((p = va_arg(ap, const char *)) != NULL) {
+ MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
+ }
+ va_end(ap);
+
+ MD5Final(hash, &ctx);
+ bin2str(buf, hash, sizeof(hash));
+}
+
+// Check the user's password, return 1 if OK
+static int check_password(const char *method, const char *ha1, const char *uri,
+ const char *nonce, const char *nc, const char *cnonce,
+ const char *qop, const char *response) {
+ char ha2[32 + 1], expected_response[32 + 1];
+
+ // Some of the parameters may be NULL
+ if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL ||
+ qop == NULL || response == NULL) {
+ return 0;
+ }
+
+ // NOTE(lsm): due to a bug in MSIE, we do not compare the URI
+ // TODO(lsm): check for authentication timeout
+ if (// strcmp(dig->uri, c->ouri) != 0 ||
+ strlen(response) != 32
+ // || now - strtoul(dig->nonce, NULL, 10) > 3600
+ ) {
+ return 0;
+ }
+
+ mg_md5(ha2, method, ":", uri, NULL);
+ mg_md5(expected_response, ha1, ":", nonce, ":", nc,
+ ":", cnonce, ":", qop, ":", ha2, NULL);
+
+ return mg_strcasecmp(response, expected_response) == 0;
+}
+
+// Use the global passwords file, if specified by auth_gpass option,
+// or search for .htpasswd in the requested directory.
+static FILE *open_auth_file(struct mg_connection *conn, const char *path) {
+ struct mg_context *ctx = conn->ctx;
+ char name[PATH_MAX];
+ const char *p, *e;
+ struct mgstat st;
+ FILE *fp;
+
+ if (ctx->config[GLOBAL_PASSWORDS_FILE] != NULL) {
+ // Use global passwords file
+ fp = mg_fopen(ctx->config[GLOBAL_PASSWORDS_FILE], "r");
+ if (fp == NULL)
+ cry(fc(ctx), "fopen(%s): %s",
+ ctx->config[GLOBAL_PASSWORDS_FILE], strerror(ERRNO));
+ } else if (!mg_stat(path, &st) && st.is_directory) {
+ (void) mg_snprintf(conn, name, sizeof(name), "%s%c%s",
+ path, '/', PASSWORDS_FILE_NAME);
+ fp = mg_fopen(name, "r");
+ } else {
+ // Try to find .htpasswd in requested directory.
+ for (p = path, e = p + strlen(p) - 1; e > p; e--)
+ if (e[0] == '/')
+ break;
+ (void) mg_snprintf(conn, name, sizeof(name), "%.*s%c%s",
+ (int) (e - p), p, '/', PASSWORDS_FILE_NAME);
+ fp = mg_fopen(name, "r");
+ }
+
+ return fp;
+}
+
+// Parsed Authorization header
+struct ah {
+ char *user, *uri, *cnonce, *response, *qop, *nc, *nonce;
+};
+
+// Return 1 on success. Always initializes the ah structure.
+static int parse_auth_header(struct mg_connection *conn, char *buf,
+ size_t buf_size, struct ah *ah) {
+ char *name, *value, *s;
+ const char *auth_header;
+
+ (void) memset(ah, 0, sizeof(*ah));
+ if ((auth_header = mg_get_header(conn, "Authorization")) == NULL ||
+ mg_strncasecmp(auth_header, "Digest ", 7) != 0) {
+ return 0;
+ }
+
+ // Make modifiable copy of the auth header
+ (void) mg_strlcpy(buf, auth_header + 7, buf_size);
+ s = buf;
+
+ // Parse authorization header
+ for (;;) {
+ // Gobble initial spaces
+ while (isspace(* (unsigned char *) s)) {
+ s++;
+ }
+ name = skip_quoted(&s, "=", " ", 0);
+ // Value is either quote-delimited, or ends at first comma or space.
+ if (s[0] == '\"') {
+ s++;
+ value = skip_quoted(&s, "\"", " ", '\\');
+ if (s[0] == ',') {
+ s++;
+ }
+ } else {
+ value = skip_quoted(&s, ", ", " ", 0); // IE uses commas, FF uses spaces
+ }
+ if (*name == '\0') {
+ break;
+ }
+
+ if (!strcmp(name, "username")) {
+ ah->user = value;
+ } else if (!strcmp(name, "cnonce")) {
+ ah->cnonce = value;
+ } else if (!strcmp(name, "response")) {
+ ah->response = value;
+ } else if (!strcmp(name, "uri")) {
+ ah->uri = value;
+ } else if (!strcmp(name, "qop")) {
+ ah->qop = value;
+ } else if (!strcmp(name, "nc")) {
+ ah->nc = value;
+ } else if (!strcmp(name, "nonce")) {
+ ah->nonce = value;
+ }
+ }
+
+ // CGI needs it as REMOTE_USER
+ if (ah->user != NULL) {
+ conn->request_info.remote_user = mg_strdup(ah->user);
+ } else {
+ return 0;
+ }
+
+ return 1;
+}
+
+// Authorize against the opened passwords file. Return 1 if authorized.
+static int authorize(struct mg_connection *conn, FILE *fp) {
+ struct ah ah;
+ char line[256], f_user[256], ha1[256], f_domain[256], buf[MG_BUF_LEN];
+
+ if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) {
+ return 0;
+ }
+
+ // Loop over passwords file
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) {
+ continue;
+ }
+
+ if (!strcmp(ah.user, f_user) &&
+ !strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain))
+ return check_password(
+ conn->request_info.request_method,
+ ha1, ah.uri, ah.nonce, ah.nc, ah.cnonce, ah.qop,
+ ah.response);
+ }
+
+ return 0;
+}
+
+// Return 1 if request is authorised, 0 otherwise.
+static int check_authorization(struct mg_connection *conn, const char *path) {
+ FILE *fp;
+ char fname[PATH_MAX];
+ struct vec uri_vec, filename_vec;
+ const char *list;
+ int authorized;
+
+ fp = NULL;
+ authorized = 1;
+
+ list = conn->ctx->config[PROTECT_URI];
+ while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) {
+ if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) {
+ mg_snprintf(conn, fname, sizeof(fname), "%.*s",
+ (int) filename_vec.len, filename_vec.ptr);
+ if ((fp = mg_fopen(fname, "r")) == NULL) {
+ cry(conn, "%s: cannot open %s: %s", __func__, fname, strerror(errno));
+ }
+ break;
+ }
+ }
+
+ if (fp == NULL) {
+ fp = open_auth_file(conn, path);
+ }
+
+ if (fp != NULL) {
+ authorized = authorize(conn, fp);
+ (void) fclose(fp);
+ }
+
+ return authorized;
+}
+
+static void send_authorization_request(struct mg_connection *conn) {
+ conn->status_code = 401;
+ (void) mg_printf(conn,
+ "HTTP/1.1 401 Unauthorized\r\n"
+ "Content-Length: 0\r\n"
+ "WWW-Authenticate: Digest qop=\"auth\", "
+ "realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
+ conn->ctx->config[AUTHENTICATION_DOMAIN],
+ (unsigned long) time(NULL));
+}
+
+static int is_authorized_for_put(struct mg_connection *conn) {
+ FILE *fp;
+ int ret = 0;
+
+ fp = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE] == NULL ? NULL :
+ mg_fopen(conn->ctx->config[PUT_DELETE_PASSWORDS_FILE], "r");
+
+ if (fp != NULL) {
+ ret = authorize(conn, fp);
+ (void) fclose(fp);
+ }
+
+ return ret;
+}
+
+int mg_modify_passwords_file(const char *fname, const char *domain,
+ const char *user, const char *pass) {
+ int found;
+ char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX];
+ FILE *fp, *fp2;
+
+ found = 0;
+ fp = fp2 = NULL;
+
+ // Regard empty password as no password - remove user record.
+ if (pass != NULL && pass[0] == '\0') {
+ pass = NULL;
+ }
+
+ (void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
+
+ // Create the file if does not exist
+ if ((fp = mg_fopen(fname, "a+")) != NULL) {
+ (void) fclose(fp);
+ }
+
+ // Open the given file and temporary file
+ if ((fp = mg_fopen(fname, "r")) == NULL) {
+ return 0;
+ } else if ((fp2 = mg_fopen(tmp, "w+")) == NULL) {
+ fclose(fp);
+ return 0;
+ }
+
+ // Copy the stuff to temporary file
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) {
+ continue;
+ }
+
+ if (!strcmp(u, user) && !strcmp(d, domain)) {
+ found++;
+ if (pass != NULL) {
+ mg_md5(ha1, user, ":", domain, ":", pass, NULL);
+ fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
+ }
+ } else {
+ (void) fprintf(fp2, "%s", line);
+ }
+ }
+
+ // If new user, just add it
+ if (!found && pass != NULL) {
+ mg_md5(ha1, user, ":", domain, ":", pass, NULL);
+ (void) fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
+ }
+
+ // Close files
+ (void) fclose(fp);
+ (void) fclose(fp2);
+
+ // Put the temp file in place of real file
+ (void) mg_remove(fname);
+ (void) mg_rename(tmp, fname);
+
+ return 1;
+}
+
+struct de {
+ struct mg_connection *conn;
+ char *file_name;
+ struct mgstat st;
+};
+
+static void url_encode(const char *src, char *dst, size_t dst_len) {
+ static const char *dont_escape = "._-$,;~()";
+ static const char *hex = "0123456789abcdef";
+ const char *end = dst + dst_len - 1;
+
+ for (; *src != '\0' && dst < end; src++, dst++) {
+ if (isalnum(*(const unsigned char *) src) ||
+ strchr(dont_escape, * (const unsigned char *) src) != NULL) {
+ *dst = *src;
+ } else if (dst + 2 < end) {
+ dst[0] = '%';
+ dst[1] = hex[(* (const unsigned char *) src) >> 4];
+ dst[2] = hex[(* (const unsigned char *) src) & 0xf];
+ dst += 2;
+ }
+ }
+
+ *dst = '\0';
+}
+
+static void print_dir_entry(struct de *de) {
+ char size[64], mod[64], href[PATH_MAX];
+
+ if (de->st.is_directory) {
+ (void) mg_snprintf(de->conn, size, sizeof(size), "%s", "[DIRECTORY]");
+ } else {
+ // We use (signed) cast below because MSVC 6 compiler cannot
+ // convert unsigned __int64 to double. Sigh.
+ if (de->st.size < 1024) {
+ (void) mg_snprintf(de->conn, size, sizeof(size),
+ "%lu", (unsigned long) de->st.size);
+ } else if (de->st.size < 0x100000) {
+ (void) mg_snprintf(de->conn, size, sizeof(size),
+ "%.1fk", (double) de->st.size / 1024.0);
+ } else if (de->st.size < 0x40000000) {
+ (void) mg_snprintf(de->conn, size, sizeof(size),
+ "%.1fM", (double) de->st.size / 1048576);
+ } else {
+ (void) mg_snprintf(de->conn, size, sizeof(size),
+ "%.1fG", (double) de->st.size / 1073741824);
+ }
+ }
+ (void) strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&de->st.mtime));
+ url_encode(de->file_name, href, sizeof(href));
+ de->conn->num_bytes_sent += mg_printf(de->conn,
+ "<tr><td><a href=\"%s%s%s\">%s%s</a></td>"
+ "<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
+ de->conn->request_info.uri, href, de->st.is_directory ? "/" : "",
+ de->file_name, de->st.is_directory ? "/" : "", mod, size);
+}
+
+// This function is called from send_directory() and used for
+// sorting directory entries by size, or name, or modification time.
+// On windows, __cdecl specification is needed in case if project is built
+// with __stdcall convention. qsort always requires __cdels callback.
+static int WINCDECL compare_dir_entries(const void *p1, const void *p2) {
+ const struct de *a = (const struct de *) p1, *b = (const struct de *) p2;
+ const char *query_string = a->conn->request_info.query_string;
+ int cmp_result = 0;
+
+ if (query_string == NULL) {
+ query_string = "na";
+ }
+
+ if (a->st.is_directory && !b->st.is_directory) {
+ return -1; // Always put directories on top
+ } else if (!a->st.is_directory && b->st.is_directory) {
+ return 1; // Always put directories on top
+ } else if (*query_string == 'n') {
+ cmp_result = strcmp(a->file_name, b->file_name);
+ } else if (*query_string == 's') {
+ cmp_result = a->st.size == b->st.size ? 0 :
+ a->st.size > b->st.size ? 1 : -1;
+ } else if (*query_string == 'd') {
+ cmp_result = a->st.mtime == b->st.mtime ? 0 :
+ a->st.mtime > b->st.mtime ? 1 : -1;
+ }
+
+ return query_string[1] == 'd' ? -cmp_result : cmp_result;
+}
+
+static int must_hide_file(struct mg_connection *conn, const char *path) {
+ const char *pw_pattern = "**" PASSWORDS_FILE_NAME "$";
+ const char *pattern = conn->ctx->config[HIDE_FILES];
+ return match_prefix(pw_pattern, strlen(pw_pattern), path) > 0 ||
+ (pattern != NULL && match_prefix(pattern, strlen(pattern), path) > 0);
+}
+
+static int scan_directory(struct mg_connection *conn, const char *dir,
+ void *data, void (*cb)(struct de *, void *)) {
+ char path[PATH_MAX];
+ struct dirent *dp;
+ DIR *dirp;
+ struct de de;
+
+ if ((dirp = opendir(dir)) == NULL) {
+ return 0;
+ } else {
+ de.conn = conn;
+
+ while ((dp = readdir(dirp)) != NULL) {
+ // Do not show current dir and hidden files
+ if (!strcmp(dp->d_name, ".") ||
+ !strcmp(dp->d_name, "..") ||
+ must_hide_file(conn, dp->d_name)) {
+ continue;
+ }
+
+ mg_snprintf(conn, path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
+
+ // If we don't memset stat structure to zero, mtime will have
+ // garbage and strftime() will segfault later on in
+ // print_dir_entry(). memset is required only if mg_stat()
+ // fails. For more details, see
+ // http://code.google.com/p/mongoose/issues/detail?id=79
+ if (mg_stat(path, &de.st) != 0) {
+ memset(&de.st, 0, sizeof(de.st));
+ }
+ de.file_name = dp->d_name;
+
+ cb(&de, data);
+ }
+ (void) closedir(dirp);
+ }
+ return 1;
+}
+
+struct dir_scan_data {
+ struct de *entries;
+ int num_entries;
+ int arr_size;
+};
+
+static void dir_scan_callback(struct de *de, void *data) {
+ struct dir_scan_data *dsd = (struct dir_scan_data *) data;
+
+ if (dsd->entries == NULL || dsd->num_entries >= dsd->arr_size) {
+ dsd->arr_size *= 2;
+ dsd->entries = (struct de *) realloc(dsd->entries, dsd->arr_size *
+ sizeof(dsd->entries[0]));
+ }
+ if (dsd->entries == NULL) {
+ // TODO(lsm): propagate an error to the caller
+ dsd->num_entries = 0;
+ } else {
+ dsd->entries[dsd->num_entries].file_name = mg_strdup(de->file_name);
+ dsd->entries[dsd->num_entries].st = de->st;
+ dsd->entries[dsd->num_entries].conn = de->conn;
+ dsd->num_entries++;
+ }
+}
+
+static void handle_directory_request(struct mg_connection *conn,
+ const char *dir) {
+ int i, sort_direction;
+ struct dir_scan_data data = { NULL, 0, 128 };
+
+ if (!scan_directory(conn, dir, &data, dir_scan_callback)) {
+ send_http_error(conn, 500, "Cannot open directory",
+ "Error: opendir(%s): %s", dir, strerror(ERRNO));
+ return;
+ }
+
+ sort_direction = conn->request_info.query_string != NULL &&
+ conn->request_info.query_string[1] == 'd' ? 'a' : 'd';
+
+ conn->must_close = 1;
+ mg_printf(conn, "%s",
+ "HTTP/1.1 200 OK\r\n"
+ "Connection: close\r\n"
+ "Content-Type: text/html; charset=utf-8\r\n\r\n");
+
+ conn->num_bytes_sent += mg_printf(conn,
+ "<html><head><title>Index of %s</title>"
+ "<style>th {text-align: left;}</style></head>"
+ "<body><h1>Index of %s</h1><pre><table cellpadding=\"0\">"
+ "<tr><th><a href=\"?n%c\">Name</a></th>"
+ "<th><a href=\"?d%c\">Modified</a></th>"
+ "<th><a href=\"?s%c\">Size</a></th></tr>"
+ "<tr><td colspan=\"3\"><hr></td></tr>",
+ conn->request_info.uri, conn->request_info.uri,
+ sort_direction, sort_direction, sort_direction);
+
+ // Print first entry - link to a parent directory
+ conn->num_bytes_sent += mg_printf(conn,
+ "<tr><td><a href=\"%s%s\">%s</a></td>"
+ "<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
+ conn->request_info.uri, "..", "Parent directory", "-", "-");
+
+ // Sort and print directory entries
+ qsort(data.entries, (size_t) data.num_entries, sizeof(data.entries[0]),
+ compare_dir_entries);
+ for (i = 0; i < data.num_entries; i++) {
+ print_dir_entry(&data.entries[i]);
+ free(data.entries[i].file_name);
+ }
+ free(data.entries);
+
+ conn->num_bytes_sent += mg_printf(conn, "%s", "</table></body></html>");
+ conn->status_code = 200;
+}
+
+// Send len bytes from the opened file to the client.
+static void send_file_data(struct mg_connection *conn, FILE *fp, int64_t len) {
+ char buf[MG_BUF_LEN];
+ int to_read, num_read, num_written;
+
+ while (len > 0) {
+ // Calculate how much to read from the file in the buffer
+ to_read = sizeof(buf);
+ if ((int64_t) to_read > len) {
+ to_read = (int) len;
+ }
+
+ // Read from file, exit the loop on error
+ if ((num_read = fread(buf, 1, (size_t)to_read, fp)) <= 0) {
+ break;
+ }
+
+ // Send read bytes to the client, exit the loop on error
+ if ((num_written = mg_write(conn, buf, (size_t)num_read)) != num_read) {
+ break;
+ }
+
+ // Both read and were successful, adjust counters
+ conn->num_bytes_sent += num_written;
+ len -= num_written;
+ }
+}
+
+static int parse_range_header(const char *header, int64_t *a, int64_t *b) {
+ return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
+}
+
+static void gmt_time_string(char *buf, size_t buf_len, time_t *t) {
+ strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t));
+}
+
+static void construct_etag(char *buf, size_t buf_len,
+ const struct mgstat *stp) {
+ snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"",
+ (unsigned long) stp->mtime, stp->size);
+}
+
+static void handle_file_request(struct mg_connection *conn, const char *path,
+ struct mgstat *stp) {
+ char date[64], lm[64], etag[64], range[64];
+ const char *msg = "OK", *hdr;
+ time_t curtime = time(NULL);
+ int64_t cl, r1, r2;
+ struct vec mime_vec;
+ FILE *fp;
+ int n;
+
+ get_mime_type(conn->ctx, path, &mime_vec);
+ cl = stp->size;
+ conn->status_code = 200;
+ range[0] = '\0';
+
+ if ((fp = mg_fopen(path, "rb")) == NULL) {
+ send_http_error(conn, 500, http_500_error,
+ "fopen(%s): %s", path, strerror(ERRNO));
+ return;
+ }
+ set_close_on_exec(fileno(fp));
+
+ // If Range: header specified, act accordingly
+ r1 = r2 = 0;
+ hdr = mg_get_header(conn, "Range");
+ if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0) {
+ conn->status_code = 206;
+ (void) fseeko(fp, r1, SEEK_SET);
+ cl = n == 2 ? r2 - r1 + 1: cl - r1;
+ (void) mg_snprintf(conn, range, sizeof(range),
+ "Content-Range: bytes "
+ "%" INT64_FMT "-%"
+ INT64_FMT "/%" INT64_FMT "\r\n",
+ r1, r1 + cl - 1, stp->size);
+ msg = "Partial Content";
+ }
+
+ // Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
+ gmt_time_string(date, sizeof(date), &curtime);
+ gmt_time_string(lm, sizeof(lm), &stp->mtime);
+ construct_etag(etag, sizeof(etag), stp);
+
+ (void) mg_printf(conn,
+ "HTTP/1.1 %d %s\r\n"
+ "Date: %s\r\n"
+ "Last-Modified: %s\r\n"
+ "Etag: %s\r\n"
+ "Content-Type: %.*s\r\n"
+ "Content-Length: %" INT64_FMT "\r\n"
+ "Connection: %s\r\n"
+ "Accept-Ranges: bytes\r\n"
+ "%s\r\n",
+ conn->status_code, msg, date, lm, etag, (int) mime_vec.len,
+ mime_vec.ptr, cl, suggest_connection_header(conn), range);
+
+ if (strcmp(conn->request_info.request_method, "HEAD") != 0) {
+ send_file_data(conn, fp, cl);
+ }
+ (void) fclose(fp);
+}
+
+void mg_send_file(struct mg_connection *conn, const char *path) {
+ struct mgstat st;
+ if (mg_stat(path, &st) == 0) {
+ handle_file_request(conn, path, &st);
+ } else {
+ send_http_error(conn, 404, "Not Found", "%s", "File not found");
+ }
+}
+
+
+// Parse HTTP headers from the given buffer, advance buffer to the point
+// where parsing stopped.
+static void parse_http_headers(char **buf, struct mg_request_info *ri) {
+ int i;
+
+ for (i = 0; i < (int) ARRAY_SIZE(ri->http_headers); i++) {
+ ri->http_headers[i].name = skip_quoted(buf, ":", " ", 0);
+ ri->http_headers[i].value = skip(buf, "\r\n");
+ if (ri->http_headers[i].name[0] == '\0')
+ break;
+ ri->num_headers = i + 1;
+ }
+}
+
+static int is_valid_http_method(const char *method) {
+ return !strcmp(method, "GET") || !strcmp(method, "POST") ||
+ !strcmp(method, "HEAD") || !strcmp(method, "CONNECT") ||
+ !strcmp(method, "PUT") || !strcmp(method, "DELETE") ||
+ !strcmp(method, "OPTIONS") || !strcmp(method, "PROPFIND");
+}
+
+// Parse HTTP request, fill in mg_request_info structure.
+// This function modifies the buffer by NUL-terminating
+// HTTP request components, header names and header values.
+static int parse_http_message(char *buf, int len, struct mg_request_info *ri) {
+ int request_length = get_request_len(buf, len);
+ if (request_length > 0) {
+ // Reset attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port
+ ri->remote_user = ri->request_method = ri->uri = ri->http_version = NULL;
+ ri->num_headers = 0;
+
+ buf[request_length - 1] = '\0';
+
+ // RFC says that all initial whitespaces should be ingored
+ while (*buf != '\0' && isspace(* (unsigned char *) buf)) {
+ buf++;
+ }
+ ri->request_method = skip(&buf, " ");
+ ri->uri = skip(&buf, " ");
+ ri->http_version = skip(&buf, "\r\n");
+ parse_http_headers(&buf, ri);
+ }
+ return request_length;
+}
+
+static int parse_http_request(char *buf, int len, struct mg_request_info *ri) {
+ int result = parse_http_message(buf, len, ri);
+ if (result > 0 &&
+ is_valid_http_method(ri->request_method) &&
+ !strncmp(ri->http_version, "HTTP/", 5)) {
+ ri->http_version += 5; // Skip "HTTP/"
+ } else {
+ result = -1;
+ }
+ return result;
+}
+
+static int parse_http_response(char *buf, int len, struct mg_request_info *ri) {
+ int result = parse_http_message(buf, len, ri);
+ return result > 0 && !strncmp(ri->request_method, "HTTP/", 5) ? result : -1;
+}
+
+// Keep reading the input (either opened file descriptor fd, or socket sock,
+// or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the
+// buffer (which marks the end of HTTP request). Buffer buf may already
+// have some data. The length of the data is stored in nread.
+// Upon every read operation, increase nread by the number of bytes read.
+static int read_request(FILE *fp, struct mg_connection *conn,
+ char *buf, int bufsiz, int *nread) {
+ int request_len, n = 1;
+
+ request_len = get_request_len(buf, *nread);
+ while (*nread < bufsiz && request_len == 0 && n > 0) {
+ n = pull(fp, conn, buf + *nread, bufsiz - *nread);
+ if (n > 0) {
+ *nread += n;
+ request_len = get_request_len(buf, *nread);
+ }
+ }
+
+ if (n < 0) {
+ // recv() error -> propagate error; do not process a b0rked-with-very-high-probability request
+ return -1;
+ }
+ return request_len;
+}
+
+// For given directory path, substitute it to valid index file.
+// Return 0 if index file has been found, -1 if not found.
+// If the file is found, it's stats is returned in stp.
+static int substitute_index_file(struct mg_connection *conn, char *path,
+ size_t path_len, struct mgstat *stp) {
+ const char *list = conn->ctx->config[INDEX_FILES];
+ struct mgstat st;
+ struct vec filename_vec;
+ size_t n = strlen(path);
+ int found = 0;
+
+ // The 'path' given to us points to the directory. Remove all trailing
+ // directory separator characters from the end of the path, and
+ // then append single directory separator character.
+ while (n > 0 && path[n - 1] == '/') {
+ n--;
+ }
+ path[n] = '/';
+
+ // Traverse index files list. For each entry, append it to the given
+ // path and see if the file exists. If it exists, break the loop
+ while ((list = next_option(list, &filename_vec, NULL)) != NULL) {
+
+ // Ignore too long entries that may overflow path buffer
+ if (filename_vec.len > path_len - (n + 2))
+ continue;
+
+ // Prepare full path to the index file
+ (void) mg_strlcpy(path + n + 1, filename_vec.ptr, filename_vec.len + 1);
+
+ // Does it exist?
+ if (mg_stat(path, &st) == 0) {
+ // Yes it does, break the loop
+ *stp = st;
+ found = 1;
+ break;
+ }
+ }
+
+ // If no index file exists, restore directory path
+ if (!found) {
+ path[n] = '\0';
+ }
+
+ return found;
+}
+
+// Return True if we should reply 304 Not Modified.
+static int is_not_modified(const struct mg_connection *conn,
+ const struct mgstat *stp) {
+ char etag[64];
+ const char *ims = mg_get_header(conn, "If-Modified-Since");
+ const char *inm = mg_get_header(conn, "If-None-Match");
+ construct_etag(etag, sizeof(etag), stp);
+ return (inm != NULL && !mg_strcasecmp(etag, inm)) ||
+ (ims != NULL && stp->mtime <= parse_date_string(ims));
+}
+
+static int forward_body_data(struct mg_connection *conn, FILE *fp,
+ SOCKET sock, SSL *ssl) {
+ const char *expect, *body;
+ char buf[MG_BUF_LEN];
+ int to_read, nread, buffered_len, success = 0;
+
+ expect = mg_get_header(conn, "Expect");
+ assert(fp != NULL);
+
+ if (conn->content_len == -1) {
+ send_http_error(conn, 411, "Length Required", "%s", "");
+ } else if (expect != NULL && mg_strcasecmp(expect, "100-continue")) {
+ send_http_error(conn, 417, "Expectation Failed", "%s", "");
+ } else {
+ if (expect != NULL) {
+ (void) mg_printf(conn, "%s", "HTTP/1.1 100 Continue\r\n\r\n");
+ }
+
+ body = conn->buf + conn->request_len + conn->consumed_content;
+ buffered_len = &conn->buf[conn->data_len] - body;
+ assert(buffered_len >= 0);
+ assert(conn->consumed_content == 0);
+
+ if (buffered_len > 0) {
+ if ((int64_t) buffered_len > conn->content_len) {
+ buffered_len = (int) conn->content_len;
+ }
+ push(fp, sock, ssl, body, (int64_t) buffered_len);
+ conn->consumed_content += buffered_len;
+ }
+
+ nread = 0;
+ while (conn->consumed_content < conn->content_len) {
+ to_read = sizeof(buf);
+ if ((int64_t) to_read > conn->content_len - conn->consumed_content) {
+ to_read = (int) (conn->content_len - conn->consumed_content);
+ }
+ nread = pull(NULL, conn, buf, to_read);
+ if (nread <= 0 || push(fp, sock, ssl, buf, nread) != nread) {
+ break;
+ }
+ conn->consumed_content += nread;
+ }
+
+ if (conn->consumed_content == conn->content_len) {
+ success = nread >= 0;
+ }
+
+ // Each error code path in this function must send an error
+ if (!success) {
+ send_http_error(conn, 577, http_500_error, "%s", "");
+ }
+ }
+
+ return success;
+}
+
+#if !defined(NO_CGI)
+// This structure helps to create an environment for the spawned CGI program.
+// Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings,
+// last element must be NULL.
+// However, on Windows there is a requirement that all these VARIABLE=VALUE\0
+// strings must reside in a contiguous buffer. The end of the buffer is
+// marked by two '\0' characters.
+// We satisfy both worlds: we create an envp array (which is vars), all
+// entries are actually pointers inside buf.
+struct cgi_env_block {
+ struct mg_connection *conn;
+ char buf[CGI_ENVIRONMENT_SIZE]; // Environment buffer
+ int len; // Space taken
+ char *vars[MAX_CGI_ENVIR_VARS]; // char **envp
+ int nvars; // Number of variables
+};
+
+static char *addenv(struct cgi_env_block *block,
+ PRINTF_FORMAT_STRING(const char *fmt), ...)
+ PRINTF_ARGS(2, 3);
+
+// Append VARIABLE=VALUE\0 string to the buffer, and add a respective
+// pointer into the vars array.
+static char *addenv(struct cgi_env_block *block, const char *fmt, ...) {
+ int n, space;
+ char *added;
+ va_list ap;
+
+ // Calculate how much space is left in the buffer
+ space = sizeof(block->buf) - block->len - 2;
+ assert(space >= 0);
+
+ // Make a pointer to the free space int the buffer
+ added = block->buf + block->len;
+
+ // Copy VARIABLE=VALUE\0 string into the free space
+ va_start(ap, fmt);
+ n = mg_vsnprintf(block->conn, added, (size_t) space, fmt, ap);
+ va_end(ap);
+
+ // Make sure we do not overflow buffer and the envp array
+ if (n > 0 && n + 1 < space &&
+ block->nvars < (int) ARRAY_SIZE(block->vars) - 2) {
+ // Append a pointer to the added string into the envp array
+ block->vars[block->nvars++] = added;
+ // Bump up used length counter. Include \0 terminator
+ block->len += n + 1;
+ } else {
+ cry(block->conn, "%s: CGI env buffer truncated for [%s]", __func__, fmt);
+ }
+
+ return added;
+}
+
+static void prepare_cgi_environment(struct mg_connection *conn,
+ const char *prog,
+ struct cgi_env_block *blk) {
+ const char *s, *slash;
+ struct vec var_vec;
+ char *p, src_addr[20];
+ int i;
+
+ blk->len = blk->nvars = 0;
+ blk->conn = conn;
+ sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
+
+ addenv(blk, "SERVER_NAME=%s", conn->ctx->config[AUTHENTICATION_DOMAIN]);
+ addenv(blk, "SERVER_ROOT=%s", conn->ctx->config[DOCUMENT_ROOT]);
+ addenv(blk, "DOCUMENT_ROOT=%s", conn->ctx->config[DOCUMENT_ROOT]);
+
+ // Prepare the environment block
+ addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1");
+ addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1");
+ addenv(blk, "%s", "REDIRECT_STATUS=200"); // For PHP
+
+ // TODO(lsm): fix this for IPv6 case
+ addenv(blk, "SERVER_PORT=%d", ntohs(conn->client.lsa.sin.sin_port));
+
+ addenv(blk, "REQUEST_METHOD=%s", conn->request_info.request_method);
+ addenv(blk, "REMOTE_ADDR=%s", src_addr);
+ addenv(blk, "REMOTE_PORT=%d", conn->request_info.remote_port);
+ addenv(blk, "REQUEST_URI=%s", conn->request_info.uri);
+
+ // SCRIPT_NAME
+ assert(conn->request_info.uri[0] == '/');
+ slash = strrchr(conn->request_info.uri, '/');
+ if ((s = strrchr(prog, '/')) == NULL)
+ s = prog;
+ addenv(blk, "SCRIPT_NAME=%.*s%s", (int) (slash - conn->request_info.uri),
+ conn->request_info.uri, s);
+
+ addenv(blk, "SCRIPT_FILENAME=%s", prog);
+ addenv(blk, "PATH_TRANSLATED=%s", prog);
+ addenv(blk, "HTTPS=%s", conn->ssl == NULL ? "off" : "on");
+
+ if ((s = mg_get_header(conn, "Content-Type")) != NULL)
+ addenv(blk, "CONTENT_TYPE=%s", s);
+
+ if (conn->request_info.query_string != NULL)
+ addenv(blk, "QUERY_STRING=%s", conn->request_info.query_string);
+
+ if ((s = mg_get_header(conn, "Content-Length")) != NULL)
+ addenv(blk, "CONTENT_LENGTH=%s", s);
+
+ if ((s = getenv("PATH")) != NULL)
+ addenv(blk, "PATH=%s", s);
+
+ if (conn->path_info != NULL) {
+ addenv(blk, "PATH_INFO=%s", conn->path_info);
+ }
+
+#if defined(_WIN32)
+ if ((s = getenv("COMSPEC")) != NULL) {
+ addenv(blk, "COMSPEC=%s", s);
+ }
+ if ((s = getenv("SYSTEMROOT")) != NULL) {
+ addenv(blk, "SYSTEMROOT=%s", s);
+ }
+ if ((s = getenv("SystemDrive")) != NULL) {
+ addenv(blk, "SystemDrive=%s", s);
+ }
+#else
+ if ((s = getenv("LD_LIBRARY_PATH")) != NULL)
+ addenv(blk, "LD_LIBRARY_PATH=%s", s);
+#endif // _WIN32
+
+ if ((s = getenv("PERLLIB")) != NULL)
+ addenv(blk, "PERLLIB=%s", s);
+
+ if (conn->request_info.remote_user != NULL) {
+ addenv(blk, "REMOTE_USER=%s", conn->request_info.remote_user);
+ addenv(blk, "%s", "AUTH_TYPE=Digest");
+ }
+
+ // Add all headers as HTTP_* variables
+ for (i = 0; i < conn->request_info.num_headers; i++) {
+ p = addenv(blk, "HTTP_%s=%s",
+ conn->request_info.http_headers[i].name,
+ conn->request_info.http_headers[i].value);
+
+ // Convert variable name into uppercase, and change - to _
+ for (; *p != '=' && *p != '\0'; p++) {
+ if (*p == '-')
+ *p = '_';
+ *p = (char) toupper(* (unsigned char *) p);
+ }
+ }
+
+ // Add user-specified variables
+ s = conn->ctx->config[CGI_ENVIRONMENT];
+ while ((s = next_option(s, &var_vec, NULL)) != NULL) {
+ addenv(blk, "%.*s", (int) var_vec.len, var_vec.ptr);
+ }
+
+ blk->vars[blk->nvars++] = NULL;
+ blk->buf[blk->len++] = '\0';
+
+ assert(blk->nvars < (int) ARRAY_SIZE(blk->vars));
+ assert(blk->len > 0);
+ assert(blk->len < (int) sizeof(blk->buf));
+}
+
+static void handle_cgi_request(struct mg_connection *conn, const char *prog) {
+ int headers_len, data_len, i, fd_stdin[2], fd_stdout[2];
+ const char *status, *status_text;
+ char buf[16384], *pbuf, dir[PATH_MAX], *p;
+ struct mg_request_info ri;
+ struct cgi_env_block blk;
+ FILE *in, *out;
+ pid_t pid;
+
+ prepare_cgi_environment(conn, prog, &blk);
+
+ // CGI must be executed in its own directory. 'dir' must point to the
+ // directory containing executable program, 'p' must point to the
+ // executable program name relative to 'dir'.
+ (void) mg_snprintf(conn, dir, sizeof(dir), "%s", prog);
+ if ((p = strrchr(dir, '/')) != NULL) {
+ *p++ = '\0';
+ } else {
+ dir[0] = '.', dir[1] = '\0';
+ p = (char *) prog;
+ }
+
+ pid = (pid_t) -1;
+ fd_stdin[0] = fd_stdin[1] = fd_stdout[0] = fd_stdout[1] = -1;
+ in = out = NULL;
+
+ if (pipe(fd_stdin) != 0 || pipe(fd_stdout) != 0) {
+ send_http_error(conn, 500, http_500_error,
+ "Cannot create CGI pipe: %s", strerror(ERRNO));
+ goto done;
+ } else if ((pid = spawn_process(conn, p, blk.buf, blk.vars,
+ fd_stdin[0], fd_stdout[1], dir)) == (pid_t) -1) {
+ send_http_error(conn, 500, http_500_error,
+ "Cannot spawn CGI process [%s]: %s", prog, strerror(ERRNO));
+ goto done;
+ }
+
+ // spawn_process() must close those!
+ // If we don't mark them as closed, close() attempt before
+ // return from this function throws an exception on Windows.
+ // Windows does not like when closed descriptor is closed again.
+ fd_stdin[0] = fd_stdout[1] = -1;
+
+ if ((in = fdopen(fd_stdin[1], "wb")) == NULL ||
+ (out = fdopen(fd_stdout[0], "rb")) == NULL) {
+ send_http_error(conn, 500, http_500_error,
+ "fopen: %s", strerror(ERRNO));
+ goto done;
+ }
+
+ setbuf(in, NULL);
+ setbuf(out, NULL);
+
+ // Send POST data to the CGI process if needed
+ if (!strcmp(conn->request_info.request_method, "POST") &&
+ !forward_body_data(conn, in, INVALID_SOCKET, NULL)) {
+ goto done;
+ }
+
+ // Close so child gets an EOF.
+ fclose(in);
+ in = NULL;
+ fd_stdin[1] = -1;
+
+ // Now read CGI reply into a buffer. We need to set correct
+ // status code, thus we need to see all HTTP headers first.
+ // Do not send anything back to client, until we buffer in all
+ // HTTP headers.
+ data_len = 0;
+ headers_len = read_request(out, conn, buf, sizeof(buf), &data_len);
+ if (headers_len <= 0) {
+ send_http_error(conn, 500, http_500_error,
+ "CGI program sent malformed or too big (>%u bytes) "
+ "HTTP headers: [%.*s]",
+ (unsigned) sizeof(buf), data_len, buf);
+ goto done;
+ }
+ pbuf = buf;
+ buf[headers_len - 1] = '\0';
+ parse_http_headers(&pbuf, &ri);
+
+ // Make up and send the status line
+ status_text = "OK";
+ if ((status = get_header(&ri, "Status")) != NULL) {
+ conn->status_code = atoi(status);
+ status_text = status;
+ while (isdigit(* (unsigned char *) status_text) || *status_text == ' ') {
+ status_text++;
+ }
+ } else if (get_header(&ri, "Location") != NULL) {
+ conn->status_code = 302;
+ } else {
+ conn->status_code = 200;
+ }
+ if (get_header(&ri, "Connection") != NULL &&
+ !mg_strcasecmp(get_header(&ri, "Connection"), "keep-alive")) {
+ conn->must_close = 1;
+ }
+ (void) mg_printf(conn, "HTTP/1.1 %d %s\r\n", conn->status_code,
+ status_text);
+
+ // Send headers
+ for (i = 0; i < ri.num_headers; i++) {
+ mg_printf(conn, "%s: %s\r\n",
+ ri.http_headers[i].name, ri.http_headers[i].value);
+ }
+ (void) mg_write(conn, "\r\n", 2);
+
+ // Send chunk of data that may have been read after the headers
+ conn->num_bytes_sent += mg_write(conn, buf + headers_len,
+ (size_t)(data_len - headers_len));
+
+ // Read the rest of CGI output and send to the client
+ send_file_data(conn, out, INT64_MAX);
+
+done:
+ if (pid != (pid_t) -1) {
+ kill(pid, SIGKILL);
+ }
+ if (fd_stdin[0] != -1) {
+ (void) close(fd_stdin[0]);
+ }
+ if (fd_stdout[1] != -1) {
+ (void) close(fd_stdout[1]);
+ }
+
+ if (in != NULL) {
+ (void) fclose(in);
+ } else if (fd_stdin[1] != -1) {
+ (void) close(fd_stdin[1]);
+ }
+
+ if (out != NULL) {
+ (void) fclose(out);
+ } else if (fd_stdout[0] != -1) {
+ (void) close(fd_stdout[0]);
+ }
+}
+#endif // !NO_CGI
+
+// For a given PUT path, create all intermediate subdirectories
+// for given path. Return 0 if the path itself is a directory,
+// or -1 on error, 1 if OK.
+static int put_dir(const char *path) {
+ char buf[PATH_MAX];
+ const char *s, *p;
+ struct mgstat st;
+ int len, res = 1;
+
+ for (s = p = path + 2; (p = strchr(s, '/')) != NULL; s = ++p) {
+ len = p - path;
+ if (len >= (int) sizeof(buf)) {
+ res = -1;
+ break;
+ }
+ memcpy(buf, path, len);
+ buf[len] = '\0';
+
+ // Try to create intermediate directory
+ DEBUG_TRACE(("mkdir(%s)", buf));
+ if (mg_stat(buf, &st) == -1 && mg_mkdir(buf, 0755) != 0) {
+ res = -1;
+ break;
+ }
+
+ // Is path itself a directory?
+ if (p[1] == '\0') {
+ res = 0;
+ }
+ }
+
+ return res;
+}
+
+static void put_file(struct mg_connection *conn, const char *path) {
+ struct mgstat st;
+ const char *range;
+ int64_t r1, r2;
+ FILE *fp;
+ int rc;
+
+ conn->status_code = mg_stat(path, &st) == 0 ? 200 : 201;
+
+ if ((rc = put_dir(path)) == 0) {
+ mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n", conn->status_code);
+ } else if (rc == -1) {
+ send_http_error(conn, 500, http_500_error,
+ "put_dir(%s): %s", path, strerror(ERRNO));
+ } else if ((fp = mg_fopen(path, "wb+")) == NULL) {
+ send_http_error(conn, 500, http_500_error,
+ "fopen(%s): %s", path, strerror(ERRNO));
+ } else {
+ set_close_on_exec(fileno(fp));
+ range = mg_get_header(conn, "Content-Range");
+ r1 = r2 = 0;
+ if (range != NULL && parse_range_header(range, &r1, &r2) > 0) {
+ conn->status_code = 206;
+ // TODO(lsm): handle seek error
+ (void) fseeko(fp, r1, SEEK_SET);
+ }
+ if (forward_body_data(conn, fp, INVALID_SOCKET, NULL)) {
+ (void) mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n", conn->status_code);
+ }
+ (void) fclose(fp);
+ }
+}
+
+static void send_ssi_file(struct mg_connection *, const char *, FILE *, int);
+
+static void do_ssi_include(struct mg_connection *conn, const char *ssi,
+ char *tag, int include_level) {
+ char file_name[MG_BUF_LEN], path[PATH_MAX], *p;
+ FILE *fp;
+
+ // sscanf() is safe here, since send_ssi_file() also uses buffer
+ // of size MG_BUF_LEN to get the tag. So strlen(tag) is always < MG_BUF_LEN.
+ if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) {
+ // File name is relative to the webserver root
+ (void) mg_snprintf(conn, path, sizeof(path), "%s%c%s",
+ conn->ctx->config[DOCUMENT_ROOT], '/', file_name);
+ } else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1) {
+ // File name is relative to the webserver working directory
+ // or it is absolute system path
+ (void) mg_snprintf(conn, path, sizeof(path), "%s", file_name);
+ } else if (sscanf(tag, " \"%[^\"]\"", file_name) == 1) {
+ // File name is relative to the currect document
+ (void) mg_snprintf(conn, path, sizeof(path), "%s", ssi);
+ if ((p = strrchr(path, '/')) != NULL) {
+ p[1] = '\0';
+ }
+ (void) mg_snprintf(conn, path + strlen(path),
+ sizeof(path) - strlen(path), "%s", file_name);
+ } else {
+ cry(conn, "Bad SSI #include: [%s]", tag);
+ return;
+ }
+
+ if ((fp = mg_fopen(path, "rb")) == NULL) {
+ cry(conn, "Cannot open SSI #include: [%s]: fopen(%s): %s",
+ tag, path, strerror(ERRNO));
+ } else {
+ set_close_on_exec(fileno(fp));
+ if (match_prefix(conn->ctx->config[SSI_EXTENSIONS],
+ strlen(conn->ctx->config[SSI_EXTENSIONS]), path) > 0) {
+ send_ssi_file(conn, path, fp, include_level + 1);
+ } else {
+ send_file_data(conn, fp, INT64_MAX);
+ }
+ (void) fclose(fp);
+ }
+}
+
+#if !defined(NO_POPEN)
+static void do_ssi_exec(struct mg_connection *conn, char *tag) {
+ char cmd[MG_BUF_LEN];
+ FILE *fp;
+
+ if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) {
+ cry(conn, "Bad SSI #exec: [%s]", tag);
+ } else if ((fp = popen(cmd, "r")) == NULL) {
+ cry(conn, "Cannot SSI #exec: [%s]: %s", cmd, strerror(ERRNO));
+ } else {
+ send_file_data(conn, fp, INT64_MAX);
+ (void) pclose(fp);
+ }
+}
+#endif // !NO_POPEN
+
+static void send_ssi_file(struct mg_connection *conn, const char *path,
+ FILE *fp, int include_level) {
+ char buf[MG_BUF_LEN];
+ int ch, len, in_ssi_tag;
+
+ if (include_level > 10) {
+ cry(conn, "SSI #include level is too deep (%s)", path);
+ return;
+ }
+
+ in_ssi_tag = 0;
+ len = 0;
+
+ while ((ch = fgetc(fp)) != EOF) {
+ if (in_ssi_tag && ch == '>') {
+ in_ssi_tag = 0;
+ buf[len++] = (char) ch;
+ buf[len] = '\0';
+ assert(len <= (int) sizeof(buf));
+ if (len < 6 || memcmp(buf, "<!--#", 5) != 0) {
+ // Not an SSI tag, pass it
+ (void) mg_write(conn, buf, (size_t)len);
+ } else {
+ if (!memcmp(buf + 5, "include", 7)) {
+ do_ssi_include(conn, path, buf + 12, include_level);
+#if !defined(NO_POPEN)
+ } else if (!memcmp(buf + 5, "exec", 4)) {
+ do_ssi_exec(conn, buf + 9);
+#endif // !NO_POPEN
+ } else {
+ cry(conn, "%s: unknown SSI " "command: \"%s\"", path, buf);
+ }
+ }
+ len = 0;
+ } else if (in_ssi_tag) {
+ if (len == 5 && memcmp(buf, "<!--#", 5) != 0) {
+ // Not an SSI tag
+ in_ssi_tag = 0;
+ } else if (len == (int) sizeof(buf) - 2) {
+ cry(conn, "%s: SSI tag is too large", path);
+ len = 0;
+ }
+ buf[len++] = ch & 0xff;
+ } else if (ch == '<') {
+ in_ssi_tag = 1;
+ if (len > 0) {
+ (void) mg_write(conn, buf, (size_t)len);
+ }
+ len = 0;
+ buf[len++] = ch & 0xff;
+ } else {
+ buf[len++] = ch & 0xff;
+ if (len == (int) sizeof(buf)) {
+ (void) mg_write(conn, buf, (size_t)len);
+ len = 0;
+ }
+ }
+ }
+
+ // Send the rest of buffered data
+ if (len > 0) {
+ (void) mg_write(conn, buf, (size_t)len);
+ }
+}
+
+static void handle_ssi_file_request(struct mg_connection *conn,
+ const char *path) {
+ FILE *fp;
+
+ if ((fp = mg_fopen(path, "rb")) == NULL) {
+ send_http_error(conn, 500, http_500_error, "fopen(%s): %s", path,
+ strerror(ERRNO));
+ } else {
+ conn->must_close = 1;
+ set_close_on_exec(fileno(fp));
+ mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html\r\nConnection: %s\r\n\r\n",
+ suggest_connection_header(conn));
+ send_ssi_file(conn, path, fp, 0);
+ (void) fclose(fp);
+ }
+}
+
+static void send_options(struct mg_connection *conn) {
+ conn->status_code = 200;
+
+ (void) mg_printf(conn,
+ "HTTP/1.1 200 OK\r\n"
+ "Allow: GET, POST, HEAD, CONNECT, PUT, DELETE, OPTIONS\r\n"
+ "DAV: 1\r\n\r\n");
+}
+
+// Writes PROPFIND properties for a collection element
+static void print_props(struct mg_connection *conn, const char* uri,
+ struct mgstat* st) {
+ char mtime[64];
+ gmt_time_string(mtime, sizeof(mtime), &st->mtime);
+ conn->num_bytes_sent += mg_printf(conn,
+ "<d:response>"
+ "<d:href>%s</d:href>"
+ "<d:propstat>"
+ "<d:prop>"
+ "<d:resourcetype>%s</d:resourcetype>"
+ "<d:getcontentlength>%" INT64_FMT "</d:getcontentlength>"
+ "<d:getlastmodified>%s</d:getlastmodified>"
+ "</d:prop>"
+ "<d:status>HTTP/1.1 200 OK</d:status>"
+ "</d:propstat>"
+ "</d:response>\n",
+ uri,
+ st->is_directory ? "<d:collection/>" : "",
+ st->size,
+ mtime);
+}
+
+static void print_dav_dir_entry(struct de *de, void *data) {
+ char href[PATH_MAX];
+ struct mg_connection *conn = (struct mg_connection *) data;
+ mg_snprintf(conn, href, sizeof(href), "%s%s",
+ conn->request_info.uri, de->file_name);
+ print_props(conn, href, &de->st);
+}
+
+static void handle_propfind(struct mg_connection *conn, const char* path,
+ struct mgstat* st) {
+ const char *depth = mg_get_header(conn, "Depth");
+
+ conn->must_close = 1;
+ conn->status_code = 207;
+ mg_printf(conn, "HTTP/1.1 207 Multi-Status\r\n"
+ "Connection: close\r\n"
+ "Content-Type: text/xml; charset=utf-8\r\n\r\n");
+
+ conn->num_bytes_sent += mg_printf(conn,
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<d:multistatus xmlns:d='DAV:'>\n");
+
+ // Print properties for the requested resource itself
+ print_props(conn, conn->request_info.uri, st);
+
+ // If it is a directory, print directory entries too if Depth is not 0
+ if (st->is_directory &&
+ !mg_strcasecmp(conn->ctx->config[ENABLE_DIRECTORY_LISTING], "yes") &&
+ (depth == NULL || strcmp(depth, "0") != 0)) {
+ scan_directory(conn, path, conn, &print_dav_dir_entry);
+ }
+
+ conn->num_bytes_sent += mg_printf(conn, "%s\n", "</d:multistatus>");
+}
+
+#if defined(USE_WEBSOCKET)
+
+// START OF SHA-1 code
+// Copyright(c) By Steve Reid <steve@edmweb.com>
+#define SHA1HANDSOFF
+#if defined(__sun)
+#include "solarisfixes.h"
+#endif
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+#if BYTE_ORDER == LITTLE_ENDIAN
+#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
+ |(rol(block->l[i],8)&0x00FF00FF))
+#elif BYTE_ORDER == BIG_ENDIAN
+#define blk0(i) block->l[i]
+#else
+#error "Endianness not defined!"
+#endif
+#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
+ ^block->l[(i+2)&15]^block->l[i&15],1))
+#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
+#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
+#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
+
+typedef struct {
+ uint32_t state[5];
+ uint32_t count[2];
+ unsigned char buffer[64];
+} SHA1_CTX;
+
+static void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) {
+ uint32_t a, b, c, d, e;
+ typedef union { unsigned char c[64]; uint32_t l[16]; } CHAR64LONG16;
+
+ CHAR64LONG16 block[1];
+ memcpy(block, buffer, 64);
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+ R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
+ R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
+ R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
+ R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
+ R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+ R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
+ R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
+ R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
+ R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
+ R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
+ R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
+ R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
+ R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
+ R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
+ R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
+ R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
+ R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
+ R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
+ R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
+ R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+ a = b = c = d = e = 0;
+ memset(block, '\0', sizeof(block));
+}
+
+static void SHA1Init(SHA1_CTX* context) {
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xEFCDAB89;
+ context->state[2] = 0x98BADCFE;
+ context->state[3] = 0x10325476;
+ context->state[4] = 0xC3D2E1F0;
+ context->count[0] = context->count[1] = 0;
+}
+
+static void SHA1Update(SHA1_CTX* context, const unsigned char* data,
+ uint32_t len) {
+ uint32_t i, j;
+
+ j = context->count[0];
+ if ((context->count[0] += len << 3) < j)
+ context->count[1]++;
+ context->count[1] += (len>>29);
+ j = (j >> 3) & 63;
+ if ((j + len) > 63) {
+ memcpy(&context->buffer[j], data, (i = 64-j));
+ SHA1Transform(context->state, context->buffer);
+ for ( ; i + 63 < len; i += 64) {
+ SHA1Transform(context->state, &data[i]);
+ }
+ j = 0;
+ }
+ else i = 0;
+ memcpy(&context->buffer[j], &data[i], len - i);
+}
+
+static void SHA1Final(unsigned char digest[20], SHA1_CTX* context) {
+ unsigned i;
+ unsigned char finalcount[8], c;
+
+ for (i = 0; i < 8; i++) {
+ finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]
+ >> ((3-(i & 3)) * 8) ) & 255);
+ }
+ c = 0200;
+ SHA1Update(context, &c, 1);
+ while ((context->count[0] & 504) != 448) {
+ c = 0000;
+ SHA1Update(context, &c, 1);
+ }
+ SHA1Update(context, finalcount, 8);
+ for (i = 0; i < 20; i++) {
+ digest[i] = (unsigned char)
+ ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
+ }
+ memset(context, '\0', sizeof(*context));
+ memset(&finalcount, '\0', sizeof(finalcount));
+}
+// END OF SHA1 CODE
+
+static void base64_encode(const unsigned char *src, int src_len, char *dst) {
+ static const char *b64 =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ int i, j, a, b, c;
+
+ for (i = j = 0; i < src_len; i += 3) {
+ a = src[i];
+ b = i + 1 >= src_len ? 0 : src[i + 1];
+ c = i + 2 >= src_len ? 0 : src[i + 2];
+
+ dst[j++] = b64[a >> 2];
+ dst[j++] = b64[((a & 3) << 4) | (b >> 4)];
+ if (i + 1 < src_len) {
+ dst[j++] = b64[(b & 15) << 2 | (c >> 6)];
+ }
+ if (i + 2 < src_len) {
+ dst[j++] = b64[c & 63];
+ }
+ }
+ while (j % 4 != 0) {
+ dst[j++] = '=';
+ }
+ dst[j++] = '\0';
+}
+
+static void send_websocket_handshake(struct mg_connection *conn) {
+ static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ char buf[100], sha[20], b64_sha[sizeof(sha) * 2];
+ SHA1_CTX sha_ctx;
+
+ mg_snprintf(conn, buf, sizeof(buf), "%s%s",
+ mg_get_header(conn, "Sec-WebSocket-Key"), magic);
+ SHA1Init(&sha_ctx);
+ SHA1Update(&sha_ctx, (unsigned char *) buf, strlen(buf));
+ SHA1Final((unsigned char *) sha, &sha_ctx);
+ base64_encode((unsigned char *) sha, sizeof(sha), b64_sha);
+ mg_printf(conn, "%s%s%s",
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: ", b64_sha, "\r\n\r\n");
+}
+
+static void read_websocket(struct mg_connection *conn) {
+ unsigned char *mask, *buf = (unsigned char *) conn->buf + conn->request_len;
+ int n, len, mask_len, body_len, discard_len;
+
+ for (;;) {
+ if ((body_len = conn->data_len - conn->request_len) >= 2) {
+ len = buf[1] & 127;
+ mask_len = buf[1] & 128 ? 4 : 0;
+ if (len < 126) {
+ conn->content_len = 2 + mask_len + len;
+ mask = buf + 2;
+ } else if (len == 126 && body_len >= 4) {
+ conn->content_len = 2 + mask_len + ((((int) buf[2]) << 8) + buf[3]);
+ mask = buf + 4;
+ } else if (body_len >= 10) {
+ conn->content_len = 2 + mask_len +
+ ((uint64_t) htonl(* (uint32_t *) &buf[2])) << 32 |
+ htonl(* (uint32_t *) &buf[6]);
+ mask = buf + 10;
+ }
+ }
+
+ if (conn->content_len > 0) {
+ if (call_user(conn, MG_WEBSOCKET_MESSAGE) != NULL) {
+ break; // Callback signalled to exit
+ }
+ discard_len = conn->content_len > body_len ? body_len : conn->content_len;
+ memmove(buf, buf + discard_len, conn->data_len - discard_len);
+ conn->data_len -= discard_len;
+ conn->content_len = conn->consumed_content = 0;
+ } else {
+ if (wait_until_socket_is_readable(conn) == 0) {
+ break;
+ }
+ n = pull(NULL, conn, conn->buf + conn->data_len,
+ conn->buf_size - conn->data_len);
+ if (n <= 0) {
+ break;
+ }
+ conn->data_len += n;
+ }
+ }
+}
+
+static void handle_websocket_request(struct mg_connection *conn) {
+ if (strcmp(mg_get_header(conn, "Sec-WebSocket-Version"), "13") != 0) {
+ send_http_error(conn, 426, "Upgrade Required", "%s", "Upgrade Required");
+ } else if (call_user(conn, MG_WEBSOCKET_CONNECT) != NULL) {
+ // Callback has returned non-NULL, do not proceed with handshake
+ } else {
+ send_websocket_handshake(conn);
+ call_user(conn, MG_WEBSOCKET_READY);
+ read_websocket(conn);
+ call_user(conn, MG_WEBSOCKET_CLOSE);
+ }
+}
+
+static int is_websocket_request(const struct mg_connection *conn) {
+ const char *host, *upgrade, *connection, *version, *key;
+
+ host = mg_get_header(conn, "Host");
+ upgrade = mg_get_header(conn, "Upgrade");
+ connection = mg_get_header(conn, "Connection");
+ key = mg_get_header(conn, "Sec-WebSocket-Key");
+ version = mg_get_header(conn, "Sec-WebSocket-Version");
+
+ return host != NULL && upgrade != NULL && connection != NULL &&
+ key != NULL && version != NULL &&
+ !mg_strcasecmp(upgrade, "websocket") &&
+ !mg_strcasecmp(connection, "Upgrade");
+}
+#endif // !USE_WEBSOCKET
+
+static int isbyte(int n) {
+ return n >= 0 && n <= 255;
+}
+
+static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) {
+ int n, a, b, c, d, slash = 32, len = 0;
+
+ if ((sscanf(spec, "%d.%d.%d.%d/%d%n", &a, &b, &c, &d, &slash, &n) == 5 ||
+ sscanf(spec, "%d.%d.%d.%d%n", &a, &b, &c, &d, &n) == 4) &&
+ isbyte(a) && isbyte(b) && isbyte(c) && isbyte(d) &&
+ slash >= 0 && slash < 33) {
+ len = n;
+ *net = ((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | d;
+ *mask = slash ? 0xffffffffU << (32 - slash) : 0;
+ }
+
+ return len;
+}
+
+static int set_throttle(const char *spec, uint32_t remote_ip, const char *uri) {
+ int throttle = 0;
+ struct vec vec, val;
+ uint32_t net, mask;
+ char mult;
+ double v;
+
+ while ((spec = next_option(spec, &vec, &val)) != NULL) {
+ mult = ',';
+ if (sscanf(val.ptr, "%lf%c", &v, &mult) < 1 || v < 0 ||
+ (lowercase(&mult) != 'k' && lowercase(&mult) != 'm' && mult != ',')) {
+ continue;
+ }
+ v *= lowercase(&mult) == 'k' ? 1024 : lowercase(&mult) == 'm' ? 1048576 : 1;
+ if (vec.len == 1 && vec.ptr[0] == '*') {
+ throttle = (int) v;
+ } else if (parse_net(vec.ptr, &net, &mask) > 0) {
+ if ((remote_ip & mask) == net) {
+ throttle = (int) v;
+ }
+ } else if (match_prefix(vec.ptr, vec.len, uri) > 0) {
+ throttle = (int) v;
+ }
+ }
+
+ return throttle;
+}
+
+static uint32_t get_remote_ip(const struct mg_connection *conn) {
+ return ntohl(* (uint32_t *) &conn->client.rsa.sin.sin_addr);
+}
+
+// This is the heart of the Mongoose's logic.
+// This function is called when the request is read, parsed and validated,
+// and Mongoose must decide what action to take: serve a file, or
+// a directory, or call embedded function, etcetera.
+static void handle_request(struct mg_connection *conn) {
+ struct mg_request_info *ri = &conn->request_info;
+ char path[PATH_MAX];
+ int stat_result, uri_len;
+ struct mgstat st;
+
+ if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) {
+ *conn->request_info.query_string++ = '\0';
+ }
+ uri_len = (int) strlen(ri->uri);
+ url_decode(ri->uri, (size_t)uri_len, ri->uri, (size_t)(uri_len + 1), 0);
+ remove_double_dots_and_double_slashes(ri->uri);
+ stat_result = convert_uri_to_file_name(conn, path, sizeof(path), &st);
+ conn->throttle = set_throttle(conn->ctx->config[THROTTLE],
+ get_remote_ip(conn), ri->uri);
+
+ DEBUG_TRACE(("%s", ri->uri));
+ if (!check_authorization(conn, path)) {
+ send_authorization_request(conn);
+#if defined(USE_WEBSOCKET)
+ } else if (is_websocket_request(conn)) {
+ handle_websocket_request(conn);
+#endif
+ } else if (call_user(conn, MG_NEW_REQUEST) != NULL) {
+ // Do nothing, callback has served the request
+ } else if (!strcmp(ri->request_method, "OPTIONS")) {
+ send_options(conn);
+ } else if (conn->ctx->config[DOCUMENT_ROOT] == NULL) {
+ send_http_error(conn, 404, "Not Found", "Not Found");
+ } else if ((!strcmp(ri->request_method, "PUT") ||
+ !strcmp(ri->request_method, "DELETE")) &&
+ (conn->ctx->config[PUT_DELETE_PASSWORDS_FILE] == NULL ||
+ is_authorized_for_put(conn) != 1)) {
+ send_authorization_request(conn);
+ } else if (!strcmp(ri->request_method, "PUT")) {
+ put_file(conn, path);
+ } else if (!strcmp(ri->request_method, "DELETE")) {
+ if (mg_remove(path) == 0) {
+ send_http_error(conn, 200, "OK", "%s", "");
+ } else {
+ send_http_error(conn, 500, http_500_error, "remove(%s): %s", path,
+ strerror(ERRNO));
+ }
+ } else if (stat_result != 0 || must_hide_file(conn, path)) {
+ send_http_error(conn, 404, "Not Found", "%s", "File not found");
+ } else if (st.is_directory && ri->uri[uri_len - 1] != '/') {
+ (void) mg_printf(conn, "HTTP/1.1 301 Moved Permanently\r\n"
+ "Location: %s/\r\n\r\n", ri->uri);
+ } else if (!strcmp(ri->request_method, "PROPFIND")) {
+ handle_propfind(conn, path, &st);
+ } else if (st.is_directory &&
+ !substitute_index_file(conn, path, sizeof(path), &st)) {
+ if (!mg_strcasecmp(conn->ctx->config[ENABLE_DIRECTORY_LISTING], "yes")) {
+ handle_directory_request(conn, path);
+ } else {
+ send_http_error(conn, 403, "Directory Listing Denied",
+ "Directory listing denied");
+ }
+#if !defined(NO_CGI)
+ } else if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
+ strlen(conn->ctx->config[CGI_EXTENSIONS]),
+ path) > 0) {
+ if (strcmp(ri->request_method, "POST") &&
+ strcmp(ri->request_method, "GET")) {
+ send_http_error(conn, 501, "Not Implemented",
+ "Method %s is not implemented", ri->request_method);
+ } else {
+ handle_cgi_request(conn, path);
+ }
+#endif // !NO_CGI
+ } else if (match_prefix(conn->ctx->config[SSI_EXTENSIONS],
+ strlen(conn->ctx->config[SSI_EXTENSIONS]),
+ path) > 0) {
+ handle_ssi_file_request(conn, path);
+ } else if (is_not_modified(conn, &st)) {
+ send_http_error(conn, 304, "Not Modified", "%s", "");
+ } else {
+ handle_file_request(conn, path, &st);
+ }
+}
+
+static void close_all_listening_sockets(struct mg_context *ctx) {
+ struct socket *sp, *tmp;
+ for (sp = ctx->listening_sockets; sp != NULL; sp = tmp) {
+ tmp = sp->next;
+ (void) closesocket(sp->sock);
+ free(sp);
+ }
+}
+
+// Valid listening port specification is: [ip_address:]port[s]
+// Examples: 80, 443s, 127.0.0.1:3128, 1.2.3.4:8080s
+// TODO(lsm): add parsing of the IPv6 address
+static int parse_port_string(const struct vec *vec, struct socket *so) {
+ int a, b, c, d, port, len;
+
+ // MacOS needs that. If we do not zero it, subsequent bind() will fail.
+ // Also, all-zeroes in the socket address means binding to all addresses
+ // for both IPv4 and IPv6 (INADDR_ANY and IN6ADDR_ANY_INIT).
+ memset(so, 0, sizeof(*so));
+
+ if (sscanf(vec->ptr, "%d.%d.%d.%d:%d%n", &a, &b, &c, &d, &port, &len) == 5) {
+ // Bind to a specific IPv4 address
+ so->lsa.sin.sin_addr.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | d);
+ } else if (sscanf(vec->ptr, "%d%n", &port, &len) != 1 ||
+ len <= 0 ||
+ len > (int) vec->len ||
+ (vec->ptr[len] && vec->ptr[len] != 's' && vec->ptr[len] != ',')) {
+ return 0;
+ }
+
+ so->is_ssl = vec->ptr[len] == 's';
+#if defined(USE_IPV6)
+ so->lsa.sin6.sin6_family = AF_INET6;
+ so->lsa.sin6.sin6_port = htons((uint16_t) port);
+#else
+ so->lsa.sin.sin_family = AF_INET;
+ so->lsa.sin.sin_port = htons((uint16_t) port);
+#endif
+
+ return 1;
+}
+
+static int set_ports_option(struct mg_context *ctx) {
+ const char *list = ctx->config[LISTENING_PORTS];
+ int on = 1, success = 1;
+ SOCKET sock;
+ struct vec vec;
+ struct socket so, *listener;
+
+ while (success && (list = next_option(list, &vec, NULL)) != NULL) {
+ if (!parse_port_string(&vec, &so)) {
+ cry(fc(ctx), "%s: %.*s: invalid port spec. Expecting list of: %s",
+ __func__, (int) vec.len, vec.ptr, "[IP_ADDRESS:]PORT[s|p]");
+ success = 0;
+ } else if (so.is_ssl &&
+ (ctx->ssl_ctx == NULL || ctx->config[SSL_CERTIFICATE] == NULL)) {
+ cry(fc(ctx), "Cannot add SSL socket, is -ssl_certificate option set?");
+ success = 0;
+ } else if ((sock = socket(so.lsa.sa.sa_family, SOCK_STREAM, 6)) ==
+ INVALID_SOCKET ||
+ // On Windows, SO_REUSEADDR is recommended only for
+ // broadcast UDP sockets
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *) &on,
+ sizeof(on)) != 0 ||
+ // Set TCP keep-alive. This is needed because if HTTP-level
+ // keep-alive is enabled, and client resets the connection,
+ // server won't get TCP FIN or RST and will keep the connection
+ // open forever. With TCP keep-alive, next keep-alive
+ // handshake will figure out that the client is down and
+ // will close the server end.
+ // Thanks to Igor Klopov who suggested the patch.
+ setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *) &on,
+ sizeof(on)) != 0 ||
+ bind(sock, &so.lsa.sa, sizeof(so.lsa)) != 0 ||
+ listen(sock, SOMAXCONN) != 0) {
+ closesocket(sock);
+ cry(fc(ctx), "%s: cannot bind to %.*s: %s", __func__,
+ (int) vec.len, vec.ptr, strerror(ERRNO));
+ success = 0;
+ } else if ((listener = (struct socket *)
+ calloc(1, sizeof(*listener))) == NULL) {
+ // NOTE(lsm): order is important: call cry before closesocket(),
+ // cause closesocket() alters the errno.
+ cry(fc(ctx), "%s: %s", __func__, strerror(ERRNO));
+ closesocket(sock);
+ success = 0;
+ } else {
+ *listener = so;
+ listener->sock = sock;
+ set_close_on_exec(listener->sock);
+ listener->next = ctx->listening_sockets;
+ ctx->listening_sockets = listener;
+ }
+ }
+
+ if (!success) {
+ close_all_listening_sockets(ctx);
+ }
+
+ return success;
+}
+
+static void log_header(const struct mg_connection *conn, const char *header,
+ FILE *fp) {
+ const char *header_value;
+
+ if ((header_value = mg_get_header(conn, header)) == NULL) {
+ (void) fprintf(fp, "%s", " -");
+ } else {
+ (void) fprintf(fp, " \"%s\"", header_value);
+ }
+}
+
+static void log_access(const struct mg_connection *conn) {
+ const struct mg_request_info *ri;
+ FILE *fp;
+ char date[64], src_addr[20];
+
+ fp = conn->ctx->config[ACCESS_LOG_FILE] == NULL ? NULL :
+ mg_fopen(conn->ctx->config[ACCESS_LOG_FILE], "a+");
+
+ if (fp == NULL)
+ return;
+
+ strftime(date, sizeof(date), "%d/%b/%Y:%H:%M:%S %z",
+ localtime(&conn->birth_time));
+
+ ri = &conn->request_info;
+ flockfile(fp);
+
+ sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
+ fprintf(fp, "%s - %s [%s] \"%s %s HTTP/%s\" %d %" INT64_FMT,
+ src_addr, ri->remote_user == NULL ? "-" : ri->remote_user, date,
+ ri->request_method ? ri->request_method : "-",
+ ri->uri ? ri->uri : "-", ri->http_version,
+ conn->status_code, conn->num_bytes_sent);
+ log_header(conn, "Referer", fp);
+ log_header(conn, "User-Agent", fp);
+ fputc('\n', fp);
+ fflush(fp);
+
+ funlockfile(fp);
+ fclose(fp);
+}
+
+// Verify given socket address against the ACL.
+// Return -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed.
+static int check_acl(struct mg_context *ctx, uint32_t remote_ip) {
+ int allowed, flag;
+ uint32_t net, mask;
+ struct vec vec;
+ const char *list = ctx->config[ACCESS_CONTROL_LIST];
+
+ // If any ACL is set, deny by default
+ allowed = list == NULL ? '+' : '-';
+
+ while ((list = next_option(list, &vec, NULL)) != NULL) {
+ flag = vec.ptr[0];
+ if ((flag != '+' && flag != '-') ||
+ parse_net(&vec.ptr[1], &net, &mask) == 0) {
+ cry(fc(ctx), "%s: subnet must be [+|-]x.x.x.x[/x]", __func__);
+ return -1;
+ }
+
+ if (net == (remote_ip & mask)) {
+ allowed = flag;
+ }
+ }
+
+ return allowed == '+';
+}
+
+static void add_to_set(SOCKET fd, fd_set *set, int *max_fd) {
+ FD_SET(fd, set);
+ if (fd > (SOCKET) *max_fd) {
+ *max_fd = (int) fd;
+ }
+}
+
+#if !defined(_WIN32)
+static int set_uid_option(struct mg_context *ctx) {
+ struct passwd *pw;
+ const char *uid = ctx->config[RUN_AS_USER];
+ int success = 0;
+
+ if (uid == NULL) {
+ success = 1;
+ } else {
+ if ((pw = getpwnam(uid)) == NULL) {
+ cry(fc(ctx), "%s: unknown user [%s]", __func__, uid);
+ } else if (setgid(pw->pw_gid) == -1) {
+ cry(fc(ctx), "%s: setgid(%s): %s", __func__, uid, strerror(errno));
+ } else if (setuid(pw->pw_uid) == -1) {
+ cry(fc(ctx), "%s: setuid(%s): %s", __func__, uid, strerror(errno));
+ } else {
+ success = 1;
+ }
+ }
+
+ return success;
+}
+#endif // !_WIN32
+
+#if !defined(NO_SSL)
+static pthread_mutex_t *ssl_mutexes;
+
+// Return OpenSSL error message
+static const char *ssl_error(void) {
+ unsigned long err;
+ err = ERR_get_error();
+ return err == 0 ? "" : ERR_error_string(err, NULL);
+}
+
+static void ssl_locking_callback(int mode, int mutex_num, const char *file,
+ int line) {
+ line = 0; // Unused
+ file = NULL; // Unused
+
+ if (mode & CRYPTO_LOCK) {
+ (void) pthread_mutex_lock(&ssl_mutexes[mutex_num]);
+ } else {
+ (void) pthread_mutex_unlock(&ssl_mutexes[mutex_num]);
+ }
+}
+
+static unsigned long ssl_id_callback(void) {
+ return (unsigned long) pthread_self();
+}
+
+#if !defined(NO_SSL_DL)
+static int load_dll(struct mg_context *ctx, const char *dll_name,
+ struct ssl_func *sw) {
+ union {void *p; void (*fp)(void);} u;
+ void *dll_handle;
+ struct ssl_func *fp;
+
+ if ((dll_handle = dlopen(dll_name, RTLD_LAZY)) == NULL) {
+ cry(fc(ctx), "%s: cannot load %s", __func__, dll_name);
+ return 0;
+ }
+
+ for (fp = sw; fp->name != NULL; fp++) {
+#ifdef _WIN32
+ // GetProcAddress() returns pointer to function
+ u.fp = (void (*)(void)) dlsym(dll_handle, fp->name);
+#else
+ // dlsym() on UNIX returns void *. ISO C forbids casts of data pointers to
+ // function pointers. We need to use a union to make a cast.
+ u.p = dlsym(dll_handle, fp->name);
+#endif // _WIN32
+ if (u.fp == NULL) {
+ cry(fc(ctx), "%s: %s: cannot find %s", __func__, dll_name, fp->name);
+ return 0;
+ } else {
+ fp->ptr = u.fp;
+ }
+ }
+
+ return 1;
+}
+#endif // NO_SSL_DL
+
+// Dynamically load SSL library. Set up ctx->ssl_ctx pointer.
+static int set_ssl_option(struct mg_context *ctx) {
+ int i, size;
+ const char *pem;
+
+ // If PEM file is not specified, skip SSL initialization.
+ if ((pem = ctx->config[SSL_CERTIFICATE]) == NULL) {
+ return 1;
+ }
+
+#if !defined(NO_SSL_DL)
+ if (!load_dll(ctx, SSL_LIB, ssl_sw) ||
+ !load_dll(ctx, CRYPTO_LIB, crypto_sw)) {
+ return 0;
+ }
+#endif // NO_SSL_DL
+
+ // Initialize SSL crap
+ SSL_library_init();
+ SSL_load_error_strings();
+
+ if ((ctx->client_ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) {
+ cry(fc(ctx), "SSL_CTX_new (client) error: %s", ssl_error());
+ }
+
+ if ((ctx->ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) {
+ cry(fc(ctx), "SSL_CTX_new (server) error: %s", ssl_error());
+ return 0;
+ }
+
+ // If user callback returned non-NULL, that means that user callback has
+ // set up certificate itself. In this case, skip sertificate setting.
+ if (call_user(fc(ctx), MG_INIT_SSL) == NULL &&
+ (SSL_CTX_use_certificate_file(ctx->ssl_ctx, pem, SSL_FILETYPE_PEM) == 0 ||
+ SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, pem, SSL_FILETYPE_PEM) == 0)) {
+ cry(fc(ctx), "%s: cannot open %s: %s", __func__, pem, ssl_error());
+ return 0;
+ }
+
+ if (pem != NULL) {
+ (void) SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, pem);
+ }
+
+ // Initialize locking callbacks, needed for thread safety.
+ // http://www.openssl.org/support/faq.html#PROG1
+ size = sizeof(pthread_mutex_t) * CRYPTO_num_locks();
+ if ((ssl_mutexes = (pthread_mutex_t *) malloc((size_t)size)) == NULL) {
+ cry(fc(ctx), "%s: cannot allocate mutexes: %s", __func__, ssl_error());
+ return 0;
+ }
+
+ for (i = 0; i < CRYPTO_num_locks(); i++) {
+ pthread_mutex_init(&ssl_mutexes[i], NULL);
+ }
+
+ CRYPTO_set_locking_callback(&ssl_locking_callback);
+ CRYPTO_set_id_callback(&ssl_id_callback);
+
+ return 1;
+}
+
+static void uninitialize_ssl(struct mg_context *ctx) {
+ int i;
+ if (ctx->ssl_ctx != NULL) {
+ CRYPTO_set_locking_callback(NULL);
+ for (i = 0; i < CRYPTO_num_locks(); i++) {
+ pthread_mutex_destroy(&ssl_mutexes[i]);
+ }
+ CRYPTO_set_locking_callback(NULL);
+ CRYPTO_set_id_callback(NULL);
+ }
+}
+#endif // !NO_SSL
+
+static int set_gpass_option(struct mg_context *ctx) {
+ struct mgstat mgstat;
+ const char *path = ctx->config[GLOBAL_PASSWORDS_FILE];
+ return path == NULL || mg_stat(path, &mgstat) == 0;
+}
+
+static int set_acl_option(struct mg_context *ctx) {
+ return check_acl(ctx, (uint32_t) 0x7f000001UL) != -1;
+}
+
+static void reset_per_request_attributes(struct mg_connection *conn) {
+ conn->path_info = conn->log_message = NULL;
+ conn->num_bytes_sent = conn->consumed_content = 0;
+ conn->status_code = -1;
+ conn->must_close = conn->request_len = conn->throttle = 0;
+}
+
+static void close_socket_gracefully(struct mg_connection *conn) {
+ char buf[MG_BUF_LEN];
+ struct linger linger;
+ int n, sock = conn->client.sock;
+
+ // Set linger option to avoid socket hanging out after close. This prevent
+ // ephemeral port exhaust problem under high QPS.
+ linger.l_onoff = 1;
+ linger.l_linger = 1;
+ setsockopt(sock, SOL_SOCKET, SO_LINGER, (char *) &linger, sizeof(linger));
+
+ // Send FIN to the client
+ (void) shutdown(sock, SHUT_WR);
+ set_non_blocking_mode(sock);
+
+ // Read and discard pending incoming data. If we do not do that and close the
+ // socket, the data in the send buffer may be discarded. This
+ // behaviour is seen on Windows, when client keeps sending data
+ // when server decides to close the connection; then when client
+ // does recv() it gets no data back.
+ do {
+ n = pull(NULL, conn, buf, sizeof(buf));
+ } while (n > 0);
+
+ // Now we know that our FIN is ACK-ed, safe to close
+ (void) closesocket(sock);
+}
+
+static void close_connection(struct mg_connection *conn) {
+ if (conn->ssl) {
+ SSL_free(conn->ssl);
+ conn->ssl = NULL;
+ }
+
+ if (conn->client.sock != INVALID_SOCKET) {
+ close_socket_gracefully(conn);
+ }
+}
+
+void mg_close_connection(struct mg_connection *conn) {
+ close_connection(conn);
+ free(conn);
+}
+
+struct mg_connection *mg_connect(struct mg_context *ctx,
+ const char *host, int port, int use_ssl) {
+ struct mg_connection *newconn = NULL;
+ struct sockaddr_in sin;
+ struct hostent *he;
+ int sock;
+
+ if (use_ssl && (ctx == NULL || ctx->client_ssl_ctx == NULL)) {
+ cry(fc(ctx), "%s: SSL is not initialized", __func__);
+ } else if ((he = gethostbyname(host)) == NULL) {
+ cry(fc(ctx), "%s: gethostbyname(%s): %s", __func__, host, strerror(ERRNO));
+ } else if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
+ cry(fc(ctx), "%s: socket: %s", __func__, strerror(ERRNO));
+ } else {
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons((uint16_t) port);
+ sin.sin_addr = * (struct in_addr *) he->h_addr_list[0];
+ if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0) {
+ cry(fc(ctx), "%s: connect(%s:%d): %s", __func__, host, port,
+ strerror(ERRNO));
+ closesocket(sock);
+ } else if ((newconn = (struct mg_connection *)
+ calloc(1, sizeof(*newconn))) == NULL) {
+ cry(fc(ctx), "%s: calloc: %s", __func__, strerror(ERRNO));
+ closesocket(sock);
+ } else {
+ newconn->ctx = ctx;
+ newconn->client.sock = sock;
+ newconn->client.rsa.sin = sin;
+ newconn->client.is_ssl = use_ssl;
+ if (use_ssl) {
+ sslize(newconn, ctx->client_ssl_ctx, SSL_connect);
+ }
+ }
+ }
+
+ return newconn;
+}
+
+FILE *mg_fetch(struct mg_context *ctx, const char *url, const char *path,
+ char *buf, size_t buf_len, struct mg_request_info *ri) {
+ struct mg_connection *newconn;
+ int n, req_length, data_length, port;
+ char host[1025], proto[10], buf2[MG_BUF_LEN];
+ FILE *fp = NULL;
+
+ if (sscanf(url, "%9[htps]://%1024[^:]:%d/%n", proto, host, &port, &n) == 3) {
+ } else if (sscanf(url, "%9[htps]://%1024[^/]/%n", proto, host, &n) == 2) {
+ port = mg_strcasecmp(proto, "https") == 0 ? 443 : 80;
+ } else {
+ cry(fc(ctx), "%s: invalid URL: [%s]", __func__, url);
+ return NULL;
+ }
+
+ if ((newconn = mg_connect(ctx, host, port,
+ !strcmp(proto, "https"))) == NULL) {
+ cry(fc(ctx), "%s: mg_connect(%s): %s", __func__, url, strerror(ERRNO));
+ } else {
+ mg_printf(newconn, "GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n", url + n, host);
+ data_length = 0;
+ req_length = read_request(NULL, newconn, buf, buf_len, &data_length);
+ if (req_length <= 0) {
+ cry(fc(ctx), "%s(%s): invalid HTTP reply", __func__, url);
+ } else if (parse_http_response(buf, req_length, ri) <= 0) {
+ cry(fc(ctx), "%s(%s): cannot parse HTTP headers", __func__, url);
+ } else if ((fp = fopen(path, "w+b")) == NULL) {
+ cry(fc(ctx), "%s: fopen(%s): %s", __func__, path, strerror(ERRNO));
+ } else {
+ // Write chunk of data that may be in the user's buffer
+ data_length -= req_length;
+ if (data_length > 0 &&
+ fwrite(buf + req_length, 1, data_length, fp) != (size_t) data_length) {
+ cry(fc(ctx), "%s: fwrite(%s): %s", __func__, path, strerror(ERRNO));
+ fclose(fp);
+ fp = NULL;
+ }
+ // Read the rest of the response and write it to the file. Do not use
+ // mg_read() cause we didn't set newconn->content_len properly.
+ while (fp && (data_length = pull(0, newconn, buf2, sizeof(buf2))) > 0) {
+ if (fwrite(buf2, 1, data_length, fp) != (size_t) data_length) {
+ cry(fc(ctx), "%s: fwrite(%s): %s", __func__, path, strerror(ERRNO));
+ fclose(fp);
+ fp = NULL;
+ break;
+ }
+ }
+ }
+ mg_close_connection(newconn);
+ }
+
+ return fp;
+}
+
+static int is_valid_uri(const char *uri) {
+ // Conform to http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
+ // URI can be an asterisk (*) or should start with slash.
+ return uri[0] == '/' || (uri[0] == '*' && uri[1] == '\0');
+}
+
+static void process_new_connection(struct mg_connection *conn) {
+ struct mg_request_info *ri = &conn->request_info;
+ int keep_alive_enabled, discard_len;
+ const char *cl;
+
+ keep_alive_enabled = !strcmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes");
+
+ do {
+ reset_per_request_attributes(conn);
+ conn->request_len = read_request(NULL, conn, conn->buf, conn->buf_size,
+ &conn->data_len);
+ assert(conn->request_len < 0 || conn->data_len >= conn->request_len);
+ if (conn->request_len == 0 && conn->data_len == conn->buf_size) {
+ send_http_error(conn, 413, "Request Too Large", "%s", "");
+ return;
+ } if (conn->request_len <= 0) {
+ return; // Remote end closed the connection
+ }
+ if (parse_http_request(conn->buf, conn->buf_size, ri) <= 0 ||
+ !is_valid_uri(ri->uri)) {
+ // Do not put garbage in the access log, just send it back to the client
+ send_http_error(conn, 400, "Bad Request",
+ "Cannot parse HTTP request: [%.*s]", conn->data_len, conn->buf);
+ conn->must_close = 1;
+ } else if (strcmp(ri->http_version, "1.0") &&
+ strcmp(ri->http_version, "1.1")) {
+ // Request seems valid, but HTTP version is strange
+ send_http_error(conn, 505, "HTTP version not supported", "%s", "");
+ log_access(conn);
+ } else {
+ // Request is valid, handle it
+ if ((cl = get_header(ri, "Content-Length")) != NULL) {
+ conn->content_len = strtoll(cl, NULL, 10);
+ } else if (!mg_strcasecmp(ri->request_method, "POST") ||
+ !mg_strcasecmp(ri->request_method, "PUT")) {
+ conn->content_len = -1;
+ } else {
+ conn->content_len = 0;
+ }
+ conn->birth_time = time(NULL);
+ handle_request(conn);
+ call_user(conn, MG_REQUEST_COMPLETE);
+ log_access(conn);
+ }
+ if (ri->remote_user != NULL) {
+ free((void *) ri->remote_user);
+ }
+
+ // Discard all buffered data for this request
+ discard_len = conn->content_len >= 0 &&
+ conn->request_len + conn->content_len < (int64_t) conn->data_len ?
+ (int) (conn->request_len + conn->content_len) : conn->data_len;
+ memmove(conn->buf, conn->buf + discard_len, conn->data_len - discard_len);
+ conn->data_len -= discard_len;
+ assert(conn->data_len >= 0);
+ assert(conn->data_len <= conn->buf_size);
+
+ } while (conn->ctx->stop_flag == 0 &&
+ keep_alive_enabled &&
+ conn->content_len >= 0 &&
+ should_keep_alive(conn));
+}
+
+// Worker threads take accepted socket from the queue
+static int consume_socket(struct mg_context *ctx, struct socket *sp) {
+ (void) pthread_mutex_lock(&ctx->mutex);
+ DEBUG_TRACE(("going idle"));
+
+ // If the queue is empty, wait. We're idle at this point.
+ while (ctx->sq_head == ctx->sq_tail && ctx->stop_flag == 0) {
+ pthread_cond_wait(&ctx->sq_full, &ctx->mutex);
+ }
+
+ // If we're stopping, sq_head may be equal to sq_tail.
+ if (ctx->sq_head > ctx->sq_tail) {
+ // Copy socket from the queue and increment tail
+ *sp = ctx->queue[ctx->sq_tail % ARRAY_SIZE(ctx->queue)];
+ ctx->sq_tail++;
+ DEBUG_TRACE(("grabbed socket %d, going busy", sp->sock));
+
+ // Wrap pointers if needed
+ while (ctx->sq_tail > (int) ARRAY_SIZE(ctx->queue)) {
+ ctx->sq_tail -= ARRAY_SIZE(ctx->queue);
+ ctx->sq_head -= ARRAY_SIZE(ctx->queue);
+ }
+ }
+
+ (void) pthread_cond_signal(&ctx->sq_empty);
+ (void) pthread_mutex_unlock(&ctx->mutex);
+
+ return !ctx->stop_flag;
+}
+
+static void worker_thread(struct mg_context *ctx) {
+ struct mg_connection *conn;
+
+ conn = (struct mg_connection *) calloc(1, sizeof(*conn) + MAX_REQUEST_SIZE);
+ if (conn == NULL) {
+ cry(fc(ctx), "%s", "Cannot create new connection struct, OOM");
+ } else {
+ conn->buf_size = MAX_REQUEST_SIZE;
+ conn->buf = (char *) (conn + 1);
+
+ // Call consume_socket() even when ctx->stop_flag > 0, to let it signal
+ // sq_empty condvar to wake up the master waiting in produce_socket()
+ while (consume_socket(ctx, &conn->client)) {
+ conn->birth_time = time(NULL);
+ conn->ctx = ctx;
+
+ // Fill in IP, port info early so even if SSL setup below fails,
+ // error handler would have the corresponding info.
+ // Thanks to Johannes Winkelmann for the patch.
+ // TODO(lsm): Fix IPv6 case
+ conn->request_info.remote_port = ntohs(conn->client.rsa.sin.sin_port);
+ memcpy(&conn->request_info.remote_ip,
+ &conn->client.rsa.sin.sin_addr.s_addr, 4);
+ conn->request_info.remote_ip = ntohl(conn->request_info.remote_ip);
+ conn->request_info.is_ssl = conn->client.is_ssl;
+
+ if (!conn->client.is_ssl ||
+ (conn->client.is_ssl &&
+ sslize(conn, conn->ctx->ssl_ctx, SSL_accept))) {
+ process_new_connection(conn);
+ }
+
+ close_connection(conn);
+ }
+ free(conn);
+ }
+
+ // Signal master that we're done with connection and exiting
+ (void) pthread_mutex_lock(&ctx->mutex);
+ ctx->num_threads--;
+ (void) pthread_cond_signal(&ctx->cond);
+ assert(ctx->num_threads >= 0);
+ (void) pthread_mutex_unlock(&ctx->mutex);
+
+ DEBUG_TRACE(("exiting"));
+}
+
+// Master thread adds accepted socket to a queue
+static void produce_socket(struct mg_context *ctx, const struct socket *sp) {
+ (void) pthread_mutex_lock(&ctx->mutex);
+
+ // If the queue is full, wait
+ while (ctx->stop_flag == 0 &&
+ ctx->sq_head - ctx->sq_tail >= (int) ARRAY_SIZE(ctx->queue)) {
+ (void) pthread_cond_wait(&ctx->sq_empty, &ctx->mutex);
+ }
+
+ if (ctx->sq_head - ctx->sq_tail < (int) ARRAY_SIZE(ctx->queue)) {
+ // Copy socket to the queue and increment head
+ ctx->queue[ctx->sq_head % ARRAY_SIZE(ctx->queue)] = *sp;
+ ctx->sq_head++;
+ DEBUG_TRACE(("queued socket %d", sp->sock));
+ }
+
+ (void) pthread_cond_signal(&ctx->sq_full);
+ (void) pthread_mutex_unlock(&ctx->mutex);
+}
+
+static void accept_new_connection(const struct socket *listener,
+ struct mg_context *ctx) {
+ struct socket accepted;
+ char src_addr[20];
+ socklen_t len;
+ int allowed;
+
+ len = sizeof(accepted.rsa);
+ accepted.lsa = listener->lsa;
+ accepted.sock = accept(listener->sock, &accepted.rsa.sa, &len);
+ if (accepted.sock != INVALID_SOCKET) {
+ allowed = check_acl(ctx, ntohl(* (uint32_t *) &accepted.rsa.sin.sin_addr));
+ if (allowed) {
+ // Put accepted socket structure into the queue
+ DEBUG_TRACE(("accepted socket %d", accepted.sock));
+ accepted.is_ssl = listener->is_ssl;
+ produce_socket(ctx, &accepted);
+ } else {
+ sockaddr_to_string(src_addr, sizeof(src_addr), &accepted.rsa);
+ cry(fc(ctx), "%s: %s is not allowed to connect", __func__, src_addr);
+ (void) closesocket(accepted.sock);
+ }
+ }
+}
+
+static void master_thread(struct mg_context *ctx) {
+ fd_set read_set;
+ struct timeval tv;
+ struct socket *sp;
+ int max_fd;
+
+ // Increase priority of the master thread
+#if defined(_WIN32)
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
+#endif
+
+#if defined(ISSUE_317)
+ struct sched_param sched_param;
+ sched_param.sched_priority = sched_get_priority_max(SCHED_RR);
+ pthread_setschedparam(pthread_self(), SCHED_RR, &sched_param);
+#endif
+
+ while (ctx->stop_flag == 0) {
+ FD_ZERO(&read_set);
+ max_fd = -1;
+
+ // Add listening sockets to the read set
+ for (sp = ctx->listening_sockets; sp != NULL; sp = sp->next) {
+ add_to_set(sp->sock, &read_set, &max_fd);
+ }
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 200 * 1000;
+
+ if (select(max_fd + 1, &read_set, NULL, NULL, &tv) < 0) {
+#ifdef _WIN32
+ // On windows, if read_set and write_set are empty,
+ // select() returns "Invalid parameter" error
+ // (at least on my Windows XP Pro). So in this case, we sleep here.
+ mg_sleep(1000);
+#endif // _WIN32
+ } else {
+ for (sp = ctx->listening_sockets; sp != NULL; sp = sp->next) {
+ if (ctx->stop_flag == 0 && FD_ISSET(sp->sock, &read_set)) {
+ accept_new_connection(sp, ctx);
+ }
+ }
+ }
+ }
+ DEBUG_TRACE(("stopping workers"));
+
+ // Stop signal received: somebody called mg_stop. Quit.
+ close_all_listening_sockets(ctx);
+
+ // Wakeup workers that are waiting for connections to handle.
+ pthread_cond_broadcast(&ctx->sq_full);
+
+ // Wait until all threads finish
+ (void) pthread_mutex_lock(&ctx->mutex);
+ while (ctx->num_threads > 0) {
+ (void) pthread_cond_wait(&ctx->cond, &ctx->mutex);
+ }
+ (void) pthread_mutex_unlock(&ctx->mutex);
+
+ // All threads exited, no sync is needed. Destroy mutex and condvars
+ (void) pthread_mutex_destroy(&ctx->mutex);
+ (void) pthread_cond_destroy(&ctx->cond);
+ (void) pthread_cond_destroy(&ctx->sq_empty);
+ (void) pthread_cond_destroy(&ctx->sq_full);
+
+#if !defined(NO_SSL)
+ uninitialize_ssl(ctx);
+#endif
+ DEBUG_TRACE(("exiting"));
+
+ // Signal mg_stop() that we're done.
+ // WARNING: This must be the very last thing this
+ // thread does, as ctx becomes invalid after this line.
+ ctx->stop_flag = 2;
+}
+
+static void free_context(struct mg_context *ctx) {
+ int i;
+
+ // Deallocate config parameters
+ for (i = 0; i < NUM_OPTIONS; i++) {
+ if (ctx->config[i] != NULL)
+ free(ctx->config[i]);
+ }
+
+ // Deallocate SSL context
+ if (ctx->ssl_ctx != NULL) {
+ SSL_CTX_free(ctx->ssl_ctx);
+ }
+ if (ctx->client_ssl_ctx != NULL) {
+ SSL_CTX_free(ctx->client_ssl_ctx);
+ }
+#ifndef NO_SSL
+ if (ssl_mutexes != NULL) {
+ free(ssl_mutexes);
+ ssl_mutexes = NULL;
+ }
+#endif // !NO_SSL
+
+ // Deallocate context itself
+ free(ctx);
+}
+
+void mg_stop(struct mg_context *ctx) {
+ ctx->stop_flag = 1;
+
+ // Wait until mg_fini() stops
+ while (ctx->stop_flag != 2) {
+ (void) mg_sleep(10);
+ }
+ free_context(ctx);
+
+#if defined(_WIN32) && !defined(__SYMBIAN32__)
+ (void) WSACleanup();
+#endif // _WIN32
+}
+
+struct mg_context *mg_start(mg_callback_t user_callback, void *user_data,
+ const char **options) {
+ struct mg_context *ctx;
+ const char *name, *value, *default_value;
+ int i;
+
+#if defined(_WIN32) && !defined(__SYMBIAN32__)
+ WSADATA data;
+ WSAStartup(MAKEWORD(2,2), &data);
+ InitializeCriticalSection(&global_log_file_lock);
+#endif // _WIN32
+
+ // Allocate context and initialize reasonable general case defaults.
+ // TODO(lsm): do proper error handling here.
+ if ((ctx = (struct mg_context *) calloc(1, sizeof(*ctx))) == NULL) {
+ return NULL;
+ }
+ ctx->user_callback = user_callback;
+ ctx->user_data = user_data;
+
+ while (options && (name = *options++) != NULL) {
+ if ((i = get_option_index(name)) == -1) {
+ cry(fc(ctx), "Invalid option: %s", name);
+ free_context(ctx);
+ return NULL;
+ } else if ((value = *options++) == NULL) {
+ cry(fc(ctx), "%s: option value cannot be NULL", name);
+ free_context(ctx);
+ return NULL;
+ }
+ if (ctx->config[i] != NULL) {
+ cry(fc(ctx), "warning: %s: duplicate option", name);
+ }
+ ctx->config[i] = mg_strdup(value);
+ DEBUG_TRACE(("[%s] -> [%s]", name, value));
+ }
+
+ // Set default value if needed
+ for (i = 0; config_options[i * ENTRIES_PER_CONFIG_OPTION] != NULL; i++) {
+ default_value = config_options[i * ENTRIES_PER_CONFIG_OPTION + 2];
+ if (ctx->config[i] == NULL && default_value != NULL) {
+ ctx->config[i] = mg_strdup(default_value);
+ DEBUG_TRACE(("Setting default: [%s] -> [%s]",
+ config_options[i * ENTRIES_PER_CONFIG_OPTION + 1],
+ default_value));
+ }
+ }
+
+ // NOTE(lsm): order is important here. SSL certificates must
+ // be initialized before listening ports. UID must be set last.
+ if (!set_gpass_option(ctx) ||
+#if !defined(NO_SSL)
+ !set_ssl_option(ctx) ||
+#endif
+ !set_ports_option(ctx) ||
+#if !defined(_WIN32)
+ !set_uid_option(ctx) ||
+#endif
+ !set_acl_option(ctx)) {
+ free_context(ctx);
+ return NULL;
+ }
+
+#if !defined(_WIN32) && !defined(__SYMBIAN32__)
+ // Ignore SIGPIPE signal, so if browser cancels the request, it
+ // won't kill the whole process.
+ (void) signal(SIGPIPE, SIG_IGN);
+ // Also ignoring SIGCHLD to let the OS to reap zombies properly.
+ (void) signal(SIGCHLD, SIG_IGN);
+#endif // !_WIN32
+
+ (void) pthread_mutex_init(&ctx->mutex, NULL);
+ (void) pthread_cond_init(&ctx->cond, NULL);
+ (void) pthread_cond_init(&ctx->sq_empty, NULL);
+ (void) pthread_cond_init(&ctx->sq_full, NULL);
+
+ // Start master (listening) thread
+ mg_start_thread((mg_thread_func_t) master_thread, ctx);
+
+ // Start worker threads
+ for (i = 0; i < atoi(ctx->config[NUM_THREADS]); i++) {
+ if (mg_start_thread((mg_thread_func_t) worker_thread, ctx) != 0) {
+ cry(fc(ctx), "Cannot start worker thread: %d", ERRNO);
+ } else {
+ ctx->num_threads++;
+ }
+ }
+
+ return ctx;
+}
diff --git a/spectro/mongoose.h b/spectro/mongoose.h
new file mode 100644
index 0000000..708cf73
--- /dev/null
+++ b/spectro/mongoose.h
@@ -0,0 +1,308 @@
+// Copyright (c) 2004-2012 Sergey Lyubka
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef MONGOOSE_HEADER_INCLUDED
+#define MONGOOSE_HEADER_INCLUDED
+
+#include <stdio.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+struct mg_context; // Handle for the HTTP service itself
+struct mg_connection; // Handle for the individual connection
+
+
+// This structure contains information about the HTTP request.
+struct mg_request_info {
+ char *request_method; // "GET", "POST", etc
+ char *uri; // URL-decoded URI
+ char *http_version; // E.g. "1.0", "1.1"
+ char *query_string; // URL part after '?' (not including '?') or NULL
+ char *remote_user; // Authenticated user, or NULL if no auth used
+ long remote_ip; // Client's IP address
+ int remote_port; // Client's port
+ int is_ssl; // 1 if SSL-ed, 0 if not
+ int num_headers; // Number of headers
+ struct mg_header {
+ char *name; // HTTP header name
+ char *value; // HTTP header value
+ } http_headers[64]; // Maximum 64 headers
+};
+
+
+// Various events on which user-defined function is called by Mongoose.
+enum mg_event {
+ MG_NEW_REQUEST, // New HTTP request has arrived from the client
+ MG_REQUEST_COMPLETE, // Mongoose has finished handling the request
+ MG_HTTP_ERROR, // HTTP error must be returned to the client
+ MG_EVENT_LOG, // Mongoose logs an event, request_info.log_message
+ MG_INIT_SSL, // SSL initialization, sent before certificate setup
+ MG_WEBSOCKET_CONNECT, // Sent on HTTP connect, before websocket handshake.
+ // If user callback returns NULL, then mongoose proceeds
+ // with handshake, otherwise it closes the connection.
+ MG_WEBSOCKET_READY, // Handshake has been successfully completed.
+ MG_WEBSOCKET_MESSAGE, // Incoming message from the client
+ MG_WEBSOCKET_CLOSE, // Client has closed the connection
+};
+
+
+// Prototype for the user-defined function. Mongoose calls this function
+// on every MG_* event.
+//
+// Parameters:
+// event: which event has been triggered.
+// conn: opaque connection handler. Could be used to read, write data to the
+// client, etc. See functions below that have "mg_connection *" arg.
+//
+// Return:
+// If handler returns non-NULL, that means that handler has processed the
+// request by sending appropriate HTTP reply to the client. Mongoose treats
+// the request as served.
+// If handler returns NULL, that means that handler has not processed
+// the request. Handler must not send any data to the client in this case.
+// Mongoose proceeds with request handling as if nothing happened.
+typedef void *(*mg_callback_t)(enum mg_event event, struct mg_connection *conn);
+
+
+// Start web server.
+//
+// Parameters:
+// callback: user defined event handling function or NULL.
+// options: NULL terminated list of option_name, option_value pairs that
+// specify Mongoose configuration parameters.
+//
+// Side-effects: on UNIX, ignores SIGCHLD and SIGPIPE signals. If custom
+// processing is required for these, signal handlers must be set up
+// after calling mg_start().
+//
+//
+// Example:
+// const char *options[] = {
+// "document_root", "/var/www",
+// "listening_ports", "80,443s",
+// NULL
+// };
+// struct mg_context *ctx = mg_start(&my_func, NULL, options);
+//
+// Please refer to http://code.google.com/p/mongoose/wiki/MongooseManual
+// for the list of valid option and their possible values.
+//
+// Return:
+// web server context, or NULL on error.
+struct mg_context *mg_start(mg_callback_t callback, void *user_data,
+ const char **options);
+
+
+// Stop the web server.
+//
+// Must be called last, when an application wants to stop the web server and
+// release all associated resources. This function blocks until all Mongoose
+// threads are stopped. Context pointer becomes invalid.
+void mg_stop(struct mg_context *);
+
+
+// Get the value of particular configuration parameter.
+// The value returned is read-only. Mongoose does not allow changing
+// configuration at run time.
+// If given parameter name is not valid, NULL is returned. For valid
+// names, return value is guaranteed to be non-NULL. If parameter is not
+// set, zero-length string is returned.
+const char *mg_get_option(const struct mg_context *ctx, const char *name);
+
+
+// Return array of strings that represent valid configuration options.
+// For each option, a short name, long name, and default value is returned.
+// Array is NULL terminated.
+const char **mg_get_valid_option_names(void);
+
+
+// Add, edit or delete the entry in the passwords file.
+//
+// This function allows an application to manipulate .htpasswd files on the
+// fly by adding, deleting and changing user records. This is one of the
+// several ways of implementing authentication on the server side. For another,
+// cookie-based way please refer to the examples/chat.c in the source tree.
+//
+// If password is not NULL, entry is added (or modified if already exists).
+// If password is NULL, entry is deleted.
+//
+// Return:
+// 1 on success, 0 on error.
+int mg_modify_passwords_file(const char *passwords_file_name,
+ const char *domain,
+ const char *user,
+ const char *password);
+
+
+// Return information associated with the request.
+// These functions always succeed.
+const struct mg_request_info *mg_get_request_info(const struct mg_connection *);
+void *mg_get_user_data(struct mg_connection *);
+const char *mg_get_log_message(const struct mg_connection *);
+int mg_get_reply_status_code(const struct mg_connection *);
+void *mg_get_ssl_context(const struct mg_connection *);
+
+
+// Send data to the client.
+// Return:
+// 0 when the connection has been closed
+// -1 on error
+// number of bytes written on success
+int mg_write(struct mg_connection *, const void *buf, size_t len);
+
+
+// Send data to the browser using printf() semantics.
+//
+// Works exactly like mg_write(), but allows to do message formatting.
+// Below are the macros for enabling compiler-specific checks for
+// printf-like arguments.
+
+#undef PRINTF_FORMAT_STRING
+#if _MSC_VER >= 1400
+#include <sal.h>
+#if _MSC_VER > 1400
+#define PRINTF_FORMAT_STRING(s) _Printf_format_string_ s
+#else
+#define PRINTF_FORMAT_STRING(s) __format_string s
+#endif
+#else
+#define PRINTF_FORMAT_STRING(s) s
+#endif
+
+#ifdef __GNUC__
+#define PRINTF_ARGS(x, y) __attribute__((format(printf, x, y)))
+#else
+#define PRINTF_ARGS(x, y)
+#endif
+
+int mg_printf(struct mg_connection *,
+ PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(2, 3);
+
+
+// Send contents of the entire file together with HTTP headers.
+void mg_send_file(struct mg_connection *conn, const char *path);
+
+
+// Read data from the remote end, return number of bytes read.
+int mg_read(struct mg_connection *, void *buf, size_t len);
+
+
+// Get the value of particular HTTP header.
+//
+// This is a helper function. It traverses request_info->http_headers array,
+// and if the header is present in the array, returns its value. If it is
+// not present, NULL is returned.
+const char *mg_get_header(const struct mg_connection *, const char *name);
+
+
+// Get a value of particular form variable.
+//
+// Parameters:
+// data: pointer to form-uri-encoded buffer. This could be either POST data,
+// or request_info.query_string.
+// data_len: length of the encoded data.
+// var_name: variable name to decode from the buffer
+// buf: destination buffer for the decoded variable
+// buf_len: length of the destination buffer
+//
+// Return:
+// On success, length of the decoded variable.
+// On error:
+// -1 (variable not found, or destination buffer is too small).
+// -2 (destination buffer is NULL or zero length).
+//
+// Destination buffer is guaranteed to be '\0' - terminated if it is not
+// NULL or zero length. In case of failure, dst[0] == '\0'.
+int mg_get_var(const char *data, size_t data_len,
+ const char *var_name, char *buf, size_t buf_len);
+
+// Fetch value of certain cookie variable into the destination buffer.
+//
+// Destination buffer is guaranteed to be '\0' - terminated. In case of
+// failure, dst[0] == '\0'. Note that RFC allows many occurrences of the same
+// parameter. This function returns only first occurrence.
+//
+// Return:
+// On success, value length.
+// On error, -1 (either "Cookie:" header is not present at all, or the
+// requested parameter is not found, or destination buffer is too small
+// to hold the value).
+int mg_get_cookie(const struct mg_connection *,
+ const char *cookie_name, char *buf, size_t buf_len);
+
+
+// Connect to the remote web server.
+// Return:
+// On success, valid pointer to the new connection
+// On error, NULL
+struct mg_connection *mg_connect(struct mg_context *ctx,
+ const char *host, int port, int use_ssl);
+
+
+// Close the connection opened by mg_connect().
+void mg_close_connection(struct mg_connection *conn);
+
+
+// Download given URL to a given file.
+// url: URL to download
+// path: file name where to save the data
+// request_info: pointer to a structure that will hold parsed reply headers
+// buf, bul_len: a buffer for the reply headers
+// Return:
+// On error, NULL
+// On success, opened file stream to the downloaded contents. The stream
+// is positioned to the end of the file. It is the user's responsibility
+// to fclose() the opened file stream.
+FILE *mg_fetch(struct mg_context *ctx, const char *url, const char *path,
+ char *buf, size_t buf_len, struct mg_request_info *request_info);
+
+
+// Convenience function -- create detached thread.
+// Return: 0 on success, non-0 on error.
+typedef void * (*mg_thread_func_t)(void *);
+int mg_start_thread(mg_thread_func_t f, void *p);
+
+
+// Return builtin mime type for the given file name.
+// For unrecognized extensions, "text/plain" is returned.
+const char *mg_get_builtin_mime_type(const char *file_name);
+
+
+// Return Mongoose version.
+const char *mg_version(void);
+
+
+// MD5 hash given strings.
+// Buffer 'buf' must be 33 bytes long. Varargs is a NULL terminated list of
+// ASCIIz strings. When function returns, buf will contain human-readable
+// MD5 hash. Example:
+// char buf[33];
+// mg_md5(buf, "aa", "bb", NULL);
+void mg_md5(char buf[33], ...);
+
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif // MONGOOSE_HEADER_INCLUDED
diff --git a/spectro/munki.c b/spectro/munki.c
new file mode 100644
index 0000000..6fa898e
--- /dev/null
+++ b/spectro/munki.c
@@ -0,0 +1,940 @@
+
+ /* X-Rite ColorMunki related functions */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 12/1/2009
+ *
+ * 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.
+ *
+ * (Based on i1pro.c)
+ */
+
+/*
+ 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#include <math.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* SALONEINSTLIB */
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* SALONEINSTLIB */
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "munki.h"
+#include "munki_imp.h"
+
+#define MAX_MES_SIZE 500 /* Maximum normal message reply size */
+#define MAX_RD_SIZE 5000 /* Maximum reading messagle reply size */
+
+/* Convert a machine specific error code into an abstract dtp code */
+static inst_code munki_interp_code(munki *p, munki_code ec);
+
+/* ------------------------------------------------------------------------ */
+
+/* Establish communications with a Munki */
+/* If it's a serial port, use the baud rate given, and timeout in to secs */
+/* Return DTP_COMS_FAIL on failure to establish communications */
+static inst_code
+munki_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ munki *p = (munki *) pp;
+ int se;
+ icomuflags usbflags = icomuf_none;
+#ifdef __APPLE__
+ /* If the ColorMunki software has been installed, then there will */
+ /* be a daemon process that has the device open. Kill that process off */
+ /* so that we can open it here, before it re-spawns. */
+ char *pnames[] = {
+ "ninjad",
+ "ColorMunkiDeviceService",
+ NULL
+ };
+ int retries = 20;
+#else /* !__APPLE__ */
+ char **pnames = NULL;
+ int retries = 0;
+#endif /* !__APPLE__ */
+
+ a1logd(p->log, 2, "munki_init_coms: called\n");
+
+ if (p->icom->port_type(p->icom) != icomt_usb) {
+ a1logd(p->log, 1, "munki_init_coms: wrong sort of coms!\n");
+ return munki_interp_code(p, MUNKI_UNKNOWN_MODEL);
+ }
+
+ a1logd(p->log, 2, "munki_init_coms: about to init USB\n");
+
+ /* Set config, interface, write end point, read end point, read quanta */
+ /* ("serial" end points aren't used - the Munki uses USB control messages) */
+ if ((se = p->icom->set_usb_port(p->icom, 1, 0x00, 0x00, usbflags, retries, pnames))
+ != ICOM_OK) {
+ a1logd(p->log, 1, "munki_init_coms: failed ICOM err 0x%x\n",se);
+ return munki_interp_code(p, icoms2munki_err(se));
+ }
+
+ a1logd(p->log, 2, "munki_init_coms: init coms has suceeded\n");
+
+ p->gotcoms = 1;
+ return inst_ok;
+}
+
+static inst_code
+munki_determine_capabilities(munki *p) {
+
+ /* Set the Munki capabilities mask */
+ p->cap = inst_mode_ref_spot
+ | inst_mode_ref_strip
+ | inst_mode_emis_spot
+ | inst_mode_emis_tele
+ | inst_mode_trans_spot /* Support this manually using a light table */
+ | inst_mode_trans_strip
+ | inst_mode_emis_strip /* Also likely to be light table reading */
+ | inst_mode_emis_ambient
+ | inst_mode_emis_ambient_flash
+ | inst_mode_emis_nonadaptive
+ | inst_mode_colorimeter
+ | inst_mode_spectral
+ | inst_mode_calibration /* Can indicate cal configuration */
+ ;
+
+ if (munki_imp_highres(p)) /* Static */
+ p->cap |= inst_mode_highres;
+
+ p->cap2 = inst2_prog_trig
+ | inst2_user_trig
+ | inst2_user_switch_trig
+ | inst2_bidi_scan
+ | inst2_has_scan_toll
+ | inst2_no_feedback
+ | inst2_has_leds
+ | inst2_has_sensmode
+ ;
+
+ if (p->m != NULL) {
+ munkiimp *m = (munkiimp *)p->m;
+ munki_state *s = &m->ms[m->mmode];
+ if (s->emiss)
+ p->cap2 |= inst2_emis_refr_meas;
+ }
+
+ p->cap3 = inst3_none;
+
+ return inst_ok;
+}
+
+/* Return current or given configuration available measurement modes. */
+static inst_code munki_meas_config(
+inst *pp,
+inst_mode *mmodes,
+inst_cal_cond *cconds,
+int *conf_ix
+) {
+ munki *p = (munki *)pp;
+ munki_code ev;
+ mk_spos spos;
+
+ if (mmodes != NULL)
+ *mmodes = inst_mode_none;
+ if (cconds != NULL)
+ *cconds = inst_calc_none;
+
+ if (conf_ix == NULL
+ || *conf_ix < mk_spos_proj
+ || *conf_ix > mk_spos_amb) {
+ /* Return current configuration measrement modes */
+ ev = munki_getstatus(p, &spos, NULL);
+ if (ev != MUNKI_OK)
+ return munki_interp_code(p, ev);
+ } else {
+ /* Return given configuration measurement modes */
+ spos = *conf_ix;
+ }
+
+ if (spos == mk_spos_proj) {
+ if (mmodes != NULL)
+ *mmodes = inst_mode_emis_tele;
+ } else if (spos == mk_spos_surf) {
+ if (mmodes != NULL)
+ *mmodes = inst_mode_ref_spot
+ | inst_mode_ref_strip
+ | inst_mode_emis_spot
+ | inst_mode_trans_spot
+ | inst_mode_trans_strip;
+ } else if (spos == mk_spos_calib) {
+ if (cconds != NULL)
+ *cconds = inst_calc_man_cal_smode;
+ if (mmodes != NULL)
+ *mmodes = inst_mode_calibration;
+ } else if (spos == mk_spos_amb) {
+ if (mmodes != NULL)
+ *mmodes = inst_mode_emis_ambient
+ | inst_mode_emis_ambient_flash;
+ }
+
+ /* Return configuration index returned */
+ if (conf_ix != NULL)
+ *conf_ix = (int)spos;
+
+ /* Add the extra dependent and independent modes */
+ if (mmodes != NULL)
+ *mmodes |= inst_mode_emis_nonadaptive
+ | inst_mode_colorimeter
+ | inst_mode_spectral;
+
+ return inst_ok;
+}
+
+
+/* Initialise the MUNKI */
+/* return non-zero on an error, with dtp error code */
+static inst_code
+munki_init_inst(inst *pp) {
+ munki *p = (munki *)pp;
+ munki_code ev = MUNKI_OK;
+
+ a1logd(p->log, 2, "munki_init_inst: called\n");
+
+ if (p->gotcoms == 0)
+ return munki_interp_code(p, MUNKI_INT_NO_COMS); /* Must establish coms before calling init */
+ if ((ev = munki_imp_init(p)) != MUNKI_OK) {
+ a1logd(p->log, 1, "munki_init_inst: failed with 0x%x\n",ev);
+ return munki_interp_code(p, ev);
+ }
+
+ p->inited = 1;
+ a1logd(p->log, 2, "munki_init_inst: instrument inited OK\n");
+
+ munki_determine_capabilities(p);
+
+ return munki_interp_code(p, ev);
+}
+
+static char *munki_get_serial_no(inst *pp) {
+ munki *p = (munki *)pp;
+
+ if (!p->gotcoms)
+ return "";
+ if (!p->inited)
+ return "";
+
+ return munki_imp_get_serial_no(p);
+}
+
+/* Read a set of strips */
+/* Return the dtp error code */
+static inst_code
+munki_read_strip(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+int npatch, /* Number of patches in the pass */
+char *pname, /* Pass name (3 chars) */
+int sguide, /* Guide number */
+double pwid, /* Patch length in mm (DTP41) */
+double gwid, /* Gap length in mm (DTP41) */
+double twid, /* Trailer length in mm (DTP41T) */
+ipatch *vals) { /* Pointer to array of instrument patch values */
+ munki *p = (munki *)pp;
+ munki_code rv;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ rv = munki_imp_measure(p, vals, npatch, 1);
+
+ return munki_interp_code(p, rv);
+}
+
+/* Read a single sample */
+/* Return the dtp error code */
+static inst_code
+munki_read_sample(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+ipatch *val, /* Pointer to instrument patch value */
+instClamping clamp) { /* Clamp XYZ/Lab to be +ve */
+ munki *p = (munki *)pp;
+ munki_code rv;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ rv = munki_imp_measure(p, val, 1, clamp);
+
+ return munki_interp_code(p, rv);
+}
+
+/* Read an emissive refresh rate */
+static inst_code
+munki_read_refrate(
+inst *pp,
+double *ref_rate) {
+ munki *p = (munki *)pp;
+ munki_code rv;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ rv = munki_imp_meas_refrate(p, ref_rate);
+
+ return munki_interp_code(p, rv);
+}
+
+/* Return needed and available inst_cal_type's */
+static inst_code munki_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
+ munki *p = (munki *)pp;
+ munki_code rv;
+
+ rv = munki_imp_get_n_a_cals(p, pn_cals, pa_cals);
+ return munki_interp_code(p, rv);
+}
+
+/* Request an instrument calibration. */
+inst_code munki_calibrate(
+inst *pp,
+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) */
+) {
+ munki *p = (munki *)pp;
+ munki_code rv;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ rv = munki_imp_calibrate(p, calt, calc, id);
+
+ return munki_interp_code(p, rv);
+}
+
+/* Instrument specific error codes interpretation */
+static char *
+munki_interp_error(inst *pp, munki_code ec) {
+// munki *p = (munki *)pp;
+ ec &= inst_imask;
+ switch (ec) {
+ case MUNKI_INTERNAL_ERROR:
+ return "Internal software error";
+ case MUNKI_COMS_FAIL:
+ return "Communications failure";
+ case MUNKI_UNKNOWN_MODEL:
+ return "Not an i1 Pro";
+ case MUNKI_DATA_PARSE_ERROR:
+ return "Data from i1 Display didn't parse as expected";
+
+ case MUNKI_USER_ABORT:
+ return "User abort";
+
+ case MUNKI_USER_TRIG:
+ return "User trigger";
+
+ case MUNKI_UNSUPPORTED:
+ return "Unsupported function";
+ case MUNKI_CAL_SETUP:
+ return "Calibration retry with correct setup is needed";
+
+ case MUNKI_OK:
+ return "No device error";
+
+ case MUNKI_DATA_RANGE:
+ return "EEProm data count location out of range";
+ case MUNKI_DATA_MEMORY:
+ return "EEProm memory alloc failure";
+
+ case MUNKI_HW_EE_SHORTREAD:
+ return "Read less bytes for EEProm read than expected";
+ case MUNKI_HW_ME_SHORTREAD:
+ return "Read less bytes for measurement read than expected";
+ case MUNKI_HW_ME_ODDREAD:
+ return "Read a number of bytes not a multiple of 274";
+ case MUNKI_HW_CALIBVERSION:
+ return "Instrument calibration version is unknown";
+ case MUNKI_HW_CALIBMATCH:
+ return "Calibration doesn't match device";
+
+ case MUNKI_RD_DARKREADINCONS:
+ return "Dark calibration reading is inconsistent";
+ case MUNKI_RD_SENSORSATURATED:
+ return "Sensor is saturated";
+ case MUNKI_RD_DARKNOTVALID:
+ return "Dark reading is not valid (too light)";
+ case MUNKI_RD_NEEDS_CAL:
+ return "Mode needs calibration";
+ case MUNKI_RD_WHITEREADINCONS:
+ return "White calibration reading is inconsistent";
+ case MUNKI_RD_WHITEREFERROR:
+ return "White reference reading error";
+ case MUNKI_RD_LIGHTTOOLOW:
+ return "Light level is too low";
+ case MUNKI_RD_LIGHTTOOHIGH:
+ return "Light level is too high";
+ case MUNKI_RD_SHORTMEAS:
+ return "Reading is too short";
+ case MUNKI_RD_READINCONS:
+ return "Reading is inconsistent";
+ case MUNKI_RD_REFWHITENOCONV:
+ return "White reference calibration didn't converge";
+ case MUNKI_RD_NOTENOUGHPATCHES:
+ return "Not enough patches";
+ case MUNKI_RD_TOOMANYPATCHES:
+ return "Too many patches";
+ case MUNKI_RD_NOTENOUGHSAMPLES:
+ return "Not enough samples per patch";
+ case MUNKI_RD_NOFLASHES:
+ return "No flashes recognized";
+ case MUNKI_RD_NOAMBB4FLASHES:
+ return "No ambient found before first flash";
+ case MUNKI_RD_NOREFR_FOUND:
+ return "No refresh rate detected or failed to measure it";
+
+ case MUNKI_SPOS_PROJ:
+ return "Sensor should be in projector position";
+ case MUNKI_SPOS_SURF:
+ return "Sensor should be in surface position";
+ case MUNKI_SPOS_CALIB:
+ return "Sensor should be in calibration position";
+ case MUNKI_SPOS_AMB:
+ return "Sensor should be in ambient position";
+
+ case MUNKI_INT_NO_COMS:
+ return "Communications hasn't been established";
+ case MUNKI_INT_EESIZE:
+ return "EEProm is not the expected size";
+ case MUNKI_INT_EEOUTOFRANGE:
+ return "EEProm access is out of range";
+ case MUNKI_INT_CALTOOSMALL:
+ return "EEProm calibration data is too short";
+ case MUNKI_INT_CALTOOBIG:
+ return "EEProm calibration data is too long";
+ case MUNKI_INT_CALBADCHSUM:
+ return "Calibration data has a bad checksum";
+ case MUNKI_INT_ODDREADBUF:
+ return "Measurement read buffer is not a multiple of 274";
+ case MUNKI_INT_INTTOOBIG:
+ return "Integration time is too big";
+ case MUNKI_INT_INTTOOSMALL:
+ return "Integration time is too small";
+ case MUNKI_INT_ILLEGALMODE:
+ return "Illegal measurement mode selected";
+ case MUNKI_INT_ZEROMEASURES:
+ return "Number of measurements requested is zero";
+ case MUNKI_INT_WRONGPATCHES:
+ return "Number of patches to match is wrong";
+ case MUNKI_INT_MEASBUFFTOOSMALL:
+ return "Measurement exceeded read buffer";
+ case MUNKI_INT_NOTIMPLEMENTED:
+ return "Support not implemented";
+ case MUNKI_INT_NOTCALIBRATED:
+ return "Unexpectedely invalid calibration";
+ case MUNKI_INT_THREADFAILED:
+ return "Creation of thread failed";
+ case MUNKI_INT_BUTTONTIMEOUT:
+ return "Button status read timed out";
+ case MUNKI_INT_CIECONVFAIL:
+ return "Creating spectral to CIE converted failed";
+ case MUNKI_INT_MALLOC:
+ return "Error in allocating memory";
+ case MUNKI_INT_CREATE_EEPROM_STORE:
+ return "Error in creating EEProm store";
+ case MUNKI_INT_NEW_RSPL_FAILED:
+ return "Creating RSPL object faild";
+ case MUNKI_INT_CAL_SAVE:
+ return "Unable to save calibration to file";
+ case MUNKI_INT_CAL_RESTORE:
+ return "Unable to restore calibration from file";
+ case MUNKI_INT_CAL_TOUCH:
+ return "Unable to update calibration file modification time";
+ case MUNKI_INT_ASSERT:
+ return "Assert fail";
+ default:
+ return "Unknown error code";
+ }
+}
+
+
+/* Convert a machine specific error code into an abstract dtp code */
+static inst_code
+munki_interp_code(munki *p, munki_code ec) {
+
+ ec &= inst_imask;
+ switch (ec) {
+
+ case MUNKI_OK:
+
+ return inst_ok;
+
+ case MUNKI_COMS_FAIL:
+ return inst_coms_fail | ec;
+
+ case MUNKI_UNKNOWN_MODEL:
+ return inst_unknown_model | ec;
+
+ case MUNKI_DATA_PARSE_ERROR:
+ return inst_protocol_error | ec;
+
+ case MUNKI_USER_ABORT:
+ return inst_user_abort;
+
+ case MUNKI_USER_TRIG:
+ return inst_user_trig;
+
+ case MUNKI_UNSUPPORTED:
+ return inst_unsupported | ec;
+
+ case MUNKI_RD_NEEDS_CAL:
+ return inst_needs_cal | ec;
+
+ case MUNKI_CAL_SETUP:
+ case MUNKI_SPOS_CALIB:
+ return inst_cal_setup | ec;
+
+ case MUNKI_SPOS_PROJ:
+ case MUNKI_SPOS_SURF:
+ case MUNKI_SPOS_AMB:
+ return inst_wrong_config | ec;
+
+ case MUNKI_DATA_RANGE:
+ case MUNKI_DATA_MEMORY:
+ case MUNKI_HW_EE_SHORTREAD:
+ case MUNKI_HW_ME_SHORTREAD:
+ case MUNKI_HW_ME_ODDREAD:
+ case MUNKI_HW_CALIBVERSION:
+ case MUNKI_HW_CALIBMATCH:
+ return inst_hardware_fail | ec;
+
+ case MUNKI_RD_DARKREADINCONS:
+ case MUNKI_RD_SENSORSATURATED:
+ case MUNKI_RD_DARKNOTVALID:
+ case MUNKI_RD_WHITEREADINCONS:
+ case MUNKI_RD_WHITEREFERROR:
+ case MUNKI_RD_LIGHTTOOLOW:
+ case MUNKI_RD_LIGHTTOOHIGH:
+ case MUNKI_RD_SHORTMEAS:
+ case MUNKI_RD_READINCONS:
+ case MUNKI_RD_REFWHITENOCONV:
+ case MUNKI_RD_NOTENOUGHPATCHES:
+ case MUNKI_RD_TOOMANYPATCHES:
+ case MUNKI_RD_NOTENOUGHSAMPLES:
+ case MUNKI_RD_NOFLASHES:
+ case MUNKI_RD_NOAMBB4FLASHES:
+ case MUNKI_RD_NOREFR_FOUND:
+ return inst_misread | ec;
+
+ case MUNKI_INTERNAL_ERROR:
+ case MUNKI_INT_NO_COMS:
+ case MUNKI_INT_EESIZE:
+ case MUNKI_INT_EEOUTOFRANGE:
+ case MUNKI_INT_CALTOOSMALL:
+ case MUNKI_INT_CALTOOBIG:
+ case MUNKI_INT_CALBADCHSUM:
+ case MUNKI_INT_ODDREADBUF:
+ case MUNKI_INT_INTTOOBIG:
+ case MUNKI_INT_INTTOOSMALL:
+ case MUNKI_INT_ILLEGALMODE:
+ case MUNKI_INT_ZEROMEASURES:
+ case MUNKI_INT_MEASBUFFTOOSMALL:
+ case MUNKI_INT_NOTIMPLEMENTED:
+ case MUNKI_INT_NOTCALIBRATED:
+ case MUNKI_INT_THREADFAILED:
+ case MUNKI_INT_BUTTONTIMEOUT:
+ case MUNKI_INT_CIECONVFAIL:
+ case MUNKI_INT_MALLOC:
+ case MUNKI_INT_CREATE_EEPROM_STORE:
+ case MUNKI_INT_NEW_RSPL_FAILED:
+ case MUNKI_INT_CAL_SAVE:
+ case MUNKI_INT_CAL_RESTORE:
+ case MUNKI_INT_CAL_TOUCH:
+ case MUNKI_INT_WRONGPATCHES:
+ case MUNKI_INT_ASSERT:
+ return inst_internal_error | ec;
+ }
+ return inst_other_error | ec;
+}
+
+/* Convert instrument specific inst_wrong_config error to inst_config enum */
+static inst_config munki_config_enum(inst *pp, int ec) {
+// munki *p = (munki *)pp;
+
+ ec &= inst_imask;
+ switch (ec) {
+
+ case MUNKI_SPOS_PROJ:
+ return inst_conf_projector;
+
+ case MUNKI_SPOS_SURF:
+ return inst_conf_surface;
+
+ case MUNKI_SPOS_AMB:
+ return inst_conf_ambient;
+
+ case MUNKI_SPOS_CALIB:
+ return inst_conf_calibration;
+ }
+ return inst_conf_unknown;
+}
+
+/* Return the instrument capabilities */
+void munki_capabilities(inst *pp,
+inst_mode *pcap1,
+inst2_capability *pcap2,
+inst3_capability *pcap3) {
+ munki *p = (munki *)pp;
+
+ if (pcap1 != NULL)
+ *pcap1 = p->cap;
+ if (pcap2 != NULL)
+ *pcap2 = p->cap2;
+ if (pcap3 != NULL)
+ *pcap3 = p->cap3;
+
+ return;
+}
+
+/* Return the corresponding munki measurement mode, */
+/* or mk_no_modes if invalid */
+static mk_mode munki_convert_mode(munki *p, inst_mode m) {
+ mk_mode mmode = 0;
+
+ /* Simple test */
+ if (m & ~p->cap)
+ return mk_no_modes;
+
+ if (IMODETST(m, inst_mode_ref_spot)) {
+ mmode = mk_refl_spot;
+ } else if (IMODETST(m, inst_mode_ref_strip)) {
+ mmode = mk_refl_scan;
+ } else if (IMODETST(m, inst_mode_trans_spot)) {
+ mmode = mk_trans_spot;
+ } else if (IMODETST(m, inst_mode_trans_strip)) {
+ mmode = mk_trans_scan;
+ } else if (IMODETST(m, inst_mode_emis_spot)) {
+ if (IMODETST(m, inst_mode_emis_nonadaptive))
+ mmode = mk_emiss_spot_na;
+ else
+ mmode = mk_emiss_spot;
+ } else if (IMODETST(m, inst_mode_emis_tele)) {
+ if (IMODETST(m, inst_mode_emis_nonadaptive))
+ mmode = mk_tele_spot_na;
+ else
+ mmode = mk_tele_spot;
+ } else if (IMODETST(m, inst_mode_emis_strip)) {
+ mmode = mk_emiss_scan;
+ } else if (IMODETST(m, inst_mode_emis_ambient)) {
+ mmode = mk_amb_spot;
+ } else if (IMODETST(m, inst_mode_emis_ambient_flash)) {
+ mmode = mk_amb_flash;
+ } else {
+ return mk_no_modes;
+ }
+
+ return mmode;
+}
+
+/* Check device measurement mode */
+inst_code munki_check_mode(inst *pp, inst_mode m) {
+ munki *p = (munki *)pp;
+ mk_mode mmode = 0;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (munki_convert_mode(p, m) == mk_no_modes)
+ return inst_unsupported;
+
+ return inst_ok;
+}
+
+/* Set device measurement mode */
+inst_code munki_set_mode(inst *pp, inst_mode m) {
+ munki *p = (munki *)pp;
+ mk_mode mmode = 0;
+ inst_mode cap = p->cap;
+ inst_code rv;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if ((mmode = munki_convert_mode(p, m)) == mk_no_modes)
+ return inst_unsupported;
+
+ if ((rv = munki_interp_code(p, munki_imp_set_mode(p, mmode, m & inst_mode_spectral)))
+ != inst_ok)
+ return rv;
+
+ munki_determine_capabilities(p);
+
+ return inst_ok;
+}
+
+/*
+ * set or reset an optional mode
+ *
+ * Some options talk to the instrument, and these will
+ * error if it hasn't been initialised.
+ */
+static inst_code
+munki_get_set_opt(inst *pp, inst_opt_type m, ...) {
+ munki *p = (munki *)pp;
+
+ if (m == inst_opt_noinitcalib) {
+ va_list args;
+ int losecs = 0;
+
+ va_start(args, m);
+ losecs = va_arg(args, int);
+ va_end(args);
+
+ munki_set_noinitcalib(p, 1, losecs);
+ return inst_ok;
+
+ } else if (m == inst_opt_initcalib) {
+ munki_set_noinitcalib(p, 0, 0);
+ return inst_ok;
+
+ /* Record the trigger mode */
+ } else if (m == inst_opt_trig_prog
+ || m == inst_opt_trig_user
+ || m == inst_opt_trig_user_switch) {
+ munki_set_trig(p, m);
+ return inst_ok;
+ }
+
+ if (m == inst_opt_scan_toll) {
+ va_list args;
+ double toll_ratio = 1.0;
+
+ va_start(args, m);
+ toll_ratio = va_arg(args, double);
+ va_end(args);
+ return munki_interp_code(p, munki_set_scan_toll(p, toll_ratio));
+ }
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* Not sure if hires can be set before init */
+ if (m == inst_opt_highres) {
+ return munki_interp_code(p, munki_set_highres(p));
+ } else if (m == inst_opt_stdres) {
+ return munki_interp_code(p, munki_set_stdres(p));
+ }
+
+ if (m == inst_opt_get_gen_ledmask) {
+ va_list args;
+ int *mask = NULL;
+
+ va_start(args, m);
+ mask = va_arg(args, int *);
+ va_end(args);
+ *mask = 0x1; /* One general LED */
+ return inst_ok;
+ } else if (m == inst_opt_set_led_state) {
+ va_list args;
+ int mask = 0;
+
+ va_start(args, m);
+ mask = 1 & va_arg(args, int);
+ va_end(args);
+ if (mask & 1) {
+ p->led_period = 1.0;
+ p->led_on_time_prop = 1.0;
+ p->led_trans_time_prop = 0.0;
+ return munki_interp_code(p, munki_setindled(p, 1000,0,0,-1,0));
+ } else {
+ p->led_period = 0.0;
+ p->led_on_time_prop = 0.0;
+ p->led_trans_time_prop = 0.0;
+ return munki_interp_code(p, munki_setindled(p, 0,0,0,0,0));
+ }
+ } else if (m == inst_opt_get_led_state) {
+ va_list args;
+ int *mask = NULL;
+
+ va_start(args, m);
+ mask = va_arg(args, int *);
+ va_end(args);
+ if (mask != NULL) *mask = p->led_state;
+ return inst_ok;
+ }
+
+ if (m == inst_opt_get_pulse_ledmask) {
+ va_list args;
+ int *mask = NULL;
+
+ va_start(args, m);
+ mask = va_arg(args, int *);
+ va_end(args);
+ *mask = 0x1; /* General LED is pulsable */
+ return inst_ok;
+ } else if (m == inst_opt_set_led_pulse_state) {
+ va_list args;
+ double period, on_time_prop, trans_time_prop;
+ int ontime, offtime, transtime, nopulses;
+
+ va_start(args, m);
+ period = va_arg(args, double);
+ on_time_prop = va_arg(args, double);
+ trans_time_prop = va_arg(args, double);
+ va_end(args);
+ if (period < 0.0
+ || on_time_prop < 0.0 || on_time_prop > 1.0
+ || trans_time_prop < 0.0 || trans_time_prop > 1.0
+ || trans_time_prop > on_time_prop || trans_time_prop > (1.0 - on_time_prop))
+ return inst_bad_parameter;
+ ontime = (int)(1000.0 * period * (on_time_prop - trans_time_prop) + 0.5);
+ offtime = (int)(1000.0 * period * (1.0 - on_time_prop - trans_time_prop) + 0.5);
+ transtime = (int)(1000.0 * period * trans_time_prop + 0.5);
+ nopulses = -1;
+ if (period == 0.0 || on_time_prop == 0.0) {
+ ontime = offtime = transtime = nopulses = 0;
+ p->led_state = 0;
+ } else {
+ p->led_state = 1;
+ }
+ p->led_period = period;
+ p->led_on_time_prop = on_time_prop;
+ p->led_trans_time_prop = trans_time_prop;
+ return munki_interp_code(p, munki_setindled(p, ontime,offtime,transtime,nopulses,0));
+ } else if (m == inst_opt_get_led_state) {
+ va_list args;
+ double *period, *on_time_prop, *trans_time_prop;
+
+ va_start(args, m);
+ period = va_arg(args, double *);
+ on_time_prop = va_arg(args, double *);
+ trans_time_prop = va_arg(args, double *);
+ va_end(args);
+ if (period != NULL) *period = p->led_period;
+ if (on_time_prop != NULL) *on_time_prop = p->led_on_time_prop;
+ if (trans_time_prop != NULL) *trans_time_prop = p->led_trans_time_prop;
+ return inst_ok;
+ }
+
+ /* Return the filter */
+ if (m == inst_stat_get_filter) {
+ inst_opt_filter *filt;
+ va_list args;
+
+ va_start(args, m);
+ filt = va_arg(args, inst_opt_filter *);
+ va_end(args);
+
+ /* The ColorMunki is always UV cut */
+ *filt = inst_opt_filter_UVCut;
+
+ return inst_ok;
+ }
+
+ /* Use default implementation of other inst_opt_type's */
+ {
+ inst_code rv;
+ va_list args;
+
+ va_start(args, m);
+ rv = inst_get_set_opt_def(pp, m, args);
+ va_end(args);
+
+ return rv;
+ }
+ return inst_unsupported;
+}
+
+/* Destroy ourselves */
+static void
+munki_del(inst *pp) {
+ munki *p = (munki *)pp;
+
+ del_munkiimp(p);
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ free(p);
+}
+
+/* Constructor */
+extern munki *new_munki(icoms *icom, instType itype) {
+ munki *p;
+ int rv;
+ if ((p = (munki *)calloc(sizeof(munki),1)) == NULL) {
+ a1loge(icom->log, 1, "new_munki: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(icom->log);
+
+ /* Inst methods */
+ p->init_coms = munki_init_coms;
+ p->init_inst = munki_init_inst;
+ p->capabilities = munki_capabilities;
+ p->meas_config = munki_meas_config;
+ p->get_serial_no = munki_get_serial_no;
+ p->check_mode = munki_check_mode;
+ p->set_mode = munki_set_mode;
+ p->get_set_opt = munki_get_set_opt;
+ p->read_strip = munki_read_strip;
+ p->read_sample = munki_read_sample;
+ p->read_refrate = munki_read_refrate;
+ p->get_n_a_cals = munki_get_n_a_cals;
+ p->calibrate = munki_calibrate;
+ p->interp_error = munki_interp_error;
+ p->config_enum = munki_config_enum;
+ p->del = munki_del;
+
+ p->icom = icom;
+ p->itype = icom->itype;
+
+ /* Preliminary capabilities */
+ munki_determine_capabilities(p);
+
+ if ((rv = add_munkiimp(p) != MUNKI_OK)) {
+ free(p);
+ a1loge(icom->log, 1, "new_munki: error %d creating munkiimp\n",rv);
+ }
+
+ return p;
+}
+
diff --git a/spectro/munki.h b/spectro/munki.h
new file mode 100644
index 0000000..01eda29
--- /dev/null
+++ b/spectro/munki.h
@@ -0,0 +1,72 @@
+
+#ifndef MUNKI_H
+
+ /* X-Rite ColorMunki related defines */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 12/1/2009
+ *
+ * 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.
+ *
+ * (Based on i1pro.h)
+ */
+
+/*
+ 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.
+ */
+
+#include "inst.h"
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/* MUNKI communication object */
+struct _munki {
+ INST_OBJ_BASE
+
+ int dtype; /* Device type: 0 = ?? */
+
+ /* *** munki private data **** */
+ inst_mode cap; /* Instrument mode capability */
+ inst2_capability cap2; /* Instrument capability 2 */
+ inst3_capability cap3; /* Instrument capability 3 */
+
+ void *m; /* Implementation - munkiimp type */
+
+ /* Other state */
+ int led_state; /* : Current LED on/off state */
+ double led_period, led_on_time_prop, led_trans_time_prop; /* Pulse state */
+
+}; typedef struct _munki munki;
+
+/* Constructor */
+extern munki *new_munki(icoms *icom, instType itype);
+
+#ifdef __cplusplus
+ }
+#endif
+
+#define MUNKI_H
+#endif /* MUNKI_H */
diff --git a/spectro/munki_imp.c b/spectro/munki_imp.c
new file mode 100644
index 0000000..c76e9bc
--- /dev/null
+++ b/spectro/munki_imp.c
@@ -0,0 +1,9103 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 12/1/2009
+ *
+ * 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.
+ *
+ * (Base on i1pro_imp.c)
+ */
+
+/*
+ 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:
+
+*/
+
+#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 */
+#undef USE_HIGH_GAIN_MODE /* [Und] Make use of high gain mode */
+#define USE_THREAD /* [Def] Need to use thread, or there are 1.5 second internal */
+ /* instrument delays ! */
+#define ENABLE_NONVCAL /* [Def] Enable saving calibration state between program runs in a file */
+#define ENABLE_NONLINCOR /* [Def] Enable non-linear correction */
+ /* NOTE :- high gain scaling will be stuffed if disabled! */
+#define ENABLE_LEDTEMPC /* [Def] Enable LED temperature compensation */
+#define ENABLE_SPOS_CHECK /* [Def] Chech the sensor position is reasonable for measurement */
+#define DCALTOUT (1 * 60 * 60) /* [1 Hrs] Dark Calibration timeout in seconds */
+#define WCALTOUT (24 * 60 * 60) /* [24 Hrs] White Calibration timeout in seconds */
+#define MAXSCANTIME 20.0 /* [20 Sec] 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. Disable */
+ /* to break dependency on rspl library. */
+
+/* Debug [Und] */
+#undef DEBUG /* Turn on extra messages & plots */
+#undef PLOT_DEBUG /* Use plot to show readings & processing */
+#undef PLOT_REFRESH /* Plot refresh rate measurement info */
+#undef RAWR_DEBUG /* Print out raw reading processing values */
+#undef DUMP_SCANV /* Dump scan readings to a file "mkdump.txt" */
+#undef DUMP_DARKM /* Append raw dark readings to file "mkddump.txt" */
+#undef APPEND_MEAN_EMMIS_VAL /* Append averaged uncalibrated reading to file "mkdump.txt" */
+#undef IGNORE_WHITE_INCONS /* Ignore define reference reading inconsistency */
+#undef TEST_DARK_INTERP /* Test out the dark interpolation (need DEBUG for plot) */
+#undef PLOT_RCALCURVE /* Plot the reflection reference curve */
+#undef PLOT_ECALCURVES /* Plot the emission reference curves */
+#undef PLOT_TEMPCOMP /* Plot before and after temp. compensation */
+#undef PATREC_DEBUG /* Print & Plot patch/flash recognition information */
+#undef HIGH_RES_DEBUG
+#undef HIGH_RES_PLOT
+#undef HIGH_RES_PLOT_STRAYL /* Plot strat light upsample */
+
+
+#define DISP_INTT 0.7 /* 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.3 /* High brightness display spot mode seconds per reading, */
+ /* Should be good up to 275 cd/m^2 */
+#define DISP_INTT3 0.1 /* High brightness display spot mode seconds per reading, */
+ /* Should be good up to 700 cd/m^2 */
+
+#define ADARKINT_MIN 0.01 /* Min cal time for adaptive dark cal */
+#define ADARKINT_MAX 2.0 /* Max cal time for adaptive dark cal with high gain mode */
+#define ADARKINT_MAX2 4.0 /* Max cal time for adaptive dark for no high gain */
+
+#define SCAN_OP_LEV 0.10 /* Degree of optimimum sensor value to aim for */
+ /* Make it scan as fast as possible */
+
+#define RDEAD_TIME 0.004432 /* Fudge figure to make reflecting intn. time scale linearly */
+
+#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/raw value we can cope with */
+
+/* High res mode settings */
+#define HIGHRES_SHORT 360 /* Wavelength to calculate */
+#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 410.0 /* Too much stray light below this in reflective mode */
+#define HIGHRES_TRANS_MIN 380.0 /* Too much stray light below this in reflective mode */
+
+#include "munki.h"
+#include "munki_imp.h"
+
+/* - - - - - - - - - - - - - - - - - - */
+
+#define PATCH_CONS_THR 0.05 /* Dark measurement consistency threshold */
+#define DARKTHSCAMIN 5000.0 /* Dark threshold scaled/offset minimum */
+
+/* - - - - - - - - - - - - - - - - - - */
+
+/* Three levels of runtime debugging messages:
+
+ ~~~ this is no longer accurate. a1logd calls
+ ~~~ probably need to be tweaked.
+ 1 = default, typical I/O messages etc.
+ 2 = more internal operation messages
+ 3 = dump extra detailes
+ 4 = dump EEPROM data
+ 5 = dump tables etc
+*/
+
+/* ============================================================ */
+
+// Print bytes as hex to debug log */
+static void dump_bytes(a1log *log, char *pfx, unsigned char *buf, int base, int len) {
+ int i, j, ii;
+ char oline[200] = { '\000' }, *bp = oline;
+ for (i = j = 0; i < len; i++) {
+ if ((i % 16) == 0)
+ bp += sprintf(bp,"%s%04x:",pfx,base+i);
+ bp += sprintf(bp," %02x",buf[i]);
+ if ((i+1) >= len || ((i+1) % 16) == 0) {
+ for (ii = i; ((ii+1) % 16) != 0; ii++)
+ bp += sprintf(bp," ");
+ bp += sprintf(bp," ");
+ for (; j <= i; j++) {
+ if (!(buf[j] & 0x80) && isprint(buf[j]))
+ bp += sprintf(bp,"%c",buf[j]);
+ else
+ bp += sprintf(bp,".");
+ }
+ bp += sprintf(bp,"\n");
+ a1logd(log,0,oline);
+ bp = oline;
+ }
+ }
+}
+
+/* ============================================================ */
+/* Debugging plot support */
+
+#if defined(DEBUG) || defined(PLOT_DEBUG) || defined(PATREC_DEBUG) || defined(HIGH_RES_PLOT) || defined(HIGH_RES_PLOT_STRAYL)
+
+# include <plot.h>
+
+static int disdebplot = 0;
+
+# define DISDPLOT disdebplot = 1;
+# define ENDPLOT disdebplot = 0;
+
+/* 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[NSEN_MAX];
+ double y1[NSEN_MAX];
+ double y2[NSEN_MAX];
+
+ 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(munkiimp *m, double *data) {
+ int i;
+ double xx[NSEN_MAX];
+ double yy[NSEN_MAX];
+
+ if (disdebplot)
+ return;
+
+ for (i = 0; i < m->nwav; i++) {
+ xx[i] = XSPECT_WL(m->wl_short, m->wl_long, m->nwav, i);
+ yy[i] = data[i];
+ }
+ do_plot(xx, yy, NULL, NULL, m->nwav);
+}
+
+/* Plot a standard res spectra */
+void plot_wav1(munkiimp *m, double *data) {
+ int i;
+ double xx[36];
+ double yy[36];
+
+ if (disdebplot)
+ return;
+
+ for (i = 0; i < m->nwav1; i++) {
+ xx[i] = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, i);
+ yy[i] = data[i];
+ }
+ do_plot(xx, yy, NULL, NULL, m->nwav1);
+}
+
+/* Plot a high res spectra */
+void plot_wav2(munkiimp *m, double *data) {
+ int i;
+ double xx[NSEN_MAX];
+ double yy[NSEN_MAX];
+
+ if (disdebplot)
+ return;
+
+ for (i = 0; i < m->nwav2; i++) {
+ xx[i] = XSPECT_WL(m->wl_short2, m->wl_long2, m->nwav2, i);
+ yy[i] = data[i];
+ }
+ do_plot(xx, yy, NULL, NULL, m->nwav2);
+}
+
+#else /* !PLOT_DEBUG */
+
+# define DISDPLOT
+# define ENDPLOT
+
+#endif /* !PLOT_DEBUG */
+
+/* ============================================================ */
+
+munki_code munki_touch_calibration(munki *p);
+
+/* Implementation struct */
+
+/* Add an implementation structure */
+munki_code add_munkiimp(munki *p) {
+ munkiimp *m;
+
+ if ((m = (munkiimp *)calloc(1, sizeof(munkiimp))) == NULL) {
+ a1logd(p->log,3,"add_munkiimp malloc %lu bytes failed (1)\n",sizeof(munkiimp));
+ return MUNKI_INT_MALLOC;
+ }
+ m->p = p;
+
+ m->lo_secs = 2000000000; /* A very long time */
+
+ p->m = (void *)m;
+ return MUNKI_OK;
+}
+
+/* Shutdown instrument, and then destroy */
+/* implementation structure */
+void del_munkiimp(munki *p) {
+
+ a1logd(p->log,3,"munki_del called\n");
+
+#ifdef ENABLE_NONVCAL
+ /* Touch it so that we know when the instrument was last open */
+ munki_touch_calibration(p);
+#endif /* ENABLE_NONVCAL */
+
+ if (p->m != NULL) {
+ int i;
+ munkiimp *m = (munkiimp *)p->m;
+ munki_state *s;
+
+ if (m->th != NULL) { /* Terminate switch monitor thread by simulating an event */
+ m->th_term = 1; /* Tell thread to exit on error */
+ munki_simulate_event(p, mk_eve_spos_change, 0);
+ for (i = 0; m->th_termed == 0 && i < 5; i++)
+ msec_sleep(50); /* Wait for thread to terminate */
+ if (i >= 5) {
+ a1logd(p->log,3,"Munki switch thread termination failed\n");
+ }
+ m->th->del(m->th);
+ usb_uninit_cancel(&m->cancelt); /* Don't need cancel token now */
+ }
+
+ /* Free any per mode data */
+ for (i = 0; i < mk_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->iwhite_data, 0, 1, -1, m->nraw-1);
+ free_dmatrix(s->idark_data, 0, 3, -1, m->nraw-1);
+
+ free_dvector(s->cal_factor1, 0, m->nwav1-1);
+ free_dvector(s->cal_factor2, 0, m->nwav2-1);
+ }
+
+ /* Free EEProm key data */
+ if (m->data != NULL)
+ m->data->del(m->data);
+
+ /* Free arrays */
+
+ if (m->lin0 != NULL)
+ free(m->lin0);
+ if (m->lin1 != NULL)
+ free(m->lin1);
+
+ if (m->white_ref1 != NULL)
+ free(m->white_ref1);
+ if (m->emis_coef1 != NULL)
+ free(m->emis_coef1);
+ if (m->amb_coef1 != NULL)
+ free(m->amb_coef1);
+ if (m->proj_coef1 != NULL)
+ free(m->proj_coef1);
+
+ if (m->white_ref2 != NULL)
+ free(m->white_ref2);
+ if (m->emis_coef2 != NULL)
+ free(m->emis_coef2);
+ if (m->amb_coef2 != NULL)
+ free(m->amb_coef2);
+ if (m->proj_coef2 != NULL)
+ free(m->proj_coef2);
+
+ if (m->straylight1 != NULL)
+ free_dmatrix(m->straylight1, 0, m->nwav1-1, 0, m->nwav1-1);
+
+ if (m->straylight2 != NULL)
+ free_dmatrix(m->straylight2, 0, m->nwav1-2, 0, m->nwav1-2);
+
+ if (m->rmtx_index1 != NULL)
+ free(m->rmtx_index1);
+ if (m->rmtx_nocoef1 != NULL)
+ free(m->rmtx_nocoef1);
+ if (m->rmtx_coef1 != NULL)
+ free(m->rmtx_coef1);
+
+ if (m->rmtx_index2 != NULL)
+ free(m->rmtx_index2);
+ if (m->rmtx_nocoef2 != NULL)
+ free(m->rmtx_nocoef2);
+ if (m->rmtx_coef2 != NULL)
+ free(m->rmtx_coef2);
+
+ if (m->emtx_index1 != NULL)
+ free(m->emtx_index1);
+ if (m->emtx_nocoef1 != NULL)
+ free(m->emtx_nocoef1);
+ if (m->emtx_coef1 != NULL)
+ free(m->emtx_coef1);
+
+ if (m->emtx_index2 != NULL)
+ free(m->emtx_index2);
+ if (m->emtx_nocoef2 != NULL)
+ free(m->emtx_nocoef2);
+ if (m->emtx_coef2 != NULL)
+ free(m->emtx_coef2);
+
+ free(m);
+ p->m = NULL;
+ }
+}
+
+/* ============================================================ */
+/* Little endian wire format conversion routines */
+
+/* Take an int, and convert it into a byte buffer little endian */
+static void int2buf(unsigned char *buf, int inv) {
+ buf[3] = (inv >> 24) & 0xff;
+ buf[2] = (inv >> 16) & 0xff;
+ buf[1] = (inv >> 8) & 0xff;
+ buf[0] = (inv >> 0) & 0xff;
+}
+
+/* Take a short, and convert it into a byte buffer little endian */
+static void short2buf(unsigned char *buf, int inv) {
+ buf[1] = (inv >> 8) & 0xff;
+ buf[0] = (inv >> 0) & 0xff;
+}
+
+/* Take a word sized buffer, and convert it to an int */
+static int buf2int(unsigned char *buf) {
+ int val;
+ val = ((signed char *)buf)[3];
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* Take a word sized buffer, and convert it to an unsigned int */
+static unsigned int buf2uint(unsigned char *buf) {
+ unsigned int val;
+ val = (0xff & buf[3]);
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[0]));
+ 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)[1];
+ val = ((val << 8) + (0xff & buf[0]));
+ 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[1]);
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* ============================================================ */
+/* High level functions */
+
+/* Initialise our software state from the hardware */
+munki_code munki_imp_init(munki *p) {
+ munkiimp *m = (munkiimp *)p->m;
+ munki_code ev = MUNKI_OK;
+ unsigned char buf[4];
+ int calsize = 0, rucalsize;
+ unsigned char *calbuf; /* EEProm contents */
+
+ a1logd(p->log,2,"munki_init:\n");
+
+ if (p->itype != instColorMunki)
+ return MUNKI_UNKNOWN_MODEL;
+
+#ifdef ENABLE_SPOS_CHECK
+ m->nosposcheck = 0;
+#else
+# pragma message("####### ColorMunki Sensor Position Check is OFF! ########")
+ m->nosposcheck = 1;
+#endif
+
+ m->trig = inst_opt_trig_user;
+ m->scan_toll_ratio = 1.0;
+
+ /* Get the firmware parameters so that we can check eeprom range. */
+ if ((ev = munki_getfirm(p, &m->fwrev, &m->tickdur, &m->minintcount, &m->noeeblocks, &m->eeblocksize)) != MUNKI_OK)
+ return ev;
+ a1logd(p->log,2,"Firmware rev = %d.%d\n",m->fwrev/256, m->fwrev % 256);
+
+ /* Check the EEProm */
+ if (m->noeeblocks != 2 || m->eeblocksize != 8192) {
+ a1logw(p->log,"EEProm is unexpected size\n");
+ return MUNKI_INT_ASSERT;
+ }
+
+ /* Dump the eeprom contents as a block */
+ if (p->log->debug >= 7) {
+ int base, size;
+
+ a1logd(p->log,7, "EEPROM contents:\n");
+
+ size = 8192;
+ for (base = 0; base < (2 * 8192); base += 8192) {
+ unsigned char eeprom[8192];
+
+ if ((ev = munki_readEEProm(p, eeprom, base, size)) != MUNKI_OK)
+ return ev;
+
+ dump_bytes(p->log, " ", eeprom, base, size);
+ }
+ }
+
+ /* Tick in seconds */
+ m->intclkp = (double)m->tickdur * 1e-6;
+
+ /* Set these to reasonable values */
+ m->min_int_time = m->intclkp * (double)m->minintcount;
+ m->max_int_time = 4.5;
+
+ a1logd(p->log,3, "minintcount %d, min_int_time = %f\n", m->minintcount, m->min_int_time);
+
+ /* Get the Chip ID */
+ if ((ev = munki_getchipid(p, m->chipid)) != MUNKI_OK)
+ return ev;
+
+ /* Get the Version String */
+ if ((ev = munki_getversionstring(p, m->vstring)) != MUNKI_OK)
+ return ev;
+
+ /* Read the calibration size */
+ if ((ev = munki_readEEProm(p, buf, 4, 4)) != MUNKI_OK)
+ return ev;
+ calsize = buf2int(buf);
+ rucalsize = (calsize + 3) & ~3; /* Round up to next 32 bits */
+
+ if (calsize < 12)
+ return MUNKI_INT_CALTOOSMALL;
+ if (calsize > (m->noeeblocks * m->eeblocksize))
+ return MUNKI_INT_CALTOOBIG;
+
+ /* Read the calibration raw data from the EEProm */
+ if ((calbuf = (unsigned char *)calloc(rucalsize, sizeof(unsigned char))) == NULL) {
+ a1logd(p->log,3,"munki_imp_init malloc %d bytes failed\n",rucalsize);
+ return MUNKI_INT_MALLOC;
+ }
+ if ((ev = munki_readEEProm(p, calbuf, 0, calsize)) != MUNKI_OK)
+ return ev;
+
+ if ((ev = munki_parse_eeprom(p, calbuf, rucalsize)) != MUNKI_OK)
+ return ev;
+
+ free(calbuf);
+ calbuf = NULL;
+
+#ifdef USE_THREAD
+ /* Setup the switch monitoring thread */
+ usb_init_cancel(&m->cancelt); /* Get cancel token ready */
+ if ((m->th = new_athread(munki_switch_thread, (void *)p)) == NULL)
+ return MUNKI_INT_THREADFAILED;
+#endif
+
+ /* Set up the current state of each mode */
+ {
+ int i, j;
+ munki_state *s;
+
+ /* First set state to basic configuration */
+ for (i = 0; i < mk_no_modes; i++) {
+ s = &m->ms[i];
+
+ s->mode = i;
+
+ /* Default to an emissive configuration */
+ s->targoscale = 0.90; /* Allow extra 10% margine by default */
+ s->targmaxitime = 2.0; /* Maximum integration time to aim for */
+ s->targoscale2 = 0.15; /* Proportion of targoscale to meed targmaxitime */
+
+ s->gainmode = 0; /* Normal gain mode */
+ s->inttime = 0.5; /* Initial integration time */
+
+
+ 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_factor1 = dvectorz(0, m->nwav1-1);
+ s->cal_factor2 = dvectorz(0, m->nwav2-1);
+ s->cal_factor = s->cal_factor1; /* Default to standard resolution */
+ s->white_data = dvectorz(-1, m->nraw-1);
+ s->iwhite_data = dmatrixz(0, 1, -1, m->nraw-1);
+
+ s->idark_valid = 0; /* Interpolatable Dark cal invalid */
+ s->idark_data = dmatrixz(0, 3, -1, m->nraw-1);
+
+ s->min_wl = 0.0; /* Default minimum to report */
+
+ s->dark_int_time = DISP_INTT; /* 0.7 */
+ s->dark_int_time2 = DISP_INTT2; /* 0.3 */
+ s->dark_int_time3 = DISP_INTT3; /* 0.1 */
+
+ s->idark_int_time[0] = s->idark_int_time[2] = m->min_int_time;
+#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; /* By default want an initial calibration */
+ s->want_dcalib = 1;
+ }
+
+ /* Then add mode specific settings */
+ for (i = 0; i < mk_no_modes; i++) {
+ s = &m->ms[i];
+ switch(i) {
+ case mk_refl_spot:
+ s->targoscale = 1.0; /* Optimised sensor scaling to full */
+ s->reflective = 1;
+ s->adaptive = 0;
+ s->inttime = s->targoscale * m->cal_int_time;
+ s->dark_int_time = s->inttime;
+
+ s->dpretime = 0.20; /* Pre-measure time */
+ s->wpretime = 0.20;
+ s->dcaltime = 0.5; /* same as reading */
+ s->wcaltime = 0.5; /* same as reading */
+ s->dreadtime = 0.5; /* same as reading */
+ s->wreadtime = 0.5;
+ s->maxscantime = 0.0;
+ s->min_wl = HIGHRES_REF_MIN; /* Not enogh illumination to go below this */
+ break;
+
+ case mk_refl_scan:
+ s->targoscale = SCAN_OP_LEV; /* Maximize scan rate */
+ s->reflective = 1;
+ s->scan = 1;
+ s->adaptive = 0;
+ s->inttime = (s->targoscale * m->cal_int_time - RDEAD_TIME) + RDEAD_TIME;
+ if (s->inttime < m->min_int_time)
+ s->inttime = m->min_int_time;
+ s->dark_int_time = s->inttime;
+
+ s->dpretime = 0.20; /* Pre-measure time */
+ s->wpretime = 0.20;
+ 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; /* Not enogh illumination to go below this */
+ break;
+
+ case mk_emiss_spot_na: /* Emissive spot not adaptive */
+ case mk_tele_spot_na: /* Tele spot not adaptive */
+ s->targoscale = 0.90; /* Allow extra 10% margine */
+ if (i == mk_emiss_spot_na) {
+ for (j = 0; j < m->nwav1; j++)
+ s->cal_factor1[j] = EMIS_SCALE_FACTOR * m->emis_coef1[j];
+ } else {
+ for (j = 0; j < m->nwav1; j++)
+ s->cal_factor1[j] = EMIS_SCALE_FACTOR * m->proj_coef1[j];
+ s->projector = 1;
+ }
+ s->cal_valid = 1;
+ s->emiss = 1;
+ s->adaptive = 0;
+
+ s->inttime = DISP_INTT; /* Default disp integration time (ie. 0.7 sec) */
+ s->dark_int_time = s->inttime;
+ s->dark_int_time2 = DISP_INTT2; /* Alternate disp integration time (ie. 0.3) */
+ s->dark_int_time3 = DISP_INTT3; /* Alternate disp integration time (ie. 0.1) */
+
+ s->dpretime = 0.0;
+ s->wpretime = 0.20;
+ s->dcaltime = 1.0; /* ie. determines number of measurements */
+ s->dcaltime2 = 1.0;
+ s->dcaltime3 = 1.0;
+ s->wcaltime = 0.0;
+ s->dreadtime = 0.0;
+ s->wreadtime = DISP_INTT;
+ s->maxscantime = 0.0;
+ break;
+
+ case mk_emiss_spot:
+ case mk_amb_spot:
+ case mk_tele_spot: /* Adaptive projector */
+ s->targoscale = 0.90; /* Allow extra 5% margine */
+ if (i == mk_emiss_spot) {
+ for (j = 0; j < m->nwav1; j++)
+ s->cal_factor1[j] = EMIS_SCALE_FACTOR * m->emis_coef1[j];
+ } else if (i == mk_amb_spot) {
+ for (j = 0; j < m->nwav1; j++)
+ s->cal_factor1[j] = AMB_SCALE_FACTOR * m->amb_coef1[j];
+ s->ambient = 1;
+ } else {
+ for (j = 0; j < m->nwav1; j++)
+ s->cal_factor1[j] = EMIS_SCALE_FACTOR * m->proj_coef1[j];
+ s->projector = 1;
+ }
+
+ s->cal_valid = 1;
+ s->emiss = 1;
+ s->adaptive = 1;
+
+ s->dpretime = 0.0;
+ s->wpretime = 0.10;
+ s->dcaltime = 1.0;
+ s->wcaltime = 0.0;
+ s->dreadtime = 0.0;
+ s->wreadtime = 1.0;
+ s->maxscantime = 0.0;
+ break;
+
+ case mk_emiss_scan:
+ case mk_amb_flash:
+ s->targoscale = 0.90; /* Allow extra 10% margine */
+ if (i == mk_emiss_scan) {
+ for (j = 0; j < m->nwav1; j++)
+ s->cal_factor1[j] = EMIS_SCALE_FACTOR * m->emis_coef1[j];
+ } else {
+ for (j = 0; j < m->nwav1; j++)
+ s->cal_factor1[j] = AMB_SCALE_FACTOR * m->amb_coef1[j];
+ s->ambient = 1;
+ s->flash = 1;
+ }
+ s->cal_valid = 1;
+ s->emiss = 1;
+ s->scan = 1;
+ s->adaptive = 0;
+ s->inttime = m->min_int_time;
+ s->dark_int_time = s->inttime;
+
+ s->dpretime = 0.0;
+ s->wpretime = 0.10;
+ s->dcaltime = 1.0;
+ s->wcaltime = 0.0;
+ s->dreadtime = 0.0;
+ s->wreadtime = 0.10;
+ s->maxscantime = MAXSCANTIME;
+ break;
+
+ /* Transparency has a white reference, and interpolated dark ref. */
+ case mk_trans_spot:
+ s->targoscale = 0.90; /* Allow extra 10% margine */
+ s->trans = 1;
+ s->adaptive = 1;
+
+ s->dpretime = 0.20;
+ s->wpretime = 0.20;
+ s->dcaltime = 1.0;
+ s->wcaltime = 1.0;
+ s->dreadtime = 0.0;
+ s->wreadtime = 1.0;
+ s->maxscantime = 0.0;
+ s->min_wl = HIGHRES_TRANS_MIN; /* Too much stray light below this ? */
+ break;
+
+ case mk_trans_scan:
+ s->targoscale = 0.90; /* Allow extra 10% margine */
+ s->trans = 1;
+ s->scan = 1;
+ s->targoscale = SCAN_OP_LEV;
+ s->inttime = s->targoscale * m->cal_int_time;
+ if (s->inttime < m->min_int_time)
+ s->inttime = m->min_int_time;
+ s->dark_int_time = s->inttime;
+ s->adaptive = 0;
+
+ s->dpretime = 0.20;
+ s->wpretime = 0.20;
+ s->dcaltime = 1.0;
+ s->wcaltime = 1.0;
+ s->dreadtime = 0.00;
+ s->wreadtime = 0.10;
+ s->maxscantime = MAXSCANTIME;
+ s->min_wl = HIGHRES_TRANS_MIN; /* Too much stray light below this ? */
+ break;
+ }
+ }
+ }
+
+#ifdef ENABLE_NONVCAL
+ /* Restore the all modes calibration from the local system */
+ munki_restore_calibration(p);
+ /* Touch it so that we know when the instrument was last opened */
+ munki_touch_calibration(p);
+#endif
+
+ a1logv(p->log, 1,
+ "Instrument Type: ColorMunki\n" // ~~ should get this from version string ?
+ "Serial Number: %s\n"
+ "Firmware version: %d\n"
+ "Chip ID: %02X-%02X%02X%02X%02X%02X%02X%02X\n"
+ "Version string: '%s'\n"
+ "Calibration Ver.: %d\n"
+ "Production No.: %d\n",
+ m->serno,
+ m->fwrev,
+ m->chipid[0], m->chipid[1], m->chipid[2], m->chipid[3],
+ m->chipid[4], m->chipid[5], m->chipid[6], m->chipid[7],
+ m->vstring,
+ m->calver,
+ m->prodno);
+
+ /* Flash the LED, just cos we can! */
+ if ((ev = munki_setindled(p, 1000,0,0,-1,0)) != MUNKI_OK)
+ return ev;
+ msec_sleep(200);
+ if ((ev = munki_setindled(p, 0,0,0,0,0)) != MUNKI_OK)
+ return ev;
+
+ return ev;
+}
+
+/* Return a pointer to the serial number */
+char *munki_imp_get_serial_no(munki *p) {
+ munkiimp *m = (munkiimp *)p->m;
+
+ return m->serno;
+}
+
+/* Set the measurement mode. It may need calibrating */
+munki_code munki_imp_set_mode(
+ munki *p,
+ mk_mode mmode,
+ int spec_en /* nz to enable reporting spectral */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+
+ a1logd(p->log,3,"munki_imp_set_mode called with %d\n",mmode);
+ switch(mmode) {
+ case mk_refl_spot:
+ case mk_refl_scan:
+ case mk_emiss_spot_na:
+ case mk_tele_spot_na:
+ case mk_emiss_spot:
+ case mk_tele_spot:
+ case mk_emiss_scan:
+ case mk_amb_spot:
+ case mk_amb_flash:
+ case mk_trans_spot:
+ case mk_trans_scan:
+ m->mmode = mmode;
+ m->spec_en = spec_en ? 1 : 0;
+ return MUNKI_OK;
+ default:
+ break;
+ }
+ return MUNKI_INT_ILLEGALMODE;
+}
+
+/* Return needed and available inst_cal_type's */
+munki_code munki_imp_get_n_a_cals(munki *p, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
+ munkiimp *m = (munkiimp *)p->m;
+ munki_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,3,"munki_imp_get_n_a_cals: checking mode %d\n",m->mmode);
+
+ /* Timout calibrations that are too old */
+ a1logd(p->log,4,"curtime = %u, iddate = %u\n",curtime,cs->iddate);
+ if ((curtime - cs->iddate) > DCALTOUT) {
+ a1logd(p->log,3,"Invalidating adaptive dark cal as %d secs from last cal\n",curtime - cs->iddate);
+ cs->idark_valid = 0;
+ }
+ if ((curtime - cs->ddate) > DCALTOUT) {
+ a1logd(p->log,3,"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,3,"Invalidating white cal as %d secs from last cal\n",curtime - cs->cfdate);
+ cs->cal_valid = 0;
+ }
+
+ 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->scan && !cs->adaptive) {
+ 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,"munki_imp_get_n_a_cals: returning n_cals 0x%x, a_cals 0x%x\n",n_cals, a_cals);
+
+ return MUNKI_OK;
+}
+
+/* - - - - - - - - - - - - - - - - */
+/* Calibrate for the current mode. */
+/* Request an instrument calibration of the current mode. */
+munki_code munki_imp_calibrate(
+ munki *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) */
+) {
+ munki_code ev = MUNKI_OK;
+ munkiimp *m = (munkiimp *)p->m;
+ int mmode = m->mmode; /* Current actual mode */
+ munki_state *cs = &m->ms[m->mmode];
+ int sx1, sx2, sx;
+ time_t cdate = time(NULL);
+ int nummeas = 0;
+ mk_spos spos;
+ int i, j, k;
+ inst_cal_type needed, available;
+
+ a1logd(p->log,3,"munki_imp_calibrate called with calt 0x%x, calc 0x%x\n",*calt, *calc);
+
+ if ((ev = munki_imp_get_n_a_cals(p, &needed, &available)) != MUNKI_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,"munki_imp_calibrate: doing calt 0x%x\n",calt);
+
+ if ((*calt & inst_calt_n_dfrble_mask) == 0) /* Nothing todo */
+ return MUNKI_OK;
+ }
+
+ /* See if it's a calibration we understand */
+ if (*calt & ~available & inst_calt_all_mask) {
+ return MUNKI_UNSUPPORTED;
+ }
+
+ /* Get current sensor position */
+ if ((ev = munki_getstatus(p, &spos, NULL)) != MUNKI_OK) {
+ return ev;
+ }
+ a1logd(p->log,4,"munki sensor position = 0x%x\n",spos);
+
+ /* Make sure that the instrument configuration matches the */
+ /* conditions */
+ if (*calc == inst_calc_man_cal_smode) {
+ if (!m->nosposcheck && spos != mk_spos_calib) {
+ return MUNKI_SPOS_CALIB;
+ }
+ } else if (*calc == inst_calc_man_trans_white) {
+ if (!m->nosposcheck && spos != mk_spos_surf) {
+ return MUNKI_SPOS_SURF;
+ }
+ }
+
+ /* If the instrument is in the calibration position, */
+ /* we know what the conditions are. */
+ if (!m->nosposcheck && spos == mk_spos_calib) {
+ *calc = inst_calc_man_cal_smode;
+ a1logd(p->log,4,"munki set calc to cal conditions\n",spos);
+ }
+
+ a1logd(p->log,4,"munki_imp_calibrate has right conditions\n");
+
+ if (*calt & inst_calt_ap_flag) {
+ sx1 = 0; sx2 = mk_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++) {
+ munki_state *s = &m->ms[sx];
+ m->mmode = sx; /* A lot of functions we call rely on this */
+
+ a1logd(p->log,3,"\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 */
+ }
+
+ /* We are now either in inst_calc_man_cal_smode, */
+ /* inst_calc_man_trans_white, inst_calc_disp_white or inst_calc_proj_white */
+ /* sequenced in that order, and in the appropriate condition for it. */
+
+ /* 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_cal_smode
+ && ( 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 = munki_comp_nummeas(p, s->dcaltime, s->inttime);
+
+ a1logd(p->log,3,"\nDoing initial display 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 = munki_dark_measure(p, s->dark_data, nummeas, &s->inttime, s->gainmode))
+ != MUNKI_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ a1logd(p->log,4,"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 = munki_comp_nummeas(p, s->dcaltime2, s->dark_int_time2);
+ a1logd(p->log,3,"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 = munki_dark_measure(p, s->dark_data2, nummeas, &s->dark_int_time2,
+ s->gainmode)) != MUNKI_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ a1logd(p->log,4,"Execution time of 2nd dark calib time %f sec = %d msec\n",s->inttime,msec_time() - stm);
+
+ nummeas = munki_comp_nummeas(p, s->dcaltime3, s->dark_int_time3);
+ a1logd(p->log,3,"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 = munki_comp_nummeas(p, s->dcaltime3, s->dark_int_time3);
+ stm = msec_time();
+ if ((ev = munki_dark_measure(p, s->dark_data3, nummeas, &s->dark_int_time3,
+ s->gainmode)) != MUNKI_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ a1logd(p->log,4,"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 < mk_no_modes; i++) {
+ munki_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;
+#ifndef NEVER // ~~99
+if (ss->dark_int_time2 != s->dark_int_time2
+ || ss->dark_int_time3 != s->dark_int_time3)
+ a1logd(p->log,1,"copying cal to mode with different cal/gain mode: %d -> %d\n",s->mode, ss->mode);
+#endif
+ 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_cal_smode
+ && (s->emiss && !s->adaptive && s->scan)) {
+ int stm;
+
+ nummeas = munki_comp_nummeas(p, s->dcaltime, s->inttime);
+
+ a1logd(p->log,3,"\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 = munki_dark_measure(p, s->dark_data, nummeas, &s->inttime, s->gainmode))
+ != MUNKI_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ a1logd(p->log,4,"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 */
+ for (i = 0; i < mk_no_modes; i++) {
+ munki_state *ss = &m->ms[i];
+ if (ss == s || ss->ddate == cdate)
+ continue;
+ if ((ss->emiss && !ss->adaptive && ss->scan)
+ && 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];
+ }
+ }
+ }
+ }
+
+ /* Adaptive black calibration: */
+ /* Emmissive adaptive and transmissive black reference. */
+ /* in non-scan mode, where the integration time and gain may vary. */
+ if ((*calt & (inst_calt_ref_dark
+ | inst_calt_em_dark
+ | inst_calt_trans_dark | inst_calt_ap_flag))
+ && *calc == inst_calc_man_cal_smode
+ && ((s->emiss && s->adaptive && !s->scan)
+ || (s->trans && s->adaptive && !s->scan))) {
+ /* 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. */
+
+ s->idark_int_time[0] = m->min_int_time;
+ nummeas = munki_comp_nummeas(p, s->dcaltime, s->idark_int_time[0]);
+ a1logd(p->log,3,"\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 = munki_dark_measure(p, s->idark_data[0], nummeas, &s->idark_int_time[0], 0))
+ != MUNKI_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+
+ nummeas = munki_comp_nummeas(p, s->dcaltime, s->idark_int_time[1]);
+ a1logd(p->log,3,"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 = munki_dark_measure(p, s->idark_data[1], nummeas, &s->idark_int_time[1], 0))
+ != MUNKI_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+
+#ifdef USE_HIGH_GAIN_MODE
+ s->idark_int_time[2] = m->min_int_time; /* 0.01 */
+ nummeas = munki_comp_nummeas(p, s->dcaltime, s->idark_int_time[2]);
+ a1logd(p->log,3,"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 = munki_dark_measure(p, s->idark_data[2], nummeas, &s->idark_int_time[2], 1))
+ != MUNKI_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+
+ s->idark_int_time[3] = ADARKINT_MAX; /* 2.0 */
+ a1logd(p->log,3,"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 = munki_comp_nummeas(p, s->dcaltime, s->idark_int_time[3]);
+ if ((ev = munki_dark_measure(p, s->idark_data[3], nummeas, &s->idark_int_time[3], 1))
+ != MUNKI_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+#endif /* USE_HIGH_GAIN_MODE */
+
+ munki_prepare_idark(p);
+
+ s->idark_valid = 1;
+ s->iddate = cdate;
+
+ if ((ev = munki_interp_dark(p, s->dark_data, s->inttime, s->gainmode)) != MUNKI_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,3,"Saving adaptive black calib to similar modes\n");
+ for (i = 0; i < mk_no_modes; i++) {
+ munki_state *ss = &m->ms[i];
+ if (ss == s || ss->iddate == cdate)
+ 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;
+#ifndef NEVER // ~~99
+if (ss->dark_int_time != s->dark_int_time
+ || ss->dark_gain_mode != s->dark_gain_mode)
+ a1logd(p->log,1,"copying cal to mode with different cal/gain mode: %d -> %d\n",s->mode, ss->mode);
+#endif
+#ifdef USE_HIGH_GAIN_MODE
+ for (j = 0; j < 4; j++)
+#else
+ for (j = 0; j < 2; j++)
+#endif
+ {
+ ss->idark_int_time[j] = s->idark_int_time[j];
+#ifndef NEVER // ~~99
+if (ss->idark_int_time[j] != s->idark_int_time[j])
+ a1logd(p->log,1,"copying cal to mode with different cal/gain mode: %d -> %d\n",s->mode, ss->mode);
+#endif
+ for (k = -1; k < m->nraw; k++)
+ ss->idark_data[j][k] = s->idark_data[j][k];
+ }
+ }
+ }
+
+ a1logd(p->log,3,"Done adaptive interpolated black calibration\n");
+
+ /* Test accuracy of dark level interpolation */
+#ifdef TEST_DARK_INTERP
+ {
+ double tinttime;
+ double ref[NSEN_MAX], interp[NSEN_MAX];
+
+ // fprintf(stderr,"Normal gain offsets, base:\n");
+ // plot_raw(s->idark_data[0]);
+ // fprintf(stderr,"Normal gain offsets, 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 = munki_comp_nummeas(p, s->dcaltime, tinttime);
+ if ((ev = munki_dark_measure(p, ref, nummeas, &tinttime, 0)) != MUNKI_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ munki_interp_dark(p, interp, tinttime, 0);
+#ifdef DEBUG
+ fprintf(stderr,"Normal gain, int time %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
+ // fprintf(stderr,"High gain offsets, base:\n");
+ // plot_raw(s->idark_data[2]);
+ // fprintf(stderr,"High gain offsets, 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 = munki_comp_nummeas(p, s->dcaltime, tinttime);
+ if ((ev = munki_dark_measure(p, ref, nummeas, &tinttime, 1)) != MUNKI_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ munki_interp_dark(p, interp, tinttime, 1);
+#ifdef DEBUG
+ printf("High gain, int time %f:\n",tinttime);
+ plot_raw2(ref, interp);
+#endif
+ if ((tinttime * 1.1) > m->max_int_time)
+ break;
+ }
+#endif /* USE_HIGH_GAIN_MODE */
+ }
+#endif /* TEST_DARK_INTERP */
+
+ }
+
+ /* 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_cal_smode
+ && ((s->emiss && s->adaptive && s->scan)
+ || (s->trans && s->adaptive && s->scan))) {
+ int j;
+ /* 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 = munki_comp_nummeas(p, s->dcaltime, s->idark_int_time[0]);
+ a1logd(p->log,3,"\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 = munki_dark_measure(p, s->idark_data[0], nummeas, &s->idark_int_time[0], 0))
+ != MUNKI_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+
+#ifdef USE_HIGH_GAIN_MODE
+ s->idark_int_time[2] = s->inttime;
+ nummeas = munki_comp_nummeas(p, s->dcaltime, s->idark_int_time[2]);
+ a1logd(p->log,3,"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 = munki_dark_measure(p, s->idark_data[2], nummeas, &s->idark_int_time[2], 1))
+ != MUNKI_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,3,"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,3,"Saving adaptive scan black calib to similar modes\n");
+ for (i = 0; i < mk_no_modes; i++) {
+ munki_state *ss = &m->ms[i];
+ if (ss == s || s->iddate == cdate)
+ 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 < 4; 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];
+ }
+ }
+ }
+ }
+
+ /* Now deal with white calibrations */
+
+ /* If we are doing a reflective white reference calibrate */
+ /* or a we are doing a tranmisive white reference calibrate */
+ if ((*calt & (inst_calt_ref_white
+ | inst_calt_trans_vwhite | inst_calt_ap_flag))
+ && ((*calc == inst_calc_man_cal_smode && s->reflective)
+ || (*calc == inst_calc_man_trans_white && s->trans))) {
+// && s->cfdate < cdate)
+ double dead_time = 0.0; /* Dead integration time */
+ double scale;
+ int i;
+ double ulimit = m->optsval / m->minsval; /* Upper scale needed limit */
+ double fulimit = sqrt(ulimit); /* Fast exit limit */
+ double llimit = m->optsval / m->maxsval; /* Lower scale needed limit */
+ double fllimit = sqrt(llimit); /* Fast exit limit */
+
+ a1logd(p->log,3,"\nDoing initial white calibration with current inttime %f, gainmode %d\n",
+ s->inttime, s->gainmode);
+ a1logd(p->log,3,"ulimit %f, llimit %f\n",ulimit,llimit);
+ a1logd(p->log,3,"fulimit %f, fllimit %f\n",fulimit,fllimit);
+ if (s->reflective) {
+ dead_time = RDEAD_TIME; /* Fudge value that makes int time calcs work */
+ /* Heat up the LED to put in in a nominal state for int time adjustment */
+ munki_heatLED(p, m->ledpreheattime);
+ }
+
+ /* Until we're done */
+ for (i = 0; i < 6; i++) {
+
+ a1logd(p->log,3,"Doing a white calibration with trial int_time %f, gainmode %d\n",
+ s->inttime,s->gainmode);
+
+ if (s->trans && s->adaptive) {
+ /* compute interpolated dark refence for chosen inttime & gainmode */
+ a1logd(p->log,3,"Interpolate dark calibration reference\n");
+ if ((ev = munki_interp_dark(p, s->dark_data, s->inttime, s->gainmode))
+ != MUNKI_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;
+ }
+ nummeas = munki_comp_nummeas(p, s->wcaltime, s->inttime);
+ ev = munki_whitemeasure(p, s->white_data, &scale, nummeas, &s->inttime, s->gainmode,
+ s->targoscale);
+ a1logd(p->log,3,"Needed scale is %f\n",scale);
+
+ if (ev == MUNKI_RD_SENSORSATURATED) {
+ scale = 0.0; /* Signal it this way */
+ ev = MUNKI_OK;
+ }
+ if (ev != MUNKI_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+
+ if (scale >= fllimit && scale <= fulimit) {
+ a1logd(p->log,3,"Close enough for early exit\n");
+ break; /* OK, we can stop straight away */
+ }
+
+ if (scale == 0.0) { /* If sensor was saturated */
+ s->inttime = m->min_int_time;
+ s->gainmode = 0;
+ s->dark_valid = 0;
+ } else {
+ double ninttime;
+
+ /* 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 = munki_optimise_sensor(p, &ninttime, &s->gainmode, s->inttime,
+ s->gainmode, s->trans, 0, &s->targoscale, scale, dead_time)) != MUNKI_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+ s->inttime = ninttime;
+ a1logd(p->log,3,"New inttime = %f\n",s->inttime);
+ }
+ }
+ if (i >= 6) {
+ if (scale == 0.0) { /* If sensor was saturated */
+ a1logd(p->log,1, "White calibration failed - sensor is saturated\n");
+ m->mmode = mmode; /* Restore actual mode */
+ return MUNKI_RD_SENSORSATURATED;
+ }
+ if (scale > ulimit || scale < llimit) {
+ a1logd(p->log,1,"White calibration failed - didn't converge (%f %f %f)\n",llimit,scale,ulimit);
+ m->mmode = mmode; /* Restore actual mode */
+ return MUNKI_RD_REFWHITENOCONV;
+ }
+ }
+
+ /* We've settled on the inttime and gain mode to get a good white reference. */
+ if (s->reflective) { /* We read the write reference - check it */
+
+ /* Let the LED cool down */
+ a1logd(p->log,3,"Waiting %f secs for LED to cool\n",m->ledwaittime);
+ msec_sleep((int)(m->ledwaittime * 1000.0 + 0.5));
+
+ /* Re-calibrate the black with the given integration time */
+ nummeas = munki_comp_nummeas(p, s->dcaltime, s->inttime);
+
+ a1logd(p->log,3,"Doing another reflective black calibration with dcaltime %f, int_time %f, nummeas %d, gainmode %d\n", s->dcaltime, s->inttime, nummeas, s->gainmode);
+ if ((ev = munki_dark_measure(p, s->dark_data, nummeas, &s->inttime, s->gainmode))
+ != MUNKI_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+
+ /* Take a reflective white reference measurement, */
+ /* subtracts black and decompose into base + LED temperature components, */
+ /* and compute reftemp white reference. */
+ nummeas = munki_comp_nummeas(p, m->calscantime, s->inttime);
+ if ((ev = munki_ledtemp_whitemeasure(p, s->white_data, s->iwhite_data, &s->reftemp,
+ nummeas, s->inttime, s->gainmode)) != MUNKI_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+
+ /* Compute wavelength white readings from ref temp sensor reading */
+ if ((ev = munki_compute_wav_whitemeas(p, s->cal_factor1, s->cal_factor2,
+ s->white_data)) != MUNKI_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+
+ /* We don't seem to sanity check the white reference. Presumably */
+ /* this is because a LED isn't going to burn out... */
+
+ /* Compute a calibration factor given the reading of the white reference. */
+ munki_compute_white_cal(p, s->cal_factor1, m->white_ref1, s->cal_factor1,
+ s->cal_factor2, m->white_ref2, s->cal_factor2);
+
+ } else {
+ /* Compute wavelength white readings from sensor */
+ if ((ev = munki_compute_wav_whitemeas(p, s->cal_factor1, s->cal_factor2,
+ s->white_data)) != MUNKI_OK) {
+ m->mmode = mmode; /* Restore actual mode */
+ return ev;
+ }
+
+ /* Compute a calibration factor given the reading of the white reference. */
+ m->transwarn |= munki_compute_white_cal(p, s->cal_factor1, NULL, s->cal_factor1,
+ s->cal_factor2, NULL, s->cal_factor2);
+ }
+ 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->cfdate < cdate
+ && (s->emiss && !s->adaptive && !s->scan)) {
+ double scale;
+ double *data;
+ double *tt, tv;
+
+ data = dvectorz(-1, m->nraw-1);
+
+ a1logd(p->log,3,"\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 = munki_comp_nummeas(p, s->wreadtime, s->inttime);
+ ev = munki_whitemeasure(p, data , &scale, nummeas,
+ &s->inttime, s->gainmode, s->targoscale);
+ /* Switch to the alternate if things are too bright */
+ /* We do this simply by swapping the alternate values in. */
+ if (ev == MUNKI_RD_SENSORSATURATED || scale < 1.0) {
+ a1logd(p->log,3,"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 = munki_comp_nummeas(p, s->wreadtime, s->inttime);
+ ev = munki_whitemeasure(p, data , &scale, nummeas,
+ &s->inttime, s->gainmode, s->targoscale);
+ /* Switch to the 3rd alternate if things are too bright */
+ /* We do this simply by swapping the alternate values in. */
+ if (ev == MUNKI_RD_SENSORSATURATED || scale < 1.0) {
+ a1logd(p->log,3,"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 != MUNKI_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,3,"Done display integration time selection\n");
+ }
+
+ } /* Look at next mode */
+ m->mmode = mmode; /* Restore actual mode */
+
+ /* Make sure there's the right condition for the calibration */
+ if (*calt & (inst_calt_ref_dark | inst_calt_ref_white)) { /* Reflective calib */
+ if (*calc != inst_calc_man_cal_smode) {
+ *calc = inst_calc_man_cal_smode;
+ return MUNKI_CAL_SETUP;
+ }
+ } else if (*calt & inst_calt_em_dark) { /* Emissive Dark calib */
+ id[0] = '\000';
+ if (*calc != inst_calc_man_cal_smode) {
+ *calc = inst_calc_man_cal_smode;
+ return MUNKI_CAL_SETUP;
+ }
+ } else if (*calt & inst_calt_trans_dark) { /* Transmissive dark */
+ id[0] = '\000';
+ if (*calc != inst_calc_man_cal_smode) {
+ *calc = inst_calc_man_cal_smode;
+ return MUNKI_CAL_SETUP;
+ }
+ } else if (*calt & inst_calt_trans_vwhite) { /* Transmissive white for emulated trans. */
+ id[0] = '\000';
+ if (*calc != inst_calc_man_trans_white) {
+ *calc = inst_calc_man_trans_white;
+ return MUNKI_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 MUNKI_CAL_SETUP;
+ }
+ }
+
+ /* Go around again if we've still got calibrations to do */
+ if (*calt & inst_calt_all_mask) {
+ return MUNKI_CAL_SETUP;
+ }
+
+ /* We must be done */
+
+#ifdef ENABLE_NONVCAL
+ /* Save the calibration to a file */
+ munki_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;
+ return MUNKI_OK;
+ }
+
+ a1logd(p->log,3,"Finished cal with dark_valid = %d, cal_valid = %d\n",cs->dark_valid, cs->cal_valid);
+
+ return ev;
+}
+
+/* Interpret an icoms error into a MUNKI error */
+int icoms2munki_err(int se) {
+ if (se != ICOM_OK)
+ return MUNKI_COMS_FAIL;
+ return MUNKI_OK;
+}
+
+
+/* - - - - - - - - - - - - - - - - */
+/* Measure a patch or strip or flash 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. */
+munki_code munki_imp_measure(
+ munki *p,
+ ipatch *vals, /* Pointer to array of instrument patch value */
+ int nvals, /* Number of values */
+ instClamping clamp /* Clamp XYZ/Lab to be +ve */
+) {
+ munki_code ev = MUNKI_OK;
+ munkiimp *m = (munkiimp *)p->m;
+ munki_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 invsampt = 0.0; /* Invalid sample time */
+ int ninvmeas = 0; /* Number of invalid measurements */
+ double **specrd = NULL; /* Cooked spectral patch values */
+ double duration = 0.0; /* Possible flash duration value */
+ mk_spos spos;
+ int user_trig = 0;
+
+ a1logd(p->log,2,"munki_imp_measure called\n");
+ a1logd(p->log,3,"Taking %d measurments in %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" : "");
+
+
+ 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,"munki_imp_measure need calibration\n");
+ return MUNKI_RD_NEEDS_CAL;
+ }
+
+ if (nvals <= 0
+ || (!s->scan && nvals > 1)) {
+ a1logd(p->log,3,"munki_imp_measure wrong number of patches\n");
+ return MUNKI_INT_WRONGPATCHES;
+ }
+
+ if (s->reflective) {
+ /* Number of invalid samples to allow for LED warmup */
+ invsampt = m->refinvalidsampt;
+ ninvmeas = munki_comp_ru_nummeas(p, invsampt, s->inttime);
+ }
+
+ /* Notional number of measurements, befor adaptive and not counting scan */
+ nummeas = munki_comp_nummeas(p, s->wreadtime, s->inttime);
+
+ /* Allocate buffer for dark measurement */
+ if (s->reflective) {
+ bsize = m->nsen * 2 * nummeas;
+ if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
+ a1logd(p->log,1,"munki_imp_measure malloc %d bytes failed (5)\n",bsize);
+ return MUNKI_INT_MALLOC;
+ }
+ }
+
+ /* Allocate buffer for measurement */
+ maxnummeas = munki_comp_nummeas(p, s->maxscantime, s->inttime);
+ if (maxnummeas < (ninvmeas + nummeas))
+ maxnummeas = (ninvmeas + 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,"munki_imp_measure malloc %d bytes failed (6)\n",mbsize);
+ return MUNKI_INT_MALLOC;
+ }
+ specrd = dmatrix(0, nvals-1, 0, m->nwav-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 = MUNKI_USER_ABORT;
+ break; /* Abort */
+ }
+ if (!s->scan && rc == inst_user_trig) {
+ ev = MUNKI_USER_TRIG;
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ msec_sleep(100);
+ }
+ }
+#else
+ /* Throw one away in case the switch was pressed prematurely */
+ munki_waitfor_switch_th(p, NULL, NULL, 0.01);
+
+ for (;;) {
+ mk_eve ecode;
+ int cerr;
+
+ if ((ev = munki_waitfor_switch_th(p, &ecode, NULL, 0.1)) != MUNKI_OK
+ && ev != MUNKI_INT_BUTTONTIMEOUT)
+ break; /* Error */
+
+ if (ev == MUNKI_OK && ecode == mk_eve_switch_press)
+ 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 = MUNKI_USER_ABORT;
+ break; /* Abort */
+ }
+ if (!s->scan && rc == inst_user_trig) {
+ ev = MUNKI_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 = MUNKI_UNSUPPORTED;
+
+ } else {
+
+ for (;;) {
+ inst_code rc;
+ if ((rc = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (rc == inst_user_abort) {
+ ev = MUNKI_USER_ABORT; /* Abort */
+ break;
+ }
+ if (rc == inst_user_trig) {
+ ev = MUNKI_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 = MUNKI_USER_ABORT; /* Abort */
+ }
+
+ if (ev != MUNKI_OK && ev != MUNKI_USER_TRIG) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
+ free(mbuf);
+ if (buf != NULL)
+ free(buf);
+ a1logd(p->log,3,"munki_imp_measure user aborted, terminated, command, or failure\n");
+ return ev; /* User abort, term, command or failure */
+ }
+
+ /* Get current sensor position */
+ if ((ev = munki_getstatus(p, &spos, NULL)) != MUNKI_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
+ free(mbuf);
+ if (buf != NULL)
+ free(buf);
+ a1logd(p->log,3,"munki_imp_measure getstatus failed\n");
+ return ev;
+ }
+
+ /* Check the current sensor position */
+ if (!m->nosposcheck) {
+ if (s->emiss) {
+ if (s->ambient) {
+ if (spos != mk_spos_amb)
+ ev = MUNKI_SPOS_AMB;
+ } else if (s->projector) {
+ if (spos != mk_spos_proj)
+ ev = MUNKI_SPOS_PROJ;
+ } else { /* Display */
+ if (spos != mk_spos_surf)
+ ev = MUNKI_SPOS_SURF;
+ }
+ } else { /* Reflective or transmissive */
+ if (spos != mk_spos_surf)
+ ev = MUNKI_SPOS_SURF;
+ }
+ if (ev != MUNKI_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
+ free(mbuf);
+ if (buf != NULL)
+ free(buf);
+ a1logd(p->log,3,"munki_imp_measure: Sensor in wrong position\n");
+ return ev;
+ }
+ }
+
+ /* Emissive adaptive, non-scan */
+ 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,3,"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 = munki_comp_nummeas(p, s->wreadtime, s->inttime);
+ if ((ev = munki_trialmeasure(p, &saturated, &optscale, nummeas, &s->inttime, s->gainmode,
+ s->targoscale)) != MUNKI_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
+ free(mbuf);
+ a1logd(p->log,3,"munki_imp_measure trial measure failed\n");
+ return ev;
+ }
+
+ if (saturated) {
+ s->inttime = m->min_int_time;
+
+ a1logd(p->log,3,"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 = munki_comp_nummeas(p, s->wreadtime, s->inttime);
+ if ((ev = munki_trialmeasure(p, &saturated, &optscale, nummeas, &s->inttime,
+ s->gainmode, s->targoscale)) != MUNKI_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
+ free(mbuf);
+ a1logd(p->log,3,"munki_imp_measure trial measure failed\n");
+ return ev;
+ }
+ }
+
+ a1logd(p->log,3,"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 = munki_optimise_sensor(p, &s->inttime, &s->gainmode,
+ s->inttime, s->gainmode, 1, 1, &s->targoscale, optscale, 0.0)) != MUNKI_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
+ free(mbuf);
+ a1logd(p->log,3,"munki_imp_measure optimise sensor failed\n");
+ return ev;
+ }
+ a1logd(p->log,3,"Computed optimal emiss inttime %f and gainmode %d\n",s->inttime,s->gainmode);
+
+ a1logd(p->log,3,"Interpolate dark calibration reference\n");
+ if ((ev = munki_interp_dark(p, s->dark_data, s->inttime, s->gainmode)) != MUNKI_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
+ free(mbuf);
+ a1logd(p->log,3,"munki_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 = munki_comp_nummeas(p, s->wreadtime, s->inttime);
+ maxnummeas = munki_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-1);
+ a1logd(p->log,1,"munki_imp_measure malloc %d bytes failed (7)\n",mbsize);
+ return MUNKI_INT_MALLOC;
+ }
+
+ } else if (s->reflective) {
+
+ DISDPLOT
+
+ a1logd(p->log,3,"Doing on the fly black calibration_1 with nummeas %d int_time %f, gainmode %d\n",
+ nummeas, s->inttime, s->gainmode);
+
+ if ((ev = munki_dark_measure_1(p, nummeas, &s->inttime, s->gainmode, buf, bsize))
+ != MUNKI_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
+ free(buf);
+ free(mbuf);
+ a1logd(p->log,3,"munki_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,3,"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) {
+ /* 100msec delay, 1KHz for 200 msec */
+ msec_beep(0 + (int)(invsampt * 1000.0 + 0.9), 1000, 200);
+ }
+
+ /* Retry loop in case a display read is saturated */
+ for (;;) {
+
+ /* Trigger measure and gather raw readings */
+ if ((ev = munki_read_patches_1(p, ninvmeas, nummeas, maxnummeas, &s->inttime, s->gainmode,
+ &nmeasuered, mbuf, mbsize)) != MUNKI_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
+ if (buf != NULL)
+ free(buf);
+ free(mbuf);
+ a1logd(p->log,3,"munki_imp_measure failed at munki_read_patches_1\n");
+ return ev;
+ }
+
+ /* Complete processing of dark readings now that main measurement has been taken */
+ 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 = munki_dark_measure_2(p, s->dark_data, nummeas, s->inttime,
+ s->gainmode, buf, bsize)) != MUNKI_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
+ free(buf);
+ free(mbuf);
+ a1logd(p->log,3,"munki_imp_measure failed at munki_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 = munki_read_patches_2(p, &duration, specrd, nvals, s->inttime, s->gainmode,
+ ninvmeas, 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 == MUNKI_RD_SENSORSATURATED
+ && s->dispswap < 2) {
+ double *tt, tv;
+
+ if (s->dispswap == 0) {
+ a1logd(p->log,3,"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,3,"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 = munki_comp_nummeas(p, s->wreadtime, s->inttime);
+ maxnummeas = munki_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-1);
+ a1logd(p->log,1,"munki_imp_measure malloc %d bytes failed (7)\n",mbsize);
+ return MUNKI_INT_MALLOC;
+ }
+ continue; /* Do the measurement again */
+ }
+
+ if (ev != MUNKI_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
+ free(mbuf);
+ a1logd(p->log,3,"munki_imp_measure failed at munki_read_patches_2\n");
+ return ev;
+ }
+ break; /* Don't repeat */
+ }
+ free(mbuf);
+
+ /* Transfer spectral and convert to XYZ */
+ if ((ev = munki_conv2XYZ(p, vals, nvals, specrd, clamp)) != MUNKI_OK) {
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
+ a1logd(p->log,3,"munki_imp_measure failed at munki_conv2XYZ\n");
+ return ev;
+ }
+ free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
+
+ if (nvals > 0)
+ vals[0].duration = duration; /* Possible flash duration */
+
+ a1logd(p->log,3,"munki_imp_measure sucessful return\n");
+ if (user_trig)
+ return MUNKI_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;
+*/
+
+munki_code munki_measure_rgb(munki *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 */
+
+munki_code munki_imp_meas_refrate(
+ munki *p,
+ double *ref_rate
+) {
+ munki_code ev = MUNKI_OK;
+ munkiimp *m = (munkiimp *)p->m;
+ munki_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,"munki_imp_meas_refrate called\n");
+
+ if (!s->emiss) {
+ a1logd(p->log,2,"munki_imp_meas_refrate not in emissive mode\n");
+ return MUNKI_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-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 = munki_read_patches_all(p, multimeas, nummeas, &inttime, 0)) != inst_ok) {
+ free_dmatrix(multimeas, 0, nummeas-1, 0, m->nwav-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; j++) {
+ double wl = XSPECT_WL(m->wl_short, m->wl_long, m->nwav, 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, "munki_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 /* Interpolate 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, "munki_measure_refresh: malloc failed\n");
+ return MUNKI_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, "munki_measure_refresh: malloc failed\n");
+ for (j = 0; j < 3; j++)
+ free(bins[j]);
+ return MUNKI_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("munki: 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 MUNKI_OK;
+ }
+ }
+ } else {
+ a1logd(p->log, 3, "Not enough tries suceeded to determine refresh rate\n");
+ }
+
+ if (ref_rate != NULL)
+ *ref_rate = 0.0;
+
+ return MUNKI_RD_NOREFR_FOUND;
+}
+#undef NFSAMPS
+#undef PBPMS
+#undef PERMIN
+#undef PERMAX
+#undef NPER
+#undef PWIDTH
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Save the calibration for all modes, stored on local system */
+
+#ifdef ENABLE_NONVCAL
+
+/* non-volatile save/restor state to/from a file */
+typedef struct {
+ int ef; /* Error flag, 1 = write failed, 2 = close failed */
+ unsigned int chsum; /* Checksum */
+} mknonv;
+
+static void update_chsum(mknonv *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;
+}
+
+/* Write an array of chars to the file. Set the error flag to nz on error */
+static void write_chars(mknonv *x, FILE *fp, char *dp, int n) {
+
+ if (fwrite((void *)dp, sizeof(char), n, fp) != n) {
+ x->ef = 1;
+ } else {
+ update_chsum(x, (unsigned char *)dp, n * sizeof(char));
+ }
+}
+
+/* Write an array of ints to the file. Set the error flag to nz on error */
+static void write_ints(mknonv *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(mknonv *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(mknonv *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(mknonv *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 chars from the file. Set the error flag to nz on error */
+static void read_chars(mknonv *x, FILE *fp, char *dp, int n) {
+
+ if (fread((void *)dp, sizeof(char), n, fp) != n) {
+ x->ef = 1;
+ } else {
+ update_chsum(x, (unsigned char *)dp, n * sizeof(char));
+ }
+}
+
+
+/* Read an array of doubles from the file. Set the error flag to nz on error */
+static void read_doubles(mknonv *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(mknonv *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));
+ }
+}
+
+munki_code munki_save_calibration(munki *p) {
+ munkiimp *m = (munkiimp *)p->m;
+ munki_code ev = MUNKI_OK;
+ munki_state *s;
+ int i;
+ char nmode[10];
+ char cal_name[100]; /* Name */
+ char **cal_paths = NULL;
+ int no_paths = 0;
+ FILE *fp;
+ mknonv 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
+
+ sprintf(cal_name, "ArgyllCMS/.mk_%s.cal", m->serno);
+ if ((no_paths = xdg_bds(NULL, &cal_paths, xdg_cache, xdg_write, xdg_user, cal_name)) < 1) {
+ a1logd(p->log,1,"munki_save_calibration xdg_bds returned no paths\n");
+ return MUNKI_INT_CAL_SAVE;
+ }
+
+ a1logd(p->log,3,"munki_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,3,"munki_save_calibration failed to open file for writing\n");
+ xdg_free(cal_paths, no_paths);
+ return MUNKI_INT_CAL_SAVE;
+ }
+
+ x.ef = 0;
+ x.chsum = 0;
+
+ /* A crude structure signature */
+ ss = sizeof(munki_state) + sizeof(munkiimp);
+
+ /* Some file identification */
+ write_ints(&x, fp, &argyllversion, 1);
+ write_ints(&x, fp, &ss, 1);
+ write_chars(&x, fp, m->serno, 17);
+ write_ints(&x, fp, &m->nraw, 1);
+ write_ints(&x, fp, (int *)&m->nwav1, 1);
+ write_ints(&x, fp, (int *)&m->nwav2, 1);
+
+ /* For each mode, save the calibration if it's valid */
+ for (i = 0; i < mk_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->projector, 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->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_factor1, m->nwav1);
+ write_doubles(&x, fp, s->cal_factor2, m->nwav2);
+ write_doubles(&x, fp, s->white_data-1, m->nraw+1);
+ write_doubles(&x, fp, &s->reftemp, 1);
+ write_doubles(&x, fp, s->iwhite_data[0]-1, m->nraw+1);
+ write_doubles(&x, fp, s->iwhite_data[1]-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,"Checkum = 0x%x\n",x.chsum);
+ write_ints(&x, fp, (int *)&x.chsum, 1);
+
+ if (fclose(fp) != 0)
+ x.ef = 2;
+
+ if (x.ef != 0) {
+ a1logd(p->log,3,"Writing calibration file failed with %d\n",x.ef);
+ delete_file(cal_paths[0]);
+ } else {
+ a1logd(p->log,3,"Writing calibration file succeeded\n");
+ }
+ xdg_free(cal_paths, no_paths);
+
+ return ev;
+}
+
+/* Restore the all modes calibration from the local system */
+munki_code munki_restore_calibration(munki *p) {
+ munkiimp *m = (munkiimp *)p->m;
+ munki_code ev = MUNKI_OK;
+ munki_state *s, ts;
+ int i, j;
+ char nmode[10];
+ char cal_name[100]; /* Name */
+ char **cal_paths = NULL;
+ int no_paths = 0;
+ FILE *fp;
+ mknonv x;
+ int argyllversion;
+ int ss, nraw, nwav1, nwav2, chsum1, chsum2;
+ char serno[17];
+
+ strcpy(nmode, "r");
+#if defined(O_BINARY) || defined(_O_BINARY)
+ strcat(nmode, "b");
+#endif
+
+ sprintf(cal_name, "ArgyllCMS/.mk_%s.cal" SSEPS "color/.mk_%s.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,1,"munki_restore_calibration xdg_bds returned no paths\n");
+ return MUNKI_INT_CAL_RESTORE;
+ }
+
+ a1logd(p->log,2,"munki_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,"munki_restore_calibration: %d secs from instrument last open\n",m->lo_secs);
+ } else {
+ a1logd(p->log,2,"munki_restore_calibration: stat on file failed\n");
+ }
+ }
+
+ if ((fp = fopen(cal_paths[0], nmode)) == NULL) {
+ a1logd(p->log,2,"munki_restore_calibration failed to open file for reading\n");
+ xdg_free(cal_paths, no_paths);
+ return MUNKI_INT_CAL_RESTORE;
+ }
+ xdg_free(cal_paths, no_paths);
+
+ x.ef = 0;
+ x.chsum = 0;
+
+ /* Check the file identification */
+ read_ints(&x, fp, &argyllversion, 1);
+ read_ints(&x, fp, &ss, 1);
+ read_chars(&x, fp, serno, 17);
+ read_ints(&x, fp, &nraw, 1);
+ read_ints(&x, fp, &nwav1, 1);
+ read_ints(&x, fp, &nwav2, 1);
+ if (x.ef != 0
+ || argyllversion != ARGYLL_VERSION
+ || ss != (sizeof(munki_state) + sizeof(munkiimp))
+ || strcmp(serno, m->serno) != 0
+ || nraw != m->nraw
+ || nwav1 != m->nwav1
+ || nwav2 != m->nwav2) {
+ a1logd(p->log,3,"Identification didn't verify\n");
+ goto reserr;
+ }
+
+ /* Do a dummy read to check the checksum */
+ for (i = 0; i < mk_no_modes; i++) {
+ int di;
+ double dd;
+ time_t dt;
+ int emiss, trans, reflective, ambient, projector, 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, &projector, 1);
+ read_ints(&x, fp, &adaptive, 1);
+
+ /* Configuration calibration is valid for */
+ read_ints(&x, fp, &di, 1); /* gainmode */
+ read_doubles(&x, fp, &dd, 1); /* inttime */
+
+ /* Calibration information */
+ read_ints(&x, fp, &di, 1); /* dark_valid */
+ read_time_ts(&x, fp, &dt, 1); /* ddate */
+ read_doubles(&x, fp, &dd, 1); /* dark_int_time */
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1); /* dark_data */
+ read_doubles(&x, fp, &dd, 1); /* dark_int_time2 */
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1); /* dark_data2 */
+ read_doubles(&x, fp, &dd, 1); /* dark_int_time3 */
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1); /* dark_data3 */
+ read_ints(&x, fp, &di, 1); /* dark_gain_mode */
+
+ if (!s->emiss) {
+ read_ints(&x, fp, &di, 1); /* cal_valid */
+ read_time_ts(&x, fp, &dt, 1); /* cfdate */
+ for (j = 0; j < m->nwav1; j++)
+ read_doubles(&x, fp, &dd, 1); /* cal_factor1 */
+ for (j = 0; j < m->nwav2; j++)
+ read_doubles(&x, fp, &dd, 1); /* cal_factor2 */
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1); /* white_data */
+ read_doubles(&x, fp, &dd, 1); /* reftemp */
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1); /* iwhite_data[0] */
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1); /* iwhite_data[1] */
+ }
+
+ read_ints(&x, fp, &di, 1); /* idark_valid */
+ read_time_ts(&x, fp, &dt, 1); /* iddate */
+ for (j = 0; j < 4; j++)
+ read_doubles(&x, fp, &dd, 1); /* idark_int_time */
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1); /* idark_data[0] */
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1); /* idark_data[1] */
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1); /* idark_data[2] */
+ for (j = -1; j < m->nraw; j++)
+ read_doubles(&x, fp, &dd, 1); /* idark_data[3] */
+ }
+
+ chsum1 = x.chsum;
+ read_ints(&x, fp, &chsum2, 1);
+
+ if (x.ef != 0
+ || chsum1 != chsum2) {
+ a1logd(p->log,3,"Checksum didn't verify, got 0x%x, expected 0x%x\n",chsum1, chsum2);
+ goto reserr;
+ }
+
+ rewind(fp);
+
+ /* 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_factor1 = dvectorz(0, m->nwav1-1);
+ ts.cal_factor2 = dvectorz(0, m->nwav2-1);
+ ts.white_data = dvectorz(-1, m->nraw-1);
+ ts.iwhite_data = dmatrixz(0, 2, -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_chars(&x, fp, m->serno, 17);
+ read_ints(&x, fp, &m->nraw, 1);
+ read_ints(&x, fp, (int *)&m->nwav1, 1);
+ read_ints(&x, fp, (int *)&m->nwav2, 1);
+
+ /* For each mode, save the calibration if it's valid */
+ for (i = 0; i < mk_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.projector, 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: */
+
+ /* 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_factor1, m->nwav1);
+ read_doubles(&x, fp, ts.cal_factor2, m->nwav2);
+ read_doubles(&x, fp, ts.white_data-1, m->nraw+1);
+ read_doubles(&x, fp, &ts.reftemp, 1);
+ read_doubles(&x, fp, ts.iwhite_data[0]-1, m->nraw+1);
+ read_doubles(&x, fp, ts.iwhite_data[1]-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->projector == ts.projector
+ && 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->projector = ts.projector;
+ s->adaptive = ts.adaptive;
+
+ s->gainmode = ts.gainmode;
+ s->inttime = ts.inttime;
+ 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->nwav1; j++)
+ s->cal_factor1[j] = ts.cal_factor1[j];
+ for (j = 0; j < m->nwav2; j++)
+ s->cal_factor2[j] = ts.cal_factor2[j];
+ for (j = -1; j < m->nraw; j++)
+ s->white_data[j] = ts.white_data[j];
+ s->reftemp = ts.reftemp;
+ for (j = -1; j < m->nraw; j++)
+ s->iwhite_data[0][j] = ts.iwhite_data[0][j];
+ for (j = -1; j < m->nraw; j++)
+ s->iwhite_data[1][j] = ts.iwhite_data[1][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 {
+//printf("~1 mode %d\n",i);
+//printf("~1 adaptive = %d\n",s->adaptive);
+//printf("~1 innttime %f %f\n",s->inttime, ts.inttime);
+//printf("~1 dark_int_time %f %f\n",s->dark_int_time,ts.dark_int_time);
+//printf("~1 dark_int_time2 %f %f\n",s->dark_int_time2,ts.dark_int_time2);
+//printf("~1 dark_int_time3 %f %f\n",s->dark_int_time3,ts.dark_int_time3);
+//printf("~1 idark_int_time0 %f %f\n",s->idark_int_time[0],ts.idark_int_time[0]);
+//printf("~1 idark_int_time1 %f %f\n",s->idark_int_time[1],ts.idark_int_time[1]);
+//printf("~1 idark_int_time2 %f %f\n",s->idark_int_time[2],ts.idark_int_time[2]);
+//printf("~1 idark_int_time3 %f %f\n",s->idark_int_time[3],ts.idark_int_time[3]);
+ a1logd(p->log,4,"Not restoring cal for mode %d since params don't match:\n",i);
+ a1logd(p->log,4,"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,4,"scan = %d : %d, flash = %d : %d, ambi = %d : %d, proj = %d : %d, adapt = %d : %d\n",s->scan,ts.scan,s->flash,ts.flash,s->ambient,ts.ambient,s->projector,ts.projector,s->adaptive,ts.adaptive);
+ a1logd(p->log,4,"inttime = %f : %f\n",s->inttime,ts.inttime);
+ a1logd(p->log,4,"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,4,"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.iwhite_data, 0, 1, -1, m->nraw-1);
+ free_dmatrix(ts.idark_data, 0, 3, -1, m->nraw-1);
+
+ free_dvector(ts.cal_factor1, 0, m->nwav1-1);
+ free_dvector(ts.cal_factor2, 0, m->nwav2-1);
+
+ a1logd(p->log,3,"munki_restore_calibration done\n");
+ reserr:;
+
+ fclose(fp);
+
+ return ev;
+}
+
+munki_code munki_touch_calibration(munki *p) {
+ munkiimp *m = (munkiimp *)p->m;
+ munki_code ev = MUNKI_OK;
+ char cal_name[100]; /* Name */
+ char **cal_paths = NULL;
+ int no_paths = 0;
+ int rv;
+
+ sprintf(cal_name, "ArgyllCMS/.mk_%s.cal" SSEPS "color/.mk_%s.cal", m->serno, m->serno);
+ if ((no_paths = xdg_bds(NULL, &cal_paths, xdg_cache, xdg_read, xdg_user, cal_name)) < 1)
+ return MUNKI_INT_CAL_TOUCH;
+
+ a1logd(p->log,2,"munki_touch_calibration touching file '%s'\n",cal_paths[0]);
+
+ if ((rv = sys_utime(cal_paths[0], NULL)) != 0) {
+ a1logd(p->log,2,"munki_touch_calibration failed with %d\n",rv);
+ xdg_free(cal_paths, no_paths);
+ return MUNKI_INT_CAL_TOUCH;
+ }
+ xdg_free(cal_paths, no_paths);
+
+ return ev;
+}
+
+#endif /* ENABLE_NONVCAL */
+
+
+/* ============================================================ */
+/* Intermediate routines - composite commands/processing */
+
+/* Take a dark reference measurement - part 1 */
+munki_code munki_dark_measure_1(
+ munki *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 */
+) {
+ munki_code ev = MUNKI_OK;
+
+ if (nummeas <= 0)
+ return MUNKI_INT_ZEROMEASURES;
+
+ if ((ev = munki_trigger_one_measure(p, nummeas, inttime, gainmode, 1, 1)) != MUNKI_OK)
+ return ev;
+
+ if ((ev = munki_readmeasurement(p, nummeas, 0, buf, bsize, NULL, 1, 1)) != MUNKI_OK)
+ return ev;
+
+ return ev;
+}
+
+/* Take a dark reference measurement - part 2 */
+munki_code munki_dark_measure_2(
+ munki *p,
+ double *sens, /* Return array [-1 nraw] of sens 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 */
+) {
+ munki_code ev = MUNKI_OK;
+ munkiimp *m = (munkiimp *)p->m;
+ double **multimes; /* Multiple measurement results */
+ double darkthresh; /* Dark threshold */
+ double sensavg; /* Overall average of sensor readings */
+ int rv;
+
+ multimes = dmatrix(0, nummeas-1, -1, m->nraw-1);
+
+ /* Take a buffer full of raw readings, and convert them to */
+ /* floating point sensor readings. Check for saturation */
+ if ((rv = munki_sens_to_raw(p, multimes, NULL, buf, 0, nummeas, m->satlimit, &darkthresh))
+ != MUNKI_OK) {
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ return rv;
+ }
+
+ /* Average a set of measurements into one. */
+ /* Return nz if the readings are not consistent */
+ /* Return the overall average. */
+ rv = munki_average_multimeas(p, sens, multimes, nummeas, &sensavg, darkthresh);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+
+#ifdef PLOT_DEBUG
+ printf("Average absolute sensor readings, average = %f, darkthresh %f\n",sensavg,darkthresh);
+ plot_raw(sens);
+#endif
+
+ if (rv) {
+ a1logd(p->log,3,"munki_dark_measure_2: readings are inconsistent\n");
+ return MUNKI_RD_DARKREADINCONS;
+ }
+
+ if (sensavg > (2.0 * darkthresh)) {
+ a1logd(p->log,3,"munki_dark_measure_2: Average %f is > 2 * darkthresh %f\n",sensavg,darkthresh);
+ return MUNKI_RD_DARKNOTVALID;
+ }
+ return ev;
+}
+
+#ifdef DUMP_DARKM
+int ddumpdarkm = 0;
+#endif
+
+/* Take a dark reference measurement (combined parts 1 & 2) */
+munki_code munki_dark_measure(
+ munki *p,
+ double *raw, /* Return array [-1 nraw] of raw 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 */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ munki_code ev = MUNKI_OK;
+ unsigned char *buf; /* Raw USB reading buffer */
+ unsigned int bsize;
+
+ a1logd(p->log,3, "munki_dark_measure with inttime %f\n",*inttime);
+ bsize = m->nsen * 2 * nummeas;
+ if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
+ a1logd(p->log,1,"munki_dark_measure malloc %d bytes failed (8)\n",bsize);
+ return MUNKI_INT_MALLOC;
+ }
+
+ if ((ev = munki_dark_measure_1(p, nummeas, inttime, gainmode, buf, bsize)) != MUNKI_OK) {
+ free(buf);
+ return ev;
+ }
+
+ if ((ev = munki_dark_measure_2(p, raw, nummeas, *inttime, gainmode, buf, bsize))
+ != MUNKI_OK) {
+ free(buf);
+ return ev;
+ }
+ free(buf);
+
+#ifdef DUMP_DARKM
+ /* Dump raw dark readings to a file "mkddump.txt" */
+ if (ddumpdarkm) {
+ int j;
+ FILE *fp;
+
+ if ((fp = fopen("mkddump.txt", "a")) == NULL)
+ a1logw(p->log,"Unable to open debug file mkddump.txt\n");
+ else {
+ fprintf(fp, "\nDark measure: nummeas %d, inttime %f, gainmode %d, darkcells %f\n",nummeas,*inttime,gainmode, raw[-1]);
+ fprintf(fp,"\t\t\t{ ");
+ for (j = 0; j < (m->nraw-1); j++)
+ fprintf(fp, "%f, ",raw[j]);
+ fprintf(fp, "%f },\n",raw[j]);
+ fclose(fp);
+ }
+ }
+#endif
+ return ev;
+}
+
+/* Heat the LED up for given number of seconds by taking a reading */
+munki_code munki_heatLED(
+ munki *p,
+ double htime /* Heat up time */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ double inttime = m->cal_int_time; /* Integration time to use/used */
+ int nummeas;
+ unsigned char *buf; /* Raw USB reading buffer */
+ unsigned int bsize;
+ int rv;
+
+ a1logd(p->log,3,"munki_heatLED called \n");
+
+ nummeas = munki_comp_ru_nummeas(p, htime, inttime);
+
+ if (nummeas <= 0)
+ return MUNKI_OK;
+
+ /* Allocate temporaries */
+ bsize = m->nsen * 2 * nummeas;
+ if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
+ a1logd(p->log,1,"munki_heatLED malloc %d bytes failed (10)\n",bsize);
+ return MUNKI_INT_MALLOC;
+ }
+
+ a1logd(p->log,3,"Triggering measurement cycle, nummeas %d, inttime %f\n", nummeas, inttime);
+
+ if ((rv = munki_trigger_one_measure(p, nummeas, &inttime, 0, 1, 0))
+ != MUNKI_OK) {
+ free(buf);
+ return rv;
+ }
+
+ a1logd(p->log,3,"Gathering readings\n");
+
+ rv = munki_readmeasurement(p, nummeas, 0, buf, bsize, NULL, 1, 0);
+
+ free(buf);
+
+ return rv;
+}
+
+
+/* Take a reflective or emissive white reference sens measurement, */
+/* subtracts black and processes into wavelenths. */
+/* (absraw is usually ->white_data) */
+munki_code munki_whitemeasure(
+ munki *p,
+ double *absraw, /* Return array [-1 nraw] of absraw values (may be NULL) */
+ double *optscale, /* Return scale for gain/int time 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 /* Ratio of optimal sensor value to aim for */
+) {
+ munki_code ev = MUNKI_OK;
+ munkiimp *m = (munkiimp *)p->m;
+ munki_state *s = &m->ms[m->mmode];
+ unsigned char *buf; /* Raw USB reading buffer */
+ unsigned int bsize;
+ int ninvmeas = 0; /* Number of invalid measurements */
+ double **multimes; /* Multiple measurement results */
+ double sensavg; /* Overall average of sensor readings */
+ double darkthresh; /* Dark threshold */
+ double trackmax[3]; /* Track optimum target & darkthresh */
+ double maxval; /* Maximum multimeas value */
+ int rv;
+
+ a1logd(p->log,3,"munki_whitemeasure called \n");
+
+ if (s->reflective) {
+ /* Compute invalid samples to allow for LED warmup */
+ ninvmeas = munki_comp_ru_nummeas(p, m->refinvalidsampt, *inttime);
+ }
+
+ if (nummeas <= 0)
+ return MUNKI_INT_ZEROMEASURES;
+
+ /* Allocate temporaries */
+ bsize = m->nsen * 2 * (ninvmeas + nummeas);
+ if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
+ a1logd(p->log,1,"munki_whitemeasure malloc %d bytes failed (10)\n",bsize);
+ return MUNKI_INT_MALLOC;
+ }
+
+ a1logd(p->log,3,"Triggering measurement cycle, ninvmeas %d, nummeas %d, inttime %f, gainmode %d\n",
+ ninvmeas, nummeas, *inttime, gainmode);
+
+ if ((ev = munki_trigger_one_measure(p, ninvmeas + nummeas, inttime, gainmode, 1, 0))
+ != MUNKI_OK) {
+ free(buf);
+ return ev;
+ }
+
+ a1logd(p->log,3,"Gathering readings\n");
+
+ if ((ev = munki_readmeasurement(p, ninvmeas + nummeas, 0, buf, bsize, NULL, 1, 0))
+ != MUNKI_OK) {
+ free(buf);
+ return ev;
+ }
+
+ multimes = dmatrix(0, nummeas-1, -1, m->nraw-1);
+
+ /* Take a buffer full of raw readings, and convert them to */
+ /* floating point sensor readings. Check for saturation */
+ if ((rv = munki_sens_to_raw(p, multimes, NULL, buf, ninvmeas, nummeas, m->satlimit,
+ &darkthresh)) != MUNKI_OK) {
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ return rv;
+ }
+
+#ifdef PLOT_DEBUG
+ printf("Dark data:\n");
+ plot_raw(s->dark_data);
+#endif
+
+ trackmax[0] = darkthresh; /* Track the dark threshold value */
+ trackmax[1] = m->optsval; /* Track the optimal sensor target value */
+ trackmax[2] = m->satlimit; /* For debugging */
+
+ /* Subtract the black from sensor values and convert to */
+ /* absolute (integration & gain scaled), zero offset based, */
+ /* linearized sensor values. */
+ /* Return the highest individual element. */
+ munki_sub_raw_to_absraw(p, nummeas, *inttime, gainmode, multimes, s->dark_data,
+ trackmax, 3, &maxval);
+ darkthresh = trackmax[0];
+ free(buf);
+
+ if (absraw != NULL) {
+ /* Average a set of measurements into one. */
+ /* Return nz if the readings are not consistent */
+ /* Return the overall average. */
+ rv = munki_average_multimeas(p, absraw, multimes, nummeas, &sensavg, darkthresh);
+
+#ifndef IGNORE_WHITE_INCONS
+ if (rv) {
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ return MUNKI_RD_WHITEREADINCONS;
+ }
+#endif /* IGNORE_WHITE_INCONS */
+
+ a1logd(p->log,3,"Average absolute sensor readings, avg %f, max %f, darkth %f satth %f\n",
+ sensavg,maxval,darkthresh,trackmax[2]);
+#ifdef PLOT_DEBUG
+ plot_raw(absraw);
+#endif
+ }
+
+ if (optscale != NULL) {
+ double opttarget; /* Optimal reading scale target value */
+
+ opttarget = targoscale * trackmax[1];
+ if (maxval < 0.01) /* Could go -ve */
+ maxval = 0.01;
+ *optscale = opttarget/maxval;
+
+ a1logd(p->log,3,"Targscale %f, maxval %f, optimal target = %f, amount to scale = %f\n",
+ targoscale, maxval, opttarget, *optscale);
+ }
+
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1); /* Free after using *pmax */
+
+ return ev;
+}
+
+/* Given an absraw white reference measurement, */
+/* compute the wavelength equivalents. */
+/* (absraw is usually ->white_data) */
+/* (abswav1 is usually ->cal_factor1) */
+/* (abswav2 is usually ->cal_factor2) */
+munki_code munki_compute_wav_whitemeas(
+ munki *p,
+ double *abswav1, /* Return array [nwav1] of abswav values (may be NULL) */
+ double *abswav2, /* Return array [nwav2] of abswav values (if hr_init, may be NULL) */
+ double *absraw /* Given array [-1 nraw] of absraw values */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+
+ /* Convert an absraw array from raw wavelengths to output wavelenths */
+ if (abswav1 != NULL) {
+ munki_absraw_to_abswav1(p, 1, &abswav1, &absraw);
+
+#ifdef PLOT_DEBUG
+ printf("Converted to wavelengths std res:\n");
+ plot_wav1(m, abswav1);
+#endif
+ }
+
+#ifdef HIGH_RES
+ if (abswav2 != NULL && m->hr_inited == 2) {
+ munki_absraw_to_abswav2(p, 1, &abswav2, &absraw);
+
+#ifdef PLOT_DEBUG
+ printf("Converted to wavelengths high res:\n");
+ plot_wav2(m, abswav2);
+#endif
+ }
+#endif /* HIGH_RES */
+
+ return MUNKI_OK;
+}
+
+/* Take a reflective white reference measurement, */
+/* subtracts black and decompose into base + LED temperature components */
+munki_code munki_ledtemp_whitemeasure(
+ munki *p,
+ double *white, /* Return [-1 nraw] of temperature compensated white reference */
+ double **iwhite, /* Return array [-1 nraw][2] of absraw base and scale values */
+ double *reftemp, /* Return a reference temperature to normalize to */
+ int nummeas, /* Number of readings to take */
+ double inttime, /* Integration time to use/used */
+ int gainmode /* Gain mode to use, 0 = normal, 1 = high */
+) {
+ munki_code ev = MUNKI_OK;
+ munkiimp *m = (munkiimp *)p->m;
+ munki_state *s = &m->ms[m->mmode];
+ unsigned char *buf; /* Raw USB reading buffer */
+ unsigned int bsize;
+ int ninvmeas = 0; /* Number of invalid measurements */
+ double **multimes; /* Multiple measurement results */
+ double *ledtemp; /* LED temperature for each measurement */
+ double darkthresh; /* Dark threshold */
+ int rv;
+
+ a1logd(p->log,3,"munki_ledtemp_whitemeasure called \n");
+
+ /* Compute invalid samples to allow for LED warmup */
+ ninvmeas = munki_comp_ru_nummeas(p, m->refinvalidsampt, inttime);
+
+ if (nummeas <= 0) {
+ return MUNKI_INT_ZEROMEASURES;
+ }
+
+ /* Allocate temporaries */
+ bsize = m->nsen * 2 * (ninvmeas + nummeas);
+ if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
+ a1logd(p->log,1,"munki_whitemeasure malloc %d bytes failed (10)\n",bsize);
+ return MUNKI_INT_MALLOC;
+ }
+
+ a1logd(p->log,3,"Triggering measurement cycle, ninvmeas %d, nummeas %d, inttime %f, gainmode %d\n",
+ ninvmeas, nummeas, inttime, gainmode);
+
+ if ((ev = munki_trigger_one_measure(p, ninvmeas + nummeas, &inttime, gainmode, 1, 0))
+ != MUNKI_OK) {
+ free(buf);
+ return ev;
+ }
+
+ a1logd(p->log,3,"Gathering readings\n");
+
+ if ((ev = munki_readmeasurement(p, ninvmeas + nummeas, 0, buf, bsize, NULL, 1, 0))
+ != MUNKI_OK) {
+ free(buf);
+ return ev;
+ }
+
+ multimes = dmatrix(0, nummeas-1, -1, m->nraw-1);
+ ledtemp = dvector(0, nummeas-1);
+
+ /* Take a buffer full of raw readings, and convert them to */
+ /* floating point sensor readings. Check for saturation */
+ if ((ev = munki_sens_to_raw(p, multimes, ledtemp, buf, ninvmeas, nummeas, m->satlimit,
+ &darkthresh)) != MUNKI_OK) {
+ free_dvector(ledtemp, 0, nummeas-1);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ return ev;
+ }
+
+ /* Make the reference temperature nominal */
+ *reftemp = 0.5 * (ledtemp[0] + ledtemp[nummeas-1]);
+
+#ifdef PLOT_DEBUG
+ printf("Dark data:\n");
+ plot_raw(s->dark_data);
+#endif
+
+ /* Subtract the black from sensor values and convert to */
+ /* absolute (integration & gain scaled), zero offset based, */
+ /* linearized sensor values. */
+ munki_sub_raw_to_absraw(p, nummeas, inttime, gainmode, multimes, s->dark_data,
+ &darkthresh, 1, NULL);
+
+ free(buf);
+
+ /* For each raw wavelength, compute a linear regression */
+ {
+ int i, w;
+ double tt, ss, sx, sy, sxdss, stt, b;
+
+ ss = (double)nummeas;
+ for (sx = 0.0, i = 0; i < nummeas; i++)
+ sx += ledtemp[i];
+ sxdss = sx/ss;
+
+ for (w = -1; w < m->nraw; w++) {
+ for (sy = 0.0, i = 0; i < nummeas; i++)
+ sy += multimes[i][w];
+
+ for (stt = b = 0.0, i = 0; i < nummeas; i++) {
+ tt = ledtemp[i] - sxdss;
+ stt += tt * tt;
+ b += tt * multimes[i][w];
+ }
+ b /= stt;
+
+ iwhite[0][w] = (sy - sx * b)/ss;
+ iwhite[1][w] = b;
+ }
+ }
+#ifdef DEBUG
+ { /* Verify the linear regression */
+ int i, w;
+ double x, terr = 0.0, errc = 0.0;
+
+ for (w = -1; w < m->nraw; w++) {
+ for (i = 0; i < nummeas; i++) {
+ x = iwhite[0][w] + ledtemp[i] * iwhite[1][w];
+ terr += fabs(x - multimes[i][w]);
+ errc++;
+ }
+ }
+ terr /= errc;
+ printf("Linear regression average error = %f\n",terr);
+ }
+#endif
+
+#ifdef PLOT_TEMPCOMP
+ /* Plot the raw spectra and model, 3 at a time */
+ {
+ int i, j, k;
+ double *indx;
+ double **mod;
+ indx = dvectorz(0, nummeas-1);
+ mod = dmatrix(0, 5, 0, nummeas-1);
+ for (i = 0; i < nummeas; i++)
+ indx[i] = 3.0 * i/(nummeas-1.0);
+
+ for (j = 0; (j+2) < (m->nraw-1); j += 3) {
+ for (i = 0; i < nummeas; i++) {
+ for (k = j; k < (j + 3); k++) {
+ mod[k-j][i] = iwhite[0][k] + ledtemp[i] * iwhite[1][k];
+ }
+ for (k = j; k < (j + 3); k++) {
+ mod[k-j+3][i] = multimes[i][k];
+ }
+ }
+
+ printf("Bands %d - %d\n",j, j+2);
+ do_plot6(indx, mod[0], mod[1], mod[2], mod[3], mod[4], mod[5], nummeas);
+ }
+ free_dvector(indx, 0, nummeas-1);
+ free_dmatrix(mod, 0, 5, 0, nummeas-1);
+ }
+#endif
+
+ a1logd(p->log,3,"Computed linear regression\n");
+
+#ifdef ENABLE_LEDTEMPC
+ /* Compute a temerature compensated set of white readings */
+ if ((ev = munki_ledtemp_comp(p, multimes, ledtemp, nummeas, *reftemp, iwhite)) != MUNKI_OK) {
+ free_dvector(ledtemp, 0, nummeas-1);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ return ev;
+ }
+#endif /* ENABLE_LEDTEMPC */
+
+ /* Average a set of measurements into one. */
+ if ((rv = munki_average_multimeas(p, white, multimes, nummeas, NULL, darkthresh)) != 0) {
+ free_dvector(ledtemp, 0, nummeas-1);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ a1logd(p->log,3,"munki_ledtemp_whitemeasure: readings are inconsistent\n");
+ return MUNKI_RD_DARKREADINCONS;
+ }
+
+ free_dvector(ledtemp, 0, nummeas-1);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+
+ return ev;
+}
+
+/* Given the ledtemp base and scale values, */
+/* return an absraw reflective white reference for the */
+/* given temperature */
+munki_code munki_ledtemp_white(
+ munki *p,
+ double *absraw, /* Return array [-1 nraw] of absraw base and scale values */
+ double **iwhite, /* ledtemp base and scale */
+ double ledtemp /* LED temperature value */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ int w;
+
+ for (w = -1; w < m->nraw; w++)
+ absraw[w] = iwhite[0][w] + ledtemp * iwhite[1][w];
+
+ return MUNKI_OK;
+}
+
+/* Given a set of absraw sensor readings and the corresponding temperature, */
+/* compensate the readings to be at the nominated temperature. */
+munki_code munki_ledtemp_comp(
+ munki *p,
+ double **absraw, /* [nummeas][raw] measurements to compensate */
+ double *ledtemp, /* LED temperature for each measurement */
+ int nummeas, /* Number of measurements */
+ double reftemp, /* LED reference temperature to compensate to */
+ double **iwhite /* ledtemp base and scale information */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ int i, w;
+
+ for (i = 0; i < nummeas; i++) {
+ for (w = 0; w < m->nraw; w++) { /* Don't try and compensate shielded values */
+ double targ, attemp;
+ targ = iwhite[0][w] + reftemp * iwhite[1][w];
+ attemp = iwhite[0][w] + ledtemp[i] * iwhite[1][w];
+
+ absraw[i][w] *= targ/attemp;
+ }
+ }
+ return MUNKI_OK;
+}
+
+/* Take a measurement reading using the current mode, part 1 */
+/* Converts to completely processed output readings. */
+munki_code munki_read_patches_1(
+ munki *p,
+ int ninvmeas, /* Number of extra invalid measurements at start */
+ 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 (excluding ninvmeas) */
+ unsigned char *buf, /* Raw USB reading buffer */
+ unsigned int bsize
+) {
+ munki_code ev = MUNKI_OK;
+ munkiimp *m = (munkiimp *)p->m;
+
+ if ((ninvmeas + minnummeas) <= 0)
+ return MUNKI_INT_ZEROMEASURES;
+
+ if ((minnummeas + ninvmeas) > maxnummeas)
+ maxnummeas = (minnummeas - ninvmeas);
+
+ a1logd(p->log,3,"Triggering & gathering cycle, ninvmeas %d, minnummeas %d, inttime %f, gainmode %d\n",
+ ninvmeas, minnummeas, *inttime, gainmode);
+
+ if ((ev = munki_trigger_one_measure(p, ninvmeas + minnummeas, inttime, gainmode, 0, 0))
+ != MUNKI_OK) {
+ return ev;
+ }
+
+ if ((ev = munki_readmeasurement(p, ninvmeas + minnummeas, m->c_measmodeflags & MUNKI_MMF_SCAN,
+ buf, bsize, nmeasuered, 0, 0)) != MUNKI_OK) {
+ return ev;
+ }
+
+ if (nmeasuered != NULL)
+ *nmeasuered -= ninvmeas; /* Correct for invalid number */
+
+ return ev;
+}
+
+/* Take a measurement reading using the current mode, part 2 */
+/* Converts to completely processed output readings. */
+munki_code munki_read_patches_2(
+ munki *p,
+ double *duration, /* Return flash duration in seconds */
+ 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 ninvmeas, /* Number of extra invalid measurements at start */
+ int nummeas, /* Number of actual measurements */
+ unsigned char *buf, /* Raw USB reading buffer */
+ unsigned int bsize
+) {
+ munki_code ev = MUNKI_OK;
+ munkiimp *m = (munkiimp *)p->m;
+ munki_state *s = &m->ms[m->mmode];
+ double **multimes; /* Multiple measurement results [maxnummeas|nummeas][-1 nraw]*/
+ double **absraw; /* Linearsised absolute sensor raw values [numpatches][-1 nraw]*/
+ double *ledtemp; /* LED temperature values */
+ double darkthresh; /* Dark threshold (for consistency checking) */
+ int rv = 0;
+
+ if (duration != NULL)
+ *duration = 0.0; /* default value */
+
+ /* Allocate temporaries */
+ multimes = dmatrix(0, nummeas-1, -1, m->nraw-1);
+ ledtemp = dvector(0, nummeas-1);
+ absraw = dmatrix(0, numpatches-1, -1, m->nraw-1);
+
+ /* Take a buffer full of raw readings, and convert them to */
+ /* floating point sensor readings. Check for saturation */
+ if ((rv = munki_sens_to_raw(p, multimes, ledtemp, buf, ninvmeas, nummeas,
+ m->satlimit, &darkthresh)) != MUNKI_OK) {
+ free_dvector(ledtemp, 0, nummeas-1);
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ return rv;
+ }
+
+ /* Subtract the black from sensor values and convert to */
+ /* absolute (integration & gain scaled), zero offset based, */
+ /* linearized sensor values. */
+ munki_sub_raw_to_absraw(p, nummeas, inttime, gainmode, multimes, s->dark_data,
+ &darkthresh, 1, NULL);
+
+#ifdef PLOT_TEMPCOMP
+ /* Plot the raw spectra, 6 at a time */
+ if (s->reflective) {
+ int i, j, k;
+ double *indx;
+ double **mod;
+ indx = dvectorz(0, nummeas-1);
+ mod = dmatrix(0, 5, 0, nummeas-1);
+ for (i = 0; i < nummeas; i++)
+ indx[i] = (double)i;
+
+// for (j = 0; (j+5) < m->nraw; j += 6) {
+ for (j = 50; (j+5) < 56; j += 6) {
+ for (i = 0; i < nummeas; i++) {
+ for (k = j; k < (j + 6); k++) {
+ mod[k-j][i] = multimes[i][k];
+ }
+ }
+
+ printf("Before temp comp, bands %d - %d\n",j, j+5);
+ do_plot6(indx, mod[0], mod[1], mod[2], mod[3], mod[4], mod[5], nummeas);
+ }
+ free_dvector(indx, 0, nummeas-1);
+ free_dmatrix(mod, 0, 5, 0, nummeas-1);
+ }
+#endif
+#ifdef DUMP_SCANV
+ /* Dump raw scan readings to a file "mkdump.txt" */
+ {
+ int i, j;
+ FILE *fp;
+
+ if ((fp = fopen("mkdump.txt", "w")) == NULL)
+ a1logw(p->log,"Unable to open debug file mkdump.txt\n");
+ else {
+ for (i = 0; i < nummeas; i++) {
+ fprintf(fp, "%d ",i);
+ for (j = 0; j < m->nraw; j++) {
+ fprintf(fp, "%f ",multimes[i][j]);
+ }
+ fprintf(fp,"\n");
+ }
+ fclose(fp);
+ }
+ }
+#endif
+
+#ifdef ENABLE_LEDTEMPC
+ /* Do LED temperature compensation of absraw values */
+ if (s->reflective) {
+ if ((ev = munki_ledtemp_comp(p, multimes, ledtemp, nummeas, s->reftemp, s->iwhite_data))
+ != MUNKI_OK) {
+ free_dvector(ledtemp, 0, nummeas-1);
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ a1logd(p->log,3,"munki_read_patches_2 ledtemp comp failed\n");
+ return ev;
+ }
+#ifdef PLOT_TEMPCOMP
+ /* Plot the raw spectra, 6 at a time */
+ {
+ int i, j, k;
+ double *indx;
+ double **mod;
+ indx = dvectorz(0, nummeas-1);
+ mod = dmatrix(0, 5, 0, nummeas-1);
+ for (i = 0; i < nummeas; i++)
+ indx[i] = (double)i;
+
+// for (j = 0; (j+5) < m->nraw; j += 6) {
+ for (j = 50; (j+5) < 56; j += 6) {
+ for (i = 0; i < nummeas; i++) {
+ for (k = j; k < (j + 6); k++) {
+ mod[k-j][i] = multimes[i][k];
+ }
+ }
+
+ printf("After temp comp, bands %d - %d\n",j, j+5);
+ do_plot6(indx, mod[0], mod[1], mod[2], mod[3], mod[4], mod[5], nummeas);
+ }
+ free_dvector(indx, 0, nummeas-1);
+ free_dmatrix(mod, 0, 5, 0, nummeas-1);
+ }
+#endif
+ }
+#endif /* ENABLE_LEDTEMPC */
+
+
+ if (!s->scan) {
+ if (numpatches != 1) {
+ free_dvector(ledtemp, 0, nummeas-1);
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ a1logd(p->log,3,"munki_read_patches_2 spot read failed because numpatches != 1\n");
+ return MUNKI_INT_WRONGPATCHES;
+ }
+
+ /* Average a set of measurements into one. */
+ /* Return zero if readings are consistent. */
+ /* Return nz if the readings are not consistent */
+ /* Return the overall average. */
+ rv = munki_average_multimeas(p, absraw[0], multimes, nummeas, NULL, darkthresh);
+ } else {
+
+ if (s->flash) {
+
+ if (numpatches != 1) {
+ free_dvector(ledtemp, 0, nummeas-1);
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ a1logd(p->log,3,"munki_read_patches_2 spot read failed because numpatches != 1\n");
+ return MUNKI_INT_WRONGPATCHES;
+ }
+ if ((ev = munki_extract_patches_flash(p, &rv, duration, absraw[0], multimes,
+ nummeas, inttime)) != MUNKI_OK) {
+ free_dvector(ledtemp, 0, nummeas-1);
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ a1logd(p->log,3,"munki_read_patches_2 spot read failed at munki_extract_patches_flash\n");
+ return ev;
+ }
+
+ } else {
+ a1logd(p->log,3,"Number of patches to be measured = %d\n",nummeas);
+
+ /* Recognise the required number of ref/trans patch locations, */
+ /* and average the measurements within each patch. */
+ if ((ev = munki_extract_patches_multimeas(p, &rv, absraw, numpatches, multimes,
+ nummeas, inttime)) != MUNKI_OK) {
+ free_dvector(ledtemp, 0, nummeas-1);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+ a1logd(p->log,3,"munki_read_patches_2 spot read failed at munki_extract_patches_multimeas\n");
+ return ev;
+ }
+ }
+ }
+ free_dvector(ledtemp, 0, nummeas-1);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+
+ if (rv) {
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+ a1logd(p->log,3,"munki_read_patches_2 spot read failed with inconsistent readings\n");
+ return MUNKI_RD_READINCONS;
+ }
+
+ /* Convert an absraw array from raw wavelengths to output wavelenths */
+ munki_absraw_to_abswav(p, numpatches, specrd, absraw);
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+
+#ifdef APPEND_MEAN_EMMIS_VAL
+ /* Append averaged emission reading to file "mkdump.txt" */
+ {
+ int i, j;
+ FILE *fp;
+
+ /* Create wavelegth label */
+ if ((fp = fopen("mkdump.txt", "r")) == NULL) {
+ if ((fp = fopen("mkdump.txt", "w")) == NULL)
+ a1logw(p->log,"Unable to reate debug file mkdump.txt\n");
+ else {
+ for (j = 0; j < m->nwav; j++)
+ fprintf(fp,"%f ",XSPECT_WL(m->wl_short, m->wl_long, m->nwav, j));
+ fprintf(fp,"\n");
+ fclose(fp);
+ }
+ }
+ if ((fp = fopen("mkdump.txt", "a")) == NULL)
+ a1logw(p->log,"Unable to open debug file mkdump.txt\n");
+ else {
+ for (j = 0; j < m->nwav; 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, specrd[0]);
+#endif
+
+ /* Scale to the calibrated output values */
+ munki_scale_specrd(p, specrd, numpatches, specrd);
+
+#ifdef PLOT_DEBUG
+ printf("Calibrated measuerment spectra:\n");
+ plot_wav(m, 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!) */
+munki_code munki_read_patches_2a(
+ munki *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
+) {
+ munki_code ev = MUNKI_OK;
+ munkiimp *m = (munkiimp *)p->m;
+ munki_state *s = &m->ms[m->mmode];
+ double **absraw; /* Linearsised absolute sensor raw values [numpatches][-1 nraw]*/
+ double *ledtemp; /* LED temperature values */
+ double satthresh; /* Saturation threshold */
+ double darkthresh; /* Dark threshold for consistency scaling limit */
+
+ /* Allocate temporaries */
+ absraw = dmatrix(0, numpatches-1, -1, m->nraw-1);
+ ledtemp = dvector(0, numpatches-1);
+
+ /* Take a buffer full of raw readings, and convert them to */
+ /* floating point sensor readings. Check for saturation */
+ if ((ev = munki_sens_to_raw(p, absraw, ledtemp, buf, 0, numpatches,
+ m->satlimit, &darkthresh)) != MUNKI_OK) {
+ free_dvector(ledtemp, 0, numpatches-1);
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+ return ev;
+ }
+
+ /* Subtract the black from sensor values and convert to */
+ /* absolute (integration & gain scaled), zero offset based, */
+ /* linearized sensor values. */
+ munki_sub_raw_to_absraw(p, numpatches, inttime, gainmode, absraw, s->dark_data,
+ &darkthresh, 1, NULL);
+
+ a1logd(p->log,3,"Number of patches measured = %d\n",numpatches);
+
+ /* Convert an absraw array from raw wavelengths to output wavelenths */
+ munki_absraw_to_abswav(p, numpatches, specrd, absraw);
+
+ free_dvector(ledtemp, 0, numpatches-1);
+ free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
+
+#ifdef PLOT_DEBUG
+ printf("Converted to wavelengths:\n");
+ plot_wav(m, specrd[0]);
+#endif
+
+ /* Scale to the calibrated output values */
+ munki_scale_specrd(p, specrd, numpatches, specrd);
+
+#ifdef PLOT_DEBUG
+ printf("Calibrated measuerment spectra:\n");
+ plot_wav(m, specrd[0]);
+#endif
+
+ 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. */
+munki_code munki_read_patches_all(
+ munki *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 */
+) {
+ munki_code ev = MUNKI_OK;
+ munkiimp *m = (munkiimp *)p->m;
+ munki_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,"munki_read_patches malloc %d bytes failed (11)\n",bsize);
+ return MUNKI_INT_MALLOC;
+ }
+
+ /* Trigger measure and gather raw readings */
+ if ((ev = munki_read_patches_1(p, 0, numpatches, numpatches, inttime, gainmode,
+ NULL, buf, bsize)) != MUNKI_OK) {
+ free(buf);
+ return ev;
+ }
+
+ /* Process the raw readings without averaging or extraction */
+ if ((ev = munki_read_patches_2a(p, specrd, numpatches, *inttime, gainmode,
+ buf, bsize)) != MUNKI_OK) {
+ free(buf);
+ return ev;
+ }
+ free(buf);
+ return ev;
+}
+
+/* Take a trial emission measurement reading using the current mode. */
+/* Used to determine if sensor is saturated, or not optimal */
+/* in adaptive emission mode. */
+munki_code munki_trialmeasure(
+ munki *p,
+ int *saturated, /* Return nz if sensor is saturated */
+ double *optscale, /* Return scale for gain/int time 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 /* Ratio of optimal sensor value to aim for */
+) {
+ munki_code ev = MUNKI_OK;
+ munkiimp *m = (munkiimp *)p->m;
+ munki_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 sensavg; /* Overall average of sensor readings */
+ double darkthresh; /* Dark threshold */
+ double trackmax[2]; /* Track optimum target */
+ double maxval; /* Maximum multimeas value */
+ int rv;
+
+ if (s->reflective) {
+ a1logw(p->log, "munki_trialmeasure: Assert - not meant to be used for reflective read!\n");
+ return MUNKI_INT_ASSERT;
+ }
+
+ if (nummeas <= 0)
+ return MUNKI_INT_ZEROMEASURES;
+
+ /* 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,"munki_trialmeasure malloc %d bytes failed (12)\n",bsize);
+ return MUNKI_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 = munki_trigger_one_measure(p, nummeas, inttime, gainmode, 1, 0)) != MUNKI_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,3,"Gathering readings\n");
+ if ((ev = munki_readmeasurement(p, nummeas, m->c_measmodeflags & MUNKI_MMF_SCAN,
+ buf, bsize, &nmeasuered, 1, 0)) != MUNKI_OK) {
+ free_dvector(absraw, -1, m->nraw-1);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ free(buf);
+ return ev;
+ }
+
+ if (saturated != NULL) /* Initialize return flag */
+ *saturated = 0;
+
+ /* Take a buffer full of raw readings, and convert them to */
+ /* floating point sensor readings. Check for saturation */
+ if ((rv = munki_sens_to_raw(p, multimes, NULL, buf, 0, nmeasuered, m->satlimit,
+ &darkthresh)) != MUNKI_OK) {
+ if (rv != MUNKI_RD_SENSORSATURATED) {
+ free(buf);
+ return rv;
+ }
+ if (saturated != NULL)
+ *saturated = 1;
+ }
+ free(buf);
+
+ /* Comute dark subtraction for this trial's parameters */
+ if ((ev = munki_interp_dark(p, s->dark_data, *inttime, gainmode)) != MUNKI_OK) {
+ free_dvector(absraw, -1, m->nraw-1);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
+ a1logd(p->log,3,"munki_imp_measure interplate dark ref failed\n");
+ return ev;
+ }
+
+ trackmax[0] = darkthresh; /* Track dark threshold */
+ trackmax[1] = m->optsval; /* Track the optimal sensor target value */
+
+ /* Subtract the black from sensor values and convert to */
+ /* absolute (integration & gain scaled), zero offset based, */
+ /* linearized sensor values. */
+ /* Return the highest individual element. */
+ munki_sub_raw_to_absraw(p, nmeasuered, *inttime, gainmode, multimes, s->dark_data,
+ trackmax, 2, &maxval);
+ darkthresh = trackmax[0];
+
+ /* Average a set of measurements into one. */
+ /* Return nz if the readings are not consistent */
+ /* Return the overall average. */
+ rv = munki_average_multimeas(p, absraw, multimes, nmeasuered, &sensavg, darkthresh);
+
+ /* Ignore iconsistent readings ?? */
+
+#ifdef PLOT_DEBUG
+ printf("Average absolute sensor readings, average = %f, max %f\n",sensavg,maxval);
+ plot_raw(absraw);
+#endif
+
+ if (optscale != NULL) {
+ double opttarget; /* Optimal sensor target */
+
+ opttarget = targoscale * trackmax[1];
+ if (maxval < 0.01) /* Could go -ve */
+ maxval = 0.01;
+ *optscale = opttarget/ maxval;
+ a1logd(p->log,4,"Targscale %f, maxval %f, optimal target = %f, amount to scale = %f\n",
+ targoscale, maxval, opttarget, *optscale);
+ }
+
+ free_dvector(absraw, -1, m->nraw-1);
+ free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1); /* Free after using *pmax */
+
+ 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 munki_readmeasurement() to collect the results */
+munki_code
+munki_trigger_one_measure(
+ munki *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 */
+ int calib_measure, /* flag - nz if this is a calibration measurement */
+ int dark_measure /* flag - nz if this is a dark measurement */
+) {
+ munki_code ev = MUNKI_OK;
+ munkiimp *m = (munkiimp *)p->m;
+ munki_state *s = &m->ms[m->mmode];
+ double dintclocks;
+ int intclocks; /* Number of integration clocks */
+ int measmodeflags; /* Measurement mode command flags */
+ int holdtempduty; /* Hold temperature duty cycle */
+
+ /* Compute integration clocks. Take account of (seeming) dead integration time */
+ dintclocks = floor((*inttime)/m->intclkp + 0.5);
+ intclocks = (int)dintclocks;
+ *inttime = (double)intclocks * m->intclkp; /* Quantized integration time */
+
+ /* Create measurement mode flag byte for this operation */
+ measmodeflags = 0;
+ if (s->scan && !calib_measure)
+ measmodeflags |= MUNKI_MMF_SCAN; /* Never scan on a calibration */
+ if (s->reflective && !dark_measure)
+ measmodeflags |= MUNKI_MMF_LAMP; /* Need lamp if reflective and not dark measure */
+ if (gainmode == 1)
+ measmodeflags |= MUNKI_MMF_HIGHGAIN; /* High gain mode */
+ holdtempduty = m->ledholdtempdc; /* From the EEProm value */
+
+ /* Trigger a measurement */
+ if ((ev = munki_triggermeasure(p, intclocks, nummeas, measmodeflags, holdtempduty)) != MUNKI_OK)
+ return ev;
+
+ m->c_measmodeflags = measmodeflags;
+
+ m->c_inttime = *inttime;
+
+ return ev;
+}
+
+/* ============================================================ */
+/* lower level reading processing and computation */
+
+/* Take a buffer full of raw readings, and convert them to */
+/* directly to floating point raw values. Return MUNKI_RD_SENSORSATURATED if any is saturated */
+/* (No black subtraction or linearization is performed) */
+munki_code munki_sens_to_raw(
+ munki *p,
+ double **raw, /* Array of [nummeas-ninvalid][-1 nraw] value to return */
+ double *ledtemp, /* Optional array [nummeas-ninvalid] LED temperature values to return */
+ unsigned char *buf, /* Raw measurement data must be 274 * nummeas */
+ int ninvalid, /* Number of initial invalid readings to skip */
+ int nummeas, /* Number of readings measured */
+ double satthresh, /* Saturation threshold to trigger error in raw units (if > 0.0) */
+ double *pdarkthresh /* Return a dark threshold value */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ int i, j, k;
+ unsigned char *bp;
+ double maxval = -1e38;
+ double darkthresh = 0.0;
+ double ndarkthresh = 0.0;
+ int sskip = 2 * 6; /* Bytes to skip at start */
+ int eskip = 2 * 3; /* Bytes to skip at end */
+
+ if ((m->nraw * 2 + sskip + eskip) != (m->nsen * 2)) {
+ a1logw(p->log,"NRAW %d and NRAWB %d don't match!\n",m->nraw,m->nsen * 2);
+ return MUNKI_INT_ASSERT;
+ }
+
+ if (ninvalid >= nummeas) {
+ a1logw(p->log,"ninvalid %d is >= nummeas %d!\n",ninvalid,nummeas);
+ return MUNKI_INT_ASSERT;
+ }
+
+ if (ninvalid > 0)
+ a1logd(p->log, 4, "munki_sens_to_raw: Skipping %d invalid readings\n",ninvalid);
+
+ /* Now process the buffer values */
+ for (bp = buf + ninvalid * m->nsen * 2, i = 0; i < nummeas; i++, bp += eskip) {
+
+ /* The first 4 readings 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 < 4; k++) {
+ darkthresh += (double)buf2ushort(bp + k * 2);
+ ndarkthresh++;
+ }
+
+ /* raw of shielded cells per reading */
+ raw[i][-1] = 0.0;
+ for (k = 0; k < 4; k++) {
+ raw[i][-1] += (double)buf2ushort(bp + k * 2);
+ }
+ raw[i][-1] /= 4.0;
+
+ /* The LED voltage drop is the last 16 bits in each reading */
+ if (ledtemp != NULL)
+ ledtemp[i] = (double)buf2ushort(bp + (m->nsen * 2) - 2);
+
+ /* The 128 raw spectral values */
+ for (bp += sskip, j = 0; j < m->nraw; j++, bp += 2) {
+ unsigned int rval;
+ double fval;
+
+ rval = buf2ushort(bp);
+ fval = (double)rval;
+ raw[i][j] = fval;
+// printf("~1 i = %d, j = %d, addr % 274 = %d, val = %f\n",i,j,(bp - buf) % 274, fval);
+
+ if (fval > maxval)
+ maxval = fval;
+ }
+ }
+
+ /* Check if any are over saturation threshold */
+ if (satthresh > 0.0) {
+ if (maxval > satthresh) {
+ a1logd(p->log,4,"munki_sens_to_raw: Max sens %f > satthresh %f\n",maxval,satthresh);
+ return MUNKI_RD_SENSORSATURATED;
+ }
+ a1logd(p->log,4,"munki_sens_to_raw: Max sens %f < satthresh %f\n",maxval,satthresh);
+ }
+
+ darkthresh /= ndarkthresh;
+ if (pdarkthresh != NULL)
+ *pdarkthresh = darkthresh;
+ a1logd(p->log,3,"munki_sens_to_raw: Dark thrheshold = %f\n",darkthresh);
+
+ return MUNKI_OK;
+}
+
+/* Subtract the black from raw values and convert to */
+/* absolute (integration & gain scaled), zero offset based, */
+/* linearized raw values. */
+/* Return the highest individual element. */
+void munki_sub_raw_to_absraw(
+ munki *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, /* Value to subtract [-1 nraw] */
+ double *trackmax, /* absraw values that should be offset the same as max */
+ int ntrackmax, /* Number of trackmax values */
+ double *maxv /* If not NULL, return the maximum value */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ int npoly; /* Number of linearisation coefficients */
+ double *polys; /* the coeficients */
+ double scale; /* Absolute scale value */
+ double submax = -1e6; /* Subtraction value maximum */
+ double asub[NSEN_MAX];
+ double avgscell, zero;
+ double rawmax, maxval = -1e38;
+ double maxzero = 0.0;
+ int i, j, k;
+
+ if (gainmode) { /* High gain */
+ npoly = m->nlin1; /* Encodes gain too */
+ polys = m->lin1;
+ } else { /* Low gain */
+ npoly = m->nlin0;
+ polys = m->lin0;
+ }
+ scale = 1.0/inttime;
+
+ /* 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% */
+
+ /* 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.08 * 0.5 * (avgscell + sub[-1]);
+
+ /* make sure that the zero point is above any black value */
+ if (zero < (1.005 * avgscell))
+ zero = 1.005 * avgscell;
+ if (zero < (1.005 * sub[-1]))
+ zero = 1.005 * sub[-1];
+ if (zero < (1.005 * submax))
+ zero = 1.005 * submax;
+
+ a1logd(p->log,4,"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("######### munki 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
+ }
+
+
+ /* For each measurement */
+ for (i = 0; i < nummeas; i++) {
+ double rval, sval, lval;
+
+ for (j = 0; j < m->nraw; j++) {
+
+ rval = absraw[i][j];
+ sval = rval - asub[j]; /* Make zero based */
+
+#ifdef ENABLE_NONLINCOR
+ /* Linearise */
+ for (lval = polys[npoly-1], k = npoly-2; k >= 0; k--)
+ lval = lval * sval + polys[k];
+#else
+ lval = sval;
+#endif
+ lval *= scale;
+ absraw[i][j] = lval;
+
+ /* Track the maximum value and the black that was subtracted from it */
+ if (lval > maxval) {
+ maxval = lval;
+ rawmax = rval;
+ maxzero = asub[j];
+ if (maxv != NULL)
+ *maxv = absraw[i][j];
+ }
+ }
+ }
+
+ /* Process the "tracked to max" values too */
+ if (ntrackmax > 0 && trackmax != NULL) {
+ for (i = 0; i < ntrackmax; i++) {
+ double rval, fval, lval;
+
+ rval = trackmax[i];
+ fval = rval - maxzero;
+
+#ifdef ENABLE_NONLINCOR
+ /* Linearise */
+ for (lval = polys[npoly-1], k = npoly-2; k >= 0; k--)
+ lval = lval * fval + polys[k];
+#else
+ lval = fval;
+#endif
+ lval *= scale;
+ trackmax[i] = lval;
+// printf("~1 trackmax[%d] = %f, maxzero = %f\n",i,lval,maxzero);
+ }
+ }
+}
+
+/* Average a set of sens or absens measurements into one. */
+/* (Make sure darkthresh is tracked if absens is being averaged!) */
+/* Return zero if readings are consistent and not saturated. */
+/* Return nz if the readings are not consistent */
+/* Return the overall average. */
+int munki_average_multimeas(
+ munki *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 *poallavg, /* If not NULL, return overall average of bands and measurements */
+ double darkthresh /* Dark threshold (used for consistency check scaling) */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ int i, j;
+ double oallavg = 0.0;
+ double maxavg = -1e38; /* Track min and max averages of readings */
+ double minavg = 1e38;
+ double norm;
+ int rv = 0;
+
+ a1logd(p->log,3,"munki_average_multimeas %d readings (darkthresh %f)\n",nummeas,darkthresh);
+
+ for (j = -1; j < m->nraw; j++)
+ avg[j] = 0.0;
+
+ /* Now process the buffer values */
+ for (i = 0; i < nummeas; i++) {
+ double measavg = 0.0;
+
+ avg[-1] += multimeas[i][-1]; /* shielded cell value */
+
+ for (j = 0; j < m->nraw; j++) {
+ double val;
+
+ val = multimeas[i][j];
+
+ measavg += val;
+ avg[j] += val;
+ }
+ measavg /= (double)m->nraw;
+ oallavg += measavg;
+ if (measavg < minavg)
+ minavg = measavg;
+ if (measavg > maxavg)
+ maxavg = measavg;
+ }
+
+ for (j = -1; j < m->nraw; j++)
+ avg[j] /= (double)nummeas;
+ oallavg /= (double)nummeas;
+
+ if (poallavg != NULL)
+ *poallavg = oallavg;
+
+ norm = fabs(0.5 * (maxavg+minavg));
+ darkthresh = fabs(darkthresh);
+ if (darkthresh < DARKTHSCAMIN)
+ darkthresh = DARKTHSCAMIN;
+ a1logd(p->log,3,"norm = %f, dark thresh = %f\n",norm,darkthresh);
+ if (norm < (2.0 * darkthresh))
+ norm = 2.0 * darkthresh;
+
+ a1logd(p->log,4,"avg_multi: overall avg = %f, minavg = %f, maxavg = %f, variance %f, THR %f (darkth %f)\n",
+ oallavg,minavg,maxavg,(maxavg - minavg)/norm, PATCH_CONS_THR,darkthresh);
+ if ((maxavg - minavg)/norm > PATCH_CONS_THR) {
+ rv |= 1;
+ }
+ return rv;
+}
+
+/* Minimum number of scan samples in a patch */
+#define MIN_SAMPLES 2
+
+/* 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 */
+} munki_patch;
+
+/* Recognise the required number of ref/trans patch locations, */
+/* and average the measurements within each patch. */
+/* *flags returns zero if readings are consistent. */
+/* *flags returns nz if the readings are not consistent */
+/* (Doesn't extract [-1] shielded values, since they have already been used) */
+munki_code munki_extract_patches_multimeas(
+ munki *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 inttime /* Integration time (used to adjust consistency threshold) */
+) {
+ munkiimp *m = (munkiimp *)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 */
+ munki_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 white_avg; /* Average of (aproximate) white data */
+ int rv = 0;
+ double patch_cons_thr = PATCH_CONS_THR * m->scan_toll_ratio;
+#ifdef PATREC_DEBUG
+ double **plot;
+#endif
+
+ a1logd(p->log,3,"munki_extract_patches_multimeas: looking for %d patches out of %d samples\n",tnpatch,nummeas);
+
+ maxval = dvectorz(-1, m->nraw-1);
+
+ /* Loosen consistency threshold for short intergation time */
+ 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 6 values each */
+ plot = dmatrixz(0, 6, 0, nummeas-1);
+// for (j = 1; j < (m->nraw-6); j += 6) /* Plot all the bands */
+// for (j = 45; j < (m->nraw-6); j += 100) /* Do just one band */
+ for (j = 5; j < (m->nraw-6); j += 30) { /* Do four bands */
+ for (k = 0; k < 6; k ++) {
+ for (i = 0; i < nummeas; i++) {
+ plot[k][i] = multimeas[i][j+k]/maxval[j+k];
+ }
+ }
+ 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);
+ }
+#endif
+
+ 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 = BL + BW; j < (BH - BW); j++) {
+ for (k = -BL; 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 = BL; j < BH; 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 = BL; j < BH; 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 = BL; jj < 6 && j < BH; jj++, j += ((BH-BL)/6)) {
+ double sum = 0.0;
+ for (k = -BL; k <= BW; k++) /* Box averaging filter over bands */
+ sum += multimeas[i][j + k];
+ plot[jj][i] = sum/((2.0 * BL + 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
+
+ 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 = (munki_patch *)malloc(sizeof(munki_patch) * apat)) == NULL) {
+ a1logd(p->log,1,"munki: malloc of patch structures failed!\n");
+ return MUNKI_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 = (munki_patch *)realloc(pat, sizeof(munki_patch) * apat)) == NULL) {
+ a1logd(p->log,1,"munki: reallloc of patch structures failed!\n");
+ return MUNKI_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,1,"Patch recog failed - unable to detect enough possible patches\n");
+ return MUNKI_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,1,"Patch recog failed - detecting too many possible patches\n");
+ return MUNKI_RD_TOOMANYPATCHES;
+ }
+ avglegth /= (double)npat;
+
+#ifdef PATREC_DEBUG
+ for (i = 0; i < npat; i++) {
+ printf("Raw patch %d, start %d, length %d\n",i, pat[i].ss, pat[i].no);
+ }
+#endif
+
+ /* 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,1,"Patch recog failed - patches sampled too sparsely\n");
+ return MUNKI_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,1,"Patch recog failed - detected too many consistent patches\n");
+ return MUNKI_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,1,"Patch recog failed - unable to find enough consistent patches\n");
+ return MUNKI_RD_NOTENOUGHPATCHES;
+ }
+
+#ifdef PATREC_DEBUG
+ printf("Got %d patches out of potentional %d:\n",tnpatch, npat);
+ printf("Average patch legth %f\n",avglegth);
+ 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);
+ }
+#endif
+
+ /* 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
+ printf("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 = BL; jj < 6 && j < BH; jj++, j += ((BH-BL)/6)) {
+ double sum = 0.0;
+ for (k = -BL; k <= BW; k++) /* Box averaging filter over bands */
+ sum += multimeas[i][j + k];
+ plot[jj][i] = sum/((2.0 * BL + 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
+
+ /* 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 cons; /* Consistency */
+
+ if (pat[k].use == 0)
+ continue;
+
+ if (pat[k].no <= MIN_SAMPLES) {
+ a1logd(p->log,7,"Too few samples\n");
+ free_dvector(slope, 0, nummeas-1);
+ free_ivector(sizepop, 0, nummeas-1);
+ free_dvector(maxval, -1, m->nraw-1);
+ free(pat);
+ a1logd(p->log,1,"Patch recog failed - patches sampled too sparsely\n");
+ return MUNKI_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 = 0; j < m->nraw; j++) {
+ double val;
+
+ val = multimeas[i][j];
+
+ measavg += val;
+ pavg[pix][j] += val;
+ }
+ measavg /= (m->nraw-2.0);
+ if (measavg < minavg)
+ minavg = measavg;
+ if (measavg > maxavg)
+ maxavg = measavg;
+ }
+
+ for (j = 0; j < m->nraw; j++)
+ pavg[pix][j] /= (double)pat[k].no;
+
+ 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,1,"Patch recog failed - patch %d is inconsistent (%f%%)\n",pix, cons);
+ rv |= 1;
+ }
+ pix++;
+ }
+
+ if (flags != NULL)
+ *flags = rv;
+
+#ifdef PATREC_DEBUG
+ free_dmatrix(plot, 0, 6, 0, nummeas-1);
+#endif
+
+ free_dvector(slope, 0, nummeas-1);
+ free_ivector(sizepop, 0, nummeas-1);
+ free_dvector(maxval, -1, m->nraw-1);
+ free(pat);
+
+ a1logd(p->log,3,"munki_extract_patches_multimeas done, sat = %s, inconsist = %s\n",
+ rv & 2 ? "true" : "false", rv & 1 ? "true" : "false");
+
+ a1logd(p->log,2,"Patch recognition returning OK\n");
+
+ return MUNKI_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. */
+/* The readings are integrated, so the the units are cd/m^2 seconds. */
+/* Return nz on an error */
+/* (Doesn't extract [-1] shielded values, since they have already been used) */
+munki_code munki_extract_patches_flash(
+ munki *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) */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ int i, j, k;
+ 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,3,"munki_extract_patches_flash: %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,1,"No flashes found in measurement\n");
+ return MUNKI_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,"munki_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
+
+#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 MUNKI_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 MUNKI_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 MUNKI_OK;
+}
+
+/* Convert an absraw array from raw wavelengths to output wavelenths */
+/* for the current resolution. Apply stray light compensation too. */
+void munki_absraw_to_abswav(
+ munki *p,
+ int nummeas, /* Return number of readings measured */
+ double **abswav, /* Desination array [nwav] */
+ double **absraw /* Source array [-1 nraw] */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ munki_state *s = &m->ms[m->mmode];
+ double *tm; /* Temporary array */
+ int i, j, k, cx, sx;
+
+ tm = dvector(0, m->nwav-1);
+
+ /* For each measurement */
+ for (i = 0; i < nummeas; i++) {
+
+ /* For each output wavelength */
+ for (cx = j = 0; j < m->nwav; j++) {
+ double oval = 0.0;
+
+ /* For each matrix value */
+ if (s->reflective) {
+ sx = m->rmtx_index[j]; /* Starting index */
+ for (k = 0; k < m->rmtx_nocoef[j]; k++, cx++, sx++)
+ oval += m->rmtx_coef[cx] * absraw[i][sx];
+ } else {
+ sx = m->emtx_index[j]; /* Starting index */
+ for (k = 0; k < m->emtx_nocoef[j]; k++, cx++, sx++)
+ oval += m->emtx_coef[cx] * absraw[i][sx];
+ }
+ tm[j] = oval;
+ }
+
+ /* Now apply stray light compensation */
+ /* For each output wavelength */
+ for (j = 0; j < m->nwav; j++) {
+ double oval = 0.0;
+
+ /* For each matrix value */
+ for (k = 0; k < m->nwav; k++)
+ oval += m->straylight[j][k] * tm[k];
+ abswav[i][j] = oval;
+ }
+ }
+ free_dvector(tm, 0, m->nwav-1);
+}
+
+/* Convert an absraw array from raw wavelengths to output wavelenths */
+/* for the standard resolution. Apply stray light compensation too. */
+void munki_absraw_to_abswav1(
+ munki *p,
+ int nummeas, /* Return number of readings measured */
+ double **abswav, /* Desination array [nwav1] */
+ double **absraw /* Source array [-1 nraw] */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ munki_state *s = &m->ms[m->mmode];
+ double *tm; /* Temporary array */
+ int i, j, k, cx, sx;
+
+ tm = dvector(0, m->nwav1-1);
+
+ /* For each measurement */
+ for (i = 0; i < nummeas; i++) {
+
+ /* For each output wavelength */
+ for (cx = j = 0; j < m->nwav1; j++) {
+ double oval = 0.0;
+
+ /* For each matrix value */
+ if (s->reflective) {
+ sx = m->rmtx_index1[j]; /* Starting index */
+ for (k = 0; k < m->rmtx_nocoef1[j]; k++, cx++, sx++)
+ oval += m->rmtx_coef1[cx] * absraw[i][sx];
+ } else {
+ sx = m->emtx_index1[j]; /* Starting index */
+ for (k = 0; k < m->emtx_nocoef1[j]; k++, cx++, sx++)
+ oval += m->emtx_coef1[cx] * absraw[i][sx];
+ }
+ tm[j] = oval;
+ }
+
+ /* Now apply stray light compensation */
+ /* For each output wavelength */
+ for (j = 0; j < m->nwav1; j++) {
+ double oval = 0.0;
+
+ /* For each matrix value */
+ for (k = 0; k < m->nwav1; k++)
+ oval += m->straylight1[j][k] * tm[k];
+ abswav[i][j] = oval;
+ }
+ }
+ free_dvector(tm, 0, m->nwav1-1);
+}
+
+/* Convert an absraw array from raw wavelengths to output wavelenths */
+/* for the high resolution. Apply light compensation too. */
+void munki_absraw_to_abswav2(
+ munki *p,
+ int nummeas, /* Return number of readings measured */
+ double **abswav, /* Desination array [nwav2] */
+ double **absraw /* Source array [-1 nraw] */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ munki_state *s = &m->ms[m->mmode];
+ double *tm; /* Temporary array */
+ int i, j, k, cx, sx;
+
+ tm = dvector(0, m->nwav2-1);
+
+ /* For each measurement */
+ for (i = 0; i < nummeas; i++) {
+
+ /* For each output wavelength */
+ for (cx = j = 0; j < m->nwav2; j++) {
+ double oval = 0.0;
+
+ /* For each matrix value */
+ if (s->reflective) {
+ sx = m->rmtx_index2[j]; /* Starting index */
+ for (k = 0; k < m->rmtx_nocoef2[j]; k++, cx++, sx++)
+ oval += m->rmtx_coef2[cx] * absraw[i][sx];
+ } else {
+ sx = m->emtx_index2[j]; /* Starting index */
+ for (k = 0; k < m->emtx_nocoef2[j]; k++, cx++, sx++)
+ oval += m->emtx_coef2[cx] * absraw[i][sx];
+ }
+ tm[j] = oval;
+ }
+
+ /* Now apply stray light compensation */
+ /* For each output wavelength */
+ for (j = 0; j < m->nwav2; j++) {
+ double oval = 0.0;
+
+ /* For each matrix value */
+ for (k = 0; k < m->nwav2; k++)
+ oval += m->straylight2[j][k] * tm[k];
+ abswav[i][j] = oval;
+ }
+ }
+ free_dvector(tm, 0, m->nwav2-1);
+}
+
+/* Convert an abswav array of output wavelengths to scaled output readings. */
+void munki_scale_specrd(
+ munki *p,
+ double **outspecrd, /* Destination */
+ int numpatches, /* Number of readings/patches */
+ double **inspecrd /* Source */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ munki_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; j++) {
+ outspecrd[i][j] = inspecrd[i][j] * s->cal_factor[j];
+ }
+ }
+}
+
+
+/* =============================================== */
+#ifdef HIGH_RES
+
+/* High res congiguration */
+#undef EXISTING_SHAPE /* Else generate filter shape */
+#undef USE_GAUSSIAN /* Use gaussian filter shape, else lanczos2 */
+
+#ifdef NEVER
+/* Plot the matrix coefficients */
+void munki_debug_plot_mtx_coef(munki *p, int ref) {
+ munkiimp *m = (munkiimp *)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 */
+ if (ref) {
+ sx = m->rmtx_index[j]; /* Starting index */
+// printf("start index = %d, nocoef %d\n",sx,m->rmtx_nocoef[j]);
+ for (k = 0; k < m->rmtx_nocoef[j]; k++, cx++, sx++) {
+// printf("offset %d, coef ix %d val %f from ccd %d\n",k, cx, m->rmtx_coef[cx], sx);
+ yy[5][sx] += 0.5 * m->rmtx_coef[cx];
+ yy[i][sx] = m->rmtx_coef[cx];
+ }
+ } else {
+ sx = m->emtx_index[j]; /* Starting index */
+// printf("start index = %d, nocoef %d\n",sx,m->emtx_nocoef[j]);
+ for (k = 0; k < m->emtx_nocoef[j]; k++, cx++, sx++) {
+// printf("offset %d, coef ix %d val %f from ccd %d\n",k, cx, m->emtx_coef[cx], sx);
+ yy[5][sx] += 0.5 * m->emtx_coef[cx];
+ yy[i][sx] = m->emtx_coef[cx];
+ }
+ }
+ }
+
+ if (ref)
+ printf("Reflective cooeficients\n");
+ else
+ printf("Emissive cooeficients\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
+
+/* Filter shape point */
+typedef struct {
+ double wl, we;
+} munki_fs;
+
+/* Filter cooeficient values */
+typedef struct {
+ int ix; /* Raw index */
+ double we; /* Weighting */
+} munki_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 */
+} munki_xp;
+
+/* Linearly interpolate the filter shape */
+static double lin_fshape(munki_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-1); 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_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));
+#else
+
+ /* 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
+ 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 high resolution mode references, */
+/* Create Reflective if ref nz, else create Emissive */
+/* We expect this to be called twice, once for each. */
+munki_code munki_create_hr(munki *p, int ref) {
+ munkiimp *m = (munkiimp *)p->m;
+ int i, j, jj, k, cx, sx;
+ munki_fc coeff[40][16]; /* Existing filter cooefficients */
+ int nwav1; /* Number of filters */
+ double wl_short1, wl_long1; /* Ouput wavelength of first and last filters */
+ double wl_step1;
+ munki_xp xp[41]; /* Crossover points each side of filter */
+ munki_code ev = MUNKI_OK;
+ rspl *raw2wav; /* Lookup from CCD index to wavelength */
+ munki_fs fshape[40 * 16]; /* Existing filter shape */
+ int ncp = 0; /* Number of shape points */
+ int *mtx_index1, **pmtx_index2, *mtx_index2;
+ int *mtx_nocoef1, **pmtx_nocoef2, *mtx_nocoef2;
+ double *mtx_coef1, **pmtx_coef2, *mtx_coef2;
+
+ /* Start with nominal values. May alter these if filters are not unique */
+ nwav1 = m->nwav1;
+ wl_short1 = m->wl_short1;
+ wl_long1 = m->wl_long1;
+ wl_step1 = (wl_long1 - m->wl_short1)/(m->nwav1-1.0);
+
+ if (ref) {
+ mtx_index1 = m->rmtx_index1;
+ mtx_nocoef1 = m->rmtx_nocoef1;
+ mtx_coef1 = m->rmtx_coef1;
+ mtx_index2 = mtx_nocoef2 = NULL;
+ mtx_coef2 = NULL;
+ pmtx_index2 = &m->rmtx_index2;
+ pmtx_nocoef2 = &m->rmtx_nocoef2;
+ pmtx_coef2 = &m->rmtx_coef2;
+ } else {
+ mtx_index1 = m->emtx_index1;
+ mtx_nocoef1 = m->emtx_nocoef1;
+ mtx_coef1 = m->emtx_coef1;
+ mtx_index2 = mtx_nocoef2 = NULL;
+ mtx_coef2 = NULL;
+ pmtx_index2 = &m->emtx_index2;
+ pmtx_nocoef2 = &m->emtx_nocoef2;
+ pmtx_coef2 = &m->emtx_coef2;
+ }
+
+ /* Convert the native filter cooeficient representation to */
+ /* a 2D array we can randomly index. Skip any duplicated */
+ /* filter cooeficients. */
+ for (cx = j = jj = 0; j < m->nwav1; j++) { /* For each output wavelength */
+ if (j >= 40) { /* Assert */
+ a1logw(p->log,"munki: number of output wavelenths is > 40\n");
+ return MUNKI_INT_ASSERT;
+ }
+
+ /* For each matrix value */
+ sx = mtx_index1[j]; /* Starting index */
+ if (jj == 0 && (j+1) < m->nwav1 && sx == mtx_index1[j+1]) { /* Same index */
+// printf("~1 skipping %d\n",j);
+ wl_short1 += wl_step1;
+ nwav1--;
+ cx += mtx_nocoef1[j];
+ continue;
+ }
+ for (k = 0; k < mtx_nocoef1[j]; k++, cx++, sx++) {
+ if (k >= 16) { /* Assert */
+ a1logw(p->log,"munki: number of filter coeefs is > 16\n");
+ return MUNKI_INT_ASSERT;
+ }
+
+ coeff[jj][k].ix = sx;
+ coeff[jj][k].we = mtx_coef1[cx];
+ }
+ jj++;
+ }
+
+#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 < nwav1; j++) {
+ i = j % 5;
+
+ /* For each matrix value */
+ for (k = 0; k < mtx_nocoef1[j]; k++) {
+ yy[5][coeff[j][k].ix] += 0.5 * coeff[j][k].we;
+ yy[i][coeff[j][k].ix] = coeff[j][k].we;
+ }
+ }
+
+ if (ref)
+ printf("Original reflection wavelength sampling curves:\n");
+ else
+ printf("Original emission 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 */
+
+// a1logd(p->log,3,"computing crossover points\n");
+ /* Compute the crossover points between each filter */
+ for (i = 0; i < (nwav1-1); i++) {
+ double den, y1, y2, y3, y4, yn, xn; /* Location of intersection */
+ double besty = -1e6;
+
+ /* 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 < (mtx_nocoef1[i]-1); j++) {
+ for (k = 0; k < (mtx_nocoef1[i+1]-1); k++) {
+ 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+1][k].we > 0.0 && coeff[i+1][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))) {
+// a1logd(p->log,3,"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);
+
+ /* 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;
+// a1logd(p->log,3,"den = %f, yn = %f, xn = %f\n",den,yn,xn);
+// a1logd(p->log,3,"Intersection %d: wav %f, raw %f, wei %f\n",i+1,xp[i+1].wav,xp[i+1].raw,xp[i+1].wei);
+ if (yn > besty) {
+ xp[i+1].wav = XSPECT_WL(wl_short1, wl_long1, nwav1, 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;
+ besty = yn;
+// a1logd(p->log,3,"Found new best y\n");
+ }
+// a1logd(p->log,3,"\n");
+ }
+ }
+ }
+ if (besty < 0.0) { /* Assert */
+ a1logw(p->log,"munki: failed to locate crossover between resampling filters\n");
+ return MUNKI_INT_ASSERT;
+ }
+// a1logd(p->log,3,"\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 < (mtx_nocoef1[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 >= mtx_nocoef1[0]) { /* Assert */
+ a1logw(p->log,"munki: failed to end crossover\n");
+ return MUNKI_INT_ASSERT;
+ }
+ den = -y4 + y3 + y2 - y1;
+ yn = (y2 * y3 - y1 * y4)/den;
+ xn = (y3 - y1)/den;
+// a1logd(p->log,3,"den = %f, yn = %f, xn = %f\n",den,yn,xn);
+ xp[0].wav = XSPECT_WL(wl_short1, wl_long1, nwav1, -0.5);
+ xp[0].raw = (1.0 - xn) * coeff[0][j].ix + xn * coeff[0][j+1].ix;
+ xp[0].wei = yn;
+// a1logd(p->log,3,"End 0 intersection %d: wav %f, raw %f, wei %f\n",0,xp[0].wav,xp[0].raw,xp[0].wei);
+// a1logd(p->log,3,"\n");
+
+ x5 = xp[nwav1-2].raw;
+ y5 = xp[nwav1-2].wei;
+ x6 = xp[nwav1-1].raw;
+ y6 = xp[nwav1-1].wei;
+
+// a1logd(p->log,3,"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 < (mtx_nocoef1[0]-1); j++) {
+ /* Extrapolate line to this segment */
+ y3 = y5 + (coeff[nwav1-1][j].ix - x5)/(x6 - x5) * (y6 - y5);
+ y4 = y5 + (coeff[nwav1-1][j+1].ix - x5)/(x6 - x5) * (y6 - y5);
+ /* This segment of curve */
+ y1 = coeff[nwav1-1][j].we;
+ y2 = coeff[nwav1-1][j+1].we;
+ if ( (( y1 >= y3 && y2 <= y4) /* Segments overlap */
+ || ( y1 <= y3 && y2 >= y4))
+ && (( coeff[nwav1-1][j].ix < x5 && coeff[nwav1-1][j].ix < x6
+ && coeff[nwav1-1][j+1].ix < x5 && coeff[nwav1-1][j+1].ix < x6)
+ || ( coeff[nwav1-1][j+1].ix > x5 && coeff[nwav1-1][j+1].ix > x6
+ && coeff[nwav1-1][j].ix > x5 && coeff[nwav1-1][j].ix > x6))) {
+ break;
+ }
+ }
+ if (j >= mtx_nocoef1[nwav1-1]) { /* Assert */
+ a1logw(p->log, "munki: failed to end crossover\n");
+ return MUNKI_INT_ASSERT;
+ }
+ den = -y4 + y3 + y2 - y1;
+ yn = (y2 * y3 - y1 * y4)/den;
+ xn = (y3 - y1)/den;
+// a1logd(p->log,3,"den = %f, yn = %f, xn = %f\n",den,yn,xn);
+ xp[nwav1].wav = XSPECT_WL(wl_short1, wl_long1, nwav1, nwav1-0.5);
+ xp[nwav1].raw = (1.0 - xn) * coeff[nwav1-1][j].ix + xn * coeff[nwav1-1][j+1].ix;
+ xp[nwav1].wei = yn;
+// a1logd(p->log,3,"End 36 intersection %d: wav %f, raw %f, wei %f\n",nwav1+1,xp[nwav1].wav,xp[nwav1].raw,xp[nwav1].wei);
+// a1logd(p->log,3,"\n");
+ }
+
+#ifdef HIGH_RES_PLOT
+ /* Plot original re-sampling curves + crossing points */
+ {
+ double *xx, *ss;
+ double **yy;
+ double *xc, *yc;
+
+ xx = dvectorz(-1, m->nraw-1); /* X index */
+ yy = dmatrixz(0, 5, -1, m->nraw-1); /* Curves distributed amongst 5 graphs */
+ xc = dvectorz(0, nwav1); /* Crossover X */
+ yc = dvectorz(0, nwav1); /* Crossover Y */
+
+ for (i = 0; i < m->nraw; i++)
+ xx[i] = i;
+
+ /* For each output wavelength */
+ for (j = 0; j < nwav1; j++) {
+ i = j % 5;
+
+ /* For each matrix value */
+ for (k = 0; k < mtx_nocoef1[j]; k++) {
+ yy[5][coeff[j][k].ix] += 0.5 * coeff[j][k].we;
+ yy[i][coeff[j][k].ix] = coeff[j][k].we;
+ }
+ }
+
+ /* Crosses at intersection points */
+ for (i = 0; i <= nwav1; i++) {
+ xc[i] = xp[i].raw;
+ yc[i] = xp[i].wei;
+ }
+
+ if (ref)
+ printf("Original reflection sampling curves + crossover points\n");
+ else
+ printf("Original emsission sampling curves + crossover points\n");
+ do_plot6p(xx, yy[0], yy[1], yy[2], yy[3], yy[4], yy[5], m->nraw, xc, yc, nwav1+1);
+ free_dvector(xx, -1, m->nraw-1);
+ free_dmatrix(yy, 0, 2, -1, m->nraw-1);
+ free_dvector(xc, 0, nwav1);
+ free_dvector(yc, 0, nwav1);
+ }
+#endif /* HIGH_RES_PLOT */
+
+#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 < nwav1; 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 < (mtx_nocoef1[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 ((raw2wav = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) {
+ a1logd(p->log,3,"munki: creating rspl for high res conversion failed\n");
+ return MUNKI_INT_NEW_RSPL_FAILED;
+ }
+
+ vlow[0] = 1e6;
+ vhigh[0] = -1e6;
+
+ for (i = 0; i < (nwav1+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] = (double)(m->nraw-1);
+ gres[0] = m->nraw;
+ avgdev[0] = 0.0;
+
+ raw2wav->fit_rspl(raw2wav, 0, sd, nwav1+1, glow, ghigh, gres, vlow, vhigh, 1.0, avgdev, NULL);
+ }
+
+#ifdef EXISTING_SHAPE
+ /* Convert each weighting curves values into normalized values and */
+ /* accumulate into a single curve. */
+ /* This probably isn't quite correct - we really need to remove */
+ /* the effects of the convolution with the CCD cell widths. */
+ /* perhaps it's closer to a lanczos2 if this were done ? */
+ {
+ for (i = 0; i < nwav1; i++) {
+ double cwl; /* center wavelegth */
+ double weight = 0.0;
+
+ for (j = 0; j < (mtx_nocoef1[i]); j++) {
+ double w1, w2, cellw;
+ co pp;
+
+ /* Translate CCD cell boundaries index to wavelength */
+ pp.p[0] = (double)coeff[i][j].ix - 0.5;
+ raw2wav->interp(raw2wav, &pp);
+ w1 = pp.v[0];
+
+ pp.p[0] = (double)coeff[i][j].ix + 0.5;
+ raw2wav->interp(raw2wav, &pp);
+ w2 = pp.v[0];
+
+ cellw = fabs(w2 - w1);
+
+ cwl = XSPECT_WL(wl_short1, wl_long1, nwav1, i);
+
+ /* Translate CCD index to wavelength */
+ pp.p[0] = (double)coeff[i][j].ix;
+ raw2wav->interp(raw2wav, &pp);
+ fshape[ncp].wl = pp.v[0] - 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(munki_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(munki_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;
+ }
+ if (ref)
+ printf("Shape of existing reflection sampling curve:\n");
+ else
+ printf("Shape of existing emission sampling 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 */
+ }
+#endif /* EXISTING_SHAPE */
+
+#ifdef HIGH_RES_DEBUG
+ /* Check that the 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 */
+
+ {
+ double fshmax; /* filter shape max wavelength from center */
+#define MXNOFC 64
+ munki_fc coeff2[500][MXNOFC]; /* New filter cooefficients */
+ double twidth;
+
+ /* Construct a set of filters that uses more CCD values */
+ twidth = HIGHRES_WIDTH;
+
+ if (m->nwav2 > 500) { /* Assert */
+ a1logw(p->log,"High res filter has too many bands\n");
+ return MUNKI_INT_ASSERT;
+ }
+
+#ifdef EXISTING_SHAPE /* Else generate filter shape */
+ /* Cut the filter width by half, to conver from 10nm to 5nm spacing */
+ for (i = 0; i < ncp; i++)
+ fshape[i].wl *= twidth/10.0;
+ fshmax = -fshape[0].wl; /* aximum extent needed around zero */
+ if (fshape[ncp-1].wl > fshmax)
+ fshmax = fshape[ncp-1].wl;
+#else
+ /* Use a crude means of determining width */
+ for (fshmax = 50.0; fshmax >= 0.0; fshmax -= 0.1) {
+ if (fabs(lanczos2(twidth, fshmax)) > 0.001) {
+ fshmax += 0.1;
+ break;
+ }
+ }
+ if (fshmax <= 0.0) {
+ a1logw(p->log,"munki: fshmax search failed\n");
+ return MUNKI_INT_ASSERT;
+ }
+#endif
+// a1logd(p->log,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 ((*pmtx_nocoef2 = mtx_nocoef2 = (int *)calloc(m->nwav2, sizeof(int))) == NULL) {
+ a1logd(p->log,1,"munki: malloc mtx_nocoef2 failed!\n");
+ return MUNKI_INT_MALLOC;
+ }
+
+ /* For all the useful CCD bands */
+ for (i = 0; i < m->nraw; i++) {
+ co pp;
+ double w1, wl, w2;
+
+ /* Translate CCD center to to wavelength */
+ pp.p[0] = (double)i;
+ raw2wav->interp(raw2wav, &pp);
+ wl = pp.v[0];
+
+ /* Translate CCD cell boundaries index to wavelength */
+ pp.p[0] = i - 0.5;
+ raw2wav->interp(raw2wav, &pp);
+ w1 = pp.v[0];
+
+ pp.p[0] = i + 0.5;
+ raw2wav->interp(raw2wav, &pp);
+ w2 = pp.v[0];
+
+// a1logd(p->log,1,"CCD %d, wl %f\n",i,wl);
+
+ /* For each filter */
+ for (j = 0; j < m->nwav2; j++) {
+ double cwl, rwl; /* center, relative wavelegth */
+ double we;
+
+ cwl = m->wl_short2 + (double)j * (m->wl_long2 - m->wl_short2)/(m->nwav2-1.0);
+ rwl = wl - cwl; /* relative wavelgth to filter */
+
+ if (fabs(w1 - cwl) > fshmax && fabs(w2 - cwl) > fshmax)
+ continue; /* Doesn't fall into this filter */
+
+#ifndef NEVER
+ /* Integrate in 0.05 nm increments from filter shape */
+ {
+ int nn;
+ double lw, ll;
+
+ nn = (int)(fabs(w2 - w1)/0.05 + 0.5);
+
+ lw = w1;
+#ifdef EXISTING_SHAPE
+ ll = lin_fshape(fshape, ncp, w1- cwl);
+#else
+ ll = lanczos2(twidth, w1- cwl);
+#endif
+ 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);
+#ifdef EXISTING_SHAPE
+ cl = lin_fshape(fshape, ncp, cw - cwl);
+#else
+ cl = lanczos2(twidth, cw- cwl);
+#endif
+ we += 0.5 * (cl + ll) * (lw - cw);
+ ll = cl;
+ lw = cw;
+ }
+ }
+
+
+#else /* Point sample with weighting */
+
+#ifdef EXISTING_SHAPE
+ we = fabs(w2 - w1) * lin_fshape(fshape, ncp, rwl);
+#else
+ we = fabs(w2 - w1) * lanczos2(twidth, rwl);
+#endif
+
+#endif /* Integrate/Point sample */
+
+ if (mtx_nocoef2[j] >= MXNOFC) {
+ a1logw(p->log,"munki: run out of high res filter space\n");
+ return MUNKI_INT_ASSERT;
+ }
+
+ coeff2[j][mtx_nocoef2[j]].ix = i;
+ coeff2[j][mtx_nocoef2[j]++].we = we;
+// a1logd(p->log,1,"filter %d, cwl %f, rwl %f, ix %d, we %f\n",j,cwl,rwl,i,we);
+ }
+ }
+
+#ifdef HIGH_RES_PLOT
+ /* Plot resampled curves */
+ {
+ double *xx, *ss;
+ double **yy;
+
+ xx = dvectorz(0, m->nraw-1); /* X index */
+ yy = dmatrixz(0, 5, 0, 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->nwav2; j++) {
+ i = j % 5;
+
+ /* For each matrix value */
+ for (k = 0; k < mtx_nocoef2[j]; k++) {
+ yy[5][coeff2[j][k].ix] += 0.5 * coeff2[j][k].we;
+ yy[i][coeff2[j][k].ix] = coeff2[j][k].we;
+ }
+ }
+
+ if (ref)
+ printf("Hi-Res reflection wavelength sampling curves:\n");
+ else
+ printf("Hi-Res emission wavelength sampling curves:\n");
+ do_plot6(xx, yy[0], yy[1], yy[2], yy[3], yy[4], yy[5], m->nraw);
+ free_dvector(xx, 0, m->nraw-1);
+ free_dmatrix(yy, 0, 2, 0, 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[NSEN_MAX], avgw; /* Weighting determined by cell widths */
+ double ccdsum[NSEN_MAX];
+
+ /* Normalize the overall filter weightings */
+ for (j = 0; j < m->nwav2; j++)
+ for (k = 0; k < mtx_nocoef2[j]; k++)
+ tot += coeff2[j][k].we;
+ tot /= (double)m->nwav2;
+ for (j = 0; j < m->nwav2; j++)
+ for (k = 0; k < mtx_nocoef2[j]; k++)
+ coeff2[j][k].we /= tot;
+
+ /* Determine the relative weights for each CCD */
+ avgw = 0.0;
+ for (i = 0; i < m->nraw; i++) {
+ co pp;
+
+ pp.p[0] = i - 0.5;
+ raw2wav->interp(raw2wav, &pp);
+ ccdweight[i] = pp.v[0];
+
+ pp.p[0] = i + 0.5;
+ raw2wav->interp(raw2wav, &pp);
+ ccdweight[i] = fabs(ccdweight[i] - pp.v[0]);
+ 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->nwav2; j++) {
+ double err;
+ tot = 0.0;
+ for (k = 0; k < mtx_nocoef2[j]; k++)
+ tot += coeff2[j][k].we;
+ err = 1.0 - tot;
+
+ for (k = 0; k < mtx_nocoef2[j]; k++)
+ coeff2[j][k].we += err/mtx_nocoef2[j];
+// for (k = 0; k < mtx_nocoef2[j]; k++)
+// coeff2[j][k].we *= 1.0/tot;
+ }
+
+ /* Check CCD sums */
+ for (i = 0; i < nraw; i++)
+ ccdsum[i] = 0.0;
+
+ for (j = 0; j < m->nwav2; j++) {
+ for (k = 0; k < mtx_nocoef2[j]; k++)
+ ccdsum[coeff2[j][k].ix] += coeff2[j][k].we;
+ }
+
+ if (ii >= 6)
+ break;
+
+ /* Readjust CCD sum */
+ for (i = 0; i < nraw; i++) {
+ ccdsum[i] = ccdtsum[i]/ccdsum[i]; /* Amount to adjust */
+ }
+
+ for (j = 0; j < m->nwav2; j++) {
+ for (k = 0; k < mtx_nocoef2[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->nwav2; j++) {
+ i = j % 5;
+
+ /* For each matrix value */
+ for (k = 0; k < mtx_nocoef2[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 into runtime format */
+ {
+ int xcount;
+
+ if ((*pmtx_index2 = mtx_index2 = (int *)calloc(m->nwav2, sizeof(int))) == NULL) {
+ a1logd(p->log,1,"munki: malloc mtx_index2 failed!\n");
+ return MUNKI_INT_MALLOC;
+ }
+
+ xcount = 0;
+ for (j = 0; j < m->nwav2; j++) {
+ mtx_index2[j] = coeff2[j][0].ix;
+ xcount += mtx_nocoef2[j];
+ }
+
+ if ((*pmtx_coef2 = mtx_coef2 = (double *)calloc(xcount, sizeof(double))) == NULL) {
+ a1logd(p->log,1,"munki: malloc mtx_coef2 failed!\n");
+ return MUNKI_INT_MALLOC;
+ }
+
+ for (i = j = 0; j < m->nwav2; j++)
+ for (k = 0; k < mtx_nocoef2[j]; k++, i++)
+ mtx_coef2[i] = coeff2[j][k].we;
+ }
+
+ /* Basic capability is initialised */
+ m->hr_inited++;
+
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+ /* If both reflective and emissive samplings have been created, */
+ /* deal with upsampling the references and calibrations */
+ if (m->hr_inited == 2) {
+#ifdef SALONEINSTLIB
+ double **slp; /* 2D Array of stray light values */
+#endif /* !SALONEINSTLIB */
+ 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, jj;
+ co pp;
+
+ /* First the 1D references */
+ if ((trspl = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) {
+ a1logd(p->log,3,"munki: creating rspl for high res conversion failed\n");
+ raw2wav->del(raw2wav);
+ return MUNKI_INT_NEW_RSPL_FAILED;
+ }
+
+ for (ii = 0; ii < 4; ii++) {
+ double **ref2, *ref1;
+
+ if (ii == 0) {
+ ref1 = m->white_ref1;
+ ref2 = &m->white_ref2;
+ } else if (ii == 1) {
+ ref1 = m->emis_coef1;
+ ref2 = &m->emis_coef2;
+ } else if (ii == 2) {
+ ref1 = m->amb_coef1;
+ ref2 = &m->amb_coef2;
+ } else {
+ ref1 = m->proj_coef1;
+ ref2 = &m->proj_coef2;
+ }
+
+ vlow[0] = 1e6;
+ vhigh[0] = -1e6;
+
+ /* Set scattered points */
+ for (i = 0; i < m->nwav1; i++) {
+
+ sd[i].p[0] = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, 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];
+ }
+
+ /* Add some corrections at short wavelengths */
+ /* (The combination of the diffraction grating and */
+ /* LED light source doesn't give us much to work with here.) */
+ if (ii == 1) { /* Emission */
+ sd[0].v[0] *= 10.0; /* 380 */
+ sd[1].v[0] *= 3.0; /* 390 */
+ sd[2].v[0] *= 1.0; /* 400 */
+ }
+
+ if (ii == 2) { /* Ambient */
+ sd[0].v[0] *= 5.0; /* 380 */ /* Doesn't help much, because */
+ sd[1].v[0] *= 2.0; /* 390 */ /* the diffuser absorbs short WL */
+ sd[2].v[0] *= 1.0; /* 400 */
+ }
+
+ if (ii == 3) { /* Projector */
+ sd[0].v[0] *= 0.1; /* 380 */
+ sd[1].v[0] *= 1.0; /* 390 */
+
+ sd[i].p[0] = 350.0; /* 350 */
+ sd[i].v[0] = 0.2 * sd[0].v[0];
+ sd[i++].w = 1.0;
+ }
+
+ glow[0] = m->wl_short2;
+ ghigh[0] = m->wl_long2;
+ gres[0] = m->nwav2;
+ avgdev[0] = 0.0;
+
+ trspl->fit_rspl_w(trspl, 0, sd, i, glow, ghigh, gres, vlow, vhigh, 0.5, avgdev, NULL);
+
+ if ((*ref2 = (double *)calloc(m->nwav2, sizeof(double))) == NULL) {
+ raw2wav->del(raw2wav);
+ trspl->del(trspl);
+ a1logd(p->log,1,"munki: malloc mtx_coef2 failed!\n");
+ return MUNKI_INT_MALLOC;
+ }
+
+ /* Create upsampled version */
+ for (i = 0; i < m->nwav2; i++) {
+ pp.p[0] = XSPECT_WL(m->wl_short2, m->wl_long2, m->nwav2, i);
+ trspl->interp(trspl, &pp);
+ if (pp.v[0] < 0.0)
+ pp.v[0] = 0.0;
+ (*ref2)[i] = pp.v[0];
+ }
+
+
+ /* Add some corrections at short wavelengths */
+ if (ii == 0) {
+ /* 376.67 - 470 */
+ double corr[5][29] = {
+ { 4.2413, 4.0654, 3.6425, 3.2194, 2.8692, 2.3964,
+ 1.9678, 1.3527, 0.7978, 0.7823, 0.8474, 0.9227,
+ 0.9833, 1.0164, 1.0270, 1.0241, 1.0157, 1.0096,
+ 1.0060, 1.0, 1.0, 1.0, 1.0, 1.0,
+ 1.0, 1.0, 1.0, 1.0, 1.0 },
+
+ };
+
+ for (i = 0; i < m->nwav2; i++) {
+ double wl;
+ int ix;
+ wl = XSPECT_WL(m->wl_short2, m->wl_long2, m->nwav2, i);
+ ix = XSPECT_IX(376.6666667, 470.0, 29, wl);
+
+ if (ix < 0)
+ ix = 0;
+ else if (ix >= 29)
+ ix = 28;
+ (*ref2)[i] *= corr[ii][ix];
+ }
+
+ }
+
+#ifdef HIGH_RES_PLOT
+ /* Plot original and upsampled reference */
+ {
+ double *x1 = dvectorz(0, m->nwav2-1);
+ double *y1 = dvectorz(0, m->nwav2-1);
+ double *y2 = dvectorz(0, m->nwav2-1);
+
+ for (i = 0; i < m->nwav2; i++) {
+ double wl = m->wl_short2 + (double)i * (m->wl_long2 - m->wl_short2)/(m->nwav2-1.0);
+ x1[i] = wl;
+ y1[i] = (*ref2)[i];
+ if (wl < m->wl_short1 || wl > m->wl_long1) {
+ y2[i] = 0.0;
+ } else {
+ double x, wl1, wl2;
+ for (j = 0; j < (m->nwav1-1); j++) {
+ wl1 = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, j);
+ wl2 = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, 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) {
+ printf("Emission cal. curve:\n");
+ } else if (ii == 2) {
+ printf("Ambient cal. curve:\n");
+ } else {
+ printf("Projector cal. curve:\n");
+ }
+ do_plot(x1, y1, y2, NULL, m->nwav2);
+
+ free_dvector(x1, 0, m->nwav2-1);
+ free_dvector(y1, 0, m->nwav2-1);
+ free_dvector(y2, 0, m->nwav2-1);
+ }
+#endif /* HIGH_RES_PLOT */
+ }
+ trspl->del(trspl);
+
+#ifdef SALONEINSTLIB
+ /* Then the 2D stray light using linear interpolation */
+ slp = dmatrix(0, m->nwav1-1, 0, m->nwav1-1);
+
+ /* Set scattered points */
+ for (i = 0; i < m->nwav1; i++) { /* Output wavelength */
+ for (j = 0; j < m->nwav1; j++) { /* Input wavelength */
+
+ slp[i][j] = m->straylight1[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->nwav1-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->nwav1-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->nwav1-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->straylight1[i][j0]
+ + w1 * m->straylight1[i][j1];
+
+ }
+ }
+ }
+#else /* !SALONEINSTLIB */
+ /* Then setup 2D stray light using rspl */
+ if ((trspl = new_rspl(RSPL_NOFLAGS, 2, 1)) == NULL) {
+ a1logd(p->log,3,"munki: creating rspl for high res conversion failed\n");
+ raw2wav->del(raw2wav);
+ return MUNKI_INT_NEW_RSPL_FAILED;
+ }
+
+ /* Set scattered points */
+ for (i = 0; i < m->nwav1; i++) { /* Output wavelength */
+ for (j = 0; j < m->nwav1; j++) { /* Input wavelength */
+ int ix = i * m->nwav1 + j;
+
+ sd[ix].p[0] = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, i);
+ sd[ix].p[1] = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, j);
+ sd[ix].v[0] = m->straylight1[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_short2;
+ glow[1] = m->wl_short2;
+ ghigh[0] = m->wl_long2;
+ ghigh[1] = m->wl_long2;
+ gres[0] = m->nwav2;
+ gres[1] = m->nwav2;
+ avgdev[0] = 0.0;
+ avgdev[1] = 0.0;
+
+ trspl->fit_rspl_w(trspl, 0, sd, m->nwav1 * m->nwav1, glow, ghigh, gres, NULL, NULL, 0.5, avgdev, NULL);
+#endif /* !SALONEINSTLIB */
+
+ m->straylight2 = dmatrixz(0, m->nwav2-1, 0, m->nwav2-1);
+
+ /* Create upsampled version */
+ for (i = 0; i < m->nwav2; i++) { /* Output wavelength */
+ for (j = 0; j < m->nwav2; j++) { /* Input wavelength */
+ double p0, p1;
+ p0 = XSPECT_WL(m->wl_short2, m->wl_long2, m->nwav2, i);
+ p1 = XSPECT_WL(m->wl_short2, m->wl_long2, m->nwav2, j);
+#ifdef SALONEINSTLIB
+ /* Do linear interp with clipping at ends */
+ {
+ int x0, x1, y0, y1;
+ double xx, yy, w0, w1, v0, v1;
+
+ xx = (m->nwav1-1.0) * (p0 - m->wl_short1)/(m->wl_long1 - m->wl_short1);
+ x0 = (int)floor(xx);
+ if (x0 <= 0)
+ x0 = 0;
+ else if (x0 >= (m->nwav1-2))
+ x0 = m->nwav1-2;
+ x1 = x0 + 1;
+ w1 = xx - (double)x0;
+ w0 = 1.0 - w1;
+
+ yy = (m->nwav1-1.0) * (p1 - m->wl_short1)/(m->wl_long1 - m->wl_short1);
+ y0 = (int)floor(yy);
+ if (y0 <= 0)
+ y0 = 0;
+ else if (y0 >= (m->nwav1-2))
+ y0 = m->nwav1-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->straylight2[i][j] = pp.v[0] * HIGHRES_WIDTH/10.0;
+ if (m->straylight2[i][j] > 0.0)
+ m->straylight2[i][j] = 0.0;
+ }
+ }
+
+ /* Fix primary wavelength weight and neighbors */
+ for (i = 0; i < m->nwav2; i++) { /* Output wavelength */
+ double sum;
+
+ if (i > 0)
+ m->straylight2[i][i-1] = 0.0;
+ m->straylight2[i][i] = 0.0;
+ if (i < (m->nwav2-1))
+ m->straylight2[i][i+1] = 0.0;
+
+ for (sum = 0.0, j = 0; j < m->nwav2; j++)
+ sum += m->straylight2[i][j];
+
+ m->straylight2[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->nwav2-1);
+ double *y1 = dvectorz(0, m->nwav2-1);
+ double *y2 = dvectorz(0, m->nwav2-1);
+
+ for (i = 0; i < m->nwav2; i++) { /* Output wavelength */
+ double wli = XSPECT_WL(m->wl_short2, m->wl_long2, m->nwav2, i);
+ int i1 = XSPECT_IX(m->wl_short1, m->wl_long1, m->nwav1, wli);
+
+ for (j = 0; j < m->nwav2; j++) {
+ double wl = XSPECT_WL(m->wl_short2, m->wl_long2, m->nwav2, j);
+ x1[j] = wl;
+ y1[j] = m->straylight2[i][j];
+ if (y1[j] == 0.0)
+ y1[j] = -8.0;
+ else
+ y1[j] = log10(fabs(y1[j]));
+ if (wli < m->wl_short1 || wli > m->wl_long1
+ || wl < m->wl_short1 || wl > m->wl_long1) {
+ y2[j] = -8.0;
+ } else {
+ double x, wl1, wl2;
+ for (k = 0; k < (m->nwav1-1); k++) {
+ wl1 = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, k);
+ wl2 = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, k+1);
+ if (wl >= wl1 && wl <= wl2)
+ break;
+ }
+ x = (wl - wl1)/(wl2 - wl1);
+ y2[j] = m->straylight1[i1][k] + (m->straylight1[i1][k+1]
+ - m->straylight1[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->nwav2);
+ }
+
+ free_dvector(x1, 0, m->nwav2-1);
+ free_dvector(y1, 0, m->nwav2-1);
+ free_dvector(y2, 0, m->nwav2-1);
+ }
+#endif /* HIGH_RES_PLOT */
+
+#ifdef SALONEINSTLIB
+ free_dmatrix(slp, 0, m->nwav1-1, 0, m->nwav1-1);
+#else /* !SALONEINSTLIB */
+ trspl->del(trspl);
+#endif /* !SALONEINSTLIB */
+
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - */
+ /* Allocate space for per mode calibration reference */
+ /* and bring high res calibration factors into line */
+ /* with current standard res. ones */
+ for (i = 0; i < mk_no_modes; i++) {
+ munki_state *s = &m->ms[i];
+
+ s->cal_factor2 = dvectorz(0, m->nwav2-1);
+
+ switch(i) {
+ case mk_refl_spot:
+ case mk_refl_scan:
+ if (s->cal_valid) {
+ munki_absraw_to_abswav1(p, 1, &s->cal_factor1, &s->white_data);
+ munki_absraw_to_abswav2(p, 1, &s->cal_factor2, &s->white_data);
+ munki_compute_white_cal(p, s->cal_factor1, m->white_ref1, s->cal_factor1,
+ s->cal_factor2, m->white_ref2, s->cal_factor2);
+ }
+ break;
+
+ case mk_emiss_spot_na:
+ case mk_emiss_spot:
+ case mk_emiss_scan:
+ for (j = 0; j < m->nwav2; j++)
+ s->cal_factor2[j] = EMIS_SCALE_FACTOR * m->emis_coef2[j];
+ break;
+
+ case mk_tele_spot_na:
+ case mk_tele_spot:
+ for (j = 0; j < m->nwav2; j++)
+ s->cal_factor2[j] = EMIS_SCALE_FACTOR * m->proj_coef2[j];
+ break;
+
+ case mk_amb_spot:
+ case mk_amb_flash:
+ if (m->amb_coef1 != NULL) {
+ for (j = 0; j < m->nwav2; j++)
+ s->cal_factor2[j] = AMB_SCALE_FACTOR * m->amb_coef2[j];
+ s->cal_valid = 1;
+ }
+ break;
+ case mk_trans_spot:
+ case mk_trans_scan:
+ if (s->cal_valid) {
+ munki_absraw_to_abswav1(p, 1, &s->cal_factor1, &s->white_data);
+ munki_absraw_to_abswav2(p, 1, &s->cal_factor2, &s->white_data);
+ munki_compute_white_cal(p, s->cal_factor1, NULL, s->cal_factor1,
+ s->cal_factor2, NULL, s->cal_factor2);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ raw2wav->del(raw2wav);
+
+ return ev;
+}
+
+#endif /* HIGH_RES */
+
+
+/* return nz if high res is supported */
+int munki_imp_highres(munki *p) {
+#ifdef HIGH_RES
+ return 1;
+#else
+ return 0;
+#endif /* HIGH_RES */
+}
+
+/* Set to high resolution mode */
+munki_code munki_set_highres(munki *p) {
+ int i;
+ munkiimp *m = (munkiimp *)p->m;
+ munki_code ev = MUNKI_OK;
+
+#ifdef HIGH_RES
+ if (m->hr_inited == 0) {
+ if ((ev = munki_create_hr(p, 1)) != MUNKI_OK) /* Reflective */
+ return ev;
+ if ((ev = munki_create_hr(p, 0)) != MUNKI_OK) /* Emissive */
+ return ev;
+ }
+
+ m->nwav = m->nwav2;
+ m->wl_short = m->wl_short2;
+ m->wl_long = m->wl_long2;
+
+ m->rmtx_index = m->rmtx_index2;
+ m->rmtx_nocoef = m->rmtx_nocoef2;
+ m->rmtx_coef = m->rmtx_coef2;
+ m->emtx_index = m->emtx_index2;
+ m->emtx_nocoef = m->emtx_nocoef2;
+ m->emtx_coef = m->emtx_coef2;
+ m->white_ref = m->white_ref2;
+ m->emis_coef = m->emis_coef2;
+ m->amb_coef = m->amb_coef2;
+ m->proj_coef = m->proj_coef2;
+ m->straylight = m->straylight2;
+
+ for (i = 0; i < mk_no_modes; i++) {
+ munki_state *s = &m->ms[i];
+ s->cal_factor = s->cal_factor2;
+ }
+ m->highres = 1;
+#else
+ ev = MUNKI_UNSUPPORTED;
+#endif /* HIGH_RES */
+
+ return ev;
+}
+
+/* Set to standard resolution mode */
+munki_code munki_set_stdres(munki *p) {
+ int i;
+ munkiimp *m = (munkiimp *)p->m;
+ munki_code ev = MUNKI_OK;
+
+#ifdef HIGH_RES
+ m->nwav = m->nwav1;
+ m->wl_short = m->wl_short1;
+ m->wl_long = m->wl_long1;
+
+ m->rmtx_index = m->rmtx_index1;
+ m->rmtx_nocoef = m->rmtx_nocoef1;
+ m->rmtx_coef = m->rmtx_coef1;
+ m->emtx_index = m->emtx_index1;
+ m->emtx_nocoef = m->emtx_nocoef1;
+ m->emtx_coef = m->emtx_coef1;
+ m->white_ref = m->white_ref1;
+ m->emis_coef = m->emis_coef1;
+ m->amb_coef = m->amb_coef1;
+ m->proj_coef = m->proj_coef1;
+ m->straylight = m->straylight1;
+
+ for (i = 0; i < mk_no_modes; i++) {
+ munki_state *s = &m->ms[i];
+ s->cal_factor = s->cal_factor1;
+ }
+ m->highres = 0;
+
+#else
+ ev = MUNKI_UNSUPPORTED;
+#endif /* HIGH_RES */
+
+ return ev;
+}
+
+/* Modify the scan consistency tolerance */
+munki_code munki_set_scan_toll(munki *p, double toll_ratio) {
+ munkiimp *m = (munkiimp *)p->m;
+ munki_code ev = MUNKI_OK;
+
+ m->scan_toll_ratio = toll_ratio;
+
+ return MUNKI_OK;
+}
+
+/* Optical 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 */
+munki_code munki_conv2XYZ(
+ munki *p,
+ ipatch *vals, /* Values to return */
+ int nvals, /* Number of values */
+ double **specrd, /* Spectral readings */
+ instClamping clamp /* Clamp XYZ/Lab to be +ve */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ munki_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; /* Number of wavelegths */
+ double wl_short = m->wl_short; /* 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 MUNKI_INT_CIECONVFAIL;
+
+ /* Don't report any wavelengths below the minimum for this mode */
+ if ((s->min_wl-1e-3) > wl_short) {
+ double wl;
+ for (j = 0; j < m->nwav; j++) {
+ wl = XSPECT_WL(m->wl_short, m->wl_long, m->nwav, j);
+ if (wl >= (s->min_wl-1e-3))
+ break;
+ }
+ six = j;
+ wl_short = wl;
+ nwl -= six;
+ }
+
+ a1logd(p->log,3,"munki_conv2XYZ got wl_short %f, wl_long %f, nwav %d, min_wl %f\n"
+ " after skip got wl_short %f, nwl = %d\n",
+ m->wl_short, m->wl_long, m->nwav, s->min_wl, 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;
+
+ if (s->emiss) {
+ for (j = six, k = 0; j < m->nwav; 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 {
+ for (j = six, k = 0; j < m->nwav; 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 MUNKI_OK;
+}
+
+/* Compute a mode calibration factor given the reading of the white reference. */
+/* Return 1 if any of the transmission wavelengths are low. */
+int munki_compute_white_cal(
+ munki *p,
+ double *cal_factor1, /* [nwav1] Calibration factor to compute */
+ double *white_ref1, /* [nwav1] White reference to aim for, NULL for 1.0 */
+ double *white_read1, /* [nwav1] The white that was read */
+ double *cal_factor2, /* [nwav2] Calibration factor to compute */
+ double *white_ref2, /* [nwav2] White reference to aim for, NULL for 1.0 */
+ double *white_read2 /* [nwav2] The white that was read */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ munki_state *s = &m->ms[m->mmode];
+ int j, warn = 0;
+
+ a1logd(p->log,3,"munki_compute_white_cal called\n");
+
+ if (white_ref1 == NULL) { /* transmission white reference */
+ double avgwh = 0.0;
+
+ /* Compute average white reference reading */
+ for (j = 0; j < m->nwav1; j++)
+ avgwh += white_read1[j];
+ avgwh /= (double)m->nwav1;
+
+ /* For each wavelength */
+ for (j = 0; j < m->nwav1; 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->nwav1; 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];
+ }
+ }
+
+#ifdef HIGH_RES
+ if (m->hr_inited == 0)
+ return warn;
+
+ if (white_ref2 == NULL) { /* transmission white reference */
+ double avgwh = 0.0;
+
+ /* Compute average white reference reading */
+ for (j = 0; j < m->nwav2; j++)
+ avgwh += white_read2[j];
+ avgwh /= (double)m->nwav2;
+
+ /* For each wavelength */
+ for (j = 0; j < m->nwav2; j++) {
+ /* If reference is < 0.4% of average */
+ if (white_read2[j]/avgwh < 0.004) {
+ cal_factor2[j] = 1.0/(0.004 * avgwh);
+ warn = 1;
+ } else {
+ cal_factor2[j] = 1.0/white_read2[j];
+ }
+ }
+
+ } else { /* Reflection white reference */
+
+ /* For each wavelength */
+ for (j = 0; j < m->nwav2; j++) {
+ if (white_read2[j] < 1000.0)
+ cal_factor2[j] = white_ref2[j]/1000.0;
+ else
+ cal_factor2[j] = white_ref2[j]/white_read2[j];
+ }
+ }
+#endif /* HIGH_RES */
+ return warn;
+}
+
+/* For adaptive mode, compute a new integration time and gain mode */
+/* in order to optimise the sensor values. */
+munki_code munki_optimise_sensor(
+ munki *p,
+ double *pnew_int_time,
+ int *pnew_gain_mode,
+ double cur_int_time, /* Current intergration time */
+ int cur_gain_mode, /* nz if currently high gain */
+ 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) */
+ /* (May be altered if integration time isn't possible) */
+ double scale, /* scale needed of current int time to reach optimum */
+ double deadtime /* Dead integration time (if any) */
+) {
+ munki_code ev = MUNKI_OK;
+ munkiimp *m = (munkiimp *)p->m;
+ munki_state *s = &m->ms[m->mmode];
+ double new_int_time;
+ double min_int_time; /* Adjusted min_int_time */
+ int new_gain_mode;
+
+ a1logd(p->log,3,"munki_optimise_sensor called, inttime %f, gain mode %d, scale %f\n",cur_int_time,cur_gain_mode, scale);
+
+ min_int_time = m->min_int_time - deadtime;
+ cur_int_time -= deadtime;
+
+ /* Compute new normal gain integration time */
+ if (cur_gain_mode)
+ 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
+ /* Hmm. It may not be a good idea to use high gain mode if it compromises */
+ /* the longer integration time which reduces noise. */
+ if (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 MUNKI_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 < min_int_time && *targoscale < 1.0) {
+ *targoscale *= min_int_time/new_int_time;
+ new_int_time = min_int_time;
+ }
+ a1logd(p->log,3,"after high light adjust, targoscale %f, inttime %f, gain mode %d\n",*targoscale, new_int_time,new_gain_mode);
+
+ /* Deal with still high light */
+ if (new_int_time < min_int_time) {
+ if (permitclip)
+ new_int_time = min_int_time;
+ else
+ return MUNKI_RD_LIGHTTOOHIGH;
+ }
+ a1logd(p->log,3,"after high light clip, returning inttime %f, gain mode %d\n",new_int_time,new_gain_mode);
+
+ new_int_time += deadtime;
+
+ a1logd(p->log,3,"munki_optimise_sensor 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 MUNKI_OK;
+}
+
+/* Compute the number of measurements needed, given the target */
+/* time and integration time. Will return 0 if target time is 0 */
+int munki_comp_nummeas(
+ munki *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;
+}
+
+/* Compute the rounded up number of measurements needed, */
+/* given the target time and integration time. */
+/* Will return 0 if target time is 0 */
+int munki_comp_ru_nummeas(
+ munki *p,
+ double meas_time,
+ double int_time
+) {
+ int nmeas;
+ if (meas_time <= 0.0)
+ return 0;
+ nmeas = (int)ceil(meas_time/int_time);
+ return nmeas;
+}
+
+/* Convert the dark interpolation data to a useful state */
+/* (also allow for interpolating the shielded cell values) */
+void
+munki_prepare_idark(
+ munki *p
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ munki_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];
+ d1 = s->idark_data[i+1][j];
+
+ /* Compute increment proportional to time */
+ 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];;
+ }
+ }
+}
+
+/* Create the dark reference for the given integration time and gain */
+/* by interpolating from the 4 readings prepared earlier */
+munki_code
+munki_interp_dark(
+ munki *p,
+ double *result, /* Put result of interpolation here */
+ double inttime,
+ int gainmode
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ munki_state *s = &m->ms[m->mmode];
+ int i, j;
+
+ if (!s->idark_valid)
+ return MUNKI_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];
+ result[j] = tt;
+ }
+ return MUNKI_OK;
+}
+
+/* Set the noinitcalib mode */
+void munki_set_noinitcalib(munki *p, int v, int losecs) {
+ munkiimp *m = (munkiimp *)p->m;
+ /* Ignore disabling init calib if more than losecs since instrument was open */
+a1logd(p->log,3,"set_noinitcalib v = %d, ->lo_secs %d, losecs %d secs\n",v, m->lo_secs,losecs);
+ 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 munki_set_trig(munki *p, inst_opt_type trig) {
+ munkiimp *m = (munkiimp *)p->m;
+ m->trig = trig;
+}
+
+/* Return the trigger config */
+inst_opt_type munki_get_trig(munki *p) {
+ munkiimp *m = (munkiimp *)p->m;
+ return m->trig;
+}
+
+/* Switch thread handler */
+int munki_switch_thread(void *pp) {
+ int nfailed = 0;
+ munki *p = (munki *)pp;
+ munkiimp *m = (munkiimp *)p->m;
+ munki_code rv = MUNKI_OK;
+ a1logd(p->log,3,"Switch thread started\n");
+ for (nfailed = 0;nfailed < 5;) {
+ mk_eve ecode;
+
+ rv = munki_waitfor_switch_th(p, &ecode, NULL, SW_THREAD_TIMEOUT);
+ if (m->th_term) {
+ m->th_termed = 1;
+ break;
+ }
+ if (rv == MUNKI_INT_BUTTONTIMEOUT) {
+ nfailed = 0;
+ continue;
+ }
+ if (rv != MUNKI_OK) {
+ nfailed++;
+ a1logd(p->log,3,"Switch thread failed with 0x%x\n",rv);
+ continue;
+ }
+ if (ecode == mk_eve_switch_press) {
+ m->switch_count++;
+ if (!m->hide_switch && p->eventcallback != NULL) {
+ p->eventcallback(p->event_cntx, inst_event_switch);
+ }
+ } else if (ecode == mk_eve_spos_change) {
+ if (p->eventcallback != NULL) {
+ p->eventcallback(p->event_cntx, inst_event_mconf);
+ }
+ }
+ }
+ a1logd(p->log,3,"Switch thread returning\n");
+ return rv;
+}
+
+/* ============================================================ */
+/* Low level commands */
+
+/* USB Instrument commands */
+
+/* Read from the EEProm */
+munki_code
+munki_readEEProm(
+ munki *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) */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ int rwbytes; /* Data bytes read or written */
+ unsigned char pbuf[8]; /* Write EEprom parameters */
+ int se, rv = MUNKI_OK;
+
+ a1logd(p->log,2,"munki_readEEProm: address 0x%x size 0x%x\n",addr,size);
+
+ if (size < 0 || addr < 0 || (addr + size) > (m->noeeblocks * m->eeblocksize))
+ return MUNKI_INT_EEOUTOFRANGE;
+
+ int2buf(&pbuf[0], addr);
+ int2buf(&pbuf[4], size);
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0x81, 0, 0, pbuf, 8, 2.0);
+
+ if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
+ a1logd(p->log,1,"munki_readEEProm: read failed (1) with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ /* Now read the bytes */
+ se = p->icom->usb_read(p->icom, NULL, 0x81, buf, size, &rwbytes, 5.0);
+ if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
+ a1logd(p->log,1,"munki_readEEProm: read failed (2) with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ if (rwbytes != size) {
+ a1logd(p->log,1,"munki_readEEProm: 0x%x bytes, short read error\n",rwbytes);
+ return MUNKI_HW_EE_SHORTREAD;
+ }
+
+ if (p->log->debug >= 5) {
+ int i;
+ char oline[100] = { '\000' }, *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,5,oline);
+ bp = oline;
+ }
+ }
+ }
+
+ a1logd(p->log,2,"munki_readEEProm: got 0x%x bytes, ICOM err 0x%x\n",rwbytes, se);
+
+ return rv;
+}
+
+
+
+/* Get the firmware parameters */
+/* return pointers may be NULL if not needed. */
+munki_code
+munki_getfirm(
+ munki *p,
+ int *fwrev, /* Return the formware version number as 8.8 */
+ int *tickdur, /* Tick duration */
+ int *minintcount, /* Minimum integration tick count */
+ int *noeeblocks, /* Number of EEPROM blocks */
+ int *eeblocksize /* Size of each block */
+) {
+ unsigned char pbuf[24]; /* status bytes read */
+ int _fwrev_maj, _fwrev_min;
+ int _tickdur;
+ int _minintcount;
+ int _noeeblocks;
+ int _eeblocksize;
+ int se, rv = MUNKI_OK;
+
+ a1logd(p->log,2,"munki_getfirm:\n");
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0x86, 0, 0, pbuf, 24, 2.0);
+
+ if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
+ a1logd(p->log,1,"munki_getfirm: failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ _fwrev_maj = buf2int(&pbuf[0]);
+ _fwrev_min = buf2int(&pbuf[4]);
+ _tickdur = buf2int(&pbuf[8]);
+ _minintcount = buf2int(&pbuf[12]);
+ _noeeblocks = buf2int(&pbuf[16]);
+ _eeblocksize = buf2int(&pbuf[20]);
+
+ a1logd(p->log,2,"munki_getfirm: returning fwrev %d.%d, tickdur %d, minint %d, eeblks %d, "
+ "eeblksz %d ICOM err 0x%x\n", _fwrev_maj, _fwrev_min, _tickdur, _minintcount,
+ _noeeblocks, _eeblocksize, se);
+
+ if (fwrev != NULL) *fwrev = _fwrev_maj * 256 + _fwrev_min ;
+ if (tickdur != NULL) *tickdur = _tickdur;
+ if (minintcount != NULL) *minintcount = _minintcount;
+ if (noeeblocks != NULL) *noeeblocks = _noeeblocks;
+ if (eeblocksize != NULL) *eeblocksize = _eeblocksize;
+
+ return rv;
+}
+
+/* Get the Chip ID */
+munki_code
+munki_getchipid(
+ munki *p,
+ unsigned char chipid[8]
+) {
+ int se, rv = MUNKI_OK;
+
+ a1logd(p->log,2,"munki_getchipid: called\n");
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0x8A, 0, 0, chipid, 8, 2.0);
+
+ if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
+ a1logd(p->log,1,"munki_getchipid: GetChipID failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ a1logd(p->log,2," GetChipID returns %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 the Version String */
+munki_code
+munki_getversionstring(
+ munki *p,
+ char vstring[37]
+) {
+ int se, rv = MUNKI_OK;
+
+ a1logd(p->log,2,"munki_getversionstring: called\n");
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0x85, 0, 0, (unsigned char *)vstring, 36, 2.0);
+
+ if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
+ a1logd(p->log,1,"munki_getversionstring: failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ vstring[36] = '\000';
+
+ a1logd(p->log,2,"munki_getversionstring: returning '%s' ICOM err 0x%x\n", vstring, se);
+
+ return rv;
+}
+
+/* Get the measurement state */
+/* return pointers may be NULL if not needed. */
+munki_code
+munki_getmeasstate(
+ munki *p,
+ int *ledtrange, /* LED temperature range */
+ int *ledtemp, /* LED temperature */
+ int *dutycycle, /* Duty Cycle */
+ int *ADfeedback /* A/D converter feedback */
+) {
+ unsigned char pbuf[16]; /* values read */
+ int _ledtrange;
+ int _ledtemp;
+ int _dutycycle;
+ int _ADfeedback;
+ int se, rv = MUNKI_OK;
+
+ a1logd(p->log,2,"munki_getmeasstate: called\n");
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0x8F, 0, 0, pbuf, 16, 2.0);
+
+ if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
+ a1logd(p->log,1,"munki_getmeasstate: failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ _ledtrange = buf2int(&pbuf[0]);
+ _ledtemp = buf2int(&pbuf[4]);
+ _dutycycle = buf2int(&pbuf[8]);
+ _ADfeedback = buf2int(&pbuf[12]);
+
+ a1logd(p->log,2,"munki_getmeasstate: returning LED temp range %d, LED temp %d, "
+ "Duty Cycle %d, ADFeefback %d, ICOM err 0x%x\n",
+ _ledtrange, _ledtemp, _dutycycle, _ADfeedback, se);
+
+ if (ledtrange != NULL) *ledtrange = _ledtrange;
+ if (ledtemp != NULL) *ledtemp = _ledtemp;
+ if (dutycycle != NULL) *dutycycle = _dutycycle;
+ if (ADfeedback != NULL) *ADfeedback = _ADfeedback;
+
+ return rv;
+}
+
+/* Get the device status */
+/* return pointers may be NULL if not needed. */
+munki_code
+munki_getstatus(
+ munki *p,
+ mk_spos *spos, /* Return the sensor position */
+ mk_but *but /* Return Button state */
+) {
+ unsigned char pbuf[2]; /* status bytes read */
+ mk_spos _spos;
+ mk_but _but;
+ int se, rv = MUNKI_OK;
+
+ a1logd(p->log,2,"munki_getstatus: called\n");
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0x87, 0, 0, pbuf, 2, 2.0);
+
+ if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
+ a1logd(p->log,1,"munki_getstatus: failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ _spos = (mk_spos)pbuf[0];
+ _but = (mk_but)pbuf[1];
+
+ if (p->log->debug >= 3) {
+ char sb1[50], sb2[50];
+ if (_spos == mk_spos_proj)
+ strcpy(sb1, "Projector");
+ else if (_spos == mk_spos_surf)
+ strcpy(sb1, "Surface");
+ else if (_spos == mk_spos_calib)
+ strcpy(sb1, "Calibration");
+ else if (_spos == mk_spos_amb)
+ strcpy(sb1, "Ambient");
+ else
+ sprintf(sb1,"Unknown 0x%x",_spos);
+ if (_but == mk_but_switch_release)
+ strcpy(sb2, "Released");
+ else if (_but == mk_but_switch_press)
+ strcpy(sb2, "Pressed");
+ else
+ sprintf(sb2,"Unknown 0x%x",_but);
+
+ a1logd(p->log,3,"munki_getstatus: Sensor pos. %s, Button state %s, ICOM err 0x%x\n",
+ sb1, sb2, se);
+ }
+
+ if (spos != NULL) *spos = _spos;
+ if (but != NULL) *but = _but;
+
+ return rv;
+}
+
+/* Set the indicator LED state (advanced) */
+/* NOTE that the instrument seems to turn it off */
+/* whenever any other sort of operation occurs. */
+munki_code
+munki_setindled(
+ munki *p,
+ int p1, /* On time (msec) */
+ int p2, /* Off time (msec) */
+ int p3, /* Transition time (msec) */
+ int p4, /* Number of pulses, -1 = max */
+ int p5 /* Ignored ? */
+) {
+ unsigned char pbuf[20]; /* command bytes written */
+ int se, rv = MUNKI_OK;
+
+ a1logd(p->log,2,"munki_setindled: %d, %d, %d, %d, %d\n",
+ p1, p2, p3, p4, p5);
+
+ int2buf(&pbuf[0], p1); /* On time (msec) */
+ int2buf(&pbuf[4], p2); /* Off time (msec) */
+ int2buf(&pbuf[8], p3); /* Transition time (msec) */
+ int2buf(&pbuf[12], p4); /* Number of pulses, -1 = max */
+ int2buf(&pbuf[16], p5); /* Unknown */
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0x92, 0, 0, pbuf, 20, 2.0);
+
+ if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
+ a1logd(p->log,1,"munki_setindled: failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ a1logd(p->log,2,"munki_setindled: OK ICOM err 0x%x\n",se);
+
+ return rv;
+}
+
+/* Trigger a measurement with the given measurement parameters */
+munki_code
+munki_triggermeasure(
+ munki *p,
+ int intclocks, /* Number of integration clocks */
+ int nummeas, /* Number of measurements to make */
+ int measmodeflags, /* Measurement mode flags */
+ int holdtempduty /* Hold temperature duty cycle */
+) {
+ munkiimp *m = (munkiimp *)p->m;
+ unsigned char pbuf[12]; /* command bytes written */
+ int se, rv = MUNKI_OK;
+
+ a1logd(p->log,2,"munki_triggermeasure: lamp %d, scan %d, gain %d, intclks %d, nummeas %d\n",
+ (measmodeflags & MUNKI_MMF_LAMP) ? 1 : 0,
+ (measmodeflags & MUNKI_MMF_SCAN) ? 1 : 0,
+ (measmodeflags & MUNKI_MMF_HIGHGAIN) ? 1 : 0,
+ intclocks, nummeas);
+
+ pbuf[0] = (measmodeflags & MUNKI_MMF_LAMP) ? 1 : 0;
+ pbuf[1] = (measmodeflags & MUNKI_MMF_SCAN) ? 1 : 0;
+ pbuf[2] = (measmodeflags & MUNKI_MMF_HIGHGAIN) ? 1 : 0;
+ pbuf[3] = holdtempduty;
+ int2buf(&pbuf[4], intclocks);
+ int2buf(&pbuf[8], nummeas);
+
+ m->tr_t1 = m->tr_t2 = m->tr_t3 = m->tr_t4 = m->tr_t5 = m->tr_t6 = m->tr_t7 = 0;
+ m->tr_t1 = msec_time(); /* Diagnostic */
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0x80, 0, 0, pbuf, 12, 2.0);
+
+ m->tr_t2 = msec_time(); /* Diagnostic */
+
+ if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
+ a1logd(p->log,1,"munki_triggermeasure: failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ a1logd(p->log,2,"munki_triggermeasure: OK ICOM err 0x%x\n",se);
+
+ return rv;
+}
+
+/* Read a measurements results. */
+/* A buffer full of bytes is returned. */
+munki_code
+munki_readmeasurement(
+ munki *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 */
+ int calib_measure, /* flag - nz if this is a calibration measurement */
+ int dark_measure /* flag - nz if this is a dark measurement */
+) {
+ munkiimp *m = (munkiimp *)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 = MUNKI_OK;
+ int treadings = 0;
+// int gotshort = 0; /* nz when got a previous short reading */
+
+ if ((bsize % (m->nsen * 2)) != 0) {
+ a1logd(p->log,1,"munki_readmeasurement: got %d bytes, nsen = %d\n",bsize,m->nsen);
+ return MUNKI_INT_ODDREADBUF;
+ }
+
+ extra = 1.0; /* Extra timeout margin */
+
+#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;
+
+ a1logd(p->log,2,"munki_readmeasurement: inummeas %d, scanflag %d, address %p bsize 0x%x, timout %f\n",inummeas, scanflag, buf, bsize, top);
+
+ 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,"munki_readmeasurement: Buffer was too short for scan\n");
+ return MUNKI_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 */
+
+ a1logd(p->log,5,"about to call usb_read with %d bytes\n",size);
+ se = p->icom->usb_read(p->icom, NULL, 0x81, 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,1,"Read timed out in %f secs after getting short read\n"
+ "(Trig & rd times %d %d %d %d)\n",
+ top,
+ 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,5,"Short read, read %d bytes, asked for %d\n"
+ "(Trig & rd times %d %d %d %d)\n",
+ rwbytes,size,
+ 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 = icoms2munki_err(se)) != MUNKI_OK) {
+ if (m->trig_rv != MUNKI_OK) {
+ a1logd(p->log,1,"munki_readmeasurement: trigger failed, ICOM err 0x%x\n",m->trig_se);
+ return m->trig_rv;
+ }
+ if (se & ICOM_TO)
+ a1logd(p->log,1,"munki_readmeasurement: read timed out with top = %f\n",top);
+
+ a1logd(p->log,1,"munki_readmeasurement: read 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,"munki_readmeasurement: read %d bytes, nsen %d, odd read error\n",rwbytes, m->nsen);
+ return MUNKI_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,"munki_readmeasurement: unexpected short read, got %d expected %d\n",rwbytes,size);
+ return MUNKI_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) {
+ a1logd(p->log,5,"done because read %d bytes != %d\n",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, 0x81, tbuf, m->nsen * 2, &rwbytes, top)) == ICOM_OK)
+ ;
+ a1logd(p->log,1,"munki_readmeasurement: buffer was too short for scan\n");
+ return MUNKI_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;
+ }
+
+ /* Must have timed out in initial readings */
+ if (treadings < inummeas) {
+ a1logd(p->log,1,"munki_readmeasurement: read failed, bytes read 0x%x, ICOM err 0x%x\n",rwbytes, se);
+ return MUNKI_RD_SHORTMEAS;
+ }
+
+ if (p->log->debug >= 5) {
+ int i, size = treadings * m->nsen * 2;
+ char oline[100] = { '\000' }, *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,5,oline);
+ bp = oline;
+ }
+ }
+ }
+
+ a1logd(p->log,2,"munki_readmeasurement: Read %d readings, ICOM err 0x%x\n"
+ "(Trig & rd times %d %d %d %d)\n",
+ treadings, se,
+ 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;
+}
+
+/* Simulating an event */
+munki_code munki_simulate_event(munki *p, mk_eve ecode, int timestamp) {
+ munkiimp *m = (munkiimp *)p->m;
+ unsigned char pbuf[8]; /* 8 bytes to write */
+ int se, rv = MUNKI_OK;
+
+ a1logd(p->log,2,"munki_simulate_event: 0x%x\n",ecode);
+
+ int2buf(&pbuf[0], ecode);
+ int2buf(&pbuf[4], timestamp); /* msec since munki power up */
+
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0x8E, 0, 0, pbuf, 8, 2.0);
+
+ if ((rv = icoms2munki_err(se)) != MUNKI_OK)
+ a1logd(p->log,1,"munki_simulate_event: event 0x%x failed with ICOM err 0x%x\n",ecode,se);
+ else
+ a1logd(p->log,2,"munki_simulate_event: 0x%x done, ICOM err 0x%x\n",ecode,se);
+
+ /* Cancel the I/O in case there is no response*/
+ msec_sleep(50);
+ if (m->th_termed == 0) {
+ a1logd(p->log,1,"munki_simulate_event: terminate switch thread failed, canceling I/O\n");
+ p->icom->usb_cancel_io(p->icom, &m->cancelt);
+ }
+
+ return rv;
+}
+
+/* Wait for a reply triggered by an event */
+munki_code munki_waitfor_switch(munki *p, mk_eve *ecode, int *timest, double top) {
+ int rwbytes; /* Data bytes read */
+ unsigned char buf[8]; /* Result */
+ int se, rv = MUNKI_OK;
+ mk_eve _ecode;
+ int _timest;
+
+ a1logd(p->log,2,"munki_waitfor_switch: Read 8 bytes from switch hit port\n");
+
+ /* Now read 8 bytes */
+ se = p->icom->usb_read(p->icom, NULL, 0x83, buf, 8, &rwbytes, top);
+
+ if (se & ICOM_TO) {
+ a1logd(p->log,1,"munki_waitfor_switch: read 0x%x bytes, timed out\n",rwbytes);
+ return MUNKI_INT_BUTTONTIMEOUT;
+ }
+
+ if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
+ a1logd(p->log,2,"munki_waitfor_switch: read failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ if (rwbytes != 8) {
+ a1logd(p->log,1,"munki_waitfor_switch: read %d bytes, short read error\n",rwbytes);
+ return MUNKI_HW_EE_SHORTREAD;
+ }
+
+ _ecode = (mk_eve) buf2int(&buf[0]);
+ _timest = buf2int(&buf[4]);
+
+ if (p->log->debug >= 3) {
+ char sbuf[100];
+ if (_ecode == mk_eve_none)
+ strcpy(sbuf, "None");
+ else if (_ecode == mk_eve_switch_press)
+ strcpy(sbuf, "Button press");
+ else if (_ecode == mk_eve_switch_release)
+ strcpy(sbuf, "Button release");
+ else if (_ecode == mk_eve_spos_change)
+ strcpy(sbuf, "Sensor position change");
+ else
+ sprintf(sbuf,"Unknown 0x%x",_ecode);
+
+ a1logd(p->log,3,"munki_waitfor_switch: Event %s, timestamp %d ICOM err 0x%x\n", sbuf, _timest, se);
+ }
+
+ a1logd(p->log,2,"munki_waitfor_switch: read %d bytes OK\n",rwbytes);
+
+ if (ecode != NULL) *ecode = _ecode;
+ if (timest != NULL) *timest = _timest;
+
+ return rv;
+}
+
+/* Wait for a reply triggered by a key press or config change (thread version) */
+/* Returns MUNKI_OK if the switch has been pressed, */
+/* or MUNKI_INT_BUTTONTIMEOUT if */
+/* no switch was pressed befor the time expired, */
+/* or some other error. */
+munki_code munki_waitfor_switch_th(munki *p, mk_eve *ecode, int *timest, double top) {
+ munkiimp *m = (munkiimp *)p->m;
+ int rwbytes; /* Data bytes read */
+ unsigned char buf[8]; /* Result */
+ int se, rv = MUNKI_OK;
+ mk_eve _ecode;
+ int _timest;
+
+ a1logd(p->log,2,"munki_waitfor_switch_th: Read 8 bytes from switch hit port\n");
+
+ /* Now read 8 bytes */
+ se = p->icom->usb_read(p->icom, &m->cancelt, 0x83, buf, 8, &rwbytes, top);
+
+ if (se & ICOM_TO) {
+ a1logd(p->log,1,"munki_waitfor_switch_th: read 0x%x bytes, timed out\n",rwbytes);
+ return MUNKI_INT_BUTTONTIMEOUT;
+ }
+
+ if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
+ a1logd(p->log,2,"munki_waitfor_switch_th: read failed with ICOM err 0x%x\n",se);
+ return rv;
+ }
+
+ if (rwbytes != 8) {
+ a1logd(p->log,1,"munki_waitfor_switch_th: read %d bytes, short read error\n",rwbytes);
+ return MUNKI_HW_EE_SHORTREAD;
+ }
+
+ _ecode = (mk_eve) buf2int(&buf[0]);
+ _timest = buf2int(&buf[4]); /* msec since munki power up */
+
+ if (p->log->debug >= 3) {
+ char sbuf[100];
+ if (_ecode == mk_eve_none)
+ strcpy(sbuf, "None");
+ else if (_ecode == mk_eve_switch_press)
+ strcpy(sbuf, "Button press");
+ else if (_ecode == mk_eve_switch_release)
+ strcpy(sbuf, "Button release");
+ else if (_ecode == mk_eve_spos_change)
+ strcpy(sbuf, "Sensor position change");
+ else
+ sprintf(sbuf,"Unknown 0x%x",_ecode);
+
+ a1logd(p->log,3,"munki_waitfor_switch_th: Event %s, timestamp %d ICOM err 0x%x\n", sbuf, _timest, se);
+ }
+
+ a1logd(p->log,2,"munki_waitfor_switch_th: read %d bytes OK\n",rwbytes);
+
+ if (ecode != NULL) *ecode = _ecode;
+ if (timest != NULL) *timest = _timest;
+
+ return rv;
+}
+
+
+/* ============================================================ */
+/* key/value dictionary support for EEProm contents */
+
+/* Fixup values for the window reference */
+
+/* Check values */
+static double proj_check[36] = {
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.827859997749328610,
+ 0.849550008773803710,
+ 0.855490028858184810,
+ 0.858709990978240970,
+ 0.861010015010833740,
+ 0.862879991531372070,
+ 0.864109992980957030,
+ 0.864619970321655270,
+ 0.865379989147186280,
+ 0.865629971027374270,
+ 0.865689992904663090,
+ 0.865499973297119140,
+ 0.865499973297119140,
+ 0.865760028362274170,
+ 0.866209983825683590,
+ 0.866630017757415770,
+ 0.867579996585845950,
+ 0.868969976902008060,
+ 0.870270013809204100,
+ 0.871270000934600830,
+ 0.872340023517608640,
+ 0.873269975185394290,
+ 0.873669981956481930,
+ 0.873640000820159910,
+ 0.874390006065368650,
+ 0.873179972171783450,
+ 0.872720003128051760,
+ 0.872640013694763180,
+ 0.873160004615783690,
+ 0.873440027236938480
+};
+
+
+/* Correction values to emission ref. */
+static double proj_fix[36] = {
+ 0.639684915542602540,
+ 0.639684915542602540,
+ 0.639684915542602540,
+ 0.812916100025177000,
+ 0.846581041812896730,
+ 0.854855418205261230,
+ 0.859299719333648680,
+ 0.861804306507110600,
+ 0.863713920116424560,
+ 0.865424513816833500,
+ 0.866307735443115230,
+ 0.867028772830963130,
+ 0.867631316184997560,
+ 0.868214190006256100,
+ 0.868206322193145750,
+ 0.868299305438995360,
+ 0.867988884449005130,
+ 0.868103504180908200,
+ 0.868657410144805910,
+ 0.869595944881439210,
+ 0.870542407035827640,
+ 0.871895790100097660,
+ 0.873195052146911620,
+ 0.874702811241149900,
+ 0.876054167747497560,
+ 0.877129673957824710,
+ 0.877931654453277590,
+ 0.877546310424804690,
+ 0.876341819763183590,
+ 0.875181615352630620,
+ 0.875020027160644530,
+ 0.875684559345245360,
+ 0.876559674739837650,
+ 0.876724362373352050,
+ 0.876553714275360110,
+ 0.875786423683166500
+};
+
+/* Initialise the calibration from the EEProm contents. */
+/* (We're handed a buffer that's been rounded up to an even 32 bits by */
+/* padding with zero's) */
+munki_code munki_parse_eeprom(munki *p, unsigned char *buf, unsigned int len) {
+ munkiimp *m = (munkiimp *)p->m;
+ mkdata *d;
+ int rv = MUNKI_OK;
+ unsigned int chsum, sum;
+ int calver, compver; /* Calibration version and compatiblity version */
+ unsigned char chipid[8]; /* Calibration chip id */
+ int tint, *tinta; /* Temporary */
+ double tdouble; /* Temporary */
+ int i, j;
+
+ a1logd(p->log,2,"munki_parse_eeprom: called with %d bytes\n",len);
+
+ /* Check the checksum */
+ chsum = buf2uint(buf+8);
+ int2buf(buf+8, 0); /* Zero it out */
+
+ for (sum = 0, i = 0; i < (len-3); i += 4) {
+ sum += buf2uint(buf + i);
+ }
+
+
+
+ a1logd(p->log,3,"munki_parse_eeprom: cal chsum = 0x%x, should be 0x%x - %s\n",sum,chsum, sum == chsum ? "OK": "BAD");
+ if (sum != chsum)
+ return MUNKI_INT_CALBADCHSUM;
+
+
+ /* Create class to handle EEProm parsing */
+ if ((d = m->data = new_mkdata(p, buf, len)) == NULL)
+ return MUNKI_INT_CREATE_EEPROM_STORE;
+
+ /* Check out the version */
+ if (d->get_u16_ints(d, &calver, 0, 1) == NULL)
+ return MUNKI_DATA_RANGE;
+ if (d->get_u16_ints(d, &compver, 2, 1) == NULL)
+ return MUNKI_DATA_RANGE;
+ a1logd(p->log,4,"cal version = %d, compatible with %d\n",calver,compver);
+
+ /* We understand versions 3 to 6 */
+
+ if (calver < 3 || compver < 3 || compver > 6)
+ return MUNKI_HW_CALIBVERSION;
+
+ /* Choose the version we will treat it as */
+ if (calver > 6 && compver <= 6)
+ m->calver = 6;
+ else
+ m->calver = calver;
+ a1logd(p->log,4,"Treating as cal version = %d\n",m->calver);
+
+ /* Parse all the calibration info common for vers 3 - 6 */
+
+ /* Production number */
+ if (d->get_32_ints(d, &m->prodno, 12, 1) == NULL)
+ return MUNKI_DATA_RANGE;
+ a1logd(p->log,4,"Produnction no = %d\n",m->prodno);
+
+ /* Chip HW ID */
+ if (d->get_8_char(d, (unsigned char *)chipid, 16, 8) == NULL)
+ return MUNKI_DATA_RANGE;
+ a1logd(p->log,4,"HW Id = %02x-%02x%02x%02x%02x%02x%02x%02x\n",
+ chipid[0], chipid[1], chipid[2], chipid[3],
+ chipid[4], chipid[5], chipid[6], chipid[7]);
+
+ /* Check that the chipid matches the calibration */
+ for (i = 0; i < 8; i++) {
+ if (chipid[i] != m->chipid[i])
+ return MUNKI_HW_CALIBMATCH;
+ }
+
+ /* Serial number */
+ if (d->get_8_asciiz(d, m->serno, 24, 16) == NULL)
+ return MUNKI_DATA_RANGE;
+ a1logd(p->log,4,"serial number '%s'\n",m->serno);
+
+ /* Underlying calibration information */
+
+ m->nsen = 137; /* Sensor bands stored */
+ m->nraw = 128; /* Raw bands stored */
+ m->nwav1 = 36; /* Standard res number of cooked spectrum band */
+ m->wl_short1 = 380.0; /* Standard res short and long wavelengths */
+ m->wl_long1 = 730.0;
+
+ /* Fill this in here too */
+ m->wl_short2 = HIGHRES_SHORT;
+ m->wl_long2 = HIGHRES_LONG;
+ m->nwav2 = (int)((m->wl_long2-m->wl_short2)/HIGHRES_WIDTH + 0.5) + 1;
+
+ /* Reflection wavelength calibration information */
+ /* This is setup assuming 128 raw bands, starting */
+ /* at offset 6 from the values returned by the hardware. */
+ if ((m->rmtx_index1 = d->get_32_ints(d, NULL, 40, 36)) == NULL)
+ return MUNKI_DATA_RANGE;
+
+ /* Fake the number of matrix cooeficients for each out wavelength */
+ if ((m->rmtx_nocoef1 = (int *)malloc(sizeof(int) * 36)) == NULL)
+ return MUNKI_DATA_MEMORY;
+ for (i = 0; i < 36; i++)
+ m->rmtx_nocoef1[i] = 16;
+
+ if ((m->rmtx_coef1 = d->get_32_doubles(d, NULL, 184, 36 * 16)) == NULL)
+ return MUNKI_DATA_RANGE;
+
+#ifdef NEVER
+// ~~~ hack !!!
+m->rmtx_index1[4] = m->rmtx_index1[5] + 3;
+m->rmtx_index1[3] = m->rmtx_index1[4] + 3;
+m->rmtx_index1[2] = m->rmtx_index1[3] + 3;
+m->rmtx_index1[1] = m->rmtx_index1[2] + 3;
+m->rmtx_index1[0] = m->rmtx_index1[1] + 3;
+
+for(i = 0; i < 5; i++) {
+ for (j = 0; j < 16; j++) {
+ m->rmtx_coef1[i * 16 + j] = m->rmtx_coef1[5 * 16 + j];
+ }
+}
+#endif
+
+ if (p->log->debug >= 7) {
+ a1logd(p->log,7,"Reflectance matrix:\n");
+ for(i = 0; i < 36; i++) {
+ a1logd(p->log,7," Wave %d, index %d\n",i, m->rmtx_index1[i]);
+ for (j = 0; j < 16; j++) {
+ if (m->rmtx_coef1[i * 16 + j] != 0.0)
+ a1logd(p->log,7," Wt %d = %f\n",j, m->rmtx_coef1[i * 16 + j]);
+ }
+ }
+ }
+
+ /* Emission wavelength calibration information */
+ if ((m->emtx_index1 = d->get_32_ints(d, NULL, 2488, 36)) == NULL)
+ return MUNKI_DATA_RANGE;
+
+ /* Fake the number of matrix cooeficients for each out wavelength */
+ if ((m->emtx_nocoef1 = (int *)malloc(sizeof(int) * 36)) == NULL)
+ return MUNKI_DATA_MEMORY;
+ for (i = 0; i < 36; i++)
+ m->emtx_nocoef1[i] = 16;
+
+ if ((m->emtx_coef1 = d->get_32_doubles(d, NULL, 2632, 36 * 16)) == NULL)
+ return MUNKI_DATA_RANGE;
+
+ if (p->log->debug >= 7) {
+ a1logd(p->log,5,"Emmission matrix:\n");
+ for(i = 0; i < 36; i++) {
+ a1logd(p->log,7," Wave %d, index %d\n",i, m->emtx_index1[i]);
+ for (j = 0; j < 16; j++) {
+ if (m->emtx_coef1[i * 16 + j] != 0.0)
+ a1logd(p->log,7," Wt %d = %f\n",j, m->emtx_coef1[i * 16 + j]);
+ }
+ }
+ }
+
+ /* Linearization */
+ if ((m->lin0 = d->rget_32_doubles(d, NULL, 4936, 4)) == NULL)
+ return MUNKI_DATA_RANGE;
+ m->nlin0 = 4;
+
+ if ((m->lin1 = d->rget_32_doubles(d, NULL, 4952, 4)) == NULL)
+ return MUNKI_DATA_RANGE;
+ m->nlin1 = 4;
+
+ if (p->log->debug >= 3) {
+ 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);
+ }
+
+ /* Reflectance reference */
+ if ((m->white_ref1 = d->get_32_doubles(d, NULL, 4968, 36)) == NULL)
+ return MUNKI_DATA_RANGE;
+
+ /* Emission reference */
+ if ((m->emis_coef1 = d->get_32_doubles(d, NULL, 5112, 36)) == NULL)
+ return MUNKI_DATA_RANGE;
+
+ /* Ambient reference */
+ if ((m->amb_coef1 = d->get_32_doubles(d, NULL, 5256, 36)) == NULL)
+ return MUNKI_DATA_RANGE;
+
+ /* Sensor target values */
+ if (d->get_u16_ints(d, &tint, 5400, 1) == NULL)
+ return MUNKI_DATA_RANGE;
+ m->minsval = (double)tint;
+ if (d->get_u16_ints(d, &tint, 5402, 1) == NULL)
+ return MUNKI_DATA_RANGE;
+ m->optsval = (double)tint;
+ if (d->get_u16_ints(d, &tint, 5404, 1) == NULL)
+ return MUNKI_DATA_RANGE;
+ m->maxsval = (double)tint;
+ if (d->get_u16_ints(d, &tint, 5406, 1) == NULL)
+ return MUNKI_DATA_RANGE;
+ m->satlimit = (double)tint;
+
+ a1logd(p->log,4,"Sensor targmin %.0f, opt %.0f, max %.0f, sat %.0f\n",
+ m->minsval,m->optsval,m->maxsval,m->satlimit);
+
+ if (d->get_32_doubles(d, &m->cal_int_time, 5408, 1) == NULL)
+ return MUNKI_DATA_RANGE;
+ m->cal_int_time *= 1e-3; /* Convert to seconds */
+
+ if (d->get_32_ints(d, &tint, 5412, 1) == NULL)
+ return MUNKI_DATA_RANGE;
+ m->ledpreheattime = tint * 1e-3; /* Convert to seconds */
+
+ if (d->get_32_ints(d, &tint, 5416, 1) == NULL)
+ return MUNKI_DATA_RANGE;
+ m->ledwaittime = tint * 1e-3; /* Convert to seconds */
+
+ if (d->get_u16_ints(d, &m->ledholdtempdc, 5420, 1) == NULL)
+ return MUNKI_DATA_RANGE;
+
+ a1logd(p->log,4,"Cal int time %f, LED pre-heat %f, Led wait %f, LED hold temp duty cycle %d\n", m->cal_int_time, m->ledpreheattime, m->ledwaittime, m->ledholdtempdc);
+
+ if (d->get_u16_ints(d, &tint, 5422, 1) == NULL)
+ return MUNKI_DATA_RANGE;
+ m->refinvalidsampt = tint * 1e-3; /* Convert to seconds */
+
+ if (d->get_32_ints(d, &tint, 5424, 1) == NULL)
+ return MUNKI_DATA_RANGE;
+ m->calscantime = tint * 1e-3; /* Convert to seconds */
+
+ a1logd(p->log,4,"Invalid sample time %f, Cal scan time %f\n",
+ m->refinvalidsampt, m->calscantime);
+
+ /* Stray light compensation. Note that 16 bit numbers are signed. */
+ if ((tinta = d->get_16_ints(d, NULL, 5428, 36 * 36)) == NULL)
+ return MUNKI_DATA_RANGE;
+ if (m->calver >= 4) {
+ if (d->get_32_doubles(d, &tdouble, 8020, 1) == NULL)
+ return MUNKI_DATA_RANGE;
+ } else {
+ tdouble = 0.001; /* Hmm. this is quite different to EEProm value */
+ }
+ /* Convert from ints to floats */
+ m->straylight1 = dmatrixz(0, 35, 0, 35);
+ for (i = 0; i < 36; i++) {
+ for (j = 0; j < 36; j++) {
+ m->straylight1[i][j] = tdouble * tinta[i * 36 + j];
+ if (i == j)
+ m->straylight1[i][j] += 1.0;
+ }
+ }
+ free(tinta);
+
+ if (p->log->debug >= 7) {
+ a1logd(p->log,7,"Stray Light matrix:\n");
+ for(i = 0; i < 36; i++) {
+ double sum = 0.0;
+ a1logd(p->log,7," Wave %d, index %d\n",i, m->rmtx_index1[i]);
+ for (j = 0; j < 36; j++) {
+ sum += m->straylight1[i][j];
+ a1logd(p->log,7," Wt %d = %f\n",j, m->straylight1[i][j]);
+ }
+ a1logd(p->log,7," Sum = %f\n",sum);
+ }
+ }
+
+ if (m->calver >= 5) {
+ /* Projector reference */
+ if ((m->proj_coef1 = d->get_32_doubles(d, NULL, 8024, 36)) == NULL)
+ return MUNKI_DATA_RANGE;
+
+ /* Apparently this can be faulty though. Check if it is */
+ for (i = 0; i < 6; i++) {
+ if (m->proj_coef1[i] != m->proj_coef1[i])
+ break; /* Not Nan */
+ }
+ if (i == 6) { /* First 6 are Nan's */
+ for (; i < 36; i++) {
+ if ((m->emis_coef1[i]/m->proj_coef1[i] - proj_check[i]) > 0.001)
+ break; /* Not less than 0.001 */
+ }
+ }
+ if (i == 36) { /* It's faulty */
+ free(m->proj_coef1);
+ m->proj_coef1 = NULL; /* Fall through to fakeup */
+ }
+ }
+
+ if (m->proj_coef1 == NULL) { /* Fake up a projector reference */
+ if ((m->proj_coef1 = (double *)malloc(sizeof(double) * 36)) == NULL)
+ return MUNKI_DATA_MEMORY;
+ for (i = 0; i < 36; i++) {
+ m->proj_coef1[i] = m->emis_coef1[i]/proj_fix[i];
+ }
+ a1logd(p->log,4,"Faked up projector cal reference\n");
+ }
+
+ if (m->calver >= 6) {
+ if (d->get_8_ints(d, &m->adctype, 8168, 1) == NULL)
+ return MUNKI_DATA_RANGE;
+ } else {
+ m->adctype = 0;
+ }
+
+ if (p->log->debug >= 7) {
+ a1logd(p->log,4,"White ref, emission cal, ambient cal, proj cal:\n");
+ for(i = 0; i < 36; i++) {
+ a1logd(p->log,7," %d: %f, %f, %f, %f\n",i, m->white_ref1[i], m->emis_coef1[i],
+ m->amb_coef1[i], m->proj_coef1[i]);
+ }
+ }
+
+#ifdef PLOT_RCALCURVE
+ /* Plot the reflection reference curve */
+ {
+ int i;
+ double xx[36];
+ double y1[36];
+
+ for (i = 0; i < m->nwav1; i++) {
+ xx[i] = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, i);
+ y1[i] = m->white_ref1[i];
+ }
+ printf("Reflection Reference (Black)\n");
+ do_plot(xx, y1, NULL, NULL, 36);
+ }
+#endif /* PLOT_RCALCURVE */
+
+#ifdef PLOT_ECALCURVES
+ /* Plot the emission reference curves */
+ {
+ int i;
+ double xx[36];
+ double y1[36], y2[36], y3[36];
+
+ printf("Emission Reference (Black), Ambient (Red), Projector (Green)\n");
+ for (i = 0; i < m->nwav1; i++) {
+ xx[i] = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, i);
+ y1[i] = m->emis_coef1[i];
+ y2[i] = m->amb_coef1[i];
+ y3[i] = m->proj_coef1[i];
+ if (y3[i] > 0.02)
+ y3[i] = 0.02;
+ }
+ do_plot(xx, y1, y2, y3, 36);
+ }
+#endif /* PLOT_ECALCURVES */
+
+ /* Default to standard resolution */
+ m->nwav = m->nwav1;
+ m->wl_short = m->wl_short1;
+ m->wl_long = m->wl_long1;
+
+ m->rmtx_index = m->rmtx_index1;
+ m->rmtx_nocoef = m->rmtx_nocoef1;
+ m->rmtx_coef = m->rmtx_coef1;
+ m->emtx_index = m->emtx_index1;
+ m->emtx_nocoef = m->emtx_nocoef1;
+ m->emtx_coef = m->emtx_coef1;
+
+ m->white_ref = m->white_ref1;
+ m->emis_coef = m->emis_coef1;
+ m->amb_coef = m->amb_coef1;
+ m->proj_coef = m->proj_coef1;
+ m->straylight = m->straylight1;
+
+ m->highgain = 1.0/m->lin1[1]; /* Gain is encoded in linearity */
+ a1logd(p->log,3, "highgain = %f\n",m->highgain);
+
+ return rv;
+}
+
+
+/* Return a pointer to an array of chars containing data from 8 bits. */
+/* If rv is NULL, the returned value will have been allocated, othewise */
+/* the rv will be returned. Return NULL if out of range. */
+static unsigned char *mkdata_get_8_char(struct _mkdata *d, unsigned char *rv, int off, int count) {
+ int i;
+
+ if (count <= 0
+ || off < 0
+ || (off + count * 1) > d->len)
+ return NULL;
+
+ if (rv == NULL) {
+ if ((rv = (unsigned char *)malloc(sizeof(int) * count)) == NULL)
+ return NULL;
+ }
+
+ for (i = 0; i < count; i++, off += 1) {
+ rv[i] = d->buf[off];
+ }
+ return rv;
+}
+
+/* Return a pointer to an nul terminated string containing data from 8 bits. */
+/* If rv is NULL, the returned value will have been allocated, othewise */
+/* the rv will be returned. Return NULL if out of range. */
+/* An extra space and a nul terminator will be added to the eeprom data */
+static char *mkdata_get_8_asciiz(struct _mkdata *d, char *rv, int off, int count) {
+ int i;
+
+ if (count <= 0
+ || off < 0
+ || (off + count * 1) > d->len)
+ return NULL;
+
+ if (rv == NULL) {
+ if ((rv = (char *)malloc(sizeof(int) * (count + 1))) == NULL)
+ return NULL;
+ }
+
+ for (i = 0; i < count; i++, off += 1) {
+ rv[i] = (char)d->buf[off];
+ }
+ rv[i] = '\000';
+
+ return rv;
+}
+
+/* Return a pointer to an array of ints containing data from 8 bits. */
+/* If rv is NULL, the returned value will have been allocated, othewise */
+/* the rv will be returned. Return NULL if out of range. */
+static int *mkdata_get_8_ints(struct _mkdata *d, int *rv, int off, int count) {
+ int i;
+
+ if (count <= 0
+ || off < 0
+ || (off + count * 1) > d->len)
+ return NULL;
+
+ if (rv == NULL) {
+ if ((rv = (int *)malloc(sizeof(int) * count)) == NULL)
+ return NULL;
+ }
+
+ for (i = 0; i < count; i++, off += 1) {
+ rv[i] = ((signed char *)d->buf)[off];
+ }
+ return rv;
+}
+
+/* Return a pointer to an array of ints containing data from unsigned 8 bits. */
+/* If rv is NULL, the returned value will have been allocated, othewise */
+/* the rv will be returned. Return NULL if out of range. */
+static int *mkdata_get_u8_ints(struct _mkdata *d, int *rv, int off, int count) {
+ int i;
+
+ if (count <= 0
+ || off < 0
+ || (off + count * 1) > d->len)
+ return NULL;
+
+ if (rv == NULL) {
+ if ((rv = (int *)malloc(sizeof(int) * count)) == NULL)
+ return NULL;
+ }
+
+ for (i = 0; i < count; i++, off += 1) {
+ rv[i] = d->buf[off];
+ }
+ return rv;
+}
+
+/* Return a pointer to an array of ints containing data from 16 bits. */
+/* If rv is NULL, the returned value will have been allocated, othewise */
+/* the rv will be returned. Return NULL if out of range. */
+static int *mkdata_get_16_ints(struct _mkdata *d, int *rv, int off, int count) {
+ int i;
+
+ if (count <= 0
+ || off < 0
+ || (off + count * 2) > d->len)
+ return NULL;
+
+ if (rv == NULL) {
+ if ((rv = (int *)malloc(sizeof(int) * count)) == NULL)
+ return NULL;
+ }
+
+ for (i = 0; i < count; i++, off += 2) {
+ rv[i] = buf2short(d->buf + off);
+ }
+ return rv;
+}
+
+/* Return a pointer to an array of ints containing data from unsigned 16 bits. */
+/* If rv is NULL, the returned value will have been allocated, othewise */
+/* the rv will be returned. Return NULL if out of range. */
+static int *mkdata_get_u16_ints(struct _mkdata *d, int *rv, int off, int count) {
+ int i;
+
+ if (count <= 0
+ || off < 0
+ || (off + count * 2) > d->len)
+ return NULL;
+
+ if (rv == NULL) {
+ if ((rv = (int *)malloc(sizeof(int) * count)) == NULL)
+ return NULL;
+ }
+
+ for (i = 0; i < count; i++, off += 2) {
+ rv[i] = buf2ushort(d->buf + off);
+ }
+ return rv;
+}
+
+/* Return a pointer to an array of ints containing data from 32 bits. */
+/* If rv is NULL, the returned value will have been allocated, othewise */
+/* the rv will be returned. Return NULL if out of range. */
+static int *mkdata_get_32_ints(struct _mkdata *d, int *rv, int off, int count) {
+ int i;
+
+ if (count <= 0
+ || off < 0
+ || (off + count * 4) > d->len)
+ return NULL;
+
+ if (rv == NULL) {
+ if ((rv = (int *)malloc(sizeof(int) * count)) == NULL)
+ return NULL;
+ }
+
+ for (i = 0; i < count; i++, off += 4) {
+ rv[i] = buf2int(d->buf + off);
+ }
+ return rv;
+}
+
+/* Return a pointer to an array of unsigned ints containing data from unsigned 32 bits. */
+/* If rv is NULL, the returned value will have been allocated, othewise */
+/* the rv will be returned. Return NULL if out of range. */
+static unsigned int *mkdata_get_u32_uints(struct _mkdata *d, unsigned int *rv, int off, int count) {
+ int i;
+
+ if (count <= 0
+ || off < 0
+ || (off + count * 4) > d->len)
+ return NULL;
+
+ if (rv == NULL) {
+ if ((rv = (unsigned int *)malloc(sizeof(unsigned int) * count)) == NULL)
+ return NULL;
+ }
+
+ for (i = 0; i < count; i++, off += 4) {
+ rv[i] = buf2uint(d->buf + off);
+ }
+ return rv;
+}
+
+/* Return a pointer to an array of doubles containing data from 32 bits. */
+/* If rv is NULL, the returned value will have been allocated, othewise */
+/* the rv will be returned. Return NULL if out of range or malloc failure. */
+static double *mkdata_get_32_doubles(struct _mkdata *d, double *rv, int off, int count) {
+ int i;
+
+ if (count <= 0
+ || off < 0
+ || (off + count * 4) > d->len)
+ return NULL;
+
+ if (rv == NULL) {
+ if ((rv = (double *)malloc(sizeof(double) * count)) == NULL)
+ return NULL;
+ }
+
+ for (i = 0; i < count; i++, off += 4) {
+ unsigned int val;
+ val = buf2uint(d->buf + off);
+ rv[i] = IEEE754todouble(val);
+ }
+ return rv;
+}
+
+/* Return a pointer to an array of doubles containing data from 32 bits, */
+/* with the array filled in reverse order. */
+/* If rv is NULL, the returned value will have been allocated, othewise */
+/* the rv will be returned. Return NULL if out of range or malloc failure. */
+static double *mkdata_rget_32_doubles(struct _mkdata *d, double *rv, int off, int count) {
+ int i;
+
+ if (count <= 0
+ || off < 0
+ || (off + count * 4) > d->len)
+ return NULL;
+
+ if (rv == NULL) {
+ if ((rv = (double *)malloc(sizeof(double) * count)) == NULL)
+ return NULL;
+ }
+
+ for (i = count-1; i >= 0; i--, off += 4) {
+ unsigned int val;
+ val = buf2uint(d->buf + off);
+ rv[i] = IEEE754todouble(val);
+ }
+ return rv;
+}
+
+
+/* Destroy ourselves */
+static void mkdata_del(mkdata *d) {
+ del_a1log(d->log); /* Unref it */
+ free(d);
+}
+
+/* Constructor for mkdata */
+mkdata *new_mkdata(munki *p, unsigned char *buf, int len) {
+ mkdata *d;
+ if ((d = (mkdata *)calloc(1, sizeof(mkdata))) == NULL) {
+ a1loge(p->log, 1, "new_mkdata: malloc failed!\n");
+ return NULL;
+ }
+
+ d->p = p;
+
+ d->log = new_a1log_d(p->log); /* Take reference */
+
+ d->buf = buf;
+ d->len = len;
+
+ d->get_8_char = mkdata_get_8_char;
+ d->get_8_asciiz = mkdata_get_8_asciiz;
+ d->get_8_ints = mkdata_get_8_ints;
+ d->get_u8_ints = mkdata_get_u8_ints;
+ d->get_16_ints = mkdata_get_16_ints;
+ d->get_u16_ints = mkdata_get_u16_ints;
+ d->get_32_ints = mkdata_get_32_ints;
+ d->get_u32_uints = mkdata_get_u32_uints;
+ d->get_32_doubles = mkdata_get_32_doubles;
+ d->rget_32_doubles = mkdata_rget_32_doubles;
+
+ d->del = mkdata_del;
+
+ return d;
+}
+
+/* ----------------------------------------------------------------- */
diff --git a/spectro/munki_imp.h b/spectro/munki_imp.h
new file mode 100644
index 0000000..a9af3a9
--- /dev/null
+++ b/spectro/munki_imp.h
@@ -0,0 +1,1029 @@
+
+#ifndef MUNKI_IMP_H
+
+ /* X-Rite ColorMunki related defines */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 12/1/2009
+ *
+ * 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.
+ *
+ * (Base on i1pro_imp.h)
+ */
+
+/*
+ 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.
+ */
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/* Implementation resources for munki driver */
+
+/* -------------------------------------------------- */
+/* Implementation class */
+
+typedef int munki_code; /* Type to use for error codes */
+
+/* MUNKI mode state. This is implementation data that */
+/* depends on the mode the instrument is in. */
+/* Each mode has a separate calibration, and configured instrument state. */
+
+typedef enum {
+ mk_refl_spot = 0,
+ mk_refl_scan = 1,
+ mk_emiss_spot_na = 2,
+ mk_tele_spot_na = 3,
+ mk_emiss_spot = 4,
+ mk_tele_spot = 5,
+ mk_emiss_scan = 6,
+ mk_amb_spot = 7,
+ mk_amb_flash = 8,
+ mk_trans_spot = 9,
+ mk_trans_scan = 10,
+ mk_no_modes = 11
+} mk_mode;
+
+struct _munki_state {
+ mk_mode mode; /* Mode number */
+
+ /* Just one of the following 3 must always be set */
+ int emiss; /* flag - Emissive mode */
+ int trans; /* flag - Transmissive mode */
+ int reflective; /* flag - Reflective mode */
+
+ /* The following modify emiss */
+ int ambient; /* flag - Ambient position mode */
+ int projector; /* flag - Projector position (tele) mode */
+
+ /* The following can be added to any of the 3: */
+ int scan; /* flag - Scanning mode */
+ int adaptive; /* flag - adaptive mode (emiss - adapt for each measurement) */
+
+ /* The following can be added to scan: */
+ int flash; /* flag - Flash detection from scan mode */
+
+ /* Configuration & state information */
+ double targoscale; /* Optimal reading scale factor <= 1.0 */
+ double targmaxitime;/* maximum integration time to aim for (ie. 2.0 sec) */
+ double targoscale2; /* Proportion of targoscale allowed to meed targmaxitime */
+ int gainmode; /* Gain mode, 0 = normal, 1 = high */
+ double inttime; /* Integration time */
+ double invsampt; /* Invalid sample time */
+
+ double dpretime; /* Target pre-read dark read time - sets no. of readings */
+ double wpretime; /* Target pre-read white/sample read time - sets no. of readings */
+
+ double dcaltime; /* Target dark calibration time - sets number of readings */
+ double wcaltime; /* Target white calibration time - sets number of readings (not used?) */
+
+ double dreadtime; /* Target dark on-the-fly cal time - sets number of readings */
+ double wreadtime; /* Target white/sample reading time - sets number of readings */
+
+ double maxscantime; /* Maximum scan time sets buffer size allocated */
+
+ double min_wl; /* Minimum wavelegth to report for this mode */
+
+ /* calibration information for this mode */
+ int dark_valid; /* dark calibration factor valid */
+ time_t ddate; /* Date/time of last dark calibration */
+ double dark_int_time; /* Integration time used for dark data */
+ double *dark_data; /* [-1 nraw] of dark level to subtract. Note that the dark value */
+ /* depends on integration time and gain mode. */
+ int dark_gain_mode; /* Gain mode used for dark data */
+
+ int cal_valid; /* calibration factor valid */
+ time_t cfdate; /* Date/time of last cal factor calibration */
+ double *cal_factor; /* [nwav] of calibration scale factor for this mode */
+ double *cal_factor1, *cal_factor2; /* (Underlying tables for two resolutions) */
+ double *white_data; /* [-1 nraw] linear absolute dark subtracted white data */
+ /* used to compute cal_factors (at reftemp) */
+ double **iwhite_data; /* [-1 nraw][2] LED temperature data to interpolate white_data from */
+ double reftemp; /* Reference temperature to correct to */
+
+ /* Adaptive emission/transparency black data */
+ int idark_valid; /* idark calibration factors valid */
+ time_t iddate; /* Date/time of last dark idark calibration */
+ double idark_int_time[4];
+ double **idark_data; /* [4][-1 nraw] of dark level for inttime/gains of : */
+ /* 0.01 norm, 1.0 norm, 0.01 high, 1.0 high */
+ /* then it's converted to base + increment with inttime */
+
+ int want_calib; /* Initial White calibration wanted */
+ int want_dcalib; /* Initial Dark Calibration wanted */
+
+ /* Display mode calibration state (emmis && !scan && !adaptive) */
+ int dispswap; /* 0 = default time, 1 = dark_int_time2, 2 = dark_int_time3 */
+ double done_dintsel; /* A display integration time selection has been done */
+ time_t diseldate; /* Date/time of last display integration time selection */
+ double dcaltime2; /* Target dark calibration time - sets number of readings */
+ double dark_int_time2; /* Integration time used for dark data 2 */
+ double *dark_data2; /* [-1 nraw] of dark level to subtract for dark_int_time2. */
+ double dcaltime3; /* Target dark calibration time - sets number of readings */
+ double dark_int_time3; /* Integration time used for dark data 3 */
+ double *dark_data3; /* [-1 nraw] of dark level to subtract for dark_int_time3. */
+
+}; typedef struct _munki_state munki_state;
+
+
+
+/* MUNKI implementation class */
+struct _munkiimp {
+ munki *p;
+
+ /* Misc. and top level */
+ struct _mkdata *data; /* EEProm data container */
+ athread *th; /* Switch monitoring thread (NULL if not used) */
+ volatile int switch_count; /* Incremented in thread */
+ volatile int hide_switch; /* Set to supress switch event during read */
+ usb_cancelt cancelt; /* Token to allow cancelling an outstanding I/O */
+ volatile int th_term; /* Thread terminate on error rather than retry */
+ volatile int th_termed; /* Thread has terminated */
+ inst_opt_type trig; /* Reading trigger mode */
+ int noinitcalib; /* Disable initial calibration if not essential */
+ int nosposcheck; /* Disable checking the sensor position */
+ int highres; /* High resolution mode */
+ int hr_inited; /* High resolution has been initialized */
+
+ /* Current settings */
+ mk_mode mmode; /* Current measurement mode selected */
+ munki_state ms[mk_no_modes]; /* Mode state */
+ int spec_en; /* Enable reporting of spectral data */
+
+ double intclkp; /* Integration clock period (computed from tickdur) */
+
+ /* Current state of hardware (~~99 are all these used ??) */
+ double c_inttime; /* Integration period (=inttime + deadtime) */
+ int c_measmodeflags; /* Measurement mode flags (set by trigger() */
+
+ /* Information from the HW */
+ int fwrev; /* int - Firmware revision number, from getfirm() */
+ /* Typically 0120 = V1.32 */
+ unsigned char chipid[8]; /* HW serial number */
+ char vstring[37]; /* Asciiz version string */
+ int tickdur; /* Tick duration (usec, converted to intclkp) */
+ int minintcount; /* Minimum integration tick count */
+ int noeeblocks; /* Number of EEPROM blocks */
+ int eeblocksize; /* Size of each block */
+
+ /* Information from the EEProm */
+ int calver; /* Effective calibration version number */
+ int prodno; /* Production number */
+ char serno[17]; /* serial number string */
+ int adctype; /* A/D converter type */
+
+ double minsval; /* Minimum sensor value target */
+ double optsval; /* Optimal sensor value target */
+ double maxsval; /* Maximum sensor value target */
+ double satlimit; /* Saturation limit */
+
+ int ledholdtempdc; /* LED Hold temparature duty cycle */
+ /* Parameter used in measure instruction. [0] */
+ double ledpreheattime; /* LED Pre-heat time, Seconds [1.0] */
+ /* Time to turn LED on before determining */
+ /* integration time that achieves optimal sensor value. */
+ double cal_int_time; /* Calibration integration time in seconds. [0.018208] */
+ /* Starting integration time use for achieving */
+ /* the optimal sensor value. */
+ double ledwaittime; /* LED Wait time, Seconds [1.0] */
+ /* Time to wait for LED to cool down */
+ /* before determining temperature/output characteristic. */
+ double calscantime; /* Calibration scan time used for determining temp/output */
+ /* characteristic of LED, Seconds. [3.0] */
+ double refinvalidsampt; /* Reflection invalid sample time in seconds. [0.1] */
+ /* This sets the number of extra samples to add to a */
+ /* reflective read, and then to discard from */
+ /* the start of the measurements. This allows for LED */
+ /* thermal warmup. */
+
+ double min_int_time; /* Minimum integration time (secs) (set from minintcount) */
+ /* (Typical value is 0.007168 = 139.5 times/sec) */
+ double max_int_time; /* Maximum integration time (secs) (fixed in sw) */
+
+ /* Underlying calibration information */
+ int nsen; /* There are 137 provided from the device, with */
+ /* 6 skipped at the start, and 3 at the end. */
+ /* The first 4 are photo shielded. */
+ /* The last reading is the LED voltage drop */
+ /* 2 at the start and 2 at the end are unused. */
+ int nraw; /* Raw sample bands stored = 128 (Must be signed!) */
+ int nwav; /* Current cooked spectrum bands stored, usually = 36 */
+ double wl_short; /* Cooked spectrum bands short wavelength, usually 380 */
+ double wl_long; /* Cooked spectrum bands short wavelength, usually 730 */
+
+ unsigned int nwav1, nwav2; /* Available bands for standard and high-res modes */
+ double wl_short1, wl_short2, wl_long1, wl_long2;
+
+ /* Reflection */
+ int *rmtx_index; /* [nwav] Matrix CCD sample starting index for each out wavelength */
+ int *rmtx_nocoef; /* [nwav] Number of matrix cooeficients for each out wavelength */
+ double *rmtx_coef; /* [nwav * rmtx_nocoef] Matrix coeef's to compute each wavelength */
+ int *rmtx_index1, *rmtx_index2; /* Underlying arrays for the two resolutions */
+ int *rmtx_nocoef1, *rmtx_nocoef2; /* first [nwav1], second [nwav2] */
+ double *rmtx_coef1, *rmtx_coef2;
+
+ /* Emission */
+ int *emtx_index; /* [nwav] Matrix CCD sample starting index for each out wavelength */
+ int *emtx_nocoef; /* [nwav] Number of matrix cooeficients for each out wavelength */
+ double *emtx_coef; /* [nwav * emtx_nocoef] Matrix coeef's to compute each wavelength */
+ int *emtx_index1, *emtx_index2; /* Underlying arrays for the two resolutions */
+ int *emtx_nocoef1, *emtx_nocoef2; /* first [nwav1], second [nwav2] */
+ double *emtx_coef1, *emtx_coef2;
+
+ unsigned int nlin0; /* Number in array */
+ double *lin0; /* Array of linearisation polinomial factors, normal gain. */
+
+ unsigned int nlin1; /* Number in array */
+ double *lin1; /* Array of linearisation polinomial factors, high gain. */
+
+ double *white_ref; /* [nwav] White calibration tile reflectance values */
+ double *emis_coef; /* [nwav] Emission calibration coefficients */
+ double *amb_coef; /* [nwav] Ambient light cal values (compound with Emission) */
+ double *proj_coef; /* [nwav] Projector light cal values (compound with Emission) */
+ double *white_ref1, *white_ref2; /* Underlying tables for normal/high res modes */
+ double *emis_coef1, *emis_coef2;
+ double *amb_coef1, *amb_coef2;
+ double *proj_coef1, *proj_coef2;
+
+ double **straylight; /* [nwav][nwav] Stray light convolution matrix */
+ double **straylight1, **straylight2; /* Underlying tables for normal/high res modes */
+
+ double highgain; /* High gain mode gain */
+ double scan_toll_ratio; /* Modifier of scan tollerance */
+
+ /* Trigger houskeeping & diagnostics */
+ int transwarn; /* Transmission calibration warning state */
+ int lo_secs; /* Seconds since last opened (from calibration file mod time) */
+ int tr_t1, tr_t2, tr_t3, tr_t4, tr_t5, tr_t6, tr_t7; /* Trigger/read timing diagnostics */
+ /* 1->2 = time to execute trigger */
+ /* 2->3 = time to between end trigger and start of first read */
+ /* 3->4 = time to exectute first read */
+ /* 6->5 = time between end of second last read and start of last read */
+ int trig_se; /* Delayed trigger icoms error */
+ munki_code trig_rv; /* Delayed trigger result */
+
+}; typedef struct _munkiimp munkiimp;
+
+/* Add an implementation structure */
+munki_code add_munkiimp(munki *p);
+
+/* Destroy implementation structure */
+void del_munkiimp(munki *p);
+
+/* ============================================================ */
+/* Error codes returned from munki_imp */
+
+/* Note: update munki_interp_error() and munki_interp_code() in munki.c */
+/* if anything of these #defines are added or subtracted */
+
+/* Fake Error codes */
+#define MUNKI_INTERNAL_ERROR 0x71 /* Internal software error */
+#define MUNKI_COMS_FAIL 0x72 /* Communication failure */
+#define MUNKI_UNKNOWN_MODEL 0x73 /* Not an munki */
+#define MUNKI_DATA_PARSE_ERROR 0x74 /* Read data parsing error */
+
+#define MUNKI_USER_ABORT 0x75 /* uicallback returned abort */
+#define MUNKI_USER_TRIG 0x76 /* uicallback retuned trigger */
+
+#define MUNKI_UNSUPPORTED 0x79 /* Unsupported function */
+#define MUNKI_CAL_SETUP 0x7A /* Cal. retry with correct setup is needed */
+
+/* Real error code */
+#define MUNKI_OK 0x00
+
+/* EEprop parsing errors */
+#define MUNKI_DATA_RANGE 0x02 /* out of range of buffer */
+#define MUNKI_DATA_MEMORY 0x03 /* memory alloc failure */
+
+/* HW errors */
+#define MUNKI_HW_EE_SHORTREAD 0x21 /* Read fewer EEProm bytes than expected */
+#define MUNKI_HW_ME_SHORTREAD 0x22 /* Read measurement bytes than expected */
+#define MUNKI_HW_ME_ODDREAD 0x23 /* Read measurement bytes was not mult 274 */
+#define MUNKI_HW_CALIBVERSION 0x24 /* calibration version is unknown */
+#define MUNKI_HW_CALIBMATCH 0x25 /* calibration doesn't match device */
+
+/* Sample read operation errors */
+#define MUNKI_RD_DARKREADINCONS 0x30 /* Dark calibration reading inconsistent */
+#define MUNKI_RD_SENSORSATURATED 0x31 /* Sensor is saturated */
+#define MUNKI_RD_DARKNOTVALID 0x32 /* Dark reading is not valid (too light) */
+#define MUNKI_RD_NEEDS_CAL 0x33 /* Mode needs calibration */
+#define MUNKI_RD_WHITEREADINCONS 0x34 /* White reference readings are inconsistent */
+#define MUNKI_RD_WHITEREFERROR 0x35 /* White reference reading error */
+#define MUNKI_RD_LIGHTTOOLOW 0x36 /* Light level is too low */
+#define MUNKI_RD_LIGHTTOOHIGH 0x37 /* Light level is too high */
+#define MUNKI_RD_SHORTMEAS 0x38 /* Measurment was too short */
+#define MUNKI_RD_READINCONS 0x39 /* Reading is inconsistent */
+#define MUNKI_RD_REFWHITENOCONV 0x3A /* White calibration didn't converge */
+#define MUNKI_RD_NOTENOUGHPATCHES 0x3B /* Not enough patches */
+#define MUNKI_RD_TOOMANYPATCHES 0x3C /* Too many patches */
+#define MUNKI_RD_NOTENOUGHSAMPLES 0x3D /* Not enough samples per patch */
+#define MUNKI_RD_NOFLASHES 0x3E /* No flashes recognized */
+#define MUNKI_RD_NOAMBB4FLASHES 0x3F /* No ambient before flashes found */
+#define MUNKI_RD_NOREFR_FOUND 0x40 /* Unable to measure refresh rate */
+
+#define MUNKI_SPOS_PROJ 0x48 /* Sensor needs to be in projector position */
+#define MUNKI_SPOS_SURF 0x49 /* Sensor needs to be in surface position */
+#define MUNKI_SPOS_CALIB 0x4A /* Sensor needs to be in calibration position */
+#define MUNKI_SPOS_AMB 0x4B /* Sensor needs to be in ambient position */
+
+/* Internal errors */
+#define MUNKI_INT_NO_COMS 0x50
+#define MUNKI_INT_EESIZE 0x51 /* EEProm read size is too big */
+#define MUNKI_INT_EEOUTOFRANGE 0x52 /* EEProm size is unexpected */
+#define MUNKI_INT_CALTOOSMALL 0x53 /* Calibration EEProm size is too small */
+#define MUNKI_INT_CALTOOBIG 0x54 /* Calibration EEProm size is too big */
+#define MUNKI_INT_CALBADCHSUM 0x55 /* Calibration has a bad checksum */
+#define MUNKI_INT_ODDREADBUF 0x56 /* Measurment read buffer is not mult 274 */
+#define MUNKI_INT_INTTOOBIG 0x57 /* Integration time is too big */
+#define MUNKI_INT_INTTOOSMALL 0x58 /* Integration time is too small */
+#define MUNKI_INT_ILLEGALMODE 0x59 /* Illegal measurement mode selected */
+#define MUNKI_INT_ZEROMEASURES 0x5A /* Number of measurements requested is zero */
+#define MUNKI_INT_WRONGPATCHES 0x5B /* Number of patches to match is wrong */
+#define MUNKI_INT_MEASBUFFTOOSMALL 0x5C /* Measurement read buffer is too small */
+#define MUNKI_INT_NOTIMPLEMENTED 0x5D /* Support not implemented */
+#define MUNKI_INT_NOTCALIBRATED 0x5E /* Unexpectedely invalid calibration */
+#define MUNKI_INT_THREADFAILED 0x5F /* Creation of thread failed */
+#define MUNKI_INT_BUTTONTIMEOUT 0x60 /* Switch status read timed out */
+#define MUNKI_INT_CIECONVFAIL 0x61 /* Creating spectral to CIE converted failed */
+#define MUNKI_INT_MALLOC 0x62 /* Error in mallocing memory */
+#define MUNKI_INT_CREATE_EEPROM_STORE 0x63 /* Error in creating EEProm store */
+#define MUNKI_INT_NEW_RSPL_FAILED 0x64 /* Creating RSPL object faild */
+#define MUNKI_INT_CAL_SAVE 0x65 /* Unable to save calibration to file */
+#define MUNKI_INT_CAL_RESTORE 0x66 /* Unable to restore calibration from file */
+#define MUNKI_INT_CAL_TOUCH 0x67 /* Unable to touch calibration file */
+
+
+#define MUNKI_INT_ASSERT 0x6F /* Internal assert */
+
+
+int icoms2munki_err(int se);
+
+/* ============================================================ */
+/* High level implementatation */
+
+/* Initialise our software state from the hardware */
+munki_code munki_imp_init(munki *p);
+
+/* Return a pointer to the serial number */
+char *munki_imp_get_serial_no(munki *p);
+
+/* Set the measurement mode. It may need calibrating */
+munki_code munki_imp_set_mode(
+ munki *p,
+ mk_mode mmode, /* munki mode to use */
+ int spec_en); /* nz to enable reporting spectral */
+
+/* Implement get_n_a_cals */
+munki_code munki_imp_get_n_a_cals(munki *p, inst_cal_type *pn_cals, inst_cal_type *pa_cals);
+
+/* Calibrate for the current mode. */
+/* Request an instrument calibration of the current mode. */
+munki_code munki_imp_calibrate(
+munki *p,
+inst_cal_type *calt, /* Calibration type to do/remaining */
+inst_cal_cond *calc, /* Current condition/desired condition */
+char id[100] /* Condition identifier (ie. white reference ID) */
+);
+
+/* Measure a patch or strip in the current mode. */
+munki_code munki_imp_measure(
+ munki *p,
+ ipatch *val, /* Pointer to array of instrument patch value */
+ int nvals, /* Number of values */
+ instClamping clamp /* Clamp XYZ/Lab to be +ve */
+);
+
+/* Measure the emissive refresh rate */
+munki_code munki_imp_meas_refrate(
+ munki *p,
+ double *ref_rate
+);
+
+/* return nz if high res is supported */
+int munki_imp_highres(munki *p);
+
+/* Set to high resolution mode */
+munki_code munki_set_highres(munki *p);
+
+/* Set to standard resolution mode */
+munki_code munki_set_stdres(munki *p);
+
+/* Modify the scan consistency tollerance */
+munki_code munki_set_scan_toll(munki *p, double toll_ratio);
+
+
+/* Save the calibration for all modes, stored on local system */
+munki_code munki_save_calibration(munki *p);
+
+/* Restore the all modes calibration from the local system */
+munki_code munki_restore_calibration(munki *p);
+
+
+/* ============================================================ */
+/* Intermediate routines - composite commands/processing */
+
+/* Take a dark reference measurement - part 1 */
+munki_code munki_dark_measure_1(
+ munki *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 */
+);
+
+/* Take a dark reference measurement - part 2 */
+munki_code munki_dark_measure_2(
+ munki *p,
+ double *sens, /* Return array [-1 nraw] of sens 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 */
+);
+
+/* Take a dark measurement */
+munki_code munki_dark_measure(
+ munki *p,
+ double *sens, /* Return array [-1 nraw] of sens 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 */
+);
+
+/* Take a white reference measurement */
+/* (Subtracts black and processes into wavelenths) */
+munki_code munki_whitemeasure(
+ munki *p,
+ double *absraw, /* Return array [-1 nraw] of absraw values (may be NULL) */
+ 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 */
+);
+
+/* Given an absraw white reference measurement, */
+/* compute the wavelength equivalents. */
+/* (absraw is usually ->white_data) */
+/* (abswav1 is usually ->cal_factor1) */
+/* (abswav2 is usually ->cal_factor2) */
+munki_code munki_compute_wav_whitemeas(
+ munki *p,
+ double *abswav1, /* Return array [nwav1] of abswav values (may be NULL) */
+ double *abswav2, /* Return array [nwav2] of abswav values (if hr_init, may be NULL) */
+ double *absraw /* Given array [-1 nraw] of absraw values */
+);
+
+/* Take a reflective white reference measurement, */
+/* subtracts black and decompose into base + LED temperature components */
+munki_code munki_ledtemp_whitemeasure(
+ munki *p,
+ double *white, /* Return [-1 nraw] of temperature compensated white reference */
+ double **iwhite, /* Return array [-1 nraw][2] of absraw base and scale values */
+ double *reftemp, /* Return a reference temperature to normalize to */
+ int nummeas, /* Number of readings to take */
+ double inttime, /* Integration time to use */
+ int gainmode /* Gain mode to use, 0 = normal, 1 = high */
+);
+
+/* Given the ledtemp base and scale values, */
+/* return a raw reflective white reference for the */
+/* given temperature */
+munki_code munki_ledtemp_white(
+ munki *p,
+ double *absraw, /* Return array [-1 nraw] of absraw base and scale values */
+ double **iwhite, /* ledtemp base and scale */
+ double ledtemp /* LED temperature value */
+);
+
+/* Given a set of absraw sensor readings and the corresponding temperature, */
+/* compensate the readings to be at the nominated temperature. */
+munki_code munki_ledtemp_comp(
+ munki *p,
+ double **absraw, /* [nummeas][raw] measurements to compensate */
+ double *ledtemp, /* LED temperature for each measurement */
+ int nummeas, /* Number of measurements */
+ double reftemp, /* LED reference temperature to compensate to */
+ double **iwhite /* ledtemp base and scale information */
+);
+
+/* Heat the LED up for given number of seconds by taking a reading */
+munki_code munki_heatLED(
+ munki *p,
+ double htime /* Heat up time */
+);
+
+/* Process a single raw white reference measurement */
+/* (Subtracts black and processes into wavelenths) */
+munki_code munki_whitemeasure_buf(
+ munki *p,
+ double *abswav1, /* Return array [nwav1] of abswav values (may be NULL) */
+ double *abswav2, /* Return array [nwav2] 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 */
+);
+
+/* Take a measurement reading using the current mode, part 1 */
+/* Converts to completely processed output readings. */
+munki_code munki_read_patches_1(
+ munki *p,
+ int ninvmeas, /* Number of extra invalid measurements at start */
+ 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 (excluding ninvmeas) */
+ unsigned char *buf, /* Raw USB reading buffer */
+ unsigned int bsize
+);
+
+/* Take a measurement reading using the current mode, part 2 */
+/* Converts to completely processed output readings. */
+munki_code munki_read_patches_2(
+ munki *p,
+ double *duration, /* return flash duration (secs) */
+ 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 ninvmeas, /* Number of extra invalid measurements at start */
+ int nmeasuered, /* Number actually measured */
+ unsigned char *buf, /* Raw USB reading buffer */
+ unsigned int bsize
+);
+
+/* Take a trial measurement reading using the current mode. */
+/* Used to determine if sensor is saturated, or not optimal */
+munki_code munki_trialmeasure(
+ munki *p,
+ int *saturated, /* Return nz if sensor is saturated */
+ double *optscale, /* Factor to scale gain/int time by to make optimal */
+ 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 */
+);
+
+/* Trigger a single measurement cycle. This could be a dark calibration, */
+/* a calibration, or a real measurement. Used to create the higher */
+/* level "calibrate" and "take reading" functions. */
+/* The setup for the operation is in the current mode state. */
+/* The called then needs to call munki_readmeasurement() */
+munki_code
+munki_trigger_one_measure(
+ munki *p,
+ int nummeas, /* Number of measurements to make */
+ double *inttime, /* Integration time to use/used */
+ int gainmode, /* Gain mode to use, 0 = normal, 1 = high */
+ int calib_measure, /* flag - nz if this is a calibration measurement */
+ int dark_measure /* flag - nz if this is a dark measurement */
+);
+
+/* ============================================================ */
+/* lower level reading processing */
+
+/* Take a buffer full of sensor readings, and convert them to */
+/* directly to floating point raw values. */
+/* Return MUNKI_RD_SENSORSATURATED if any is saturated */
+munki_code munki_sens_to_raw(
+ munki *p,
+ double **raw, /* Array of [nummeas-ninvalid][-1 nraw] value to return */
+ double *ledtemp, /* Optional array [nummeas-ninvalid] LED temperature values to return */
+ unsigned char *buf, /* Sensor measurement data must be 274 * nummeas */
+ int nummeas, /* Number of readings measured */
+ int ninvalid, /* Number of initial invalid readings to skip */
+ double satthresh, /* Sauration threshold in raw units */
+ double *darkthresh /* Return a dark threshold value */
+);
+
+/* Subtract the black from raw values and convert to */
+/* absolute (integration & gain scaled), zero offset based, */
+/* linearized sensor values. */
+void munki_sub_raw_to_absraw(
+ munki *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, /* Value to subtract [-1 nraw] */
+ double *trackmax, /* absraw values that should be offset the same as max */
+ int ntrackmax, /* Number of trackmax values */
+ double *maxv /* If not NULL, return the maximum value */
+);
+
+/* Average a set of sens or absens measurements into one. */
+/* (Make sure darkthresh is tracked if absens is being averaged!) */
+/* Return zero if readings are consistent and not saturated. */
+/* Return nz if the readings are not consistent */
+/* Return the overall average. */
+int munki_average_multimeas(
+ munki *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 *poallavg, /* If not NULL, return overall average of bands and measurements */
+ double darkthresh /* Dark threshold (used for consistency check scaling) */
+);
+
+/* Recognise the required number of ref/trans patch locations, */
+/* and average the measurements within each patch. */
+/* Return flags zero if readings are consistent. */
+/* Return flags nz if the readings are not consistent */
+munki_code munki_extract_patches_multimeas(
+ munki *p,
+ int *flags, /* return flags */
+ double **pavg, /* return patch average [naptch][-1 nraw] */
+ int npatch, /* number of patches to recognise */
+ double **multimeas, /* Array of [nummeas][-1 nraw] value to extract from */
+ int nummeas, /* number of readings to recognise them from */
+ double inttime /* Integration time (used to adjust consistency threshold) */
+);
+
+/* Recognise any flashes in the readings, and */
+/* and average their values together as well as summing their duration. */
+/* Return nz on an error */
+munki_code munki_extract_patches_flash(
+ munki *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) */
+);
+
+/* Convert an absraw array from raw wavelengths to output wavelenths */
+/* for the current resolution */
+void munki_absraw_to_abswav(
+ munki *p,
+ int nummeas, /* Return number of readings measured */
+ double **abswav, /* Desination array [nwav] */
+ double **absraw /* Source array [-1 nraw] */
+);
+
+/* Convert an absraw array from raw wavelengths to output wavelenths */
+/* for the standard resolution */
+void munki_absraw_to_abswav1(
+ munki *p,
+ int nummeas, /* Return number of readings measured */
+ double **abswav, /* Desination array [nwav1] */
+ double **absraw /* Source array [-1 nraw] */
+);
+
+/* Convert an absraw array from raw wavelengths to output wavelenths */
+/* for the high resolution */
+void munki_absraw_to_abswav2(
+ munki *p,
+ int nummeas, /* Return number of readings measured */
+ double **abswav, /* Desination array [nwav2] */
+ double **absraw /* Source array [-1 nraw] */
+);
+
+/* Convert an abswav array of output wavelengths to scaled output readings. */
+void munki_scale_specrd(
+ munki *p,
+ double **outspecrd, /* Destination */
+ int numpatches, /* Number of readings/patches */
+ double **inspecrd /* Source */
+);
+
+/* Convert from spectral to XYZ, and transfer to the ipatch array */
+munki_code munki_conv2XYZ(
+ munki *p,
+ ipatch *vals, /* Values to return */
+ int nvals, /* Number of values */
+ double **specrd, /* Spectral readings */
+ instClamping clamp /* Clamp XYZ/Lab to be +ve */
+);
+
+/* Compute a calibration factor given the reading of the white reference. */
+/* Return nz if any of the transmission wavelengths are low */
+int munki_compute_white_cal(
+ munki *p,
+ double *cal_factor1, /* [nwav1] Calibration factor to compute */
+ double *white_ref1, /* [nwav1] White reference to aim for, NULL for 1.0 */
+ double *white_read1, /* [nwav1] The white that was read */
+ double *cal_factor2, /* [nwav2] Calibration factor to compute */
+ double *white_ref2, /* [nwav2] White reference to aim for, NULL for 1.0 */
+ double *white_read2 /* [nwav2] The white that was read */
+);
+
+/* For adaptive mode, compute a new integration time and gain mode */
+/* in order to optimise the sensor values. */
+munki_code munki_optimise_sensor(
+ munki *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) */
+ /* (May be altered if integration time isn't possible) */
+ double scale, /* scale needed of current int time to reach optimum */
+ double deadtime /* Dead integration time (if any) */
+);
+
+/* Compute the number of measurements needed, given the target */
+/* time and integration time. Will return 0 if target time is 0 */
+int munki_comp_nummeas(
+ munki *p,
+ double meas_time,
+ double int_time
+);
+
+/* Compute the rounded up number of measurements needed, */
+/* given the target time and integration time. */
+/* Will return 0 if target time is 0 */
+int munki_comp_ru_nummeas(
+ munki *p,
+ double meas_time,
+ double int_time
+);
+
+/* Convert the dark interpolation data to a useful state */
+void munki_prepare_idark(munki *p);
+
+/* Create the dark reference for the given integration time and gain */
+/* by interpolating from the 4 readings taken earlier. */
+munki_code munki_interp_dark(
+ munki *p,
+ double *result, /* Put result of interpolation here */
+ double inttime,
+ int gainmode
+);
+
+/* Create high resolution mode references. */
+/* Create Reflective if ref nz, else create Emissive */
+munki_code munki_create_hr(munki *p, int ref);
+
+/* Set the noinitcalib mode */
+void munki_set_noinitcalib(munki *p, int v, int losecs);
+
+/* Set the trigger config */
+void munki_set_trig(munki *p, inst_opt_type trig);
+
+/* Return the trigger config */
+inst_opt_type munki_get_trig(munki *p);
+
+/* Set the trigger return */
+void munki_set_trigret(munki *p, int val);
+
+/* Switch thread handler */
+int munki_switch_thread(void *pp);
+
+/* ============================================================ */
+/* Low level commands */
+
+/* USB Commands */
+
+/* Read from the EEProm */
+munki_code
+munki_readEEProm(
+ struct _munki *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) */
+);
+
+
+/* Get the firmware parameters */
+/* return pointers may be NULL if not needed. */
+munki_code
+munki_getfirm(
+ munki *p,
+ int *fwrev, /* Return the formware version number as 8.8 */
+ int *tickdur, /* Tick duration */
+ int *minintcount, /* Minimum integration tick count */
+ int *noeeblocks, /* Number of EEPROM blocks */
+ int *eeblocksize /* Size of each block */
+);
+
+/* Get the Chip ID */
+munki_code
+munki_getchipid(
+ munki *p,
+ unsigned char chipid[8]
+);
+
+/* Get the Version String */
+munki_code
+munki_getversionstring(
+ munki *p,
+ char vstring[37]
+);
+
+/* Get the measurement state */
+/* return pointers may be NULL if not needed. */
+munki_code
+munki_getmeasstate(
+ munki *p,
+ int *ledtrange, /* LED temperature range */
+ int *ledtemp, /* LED temperature */
+ int *dutycycle, /* Duty Cycle */
+ int *ADfeedback /* A/D converter feedback */
+);
+
+/* Munki sensor positions */
+typedef enum {
+ mk_spos_proj = 0x00, /* Projector/Between detents */
+ mk_spos_surf = 0x01, /* Surface */
+ mk_spos_calib = 0x02, /* Calibration tile */
+ mk_spos_amb = 0x03 /* Ambient */
+} mk_spos;
+
+/* Munki switch state */
+typedef enum {
+ mk_but_switch_release = 0x00, /* Button is released */
+ mk_but_switch_press = 0x01 /* Button is pressed */
+} mk_but;
+
+/* Get the device status */
+/* return pointers may be NULL if not needed. */
+munki_code
+munki_getstatus(
+ munki *p,
+ mk_spos *spos, /* Return the sensor position */
+ mk_but *but /* Return Button state */
+);
+
+/* Set the indicator LED state */
+munki_code
+munki_setindled(
+ munki *p,
+ int ontime, /* On time (msec) */
+ int offtrime, /* Off time (msec) */
+ int transtime, /* Transition time (msec) */
+ int nopulses, /* Number of pulses, -1 = max */
+ int p5 /* Ignored ? */
+);
+
+/* Measuremend mode flags */
+#define MUNKI_MMF_LAMP 0x01 /* Lamp mode, else no illumination of sample */
+#define MUNKI_MMF_SCAN 0x02 /* Scan mode bit, else spot mode */
+#define MUNKI_MMF_HIGHGAIN 0x04 /* High gain, else normal gain. */
+
+/* Trigger a measurement with the given measurement parameters */
+munki_code
+munki_triggermeasure(
+ munki *p,
+ int intclocks, /* Number of integration clocks */
+ int nummeas, /* Number of measurements to make */
+ int measmodeflags, /* Measurement mode flags */
+ int holdtempduty /* Hold temperature duty cycle */
+);
+
+/* Read a measurements results */
+munki_code
+munki_readmeasurement(
+ munki *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 */
+ int calib_measure, /* flag - nz if this is a calibration measurement */
+ int dark_measure /* flag - nz if this is a dark measurement */
+);
+
+/* Set the measurement clock mode */
+/* Version >= 301 only */
+munki_code
+munki_setmcmode(
+ munki *p,
+ int mcmode /* Measurement clock mode, 1..mxmcmode */
+);
+
+/* Munki event values, returned by event pipe, or */
+/* parameter to simulate event */
+typedef enum {
+ mk_eve_none = 0x0000, /* No event */
+ mk_eve_switch_press = 0x0001, /* Button has been pressed */
+ mk_eve_switch_release = 0x0002, /* Button has been released */
+ mk_eve_spos_change = 0x0100 /* Sensor position is being changed */
+} mk_eve;
+
+/* Simulating an event (use to terminate event thread) */
+/* timestamp is msec since munki power up */
+munki_code munki_simulate_event(munki *p, mk_eve ecode, int timestamp);
+
+/* Wait for a reply triggered by a key press */
+munki_code munki_waitfor_switch(munki *p, mk_eve *ecode, int *timest, double top);
+
+/* Wait for a reply triggered by a key press (thread version) */
+munki_code munki_waitfor_switch_th(munki *p, mk_eve *ecode, int *timest, double top);
+
+/* -------------------------------------------------- */
+/* EEProm parsing support. */
+
+/* Initialise the calibration from the EEProm contents. */
+/* (We're handed a buffer that's been rounded up to an even 32 bits by */
+/* padding with zero's) */
+munki_code munki_parse_eeprom(munki *p, unsigned char *buf, unsigned int len);
+
+struct _mkdata {
+ /* private: */
+ munki *p;
+
+ a1log *log;
+ unsigned char *buf; /* Buffer to parse */
+ int len; /* Length of buffer */
+
+ /* public: */
+
+ /* Return a pointer to an array of chars containing data from 8 bits. */
+ /* If rv is NULL, the returned value will have been allocated, othewise */
+ /* the rv will be returned. Return NULL if out of range. */
+ unsigned char *(*get_8_char)(struct _mkdata *d, unsigned char *rv, int offset, int count);
+
+ /* Return a pointer to an nul terminated string containing data from 8 bits. */
+ /* If rv is NULL, the returned value will have been allocated, othewise */
+ /* the rv will be returned. Return NULL if out of range. */
+ /* An extra space and a nul terminator will be added to the eeprom data */
+ char *(*get_8_asciiz)(struct _mkdata *d, char *rv, int offset, int count);
+
+
+ /* Return a pointer to an array of ints containing data from 8 bits. */
+ /* If rv is NULL, the returned value will have been allocated, othewise */
+ /* the rv will be returned. Return NULL if out of range. */
+ int *(*get_8_ints)(struct _mkdata *d, int *rv, int offset, int count);
+
+ /* Return a pointer to an array of ints containing data from unsigned 8 bits. */
+ /* If rv is NULL, the returned value will have been allocated, othewise */
+ /* the rv will be returned. Return NULL if out of range. */
+ int *(*get_u8_ints)(struct _mkdata *d, int *rv, int offset, int count);
+
+
+ /* Return a pointer to an array of ints containing data from 16 bits. */
+ /* If rv is NULL, the returned value will have been allocated, othewise */
+ /* the rv will be returned. Return NULL if out of range. */
+ int *(*get_16_ints)(struct _mkdata *d, int *rv, int offset, int count);
+
+ /* Return a pointer to an array of ints containing data from unsigned 16 bits. */
+ /* If rv is NULL, the returned value will have been allocated, othewise */
+ /* the rv will be returned. Return NULL if out of range. */
+ int *(*get_u16_ints)(struct _mkdata *d, int *rv, int offset, int count);
+
+
+ /* Return a pointer to an array of ints containing data from 32 bits. */
+ /* If rv is NULL, the returned value will have been allocated, othewise */
+ /* the rv will be returned. Return NULL if out of range. */
+ int *(*get_32_ints)(struct _mkdata *d, int *rv, int offset, int count);
+
+ /* Return a pointer to an array of unsigned ints containing data from 32 bits. */
+ /* If rv is NULL, the returned value will have been allocated, othewise */
+ /* the rv will be returned. Return NULL if out of range. */
+ unsigned int *(*get_u32_uints)(struct _mkdata *d, unsigned int *rv, int offset, int count);
+
+
+ /* Return a pointer to an array of doubles containing data from 32 bits. */
+ /* If rv is NULL, the returned value will have been allocated, othewise */
+ /* the rv will be returned. Return NULL if out of range. */
+ double *(*get_32_doubles)(struct _mkdata *d, double *rv, int offset, int count);
+
+ /* Return a pointer to an array of doubles containing data from 32 bits, */
+ /* with the array filled in reverse order. */
+ /* If rv is NULL, the returned value will have been allocated, othewise */
+ /* the rv will be returned. Return NULL if out of range. */
+ double *(*rget_32_doubles)(struct _mkdata *d, double *rv, int offset, int count);
+
+ /* Destroy ourselves */
+ void (*del)(struct _mkdata *d);
+
+}; typedef struct _mkdata mkdata;
+
+/* Constructor. Construct from the EEprom calibration contents */
+extern mkdata *new_mkdata(munki *p, unsigned char *buf, int len);
+
+#ifdef __cplusplus
+ }
+#endif
+
+#define MUNKI_IMP
+#endif /* MUNKI_IMP */
diff --git a/spectro/oemarch.c b/spectro/oemarch.c
new file mode 100644
index 0000000..733156e
--- /dev/null
+++ b/spectro/oemarch.c
@@ -0,0 +1,2610 @@
+
+ /* OEM archive access library. */
+ /* This supports installing OEM data files */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 13/11/2012
+ *
+ * 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.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <string.h>
+#if defined (NT)
+#define WIN32_LEAN_AND_MEAN
+#include <io.h>
+#include <windows.h>
+#endif
+#ifdef UNIX
+#include <unistd.h>
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <ctype.h>
+#endif /* UNIX */
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* SALONEINSTLIB */
+#include <fcntl.h>
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* SALONEINSTLIB */
+#include "xdg_bds.h"
+#include "xspect.h"
+#include "conv.h"
+#include "aglob.h"
+#include "ccss.h"
+#include "LzmaDec.h"
+#include "oemarch.h"
+
+#undef DEBUG
+#undef PLOT_SAMPLES /* Plot spectral samples */
+
+/* Configured target information */
+oem_target oemtargs = {
+#ifdef NT
+ { /* Installed files */
+ { "/ColorVision/Spyder2express/CVSpyder.dll", targ_spyd_pld, file_dllcab },
+ { "/ColorVision/Spyder2pro/CVSpyder.dll", targ_spyd_pld, file_dllcab },
+ { "/PANTONE COLORVISION/ColorPlus/CVSpyder.dll", targ_spyd_pld, file_dllcab },
+ { "/Datacolor/Spyder4Express/dccmtr.dll", targ_spyd_cal, file_dllcab },
+ { "/Datacolor/Spyder4Pro/dccmtr.dll", targ_spyd_cal, file_dllcab },
+ { "/Datacolor/Spyder4Elite/dccmtr.dll", targ_spyd_cal, file_dllcab },
+ { "/Datacolor/Spyder4TV HD/dccmtr.dll", targ_spyd_cal, file_dllcab },
+ { "/X-Rite/Devices/i1d3/Calibrations/*.edr", targ_i1d3_edr, file_data },
+ { NULL }
+ },
+ { /* Volume names */
+ { "ColorVision", targ_spyd_pld | targ_spyd_cal },
+ { "Datacolor", targ_spyd_pld | targ_spyd_cal },
+ { "i1Profiler", targ_i1d3_edr },
+ { "ColorMunki Displ", targ_i1d3_edr },
+ { NULL }
+ },
+ { /* Archive names */
+ { "/setup/setup.exe", targ_spyd_pld },
+ { "/Data/setup.exe", targ_spyd_cal },
+ { "/Installer/Setup.exe", targ_i1d3_edr },
+ { "/Installer/ColorMunkiDisplaySetup.exe", targ_i1d3_edr },
+ { NULL }
+ }
+#endif /* NT */
+#ifdef __APPLE__
+ { /* Installed files */
+ { "/Applications/Spyder2express 2.2/Spyder2express.app/Contents/MacOSClassic/Spyder.lib", targ_spyd_pld },
+ { "/Applications/Spyder2pro 2.2/Spyder2pro.app/Contents/MacOSClassic/Spyder.lib", targ_spyd_pld },
+
+ { "/Library/Application Support/X-Rite/Devices/i1d3xrdevice/Contents/Resources/Calibrations/*.edr", targ_i1d3_edr },
+ { NULL }
+ },
+ { /* Volume names */
+ { "/Volumes/ColorVision", targ_spyd_pld | targ_spyd_cal },
+ { "/Volumes/Datacolor", targ_spyd_pld | targ_spyd_cal },
+ { "/Volumes/i1Profiler", targ_i1d3_edr },
+ { "/Volumes/ColorMunki Display", targ_i1d3_edr },
+ { NULL }
+ },
+ { /* Archive names */
+ { "/setup/setup.exe", targ_spyd_pld },
+ { "/Data/setup.exe", targ_spyd_cal },
+ { "/Installer/Setup.exe", targ_i1d3_edr },
+ { "/Installer/ColorMunkiDisplaySetup.exe", targ_i1d3_edr },
+ { NULL }
+ }
+#endif /* __APPLE__ */
+#ifdef UNIX_X11
+ { /* Installed files */
+ { NULL }
+ },
+ { /* Volume names the CDROM may have */
+ { "/media/ColorVision", targ_spyd_pld | targ_spyd_cal },
+ { "/media/Datacolor", targ_spyd_pld | targ_spyd_cal },
+ { "/media/i1Profiler", targ_i1d3_edr },
+ { "/media/ColorMunki Displ", targ_i1d3_edr },
+ { "/mnt/cdrom", targ_spyd_pld | targ_spyd_cal | targ_i1d3_edr },
+ { "/mnt/cdrecorder", targ_spyd_pld | targ_spyd_cal | targ_i1d3_edr },
+ { "/media/cdrom", targ_spyd_pld | targ_spyd_cal | targ_i1d3_edr },
+ { "/media/cdrecorder", targ_spyd_pld | targ_spyd_cal | targ_i1d3_edr },
+ { "/cdrom", targ_spyd_pld | targ_spyd_cal | targ_i1d3_edr },
+ { "/cdrecorder", targ_spyd_pld | targ_spyd_cal | targ_i1d3_edr },
+ { NULL }
+ },
+ { /* Archive names */
+ { "/setup/setup.exe", targ_spyd_pld },
+ { "/Data/setup.exe", targ_spyd_cal },
+ { "/Installer/Setup.exe", targ_i1d3_edr },
+ { "/Installer/ColorMunkiDisplaySetup.exe", targ_i1d3_edr },
+ { NULL }
+ }
+#endif /* UNIX_X11 */
+};
+
+#if defined(__APPLE__)
+/* Global: */
+char *oemamount_path = NULL;
+#endif
+
+/* Cleanup function for transfer on Apple OS X */
+void oem_umiso() {
+#if defined(__APPLE__)
+ if (oemamount_path != NULL) {
+ char sbuf[MAXNAMEL+1 + 100];
+ sprintf(sbuf, "umount \"%s\"",oemamount_path);
+ system(sbuf);
+ sprintf(sbuf, "rmdir \"%s\"",oemamount_path);
+ system(sbuf);
+ }
+#endif /* __APPLE__ */
+}
+
+static xfile *locate_volume(int verb);
+static xfile *locate_read_archive(xfile *vol, int verb);
+static xfile *locate_read_oeminstall(xfile **pxf, int verb);
+
+void classify_file(xfile *xf, int verb);
+
+/* Spyder related archive functions */
+int is_vise(xfile *xf);
+int is_dll(xfile *xf);
+static xfile *vise_extract(xfile **pxf, xfile *xi, char *tfilename, int verb);
+static xfile *spyd2pld_extract(xfile **pxf, xfile *dll, int verb);
+static xfile *spyd4cal_extract(xfile **pxf, xfile *dll, int verb);
+int is_s2pld(xfile *xf);
+int is_s4cal(xfile *xf);
+
+/* i1d3 archive related functions */
+int is_inno(xfile *xf);
+int is_cab(xfile *xf);
+static xfile *inno_extract(xfile *xi, char *tfilename, int verb);
+static xfile *msi_extract(xfile **pxf, xfile *xi, char *tname, int verb);
+static xfile *cab_extract(xfile **pxf, xfile *xi, char *text, int verb);
+
+/* edr to ccss functions */
+int is_edr(xfile *xf);
+static xfile *edr_convert(xfile **pxf, xfile *xi, int verb);
+int is_ccss(xfile *xf);
+
+/* ccmx functions */
+int is_ccmx(xfile *xf);
+
+#ifdef DEBUG
+static void list_files(char *s, xfile *xf) {
+ int i;
+
+ if (xf == NULL)
+ printf("%s: no files\n",s);
+ else {
+ printf("%s:\n",s);
+ for (i = 0; xf[i].name != NULL; i++) {
+ printf("Got '%s' size %d ftype 0x%x ttype 0x%x\n",xf[i].name,xf[i].len,xf[i].ftype,xf[i].ttype);
+// save_xfile(&xf[i], NULL, "oem/", 1);
+ }
+ }
+}
+#endif
+
+/* Given a list of source archives or files, convert them to a list of install files, */
+/* or if no files are given look for installed files or files from a CD. */
+/* Return NULL if none found. files is deleted. */
+xfile *oemarch_get_ifiles(xfile *files, int verb) {
+ int i;
+ xfile *ofiles, *nfiles = NULL; /* Ping pong */
+
+#ifdef DEBUG
+ list_files("On entry", files);
+#endif
+
+ /* Classify the files we've been given */
+ if (files != NULL) {
+ for (i = 0; files[i].name != NULL; i++) {
+ classify_file(&files[i], verb);
+ }
+#ifdef DEBUG
+ list_files("After classification",files);
+#endif
+ }
+
+
+ /* See if there are any OEM files installed */
+ if (files == NULL) {
+
+ locate_read_oeminstall(&files, verb);
+
+#ifdef DEBUG
+ list_files("OEM Installed files", files);
+#endif
+ }
+
+ if (files == NULL) {
+ /* Look for files on a CD */
+ xfile *vol; /* CD volume located */
+
+ if ((vol = locate_volume(verb)) == NULL) {
+ return NULL;
+ }
+
+ if ((files = locate_read_archive(vol, verb)) == NULL) {
+ oem_umiso();
+ return NULL;
+ }
+ oem_umiso();
+ del_xf(vol); vol = NULL;
+
+#ifdef DEBUG
+ list_files("CD files", files);
+#endif
+ }
+
+ if (files == NULL)
+ return NULL;
+
+ /* Now process any archives - extract dll & cab's */
+ for (i = 0; files[i].name != NULL; i++) {
+ xfile *arch = files + i;
+
+ /* Preserve & skip non-archives */
+ if (files[i].ftype != file_arch) {
+ new_add_xf(&nfiles, files[i].name, files[i].buf, files[i].len,
+ files[i].ftype, files[i].ttype);
+ files[i].buf = NULL; /* We've taken these */
+ files[i].len = 0;
+ continue;
+ }
+
+ /* If this could be spyder 2 PLD pattern: */
+ if (arch->ttype & targ_spyd_pld) {
+ xfile *dll = NULL; /* dccmtr.dll */
+
+ if (vise_extract(&nfiles, arch, "CVSpyder.dll", verb) != NULL)
+ continue;
+ }
+
+ /* If this could be spyder 4 calibration file: */
+ if (arch->ttype & targ_spyd_cal) {
+ xfile *dll = NULL; /* dccmtr.dll */
+
+ if (vise_extract(&nfiles, arch, "dccmtr.dll", verb) != NULL)
+ continue;
+ }
+
+ /* If this could be i1d3 .edr files: */
+ if (arch->ttype & targ_i1d3_edr) {
+ xfile *msi = NULL; /* .msi */
+
+#ifdef NEVER /* Don't have to do it this way */
+ /* Extract .msi from it */
+ if ((msi = inno_extract(arch, "{tmp}\\XRD i1d3.msi", verb)) != NULL) {
+
+ /* Extract the .cab from it */
+ if (msi_extract(&nfiles, msi, "XRD_i1d3.cab", verb) != NULL) {
+ del_xf(msi);
+ continue;
+ }
+ del_xf(msi);
+ }
+#else
+ /* Extract the .cab directly from Setup.exe */
+ if (msi_extract(&nfiles, arch, "XRD_i1d3.cab", verb) != NULL)
+ continue;
+ if (msi_extract(&nfiles, arch, "XRD_Manager.cab", verb) != NULL)
+ continue;
+#endif
+ }
+ if (verb) printf("Warning: unhandled '%s' discarded\n",arch->name);
+ }
+ ofiles = files; /* Swap to new list */
+ files = nfiles;
+ del_xf(ofiles);
+ nfiles = NULL;
+
+#ifdef DEBUG
+ list_files("After de-archive", files);
+#endif
+
+ if (files == NULL)
+ return NULL;
+
+ /* Process any dll & cab files - extract individual files */
+ for (i = 0; files[i].name != NULL; i++) {
+ xfile *dllcab = files + i;
+
+ /* Preserve non-dll/cab */
+ if (files[i].ftype != file_dllcab) {
+ new_add_xf(&nfiles, files[i].name, files[i].buf, files[i].len,
+ files[i].ftype, files[i].ttype);
+ files[i].buf = NULL; /* We've taken these */
+ files[i].len = 0;
+ continue;
+ }
+
+ /* If this could be spyder 2 PLD pattern: */
+ if (dllcab->ttype & targ_spyd_pld) {
+
+ if (spyd2pld_extract(&nfiles, dllcab, verb) != NULL)
+ continue;
+ }
+
+ /* If this could be spyder 4 calibration file: */
+ if (dllcab->ttype & targ_spyd_cal) {
+
+ if (spyd4cal_extract(&nfiles, dllcab, verb) != NULL)
+ continue;
+ }
+
+ /* If this could be i1d3 .edr files: */
+ if (dllcab->ttype & targ_i1d3_edr) {
+
+ /* Extract the .edr's from it */
+ if (cab_extract(&nfiles, dllcab, ".edr", verb) != NULL)
+ continue;
+ }
+ if (verb) printf("Warning: unhandled '%s' discarded\n",dllcab->name);
+ }
+ ofiles = files; /* Swap to new list */
+ files = nfiles;
+ del_xf(ofiles);
+ nfiles = NULL;
+
+#ifdef DEBUG
+ list_files("After file extract", files);
+#endif
+
+ if (files == NULL)
+ return NULL;
+
+ /* Process any .edr files - convert to ccss */
+ for (i = 0; files[i].name != NULL; i++) {
+
+ /* Preserve non-edr */
+ if (files[i].ftype != file_data
+ || (files[i].ttype & targ_i1d3_edr) == 0
+ || !is_edr(&files[i])
+ ) {
+ new_add_xf(&nfiles, files[i].name, files[i].buf, files[i].len,
+ files[i].ftype, files[i].ttype);
+ files[i].buf = NULL; /* We've taken these */
+ files[i].len = 0;
+ continue;
+ }
+ if (edr_convert(&nfiles, files + i, verb) != NULL)
+ continue;
+
+ if (verb) printf("Warning: unhandled '%s' discarded\n",files[i].name);
+ }
+ ofiles = files; /* Swap to new list */
+ files = nfiles;
+ del_xf(ofiles);
+ nfiles = NULL;
+
+#ifdef DEBUG
+ list_files("Returning", files);
+ printf("\n");
+#endif
+
+ return files;
+}
+
+/* -------------------------------------------- */
+/* Determine a file type */
+
+void classify_file(xfile *xf, int verb) {
+ if (is_dll(xf)) {
+ xf->ftype = file_dllcab;
+ xf->ttype &= (targ_spyd_pld | targ_spyd_cal);
+ if (verb) printf("'%s' seems to be a .dll file\n",xf->name);
+ return;
+ }
+ if (is_vise(xf)) {
+ xf->ftype = file_arch;
+ xf->ttype &= (targ_spyd_pld | targ_spyd_cal);
+ if (verb) printf("'%s' seems to be a VISE archive\n",xf->name);
+ return;
+ }
+ if (is_inno(xf)) {
+ xf->ftype = file_arch;
+ xf->ttype &= targ_i1d3_edr;
+ if (verb) printf("'%s' seems to be an Inno archive\n",xf->name);
+ return;
+ }
+ if (is_cab(xf)) {
+ xf->ftype = file_dllcab;
+ xf->ttype &= targ_i1d3_edr;
+ if (verb) printf("'%s' seems to be a .cab file\n",xf->name);
+ return;
+ }
+ if (is_edr(xf) || is_ccss(xf)) {
+ xf->ftype = file_data;
+ xf->ttype &= targ_i1d3_edr;
+ if (verb) printf("'%s' seems to be a i1d3 calibration file or .ccss\n",xf->name);
+ return;
+ }
+ if (is_ccmx(xf)) {
+ xf->ftype = file_data;
+ xf->ttype &= targ_ccmx;
+ if (verb) printf("'%s' seems to be a .ccmx\n",xf->name);
+ return;
+ }
+ if (is_s2pld(xf)) {
+ xf->ftype = file_data;
+ xf->ttype &= targ_spyd_pld;
+ if (verb) printf("'%s' seems to be a Spyder 2 PLD file\n",xf->name);
+ return;
+ }
+ if (is_s4cal(xf)) {
+ xf->ftype = file_data;
+ xf->ttype &= targ_spyd_cal;
+ if (verb) printf("'%s' seems to be a Spyder 4 calibration file\n",xf->name);
+ return;
+ }
+ /* Hmm. */
+ if (verb) printf("'%s' is unknown\n",xf->name);
+ xf->ftype = file_arch | file_dllcab | file_data;
+}
+
+/* ============================================================================= */
+
+/* Locate a CD volume. Return NULL if no CD found */
+/* free when done */
+static xfile *locate_volume(int verb) {
+ xfile *xf = NULL; /* return value */
+
+ if (verb) { printf("Looking for CDROM to install from .. \n"); fflush(stdout); }
+
+#ifdef NT
+ {
+ char buf[1000];
+ char vol_name[MAXNAMEL+1] = { '\000' };
+ char drive[50];
+ int len, i, j;
+
+ len = GetLogicalDriveStrings(1000, buf);
+ if (len > 1000)
+ error("GetLogicalDriveStrings too large");
+ for (i = 0; ;) { /* For all drives */
+ if (buf[i] == '\000')
+ break;
+ if (GetDriveType(buf+i) == DRIVE_CDROM) {
+ DWORD maxvoll, fsflags;
+ if (GetVolumeInformation(buf + i, vol_name, MAXNAMEL,
+ NULL, &maxvoll, &fsflags, NULL, 0) != 0) {
+ for (j = 0;;j++) { /* For all volume names */
+ if (oemtargs.volnames[j].path == NULL)
+ break;
+ if (strcmp(vol_name, oemtargs.volnames[j].path) == 0) {
+ strcpy(drive, buf+i);
+ drive[2] = '\000'; /* Remove '\' */
+ /* Found the instalation CDROM volume name */
+ new_add_xf(&xf, drive, NULL, 0, file_vol, oemtargs.volnames[j].ttype);
+ if (verb)
+ printf("Found Volume '%s' on drive '%s'\n",vol_name,drive);
+ break;
+ }
+ }
+ if (oemtargs.volnames[j].path != NULL) /* Found a match */
+ break;
+ }
+ }
+ i += strlen(buf + i) + 1;
+ }
+ }
+#endif /* NT */
+
+#if defined(__APPLE__)
+ {
+ int j;
+ char tname[MAXNAMEL+1] = { '\000' };
+
+ for (j = 0; ; j++) {
+ if (oemtargs.volnames[j].path == NULL)
+ break;
+
+ /* In case it's already mounted (previously aborted ?) */
+ strcpy(tname, oemtargs.volnames[j].path);
+ strcat(tname, "_ISO");
+ if (oemamount_path == NULL) { /* Remember to unmount it */
+ if ((oemamount_path = strdup(tname)) == NULL)
+ error("Malloc of amount_path failed");
+ }
+ if (verb) { printf("Checking if '%s' is already mounted .. ",tname); fflush(stdout); }
+ if (access(tname, 0) == 0) {
+ if (verb) printf("yes\n");
+ new_add_xf(&xf, tname, NULL, 0, file_vol, oemtargs.volnames[j].ttype);
+ break;
+ } else if (verb)
+ printf("no\n");
+
+ /* Not already mounted. */
+ if (access(oemtargs.volnames[j].path, 0) == 0) {
+ struct statfs buf;
+ char sbuf[MAXNAMEL+1 + 100];
+ int sl, rv;
+ if (verb) { printf("Mounting ISO partition .. "); fflush(stdout); }
+
+ /* but we need the ISO partition */
+ /* Locate the mount point */
+ if (statfs(oemtargs.volnames[j].path, &buf) != 0)
+ error("\nUnable to locate mount point for '%s' of install CDROM",oemtargs.volnames[j].path);
+ if ((sl = strlen(buf.f_mntfromname)) > 3
+ && buf.f_mntfromname[sl-2] == 's'
+ && buf.f_mntfromname[sl-1] >= '1'
+ && buf.f_mntfromname[sl-1] <= '9')
+ buf.f_mntfromname[sl-2] = '\000';
+ else
+ error("\nUnable to determine CDROM mount point from '%s'",buf.f_mntfromname);
+
+ strcpy(tname, oemtargs.volnames[j].path);
+ strcat(tname, "_ISO");
+ sprintf(sbuf, "mkdir \"%s\"", tname);
+ if ((rv = system(sbuf)) != 0)
+ error("\nCreating ISO9660 volume of CDROM failed with %d",rv);
+ sprintf(sbuf, "mount_cd9660 %s \"%s\"", buf.f_mntfromname,tname);
+ if ((rv = system(sbuf)) != 0) {
+ sprintf(sbuf, "rmdir \"%s\"", tname);
+ system(sbuf);
+ error("\nMounting ISO9660 volume of CDROM failed with %d",rv);
+ }
+ if (verb)
+ printf("done\n");
+ if ((oemamount_path = strdup(tname)) == NULL) {
+ oem_umiso();
+ error("Malloc of amount_path failed");
+ }
+ new_add_xf(&xf, tname, NULL, 0, file_vol, oemtargs.volnames[j].ttype);
+ break;
+ }
+ }
+ }
+#endif /* __APPLE__ */
+
+#if defined(UNIX_X11)
+ {
+ int j;
+
+ /* See if we can see what we're looking for on one of the volumes */
+ /* It would be nice to be able to read the volume name ! */
+ for (j = 0;;j++) {
+ if (oemtargs.volnames[j].path == NULL)
+ break;
+
+ if (access(oemtargs.volnames[j].path, 0) == 0) {
+ if (verb) printf("found '%s'\n",oemtargs.volnames[j].path);
+ new_add_xf(&xf, oemtargs.volnames[j].path, NULL, 0,
+ file_vol, oemtargs.volnames[j].ttype);
+ break;
+ }
+ }
+ }
+#endif /* UNIX */
+
+ return xf;
+}
+
+/* Locate and read the archive on the given volume. */
+/* Return NULL if not found */
+static xfile *locate_read_archive(xfile *vol, int verb) {
+ int j;
+ char buf[1000], *ap;
+ xfile *xf = NULL; /* return value */
+
+ if (verb) { printf("Looking for archive on volume '%s' .. ",vol->name); fflush(stdout); }
+
+ strcpy(buf, vol->name);
+ ap = buf + strlen(buf);
+
+ for (j = 0; oemtargs.archnames[j].path != NULL; j++) {
+ targ_type ttype = vol->ttype & oemtargs.archnames[j].ttype;
+
+ if (ttype == targ_none) {
+ continue;
+ }
+
+ strcpy(ap, oemtargs.archnames[j].path);
+ if (sys_access(buf, 0) == 0) {
+ if (verb) printf("found\n");
+ new_add_xf(&xf, buf, NULL, 0, file_arch, ttype);
+ if (load_xfile(xf, verb))
+ error("Failed to load file '%s'",xf->name);
+ xf->ftype = file_arch;
+ xf->ttype = oemtargs.archnames[j].ttype & vol->ttype;
+ break;
+ }
+ }
+ if (verb && oemtargs.archnames[j].path == NULL)
+ printf("not found\n");
+
+ return xf;
+}
+
+/* Locate and read any OEM install files. */
+/* Return NULL if not found */
+static xfile *locate_read_oeminstall(xfile **pxf, int verb) {
+ int j;
+ char tname[1000], *pf, *ap;
+ xfile *xf = NULL; /* return value */
+ aglob ag;
+
+ if (verb) { printf("Looking for OEM install files .. \n"); fflush(stdout); }
+
+ tname[0] = '\000';
+
+#ifdef NT
+ /* Where the normal instalation goes */
+ if ((pf = getenv("PROGRAMFILES")) != NULL)
+ strcpy(tname, pf);
+ else
+ strcpy(tname, "C:/Program Files");
+#endif /* NT */
+
+ ap = tname + strlen(tname);
+
+ for (j = 0; oemtargs.instpaths[j].path != NULL; j++) {
+
+ strcpy(ap, oemtargs.instpaths[j].path);
+
+ if (verb) printf("Looking for '%s'\n",tname);
+
+ if (aglob_create(&ag, tname))
+ error("Searching for '%s' malloc error",oemtargs.instpaths[j].path);
+
+ for (;;) {
+ char *pp;
+ if ((pp = aglob_next(&ag)) == NULL)
+ break;
+ xf = new_add_xf(&xf, pp, NULL, 0, oemtargs.archnames[j].ftype,
+ oemtargs.archnames[j].ttype);
+ if (load_xfile(xf, verb))
+ error("Failed to load file '%s'",xf->name);
+ }
+ aglob_cleanup(&ag);
+ }
+ return xf;
+}
+
+/* ============================================================================= */
+/* A list of files stored in memory. The last entry of the list has name == NULL */
+
+/* return a list with the given number of available entries */
+xfile *new_xf(int n) {
+ xfile *l;
+
+ if ((l = (xfile *)calloc((n + 1), sizeof(xfile))) == NULL)
+ error("new_xf: Failed to allocate xfile structure");
+
+ return l;
+}
+
+/* Add an entry to the list. Create the list if it is NULL */
+/* Return point to that entry */
+xfile *add_xf(xfile **l) {
+ int n;
+ xfile *ll;
+
+ if (*l == NULL)
+ *l = new_xf(0);
+
+ /* Count number of existing entries */
+ for (ll = *l, n = 0; ll->name != NULL; ll++, n++)
+ ;
+
+ if ((*l = (xfile *)realloc(*l, (n+2) * sizeof(xfile))) == NULL)
+ error("new_xf: Failed to realloc xfile structure");
+ (*l)[n+1].name = NULL; /* End marker */
+ (*l)[n+1].buf = NULL;
+ (*l)[n+1].len = 0;
+ (*l)[n+1].ftype = 0;
+ (*l)[n+1].ttype = 0;
+
+ return &(*l)[n];
+}
+
+/* Add an entry and copy details to the list. Create the list if it is NULL */
+/* Return point to that entry */
+xfile *new_add_xf(xfile **pxf, char *name, unsigned char *buf, unsigned long len,
+ file_type ftype, targ_type ttype) {
+ xfile *xf;
+ xf = add_xf(pxf);
+ if ((xf->name = strdup(name)) == NULL)
+ error("new_add_xf: strdup failed");
+ xf->buf = buf;
+ xf->len = len;
+ xf->ftype = ftype;
+ xf->ttype = ttype;
+ return xf;
+}
+
+/* Free up a whole list */
+void del_xf(xfile *l) {
+ int n;
+
+ if (l != NULL) {
+ for (n = 0; l[n].name != NULL; n++) {
+ if (l[n].name != NULL)
+ free(l[n].name);
+ if (l[n].buf != NULL)
+ free(l[n].buf);
+ }
+ free(l);
+ }
+}
+
+/* Allocate and load the given entry. */
+/* Return NZ on error */
+int load_xfile(xfile *xf, int verb) {
+ FILE *fp;
+ int i, nfiles;
+ unsigned char *ibuf;
+ unsigned long ilen, bread;
+
+ if (verb) { printf("Loading file '%s'..",xf->name); fflush(stdout); }
+
+ /* Open up the file for reading */
+#if !defined(O_CREAT) && !defined(_O_CREAT)
+# error "Need to #include fcntl.h!"
+#endif
+#if defined(O_BINARY) || defined(_O_BINARY)
+ if ((fp = fopen(xf->name,"rb")) == NULL)
+#else
+ if ((fp = fopen(xf->name,"r")) == NULL)
+#endif
+ {
+ if (verb) printf("fopen '%s' failed\n",xf->name);
+ return 1;
+ }
+
+ /* Figure out how big it is */
+ if (fseek(fp, 0, SEEK_END)) {
+ if (verb) printf("fseek to EOF of '%s' failed\n",xf->name);
+ return 1;
+ }
+
+ ilen = (unsigned long)ftell(fp);
+
+ if (verb > 1) printf("Size of file '%s' is %ld bytes\n",xf->name, ilen);
+
+ if (fseek(fp, 0, SEEK_SET)) {
+ if (verb) printf("fseek to SOF of file '%s' failed\n",xf->name);
+ return 1;
+ }
+
+ if ((ibuf = (unsigned char *)malloc(ilen)) == NULL)
+ error("\nmalloc buffer for file '%s' failed",xf->name);
+
+ if (verb > 1) printf("(Reading file '%s')\n",xf->name);
+
+ if ((bread = fread(ibuf, 1, ilen, fp)) != ilen) {
+ if (verb) printf("Failed to read file '%s', read %ld out of %ld bytes\n",xf->name,bread,ilen);
+ return 1;
+ }
+
+ fclose(fp);
+
+ if (xf->buf != NULL)
+ free(xf->buf);
+ xf->buf = ibuf;
+ xf->len = ilen;
+
+ if (verb) printf("done\n");
+
+ return 0;
+}
+
+/* Save a file to the given sname, or of it is NULL, */
+/* to prefix + xf-file name (path is ignored) */
+/* Error on failure */
+void save_xfile(xfile *xf, char *sname, char *pfx, int verb) {
+ FILE *fp;
+ char *cp, *fname;
+ size_t len;
+
+ if (sname != NULL) {
+ fname = sname;
+ } else {
+ if ((cp = strrchr(xf->name, '/')) == NULL
+ && (cp = strrchr(xf->name, '\\')) == NULL)
+ cp = xf->name;
+ else
+ cp++;
+
+ len = strlen(pfx) + strlen(cp) + 1;
+ if ((fname = malloc(len)) == NULL)
+ error("malloc fname %d bytes failed",len);
+ strcpy(fname, pfx);
+ strcat(fname, cp);
+ }
+
+ /* Open up the file for writing */
+#if !defined(O_CREAT) && !defined(_O_CREAT)
+# error "Need to #include fcntl.h!"
+#endif
+#if defined(O_BINARY) || defined(_O_BINARY)
+ if ((fp = fopen(fname,"wb")) == NULL)
+#else
+ if ((fp = fopen(fname,"w")) == NULL)
+#endif
+ error("Can't open file '%s' for writing",fname);
+
+ if ((fwrite(xf->buf, 1, xf->len, fp)) != xf->len)
+ error("Failed to write file '%s'",fname);
+
+ if (fclose(fp))
+ error("Failed to close file '%s' after writing",fname);
+
+ if (verb) printf("Wrote '%s' %ld bytes\n",fname, xf->len);
+
+ if (sname == NULL)
+ free(fname);
+}
+
+/* ============================================================================= */
+/* VISE archive file extraction */
+
+/* structure holding the VISE archive information */
+struct _visearch {
+ int verb;
+ int isvise; /* Is this an archive ? */
+ unsigned int vbase; /* Base address of archive */
+ unsigned char *abuf; /* Buffer holding archive file */
+ unsigned int asize; /* Nuber of bytes in file */
+ unsigned int off; /* Current offset */
+ unsigned char *dbuf; /* Room for decompressed driver file */
+ unsigned int dsize; /* current write location/size of decompressed file */
+ unsigned int dasize; /* current allocated size of dbuf */
+
+ /* Locate the given name, and set the offset */
+ /* to the locatation the compressed data is at */
+ /* return nz if not located */
+ int (*locate_file)(struct _visearch *p, char *name);
+
+ /* Set the current file offset */
+ void (*setoff)(struct _visearch *p, unsigned int off);
+
+ /* Get the next (big endian) 16 bits from the archive */
+ /* return 0 if past the end of the file */
+ unsigned int (*get16)(struct _visearch *p);
+
+ /* Unget 16 bytes */
+ void (*unget16)(struct _visearch *p);
+
+ /* Destroy ourselves */
+ void (*del)(struct _visearch *p);
+
+}; typedef struct _visearch visearch;
+
+static void del_arch(visearch *p) {
+ if (p->dbuf != NULL)
+ free(p->dbuf);
+ free(p);
+}
+
+/* Set the current file offset */
+static void setoff_arch(visearch *p, unsigned int off) {
+ if (off >= p->asize)
+ off = p->asize-1;
+ p->off = off;
+}
+
+/* Locate the given name, and set the offset */
+/* to the locatation the compressed data is at */
+/* return nz if not located */
+static int locate_file_arch(visearch *p, char *name) {
+ int i, sl;
+
+ sl = strlen(name); /* length excluding null */
+
+ if (sl == 0)
+ return 1;
+
+ /* Locate the filname with a search */
+ for (i = 0x10000; i < (p->asize - sl); i++) {
+ if (p->abuf[i] == name[0]) {
+ if (strncmp((char *)&p->abuf[i], name, sl) == 0) {
+ int sto;
+ /* Found it */
+ if (p->verb)
+ printf("Located driver entry '%s' at offset 0x%x\n",name,i);
+ i += sl;
+ if (i >= (p->asize - 1))
+ return 1;
+ sto = p->abuf[i];
+ i += (4 + sto) * 4; /* This is a complete hack. */
+ if (i >= (p->asize - 4))
+ return 1;
+ p->off = p->abuf[i + 0];
+ p->off += (p->abuf[i + 1] << 8);
+ p->off += (p->abuf[i + 2] << 16);
+ p->off += (p->abuf[i + 3] << 24);
+ p->off += p->vbase;
+ if (p->off >= p->asize - 10)
+ return 1;
+ if (p->verb)
+ printf("Located driver file '%s' at offset 0x%x\n",name,p->off);
+ return 0;
+ }
+ }
+ }
+ return 1;
+}
+
+/* Get the next (big endian) 16 bits from the archive */
+/* return 0 if past the end of the file */
+static unsigned int get16_arch(visearch *p) {
+ unsigned int val;
+
+ if (p->off >= (p->asize-1)) {
+ error("Went past end of archive");
+ }
+
+ val = (0xff & p->abuf[p->off]);
+ val = ((val << 8) + (0xff & p->abuf[p->off+1]));
+ p->off += 2;
+ return val;
+}
+
+/* Unget the 16 bits from the archive. */
+static void unget16_arch(visearch *p) {
+
+ if (p->off > 1)
+ p->off -= 2;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Interface with vinflate.c */
+
+int vinflate(void);
+
+visearch *g_va = NULL;
+
+/* fetch the next 16 bits, big endian */
+/* if we get 0xffffffff, we are at EOF */
+unsigned int vget_16bits() {
+ return get16_arch(g_va);
+}
+
+/* unget 16 bits */
+void vunget_16bits() {
+ unget16_arch(g_va);
+}
+
+/* Save the decompressed file to the buffer */
+int vwrite_output(unsigned char *buf, unsigned int len) {
+
+ /* If run out of space */
+ if ((g_va->dsize + len) >= g_va->dasize) {
+ size_t nlen = g_va->dsize + len;
+
+ /* Round up allocation */
+ if (nlen <= 1024)
+ nlen += 1024;
+ else
+ nlen += 4096;
+
+ if ((g_va->dbuf = realloc(g_va->dbuf, nlen)) == NULL)
+ error("realloc failed on VISE decompress buffer (%d bytes)",nlen);
+
+ g_va->dasize = nlen;
+ }
+ memmove(g_va->dbuf + g_va->dsize, buf, len);
+ g_va->dsize += len;
+
+ return 0;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Return NZ if the file appears to be a VISE archive */
+int is_vise(xfile *xf) {
+ int i;
+
+ /* See if it looks like a VIZE archive */
+ for (i = 0x10000; i < 0x11000 && i < (xf->len-4); i++) {
+ if (xf->buf[i + 3] == 'V'
+ && xf->buf[i + 2] == 'I'
+ && xf->buf[i + 1] == 'S'
+ && xf->buf[i + 0] == 'E') {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* create an archive from given memory data */
+/* Return NULL if it is not a VISE archive */
+visearch *new_arch(unsigned char *abuf, unsigned int asize, int verb) {
+ int i, rv = 0;
+ visearch *p;
+
+ if ((p = (visearch *)calloc(sizeof(visearch),1)) == NULL)
+ error("Malloc failed!");
+
+ p->locate_file = locate_file_arch;
+ p->setoff = setoff_arch;
+ p->verb = verb;
+ p->get16 = get16_arch;
+ p->unget16 = unget16_arch;
+ p->del = del_arch;
+ p->abuf = abuf;
+ p->asize = asize;
+
+ /* See if it looks like a VIZE archive */
+ for (i = 0x10000; i < 0x11000 && i < (p->asize-4); i++) {
+ if (p->abuf[i + 3] == 'V'
+ && p->abuf[i + 2] == 'I'
+ && p->abuf[i + 1] == 'S'
+ && p->abuf[i + 0] == 'E') {
+ p->isvise = 1;
+ p->vbase = i;
+ }
+ }
+
+ if (!p->isvise) {
+ free(p);
+ return NULL;
+ }
+
+ return p;
+}
+
+/* Extract a VISE file */
+/* Return NULL if not found */
+static xfile *vise_extract(xfile **pxf, xfile *arch, char *tfilename, int verb) {
+ visearch *vi;
+ char *cp;
+ xfile *xf = NULL;
+
+ if ((vi = new_arch(arch->buf, arch->len, verb)) == NULL) {
+ return NULL;
+ }
+
+ if (verb)
+ printf("Input file '%s' is a VISE archive file base 0x%x\n",arch->name,vi->vbase);
+
+ if (vi->locate_file(vi, tfilename)) {
+ if (verb) printf("Failed to locate file '%s' in VISE archive",tfilename);
+ return NULL;
+ }
+
+ g_va = vi;
+ if (vinflate()) {
+ if (verb) printf("Inflating file '%s' failed",tfilename);
+ return NULL;
+ }
+ g_va = NULL;
+
+ if (verb)
+ printf("Located and decompressed file '%s' from VISE archive\n",tfilename);
+
+ xf = add_xf(pxf);
+
+ /* Just return base filename */
+ if ((cp = strrchr(tfilename, '/')) == NULL
+ && (cp = strrchr(tfilename, '\\')) == NULL)
+ cp = tfilename;
+ else
+ cp++;
+
+ if ((xf->name = strdup(cp)) == NULL) {
+ fprintf(stderr,"strdup failed on filename\n");
+ exit(-1);
+ }
+ xf->buf = vi->dbuf;
+ xf->len = vi->dsize;
+ xf->ftype = file_dllcab;
+ xf->ttype = arch->ttype;
+
+ vi->dbuf = NULL; /* We've taken buffer */
+ vi->dsize = vi->dasize = 0;
+ vi->del(vi);
+
+ if (verb) printf("Returning '%s' length %ld from '%s'\n",xf->name, xf->len, arch[0].name);
+
+
+ return xf;
+}
+
+/* ============================================================================= */
+/* Spyder 2 PLD pattern extraction */
+
+int is_s2pld(xfile *xf) {
+
+ if (xf->len != 6817)
+ return 0;
+
+ if (xf->buf[0] == 0xff
+ && xf->buf[1] == 0x04
+ && xf->buf[2] == 0xb0
+ && xf->buf[3] == 0x0a)
+ return 1;
+ return 0;
+}
+
+static xfile *spyd2pld_extract(xfile **pxf, xfile *dll, int verb) {
+ unsigned char *buf;
+ unsigned int size;
+ unsigned int i, j;
+ unsigned int rfsize;
+ unsigned char *firmware;
+ unsigned int firmwaresize;
+ /* First few bytes of the standard Xilinx XCS05XL PLD pattern */
+ unsigned char magic[4] = { 0xff, 0x04, 0xb0, 0x0a };
+ xfile *xf = NULL;
+
+ buf = dll->buf;
+ size = dll->len;
+
+ firmwaresize = 6817;
+ rfsize = (firmwaresize + 7) & ~7;
+
+ /* Search for start of PLD pattern */
+ for(i = 0; (i + rfsize) < size ;i++) {
+ if (buf[i] == magic[0]) {
+ for (j = 0; j < 4; j++) {
+ if (buf[i + j] != magic[j])
+ break;
+ }
+ if (j >= 4)
+ break; /* found it */
+ }
+ }
+ if ((i + rfsize) >= size) {
+ if (verb) printf("Failed to locate Spyder 2 firmware in '%s'\n",dll->name);
+ return NULL;
+ }
+
+ firmware = buf + i;
+
+ xf = add_xf(pxf);
+
+ if ((xf->name = strdup("spyd2PLD.bin")) == NULL) {
+ fprintf(stderr,"strdup failed on filename\n");
+ exit(-1);
+ }
+ if ((xf->buf = malloc(firmwaresize)) == NULL) {
+ fprintf(stderr,"malloc failed for Spyder 2 PLD pattern (%d bytes)\n",firmwaresize);
+ exit(-1);
+ }
+ memcpy(xf->buf, firmware, firmwaresize);
+ xf->len = firmwaresize;
+ xf->ftype = file_data;
+ xf->ttype = targ_spyd_pld;
+
+ if (verb) printf("Returning '%s' length %ld from '%s'\n",xf->name, xf->len, dll->name);
+
+ return xf;
+}
+
+/* ============================================================================= */
+/* Spyder 4 calibration table extraction */
+
+int is_s4cal(xfile *xf) {
+ int i;
+ unsigned char cal2vals[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f };
+
+ if (xf->len < 8)
+ return 0;
+
+ for (i = 0; i < 8 ; i++) {
+ if (xf->buf[i] != cal2vals[i])
+ return 0;
+ }
+ return 1;
+}
+
+/* Extract the spyder 4 calibration table from the .dll */
+/* Return NULL if not found */
+static xfile *spyd4cal_extract(xfile **pxf, xfile *dll, int verb) {
+ unsigned char *buf, *caldata;
+ unsigned int size;
+ unsigned int i, j;
+ int nocals;
+ unsigned int rfsize;
+ /* First 41 x 8 bytes are the following pattern: */
+ unsigned char cal2vals[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f };
+ unsigned char cal2evals[7] = { '3', '3', '3', '7', '1', '0', '-' };
+ xfile *xf = NULL;
+
+ buf = dll->buf;
+ size = dll->len;
+
+ /* Search for start of the calibration data */
+ for(i = 0; i < (size - 41 * 8 - 7) ;i++) {
+ if (buf[i] == cal2vals[0]) {
+ for (j = 0; j < (41 * 8); j++) {
+ if (buf[i + j] != cal2vals[j % 8])
+ break;
+ }
+ if (j >= (41 * 8))
+ break; /* found first set of doubles */
+ }
+ }
+ if (i >= (size - 41 * 8 - 7)) {
+ if (verb) printf("Failed to locate Spyder 4 calibration data in '%s'\n",dll->name);
+ return NULL;
+ }
+
+ caldata = buf + i;
+
+ /* Search for end of the calibration data */
+ for(i += (41 * 8); i < (size-7) ;i++) {
+ if (buf[i] == cal2evals[0]) {
+ for (j = 0; j < 7; j++) {
+ if (buf[i + j] != cal2evals[j])
+ break;
+ }
+ if (j >= 7)
+ break; /* found string after calibration data */
+ }
+ }
+ if (i >= (size-7)) {
+ if (verb) printf("Failed to locate end of Spyder 4 calibration data in '%s'\n",dll->name);
+ return NULL;
+ }
+
+ rfsize = buf + i - caldata;
+
+ nocals = rfsize / (41 * 8);
+ if (rfsize != (nocals * 41 * 8)) {
+ if (verb) printf("Spyder 4 calibration data is not a multiple of %d bytes\n",41 * 8);
+ return NULL;
+ }
+
+ if (nocals != 6) {
+ if (verb) printf("Spyder 4 calibration data has an unexpected number of entries (%d)\n",nocals);
+ return NULL;
+ }
+
+ xf = add_xf(pxf);
+
+ if ((xf->name = strdup("spyd4cal.bin")) == NULL) {
+ fprintf(stderr,"strdup failed on filename\n");
+ exit(-1);
+ }
+ if ((xf->buf = malloc(rfsize)) == NULL) {
+ fprintf(stderr,"malloc failed for Spyder 4 calibration data (%d bytes)\n",rfsize);
+ exit(-1);
+ }
+ memcpy(xf->buf, caldata, rfsize);
+ xf->len = rfsize;
+ xf->ftype = file_data;
+ xf->ttype = targ_spyd_cal;
+
+ if (verb) printf("Returning '%s' length %ld from '%s'\n",xf->name, xf->len, dll[0].name);
+
+ return xf;
+}
+
+/* ============================================================================= */
+/* EDR to CCSS */
+
+static ccss *parse_EDR(unsigned char *buf, unsigned long len, char *name, int verb);
+static ccss *read_EDR_file(char *name, int verb);
+
+/* Do conversion. Return NZ on sucess */
+static xfile *edr_convert(xfile **pxf, xfile *xi, int verb) {
+ xfile *xf = NULL;
+ ccss *c;
+
+ if (verb) printf("Translating '%s' (%d bytes)\n",xi->name, xi->len);
+
+ if ((c = parse_EDR(xi->buf, xi->len, xi->name, verb)) == NULL) {
+ if (verb) printf("Failed to parse EDR '%s'\n",xi->name);
+ return NULL;
+ } else {
+ char *ccssname;
+ char *edrname;
+ unsigned char *buf;
+ int len;
+ if (c->buf_write_ccss(c, &buf, &len)) {
+ error("Failed to create ccss for '%s'",xi->name);
+ }
+ /* Convert .edr file name to .ccss */
+
+ /* Get basename of file */
+ if ((edrname = strrchr(xi->name, '/')) == NULL)
+ edrname = xi->name;
+ else
+ edrname++;
+
+ if ((ccssname = malloc(strlen(edrname) + 10)) == NULL)
+ error("Malloc failed on ccssname");
+
+ strcpy(ccssname, edrname);
+
+ /* Fix extension */
+ if (strlen(ccssname) > 4
+ && (strncmp(ccssname + strlen(ccssname) -4, ".edr", 4) == 0
+ || strncmp(ccssname + strlen(ccssname) -4, ".EDR", 4) == 0))
+ strcpy(ccssname + strlen(ccssname) -4, ".ccss");
+ xf = new_add_xf(pxf, ccssname, buf, len, file_data, targ_i1d3_edr);
+ free(ccssname);
+ }
+ return xf;
+}
+
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
+/* Lower level */
+
+// Print bytes as hex to fp
+static void dump_bytes(FILE *fp, char *pfx, unsigned char *buf, int len) {
+ int i, j, ii;
+ for (i = j = 0; i < len; i++) {
+ if ((i % 16) == 0)
+ fprintf(fp,"%s%04x:",pfx,i);
+ fprintf(fp," %02x",buf[i]);
+ if ((i+1) >= len || ((i+1) % 16) == 0) {
+ for (ii = i; ((ii+1) % 16) != 0; ii++)
+ fprintf(fp," ");
+ fprintf(fp," ");
+ for (; j <= i; j++) {
+ if (isprint(buf[j]))
+ fprintf(fp,"%c",buf[j]);
+ else
+ fprintf(fp,".");
+ }
+ fprintf(fp,"\n");
+ }
+ }
+}
+
+/* Take a 64 sized return buffer, and convert it to an ORD64 */
+static ORD64 buf2ord64(unsigned char *buf) {
+ ORD64 val;
+ val = buf[7];
+ val = ((val << 8) + (0xff & buf[6]));
+ val = ((val << 8) + (0xff & buf[5]));
+ val = ((val << 8) + (0xff & buf[4]));
+ val = ((val << 8) + (0xff & buf[3]));
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* Take a word sized return buffer, and convert it to an unsigned int */
+static unsigned int buf2uint(unsigned char *buf) {
+ unsigned int val;
+ val = buf[3];
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* Take a word sized return buffer, and convert it to an int */
+static int buf2int(unsigned char *buf) {
+ int val;
+ val = buf[3];
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* Take a short sized return buffer, and convert it to an int */
+static int buf2short(unsigned char *buf) {
+ int val;
+ val = buf[1];
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* Detect this file as an EDR file */
+int is_edr(xfile *xf) {
+
+ if (xf->len < 16)
+ return 0;
+
+ /* See if it has the right file ID */
+ if (strncmp("EDR DATA1", (char *)xf->buf, 16) == 0)
+ return 1;
+ return 0;
+}
+
+/* Given a buffer holding and EDR file, parse it and return a ccss */
+/* Return NULL on error */
+static ccss *parse_EDR(
+ unsigned char *buf,
+ unsigned long len,
+ char *name, /* Name of the file */
+ int verb /* Verbose flag */
+) {
+ int i, j;
+ FILE *fp;
+ unsigned char *nbuf;
+ unsigned long nlen;
+ unsigned int off;
+ unsigned char *dbuf = NULL; /* Buffer for spectral samples */
+ INR64 edrdate;
+ char creatdate[30];
+ char dispdesc[256];
+ int ttmin, ttmax; /* Min & max technology strings (inclusive) */
+ int *trefmodes; /* Corresponding refresh mode for tecnology */
+ char **tsels; /* Corresponding UI selection chars */
+ char **ttstrings; /* Corresponding technology strings */
+ int ttype; /* Technology type idex */
+ int nsets, set;
+ xspect *samples = NULL, sp;
+ int hasspec;
+ double nmstart, nmend, nmspace;
+ int nsamples;
+ ccss *rv;
+
+ /* We hard code the Technology strings for now. In theory we could */
+ /* read them from the TechnologyStrings.txt file that comes with the .edr's */
+ {
+ ttmin = 0;
+ ttmax = 22;
+ if ((ttstrings = (char **)malloc(sizeof(char *) * (ttmax - ttmin + 2))) == NULL) {
+ if (verb) printf("Malloc failed\n");
+ return NULL;
+ }
+ if ((trefmodes = (int *)malloc(sizeof(int) * (ttmax - ttmin + 2))) == NULL) {
+ free(ttstrings);
+ if (verb) printf("Malloc failed\n");
+ return NULL;
+ }
+ if ((tsels = (char **)malloc(sizeof(char *) * (ttmax - ttmin + 2))) == NULL) {
+ free(trefmodes);
+ free(ttstrings);
+ if (verb) printf("Malloc failed\n");
+ return NULL;
+ }
+
+ trefmodes[0] = 0; tsels[0] = NULL; ttstrings[0] = "Color Matching Function";
+ trefmodes[1] = 0; tsels[1] = NULL; ttstrings[1] = "Custom";
+ trefmodes[2] = 1; tsels[2] = "c"; ttstrings[2] = "CRT";
+ trefmodes[3] = 0; tsels[3] = "l"; ttstrings[3] = "LCD CCFL IPS";
+ trefmodes[4] = 0; tsels[4] = NULL; ttstrings[4] = "LCD CCFL VPA";
+ trefmodes[5] = 0; tsels[5] = NULL; ttstrings[5] = "LCD CCFL TFT";
+ trefmodes[6] = 0; tsels[6] = "L"; ttstrings[6] = "LCD CCFL Wide Gamut IPS";
+ trefmodes[7] = 0; tsels[7] = NULL; ttstrings[7] = "LCD CCFL Wide Gamut VPA";
+ trefmodes[8] = 0; tsels[8] = NULL; ttstrings[8] = "LCD CCFL Wide Gamut TFT";
+ trefmodes[9] = 0; tsels[9] = "e"; ttstrings[9] = "LCD White LED IPS";
+ trefmodes[10] = 0; tsels[10] = NULL; ttstrings[10] = "LCD White LED VPA";
+ trefmodes[11] = 0; tsels[11] = NULL; ttstrings[11] = "LCD White LED TFT";
+ trefmodes[12] = 0; tsels[12] = "b"; ttstrings[12] = "LCD RGB LED IPS";
+ trefmodes[13] = 0; tsels[13] = NULL; ttstrings[13] = "LCD RGB LED VPA";
+ trefmodes[14] = 0; tsels[14] = NULL; ttstrings[14] = "LCD RGB LED TFT";
+ trefmodes[15] = 0; tsels[15] = "o"; ttstrings[15] = "LED OLED";
+ trefmodes[16] = 0; tsels[16] = "a"; ttstrings[16] = "LED AMOLED";
+ trefmodes[17] = 1; tsels[17] = "m"; ttstrings[17] = "Plasma";
+ trefmodes[18] = 0; tsels[18] = NULL; ttstrings[18] = "LCD RG Phosphor"; // is this LCD ??
+ trefmodes[19] = 1; tsels[19] = NULL; ttstrings[19] = "Projector RGB Filter Wheel";
+ trefmodes[20] = 1; tsels[10] = NULL; ttstrings[20] = "Projector RGBW Filter Wheel";
+ trefmodes[21] = 1; tsels[21] = NULL; ttstrings[21] = "Projector RGBCMY Filter Wheel";
+ trefmodes[22] = 0; tsels[22] = "p"; ttstrings[22] = "Projector";
+ trefmodes[23] = 0; tsels[23] = NULL; ttstrings[23] = "Unknown";
+ }
+
+ if (len < 600) {
+ if (verb) printf("Unable to read '%s' header\n",name);
+ return NULL;
+ }
+ nbuf = buf + 600; /* Next time we "read" the file */
+ nlen = len - 600;
+
+ /* See if it has the right file ID */
+ if (strncmp("EDR DATA1", (char *)buf, 16) != 0) {
+ if (verb) printf("File '%s' isn't an EDR\n",name);
+ return NULL;
+ }
+
+ /* Creation Date&time of EDR */
+ edrdate = buf2ord64(buf + 0x0018);
+ strcpy(creatdate, ctime_64(&edrdate));
+
+ /* Creation tool string @ 0x0020 */
+
+ /* Display description */
+ strncpy(dispdesc, (char *)buf + 0x0060, 255); dispdesc[255] = '\000';
+
+ /* Technology type index. */
+ ttype = buf2int(buf + 0x0160);
+
+ /* Number of data sets */
+ nsets = buf2int(buf + 0x0164);
+
+ if (nsets < 3 || nsets > 100) {
+ if (verb) printf("File '%s' number of data sets %d out of range\n",name,nsets);
+ return NULL;
+ }
+
+ /* Model number string @ 0x0168 ? */
+ /* Part code string @ 0x01a8 ? */
+ /* Another number string @ 0x01e8 ? */
+
+ /* Unknown Flag/number = 1 @ 0x022c */
+
+ /* "has spectral data" flag ? */
+ hasspec = buf2short(buf + 0x022E);
+ if (hasspec != 1) {
+ if (verb) printf("Has Data flag != 1 in EDR file '%s'\n",name);
+ return NULL;
+ }
+
+ /* The spectral sample characteristics */
+ nmstart = IEEE754_64todouble(buf2ord64(buf + 0x0230));
+ nmend = IEEE754_64todouble(buf2ord64(buf + 0x0238));
+ nmspace = IEEE754_64todouble(buf2ord64(buf + 0x0240));
+
+ /* Setup prototype spectral values */
+ sp.spec_wl_short = nmstart;
+ sp.spec_wl_long = nmend;
+ sp.spec_n = (int)(1.0 + (nmend - nmstart)/nmspace + 0.5);
+ sp.norm = 1.0;
+
+ /* Unknown Flag/number = 0 @ 0x0248 */
+ /* Error if not 0 ??? */
+
+ if (hasspec) {
+ /* Allocate space for the sets */
+ if ((samples = (xspect *)malloc(sizeof(xspect) * nsets)) == NULL) {
+ if (verb) printf("Malloc of spectral samples failed\n");
+ return NULL;
+ }
+ }
+
+ /* Read the sets of data */
+ for (set = 0; set < nsets; set++) {
+
+ /* "Read" in the 128 byte data set header */
+ buf = nbuf;
+ len = nlen;
+ if (len < 128) {
+ if (verb) printf("Unable to read file '%s' set %d data header\n",name,set);
+ if (samples != NULL) free(samples);
+ return NULL;
+ }
+ nbuf = buf + 128; /* Next time we "read" the file */
+ nlen = len - 128;
+
+ /* See if it has the right ID */
+ if (strncmp("DISPLAY DATA", (char *)buf, 16) != 0) {
+ if (verb) printf("File '%s' set %d data header has unknown identifier\n",name,set);
+ if (samples != NULL) free(samples);
+ return NULL;
+ }
+
+ /* double Yxy(z) of sample at +0x0058 in data header ? */
+
+ if (hasspec == 0) /* No spectral data, so skip it */
+ continue;
+
+ /* Read in the 28 byte data set header */
+ buf = nbuf;
+ len = nlen;
+ if (len < 28) {
+ if (verb) printf("Unable to read file '%s' set %d spectral data header\n",name,set);
+ if (samples != NULL) free(samples);
+ return NULL;
+ }
+ nbuf = buf + 28; /* Next time we "read" the file */
+ nlen = len - 28;
+
+ /* See if it has the right ID */
+ if (strncmp("SPECTRAL DATA", (char *)buf, 16) != 0) {
+ if (verb) printf("File '%s' set %d data header has unknown identifier\n",name,set);
+ if (samples != NULL) free(samples);
+ return NULL;
+ }
+
+ /* Number of doubles in set */
+ nsamples = buf2int(buf + 0x0010);
+
+ if (nsamples != sp.spec_n) {
+ if (verb) printf("File '%s' set %d number of samles %d doesn't match wavelengths %d\n",name,set,nsamples,sp.spec_n);
+ if (samples != NULL) free(samples);
+ return NULL;
+ }
+
+ /* Read in the spectral values */
+ buf = nbuf;
+ len = nlen;
+ if (len < 8 * sp.spec_n) {
+ if (verb) printf("Unable to read file '%s' set %d spectral data\n",name,set);
+ if (samples != NULL) free(samples);
+ return NULL;
+ }
+ nbuf = buf + 8 * sp.spec_n; /* Next time we "read" the file */
+ nlen = len - 8 * sp.spec_n;
+
+ XSPECT_COPY_INFO(&samples[set], &sp);
+
+ /* Read the spectral values for this sample, */
+ /* and convert it from W/nm/m^2 to mW/nm/m^2 */
+ for (j = 0; j < sp.spec_n; j++) {
+ samples[set].spec[j] = IEEE754_64todouble(buf2ord64(buf + j * 8));
+ samples[set].spec[j] *= 1000.0;
+ }
+#ifdef PLOT_SAMPLES
+ /* Plot the spectra */
+ {
+ double xx[500];
+ double y1[500];
+
+ for (j = 0; j < samples[set].spec_n; j++) {
+ xx[j] = XSPECT_XWL(&sp, j);
+ y1[j] = samples[set].spec[j];
+ }
+ printf("EDR sample %d (uncorrected)\n",set+1);
+ do_plot6(xx, y1, NULL, NULL, NULL, NULL, NULL, samples[set].spec_n);
+ }
+#endif /* PLOT_SAMPLES */
+
+ }
+
+ /* After the spectral data comes the "correction" data. This seems to be */
+ /* related to the measurement instrument (typically a Minolta CS1000). */
+ /* It's not obvious why this isn't already applied to the spectral data, */
+ /* and doesn't cover the same wavelength range as the samples. */
+
+ /* Try and read in the 92 byte correction header */
+ buf = nbuf;
+ len = nlen;
+ if (len >= 92) {
+ nbuf = buf + 92; /* Next time we "read" the file */
+ nlen = len - 92;
+
+ /* See if it has the right ID */
+ if (strncmp("CORRECTION DATA", (char *)buf, 16) != 0) {
+ if (verb) printf("File '%s' correction data header has unknown identifier\n",name);
+ if (samples != NULL) free(samples);
+ return NULL;
+ }
+
+ /* Number of doubles in set */
+ nsamples = buf2int(buf + 0x0050);
+
+ if (nsamples == 351) {
+ sp.spec_wl_short = 380.0;
+ sp.spec_wl_long = 730.0;
+ sp.spec_n = nsamples;
+ sp.norm = 1.0;
+ } else if (nsamples == 401) {
+ sp.spec_wl_short = 380.0;
+ sp.spec_wl_long = 780.0;
+ sp.spec_n = nsamples;
+ sp.norm = 1.0;
+ } else {
+ if (verb) printf("File '%s' correction data has unknown range %d\n\n",name,nsamples);
+ if (samples != NULL) free(samples);
+ return NULL;
+ }
+
+ /* Read in the spectral values */
+ buf = nbuf;
+ len = nlen;
+ if (len < 8 * sp.spec_n) {
+ if (verb) printf("Unable to read file '%s' correction spectral data\n",name);
+ if (samples != NULL) free(samples);
+ return NULL;
+ }
+ nbuf = buf + 8 * sp.spec_n; /* Next time we "read" the file */
+ nlen = len - 8 * sp.spec_n;
+
+ for (j = 0; j < sp.spec_n; j++) {
+ sp.spec[j] = IEEE754_64todouble(buf2ord64(buf + j * 8));
+ }
+
+#ifdef PLOT_SAMPLES
+ /* Plot the spectra */
+ {
+ double xx[500];
+ double y1[500];
+
+ for (j = 0; j < sp.spec_n; j++) {
+ xx[j] = XSPECT_XWL(&sp, j);
+ y1[j] = sp.spec[j];
+ }
+ printf("Correction data\n");
+ do_plot6(xx, y1, NULL, NULL, NULL, NULL, NULL, sp.spec_n);
+ }
+#endif /* PLOT_SAMPLES */
+
+ /* Apply the correction to all the spectral samples */
+ for (set = 0; set < nsets; set++) {
+ for (j = 0; j < samples[set].spec_n; j++)
+ samples[set].spec[j] *= value_xspect(&sp, XSPECT_XWL(&samples[set], j));
+ }
+ }
+
+ /* Creat a ccss */
+ if ((rv = new_ccss()) == NULL) {
+ if (verb) printf("Unable to read file '%s' correction spectral data\n",name);
+ if (samples != NULL) free(samples);
+ return NULL;
+ }
+
+ if (ttype < ttmin || ttype > ttmax) {
+ ttype = ttmax + 1; /* Set to Unknown */
+ }
+
+ /* Set it's values */
+ rv->set_ccss(rv, "X-Rite", creatdate, NULL, dispdesc, ttstrings[ttype], trefmodes[ttype], tsels[ttype], "CS1000", samples, nsets);
+
+ free(tsels);
+ free(trefmodes);
+ free(ttstrings);
+ free(samples);
+
+ return rv;
+}
+
+/* Return nz if this looks like a .ccss */
+int is_ccss(xfile *xf) {
+ if (xf->len < 7)
+ return 0;
+
+ /* See if it has the right file ID */
+ if (strncmp("CCSS ", (char *)xf->buf, 7) == 0)
+ return 1;
+ return 0;
+}
+
+/* Read am EDR file and return a ccss class */
+/* Return NULL on error */
+static ccss *read_EDR_file(
+ char *name, /* File to read */
+ int verb /* Verbose flag */
+) {
+ FILE *fp;
+ unsigned char *ibuf;
+ unsigned long ilen, bread;
+ ccss *rv;
+
+ /* Open up the file for reading */
+#if !defined(O_CREAT) && !defined(_O_CREAT)
+# error "Need to #include fcntl.h!"
+#endif
+#if defined(O_BINARY) || defined(_O_BINARY)
+ if ((fp = fopen(name,"rb")) == NULL)
+#else
+ if ((fp = fopen(name,"r")) == NULL)
+#endif
+ {
+ if (verb) printf("Unable to open file file '%s'\n",name);
+ return NULL;
+ }
+
+ /* Figure out how big it is */
+ if (fseek(fp, 0, SEEK_END)) {
+ if (verb) printf("Seek to EOF '%s' failed'\n",name);
+ return NULL;
+ }
+ ilen = (unsigned long)ftell(fp);
+
+ if (fseek(fp, 0, SEEK_SET)) {
+ if (verb) printf("Seek to SOF '%s' failed'\n",name);
+ return NULL;
+ }
+
+ if ((ibuf = (unsigned char *)malloc(ilen)) == NULL) {
+ if (verb) printf("Malloc of buffer for file '%s' failed\n",name);
+ return NULL;
+ }
+
+ if ((bread = fread(ibuf, 1, ilen, fp)) != ilen) {
+ if (verb) printf("Failed to read file '%s', read %ld out of %ld bytes\n",name,bread,ilen);
+ free(ibuf);
+ return NULL;
+ }
+ fclose(fp);
+
+ rv = parse_EDR(ibuf, ilen, name, verb);
+
+ free(ibuf);
+
+ return rv;
+}
+
+/* ========================================================= */
+
+/* Return nz if this looks like a .ccmx */
+int is_ccmx(xfile *xf) {
+ if (xf->len < 7)
+ return 0;
+
+ /* See if it has the right file ID */
+ if (strncmp("CCMX ", (char *)xf->buf, 7) == 0)
+ return 1;
+ return 0;
+}
+
+/* ========================================================= */
+/* i1d3 Archive support */
+/* Extract a file from an inno archive. */
+
+/* (It turns out we don't actually need this inno parsing code, */
+/* since the .cab file is uncompressed and contiguous, so */
+/* we can locate and save it directly from Setup.exe.) */
+
+/* Function callbacks for LzmaDec */
+static void *SzAlloc(void *p, size_t size) { p = p; return malloc(size); }
+static void SzFree(void *p, void *address) { p = p; free(address); }
+ISzAlloc alloc = { SzAlloc, SzFree };
+
+
+/* Version of lzma decode that skips the 4 byte CRC before each 4096 byte block */
+static SRes LzmaDecodeX(
+Byte *dest, unsigned long *destLen, const Byte *src, unsigned long *srcLen,
+ELzmaFinishMode finishMode, ELzmaStatus *status, ISzAlloc *alloc) {
+ SRes res;
+ CLzmaDec state;
+ SizeT slen = *srcLen;
+ SizeT dlen = *destLen;
+ SizeT len;
+ int doneheader = 0;
+
+ if (slen < (4 + 5))
+ return SZ_ERROR_INPUT_EOF;
+
+ LzmaDec_Construct(&state);
+ res = LzmaDec_Allocate(&state, src + 4, LZMA_PROPS_SIZE, alloc);
+ if (res != SZ_OK)
+ return res;
+
+ *srcLen = 0;
+ *destLen = 0;
+ LzmaDec_Init(&state);
+
+ for (;slen > 0 && dlen > 0;) {
+ SizeT ddlen;
+
+ if (slen < 4)
+ return SZ_ERROR_INPUT_EOF;
+ if (dlen == 0)
+ return SZ_ERROR_OUTPUT_EOF;
+
+ /* Skip the CRC */
+ src += 4;
+ slen -= 4;
+ *srcLen += 4;
+
+ len = 4096; /* Bytes to next CRC */
+
+ if (doneheader == 0) { /* Skip the 5 + 8 byte header */
+ int inc = 5;
+ len -= inc;
+ src += inc;
+ slen -= inc;
+ *srcLen += inc;
+ doneheader = 1;
+ }
+
+ if (len > slen)
+ len = slen;
+ ddlen = dlen;
+
+ res = LzmaDec_DecodeToBuf(&state, dest, &ddlen, src, &len, finishMode, status);
+ if (res != SZ_OK) {
+ LzmaDec_Free(&state, alloc);
+ return res;
+ }
+
+ src += len;
+ slen -= len;
+ *srcLen += len;
+
+ dest += ddlen;
+ dlen -= ddlen;
+ *destLen += ddlen;
+
+ if (*status == LZMA_STATUS_FINISHED_WITH_MARK
+ || *status == LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) {
+ break;
+ }
+ }
+ LzmaDec_Free(&state, alloc);
+
+ return res;
+}
+
+/* extract the given file from Setup.exe */
+
+/* Return a list of xfiles, with one entry if successful */
+/* Return NULL if not found or not inno file */
+static xfile *inno_extract(xfile *xi, char *tfilename, int verb) {
+ int i, j, k;
+ unsigned char *ibuf;
+ unsigned long ilen;
+ char *headerid = "Inno Setup Setup Data (5.3.10)";
+ int headerlen = strlen(headerid);
+ unsigned int ldrbase = 0;
+ unsigned long haddr, srclen;
+ unsigned char *d1buf, *d2buf; /* Decompress buffer */
+ unsigned long d1sz, d2sz;
+ unsigned char *msibuf;
+ unsigned long msisz;
+ unsigned long cblocklen;
+ SRes rv;
+ ELzmaStatus dstat;
+ unsigned long ix;
+ int filelen = strlen(tfilename);
+ unsigned long fileso, filesz;
+ char *cp;
+ xfile *xf = NULL;
+
+ ibuf = xi->buf;
+ ilen = xi->len;
+
+ /* Search for the start of the loader. All the file offsets */
+ /* are relative to this */
+ for (i = 0; i < (ilen - 4); i++) {
+ if (ibuf[i + 0] == 0x4d
+ && ibuf[i + 1] == 0x5a
+ && ibuf[i + 2] == 0x90
+ && ibuf[i + 3] == 0x00) {
+ if (verb > 1) printf("Found archive base 0x%x\n",i);
+ ldrbase = i;
+ break;
+ }
+ }
+ if (i >= (ilen - 4)) {
+ if (verb) printf("Failed to locate loader base\n");
+ return NULL;
+ }
+
+ /* Search for inno header pattern. */
+ for (i = 0; i < (ilen - 64 - 4 - 5 - 4); i++) {
+ if (ibuf[i] == headerid[0]
+ && strncmp((char *)ibuf + i, headerid, headerlen) == 0
+ && ibuf[i + 64] != 'I'
+ && ibuf[i + 65] != 'n'
+ && ibuf[i + 66] != 'n'
+ && ibuf[i + 67] != 'o') {
+ haddr = i;
+ }
+ }
+ if (i >= (ilen - 64 - 4 - 5 - 4)) {
+ if (verb) printf("Failed to locate header pattern\n");
+ return NULL;
+ }
+
+ if (verb > 1) printf("Found header at 0x%x\n",i);
+
+ /* Use the last header found (or cound search all found ?) */
+ haddr += 64; /* Skip Inno header */
+
+ /* Next 9 bytes are the compression header */
+ haddr += 4; /* Skip Inno header CRC */
+ cblocklen = buf2uint(ibuf + haddr); /* Compressed block length */
+
+ if (verb > 1) printf("Header compression block length = %ld\n",cblocklen);
+
+ if ((haddr + cblocklen) > ilen) {
+ if (verb) printf("Compression block is longer than setup.exe\n");
+ return NULL;
+ }
+
+ if (ibuf[haddr + 4] != 1) {
+ if (verb) printf("Inno header is expected to be compressed\n");
+ return NULL;
+ }
+ haddr += 5; /* Skip compression header */
+
+ /* We'r now at the start of the compressed data */
+
+ d1sz = cblocklen * 30;
+ if ((d1buf = (unsigned char *)malloc(d1sz)) == NULL) {
+ fprintf(stderr,"Failed to allocate decompression buffer\n");
+ exit(-1);
+ }
+
+ // if (verb) printf("CRC + lzma at %d\n",haddr);
+
+ srclen = ilen - haddr;
+
+ /* Decode using CRC + 4096 byte blocks */
+ rv = LzmaDecodeX(d1buf, &d1sz, ibuf + haddr, &srclen, LZMA_FINISH_END, &dstat, &alloc);
+
+ if (rv != SZ_OK) {
+ if (verb) printf("lzma decode failed with rv %d and status %d\n",rv, dstat);
+ free(d1buf);
+ return NULL;
+ }
+ if (verb > 1) printf("Decoded %ld bytes to created %ld bytes of Header output (ratio %.1f)\n",srclen,d1sz,(double)d1sz/srclen);
+
+// dump_bytes(stdout, " ", d1buf, d1sz);
+
+ /* - - - - - - - - - - - - - - - - -*/
+
+ /* Skip to the start of the next compression block header */
+ haddr += cblocklen;
+
+ if (verb > 1) printf("Expect File Location compressed block to be at 0x%lx\n",haddr);
+ if ((haddr + 20) > ilen) {
+ if (verb) printf("Setup.exe too short for 2nd header block\n");
+ free(d1buf);
+ return NULL;
+ }
+
+ /* Next 9 bytes are the compression header */
+ haddr += 4; /* Skip Inno header CRC */
+ cblocklen = buf2uint(ibuf + haddr); /* Compressed block length */
+
+ if (verb > 1) printf("File Location compression block length = %ld\n",cblocklen);
+
+ if ((haddr + cblocklen) > ilen) {
+ if (verb) printf("2nd compression block is longer than setup.exe\n");
+ free(d1buf);
+ return NULL;
+ }
+
+ if (ibuf[haddr + 4] != 1) {
+ if (verb) printf("Inno 2nd header is expected to be compressed\n");
+ free(d1buf);
+ return NULL;
+ }
+ haddr += 5; /* Skip compression header */
+
+ /* We're now at the start of the compressed data */
+
+ d2sz = cblocklen * 10;
+ if ((d2buf = (unsigned char *)malloc(d2sz)) == NULL) {
+ if (verb) printf("Failed to allocate 2nd block decompression buffer\n");
+ free(d1buf);
+ free(d2buf);
+ return NULL;
+ }
+
+ //if (verb) printf("CRC + lzma at %d\n",haddr);
+ srclen = ilen - haddr;
+
+ /* Decode using CRC + 4096 byte blocks */
+ rv = LzmaDecodeX(d2buf, &d2sz, ibuf + haddr, &srclen, LZMA_FINISH_END, &dstat, &alloc);
+
+ if (rv != SZ_OK) {
+ if (verb) printf("lzma decode of 2nd block failed with rv %d and status %d\n",rv, dstat);
+ free(d1buf);
+ free(d2buf);
+ return NULL;
+ }
+ if (verb > 1) printf("Decoded %ld bytes to created %ld bytes of File Location output (ratio %.1f)\n",srclen,d1sz,(double)d1sz/srclen);
+
+// dump_bytes(stdout, " ", d2buf, d2sz);
+
+ if (verb > 1) printf("Searching for file '%s' in Header\n",tfilename);
+ for (i = 0; i < (d1sz - 101); i++) {
+ if (d1buf[i+4] == tfilename[0]
+ && strncmp((char *)d1buf + 4 + i, tfilename, filelen) == 0
+ && d1buf[i+0] == filelen
+ && d1buf[i+1] == 0
+ && d1buf[i+2] == 0
+ && d1buf[i+3] == 0) {
+ if (verb > 1) printf("Found it at 0x%x\n",i);
+ break;
+ }
+ }
+ if (i >= (d1sz - 101)) {
+ if (verb) printf("Failed to find file '%s'\n",tfilename);
+ free(d1buf);
+ free(d2buf);
+ return NULL;
+ }
+
+ /* Need to skip 8 more strings */
+ i += 4 + filelen;
+ for (j = 0; j < 8; j++) {
+ unsigned long len;
+ len = buf2uint(d1buf + i);
+ i += 4 + len;
+ }
+ /* Skip another 40 bytes to location entry index */
+ i += 20;
+
+ ix = buf2uint(d1buf + i);
+
+ if (verb > 1) printf("Got file location index %ld at 0x%x\n",ix,i);
+
+
+ /* Now get the ix file entry information. */
+ /* They are in 74 byte structures */
+ i = ix * 74;
+
+ if ((i + 74) > d2sz) {
+ if (verb) printf("File location structure is out of range\n");
+ free(d1buf);
+ free(d2buf);
+ return NULL;
+ }
+
+ /* The start offset is at 8 */
+ fileso = buf2uint(d2buf + i + 8);
+
+ /* The original and compresses sizes are at 20 and 28 */
+ filesz = buf2uint(d2buf + i + 20); /* Actually 64 bit, but we don't need it */
+ if (filesz != buf2uint(d2buf + i + 28)) {
+ if (verb) printf("Can't handle compressed '%s'\n",tfilename);
+ free(d1buf);
+ free(d2buf);
+ return NULL;
+ }
+
+ if (verb > 1) printf("File '%s' is at offset 0x%lx, length %ld\n",tfilename,fileso,filesz);
+
+ if ((fileso + ldrbase) > ilen
+ || (fileso + ldrbase + filesz-1) > ilen) {
+ if (verb) printf("File '%s' is outsize file '%s' range \n",tfilename,xi->name);
+ free(d1buf);
+ free(d2buf);
+ return NULL;
+ }
+ /* Sanity check */
+ if (ibuf[ldrbase + fileso + 0] != 0xd0
+ || ibuf[ldrbase + fileso + 1] != 0xcf
+ || ibuf[ldrbase + fileso + 2] != 0x11
+ || ibuf[ldrbase + fileso + 3] != 0xe0) {
+ if (verb) printf("File '%s' doesn't seem to be at location\n",tfilename);
+ free(d1buf);
+ free(d2buf);
+ return NULL;
+ }
+
+ /* Copy to new buffer and free everything */
+ msisz = filesz;
+ if ((msibuf = (unsigned char *)malloc(msisz)) == NULL) {
+ fprintf(stderr,"Failed to allocate file '%s' buffer\n",tfilename);
+ free(d1buf);
+ free(d2buf);
+ exit(-1);
+ }
+ memmove(msibuf, ibuf + ldrbase + fileso, filesz);
+
+ free(d1buf);
+ free(d2buf);
+
+ xf = new_xf(1);
+
+ /* Just return base filename */
+ if ((cp = strrchr(tfilename, '/')) == NULL
+ && (cp = strrchr(tfilename, '\\')) == NULL)
+ cp = tfilename;
+ else
+ cp++;
+
+ if ((xf->name = strdup(cp)) == NULL) {
+ fprintf(stderr,"strdup failed on filename\n");
+ exit(-1);
+ }
+ xf->buf = msibuf;
+ xf->len = filesz;
+
+ if (verb) printf("Returning '%s' length %ld from '%s'\n",xf->name,xf->len, xi->name);
+
+ return xf;
+}
+
+/* ====================================================== */
+/* i1d3 Archive support */
+
+int is_inno(xfile *xf) {
+ int i;
+
+ /* Search for the start of the loader. All the file offsets */
+ /* are relative to this */
+ for (i = 0; i < (xf->len - 4); i++) {
+ if (xf->buf[i + 0] == 0x4d
+ && xf->buf[i + 1] == 0x5a
+ && xf->buf[i + 2] == 0x90
+ && xf->buf[i + 3] == 0x00) {
+ break;
+ }
+ }
+ if (i >= (xf->len - 4)) {
+ return 0;
+ }
+
+ /* Search for an Inno header pattern. */
+ for (i = 0; i < (xf->len - 64 - 4 - 5 - 4); i++) {
+ if (xf->buf[i + 0] == 'I'
+ && xf->buf[i + 1] == 'n'
+ && xf->buf[i + 2] == 'n'
+ && xf->buf[i + 3] == 'o') {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* Return NZ if this is .cab file */
+int is_cab(xfile *xf) {
+
+ if (xf->len < 8)
+ return 0;
+
+ if (xf->buf[0] == 0x4d
+ && xf->buf[1] == 0x53
+ && xf->buf[2] == 0x43
+ && xf->buf[3] == 0x46
+ && xf->buf[4] == 0x00
+ && xf->buf[5] == 0x00
+ && xf->buf[6] == 0x00
+ && xf->buf[7] == 0x00)
+ return 1;
+ return 0;
+}
+
+/* Return NZ if this seems to be a .dll */
+int is_dll(xfile *xf) {
+ int off;
+ int chars;
+
+ if (xf->len < 0x40
+ || xf->buf[0] != 0x4d /* "MZ" */
+ || xf->buf[1] != 0x5a)
+ return 0;
+
+ off = xf->buf[0x3c] /* Offset to PE header */
+ + (xf->buf[0x3d] << 8)
+ + (xf->buf[0x3e] << 16)
+ + (xf->buf[0x3f] << 24);
+
+ if (xf->len < off + 0x18)
+ return 0;
+
+ if (xf->buf[off + 0] != 0x50 /* "PE" */
+ || xf->buf[off + 1] != 0x45
+ || xf->buf[off + 2] != 0x00
+ || xf->buf[off + 3] != 0x00)
+ return 0;
+
+ chars = xf->buf[off + 0x16] /* PE Characteristics */
+ + (xf->buf[off + 0x17] << 8);
+
+ /*
+ 0x0002 = executable (no unresolved refs)
+ 0x1000 = .sys driver
+ */
+
+ if (chars & 0x2000) /* It is a DLL */
+ return 1;
+
+ return 0;
+}
+
+/* Extract the .cab file from another file. */
+/* It's stored in the .msi uncompressed and contiguous, so we */
+/* just need to identify where it is and its length. */
+/* (This will work on any file that has the .cab file uncompressed and contiguous) */
+/* Return NULL if not found */
+static xfile *msi_extract(xfile **pxf, xfile *xi, char *tname, int verb) {
+ int i, j, k;
+ xfile *xf = NULL;
+ char *fid = "i1d3.xrdevice"; /* File in .cab to look for */
+ unsigned long fle = strlen(fid);
+ unsigned long cabo, cabsz;
+ size_t len;
+
+ if (verb) printf("Attempting to extract '%s' from '%s'\n",tname,xi->name);
+
+ /* Search for a filename in the .cab */
+ for (i = 0; i < (xi->len - fle - 2); i++) {
+ if (xi->buf[i + 0] == 0x00
+ && xi->buf[i + 1] == fid[0]
+ && strncmp((char *)xi->buf + i + 1, fid, fle) == 0) {
+ if (verb > 1) printf("Found file name '%s' in '%s' at 0x%x\n",fid,xi->name,i);
+ break;
+ }
+ }
+ if (i >= (xi->len - fle - 2)) {
+ if (verb) printf(".cab not found\n");
+ return NULL;
+ }
+
+ /* Search backwards for .cab signature */
+ for (; i >= 0; i--) {
+ if (xi->buf[i + 0] == 0x4d
+ && xi->buf[i + 1] == 0x53
+ && xi->buf[i + 2] == 0x43
+ && xi->buf[i + 3] == 0x46
+ && xi->buf[i + 4] == 0x00
+ && xi->buf[i + 5] == 0x00
+ && xi->buf[i + 6] == 0x00
+ && xi->buf[i + 7] == 0x00) {
+ if (verb > 1) printf("Found '%s' at 0x%x\n",tname,i);
+ break;
+ }
+ }
+ if (i < 0) {
+ if (verb) printf(".cab not found\n");
+ return NULL;
+ }
+
+ /* Lookup the .cab size (really 64 bit, but we don't care) */
+ len = buf2uint(xi->buf + i + 8);
+
+ if (verb > 1) printf("'%s' is length %ld\n",tname,len);
+
+ if ((xi->len - i) < len) {
+ if (verb) printf("Not enough room for .cab file in source\n");
+ return NULL;
+ }
+
+ xf = add_xf(pxf);
+ xf->len = len;
+
+ if ((xf->buf = malloc(xf->len)) == NULL) {
+ fprintf(stderr,"maloc of .cab buffer failed\n");
+ exit(-1);
+ }
+ memmove(xf->buf, xi->buf + i ,xf->len);
+
+ if ((xf->name = strdup(tname)) == NULL) {
+ fprintf(stderr,"maloc of .cab name failed\n");
+ exit(-1);
+ }
+
+ xf->ftype = file_dllcab;
+ xf->ttype = xi->ttype;
+
+ if (verb) printf("Extacted '%s' length %ld\n",xf->name,xf->len);
+
+ return xf;
+}
+
+
+/* ================================================================ */
+/* Extract files of a given type from a .cab file */
+
+/* Interface with inflate.c */
+/* We use globals for this */
+
+unsigned char *i_buf = NULL;
+unsigned long i_len = 0;
+unsigned long i_ix = 0;
+
+unsigned char *o_buf = NULL;
+unsigned long o_len = 0;
+unsigned long o_ix = 0;
+
+/* Interface to inflate */
+
+int inflate(void);
+
+/* fetch the next 8 bits */
+/* if we get 0xffffffff, we are at EOF */
+unsigned int inflate_get_byte() {
+ if (i_ix < i_len)
+ return i_buf[i_ix++];
+ return 0xff;
+}
+
+/* unget 8 bits */
+void inflate_unget_byte() {
+ if (i_ix > 0)
+ i_ix--;
+}
+
+/* Save the decompressed file to the buffer */
+int inflate_write_output(unsigned char *buf, unsigned int len) {
+ if ((o_ix + len) > o_len) {
+ fprintf(stderr,"Uncompressed buffer is unexpectedly large (%ld > %ld)!\n", o_ix + len, o_len);
+ return 1;
+ }
+ memmove(o_buf + o_ix, buf, len);
+ o_ix += len;
+ return 0;
+}
+
+/* Extract all the .edr files from the .cab */
+/* Returns the last file extracted, or NULL if none found */
+static xfile *cab_extract(xfile **pxf, xfile *xi, char *text, int verb) {
+ int i, j, k;
+ xfile *xf = NULL;
+ unsigned char *buf = xi->buf;
+ unsigned long len = xi->len;
+ unsigned long off;
+ unsigned long filesize, headeroffset, datastart;
+ int nofolders, nofiles, flags, comptype;
+ unsigned int totubytes;
+ int ufiles = 0;
+ unsigned char *obuf;
+ unsigned long olen;
+
+ if (verb) printf("Attempting to extract '*%s' from '%s'\n",text, xi->name);
+
+ /* Check it is a .cab file */
+ if (len < 0x2c
+ || buf[0] != 0x4d
+ || buf[1] != 0x53
+ || buf[2] != 0x43
+ || buf[3] != 0x46
+ || buf[4] != 0x00
+ || buf[5] != 0x00
+ || buf[6] != 0x00
+ || buf[7] != 0x00) {
+ fprintf(stderr,"'%s' is not a .cab file\n",xi->name);
+ exit(-1);
+ }
+
+ filesize = buf2uint(buf + 0x08);
+ headeroffset = buf2uint(buf + 0x10);
+ nofolders = buf2short(buf + 0x1a);
+ nofiles = buf2short(buf + 0x1c);
+ flags = buf2short(buf + 0x1e);
+
+ if (filesize != len) {
+ fprintf(stderr,"'%s' filesize desn't match\n",xi->name);
+ exit(-1);
+ }
+ if (nofolders != 1) {
+ fprintf(stderr,"'%s' has more than one folder\n",xi->name);
+ exit(-1);
+ }
+ if (flags != 0) {
+ fprintf(stderr,"'%s' has non-zero flags\n",xi->name);
+ exit(-1);
+ }
+
+ /* Read the first folders info (assumed flags == 0) */
+ datastart = buf2uint(buf + 0x24);
+ comptype = buf[0x2a];
+ if (comptype!= 1) {
+ fprintf(stderr,"'%s' doesn't use MSZip compression\n",xi->name);
+ exit(-1);
+ }
+
+ if (verb > 1) printf(".cab headeroffset = 0x%lx, datastart = 0x%lx, nofiles = %d\n",headeroffset,datastart,nofiles);
+
+ /* Look at each file */
+ for (off = headeroffset, k = 0; k < nofiles; k++) {
+ unsigned long fsize; /* Uncompressed size */
+ unsigned long foff;
+ short ffix;
+ char fname[95];
+
+ if (off > (len - 80)) {
+ fprintf(stderr,"'%s' too short for directory\n",xi->name);
+ exit(-1);
+ }
+
+ fsize = buf2uint(buf + off + 0x00);
+ foff = buf2uint(buf + off + 0x04);
+ ffix = buf2short(buf + off + 0x08);
+
+ strncpy(fname, (char *)buf + off + 0x10, 94);
+ fname[94] = '\000';
+
+ if (verb > 1) printf("file %d is '%s' at 0x%lx length %ld\n",k,fname, foff,fsize);
+
+ off += 0x10 + strlen(fname) + 1; /* Next entry */
+ }
+
+ /* Now come the data blocks */
+ totubytes = 0;
+ for (off = datastart, j = 0; ; j++) {
+ unsigned long chsum;
+ unsigned long cbytes;
+ unsigned long ubytes;
+
+ if (off > (len - 8)) {
+ if (verb > 1) printf("Got to end of data blocks at 0x%lx\n",off);
+ break;
+ }
+
+ chsum = buf2uint(buf + off + 0x00);
+ cbytes = buf2short(buf + off + 0x04);
+ ubytes = buf2short(buf + off + 0x06);
+
+ if (verb > 1) printf("Compression block %d, cbytes %ld, ubytes %ld\n",j,cbytes,ubytes);
+
+ totubytes += ubytes;
+
+ off += 8 + cbytes;
+ }
+
+
+ if (verb > 1) printf("Total uncompressed bytes = %d\n",totubytes);
+
+ olen = totubytes;
+ if ((obuf = malloc(olen)) == NULL) {
+ fprintf(stderr,"maloc of uncompressed output buffer failed\n");
+ exit(-1);
+ }
+
+ o_buf = obuf;
+ o_len = olen;
+ o_ix = 0;
+
+ for (off = datastart, j = 0; ; j++) {
+ unsigned long chsum;
+ unsigned long cbytes;
+ unsigned long ubytes;
+
+ if (off > (len - 8))
+ break;
+
+ chsum = buf2uint(buf + off + 0x00);
+ cbytes = buf2short(buf + off + 0x04);
+ ubytes = buf2short(buf + off + 0x06);
+
+ i_buf = buf + off + 8;
+ i_len = cbytes;
+ i_ix = 0;
+
+ /* MSZIP has a two byte signature at the start of each block */
+ if (inflate_get_byte() != 0x43 || inflate_get_byte() != 0x4B) {
+ printf(".cab block doesn't start with 2 byte MSZIP signature\n");
+ exit (-1);
+ }
+
+ if (inflate()) {
+ fprintf(stderr, "inflate of '%s' failed at i_ix 0x%lx, o_ix 0x%lx\n",xi->name,i_ix,o_ix);
+ exit (-1);
+ }
+
+ /* The history buffer is meant to survive from one block to the next. */
+ /* Not sure what that means, as it seems to work as is... */
+
+ off += 8 + cbytes;
+ }
+
+ xf = add_xf(pxf);
+
+ /* Create a buffer for each file */
+ for (ufiles = 0, off = headeroffset, k = 0; k < nofiles; k++) {
+ unsigned long fsize; /* Uncompressed size */
+ unsigned long foff;
+ short ffix;
+ char fname[95], *cp;
+ int namelen;
+
+ fsize = buf2uint(buf + off + 0x00);
+ foff = buf2uint(buf + off + 0x04);
+ ffix = buf2short(buf + off + 0x08);
+
+ strncpy(fname, (char *)buf + off + 0x10, 94);
+ fname[94] = '\000';
+ namelen = strlen(fname);
+
+ /* Lop of the junk in the filename */
+ if ((cp = strrchr(fname, '.')) != NULL)
+ *cp = '\000';
+
+ /* See if it's the type of file we want */
+ if ((cp = strrchr(fname, '.')) != NULL
+ && strcmp(cp, text) == 0) {
+ xfile *xx;
+
+ xf = xx = add_xf(pxf);
+
+ if (foff >= olen || (foff + fsize) > olen) {
+ fprintf(stderr,"file '%s' doesn't fit in decomressed buffer\n",fname);
+ exit(-1);
+ }
+ if ((xx->buf = malloc(fsize)) == NULL) {
+ fprintf(stderr,"maloc of file '%s' buffer len %ld failed\n",fname,fsize);
+ exit(-1);
+ }
+ xx->len = fsize;
+ memmove(xx->buf, obuf + foff, fsize);
+
+ if ((xx->name = strdup(fname)) == NULL) {
+ fprintf(stderr,"maloc of .edr name failed\n");
+ exit(-1);
+ }
+ xx->ftype = file_data;
+ xx->ttype = targ_i1d3_edr;
+ ufiles++;
+ }
+ off += 0x10 + namelen + 1; /* Next entry */
+ }
+
+ if (verb) printf("Found %d %s files out of %d files in .cab\n",ufiles, text, nofiles);
+
+ return xf;
+}
+
+
diff --git a/spectro/oemarch.h b/spectro/oemarch.h
new file mode 100644
index 0000000..72a3bb6
--- /dev/null
+++ b/spectro/oemarch.h
@@ -0,0 +1,102 @@
+
+#ifndef OEMARCH_H
+
+ /* OEM archive access library.
+ /* This supports installing OEM data files */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 13/11/2012
+ *
+ * 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.
+ *
+ */
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#define MAX_ARCH_LISTS 40
+
+/* Possible type of files/stage of processing */
+typedef enum {
+ file_none = 0x0000, /* None */
+ file_vol = 0x0001, /* Volume name */
+ file_arch = 0x0002, /* Archive file */
+ file_dllcab = 0x0004, /* .dll or .cab */
+ file_data = 0x0008 /* final data file */
+} file_type;
+
+/* Possible type of target */
+typedef enum {
+ targ_none = 0x0000, /* None */
+ targ_spyd_pld = 0x0001, /* Spyder PLD pattern */
+ targ_spyd_cal = 0x0002, /* Spyder spectral calibration */
+ targ_i1d3_edr = 0x0004, /* i1d3 .edr or .ccss */
+ targ_ccmx = 0x0008 /* .ccmx */
+} targ_type;
+
+/* An install path, volume name or archive name */
+typedef struct {
+ char *path;
+ targ_type ttype; /* Hint to target type */
+ file_type ftype; /* Hint to file type (instpaths only) */
+} oem_source;
+
+/* A definition of an install target */
+typedef struct {
+ oem_source instpaths[MAX_ARCH_LISTS]; /* Possible archive install paths, NULL terminated */
+ oem_source volnames[MAX_ARCH_LISTS]; /* Possible volume names, NULL terminated */
+ oem_source archnames[MAX_ARCH_LISTS]; /* Possible archive file names, NULL terminated */
+} oem_target;
+
+/* A list of files stored in memory. */
+typedef struct {
+ char *name; /* Name of file, NULL for last entry */
+ unsigned char *buf; /* It's contents */
+ size_t len; /* The length of the contents */
+ file_type ftype; /* Hint to file type */
+ targ_type ttype; /* Target type */
+} xfile;
+
+/* return a list with the given number of available entries */
+xfile *new_xf(int n);
+
+/* Add an entry to the list. Create the list if it is NULL */
+/* Return point to that entry */
+xfile *add_xf(xfile **l);
+
+/* Append an entry and copy details to the list. Create the list if it is NULL */
+/* Return point to that entry */
+xfile *new_add_xf(xfile **l, char *name, unsigned char *buf, unsigned long len,
+ file_type ftype, targ_type ttype);
+
+/* Free up a whole list */
+void del_xf(xfile *l);
+
+/* Allocate and load the given entry. */
+/* Return NZ on error */
+int load_xfile(xfile *xf, int verb);
+
+/* Save a file to the given sname, or of it is NULL, */
+/* to prefix + file name (path is ignored) */
+/* Error on failure */
+void save_xfile(xfile *xf, char *sname, char *pfx, int verb);
+
+/* Given a list of source archives or files, convert them to a list of install files, */
+/* or if no files are given look for installed files or files from a CD. */
+/* Return NULL if none found. files is deleted. */
+xfile *oemarch_get_ifiles(xfile *files, int verb);
+
+#ifdef __cplusplus
+ }
+#endif
+
+#define OEMARCH_H
+#endif /* OEMARCH_H */
diff --git a/spectro/oeminst.c b/spectro/oeminst.c
new file mode 100644
index 0000000..88efae0
--- /dev/null
+++ b/spectro/oeminst.c
@@ -0,0 +1,276 @@
+/*
+ * Argyll Color Correction System
+ *
+ * OEM data file installer.
+ *
+ * Author: Graeme W. Gill
+ * Date: 13/11/2012
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* SALONEINSTLIB */
+#include <fcntl.h>
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* SALONEINSTLIB */
+#include "xdg_bds.h"
+#include "conv.h"
+#include "aglob.h"
+#include "oemarch.h"
+#include "xspect.h"
+#include "ccmx.h"
+#include "ccss.h"
+
+void usage(void) {
+ fprintf(stderr,"Install OEM data files, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the GPL Version 2 or later\n");
+ fprintf(stderr,"usage: oeminst [-options] [infile(s)]\n");
+ fprintf(stderr," -v Verbose\n");
+ fprintf(stderr," -n Don't install, show where files would be installed\n");
+ fprintf(stderr," -c Don't install, save files to current directory\n");
+ fprintf(stderr," -S d Specify the install scope u = user (def.), l = local system]\n");
+ fprintf(stderr," infile setup.exe CD install file(s) or .dll(s) containing install files\n");
+ fprintf(stderr," infile.[edr|ccss|ccmx] EDR file(s) to translate and install or CCSS or CCMX files to install\n");
+ fprintf(stderr," If no file is provided, oeminst will look for the install CD.\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[]) {
+ int fa,nfa; /* argument we're looking at */
+ xdg_scope scope = xdg_user;
+ int verb = 0;
+ int install = 1;
+ int local = 0;
+ int has_ccss = 0;
+ char *install_dir = "";
+ int i;
+ xfile *files = NULL;
+ int rv = 0;
+
+ set_exe_path(argv[0]); /* Set global exe_path and error_program */
+
+ if (argc < 1)
+ usage();
+
+ /* Process the arguments */
+ for(fa = 1;fa < argc;fa++) {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage();
+
+ /* Verbosity */
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') {
+ verb = 1;
+ }
+
+ /* No install */
+ else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') {
+ install = 0;
+ }
+
+ /* Save to current directory */
+ else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') {
+ local = 1;
+ }
+
+ /* Install scope */
+ else if (argv[fa][1] == 'S') {
+ fa = nfa;
+ if (na == NULL) usage();
+ else if (na[0] == 'l' || na[0] == 'L')
+ scope = xdg_local;
+ else if (na[0] == 'u' || na[0] == 'U')
+ scope = xdg_user;
+ }
+ else
+ usage();
+ }
+ else
+ break;
+ }
+
+ if (fa > argc || (fa < argc && argv[fa][0] == '-')) usage();
+
+ /* If filename(s) are provided, load the files up. */
+ /* We don't know what they are, but oemarch_get_ifiles() will figure it out */
+ for (; fa < argc; fa++) {
+ xfile *xf;
+ xf = new_add_xf(&files, argv[fa], NULL, 0, file_arch | file_dllcab | file_data,
+ targ_spyd_pld | targ_spyd_cal
+ | targ_i1d3_edr | targ_ccmx);
+ if (load_xfile(xf, verb))
+ error("Unable to load file '%s'",xf->name);
+ }
+
+ /* Turn files into installables */
+ if ((files = oemarch_get_ifiles(files, verb)) == NULL)
+ error("Didn't locate any files to install - no CD present ?\n");
+
+ /* If there are any ccss files to install, supliment with any found in ../ref */
+ for (i = 0; files[i].name != NULL; i++) {
+ if (files[i].ftype == file_data
+ && files[i].ttype == targ_i1d3_edr)
+ has_ccss = 1;
+ }
+ if (has_ccss) {
+ int len;
+ char *pp;
+ char tname[MAXNAMEL+1] = { '\000' };
+ aglob ag;
+
+ strcpy(tname, exe_path);
+
+ len = strlen(tname);
+ if ((pp = strrchr(tname, '/')) != NULL)
+ *pp = '\000';
+ if ((pp = strrchr(tname, '/')) != NULL) {
+ strcpy(pp, "/ref/*.ccss");
+
+ if (aglob_create(&ag, tname))
+ error("Searching for '%s' malloc error",tname);
+
+ for (;;) {
+ char *cp;
+ xfile *xf;
+ if ((pp = aglob_next(&ag)) == NULL)
+ break;
+
+ /* Leave just base filename */
+ if ((cp = strrchr(pp, '/')) == NULL
+ && (cp = strrchr(pp, '\\')) == NULL)
+ cp = pp;
+ else
+ cp++;
+
+ xf = new_add_xf(&files, pp, NULL, 0, file_data, targ_i1d3_edr);
+ if (load_xfile(xf, verb))
+ error("Unable to load file '%s'",xf->name);
+ free(xf->name);
+ if ((xf->name = strdup(cp)) == NULL)
+ error("strdup failed");
+ }
+ aglob_cleanup(&ag);
+ }
+ }
+
+#ifdef NEVER
+ for (i = 0; files[i].name != NULL; i++) {
+ printf("Got '%s' size %d ftype 0x%x ttype 0x%x to install\n",files[i].name,files[i].len,files[i].ftype,files[i].ttype);
+ }
+#endif
+
+ /* Do a special check of ccmx and ccss files, to warn if there will be problems with them */
+ for (i = 0; files[i].name != NULL; i++) {
+ xfile *xf = &files[i];
+
+ if (xf->ttype & targ_ccmx) {
+ ccmx *cx;
+ if ((cx = new_ccmx()) == NULL)
+ error("new_ccmx failed");
+ if (cx->buf_read_ccmx(cx, xf->buf, xf->len)) {
+ error("Reading '%s' failed with '%s'\n",xf->name,cx->err);
+ }
+ if (cx->cbid <= 0)
+ error("'%s' doesn't contain DISPLAY_TYPE_BASE_ID field :- it can't be installed without this!",xf->name);
+ if (cx->refrmode < 0)
+ warning("'%s' doesn't contain DISPLAY_TYPE_REFRESH field :- non-refresh will be assumed!",xf->name);
+ cx->del(cx);
+ } else if (xf->ttype & targ_i1d3_edr) {
+ ccss *ss;
+ if ((ss = new_ccss()) == NULL)
+ error("new_ccss failed");
+ if (ss->buf_read_ccss(ss, xf->buf, xf->len)) {
+ error("Reading '%s' failed with '%s'\n",xf->name,ss->err);
+ }
+ if (ss->refrmode < 0)
+ warning("'%s' doesn't contain DISPLAY_TYPE_REFRESH field :- non-refresh will be assumed!",xf->name);
+ ss->del(ss);
+ }
+ }
+
+ /* We now have all the install files loaded into files. Save or install them all */
+
+ if (!local) /* Install them in ArgyllCMS sub-directory */
+ install_dir = "ArgyllCMS/";
+
+ /* Save all the install file */
+ for (i = 0; files[i].name != NULL; i++) {
+ xfile *xf = &files[i];
+ size_t len;
+ char *install_name = NULL, *cp;
+
+ if (xf->ftype != file_data) {
+ if (verb) printf("Skipping '%s' as its type is unknown\n",xf->name);
+ continue;
+ }
+
+ /* Create install path and name */
+ len = strlen(install_dir) + strlen(xf->name);
+ if ((install_name = malloc(len)) == NULL)
+ error("malloc install_name %d bytes failed",len);
+
+ strcpy(install_name, install_dir);
+
+ /* Append just the basename of the xf */
+ if ((cp = strrchr(xf->name, '/')) == NULL
+ && (cp = strrchr(xf->name, '\\')) == NULL)
+ cp = xf->name;
+ else
+ cp++;
+
+ strcat(install_name, cp);
+
+ if (!local) {
+ char **paths = NULL;
+ int npaths = 0;
+
+ /* Get destination path. This may drop uid/gid if we are su */
+ if ((npaths = xdg_bds(NULL, &paths, xdg_data, xdg_write, scope, install_name)) < 1) {
+ error("Failed to find/create XDG_DATA path '%s'",install_name);
+ }
+ if (install)
+ save_xfile(xf, paths[0], NULL, verb);
+ else
+ printf("Would install '%s'\n",paths[0]);
+ xdg_free(paths, npaths);
+
+ } else {
+ if (install)
+ save_xfile(xf, install_name, NULL, verb);
+ else
+ printf("Would save '%s'\n",install_name);
+ }
+ free(install_name);
+ }
+
+ del_xf(files);
+
+ return 0;
+}
diff --git a/spectro/pollem.c b/spectro/pollem.c
new file mode 100644
index 0000000..f7578c8
--- /dev/null
+++ b/spectro/pollem.c
@@ -0,0 +1,111 @@
+
+ /* Unix serial I/O class poll() emulation. */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 12/9/2004
+ *
+ * Copyright 2004, 2010 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.
+ */
+
+#ifdef UNIX
+
+/* Fake up poll() support on systems that only support select() */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <memory.h>
+
+/* select() defined, but not poll(), so emulate poll() */
+#if defined(FD_CLR) && !defined(POLLIN)
+
+#include <sys/time.h>
+
+#include "pollem.h"
+
+int pollem(struct pollfd *fds, unsigned long nfds, int timeout) {
+ int i, nfd;
+ fd_set rd_ary; /* Select array for read file descriptors */
+ fd_set wr_ary; /* Select array for write file descriptors */
+ fd_set ex_ary; /* Select array for exception file descriptors */
+ struct timeval tv; /* Timeout value */
+ struct timeval *ptv = &tv; /* Pointer to above */
+ int result; /* Select return value */
+
+ /* Translate files and events */
+ FD_ZERO(&rd_ary);
+ FD_ZERO(&wr_ary);
+ FD_ZERO(&ex_ary);
+
+ for (i = nfd = 0; i < nfds; i++) {
+ fds[i].revents = 0;
+
+ if (fds[i].events & POLLIN) {
+ FD_SET(fds[i].fd, &rd_ary);
+ if (fds[i].fd > nfd)
+ nfd = fds[i].fd;
+ }
+
+ if (fds[i].events & POLLPRI) {
+ FD_SET(fds[i].fd, &ex_ary);
+ if (fds[i].fd > nfd)
+ nfd = fds[i].fd;
+ }
+
+ if (fds[i].events & POLLOUT) {
+ FD_SET(fds[i].fd, &wr_ary);
+ if (fds[i].fd > nfd)
+ nfd = fds[i].fd;
+ }
+ }
+ nfd++; /* Maximum file desciptor index + 1 */
+
+ /* Translate timeout */
+ if (timeout == -1) {
+ ptv = NULL; /* Wait forever */
+
+ } else if (timeout == 0) {
+ tv.tv_sec = 0; /* Return imediately */
+ tv.tv_usec = 0;
+
+ } else { /* Convert milliseconds to seconds and useconds */
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout - tv.tv_sec * 1000) * 1000;
+ }
+
+ /* Do the select */
+ if ((result = select(nfd, &rd_ary, &wr_ary, &ex_ary, ptv)) > 0) {
+
+ for (i = 0; i < nfds; i++) {
+ fds[i].revents = 0;
+
+ if (FD_ISSET(fds[i].fd, &ex_ary))
+ fds[i].revents |= POLLPRI;
+
+ if (FD_ISSET(fds[i].fd, &rd_ary))
+ fds[i].revents |= POLLIN;
+
+ if (FD_ISSET(fds[i].fd, &wr_ary))
+ fds[i].revents |= POLLOUT;
+ }
+ }
+
+ return result;
+}
+
+#endif /* FD_CLR */
+
+#endif /* UNIX */
diff --git a/spectro/pollem.h b/spectro/pollem.h
new file mode 100644
index 0000000..f8a86ee
--- /dev/null
+++ b/spectro/pollem.h
@@ -0,0 +1,50 @@
+
+#ifndef POLLEN_H
+
+ /* Unix serial I/O class poll() emulation. */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 12/9/2004
+ *
+ * Copyright 2004, 2010 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.
+ */
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#ifdef UNIX
+
+/* Fake up poll() support on systems that only support select() */
+
+/* Fake poll array structure */
+struct pollfd {
+ int fd; /* File descriptor */
+ short events; /* Requested events */
+ short revents; /* Returned events */
+};
+
+/* Fake Poll flag values supported */
+#define POLLIN 0x01
+#define POLLPRI 0x02
+#define POLLOUT 0x04
+
+/* Timeout is in milliseconds, -1 == wait forever */
+int pollem(struct pollfd fds[], unsigned long nfds, int timeout);
+
+#define POLLEN_H
+#endif /* POLLEN_H */
+
+#endif /* UNIX */
+
+#ifdef __cplusplus
+ }
+#endif
+
diff --git a/spectro/spec2cie.c b/spectro/spec2cie.c
new file mode 100644
index 0000000..99fe898
--- /dev/null
+++ b/spectro/spec2cie.c
@@ -0,0 +1,831 @@
+
+/*
+ * Argyll Color Correction System
+ * Spectral .ti3 file converter
+ *
+ * Copyright 2005 Gerhard Fuernkranz
+ * All rights reserved.
+ *
+ * Copyright 2006, 2007 Graeme Gill.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * See the License2.txt file for details.
+ *
+ * Pursuant to the above, this file is licenced in ArgyllCMS
+ * under the GNU GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ *
+ * This program takes the spectral data in a .ti3 file, converts them
+ * to XYZ and Lab and fills the XYZ_[XYZ] and LAB_[LAB] columns in the
+ * output .ti3 file with the computed XYZ and Lab values. If the columns
+ * XYZ_[XYZ] and/or LAB_[LAB] are missing in the input file, they are
+ * added to the output file.
+ *
+ * All other colums are copied from the input to the output .ti3 file.
+ *
+ * If the -f option is used, the FWA corrected spectral reflectances
+ * are written to the output .ti3 file, instead of simply copying the
+ * spectral reflectances from the input .ti3 file. In this case, the
+ * XYZ_[XYZ] and LAB_[LAB] values are computed from the FWA corrected
+ * reflectances as well.
+ */
+
+/*
+ NOTE this uses hard coded space signatures, and should be converted
+ to use xcolorants instead.
+
+ This doesn't handle display .ti3's properly, as the resulting CIE values
+ need to be normalised to Y=100 or marked as not normalised.
+
+ Calibration tables aren't being passed through either ??
+ */
+
+#define ALLOW_PLOT
+#define XRES 200
+
+
+#include <stdio.h>
+#if defined(__IBMC__)
+#include <float.h>
+#endif
+#include "aconfig.h"
+#include "numlib.h"
+#include "cgats.h"
+#include "xicc.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "inst.h"
+#ifdef ALLOW_PLOT
+#include "plot.h"
+#endif
+
+
+void
+usage (void)
+{
+ fprintf (stderr, "Convert spectral .ti3 file, Version %s\n", ARGYLL_VERSION_STR);
+ fprintf (stderr, "Author: Gerhard Fuernkranz, licensed under the AGPL Version 3\n");
+ fprintf (stderr, "\n");
+ fprintf (stderr, "Usage: spec2cie [options] input.ti3 output.ti3\n");
+ fprintf (stderr, " -v Verbose mode\n");
+ fprintf (stderr, " -I illum Override actual instrument illuminant in .ti3 file:\n");
+ fprintf (stderr, " A, C, D50, D50M2, D65, F5, F8, F10 or file.sp\n");
+ fprintf (stderr, " (only used in conjunction with -f)\n");
+ fprintf (stderr, " -f [illum] Use Fluorescent Whitening Agent compensation [simulated inst. illum.:\n");
+ fprintf (stderr, " M0, M1, M2, A, C, D50 (def.), D50M2, D65, F5, F8, F10 or file.sp]\n");
+ fprintf (stderr, " -i illum Choose illuminant for computation of CIE XYZ from spectral data & FWA:\n");
+ fprintf (stderr, " A, C, D50 (def.), D50M2, D65, F5, F8, F10 or file.sp\n");
+ fprintf (stderr, " -o observ Choose CIE Observer for spectral data:\n");
+ fprintf (stderr, " 1931_2 (def), 1964_10, S&B 1955_2, shaw, J&V 1978_2\n");
+ fprintf (stderr, " -n Don't output spectral values\n");
+#ifdef ALLOW_PLOT
+ fprintf (stderr, " -p Plot each values spectrum\n");
+#endif
+ fprintf (stderr, " input.ti3 Measurement file\n");
+ fprintf (stderr, " output.ti3 Converted measurement file\n");
+ exit (1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fa, nfa; /* current argument we're looking at */
+ int verb = 0;
+ int nospec = 0; /* NZ if not to output spectral values */
+ char *in_ti3_name;
+ char *out_ti3_name;
+ cgats *icg; /* input cgats structure */
+ cgats *ocg; /* output cgats structure */
+ cgats_set_elem *elems;
+
+ int isdisp = 0; /* nz if this is a display device */
+ int isdnormed = 0; /* Has display data been normalised to 100 ? */
+ icColorSpaceSignature devspace = icmSigDefaultData; /* The device colorspace */
+ int isAdditive = 0; /* 0 if subtractive, 1 if additive colorspace */
+ int isInverted = 0; /* nz if inverted real device */
+ int ci, mi, yi, ki; /* Indexes of device values */
+ int fwacomp = 0; /* FWA compensation */
+ int doplot = 0; /* Plot each patches spectrum */
+ char* illum_str = "D50";
+ icxIllumeType tillum = icxIT_none; /* Target/simulated instrument illuminant */
+ xspect cust_tillum, *tillump = NULL; /* Custom target/simulated illumination spectrum */
+ icxIllumeType illum = icxIT_D50; /* Spectral defaults */
+ xspect cust_illum; /* Custom illumination spectrum */
+ icxIllumeType inst_illum = icxIT_none; /* Spectral defaults */
+ xspect inst_cust_illum; /* Custom illumination spectrum */
+ icxObserverType observ = icxOT_CIE_1931_2;
+
+ int npat; /* Number of patches */
+ char *kw;
+ int i, j, jj, k;
+
+#if defined(__IBMC__)
+ _control87 (EM_UNDERFLOW, EM_UNDERFLOW);
+ _control87 (EM_OVERFLOW, EM_OVERFLOW);
+#endif
+
+ if (argc < 3)
+ usage ();
+
+ /* Process the arguments */
+ for (fa = 1; fa < argc; fa++) {
+
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa + 1) < argc) {
+ if (argv[fa + 1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage ();
+
+ /* Verbose */
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V')
+ verb = 1;
+
+ /* Don't output spectral */
+ else if (argv[fa][1] == 'n' || argv[fa][1] == 'N')
+ nospec = 1;
+
+ /* Plot each patch spectral value */
+ else if (argv[fa][1] == 'p' || argv[fa][1] == 'P')
+ doplot = 1;
+
+ /* Instrument Illuminant type */
+ else if (argv[fa][1] == 'I') {
+ fa = nfa;
+ if (na == NULL)
+ usage ();
+ if (strcmp (na, "A") == 0) {
+ inst_illum = icxIT_A;
+ }
+ else if (strcmp (na, "C") == 0) {
+ inst_illum = icxIT_C;
+ }
+ else if (strcmp (na, "D50") == 0) {
+ inst_illum = icxIT_D50;
+ }
+ else if (strcmp (na, "D50M2") == 0) {
+ inst_illum = icxIT_D50M2;
+ }
+ else if (strcmp (na, "D65") == 0) {
+ inst_illum = icxIT_D65;
+ }
+ else if (strcmp (na, "F5") == 0) {
+ inst_illum = icxIT_F5;
+ }
+ else if (strcmp (na, "F8") == 0) {
+ inst_illum = icxIT_F8;
+ }
+ else if (strcmp (na, "F10") == 0) {
+ inst_illum = icxIT_F10;
+ }
+ else { /* Assume it's a filename */
+ inst_illum = icxIT_custom;
+ if (read_xspect (&inst_cust_illum, na) != 0)
+ usage ();
+ }
+ }
+
+ /* FWA comp & simulated instrument illuminant */
+ else if (argv[fa][1] == 'f') {
+ fwacomp = 1;
+
+ if (na != NULL) { /* Argument is present - target/simulated instr. illum. */
+ fa = nfa;
+ if (strcmp(na, "A") == 0
+ || strcmp(na, "M0") == 0) {
+ tillum = icxIT_A;
+ } else if (strcmp(na, "C") == 0) {
+ tillum = icxIT_C;
+ } else if (strcmp(na, "D50") == 0
+ || strcmp(na, "M1") == 0) {
+ tillum = icxIT_D50;
+ } else if (strcmp(na, "D50M2") == 0
+ || strcmp(na, "M2") == 0) {
+ tillum = icxIT_D50M2;
+ } else if (strcmp(na, "D65") == 0) {
+ tillum = icxIT_D65;
+ } else if (strcmp(na, "F5") == 0) {
+ tillum = icxIT_F5;
+ } else if (strcmp(na, "F8") == 0) {
+ tillum = icxIT_F8;
+ } else if (strcmp(na, "F10") == 0) {
+ tillum = icxIT_F10;
+ } else { /* Assume it's a filename */
+ tillum = icxIT_custom;
+ if (read_xspect(&cust_tillum, na) != 0)
+ usage();
+ }
+ }
+ }
+
+ /* CIE tristimulous spectral Illuminant type */
+ else if (argv[fa][1] == 'i') {
+ fa = nfa;
+ if (na == NULL)
+ usage ();
+ illum_str = na;
+ if (strcmp (na, "A") == 0) {
+ illum = icxIT_A;
+ }
+ else if (strcmp (na, "C") == 0) {
+ illum = icxIT_C;
+ }
+ else if (strcmp (na, "D50") == 0) {
+ illum = icxIT_D50;
+ }
+ else if (strcmp (na, "D50M2") == 0) {
+ illum = icxIT_D50M2;
+ }
+ else if (strcmp (na, "D65") == 0) {
+ illum = icxIT_D65;
+ }
+ else if (strcmp (na, "F5") == 0) {
+ illum = icxIT_F5;
+ }
+ else if (strcmp (na, "F8") == 0) {
+ illum = icxIT_F8;
+ }
+ else if (strcmp (na, "F10") == 0) {
+ illum = icxIT_F10;
+ }
+ else { /* Assume it's a filename */
+ illum = icxIT_custom;
+ if (read_xspect (&cust_illum, na) != 0)
+ usage ();
+ }
+ }
+
+ /* Spectral Observer type */
+ else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') {
+ fa = nfa;
+ if (na == NULL)
+ usage ();
+ if (strcmp (na, "1931_2") == 0) { /* Classic 2 degree */
+ observ = icxOT_CIE_1931_2;
+ }
+ else if (strcmp (na, "1964_10") == 0) { /* Classic 10 degree */
+ observ = icxOT_CIE_1964_10;
+ }
+ else if (strcmp (na, "1955_2") == 0) { /* Stiles and Burch 1955 2 degree */
+ observ = icxOT_Stiles_Burch_2;
+ }
+ else if (strcmp (na, "1978_2") == 0) { /* Judd and Voss 1978 2 degree */
+ observ = icxOT_Judd_Voss_2;
+ }
+ else if (strcmp (na, "shaw") == 0) { /* Shaw and Fairchilds 1997 2 degree */
+ observ = icxOT_Shaw_Fairchild_2;
+ }
+ else
+ usage ();
+ }
+
+ else
+ usage ();
+ }
+ else
+ break;
+ }
+
+ /* Get the file name arguments */
+ if (fa >= argc || argv[fa][0] == '-')
+ usage ();
+
+ in_ti3_name = argv[fa++];
+
+ if (fa >= argc || argv[fa][0] == '-')
+ usage ();
+
+ out_ti3_name = argv[fa++];
+
+ /* Open and look at the .ti3 profile patches file */
+
+ icg = new_cgats (); /* Create a CGATS structure */
+ icg->add_other (icg, "CTI3"); /* Calibration Target Information 3 */
+
+ ocg = new_cgats (); /* Create a CGATS structure */
+ ocg->add_other (ocg, "CTI3"); /* Calibration Target Information 3 */
+
+ if (icg->read_name (icg, in_ti3_name))
+ error ("CGATS file read error: %s", icg->err);
+
+ if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0)
+ error ("Input file isn't a CTI3 format file");
+ if (icg->ntables < 1)
+ error ("Input file doesn't contain at least one table");
+
+ /* add table to output file */
+
+ ocg->add_table(ocg, tt_other, 0);
+
+ /* copy keywords */
+
+ for (i = 0; i < icg->t[0].nkwords; i++) {
+ kw = icg->t[0].ksym[i];
+ if (fwacomp && strcmp(kw, "TARGET_INSTRUMENT") == 0) {
+ /*
+ * overwrite TARGET_INSTRUMENT with the new illuminant,
+ * since the FWA corrected spectral data are no longer
+ * valid for the original instrument's illuminant
+ */
+ ocg->add_kword (ocg, 0, kw, illum_str, NULL);
+ }
+ else {
+ ocg->add_kword (ocg, 0, kw,
+ icg->t[0].kdata[i], icg->t[0].kcom[i]);
+ }
+ }
+
+ /* Figure out what sort of device it is */
+
+ {
+ int ti;
+
+ if ((ti = icg->find_kword (icg, 0, "DEVICE_CLASS")) < 0)
+ error ("Input file doesn't contain keyword DEVICE_CLASS");
+
+ if (strcmp (icg->t[0].kdata[ti], "DISPLAY") == 0) {
+ isdisp = 1;
+ }
+
+ /* See if the display CIE data has been normalised to Y = 100 */
+ if ((ti = icg->find_kword(icg, 0, "NORMALIZED_TO_Y_100")) < 0
+ || strcmp(icg->t[0].kdata[ti],"NO") == 0) {
+ isdnormed = 0;
+ } else {
+ isdnormed = 1;
+ }
+
+ if (isdisp && fwacomp) {
+ error ("FWA compensation cannot be used for DISPLAY devices");
+ }
+
+ if ((ti = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
+ error("Input file doesn't contain keyword COLOR_REP");
+
+ if (strncmp(icg->t[0].kdata[ti],"CMYK_",5) == 0)
+ devspace = icSigCmykData;
+ else if (strncmp(icg->t[0].kdata[ti],"CMY_",4) == 0)
+ devspace = icSigCmyData;
+ else if (strncmp(icg->t[0].kdata[ti],"RGB_",4) == 0)
+ devspace = icSigRgbData;
+ else if (strncmp(icg->t[0].kdata[ti],"iRGB_",4) == 0) {
+ devspace = icSigRgbData;
+ isInverted = 1;
+ } else if (strncmp(icg->t[0].kdata[ti],"K_",2) == 0) {
+ devspace = icSigGrayData;
+ isAdditive = 0;
+ } else if (strncmp(icg->t[0].kdata[ti],"W_",2) == 0) {
+ devspace = icSigGrayData;
+ isAdditive = 1;
+ } else
+ error("Device has unhandled color representation '%s'",icg->t[0].kdata[ti]);
+
+ if (devspace == icSigGrayData) {
+
+ if (isAdditive) {
+ if ((ci = icg->find_field(icg, 0, "GRAY_W")) < 0)
+ error("Input file doesn't contain field GRAY_W");
+ if (icg->t[0].ftype[ci] != r_t)
+ error("Field GRAY_W is wrong type - corrupted file ?");
+ } else {
+ if ((ci = icg->find_field(icg, 0, "GRAY_K")) < 0)
+ error("Input file doesn't contain field GRAY_K");
+ if (icg->t[0].ftype[ci] != r_t)
+ error("Field GRAY_K is wrong type - corrupted file ?");
+ }
+ mi = yi = ki = ci;
+
+ } else if (devspace == icSigRgbData) {
+
+ if ((ci = icg->find_field(icg, 0, "RGB_R")) < 0)
+ error("Input file doesn't contain field RGB_R");
+ if (icg->t[0].ftype[ci] != r_t)
+ error("Field RGB_R is wrong type - corrupted file ?");
+ if ((mi = icg->find_field(icg, 0, "RGB_G")) < 0)
+ error("Input file doesn't contain field RGB_G");
+ if (icg->t[0].ftype[mi] != r_t)
+ error("Field RGB_G is wrong type - corrupted file ?");
+ if ((yi = icg->find_field(icg, 0, "RGB_B")) < 0)
+ error("Input file doesn't contain field RGB_B");
+ if (icg->t[0].ftype[yi] != r_t)
+ error("Field RGB_B is wrong type - corrupted file ?");
+ ki = yi;
+
+ } else if (devspace == icSigCmyData) {
+
+ if ((ci = icg->find_field(icg, 0, "CMY_C")) < 0)
+ error("Input file doesn't contain field CMY_C");
+ if (icg->t[0].ftype[ci] != r_t)
+ error("Field CMY_C is wrong type - corrupted file ?");
+ if ((mi = icg->find_field(icg, 0, "CMY_M")) < 0)
+ error("Input file doesn't contain field CMY_M");
+ if (icg->t[0].ftype[mi] != r_t)
+ error("Field CMY_M is wrong type - corrupted file ?");
+ if ((yi = icg->find_field(icg, 0, "CMY_Y")) < 0)
+ error("Input file doesn't contain field CMY_Y");
+ if (icg->t[0].ftype[yi] != r_t)
+ error("Field CMY_Y is wrong type - corrupted file ?");
+ ki = yi;
+
+ } else { /* Assume CMYK */
+
+ if ((ci = icg->find_field(icg, 0, "CMYK_C")) < 0)
+ error("Input file doesn't contain field CMYK_C");
+ if (icg->t[0].ftype[ci] != r_t)
+ error("Field CMYK_C is wrong type - corrupted file ?",icg->t[0].ftype[ci],r_t);
+ if ((mi = icg->find_field(icg, 0, "CMYK_M")) < 0)
+ error("Input file doesn't contain field CMYK_M");
+ if (icg->t[0].ftype[mi] != r_t)
+ error("Field CMYK_M is wrong type - corrupted file ?");
+ if ((yi = icg->find_field(icg, 0, "CMYK_Y")) < 0)
+ error("Input file doesn't contain field CMYK_Y");
+ if (icg->t[0].ftype[yi] != r_t)
+ error("Field CMYK_Y is wrong type - corrupted file ?");
+ if ((ki = icg->find_field(icg, 0, "CMYK_K")) < 0)
+ error("Input file doesn't contain field CMYK_K");
+ if (icg->t[0].ftype[ki] != r_t)
+ error("Field CMYK_K is wrong type - corrupted file ?");
+ }
+ }
+
+ if ((npat = icg->t[0].nsets) <= 0)
+ error ("No sets of data");
+
+ /* Read in the CGATs fields */
+
+ {
+ int sidx; /* Sample ID index */
+ int ti, ii, Xi, Yi, Zi, Li, ai, bi;
+ int spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */
+ xsp2cie *sp2cie; /* Spectral conversion object */
+ xspect sp;
+ double XYZ[3];
+ double Lab[3];
+ char buf[100];
+ /* These are only set if fwa is needed */
+ xspect rmwsp; /* Raw medium white spectrum */
+ xspect mwsp; /* FWA compensated medium white spectrum */
+ double mwXYZ[3]; /* Media white XYZ */
+
+ if ((sidx = icg->find_field (icg, 0, "SAMPLE_ID")) < 0)
+ error ("Input file doesn't contain field SAMPLE_ID");
+ if (icg->t[0].ftype[sidx] != nqcs_t)
+ error ("Field SAMPLE_ID is wrong type");
+
+ /* Using spectral data */
+
+ if ((ii = icg->find_kword (icg, 0, "SPECTRAL_BANDS")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_BANDS");
+ sp.spec_n = atoi (icg->t[0].kdata[ii]);
+ if ((ii = icg->find_kword (icg, 0, "SPECTRAL_START_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_START_NM");
+ sp.spec_wl_short = atof (icg->t[0].kdata[ii]);
+ if ((ii = icg->find_kword (icg, 0, "SPECTRAL_END_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_END_NM");
+ sp.spec_wl_long = atof (icg->t[0].kdata[ii]);
+ if (!isdisp || isdnormed != 0)
+ sp.norm = 100.0;
+ else
+ sp.norm = 1.0;
+
+ /* Find the fields for spectral values */
+ for (j = 0; j < sp.spec_n; j++) {
+ int nm;
+
+ /* Compute nearest integer wavelength */
+ nm = (int) (sp.spec_wl_short +
+ ((double) j / (sp.spec_n - 1.0))
+ * (sp.spec_wl_long - sp.spec_wl_short) + 0.5);
+
+ sprintf (buf, "SPEC_%03d", nm);
+
+ if ((spi[j] = icg->find_field (icg, 0, buf)) < 0)
+ error ("Input file doesn't contain field %s", buf);
+ }
+
+ if (isdisp) {
+ illum = icxIT_none; /* Displays are assumed to be self luminous */
+ }
+
+ /* copy fields to output file (except spectral if nospec) */
+ for (i = 0; i < icg->t[0].nfields; i++) {
+ /* See if this is a input spectral field */
+ for (j = 0; nospec && j < sp.spec_n; j++) {
+ if (spi[j] == i)
+ break; /* Yes it is */
+ }
+ if (nospec == 0 || j >= sp.spec_n)
+ ocg->add_field (ocg, 0, icg->t[0].fsym[i], icg->t[0].ftype[i]);
+ }
+
+ /* create field for XYZ and Lab if not present */
+
+ if ((Xi = icg->find_field(icg, 0, "XYZ_X")) < 0)
+ if ((Xi = ocg->add_field(ocg, 0, "XYZ_X", r_t)) < 0)
+ error ("Cannot add field to table");
+
+ if ((Yi = icg->find_field(icg, 0, "XYZ_Y")) < 0)
+ if ((Yi = ocg->add_field(ocg, 0, "XYZ_Y", r_t)) < 0)
+ error ("Cannot add field to table");
+
+ if ((Zi = icg->find_field(icg, 0, "XYZ_Z")) < 0)
+ if ((Zi = ocg->add_field(ocg, 0, "XYZ_Z", r_t)) < 0)
+ error ("Cannot add field to table");
+
+ if ((Li = icg->find_field(icg, 0, "LAB_L")) < 0)
+ if ((Li = ocg->add_field(ocg, 0, "LAB_L", r_t)) < 0)
+ error ("Cannot add field to table");
+
+ if ((ai = icg->find_field(icg, 0, "LAB_A")) < 0)
+ if ((ai = ocg->add_field(ocg, 0, "LAB_A", r_t)) < 0)
+ error ("Cannot add field to table");
+
+ if ((bi = icg->find_field(icg, 0, "LAB_B")) < 0)
+ if ((bi = ocg->add_field(ocg, 0, "LAB_B", r_t)) < 0)
+ error ("Cannot add field to table");
+
+ /* allocate elements */
+
+ if ((elems = (cgats_set_elem *)
+ calloc(ocg->t[0].nfields, sizeof(cgats_set_elem))) == NULL)
+ {
+ error("Out of memory");
+ }
+
+ /* Create a spectral conversion object */
+ if ((sp2cie = new_xsp2cie (illum,
+ illum == icxIT_none ? NULL : &cust_illum,
+ observ, NULL, icSigXYZData, icxClamp)) == NULL)
+ {
+ error ("Creation of spectral conversion object failed");
+ }
+
+ if (fwacomp) {
+ double nw = 0.0; /* Number of media white patches */
+
+ rmwsp = sp; /* Initial Struct copy */
+
+ /* Find the media white spectral reflectance */
+ for (j = 0; j < rmwsp.spec_n; j++)
+ rmwsp.spec[j] = 0.0;
+
+ /* Compute the mean of all the media white patches */
+ for (i = 0; i < npat; i++) {
+ int use = 0;
+
+ if (devspace == icSigGrayData) {
+ if (isAdditive) {
+ if (*((double *)icg->t[0].fdata[i][ci]) > (100.0 - 0.1))
+ use = 1;
+ } else {
+ if (*((double *)icg->t[0].fdata[i][ci]) < 0.1)
+ use = 1;
+ }
+ } else if (devspace == icSigRgbData) {
+ if (*((double *)icg->t[0].fdata[i][ci]) > (100.0 - 0.1)
+ && *((double *)icg->t[0].fdata[i][mi]) > (100.0 - 0.1)
+ && *((double *)icg->t[0].fdata[i][yi]) > (100.0 - 0.1))
+ use = 1;
+ } else if (devspace == icSigCmyData) {
+ if (*((double *)icg->t[0].fdata[i][ci]) < 0.1
+ && *((double *)icg->t[0].fdata[i][mi]) < 0.1
+ && *((double *)icg->t[0].fdata[i][yi]) < 0.1)
+ use = 1;
+ } else { /* Assume CMYK */
+
+ if (*((double *)icg->t[0].fdata[i][ci]) < 0.1
+ && *((double *)icg->t[0].fdata[i][mi]) < 0.1
+ && *((double *)icg->t[0].fdata[i][yi]) < 0.1
+ && *((double *)icg->t[0].fdata[i][ki]) < 0.1) {
+ use = 1;
+ }
+ }
+
+ if (use) {
+ /* Read the spectral values for this patch */
+ for (j = 0; j < rmwsp.spec_n; j++) {
+ rmwsp.spec[j] += *((double *)icg->t[0].fdata[i][spi[j]]);
+ }
+ nw++;
+ }
+ }
+
+ if (nw == 0.0) {
+ warning("Can't find a media white patch to compute white reference");
+ warning("Using maximum of all spectral readings instead");
+
+ /* Track the maximum reflectance for any band to determine white. */
+ /* This might give bogus results if there is no white patch... */
+ for (i = 0; i < npat; i++) {
+ for (j = 0; j < rmwsp.spec_n; j++) {
+ double rv = *((double *)icg->t[0].fdata[i][spi[j]]);
+ if (rv > rmwsp.spec[j])
+ rmwsp.spec[j] = rv;
+ }
+ }
+ nw++;
+ }
+ for (j = 0; j < rmwsp.spec_n; j++) {
+ rmwsp.spec[j] /= nw; /* Compute average */
+ }
+ mwsp = rmwsp; /* Structure copy */
+ }
+
+ if (fwacomp) {
+ instType itype; /* Spectral instrument type */
+ xspect insp; /* Instrument illuminant */
+
+ if (inst_illum == icxIT_none) {
+ /* try to get from .ti3 file */
+ if ((ti = icg->find_kword (icg, 0, "TARGET_INSTRUMENT")) < 0)
+ error ("Can't find target instrument needed for FWA compensation");
+
+ if ((itype = inst_enum (icg->t[0].kdata[ti])) == instUnknown)
+ error ("Unrecognised target instrument '%s'",
+ icg->t[0].kdata[ti]);
+
+ if (inst_illuminant (&insp, itype) != 0)
+ error ("Instrument doesn't have an FWA illuminent");
+ }
+ else if (inst_illum == icxIT_custom) {
+ insp = inst_cust_illum; /* Structure copy */
+ }
+ else {
+ if (standardIlluminant(&insp, inst_illum, 0) != 0)
+ error ("Failed to find standard illuminant");
+ }
+
+ /* If we are setting a specific simulated instrument illuminant */
+ if (tillum != icxIT_none) {
+ tillump = &cust_tillum;
+ if (tillum != icxIT_custom) {
+ if (standardIlluminant(tillump, tillum, 0.0)) {
+ error("simulated inst. illum. not recognised");
+ }
+ }
+ }
+
+ /* (Note that sp and mwsp.norm is set to 100.0) */
+ if (sp2cie->set_fwa(sp2cie, &insp, tillump, &mwsp))
+ error ("Set FWA on sp2cie failed");
+
+ if (verb) {
+ double FWAc;
+ sp2cie->get_fwa_info(sp2cie, &FWAc);
+ printf("FWA content = %f\n",FWAc);
+ }
+
+ /* Create an FWA compensated white spectrum and XYZ value */
+ sp2cie->sconvert (sp2cie, &rmwsp, mwXYZ, &mwsp);
+ }
+
+ for (i = 0; i < npat; i++) {
+
+ xspect corr_sp;
+
+ /* copy all input colums to output (except spectral if nospec) */
+
+ for (jj = j = 0; j < icg->t[0].nfields; j++) {
+ for (k = 0; nospec && k < sp.spec_n; k++) {
+ if (spi[k] == j)
+ break;
+ }
+ if (nospec == 0 || k >= sp.spec_n) {
+ switch (icg->t[0].ftype[j]) {
+ case r_t:
+ elems[jj].d = *((double *) icg->t[0].fdata[i][j]);
+ break;
+ case i_t:
+ elems[jj].i = *((int *) icg->t[0].fdata[i][j]);
+ break;
+ default:
+ elems[jj].c = (char *) icg->t[0].fdata[i][j];
+ }
+ jj++;
+ }
+ }
+
+ /* Read the spectral values for this patch */
+ for (j = 0; j < sp.spec_n; j++) {
+ sp.spec[j] = *((double *)icg->t[0].fdata[i][spi[j]]);
+ }
+
+ if (fwacomp) {
+ corr_sp = sp;
+
+ /* Convert it to CIE space */
+ sp2cie->sconvert (sp2cie, &corr_sp, XYZ, &sp);
+
+ /* Write the corrected spectral values for this patch */
+ if (nospec == 0) {
+ for (j = 0; j < sp.spec_n; j++) {
+ elems[spi[j]].d = corr_sp.spec[j];
+ }
+ }
+#ifdef ALLOW_PLOT
+ if (doplot) {
+ int ii;
+ double xx[XRES];
+ double y1[XRES];
+ double y2[XRES];
+ double lab[3];
+
+ icmXYZ2Lab(&icmD50, lab, XYZ);
+ printf("Patch %d, XYZ = %f %f %f, Lab = %f %f %f\n",i,
+ XYZ[0], XYZ[1], XYZ[2], lab[0], lab[1], lab[2]);
+
+ /* Plot spectrum out */
+ for (ii = 0; ii < XRES; ii++) {
+ double ww;
+
+ ww = (sp.spec_wl_long - sp.spec_wl_short)
+ * ((double)ii/(XRES-1.0)) + sp.spec_wl_short;
+
+ xx[ii] = ww;
+ y1[ii] = value_xspect(&sp, ww);
+ y2[ii] = 100.0 * value_xspect(&corr_sp, ww);
+ }
+ do_plot(xx,y1,y2,NULL,ii);
+ }
+#endif
+ }
+ else {
+ /* Convert it to CIE space */
+ sp2cie->convert (sp2cie, XYZ, &sp);
+#ifdef ALLOW_PLOT
+ if (doplot) {
+ int ii;
+ double xx[XRES];
+ double y1[XRES];
+ double lab[3];
+
+ icmXYZ2Lab(&icmD50, lab, XYZ);
+ printf("Patch %d, XYZ = %f %f %f, Lab = %f %f %f\n",i,
+ XYZ[0], XYZ[1], XYZ[2], lab[0], lab[1], lab[2]);
+
+ /* Plot spectrum out */
+ for (ii = 0; ii < XRES; ii++) {
+ double ww;
+
+ ww = (sp.spec_wl_long - sp.spec_wl_short)
+ * ((double)ii/(XRES-1.0)) + sp.spec_wl_short;
+
+ xx[ii] = ww;
+ y1[ii] = value_xspect(&sp, ww);
+ }
+ do_plot(xx,y1,NULL,NULL,ii);
+ }
+#endif
+ }
+
+ icmXYZ2Lab(&icmD50, Lab, XYZ);
+
+ elems[Xi].d = XYZ[0] * 100.0;
+ elems[Yi].d = XYZ[1] * 100.0;
+ elems[Zi].d = XYZ[2] * 100.0;
+
+ elems[Li].d = Lab[0];
+ elems[ai].d = Lab[1];
+ elems[bi].d = Lab[2];
+
+ ocg->add_setarr(ocg, 0, elems);
+ }
+
+ if (ocg->write_name(ocg, out_ti3_name)) {
+ error ("Write error: %s", ocg->err);
+ }
+
+ sp2cie->del (sp2cie); /* Done with this */
+
+ ocg->del (ocg); /* Clean up */
+ icg->del (icg); /* Clean up */
+
+ free (elems);
+ }
+
+ return 0;
+}
+
diff --git a/spectro/spotread.c b/spectro/spotread.c
new file mode 100644
index 0000000..3218243
--- /dev/null
+++ b/spectro/spotread.c
@@ -0,0 +1,2271 @@
+
+ /* Spectrometer/Colorimeter color spot reader utility */
+
+/*
+ * Argyll Color Correction System
+ * Author: Graeme W. Gill
+ * Date: 3/10/2001
+ *
+ * Derived from printread.c/chartread.c
+ * Was called printspot.
+ *
+ * Copyright 2001 - 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.
+ */
+
+/* This program reads a spot reflection/transmission/emission value using */
+/* a spectrometer or colorimeter. */
+
+/* TTBD
+ *
+ * Should fix plot so that it is a separate object running its own thread,
+ * so that it can be sent a graph without needing to be clicked in all the time.
+ *
+ * Should add option to show reflective/tranmsission density values.
+ *
+ * Should add option for Y u' v' values.
+ */
+
+#undef DEBUG
+#undef TEST_EVENT_CALLBACK /* Report async event callbacks */
+
+#define COMPORT 1 /* Default com port 1..4 */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <time.h>
+#include <string.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "cgats.h"
+#include "xicc.h"
+#include "conv.h"
+#include "plot.h"
+#else /* SALONEINSTLIB */
+#include "sa_config.h"
+#include "numsup.h"
+#include "xspect.h"
+#include "conv.h"
+#endif /* SALONEINSTLIB */
+#include "ccss.h"
+#include "ccmx.h"
+#include "inst.h"
+#include "icoms.h"
+#include "instappsup.h"
+#include "spyd2setup.h"
+#ifdef EN_SPYD2
+#include "spyd2setup.h"
+#endif
+
+#if defined (NT)
+#include <conio.h>
+#endif
+
+#ifdef NEVER /* Not currently used */
+/* Convert control chars to ^[A-Z] notation in a string */
+static char *
+fix_asciiz(char *s) {
+ static char buf [200];
+ char *d;
+ for(d = buf; ;) {
+ if (*s < ' ' && *s > '\000') {
+ *d++ = '^';
+ *d++ = *s++ + '@';
+ } else
+ *d++ = *s++;
+ if (s[-1] == '\000')
+ break;
+ }
+ return buf;
+}
+#endif
+
+#ifdef SALONEINSTLIB
+
+#define D50_X_100 96.42
+#define D50_Y_100 100.00
+#define D50_Z_100 82.49
+
+/* CIE XYZ 0..100 to perceptual D50 CIE 1976 L*a*b* */
+static void
+XYZ2Lab(double *out, double *in) {
+ double X = in[0], Y = in[1], Z = in[2];
+ double x,y,z,fx,fy,fz;
+
+ x = X/D50_X_100;
+ y = Y/D50_Y_100;
+ z = Z/D50_Z_100;
+
+ if (x > 0.008856451586)
+ fx = pow(x,1.0/3.0);
+ else
+ fx = 7.787036979 * x + 16.0/116.0;
+
+ if (y > 0.008856451586)
+ fy = pow(y,1.0/3.0);
+ else
+ fy = 7.787036979 * y + 16.0/116.0;
+
+ if (z > 0.008856451586)
+ fz = pow(z,1.0/3.0);
+ else
+ fz = 7.787036979 * z + 16.0/116.0;
+
+ out[0] = 116.0 * fy - 16.0;
+ out[1] = 500.0 * (fx - fy);
+ out[2] = 200.0 * (fy - fz);
+}
+
+/* Return the normal Delta E given two Lab values */
+static double LabDE(double *Lab0, double *Lab1) {
+ double rv = 0.0, tt;
+
+ tt = Lab0[0] - Lab1[0];
+ rv += tt * tt;
+ tt = Lab0[1] - Lab1[1];
+ rv += tt * tt;
+ tt = Lab0[2] - Lab1[2];
+ rv += tt * tt;
+
+ return sqrt(rv);
+}
+
+/* Lab to LCh */
+void Lab2LCh(double *out, double *in) {
+ double C, h;
+
+ C = sqrt(in[1] * in[1] + in[2] * in[2]);
+
+ h = (180.0/3.14159265359) * atan2(in[2], in[1]);
+ h = (h < 0.0) ? h + 360.0 : h;
+
+ out[0] = in[0];
+ out[1] = C;
+ out[2] = h;
+}
+
+/* XYZ to Yxy */
+static void XYZ2Yxy(double *out, double *in) {
+ double sum = in[0] + in[1] + in[2];
+ double Y, x, y;
+
+ if (sum < 1e-9) {
+ Y = 0.0;
+ y = 0.0;
+ x = 0.0;
+ } else {
+ Y = in[1];
+ x = in[0]/sum;
+ y = in[1]/sum;
+ }
+ out[0] = Y;
+ out[1] = x;
+ out[2] = y;
+}
+
+#endif /* SALONEINSTLIB */
+
+/* Replacement for gets */
+char *getns(char *buf, int len) {
+ int i;
+ if (fgets(buf, len, stdin) == NULL)
+ return NULL;
+
+ for (i = 0; i < len; i++) {
+ if (buf[i] == '\n') {
+ buf[i] = '\000';
+ return buf;
+ }
+ }
+ buf[len-1] = '\000';
+ return buf;
+}
+
+/* Deal with an instrument error. */
+/* Return 0 to retry, 1 to abort */
+static int ierror(inst *it, inst_code ic) {
+ int ch;
+ empty_con_chars();
+ printf("Got '%s' (%s) error.\nHit Esc or Q to give up, any other key to retry:",
+ it->inst_interp_error(it, ic), it->interp_error(it, ic));
+ fflush(stdout);
+ ch = next_con_char();
+ printf("\n");
+ if (ch == 0x03 || ch == 0x1b || ch == 'q' || ch == 'Q') /* Escape, ^C or Q */
+ return 1;
+ return 0;
+}
+
+/* A color structure */
+/* This can hold all representations simultaniously */
+typedef struct {
+ double gy;
+ double r,g,b;
+ double cmyk[4];
+ double XYZ[4]; /* Colorimeter readings */
+ double eXYZ[4]; /* Expected XYZ values */
+ xspect sp; /* Spectral reading */
+
+ char *id; /* Id string */
+ char *loc; /* Location string */
+ int loci; /* Location integer = pass * 256 + step */
+} col;
+
+#ifdef TEST_EVENT_CALLBACK
+ void test_event_callback(void *cntx, inst_event_type event) {
+ a1logd(g_log,0,"Got event_callback with 0x%x\n",event);
+ }
+#endif
+
+#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 */
+
+/*
+
+ Flags used:
+
+ ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ upper ... . . . . .. ...
+ lower . .... .. . .. . ..
+
+*/
+
+void
+usage(char *diag, ...) {
+ int i;
+ icompaths *icmps;
+ inst2_capability cap2 = 0;
+ fprintf(stderr,"Measure spot values, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the GPL Version 2 or later\n");
+ if (setup_spyd2() == 2)
+ fprintf(stderr,"WARNING: This file contains a proprietary firmware image, and may not be freely distributed !\n");
+ if (diag != NULL) {
+ va_list args;
+ fprintf(stderr,"Diagnostic: ");
+ va_start(args, diag);
+ vfprintf(stderr, diag, args);
+ va_end(args);
+ fprintf(stderr,"\n");
+ }
+ fprintf(stderr,"usage: spotread [-options] [logfile]\n");
+ fprintf(stderr," -v Verbose mode\n");
+ fprintf(stderr," -s Print spectrum for each reading\n");
+#ifndef SALONEINSTLIB
+ fprintf(stderr," -S Plot spectrum for each reading\n");
+#endif /* !SALONEINSTLIB */
+ fprintf(stderr," -c listno Set communication port from the following list (default %d)\n",COMPORT);
+ if ((icmps = new_icompaths(g_log)) != NULL) {
+ icompath **paths;
+ if ((paths = icmps->paths) != NULL) {
+ int i;
+ for (i = 0; ; i++) {
+ if (paths[i] == NULL)
+ break;
+ if (paths[i]->itype == instSpyder2 && setup_spyd2() == 0)
+ fprintf(stderr," %d = '%s' !! Disabled - no firmware !!\n",i+1,paths[i]->name);
+ else
+ fprintf(stderr," %d = '%s'\n",i+1,paths[i]->name);
+ }
+ } else
+ fprintf(stderr," ** No ports found **\n");
+ }
+ fprintf(stderr," -t Use transmission measurement mode\n");
+ fprintf(stderr," -e Use emissive measurement mode (absolute results)\n");
+ fprintf(stderr," -eb Use display white brightness relative measurement mode\n");
+ fprintf(stderr," -ew Use display white relative measurement mode\n");
+ fprintf(stderr," -p Use telephoto measurement mode (absolute results)\n");
+ fprintf(stderr," -pb Use projector white brightness relative measurement mode\n");
+ fprintf(stderr," -pw Use projector white relative measurement mode\n");
+ fprintf(stderr," -a Use ambient measurement mode (absolute results)\n");
+ fprintf(stderr," -f Use ambient flash measurement mode (absolute results)\n");
+ cap2 = inst_show_disptype_options(stderr, " -y ", icmps, 0);
+#ifndef SALONEINSTLIB
+ fprintf(stderr," -I illum Set simulated instrument illumination using FWA (def -i illum):\n");
+ fprintf(stderr," M0, M1, M2, A, C, D50, D50M2, D65, F5, F8, F10 or file.sp]\n");
+#endif
+ fprintf(stderr," -i illum Choose illuminant for computation of CIE XYZ from spectral data & FWA:\n");
+#ifndef SALONEINSTLIB
+ fprintf(stderr," A, C, D50 (def.), D50M2, D65, F5, F8, F10 or file.sp\n");
+#else
+ fprintf(stderr," A, C, D50 (def.), D65\n");
+#endif
+ fprintf(stderr," -Q observ Choose CIE Observer for spectral data or CCSS instrument:\n");
+#ifndef SALONEINSTLIB
+ fprintf(stderr," 1931_2 (def), 1964_10, S&B 1955_2, shaw, J&V 1978_2\n");
+#else
+ fprintf(stderr," 1931_2 (def), 1964_10\n");
+#endif
+#ifndef SALONEINSTLIB
+ fprintf(stderr," (Choose FWA during operation)\n");
+#endif
+ fprintf(stderr," -F filter Set filter configuration (if aplicable):\n");
+ fprintf(stderr," n None\n");
+ fprintf(stderr," p Polarising filter\n");
+ fprintf(stderr," 6 D65\n");
+ fprintf(stderr," u U.V. Cut\n");
+ fprintf(stderr," -E extrafilterfile Apply extra filter compensation file\n");
+ fprintf(stderr," -x Display Yxy instead of Lab\n");
+ fprintf(stderr," -h Display LCh instead of Lab\n");
+ fprintf(stderr," -V Show running average and std. devation from ref.\n");
+#ifndef SALONEINSTLIB
+ fprintf(stderr," -T Display correlated color temperatures and CRI\n");
+#endif /* !SALONEINSTLIB */
+// fprintf(stderr," -K type Run instrument calibration first\n");
+ fprintf(stderr," -N Disable auto calibration of instrument\n");
+ fprintf(stderr," -H Start in high resolution spectrum mode (if available)\n");
+ if (cap2 & inst2_ccmx)
+ fprintf(stderr," -X file.ccmx Apply Colorimeter Correction Matrix\n");
+ if (cap2 & inst2_ccss) {
+ fprintf(stderr," -X file.ccss Use Colorimeter Calibration Spectral Samples for calibration\n");
+ }
+ fprintf(stderr," -Y r|n Override refresh, non-refresh display mode\n");
+ fprintf(stderr," -Y A Use non-adaptive integration time mode (if available).\n");
+// fprintf(stderr," -Y U Test i1pro2 UV measurement mode\n");
+ fprintf(stderr," -W n|h|x Override serial port flow control: n = none, h = HW, x = Xon/Xoff\n");
+ fprintf(stderr," -D [level] Print debug diagnostics to stderr\n");
+ fprintf(stderr," logfile Optional file to save reading results as text\n");
+
+ if (icmps != NULL)
+ icmps->del(icmps);
+ exit(1);
+}
+
+int main(int argc, char *argv[]) {
+ int i, j;
+ int fa, nfa, mfa; /* current argument we're looking at */
+ int verb = 0;
+ int debug = 0;
+ int docalib = 0; /* Do a manual instrument calibration */
+ int nocal = 0; /* Disable auto calibration */
+ int pspec = 0; /* 1 = Print out the spectrum for each reading */
+ /* 2 = Plot out the spectrum for each reading */
+ int trans = 0; /* Use transmissioin mode */
+ int emiss = 0; /* 1 = Use emissive mode, 2 = display bright rel. */
+ /* 3 = display white rel. */
+ int tele = 0; /* 1 = Use telephoto emissive sub-mode. */
+ int ambient = 0; /* 1 = Use ambient emissive mode, 2 = ambient flash mode */
+ int highres = 0; /* Use high res mode if available */
+ int uvmode = 0; /* ~~~ i1pro2 test mode ~~~ */
+ int refrmode = -1; /* -1 = default, 0 = non-refresh mode, 1 = non-refresh mode */
+ int nadaptive = 0; /* Use non-apative mode if available */
+ int doYxy= 0; /* Display Yxy instead of Lab */
+ int doLCh= 0; /* Display LCh instead of Lab */
+ int doCCT= 0; /* Display correlated color temperatures */
+ inst_mode mode = 0, smode = 0; /* Normal mode and saved readings mode */
+ inst_opt_type trigmode = inst_opt_unknown; /* Chosen trigger mode */
+ inst_opt_filter fe = inst_opt_filter_unknown;
+ /* Filter configuration */
+ char outname[MAXNAMEL+1] = "\000"; /* Output logfile name */
+ char ccxxname[MAXNAMEL+1] = "\000"; /* Colorimeter Correction/Colorimeter Calibration name */
+ char filtername[MAXNAMEL+1] = "\000"; /* Filter compensation */
+ FILE *fp = NULL; /* Logfile */
+ icompaths *icmps = NULL;
+ int comport = COMPORT; /* COM port used */
+ icompath *ipath = NULL;
+ int dtype = 0; /* Display type selection charater */
+ inst_mode cap = inst_mode_none; /* Instrument mode capabilities */
+ inst2_capability cap2 = inst2_none; /* Instrument capabilities 2 */
+ inst3_capability cap3 = inst3_none; /* Instrument capabilities 3 */
+ double lx, ly; /* Read location on xy table */
+ baud_rate br = baud_38400; /* Target baud rate */
+ flow_control fc = fc_nc; /* Default flow control */
+ inst *it; /* Instrument object */
+ inst_code rv;
+ int uswitch = 0; /* Instrument switch is enabled */
+ int spec = 0; /* Need spectral data for observer/illuminant flag */
+ icxIllumeType tillum = icxIT_none; /* Target/simulated instrument illuminant */
+ xspect cust_tillum, *tillump = NULL; /* Custom target/simulated illumination spectrum */
+ icxIllumeType illum = icxIT_D50; /* Spectral defaults */
+ xspect cust_illum; /* Custom illumination spectrum */
+ icxObserverType obType = icxOT_default;
+ xspect sp; /* Last spectrum read */
+ xspect rsp; /* Reference spectrum */
+ xsp2cie *sp2cie = NULL; /* default conversion */
+ xsp2cie *sp2cief[26]; /* FWA corrected conversions */
+ double wXYZ[3] = { -10.0, 0, 0 };/* White XYZ for display white relative */
+ double Lab[3] = { -10.0, 0, 0}; /* Last Lab */
+ double rXYZ[3] = { 0.0, -10.0, 0}; /* Reference XYZ */
+ double rLab[3] = { -10.0, 0, 0}; /* Reference Lab */
+ double Yxy[3] = { 0.0, 0, 0}; /* Yxy value */
+ double LCh[3] = { 0.0, 0, 0}; /* LCh value */
+ double refstats = 0; /* Print running avg & stddev against ref */
+ double rstat_n; /* Stats N */
+ double rstat_XYZ[3]; /* Stats sum of XYZ's */
+ double rstat_XYZsq[3]; /* Stats sum of XYZ's squared */
+ double rstat_Lab[3]; /* Stats sum of Lab's */
+ double rstat_Labsq[3]; /* Stats sum of Lab's squared */
+ int savdrd = 0; /* At least one saved reading is available */
+ int ix; /* Reading index number */
+ int loghead = 0; /* NZ if log file heading has been printed */
+
+ set_exe_path(argv[0]); /* Set global exe_path and error_program */
+ check_if_not_interactive();
+ setup_spyd2(); /* Load firware if available */
+
+ for (i = 0; i < 26; i++)
+ sp2cief[i] = NULL;
+
+ sp.spec_n = 0;
+ rsp.spec_n = 0;
+
+ /* Process the arguments */
+ mfa = 0; /* Minimum final arguments */
+ for(fa = 1;fa < argc;fa++) {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1+mfa) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == 'D') {
+ debug = 1;
+ if (na != NULL && na[0] >= '0' && na[0] <= '9') {
+ debug = atoi(na);
+ fa = nfa;
+ }
+ g_log->debug = debug;
+ } else if (argv[fa][1] == '?') {
+ usage("Usage requested");
+
+ } else if (argv[fa][1] == 'v') {
+ verb = 1;
+ g_log->verb = verb;
+
+ } else if (argv[fa][1] == 's') {
+ pspec = 1;
+
+#ifndef SALONEINSTLIB
+ } else if (argv[fa][1] == 'S') {
+ pspec = 2;
+#endif /* !SALONEINSTLIB */
+
+ /* COM port */
+ } else if (argv[fa][1] == 'c') {
+ fa = nfa;
+ if (na == NULL) usage("Paramater expected following -c");
+ comport = atoi(na);
+ if (comport < 1 || comport > 40) usage("-c parameter %d out of range",comport);
+
+ /* Display type */
+ } else if (argv[fa][1] == 'y') {
+ fa = nfa;
+ if (na == NULL) usage("Paramater expected following -y");
+ dtype = na[0];
+
+#ifndef SALONEINSTLIB
+ /* Simulated instrument illumination (FWA) */
+ } else if (argv[fa][1] == 'I') {
+
+ fa = nfa;
+ if (na == NULL) usage("Paramater expected following -I");
+ if (strcmp(na, "A") == 0
+ || strcmp(na, "M0") == 0) {
+ spec = 1;
+ tillum = icxIT_A;
+ } else if (strcmp(na, "C") == 0) {
+ spec = 1;
+ tillum = icxIT_C;
+ } else if (strcmp(na, "D50") == 0
+ || strcmp(na, "M1") == 0) {
+ spec = 1;
+ tillum = icxIT_D50;
+ } else if (strcmp(na, "D50M2") == 0
+ || strcmp(na, "M2") == 0) {
+ spec = 1;
+ tillum = icxIT_D50M2;
+ } else if (strcmp(na, "D65") == 0) {
+ spec = 1;
+ tillum = icxIT_D65;
+ } else if (strcmp(na, "F5") == 0) {
+ spec = 1;
+ tillum = icxIT_F5;
+ } else if (strcmp(na, "F8") == 0) {
+ spec = 1;
+ tillum = icxIT_F8;
+ } else if (strcmp(na, "F10") == 0) {
+ spec = 1;
+ tillum = icxIT_F10;
+ } else { /* Assume it's a filename */
+ spec = 1;
+ tillum = icxIT_custom;
+ if (read_xspect(&cust_tillum, na) != 0)
+ usage("Failed to read custom target illuminant spectrum in file '%s'",na);
+ }
+#endif /* SALONEINSTLIB */
+
+ /* Spectral Illuminant type for XYZ computation */
+ } else if (argv[fa][1] == 'i') {
+ fa = nfa;
+ if (na == NULL) usage("Paramater expected following -i");
+ if (strcmp(na, "A") == 0) {
+ spec = 1;
+ illum = icxIT_A;
+ } else if (strcmp(na, "C") == 0) {
+ spec = 1;
+ illum = icxIT_C;
+ } else if (strcmp(na, "D50") == 0) {
+ spec = 1;
+ illum = icxIT_D50;
+ } else if (strcmp(na, "D50M2") == 0) {
+ spec = 1;
+ illum = icxIT_D50M2;
+ } else if (strcmp(na, "D65") == 0) {
+ spec = 1;
+ illum = icxIT_D65;
+#ifndef SALONEINSTLIB
+ } else if (strcmp(na, "F5") == 0) {
+ spec = 1;
+ illum = icxIT_F5;
+ } else if (strcmp(na, "F8") == 0) {
+ spec = 1;
+ illum = icxIT_F8;
+ } else if (strcmp(na, "F10") == 0) {
+ spec = 1;
+ illum = icxIT_F10;
+ } else { /* Assume it's a filename */
+ spec = 1;
+ illum = icxIT_custom;
+ if (read_xspect(&cust_illum, na) != 0)
+ usage("Unable to read custom illuminant file '%s'",na);
+ }
+#else /* SALONEINSTLIB */
+ } else
+ usage("Unrecognised illuminant '%s'",na);
+#endif /* SALONEINSTLIB */
+
+ /* Spectral Observer type */
+ } else if (argv[fa][1] == 'Q') {
+ fa = nfa;
+ if (na == NULL) usage("Paramater expected following -Q");
+ if (strcmp(na, "1931_2") == 0) { /* Classic 2 degree */
+ obType = icxOT_CIE_1931_2;
+ } else if (strcmp(na, "1964_10") == 0) { /* Classic 10 degree */
+ obType = icxOT_CIE_1964_10;
+#ifndef SALONEINSTLIB
+ } else if (strcmp(na, "1955_2") == 0) { /* Stiles and Burch 1955 2 degree */
+ obType = icxOT_Stiles_Burch_2;
+ } else if (strcmp(na, "1978_2") == 0) { /* Judd and Voss 1978 2 degree */
+ obType = icxOT_Judd_Voss_2;
+ } else if (strcmp(na, "shaw") == 0) { /* Shaw and Fairchilds 1997 2 degree */
+ obType = icxOT_Shaw_Fairchild_2;
+#endif /* !SALONEINSTLIB */
+ } else
+ usage("Spectral observer type '%s' not recognised",na);
+
+ /* Request transmission measurement */
+ } else if (argv[fa][1] == 't') {
+ emiss = 0;
+ trans = 1;
+ tele = 0;
+ ambient = 0;
+
+ /* Request emissive measurement */
+ } else if (argv[fa][1] == 'e' || argv[fa][1] == 'd') {
+
+ if (argv[fa][1] == 'd')
+ warning("spotread -d flag is deprecated");
+
+ emiss = 1;
+ trans = 0;
+ tele = 0;
+ ambient = 0;
+
+ if (argv[fa][2] != '\000') {
+ if (argv[fa][2] == 'b' || argv[fa][2] == 'B')
+ emiss = 2;
+ else if (argv[fa][2] == 'w' || argv[fa][2] == 'W')
+ emiss = 3;
+ else
+ usage("-d modifier '%c' not recognised",argv[fa][2]);
+ }
+
+ /* Request telephoto measurement */
+ } else if (argv[fa][1] == 'p') {
+
+ emiss = 1;
+ trans = 0;
+ tele = 1;
+ ambient = 0;
+
+ if (argv[fa][2] != '\000') {
+ fa = nfa;
+ if (argv[fa][2] == 'b' || argv[fa][2] == 'B')
+ emiss = 2;
+ else if (argv[fa][2] == 'w' || argv[fa][2] == 'W')
+ emiss = 3;
+ else
+ usage("-p modifier '%c' not recognised",argv[fa][2]);
+ }
+
+ /* Request ambient measurement */
+ } else if (argv[fa][1] == 'a') {
+ emiss = 1;
+ trans = 0;
+ tele = 0;
+ ambient = 1;
+
+ /* Request ambient flash measurement */
+ } else if (argv[fa][1] == 'f') {
+ emiss = 1;
+ trans = 0;
+ tele = 0;
+ ambient = 2;
+
+ /* Filter configuration */
+ } else if (argv[fa][1] == 'F') {
+ fa = nfa;
+ if (na == NULL) usage("Paramater expected following -F");
+ if (na[0] == 'n' || na[0] == 'N')
+ fe = inst_opt_filter_none;
+ else if (na[0] == 'p' || na[0] == 'P')
+ fe = inst_opt_filter_pol;
+ else if (na[0] == '6')
+ fe = inst_opt_filter_D65;
+ else if (na[0] == 'u' || na[0] == 'U')
+ fe = inst_opt_filter_UVCut;
+ else
+ usage("-F type '%c' not recognised",na[0]);
+
+ /* Extra filter compensation file */
+ } else if (argv[fa][1] == 'E') {
+ fa = nfa;
+ if (na == NULL) usage("Paramater expected following -E");
+ strncpy(filtername,na,MAXNAMEL-1); filtername[MAXNAMEL-1] = '\000';
+
+ /* Show Yxy */
+ } else if (argv[fa][1] == 'x') {
+ doYxy = 1;
+ doLCh = 0;
+
+ /* Show LCh */
+ } else if (argv[fa][1] == 'h') {
+ doYxy = 0;
+ doLCh = 1;
+
+ /* Compute running average and standard deviation from ref. */
+ /* Also turns off clamping */
+ } else if (argv[fa][1] == 'V') {
+ refstats = 1;
+#ifndef SALONEINSTLIB
+
+ /* Show CCT etc. */
+ } else if (argv[fa][1] == 'T') {
+ doCCT = 1;
+#endif /* !SALONEINSTLIB */
+
+ /* Manual calibration */
+ } else if (argv[fa][1] == 'K') {
+
+ if (na != NULL && na[0] >= '0' && na[0] <= '9') {
+ docalib = atoi(na);
+ fa = nfa;
+ } else
+ usage("-K parameter '%c' not recognised",na[0]);
+
+ /* No auto calibration */
+ } else if (argv[fa][1] == 'N') {
+ nocal = 1;
+
+ /* High res mode */
+ } else if (argv[fa][1] == 'H') {
+ highres = 1;
+
+ /* Colorimeter Correction Matrix or */
+ /* Colorimeter Calibration Spectral Samples */
+ } else if (argv[fa][1] == 'X') {
+ int ix;
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -K");
+ strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
+
+ /* Serial port flow control */
+ } else if (argv[fa][1] == 'W') {
+ fa = nfa;
+ if (na == NULL) usage("Parameter expected after -W");
+ if (na[0] == 'n' || na[0] == 'N')
+ fc = fc_none;
+ else if (na[0] == 'h' || na[0] == 'H')
+ fc = fc_Hardware;
+ else if (na[0] == 'x' || na[0] == 'X')
+ fc = fc_XonXOff;
+ else
+ usage("-W parameter '%c' not recognised",na[0]);
+
+ /* Extra flags */
+ } else if (argv[fa][1] == 'Y') {
+ if (na == NULL)
+ usage("Parameter expected after -Y");
+
+ if (na[0] == 'A') {
+ nadaptive = 1;
+ } else if (na[0] == 'r') {
+ refrmode = 1;
+ } else if (na[0] == 'n') {
+ refrmode = 0;
+ /* ~~~ i1pro2 test code ~~~ */
+ } else if (na[0] == 'U') {
+ uvmode = 1;
+ } else {
+ usage("-Y parameter '%c' not recognised",na[0]);
+ }
+
+ } else
+ usage("Flag -%c not recognised",argv[fa][1]);
+ }
+ else
+ break;
+ }
+
+ /* Get the optional file name argument */
+ if (fa < argc) {
+ strncpy(outname,argv[fa++],MAXNAMEL-1); outname[MAXNAMEL-1] = '\000';
+ if ((fp = fopen(outname, "w")) == NULL)
+ error("Unable to open logfile '%s' for writing\n",outname);
+ }
+
+
+ /* See if there is an environment variable ccxx */
+ if (ccxxname[0] == '\000') {
+ char *na;
+ if ((na = getenv("ARGYLL_COLMTER_CAL_SPEC_SET")) != NULL) {
+ strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
+
+ } else if ((na = getenv("ARGYLL_COLMTER_COR_MATRIX")) != NULL) {
+ strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
+ }
+ }
+
+ /* - - - - - - - - - - - - - - - - - - - */
+ if ((icmps = new_icompaths(g_log)) == NULL)
+ error("Finding instrument paths failed");
+ if ((ipath = icmps->get_path(icmps, comport)) == NULL)
+ error("No instrument at port %d",comport);
+
+
+ /* Setup the instrument ready to do reads */
+ if ((it = new_inst(ipath, 0, g_log, DUIH_FUNC_AND_CONTEXT)) == NULL) {
+ usage("Unknown, inappropriate or no instrument detected");
+ }
+
+#ifdef TEST_EVENT_CALLBACK
+ it->set_event_callback(it, test_event_callback, (void *)it);
+#endif
+
+ if (verb)
+ printf("Connecting to the instrument ..\n");
+
+#ifdef DEBUG
+ printf("About to init the comms\n");
+#endif
+
+ /* Establish communications */
+ if ((rv = it->init_coms(it, br, fc, 15.0)) != inst_ok) {
+ printf("Failed to initialise communications with instrument\n"
+ "or wrong instrument or bad configuration!\n"
+ "('%s' + '%s')\n", it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+
+#ifdef DEBUG
+ printf("Established comms\n");
+#endif
+
+#ifdef DEBUG
+ printf("About to init the instrument\n");
+#endif
+
+ /* set filter configuration before initialising/calibrating */
+ if (fe != inst_opt_filter_unknown) {
+ if ((rv = it->get_set_opt(it, inst_opt_set_filter, fe)) != inst_ok) {
+ printf("Setting filter configuration not supported by instrument\n");
+ it->del(it);
+ return -1;
+ }
+ }
+
+ /* Initialise the instrument */
+ if ((rv = it->init_inst(it)) != inst_ok) {
+ printf("Instrument initialisation failed with '%s' (%s)!\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+
+ /* Configure the instrument mode */
+ {
+ int ccssset = 0;
+ it->capabilities(it, &cap, &cap2, &cap3);
+
+ /* Don't fail if the instrument has only one mode, and */
+ /* it's not been selected */
+
+ if (trans == 0 && emiss == 0 && !IMODETST(cap, inst_mode_reflection)) {
+ /* This will fail. Switch to a mode the instrument has */
+ if (IMODETST(cap, inst_mode_emission)) {
+ if (verb)
+ printf("Defaulting to emission measurement\n");
+ emiss = 1;
+
+ } else if (IMODETST(cap, inst_mode_transmission)) {
+ if (verb)
+ printf("Defaulting to transmission measurement\n");
+ trans = 1;
+ }
+ }
+
+ if (trans) {
+ if (!IMODETST(cap, inst_mode_trans_spot)
+ || it->check_mode(it, inst_mode_trans_spot) != inst_ok) {
+ printf("Need transmission spot capability,\n");
+ printf("and instrument doesn't support it\n");
+ it->del(it);
+ return -1;
+ }
+
+ } else if (ambient == 1) {
+ if (!IMODETST(cap, inst_mode_emis_ambient)
+ || it->check_mode(it, inst_mode_emis_ambient) != inst_ok) {
+ printf("Requested ambient light capability,\n");
+ printf("and instrument doesn't support it.\n");
+ if (!IMODETST(cap, inst_mode_emis_spot)) {
+ it->del(it);
+ return -1;
+ } else {
+ printf("Will use emissive mode instead,\n");
+ printf("but note that light level readings may be wrong!\n");
+
+ }
+ } else {
+ if (verb) {
+ printf("Please make sure the instrument is fitted with\n");
+ printf("the appropriate ambient light measuring head\n");
+ }
+ }
+
+ } else if (ambient == 2) {
+ if (!IMODETST(cap, inst_mode_emis_ambient_flash)
+ || it->check_mode(it, inst_mode_emis_ambient_flash) != inst_ok) {
+ printf("Requested ambient flash capability,\n");
+ printf("and instrument doesn't support it.\n");
+ it->del(it);
+ return -1;
+ } else {
+ if (verb) {
+ printf("Please make sure the instrument is fitted with\n");
+ printf("the appropriate ambient light measuring head, and that\n");
+ printf("you are ready to trigger the flash.\n");
+ }
+ }
+
+ } else if (emiss || tele) {
+
+ if (tele) {
+ if (!IMODETST(cap, inst_mode_emis_tele)
+ || it->check_mode(it, inst_mode_emis_tele) != inst_ok) {
+ printf("Need telephoto spot capability\n");
+ printf("and instrument doesn't support it\n");
+ it->del(it);
+ return -1;
+ }
+ } else {
+ if (!IMODETST(cap, inst_mode_emis_spot)
+ || it->check_mode(it, inst_mode_emis_spot) != inst_ok) {
+ printf("Need emissive spot capability\n");
+ printf("and instrument doesn't support it\n");
+ it->del(it);
+ return -1;
+ }
+ }
+
+ if (nadaptive && !IMODETST(cap, inst_mode_emis_nonadaptive)) {
+ if (verb) {
+ printf("Requested non-adaptive mode and instrument doesn't support it (ignored)\n");
+ nadaptive = 0;
+ }
+ }
+ if (refrmode >= 0 && !IMODETST(cap, inst_mode_emis_refresh_ovd)
+ && !IMODETST(cap, inst_mode_emis_norefresh_ovd)) {
+ if (verb) {
+ printf("Requested refresh mode override and instrument doesn't support it (ignored)\n");
+ refrmode = -1;
+ }
+ }
+
+ /* Set display type */
+ if (dtype != 0) {
+
+ if (cap2 & inst2_disptype) {
+ int ix;
+ if ((ix = inst_get_disptype_index(it, dtype, 0)) < 0) {
+ it->del(it);
+ usage("Failed to locate display type matching '%c'",dtype);
+ }
+
+ if ((rv = it->set_disptype(it, ix)) != inst_ok) {
+ printf("Setting display type ix %d not supported by instrument\n",ix);
+ it->del(it);
+ return -1;
+ }
+ } else
+ printf("Display type ignored - instrument doesn't support display type\n");
+ }
+
+ } else {
+ if (!IMODETST(cap, inst_mode_ref_spot)
+ || it->check_mode(it, inst_mode_ref_spot) != inst_ok) {
+ printf("Need reflection spot reading capability,\n");
+ printf("and instrument doesn't support it\n");
+ it->del(it);
+ return -1;
+ }
+ }
+
+ /* If we have non-standard observer we need spectral or CCSS */
+ if (obType != icxOT_default && !IMODETST(cap, inst_mode_spectral) && !(cap2 & inst2_ccss)) {
+ printf("Non standard observer needs spectral information or CCSS capability\n");
+ printf("and instrument doesn't support either.\n");
+ it->del(it);
+ return -1;
+ }
+
+ /* If we don't have CCSS then we need spectral for non-standard observer */
+ if (obType != icxOT_default && (cap2 & inst2_ccss) == 0) {
+ spec = 1;
+ }
+
+ if ((spec || pspec) && !IMODETST(cap, inst_mode_spectral)) {
+ printf("Need spectral information for custom illuminant or observer\n");
+ printf("and instrument doesn't support it\n");
+ it->del(it);
+ return -1;
+ }
+
+ /* Disable initial calibration of machine if selected */
+ if (nocal != 0) {
+ if ((rv = it->get_set_opt(it,inst_opt_noinitcalib, 0)) != inst_ok) {
+ printf("Setting no-initial calibrate failed with '%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ printf("Disable initial-calibrate not supported\n");
+ }
+ }
+ if (highres) {
+ if (IMODETST(cap, inst_mode_highres)) {
+ inst_code ev;
+ if ((ev = it->get_set_opt(it, inst_opt_highres)) != inst_ok) {
+ printf("\nSetting high res mode failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, ev), it->interp_error(it, ev));
+ it->del(it);
+ return -1;
+ }
+ highres = 1;
+ } else if (verb) {
+ printf("high resolution ignored - instrument doesn't support high res. mode\n");
+ }
+ }
+
+ /* Set it to the appropriate mode */
+
+ /* Should look at instrument type & user spec ??? */
+ if (trans)
+ smode = mode = inst_mode_trans_spot;
+ else if (ambient == 1 && IMODETST(cap, inst_mode_emis_ambient)
+ && it->check_mode(it, inst_mode_emis_ambient) == inst_ok)
+ smode = mode = inst_mode_emis_ambient;
+ else if (ambient == 2 && IMODETST(cap, inst_mode_emis_ambient_flash)
+ && it->check_mode(it, inst_mode_emis_ambient_flash) == inst_ok)
+ smode = mode = inst_mode_emis_ambient_flash;
+ else if (tele) // Hmm. What about tele flash ?
+ smode = mode = inst_mode_emis_tele;
+ else if (emiss || ambient)
+ smode = mode = inst_mode_emis_spot;
+ else {
+ smode = mode = inst_mode_ref_spot;
+ if (IMODETST(cap, inst_mode_s_ref_spot)
+ && it->check_mode(it, inst_mode_s_ref_spot) == inst_ok)
+ smode = inst_mode_s_ref_spot;
+ }
+
+ /* Mode dependent extra modes */
+ if (emiss || tele) {
+ if (nadaptive) {
+ mode |= inst_mode_emis_nonadaptive;
+ smode |= inst_mode_emis_nonadaptive;
+ }
+ if (refrmode == 0) {
+ mode |= inst_mode_emis_norefresh_ovd;
+ smode |= inst_mode_emis_norefresh_ovd;
+ }
+ else if (refrmode == 1) {
+ mode |= inst_mode_emis_refresh_ovd;
+ smode |= inst_mode_emis_refresh_ovd;
+ }
+ }
+
+ /* Use spec if requested or if available in case of CRI or FWA */
+ if (spec || pspec || IMODETST(cap, inst_mode_spectral)) {
+ mode |= inst_mode_spectral;
+ smode |= inst_mode_spectral;
+ }
+
+ // ~~~ i1pro2 test code ~~~ */
+ if (uvmode) {
+ if (!IMODETST(cap, inst_mode_ref_uv)) {
+ printf("UV measurement mode requested, but instrument doesn't support this mode\n");
+ it->del(it);
+ return -1;
+ }
+ mode |= inst_mode_ref_uv;
+ smode |= inst_mode_ref_uv;
+ }
+
+ if ((rv = it->set_mode(it, mode)) != inst_ok) {
+ printf("\nSetting instrument mode failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+ it->capabilities(it, &cap, &cap2, &cap3);
+
+ /* Apply Extra filter compensation */
+ if (filtername[0] != '\000') {
+ if ((rv = it->comp_filter(it, filtername)) != inst_ok) {
+ printf("\nSetting filter compensation failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+ }
+
+ /* Colorimeter Correction Matrix */
+ if (ccxxname[0] != '\000') {
+ ccss *cs = NULL;
+ ccmx *cx = NULL;
+
+ if ((cx = new_ccmx()) == NULL) {
+ printf("\nnew_ccmx failed\n");
+ it->del(it);
+ return -1;
+ }
+ if (cx->read_ccmx(cx,ccxxname) == 0) {
+ if ((cap2 & inst2_ccmx) == 0) {
+ printf("\nInstrument doesn't have Colorimeter Correction Matrix capability\n");
+ it->del(it);
+ return -1;
+ }
+ if ((rv = it->col_cor_mat(it, cx->matrix)) != inst_ok) {
+ printf("\nSetting Colorimeter Correction Matrix failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ cx->del(cx);
+ it->del(it);
+ return -1;
+ }
+ cx->del(cx);
+
+ } else {
+ cx->del(cx);
+ cx = NULL;
+
+ /* CCMX failed, try CCSS */
+ if ((cs = new_ccss()) == NULL) {
+ printf("\nnew_ccss failed\n");
+ it->del(it);
+ return -1;
+ }
+ if (cs->read_ccss(cs,ccxxname)) {
+ printf("\nReading CCMX/CCSS File '%s' failed with error %d:'%s'\n",
+ ccxxname, cs->errc, cs->err);
+ cs->del(cs);
+ it->del(it);
+ return -1;
+ }
+ if ((cap2 & inst2_ccss) == 0) {
+ printf("\nInstrument doesn't have Colorimeter Calibration Spectral Sample capability\n");
+ cs->del(cs);
+ it->del(it);
+ return -1;
+ }
+ if ((rv = it->get_set_opt(it, inst_opt_set_ccss_obs, obType, NULL)) != inst_ok) {
+ printf("\nSetting CCS Observer failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ cs->del(cs);
+ it->del(it);
+ return -1;
+ }
+ if ((rv = it->col_cal_spec_set(it, cs->samples, cs->no_samp)) != inst_ok) {
+ printf("\nSetting Colorimeter Calibration Spectral Samples failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ cs->del(cs);
+ it->del(it);
+ return -1;
+ }
+ ccssset = 1;
+ cs->del(cs);
+ }
+ }
+
+ /* If non-standard observer wasn't set by a CCSS file above */
+ if (obType != icxOT_default && (cap2 & inst2_ccss) && ccssset == 0) {
+ if ((rv = it->get_set_opt(it, inst_opt_set_ccss_obs, obType, 0)) != inst_ok) {
+ printf("\nSetting CCSS Observer failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+ }
+
+ /* If it batter powered, show the status of the battery */
+ if ((cap2 & inst2_has_battery)) {
+ double batstat = 0.0;
+ if ((rv = it->get_set_opt(it, inst_stat_battery, &batstat)) != inst_ok) {
+ printf("\nGetting instrument battery status failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+ printf("The battery charged level is %.0f%%\n",batstat * 100.0);
+ }
+
+ /* If it's an instrument that need positioning let user trigger via uicallback */
+ /* in spotread, else enable switch or user via uicallback trigger if possible. */
+ if ((cap2 & inst2_xy_locate) && (cap2 & inst2_xy_position)) {
+ trigmode = inst_opt_trig_prog;
+
+ } else if (cap2 & inst2_user_switch_trig) {
+ trigmode = inst_opt_trig_user_switch;
+ uswitch = 1;
+
+ /* Or go for keyboard trigger */
+ } else if (cap2 & inst2_user_trig) {
+ trigmode = inst_opt_trig_user;
+
+ /* Or something is wrong with instrument capabilities */
+ } else {
+ printf("\nNo reasonable trigger mode avilable for this instrument\n");
+ it->del(it);
+ return -1;
+ }
+ if ((rv = it->get_set_opt(it, trigmode)) != inst_ok) {
+ printf("\nSetting trigger mode failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+
+ /* Setup the keyboard trigger to return our commands */
+ inst_set_uih(0x0, 0xff, DUIH_TRIG);
+ inst_set_uih('r', 'r', DUIH_CMND);
+ inst_set_uih('R', 'R', DUIH_CMND);
+ inst_set_uih('h', 'h', DUIH_CMND);
+ inst_set_uih('H', 'H', DUIH_CMND);
+ inst_set_uih('k', 'k', DUIH_CMND);
+ inst_set_uih('K', 'K', DUIH_CMND);
+ inst_set_uih('s', 's', DUIH_CMND);
+ inst_set_uih('S', 'S', DUIH_CMND);
+ inst_set_uih('f', 'f', DUIH_CMND);
+ inst_set_uih('F', 'F', DUIH_CMND);
+ inst_set_uih('q', 'q', DUIH_ABORT);
+ inst_set_uih('Q', 'Q', DUIH_ABORT);
+ inst_set_uih(0x03, 0x03, DUIH_ABORT); /* ^c */
+ inst_set_uih(0x1b, 0x1b, DUIH_ABORT); /* Esc */
+ }
+
+#ifdef DEBUG
+ printf("About to enter read loop\n");
+#endif
+
+ if (verb)
+ printf("Init instrument success !\n");
+
+ if (spec) {
+ /* Any non-illuminated mode has no illuminant */
+ if (emiss || tele || ambient)
+ illum = icxIT_none;
+
+ /* Create a spectral conversion object */
+ if ((sp2cie = new_xsp2cie(illum, &cust_illum, obType, NULL, icSigXYZData,
+ refstats ? icxNoClamp : icxClamp)) == NULL)
+ error("Creation of spectral conversion object failed");
+
+ /* If we are setting a specific simulated instrument illuminant */
+ if (tillum != icxIT_none) {
+ tillump = &cust_tillum;
+ if (tillum != icxIT_custom) {
+ if (standardIlluminant(tillump, tillum, 0.0)) {
+ error("simulated inst. illum. not recognised");
+ }
+ }
+ }
+ }
+
+ /* Hold table */
+ if (cap2 & inst2_xy_holdrel) {
+ for (;;) { /* retry loop */
+ if ((rv = it->xy_sheet_hold(it)) == inst_ok)
+ break;
+
+ if (ierror(it, rv)) {
+ it->xy_clear(it);
+ it->del(it);
+ return -1;
+ }
+ }
+ }
+
+ /* Read spots until the user quits */
+ for (ix = 1;; ix++) {
+ ipatch val;
+ double XYZ[3]; /* XYZ scaled 0..100 or absolute */
+ double tXYZ[3];
+#ifndef SALONEINSTLIB
+ double cct, vct, vdt;
+ double cct_de, vct_de, vdt_de;
+#endif /* !SALONEINSTLIB */
+ int ch = '0'; /* Character */
+ int sufwa = 0; /* Setup for FWA compensation */
+ int dofwa = 0; /* Do FWA compensation */
+ int fidx = -1; /* FWA compensated index, default = none */
+
+ if (savdrd != -1 && IMODETST(cap, inst_mode_s_ref_spot)
+ && it->check_mode(it, inst_mode_s_ref_spot) == inst_ok) {
+ inst_stat_savdrd sv;
+
+ savdrd = 0;
+ if ((rv = it->get_set_opt(it, inst_stat_saved_readings, &sv)) != inst_ok) {
+ printf("\nGetting saved reading status failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ it->del(it);
+ return -1;
+ }
+ if (sv & inst_stat_savdrd_spot)
+ savdrd = 1;
+ }
+
+ /* Read a stored value */
+ if (savdrd == 1) {
+ inst_code ev;
+
+ /* Set to saved spot mode */
+ if ((ev = it->set_mode(it, smode)) != inst_ok) {
+ printf("\nSetting instrument mode failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, ev), it->interp_error(it, ev));
+ it->del(it);
+ return -1;
+ }
+ it->capabilities(it, &cap, &cap2, &cap3);
+
+ /* Set N and n to be a command */
+ inst_set_uih('N', 'N', DUIH_CMND);
+ inst_set_uih('n', 'n', DUIH_CMND);
+
+ /* Set to keyboard only trigger */
+ if ((ev = it->get_set_opt(it, inst_opt_trig_user)) != inst_ok) {
+ printf("\nSetting trigger mode failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, ev), it->interp_error(it, ev));
+ it->del(it);
+ return -1;
+ }
+
+ printf("\nThere are saved spot readings in the instrument.\n");
+#ifndef SALONEINSTLIB
+ printf("Hit [A-Z] to use reading for white and setup FWA compensation (keyed to letter)\n");
+ printf("[a-z] to use reading for FWA compensated value from keyed reference\n");
+ printf("'r' to set reference, 's' to save spectrum,\n");
+#else /* SALONEINSTLIB */
+ printf("Hit 'r' to set reference\n");
+#endif /* SALONEINSTLIB */
+ printf("Hit ESC or Q to exit, N to not read saved reading,\n");
+ printf("any other key to use reading: ");
+ fflush(stdout);
+
+ /* Read the sample or get a command */
+ rv = it->read_sample(it, "SPOT", &val, refstats ? instNoClamp : instClamp);
+
+ ch = inst_get_uih_char();
+
+ /* Restore the trigger mode */
+ if ((ev = it->get_set_opt(it, trigmode)) != inst_ok) {
+ printf("\nSetting trigger mode failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, ev), it->interp_error(it, ev));
+ it->del(it);
+ return -1;
+ }
+
+ /* Set N and n to be a trigger */
+ inst_set_uih('N', 'N', DUIH_TRIG);
+ inst_set_uih('n', 'n', DUIH_TRIG);
+
+ /* Set back to read spot mode */
+ if ((ev = it->set_mode(it, mode)) != inst_ok) {
+ printf("\nSetting instrument mode failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, ev), it->interp_error(it, ev));
+ it->del(it);
+ return -1;
+ }
+ it->capabilities(it, &cap, &cap2, &cap3);
+
+ /* If user said no to reading stored values */
+ if ((rv & inst_mask) == inst_user_abort
+ && (ch & DUIH_CMND) && ((ch & 0xff) == 'N' || (ch & 0xff) == 'n')) {
+ printf("\n");
+ savdrd = -1;
+ continue;
+ }
+
+ /* Do a normal read */
+ } else {
+
+ /* Do any needed calibration before the user places the instrument on a desired spot */
+ if (it->needs_calibration(it) & inst_calt_n_dfrble_mask) {
+ inst_code ev;
+
+ printf("\nSpot read needs a calibration before continuing\n");
+
+ /* save current location */
+ if ((cap2 & inst2_xy_locate) && (cap2 & inst2_xy_position)) {
+ for (;;) { /* retry loop */
+ if ((ev = it->xy_get_location(it, &lx, &ly)) == inst_ok)
+ break;
+ if (ierror(it, ev) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (ev != inst_ok) {
+ printf("\nSpot read got abort or error from xy_get_location\n");
+ break; /* Abort */
+ }
+ }
+
+ ev = inst_handle_calibrate(it, inst_calt_needed, inst_calc_none, NULL, NULL);
+ if (ev != inst_ok) { /* Abort or fatal error */
+ printf("\nSpot read got abort or error from calibration\n");
+ break;
+ }
+
+ /* restore location */
+ if ((cap2 & inst2_xy_locate) && (cap2 & inst2_xy_position)) {
+ for (;;) { /* retry loop */
+ if ((ev = it->xy_position(it, 0, lx, ly)) == inst_ok)
+ break;
+ if (ierror(it, ev) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (ev != inst_ok) {
+ printf("\nSpot read got abort or error from xy_get_location\n");
+ break; /* Abort */
+ }
+ }
+ }
+
+ if (ambient == 2) { /* Flash ambient */
+ printf("\nConfigure for ambient, press and hold button, trigger flash then release button,\n");
+#ifndef SALONEINSTLIB
+ printf("or hit 'r' to set reference, 's' to save spectrum,\n");
+#else /* SALONEINSTLIB */
+ printf("or hit 'r' to set reference\n");
+#endif /* SALONEINSTLIB */
+ printf("'h' to toggle high res., 'k' to do a calibration\n");
+
+ } else {
+ /* If this is an xy instrument: */
+ if ((cap2 & inst2_xy_locate) && (cap2 & inst2_xy_position)) {
+
+ /* Allow the user to position the instrument */
+ for (;;) { /* retry loop */
+ if ((rv = it->xy_locate_start(it)) == inst_ok)
+ break;
+ if (ierror(it, rv) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (rv != inst_ok) {
+ printf("\nSpot read got abort or error from xy_locate_start\n");
+ break; /* Abort */
+ }
+
+ printf("\nUsing the XY table controls, locate the point to measure with the sight,\n");
+
+ /* Purely manual instrument */
+ } else {
+
+ /* If this is display white brightness relative, read the white */
+ if ((emiss > 1 || tele > 1) && wXYZ[0] < 0.0)
+ printf("\nPlace instrument on white reference spot,\n");
+ else {
+ printf("\nPlace instrument on spot to be measured,\n");
+ }
+ }
+#ifndef SALONEINSTLIB
+ printf("and hit [A-Z] to read white and setup FWA compensation (keyed to letter)\n");
+ printf("[a-z] to read and make FWA compensated reading from keyed reference\n");
+ printf("'r' to set reference, 's' to save spectrum,\n");
+ printf("'f' to report cal. refresh rate, 'F' to measure refresh rate\n");
+#else /* SALONEINSTLIB */
+ printf("Hit 'r' to set reference\n");
+#endif /* SALONEINSTLIB */
+ printf("'h' to toggle high res., 'k' to do a calibration\n");
+ }
+ if (uswitch)
+ printf("Hit ESC or Q to exit, instrument switch or any other key to take a reading: ");
+ else
+ printf("Hit ESC or Q to exit, any other key to take a reading: ");
+ fflush(stdout);
+
+ if ((cap2 & inst2_xy_locate) && (cap2 & inst2_xy_position)) {
+ /* Wait for the user to hit a key */
+ for (;;) {
+ if ((rv = inst_get_uicallback()(inst_get_uicontext(), inst_armed)) != inst_ok)
+ break;
+ }
+
+ if (rv == inst_user_abort) {
+ break; /* Abort */
+
+ } else if (rv == inst_user_trig) {
+ inst_code ev;
+
+ /* Take the location set on the sight, and move the instrument */
+ /* to take the measurement there. */
+ if ((cap2 & inst2_xy_locate) && (cap2 & inst2_xy_position)) {
+ for (;;) { /* retry loop */
+ if ((rv = it->xy_get_location(it, &lx, &ly)) == inst_ok)
+ break;
+ if (ierror(it, rv) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (rv != inst_ok) {
+ printf("\nSpot read got abort or error from xy_get_location\n");
+ break; /* Abort */
+ }
+
+ for (;;) { /* retry loop */
+ if ((rv = it->xy_locate_end(it)) == inst_ok)
+ break;
+ if (ierror(it, rv) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (rv != inst_ok) {
+ printf("\nSpot read got abort or error from xy_locate_end\n");
+ break; /* Abort */
+ }
+
+ for (;;) { /* retry loop */
+ if ((rv = it->xy_position(it, 1, lx, ly)) == inst_ok)
+ break;
+ if (ierror(it, rv) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (rv != inst_ok) {
+ printf("\nSpot read got abort or error from xy_position\n");
+ break; /* Abort */
+ }
+ }
+ rv = it->read_sample(it, "SPOT", &val, refstats ? instNoClamp : instClamp);
+
+ /* Restore the location the instrument to have the location */
+ /* sight over the selected patch. */
+ for (;;) { /* retry loop */
+ if ((ev = it->xy_position(it, 0, lx, ly)) == inst_ok)
+ break;
+ if (ierror(it, ev) == 0) /* Ignore */
+ continue;
+ break; /* Abort */
+ }
+ if (ev != inst_ok) {
+ printf("\nSpot read got abort or error from xy_position\n");
+ break; /* Abort */
+ }
+ }
+ /* else what ? */
+ } else {
+ rv = it->read_sample(it, "SPOT", &val, refstats ? instNoClamp : instClamp);
+ }
+ }
+
+#ifdef DEBUG
+ printf("read_sample returned '%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+#endif /* DEBUG */
+
+ /* Get any command or trigger character */
+ if ((rv & inst_mask) == inst_user_trig
+ || (rv & inst_mask) == inst_user_abort)
+ ch = inst_get_uih_char();
+ else
+ ch = '0';
+
+ /* Do return after command */
+ if ((rv & inst_mask) == inst_user_abort && (ch & DUIH_CMND))
+ printf("\n");
+
+ /* Deal with a user abort */
+ if ((rv & inst_mask) == inst_user_abort && (ch & DUIH_ABORT)) {
+ printf("\n\nSpot read stopped at user request!\n");
+ printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
+ ch = next_con_char();
+ if (ch == 0x1b || ch == 0x03 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ break;
+ }
+ printf("\n");
+ continue;
+
+ /* Deal with a needs calibration */
+ } else if ((rv & inst_mask) == inst_needs_cal) {
+ inst_code ev;
+ printf("\n\nSpot read failed because instruments needs calibration.\n");
+ ev = inst_handle_calibrate(it, inst_calt_needed, inst_calc_none, NULL, NULL);
+ if (ev != inst_ok) { /* Abort or fatal error */
+ printf("\nSpot read got abort or error from calibrate\n");
+ break;
+ }
+ continue;
+
+ /* Deal with a bad sensor position */
+ } else if ((rv & inst_mask) == inst_wrong_config) {
+ printf("\n\nSpot read failed due to the sensor being in the wrong position\n(%s)\n",it->interp_error(it, rv));
+ continue;
+
+ /* Deal with a misread */
+ } else if ((rv & inst_mask) == inst_misread) {
+ empty_con_chars();
+ printf("\n\nSpot read failed due to misread (%s)\n",it->interp_error(it, rv));
+ printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
+ ch = next_con_char();
+ printf("\n");
+ if (ch == 0x1b || ch == 0x03 || ch == 'q' || ch == 'Q') {
+ break;
+ }
+ continue;
+
+ /* Deal with a communications error */
+ } else if ((rv & inst_mask) == inst_coms_fail) {
+ empty_con_chars();
+ printf("\n\nSpot read failed due to communication problem.\n");
+ printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
+ ch = next_con_char();
+ if (ch == 0x1b || ch == 0x03 || ch == 'q' || ch == 'Q') {
+ printf("\n");
+ break;
+ }
+ printf("\n");
+ if (it->icom->port_type(it->icom) == icomt_serial) {
+ /* Allow retrying at a lower baud rate */
+ int tt = it->last_scomerr(it);
+ if (tt & (ICOM_BRK | ICOM_FER | ICOM_PER | ICOM_OER)) {
+ if (br == baud_57600) br = baud_38400;
+ else if (br == baud_38400) br = baud_9600;
+ else if (br == baud_9600) br = baud_4800;
+ else if (br == baud_9600) br = baud_4800;
+ else if (br == baud_2400) br = baud_1200;
+ else br = baud_1200;
+ }
+ if ((rv = it->init_coms(it, br, fc, 15.0)) != inst_ok) {
+ printf("init_coms returned '%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ break;
+ }
+ }
+ continue;
+
+ /* Some fatal error */
+ } else if ((rv & inst_mask) != inst_ok
+ && (rv & inst_mask) != inst_user_trig
+ && (rv & inst_mask) != inst_user_abort) {
+ printf("\n\nGot fatal error '%s' (%s)\n",
+ it->inst_interp_error(it, rv), it->interp_error(it, rv));
+ break;
+ }
+
+ /* Process command or reading */
+ ch &= 0xff;
+ if (ch == 0x1b || ch == 0x03 || ch == 'q' || ch == 'Q') { /* Or ^C */
+ break;
+ }
+ if (ch == 'H' || ch == 'h') { /* Toggle high res mode */
+ if (IMODETST(cap, inst_mode_highres)) {
+ inst_code ev;
+ if (highres) {
+ if ((ev = it->get_set_opt(it, inst_opt_stdres)) != inst_ok) {
+ printf("\nSetting std res mode failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, ev), it->interp_error(it, ev));
+ it->del(it);
+ return -1;
+ }
+ highres = 0;
+ if (pspec)
+ loghead = 0;
+ printf("\n Instrument set to standard resolution spectrum mode\n");
+ } else {
+ if ((ev = it->get_set_opt(it, inst_opt_highres)) != inst_ok) {
+ printf("\nSetting high res mode failed with error :'%s' (%s)\n",
+ it->inst_interp_error(it, ev), it->interp_error(it, ev));
+ it->del(it);
+ return -1;
+ }
+ highres = 1;
+ if (pspec)
+ loghead = 0;
+ printf("\n Instrument set to high resolution spectrum mode\n");
+ }
+ } else {
+ printf("\n 'H' Command ignored - instrument doesn't support high res. mode\n");
+ }
+ --ix;
+ continue;
+ }
+ if (ch == 'R' || ch == 'r') { /* Make last reading the reference */
+ if (Lab[0] >= -9.0) {
+ rXYZ[0] = XYZ[0];
+ rXYZ[1] = XYZ[1];
+ rXYZ[2] = XYZ[2];
+ rLab[0] = Lab[0];
+ rLab[1] = Lab[1];
+ rLab[2] = Lab[2];
+ if (pspec) {
+ rsp = sp; /* Save spectral reference too */
+ }
+ if (refstats) {
+ rstat_n = 1;
+ for (j = 0; j < 3; j++) {
+ rstat_XYZ[j] = XYZ[j];
+ rstat_XYZsq[j] = XYZ[j] * XYZ[j];
+ rstat_Lab[j] = Lab[j];
+ rstat_Labsq[j] = Lab[j] * Lab[j];
+ }
+ }
+ printf("\n Reference is now XYZ: %f %f %f Lab: %f %f %f\n", rXYZ[0], rXYZ[1], rXYZ[2],rLab[0], rLab[1], rLab[2]);
+ } else {
+ printf("\n No previous reading to use as reference\n");
+ }
+ --ix;
+ continue;
+ }
+#ifndef SALONEINSTLIB
+ if (ch == 'S' || ch == 's') { /* Save last spectral into file */
+ if (sp.spec_n > 0) {
+ char buf[500];
+ printf("\nEnter filename (ie. xxxx.sp): "); fflush(stdout);
+ if (getns(buf, 500) != NULL && strlen(buf) > 0) {
+ if(write_xspect(buf, &sp))
+ printf("\nWriting file '%s' failed\n",buf);
+ else
+ printf("\nWriting file '%s' succeeded\n",buf);
+ } else {
+ printf("\nNo filename, nothing saved\n");
+ }
+ } else {
+ printf("\nNo previous spectral reading to save to file (Use -s flag ?)\n");
+ }
+ --ix;
+ continue;
+ }
+#endif /* !SALONEINSTLIB */
+ if (ch == 'K' || ch == 'k') { /* Do a calibration */
+ inst_code ev;
+
+ printf("\nDoing a calibration\n");
+
+ /* save current location */
+ if ((cap2 & inst2_xy_locate) && (cap2 & inst2_xy_position)) {
+ for (;;) { /* retry loop */
+ if ((ev = it->xy_get_location(it, &lx, &ly)) == inst_ok)
+ break;
+ if (ierror(it, ev) == 0) { /* Ignore */
+ --ix;
+ continue;
+ }
+ break; /* Abort */
+ }
+ if (ev != inst_ok) {
+ printf("\nSpot read got abort or error from xy_get_location\n");
+ break; /* Abort */
+ }
+ }
+
+ ev = inst_handle_calibrate(it, inst_calt_available, inst_calc_none, NULL, NULL);
+ if (ev != inst_ok) { /* Abort or fatal error */
+ printf("\nSpot read got abort or error from calibrate\n");
+ break;
+ }
+
+ /* restore location */
+ if ((cap2 & inst2_xy_locate) && (cap2 & inst2_xy_position)) {
+ for (;;) { /* retry loop */
+ if ((ev = it->xy_position(it, 0, lx, ly)) == inst_ok)
+ break;
+ if (ierror(it, ev) == 0) { /* Ignore */
+ --ix;
+ continue;
+ }
+ break; /* Abort */
+ }
+ if (ev != inst_ok) {
+ printf("\nSpot read got abort or error from xy_position");
+ break; /* Abort */
+ }
+ }
+ --ix;
+ continue;
+ }
+
+ /* Measure refresh rate */
+ if (ch == 'F') {
+ double refr;
+ inst_code ev;
+
+ if (!(cap2 & inst2_emis_refr_meas)) {
+ printf("\nInstrument isn't capable of refresh rate measurement in current mode\n");
+ --ix;
+ continue;
+ }
+
+ ev = it->read_refrate(it, &refr);
+ if (ev == inst_unsupported) {
+ printf("\nInstrument isn't capable of refresh rate measurement in current mode\n");
+ --ix;
+ continue;
+ }
+
+ if (ev == inst_misread) {
+ printf("\nNo refresh detectable, or measurement failed\n");
+ --ix;
+ continue;
+
+ } else if (ev != inst_ok) {
+ if (ierror(it, ev) == 0) { /* Ignore */
+ --ix;
+ continue;
+ }
+ break; /* Abort */
+ } else {
+ printf("\nRefresh rate = %f Hz\n",refr);
+ }
+ --ix;
+ continue;
+ }
+
+ /* Report calibrated refresh rate */
+ if (ch == 'f') {
+ double refr;
+ inst_code ev;
+
+ if (!(cap2 & inst2_refresh_rate)) {
+ printf("\nInstrument isn't capable of refresh rate calibration\n");
+ --ix;
+ continue;
+ }
+
+ ev = it->get_refr_rate(it, &refr);
+ if (ev == inst_unsupported) {
+ printf("\nInstrument isn't capable of refresh rate calibration\n");
+ --ix;
+ continue;
+
+ } else if (ev == inst_needs_cal) {
+ int refrmode;
+
+ printf("\nRefresh rate hasn't been calibrated\n");
+
+ if ((ev = it->get_set_opt(it, inst_opt_get_dtinfo, &refrmode, NULL)) != inst_ok) {
+ printf("Can't get curretn refresh mode from instrument\n");
+ --ix;
+ continue;
+ }
+ if (!refrmode) {
+ printf("Instrument isn't set to a refresh display type\n");
+ --ix;
+ continue;
+ }
+
+ ev = inst_handle_calibrate(it, inst_calt_ref_freq, inst_calc_none, NULL, NULL);
+
+ if (ev != inst_ok) { /* Abort or fatal error */
+ printf("\nSpot read got abort or error from calibrate\n");
+ if (ierror(it, ev) == 0) { /* Ignore */
+ --ix;
+ continue;
+ }
+ break;
+ }
+ ev = it->get_refr_rate(it, &refr);
+ }
+
+ if (ev == inst_misread) {
+ printf("\nNo refresh, or wasn't able to measure one\n");
+ --ix;
+ continue;
+
+ } else if (ev != inst_ok) {
+ if (ierror(it, ev) == 0) { /* Ignore */
+ --ix;
+ continue;
+ }
+ break; /* Abort */
+ } else {
+ printf("\nRefresh rate = %f Hz\n",refr);
+ }
+ --ix;
+ continue;
+ }
+
+#ifndef SALONEINSTLIB
+ if (ch >= 'A' && ch <= 'Z') {
+ printf("\nMeasured media to setup FWA compensation slot '%c'\n",ch);
+ sufwa = 1;
+ fidx = ch - 'A';
+ }
+ if (ch >= 'a' && ch <= 'z') {
+ fidx = ch - 'a';
+ if (fidx < 0 || sp2cief[fidx] == NULL) {
+ printf("\nUnable to apply FWA compensation because it wasn't set up\n");
+ fidx = -1;
+ } else {
+ dofwa = 1;
+ }
+
+ }
+
+ /* Setup FWA compensation */
+ if (sufwa) {
+ double FWAc;
+ xspect insp; /* Instrument illuminant */
+
+ if (val.sp.spec_n <= 0) {
+ error("Instrument didn't return spectral data");
+ }
+
+ if (inst_illuminant(&insp, it->get_itype(it)) != 0)
+ error ("Instrument doesn't have an FWA illuminent");
+
+ /* Creat the base conversion object */
+ if (sp2cief[fidx] == NULL) {
+ if ((sp2cief[fidx] = new_xsp2cie(illum, &cust_illum, obType,
+ NULL, icSigXYZData, refstats ? icxNoClamp : icxClamp)) == NULL)
+ error("Creation of spectral conversion object failed");
+ }
+
+ if (sp2cief[fidx]->set_fwa(sp2cief[fidx], &insp, tillump, &val.sp))
+ error ("Set FWA on sp2cie failed");
+
+ sp2cief[fidx]->get_fwa_info(sp2cief[fidx], &FWAc);
+ printf("FWA content = %f\n",FWAc);
+ }
+#else /* !SALONEINSTLIB */
+ if (sufwa) {
+ error ("FWA compensation not supported in this version");
+ }
+#endif /* !SALONEINSTLIB */
+
+ /* Print and/or plot out spectrum, */
+ /* even if it's an FWA setup */
+ if (pspec) {
+ xspect tsp;
+
+ if (val.sp.spec_n <= 0)
+ error("Instrument didn't return spectral data");
+
+ tsp = val.sp; /* Temp. save spectral reading */
+
+ /* Compute FWA corrected spectrum */
+ if (dofwa != 0) {
+ sp2cief[fidx]->sconvert(sp2cief[fidx], &tsp, NULL, &tsp);
+ }
+
+ printf("Spectrum from %f to %f nm in %d steps\n",
+ tsp.spec_wl_short, tsp.spec_wl_long, tsp.spec_n);
+
+ for (j = 0; j < tsp.spec_n; j++)
+ printf("%s%8.3f",j > 0 ? ", " : "", tsp.spec[j]);
+ printf("\n");
+
+#ifndef SALONEINSTLIB
+ /* Plot the spectrum */
+ if (pspec == 2) {
+ double xx[XSPECT_MAX_BANDS];
+ double yy[XSPECT_MAX_BANDS];
+ double yr[XSPECT_MAX_BANDS];
+ double xmin, xmax, ymin, ymax;
+ xspect *ss; /* Spectrum range to use */
+ int nn;
+
+ if (rsp.spec_n > 0) {
+ if ((tsp.spec_wl_long - tsp.spec_wl_short) >
+ (rsp.spec_wl_long - rsp.spec_wl_short))
+ ss = &tsp;
+ else
+ ss = &rsp;
+ } else
+ ss = &tsp;
+
+ if (tsp.spec_n > rsp.spec_n)
+ nn = tsp.spec_n;
+ else
+ nn = rsp.spec_n;
+
+ if (nn > XSPECT_MAX_BANDS)
+ error("Got > %d spectral values (%d)",XSPECT_MAX_BANDS,nn);
+
+ for (j = 0; j < nn; j++) {
+#if defined(__APPLE__) && defined(__POWERPC__)
+ gcc_bug_fix(j);
+#endif
+ xx[j] = ss->spec_wl_short
+ + j * (ss->spec_wl_long - ss->spec_wl_short)/(nn-1);
+
+ yy[j] = value_xspect(&tsp, xx[j]);
+
+ if (rLab[0] >= -1.0) { /* If there is a reference */
+ yr[j] = value_xspect(&rsp, xx[j]);
+ }
+ }
+
+ xmax = ss->spec_wl_long;
+ xmin = ss->spec_wl_short;
+ if (emiss || uvmode) {
+ ymin = ymax = 0.0; /* let it scale */
+ } else {
+ ymin = 0.0;
+ ymax = 120.0;
+ }
+ do_plot_x(xx, yy, rLab[0] >= -1.0 ? yr : NULL, NULL, nn, 1,
+ xmin, xmax, ymin, ymax, 2.0);
+ }
+#endif /* !SALONEINSTLIB */
+ }
+
+ if (sufwa == 0) { /* Not setting up fwa, so show reading */
+
+ sp = val.sp; /* Save as last spectral reading */
+
+ /* Compute the XYZ & Lab */
+ if (dofwa == 0 && spec == 0) {
+ if (val.XYZ_v == 0)
+ error("Instrument didn't return XYZ value");
+
+ for (j = 0; j < 3; j++)
+ XYZ[j] = val.XYZ[j];
+
+ } else {
+ /* Compute XYZ given spectral */
+
+ if (sp.spec_n <= 0) {
+ error("Instrument didn't return spectral data");
+ }
+
+ if (dofwa == 0) {
+ /* Convert it to XYZ space using uncompensated */
+ sp2cie->convert(sp2cie, XYZ, &sp);
+ } else {
+ /* Convert using compensated conversion */
+ sp2cief[fidx]->sconvert(sp2cief[fidx], &sp, XYZ, &sp);
+ }
+ if (!(emiss || tele || ambient)) {
+ for (j = 0; j < 3; j++)
+ XYZ[j] *= 100.0; /* 0..100 scale */
+ }
+ }
+
+ /* XYZ is 0 .. 100 for reflective/transmissive, and absolute for emissibe here */
+ /* XYZ is 0 .. 1 for reflective/transmissive, and absolute for emissibe here */
+
+#ifndef SALONEINSTLIB
+ /* Compute color temperatures */
+ if (ambient || doCCT) {
+ icmXYZNumber wp;
+ double nxyz[3], axyz[3];
+ double lab[3], alab[3];
+
+ nxyz[0] = XYZ[0] / XYZ[1];
+ nxyz[2] = XYZ[2] / XYZ[1];
+ nxyz[1] = XYZ[1] / XYZ[1];
+
+ /* Compute CCT */
+ if ((cct = icx_XYZ2ill_ct(axyz, icxIT_Ptemp, obType, NULL, XYZ, NULL, 0)) < 0)
+ error ("Got bad cct\n");
+ axyz[0] /= axyz[1];
+ axyz[2] /= axyz[1];
+ axyz[1] /= axyz[1];
+ icmAry2XYZ(wp, axyz);
+ icmXYZ2Lab(&wp, lab, nxyz);
+ icmXYZ2Lab(&wp, alab, axyz);
+ cct_de = icmLabDE(lab, alab);
+
+ /* Compute VCT */
+ if ((vct = icx_XYZ2ill_ct(axyz, icxIT_Ptemp, obType, NULL, XYZ, NULL, 1)) < 0)
+ error ("Got bad vct\n");
+ axyz[0] /= axyz[1];
+ axyz[2] /= axyz[1];
+ axyz[1] /= axyz[1];
+ icmAry2XYZ(wp, axyz);
+ icmXYZ2Lab(&wp, lab, nxyz);
+ icmXYZ2Lab(&wp, alab, axyz);
+ vct_de = icmLabDE(lab, alab);
+
+ /* Compute VDT */
+ if ((vdt = icx_XYZ2ill_ct(axyz, icxIT_Dtemp, obType, NULL, XYZ, NULL, 1)) < 0)
+ error ("Got bad vct\n");
+ axyz[0] /= axyz[1];
+ axyz[2] /= axyz[1];
+ axyz[1] /= axyz[1];
+ icmAry2XYZ(wp, axyz);
+ icmXYZ2Lab(&wp, lab, nxyz);
+ icmXYZ2Lab(&wp, alab, axyz);
+ vdt_de = icmLabDE(lab, alab);
+ }
+
+ /* Compute D50 Lab from XYZ */
+ icmXYZ2Lab(&icmD50_100, Lab, XYZ);
+
+ /* Compute Yxy from XYZ */
+ icmXYZ2Yxy(Yxy, XYZ);
+
+ /* Compute LCh from Lab */
+ icmLab2LCh(LCh, Lab);
+
+#else /* SALONEINSTLIB */
+ /* Compute D50 Lab from XYZ */
+ XYZ2Lab(Lab, XYZ);
+
+ /* Compute Yxy from XYZ */
+ XYZ2Yxy(Yxy, XYZ);
+
+ /* Compute LCh from Lab */
+ Lab2LCh(LCh, Lab);
+#endif /* SALONEINSTLIB */
+
+ if (emiss > 1 || tele > 1) {
+ if (wXYZ[0] < 0.0) {
+ if (XYZ[1] < 10.0)
+ error ("White of XYZ %f %f %f doesn't seem reasonable",XYZ[0], XYZ[1], XYZ[2]);
+ printf("\n Making result XYZ: %f %f %f, D50 Lab: %f %f %f white reference.\n",
+ XYZ[0], XYZ[1], XYZ[2], Lab[0], Lab[1], Lab[2]);
+ wXYZ[0] = XYZ[0];
+ wXYZ[1] = XYZ[1];
+ wXYZ[2] = XYZ[2];
+ continue;
+ }
+ if (emiss == 2 || tele == 2) {
+ /* Normalize to white Y value and scale to 0..100 */
+ XYZ[0] = 100.0 * XYZ[0] / wXYZ[1];
+ XYZ[1] = 100.0 * XYZ[1] / wXYZ[1];
+ XYZ[2] = 100.0 * XYZ[2] / wXYZ[1];
+ }
+#ifndef SALONEINSTLIB
+ else {
+
+ /* Normalize to white and scale to 0..100 */
+ XYZ[0] = XYZ[0] * icmD50_100.X / wXYZ[0];
+ XYZ[1] = XYZ[1] * icmD50_100.Y / wXYZ[1];
+ XYZ[2] = XYZ[2] * icmD50_100.Z / wXYZ[2];
+ }
+
+ /* recompute Lab */
+ icmXYZ2Lab(&icmD50_100, Lab, XYZ);
+
+ /* recompute Yxy from XYZ */
+ icmXYZ2Yxy(Yxy, XYZ);
+
+ /* recompute LCh from Lab */
+ icmLab2LCh(LCh, Lab);
+#else /* SALONEINSTLIB */
+ else {
+
+ /* Normalize to white */
+ XYZ[0] = XYZ[0] * D50_X_100 / wXYZ[0];
+ XYZ[1] = XYZ[1] * D50_Y_100 / wXYZ[1];
+ XYZ[2] = XYZ[2] * D50_Z_100 / wXYZ[2];
+ }
+
+ /* recompute Lab */
+ XYZ2Lab(Lab, XYZ);
+
+ /* recompute Yxy from XYZ */
+ XYZ2Yxy(Yxy, XYZ);
+
+ /* recompute LCh from Lab */
+ Lab2LCh(LCh, Lab);
+#endif /* SALONEINSTLIB */
+ }
+
+ if (ambient && (cap2 & inst2_ambient_mono)) {
+ printf("\n Result is Y: %f, L*: %f\n",XYZ[1], Lab[0]);
+ } else {
+ if (doYxy) {
+ /* Print out the XYZ and Yxy */
+ printf("\n Result is XYZ: %f %f %f, Yxy: %f %f %f\n",
+ XYZ[0], XYZ[1], XYZ[2], Yxy[0], Yxy[1], Yxy[2]);
+ } else if (doLCh) {
+ /* Print out the XYZ and LCh */
+ printf("\n Result is XYZ: %f %f %f, LCh: %f %f %f\n",
+ XYZ[0], XYZ[1], XYZ[2], LCh[0], LCh[1], LCh[2]);
+ } else {
+ /* Print out the XYZ and Lab */
+ printf("\n Result is XYZ: %f %f %f, D50 Lab: %f %f %f\n",
+ XYZ[0], XYZ[1], XYZ[2], Lab[0], Lab[1], Lab[2]);
+ }
+ }
+
+ if (rLab[0] >= -9.0) {
+ if (refstats) {
+ double avg[3], sdev[3];
+ rstat_n++;
+
+ for (j = 0; j < 3; j++) {
+ rstat_XYZ[j] += XYZ[j];
+ rstat_XYZsq[j] += XYZ[j] * XYZ[j];
+
+ avg[j] = rstat_XYZ[j]/rstat_n;
+ sdev[j] = sqrt(rstat_n * rstat_XYZsq[j] - rstat_XYZ[j] * rstat_XYZ[j])/rstat_n;
+ }
+ printf(" XYZ stats %.0f: Avg %f %f %f, S.Dev %f %f %f\n",
+ rstat_n, avg[0], avg[1], avg[2], sdev[0], sdev[1], sdev[2]);
+
+ for (j = 0; j < 3; j++) {
+ rstat_Lab[j] += Lab[j];
+ rstat_Labsq[j] += Lab[j] * Lab[j];
+
+ avg[j] = rstat_Lab[j]/rstat_n;
+ sdev[j] = sqrt(rstat_n * rstat_Labsq[j] - rstat_Lab[j] * rstat_Lab[j])/rstat_n;
+ }
+ printf(" Lab stats %.0f: Avg %f %f %f, S.Dev %f %f %f\n",
+ rstat_n, avg[0], avg[1], avg[2], sdev[0], sdev[1], sdev[2]);
+ }
+#ifndef SALONEINSTLIB
+ printf(" Delta E to reference is %f %f %f (%f, CIE94 %f)\n",
+ Lab[0] - rLab[0], Lab[1] - rLab[1], Lab[2] - rLab[2],
+ icmLabDE(Lab, rLab), icmCIE94(Lab, rLab));
+#else
+ printf(" Delta E to reference is %f %f %f (%f)\n",
+ Lab[0] - rLab[0], Lab[1] - rLab[1], Lab[2] - rLab[2],
+ LabDE(Lab, rLab));
+#endif
+ }
+ if (ambient) {
+ if (ambient == 2)
+ printf(" Apparent flash duration = %f seconds\n",val.duration);
+ if (cap2 & inst2_ambient_mono) {
+ printf(" Ambient = %.1f Lux%s\n",
+ 3.141592658 * XYZ[1], ambient == 2 ? "-Seconds" : "");
+ if (ambient != 2)
+ printf(" Suggested EV @ ISO100 for %.1f Lux incident light = %.1f\n",
+ 3.141592658 * XYZ[1],
+ log(3.141592658 * XYZ[1]/2.5)/log(2.0));
+ } else {
+#ifndef SALONEINSTLIB
+ printf(" Ambient = %.1f Lux%s, CCT = %.0fK (Delta E %f)\n",
+ 3.1415926 * XYZ[1], ambient == 2 ? "-Seconds" : "",
+ cct, cct_de);
+ if (ambient != 2)
+ printf(" Suggested EV @ ISO100 for %.1f Lux incident light = %.1f\n",
+ 3.141592658 * XYZ[1],
+ log(3.141592658 * XYZ[1]/2.5)/log(2.0));
+ printf(" Closest Planckian temperature = %.0fK (Delta E %f)\n",vct, vct_de);
+ printf(" Closest Daylight temperature = %.0fK (Delta E %f)\n",vdt, vdt_de);
+#else /* SALONEINSTLIB */
+ printf(" Ambient = %.1f Lux%s\n",
+ 3.1415926 * XYZ[1], ambient == 2 ? "-Seconds" : "");
+#endif /* SALONEINSTLIB */
+ }
+#ifndef SALONEINSTLIB
+ } else if (doCCT) {
+ printf(" CCT = %.0fK (Delta E %f)\n",cct, cct_de);
+ printf(" Closest Planckian temperature = %.0fK (Delta E %f)\n",vct, vct_de);
+ printf(" Closest Daylight temperature = %.0fK (Delta E %f)\n",vdt, vdt_de);
+#endif
+ }
+#ifndef SALONEINSTLIB
+ if (val.sp.spec_n > 0 && (ambient || doCCT)) {
+ int invalid = 0;
+ double cri;
+ cri = icx_CIE1995_CRI(&invalid, &sp);
+ printf(" Color Rendering Index (Ra) = %.1f%s\n",cri,invalid ? " (Invalid)" : "");
+ }
+#endif
+
+ /* Save reading to the log file */
+ if (fp != NULL) {
+ /* Print some column titles */
+ if (loghead == 0) {
+ fprintf(fp,"Reading\tX\tY\tZ\tL*\ta*\tb*");
+ if (pspec) { /* Print or plot out spectrum */
+ for (j = 0; j < sp.spec_n; j++) {
+ double wvl = sp.spec_wl_short
+ + j * (sp.spec_wl_long - sp.spec_wl_short)/(sp.spec_n-1);
+ fprintf(fp,"\t%3f",wvl);
+ }
+ }
+ fprintf(fp,"\n");
+ loghead = 1;
+ }
+
+ /* Print results */
+ fprintf(fp,"%d\t%f\t%f\t%f\t%f\t%f\t%f",
+ ix, XYZ[0], XYZ[1], XYZ[2], Lab[0], Lab[1], Lab[2]);
+
+ if (pspec != 0) {
+ for (j = 0; j < sp.spec_n; j++)
+ fprintf(fp,"\t%8.3f",sp.spec[j]);
+ }
+ fprintf(fp,"\n");
+ }
+ }
+ } /* Next reading */
+
+ /* Release paper */
+ if (cap2 & inst2_xy_holdrel) {
+ it->xy_clear(it);
+ }
+
+#ifdef DEBUG
+ printf("About to exit\n");
+#endif
+
+ if (sp2cie != NULL)
+ sp2cie->del(sp2cie);
+ for (i = 0; i < 26; i++)
+ if (sp2cief[i] != NULL)
+ sp2cief[i]->del(sp2cief[i]);
+
+ /* Free instrument */
+ it->del(it);
+
+ icmps->del(icmps);
+
+ if (fp != NULL)
+ fclose(fp);
+
+ return 0;
+}
+
+
+
+
diff --git a/spectro/spyd2.c b/spectro/spyd2.c
new file mode 100644
index 0000000..f8f9bac
--- /dev/null
+++ b/spectro/spyd2.c
@@ -0,0 +1,3808 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * Datacolor/ColorVision Spyder 2/3/4 related software.
+ *
+ * Author: Graeme W. Gill
+ * Date: 17/9/2007
+ *
+ * Copyright 2006 - 2013, Graeme W. Gill
+ * All rights reserved.
+ *
+ * (Based initially on i1disp.c)
+ *
+ * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
+ * see the License2.txt file for licencing details.
+ */
+
+/*
+ IMPORTANT NOTES:
+
+ The Spyder 2 instrument cannot function without the driver software
+ having access to the vendor supplied PLD firmware pattern for it.
+ This firmware is not provided with Argyll, since it is not available
+ under a compatible license.
+
+ The purchaser of a Spyder 2 instrument should have received a copy
+ of this firmware along with their instrument, and should therefore be able to
+ enable the Argyll driver for this instrument by using the spyd2en utility
+ to create a spyd2PLD.bin file.
+
+ [ The Spyder 3 & 4 don't need a PLD firmware file. ]
+
+
+ The Spyder 4 instrument will not have the full range of manufacturer
+ calibration settings available without the vendor calibration data.
+ This calibration day is not provided with Argyll, since it is not
+ available under a compatible license.
+
+ The purchaser of a Spyder 4 instrument should have received a copy
+ of this calibration data along with their instrument, and should therefore
+ be able to enable the use of the full range of calibration settings
+ by using the spyd4en utility to create a spyd4cal.bin file.
+
+ Alternatively, you can use Argyll .ccss files to set a Spyder 4
+ calibration.
+
+ [ The Spyder 2 & 3 don't need a calibration data file. ]
+
+ The hwver == 5 code is not fully implemented.
+ It's not possible to get it going without an instrument to verify on.
+ (Perhaps it has only 4 sensors ?)
+
+ The frequency measurement is not very accurate, particularly for
+ the Spyder 3 & 4, being too low by about 3.5%.
+
+ */
+
+/*
+ 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:
+
+ Would be good to add read/write data values if debug >= 3
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#include <math.h>
+#include <fcntl.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* SALONEINSTLIB */
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* SALONEINSTLIB */
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "spyd2.h"
+
+#undef PLOT_SPECTRA /* Plot the sensor senitivity spectra */
+#undef PLOT_SPECTRA_EXTRA /* Plot the sensor senitivity spectra extra values */
+#undef SAVE_SPECTRA /* Save the sensor senitivity spectra to "sensors.sp" */
+#undef SAVE_XYZSPECTRA /* Save the XYZ senitivity spectra to "sensorsxyz.sp" (scale 1.4) */
+#undef SAVE_STDXYZ /* save 1931 2 degree to stdobsxyz.sp */
+
+
+#define DO_RESETEP /* Do the miscelanous resetep()'s */
+#define CLKRATE 1000000 /* Clockrate the Spyder 2 hardware runs at */
+#define MSECDIV (CLKRATE/1000) /* Divider to turn clocks into msec */
+#define DEFRRATE 50 /* Default display refresh rate */
+#define DO_ADAPTIVE /* Adapt the integration time to the light level */
+ /* This helps repeatability at low levels A LOT */
+
+#define LEVEL2 /* Second level (nonliniarity) calibration */
+#define RETRIES 4 /* usb_reads are unreliable - bug in spyder H/W ?*/
+
+#ifdef DO_ADAPTIVE
+# define RINTTIME 2.0 /* Base time to integrate reading over - refresh display */
+# define NINTTIME 2.0 /* Base time to integrate reading over - non-refresh display */
+#else /* !DO_ADAPTIVE */
+# define RINTTIME 5.0 /* Integrate over fixed longer time (manufacturers default) */
+# define NINTTIME 5.0 /* Integrate over fixed longer time (manufacturers default) */
+#endif /* !DO_ADAPTIVE */
+
+static inst_code spyd2_interp_code(inst *pp, int ec);
+
+/* ------------------------------------------------------------------------ */
+/* Implementation */
+
+/* Interpret an icoms error into a SPYD2 error */
+static int icoms2spyd2_err(int se) {
+ if (se != ICOM_OK)
+ return SPYD2_COMS_FAIL;
+ return SPYD2_OK;
+}
+
+/* ----------------------------------------------------- */
+/* 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] = (unsigned char)(inv >> 24) & 0xff;
+ buf[1] = (unsigned char)(inv >> 16) & 0xff;
+ buf[2] = (unsigned char)(inv >> 8) & 0xff;
+ buf[3] = (unsigned char)(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] = (unsigned char)(inv >> 8) & 0xff;
+ buf[1] = (unsigned char)(inv >> 0) & 0xff;
+}
+
+/* Take a short sized buffer, and convert it to an int big endian */
+static int buf2short(unsigned char *buf) {
+ int val;
+ val = buf[0];
+ val = ((val << 8) + (0xff & buf[1]));
+ return val;
+}
+
+/* Take a unsigned short sized buffer, and convert it to an int big endian */
+static int buf2ushort(unsigned char *buf) {
+ int val;
+ val = (0xff & buf[0]);
+ val = ((val << 8) + (0xff & buf[1]));
+ return val;
+}
+
+/* Take a word sized buffer, and convert it to an int big endian. */
+static int buf2int(unsigned char *buf) {
+ int val;
+ val = buf[0];
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[3]));
+ return val;
+}
+
+/* Take a word sized buffer, and convert it to an unsigned int big endian. */
+static unsigned int buf2uint(unsigned char *buf) {
+ unsigned int val;
+ val = buf[0];
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[3]));
+ return val;
+}
+
+
+/* Take a 3 byte buffer, and convert it to an unsigned int big endian. */
+static unsigned int buf2uint24(unsigned char *buf) {
+ unsigned int val;
+ val = buf[0];
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[2]));
+ return val;
+}
+
+/* Take a 24 bit unsigned sized buffer in little endian */
+/* format, and return an int */
+static unsigned int buf2uint24le(unsigned char *buf) {
+ unsigned int val;
+ val = (0xff & buf[2]);
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* Take a 24 bit unsigned sized buffer in little endian */
+/* nibble swapped format, and return an int */
+static unsigned int buf2uint24lens(unsigned char *buf) {
+ unsigned int val;
+ val = (0xf & buf[2]);
+ val = (val << 4) + (0xf & (buf[2] >> 4));
+ val = (val << 4) + (0xf & buf[1]);
+ val = (val << 4) + (0xf & (buf[1] >> 4));
+ val = (val << 4) + (0xf & buf[0]);
+ val = (val << 4) + (0xf & (buf[0] >> 4));
+ return val;
+}
+
+/* Take a 64 sized return buffer, and convert it to a ORD64 */
+static ORD64 buf2ord64(unsigned char *buf) {
+ ORD64 val;
+ val = buf[7];
+ val = ((val << 8) + (0xff & buf[6]));
+ val = ((val << 8) + (0xff & buf[5]));
+ val = ((val << 8) + (0xff & buf[4]));
+ val = ((val << 8) + (0xff & buf[3]));
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* ============================================================ */
+/* Low level commands */
+
+
+/* USB Instrument commands */
+
+/* Spyder 2: Reset the instrument */
+static inst_code
+spyd2_reset(
+ spyd2 *p
+) {
+ int se;
+ int retr;
+ inst_code rv = inst_ok;
+
+ a1logd(p->log, 3, "spyd2_reset: called\n");
+
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC7, 0, 0, NULL, 0, 5.0);
+
+ if (se == ICOM_OK) {
+ a1logd(p->log, 6, "spyd2_reset: complete, ICOM code 0x%x\n",se);
+ break;
+ }
+ if (retr >= RETRIES ) {
+ a1logd(p->log, 1, "spyd2_reset: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_reset: reset retry with ICOM err 0x%x\n",se);
+ }
+
+ return rv;
+}
+
+/* Spyder 2: Get status */
+/* return pointer may be NULL if not needed. */
+static inst_code
+spyd2_getstatus(
+ spyd2 *p,
+ int *stat /* Return the 1 byte status code */
+) {
+ unsigned char pbuf[8]; /* status bytes read */
+ int _stat;
+ int se;
+ int retr;
+ inst_code rv = inst_ok;
+
+ a1logd(p->log, 3, "spyd2_getstatus: called\n");
+
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC6, 0, 0, pbuf, 8, 5.0);
+
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES ) {
+ a1logd(p->log, 1, "spyd2_getstatus: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_getstatus: retry with ICOM err 0x%x\n",se);
+ }
+ msec_sleep(100); /* Limit rate status commands can be given */
+
+ _stat = pbuf[0]; /* Only the first byte is examined. */
+ /* Other bytes have information, but SW ignores them */
+
+ a1logd(p->log, 3, "spyd2_getstatus: returns %d ICOM err 0x%x\n", _stat, se);
+
+ if (stat != NULL) *stat = _stat;
+
+ return rv;
+}
+
+/* Read Serial EEProm bytes (implementation) */
+/* Can't read more than 256 in one go */
+static inst_code
+spyd2_readEEProm_imp(
+ spyd2 *p,
+ unsigned char *buf, /* Buffer to return bytes in */
+ int addr, /* Serial EEprom address, 0 - 1023 */
+ int size /* Number of bytes to read, 0 - 128 (ie. max of bank) */
+) {
+ int se;
+ int retr;
+ inst_code rv = inst_ok;
+
+ a1logd(p->log, 3, "spyd2_readEEProm_imp: addr %d, bytes %d\n",addr,size);
+
+ if (addr < 0
+ || (p->hwver < 7 && (addr + size) > 512)
+ || (p->hwver == 7 && (addr + size) > 1024))
+ return spyd2_interp_code((inst *)p, SPYD2_BAD_EE_ADDRESS);
+
+ if (size >= 256)
+ return spyd2_interp_code((inst *)p, SPYD2_BAD_EE_SIZE);
+
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC4, addr, size, buf, size, 5.0);
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES) {
+ a1logd(p->log, 1, "spyd2_readEEProm_imp: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_readEEProm_imp: retry with ICOM err 0x%x\n",se);
+ }
+
+
+ a1logd(p->log, 3, "spyd2_readEEProm_imp: returning ICOM err 0x%x\n", se);
+
+ return rv;
+}
+
+/* Read Serial EEProm bytes */
+/* (Handles reads > 256 bytes) */
+static inst_code
+spyd2_readEEProm(
+ spyd2 *p,
+ unsigned char *buf, /* Buffer to return bytes in */
+ int addr, /* Serial EEprom address, 0 - 511 */
+ int size /* Number of bytes to read, 0 - 511 */
+) {
+
+ if (addr < 0
+ || (p->hwver < 7 && (addr + size) > 512)
+ || (p->hwver == 7 && (addr + size) > 1024))
+ return spyd2_interp_code((inst *)p, SPYD2_BAD_EE_ADDRESS);
+
+ while (size > 255) { /* Single read is too big */
+ inst_code rv;
+ if ((rv = spyd2_readEEProm_imp(p, buf, addr, 255)) != inst_ok)
+ return rv;
+ size -= 255;
+ buf += 255;
+ addr += 255;
+ }
+ return spyd2_readEEProm_imp(p, buf, addr, size);
+}
+
+/* Spyder 2: Download PLD pattern */
+static inst_code
+spyd2_loadPLD(
+ spyd2 *p,
+ unsigned char *buf, /* Bytes to download */
+ int size /* Number of bytes */
+) {
+ int se;
+ int retr;
+ inst_code rv = inst_ok;
+
+ a1logd(p->log, 6, "spyd2_loadPLD: Load PLD %d bytes\n",size);
+
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC0, 0, 0, buf, size, 5.0);
+
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES ) {
+ a1logd(p->log, 1, "spyd2_loadPLD: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_loadPLD: retry with ICOM err 0x%x\n",se);
+ }
+
+ a1logd(p->log, 6, "spyd2_loadPLD: returns ICOM err 0x%x\n", se);
+
+ return rv;
+}
+
+/* Get minmax command. */
+/* Figures out the current minimum and maximum frequency periods */
+/* so as to be able to set a frame detect threshold. */
+/* Note it returns 0,0 if there is not enough light. */
+/* (The light to frequency output period size is inversly */
+/* related to the lightness level) */
+/* (This isn't used by the manufacturers Spyder3/4 driver, */
+/* but the instrument seems to impliment it.) */
+static inst_code
+spyd2_GetMinMax(
+ spyd2 *p,
+ int *clocks, /* Number of clocks to use (may get limited) */
+ int *min, /* Return min and max light->frequency periods */
+ int *max
+) {
+ int rwbytes; /* Data bytes read or written */
+ int se;
+ inst_code rv = inst_ok;
+ int value;
+ int index;
+ int retr;
+ unsigned char buf[8]; /* return bytes read */
+
+ a1logd(p->log, 2, "spyd2_GetMinMax: %d clocks\n",*clocks);
+
+ /* Issue the triggering command */
+ if (*clocks > 0xffffff)
+ *clocks = 0xffffff; /* Maximum count hardware will take ? */
+ value = *clocks >> 8;
+ value = (value >> 8) | ((value << 8) & 0xff00); /* Convert to big endian */
+ index = (*clocks << 8) & 0xffff;
+ index = (index >> 8) | ((index << 8) & 0xff00); /* Convert to big endian */
+
+ for (retr = 0; ; retr++) {
+ /* Issue the trigger command */
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC2, value, index, NULL, 0, 5.0);
+
+ if ((se != ICOM_OK && retr >= RETRIES)) {
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ msec_sleep(*clocks/MSECDIV);
+ p->icom->usb_read(p->icom, NULL, 0x81, buf, 8, &rwbytes, 1.0);
+
+ a1logd(p->log, 1, "spyd2_GetMinMax: trig failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ if (se != ICOM_OK) {
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ msec_sleep(*clocks/MSECDIV);
+ p->icom->usb_read(p->icom, NULL, 0x81, buf, 8, &rwbytes, 1.0);
+
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_GetMinMax: trig retry with ICOM err 0x%x\n",se);
+ continue;
+ }
+
+ a1logd(p->log, 3, "spyd2_GetMinMax: trig returns ICOM err 0x%x\n", se);
+
+ /* Allow some time for the instrument to respond */
+ msec_sleep(*clocks/MSECDIV);
+
+ /* Now read the bytes */
+ se = p->icom->usb_read(p->icom, NULL, 0x81, buf, 8, &rwbytes, 5.0);
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES) {
+ a1logd(p->log, 1, "spyd2_GetMinMax: get failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_GetMinMax: get retry with ICOM err 0x%x\n",se);
+ }
+
+ if (rwbytes != 8) {
+ a1logd(p->log, 1, "spyd2_GetMinMax: got short data read %d",rwbytes);
+ return spyd2_interp_code((inst *)p, SPYD2_BADREADSIZE);
+ }
+
+ *min = buf2ushort(&buf[0]);
+ *max = buf2ushort(&buf[2]);
+
+ a1logd(p->log, 3, "spyd2_GetMinMax: got %d/%d returns ICOM err 0x%x\n", *min, *max, se);
+
+ return rv;
+}
+
+/* Get refresh rate (low level) command */
+/* (This isn't used by the manufacturers Spyder3 driver, */
+/* but the instrument seems to implement it.) */
+static inst_code
+spyd2_GetRefRate_ll(
+ spyd2 *p,
+ int *clocks, /* Maximum number of clocks to use */
+ int nframes, /* Number of frames to count */
+ int thresh, /* Frame detection threshold */
+ int *minfclks, /* Minimum number of clocks per frame */
+ int *maxfclks, /* Maximum number of clocks per frame */
+ int *clkcnt /* Return number of clocks for nframes frames */
+) {
+ int rwbytes; /* Data bytes read or written */
+ int se;
+ inst_code rv = inst_ok;
+ int value;
+ int index;
+ int flag;
+ int retr;
+ unsigned char buf1[8]; /* send bytes */
+ unsigned char buf2[8]; /* return bytes read */
+
+ a1logd(p->log, 3, "spyd2_GetRefRate_ll: %d clocks\n",*clocks);
+
+ /* Setup the triggering parameters */
+ if (*clocks > 0xffffff) /* Enforce hardware limits */
+ *clocks = 0xffffff;
+ if (*minfclks > 0x7fff)
+ *minfclks = 0x7fff;
+ if (*maxfclks > 0x7fff)
+ *maxfclks = 0x7fff;
+ value = *clocks >> 8;
+ value = (value >> 8) | ((value << 8) & 0xff00); /* Convert to big endian */
+ index = (*clocks << 8) & 0xffff;
+ index = (index >> 8) | ((index << 8) & 0xff00); /* Convert to big endian */
+
+ /* Setup parameters in send buffer */
+ short2buf(&buf1[0], thresh);
+ short2buf(&buf1[2], nframes);
+ short2buf(&buf1[4], *minfclks);
+ short2buf(&buf1[6], *maxfclks);
+
+ /* Issue the triggering command */
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC3, value, index, buf1, 8, 5.0);
+
+ if (se != ICOM_OK && retr >= RETRIES) {
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ msec_sleep(*clocks/MSECDIV);
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 1.0);
+
+ a1logd(p->log, 1, "spyd2_GetRefRate_ll: trig failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ if (se != ICOM_OK) {
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ msec_sleep(*clocks/MSECDIV);
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 1.0);
+
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_GetRefRate_ll: trig retry with ICOM err 0x%x\n",se);
+ continue;
+ }
+
+ a1logd(p->log, 3, "spyd2_GetRefRate_ll: trig returns ICOM err 0x%x\n", se);
+
+ /* Allow some time for the instrument to respond */
+ msec_sleep(*clocks/MSECDIV);
+
+ /* Now read the bytes */
+ se = p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 5.0);
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES) {
+ a1logd(p->log, 3, "spyd2_GetRefRate_ll: get failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_GetRefRate_ll: get retry with ICOM err 0x%x\n",se);
+ }
+
+ if (rwbytes != 8) {
+ a1logd(p->log, 1, "spyd2_GetRefRate_ll: got short data read %d",rwbytes);
+ return spyd2_interp_code((inst *)p, SPYD2_BADREADSIZE);
+ }
+
+ flag = buf2[0];
+ *clkcnt = buf2uint24(&buf2[1]);
+
+ /* Spyder2 */
+ if (p->hwver < 4 && flag == 1) {
+ a1logd(p->log, 1, "spyd2_GetRefRate_ll: got trigger timeout");
+ return spyd2_interp_code((inst *)p, SPYD2_TRIGTIMEOUT);
+ }
+
+ /* Spyder2 */
+ if (p->hwver < 4 && flag == 2) {
+ a1logd(p->log, 1, "spyd2_GetRefRate_ll: got overall timeout");
+ return spyd2_interp_code((inst *)p, SPYD2_OVERALLTIMEOUT);
+ }
+
+ a1logd(p->log, 3, "spyd2_GetRefRate_ll: result %d, returns ICOM err 0x%x\n", *clkcnt, se);
+
+ return rv;
+}
+
+/* Get a reading (low level) command */
+static inst_code
+spyd2_GetReading_ll(
+ spyd2 *p,
+ int *clocks, /* Nominal/Maximum number of integration clocks to use */
+ int nframes, /* Number of refresh frames being measured (not used ?) */
+ int thresh, /* Frame detection threshold */
+ int *minfclks, /* Minimum number of clocks per frame */
+ int *maxfclks, /* Maximum number of clocks per frame */
+ double *sensv, /* Return the 8 sensor readings (may be NULL) */
+ int *maxtcnt, /* Return the maximum transition count (may be NULL) */
+ int *mintcnt /* Return the minimum transition count (may be NULL) */
+) {
+ int rwbytes; /* Data bytes read or written */
+ int se;
+ inst_code rv = inst_ok;
+ int value;
+ int index;
+ int flag;
+ int nords, retr;
+ unsigned char buf1[8]; /* send bytes */
+ unsigned char buf2[9 * 8]; /* return bytes read */
+ int rvals[3][8]; /* Raw values */
+ int _maxtcnt = 0; /* Maximum transition count */
+ int _mintcnt = 0x7fffffff; /* Minumum transition count */
+ int i, j, k;
+
+ a1logd(p->log, 3, "spyd2_GetReading_ll: clocks = %d, minfc = %d, maxfc = %d\n",*clocks,*minfclks,*maxfclks);
+
+ /* Setup the triggering parameters */
+ if (*clocks > 0xffffff)
+ *clocks = 0xffffff;
+ if (*minfclks > 0x7fff)
+ *minfclks = 0x7fff;
+ if (*maxfclks > 0x7fff)
+ *maxfclks = 0x7fff;
+ value = *clocks >> 8;
+ if (p->hwver == 5) { /* Hmm. not sure if this is right */
+ value /= 1000;
+ }
+ value = (value >> 8) | ((value << 8) & 0xff00); /* Convert to big endian */
+ index = (*clocks << 8) & 0xffff;
+ index = (index >> 8) | ((index << 8) & 0xff00); /* Convert to big endian */
+
+ /* Setup parameters in send buffer */
+ /* (Spyder3 doesn't seem to use these. Perhaps it does its */
+ /* own internal refresh detection and syncronization ?) */
+ thresh *= 256;
+ int2buf(&buf1[0], thresh);
+ short2buf(&buf1[4], *minfclks);
+ short2buf(&buf1[6], *maxfclks);
+
+ /* If we aborted a read, the prevraw values are now invalid. */
+ /* We fix it by doing a dumy reading. */
+ if (p->hwver < 4 && p->prevrawinv) {
+ int clocks = 500;
+ int minfclks = 0;
+ int maxfclks = 0;
+ p->prevrawinv = 0;
+ a1logd(p->log, 3, "spyd2_GetReading_ll: doing dummy read to get prevraw\n");
+ if ((rv = spyd2_GetReading_ll(p, &clocks, 10, 0, &minfclks, &maxfclks, NULL, NULL, NULL)) != inst_ok) {
+ a1logd(p->log, 1, "spyd2_GetReading_ll: dummy read failed\n");
+ p->prevrawinv = 1;
+ return rv;
+ }
+ }
+
+ /* The Spyder comms seems especially flakey... */
+ for (retr = 0, nords = 1; ; retr++, nords++) {
+
+//int start = msec_time();
+
+ /* Issue the triggering command */
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC1, value, index, buf1, 8, 5.0);
+
+ if (se != ICOM_OK && retr >= RETRIES) {
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ msec_sleep(*clocks/MSECDIV);
+ for (i = 0; i < (1+9); i++)
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 1.0);
+ p->prevrawinv = 1; /* prevraw are now invalid */
+
+ a1logd(p->log, 1, "spyd2_GetReading_ll: trig failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ if (se != ICOM_OK) {
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ msec_sleep(*clocks/MSECDIV);
+ for (i = 0; i < (1+9); i++)
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 1.0);
+ p->prevrawinv = 1; /* prevraw are now invalid */
+
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_GetReading_ll: trig retry with ICOM err 0x%x\n",se);
+ continue;
+ }
+
+ a1logd(p->log, 3, "spyd2_GetReading_ll: reading returns ICOM code 0x%x\n", se);
+
+ /* Allow some time for the instrument to respond */
+ msec_sleep(*clocks/MSECDIV);
+
+ /* Now read the first 8 bytes (status etc.) */
+ se = p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 5.0);
+ if (se != ICOM_OK && retr >= RETRIES) {
+ a1logd(p->log, 1, "spyd2_GetReading_ll: read stat failed with ICOM err 0x%x\n",se);
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ for (i = 0; i < (1+9); i++)
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 0.5);
+ p->prevrawinv = 1; /* prevraw are now invalid */
+ msec_sleep(500);
+
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+//printf("~1 trig -> read = %d msec\n",msec_time() - start);
+
+ if (se != ICOM_OK) {
+ a1logd(p->log, 1, "spyd2_GetReading_ll: read stat retry with ICOM err 0x%x\n", se);
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ for (i = 0; i < (1+9); i++)
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 0.5);
+ p->prevrawinv = 1; /* prevraw are now invalid */
+ msec_sleep(500);
+ continue; /* Retry the whole command */
+ }
+
+ if (rwbytes != 8) {
+ a1logd(p->log, 1, "spyd2_GetReading_ll: read stat got short data read %d",rwbytes);
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ for (i = 0; i < (1+9); i++)
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 0.5);
+ p->prevrawinv = 1; /* prevraw are now invalid */
+ msec_sleep(500);
+
+ return spyd2_interp_code((inst *)p, SPYD2_BADREADSIZE);
+ }
+
+ flag = buf2[0];
+
+ /* Spyder2 */
+ if (p->hwver < 4 && flag == 1) {
+ a1logd(p->log, 1, "spyd2_GetReading_ll: read stat is trigger timeout");
+ return spyd2_interp_code((inst *)p, SPYD2_TRIGTIMEOUT);
+ }
+
+ /* Spyder2 */
+ if (p->hwver < 4 && flag == 2) {
+ a1logd(p->log, 1, "spyd2_GetReading_ll: read stat is overall timeout");
+ return spyd2_interp_code((inst *)p, SPYD2_OVERALLTIMEOUT);
+ }
+
+ /* Now read the following 9 x 8 bytes of sensor data */
+ for (i = 0; i < 9; i++) {
+
+ se = p->icom->usb_read(p->icom, NULL, 0x81, buf2 + i * 8, 8, &rwbytes, 5.0);
+ if (se != ICOM_OK && retr >= RETRIES) {
+ int ii = i;
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ for (; ii < (1+9); ii++)
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2 + i * 8, 8, &rwbytes, 0.5);
+ p->prevrawinv = 1; /* prevraw are now invalid */
+
+ a1logd(p->log, 1, "spyd2_GetReading_ll: get reading failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ if (se != ICOM_OK) {
+ int ii = i;
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ for (; ii < (1+9); ii++)
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2 + i * 8, 8, &rwbytes, 0.5);
+ p->prevrawinv = 1; /* prevraw are now invalid */
+
+ break;
+ }
+ if (rwbytes != 8) {
+ int ii = i;
+ a1logd(p->log, 1, "spyd2_GetReading_ll: got short data read %d",rwbytes);
+
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ for (; ii < (1+9); ii++)
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2 + i * 8, 8, &rwbytes, 0.5);
+ p->prevrawinv = 1; /* prevraw are now invalid */
+
+ return spyd2_interp_code((inst *)p, SPYD2_BADREADSIZE);
+ }
+ }
+
+ if (i >= 9)
+ break; /* We're done */
+
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_GetReading_ll: reading retry with ICOM err 0x%x\n",se);
+
+#ifdef DO_RESETEP /* Do the miscelanous resetep()'s */
+ a1logd(p->log, 1, "spyd2_GetReading_ll: resetting end point\n");
+ p->icom->usb_resetep(p->icom, 0x81);
+ msec_sleep(1); /* Let device recover ? */
+#endif /* DO_RESETEP */
+ } /* End of whole command retries */
+
+ if (p->log->debug >= 5) {
+ a1logd(p->log, 5, "spyd2_GetReading_ll: got bytes:\n");
+ for (i = 0; i < 9; i++) {
+ a1logd(p->log, 5, " %d: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",i * 8,
+ buf2[i * 8 + 0], buf2[i * 8 + 1], buf2[i * 8 + 2], buf2[i * 8 + 3],
+ buf2[i * 8 + 4], buf2[i * 8 + 5], buf2[i * 8 + 6], buf2[i * 8 + 7]);
+ }
+ }
+
+ /* Spyder 2 decoding */
+ /* Spyder2 */
+ if (p->hwver < 4) {
+ /* Convert the raw buffer readings into 3 groups of 8 integers. */
+ /* At the start of each reading, the HW starts counting master */
+ /* (1MHz) clocks. When the first transition after the start of */
+ /* the reading is received from a light->frequency sensor (TAOS TSL237), */
+ /* the clock count is recorded, and returned as the second of the three */
+ /* numbers returned for each sensor. */
+ /* When the last transition before the end of the reading period is */
+ /* received, the clock count is recorded, and returned as the first */
+ /* of the three numbers. The integration period is therefore */
+ /* the first number minus the second. */
+ /* The third number is the number of transitions from the sensor */
+ /* counted during the integration period. Since this 24 bit counter is */
+ /* not reset between readings, the previous count is recorded (prevraw[]), */
+ /* and subtracted (modulo 24 bits) from the current value. */
+ /* The light level is directly proportional to the frequency, */
+ /* hence the transitions-1 counted. */
+ /* In the case of a CRT, the total number of clocks is assumed to be */
+ /* set to an integer number of refresh cycles, and the total transitions */
+ /* over that period are counted. */
+ for (i = j = 0; j < 3; j++) {
+ for (k = 0; k < 8; k++, i += 3) {
+ rvals[j][k] = buf2uint24lens(buf2 + i);
+// a1logd(p->log, 1, "got rvals[%d][%d] = 0x%x\n",j,k,rvals[j][k]);
+ }
+ }
+
+ /* And convert 3 values per sensor into sensor values */
+ for (k = 0; k < 8; k++) {
+ int transcnt, intclks;
+
+ /* Compute difference of L2F count to previous value */
+ /* read, modulo 24 bits */
+ a1logd(p->log, 5, "%d: transcnt %d, previous %d\n", k,rvals[2][k],p->prevraw[k]);
+ if (p->prevraw[k] <= rvals[2][k]) {
+ transcnt = rvals[2][k] - p->prevraw[k];
+ } else {
+ transcnt = rvals[2][k] + 0x1000000 - p->prevraw[k];
+ }
+
+ p->prevraw[k] = rvals[2][k]; /* New previuos value */
+
+ /* Compute difference of 1MHz clock count of first to second value */
+ intclks = rvals[0][k] - rvals[1][k];
+
+ if (transcnt == 0 || intclks <= 0) { /* It's too dark ... */
+ if (sensv != NULL)
+ sensv[k] = 0.0;
+ _mintcnt = 0;
+ } else { /* We discard 0'th L2F transion to give transitions */
+ /* over the time period. */
+ if (sensv != NULL)
+ sensv[k] = ((double)transcnt - 1.0) * (double)CLKRATE
+ / ((double)nords * (double)intclks);
+ if (transcnt > _maxtcnt)
+ _maxtcnt = transcnt;
+ if (transcnt < _mintcnt)
+ _mintcnt = transcnt;
+ }
+ if (p->log->debug >= 4 && sensv != NULL)
+ a1logd(p->log, 4, "%d: initial senv %f from transcnt %d and intclls %d\n",
+ k,sensv[k],transcnt,intclks);
+
+#ifdef NEVER /* This seems to make repeatability worse ??? */
+ /* If CRT and bright enough */
+ if (sensv != NULL && sensv[k] > 1.5 && p->refrmode != 0) {
+ sensv[k] = ((double)transcnt) * (double)p->refrate/(double)nframes;
+ }
+#endif
+ }
+
+ /* Spyder 3/4 decoding */
+ } else {
+ /* Convert the raw buffer readings into 3 groups of 8 integers. */
+ /* At the start of each reading, the HW starts counting master */
+ /* (1MHz) clocks. When the first transition after the start of */
+ /* the reading is received from a light->frequency sensor (TAOS TSL238T), */
+ /* the clock count is recorded, and returned as the second of the three */
+ /* numbers returned for each sensor. */
+ /* When the last transition before the end of the reading period is */
+ /* received, the clock count is recorded, and returned as the first */
+ /* of the three numbers. The integration period is therefore */
+ /* the first number minus the second. */
+ /* The third number is the number of transitions from the sensor */
+ /* counted during the integration period. */
+ /* The light level is directly proportional to the frequency, */
+ /* hence the transitions-1 counted. */
+
+ int map[8] = { 0,0,1,2,5,6,7,4 }; /* Map sensors into Spyder 2 order */
+
+ for (j = 0; j < 3; j++) {
+ for (k = 0; k < 8; k++) {
+ rvals[j][k] = buf2uint24le(buf2 + (j * 24 + map[k] * 3));
+// a1logd(p->log, 1, "got rvals[%d][%d] = 0x%x\n",j,k,rvals[j][k]);
+ }
+ }
+
+ /* And convert 3 integers per sensor into sensor values */
+ for (k = 0; k < 8; k++) {
+ int transcnt, intclks;
+
+ /* Number of sensor transitions */
+ transcnt = rvals[2][k];
+
+ /* Compute difference of first integer to second */
+ intclks = rvals[0][k] - rvals[1][k];
+
+ if (transcnt == 0 || intclks <= 0) { /* It's too dark ... */
+ if (sensv != NULL)
+ sensv[k] = 0.0;
+ _mintcnt = 0;
+ } else { /* Transitions within integration period */
+ /* hence one is discarded ? */
+ if (sensv != NULL)
+ sensv[k] = ((double)transcnt - 1.0) * (double)CLKRATE
+ / ((double)intclks * 8.125);
+ if (transcnt > _maxtcnt)
+ _maxtcnt = transcnt;
+ if (transcnt < _mintcnt)
+ _mintcnt = transcnt;
+ }
+ if (p->log->debug >= 4 && sensv != NULL)
+ a1logd(p->log, 4, "%d: initial senv %f from transcnt %d and intclls %d\n",
+ k,sensv[k],transcnt,intclks);
+ }
+ }
+
+ if (maxtcnt != NULL)
+ *maxtcnt = _maxtcnt;
+ if (mintcnt != NULL)
+ *mintcnt = _mintcnt;
+
+ return rv;
+}
+
+/* Spyder 3: Set the LED */
+static inst_code
+spyd2_setLED(
+ spyd2 *p,
+ int mode, /* LED mode: 0 = off, 1 = pulse, 2 = on */
+ double period /* Pulse period in seconds */
+) {
+ int se;
+ inst_code rv = inst_ok;
+ int value;
+ int index;
+ int retr;
+ int ptime; /* Pulse time, 1 - 255 x 20 msec */
+
+ if (mode < 0)
+ mode = 0;
+ else if (mode > 2)
+ mode = 2;
+ ptime = (int)(period/0.02 + 0.5);
+ if (ptime < 0)
+ ptime = 0;
+ else if (ptime > 255)
+ ptime = 255;
+
+ if (p->log->debug >= 2) {
+ if (mode == 1)
+ a1logd(p->log, 3, "spyd2_setLED: set to pulse, %f secs\n",ptime * 0.02);
+ else
+ a1logd(p->log, 3, "spyd2_setLED: set to %s\n",mode == 0 ? "off" : "on");
+ }
+
+ value = mode; /* Leave little endian */
+ index = ptime;
+
+ for (retr = 0; ; retr++) {
+ /* Issue the trigger command */
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xF6, value, index, NULL, 0, 5.0);
+
+ if (se == ICOM_OK) {
+ a1logd(p->log, 5, "spyd2_setLED: OK, ICOM code 0x%x\n",se);
+ break;
+ }
+ if (retr >= RETRIES) {
+ a1logd(p->log, 1, "spyd2_setLED: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_setLED: retry with ICOM err 0x%x\n",se);
+ }
+
+ return rv;
+}
+
+
+/* Spyder 3: Set the ambient control register */
+static inst_code
+spyd2_SetAmbReg(
+ spyd2 *p,
+ int val /* 8 bit ambient config register value */
+) {
+ int se;
+ inst_code rv = inst_ok;
+ int value;
+ int retr;
+
+ a1logd(p->log, 3, "spyd2_SetAmbReg: control register to %d\n",val);
+
+ if (val < 0)
+ val = 0;
+ else if (val > 255)
+ val = 255;
+ value = val; /* Leave little endian */
+
+ for (retr = 0; ; retr++) {
+ /* Issue the trigger command */
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xF3, value, 0, NULL, 0, 5.0);
+
+ if (se == ICOM_OK) {
+ a1logd(p->log, 5, "spyd2_SetAmbReg: OK, ICOM code 0x%x\n",se);
+ break;
+ }
+ if (retr >= RETRIES) {
+ a1logd(p->log, 1, "spyd2_SetAmbReg: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_SetAmbReg: retry with ICOM err 0x%x\n",se);
+ }
+
+ return rv;
+}
+
+/* Spyder3/4: Read ambient light timing */
+/* The byte value seems to be composed of:
+ bits 0,1
+ bits 4
+*/
+static inst_code
+spyd2_ReadAmbTiming(
+ spyd2 *p,
+ int *val /* Return the 8 bit timing value */
+) {
+ unsigned char pbuf[1]; /* Timing value read */
+ int _val;
+ int se;
+ int retr;
+ inst_code rv = inst_ok;
+
+ a1logd(p->log, 3, "spyd2_ReadAmbTiming: called\n");
+
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xF4, 0, 0, pbuf, 1, 5.0);
+
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES) {
+ a1logd(p->log, 1, "spyd2_ReadAmbTiming: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_ReadAmbTiming: retry with ICOM err 0x%x\n",se);
+ }
+
+ _val = pbuf[0];
+
+ a1logd(p->log, 5, "spyd2_ReadAmbTiming: returning val %d ICOM err 0x%x\n",_val, se);
+
+ if (val != NULL) *val = _val;
+
+ return rv;
+}
+
+
+/* Spyder3/4: Read ambient light channel 0 or 1 */
+static inst_code
+spyd2_ReadAmbChan(
+ spyd2 *p,
+ int chan, /* Ambient channel, 0 or 1 */
+ int *val /* Return the 16 bit channel value */
+) {
+ unsigned char pbuf[2]; /* Channel value read */
+ int _val;
+ int se;
+ int retr;
+ inst_code rv = inst_ok;
+
+ chan &= 1;
+
+ a1logd(p->log, 3, "spyd2_ReadAmbChan: channel %d\n",chan);
+
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xF0 + chan, 0, 0, pbuf, 2, 5.0);
+
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES) {
+ a1logd(p->log, 2, "spyd2_ReadAmbChan: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 2, "spyd2_ReadAmbChan: retry with ICOM err 0x%x\n",se);
+ }
+
+ _val = buf2ushort(pbuf);
+
+ a1logd(p->log, 3, "spyd2_ReadAmbChan: chan %d returning %d ICOM err 0x%x\n", chan, _val, se);
+
+ if (val != NULL) *val = _val;
+
+ return rv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Spyder3/4: Read temperature config */
+static inst_code
+spyd2_ReadTempConfig(
+ spyd2 *p,
+ int *val /* Return the 8 bit config value */
+) {
+ unsigned char pbuf[1]; /* Config value read */
+ int _val;
+ int se;
+ int retr;
+ inst_code rv = inst_ok;
+
+ a1logd(p->log, 3, "spyd2_ReadTempConfig: called\n");
+
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xE1, 0, 0, pbuf, 1, 5.0);
+
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES) {
+ a1logd(p->log, 1, "spyd2_ReadTempConfig: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_ReadTempConfig: retry with ICOM err 0x%x\n",se);
+ }
+
+ _val = pbuf[0];
+
+ a1logd(p->log, 3, "spyd2_ReadTempConfig: returning %d ICOM err 0x%x\n", _val, se);
+
+ if (val != NULL) *val = _val;
+
+ return rv;
+}
+
+/* Spyder 3/4: Write Register */
+static inst_code
+spyd2_WriteReg(
+ spyd2 *p,
+ int reg,
+ int val /* 8 bit temp config register value */
+) {
+ int se;
+ inst_code rv = inst_ok;
+ int value;
+ int retr;
+
+ a1logd(p->log, 3, "spyd2_WriteReg: val %d to register %d\n",reg, val);
+
+ if (val < 0)
+ val = 0;
+ else if (val > 255)
+ val = 255;
+ value = val; /* Leave little endian */
+
+ reg &= 0xff;
+ value |= (reg << 8);
+
+ for (retr = 0; ; retr++) {
+ /* Issue the trigger command */
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xE2, value, 0, NULL, 0, 5.0);
+
+ if (se == ICOM_OK) {
+ a1logd(p->log, 5, "spyd2_WriteReg: OK, ICOM code 0x%x\n",se);
+ break;
+ }
+ if (retr >= RETRIES) {
+ a1logd(p->log, 5, "spyd2_WriteReg: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 5, "spyd2_WriteReg: retry with ICOM err 0x%x\n",se);
+ }
+
+ return rv;
+}
+
+
+/* Spyder3/4: Read Register */
+static inst_code
+spyd2_ReadRegister(
+ spyd2 *p,
+ int reg,
+ int *pval /* Return the register value */
+) {
+ unsigned char pbuf[2]; /* Temp value read */
+ int ival;
+// double _val;
+ int se;
+ int retr;
+ inst_code rv = inst_ok;
+
+ reg &= 0xff;
+
+ a1logd(p->log, 3, "spyd2_ReadRegister: register %d\n",reg);
+
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xE0, reg, 0, pbuf, 2, 5.0);
+
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES) {
+ a1logd(p->log, 1, "spyd2_ReadRegister: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_ReadRegister: retry with ICOM err 0x%x\n",se);
+ }
+
+ ival = buf2ushort(&pbuf[0]);
+// _val = (double)ival * 12.5; /* Read temperature */
+
+ a1logd(p->log, 1, "spyd2_ReadRegister: reg %d returning %d ICOM err 0x%x\n", ival, rv, se);
+
+ if (pval != NULL) *pval = ival;
+
+ return rv;
+}
+
+
+/* hwver == 5, set gain */
+/* Valid gain values 1, 4, 16, 64 */
+static inst_code
+spyd2_SetGain(
+ spyd2 *p,
+ int gain
+) {
+ int gv = 0;
+
+ p->gain = (double)gain;
+
+ switch (gain) {
+ case 1:
+ gv = 0;
+ break;
+ case 4:
+ gv = 16;
+ break;
+ case 16:
+ gv = 32;
+ break;
+ case 64:
+ gv = 48;
+ break;
+ }
+ return spyd2_WriteReg(p, 7, gv);
+}
+
+/* ============================================================ */
+/* Medium level commands */
+
+/* Read a 8 bits from the EEProm */
+static inst_code
+spyd2_rd_ee_uchar(
+ spyd2 *p, /* Object */
+ unsigned int *outp, /* Where to write value */
+ int addr /* EEprom Address, 0 - 510 */
+) {
+ inst_code ev;
+ unsigned char buf[1];
+
+ if ((ev = spyd2_readEEProm(p, buf, addr, 1)) != inst_ok)
+ return ev;
+
+ *outp = buf[0];
+
+ return inst_ok;
+}
+
+/* Read a 16 bit word from the EEProm */
+static inst_code
+spyd2_rd_ee_ushort(
+ spyd2 *p, /* Object */
+ unsigned int *outp, /* Where to write value */
+ int addr /* EEprom Address, 0 - 510 */
+) {
+ inst_code ev;
+ unsigned char buf[2];
+
+ if ((ev = spyd2_readEEProm(p, buf, addr, 2)) != inst_ok)
+ return ev;
+
+ *outp = buf2ushort(buf);
+
+ return inst_ok;
+}
+
+/* Read a 32 bit word from the EEProm */
+static inst_code
+spyd2_rd_ee_int(
+ spyd2 *p, /* Object */
+ int *outp, /* Where to write value */
+ int addr /* EEprom Address, 0 - 508 */
+) {
+ inst_code ev;
+ unsigned char buf[4];
+
+ if ((ev = spyd2_readEEProm(p, buf, addr, 4)) != inst_ok)
+ return ev;
+
+ *outp = buf2int(buf);
+
+ return inst_ok;
+}
+
+/* Read a float from the EEProm */
+static inst_code
+spyd2_rdreg_float(
+ spyd2 *p, /* Object */
+ double *outp, /* Where to write value */
+ int addr /* Register Address, 0 - 508 */
+) {
+ inst_code ev;
+ int val;
+
+ if ((ev = spyd2_rd_ee_int(p, &val, addr)) != inst_ok)
+ return ev;
+
+ *outp = IEEE754todouble((unsigned int)val);
+ return inst_ok;
+}
+
+unsigned int spyd4_crctab[256];
+
+static void spyd4_crc32_init(void) {
+ int i, j;
+
+ unsigned int crc;
+
+ for (i = 0; i < 256; i++) {
+ crc = i;
+ for (j = 0; j < 8; j++) {
+ if (crc & 1)
+ crc = (crc >> 1) ^ 0xedb88320;
+ else
+ crc = crc >> 1;
+ }
+ spyd4_crctab[i] = crc;
+// a1logd(p->log, 1, "spyd4_crc32_init: crctab[%d] = 0x%08x\n",i,crctab[i]);
+ }
+}
+
+static unsigned int spyd4_crc32(unsigned char *data, int len) {
+ unsigned int crc;
+ int i;
+
+ crc = ~0;
+ for (i = 0; i < len; i++)
+ crc = spyd4_crctab[(crc ^ *data++) & 0xff] ^ (crc >> 8);
+ return ~crc;
+}
+
+/* For HWV 7, check the EEPRom CRC */
+static inst_code
+spyd2_checkEECRC(
+ spyd2 *p /* Object */
+) {
+ inst_code ev;
+ unsigned char buf[1024], *bp;
+ unsigned int crct, crc; /* Target value, computed value */
+ int i;
+
+ spyd4_crc32_init();
+
+ if ((ev = spyd2_readEEProm(p, buf, 0, 1024)) != inst_ok)
+ return ev;
+
+ /* Target value */
+ crct = buf2uint(buf + 1024 - 4);
+
+ bp = buf;
+ crc = ~0;
+ for (i = 0; i < (1024 - 4); i++, bp++)
+ crc = spyd4_crctab[(crc ^ *bp) & 0xff] ^ (crc >> 8);
+ crc = ~crc;
+
+ a1logd(p->log, 4, "spyd2_checkEECRC: EEProm CRC is 0x%x, should be 0x%x\n",crc,crct);
+
+ if (crc != crct)
+ return spyd2_interp_code((inst *)p, SPYD2_BAD_EE_CRC);
+
+ return inst_ok;
+}
+
+/* Special purpose float read, */
+/* Read three 9 vectors of floats from the EEprom */
+static inst_code
+spyd2_rdreg_3x9xfloat(
+ spyd2 *p, /* Object */
+ double *out0, /* Where to write first 9 doubles */
+ double *out1, /* Where to write second 9 doubles */
+ double *out2, /* Where to write third 9 doubles */
+ int addr /* Register Address, 0 - 1023 */
+) {
+ inst_code ev;
+ unsigned char buf[3 * 9 * 4], *bp;
+ int i;
+
+ if ((ev = spyd2_readEEProm(p, buf, addr, 3 * 9 * 4)) != inst_ok)
+ return ev;
+
+ bp = buf;
+ for (i = 0; i < 9; i++, bp +=4, out0++) {
+ int val;
+ val = buf2int(bp);
+ *out0 = IEEE754todouble((unsigned int)val);
+ }
+
+ for (i = 0; i < 9; i++, bp +=4, out1++) {
+ int val;
+ val = buf2int(bp);
+ *out1 = IEEE754todouble((unsigned int)val);
+ }
+
+ for (i = 0; i < 9; i++, bp +=4, out2++) {
+ int val;
+ val = buf2int(bp);
+ *out2 = IEEE754todouble((unsigned int)val);
+ }
+
+ return inst_ok;
+}
+
+/* Special purpose short read, */
+/* Read 7 x 41 vectors of ints from the EEprom */
+static inst_code
+spyd2_rdreg_7x41xshort(
+ spyd2 *p, /* Object */
+ double sens[7][41], /* Destination */
+ int addr /* Register Address, 0 - 1023 */
+) {
+ inst_code ev;
+ unsigned char buf[7 * 41 * 2], *bp;
+ int i, j;
+
+ if ((ev = spyd2_readEEProm(p, buf, addr, 7 * 41 * 2)) != inst_ok)
+ return ev;
+
+ bp = buf;
+ for (i = 0; i < 7; i++) {
+ for (j = 0; j < 41; j++, bp += 2) {
+ int val;
+ val = buf2ushort(bp);
+ sens[i][j] = val / 100.0;
+ }
+ }
+
+ return inst_ok;
+}
+
+/* Get refresh rate command. Set it to DEFRRATE if not detectable */
+/* if no refresh rate can be established */
+/* (This isn't used by the manufacturers Spyder3/4 driver, */
+/* but the instrument seems to impliment it.) */
+static inst_code
+spyd2_read_refrate(
+ inst *pp,
+ double *ref_rate
+) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_code ev;
+ int clocks; /* Clocks to run commands */
+ int min, max; /* min and max light intensity frequency periods */
+
+ a1logd(p->log, 3, "spyd2_read_refrate: called\n");
+
+ /* Establish the frame rate detect threshold level */
+ clocks = (10 * CLKRATE)/DEFRRATE;
+
+ if ((ev = spyd2_GetMinMax(p, &clocks, &min, &max)) != inst_ok)
+ return ev;
+
+ if (min == 0 || max < (5 * min)) {
+ a1logd(p->log, 3, "spyd2_read_refrate: no refresh rate detectable\n");
+ if (ref_rate != NULL)
+ *ref_rate = 0.0;
+ return inst_ok;
+ } else {
+ int frclocks; /* notional clocks per frame */
+ int nframes; /* Number of frames to count */
+ int thresh; /* Frame detection threshold */
+ int minfclks; /* Minimum number of clocks per frame */
+ int maxfclks; /* Maximum number of clocks per frame */
+ int clkcnt; /* Return number of clocks for nframes frames */
+
+ frclocks = CLKRATE/DEFRRATE;
+ nframes = 50;
+ thresh = (max - min)/5 + min; /* Threshold is at 80% of max brightness */
+ minfclks = frclocks/3; /* Allow for 180 Hz */
+ maxfclks = (frclocks * 5)/2; /* Allow for 24 Hz */
+ clocks = nframes * frclocks * 2; /* Allow for 120 Hz */
+
+ if ((ev = spyd2_GetRefRate_ll(p, &clocks, nframes, thresh, &minfclks, &maxfclks,
+ &clkcnt)) != inst_ok)
+ return ev;
+
+ /* Compute the refresh rate */
+ if (ref_rate != NULL)
+ *ref_rate = ((double)nframes * (double)CLKRATE)/(double)clkcnt;
+ return inst_ok;
+ }
+}
+
+/* Get refresh rate command. Set it to DEFRRATE if not detectable */
+/* if no refresh rate can be established */
+/* (This isn't used by the manufacturers Spyder3/4 driver, */
+/* but the instrument seems to impliment it.) */
+static inst_code
+spyd2_GetRefRate(
+ spyd2 *p
+) {
+ int i;
+ inst_code ev;
+
+ a1logd(p->log, 3, "Frequency calibration called\n");
+
+ if ((ev = spyd2_read_refrate((inst *)p, &p->refrate)) != inst_ok) {
+ return ev;
+ }
+ if (p->refrate != 0.0) {
+ a1logd(p->log, 3, "spyd2_GetRefRate: refresh rate is %f Hz\n",p->refrate);
+ p->refrvalid = 1;
+ } else {
+ a1logd(p->log, 3, "spyd2_GetRefRate: no refresh rate detectable\n");
+ p->refrate = DEFRRATE;
+ p->refrvalid = 0;
+ }
+ p->rrset = 1;
+
+ return inst_ok;
+}
+
+/* Do a reading. */
+/* Note that the Spyder 3 seems to give USB errors on the data */
+/* read if the measurement time is too small (ie. 0.2 seconds) */
+/* when reading dark values. */
+static inst_code
+spyd2_GetReading(
+ spyd2 *p,
+ double *XYZ /* return the XYZ values */
+) {
+ inst_code ev;
+ int clocks1, clocks2; /* Clocks to run commands */
+ int min, max; /* min and max light intensity frequency periods */
+ int frclocks; /* notional clocks per frame */
+ int nframes; /* Number of frames to measure over */
+ int thresh; /* Frame detection threshold */
+ int minfclks; /* Minimum number of clocks per frame */
+ int maxfclks; /* Maximum number of clocks per frame */
+ double sensv[8]; /* The 8 final sensor value readings */
+ int maxtcnt; /* The maximum transition count measured */
+ int mintcnt; /* The minumum transition count measured */
+ double a_sensv[8]; /* Accumulated sensor value readings */
+ double a_w[8]; /* Accumulated sensor value weight */
+ double pows[9]; /* Power combinations of initial XYZ */
+ int i, j, k;
+ double inttime = 0.0;
+
+ a1logd(p->log, 3, "spyd2_GetReading: called\n");
+
+ if (p->refrmode)
+ inttime = RINTTIME; /* ie. 1 second */
+ else
+ inttime = NINTTIME; /* ie. 1 second */
+
+ /* Compute number of frames for desired base read time */
+ nframes = (int)(inttime * p->refrate + 0.5);
+
+ /* Establish the frame rate detect threshold level */
+ /* (The Spyder 3 doesn't use this ?) */
+ clocks1 = (int)((nframes * CLKRATE)/(10 * p->refrate) + 0.5); /* Use 10% of measurement clocks */
+
+ if ((ev = spyd2_GetMinMax(p, &clocks1, &min, &max)) != inst_ok)
+ return ev;
+
+ /* Setup for measurement */
+ thresh = (max - min)/5 + min; /* Threshold is at 80% of max brightness */
+ if (thresh == 0)
+ thresh = 65535; /* Set to max, otherwise reading will be 0 */
+ frclocks = (int)(CLKRATE/p->refrate + 0.5); /* Nominal clocks per frame */
+ minfclks = frclocks/3; /* Allow for 180 Hz */
+ maxfclks = (frclocks * 5)/2; /* Allow for 24 Hz */
+
+ if (p->hwver < 7) {
+ /* Check calibration is valid */
+ if ((p->icx & 1) == 0 && (p->fbits & 1) == 0) {
+// return spyd2_interp_code((inst *)p, SPYD2_NOCRTCAL);
+ a1logd(p->log, 1, "spyd2_GetReading: instrument appears to have no CRT calibration "
+ "table! Proceeding anyway..\n");
+ }
+
+ if ((p->icx & 1) == 1 && (p->fbits & 2) == 0) {
+// return spyd2_interp_code((inst *)p, SPYD2_NOLCDCAL);
+ a1logd(p->log, 1, "spyd2_GetReading: instrument appears to have no LCD calibration "
+ "table! Proceeding anyway..\n");
+ }
+ }
+
+ a1logd(p->log, 3, "spyd2_GetReading: Using cal table %d\n",(p->icx & 1));
+ if (p->hwver)
+ a1logd(p->log, 3, "spyd2_GetReading: using spectral cal table %d\n",p->icx >> 1);
+
+ for (k = 0; k < 8; k++) /* Zero weighted average */
+ a_sensv[k] = a_w[k] = 0.0;
+
+ /* For initial and possible adaptive readings */
+ for (i = 0;; i++) {
+ double itime; /* Integration time */
+
+ clocks2 = (int)((double)nframes/p->refrate * (double)CLKRATE + 0.5);
+
+ if ((ev = spyd2_GetReading_ll(p, &clocks2, nframes, thresh, &minfclks, &maxfclks,
+ sensv, &maxtcnt, &mintcnt)) != inst_ok)
+ return ev;
+// a1logd(p->log, 3, "spyd2_GetReading: returned number of clocks = %d\n",clocks2);
+
+ if (p->log->debug >= 3) {
+ for (k = 0; k < 8; k++)
+ a1logd(p->log, 3, "Sensor %d value = %f\n",k,sensv[k]);
+ }
+
+ itime = (double)clocks2 / (double)CLKRATE;
+// a1logd(p->log, 3, "spyd2_GetReading: reading %d was %f secs\n",i,itime);
+
+ /* Accumulate it for weighted average */
+ for (k = 0; k < 8; k++) {
+ if (sensv[k] != 0.0) { /* Skip value where we didn't get any transitions */
+ a_sensv[k] += sensv[k] * itime;
+ a_w[k] += itime;
+ }
+ }
+
+#ifdef DO_ADAPTIVE
+ a1logd(p->log, 3, "spyd2_GetReading: Maxtcnt = %d, Mintcnt = %d\n",maxtcnt,mintcnt);
+ if (i > 0)
+ break; /* Done adaptive */
+
+ /* Decide whether to go around again */
+
+ if (maxtcnt <= (100/16)) {
+ nframes *= 16; /* Typically 16 seconds */
+ a1logd(p->log, 3, "spyd2_GetReading: using maximum integration time\n");
+ } else if (maxtcnt < 100) {
+ double mulf;
+ mulf = 100.0/maxtcnt;
+ mulf -= 0.8; /* Just want to accumulate up to target, not re-do it */
+ nframes = (int)(nframes * mulf + 0.5);
+ a1logd(p->log, 3, "spyd2_GetReading: increasing total integration time "
+ "by %.1f times\n",1+mulf);
+ } else {
+ break; /* No need for another reading */
+ }
+#else /* !DO_ADAPTIVE */
+ a1logw(p->log, "!!!! Spyder 2 DO_ADAPTIVE is off !!!!\n");
+ break;
+#endif /* !DO_ADAPTIVE */
+ }
+
+ /* Compute weighted average and guard against silliness */
+ for (k = 0; k < 8; k++) {
+ if (a_w[k] > 0.0) {
+ a_sensv[k] /= a_w[k];
+ }
+ }
+
+ if (p->hwver == 5) {
+ double gainscale = 1.0;
+ unsigned int v381;
+
+ if ((ev = spyd2_rd_ee_uchar(p, &v381, 381)) != inst_ok)
+ return ev;
+
+ gainscale = (double)v381/p->gain;
+ a1logd(p->log, 3, "spyd2_GetReading: hwver5 v381 = %d, gain = %f, gainscale = %f\n",
+ v381,p->gain,gainscale);
+ /* Convert sensor readings to XYZ value */
+ for (j = 0; j < 3; j++) {
+ XYZ[j] = p->cal_A[p->icx & 1][j][0]; /* First entry is a constant */
+ for (k = 1; k < 8; k++)
+ XYZ[j] += a_sensv[k] * p->cal_A[p->icx & 1][j][k+2] * gainscale;
+ }
+
+ } else {
+ /* Convert sensor readings to XYZ value */
+ for (j = 0; j < 3; j++) {
+ XYZ[j] = p->cal_A[p->icx & 1][j][0]; /* First entry is a constant */
+ for (k = 1; k < 8; k++)
+ XYZ[j] += a_sensv[k] * p->cal_A[p->icx & 1][j][k+1];
+ }
+ }
+
+// a1logd(p->log, 3, "spyd2_GetReading: real Y = %f\n",XYZ[1]);
+ a1logd(p->log, 3, "spyd2_GetReading: initial XYZ reading %f %f %f\n",XYZ[0], XYZ[1], XYZ[2]);
+
+#ifdef LEVEL2
+ /* Add "level 2" correction factors */
+ pows[0] = XYZ[0];
+ pows[1] = XYZ[1];
+ pows[2] = XYZ[2];
+ pows[3] = XYZ[0] * XYZ[1];
+ pows[4] = XYZ[0] * XYZ[2];
+ pows[5] = XYZ[1] * XYZ[2];
+ pows[6] = XYZ[0] * XYZ[0];
+ pows[7] = XYZ[1] * XYZ[1];
+ pows[8] = XYZ[2] * XYZ[2];
+
+
+ for (j = 0; j < 3; j++) {
+ XYZ[j] = 0.0;
+
+ for (k = 0; k < 9; k++) {
+ XYZ[j] += pows[k] * p->cal_B[p->icx & 1][j][k];
+ }
+ }
+ a1logd(p->log, 3, "spyd2_GetReading: 2nd level XYZ reading %f %f %f\n",XYZ[0], XYZ[1], XYZ[2]);
+#endif
+
+ /* Protect against silliness (This may stuff up averages though!) */
+ for (j = 0; j < 3; j++) {
+ if (XYZ[j] < 0.0)
+ XYZ[j] = 0.0;
+ }
+ a1logd(p->log, 3, "spyd2_GetReading: final XYZ reading %f %f %f\n",XYZ[0], XYZ[1], XYZ[2]);
+
+ return ev;
+}
+
+/* Spyder3/4: Do an ambient reading */
+
+/* NOTE :- the ambient sensor is something like a TAOS TLS 2562CS. */
+/* It has two sensors, one wide band and the other infra-red, */
+/* the idea being to subtract them to get a rough human response. */
+/* The reading is 16 bits, and the 8 bit conrol register */
+/* controls gain and integration time: */
+
+/* Bits 0,1 inttime, 0 = scale 0.034, 1 = scale 0.252. 2 = scale 1, 3 = manual */
+/* Bit 2, manual, 0 = stop int, 1 = start int */
+/* Bit 4, gain, 0 = gain 1, 1 = gain 16 */
+
+
+static inst_code
+spyd2_GetAmbientReading(
+ spyd2 *p,
+ double *XYZ /* return the ambient XYZ values */
+) {
+ inst_code ev = inst_ok;
+ int tconf; /* Ambient timing config */
+ int iamb0, iamb1;
+ double amb0, amb1; /* The two ambient values */
+ double trv; /* Trial value */
+ double thr[8] = { 64/512.0, 128/512.0, 192/512.0, 256/512.0, /* Magic tables */
+ 312/512.0, 410/512.0, 666/512.0, 0 };
+ double s0[8] = { 498/128.0, 532/128.0, 575/128.0, 624/128.0,
+ 367/128.0, 210/128.0, 24/128.0, 0 };
+ double s1[8] = { 446/128.0, 721/128.0, 891/128.0, 1022/128.0,
+ 508/128.0, 251/128.0, 18/128.0, 0 };
+ double amb; /* Combined ambient value */
+ double sfact;
+ int i;
+
+ a1logd(p->log, 3, "spyd2_GetAmbientReading: called\n");
+
+ /* Set the ambient control register to 3 */
+ if ((ev = spyd2_SetAmbReg(p, 3)) != inst_ok)
+ return ev;
+
+ /* Wait one second */
+ msec_sleep(1000);
+
+ /* Read the ambient timing config value */
+ if ((ev = spyd2_ReadAmbTiming(p, &tconf)) != inst_ok)
+ return ev;
+// a1logd(p->log, 4, "spyd2_GetAmbientReading: timing = %d\n",tconf);
+
+ /* Read the ambient values */
+ if ((ev = spyd2_ReadAmbChan(p, 0, &iamb0)) != inst_ok)
+ return ev;
+ if ((ev = spyd2_ReadAmbChan(p, 1, &iamb1)) != inst_ok)
+ return ev;
+
+// a1logd(p->log, 4, "spyd2_GetAmbientReading: values = %d, %d\n",iamb0,iamb1);
+ amb0 = iamb0/128.0;
+ amb1 = iamb1/128.0;
+
+ /* Set the ambient control register to 0 */
+ if ((ev = spyd2_SetAmbReg(p, 0)) != inst_ok)
+ return ev;
+
+ /* Compute the scale factor from the timing config value */
+ if ((tconf & 3) == 0)
+ sfact = 1.0/0.034;
+ else if ((tconf & 3) == 1)
+ sfact = 1.0/0.252;
+ else
+ sfact = 1.0;
+
+ if ((tconf & 0x10) == 0)
+ sfact *= 16.0;
+
+ amb0 *= sfact;
+ amb1 *= sfact;
+
+ if (amb0 > 0.0)
+ trv = amb1/amb0;
+ else
+ trv = 0.0;
+
+ for (i = 0; i < 7; i++) {
+ if (trv <= thr[i])
+ break;
+ }
+// a1logd(p->log, 4, "spyd2_GetAmbientReading: trv = %f, s0 = %f, s1 = %f\n",trv, s0[i],s1[i]);
+ /* Compute ambient in Lux */
+ amb = s0[i] * amb0 - s1[i] * amb1;
+
+// a1logd(p->log, 4, "spyd2_GetAmbientReading: combined ambient = %f Lux\n",amb);
+ /* Compute the Y value */
+
+ XYZ[1] = amb; /* cd/m^2 ??? - not very accurate, due to */
+ /* spectral response and/or integration angle. */
+ XYZ[0] = icmD50.X * XYZ[1]; /* Convert to D50 neutral */
+ XYZ[2] = icmD50.Z * XYZ[1];
+
+ a1logd(p->log, 3, "spyd2_GetAmbientReading: returning %f %f %f\n",XYZ[0],XYZ[1],XYZ[2]);
+
+ return ev;
+}
+
+/* ------------------------------------------------------------ */
+/* Spyder 4 manufacturer calibration data */
+int spyd4_nocals = 0; /* Number of calibrations */
+xspect *spyd4_cals = NULL; /* [nocals] Device spectrum */
+
+/* ------------------------------------------------------------ */
+/* Spyder4: Create a calibration matrix using the manufacturers */
+/* calibration data. */
+
+static inst_code
+spyd4_set_cal(
+ spyd2 *p, /* Object */
+ int ix /* Selection, 0 .. spyd4_nocals-1 */
+) {
+ int i, j, k;
+ xspect *oc[3]; /* The XYZ observer curves */
+
+ if (ix < 0 || ix >= spyd4_nocals) {
+ return spyd2_interp_code((inst *)p, SPYD2_DISP_SEL_RANGE) ;
+ }
+
+ /* The Manufacturers calibration routine computes a least squares */
+ /* spectral fit of the sensor spectral sensitivities to the */
+ /* standard observer curves, weighted by the white illuminant */
+ /* of the display technology. We use the same approach for the */
+ /* default calibration selections, to be faithful to the Manufacturers */
+ /* intentions. */
+
+ if (standardObserver(oc, icxOT_CIE_1931_2)) {
+ return spyd2_interp_code((inst *)p, SPYD2_DISP_SEL_RANGE) ;
+ }
+
+ /* We compute X,Y & Z independently. */
+ for (k = 0; k < 3; k++) {
+ double target[81]; /* Spectral target @ 5nm spacing */
+ double **wsens; /* Weighted sensor sensitivities */
+ double **psisens; /* Pseudo inverse of sensitivies */
+
+ /* Load up the observer curve and weight it by the display spectrum */
+ /* and mW Lumoinance efficiency factor. */
+ for (i = 0; i < 81; i++) {
+ double nm = 380.0 + i * 5.0;
+ target[i] = value_xspect(&spyd4_cals[ix], nm) * value_xspect(oc[k], nm) * 0.683002;
+ }
+
+ /* Load up the sensor curves and weight by the display spectrum */
+ wsens = dmatrix(0, 6, 0, 80);
+ psisens = dmatrix(0, 80, 0, 6);
+ for (j = 0; j < 7; j++) {
+ for (i = 0; i < 81; i++) {
+ double nm = 380.0 + i * 5.0;
+ wsens[j][i] = value_xspect(&spyd4_cals[ix], nm) * value_xspect(&p->sens[j], nm);
+ }
+ }
+
+ /* Compute the pseudo-inverse matrix */
+ if (lu_psinvert(psisens, wsens, 7, 81) != 0) {
+ free_dmatrix(wsens, 0, 6, 0, 80);
+ free_dmatrix(psisens, 0, 80, 0, 6);
+ return spyd2_interp_code((inst *)p, SPYD2_CAL_FAIL) ;
+ }
+
+ {
+ double *cc, *tt;
+ p->cal_A[1][k][0] = 0.0; /* Offset is zero */
+ p->cal_A[1][k][1] = 0.0; /* Unused is zero */
+ cc = &p->cal_A[1][k][2]; /* 7 cal values go here */
+ tt = target;
+
+ /* Multiply inverse by target to get calibration matrix */
+ if (matrix_mult(&cc, 1, 7, &tt, 1, 81, psisens, 81, 7))
+ return spyd2_interp_code((inst *)p, SPYD2_CAL_FAIL) ;
+ }
+// a1logd(p->log, 3, "Cal %d = %f %f %f %f %f %f %f\n", k, p->cal_A[1][k][2], p->cal_A[1][k][3], p->cal_A[1][k][4], p->cal_A[1][k][5], p->cal_A[1][k][6], p->cal_A[1][k][7], p->cal_A[1][k][8]);
+ }
+
+#ifdef PLOT_SPECTRA
+ /* Plot the calibrated sensor spectra */
+ {
+ int i, j, k;
+ double xx[81];
+ double yy[10][81], *yp[10];
+
+ for (i = 0; i < 81; i++)
+ xx[i] = 380.0 + i * 5.0;
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < 81; i++) {
+ yy[j][i] = 0.0;
+ for (k = 0; k < 7; k++) {
+ yy[j][i] += p->cal_A[1][j][k+2] * value_xspect(&p->sens[k], xx[i]);
+ }
+ }
+ yp[j] = yy[j];
+ }
+ for (; j < 10; j++)
+ yp[j] = NULL;
+
+ printf("The calibrated sensor sensitivities\n");
+ do_plot10(xx, yp[0], yp[1], yp[2], yp[3], yp[4], yp[5], yp[6], yp[7], yp[8], yp[9], 81, 0);
+ }
+#endif /* PLOT_SPECTRA */
+
+#ifdef SAVE_XYZSPECTRA /* Save the default XYZ senitivity spectra to "sensorsxyz.sp" */
+ {
+ int i, j, k;
+ xspect xyz[3];
+ double wl;
+
+ for (j = 0; j < 3; j++) {
+ xyz[j].spec_n = 81;
+ xyz[j].spec_wl_short = 380;
+ xyz[j].spec_wl_long = 780;
+ xyz[j].norm = 1.0;
+ for (i = 0; i < 81; i++) {
+ wl = 380.0 + i * 5.0;
+ xyz[j].spec[i] = 0.0;
+ for (k = 0; k < 7; k++)
+ xyz[j].spec[i] += p->cal_A[1][j][k+2] * value_xspect(&p->sens[k], wl);
+ xyz[j].spec[i] *= 1.4; /* Align with std XYZ */
+ }
+ }
+ write_nxspect("sensorsxyz.sp", xyz, 3, 0);
+ }
+#endif
+#ifdef SAVE_STDXYZ
+ {
+ xspect xyz[3];
+ standardObserver(&xyz[0], &xyz[1], &xyz[2],icxOT_CIE_1931_2);
+ write_nxspect("stdobsxyz.sp", xyz, 3, 0);
+ }
+#endif /* SAVE_STDXYZ */
+
+ return inst_ok;
+}
+
+
+/* The CCSS calibration uses a set of spectral samples, */
+/* and a least squares matrix is computed to map the sensor RGB */
+/* to the computed XYZ values. This allows better accuracy for */
+/* a typical display that has only 3 degrees of freedom, and */
+/* allows weigting towards a distribution of actual spectral samples. */
+/* Because the typical display has only three degrees of freedom, */
+/* while the instrument has 7 sensors, some extra dummy spectral */
+/* samples are added to the list to provide some slight extra goal. */
+
+/* [ Given the poor curve shapes that can come out of this, it's not */
+/* clear that it wouldn't be better using the default flat-spetrum */
+/* calibration and computing a 3x3 calibration matrix over the top of it. ] */
+static inst_code
+spyd4_comp_calmat(
+ spyd2 *p,
+ icxObserverType obType, /* XYZ Observer type */
+ xspect custObserver[3], /* Optional custom observer */ \
+ xspect *samples, /* Array of nsamp spectral samples */
+ int nsamp /* Number of real samples */
+) {
+ int i, j;
+ int nasamp = nsamp + 81; /* Number of real + augmented samples */
+ double exwt = 1.0; /* Extra spectral point weight */
+ double **sampXYZ; /* Sample XYZ values */
+ double **sampSENS; /* Sample Sensor values */
+ double **isampSENS; /* Pseudo-inverse of sensor values */
+ double **calm; /* Calibration matrix */
+ xsp2cie *conv;
+ double wl;
+ xspect white;
+
+ if (nsamp < 3)
+ return spyd2_interp_code((inst *)p, SPYD2_TOO_FEW_CALIBSAMP);
+
+ /* Create white spectrum samples */
+ XSPECT_COPY_INFO(&white, &samples[0]);
+ for (j = 0; j < white.spec_n; j++)
+ white.spec[j] = 0.0;
+ for (i = 0; i < nsamp; i++) {
+ for (j = 0; j < white.spec_n; j++)
+ if (samples[i].spec[j] > white.spec[j])
+ white.spec[j] = samples[i].spec[j];
+ }
+
+ /* Compute XYZ of the real sample array. */
+ if ((conv = new_xsp2cie(icxIT_none, NULL, obType, custObserver, icSigXYZData, icxClamp)) == NULL)
+ return spyd2_interp_code((inst *)p, SPYD2_INT_CIECONVFAIL);
+ sampXYZ = dmatrix(0, nasamp-1, 0, 3-1);
+ for (i = 0; i < nsamp; i++) {
+ conv->convert(conv, sampXYZ[i], &samples[i]);
+// a1logd(p->log, 3, "asamp[%d] XYZ = %f %f %f\n", i,sampXYZ[nsamp+i][0],sampXYZ[nsamp+i][1], sampXYZ[nsamp+i][2]);
+ }
+
+ /* Create extra spectral samples */
+ for (i = 0; i < 81; i++) {
+ for (j = 0; j < 3; j++) {
+ wl = 380.0 + i * 5;
+ sampXYZ[nsamp+i][j] = exwt * value_xspect(&white, wl)
+ * value_xspect(&conv->observer[j], wl) * 0.683002;
+ }
+// a1logd(p->log, 3, "asamp[%d] XYZ = %f %f %f\n", i,sampXYZ[nsamp+i][0],sampXYZ[nsamp+i][1], sampXYZ[nsamp+i][2]);
+ }
+ conv->del(conv);
+
+ sampSENS = dmatrix(0, nasamp-1, 0, 7-1);
+
+ /* Compute sensor values of the sample array */
+ for (i = 0; i < nsamp; i++) {
+ for (j = 0; j < 7; j++) {
+ sampSENS[i][j] = 0.0;
+ for (wl = p->sens[0].spec_wl_short; wl <= p->sens[0].spec_wl_long; wl += 1.0) {
+ sampSENS[i][j] += value_xspect(&samples[i], wl) * value_xspect(&p->sens[j], wl);
+ }
+ }
+ }
+ /* Create sensor values of the extra sample array */
+ for (i = 0; i < 81; i++) {
+ for (j = 0; j < 7; j++) {
+ wl = 380.0 + i * 5;
+ sampSENS[nsamp+i][j] = exwt * value_xspect(&white, wl) * value_xspect(&p->sens[j], wl);
+ }
+// a1logd(p->log, 3, "asamp[%d] Sens = %f %f %f %f %f %f %f\n", i,
+// sampSENS[nsamp+i][0],sampSENS[nsamp+i][1], sampSENS[nsamp+i][2],
+// sampSENS[nsamp+i][3],sampSENS[nsamp+i][4], sampSENS[nsamp+i][5],
+// sampSENS[nsamp+i][6]);
+ }
+#if defined(PLOT_SPECTRA_EXTRA)
+ /* Plot the target extra values */
+ {
+ int i, j, k;
+ double xx[81];
+ double yy[10][81], *yp[10];
+
+ for (i = 0; i < 81; i++)
+ xx[i] = 380.0 + i * 5;
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < 81; i++) {
+ yy[j][i] = sampXYZ[nsamp+i][j];
+ }
+ yp[j] = yy[j];
+ }
+ for (; j < 10; j++)
+ yp[j] = NULL;
+
+ printf("The target extra XYZ values\n");
+ do_plot10(xx, yp[0], yp[1], yp[2], yp[3], yp[4], yp[5], yp[6], yp[7], yp[8], yp[9], 81, 0);
+
+ for (j = 0; j < 7; j++) {
+ for (i = 0; i < 81; i++) {
+ yy[j][i] = sampSENS[nsamp+i][j];
+ }
+ yp[j] = yy[j];
+ }
+ for (; j < 10; j++)
+ yp[j] = NULL;
+
+ printf("The given extra sensor values\n");
+ do_plot10(xx, yp[0], yp[1], yp[2], yp[3], yp[4], yp[5], yp[6], yp[7], yp[8], yp[9], 81, 0);
+ }
+#endif /* PLOT_SPECTRA_EXTRA */
+
+
+ isampSENS = dmatrix(0, 7-1, 0, nasamp-1);
+
+ /* Compute the pseudo inverse of sampSENS */
+ if (lu_psinvert(isampSENS, sampSENS, nasamp, 7) != 0) {
+ free_dmatrix(sampXYZ, 0, nasamp-1, 0, 3-1);
+ free_dmatrix(sampSENS, 0, nasamp-1, 0, 7-1);
+ free_dmatrix(isampSENS, 0, 7-1, 0, nasamp-1);
+ return spyd2_interp_code((inst *)p, SPYD2_CAL_FAIL) ;
+ }
+
+ calm = dmatrix(0, 7-1, 0, 3-1);
+
+ /* Multiply inverse by target to get calibration matrix */
+ if (matrix_mult(calm, 7, 3, isampSENS, 7, nasamp, sampXYZ, nasamp, 3)) {
+ free_dmatrix(sampXYZ, 0, nasamp-1, 0, 3-1);
+ free_dmatrix(sampSENS, 0, nasamp-1, 0, 7-1);
+ free_dmatrix(isampSENS, 0, 7-1, 0, nasamp-1);
+ free_dmatrix(calm, 0, 7-1, 0, 3-1);
+ return spyd2_interp_code((inst *)p, SPYD2_CAL_FAIL);
+ }
+
+ /* Copy the matrix into place */
+ for (i = 0; i < 7; i++) {
+ for (j = 0; j < 3; j++) {
+//calm[i][j] = 0.5;
+ p->cal_A[1][j][2+i] = calm[i][j];
+ }
+ }
+
+ free_dmatrix(calm, 0, 7-1, 0, 3-1);
+
+#ifdef NEVER
+
+ /* Compute the residuals */
+ {
+ double **refXYZ;
+ double t1, t2;
+
+ refXYZ = dmatrix(0, nasamp-1, 0, 3-1);
+
+ if (matrix_mult(refXYZ, nasamp, 3, sampSENS, nasamp, 7, calm, 7, 3)) {
+ printf("Residual matrix mult failed\n");
+ } else {
+ t1 = 0.0;
+ for (i = 0; i < nsamp; i++) {
+ t1 += icmLabDE(refXYZ[i],sampXYZ[i]);
+ }
+ t1 /= nsamp;
+ printf("Average error for sample points = %f\n",t1);
+ t2 = 0.0;
+ for (i = nsamp; i < (nsamp + 81); i++) {
+ t2 += icmLabDE(refXYZ[i],sampXYZ[i]);
+// printf("Resid %d error = %f, %f %f %f, %f %f %f\n",
+// i, icmLabDE(refXYZ[i],sampXYZ[i]), sampXYZ[i][0], sampXYZ[i][1],
+// sampXYZ[i][2], refXYZ[i][0], refXYZ[i][1], refXYZ[i][2]);
+ }
+ t2 /= 81;
+ printf("Average error for extra points = %f\n",t2);
+ }
+ }
+#endif
+
+#ifdef PLOT_SPECTRA
+ /* Plot the calibrated sensor spectra */
+ {
+ int i, j, k;
+ double xx[81];
+ double yy[10][81], *yp[10];
+
+ for (i = 0; i < 81; i++)
+ xx[i] = 380.0 + i * 5.0;
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < 81; i++) {
+ yy[j][i] = 0.0;
+ for (k = 0; k < 7; k++) {
+ yy[j][i] += p->cal_A[1][j][k+2] * value_xspect(&p->sens[k], xx[i]);
+ }
+ }
+ yp[j] = yy[j];
+ }
+ for (; j < 10; j++)
+ yp[j] = NULL;
+
+ printf("The calibrated sensor sensitivities\n");
+ do_plot10(xx, yp[0], yp[1], yp[2], yp[3], yp[4], yp[5], yp[6], yp[7], yp[8], yp[9], 81, 0);
+ }
+#endif /* PLOT_SPECTRA */
+
+ free_dmatrix(sampXYZ, 0, nasamp-1, 0, 3-1);
+ free_dmatrix(sampSENS, 0, nasamp-1, 0, 7-1);
+ free_dmatrix(isampSENS, 0, 7-1, 0, nasamp-1);
+
+ return inst_ok;
+}
+
+
+/* ------------------------------------------------------------ */
+
+/* Read all the relevant register values */
+static inst_code
+spyd2_read_all_regs(
+ spyd2 *p /* Object */
+) {
+ inst_code ev;
+
+ a1logd(p->log, 3, "spyd2_read_all_regs: about to read all the EEProm values\n");
+
+ /* HW version */
+ if ((ev = spyd2_rd_ee_uchar(p, &p->hwver, 5)) != inst_ok)
+ return ev;
+
+ /* Feature bits */
+ if ((ev = spyd2_rd_ee_uchar(p, &p->fbits, 6)) != inst_ok)
+ return ev;
+
+ a1logd(p->log, 3, "spyd2_read_all_regs: hwver = 0x%02x%02x\n",p->hwver,p->fbits);
+
+ /* Check the EEProm checksum */
+ if (p->hwver == 7) {
+ if ((ev = spyd2_checkEECRC(p)) != inst_ok)
+ return ev;
+ }
+
+ /* Serial number */
+ if ((ev = spyd2_readEEProm(p, (unsigned char *)p->serno, 8, 8)) != inst_ok)
+ return ev;
+ p->serno[8] = '\000';
+ a1logd(p->log, 3, "spyd2_read_all_regs: serno = '%s'\n",p->serno);
+
+ if (p->hwver < 7) {
+
+ /* Hmm. We deliberately ignore the fbits 0, 1 & 2 here, in case they are faulty */
+ /* (Not sure if we should look at fbits 1 or not) */
+
+ /* Spyde2: CRT calibration values */
+ /* Spyde3: Unknown calibration values */
+ if ((ev = spyd2_rdreg_3x9xfloat(p, p->cal_A[0][0], p->cal_A[0][1], p->cal_A[0][2], 16))
+ != inst_ok)
+ return ev;
+ if ((ev = spyd2_rdreg_3x9xfloat(p, p->cal_B[0][0], p->cal_B[0][1], p->cal_B[0][2], 128))
+ != inst_ok)
+ return ev;
+
+
+ /* Hmm. The 0 table seems to sometimes be scaled. Is this a bug ? */
+ /* (might be gain factor ?) */
+ /* The spyder 3/4 doesn't use this anyway. */
+ if (p->hwver >= 4) {
+ int j, k, i;
+ double avgmag = 0.0;
+
+ for (i = j = 0; j < 3; j++) {
+ for (k = 0; k < 9; k++) {
+ if (p->cal_A[0][j][k] != 0.0) {
+ avgmag += fabs(p->cal_A[0][j][k]);
+ i++;
+ }
+ }
+ }
+ avgmag /= (double)(i);
+ a1logd(p->log, 4, "spyd2_read_all_regs: Cal_A avgmag = %f\n",avgmag);
+
+ if (avgmag < 0.05) {
+ a1logd(p->log, 5, "spyd2_read_all_regs: Scaling Cal_A by 16\n");
+ for (j = 0; j < 3; j++) {
+ for (k = 0; k < 9; k++) {
+ p->cal_A[0][j][k] *= 16.0;
+ }
+ }
+ }
+ }
+
+ /* Spyder2: LCD calibration values */
+ /* Spyder3: Normal CRT/LCD calibration values */
+ if ((ev = spyd2_rdreg_3x9xfloat(p, p->cal_A[1][0], p->cal_A[1][1], p->cal_A[1][2], 256))
+ != inst_ok)
+ return ev;
+ if ((ev = spyd2_rdreg_3x9xfloat(p, p->cal_B[1][0], p->cal_B[1][1], p->cal_B[1][2], 384))
+ != inst_ok)
+ return ev;
+
+ /* The monochrome "TOKIOBLUE" calibration */
+ /* (Not sure if this is fbits 2 and 4 or not) */
+
+ /* Luminence only calibration values ??? */
+ if ((ev = spyd2_rdreg_float(p, &p->cal_F[0], 240)) != inst_ok)
+ return ev;
+ if ((ev = spyd2_rdreg_float(p, &p->cal_F[1], 244)) != inst_ok)
+ return ev;
+ if ((ev = spyd2_rdreg_float(p, &p->cal_F[2], 248)) != inst_ok)
+ return ev;
+ if ((ev = spyd2_rdreg_float(p, &p->cal_F[3], 252)) != inst_ok)
+ return ev;
+ if ((ev = spyd2_rdreg_float(p, &p->cal_F[4], 364)) != inst_ok)
+ return ev;
+ if ((ev = spyd2_rdreg_float(p, &p->cal_F[5], 368)) != inst_ok)
+ return ev;
+ if ((ev = spyd2_rdreg_float(p, &p->cal_F[6], 372)) != inst_ok)
+ return ev;
+
+ if (p->log->debug >= 4) {
+ int i, j, k;
+
+ a1logd(p->log, 4, "Cal_A:\n");
+ for (i = 0; i < 2;i++) {
+ for (j = 0; j < 3; j++) {
+ for (k = 0; k < 9; k++) {
+ a1logd(p->log, 4, "Cal_A [%d][%d][%d] = %f\n",i,j,k,p->cal_A[i][j][k]);
+ }
+ }
+ }
+ a1logd(p->log, 4, "\nCal_B:\n");
+ for (i = 0; i < 2;i++) {
+ for (j = 0; j < 3; j++) {
+ for (k = 0; k < 9; k++) {
+ a1logd(p->log, 4, "Cal_B [%d][%d][%d] = %f\n",i,j,k,p->cal_B[i][j][k]);
+ }
+ }
+ }
+ a1logd(p->log, 4, "\nCal_F:\n");
+ for (i = 0; i < 7;i++) {
+ a1logd(p->log, 4, "Cal_F [%d] = %f\n",i,p->cal_F[i]);
+ }
+ a1logd(p->log, 4, "\n");
+ }
+
+ } else if (p->hwver == 7) {
+ int i, j;
+ unsigned int sscal;
+ double tsens[7][41];
+
+ /* Read sensor sensitivity spectral data */
+ if ((ev = spyd2_rdreg_7x41xshort(p, tsens, 170)) != inst_ok)
+ return ev;
+
+ /* Sensor scale factor */
+ if ((ev = spyd2_rd_ee_ushort(p, &sscal, 21)) != inst_ok)
+ return ev;
+
+ /* And apply it to the sensor data */
+ for (j = 0; j < 7; j++) {
+ for (i = 0; i < 41; i++) {
+ tsens[j][i] /= 1000; /* Convert to Hz per mW/nm/m^2 */
+ tsens[j][i] /= sscal/1e5; /* Sensitivity scale value */
+ }
+ }
+
+ /* Convert sensor values to xspect's */
+ for (i = 0; i < 7; i++) {
+ p->sens[i].spec_n = 41;
+ p->sens[i].spec_wl_short = 380;
+ p->sens[i].spec_wl_long = 780;
+ p->sens[i].norm = 1.0;
+ for (j = 0; j < 41; j++) {
+ p->sens[i].spec[j] = tsens[i][j];
+ }
+ }
+#ifdef SAVE_SPECTRA
+ write_nxspect("sensors.sp", p->sens, 7, 0);
+#endif
+
+ /* Linearization */
+ if ((ev = spyd2_rdreg_3x9xfloat(p, p->cal_B[1][0], p->cal_B[1][1], p->cal_B[1][2], 60))
+ != inst_ok)
+ return ev;
+
+#ifdef PLOT_SPECTRA
+ /* Plot the sensor spectra */
+ {
+ int i, j;
+ double xx[81];
+ double yy[10][81], *yp[10];
+
+ for (i = 0; i < 81; i++)
+ xx[i] = 380.0 + i * 5.0;
+
+ for (j = 0; j < 7; j++) {
+ for (i = 0; i < 81; i++)
+ yy[j][i] = value_xspect(&p->sens[j], xx[i]);
+ yp[j] = yy[j];
+ }
+ for (; j < 10; j++)
+ yp[j] = NULL;
+
+ printf("The sensor and ambient sensor sensitivy curves\n");
+ do_plot10(xx, yp[0], yp[1], yp[2], yp[3], yp[4], yp[5], yp[6], yp[7], yp[8], yp[9], 81, 0);
+
+
+ for (j = 0; j < spyd4_nocals; j++) {
+ double max = 0;
+ for (i = 0; i < 81; i++) {
+ if (yy[j][i] = value_xspect(&spyd4_cals[j], xx[i]) > max)
+ max = value_xspect(&spyd4_cals[j], xx[i]);
+ }
+ for (i = 0; i < 81; i++)
+ yy[j][i] = value_xspect(&spyd4_cals[j], xx[i])/max;
+ yp[j] = yy[j];
+ }
+ for (; j < 10; j++)
+ yp[j] = NULL;
+
+ printf("The display spectra\n");
+ do_plot10(xx, yp[0], yp[1], yp[2], yp[3], yp[4], yp[5], yp[6], yp[7], yp[8], yp[9], 81, 0);
+ }
+#endif /* PLOT_SPECTRA */
+
+ }
+
+ a1logd(p->log, 3, "spyd2_read_all_regs: all EEProm read OK\n");
+
+ return inst_ok;
+}
+
+/* ------------------------------------------------------------ */
+
+/* Table to hold Spyder 2 Firmware, if it's installed */
+unsigned int _spyder2_pld_size = 0; /* Number of bytes to download */
+unsigned int *spyder2_pld_size = &_spyder2_pld_size;
+unsigned char *spyder2_pld_bytes = NULL;
+
+/* Spyder 2: Download the PLD if it is available, and check status */
+static inst_code
+spyd2_download_pld(
+ spyd2 *p /* Object */
+) {
+ inst_code ev;
+ int stat;
+ int i;
+
+ a1logd(p->log, 2, "spyd2_download_pld: called\n");
+
+ if (*spyder2_pld_size == 0 || *spyder2_pld_size == 0x11223344) {
+ a1logd(p->log, 1, "spyd2_download_pld: No PLD pattern available! (have you run spyd2en ?)\n");
+ return spyd2_interp_code((inst *)p, SPYD2_NO_PLD_PATTERN) ;
+ }
+
+ for (i = 0; i < *spyder2_pld_size; i += 8) {
+ if ((ev = spyd2_loadPLD(p, spyder2_pld_bytes + i, 8)) != inst_ok)
+ return ev;
+ }
+
+ /* Let the PLD initialize */
+ msec_sleep(500);
+
+#ifdef DO_RESETEP /* Do the miscelanous resetep()'s */
+ /* Reset the coms */
+ p->icom->usb_resetep(p->icom, 0x81);
+ msec_sleep(1); /* Let device recover ? */
+#endif /* DO_RESETEP */
+
+ /* Check the status */
+ if ((ev = spyd2_getstatus(p, &stat)) != inst_ok)
+ return ev;
+
+ if (stat != 0) {
+ a1logd(p->log, 1, "spyd2_download_pld: PLD download failed!\n");
+ return spyd2_interp_code((inst *)p, SPYD2_PLDLOAD_FAILED);
+ }
+
+ a1logd(p->log, 2, "spyd2_download_pld: PLD download OK\n");
+
+ msec_sleep(500);
+#ifdef DO_RESETEP /* Do the miscelanous resetep()'s */
+ p->icom->usb_resetep(p->icom, 0x81);
+ msec_sleep(1); /* Let device recover ? */
+#endif /* DO_RESETEP */
+
+ return inst_ok;
+}
+
+
+/* ------------------------------------------------------------ */
+/* Setup Spyder4 native calibrations */
+
+/* Load the manufacturers Spyder4 calibration data */
+/* Return a SPYD2_ error value */
+static int
+spyd4_load_cal(spyd2 *p) {
+ char **bin_paths = NULL;
+ int no_paths = 0;
+ unsigned int size;
+ unsigned char *buf = NULL;
+ FILE *fp = NULL;
+ int nocals = 0;
+ int i, j;
+
+ /* If already loaded */
+ if (spyd4_nocals != 0)
+ return SPYD2_OK;
+
+
+ for (;;) { /* So we can break */
+ if ((no_paths = xdg_bds(NULL, &bin_paths, xdg_data, xdg_read, xdg_user,
+ "ArgyllCMS/spyd4cal.bin" XDG_FUDGE "color/spyd4cal.bin"
+ )) < 1)
+ break;
+
+ /* open binary file */
+#if !defined(O_CREAT) && !defined(_O_CREAT)
+# error "Need to #include fcntl.h!"
+#endif
+#if defined(O_BINARY) || defined(_O_BINARY)
+ if ((fp = fopen(bin_paths[0],"rb")) == NULL)
+#else
+ if ((fp = fopen(bin_paths[0],"r")) == NULL)
+#endif
+ break;
+ xdg_free(bin_paths, no_paths);
+
+ /* Figure out how big file it is */
+ if (fseek(fp, 0, SEEK_END)) {
+ fclose(fp);
+ break;
+ }
+ size = (unsigned long)ftell(fp);
+
+ if ((size % (41 * 8)) != 0) {
+ fclose(fp);
+ a1logd(p->log, 1, "spyd4_load_cal: calibration file '%s' is unexpected size\n",bin_paths[0]);
+ break;
+ }
+
+ nocals = size/(41 * 8);
+ if (nocals != 6) {
+ fclose(fp);
+ a1logd(p->log, 1, "spyd4_load_cal: calibration file '%s' is unexpected number of calibrations (%d)\n",bin_paths[0],nocals);
+ break;
+ }
+
+ if (fseek(fp, 0, SEEK_SET)) {
+ fclose(fp);
+ break;
+ }
+
+ if ((buf = (unsigned char *)calloc(nocals * 41, 8)) == NULL) {
+ fclose(fp);
+ return SPYD2_MALLOC;
+ }
+
+ if (fread(buf, 1, size, fp) != size) {
+ free(buf);
+ fclose(fp);
+ break;
+ }
+ fclose(fp);
+ break;
+ }
+
+ if (buf == NULL)
+ nocals = 1;
+
+ if ((spyd4_cals = (xspect *)calloc(nocals, sizeof(xspect))) == NULL) {
+ if (buf != NULL)
+ free(buf);
+ return SPYD2_MALLOC;
+ }
+
+ /* If we have calibrations */
+ if (buf != NULL) {
+ unsigned char *bp;
+
+ for (i = 0; i < nocals; i++) {
+ bp = buf + 41 * 8 * i;
+
+ spyd4_cals[i].spec_n = 41;
+ spyd4_cals[i].spec_wl_short = 380;
+ spyd4_cals[i].spec_wl_long = 780;
+ spyd4_cals[i].norm = 1.0;
+
+ for (j = 0; j < 41; j++, bp += 8) {
+ ORD64 val;
+
+ val = buf2ord64(bp);
+ spyd4_cals[i].spec[j] = IEEE754_64todouble(val);
+// a1logd(p->log, 3, "cal[%d][%d] = %f\n",i,j,spyd4_cals[i].spec[j]);
+ }
+ }
+
+ } else {
+
+ /* Create a default calibration */
+ for (j = 0; j < 41; j++)
+ spyd4_cals[0].spec_n = 41;
+ spyd4_cals[0].spec_wl_short = 380;
+ spyd4_cals[0].spec_wl_long = 780;
+ spyd4_cals[0].norm = 1.0;
+
+ for (j = 0; j < 41; j++) {
+ spyd4_cals[0].spec[j] = 1.0;
+ }
+ }
+
+ spyd4_nocals = nocals;
+
+ return SPYD2_OK;
+}
+
+/* ============================================================ */
+
+
+/* Establish communications with a SPYD2 */
+/* If it's a serial port, use the baud rate given, and timeout in to secs */
+/* Return DTP_COMS_FAIL on failure to establish communications */
+static inst_code
+spyd2_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ spyd2 *p = (spyd2 *) pp;
+ int se;
+ icomuflags usbflags = icomuf_none;
+
+ a1logd(p->log, 2, "spyd2_init_coms: about to init coms\n");
+
+ if (p->icom->port_type(p->icom) != icomt_usb) {
+ a1logd(p->log, 1, "spyd2_init_coms: coms is not the right type!\n");
+ return spyd2_interp_code((inst *)p, SPYD2_UNKNOWN_MODEL);
+ }
+
+ a1logd(p->log, 2, "spyd2_init_coms: about to init USB\n");
+
+ /* On MSWindows the Spyder 3 doesn't work reliably unless each */
+ /* read is preceeded by a reset endpoint. */
+ /* (!!! This needs checking to see if it's still true. */
+ /* Should switch back to libusb0.sys and re-test.) */
+ /* (and Spyder 2 hangs if a reset ep is done on MSWin.) */
+ /* The spyder 2 doesn't work well with the winusb driver either, */
+ /* it needs icomuf_resetep_before_read to work at all, and */
+ /* gets retries anyway. So we use the libusb0 driver for it. */
+#if defined(NT)
+ if (p->itype == instSpyder3) {
+ usbflags |= icomuf_resetep_before_read; /* The spyder USB is buggy ? */
+ }
+#endif
+
+ /* On OS X the Spyder 2 can't close properly */
+#if defined(__APPLE__) /* OS X*/
+ if (p->itype == instSpyder2) {
+ usbflags |= icomuf_reset_before_close; /* The spyder 2 USB is buggy ? */
+ }
+#endif
+
+#ifdef NEVER /* Don't want this now that we avoid 2nd set_config on Linux */
+#if defined(UNIX_X11) /* Linux*/
+ /* On Linux the Spyder 2 doesn't work reliably unless each */
+ /* read is preceeded by a reset endpoint. */
+ if (p->itype == instSpyder2) {
+ usbflags |= icomuf_resetep_before_read; /* The spyder USB is buggy ? */
+ }
+#endif
+#endif
+
+ /* Set config, interface, write end point, read end point */
+ /* ("serial" end points aren't used - the spyd2lay uses USB control messages) */
+ if ((se = p->icom->set_usb_port(p->icom, 1, 0x00, 0x00, usbflags, 0, NULL)) != ICOM_OK) {
+ a1logd(p->log, 1, "spyd2_init_coms: failed ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+
+ a1logd(p->log, 2, "spyd2_init_coms: suceeded\n");
+
+ p->gotcoms = 1;
+ return inst_ok;
+}
+
+static inst_code set_default_disp_type(spyd2 *p);
+
+/* Initialise the SPYD2 */
+/* return non-zero on an error, with an inst_code */
+static inst_code
+spyd2_init_inst(inst *pp) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_code ev = inst_ok;
+ int stat;
+ int i;
+
+ a1logd(p->log, 2, "spyd2_init_inst: called\n");
+
+ if (p->gotcoms == 0) /* Must establish coms before calling init */
+ return spyd2_interp_code((inst *)p, SPYD2_NO_COMS);
+
+ if (p->itype != instSpyder2
+ && p->itype != instSpyder3
+ && p->itype != instSpyder4)
+ return spyd2_interp_code((inst *)p, SPYD2_UNKNOWN_MODEL);
+
+ p->refrate = DEFRRATE;
+ for (i = 0; i < 8; i++)
+ p->prevraw[i] = 0; /* Internal counters will be reset */
+ p->prevrawinv = 0; /* prevraw is valid */
+
+ /* For Spyder 1 & 2, reset the hardware and wait for it to become ready. */
+ if (p->itype != instSpyder3
+ && p->itype != instSpyder4) {
+
+ /* Reset the instrument */
+ if ((ev = spyd2_reset(p)) != inst_ok)
+ return ev;
+
+ /* Fetch status until we get a status = 1 */
+ for (i = 0; i < 50; i++) {
+ if ((ev = spyd2_getstatus(p, &stat)) != inst_ok)
+ return ev;
+
+ if (stat == 1)
+ break;
+ }
+ if (i >= 50)
+ return spyd2_interp_code((inst *)p, SPYD2_BADSTATUS);
+
+ } else {
+ /* Because the Spyder 3/4 doesn't have a reset command, */
+ /* it may be left in a borked state if the driver is aborted. */
+ /* Make sure there's no old read data hanging around. */
+ /* Sometimes it takes a little while for the old data to */
+ /* turn up, so try at least for 1 second. */
+ /* This won't always work if the driver is re-started */
+ /* quickly after aborting a long integration read. */
+
+ unsigned char buf[8]; /* return bytes read */
+ int rwbytes; /* Data bytes read or written */
+
+
+ for (i = 0; i < 10; i++) {
+ if ((p->icom->usb_read(p->icom, NULL, 0x81, buf, 8, &rwbytes, 0.1) & ICOM_TO)
+ && i > 9)
+ break; /* Done when read times out */
+ }
+ }
+
+ /* Read the Serial EEProm contents */
+ if ((ev = spyd2_read_all_regs(p)) != inst_ok)
+ return ev;
+
+ /* Spyder 2 */
+ if (p->hwver < 4) {
+ /* Download the PLD pattern and check the status */
+ if ((ev = spyd2_download_pld(p)) != inst_ok)
+ return ev;
+ }
+
+ p->gain = 1.0;
+ if (p->hwver == 5) {
+ if ((ev = spyd2_SetGain(p, 4)) != inst_ok)
+ return ev;
+ }
+
+ /* Set a default calibration */
+ if ((ev = set_default_disp_type(p)) != inst_ok) {
+ return ev;
+ }
+
+ /* Do a dumy sensor read. This will set prevraw[] values. */
+ {
+ int clocks = 500;
+ int minfclks = 0;
+ int maxfclks = 0;
+ msec_sleep(100);
+ if ((ev = spyd2_GetReading_ll(p, &clocks, 10, 0, &minfclks, &maxfclks, NULL, NULL, NULL)) != inst_ok)
+ return ev;
+ }
+
+ p->trig = inst_opt_trig_user; /* default trigger mode */
+
+ p->inited = 1;
+ a1logd(p->log, 2, "spyd2_init_inst: inited OK\n");
+
+ if (p->hwver >= 4) {
+ /* Flash the LED, just cos we can! */
+ if ((ev = spyd2_setLED(p, 2, 0.0)) != inst_ok)
+ return ev;
+ msec_sleep(200);
+ if ((ev = spyd2_setLED(p, 0, 0.0)) != inst_ok)
+ return ev;
+ }
+
+ a1logv(p->log, 1, "Instrument Type: %s\n"
+ "Serial Number: %s\n"
+ "Hardware version: 0x%02x%02x\n"
+ ,inst_name(p->itype) ,p->serno ,p->hwver,p->fbits);
+
+ return inst_ok;
+}
+
+/* Read a single sample */
+/* Return the dtp error code */
+static inst_code
+spyd2_read_sample(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+ipatch *val, /* Pointer to instrument patch value */
+instClamping clamp) { /* NZ if clamp XYZ/Lab to be +ve */
+ spyd2 *p = (spyd2 *)pp;
+ int user_trig = 0;
+ inst_code ev = inst_protocol_error;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->trig == inst_opt_trig_user) {
+
+ if (p->uicallback == NULL) {
+ a1logd(p->log, 1, "sptyd2: inst_opt_trig_user but no uicallback function set!\n");
+ return inst_unsupported;
+ }
+
+ for (;;) {
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (ev == inst_user_abort)
+ return ev; /* Abort */
+ if (ev == inst_user_trig) {
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ msec_sleep(200);
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ 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_trig)
+ return ev; /* Abort */
+ }
+
+ if (IMODETST(p->mode, inst_mode_emis_ambient)) {
+ if ((ev = spyd2_GetAmbientReading(p, val->XYZ)) != inst_ok)
+ return ev;
+
+ } else {
+
+ /* Attempt a CRT frame rate calibration if needed */
+ if (p->refrmode != 0 && p->rrset == 0) {
+ if ((ev = spyd2_GetRefRate(p)) != inst_ok)
+ return ev;
+ }
+
+ /* Read the XYZ value */
+ if ((ev = spyd2_GetReading(p, val->XYZ)) != inst_ok)
+ return ev;
+
+ /* Apply the colorimeter correction matrix */
+ icmMulBy3x3(val->XYZ, p->ccmat, val->XYZ);
+ }
+ /* This may not change anything since instrument may clamp */
+ if (clamp)
+ icmClamp3(val->XYZ, val->XYZ);
+
+ val->loc[0] = '\000';
+ if (IMODETST(p->mode, inst_mode_emis_ambient))
+ val->mtype = inst_mrt_ambient;
+ else
+ val->mtype = inst_mrt_emission;
+ val->XYZ_v = 1; /* These are absolute XYZ readings ? */
+ val->sp.spec_n = 0;
+ val->duration = 0.0;
+
+ if (user_trig)
+ return inst_user_trig;
+ return ev;
+}
+
+/* Insert a colorimetric correction matrix in the instrument XYZ readings */
+/* This is only valid for colorimetric instruments. */
+/* To remove the matrix, pass NULL for the filter filename */
+inst_code spyd2_col_cor_mat(
+inst *pp,
+double mtx[3][3]
+) {
+ spyd2 *p = (spyd2 *)pp;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (mtx == NULL) {
+ icmSetUnity3x3(p->ccmat);
+ } else {
+ if (p->cbid == 0) {
+ a1loge(p->log, 1, "spyd2: can't set col_cor_mat over non base display type\n");
+ inst_wrong_setup;
+ }
+ icmCpy3x3(p->ccmat, mtx);
+ }
+
+ return inst_ok;
+}
+
+/* Use a Colorimeter Calibration Spectral Set to set the */
+/* instrumen calibration. */
+/* This is only valid for colorimetric instruments. */
+/* To set calibration back to default, pass NULL for sets. */
+inst_code spyd2_col_cal_spec_set(
+inst *pp,
+xspect *sets,
+int no_sets
+) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_code ev = inst_ok;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+ if (p->hwver < 7)
+ return inst_unsupported;
+
+ if (sets == NULL || no_sets <= 0) {
+ if ((ev = set_default_disp_type(p)) != inst_ok)
+ return ev;
+ } else {
+ /* Use given spectral samples */
+ if ((ev = spyd4_comp_calmat(p, p->obType, p->custObserver, sets, no_sets)) != inst_ok)
+ return ev;
+ p->icx = (99 << 1) | 1; /* Out of range index */
+ }
+ return ev;
+}
+
+/* Return needed and available inst_cal_type's */
+static inst_code spyd2_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
+ spyd2 *p = (spyd2 *)pp;
+
+ inst_cal_type n_cals = inst_calt_none;
+ inst_cal_type a_cals = inst_calt_none;
+
+ if (p->refrmode != 0) {
+ if (p->rrset == 0)
+ n_cals |= inst_calt_ref_freq;
+ a_cals |= inst_calt_ref_freq;
+ }
+
+ if (pn_cals != NULL)
+ *pn_cals = n_cals;
+
+ if (pa_cals != NULL)
+ *pa_cals = a_cals;
+
+ return inst_ok;
+}
+
+/* Request an instrument calibration. */
+/* This is use if the user decides they want to do a calibration, */
+/* in anticipation of a calibration (needs_calibration()) to avoid */
+/* requiring one during measurement, or in response to measuring */
+/* returning inst_needs_cal. Initially us an inst_cal_cond of inst_calc_none, */
+/* and then be prepared to setup the right conditions, or ask the */
+/* user to do so, each time the error inst_cal_setup is returned. */
+inst_code spyd2_calibrate(
+inst *pp,
+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) */
+) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_code ev = inst_ok;
+ inst_cal_type needed, available;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ id[0] = '\000';
+
+ if ((ev = spyd2_get_n_a_cals((inst *)p, &needed, &available)) != inst_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,"spyd2_calibrate: doing calt 0x%x\n",calt);
+
+ if ((*calt & inst_calt_n_dfrble_mask) == 0) /* Nothing todo */
+ return inst_ok;
+ }
+
+ if ((*calt & inst_calt_ref_freq) && p->refrmode != 0) {
+
+ if (*calc != inst_calc_emis_white) {
+ *calc = inst_calc_emis_white;
+ return inst_cal_setup;
+ }
+
+ /* Do CRT frame rate calibration */
+ if ((ev = spyd2_GetRefRate(p)) != inst_ok)
+ return ev;
+
+ *calt &= ~inst_calt_ref_freq;
+ }
+
+ return inst_ok;
+}
+
+/* Return the last calibrated refresh rate in Hz. Returns: */
+static inst_code spyd2_get_refr_rate(inst *pp,
+double *ref_rate
+) {
+ spyd2 *p = (spyd2 *)pp;
+ if (p->refrvalid) {
+ *ref_rate = p->refrate;
+ return inst_ok;
+ } else if (p->rrset) {
+ *ref_rate = 0.0;
+ return inst_misread;
+ }
+ return inst_needs_cal;
+}
+
+/* Set the calibrated refresh rate in Hz. */
+/* Set refresh rate to 0.0 to mark it as invalid */
+/* Rates outside the range 5.0 to 150.0 Hz will return an error */
+static inst_code spyd2_set_refr_rate(inst *pp,
+double ref_rate
+) {
+ spyd2 *p = (spyd2 *)pp;
+
+ if (ref_rate != 0.0 && (ref_rate < 5.0 || ref_rate > 150.0))
+ return inst_bad_parameter;
+
+ p->refrate = ref_rate;
+ if (ref_rate == 0.0)
+ p->refrate = DEFRRATE;
+ else
+ p->refrvalid = 1;
+ p->rrset = 1;
+
+ return inst_ok;
+}
+
+/* Error codes interpretation */
+static char *
+spyd2_interp_error(inst *pp, int ec) {
+// spyd2 *p = (spyd2 *)pp;
+ ec &= inst_imask;
+ switch (ec) {
+ case SPYD2_INTERNAL_ERROR:
+ return "Non-specific software internal software error";
+ case SPYD2_COMS_FAIL:
+ return "Communications failure";
+ case SPYD2_UNKNOWN_MODEL:
+ return "Not a Spyder 2 or 3";
+ case SPYD2_DATA_PARSE_ERROR:
+ return "Data from i1 Display didn't parse as expected";
+
+ case SPYD2_OK:
+ return "No device error";
+
+ /* device specific errors */
+ case SPYD2_BADSTATUS:
+ return "Too many retries waiting for status to come good";
+ case SPYD2_PLDLOAD_FAILED:
+ return "Wrong status after download of PLD";
+ case SPYD2_BADREADSIZE:
+ return "Didn't read expected amount of data";
+ case SPYD2_TRIGTIMEOUT:
+ return "Trigger timout";
+ case SPYD2_OVERALLTIMEOUT:
+ return "Overall timout";
+ case SPYD2_BAD_EE_CRC:
+ return "Serial EEProm CRC failed";
+
+ /* Internal errors */
+ case SPYD2_BAD_EE_ADDRESS:
+ return "Serial EEProm read is out of range";
+ case SPYD2_BAD_EE_SIZE:
+ return "Serial EEProm read size > 256";
+ case SPYD2_NO_PLD_PATTERN:
+ return "No PLD firmware pattern is available (have you run spyd2en ?)";
+ case SPYD2_NO_COMS:
+ return "Communications hasn't been established";
+ case SPYD2_NOT_INITED:
+ return "Insrument hasn't been initialised";
+ case SPYD2_NOCRTCAL:
+ return "Insrument is missing the CRT calibration table";
+ case SPYD2_NOLCDCAL:
+ return "Insrument is missing the Normal or LCD calibration table";
+ case SPYD2_MALLOC:
+ return "Memory allocation failure";
+ case SPYD2_OBS_SELECT:
+ return "Failed to set observer type";
+ case SPYD2_CAL_FAIL:
+ return "Calibration calculation failed";
+ case SPYD2_INT_CIECONVFAIL:
+ return "Creating spectral to CIE converted failed";
+ case SPYD2_TOO_FEW_CALIBSAMP:
+ return "There are too few spectral calibration samples - need at least 3";
+
+ /* Configuration */
+ case SPYD2_DISP_SEL_RANGE:
+ return "Display device selection out of range";
+
+ default:
+ return "Unknown error code";
+ }
+}
+
+
+/* Convert a machine specific error code into an abstract dtp code */
+static inst_code
+spyd2_interp_code(inst *pp, int ec) {
+// spyd2 *p = (spyd2 *)pp;
+
+ ec &= inst_imask;
+ switch (ec) {
+
+ case SPYD2_OK:
+ return inst_ok;
+
+ case SPYD2_INTERNAL_ERROR:
+ case SPYD2_NO_COMS:
+ case SPYD2_NOT_INITED:
+ case SPYD2_BAD_EE_ADDRESS:
+ case SPYD2_BAD_EE_SIZE:
+ case SPYD2_NO_PLD_PATTERN:
+ case SPYD2_MALLOC:
+ case SPYD2_OBS_SELECT:
+ case SPYD2_CAL_FAIL:
+ case SPYD2_INT_CIECONVFAIL:
+ case SPYD2_TOO_FEW_CALIBSAMP:
+ return inst_internal_error | ec;
+
+ case SPYD2_COMS_FAIL:
+ case SPYD2_BADREADSIZE:
+ case SPYD2_TRIGTIMEOUT:
+ case SPYD2_BADSTATUS:
+ case SPYD2_OVERALLTIMEOUT:
+ return inst_coms_fail | ec;
+
+ case SPYD2_UNKNOWN_MODEL:
+ return inst_unknown_model | ec;
+
+// return inst_protocol_error | ec;
+
+ case SPYD2_NOCRTCAL:
+ case SPYD2_NOLCDCAL:
+ case SPYD2_PLDLOAD_FAILED:
+ case SPYD2_BAD_EE_CRC:
+ return inst_hardware_fail | ec;
+
+ case SPYD2_DISP_SEL_RANGE:
+ return inst_wrong_setup | ec;
+
+ }
+ return inst_other_error | ec;
+}
+
+/* Destroy ourselves */
+static void
+spyd2_del(inst *pp) {
+ spyd2 *p = (spyd2 *)pp;
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ inst_del_disptype_list(p->dtlist, p->ndtlist);
+ free(p);
+}
+
+/* Return the instrument mode capabilities */
+void spyd2_capabilities(inst *pp,
+inst_mode *pcap1,
+inst2_capability *pcap2,
+inst3_capability *pcap3) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_mode cap1= 0;
+ inst2_capability cap2 = 0;
+
+ cap1 |= inst_mode_emis_spot
+ | inst_mode_emis_refresh_ovd
+ | inst_mode_emis_norefresh_ovd
+ | inst_mode_colorimeter
+ ;
+
+ /* We don't seem to have a way of detecting the lack */
+ /* of ambinent capability, short of doing a read */
+ /* and noticing the result is zero. */
+ if (p->itype == instSpyder3
+ || p->itype == instSpyder4) {
+ cap1 |= inst_mode_emis_ambient;
+ }
+
+ cap2 |= inst2_prog_trig
+ | inst2_user_trig
+ | inst2_ccmx
+ | inst2_refresh_rate
+ | inst2_emis_refr_meas
+ ;
+
+ if (p->itype == instSpyder3
+ || p->itype == instSpyder4) {
+ cap2 |= inst2_disptype;
+ cap2 |= inst2_has_leds;
+ cap2 |= inst2_ambient_mono;
+ } else {
+ cap2 |= inst2_disptype;
+ }
+
+ if (p->itype == instSpyder4)
+ cap2 |= inst2_ccss; /* Spyder4 has spectral sensiivities */
+
+ if (pcap1 != NULL)
+ *pcap1 = cap1;
+ if (pcap2 != NULL)
+ *pcap2 = cap2;
+ if (pcap3 != NULL)
+ *pcap3 = inst3_none;
+}
+
+/* Check device measurement mode */
+inst_code spyd2_check_mode(inst *pp, inst_mode m) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_mode cap;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ pp->capabilities(pp, &cap, NULL, NULL);
+
+ /* Simple test */
+ if (m & ~cap)
+ return inst_unsupported;
+
+ if (!IMODETST(m, inst_mode_emis_spot)
+ && !IMODETST(m, inst_mode_emis_ambient)) {
+ return inst_unsupported;
+ }
+
+ return inst_ok;
+}
+
+/* Set device measurement mode */
+inst_code spyd2_set_mode(inst *pp, inst_mode m) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_code ev;
+
+ if ((ev = spyd2_check_mode(pp, m)) != inst_ok)
+ return ev;
+
+ p->mode = m;
+
+ if ( IMODETST(p->mode, inst_mode_emis_norefresh_ovd)) /* Must test this first! */
+ p->refrmode = 0;
+ else if (IMODETST(p->mode, inst_mode_emis_refresh_ovd))
+ p->refrmode = 1;
+
+ return inst_ok;
+}
+
+inst_disptypesel spyd2_disptypesel[3] = {
+ {
+ inst_dtflags_default,
+ 1,
+ "l",
+ "LCD display",
+ 0,
+ 1
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 2, /* cbid */
+ "c", /* sel */
+ "CRT display", /* desc */
+ 1, /* refr */
+ 0 /* ix */
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ 0
+ }
+};
+
+inst_disptypesel spyd3_disptypesel[3] = {
+ {
+ inst_dtflags_default,
+ 1,
+ "nl",
+ "Non-Refresh display",
+ 0,
+ 1
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 2, /* cbid */
+ "rc", /* sel */
+ "Refresh display", /* desc */
+ 1, /* refr */
+ 1 /* ix */
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ 0
+ }
+};
+
+inst_disptypesel spyd4_disptypesel_1[8] = {
+ {
+ inst_dtflags_default,
+ 1,
+ "nl",
+ "Generic Non-Refresh Display",
+ 0,
+ 1
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 2, /* cbid */
+ "rc", /* sel */
+ "Generic Refresh Display", /* desc */
+ 1, /* refr */
+ 1 /* ix */
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ 0
+ }
+};
+
+inst_disptypesel spyd4_disptypesel[8] = {
+ {
+ inst_dtflags_default,
+ 1,
+ "n",
+ "Generic Non-Refresh Display",
+ 0,
+ 1
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 2, /* cbid */
+ "r", /* sel */
+ "Generic Refresh Display", /* desc */
+ 1, /* refr */
+ 1 /* ix = hw bit + spec table << 1 */
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 0,
+ "f",
+ "LCD, CCFL Backlight",
+ 0,
+ (1 << 1) | 1
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 0,
+ "L",
+ "Wide Gamut LCD, CCFL Backlight",
+ 0,
+ (2 << 1) | 1
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 0,
+ "e",
+ "LCD, White LED Backlight",
+ 0,
+ (3 << 1) | 1
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 0,
+ "B",
+ "Wide Gamut LCD, RGB LED Backlight",
+ 0,
+ (4 << 1) | 1
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 0,
+ "x",
+ "LCD, CCFL Backlight (Laptop ?)",
+ 0,
+ (5 << 1) | 1
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ 0
+ }
+};
+
+static void set_base_disptype_list(spyd2 *p) {
+ /* set the base display type list */
+ if (p->itype == instSpyder4) {
+ if (spyd4_nocals <= 1) {
+ p->_dtlist = spyd4_disptypesel_1;
+ } else {
+ p->_dtlist = spyd4_disptypesel;
+ }
+ } else if (p->itype == instSpyder3) {
+ p->_dtlist = spyd3_disptypesel;
+ } else {
+ p->_dtlist = spyd2_disptypesel;
+ }
+}
+
+/* Get mode and option details */
+static inst_code spyd2_get_disptypesel(
+inst *pp,
+int *pnsels, /* Return number of display types */
+inst_disptypesel **psels, /* Return the array of display types */
+int allconfig, /* nz to return list for all configs, not just current. */
+int recreate /* nz to re-check for new ccmx & ccss files */
+) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_code rv = inst_ok;
+
+ /* Create/Re-create a current list of abailable display types */
+ if (p->dtlist == NULL || recreate) {
+ if ((rv = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
+ p->_dtlist, p->hwver >= 7 ? 1 : 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return rv;
+ }
+
+ if (pnsels != NULL)
+ *pnsels = p->ndtlist;
+
+ if (psels != NULL)
+ *psels = p->dtlist;
+
+ return inst_ok;
+}
+
+/* Given a display type entry, setup for that type */
+static inst_code set_disp_type(spyd2 *p, inst_disptypesel *dentry) {
+ inst_code ev;
+ int refrmode;
+
+ p->icx = dentry->ix;
+ p->cbid = dentry->cbid;
+ refrmode = dentry->refr;
+
+ if ( IMODETST(p->mode, inst_mode_emis_norefresh_ovd)) { /* Must test this first! */
+ refrmode = 0;
+ } else if (IMODETST(p->mode, inst_mode_emis_refresh_ovd)) {
+ refrmode = 1;
+ }
+
+ if (p->refrmode != refrmode) {
+ p->rrset = 0; /* This is a hint we may have swapped displays */
+ p->refrvalid = 0;
+ }
+ p->refrmode = refrmode;
+
+ if (dentry->flags & inst_dtflags_ccss) {
+
+ if ((ev = spyd4_comp_calmat(p, p->obType, p->custObserver, dentry->sets, dentry->no_sets))
+ != inst_ok) {
+ a1logd(p->log, 1, "spyd4_set_disp_type: comp_calmat ccss failed with rv = 0x%x\n",ev);
+ return ev;
+ }
+ p->icx = (99 << 1) | 1; /* Out of range index */
+ icmSetUnity3x3(p->ccmat);
+
+ } else {
+
+ if (p->hwver >= 7) {
+ if ((p->icx >> 1) > spyd4_nocals)
+ return inst_unsupported;
+
+ /* Create the calibration matrix */
+ if ((ev = spyd4_set_cal(p, p->icx >> 1)) != inst_ok)
+ return ev;
+ }
+
+ if (dentry->flags & inst_dtflags_ccmx) {
+ icmCpy3x3(p->ccmat, dentry->mat);
+ } else {
+ icmSetUnity3x3(p->ccmat);
+ }
+ }
+
+ return inst_ok;
+}
+
+
+/* Setup the default display type */
+static inst_code set_default_disp_type(spyd2 *p) {
+ inst_code ev;
+ int i;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list((inst *)p, &p->ndtlist, &p->dtlist,
+ p->_dtlist, p->hwver >= 7 ? 1 : 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ for (i = 0; !(p->dtlist[i].flags & inst_dtflags_end); i++) {
+ if (p->dtlist[i].flags & inst_dtflags_default)
+ break;
+ }
+ if (p->dtlist[i].flags & inst_dtflags_end) {
+ a1loge(p->log, 1, "set_default_disp_type: failed to find type!\n");
+ return inst_internal_error;
+ }
+ if ((ev = set_disp_type(p, &p->dtlist[i])) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+/* Set the display type */
+static inst_code spyd2_set_disptype(inst *pp, int ix) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_code ev;
+ inst_disptypesel *dentry;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
+ p->_dtlist, p->hwver >= 7 ? 1 : 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ if (ix < 0 || ix >= p->ndtlist)
+ return inst_unsupported;
+
+ dentry = &p->dtlist[ix];
+
+ if ((ev = set_disp_type(p, dentry)) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+/*
+ * set or reset an optional mode
+ *
+ * Some options talk to the instrument, and these will
+ * error if it hasn't been initialised.
+ * [We could fix this by setting a flag and adding
+ * some extra logic in init()]
+ */
+static inst_code
+spyd2_get_set_opt(inst *pp, inst_opt_type m, ...) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_code ev = inst_ok;
+
+ /* Record the trigger mode */
+ if (m == inst_opt_trig_prog
+ || m == inst_opt_trig_user) {
+ p->trig = m;
+ return inst_ok;
+ }
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* Get the display type information */
+ if (m == inst_opt_get_dtinfo) {
+ va_list args;
+ int *refrmode, *cbid;
+
+ va_start(args, m);
+ refrmode = va_arg(args, int *);
+ cbid = va_arg(args, int *);
+ va_end(args);
+
+ if (refrmode != NULL)
+ *refrmode = p->refrmode;
+ if (cbid != NULL)
+ *cbid = p->cbid;
+
+ return inst_ok;
+ }
+
+ /* Set the ccss observer type */
+ if (m == inst_opt_set_ccss_obs) {
+ va_list args;
+ icxObserverType obType;
+ xspect *custObserver;
+
+ va_start(args, m);
+ obType = va_arg(args, icxObserverType);
+ custObserver = va_arg(args, xspect *);
+ va_end(args);
+
+ if (obType == icxOT_default)
+ obType = icxOT_CIE_1931_2;
+ p->obType = obType;
+ if (obType == icxOT_custom) {
+ p->custObserver[0] = custObserver[0];
+ p->custObserver[1] = custObserver[1];
+ p->custObserver[2] = custObserver[2];
+ }
+
+ return inst_ok;
+ }
+
+ /* Operate the LED */
+ if (p->hwver >= 4) {
+ if (m == inst_opt_get_gen_ledmask) {
+ va_list args;
+ int *mask = NULL;
+
+ va_start(args, m);
+ mask = va_arg(args, int *);
+ va_end(args);
+ *mask = 0x1; /* One general LED */
+ return inst_ok;
+ } else if (m == inst_opt_get_led_state) {
+ va_list args;
+ int *mask = NULL;
+
+ va_start(args, m);
+ mask = va_arg(args, int *);
+ va_end(args);
+ *mask = p->led_state;
+ return inst_ok;
+ } else if (m == inst_opt_set_led_state) {
+ va_list args;
+ int mask = 0;
+
+ va_start(args, m);
+ mask = 1 & va_arg(args, int);
+ va_end(args);
+ if ((ev = spyd2_setLED(p, mask & 1 ? 2 : 0, 0.0)) == inst_ok) {
+ p->led_state = mask;
+ }
+ return ev;
+ }
+ }
+
+ if (m == inst_opt_get_pulse_ledmask) {
+ va_list args;
+ int *mask = NULL;
+
+ va_start(args, m);
+ mask = va_arg(args, int *);
+ va_end(args);
+ *mask = 0x1; /* General LED is pulsable */
+ return inst_ok;
+ } else if (m == inst_opt_set_led_pulse_state) {
+ va_list args;
+ double period, on_time_prop, trans_time_prop;
+ int mode;
+
+ va_start(args, m);
+ period = va_arg(args, double);
+ on_time_prop = va_arg(args, double);
+ trans_time_prop = va_arg(args, double);
+ va_end(args);
+ if (period < 0.0
+ || on_time_prop < 0.0 || on_time_prop > 1.0
+ || trans_time_prop < 0.0 || trans_time_prop > 1.0
+ || trans_time_prop > on_time_prop || trans_time_prop > (1.0 - on_time_prop))
+ return inst_bad_parameter;
+ if (period == 0.0 || on_time_prop == 0.0) {
+ period = 0.0;
+ mode = 0;
+ p->led_state = 0;
+ } else {
+ mode = 1;
+ p->led_state = 1;
+ }
+ p->led_period = period;
+ p->led_on_time_prop = on_time_prop;
+ p->led_trans_time_prop = trans_time_prop;
+ return spyd2_setLED(p, mode, period);
+ } else if (m == inst_opt_get_led_state) {
+ va_list args;
+ double *period, *on_time_prop, *trans_time_prop;
+
+ va_start(args, m);
+ period = va_arg(args, double *);
+ on_time_prop = va_arg(args, double *);
+ trans_time_prop = va_arg(args, double *);
+ va_end(args);
+ if (period != NULL) *period = p->led_period;
+ if (on_time_prop != NULL) *on_time_prop = p->led_on_time_prop;
+ if (trans_time_prop != NULL) *trans_time_prop = p->led_trans_time_prop;
+ return inst_ok;
+ }
+ return inst_unsupported;
+}
+
+/* Constructor */
+extern spyd2 *new_spyd2(icoms *icom, instType itype) {
+ spyd2 *p;
+ if ((p = (spyd2 *)calloc(sizeof(spyd2),1)) == NULL) {
+ a1loge(icom->log, 1, "new_spyd2: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(icom->log);
+
+ p->init_coms = spyd2_init_coms;
+ p->init_inst = spyd2_init_inst;
+ p->capabilities = spyd2_capabilities;
+ p->check_mode = spyd2_check_mode;
+ p->set_mode = spyd2_set_mode;
+ p->get_disptypesel = spyd2_get_disptypesel;
+ p->set_disptype = spyd2_set_disptype;
+ p->get_set_opt = spyd2_get_set_opt;
+ p->read_sample = spyd2_read_sample;
+ p->read_refrate = spyd2_read_refrate;
+ p->get_n_a_cals = spyd2_get_n_a_cals;
+ p->calibrate = spyd2_calibrate;
+ p->col_cor_mat = spyd2_col_cor_mat;
+ p->col_cal_spec_set = spyd2_col_cal_spec_set;
+ p->get_refr_rate = spyd2_get_refr_rate;
+ p->set_refr_rate = spyd2_set_refr_rate;
+ p->interp_error = spyd2_interp_error;
+ p->del = spyd2_del;
+
+ p->icom = icom;
+ p->itype = icom->itype;
+
+ /* Load manufacturers Spyder4 calibrations */
+ if (itype == instSpyder4) {
+ int rv;
+ p->hwver = 7; /* Set preliminary version */
+ if ((rv = spyd4_load_cal(p)) != SPYD2_OK)
+ a1logd(p->log, 1, "Loading Spyder4 calibrations failed with '%s'\n",p->interp_error((inst *)p, rv));
+ if (spyd4_nocals < 1)
+ a1logd(p->log, 1, "Spyder4 choice of calibrations not available\n");
+ }
+ if (itype == instSpyder3) {
+ p->hwver = 4; /* Set preliminary version */
+ }
+ if (itype == instSpyder2) {
+ p->hwver = 3; /* Set preliminary version */
+ }
+
+ icmSetUnity3x3(p->ccmat); /* Set the colorimeter correction matrix to do nothing */
+ set_base_disptype_list(p);
+
+ return p;
+}
+
diff --git a/spectro/spyd2.h b/spectro/spyd2.h
new file mode 100644
index 0000000..d3a208c
--- /dev/null
+++ b/spectro/spyd2.h
@@ -0,0 +1,168 @@
+#ifndef SPYD2_H
+
+/*
+ * Argyll Color Correction System
+ *
+ * ColorVision Spyder 2 & 3 related software.
+ *
+ * Author: Graeme W. Gill
+ * Date: 19/10/2006
+ *
+ * Copyright 2006 - 2013, Graeme W. Gill
+ * All rights reserved.
+ *
+ * (Based on i1disp.c)
+ *
+ * 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.
+ */
+
+#include "inst.h"
+
+/* Note: update spyd2_interp_error() and spyd2_interp_code() in spyd2.c */
+/* if anything of these #defines are added or subtracted */
+
+/* Fake Error codes */
+#define SPYD2_INTERNAL_ERROR 0x61 /* Internal software error */
+#define SPYD2_COMS_FAIL 0x62 /* Communication failure */
+#define SPYD2_UNKNOWN_MODEL 0x63 /* Not an spyd2lay */
+#define SPYD2_DATA_PARSE_ERROR 0x64 /* Read data parsing error */
+
+/* Real error code */
+#define SPYD2_OK 0x00
+
+/* Sub codes for device specific reasoning */
+#define SPYD2_BADSTATUS 0x01
+#define SPYD2_PLDLOAD_FAILED 0x02
+#define SPYD2_BADREADSIZE 0x03
+#define SPYD2_TRIGTIMEOUT 0x04
+#define SPYD2_OVERALLTIMEOUT 0x05
+#define SPYD2_BAD_EE_CRC 0x06
+
+/* Internal software errors */
+#define SPYD2_BAD_EE_ADDRESS 0x21
+#define SPYD2_BAD_EE_SIZE 0x22
+#define SPYD2_NO_PLD_PATTERN 0x23
+#define SPYD2_NO_COMS 0x24
+#define SPYD2_NOT_INITED 0x25
+#define SPYD2_NOCRTCAL 0x26 /* No CRT calibration data */
+#define SPYD2_NOLCDCAL 0x27 /* No LCD calibration data */
+#define SPYD2_MALLOC 0x28
+#define SPYD2_OBS_SELECT 0x29 /* Observer */
+#define SPYD2_CAL_FAIL 0x2A
+#define SPYD2_TOO_FEW_CALIBSAMP 0x2B
+#define SPYD2_INT_CIECONVFAIL 0x2C
+
+/* Configuration */
+#define SPYD2_DISP_SEL_RANGE 0x40 /* Calibration selection is out of range */
+
+/* SPYD2/3 communication object */
+struct _spyd2 {
+ INST_OBJ_BASE
+
+ inst_mode mode; /* Currently selected mode (emis/ambient/etc.) */
+
+ inst_opt_type trig; /* Reading trigger mode */
+
+ /* Serial EEPROM registers */
+ /* versioni & feature bits */
+ /* Spyder2 = 0x0307 */
+ /* Spyder3 Express = 0x040f */
+ /* Spyder3 Pro = 0x0407 */
+ /* Spyder3 Elite = 0x0407 */
+ /* Spyder4 Pro = 0x070F */
+
+ unsigned int hwver; /* 5:B Harware version number */
+ /* Spyder2 = 3 */
+ /* Spyder3 = 4 */
+ /* Spyder4 = 7 */
+
+ unsigned int fbits; /* 6:B Feature bits 0,1,2,3 correspond to calibration types */
+ /* CRT/UNK, LCD/NORM, TOK, CRT/UNK */
+
+ char serno[9]; /* 8:8xB Serial number as zero terminated string */
+
+ /* Spyder2: [0][][] = CRT, [1][][] = LCD */
+ /* Spyder3: [0][][] = UNK, [1][][] = CRT & LCD */
+
+ /* hwver 3..6 uses these calibrations */
+ double cal_A[2][3][9]; /* HW3..6: 16, 256 CRT/LCD A calibration matrix */
+ double cal_B[2][3][9]; /* HW3..6: 128, 384 CRT/LCD B calibration matrix */
+
+ /* HW3 [0] = CRT/UNK, [1] = LCD/NORM */
+ /* HW4..6 [0] = not used, [1] = LCD */
+
+ /* HW7 [0] = No used */
+ /* HW7: cal_A[1] computed from sensor spectral data. */
+ /* HWy: cal_B[1] 60, 384 Linearity correction */
+
+ /* The first (A) 3x9 is a sensor to XYZ transform. */
+ /* cal[0] is an offset value, while the */
+ /* remaining 8 entries are the sensor weightings. */
+ /* Because there are only 7 real sensors, cal[1] */
+ /* and sensor[0] are skipped. */
+
+ /* The second (B) 3x9 is an additional non-linearity */
+ /* correction matrix, applied to the XYZ created */
+ /* from the first 3x9. The cooeficients consist */
+ /* of the weights for each product, ie: */
+ /* X, Y, Z, X*X, X*Z, Y*Z, X*X, Y*Y, Z*Z */
+ /* for each corrected output XYZ */
+
+ double cal_F[7]; /* 240:4, 364:3 F calibration vector */
+ /* This might be Y only weightings for the 7 sensor values, */
+ /* with no offset value (TOK type ?). */
+
+ /* hwver 7 (Spyder 4) uses computed calibrations */
+ xspect sens[7]; /* Sensor sensitivity curves in Hz per mW/nm/m^2 */
+
+ /* Computed factors and state */
+ inst_disptypesel *_dtlist; /* Base list */
+ inst_disptypesel *dtlist; /* Display Type list */
+ int ndtlist; /* Number of valid dtlist entries */
+
+ int refrmode; /* 0 for constant, 1 for refresh display */
+ int cbid; /* calibration base ID, 0 if not a base */
+ int icx; /* Bit 0: Cal table index, 0 = CRT, 1 = LCD/normal */
+ /* Bits 31-1: Spyder 4 spectral cal index, 0..spyd4_nocals-1 */
+ int rrset; /* Flag, nz if the refresh rate has been determined */
+ double refrate; /* Current refresh rate. Set to DEFREFR if not measurable */
+ int refrvalid; /* nz if refrate was measured */
+ double gain; /* hwver == 5 gain value (default 4) */
+ double ccmat[3][3]; /* Colorimeter correction matrix */
+ icxObserverType obType; /* ccss observer to use */
+ xspect custObserver[3]; /* Custom ccss observer to use */
+
+ int prevraw[8]; /* Previous raw reading values */
+ int prevrawinv; /* Previous raw readings invalid flag - after an abort */
+
+ /* Other state */
+ int led_state; /* Spyder 3: Current LED state */
+ double led_period, led_on_time_prop, led_trans_time_prop; /* Spyder 3: Pulse state */
+
+}; typedef struct _spyd2 spyd2;
+
+/* Constructor */
+extern spyd2 *new_spyd2(icoms *icom, instType itype);
+
+
+#define SPYD2_H
+#endif /* SPYD2_H */
diff --git a/spectro/spyd2PLD.h b/spectro/spyd2PLD.h
new file mode 100644
index 0000000..4a71cd8
--- /dev/null
+++ b/spectro/spyd2PLD.h
@@ -0,0 +1,10 @@
+
+/* Spyder 2 Colorimeter Xilinx XCS05XL firmware pattern */
+/* needs to be transfered here for the instrument to work. */
+
+/* The end user should see the spyd2en utility.*/
+
+static unsigned int pld_size = 0x11223344; /* Endian indicator */
+static unsigned int pld_space = 6824;
+static unsigned char pld_bytes[6824] = "XCS05XL firmware pattern"; /* Magic number */
+
diff --git a/spectro/spyd2setup.h b/spectro/spyd2setup.h
new file mode 100644
index 0000000..def83b6
--- /dev/null
+++ b/spectro/spyd2setup.h
@@ -0,0 +1,119 @@
+#ifndef SPYD2SETUP_H
+
+/*
+ * Argyll Color Correction System
+ *
+ * ColorVision Spyder 2 related software.
+ *
+ * Author: Graeme W. Gill
+ * Date: 19/10/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.
+ */
+
+/* This file is only included in top utilities that need to */
+/* be able to access the Spyder 2 colorimeter. This provides */
+/* a mechanism for ensuring that only such utilities load the */
+/* proprietary Spyder firmware, as well as providing a means to */
+/* detect if the spyder driver is going to be funcional. */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern unsigned int *spyder2_pld_size; /* in spyd2.c */
+extern unsigned char *spyder2_pld_bytes;
+
+#ifdef __cplusplus
+}
+#endif
+
+/* Return 0 if Spyder 2 firmware is not available */
+/* Return 1 if Spyder 2 firmware is available from an external file */
+/* Return 2 if Spyder 2 firmware is part of this executable */
+int setup_spyd2() {
+#ifdef ENABLE_USB
+ static int loaded = 0; /* Was loaded from a file */
+ char **bin_paths = NULL;
+ int no_paths = 0;
+ unsigned int size, rsize;
+ FILE *fp;
+ int i;
+
+ /* Spyder 2 Colorimeter Xilinx XCS05XL firmware pattern. */
+ /* This is a placeholder in the distributed files. */
+ /* It could be replaced with the actual end users firmware */
+ /* by using the spyd2trans utility, but normally the spyd2PLD.bin */
+ /* file is loaded instead. */
+
+#include "spyd2PLD.h"
+
+ spyder2_pld_size = &pld_size;
+ spyder2_pld_bytes = pld_bytes;
+
+ /* If no firmware compiled in, see if there is a file to load from. */
+ if ((pld_size == 0 || pld_size == 0x11223344) && loaded == 0) {
+
+
+ for (;;) { /* So we can break out */
+ if ((no_paths = xdg_bds(NULL, &bin_paths, xdg_data, xdg_read, xdg_user,
+ "ArgyllCMS/spyd2PLD.bin" XDG_FUDGE "color/spyd2PLD.bin"
+)) < 1) {
+ a1logd(g_log, 1, "etup_spyd2: failed to find PLD file\n");
+ break;
+ }
+
+ /* open binary file */
+#if !defined(O_CREAT) && !defined(_O_CREAT)
+# error "Need to #include fcntl.h!"
+#endif
+#if defined(O_BINARY) || defined(_O_BINARY)
+ if ((fp = fopen(bin_paths[0],"rb")) == NULL)
+#else
+ if ((fp = fopen(bin_paths[0],"r")) == NULL)
+#endif
+ break;
+ xdg_free(bin_paths, no_paths);
+
+ /* Figure out how file it is */
+ if (fseek(fp, 0, SEEK_END)) {
+ fclose(fp);
+ break;
+ }
+ size = (unsigned long)ftell(fp);
+
+ if (size > pld_space)
+ size = pld_space;
+
+ if (fseek(fp, 0, SEEK_SET)) {
+ fclose(fp);
+ break;
+ }
+
+ if (fread(pld_bytes, 1, size, fp) != size) {
+ fclose(fp);
+ break;
+ }
+ pld_size = size;
+ loaded = 1; /* We've loaded it from a file */
+// a1logd(g_log,0,"Spyder2 pld bytes = 0x%x 0x%x 0x%x 0x%x\n",pld_bytes[0], pld_bytes[1], pld_bytes[2], pld_bytes[3]);
+ fclose(fp);
+ break;
+ }
+ }
+
+ if (pld_size != 0 && pld_size != 0x11223344) {
+ if (loaded)
+ return 1; /* Was loaded from a file */
+ return 2; /* Was compiled in */
+ }
+#endif /* ENABLE_USB */
+ return 0; /* Not available */
+}
+
+#define SPYD2SETUP_H
+#endif /* SPYD2SETUP_H */
diff --git a/spectro/ss.c b/spectro/ss.c
new file mode 100644
index 0000000..c72e189
--- /dev/null
+++ b/spectro/ss.c
@@ -0,0 +1,2109 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * Gretag Spectrolino and Spectroscan related
+ * defines and declarations.
+ *
+ * Author: Graeme W. Gill
+ * Date: 13/7/2005
+ *
+ * Copyright 2005 - 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.
+ *
+ * Derived from DTP41.h
+ *
+ * This is an alternative driver to spm/gretag.
+ */
+
+/*
+ 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:
+
+ There is a bug or limitation with using -N to skip the calibration
+ when using any of the emissive modes - the readings end up being nearly zero.
+
+ We aren't saving the spectrolino fake tranmission white reference in
+ a calibration file, so -N doesn't work with it.
+
+ You can't trigger a calibration reading using the instrument switch.
+
+ You should be able to do use the table enter key anywhere the user
+ is asked to hit a key.
+
+ The corner positioning could be smarter.
+
+ The SpectroscanT transmission cal. merely reminds the user (vie verbose)
+ that it is assuming the correct apatture, rather than given them
+ a chance to change it.
+
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <math.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* SALONEINSTLIB */
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* SALONEINSTLIB */
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "ss.h"
+
+/* Default flow control */
+#define DEFFC fc_Hardware
+
+#include <stdarg.h>
+
+/* Some tables to convert between emums and text descriptions */
+
+/* Filter type */
+char* filter_desc[] = {
+ "Filter not defined",
+ "No Filter (U)",
+ "Polarizing Filter",
+ "D65 Filter",
+ "(Unknown Filter)",
+ "UV cut Filter",
+ "Custon Filter"
+};
+
+#define SS_REF_CAL_COUNT 50
+#define SS_TRANS_CAL_COUNT 10
+
+/* Track the number of measurements taken */
+static void inc_calcount(ss *p) {
+ p->calcount++;
+}
+
+/* Check if a calibration should be done, and set flags appropriately. */
+/* (atstart should be nz at start of serpentine) */
+static void check_calcount(ss *p, int atstart) {
+ a1logd(p->log, 4, "ss check_calcount: atstart %d, calcount %d, pisrow %d\n",
+ atstart,p->calcount,p->pisrow);
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_reflection) {
+ /* If forced or out and back will cross count, do cal now. */
+ if (p->forcecalib /* Forced */
+ || p->calcount >= SS_REF_CAL_COUNT /* Needs cal now */
+ || ( atstart /* At start of up & back */
+ && p->pisrow < SS_REF_CAL_COUNT /* possible not to cross */
+ && (p->calcount + p->pisrow) > SS_REF_CAL_COUNT)) { /* and will cross */
+ p->need_wd_cal = 1;
+ p->forcecalib = 0;
+ a1logd(p->log, 4, "ss check_calcount: need_wd_cal set\n");
+ }
+ } else if (((p->mode & inst_mode_illum_mask) == inst_mode_transmission
+ && (p->calcount >= SS_TRANS_CAL_COUNT || p->forcecalib))) {
+ p->forcecalib = 0;
+ p->need_t_cal = 2;
+ a1logd(p->log, 4, "ss check_calcount: need_t_cal set\n");
+ }
+}
+
+/* Establish communications with a Spectrolino/Spectroscan */
+/* Use the baud rate given, and timeout in to secs */
+/* Return DTP_COMS_FAIL on failure to establish communications */
+static inst_code
+ss_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ ss *p = (ss *)pp;
+ /* We're a bit stuffed if the Specrolino/scan is set to 28800, since */
+ /* this rate isn't universally supported by computer systems. */
+ baud_rate brt[7] = { baud_9600, baud_19200, baud_57600,
+ baud_2400, baud_1200, baud_600,
+ baud_300 };
+ ss_bt ssbrc[7] = { ss_bt_9600, ss_bt_19200, ss_bt_57600,
+ ss_bt_2400, ss_bt_1200, ss_bt_600,
+ ss_bt_300 };
+ ss_ctt sobrc[7] = { ss_ctt_SetBaud9600, ss_ctt_SetBaud19200, ss_ctt_SetBaud57600,
+ ss_ctt_SetBaud2400, ss_ctt_SetBaud1200, ss_ctt_SetBaud600,
+ ss_ctt_SetBaud300 };
+ ss_ctt fcc1;
+ ss_hst fcc2;
+ long etime;
+ int ci, bi, i, se;
+ inst_code ev = inst_ok;
+
+ a1logd(p->log, 2, "ss_init_coms: About to init Serial I/O\n");
+
+ /* Deal with flow control setting */
+ if (fc == fc_nc)
+ fc = DEFFC;
+
+ if (fc == fc_XonXOff) {
+ fcc1 = ss_ctt_ProtokolWithXonXoff;
+ fcc2 = ss_hst_XonXOff;
+ } else if (fc == fc_Hardware) {
+ fcc1 = ss_ctt_ProtokolWithHardwareHS;
+ fcc2 = ss_hst_Hardware;
+ } else {
+ fc = fc_none;
+ fcc1 = ss_ctt_ProtokolWithoutXonXoff;
+ fcc2 = ss_hst_None;
+ }
+
+ /* Figure Spectrolino baud rate being asked for */
+ for (bi = 0; bi < 7; bi++) {
+ if (brt[bi] == br)
+ break;
+ }
+ if (bi >= 7)
+ bi = 0;
+
+ /* Figure current icoms baud rate */
+ for (ci = 0; ci < 7; ci++) {
+ if (brt[ci] == p->icom->br)
+ break;
+ }
+ if (ci >= 7)
+ ci = bi;
+
+ /* The tick to give up on */
+ etime = clock() + (long)(CLOCKS_PER_SEC * tout + 0.5);
+
+ /* Until we establish comms or give up */
+ while (clock() < etime) {
+
+ /* Until we time out, find the correct baud rate */
+ for (i = ci; clock() < etime;) {
+ a1logd(p->log, 4, "ss_init_coms: trying baud rate %d\n",i);
+ if ((se = p->icom->set_ser_port(p->icom, fc_none, brt[i], parity_none,
+ stop_1, length_8)) != ICOM_OK) {
+ a1logd(p->log, 1, "ss_init_coms: set_ser_port failed ICOM err 0x%x\n",se);
+ p->snerr = icoms2ss_err(se);
+ return ss_inst_err(p);
+ }
+
+ /* Try a SpectroScan Output Status */
+ ss_init_send(p);
+ ss_add_ssreq(p, ss_OutputStatus);
+ ss_command(p, SH_TMO);
+
+ if (ss_sub_1(p) == ss_AnsPFX) { /* Got comms */
+ p->itype = instSpectroScan; /* Preliminary */
+ break;
+ }
+
+ /* Try a Spectrolino Parameter Request */
+ ss_init_send(p);
+ ss_add_soreq(p, ss_ParameterRequest);
+ ss_command(p, SH_TMO);
+
+ if (ss_sub_1(p) == ss_ParameterAnswer) { /* Got comms */
+ p->itype = instSpectrolino;
+ break;
+ }
+
+ /* Check for user abort */
+ if (p->uicallback != NULL) {
+ inst_code ev;
+ if ((ev = p->uicallback(p->uic_cntx, inst_negcoms)) == inst_user_abort) {
+ a1logd(p->log, 1, "ss_init_coms: user aborted\n");
+ return inst_user_abort;
+ }
+ }
+
+ if (++i >= 7)
+ i = 0;
+ }
+ break; /* Got coms */
+ }
+
+ if (clock() >= etime) { /* We haven't established comms */
+ return inst_coms_fail;
+ }
+
+ a1logd(p->log, 4, "ss_init_coms: got basic communications\n");
+
+ /* Finalise the communications */
+ if (p->itype == instSpectrolino) {
+
+ if ((ev = so_do_MeasControlDownload(p, fcc1)) != inst_ok)
+ return ev;
+
+ /* Do baudrate change without checking results */
+ so_do_MeasControlDownload(p, sobrc[bi]);
+ if ((se = p->icom->set_ser_port(p->icom, fc, brt[bi], parity_none,
+ stop_1, length_8)) != ICOM_OK) {
+ a1logd(p->log, 1, "ss_init_coms: spectrolino set_ser_port failed ICOM err 0x%x\n",se);
+ p->snerr = icoms2ss_err(se);
+ return ss_inst_err(p);
+ }
+
+ } else { /* Spectroscan */
+
+ ss_do_SetDeviceOnline(p); /* Put the device online */
+
+ /* Make sure other communication parameters are right */
+ if ((ev = ss_do_ChangeHandshake(p, fcc2)) != inst_ok)
+ return ev;
+
+ /* Do baudrate change without checking results */
+ ss_do_ChangeBaudRate(p, ssbrc[bi]);
+ if ((se = p->icom->set_ser_port(p->icom, fc, brt[bi], parity_none,
+ stop_1, length_8)) != ICOM_OK) {
+ a1logd(p->log, 1, "ss_init_coms: spectroscan set_ser_port failed ICOM err 0x%x\n",se);
+ p->snerr = icoms2ss_err(se);
+ return ss_inst_err(p);
+ }
+
+ /* Make sure the Spectrolino is talking to us. */
+ if ((ev = ss_do_ScanSpectrolino(p)) != inst_ok) {
+ a1logd(p->log, 1, "ss_init_coms: spectroscan, instrument isn't communicating ICOM err 0x%x\n",se);
+ return ev;
+ }
+ }
+
+ a1logd(p->log, 4, "ss_init_coms: establish communications\n");
+
+ /* See if we have a Spectroscan or SpectroscanT, and get other details */
+ p->itype = instUnknown;
+ {
+ char devn[19];
+
+ ev = ss_do_OutputType(p, devn);
+
+ if (ev == inst_ok) {
+ a1logd(p->log, 5, "ss_init_coms: got device name '%s'\n",devn);
+ if (strncmp(devn, "SpectroScanT",12) == 0) {
+ p->itype = instSpectroScanT;
+ } else if (strncmp(devn, "SpectroScan",11) == 0) {
+ p->itype = instSpectroScan;
+ }
+ }
+ }
+ /* Check if there's a Spectrolino */
+ {
+ char devn[19];
+ int sn, sr, yp, mp, dp, hp, np; /* Date & Time of production */
+ int fswl, nosw, dsw; /* Wavelengths sampled */
+ ss_ttt tt; /* Target Type */
+
+ ev = so_do_TargetIdRequest(p, devn, &sn, &sr, &yp, &mp, &dp, &hp, &np,
+ &tt, &fswl, &nosw, &dsw);
+
+ if (ev == inst_ok) {
+
+ a1logd(p->log, 4, "ss_init_coms:\n"
+ " Got device name '%s'\n"
+ " Got target type '%d'\n"
+ " Start wl %d, no wl %d, wl space %d\n", devn ,tt, fswl, nosw, dsw);
+
+ /* "Spectrolino" and "Spectrolino 8mm" are known */
+ if (tt != ss_ttt_Spectrolino
+ || strncmp(devn, "Spectrolino",11) != 0)
+ return inst_unknown_model;
+
+ if (p->itype == instUnknown) /* No SpectrScan */
+ p->itype = instSpectrolino;
+ }
+ }
+
+#ifdef EMSST
+ a1logv(p->log, 0, "DEBUG: Emulating SpectroScanT with SpectroScan!\n");
+#endif
+
+ p->gotcoms = 1;
+
+ a1logd(p->log, 2, "ss_init_coms: init coms has suceeded\n");
+
+ return inst_ok;
+}
+
+/* Set the capabilities values for the type of instrument */
+static void ss_determine_capabilities(ss *p) {
+
+ /* Set the capabilities mask */
+ p->cap = inst_mode_ref_spot
+ | inst_mode_emis_spot
+ | inst_mode_emis_tele
+ | inst_mode_colorimeter
+ | inst_mode_spectral
+ ;
+
+ if (p->itype == instSpectrolino) {
+ p->cap |= inst_mode_trans_spot; /* Support this manually using a light table */
+ }
+
+ if (p->itype == instSpectroScan
+ || p->itype == instSpectroScanT) /* Only in reflective mode */
+ p->cap |= inst_mode_ref_xy;
+
+ if (p->itype == instSpectroScanT) {
+ p->cap |= inst_mode_trans_spot;
+ }
+
+ /* Set the capabilities mask 2 */
+ p->cap2 = inst2_prog_trig
+ | inst2_user_trig
+ | inst2_user_switch_trig
+ ;
+
+ if (p->itype == instSpectroScan
+ || p->itype == instSpectroScanT) {
+ /* These are not available in transmission mode */
+ if ((p->mode & inst_mode_illum_mask) != inst_mode_transmission) {
+ p->cap2 |= inst2_xy_holdrel
+ | inst2_xy_locate
+ | inst2_xy_position
+ ;
+ }
+ }
+
+ p->cap3 = inst3_none;
+
+ a1logd(p->log, 4, "ss_determine_capabilities got cap1 0x%x cap2 0x%x\n",p->cap,p->cap2);
+}
+
+/* Initialise the Spectrolino/SpectroScan. */
+/* return non-zero on an error, with dtp error code */
+static inst_code
+ss_init_inst(inst *pp) {
+ ss *p = (ss *)pp;
+ inst_code rv = inst_ok;
+
+ a1logd(p->log, 2, "ss_init_inst: called\n");
+
+ if (p->gotcoms == 0)
+ return inst_internal_error; /* Must establish coms before calling init */
+
+ /* Reset the instrument to a known state */
+ if (p->itype != instSpectrolino) {
+
+ /* Initialise the device without resetting the baud rate */
+ if (p->itype == instSpectroScanT) {
+ if ((rv = ss_do_SetTableMode(p, ss_tmt_Reflectance)) != inst_ok)
+ return rv;
+ }
+ if ((rv = ss_do_SetDeviceOnline(p)) != inst_ok)
+ return rv;
+ if ((rv = ss_do_ResetKeyAcknowlge(p)) != inst_ok)
+ return rv;
+ if ((rv = ss_do_ReleasePaper(p)) != inst_ok)
+ return rv;
+ if ((rv = ss_do_InitMotorPosition(p)) != inst_ok)
+ return rv;
+
+ if (p->log->verb) {
+ char dn[19]; /* Device name */
+ unsigned int sn; /* Serial number */
+ char pn[9]; /* Part number */
+ int yp; /* Year of production (e.g. 1996) */
+ int mp; /* Month of production (1-12) */
+ int dp; /* Day of production (1-31) */
+ char sv[13]; /* Software version */
+
+ if ((rv = ss_do_OutputType(p, dn)) != inst_ok)
+ return rv;
+ if ((rv = ss_do_OutputSerialNumber(p, &sn)) != inst_ok)
+ return rv;
+ if ((rv = ss_do_OutputArticleNumber(p, pn)) != inst_ok)
+ return rv;
+ if ((rv = ss_do_OutputProductionDate(p, &yp, &mp, &dp)) != inst_ok)
+ return rv;
+ if ((rv = ss_do_OutputSoftwareVersion(p, sv)) != inst_ok)
+ return rv;
+
+ a1logv(p->log, 1,
+ " Device: %s\n"
+ " Serial No: %u\n"
+ " Part No: %s\n"
+ " Prod Date: %d/%d/%d\n"
+ " SW Version: %s\n", dn, sn, pn, dp, mp, yp, sv);
+ }
+ }
+ /* Do Spectrolino stuff */
+ if ((rv = so_do_ResetStatusDownload(p, ss_smt_InitWithoutRemote)) != inst_ok)
+ return rv;
+ if ((rv = so_do_ExecWhiteRefToOrigDat(p)) != inst_ok)
+ return rv;
+
+ if (p->log->verb) {
+ char dn[19]; /* device name */
+ ss_dnot dno; /* device number */
+ char pn[9]; /* part number */
+ unsigned int sn; /* serial number */
+ char sv[13]; /* software release */
+ int yp; /* Year of production (e.g. 1996) */
+ int mp; /* Month of production (1-12) */
+ int dp; /* Day of production (1-31) */
+ char devn[19];
+ int sn2, sr, hp, np; /* Date & Time of production */
+ int fswl, nosw, dsw; /* Wavelengths sampled */
+ ss_ttt tt; /* Target Type */
+
+ if ((rv = so_do_DeviceDataRequest(p, dn, &dno, pn, &sn, sv)) != inst_ok)
+ return rv;
+
+ if ((rv = so_do_TargetIdRequest(p, devn, &sn2, &sr, &yp, &mp, &dp, &hp, &np,
+ &tt, &fswl, &nosw, &dsw)) != inst_ok)
+ return rv;
+
+ a1logv(p->log, 1,
+ "Device: %s\n"
+ "Serial No: %u\n"
+ "Part No: %s\n"
+ "Prod Date: %d/%d/%d\n"
+ "SW Version: %s\n", dn, sn, pn, dp, mp, yp, sv);
+ }
+
+ /* Set the default colorimetric parameters */
+ if ((rv = so_do_ParameterDownload(p, p->dstd, p->wbase, p->illum, p->obsv)) != inst_ok)
+ return rv;
+
+ /* Set the capabilities masks */
+ ss_determine_capabilities(p);
+
+ /* Deactivate measurement switch */
+ if ((rv = so_do_TargetOnOffStDownload(p,ss_toost_Deactivated)) != inst_ok)
+ return rv;
+ p->trig = inst_opt_trig_user;
+
+ p->inited = 1;
+ a1logd(p->log, 2, "ss_init_inst: instrument inited OK\n");
+
+ return inst_ok;
+}
+
+/* For an xy instrument, release the paper */
+/* Return the inst error code */
+static inst_code
+ss_xy_sheet_release(
+struct _inst *pp) {
+ ss *p = (ss *)pp;
+ inst_code rv = inst_ok;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->cap2 & inst2_xy_holdrel)
+ rv = ss_do_ReleasePaper(p);
+ return rv;
+}
+
+/* For an xy instrument, hold the paper */
+/* Return the inst error code */
+static inst_code
+ss_xy_sheet_hold(
+struct _inst *pp) {
+ ss *p = (ss *)pp;
+ inst_code rv = inst_ok;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->cap2 & inst2_xy_holdrel)
+ rv = ss_do_HoldPaper(p);
+ return rv;
+}
+
+/* For an xy instrument, allow the user to locate a point */
+/* Return the inst error code */
+static inst_code
+ss_xy_locate_start(
+struct _inst *pp) {
+ ss *p = (ss *)pp;
+ inst_code rv = inst_ok;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->cap2 & inst2_xy_locate) {
+ rv = ss_do_SetDeviceOffline(p);
+ p->offline = 1;
+ }
+ return rv;
+}
+
+/* For an xy instrument, position the reading point */
+/* Return the inst error code */
+static inst_code ss_xy_position(
+struct _inst *pp,
+int measure, /* nz if position measure point, z if locate point */
+double x, double y
+) {
+ ss *p = (ss *)pp;
+ inst_code rv = inst_ok;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->cap2 & inst2_xy_position)
+ if ((rv = ss_do_MoveAbsolut(p, measure ? ss_rt_SensorRef : ss_rt_SightRef, x, y)) != inst_ok)
+ return rv;
+
+ return rv;
+}
+
+/* For an xy instrument, read back the location */
+/* Return the inst error code */
+static inst_code
+ss_xy_get_location(
+struct _inst *pp,
+double *x, double *y) {
+ ss *p = (ss *)pp;
+ inst_code rv = inst_ok;
+ ss_rt rr;
+ ss_zkt zk;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->cap2 & inst2_xy_position)
+ if ((rv = ss_do_OutputActualPosition(p, ss_rt_SightRef, &rr, x, y, &zk)) != inst_ok)
+ return rv;
+
+ return rv;
+}
+
+/* For an xy instrument, ends allowing the user to locate a point */
+/* Return the inst error code */
+static inst_code
+ss_xy_locate_end(
+struct _inst *pp) {
+ ss *p = (ss *)pp;
+ inst_code rv = inst_ok;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->cap2 & inst2_xy_position) {
+ rv = ss_do_SetDeviceOnline(p);
+ p->offline = 0;
+ }
+ return rv;
+}
+
+/* For an xy instrument, try and clear the table after an abort */
+/* Return the inst error code */
+static inst_code
+ss_xy_clear(
+struct _inst *pp) {
+ ss *p = (ss *)pp;
+ inst_code rv = inst_ok;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->cap2 & inst2_xy_position) {
+ ss_do_SetDeviceOnline(p); /* Put the device online */
+ ss_do_MoveUp(p); /* Raise the sensor */
+ ss_do_ReleasePaper(p); /* Release the paper */
+ ss_do_MoveHome(p); /* Move to the home position */
+ }
+
+ return rv;
+}
+
+static inst_code ss_calibrate_imp(ss *p, inst_cal_type *calt, inst_cal_cond *calc, char id[CALIDLEN]);
+
+/* Read a sheet full of patches using xy mode */
+/* Return the inst error code */
+static inst_code
+ss_read_xy(
+inst *pp,
+int pis, /* Passes in strip (letters in sheet) */
+int sip, /* Steps in pass (numbers in sheet) */
+int npatch, /* Total patches in strip (skip in last pass) */
+char *pname, /* Starting pass name (' A' to 'ZZ') */
+char *sname, /* Starting step name (' 1' to '99') */
+double ox, double oy, /* Origin of first patch */
+double ax, double ay, /* pass increment */
+double aax, double aay, /* pass offset for odd patches */
+double px, double py, /* step/patch increment */
+ipatch *vals) { /* Pointer to array of values */
+ ss *p = (ss *)pp;
+ inst_code rv = inst_ok;
+ int pass, step, patch;
+ int tries, tc; /* Total read tries */
+ int fstep = 0; /* NZ if step is fast & quiet direction */
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->itype != instSpectroScan
+ && p->itype != instSpectroScanT)
+ return inst_unsupported;
+
+ /* Move quickest in X direction to minimise noise, */
+ /* and maximise speed. This means we either increment the step or */
+ /* the pass fastest, depending on fstep. */
+ if (fabs(px) > fabs(ax)) {
+ fstep = 1;
+ p->pisrow = 2 * sip;
+ } else {
+ p->pisrow = 2 * pis;
+ }
+
+ tries = sip * pis; /* Total grid patch count. Not all may have real patches. */
+
+ /* Read all the patches */
+ for (step = pass = tc = 0; tc < tries; tc++) {
+ int astep, apass; /* Actual step and pass to use */
+ double ix, iy;
+
+ /* Move in serpentine order */
+ if (fstep) {
+ astep = (pass & 1) ? sip - 1 - step : step;
+ apass = pass;
+ } else {
+ astep = step;
+ apass = (step & 1) ? pis - 1 - pass : pass;
+ }
+
+ patch = apass * sip + astep;
+
+ if (patch < npatch) { /* Over a valid patch */
+ int try, notries = 5;
+
+ ix = ox + apass * ax + astep * px;
+ iy = oy + apass * ay + astep * py;
+
+ if ((step & 1) == 1) { /* Offset for odd hex patches */
+ ix += aax;
+ iy += aay;
+ }
+
+ /* Retry a reading if it fails. */
+ /* (Note that we actually check if retries have failed within the loop) */
+ for (try = 0; try < notries; try++) {
+
+ a1logd(p->log, 3, "ss_read_xy: fstep %d, astep/pass %d\n",
+ fstep, fstep ? astep : apass);
+ /* Do calibration if it is absolutely needed, or when it would */
+ /* avoid a long move. */
+ check_calcount(p, fstep ? (pass & 1) == 0 && step == 0 : (step & 1) == 0 && pass == 0);
+ if ( (p->need_wd_cal || p->need_t_cal) && p->noinitcalib == 0) {
+ inst_cal_type calt = inst_calt_needed;
+ inst_cal_cond calc = inst_calc_none;
+ char id[CALIDLEN];
+
+ /* We expect this to be automatic, but handle as if it mightn't be */
+ if ((rv = ss_calibrate_imp(p, &calt, &calc, id)) != inst_ok) {
+ if (rv == inst_cal_setup)
+ return inst_needs_cal; /* Not automatic, needs a manual setup */
+ if (try < notries) {
+ p->forcecalib = 1; /* Force a calibration */
+ inc_calcount(p); /* Set correct type of calib */
+ a1logv(p->log, 1, "Calibration failed, retrying...\n");
+ continue; /* Retry */
+ }
+ return rv; /* Error */
+ }
+ }
+
+ {
+ ss_rvt refvalid;
+ double col[3], spec[36];
+ int i;
+
+ vals[patch].loc[0] = '\000';
+ vals[patch].mtype = inst_mrt_none;
+ vals[patch].XYZ_v = 0;
+ vals[patch].sp.spec_n = 0;
+ vals[patch].duration = 0.0;
+
+ /* move and measure gives us spectrum data anyway */
+ if ((rv = ss_do_MoveAndMeasure(p, ix, iy, spec, &refvalid)) != inst_ok) {
+ if (try < notries) {
+ p->forcecalib = 1; /* Force a calibration */
+ inc_calcount(p); /* Set correct type of calib */
+ a1logv(p->log, 1, "Measurement failed, retrying...\n");
+ continue; /* Retry */
+ }
+ return rv; /* Error */
+ }
+
+ vals[patch].sp.spec_n = 36;
+ vals[patch].sp.spec_wl_short = 380;
+ vals[patch].sp.spec_wl_long = 730;
+ vals[patch].sp.norm = 100.0;
+
+ for (i = 0; i < vals[patch].sp.spec_n; i++)
+ vals[patch].sp.spec[i] = 100.0 * (double)spec[i];
+
+ /* Get the XYZ */
+ {
+ ss_cst rct;
+ ss_rvt rvf;
+ ss_aft af;
+ ss_wbt wb;
+ ss_ilt it;
+ ss_ot ot;
+
+ if ((rv = so_do_CParameterRequest(p, ss_cst_XYZ, &rct, col, &rvf,
+ &af, &wb, &it, &ot)) != inst_ok
+ || rvf != ss_rvt_True) {
+ if (try < notries) {
+ p->forcecalib = 1; /* Force a calibration */
+ inc_calcount(p); /* Set correct type of calib */
+ a1logv(p->log, 1, "XYZ reading failed, retrying...\n");
+ continue; /* Retry */
+ }
+ return rv; /* Error */
+ }
+ }
+ vals[patch].XYZ_v = 1;
+ vals[patch].XYZ[0] = col[0];
+ vals[patch].XYZ[1] = col[1];
+ vals[patch].XYZ[2] = col[2];
+
+ /* Track the need for a calibration */
+ inc_calcount(p);
+
+ break; /* Don't need to retry */
+ }
+ } /* Retry */
+ }
+
+ /* Move on to the next patch */
+ if (fstep) {
+ if (++step >= sip) {
+ step = 0;
+ pass++;
+ }
+ } else {
+ if (++pass >= pis) {
+ pass = 0;
+ step++;
+ }
+ }
+ }
+
+ return rv;
+}
+
+/* Read a set of strips */
+/* Return the inst error code */
+static inst_code
+ss_read_strip(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+int npatch, /* Number of patches in the pass */
+char *pname, /* Pass name (3 chars) */
+int sguide, /* Guide number */
+double pwid, /* Patch length in mm (For DTP41) */
+double gwid, /* Gap length in mm (For DTP41) */
+double twid, /* Trailer length in mm (For DTP41T) */
+ipatch *vals) { /* Pointer to array of instrument patch values */
+// ss *p = (ss *)pp;
+ inst_code rv = inst_unsupported;
+ return rv;
+}
+
+
+/* Observer weightings for Spectrolino spectrum, 380 .. 730 nm in 10nm steps */
+/* 1931 2 degree/10 degree, X, Y, Z */
+/* Derived from the 1mm CIE data by integrating over +/- 5nm */
+double obsv[2][3][36] = {
+ {
+ {
+ 0.001393497640, 0.004448031900, 0.014518206300, 0.045720800000, 0.138923633000,
+ 0.279645970000, 0.344841960000, 0.335387990000, 0.288918940000, 0.196038970000,
+ 0.097089264500, 0.033433134500, 0.006117900200, 0.011512466000, 0.065321232000,
+ 0.166161125000, 0.291199155000, 0.434290495000, 0.594727005000, 0.761531500000,
+ 0.914317000000, 1.023460340000, 1.058604000000, 0.999075000000, 0.851037990000,
+ 0.644076660000, 0.449047000000, 0.285682340000, 0.166610680000, 0.089139475000,
+ 0.047203532000, 0.023272100000, 0.011556993000, 0.005897781550, 0.002960988050,
+ 0.001468472565
+ },
+ {
+ 0.000040014416, 0.000126320071, 0.000402526680, 0.001272963400, 0.004268400000,
+ 0.011759799700, 0.023092867000, 0.038306468000, 0.060303866000, 0.091762739000,
+ 0.139594730000, 0.210065540000, 0.326613130000, 0.504776000000, 0.706552500000,
+ 0.859214005000, 0.951809665000, 0.993340440000, 0.993019710000, 0.950281660000,
+ 0.868557660000, 0.756550000000, 0.630964340000, 0.503366340000, 0.380962000000,
+ 0.266444660000, 0.175871340000, 0.108002605000, 0.061709066000, 0.032657466000,
+ 0.017165009000, 0.008419183400, 0.004173919800, 0.002129796800, 0.001069267000,
+ 0.000530292340
+ },
+ {
+ 0.006568973000, 0.021026087500, 0.068865635000, 0.218090190000, 0.668415545000,
+ 1.366703205000, 1.731816230000, 1.769890130000, 1.658588340000, 1.288104470000,
+ 0.818359800000, 0.471791600000, 0.275824200000, 0.159485840000, 0.080436864500,
+ 0.042599734500, 0.020750932000, 0.009028633450, 0.004015999900, 0.002160166550,
+ 0.001629500100, 0.001143333400, 0.000804400000, 0.000372600000, 0.000180700000,
+ 0.000054966665, 0.000019933332, 0.000002266667, 0.000000000000, 0.000000000000,
+ 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
+ 0.000000000000
+ }
+ },
+ {
+ {
+ 0.000221161200, 0.002892312000, 0.021223545000, 0.087243800000, 0.203891450000,
+ 0.313689500000, 0.379737550000, 0.368800750000, 0.301126300000, 0.194835400000,
+ 0.082524250000, 0.018557800000, 0.006085000000, 0.039474500000, 0.119180250000,
+ 0.237142500000, 0.377122750000, 0.531279950000, 0.705108350000, 0.876453800000,
+ 1.013894200000, 1.113552000000, 1.119829000000, 1.026837000000, 0.855199200000,
+ 0.646495700000, 0.434312700000, 0.270230400000, 0.154505300000, 0.082548760000,
+ 0.041674230000, 0.020379080000, 0.009795728000, 0.004661858000, 0.002225776000,
+ 0.001069074500
+ },
+ {
+ 0.000023956650, 0.000309054000, 0.002220395000, 0.009005150000, 0.021600050000,
+ 0.038992450000, 0.062072600000, 0.089764700000, 0.128515350000, 0.185550850000,
+ 0.255690850000, 0.342051100000, 0.461805650000, 0.607378100000, 0.759122200000,
+ 0.874795100000, 0.958787300000, 0.991541600000, 0.995046500000, 0.953158750000,
+ 0.869616900000, 0.775837500000, 0.657942650000, 0.527902700000, 0.399013000000,
+ 0.283553200000, 0.181354350000, 0.108672400000, 0.061082550000, 0.032323865000,
+ 0.016231270000, 0.007919470000, 0.003803046000, 0.001810832500, 0.000865886500,
+ 0.000416835450
+ },
+ {
+ 0.000975787600, 0.012867500000, 0.095777190000, 0.402301600000, 0.971629200000,
+ 1.549938000000, 1.948388000000, 1.984670000000, 1.739857000000, 1.308151000000,
+ 0.781796500000, 0.422593900000, 0.222878650000, 0.115174050000, 0.061302800000,
+ 0.030879300000, 0.013841200000, 0.004144350000, 0.000189450000, 0.000000000000,
+ 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
+ 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
+ 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
+ 0.000000000000
+ }
+ }
+};
+
+/* Read a single sample */
+/* Return the dtp error code */
+static inst_code
+ss_read_sample(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+ipatch *val, /* Pointer to instrument patch value */
+instClamping clamp) { /* NZ if clamp XYZ/Lab to be +ve */
+ ss *p = (ss *)pp;
+ inst_code rv = inst_ok;
+ int switch_trig = 0;
+ int user_trig = 0;
+ double col[3], spec[36];
+ int i;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (!val)
+ return inst_internal_error;
+
+ a1logd(p->log, 4, "ss_read_sample()\n");
+
+ val->loc[0] = '\000';
+ val->XYZ_v = 0;
+ val->sp.spec_n = 0;
+ val->duration = 0.0;
+
+ /* Do calibration if it is needed */
+ if ((p->need_wd_cal || p->need_t_cal) && p->noinitcalib == 0) {
+ inst_cal_type calt = inst_calt_needed;
+ inst_cal_cond calc = inst_calc_none;
+ char id[CALIDLEN];
+
+ /* This could be automatic or need manual intervention */
+ if ((rv = ss_calibrate_imp(p, &calt, &calc, id)) != inst_ok) {
+ if (rv == inst_cal_setup) {
+ return inst_needs_cal; /* Not automatic, needs a manual setup */
+ }
+ return rv; /* Error */
+ }
+ }
+
+ if (p->trig == inst_opt_trig_user_switch) {
+
+ a1logd(p->log, 4, "inst_opt_trig_user_switch\n");
+
+ /* We're assuming that switch trigger won't be selected */
+ /* for spot measurement on the spectroscan, so we don't lower the head. */
+
+ /* Activate measurement switch */
+ if ((rv = so_do_TargetOnOffStDownload(p,ss_toost_Activated)) != inst_ok) {
+ return rv;
+ }
+
+ /* Wait for a measurement or for the user to hit a key */
+ for (;;) {
+ ss_nmt nm;
+
+ /* Query whether a new measurement was performed since the last */
+ if ((rv = so_do_NewMeasureRequest(p, &nm)) != inst_ok) {
+ return rv; /* Error */
+ }
+ if (nm == ss_nmt_NewMeas) {
+ switch_trig = 1;
+ break;
+ }
+ /* If SpectroScanT transmission, poll the enter key */
+ if (p->itype == instSpectroScanT
+ && (p->mode & inst_mode_illum_mask) == inst_mode_transmission) {
+ ss_sks sk;
+ ss_ptt pt;
+ if ((rv = ss_do_OutputActualKey(p, &sk, &pt)) == inst_ok
+ && sk == ss_sks_EnterKey) {
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+
+ if (p->uicallback != NULL) { /* Check for user trigger */
+ if ((rv = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (rv == inst_user_abort)
+ return rv; /* Abort */
+ if (rv == inst_user_trig) {
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ }
+ }
+
+ /* Deactivate measurement switch */
+ if ((rv = so_do_TargetOnOffStDownload(p,ss_toost_Deactivated)) != inst_ok)
+ return rv;
+
+ /* Notify of trigger */
+ if (p->uicallback)
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ } else if (p->trig == inst_opt_trig_user) {
+
+ a1logd(p->log, 4, "inst_opt_trig_user\n");
+
+ if (p->uicallback == NULL) {
+ a1logd(p->log, 1, "ss: inst_opt_trig_user but no uicallback function set!\n");
+ return inst_unsupported;
+ }
+
+ for (;;) {
+ if ((rv = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (rv == inst_user_abort)
+ return rv; /* Abort */
+ if (rv == inst_user_trig) {
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ msec_sleep(200);
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ /* Progromatic Trigger */
+ } else {
+ a1logd(p->log, 4, "program trigger\n");
+
+ /* Check for abort */
+ if (p->uicallback != NULL
+ && (rv = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_abort)
+ return rv; /* Abort */
+ }
+
+ /* Trigger a read if the switch has not been used */
+ if (switch_trig == 0) {
+ ss_nmt nm;
+
+ a1logd(p->log, 4, "starting a read because switch wasn't used\n");
+
+ /* For the spectroscan, make sure the instrument is on line, */
+ /* since it may be off line to allow the user to position it. */
+ if (p->itype != instSpectrolino && p->offline) {
+ if ((rv = p->xy_locate_end((inst *)p)) != inst_ok)
+ return rv;
+ }
+
+ /* For reflection spot mode on a SpectroScan, lower the head. */
+ /* (A SpectroScanT in transmission will position automatically) */
+ if (p->itype != instSpectrolino
+ && (p->mode & inst_mode_illum_mask) != inst_mode_transmission) {
+ if ((rv = ss_do_MoveDown(p)) != inst_ok)
+ return rv;
+ }
+
+ /* trigger it in software */
+ if ((rv = so_do_ExecMeasurement(p)) != inst_ok)
+ return rv;
+ /* Query measurement to reset count */
+ if ((rv = so_do_NewMeasureRequest(p, &nm)) != inst_ok)
+ return rv;
+
+ /* For reflection spot mode on a SpectroScan, raise the head. */
+ /* (A SpectroScanT in transmission will position automatically) */
+ if (p->itype != instSpectrolino
+ && (p->mode & inst_mode_illum_mask) != inst_mode_transmission) {
+ if ((rv = ss_do_MoveUp(p)) != inst_ok)
+ return rv;
+ }
+ }
+
+ /* Track the need for a calibration */
+ inc_calcount(p);
+
+ /* Get the XYZ: */
+
+ /* Emulated spot transmission mode: */
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_transmission
+ && p->itype == instSpectrolino) {
+ ss_st rst; /* Return Spectrum Type (Reflectance/Density) */
+ ss_rvt rvf; /* Return Reference Valid Flag */
+ ss_aft af; /* Return filter being used (None/Pol/D65/UV/custom */
+ ss_wbt wb; /* Return white base (Paper/Absolute) */
+ double norm; /* Y normalisation factor */
+ int j, tix = 0; /* Default 2 degree */
+
+ /* Get the spectrum */
+ if ((rv = so_do_SpecParameterRequest(p, ss_st_LinearSpectrum,
+ &rst, spec, &rvf, &af, &wb)) != inst_ok)
+ return rv;
+
+ /* Divide by transmission white reference to get transmission level. */
+ for (i = 0; i < 36; i++) {
+ if (p->tref[i] >= 0.0001)
+ spec[i] = spec[i]/p->tref[i];
+ else
+ spec[i] = 0.0;
+ }
+
+ if (p->mode & inst_mode_spectral) {
+ val->sp.spec_n = 36;
+ val->sp.spec_wl_short = 380;
+ val->sp.spec_wl_long = 730;
+ val->sp.norm = 100.0;
+ for (i = 0; i < val->sp.spec_n; i++)
+ val->sp.spec[i] = 100.0 * (double)spec[i];
+ }
+
+ /* Convert to desired illuminant XYZ */
+ val->XYZ[0] = 0.0;
+ val->XYZ[1] = 0.0;
+ val->XYZ[2] = 0.0;
+ if (p->obsv == ss_ot_TenDeg)
+ tix = 1;
+
+ /* Compute normalisation factor */
+ for (norm = 0.0, i = 0; i < 36; i++) {
+ if (p->tref[i] >= 0.0001)
+ norm += obsv[tix][1][i] * p->cill[i];
+ }
+ norm = 100.0/norm;
+
+ /* Compute XYZ */
+ for (i = 0; i < 36; i++) {
+ if (p->tref[i] >= 0.0001) {
+ for (j = 0; j < 3; j++)
+ val->XYZ[j] += obsv[tix][j][i] * p->cill[i] * spec[i];
+ }
+ }
+ for (j = 0; j < 3; j++)
+ val->XYZ[j] *= norm;
+
+ /* This may not change anything since instrument may clamp */
+ if (clamp)
+ icmClamp3(val->XYZ, val->XYZ);
+
+ val->XYZ_v = 1;
+ val->mtype = inst_mrt_transmissive;
+
+
+ /* Using filter compensation */
+ /* This isn't applicable to emulated transmission mode, because */
+ /* the filter will be calibrated out in the illuminant measurement. */
+ } else if (p->compen != 0) {
+ ss_cst rct;
+ ss_st rst; /* Return Spectrum Type (Reflectance/Density) */
+ ss_rvt rvf; /* Return Reference Valid Flag */
+ ss_aft af; /* Return filter being used (None/Pol/D65/UV/custom */
+ ss_ilt it;
+ ss_ot ot;
+ ss_wbt wb; /* Return white base (Paper/Absolute) */
+#ifdef NEVER
+ double norm; /* Y normalisation factor */
+#endif
+ double XYZ[3];
+ int j, tix = 0; /* Default 2 degree */
+
+ /* Get the XYZ */
+ if ((rv = so_do_CParameterRequest(p, ss_cst_XYZ, &rct, col, &rvf,
+ &af, &wb, &it, &ot)) != inst_ok)
+ return rv;
+
+ /* Get the spectrum */
+ if ((rv = so_do_SpecParameterRequest(p, ss_st_LinearSpectrum,
+ &rst, spec, &rvf, &af, &wb)) != inst_ok)
+ return rv;
+
+ /* Multiply by the filter compensation values to do correction */
+ for (i = 0; i < 36; i++) {
+ spec[i] *= p->comp[i];
+ }
+
+ /* Return the results */
+ if (p->mode & inst_mode_spectral) {
+ val->sp.spec_n = 36;
+ val->sp.spec_wl_short = 380;
+ val->sp.spec_wl_long = 730;
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_emission) {
+ val->sp.norm = 1.0;
+ for (i = 0; i < val->sp.spec_n; i++)
+ val->sp.spec[i] = (double)spec[i];
+ } else {
+ val->sp.norm = 100.0;
+ for (i = 0; i < val->sp.spec_n; i++)
+ val->sp.spec[i] = 100.0 * (double)spec[i];
+ }
+ }
+
+ /* Convert to desired illuminant XYZ */
+ if (p->obsv == ss_ot_TenDeg)
+ tix = 1;
+
+ /* Compute XYZ */
+ for (j = 0; j < 3; j++)
+ XYZ[j] = 0.0;
+ for (i = 0; i < 36; i++)
+ for (j = 0; j < 3; j++)
+ XYZ[j] += obsv[tix][j][i] * spec[i];
+
+ /* This may not change anything since instrument may clamp */
+ if (clamp)
+ icmClamp3(XYZ, XYZ);
+
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_emission) {
+ /* Emission XYZ seems to be Luminous Watts, so */
+ /* convert to cd/m^2 */
+ val->XYZ_v = 1;
+ val->XYZ[0] = XYZ[0] * 683.002;
+ val->XYZ[1] = XYZ[1] * 683.002;
+ val->XYZ[2] = XYZ[2] * 683.002;
+ val->mtype = inst_mrt_emission;
+ } else {
+ val->XYZ_v = 1;
+ val->XYZ[0] = XYZ[0];
+ val->XYZ[1] = XYZ[1];
+ val->XYZ[2] = XYZ[2];
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_transmission)
+ val->mtype = inst_mrt_transmissive;
+ else
+ val->mtype = inst_mrt_reflective;
+ }
+
+ /* Normal instrument values */
+ } else {
+ ss_cst rct;
+ ss_rvt rvf;
+ ss_aft af;
+ ss_wbt wb;
+ ss_ilt it;
+ ss_ot ot;
+
+ if ((rv = so_do_CParameterRequest(p, ss_cst_XYZ, &rct, col, &rvf,
+ &af, &wb, &it, &ot)) != inst_ok)
+ return rv;
+
+ /* This may not change anything since instrument may clamp */
+ if (clamp)
+ icmClamp3(col, col);
+
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_emission) {
+ val->XYZ_v = 1;
+ val->XYZ[0] = col[0];
+ val->XYZ[1] = col[1];
+ val->XYZ[2] = col[2];
+ val->mtype = inst_mrt_emission;
+ } else {
+ val->XYZ_v = 1;
+ val->XYZ[0] = col[0];
+ val->XYZ[1] = col[1];
+ val->XYZ[2] = col[2];
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_transmission)
+ val->mtype = inst_mrt_transmissive;
+ else
+ val->mtype = inst_mrt_reflective;
+ }
+
+ /* spectrum data is returned only if requested */
+ if (p->mode & inst_mode_spectral) {
+ ss_st rst; /* Return Spectrum Type (Reflectance/Density) */
+ ss_rvt rvf; /* Return Reference Valid Flag */
+ ss_aft af; /* Return filter being used (None/Pol/D65/UV/custom */
+ ss_wbt wb; /* Return white base (Paper/Absolute) */
+
+ if ((rv = so_do_SpecParameterRequest(p, ss_st_LinearSpectrum,
+ &rst, spec, &rvf, &af, &wb)) != inst_ok)
+ return rv;
+
+ val->sp.spec_n = 36;
+ val->sp.spec_wl_short = 380;
+ val->sp.spec_wl_long = 730;
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_emission) {
+ /* Spectral data is in mW/nm/m^2 interpolated to 10nm spacing */
+ val->sp.norm = 1.0;
+ for (i = 0; i < val->sp.spec_n; i++)
+ val->sp.spec[i] = (double)spec[i];
+ } else {
+ val->sp.norm = 100.0;
+ for (i = 0; i < val->sp.spec_n; i++)
+ val->sp.spec[i] = 100.0 * (double)spec[i];
+ }
+ }
+ }
+ if (user_trig)
+ return inst_user_trig;
+ return rv;
+}
+
+/* Return needed and available inst_cal_type's */
+static inst_code ss_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
+ ss *p = (ss *)pp;
+ inst_cal_type n_cals = inst_calt_none;
+ inst_cal_type a_cals = inst_calt_none;
+
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_transmission) {
+ if (p->itype == instSpectrolino) { /* Emulated transmission */
+ if (p->need_wd_cal && p->noinitcalib == 0)
+ n_cals |= inst_calt_ref_white;
+ a_cals |= inst_calt_ref_white;
+ if (p->need_t_cal) /* We aren't saving trans white ref */
+ n_cals |= inst_calt_trans_vwhite;
+ a_cals |= inst_calt_trans_vwhite;
+ } else { /* SpectroscanT */
+ if (p->need_wd_cal && p->noinitcalib == 0)
+ n_cals |= inst_calt_ref_white;
+ a_cals |= inst_calt_ref_white;
+ if (p->need_t_cal && p->noinitcalib == 0)
+ n_cals |= inst_calt_trans_white;
+ a_cals |= inst_calt_trans_white;
+ }
+
+ } else {
+ if (p->need_wd_cal && p->noinitcalib == 0)
+ n_cals |= inst_calt_ref_white;
+ a_cals |= inst_calt_ref_white;
+ }
+
+ if (pn_cals != NULL)
+ *pn_cals = n_cals;
+
+ if (pa_cals != NULL)
+ *pa_cals = a_cals;
+
+ return inst_ok;
+}
+
+/* Perform an instrument calibration (implementation). */
+static inst_code ss_calibrate_imp(
+ss *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) */
+) {
+ inst_code rv = inst_ok;
+ ss_aft afilt; /* Actual filter */
+ double sp[36]; /* Spectral values of filter */
+ ss_owrt owr; /* Original white reference */
+ inst_cal_type needed, available;
+
+ id[0] = '\000';
+
+ a1logd(p->log, 3, "ss calibrate called with calt = 0x%x, condition 0x%x, need w %d, t %d\n", *calt, *calc, p->need_wd_cal, p->need_t_cal);
+
+ if ((rv = ss_get_n_a_cals((inst *)p, &needed, &available)) != inst_ok)
+ return rv;
+
+ /* 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,"ss_imp_calibrate: doing calt 0x%x\n",*calt);
+
+ if ((*calt & inst_calt_n_dfrble_mask) == 0) /* Nothing todo */
+ return inst_ok;
+ }
+
+ /* See if it's a calibration we understand */
+ if (*calt & ~available & inst_calt_all_mask) {
+ return inst_unsupported;
+ }
+
+ /* There are different procedures depending on the intended mode, */
+ /* whether this is a Spectrolino or SpectrScan, and whether just a white, */
+ /* or a transmission calibration are needed or both. */
+
+ /* All first time calibrations do an initial reflective white calibration. */
+ if (*calt & inst_calt_ref_white) {
+
+ a1logd(p->log, 3, "ss cal dealing with being on ref_white\n");
+
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_emission)
+ p->filt = ss_aft_NoFilter; /* Need no filter for emission */
+
+ /* Set mode to reflection as a default for calibration */
+ if (p->itype == instSpectroScanT) {
+ if ((rv = ss_do_SetTableMode(p, ss_tmt_Reflectance)) != inst_ok)
+ return rv;
+ } else {
+ if ((rv = so_do_MeasControlDownload(p, ss_ctt_RemissionMeas)) != inst_ok)
+ return rv;
+ }
+
+ /* Set the desired colorimetric parameters + absolute white base */
+ if ((rv = so_do_ParameterDownload(p, p->dstd, ss_wbt_Abs, p->illum, p->obsv)) != inst_ok)
+ return rv;
+
+ /* Get the name of the expected white reference */
+ if ((rv = so_do_WhiteReferenceRequest(p, p->filt, &afilt, sp, &owr, id)) != inst_ok)
+ return rv;
+
+ if (p->noinitcalib == 0) {
+
+ /* Make sure we're in a condition to do the calibration */
+ if (p->itype == instSpectrolino && *calc != inst_calc_man_ref_white) {
+ *calc = inst_calc_man_ref_white;
+ a1logd(p->log, 3, "ss cal need cond. inst_calc_man_ref_white and haven't got it\n");
+ return inst_cal_setup;
+ }
+
+ /* Do the white calibration */
+ for (;;) { /* Untill everything is OK */
+
+ a1logd(p->log, 3, "ss cal doing white reflective cal\n");
+ /* For SpectroScan, move to the white reference in slot 1 and lower */
+ if (p->itype != instSpectrolino) {
+ if ((rv = ss_do_MoveToWhiteRefPos(p, ss_wrpt_RefTile1)) != inst_ok)
+ return rv;
+ if ((rv = ss_do_MoveDown(p)) != inst_ok)
+ return rv;
+ }
+
+ /* Calibrate */
+ if ((rv = so_do_ExecRefMeasurement(p, ss_mmt_WhiteCalWithWarn))
+ != (inst_notify | ss_et_WhiteMeasOK))
+ return rv;
+ rv = inst_ok;
+
+ /* For SpectroScan, raise */
+ if (p->itype != instSpectrolino) {
+ if ((rv = ss_do_MoveUp(p)) != inst_ok)
+ return rv;
+ }
+
+ /* Verify the filter. */
+ {
+ ss_dst ds;
+ ss_wbt wb;
+ ss_ilt it;
+ ss_ot ot;
+ ss_aft af;
+
+ if ((rv = so_do_ParameterRequest(p, &ds, &wb, &it, &ot, &af)) != inst_ok)
+ return rv;
+ if (af == p->filt)
+ break;
+
+ a1logd(p->log, 3, "got filt %d, want %d\n",af,p->filt);
+
+ strcpy(id, filter_desc[p->filt]);
+ *calc = inst_calc_change_filter;
+ return inst_cal_setup;
+ }
+ }
+ a1logd(p->log, 3, "reflection calibration and filter verify is complete\n");
+
+ /* Emission or spot transmission mode, dark calibration. */
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_emission
+ || ((p->mode & inst_mode_illum_mask) == inst_mode_transmission
+ && p->itype == instSpectrolino)) {
+ a1logd(p->log, 3, "emmission/transmission dark calibration:\n");
+ /* Set emission mode */
+ if ((rv = so_do_MeasControlDownload(p, ss_ctt_EmissionMeas)) != inst_ok)
+ return rv;
+ if (p->noinitcalib == 0) {
+ /* Do dark calibration (Assume we're still on white reference) */
+ if ((rv = so_do_ExecRefMeasurement(p, ss_mmt_EmissionCal))
+ != (inst_notify | ss_et_EmissionCalOK))
+ return rv;
+ }
+ rv = inst_ok;
+ a1logd(p->log, 3, "emmission/transmisson dark calibration done\n");
+ }
+
+ p->calcount = 0;
+ p->need_wd_cal = 0;
+ }
+
+ /* Restore the instrument to the desired mode */
+ /* SpectroScanT - Transmission mode, set transmission mode. */
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_transmission
+ && p->itype == instSpectroScanT) {
+
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_transmission) {
+ if ((rv = ss_do_SetTableMode(p, ss_tmt_Transmission)) != inst_ok)
+ return rv;
+ }
+ }
+ *calt &= ~inst_calt_ref_white;
+ }
+
+ /* ??? If White Base Type is not Absolute, where is Paper type set, */
+ /* and how is it calibrated ????? */
+
+ /* For non-reflective measurement, do the recalibration or 2nd part of calibration. */
+
+ /* Transmission mode calibration: */
+ if ((*calt & (inst_calt_trans_vwhite | inst_calt_trans_white))
+ && (p->mode & inst_mode_illum_mask) == inst_mode_transmission) {
+
+ a1logd(p->log, 3, "ss cal need trans with calt = trans_white\n");
+ /* Emulated spot transmission using spectrolino */
+ if ((*calt & inst_calt_trans_vwhite) && p->itype == instSpectrolino) {
+ ss_st rst; /* Return Spectrum Type (Reflectance/Density) */
+ ss_rvt rvf; /* Return Reference Valid Flag */
+ ss_aft af; /* Return filter being used (None/Pol/D65/UV/custom */
+ ss_wbt wb; /* Return white base (Paper/Absolute) */
+ ss_ilt it; /* Return illuminant type */
+ int i;
+
+ a1logd(p->log, 3, "ss cal need trans, spectrolino\n");
+ /* Make sure we're in a condition to do the calibration */
+ if (*calc != inst_calc_man_trans_white) {
+ *calc = inst_calc_man_trans_white;
+ a1logd(p->log, 3, "ss cal need cond. inst_calc_man_trans_white and haven't got it\n");
+ return inst_cal_setup;
+ }
+
+ /* Measure white reference spectrum */
+ if ((rv = so_do_ExecMeasurement(p)) != inst_ok)
+ return rv;
+ if ((rv = so_do_SpecParameterRequest(p, ss_st_LinearSpectrum,
+ &rst, p->tref, &rvf, &af, &wb)) != inst_ok)
+ return rv;
+
+ so_do_NewMeasureRequest(p, NULL); /* flush pending measurement */
+
+ /* See how good a source it is */
+ for (i = 0; i < 36; i++) {
+ if (p->tref[i] < 0.0001)
+ break;
+ }
+
+ if (i < 36) {
+ *calc = inst_calc_message;
+ strcpy(id, "Warning: Transmission light source is low at some wavelengths!");
+ rv = inst_ok;
+ }
+
+ /* Get the instrument illuminant */
+ if ((rv = so_do_IllumTabRequest(p, p->illum, &it, p->cill)) != inst_ok)
+ return rv;
+
+ p->calcount = 0;
+ p->need_t_cal = 0;
+ a1logd(p->log, 3, "transmission lino cal done\n");
+
+ /* SpectroScanT */
+ } else if ((*calt & inst_calt_trans_white) && p->itype != instSpectrolino) {
+ /* Hmm. Should we really return a message and resume somehow, */
+ /* rather than communicating directly with the user... */
+
+ a1logd(p->log, 3, "transmission scan cal being done\n");
+#ifdef NEVER
+ /* Advise user to change aperture before switching to transmission mode. */
+ p->message_user((inst *)p, "\nEnsure that desired transmission aperture is fitted,\n"
+ "and press space to continue:\n");
+ p->poll_user((inst *)p, 1);
+#else
+ a1logv(p->log,1,"It is assumed that the desired transmission aperture is fitted\n");
+#endif
+ /* Presuming this is the right return code */
+ if ((rv = so_do_ExecRefMeasurement(p, ss_mmt_WhiteCalWithWarn))
+ != (inst_notify | ss_et_WhiteMeasOK))
+ return rv;
+ rv = inst_ok;
+ p->calcount = 0;
+ p->need_t_cal = 0;
+ a1logd(p->log, 3, "transmission scan cal done\n");
+ }
+ *calt &= ~(inst_calt_trans_vwhite | inst_calt_trans_white);
+ }
+
+ a1logd(p->log, 3, "calibration completed\n");
+ return rv;
+}
+
+/* Request an instrument calibration. */
+/* This is use if the user decides they want to do a calibration, */
+/* in anticipation of a calibration (needs_calibration()) to avoid */
+/* requiring one during measurement, or in response to measuring */
+/* returning inst_needs_cal. Initially use an inst_cal_cond of inst_calc_none, */
+/* and then be prepared to setup the right conditions, or ask the */
+/* user to do so, each time the error inst_cal_setup is returned. */
+inst_code ss_calibrate(
+inst *pp,
+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) */
+) {
+ ss *p = (ss *)pp;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ return ss_calibrate_imp(p, calt, calc, id);
+}
+
+/* Insert a compensation filter in the instrument readings */
+/* This is typically needed if an adapter is being used, that alters */
+/* the spectrum of the light reaching the instrument */
+/* To remove the filter, pass NULL for the filter filename */
+inst_code ss_comp_filter(
+struct _inst *pp,
+char *filtername
+) {
+ ss *p = (ss *)pp;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+#ifndef SALONEINSTLIB
+ if (filtername == NULL) {
+ p->compen = 0;
+ } else {
+ xspect sp;
+ int i;
+ if (read_xspect(&sp, filtername) != 0) {
+ return inst_wrong_setup;
+ }
+ if (sp.spec_n != 36 || sp.spec_wl_short != 380.0 || sp.spec_wl_long != 730.0) {
+ return inst_wrong_setup;
+ }
+ for (i = 0; i < 36; i++)
+ p->comp[i] = sp.spec[i];
+ p->compen = 1;
+ }
+ return inst_ok;
+#else /* SALONEINSTLIB */
+ return inst_unsupported;
+#endif /* SALONEINSTLIB */
+}
+
+/* Instrument specific error codes interpretation */
+static char *
+ss_interp_error(inst *pp, int ec) {
+// ss *p = (ss *)pp;
+ ec &= inst_imask;
+
+ switch (ec) {
+
+ /* Device errors */
+ case ss_et_NoError:
+ return "No device error";
+ case ss_et_MemoryFailure:
+ return "Memory failure";
+ case ss_et_PowerFailure:
+ return "Power failure";
+ case ss_et_LampFailure:
+ return "Lamp failure";
+ case ss_et_HardwareFailure:
+ return "Hardware failure";
+ case ss_et_FilterOutOfPos:
+ return "Filter wheel out of position";
+ case ss_et_SendTimeout:
+ return "Data transmission timout";
+ case ss_et_DriveError:
+ return "Data drive defect";
+ case ss_et_MeasDisabled:
+ return "Measuring disabled";
+ case ss_et_DensCalError:
+ return "Incorrect input during densitometric calibration";
+ case ss_et_EPROMFailure:
+ return "Defective EPROM";
+ case ss_et_RemOverFlow:
+ return "Too much light or wrong white calibration";
+ case ss_et_MemoryError:
+ return "Checksum error in memory";
+ case ss_et_FullMemory:
+ return "Memory is full";
+ case ss_et_WhiteMeasOK:
+ return "White measurement is OK";
+ case ss_et_NotReady:
+ return "Instrument is not ready - please wait";
+ case ss_et_WhiteMeasWarn:
+ return "White measurement warning";
+ case ss_et_ResetDone:
+ return "Reset is done";
+ case ss_et_EmissionCalOK:
+ return "Emission calibration is OK";
+ case ss_et_OnlyEmission:
+ return "Only for emission (not reflection)";
+ case ss_et_CheckSumWrong:
+ return "Wrong checksum";
+ case ss_et_NoValidMeas:
+ return "No valid measurement (e.g. no white measurement)";
+ case ss_et_BackupError:
+ return "Error in backing up values";
+ case ss_et_ProgramROMError:
+ return "Errors in programming ROM";
+
+ /* Incorporate remote error set codes thus: */
+ case ss_et_NoValidDStd:
+ return "No valid Density standard set";
+ case ss_et_NoValidWhite:
+ return "No valid White standard set";
+ case ss_et_NoValidIllum:
+ return "No valid Illumination set";
+ case ss_et_NoValidObserver:
+ return "No valid Observer set";
+ case ss_et_NoValidMaxLambda:
+ return "No valid maximum Lambda set";
+ case ss_et_NoValidSpect:
+ return "No valid spectrum";
+ case ss_et_NoValidColSysOrIndex:
+ return "No valid color system or index";
+ case ss_et_NoValidChar:
+ return "No valid character";
+ case ss_et_DorlOutOfRange:
+ return "Density is out of range";
+ case ss_et_ReflectanceOutOfRange:
+ return "Reflectance is out of range";
+ case ss_et_Color1OutOfRange:
+ return "Color 1 is out of range";
+ case ss_et_Color2OutOfRange:
+ return "Color 2 is out of range";
+ case ss_et_Color3OutOfRange:
+ return "Color 3 is out of range";
+ case ss_et_NotAnSROrBoolean:
+ return "Not an SR or Boolean";
+ case ss_et_NoValidValOrRef:
+ return "No valid value or reference";
+
+ /* Translated scan error codes thus: */
+ case ss_et_DeviceIsOffline:
+ return "Device has been set offline";
+ case ss_et_OutOfRange:
+ return "A parameter of the command is out of range";
+ case ss_et_ProgrammingError:
+ return "Error writing to Flash-EPROM";
+ case ss_et_NoUserAccess:
+ return "No access to internal function";
+ case ss_et_NoValidCommand:
+ return "Unknown command sent";
+ case ss_et_NoDeviceFound:
+ return "Spectrolino can't be found";
+ case ss_et_MeasurementError:
+ return "Measurement error";
+ case ss_et_NoTransmTable:
+ return "SpectroScanT command when no tansmission table";
+ case ss_et_NotInTransmMode:
+ return "SpectroScanT transmission command in reflection mode";
+ case ss_et_NotInReflectMode:
+ return "SpectroScanT reflection command in transmission mode";
+
+ /* Translated device communication errors */
+ case ss_et_StopButNoStart:
+ return "No start character received by instrument";
+ case ss_et_IllegalCharInRec:
+ return "Invalid character received by instrument";
+ case ss_et_IncorrectRecLen:
+ return "Record length received by instrument incorrect";
+ case ss_et_IllegalRecType:
+ return "Invalid message number receivec by instrument";
+ case ss_et_NoTagField:
+ return "No message number received by instrument";
+ case ss_et_ConvError:
+ return "Received data couldn't be converted by instrument";
+ case ss_et_InvalidForEmission:
+ return "Invalid message number for emission instrument";
+ case ss_et_NoAccess:
+ return "Failure in user identification by instrument";
+
+ /* Our own communication errors here too. */
+ case ss_et_SerialFail:
+ return "Serial communications failure";
+
+ case ss_et_SendBufferFull:
+ return "Message send buffer is full";
+ case ss_et_RecBufferEmpty:
+ return "Message receive buffer is full";
+ case ss_et_BadAnsFormat:
+ return "Message received from instrument is badly formatted";
+ case ss_et_BadHexEncoding:
+ return "Message received from instrument has bad Hex encoding";
+ case ss_et_RecBufferOverun:
+ return "Message received from instrument would overflow recieve buffer";
+ default:
+ return "Unknown error code";
+ }
+}
+
+/* Return the instrument mode capabilities */
+void ss_capabilities(inst *pp,
+inst_mode *pcap1,
+inst2_capability *pcap2,
+inst3_capability *pcap3) {
+ ss *p = (ss *)pp;
+
+ if (p->cap == inst_mode_none)
+ ss_determine_capabilities(p);
+
+ if (pcap1 != NULL)
+ *pcap1 = p->cap;
+ if (pcap2 != NULL)
+ *pcap2 = p->cap2;
+ if (pcap3 != NULL)
+ *pcap3 = p->cap3;
+}
+
+/*
+ * Check measurement mode
+ * We assume that the instrument has been initialised.
+ */
+static inst_code
+ss_check_mode(inst *pp, inst_mode m) {
+ ss *p = (ss *)pp;
+ inst_mode cap;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ pp->capabilities(pp, &cap, NULL, NULL);
+
+ a1logd(p->log, 4, "check_mode 0x%x with cap 0x%x\n",m, cap);
+
+ /* Simple test */
+ if (m & ~cap)
+ return inst_unsupported;
+
+ /* Check specific modes */
+ if (!IMODETST(m, inst_mode_ref_spot)
+ && !IMODETST(m, inst_mode_emis_spot)
+ && !IMODETST(m, inst_mode_emis_tele)
+ && !IMODETST2(cap, m, inst_mode_ref_xy)
+ && !IMODETST2(cap, m, inst_mode_trans_spot)) { /* Fake or SpectroScanT */
+ return inst_unsupported;
+ }
+
+ return inst_ok;
+}
+
+/*
+ * set measurement mode
+ * We assume that the instrument has been initialised.
+ * The measurement mode is activated.
+ */
+static inst_code
+ss_set_mode(inst *pp, inst_mode m) {
+ ss *p = (ss *)pp;
+ inst_code ev;
+
+ if ((ev = ss_check_mode(pp, m)) != inst_ok)
+ return ev;
+
+ p->nextmode = m;
+
+ /* Now activate the next mode */
+ if (((p->nextmode & inst_mode_illum_mask) == inst_mode_reflection
+ && (p->mode & inst_mode_illum_mask) != inst_mode_reflection)
+ || ((p->nextmode & inst_mode_illum_mask) == inst_mode_emission
+ && (p->mode & inst_mode_illum_mask) != inst_mode_emission)
+ || ((p->nextmode & inst_mode_illum_mask) == inst_mode_transmission
+ && (p->mode & inst_mode_illum_mask) != inst_mode_transmission)) {
+
+ /* Mode has changed */
+ p->mode = p->nextmode;
+
+ /* Capabilities may have changed */
+ ss_determine_capabilities(p);
+
+ /* So we need calibration (do we, if this mode has been calibrated before ?) */
+ p->need_wd_cal = 1;
+ if ((p->mode & inst_mode_illum_mask) == inst_mode_transmission) {
+ p->need_t_cal = 1;
+ }
+ }
+ return inst_ok;
+}
+
+/*
+ * Get a status or get or set an option
+ * Since there is no interaction with the instrument,
+ * was assume that all of these can be done before initialisation.
+ */
+static inst_code
+ss_get_set_opt(inst *pp, inst_opt_type m, ...) {
+ ss *p = (ss *)pp;
+
+ if (m == inst_opt_noinitcalib) {
+ va_list args;
+ int losecs = 0;
+
+ va_start(args, m);
+ losecs = va_arg(args, int);
+ va_end(args);
+
+ /* Note that we're not implementing support for losecs at the moment. */
+ /* We would have to save a dummy calibration file to track the last time */
+ /* the instrument was opened. */
+
+ if (losecs == 0) /* If losecs != 0, then don't disable init calib */
+ p->noinitcalib = 1;
+
+ return inst_ok;
+
+ } else if (m == inst_opt_initcalib) {
+ p->noinitcalib = 0;
+ return inst_ok;
+
+ } else if (m == inst_opt_set_filter) {
+ va_list args;
+ inst_opt_filter fe = inst_opt_filter_unknown;
+
+ va_start(args, m);
+
+ fe = va_arg(args, inst_opt_filter);
+
+ va_end(args);
+
+ switch(fe) {
+
+ case inst_opt_filter_none:
+ p->filt = ss_aft_NoFilter;
+ return inst_ok;
+ case inst_opt_filter_pol:
+ p->filt = ss_aft_PolFilter;
+ return inst_ok;
+ case inst_opt_filter_D65:
+ p->filt = ss_aft_D65Filter;
+ return inst_ok;
+ case inst_opt_filter_UVCut:
+ p->filt = ss_aft_UVCutFilter;
+ return inst_ok;
+ case inst_opt_filter_Custom:
+ p->filt = ss_aft_CustomFilter;
+ return inst_ok;
+ default:
+ break;
+ }
+ return inst_unsupported;
+ }
+
+ /* Record the trigger mode */
+ if (m == inst_opt_trig_prog
+ || m == inst_opt_trig_user
+ || m == inst_opt_trig_user_switch) {
+
+ p->trig = m;
+ return inst_ok;
+ }
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* Return the filter */
+ if (m == inst_stat_get_filter) {
+ inst_opt_filter *filt;
+ inst_code rv;
+ va_list args;
+ ss_dst ds;
+ ss_wbt wb;
+ ss_ilt it;
+ ss_ot ot;
+ ss_aft af;
+
+ va_start(args, m);
+ filt = va_arg(args, inst_opt_filter *);
+ va_end(args);
+
+ /* Get the filter. */
+ if ((rv = so_do_ParameterRequest(p, &ds, &wb, &it, &ot, &af)) != inst_ok)
+ return rv;
+
+ switch (af) {
+ case ss_aft_NoFilter:
+ *filt = inst_opt_filter_none;
+ break;
+ case ss_aft_PolFilter:
+ *filt = inst_opt_filter_pol;
+ break;
+ case ss_aft_D65Filter:
+ *filt = inst_opt_filter_D65 ;
+ break;
+ case ss_aft_UVCutFilter:
+ *filt = inst_opt_filter_UVCut;
+ break;
+ case ss_aft_CustomFilter:
+ *filt = inst_opt_filter_Custom;
+ break;
+ default:
+ *filt = inst_opt_filter_unknown;
+ break;
+ }
+ return inst_ok;
+ }
+
+ /* Use default implementation of other inst_opt_type's */
+ {
+ va_list args;
+ inst_code rv;
+
+ va_start(args, m);
+ rv = inst_get_set_opt_def(pp, m, args);
+ va_end(args);
+
+ return rv;
+ }
+}
+
+/* Destroy ourselves */
+static void
+ss_del(inst *pp) {
+ ss *p = (ss *)pp;
+
+ if (p->inited
+ && p->itype == instSpectroScanT
+ && (p->mode & inst_mode_illum_mask) == inst_mode_transmission) {
+ ss_do_SetDeviceOnline(p);
+ ss_do_SetTableMode(p, ss_tmt_Reflectance);
+ ss_do_MoveUp(p); /* Raise the sensor */
+ ss_do_ReleasePaper(p); /* Release the paper */
+ ss_do_MoveHome(p); /* Move to the home position */
+ }
+
+ if (p->inited
+ && (p->mode & inst_mode_illum_mask) != inst_mode_transmission) {
+ ss_xy_clear(pp);
+ }
+
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ free (p);
+}
+
+/* Constructor */
+extern ss *new_ss(icoms *icom, instType itype) {
+ ss *p;
+ if ((p = (ss *)calloc(sizeof(ss),1)) == NULL) {
+ a1loge(icom->log, 1, "new_ss: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(icom->log);
+
+ /* Init public methods */
+ p->init_coms = ss_init_coms;
+ p->init_inst = ss_init_inst;
+ p->capabilities = ss_capabilities;
+ p->check_mode = ss_check_mode;
+ p->set_mode = ss_set_mode;
+ p->get_set_opt = ss_get_set_opt;
+ p->xy_sheet_release = ss_xy_sheet_release;
+ p->xy_sheet_hold = ss_xy_sheet_hold;
+ p->xy_locate_start = ss_xy_locate_start;
+ p->xy_get_location = ss_xy_get_location;
+ p->xy_locate_end = ss_xy_locate_end;
+ p->xy_position = ss_xy_position;
+ p->xy_clear = ss_xy_clear;
+ p->read_xy = ss_read_xy;
+ p->read_strip = ss_read_strip;
+ p->read_sample = ss_read_sample;
+ p->get_n_a_cals = ss_get_n_a_cals;
+ p->calibrate = ss_calibrate;
+ p->comp_filter = ss_comp_filter;
+ p->interp_error = ss_interp_error;
+ p->del = ss_del;
+
+ /* Init state */
+ p->icom = icom;
+ p->itype = icom->itype;
+ p->cap = inst_mode_none; /* Unknown until initialised */
+ p->mode = inst_mode_none; /* Not in a known mode yet */
+ p->nextmode = inst_mode_none; /* Not in a known mode yet */
+
+ /* Set default measurement configuration */
+ p->filt = ss_aft_NoFilter;
+ p->dstd = ss_dst_ANSIT;
+ p->illum = ss_ilt_D50;
+ p->obsv = ss_ot_TwoDeg;
+ p->wbase = ss_wbt_Abs;
+ p->phmode = ss_ctt_PhotometricAbsolute;
+ p->phref = 1.0;
+
+ /* Init serialisation stuff */
+ p->sbuf = &p->_sbuf[0];
+ p->sbufe = &p->_sbuf[SS_MAX_WR_SIZE-2]; /* Allow one byte for nul */
+ p->rbuf = &p->_rbuf[0];
+ p->rbufe = &p->_rbuf[0]; /* Initialy empty */
+ p->snerr = ss_et_NoError; /* COMs error */
+
+#ifdef EMSST
+ p->tmode = 0; /* Reflection mode */
+ p->sbr = ss_rt_SensorRef;
+ p->sbx = 100.0;
+ p->sby = 200.0;
+#endif
+
+ ss_determine_capabilities(p);
+
+ return p;
+}
diff --git a/spectro/ss.h b/spectro/ss.h
new file mode 100644
index 0000000..bff0846
--- /dev/null
+++ b/spectro/ss.h
@@ -0,0 +1,115 @@
+
+#ifndef SS_H
+
+/*
+ * Argyll Color Correction System
+ *
+ * Gretag Spectrolino and Spectroscan related
+ * defines and declarations.
+ *
+ * Author: Graeme W. Gill
+ * Date: 13/7/2005
+ *
+ * Copyright 2005 - 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.
+ *
+ * Derived from DTP41.h
+ *
+ * This is an alternative driver to spm/gretag.
+ */
+
+/*
+ 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.
+ */
+
+#undef EMSST /* Debug - emulate a SpectroScanT with a SpectroScan */
+
+#include "inst.h"
+#include "ss_imp.h"
+
+#define SS_MAX_WR_SIZE 1000 /* Assumed maximum normal message query size */
+#define SS_MAX_RD_SIZE 1000 /* Assumed maximum normal messagle answer size */
+
+/* Gretag Spectrolino/Spectroscan communication object */
+struct _ss {
+ /* **** base instrument class **** */
+ INST_OBJ_BASE
+
+ /* *** Spectroscan/lino private data **** */
+ inst_mode cap; /* Instrument mode capability */
+ inst2_capability cap2; /* Instrument capability 2 */
+ inst3_capability cap3; /* Instrument capability 3 */
+ inst_mode nextmode; /* Next requested mode */
+ inst_mode mode; /* Currently instrument mode */
+
+ /* Desired measurement configuration */
+ ss_aft filt; /* Filter type (None/UV/D65 etc.) */
+ ss_dst dstd; /* Density standard (ANSI A/ANSI T/DIN etc.) */
+ ss_ilt illum; /* Illuminant type (A/C/D50 etc.) */
+ ss_ot obsv; /* Observer type (2deg/10deg) */
+ ss_wbt wbase; /* White base type (Paper/Abs>) */
+ ss_ctt phmode; /* Photometric mode (Absolute/Relative) */
+ double phref; /* Photometric reference (cd/m^2) */
+
+ int calcount; /* Calibration needed counter */
+ int pisrow; /* patches in a reading direction serpentine rows */
+ int need_wd_cal; /* White/dark calibration needed flag */
+ int need_t_cal; /* Transmission calibration needed flag */
+ int noinitcalib; /* Don't mode change or do initilal calibrate */
+ int forcecalib; /* Force a calibration even if not near white tile */
+ inst_opt_type trig; /* Reading trigger mode */
+ int offline; /* nz if we're off line */
+
+ /* Emulated manual transmission mode support */
+ double tref[36]; /* White reference spectrum */
+ double cill[36]; /* Colorimetry illuminant */
+
+ /* telescopic adapter compensation */
+ int compen; /* Compensation filter enabled */
+ double comp[36]; /* Compensation filter */
+
+#ifdef EMSST
+ int tmode; /* Transmission mode */
+ ss_rt sbr; /* Standby reference */
+ double sbx, sby; /* Standby location */
+#endif
+
+ /* Serialisation support: */
+
+ /* Send buffer */
+ char _sbuf[SS_MAX_WR_SIZE]; /* Buffer allocation */
+ char *sbufe; /* Pointer to last valid location in _sbuf[] */
+ char *sbuf; /* Next character output */
+
+ /* Receive buffer */
+ char _rbuf[SS_MAX_RD_SIZE]; /* Buffer allocation */
+ char *rbufe; /* Pointer to last valid location in _rbuf[] */
+ char *rbuf; /* Next character output */
+
+ /* Accumulated device error */
+ ss_et snerr;
+
+ }; typedef struct _ss ss;
+
+/* Constructor */
+extern ss *new_ss(icoms *icom, instType itype);
+
+#define SS_H
+#endif /* SS_H */
diff --git a/spectro/ss_imp.c b/spectro/ss_imp.c
new file mode 100644
index 0000000..b4c5d8a
--- /dev/null
+++ b/spectro/ss_imp.c
@@ -0,0 +1,1943 @@
+
+#ifndef SS_IMP_H
+
+/*
+ * Argyll Color Correction System
+ *
+ * Gretag Spectrolino and Spectroscan related
+ * defines and declarations - implementation.
+ *
+ * Author: Graeme W. Gill
+ * Date: 14/7/2005
+ *
+ * Copyright 2005 - 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.
+ *
+ * This is an alternative driver to spm/gretag.
+ */
+
+/*
+ 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* SALONEINSTLIB */
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* SALONEINSTLIB */
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "ss.h"
+
+/* ------------------------------------------- */
+/* Serialisation for different types functions */
+
+/* QUERY: */
+
+/* These add characters to the current send buffer, */
+/* And set the status appropriately */
+
+/* 4 bits to hex character conversion table */
+static char b2h[16] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+};
+
+/* Check that there is enough space to write size more characters */
+#define CHWSPACE(size) \
+ if (p->snerr == ss_et_NoError \
+ && (p->sbufe - p->sbuf) < (size)) { \
+ p->snerr = ss_et_SendBufferFull; \
+ } \
+ if (p->snerr != ss_et_NoError) \
+ return;
+
+/* Reset send buffer, and init with start character */
+void ss_init_send(ss *p) {
+ p->snerr = ss_et_NoError;
+ p->sbuf = p->_sbuf;
+ CHWSPACE(1);
+ p->sbuf[0] = ';';
+ p->sbuf += 1;
+}
+
+/* Reset send buffer, and add an Spectrolino Request enum */
+void ss_add_soreq(ss *p, int rq) {
+ ss_init_send(p);
+ CHWSPACE(2);
+ p->sbuf[0] = b2h[(rq >> 4) & 0xf];
+ p->sbuf[1] = b2h[(rq >> 0) & 0xf];
+ p->sbuf += 2;
+}
+
+/* Reset send buffer, and add an SpectroScan Request enum */
+void ss_add_ssreq(ss *p, int rq) {
+ ss_init_send(p);
+ CHWSPACE(4);
+ p->sbuf[0] = b2h[((int)ss_ReqPFX >> 4) & 0xf]; /* Prefix */
+ p->sbuf[1] = b2h[((int)ss_ReqPFX >> 0) & 0xf]; /* Prefix */
+ p->sbuf[2] = b2h[(rq >> 4) & 0xf];
+ p->sbuf[3] = b2h[(rq >> 0) & 0xf];
+ p->sbuf += 4;
+}
+
+/* Add an int/enum/char into one byte type */
+void ss_add_1(ss *p, int c) {
+ CHWSPACE(2);
+ p->sbuf[0] = b2h[(c >> 4) & 0xf];
+ p->sbuf[1] = b2h[(c >> 0) & 0xf];
+ p->sbuf += 2;
+}
+
+/* Add an int/enum into two byte type */
+void ss_add_2(ss *p, int s) {
+ CHWSPACE(4);
+ p->sbuf[0] = b2h[(s >> 4) & 0xf]; /* LSB */
+ p->sbuf[1] = b2h[(s >> 0) & 0xf];
+ p->sbuf[2] = b2h[(s >> 12) & 0xf]; /* MSB */
+ p->sbuf[3] = b2h[(s >> 8) & 0xf];
+ p->sbuf += 4;
+}
+
+/* Add an int/enum into four byte type */
+void ss_add_4(ss *p, int i) {
+ CHWSPACE(8);
+ p->sbuf[0] = b2h[(i >> 4) & 0xf]; /* LSB */
+ p->sbuf[1] = b2h[(i >> 0) & 0xf];
+ p->sbuf[2] = b2h[(i >> 12) & 0xf];
+ p->sbuf[3] = b2h[(i >> 8) & 0xf];
+ p->sbuf[4] = b2h[(i >> 20) & 0xf];
+ p->sbuf[5] = b2h[(i >> 16) & 0xf];
+ p->sbuf[6] = b2h[(i >> 28) & 0xf]; /* MSB */
+ p->sbuf[7] = b2h[(i >> 24) & 0xf];
+ p->sbuf += 8;
+}
+
+/* Add a double into four byte type */
+void ss_add_double(ss *p, double d) {
+ unsigned int id;
+
+ id = doubletoIEEE754(d);
+
+ ss_add_4(p, id);
+}
+
+/* Add an ASCII string into the send buffer. */
+/* The string will be padded with nul's up to len. */
+void ss_add_string(ss *p, char *t, int len) {
+ int i;
+
+ CHWSPACE(2 * len);
+ for (i = 0; i < len; i++) {
+ p->sbuf[2 * i + 0] = b2h[(t[i] >> 4) & 0xf];
+ p->sbuf[2 * i + 1] = b2h[(t[i] >> 0) & 0xf];
+ if (t[i] == '\000')
+ break;
+ }
+ for (; i < len; i++) {
+ p->sbuf[2 * i + 0] = '0';
+ p->sbuf[2 * i + 1] = '0';
+ }
+ p->sbuf += 2 * len;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - */
+/* ANSWER: */
+
+/* Check that there is not already an error, */
+/* and that there is enough space to read size more characters. */
+/* Return nz if not. */
+static int chrspace(ss *p, int size) {
+
+ if (p->snerr != ss_et_NoError) /* Problem assembling request */
+ return 1;
+
+ if ((p->rbufe - p->rbuf) < (size)) {
+ p->snerr = ss_et_RecBufferEmpty;
+ return 1;
+ }
+ {
+ char *rr = p->rbuf, *re = p->rbuf + size;
+ while (rr < re && *rr != '\000')
+ rr++;
+ if (rr < re) {
+ p->snerr = ss_et_RecBufferEmpty;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* Check that the read buffer is fully consumed. */
+static void chended(ss *p) {
+ if (p->snerr == ss_et_NoError /* Don't overrite any existing error */
+ && p->rbufe != p->rbuf) {
+ p->snerr = ss_et_BadAnsFormat;
+ }
+}
+
+/* Convert an ASCII Hex character to an integer. */
+static int h2b(ss *p, char c) {
+ if (c >= '0' && c <= '9')
+ return (c-(int)'0');
+ if (c >= 'A' && c <= 'F')
+ return (10 + c-(int)'A');
+ if (c >= 'a' && c <= 'f')
+ return (10 + c-(int)'a');
+ if (p->snerr == ss_et_NoError) /* Don't overrite any existing error */
+ p->snerr = ss_et_BadHexEncoding;
+ return 0;
+}
+
+/* Return the first enum from the recieve buffer without removing it. */
+int ss_peek_ans(ss *p) {
+ int rv;
+
+ if (chrspace(p, 2))
+ return 0;
+ rv = (h2b(p, p->rbuf[0]) << 4)
+ | (h2b(p, p->rbuf[1]) << 0);
+
+ return rv;
+}
+
+/* Remove a Spectrolino answer enum from the revieve buffer, */
+/* and check it is correct. */
+void ss_sub_soans(ss *p, int cv) {
+ int rv;
+
+ if (chrspace(p, 2))
+ return;
+ rv = (h2b(p, p->rbuf[0]) << 4)
+ | (h2b(p, p->rbuf[1]) << 0);
+ p->rbuf += 2;
+ if (rv != cv) {
+ if (p->snerr == ss_et_NoError) /* Don't overrite any existing error */
+ p->snerr = ss_et_BadAnsFormat;
+ return;
+ }
+ return;
+}
+
+/* Remove a SpectroScan Prefix and answer enum from the revieve buffer, */
+/* and check it is correct. */
+void ss_sub_ssans(ss *p, int cv) {
+ int rv;
+
+ if (chrspace(p, 4))
+ return;
+ if (p->rbuf[0] != 'D'
+ || p->rbuf[1] != '1') {
+ if (p->snerr == ss_et_NoError) /* Don't overrite any existing error */
+ p->snerr = ss_et_BadAnsFormat;
+ return;
+ }
+ rv = (h2b(p, p->rbuf[2]) << 4)
+ | (h2b(p, p->rbuf[3]) << 0);
+ p->rbuf += 4;
+ if (rv != cv) {
+ if (p->snerr == ss_et_NoError) /* Don't overrite any existing error */
+ p->snerr = ss_et_BadAnsFormat;
+ return;
+ }
+ return;
+}
+
+/* Remove an int/enum/char into one byte type */
+int ss_sub_1(ss *p) {
+ int rv;
+
+ if (chrspace(p, 2))
+ return 0;
+ rv = (h2b(p, p->rbuf[0]) << 4)
+ | (h2b(p, p->rbuf[1]) << 0);
+ p->rbuf += 2;
+
+ return rv;
+}
+
+/* Remove an int/enum into two byte type */
+int ss_sub_2(ss *p) {
+ int rv;
+
+ if (chrspace(p, 4))
+ return 0;
+ rv = (h2b(p, p->rbuf[0]) << 4)
+ | (h2b(p, p->rbuf[1]) << 0)
+ | (h2b(p, p->rbuf[2]) << 12)
+ | (h2b(p, p->rbuf[3]) << 8);
+ p->rbuf += 4;
+
+ return rv;
+}
+
+/* Remove an int/enum into four byte type */
+int ss_sub_4(ss *p) {
+ int rv;
+
+ if (chrspace(p, 8))
+ return 0;
+ rv = (h2b(p, p->rbuf[0]) << 4)
+ | (h2b(p, p->rbuf[1]) << 0)
+ | (h2b(p, p->rbuf[2]) << 12)
+ | (h2b(p, p->rbuf[3]) << 8)
+ | (h2b(p, p->rbuf[4]) << 20)
+ | (h2b(p, p->rbuf[5]) << 16)
+ | (h2b(p, p->rbuf[6]) << 28)
+ | (h2b(p, p->rbuf[7]) << 24);
+ p->rbuf += 8;
+
+ return rv;
+}
+
+/* Remove a double into four byte type */
+double ss_sub_double(ss *p) {
+ unsigned int ip;
+ double op;
+
+ ip = (unsigned int)ss_sub_4(p);
+
+ op = IEEE754todouble(ip);
+
+ return op;
+}
+
+/* Remove an ASCII string from the receive buffer. */
+/* The string will be terminated with a nul, so a buffer */
+/* of len+1 should be provided to return the string in. */
+void ss_sub_string(ss *p, char *t, int len) {
+ int i;
+
+ if (chrspace(p, 2 * len))
+ return;
+ for (i = 0; i < len; i++) {
+ t[i] = (char)((h2b(p, p->rbuf[2 * i + 0]) << 4)
+ | (h2b(p, p->rbuf[2 * i + 1]) << 0));
+ }
+ t[i] = '\000';
+ p->rbuf += 2 * len;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - */
+/* ERROR CODES: */
+
+/* Convert an ss error into an inst_error */
+inst_code ss_inst_err(ss *p) {
+ ss_et ec = p->snerr;
+
+ switch(ec) {
+ case ss_et_NoError:
+ return inst_ok;
+
+ case ss_et_WhiteMeasOK:
+ case ss_et_ResetDone:
+ case ss_et_EmissionCalOK:
+ return inst_notify | ec;
+
+ case ss_et_WhiteMeasWarn:
+ return inst_warning | ec;
+
+ case ss_et_SendTimeout:
+ case ss_et_SerialFail:
+ return inst_coms_fail | ec;
+
+ case ss_et_StopButNoStart:
+ case ss_et_IllegalCharInRec:
+ case ss_et_IncorrectRecLen:
+ case ss_et_IllegalRecType:
+ case ss_et_NoTagField:
+ case ss_et_ConvError:
+ case ss_et_RecBufferEmpty:
+ case ss_et_BadAnsFormat:
+ case ss_et_BadHexEncoding:
+ case ss_et_RecBufferOverun:
+ case ss_et_OutOfRange:
+ case ss_et_NotAnSROrBoolean:
+ return inst_protocol_error | ec;
+
+ case ss_et_RemOverFlow:
+ case ss_et_MeasDisabled:
+ case ss_et_MeasurementError:
+ case ss_et_DorlOutOfRange:
+ case ss_et_ReflectanceOutOfRange:
+ case ss_et_Color1OutOfRange:
+ case ss_et_Color2OutOfRange:
+ case ss_et_Color3OutOfRange:
+ return inst_misread | ec;
+
+ case ss_et_NoValidCommand:
+ case ss_et_NoUserAccess:
+ return inst_unsupported | ec;
+
+ case ss_et_NotReady:
+ case ss_et_NoAccess:
+ return inst_other_error | ec;
+ break;
+
+ case ss_et_SendBufferFull:
+ return inst_internal_error | ec;
+
+ case ss_et_NoValidMeas:
+ case ss_et_OnlyEmission:
+ case ss_et_DensCalError:
+ case ss_et_NoTransmTable:
+ case ss_et_InvalidForEmission:
+ case ss_et_NotInTransmMode:
+ case ss_et_NotInReflectMode:
+ case ss_et_NoValidDStd:
+ case ss_et_NoValidWhite:
+ case ss_et_NoValidIllum:
+ case ss_et_NoValidObserver:
+ case ss_et_NoValidMaxLambda:
+ case ss_et_NoValidSpect:
+ case ss_et_NoValidColSysOrIndex:
+ case ss_et_NoValidChar:
+ case ss_et_NoValidValOrRef:
+ case ss_et_DeviceIsOffline:
+ case ss_et_NoDeviceFound:
+ return inst_wrong_setup | ec;
+
+ case ss_et_BackupError:
+ case ss_et_ProgramROMError:
+ case ss_et_CheckSumWrong:
+ case ss_et_MemoryError:
+ case ss_et_FullMemory:
+ case ss_et_EPROMFailure:
+ case ss_et_MemoryFailure:
+ case ss_et_PowerFailure:
+ case ss_et_LampFailure:
+ case ss_et_HardwareFailure:
+ case ss_et_DriveError:
+ case ss_et_FilterOutOfPos:
+ case ss_et_ProgrammingError:
+ return inst_hardware_fail | ec;
+
+ }
+ return inst_other_error | ec;
+}
+
+/* Incorporate a error into the snerr value */
+void ss_incorp_err(ss *p, ss_et se) {
+ if (p->snerr != ss_et_NoError) /* Don't overrite any existing error */
+ return;
+ if (se == ss_set_NoError)
+ return;
+
+ p->snerr = se;
+}
+
+/* Incororate Remote Error Set values into snerr value */
+/* Since ss_res is a bit mask, we just prioritize the errors. */
+void ss_incorp_remerrset(ss *p, ss_res es) {
+ int i, ii;
+ if (p->snerr != ss_et_NoError) /* Don't overrite any existing error */
+ return;
+ if (es == ss_res_NoError)
+ return;
+
+ for (i = ss_et_NoValidDStd, ii = 0x0001; ii < 0x10000; i++, ii <<= 1) {
+ if ((ii & es) != 0) {
+ break;
+ }
+ }
+ p->snerr = i;
+}
+
+/* Incorporate a scan error into the snerr value */
+void ss_incorp_scanerr(ss *p, ss_set se) {
+ if (p->snerr != ss_et_NoError) /* Don't overrite any existing error */
+ return;
+ if (se == ss_set_NoError)
+ return;
+
+ p->snerr = se + ss_et_DeviceIsOffline - 1;
+}
+
+/* Incorporate a device communication error into the snerr value */
+void ss_incorp_comerr(ss *p, ss_cet se) {
+ if (p->snerr != ss_et_NoError) /* Don't overrite any existing error */
+ return;
+ if (se == ss_cet_NoError)
+ return;
+
+ p->snerr = se + ss_et_StopButNoStart - 1;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - */
+/* EXECUTION: */
+
+/* Interpret an icoms error into a SS error */
+int icoms2ss_err(int se) {
+ if (se != ICOM_OK)
+ return ss_et_SerialFail;
+ return ss_et_NoError;
+}
+
+/* Terminate the send buffer, and then do a */
+/* send/receieve to the device. */
+/* Leave any error in p->snerr */
+void ss_command(ss *p, double tmo) {
+ int se;
+
+ CHWSPACE(3);
+ p->sbuf[0] = '\r';
+ p->sbuf[1] = '\n';
+ p->sbuf[2] = '\00'; /* write_read terminates on nul */
+
+ p->rbuf = p->_rbuf; /* Reset read pointer */
+ if ((se = p->icom->write_read(p->icom, p->_sbuf, p->_rbuf, SS_MAX_RD_SIZE, '\n', 1, tmo)) != 0) {
+ p->snerr = icoms2ss_err(se);
+ return;
+ }
+
+ /* Figure out receive size, and clean up termination. */
+ p->rbufe = p->_rbuf + strlen(p->_rbuf);
+ if ((p->rbufe - p->rbuf) >= 1 && p->rbufe[-1] == '\n') {
+ --p->rbufe;
+ *p->rbufe = '\000';
+ }
+ if ((p->rbufe - p->rbuf) >= 1 && p->rbufe[-1] == '\r') {
+ --p->rbufe;
+ *p->rbufe = '\000';
+ }
+
+ /* Check receive format and look for a COM error */
+ if ((p->rbufe - p->rbuf) < 1 || p->rbuf[0] != ':') {
+ p->snerr = ss_et_BadAnsFormat;
+ return;
+ }
+ p->rbuf++;
+
+ /* See if this is a Spectroscan COM error */
+ if ((p->rbufe - p->rbuf) >= 2
+ && p->rbuf[0] == 'D'
+ && p->rbuf[1] == '1') {
+
+ if ((p->rbufe - p->rbuf) >= 4
+ && p->rbuf[0] == 'A'
+ && p->rbuf[1] == '0') { /* COMM Error */
+ p->rbuf += 4;
+ ss_incorp_comerr(p, (ss_cet)ss_sub_1(p));
+ return;
+ }
+ }
+
+ /* See if it is a Spectrolino COMM error */
+ if ((p->rbufe - p->rbuf) >= 2
+ && p->rbuf[0] == '2'
+ && p->rbuf[1] == '6') { /* COMM Error */
+ p->rbuf += 2;
+ ss_incorp_comerr(p, (ss_cet)ss_sub_1(p));
+ return;
+ }
+ return;
+}
+
+/* =================================================================== */
+/* Complete Spectrolino instructions. */
+/* [This is less elegant than Neil Okamoto's */
+/* table approach, but is more flexible, */
+/* and it does the job.] */
+
+/* - - - - - - - - - - - - - - - - - - - - */
+/* Device Initialisation and configuration */
+
+/* Reset instrument */
+inst_code so_do_ResetStatusDownload(
+ss *p,
+ss_smt sm /* Init all or all except communications */
+) {
+ ss_add_soreq(p, ss_ResetStatusDownload);
+ ss_add_1(p, 0x01);
+ ss_add_1(p, 0x04);
+ ss_add_1(p, sm);
+ ss_command(p, IT_TMO);
+ ss_sub_soans(p, ss_DownloadError);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Load various parameters, such as: */
+/* comm flow control, baud rate, speaker, */
+/* reflective/tranmission/emmission mode, */
+/* custom filter on/off */
+inst_code so_do_MeasControlDownload(
+ss *p,
+ss_ctt ct /* Control */
+) {
+ ss_add_soreq(p, ss_MeasControlDownload);
+ ss_add_1(p, ct);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_DownloadError);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+
+ return ss_inst_err(p);
+}
+
+/* Query various current parameters, such as: */
+/* comm flow control, baud rate, speaker, */
+/* reflective/tranmission/emmission mode, */
+/* custom filter on/off. */
+inst_code so_do_MeasControlRequest(
+ss *p,
+ss_ctt ct, /* Control to query */
+ss_ctt *rct /* Return current state */
+) {
+ ss_add_soreq(p, ss_MeasControlRequest);
+ ss_add_1(p, ct);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_MeasControlAnswer);
+ ss_sub_1(p); /* Should be the same as ct */
+ *rct = ss_sub_1(p);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Queries specific device data */
+inst_code so_do_DeviceDataRequest(
+ss *p,
+char dn[19], /* Return the device name */
+ss_dnot *dno, /* Return device number */
+char pn[9], /* Return the part number */
+unsigned int *sn, /* Return serial number */
+char sv[13] /* Return software version */
+) {
+ char rsv[17]; /* Space for resered field */
+ ss_add_soreq(p, ss_DeviceDataRequest);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_DeviceDataAnswer);
+ ss_sub_string(p, dn, 18);
+ *dno = ss_sub_1(p);
+ ss_sub_string(p, pn, 8);
+ *sn = (unsigned int)ss_sub_4(p);
+ ss_sub_string(p, sv, 12);
+ ss_sub_string(p, rsv, 16);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query special device data */
+inst_code so_do_TargetIdRequest(
+ss *p,
+char dn[19], /* Return Device Name */
+int *sn, /* Return Serial Number (1-65535) */
+int *sr, /* Return Software Release */
+int *yp, /* Return Year of production (e.g. 1996) */
+int *mp, /* Return Month of production (1-12) */
+int *dp, /* Return Day of production (1-31) */
+int *hp, /* Return Hour of production (0-23) */
+int *np, /* Return Minuite of production (0-59) */
+ss_ttt *tt, /* Return Target Tech Type (SPM/Spectrolino etc.) */
+int *fswl, /* Return First spectral wavelength (nm) */
+int *nosw, /* Return Number of spectral wavelengths */
+int *dpsw /* Return Distance between spectral wavelengths (nm) */
+) {
+ ss_add_soreq(p, ss_TargetIdRequest);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_TargetIdAnswer);
+ ss_sub_string(p, dn, 18);
+ *sn = ss_sub_2(p);
+ *sr = ss_sub_2(p);
+ *yp = ss_sub_2(p);
+ *mp = ss_sub_2(p);
+ *dp = ss_sub_2(p);
+ *hp = ss_sub_2(p);
+ *np = ss_sub_2(p);
+ *tt = ss_sub_1(p);
+ *fswl = ss_sub_2(p);
+ *nosw = ss_sub_2(p);
+ *dpsw = ss_sub_2(p);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* - - - - - - - - - - - - - */
+/* Measurement configuration */
+
+/* Query the standard or user definable densitometric tables */
+inst_code so_do_DensTabRequest(
+ss *p,
+ss_dst ds, /* Density standard (ANSI/DIN/User etc.) */
+ss_dst *rds, /* Return Density standard (ANSI/DIN/User etc.) */
+double sp[4][36] /* Return 4 * 36 spectral weighting values */
+) {
+ int n,i;
+ ss_add_soreq(p, ss_DensTabRequest);
+ ss_add_1(p, 0x00);
+ ss_add_1(p, ds);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_DensTabAnswer);
+ ss_sub_soans(p, 0x00);
+ *rds = ss_sub_1(p);
+ for (n = 0; n < 4; n++)
+ for (i = 0; i < 36; i++)
+ sp[n][i] = ss_sub_double(p);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Download user definable densitometric tables */
+inst_code so_do_DensTabDownload(
+ss *p,
+double sp[4][36] /* 4 * 36 spectral weighting values */
+) {
+ int i, n;
+ ss_add_soreq(p, ss_DensTabDownload);
+ ss_add_1(p, 0x08);
+ for (n = 0; n < 4; n++)
+ for (i = 0; i < 36; i++)
+ ss_add_double(p, sp[n][i]);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_DownloadError);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Set slope values for densitometry */
+inst_code so_do_SlopeDownload(
+ss *p,
+double dv[4] /* Db Dc Dm Dy density values */
+) {
+ int i;
+ ss_add_soreq(p, ss_SlopeDownload);
+ for (i = 0; i < 4; i++)
+ ss_add_double(p, dv[i]);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_DownloadError);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query slope values of densitometry */
+inst_code so_do_SlopeRequest(
+ss *p,
+double dv[4] /* Return Db Dc Dm Dy density values */
+) {
+ int i;
+ ss_add_soreq(p, ss_SlopeRequest);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_SlopeAnswer);
+ for (i = 0; i < 4; i++)
+ dv[i] = ss_sub_double(p);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Set the colorimetric parameters */
+inst_code so_do_ParameterDownload(
+ss *p,
+ss_dst ds, /* Density standard (ANSI/DIN etc.) */
+ss_wbt wb, /* White base (Paper/Absolute) */
+ss_ilt it, /* Illuminant type (A/C/D65 etc.) */
+ss_ot ot /* Observer type (2deg/10deg) */
+) {
+ ss_add_soreq(p, ss_ParameterDownload);
+ ss_add_1(p, ds);
+ ss_add_1(p, wb);
+ ss_add_1(p, it);
+ ss_add_1(p, ot);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_DownloadError);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query colorimetric parameters */
+inst_code so_do_ParameterRequest(
+ss *p,
+ss_dst *ds, /* Return Density Standard */
+ss_wbt *wb, /* Return White Base */
+ss_ilt *it, /* Return Illuminant type (A/C/D65/User etc.) */
+ss_ot *ot, /* Return Observer type (2deg/10deg) */
+ss_aft *af /* Return Filter being used (None/Pol/D65/UV/custom */
+) {
+ ss_add_soreq(p, ss_ParameterRequest);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_ParameterAnswer);
+ *ds = ss_sub_1(p);
+ *wb = ss_sub_1(p);
+ *it = ss_sub_1(p);
+ *ot = ss_sub_1(p);
+ *af = ss_sub_1(p);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query the standard or user defined illuminant tables (Colorimetry) */
+inst_code so_do_IllumTabRequest(
+ss *p,
+ss_ilt it, /* Illuminant type (A/C/D65/User etc.) */
+ss_ilt *rit, /* Return Illuminant type (A/C/D65/User etc.) */
+double sp[36] /* Return 36 spectral values */
+) {
+ int i;
+ ss_add_soreq(p, ss_IllumTabRequest);
+ ss_add_1(p, 0x00);
+ ss_add_1(p, it);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_IllumTabAnswer);
+ ss_sub_soans(p, 0x00);
+ *rit = ss_sub_1(p);
+ for (i = 0; i < 36; i++)
+ sp[i] = ss_sub_double(p);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Download user definable illuminant tables (Colorimetry) */
+inst_code so_do_IllumTabDownload(
+ss *p,
+double sp[36] /* 36 spectral values to set */
+) {
+ int i;
+ ss_add_soreq(p, ss_IllumTabDownload);
+ ss_add_1(p, 0x08);
+ for (i = 0; i < 36; i++)
+ ss_add_double(p, sp[i]);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_DownloadError);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query for the color temperature of daylight illuminant Dxx */
+inst_code so_do_GetValNr(
+ss *p,
+int *ct /* Return color temperature in deg K/100 */
+) {
+ ss_add_soreq(p, ss_GetValNr);
+ ss_add_1(p, 0x60);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_ValNrAnswer);
+ ss_sub_soans(p, 0x60);
+ *ct = ss_sub_1(p);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Download user definable illuminant tables (Colorimetry) */
+inst_code so_do_SetValNr(
+ss *p,
+int ct /* Color temperature to set for illuminant Dxx in deg K/100 */
+) {
+ ss_add_soreq(p, ss_IllumTabDownload);
+ ss_add_1(p, 0x60);
+ ss_add_2(p, ct);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_DownloadError);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Queries the spectra of the white reference for the desired filter */
+inst_code so_do_WhiteReferenceRequest(
+ss *p,
+ss_aft af, /* Filter being queried (None/Pol/D65/UV/custom */
+ss_aft *raf, /* Return filter being queried (None/Pol/D65/UV/custom */
+double sp[36], /* Return 36 spectral values */
+ss_owrt *owr, /* Return original white reference */
+char dtn[19] /* Return name of data table */
+) {
+ int i;
+ ss_add_soreq(p, ss_WhiteReferenceRequest);
+ ss_add_1(p, af);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_WhiteReferenceAnswer);
+ *raf = ss_sub_1(p);
+ for (i = 0; i < 36; i++)
+ sp[i] = ss_sub_double(p);
+ *owr = ss_sub_1(p);
+ ss_sub_string(p, dtn, 18);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Load spectra of a user defined white reference for the desired filter. */
+/* A name can be given to the white reference. */
+inst_code so_do_WhiteReferenceDownld(
+ss *p,
+ss_aft af, /* Filter being set (None/Pol/D65/UV/custom */
+double sp[36], /* 36 spectral values being set */
+char dtn[19] /* Name for data table */
+) {
+ int i;
+ ss_add_soreq(p, ss_WhiteReferenceDownld);
+ ss_add_1(p, af);
+ for (i = 0; i < 36; i++)
+ ss_add_double(p, sp[i]);
+ ss_add_string(p, dtn, 18);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_DownloadError);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query the reference value for the relative photometric (emission) reference value */
+inst_code so_do_FloatRequest(
+ss *p,
+ss_comft comf, /* Choose common float type (PhotometricYRef) */
+ss_comft *rcomf, /* Return common float type (PhotometricYRef) */
+double *comfv /* Return the reference value */
+) {
+ ss_add_soreq(p, ss_FloatRequest);
+ ss_add_1(p, comf);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_FloatAnswer);
+ *rcomf = ss_sub_1(p);
+ *comfv = ss_sub_double(p);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Set the reference value for the relative photometric (emission) reference value */
+inst_code so_do_FloatDownload(
+ss *p,
+ss_comft comf, /* Choose common float type (PhotometricYRef) */
+double comfv /* The reference value */
+) {
+ ss_add_soreq(p, ss_FloatDownload);
+ ss_add_1(p, comf);
+ ss_add_double(p, comfv);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_DownloadError);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* - - - - - - */
+/* Calibration */
+
+/* Reset the spectra of the respective white reference to the original data */
+inst_code so_do_ExecWhiteRefToOrigDat(
+ss *p
+) {
+ ss_add_soreq(p, ss_ExecWhiteRefToOrigDat);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_DownloadError); /* Instrument behavour doesn't match doco. */
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+
+/* Perform a Reference Measurement */
+inst_code so_do_ExecRefMeasurement(
+ss *p,
+ss_mmt mm /* Measurement Mode (Meas/Cal etc.) */
+) {
+#ifdef EMSST
+ if (p->tmode != 0) {
+ ss_rvt rv;
+ double sp[36];
+ ss_nmt nm;
+ p->tmode = 0;
+ ss_do_MoveAndMeasure(p, 155.0, 230.0, sp, &rv);
+ so_do_NewMeasureRequest(p, &nm);
+ ss_do_MoveAbsolut(p, p->sbr, p->sbx, p->sby);
+ p->tmode = 1;
+ return (inst_notify | ss_et_WhiteMeasOK);
+ }
+#endif
+ ss_add_soreq(p, ss_ExecRefMeasurement);
+ ss_add_1(p, 0x09);
+ ss_add_1(p, mm);
+ ss_command(p, 2.0 * DF_TMO);
+ ss_sub_soans(p, ss_ExecError);
+ ss_incorp_err(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Perform a White Measurement - not recommended */
+/* (ExecRefMeasuremen is preferred instead) */
+inst_code so_do_ExecWhiteMeasurement(
+ss *p
+) {
+ ss_add_soreq(p, ss_ExecWhiteMeasurement);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_ExecError);
+ ss_incorp_err(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* - - - - - - - - - - - - */
+/* Performing measurements */
+
+
+/* Perform a Measurement */
+inst_code so_do_ExecMeasurement(
+ss *p
+) {
+#ifdef EMSST
+ if (p->tmode != 0) {
+ inst_code rc;
+ p->tmode = 0;
+ ss_do_MoveAbsolut(p, ss_rt_SensorRef, 155.0, 230.0);
+ ss_do_MoveDown(p);
+ rc = so_do_ExecMeasurement(p);
+ ss_do_MoveUp(p);
+ ss_do_MoveAbsolut(p, p->sbr, p->sbx, p->sby);
+ p->tmode = 1;
+ return rc;
+ }
+#endif
+ ss_add_soreq(p, ss_ExecMeasurement);
+ ss_command(p, 2.0 * DF_TMO);
+ ss_sub_soans(p, ss_ExecError);
+ ss_incorp_err(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Define automatic output after each measurement */
+/* [Note that dealing with the resulting measurement replies */
+/* isn't directly supported, currently.] */
+inst_code so_do_SetMeasurementOutput(
+ss *p,
+ss_ost os, /* Type of output to request */
+ss_os o /* bitmask of output */
+) {
+ ss_add_soreq(p, ss_SetMeasurementOutput);
+ ss_add_1(p, os);
+ ss_add_1(p, o.i);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_DownloadError);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+
+ return ss_inst_err(p);
+}
+
+/* - - - - - - - - */
+/* Getting results */
+
+/* so_do_Printout isn't currently defined. */
+/* It needs to cope with the expected number of values that */
+/* will be returned. This could probably be figured out from */
+/* the string itself ? */
+
+/* Query Density measurement results and associated parameters */
+inst_code so_do_DensityParameterRequest(
+ss *p,
+ss_cst *rct, /* Return Color Type (Lab/XYZ etc.) */
+double dv[4], /* Return Db Dc Dm Dy density values */
+ss_sdft *sdf, /* Return Standard Density Filter (Db/Dc/Dm/Dy) */
+ss_rvt *rv, /* Return Reference Valid Flag */
+ss_aft *af, /* Return filter being used (None/Pol/D65/UV/custom */
+ss_wbt *wb, /* Return white base (Paper/Absolute) */
+ss_dst *ds, /* Return Density standard (ANSI/DIN/User etc.) */
+ss_ilt *rit, /* Return Illuminant type (A/C/D65/User etc.) */
+ss_ot *ot /* Return Observer type (2deg/10deg) */
+) {
+ int i;
+ ss_add_soreq(p, ss_DensityParameterRequest);
+ ss_add_1(p, 0x09);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_DensityParameterAnswer);
+ ss_sub_soans(p, 0x09);
+ for (i = 0; i < 4; i++)
+ dv[i] = ss_sub_double(p);
+ *sdf = ss_sub_1(p);
+ *rv = ss_sub_1(p);
+ *af = ss_sub_1(p);
+ *wb = ss_sub_1(p);
+ ss_sub_soans(p, 0x02);
+ *ds = ss_sub_1(p);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query Densitometric measurement values - not recommended */
+/* (DensityParameterRequest is preferred instead) */
+inst_code so_do_DensityRequest(
+ss *p,
+double dv[4], /* Return Db Dc Dm Dy density values */
+ss_sdft *sdf, /* Return Standard Density Filter (Db/Dc/Dm/Dy) */
+ss_rvt *rv /* Return Reference Valid */
+) {
+ int i;
+ ss_add_soreq(p, ss_DensityRequest);
+ ss_add_1(p, 0x09);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_DensityAnswer);
+ ss_sub_soans(p, 0x09);
+ for (i = 0; i < 4; i++)
+ dv[i] = ss_sub_double(p);
+ *sdf = ss_sub_1(p);
+ *rv = ss_sub_1(p);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query maximum density reading */
+inst_code so_do_DmaxRequest(
+ss *p,
+double *Dmax, /* Return Value of Maximum Density */
+int *lambda, /* Return wavelength where maximum density was found */
+ss_dmot *dmo, /* Return Dmax OK flag. */
+ss_rvt *rv /* Return Reference Valid Flag */
+) {
+ ss_add_soreq(p, ss_DmaxRequest);
+ ss_add_1(p, 0x09);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_DmaxAnswer);
+ ss_sub_soans(p, 0x09);
+ *Dmax = ss_sub_double(p);
+ *lambda = ss_sub_2(p);
+ *dmo = ss_sub_1(p);
+ *rv = ss_sub_1(p);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query Color measurement results and associated parameters */
+inst_code so_do_CParameterRequest(
+ss *p,
+ss_cst ct, /* Choose Color Type (Lab/XYZ etc.) */
+ss_cst *rct, /* Return Color Type (Lab/XYZ etc.) */
+double cv[3], /* Return 3 color values */
+ss_rvt *rv, /* Return Reference Valid Flag */
+ss_aft *af, /* Return filter being used (None/Pol/D65/UV/custom) */
+ss_wbt *wb, /* Return white base (Paper/Absolute) */
+ss_ilt *it, /* Return Illuminant type (A/C/D65/User etc.) */
+ss_ot *ot /* Return Observer type (2deg/10deg) */
+) {
+ int i;
+ ss_add_soreq(p, ss_CParameterRequest);
+ ss_add_1(p, 0x09);
+ ss_add_1(p,ct);
+ ss_command(p, DF_TMO);
+
+ ss_sub_soans(p, ss_CParameterAnswer);
+ ss_sub_soans(p, 0x09);
+ *rct = ss_sub_1(p);
+ for (i = 0; i < 3; i++)
+ cv[i] = ss_sub_double(p);
+ *rv = ss_sub_1(p);
+ *af = ss_sub_1(p);
+ *wb = ss_sub_1(p);
+ ss_sub_soans(p, 0x02);
+ *it = ss_sub_1(p);
+ *ot = ss_sub_1(p);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query Colorimetric measurement results - not recommended */
+/* (CParameterRequest is prefered instead) */
+inst_code so_do_CRequest(
+ss *p,
+ss_cst *ct, /* Return Color Type (Lab/XYZ etc.) */
+double *cv[3], /* Return 3 color values */
+ss_rvt *rv /* Return Reference Valid Flag */
+) {
+ int i;
+ ss_add_soreq(p, ss_CRequest);
+ ss_add_1(p, 0x09);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_CAnswer);
+ ss_sub_soans(p, 0x09);
+ *ct = ss_sub_1(p);
+ for (i = 0; i < 3; i++)
+ *cv[i] = ss_sub_double(p);
+ *rv = ss_sub_1(p);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query Spectral measurement results and associated parameters */
+inst_code so_do_SpecParameterRequest(
+ss *p,
+ss_st st, /* Choose Spectrum Type (Reflectance/Density) */
+ss_st *rst, /* Return Spectrum Type (Reflectance/Density) */
+double sp[36], /* Return 36 spectral values */
+ss_rvt *rv, /* Return Reference Valid Flag */
+ss_aft *af, /* Return filter being used (None/Pol/D65/UV/custom */
+ss_wbt *wb /* Return white base (Paper/Absolute) */
+) {
+ int i;
+ ss_add_soreq(p, ss_SpecParameterRequest);
+ ss_add_1(p, 0x09);
+ ss_add_1(p,st);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_SpecParameterAnswer);
+ ss_sub_soans(p, 0x09);
+ *rst = ss_sub_1(p);
+ for (i = 0; i < 36; i++)
+ sp[i] = ss_sub_double(p);
+ *rv = ss_sub_1(p);
+ *af = ss_sub_1(p);
+ *wb = ss_sub_1(p);
+ ss_sub_soans(p, 0x02);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query Spectral measurement results - not recommended */
+/* (SpecParameterRequest is preferred instead) */
+inst_code so_do_SpectrumRequest(
+ss *p,
+ss_st *st, /* Return Spectrum Type (Reflectance/Density) */
+double sp[36], /* Return 36 spectral values */
+ss_rvt *rv /* Return Reference Valid Flag */
+) {
+ int i;
+ ss_add_soreq(p, ss_SpectrumRequest);
+ ss_add_1(p, 0x09);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_SpectrumAnswer);
+ ss_sub_soans(p, 0x09);
+ *st = ss_sub_1(p);
+ for (i = 0; i < 36; i++)
+ sp[i] = ss_sub_double(p);
+ *rv = ss_sub_1(p);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* - - - - - - */
+/* Miscelanious */
+
+/* Query whether a new measurement was performed since the last access */
+inst_code so_do_NewMeasureRequest(
+ss *p,
+ss_nmt *nm /* Return New Measurement (None/Meas/White etc.) */
+) {
+ ss_add_soreq(p, ss_NewMeasureRequest);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_NewMeasureAnswer);
+ if (nm != NULL)
+ *nm = ss_sub_1(p);
+ ss_sub_soans(p, 0x09);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query whether a key was pressed since the last access */
+inst_code so_do_NewKeyRequest(
+ss *p,
+ss_nkt *nk, /* Return whether a new key was pressed */
+ss_ks *k /* Return the key that was pressed (none/meas) */
+) {
+ ss_add_soreq(p, ss_NewKeyRequest);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_NewKeyAnswer);
+ *nk = ss_sub_1(p);
+ *k = ss_sub_2(p);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query for the general error status */
+inst_code so_do_ActErrorRequest(
+ss *p
+) {
+ ss_add_soreq(p, ss_ActErrorRequest);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_ActErrorAnswer);
+ ss_incorp_err(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Set Target On/Off status (locks key function of device?) */
+inst_code so_do_TargetOnOffStDownload(
+ss *p,
+ss_toost oo /* Activated/Deactivated */
+) {
+ ss_add_soreq(p, ss_TargetOnOffStDownload);
+ ss_add_1(p, 0x00);
+ ss_add_1(p, oo);
+ ss_add_1(p, 0x00);
+ ss_command(p, DF_TMO);
+ ss_sub_soans(p, ss_DownloadError);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* =========================================== */
+/* SpectroScan/T specific commands and queries */
+
+/* - - - - - - - - - - - - - - - - - - - - */
+/* Device Initialisation and configuration */
+
+/* Initialise the device. Scans the Spectrolino */
+/* (Doesn't work when device is offline ) */
+inst_code ss_do_ScanInitializeDevice(ss *p) {
+ ss_add_ssreq(p, ss_InitializeDevice);
+ ss_command(p, IT_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Establish communications between the SpectroScan and Spectrolino */
+/* at the highest possible baud rate. */
+/* (Doesn't work when device is offline ) */
+inst_code ss_do_ScanSpectrolino(ss *p) {
+ ss_add_ssreq(p, ss_ScanSpectrolino);
+ ss_command(p, IT_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Establish the zero position of the motors and set the position to 0,0 */
+/* (Doesn't work when device is offline ) */
+inst_code ss_do_InitMotorPosition(ss *p) {
+ ss_add_ssreq(p, ss_InitMotorPosition);
+ ss_command(p, MV_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Change the SpectroScan baud rate */
+inst_code ss_do_ChangeBaudRate(
+ss *p,
+ss_bt br /* Baud rate (110 - 57600) */
+) {
+ ss_add_ssreq(p, ss_ChangeBaudRate);
+ ss_add_1(p, br);
+ ss_command(p, SH_TMO); /* Don't really expect an answer */
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p); /* Will probably bomb because comms will break down */
+}
+
+/* Change the SpectroScan handshaking mode. */
+inst_code ss_do_ChangeHandshake(
+ss *p,
+ss_hst hs /* Handshake type (None/XonXoff/HW) */
+) {
+ ss_add_ssreq(p, ss_ChangeHandshake);
+ ss_add_1(p, hs);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query the type of XY table */
+inst_code ss_do_OutputType(
+ss *p,
+char dt[19] /* Return Device Type ("SpectroScan", "SpectroScan " or "SpectroScanT") */
+) {
+ ss_add_ssreq(p, ss_OutputType);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_TypeAnswer);
+ ss_sub_string(p, dt, 18);
+ chended(p);
+#ifdef EMSST
+ if (strcmp(dt,"SpectroScan ") == 0
+ || strcmp(dt,"SpectroScan") == 0)
+ sprintf(dt,"SpectroScanT");
+#endif
+ return ss_inst_err(p);
+}
+
+/* Query the serial number of the XY table */
+inst_code ss_do_OutputSerialNumber(
+ss *p,
+unsigned int *sn /* Return serial number */
+) {
+ ss_add_ssreq(p, ss_OutputSerialNumber);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_SerialNumberAnswer);
+ *sn = (unsigned int)ss_sub_4(p);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query the part number of the XY table */
+inst_code ss_do_OutputArticleNumber(
+ss *p,
+char pn[9] /* Return Part Number */
+) {
+ ss_add_ssreq(p, ss_OutputArticleNumber);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_ArticleNumberAnswer);
+ ss_sub_string(p, pn, 8);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query the production date of the XY table */
+inst_code ss_do_OutputProductionDate(
+ss *p,
+int *yp, /* Return Year of production (e.g. 1996) */
+int *mp, /* Return Month of production (1-12) */
+int *dp /* Return Day of production (1-31) */
+) {
+ ss_add_ssreq(p, ss_OutputProductionDate);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_ProductionDateAnswer);
+ *dp = ss_sub_2(p);
+ *mp = ss_sub_2(p);
+ *yp = ss_sub_2(p);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query the Software Version of the XY table */
+inst_code ss_do_OutputSoftwareVersion(
+ss *p,
+char sv[13] /* Return Software Version */
+) {
+ ss_add_ssreq(p, ss_OutputSoftwareVersion);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_SoftwareVersionAnswer);
+ ss_sub_string(p, sv, 12);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* - - - - - - - - - - - - - */
+/* Measurement configuration */
+
+/* Set the SpectroScanT to reflectance or transmission. */
+/* The Spectrolino is also automatically set to the corresponding mode. */
+/* (Doesn't work when device is offline ) */
+inst_code ss_do_SetTableMode(
+ss *p,
+ss_tmt tm /* Table mode (Reflectance/Transmission) */
+) {
+#ifdef EMSST
+ if (tm == ss_tmt_Transmission) {
+ if (p->tmode == 0) {
+ ss_do_MoveAbsolut(p, p->sbr, p->sbx, p->sby); /* ?? */
+ }
+ p->tmode = 1;
+ } else {
+ if (p->tmode != 0) {
+ p->tmode = 0;
+ ss_do_MoveHome(p);
+ }
+ p->tmode = 0;
+ }
+ return inst_ok;
+#endif
+ ss_add_ssreq(p, ss_SetTableMode);
+ ss_add_1(p, tm);
+ ss_command(p, IT_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* - - - - - - - - - - - - - */
+/* Table operation */
+
+/* Set the SpectrScan to online. All moving keys are disabled. */
+/* (Only valid when device is in reflectance mode.) */
+inst_code ss_do_SetDeviceOnline(ss *p) {
+#ifdef EMSST
+ if (p->tmode != 0)
+ *((char *)0) = 55;
+// return inst_unsupported;
+#endif
+ ss_add_ssreq(p, ss_SetDeviceOnline);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Set the SpectrScan to offline. All moving keys are enabled. */
+/* (Only valid when device is in reflectance mode.) */
+/* (All remote commands to move the device will be ignored.) */
+inst_code ss_do_SetDeviceOffline(ss *p) {
+#ifdef EMSST
+ if (p->tmode != 0)
+ *((char *)0) = 55;
+#endif
+ ss_add_ssreq(p, ss_SetDeviceOffline);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Enable electrostatic paper hold. */
+/* (Not valid when device is offline) */
+inst_code ss_do_HoldPaper(ss *p) {
+ ss_add_ssreq(p, ss_HoldPaper);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Disable electrostatic paper hold. */
+/* (Not valid when device is offline) */
+inst_code ss_do_ReleasePaper(ss *p) {
+ ss_add_ssreq(p, ss_ReleasePaper);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* - - - - - - */
+/* Positioning */
+
+/* Move either the sight or sensor to an absolute position. */
+/* (Doesn't work when device is offline or transmissioin mode.) */
+inst_code ss_do_MoveAbsolut(
+ss *p,
+ss_rt r, /* Reference (Sensor/Sight) */
+double x, /* X coord in mm, 0-310.0, accurate to 0.1mm */
+double y /* Y coord in mm, 0-230.0, accurate to 0.1mm */
+) {
+#ifdef EMSST
+ if (p->tmode != 0)
+ *((char *)0) = 55;
+#endif
+ ss_add_ssreq(p, ss_MoveAbsolut);
+ ss_add_1(p, r);
+ ss_add_2(p, (int)(x * 10 + 0.5));
+ ss_add_2(p, (int)(y * 10 + 0.5));
+ ss_command(p, MV_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Move relative to current position. */
+/* (Doesn't work when device is offline or transmissioin mode.) */
+inst_code ss_do_MoveRelative(
+ss *p,
+double x, /* X distance in mm, 0-310.0, accurate to 0.1mm */
+double y /* Y distance in mm, 0-230.0, accurate to 0.1mm */
+) {
+#ifdef EMSST
+ if (p->tmode != 0)
+ *((char *)0) = 55;
+#endif
+ ss_add_ssreq(p, ss_MoveRelative);
+ ss_add_2(p, (int)(x * 10 + 0.5));
+ ss_add_2(p, (int)(y * 10 + 0.5));
+ ss_command(p, MV_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Move to the home position (== 0,0). */
+/* (Doesn't work when device is offline or transmissioin mode.) */
+inst_code ss_do_MoveHome(
+ss *p
+) {
+#ifdef EMSST
+ if (p->tmode != 0)
+ *((char *)0) = 55;
+#endif
+ ss_add_ssreq(p, ss_MoveHome);
+ ss_command(p, MV_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Move to the sensor up. */
+/* (Doesn't work when device is offline or transmissioin mode.) */
+inst_code ss_do_MoveUp(
+ss *p
+) {
+#ifdef EMSST
+ if (p->tmode != 0)
+ *((char *)0) = 55;
+#endif
+ ss_add_ssreq(p, ss_MoveUp);
+ ss_command(p, MV_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Move to the sensor down. */
+/* (Doesn't work when device is offline or transmission mode.) */
+inst_code ss_do_MoveDown(
+ss *p
+) {
+#ifdef EMSST
+ if (p->tmode != 0)
+ *((char *)0) = 55;
+#endif
+ ss_add_ssreq(p, ss_MoveDown);
+ ss_command(p, MV_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query the current absolute position of the sensor or sight. */
+/* (Doesn't work when device is offline or transmissioin mode.) */
+inst_code ss_do_OutputActualPosition(
+ss *p,
+ss_rt r, /* Reference (Sensor/Sight) */
+ss_rt *rr, /* Return reference (Sensor/Sight) */
+double *x, /* Return the X coord in mm, 0-310.0, accurate to 0.1mm */
+double *y, /* Return the Y coord in mm, 0-230.0, accurate to 0.1mm */
+ss_zkt *zk /* Return the Z coordinate (Up/Down) */
+) {
+#ifdef EMSST
+ if (p->tmode != 0)
+ *((char *)0) = 55;
+#endif
+ ss_add_ssreq(p, ss_OutputActualPosition);
+ ss_add_1(p, r);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_PositionAnswer);
+ *rr = ss_sub_1(p);
+ ss_sub_soans(p, 0x00);
+ ss_sub_soans(p, 0x00);
+ *x = ss_sub_2(p)/10.0;
+ *y = ss_sub_2(p)/10.0;
+ *zk = ss_sub_1(p);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Move to the white reference position */
+/* (Doesn't work when device is offline or transmissioin mode.) */
+inst_code ss_do_MoveToWhiteRefPos(
+ss *p,
+ss_wrpt wrp /* White Reference Position (Tile1/Tile2) */
+) {
+#ifdef EMSST
+ if (p->tmode != 0)
+ *((char *)0) = 55;
+#endif
+ ss_add_ssreq(p, ss_MoveToWhiteRefPos);
+ ss_add_1(p, wrp);
+ ss_command(p, MV_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* - - - - - - - - - - - - */
+/* Performing measurements */
+
+/* Move the sensor to an absolute position, move the */
+/* sensor down, execute a measurement, move the head up, */
+/* and return spectral measuring results. */
+inst_code ss_do_MoveAndMeasure(
+ss *p,
+double x, /* X coord in mm, 0-310.0, accurate to 0.1mm */
+double y, /* Y coord in mm, 0-230.0, accurate to 0.1mm */
+double sp[36], /* Return 36 spectral values */
+ss_rvt *rv /* Return Reference Valid Flag */
+) {
+#ifdef EMSST
+ /* Not sure if this is valid on the SpectroScanT in trans mode ? */
+ if (p->tmode != 0) {
+ inst_code rc;
+ p->tmode = 0;
+ rc = ss_do_MoveAndMeasure(p, 155.0, 230.0, sp, rv);
+ ss_do_MoveAbsolut(p, p->sbr, p->sbx, p->sby);
+ p->tmode = 1;
+ return rc;
+ }
+#endif
+ ss_add_ssreq(p, ss_MoveAndMeasure);
+ ss_add_2(p, (int)(x * 10 + 0.5));
+ ss_add_2(p, (int)(y * 10 + 0.5));
+ ss_command(p, MV_TMO);
+ if (ss_peek_ans(p) == ss_SpectrumAnswer) {
+ int i;
+ ss_sub_soans(p, ss_SpectrumAnswer);
+ ss_sub_soans(p, 0x09);
+ ss_sub_soans(p, 0x00);
+ for (i = 0; i < 36; i++)
+ sp[i] = ss_sub_double(p);
+ *rv = ss_sub_1(p);
+ ss_incorp_remerrset(p, ss_sub_2(p));
+ } else {
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ }
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* - - - - - - */
+/* Miscelanious */
+
+/* Set the SpectroScanT transmission light level during standby. */
+/* (Only valid on SpectroScanT in transmission mode) */
+inst_code ss_do_SetLightLevel(
+ss *p,
+ss_llt ll /* Transmission light level (Off/Surround/Low) */
+) {
+#ifdef EMSST
+ if (p->tmode != 0)
+ return inst_ok;
+ else
+ *((char *)0) = 55;
+#endif
+ ss_add_ssreq(p, ss_SetLightLevel);
+ ss_add_1(p, ll);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Set tranmission standby position. */
+/* (Only valid on SpectroScanT in transmission mode) */
+inst_code ss_do_SetTransmStandbyPos(
+ss *p,
+ss_rt r, /* Reference (Sensor/Sight) */
+double x, /* X coord in mm, 0-310.0, accurate to 0.1mm */
+double y /* Y coord in mm, 0-230.0, accurate to 0.1mm */
+) {
+#ifdef EMSST
+ if (p->tmode != 0) {
+ p->sbr = r;
+ p->sbx = x;
+ p->sby = y;
+ return inst_ok;
+ } else {
+ *((char *)0) = 55;
+ }
+#endif
+ ss_add_ssreq(p, ss_SetTransmStandbyPos);
+ ss_add_1(p, r);
+ ss_add_2(p, (int)(x * 10 + 0.5));
+ ss_add_2(p, (int)(y * 10 + 0.5));
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Set digitizing mode. Clears digitizing buffer, */
+/* and puts the device offline. The user can move */
+/* and enter positions. */
+inst_code ss_do_SetDigitizingMode(ss *p) {
+ ss_add_ssreq(p, ss_SetDigitizingMode);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Get last digitized position from memory. */
+inst_code ss_do_OutputDigitizingValues(
+ss *p,
+ss_rt r, /* Reference (Sensor/Sight) */
+ss_rt *rr, /* Return reference (Sensor/Sight) */
+int *nrp,/* Return the number of remaining positions in memory. */
+double *x, /* Return the X coord in mm, 0-310.0, accurate to 0.1mm */
+double *y, /* Return the Y coord in mm, 0-230.0, accurate to 0.1mm */
+ss_zkt *zk /* Return the Z coordinate (Up/Down) */
+) {
+ ss_add_ssreq(p, ss_OutputDigitizingValues);
+ ss_add_1(p, r);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_PositionAnswer);
+ *rr = ss_sub_1(p);
+ *nrp = ss_sub_2(p); /* Should be unsigned ?? */
+ *x = ss_sub_2(p)/10.0;
+ *y = ss_sub_2(p)/10.0;
+ *zk = ss_sub_1(p);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Turn on key aknowledge mode. Causes a KeyAnswer message */
+/* to be generated whenever a key is pressed. */
+/* (KetAnswer isn't well supported here ?) */
+inst_code ss_do_SetKeyAcknowlge(ss *p) {
+ ss_add_ssreq(p, ss_SetKeyAcknowlge);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Turn off key aknowledge mode. */
+inst_code ss_do_ResetKeyAcknowlge(ss *p) {
+ ss_add_ssreq(p, ss_ResetKeyAcknowlge);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query the keys that are currently pressed */
+inst_code ss_do_OutputActualKey(
+ss *p,
+ss_sks *sk, /* Return Scan Key Set (Key bitmask) */
+ss_ptt *pt /* Return press time (Short/Long) */
+) {
+ ss_add_ssreq(p, ss_OutputActualKey);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_KeyAnswer);
+ *sk = ss_sub_1(p);
+ *pt = ss_sub_1(p);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query the keys that were last pressed */
+inst_code ss_do_OutputLastKey(
+ss *p,
+ss_sks *sk, /* Return Scan Key bitmask (Keys) */
+ss_ptt *pt /* Return press time (Short/Long) */
+) {
+ ss_add_ssreq(p, ss_OutputLastKey);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_KeyAnswer);
+ *sk = ss_sub_1(p);
+ *pt = ss_sub_1(p);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query the status register */
+inst_code ss_do_OutputStatus(
+ss *p,
+ss_sts *st /* Return status bitmask (Enter key/Online/Digitize/KeyAck/Paper) */
+) {
+ ss_add_ssreq(p, ss_OutputStatus);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_StatusAnswer);
+ *st = ss_sub_1(p);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Clear the status register */
+inst_code ss_do_ClearStatus(
+ss *p,
+ss_sts st /* Status to reset (Enter key/Online/Digitize/KeyAck/Paper) */
+) {
+ ss_add_ssreq(p, ss_ClearStatus);
+ ss_add_1(p, st);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Set the special status register */
+/* (Set to all 0 on reset) */
+inst_code ss_do_SetSpecialStatus(
+ss *p,
+ss_sss sss /* Status bits to set (HeadDwnOnMv/TableInTransMode/AllLightsOn) */
+) {
+ ss_add_ssreq(p, ss_SetSpecialStatus);
+ ss_add_1(p, sss);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Clear the special status register */
+inst_code ss_do_ClearSpecialStatus(
+ss *p,
+ss_sss sss /* Status bits to clear (HeadDwnOnMv/TableInTransMode/AllLightsOn) */
+) {
+ ss_add_ssreq(p, ss_ClearSpecialStatus);
+ ss_add_1(p, sss);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_ErrorAnswer);
+ ss_incorp_scanerr(p, ss_sub_1(p));
+ chended(p);
+ return ss_inst_err(p);
+}
+
+/* Query the special status register */
+inst_code ss_do_OutputSpecialStatus(
+ss *p,
+ss_sss *sss /* Return Special Status bits */
+) {
+ ss_add_ssreq(p, ss_OutputSpecialStatus);
+ ss_command(p, DF_TMO);
+ ss_sub_ssans(p, ss_StatusAnswer);
+ *sss = ss_sub_1(p);
+ chended(p);
+ return ss_inst_err(p);
+}
+
+
+#define SS_IMP_H
+#endif /* SS_IMP_H */
+
+
diff --git a/spectro/ss_imp.h b/spectro/ss_imp.h
new file mode 100644
index 0000000..c298e80
--- /dev/null
+++ b/spectro/ss_imp.h
@@ -0,0 +1,1335 @@
+
+#ifndef SS_IMP_H
+
+/*
+ * Argyll Color Correction System
+ *
+ * Gretag Spectrolino and Spectroscan related
+ * defines and declarations - implementation.
+ *
+ * Author: Graeme W. Gill
+ * Date: 13/7/2005
+ *
+ * Copyright 2005 - 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.
+ *
+ * This is an alternative driver to spm/gretag.
+ */
+
+/*
+ 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.
+ */
+
+
+/* Communication symbol definitions */
+/* From the Gretag Spectrolino/Spectroscan */
+/* Serial Interface manual. */
+/* We are using the Hex communication method */
+
+/* Timout values for commands */
+#define SH_TMO 0.5 /* Short timout for establishing communications */
+#define IT_TMO 18.0 /* Initialisation commands */
+#define MV_TMO 10.0 /* Move commands */
+#define DF_TMO 6.0 /* Other commands */
+
+/* Actual Filter Type */
+typedef enum {
+ ss_aft_NoDefined = 0x00,
+ ss_aft_NoFilter = 0x01,
+ ss_aft_PolFilter = 0x02,
+ ss_aft_D65Filter = 0x03,
+ ss_aft_UVCutFilter = 0x05,
+ ss_aft_CustomFilter = 0x06
+} ss_aft;
+
+/* Article Number Type */
+
+/* Baudrate Type */
+typedef enum {
+ ss_bt_110 = 0x00,
+ ss_bt_150 = 0x01,
+ ss_bt_300 = 0x02,
+ ss_bt_600 = 0x03,
+ ss_bt_1200 = 0x04,
+ ss_bt_2400 = 0x05,
+ ss_bt_4800 = 0x06,
+ ss_bt_9600 = 0x07,
+ ss_bt_19200 = 0x08,
+ ss_bt_28080 = 0x09,
+ ss_bt_57600 = 0x0A
+} ss_bt;
+
+/* Color Type */
+
+/* COM Float Type */
+typedef enum {
+ ss_comft_vPhotometricYRef = 0x01 /* cd/m^2 */
+} ss_comft;
+
+/* Control Type */
+typedef enum {
+ ss_ctt_ProtokolWithXonXoff = 0x1E,
+ ss_ctt_ProtokolWithoutXonXoff = 0x1F,
+ ss_ctt_ProtokolWithHardwareHS = 0xCF,
+ ss_ctt_SetBaud110 = 0x20,
+ ss_ctt_SetBaud150 = 0x21,
+ ss_ctt_SetBaud300 = 0x22,
+ ss_ctt_SetBaud600 = 0x23,
+ ss_ctt_SetBaud1200 = 0x24,
+ ss_ctt_SetBaud2400 = 0x25,
+ ss_ctt_SetBaud4800 = 0x26,
+ ss_ctt_SetBaud9600 = 0x27,
+ ss_ctt_SetBaud19200 = 0x28,
+ ss_ctt_SetBaud28800 = 0x98,
+ ss_ctt_SetBaud57600 = 0x99,
+ ss_ctt_SpeakerON = 0x54,
+ ss_ctt_SpeakerOFF = 0x55,
+ ss_ctt_RemissionMeas = 0x9B,
+ ss_ctt_TransmissionMeas = 0x9C,
+ ss_ctt_EmissionMeas = 0x9D,
+ ss_ctt_PhotometricAbsolute = 0x9E,
+ ss_ctt_PhotometricRelative = 0x9F,
+ ss_ctt_SetCustomFilter = 0xA0,
+ ss_ctt_ReleaseCustomFilter = 0xA1
+} ss_ctt;
+
+/* Color Space Type */
+typedef enum {
+ ss_cst_XyY = 0x00,
+ ss_cst_Lab = 0x01,
+ ss_cst_LChab = 0x02,
+ ss_cst_Luv = 0x03,
+ ss_cst_XYZ = 0x04,
+ ss_cst_RxRyRz = 0x05,
+ ss_cst_HLab = 0x06,
+ ss_cst_LABmg = 0x0B,
+ ss_cst_LCHmg = 0x0C,
+ ss_cst_LCHuv = 0x0D
+} ss_cst;
+
+/* Date Type */
+
+/* Density Filter Spectral Array Type */
+
+/* Density Filter Array Type */
+
+/* Density Filter Type */
+typedef enum {
+ ss_dft_Db = 0x00,
+ ss_dft_Dc = 0x01,
+ ss_dft_Dm = 0x02,
+ ss_dft_Dy = 0x03,
+ ss_dft_Dmax = 0x04,
+ ss_dft_Dauto = 0x05
+} ss_dft;
+
+/* Device Name Type */
+
+/* Device Number Type */
+typedef enum {
+ ss_dnot_SPM10 = 0x00,
+ ss_dnot_SPM50 = 0x01,
+ ss_dnot_SPM55 = 0x02,
+ ss_dnot_SPM60 = 0x03,
+ ss_dnot_SPM100 = 0x04,
+ ss_dnot_SPM100IInl = 0x05,
+ ss_dnot_SPM100II = 0x06,
+ ss_dnot_D196 = 0x10,
+ ss_dnot_D19C = 0x11,
+ ss_dnot_D118C = 0x12,
+ ss_dnot_DM620 = 0x13,
+ ss_dnot_SPECTROLINO = 0x20,
+ ss_dnot_VIDEOLINO = 0x30,
+ ss_dnot_SPECTROSCAN = 0x40
+} ss_dnot;
+
+/* Dmax OK Type */
+typedef enum {
+ ss_dmot_FALSE = 0x00,
+ ss_dmot_TRUE = 0x01
+} ss_dmot;
+
+/* Dmax Type */
+
+/* Density Standard Type */
+typedef enum {
+ ss_dst_ANSIA = 0x00,
+ ss_dst_ANSIT = 0x01,
+ ss_dst_DIN = 0x02,
+ ss_dst_DINNB = 0x03,
+ ss_dst_DS1 = 0x08 /* User defined */
+} ss_dst;
+
+/* Communication Error Type */
+typedef enum {
+ ss_cet_NoError = 0x00,
+ ss_cet_StopButNoStart = 0x01,
+ ss_cet_IllegalCharInRec = 0x02,
+ ss_cet_IncorrectRecLen = 0x03,
+ ss_cet_IllegalRecType = 0x04,
+ ss_cet_NoTagField = 0x06,
+ ss_cet_ConvError = 0x07,
+ ss_cet_InvalidForEmission = 0x08,
+ ss_cet_NoAccess = 0x10,
+} ss_cet;
+
+/* Daylight Color Temperature Type */
+
+/* Error Type + augmentation */
+typedef enum {
+ ss_et_NoError = 0x00,
+ ss_et_MemoryFailure = 0x01,
+ ss_et_PowerFailure = 0x02,
+ ss_et_LampFailure = 0x04,
+ ss_et_HardwareFailure = 0x05,
+ ss_et_FilterOutOfPos = 0x06,
+ ss_et_SendTimeout = 0x07,
+ ss_et_DriveError = 0x08,
+ ss_et_MeasDisabled = 0x09,
+ ss_et_DensCalError = 0x0A,
+ ss_et_EPROMFailure = 0x0D,
+ ss_et_RemOverFlow = 0x0E,
+ ss_et_MemoryError = 0x10,
+ ss_et_FullMemory = 0x11,
+ ss_et_WhiteMeasOK = 0x13,
+ ss_et_NotReady = 0x15,
+ ss_et_WhiteMeasWarn = 0x32,
+ ss_et_ResetDone = 0x33,
+ ss_et_EmissionCalOK = 0x34,
+ ss_et_OnlyEmission = 0x35,
+ ss_et_CheckSumWrong = 0x36,
+ ss_et_NoValidMeas = 0x37,
+ ss_et_BackupError = 0x38,
+ ss_et_ProgramROMError = 0x3C,
+
+ /* Incororate Remote Error Set values into snerr value */
+ /* Since ss_res is a bit mask, we just prioritize the errors: */
+ ss_et_NoValidDStd = 0x41,
+ ss_et_NoValidWhite = 0x42,
+ ss_et_NoValidIllum = 0x43,
+ ss_et_NoValidObserver = 0x44,
+ ss_et_NoValidMaxLambda = 0x45,
+ ss_et_NoValidSpect = 0x46,
+ ss_et_NoValidColSysOrIndex = 0x47,
+ ss_et_NoValidChar = 0x48,
+ ss_et_DorlOutOfRange = 0x49,
+ ss_et_ReflectanceOutOfRange = 0x4A,
+ ss_et_Color1OutOfRange = 0x4B,
+ ss_et_Color2OutOfRange = 0x4C,
+ ss_et_Color3OutOfRange = 0x4D,
+ ss_et_NotAnSROrBoolean = 0x4E,
+ ss_et_NoValidValOrRef = 0x4F,
+
+ /* Incorporate scan error codes thus: */
+ ss_et_DeviceIsOffline = 0x61,
+ ss_et_OutOfRange = 0x62,
+ ss_et_ProgrammingError = 0x63,
+ ss_et_NoUserAccess = 0x64,
+ ss_et_NoValidCommand = 0x65,
+ ss_et_NoDeviceFound = 0x66,
+ ss_et_MeasurementError = 0x67,
+ ss_et_NoTransmTable = 0x68,
+ ss_et_NotInTransmMode = 0x69,
+ ss_et_NotInReflectMode = 0x6A,
+
+ /* Incorporate communication errors thus: */
+ ss_et_StopButNoStart = 0x81,
+ ss_et_IllegalCharInRec = 0x82,
+ ss_et_IncorrectRecLen = 0x83,
+ ss_et_IllegalRecType = 0x84,
+ ss_et_NoTagField = 0x86,
+ ss_et_ConvError = 0x87,
+ ss_et_InvalidForEmission = 0x88,
+ ss_et_NoAccess = 0x90,
+
+ /* Add our own communication errors here too. */
+ ss_et_SerialFail = 0xF0,
+ ss_et_SendBufferFull = 0xF5,
+ ss_et_RecBufferEmpty = 0xF6,
+ ss_et_BadAnsFormat = 0xF7,
+ ss_et_BadHexEncoding = 0xF8,
+ ss_et_RecBufferOverun = 0xF9
+} ss_et;
+
+/* Handshake Type */
+typedef enum {
+ ss_hst_None = 0x00,
+ ss_hst_XonXOff = 0x01,
+ ss_hst_Hardware = 0x02,
+} ss_hst;
+
+/* Illuminant Type */
+typedef enum {
+ ss_ilt_A = 0x00,
+ ss_ilt_C = 0x01,
+ ss_ilt_D65 = 0x02,
+ ss_ilt_D50 = 0x03,
+ ss_ilt_1 = 0x08, /* User defined */
+ ss_ilt_Dxx = 0x10, /* Variable daylight table */
+ ss_ilt_F1 = 0x18,
+ ss_ilt_F2 = 0x19,
+ ss_ilt_F3 = 0x1A,
+ ss_ilt_F4 = 0x1B,
+ ss_ilt_F5 = 0x1C,
+ ss_ilt_F6 = 0x1D,
+ ss_ilt_F7 = 0x1E,
+ ss_ilt_F8 = 0x1F,
+ ss_ilt_F9 = 0x20,
+ ss_ilt_F10 = 0x21,
+ ss_ilt_F11 = 0x22,
+ ss_ilt_F12 = 0x23
+} ss_ilt;
+
+/* Keyset - treat as cardinal */
+typedef enum {
+ ss_ks_NoKey = 0x0000,
+ ss_ks_MeasurementKey = 0x0080
+} ss_ks;
+
+/* Lambda Type */
+
+/* Light Level Type */
+typedef enum {
+ ss_llt_AllOff = 0x00, /* All lights off during standby */
+ ss_llt_Standby1 = 0x01, /* Surround is on but measurement lamp is off */
+ ss_llt_Standby2 = 0x02, /* Surround is on and measurement lamp is on low */
+} ss_llt;
+
+/* Measurement Mode Type */
+typedef enum {
+ ss_mmt_NormalMeas = 0x00,
+ ss_mmt_WhiteCalibration = 0x01,
+ ss_mmt_WhiteCalWithWarn = 0x07,
+ ss_mmt_EmissionCal = 0x08
+} ss_mmt;
+
+/* New Key Type */
+typedef enum {
+ ss_nkt_False = 0x00,
+ ss_nkt_True = 0x01
+} ss_nkt;
+
+/* New Measurement Type */
+typedef enum {
+ ss_nmt_NoNewMeas = 0x00,
+ ss_nmt_NewMeas = 0x01,
+ ss_nmt_NewWhiteCal = 0x02,
+ ss_nmt_NewWhiteCalWW = 0x03,
+ ss_nmt_NewEmissionCal = 0x04
+} ss_nmt;
+
+/* Observer Type */
+typedef enum {
+ ss_ot_TwoDeg = 0x00,
+ ss_ot_TenDeg = 0x01
+} ss_ot;
+
+/* Original White Reference Type */
+typedef enum {
+ ss_owrt_OriginalWhiteRef = 0x00,
+ ss_owrt_OriginalUserWhiteRef = 0x01,
+ ss_owrt_NotDefWhiteRef = 0x02
+} ss_owrt;
+
+
+/* Output Set Type */
+typedef enum {
+ ss_ost_ParameterSet = 0x00, /* To define measurement of parameters for output */
+ ss_ost_SpectrumSet = 0x01, /* To define spectra for output */
+ ss_ost_CMetry1Set = 0x02, /* To define colorimetry values for output */
+ ss_ost_CMetry2Set = 0x03, /* To define colorimetry values for output */
+ ss_ost_DensitySet = 0x04, /* To define densitometry values for output */
+ ss_ost_ErrorType = 0xFF /* To get the error of the measurement */
+} ss_ost;
+
+/* Output Parameter Set - bit masks */
+typedef enum {
+ ss_ops_None = 0x00,
+ ss_ops_DStdType = 0x01,
+ ss_ops_WBase = 0x02,
+ ss_ops_Illuminant = 0x04,
+ ss_ops_Observer = 0x08,
+ ss_ops_ActualFilter = 0x10
+} ss_ops;
+
+/* Output Spectrum Set - bit masks */
+typedef enum {
+ ss_oss_None = 0x00,
+ ss_oss_Spectrum = 0x01,
+ ss_oss_Density = 0x02
+} ss_oss;
+
+/* Output Colorimetry 1 Set - bit masks */
+typedef enum {
+ ss_oc1s_None = 0x00,
+ ss_oc1s_xyY = 0x01,
+ ss_oc1s_Lab = 0x02,
+ ss_oc1s_LChab = 0x04,
+ ss_oc1s_Luv = 0x08,
+ ss_oc1s_XYZ = 0x10,
+ ss_oc1s_RxRyRz = 0x20,
+ ss_oc1s_HLab = 0x40
+} ss_oc1s;
+
+/* Output Colorimetry 2 Set - bit masks */
+typedef enum {
+ ss_oc2s_None = 0x00,
+ ss_oc2s_LABmg = 0x01,
+ ss_oc2s_LCHmg = 0x02,
+ ss_oc2s_LChuv = 0x04
+} ss_oc2s;
+
+/* Output Density Set - bit masks */
+typedef enum {
+ ss_ods_None = 0x00,
+ ss_ods_Black = 0x01,
+ ss_ods_Cyan = 0x02,
+ ss_ods_Magenta = 0x04,
+ ss_ods_Yellow = 0x08,
+ ss_ods_Max = 0x10,
+ ss_ods_Auto = 0x20
+} ss_ods;
+
+/* Type that is one of the above, depending on what ss_ost is selected */
+typedef union {
+ ss_ods od;
+ ss_oss os;
+ ss_oc1s oc1;
+ ss_oc2s oc2;
+ ss_ops op;
+ int i;
+} ss_os;
+
+
+/* Press Time Type */
+typedef enum {
+ ss_ptt_Short = 0x00,
+ ss_ptt_Long = 0x01
+} ss_ptt;
+
+/* Reference Type */
+typedef enum {
+ ss_rt_SensorRef = 0x00,
+ ss_rt_SightRef = 0x01
+} ss_rt;
+
+/* Reference Valid Type */
+typedef enum {
+ ss_rvt_False = 0x00,
+ ss_rvt_True = 0x01
+} ss_rvt;
+
+/* Remaining Positions Type */
+
+/* Remote Error Set - bit mask - treat as cardinal */
+typedef enum {
+ ss_res_NoError = 0x0000,
+ ss_res_NoValidDStd = 0x0001,
+ ss_res_NoValidWhite = 0x0002,
+ ss_res_NoValidIllum = 0x0004,
+ ss_res_NoValidObserver = 0x0008,
+ ss_res_NoValidMaxLambda = 0x0010,
+ ss_res_NoValidSpect = 0x0020,
+ ss_res_NoValidColSysOrIndex = 0x0040,
+ ss_res_NoValidChar = 0x0080,
+ ss_res_SlopeOutOfRange = 0x0100,
+ ss_res_DorlOutOfRange = 0x0200,
+ ss_res_ReflectanceOutOfRange = 0x0400,
+ ss_res_Color1OutOfRange = 0x0800,
+ ss_res_Color2OutOfRange = 0x1000,
+ ss_res_Color3OutOfRange = 0x2000,
+ ss_res_NotAnSROrBoolean = 0x4000,
+ ss_res_NoValidValOrRef = 0x8000,
+} ss_res;
+
+/* Scan Error Type */
+typedef enum {
+ ss_set_NoError = 0x00,
+ ss_set_DeviceIsOffline = 0x01,
+ ss_set_OutOfRange = 0x02,
+ ss_set_ProgrammingError = 0x03,
+ ss_set_NoUserAccess = 0x04,
+ ss_set_NoValidCommand = 0x05,
+ ss_set_NoDeviceFound = 0x06,
+ ss_set_MeasurementError = 0x07,
+ ss_set_NoTransmTable = 0x08,
+ ss_set_NotInTransmMode = 0x09,
+ ss_set_NotInReflectMode = 0x0A
+} ss_set;
+
+/* Scan Key Set - bit mask */
+typedef enum {
+ ss_sks_EnterKey = 0x01,
+ ss_sks_PaperKey = 0x02,
+ ss_sks_OnlineKey = 0x04,
+ ss_sks_UpDownKey = 0x08,
+ ss_sks_MoveRightKey = 0x10,
+ ss_sks_MoveLeftKey = 0x20,
+ ss_sks_MoveDownKey = 0x40,
+ ss_sks_MoveUpKey = 0x80
+} ss_sks;
+
+/* Serial Number Type */
+
+/* Special Status Set - bit mask */
+typedef enum {
+ ss_sss_HeadDwnOnMove = 0x01, /* Don't lift the head when moving */
+ ss_sss_TableInTransMode = 0x10, /* Table is set to transmission mode */
+ ss_sss_AllLightsOn = 0x20 /* Surround light on + low measure light */
+} ss_sss;
+
+/* Spect. Type */
+typedef enum {
+ ss_st_LinearSpectrum = 0x00,
+ ss_st_DensitySpectrum = 0x01
+} ss_st;
+
+/* Status Mode Type */
+typedef enum {
+ ss_smt_InitAll = 0x01,
+ ss_smt_InitWithoutRemote = 0x05
+} ss_smt;
+
+/* Status Set - bit mask */
+typedef enum {
+ ss_sts_EnterKeyPressed = 0x01,
+ ss_sts_DeviceIsOnline = 0x10,
+ ss_sts_DigitizingModeOn = 0x20,
+ ss_sts_KeyAckModeOn = 0x40,
+ ss_sts_PaperIsHeld = 0x80
+} ss_sts;
+
+/* Standard Density Filter Type */
+typedef enum {
+ ss_sdft_Db = 0x00,
+ ss_sdft_Dc = 0x01,
+ ss_sdft_Dm = 0x02,
+ ss_sdft_Dy = 0x03
+} ss_sdft;
+
+/* Table Mode Type */
+typedef enum {
+ ss_tmt_Reflectance = 0x00,
+ ss_tmt_Transmission = 0x01
+} ss_tmt;
+
+/* Table Value Type */
+typedef enum {
+ ss_tvt_vDxx1 = 0x60
+} ss_tvt;
+
+/* Target On Off Status Type (Enables/Disables measurement key ?) */
+typedef enum {
+ ss_toost_Activated = 0x00,
+ ss_toost_Deactivated = 0x01
+} ss_toost;
+
+/* Target Tech Type */
+typedef enum {
+ ss_ttt_SPM = 0x00,
+ ss_ttt_D190 = 0x01,
+ ss_ttt_Spectrolino = 0x02,
+ ss_ttt_Videolino = 0x03,
+ ss_ttt_SpectroScan = 0x04
+} ss_ttt;
+
+/* White Base Type */
+typedef enum {
+ ss_wbt_Pap = 0x00,
+ ss_wbt_Abs = 0x01
+} ss_wbt;
+
+/* White Reference Position Type */
+typedef enum {
+ ss_wrpt_RefTile1 = 0x00,
+ ss_wrpt_RefTile2 = 0x01
+} ss_wrpt;
+
+/* Zed Koordinate Type Type */
+typedef enum {
+ ss_zkt_SensorUp = 0x00,
+ ss_zkt_SensorDown = 0x01
+} ss_zkt;
+
+/* Spectrolino request and answer types */
+typedef enum {
+ ss_ParameterRequest = 0x00,
+ ss_ParameterAnswer = 0x0B,
+ ss_SlopeRequest = 0x01,
+ ss_SlopeAnswer = 0x0C,
+ ss_DensityRequest = 0x03,
+ ss_DensityAnswer = 0x0E,
+ ss_DmaxRequest = 0x04,
+ ss_DmaxAnswer = 0x0F,
+ ss_SpectrumRequest = 0x05,
+ ss_SpectrumAnswer = 0x10,
+ ss_CRequest = 0x06,
+ ss_CAnswer = 0x11,
+ ss_NewMeasureRequest = 0x07,
+ ss_NewMeasureAnswer = 0x12,
+ ss_NewKeyRequest = 0x08,
+ ss_NewKeyAnswer = 0x13,
+ ss_ParameterDownload = 0x16,
+ ss_SlopeDownload = 0x17,
+ ss_DownloadError = 0x1F,
+ ss_ExecMeasurement = 0x20,
+ ss_ExecWhiteMeasurement = 0x21,
+ ss_ExecRefMeasurement = 0x22,
+ ss_ExecError = 0x25,
+ ss_ActErrorRequest = 0x29,
+ ss_ActErrorAnswer = 0x2F,
+ ss_TargetIdRequest = 0x2B,
+ ss_TargetIdAnswer = 0x31,
+ ss_TargetOnOffStDownload = 0x33,
+ ss_IllumTabRequest = 0x38,
+ ss_IllumTabAnswer = 0x39,
+ ss_IllumTabDownload = 0x3A,
+ ss_DensTabRequest = 0x3B,
+ ss_DensTabAnswer = 0x3C,
+ ss_DensTabDownload = 0x3D,
+ ss_GetValNr = 0x47,
+ ss_ValNrAnswer = 0x48,
+ ss_SetValNr = 0x49,
+ ss_ExecWhiteRefToOrigDat = 0x4A,
+ ss_MeasControlDownload = 0x4D,
+ ss_ResetStatusDownload = 0x5A,
+ ss_MeasControlRequest = 0x5B,
+ ss_MeasControlAnswer = 0x5C,
+ ss_SetMeasurementOutput = 0xB1,
+ ss_WhiteReferenceRequest = 0xB3,
+ ss_WhiteReferenceAnswer = 0xB4,
+ ss_DeviceDataRequest = 0xB5,
+ ss_DeviceDataAnswer = 0xB6,
+ ss_WhiteReferenceDownld = 0xB7,
+ ss_SpecParameterRequest = 0xB8,
+ ss_SpecParameterAnswer = 0xB9,
+ ss_CParameterRequest = 0xBA,
+ ss_CParameterAnswer = 0xBB,
+ ss_DensityParameterAnswer = 0xBC,
+ ss_DensityParameterRequest = 0xBD,
+ ss_Printout = 0xBE,
+ ss_FloatRequest = 0xC0,
+ ss_FloatAnswer = 0xC1,
+ ss_FloatDownload = 0xC2,
+ ss_COMErr = 0x26
+} ss_so_cat;
+
+/* Spectroscan request and answer types */
+typedef enum {
+ ss_ReqPFX = 0xD0, /* Prefix */
+ ss_AnsPFX = 0xD1, /* Prefix */
+ ss_MoveAbsolut = 0x00,
+ ss_MoveRelative = 0x01,
+ ss_MoveHome = 0x02,
+ ss_MoveUp = 0x03,
+ ss_MoveDown = 0x04,
+ ss_OutputActualPosition = 0x05,
+ ss_MoveToWhiteRefPos = 0x06,
+ ss_MoveAndMeasure = 0x07,
+ ss_InitializeDevice = 0x0A,
+ ss_ScanSpectrolino = 0x0B,
+ ss_InitMotorPosition = 0x0C,
+ ss_SetTableMode = 0x0D,
+ ss_SetLightLevel = 0x0E,
+ ss_SetTransmStandbyPos = 0x0F,
+ ss_SetDeviceOnline = 0x10,
+ ss_SetDeviceOffline = 0x11,
+ ss_HoldPaper = 0x12,
+ ss_ReleasePaper = 0x13,
+ ss_SetDigitizingMode = 0x14,
+ ss_OutputDigitizingValues = 0x15,
+ ss_SetKeyAcknowlge = 0x16,
+ ss_ResetKeyAcknowlge = 0x17,
+ ss_ChangeBaudRate = 0x20,
+ ss_ChangeHandshake = 0x21,
+ ss_OutputActualKey = 0x22,
+ ss_OutputLastKey = 0x23,
+ ss_OutputStatus = 0x24,
+ ss_ClearStatus = 0x25,
+ ss_SetSpecialStatus = 0x26,
+ ss_ClearSpecialStatus = 0x27,
+ ss_OutputSpecialStatus = 0x28,
+ ss_OutputType = 0x30,
+ ss_OutputSerialNumber = 0x31,
+ ss_OutputArticleNumber = 0x32,
+ ss_OutputProductionDate = 0x33,
+ ss_OutputSoftwareVersion = 0x34,
+ ss_ErrorAnswer = 0x80,
+ ss_PositionAnswer = 0x81,
+ ss_KeyAnswer = 0x82,
+ ss_StatusAnswer = 0x83,
+ ss_TypeAnswer = 0x90,
+ ss_SerialNumberAnswer = 0x91,
+ ss_ArticleNumberAnswer = 0x92,
+ ss_ProductionDateAnswer = 0x93,
+ ss_SoftwareVersionAnswer = 0x94,
+ ss_SSCOMErr = 0xA0
+} ss_ss_cat;
+
+/* -------------------------- */
+/* Interface declarations */
+
+struct _ss;
+
+/* ------------------------------------------- */
+/* Serialisation for different types functions */
+
+/* QUERY: */
+/* Reset send buffer, and init with start character */
+void ss_init_send(struct _ss *p);
+
+/* Reset send buffer, and add an Spectrolino Request enum */
+void ss_add_soreq(struct _ss *p, int rq);
+
+/* Reset send buffer, and add an SpectroScan Request enum */
+void ss_add_ssreq(struct _ss *p, int rq);
+
+/* Add an int/enum/char into one byte type */
+void ss_add_1(struct _ss *p, int c);
+
+/* Add an int/enum into two byte type */
+void ss_add_2(struct _ss *p, int s);
+
+/* Add an int/enum into four byte type */
+void ss_add_4(struct _ss *p, int i);
+
+/* Add a double into four byte type */
+void ss_add_double(struct _ss *p, double d);
+
+/* Add an ASCII string into the send buffer. */
+/* The string will be padded with nul's up to len. */
+void ss_add_string(struct _ss *p, char *t, int len);
+
+/* - - - - - - - - - - - - - - - - - - - - - */
+/* ANSWER: */
+
+/* Return the first enum from the recieve buffer without removing it. */
+int ss_peek_ans(struct _ss *p);
+
+/* Remove a Spectrolino answer enum from the revieve buffer, */
+/* and check it is correct. */
+void ss_sub_soans(struct _ss *p, int cv);
+
+/* Remove a SpectroScan Prefix and answer enum from the revieve buffer, */
+/* and check it is correct. */
+void ss_sub_ssans(struct _ss *p, int cv);
+
+/* Remove an int/enum/char into one byte type */
+int ss_sub_1(struct _ss *p);
+
+/* Remove an int/enum into two byte type */
+int ss_sub_2(struct _ss *p);
+
+/* Remove an int/enum into four byte type */
+int ss_sub_4(struct _ss *p);
+
+/* Remove a double into four byte type */
+double ss_sub_double(struct _ss *p);
+
+/* Remove an ASCII string from the receive buffer. */
+/* The string will be terminated with a nul, so a buffer */
+/* of len+1 should be provided to return the string in. */
+void ss_sub_string(struct _ss *p, char *t, int len);
+
+/* - - - - - - - - - - - - - - - - - - - - - */
+/* ERROR CODES: */
+
+/* Convert an ss error into an inst_error */
+inst_code ss_inst_err(struct _ss *p);
+
+/* Incorporate a error into the snerr value */
+void ss_incorp_err(struct _ss *p, ss_et se);
+
+/* Incororate Remote Error Set values into snerr value */
+/* Since ss_res is a bit mask, we just prioritize the errors. */
+void ss_incorp_remerrset(struct _ss *p, ss_res es);
+
+/* Incorporate a scan error into the snerr value */
+void ss_incorp_scanerr(struct _ss *p, ss_set se);
+
+/* Incorporate a device communication error into the snerr value */
+void ss_incorp_comerr(struct _ss *p, ss_cet se);
+
+/* - - - - - - - - - - - - - - - - - - - - - */
+/* EXECUTION: */
+
+/* Interpret an icoms error into a SS error */
+int icoms2ss_err(int se);
+
+/* Terminate the send buffer, and then do a */
+/* send/receieve to the device. */
+/* Leave any error in p->snerr */
+void ss_command(struct _ss *p, double tmo);
+
+/* - - - - - - - - - - - - - - - - - - - - */
+/* Device Initialisation and configuration */
+
+/* Reset instrument */
+inst_code so_do_ResetStatusDownload(
+struct _ss *p,
+ss_smt sm /* Init all or all except communications */
+);
+
+/* Load various parameters, such as: */
+/* comm flow control, baud rate, speaker, */
+/* reflective/tranmission/emmission mode, */
+/* custom filter on/off */
+inst_code so_do_MeasControlDownload(
+struct _ss *p,
+ss_ctt ct /* Control */
+);
+
+/* Query various current parameters, such as: */
+/* comm flow control, baud rate, speaker, */
+/* reflective/tranmission/emmission mode, */
+/* custom filter on/off. */
+inst_code so_do_MeasControlRequest(
+struct _ss *p,
+ss_ctt ct, /* Control to query */
+ss_ctt *rct /* Return current state */
+);
+
+/* Queries specific device data */
+inst_code so_do_DeviceDataRequest(
+struct _ss *p,
+char dn[19], /* Return the device name */
+ss_dnot *dno, /* Return device number */
+char pn[9], /* Return the part number */
+unsigned int *sn, /* Return serial number */
+char sv[13] /* Return software version */
+);
+
+/* Query special device data */
+inst_code so_do_TargetIdRequest(
+struct _ss *p,
+char dn[19], /* Return Device Name */
+int *sn, /* Return Serial Number (1-65535) */
+int *sr, /* Return Software Release */
+int *yp, /* Return Year of production (e.g. 1996) */
+int *mp, /* Return Month of production (1-12) */
+int *dp, /* Return Day of production (1-31) */
+int *hp, /* Return Hour of production (0-23) */
+int *np, /* Return Minuite of production (0-59) */
+ss_ttt *tt, /* Return Target Tech Type (SPM/Spectrolino etc.) */
+int *fswl, /* Return First spectral wavelength (nm) */
+int *nosw, /* Return Number of spectral wavelengths */
+int *dpsw /* Return Distance between spectral wavelengths (nm) */
+);
+
+/* - - - - - - - - - - - - - */
+/* Measurement configuration */
+
+/* Query the standard or user definable densitometric tables */
+inst_code so_do_DensTabRequest(
+struct _ss *p,
+ss_dst ds, /* Density standard (ANSI/DIN/User etc.) */
+ss_dst *rds, /* Return Density standard (ANSI/DIN/User etc.) */
+double sp[4][36] /* Return 4 * 36 spectral weighting values */
+);
+
+/* Download user definable densitometric tables */
+inst_code so_do_DensTabDownload(
+struct _ss *p,
+double sp[4][36] /* 4 * 36 spectral weighting values */
+);
+
+/* Set slope values for densitometry */
+inst_code so_do_SlopeDownload(
+struct _ss *p,
+double dv[4] /* Db Dc Dm Dy density values */
+);
+
+/* Query slope values of densitometry */
+inst_code so_do_SlopeRequest(
+struct _ss *p,
+double dv[4] /* Return Db Dc Dm Dy density values */
+);
+
+/* Set the colorimetric parameters */
+inst_code so_do_ParameterDownload(
+struct _ss *p,
+ss_dst ds, /* Density standard (ANSI/DIN etc.) */
+ss_wbt wb, /* White base (Paper/Absolute) */
+ss_ilt it, /* Illuminant type (A/C/D65 etc.) */
+ss_ot ot /* Observer type (2deg/10deg) */
+);
+
+/* Query colorimetric parameters */
+inst_code so_do_ParameterRequest(
+struct _ss *p,
+ss_dst *ds, /* Return Density Standard */
+ss_wbt *wb, /* Return White Base */
+ss_ilt *it, /* Return Illuminant type (A/C/D65/User etc.) */
+ss_ot *ot, /* Return Observer type (2deg/10deg) */
+ss_aft *af /* Return Filter being used (None/Pol/D65/UV/custom */
+);
+
+/* Query the standard or user defined illuminant tables (Colorimetry) */
+inst_code so_do_IllumTabRequest(
+struct _ss *p,
+ss_ilt it, /* Illuminant type (A/C/D65/User etc.) */
+ss_ilt *rit, /* Return Illuminant type (A/C/D65/User etc.) */
+double sp[36] /* Return 36 spectral values */
+);
+
+/* Download user definable illuminant tables (Colorimetry) */
+inst_code so_do_IllumTabDownload(
+struct _ss *p,
+double sp[36] /* 36 spectral values to set */
+);
+
+/* Query for the color temperature of daylight illuminant Dxx */
+inst_code so_do_GetValNr(
+struct _ss *p,
+int *ct /* Return color temperature in deg K/100 */
+);
+
+/* Download user definable illuminant tables (Colorimetry) */
+inst_code so_do_SetValNr(
+struct _ss *p,
+int ct /* Color temperature to set for illuminant Dxx in deg K/100 */
+);
+
+/* Queries the spectra of the white reference for the desired filter */
+inst_code so_do_WhiteReferenceRequest(
+struct _ss *p,
+ss_aft af, /* Filter being queried (None/Pol/D65/UV/custom */
+ss_aft *raf, /* Return filter being queried (None/Pol/D65/UV/custom */
+double sp[36], /* Return 36 spectral values */
+ss_owrt *owr, /* Return original white reference */
+char dtn[19] /* Return name of data table */
+);
+
+/* Load spectra of a user defined white reference for the desired filter. */
+/* A name can be given to the white reference. */
+inst_code so_do_WhiteReferenceDownld(
+struct _ss *p,
+ss_aft af, /* Filter being set (None/Pol/D65/UV/custom */
+double sp[36], /* 36 spectral values being set */
+char dtn[19] /* Name for data table */
+);
+
+/* Query the reference value for the relative photometric (emission) reference value */
+inst_code so_do_FloatRequest(
+struct _ss *p,
+ss_comft comf, /* Choose common float type (PhotometricYRef) */
+ss_comft *rcomf, /* Return common float type (PhotometricYRef) */
+double *comfv /* Return the reference value */
+);
+
+/* Set the reference value for the relative photometric (emission) reference value */
+inst_code so_do_FloatDownload(
+struct _ss *p,
+ss_comft comf, /* Choose common float type (PhotometricYRef) */
+double comfv /* The reference value */
+);
+
+/* - - - - - - */
+/* Calibration */
+
+/* Reset the spectra of the respective white reference to the original data */
+inst_code so_do_ExecWhiteRefToOrigDat(
+struct _ss *p
+);
+
+
+/* Perform a Reference Measurement */
+inst_code so_do_ExecRefMeasurement(
+struct _ss *p,
+ss_mmt mm /* Measurement Mode (Meas/Cal etc.) */
+);
+
+/* Perform a White Measurement - not recommended */
+/* (ExecRefMeasuremen is preferred instead) */
+inst_code so_do_ExecWhiteMeasurement(struct _ss *p);
+
+/* - - - - - - - - - - - - */
+/* Performing measurements */
+
+/* Perform a Measurement */
+inst_code so_do_ExecMeasurement(struct _ss *p);
+
+/* Define automatic output after each measurement */
+/* [Note that dealing with the resulting measurement replies */
+/* isn't directly supported, currently.] */
+inst_code so_do_SetMeasurementOutput(
+struct _ss *p,
+ss_ost os, /* Type of output to request */
+ss_os o /* bitmask of output */
+);
+
+/* - - - - - - - - */
+/* Getting results */
+
+/* Query Density measurement results and associated parameters */
+inst_code so_do_DensityParameterRequest(
+struct _ss *p,
+ss_cst *rct, /* Return Color Type (Lab/XYZ etc.) */
+double dv[4], /* Return Db Dc Dm Dy density values */
+ss_sdft *sdf, /* Return Standard Density Filter (Db/Dc/Dm/Dy) */
+ss_rvt *rv, /* Return Reference Valid Flag */
+ss_aft *af, /* Return filter being used (None/Pol/D65/UV/custom */
+ss_wbt *wb, /* Return white base (Paper/Absolute) */
+ss_dst *ds, /* Return Density standard (ANSI/DIN/User etc.) */
+ss_ilt *rit, /* Return Illuminant type (A/C/D65/User etc.) */
+ss_ot *ot /* Return Observer type (2deg/10deg) */
+);
+
+/* Query Densitometric measurement values - not recommended */
+/* (DensityParameterRequest is preferred instead) */
+inst_code so_do_DensityRequest(
+struct _ss *p,
+double dv[4], /* Return Db Dc Dm Dy density values */
+ss_sdft *sdf, /* Return Standard Density Filter (Db/Dc/Dm/Dy) */
+ss_rvt *rv /* Return Reference Valid */
+);
+
+/* Query maximum density reading */
+inst_code so_do_DmaxRequest(
+struct _ss *p,
+double *Dmax, /* Return Value of Maximum Density */
+int *lambda, /* Return wavelength where maximum density was found */
+ss_dmot *dmo, /* Return Dmax OK flag. */
+ss_rvt *rv /* Return Reference Valid Flag */
+);
+
+/* Query Color measurement results and associated parameters */
+inst_code so_do_CParameterRequest(
+struct _ss *p,
+ss_cst ct, /* Choose Color Type (Lab/XYZ etc.) */
+ss_cst *rct, /* Return Color Type (Lab/XYZ etc.) */
+double cv[3], /* Return 3 color values */
+ss_rvt *rv, /* Return Reference Valid Flag */
+ss_aft *af, /* Return filter being used (None/Pol/D65/UV/custom) */
+ss_wbt *wb, /* Return white base (Paper/Absolute) */
+ss_ilt *it, /* Return Illuminant type (A/C/D65/User etc.) */
+ss_ot *ot /* Return Observer type (2deg/10deg) */
+);
+
+/* Query Colorimetric measurement results - not recommended */
+/* (CParameterRequest is prefered instead) */
+inst_code so_do_CRequest(
+struct _ss *p,
+ss_cst *ct, /* Return Color Type (Lab/XYZ etc.) */
+double *cv[3], /* Return 3 color values */
+ss_rvt *rv /* Return Reference Valid Flag */
+);
+
+/* Query Spectral measurement results and associated parameters */
+inst_code so_do_SpecParameterRequest(
+struct _ss *p,
+ss_st st, /* Choose Spectrum Type (Reflectance/Density) */
+ss_st *rst, /* Return Spectrum Type (Reflectance/Density) */
+double sp[36], /* Return 36 spectral values */
+ss_rvt *rv, /* Return Reference Valid Flag */
+ss_aft *af, /* Return filter being used (None/Pol/D65/UV/custom */
+ss_wbt *wb /* Return white base (Paper/Absolute) */
+);
+
+/* Query Spectral measurement results - not recommended */
+/* (SpecParameterRequest is preferred instead) */
+inst_code so_do_SpectrumRequest(
+struct _ss *p,
+ss_st *st, /* Return Spectrum Type (Reflectance/Density) */
+double sp[36], /* Return 36 spectral values */
+ss_rvt *rv /* Return Reference Valid Flag */
+);
+
+/* - - - - - - */
+/* Miscelanious */
+
+/* Query whether a new measurement was performed since the last accestruct _ss */
+inst_code so_do_NewMeasureRequest(
+struct _ss *p,
+ss_nmt *nm /* Return New Measurement (None/Meas/White etc.) */
+);
+
+/* Query whether a key was pressed since the last accestruct _ss */
+inst_code so_do_NewKeyRequest(
+struct _ss *p,
+ss_nkt *nk, /* Return whether a new key was pressed */
+ss_ks *k /* Return the key that was pressed (none/meas) */
+);
+
+/* Query for the general error status */
+inst_code so_do_ActErrorRequest(
+struct _ss *p
+);
+
+/* Set Target On/Off status (Enables/Disables measurement key ?) */
+inst_code so_do_TargetOnOffStDownload(
+struct _ss *p,
+ss_toost oo /* Activated/Deactivated */
+);
+
+/* =========================================== */
+/* SpectroScan/T specific commands and queries */
+
+/* - - - - - - - - - - - - - - - - - - - - */
+/* Device Initialisation and configuration */
+
+/* Initialise the device. Scans the Spectrolino */
+/* (Doesn't work when device is offline ) */
+inst_code ss_do_ScanInitializeDevice(struct _ss *p);
+
+/* Establish communications between the SpectroScan and Spectrolino */
+/* at the highest possible baud rate. */
+/* (Doesn't work when device is offline ) */
+inst_code ss_do_ScanSpectrolino(struct _ss *p);
+
+/* Establish the zero position of the motors and set the position to 0,0 */
+/* (Doesn't work when device is offline ) */
+inst_code ss_do_InitMotorPosition(struct _ss *p);
+
+/* Change the SpectroScan baud rate */
+inst_code ss_do_ChangeBaudRate(
+struct _ss *p,
+ss_bt br /* Baud rate (110 - 57600) */
+);
+
+/* Change the SpectroScan handshaking mode. */
+inst_code ss_do_ChangeHandshake(
+struct _ss *p,
+ss_hst hs /* Handshake type (None/XonXoff/HW) */
+);
+
+/* Query the type of XY table */
+inst_code ss_do_OutputType(
+struct _ss *p,
+char dt[19] /* Return Device Type ("SpectroScan " or "SpectroScanT") */
+);
+
+/* Query the serial number of the XY table */
+inst_code ss_do_OutputSerialNumber(
+struct _ss *p,
+unsigned int *sn /* Return serial number */
+);
+
+/* Query the part number of the XY table */
+inst_code ss_do_OutputArticleNumber(
+struct _ss *p,
+char pn[9] /* Return Part Number */
+);
+
+/* Query the production date of the XY table */
+inst_code ss_do_OutputProductionDate(
+struct _ss *p,
+int *yp, /* Return Year of production (e.g. 1996) */
+int *mp, /* Return Month of production (1-12) */
+int *dp /* Return Day of production (1-31) */
+);
+
+/* Query the Software Version of the XY table */
+inst_code ss_do_OutputSoftwareVersion(
+struct _ss *p,
+char sv[13] /* Return Software Version */
+);
+
+/* - - - - - - - - - - - - - */
+/* Measurement configuration */
+
+/* Set the SpectroScanT to reflectance or transmission. */
+/* The Spectrolino is also automatically set to the corresponding mode. */
+/* (Doesn't work when device is offline ) */
+inst_code ss_do_SetTableMode(
+struct _ss *p,
+ss_tmt tm /* Table mode (Reflectance/Transmission) */
+);
+
+/* - - - - - - - - - - - - - */
+/* Table operation */
+
+/* Set the SpectrScan to online. All moving keys are disabled. */
+/* (Only valid when device is in reflectance mode.) */
+inst_code ss_do_SetDeviceOnline(struct _ss *p);
+
+/* Set the SpectrScan to offline. All moving keys are enabled. */
+/* (Only valid when device is in reflectance mode.) */
+/* (All remote commands to move the device will be ignored.) */
+inst_code ss_do_SetDeviceOffline(struct _ss *p);
+
+/* Enable electrostatic paper hold. */
+/* (Not valid when device is offline) */
+inst_code ss_do_HoldPaper(struct _ss *p);
+
+/* Disable electrostatic paper hold. */
+/* (Not valid when device is offline) */
+inst_code ss_do_ReleasePaper(struct _ss *p);
+
+/* - - - - - - */
+/* Positioning */
+
+/* Move either the sight or sensor to an absolute position. */
+/* (Doesn't work when device is offline or transmissioin mode.) */
+inst_code ss_do_MoveAbsolut(
+struct _ss *p,
+ss_rt r, /* Reference (Sensor/Sight) */
+double x, /* X coord in mm, 0-310.0, accurate to 0.1mm */
+double y /* Y coord in mm, 0-230.0, accurate to 0.1mm */
+);
+
+/* Move relative to current position. */
+/* (Doesn't work when device is offline or transmissioin mode.) */
+inst_code ss_do_MoveRelative(
+struct _ss *p,
+double x, /* X distance in mm, 0-310.0, accurate to 0.1mm */
+double y /* Y distance in mm, 0-230.0, accurate to 0.1mm */
+);
+
+/* Move to the home position (== 0,0). */
+/* (Doesn't work when device is offline or transmissioin mode.) */
+inst_code ss_do_MoveHome(
+struct _ss *p
+);
+
+/* Move to the sensor up. */
+/* (Doesn't work when device is offline or transmissioin mode.) */
+inst_code ss_do_MoveUp(
+struct _ss *p
+);
+
+/* Move to the sensor down. */
+/* (Doesn't work when device is offline or transmission mode.) */
+inst_code ss_do_MoveDown(
+struct _ss *p
+);
+
+/* Query the current absolute position of the sensor or sight. */
+/* (Doesn't work when device is offline or transmissioin mode.) */
+inst_code ss_do_OutputActualPosition(
+struct _ss *p,
+ss_rt r, /* Reference (Sensor/Sight) */
+ss_rt *rr, /* Return reference (Sensor/Sight) */
+double *x, /* Return the X coord in mm, 0-310.0, accurate to 0.1mm */
+double *y, /* Return the Y coord in mm, 0-230.0, accurate to 0.1mm */
+ss_zkt *zk /* Return the Z coordinate (Up/Down) */
+);
+
+/* Move to the white reference position */
+/* (Doesn't work when device is offline or transmissioin mode.) */
+inst_code ss_do_MoveToWhiteRefPos(
+struct _ss *p,
+ss_wrpt wrp /* White Reference Position (Tile1/Tile2) */
+);
+
+/* - - - - - - - - - - - - */
+/* Performing measurements */
+
+/* Move the sensor to an absolute position, move the */
+/* sensor down, execute a measurement, move the head up, */
+/* and return spectral measuring results. */
+inst_code ss_do_MoveAndMeasure(
+struct _ss *p,
+double x, /* X coord in mm, 0-310.0, accurate to 0.1mm */
+double y, /* Y coord in mm, 0-230.0, accurate to 0.1mm */
+double sp[36], /* Return 36 spectral values */
+ss_rvt *rv /* Return Reference Valid Flag */
+);
+
+/* - - - - - - */
+/* Miscelanious */
+
+/* Set the SpectroScanT transmission light level during standby. */
+/* (Only valid on SpectroScanT in transmission mode) */
+inst_code ss_do_SetLightLevel(
+struct _ss *p,
+ss_llt ll /* Transmission light level (Off/Surround/Low) */
+);
+
+/* Set tranmission standby position. */
+/* (Only valid on SpectroScanT in transmission mode) */
+inst_code ss_do_SetTransmStandbyPos(
+struct _ss *p,
+ss_rt r, /* Reference (Sensor/Sight) */
+double x, /* X coord in mm, 0-310.0, accurate to 0.1mm */
+double y /* Y coord in mm, 0-230.0, accurate to 0.1mm */
+);
+
+/* Set digitizing mode. Clears digitizing buffer, */
+/* and puts the device offline. The user can move */
+/* and enter positions. */
+inst_code ss_do_SetDigitizingMode(struct _ss *p);
+
+/* Get last digitized position from memory. */
+inst_code ss_do_OutputDigitizingValues(
+struct _ss *p,
+ss_rt r, /* Reference (Sensor/Sight) */
+ss_rt *rr, /* Return reference (Sensor/Sight) */
+int *nrp,/* Return the number of remaining positions in memory. */
+double *x, /* Return the X coord in mm, 0-310.0, accurate to 0.1mm */
+double *y, /* Return the Y coord in mm, 0-230.0, accurate to 0.1mm */
+ss_zkt *zk /* Return the Z coordinate (Up/Down) */
+);
+
+/* Turn on key aknowledge mode. Causes a KeyAnswer message */
+/* to be generated whenever a key is pressed. */
+/* (KetAnswer isn't well supported here ?) */
+inst_code ss_do_SetKeyAcknowlge(struct _ss *p);
+
+/* Turn off key aknowledge mode. */
+inst_code ss_do_ResetKeyAcknowlge(struct _ss *p);
+
+/* Query the keys that are currently pressed */
+inst_code ss_do_OutputActualKey(
+struct _ss *p,
+ss_sks *sk, /* Return Scan Key Set (Key bitmask) */
+ss_ptt *pt /* Return press time (Short/Long) */
+);
+
+/* Query the keys that were last pressed */
+inst_code ss_do_OutputLastKey(
+struct _ss *p,
+ss_sks *sk, /* Return Scan Key bitmask (Keys) */
+ss_ptt *pt /* Return press time (Short/Long) */
+);
+
+/* Query the status register */
+inst_code ss_do_OutputStatus(
+struct _ss *p,
+ss_sts *st /* Return status bitmask (Enter key/Online/Digitize/KeyAck/Paper) */
+);
+
+/* Clear the status register */
+inst_code ss_do_ClearStatus(
+struct _ss *p,
+ss_sts st /* Status to reset (Enter key/Online/Digitize/KeyAck/Paper) */
+);
+
+/* Set the special status register */
+/* (Set to all 0 on reset) */
+inst_code ss_do_SetSpecialStatus(
+struct _ss *p,
+ss_sss sss /* Status bits to set (HeadDwnOnMv/TableInTransMode/AllLightsOn) */
+);
+
+/* Clear the special status register */
+inst_code ss_do_ClearSpecialStatus(
+struct _ss *p,
+ss_sss sss /* Status bits to clear (HeadDwnOnMv/TableInTransMode/AllLightsOn) */
+);
+
+/* Query the special status register */
+inst_code ss_do_OutputSpecialStatus(
+struct _ss *p,
+ss_sss *sss /* Return Special Status bits */
+);
+
+#define SS_IMP_H
+#endif /* SS_IMP_H */
diff --git a/spectro/strange.cal b/spectro/strange.cal
new file mode 100644
index 0000000..23c1e87
--- /dev/null
+++ b/spectro/strange.cal
@@ -0,0 +1,275 @@
+CAL
+
+DESCRIPTOR "Argyll Device Calibration Curves"
+ORIGINATOR "Argyll synthcal"
+CREATED "Sat Mar 09 18:33:22 2013"
+KEYWORD "DEVICE_CLASS"
+DEVICE_CLASS "DISPLAY"
+KEYWORD "COLOR_REP"
+COLOR_REP "RGB"
+
+KEYWORD "RGB_I"
+NUMBER_OF_FIELDS 4
+BEGIN_DATA_FORMAT
+RGB_I RGB_R RGB_G RGB_B
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 256
+BEGIN_DATA
+0.0000 0.0000 0.0000 0.0000
+3.9216e-003 5.6752e-005 0.011879 0.018606
+7.8431e-003 1.8439e-004 0.020682 0.030226
+0.011765 3.6735e-004 0.028607 0.040147
+0.015686 5.9908e-004 0.036009 0.049103
+0.019608 8.7544e-004 0.043047 0.057404
+0.023529 1.1935e-003 0.049807 0.065218
+0.027451 1.5511e-003 0.056344 0.072650
+0.031373 1.9464e-003 0.062696 0.079768
+0.035294 2.3779e-003 0.068891 0.086623
+0.039216 2.8443e-003 0.074949 0.093253
+0.043137 3.3446e-003 0.080888 0.099687
+0.047059 3.8778e-003 0.086719 0.10595
+0.050980 4.4431e-003 0.092453 0.11205
+0.054902 5.0396e-003 0.098100 0.11802
+0.058824 5.6668e-003 0.10367 0.12386
+0.062745 6.3239e-003 0.10916 0.12958
+0.066667 7.0104e-003 0.11458 0.13520
+0.070588 7.7258e-003 0.11995 0.14072
+0.074510 8.4696e-003 0.12525 0.14615
+0.078431 9.2413e-003 0.13049 0.15149
+0.082353 0.010040 0.13569 0.15675
+0.086275 0.010867 0.14083 0.16194
+0.090196 0.011720 0.14593 0.16706
+0.094118 0.012599 0.15099 0.17211
+0.098039 0.013504 0.15600 0.17710
+0.10196 0.014436 0.16097 0.18203
+0.10588 0.015392 0.16590 0.18690
+0.10980 0.016374 0.17080 0.19172
+0.11373 0.017380 0.17567 0.19649
+0.11765 0.018411 0.18049 0.20121
+0.12157 0.019467 0.18529 0.20588
+0.12549 0.020546 0.19006 0.21051
+0.12941 0.021650 0.19480 0.21509
+0.13333 0.022777 0.19950 0.21963
+0.13725 0.023927 0.20418 0.22414
+0.14118 0.025101 0.20884 0.22860
+0.14510 0.026298 0.21347 0.23303
+0.14902 0.027518 0.21807 0.23742
+0.15294 0.028760 0.22265 0.24177
+0.15686 0.030025 0.22720 0.24610
+0.16078 0.031312 0.23174 0.25039
+0.16471 0.032621 0.23625 0.25465
+0.16863 0.033953 0.24074 0.25888
+0.17255 0.035306 0.24521 0.26308
+0.17647 0.036681 0.24965 0.26725
+0.18039 0.038077 0.25408 0.27139
+0.18431 0.039495 0.25849 0.27551
+0.18824 0.040935 0.26288 0.27960
+0.19216 0.042395 0.26725 0.28366
+0.19608 0.043876 0.27161 0.28770
+0.20000 0.045378 0.27595 0.29172
+0.20392 0.046901 0.28027 0.29571
+0.20784 0.048445 0.28457 0.29968
+0.21176 0.050009 0.28886 0.30363
+0.21569 0.051594 0.29313 0.30755
+0.21961 0.053198 0.29738 0.31146
+0.22353 0.054824 0.30163 0.31534
+0.22745 0.056469 0.30585 0.31920
+0.23137 0.058134 0.31006 0.32304
+0.23529 0.059819 0.31426 0.32687
+0.23922 0.061523 0.31844 0.33067
+0.24314 0.063248 0.32261 0.33446
+0.24706 0.064992 0.32677 0.33822
+0.25098 0.066755 0.33091 0.34197
+0.25490 0.068538 0.33504 0.34570
+0.25882 0.070340 0.33916 0.34942
+0.26275 0.072162 0.34326 0.35312
+0.26667 0.074002 0.34736 0.35680
+0.27059 0.075862 0.35144 0.36046
+0.27451 0.077740 0.35551 0.36411
+0.27843 0.079638 0.35956 0.36774
+0.28235 0.081554 0.36361 0.37136
+0.28627 0.083489 0.36764 0.37496
+0.29020 0.085442 0.37167 0.37855
+0.29412 0.087415 0.37568 0.38213
+0.29804 0.089405 0.37968 0.38569
+0.30196 0.091414 0.38367 0.38923
+0.30588 0.093442 0.38765 0.39276
+0.30980 0.095487 0.39162 0.39628
+0.31373 0.097551 0.39559 0.39979
+0.31765 0.099633 0.39954 0.40328
+0.32157 0.10173 0.40348 0.40676
+0.32549 0.10385 0.40741 0.41022
+0.32941 0.10599 0.41133 0.41368
+0.33333 0.10814 0.41524 0.41712
+0.33725 0.11031 0.41915 0.42055
+0.34118 0.11250 0.42304 0.42396
+0.34510 0.11471 0.42693 0.42737
+0.34902 0.11693 0.43080 0.43076
+0.35294 0.11918 0.43467 0.43414
+0.35686 0.12144 0.43853 0.43752
+0.36078 0.12371 0.44238 0.44088
+0.36471 0.12601 0.44623 0.44422
+0.36863 0.12832 0.45006 0.44756
+0.37255 0.13065 0.45389 0.45089
+0.37647 0.13300 0.45770 0.45421
+0.38039 0.13536 0.46151 0.45751
+0.38431 0.13774 0.46532 0.46081
+0.38824 0.14014 0.46911 0.46410
+0.39216 0.14255 0.47290 0.46737
+0.39608 0.14499 0.47668 0.47064
+0.40000 0.14743 0.48045 0.47390
+0.40392 0.14990 0.48421 0.47715
+0.40784 0.15238 0.48797 0.48038
+0.41176 0.15488 0.49172 0.48361
+0.41569 0.15740 0.49546 0.48683
+0.41961 0.15993 0.49920 0.49004
+0.42353 0.16248 0.50293 0.49324
+0.42745 0.16505 0.50665 0.49644
+0.43137 0.16763 0.51037 0.49962
+0.43529 0.17023 0.51407 0.50279
+0.43922 0.17284 0.51778 0.50596
+0.44314 0.17547 0.52147 0.50912
+0.44706 0.17812 0.52516 0.51227
+0.45098 0.18079 0.52884 0.51541
+0.45490 0.18347 0.53252 0.51854
+0.45882 0.18616 0.53619 0.52167
+0.46275 0.18888 0.53985 0.52479
+0.46667 0.19161 0.54351 0.52789
+0.47059 0.19435 0.54716 0.53100
+0.47451 0.19711 0.55080 0.53409
+0.47843 0.19989 0.55444 0.53718
+0.48235 0.20268 0.55807 0.54025
+0.48627 0.20549 0.56170 0.54332
+0.49020 0.20832 0.56532 0.54639
+0.49412 0.21116 0.56894 0.54944
+0.49804 0.21402 0.57255 0.55249
+0.50196 0.21689 0.57615 0.55553
+0.50588 0.21978 0.57975 0.55857
+0.50980 0.22268 0.58334 0.56160
+0.51373 0.22560 0.58693 0.56462
+0.51765 0.22854 0.59051 0.56763
+0.52157 0.23149 0.59409 0.57064
+0.52549 0.23445 0.59766 0.57364
+0.52941 0.23744 0.60122 0.57663
+0.53333 0.24043 0.60478 0.57962
+0.53725 0.24345 0.60834 0.58260
+0.54118 0.24648 0.61189 0.58557
+0.54510 0.24952 0.61543 0.58854
+0.54902 0.25258 0.61897 0.59150
+0.55294 0.25565 0.62251 0.59445
+0.55686 0.25874 0.62603 0.59740
+0.56078 0.26185 0.62956 0.60034
+0.56471 0.26497 0.63308 0.60328
+0.56863 0.26811 0.63659 0.60621
+0.57255 0.27126 0.64010 0.60913
+0.57647 0.27442 0.64361 0.61205
+0.58039 0.27760 0.64711 0.61496
+0.58431 0.28080 0.65060 0.61787
+0.58824 0.28401 0.65410 0.62077
+0.59216 0.28724 0.65758 0.62366
+0.59608 0.29048 0.66106 0.62655
+0.60000 0.29373 0.66454 0.62943
+0.60392 0.29701 0.66801 0.63231
+0.60784 0.30029 0.67148 0.63518
+0.61176 0.30359 0.67494 0.63805
+0.61569 0.30691 0.67840 0.64091
+0.61961 0.31024 0.68186 0.64376
+0.62353 0.31358 0.68531 0.64661
+0.62745 0.31694 0.68875 0.64945
+0.63137 0.32032 0.69220 0.65229
+0.63529 0.32371 0.69563 0.65513
+0.63922 0.32711 0.69907 0.65795
+0.64314 0.33053 0.70249 0.66078
+0.64706 0.33397 0.70592 0.66360
+0.65098 0.33741 0.70934 0.66641
+0.65490 0.34088 0.71276 0.66922
+0.65882 0.34435 0.71617 0.67202
+0.66275 0.34785 0.71958 0.67482
+0.66667 0.35135 0.72298 0.67761
+0.67059 0.35487 0.72638 0.68040
+0.67451 0.35841 0.72978 0.68318
+0.67843 0.36196 0.73317 0.68596
+0.68235 0.36552 0.73656 0.68873
+0.68627 0.36910 0.73994 0.69150
+0.69020 0.37269 0.74332 0.69426
+0.69412 0.37630 0.74670 0.69702
+0.69804 0.37992 0.75007 0.69977
+0.70196 0.38356 0.75344 0.70252
+0.70588 0.38721 0.75681 0.70527
+0.70980 0.39087 0.76017 0.70801
+0.71373 0.39455 0.76353 0.71075
+0.71765 0.39824 0.76688 0.71348
+0.72157 0.40195 0.77023 0.71620
+0.72549 0.40567 0.77358 0.71893
+0.72941 0.40940 0.77692 0.72164
+0.73333 0.41315 0.78026 0.72436
+0.73725 0.41692 0.78360 0.72707
+0.74118 0.42069 0.78693 0.72977
+0.74510 0.42448 0.79026 0.73247
+0.74902 0.42829 0.79359 0.73517
+0.75294 0.43211 0.79691 0.73786
+0.75686 0.43594 0.80023 0.74055
+0.76078 0.43979 0.80354 0.74323
+0.76471 0.44365 0.80686 0.74591
+0.76863 0.44752 0.81016 0.74859
+0.77255 0.45141 0.81347 0.75126
+0.77647 0.45531 0.81677 0.75393
+0.78039 0.45923 0.82007 0.75659
+0.78431 0.46316 0.82336 0.75925
+0.78824 0.46710 0.82666 0.76191
+0.79216 0.47106 0.82994 0.76456
+0.79608 0.47503 0.83323 0.76721
+0.80000 0.47902 0.83651 0.76985
+0.80392 0.48302 0.83979 0.77249
+0.80784 0.48703 0.84307 0.77512
+0.81176 0.49105 0.84634 0.77776
+0.81569 0.49509 0.84961 0.78038
+0.81961 0.49915 0.85287 0.78301
+0.82353 0.50321 0.85614 0.78563
+0.82745 0.50729 0.85940 0.78825
+0.83137 0.51139 0.86265 0.79086
+0.83529 0.51550 0.86591 0.79347
+0.83922 0.51962 0.86916 0.79608
+0.84314 0.52375 0.87241 0.79868
+0.84706 0.52790 0.87565 0.80128
+0.85098 0.53206 0.87889 0.80387
+0.85490 0.53624 0.88213 0.80646
+0.85882 0.54042 0.88537 0.80905
+0.86275 0.54463 0.88860 0.81163
+0.86667 0.54884 0.89183 0.81421
+0.87059 0.55307 0.89506 0.81679
+0.87451 0.55731 0.89828 0.81937
+0.87843 0.56157 0.90150 0.82194
+0.88235 0.56584 0.90472 0.82450
+0.88627 0.57012 0.90793 0.82707
+0.89020 0.57441 0.91115 0.82963
+0.89412 0.57872 0.91436 0.83218
+0.89804 0.58304 0.91756 0.83474
+0.90196 0.58738 0.92077 0.83729
+0.90588 0.59173 0.92397 0.83983
+0.90980 0.59609 0.92717 0.84238
+0.91373 0.60046 0.93036 0.84492
+0.91765 0.60485 0.93356 0.84745
+0.92157 0.60925 0.93675 0.84999
+0.92549 0.61366 0.93993 0.85252
+0.92941 0.61809 0.94312 0.85504
+0.93333 0.62253 0.94630 0.85757
+0.93725 0.62698 0.94948 0.86009
+0.94118 0.63145 0.95266 0.86261
+0.94510 0.63593 0.95583 0.86512
+0.94902 0.64042 0.95900 0.86763
+0.95294 0.64493 0.96217 0.87014
+0.95686 0.64945 0.96534 0.87264
+0.96078 0.65398 0.96850 0.87515
+0.96471 0.65852 0.97166 0.87765
+0.96863 0.66308 0.97482 0.88014
+0.97255 0.66765 0.97798 0.88263
+0.97647 0.67223 0.98113 0.88512
+0.98039 0.67683 0.98428 0.88761
+0.98431 0.68144 0.98743 0.89009
+0.98824 0.68606 0.99058 0.89258
+0.99216 0.69069 0.99372 0.89505
+0.99608 0.69534 0.99686 0.89753
+1.0000 0.70000 1.0000 0.90000
+END_DATA
diff --git a/spectro/synthcal.c b/spectro/synthcal.c
new file mode 100644
index 0000000..75d18be
--- /dev/null
+++ b/spectro/synthcal.c
@@ -0,0 +1,350 @@
+
+/*
+ * Argyll Color Correction System
+ * Create a linear display calibration file.
+ *
+ * Author: Graeme W. Gill
+ * Date: 15/11/2005
+ *
+ * Copyright 1996 - 2007 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+#undef DEBUG
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/types.h>
+#include <time.h>
+#include <string.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "cgats.h"
+#include "xicc.h"
+
+#include "sort.h"
+
+#include <stdarg.h>
+
+#if defined (NT)
+#include <conio.h>
+#endif
+
+void
+usage(int level) {
+ int i;
+ fprintf(stderr,"Create a synthetic calibration file, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ fprintf(stderr,"usage: synthcal [-options] outfile\n");
+ fprintf(stderr," -t N i = input, o = output, d = display (default)\n");
+ fprintf(stderr," -d col_comb choose colorant combination from the following (default 3):\n");
+ for (i = 0; ; i++) {
+ char *desc;
+ if (icx_enum_colorant_comb(i, &desc) == 0)
+ break;
+ fprintf(stderr," %d: %s\n",i,desc);
+ }
+ fprintf(stderr," -D colorant Add or delete colorant from combination:\n");
+ if (level == 0)
+ fprintf(stderr," (Use -?? to list known colorants)\n");
+ else {
+ fprintf(stderr," %d: %s\n",0,"Additive");
+ for (i = 0; ; i++) {
+ char *desc;
+ if (icx_enum_colorant(i, &desc) == 0)
+ break;
+ fprintf(stderr," %d: %s\n",i+1,desc);
+ }
+ }
+ fprintf(stderr," -o o1,o2,o3, Set non-linear curve offset (default 0.0)\n");
+ fprintf(stderr," -s s1,s2,s3, Set non-linear curve scale (default 1.0)\n");
+ fprintf(stderr," -p p1,p2,p3, Set non-linear curve powers (default 1.0)\n");
+ fprintf(stderr," -E description Set the profile dEscription string\n");
+ fprintf(stderr," outfile Base name for output .cal file\n");
+ exit(1);
+ }
+
+int main(int argc, char *argv[])
+{
+ int fa,nfa; /* current argument we're looking at */
+ int j;
+ int verb = 0;
+ char outname[MAXNAMEL+1] = { 0 }; /* Output cgats file base name */
+ char *profDesc = NULL; /* Description */
+ int devtype = 2; /* debice type, 0 = in, 1 = out, 2 = display */
+ inkmask devmask = 0; /* ICX ink mask of device space */
+ int devchan; /* Number of chanels in device space */
+ char *ident; /* Ink combination identifier (includes possible leading 'i') */
+ char *bident; /* Base ink combination identifier */
+ double off[MAX_CHAN]; /* Output offset */
+ double sca[MAX_CHAN]; /* Output scale */
+ double gam[MAX_CHAN]; /* Gamma applied */
+
+ for (j = 0; j < MAX_CHAN; j++)
+ off[j] = 0.0, sca[j] = gam[j] = 1.0;
+
+ error_program = "synthcal";
+ if (argc <= 1)
+ usage(0);
+
+ /* Process the arguments */
+ for(fa = 1;fa < argc;fa++) {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') /* Look for any flags */
+ {
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1) < argc) {
+ if (argv[fa+1][0] != '-')
+ {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?' || argv[fa][1] == '-') {
+ if (argv[fa][2] == '?' || argv[fa][2] == '-')
+ usage(1);
+ usage(0);
+ }
+
+ else if (argv[fa][1] == 'v')
+ verb = 1;
+
+ /* Select the device type */
+ else if (argv[fa][1] == 't' || argv[fa][1] == 'T') {
+ fa = nfa;
+ if (na == NULL) usage(0);
+ if (na[0] == 'i' || na[0] == 'I')
+ devtype = 0;
+ else if (na[0] == 'o' || na[0] == 'O')
+ devtype = 1;
+ else if (na[0] == 'd' || na[0] == 'D')
+ devtype = 2;
+ else
+ usage(0);
+ }
+
+ /* Select the ink enumeration */
+ else if (argv[fa][1] == 'd') {
+ int ix;
+ fa = nfa;
+ if (na == NULL) usage(0);
+ ix = atoi(na);
+ if (ix == 0 && na[0] != '0')
+ usage(0);
+ if ((devmask = icx_enum_colorant_comb(ix, NULL)) == 0)
+ usage(0);
+ }
+
+ /* Toggle the colorant in ink combination */
+ else if (argv[fa][1] == 'D') {
+ int ix, tmask;
+ fa = nfa;
+ if (na == NULL) usage(0);
+ ix = atoi(na);
+ if (ix == 0 && na[0] != '0')
+ usage(0);
+ if (ix == 0)
+ tmask = ICX_ADDITIVE;
+ else
+ if ((tmask = icx_enum_colorant(ix-1, NULL)) == 0)
+ usage(0);
+ devmask ^= tmask;
+ }
+
+ /* curve offset */
+ else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') {
+ fa = nfa;
+ if (na == NULL) usage(0);
+ for (j = 0; j < MAX_CHAN; j++) {
+ int i;
+ for (i = 0; ; i++) {
+ if (na[i] == ','){
+ na[i] = '\000';
+ break;
+ }
+ if (na[i] == '\000') {
+ i = 0;
+ break;
+ }
+ }
+ off[j] = atof(na);
+ if (i == 0)
+ break;
+ na += i+1;
+ }
+ }
+
+ /* curve scale */
+ else if (argv[fa][1] == 's' || argv[fa][1] == 'S') {
+ fa = nfa;
+ if (na == NULL) usage(0);
+ for (j = 0; j < MAX_CHAN; j++) {
+ int i;
+ for (i = 0; ; i++) {
+ if (na[i] == ','){
+ na[i] = '\000';
+ break;
+ }
+ if (na[i] == '\000') {
+ i = 0;
+ break;
+ }
+ }
+ sca[j] = atof(na);
+ if (i == 0)
+ break;
+ na += i+1;
+ }
+ }
+
+ /* curve power */
+ else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') {
+ fa = nfa;
+ if (na == NULL) usage(0);
+ for (j = 0; j < MAX_CHAN; j++) {
+ int i;
+ for (i = 0; ; i++) {
+ if (na[i] == ','){
+ na[i] = '\000';
+ break;
+ }
+ if (na[i] == '\000') {
+ i = 0;
+ break;
+ }
+ }
+ gam[j] = atof(na);
+ if (i == 0)
+ break;
+ na += i+1;
+ }
+ }
+
+ /* Profile Description */
+ else if (argv[fa][1] == 'E') {
+ fa = nfa;
+ if (na == NULL) usage(0);
+ profDesc = na;
+ }
+
+
+ else
+ usage(0);
+ } else
+ break;
+ }
+
+ /* Get the file name argument */
+ if (fa >= argc || argv[fa][0] == '-') usage(0);
+ strcpy(outname,argv[fa]);
+ if (strlen(outname) < 4 || strcmp(".cal",outname + strlen(outname)-4) != 0)
+ strcat(outname,".cal");
+
+ /* Implement defaults */
+ if (devmask == 0) {
+ if (devtype == 0 || devtype == 2)
+ devmask = ICX_RGB;
+ else
+ devmask = ICX_CMYK;
+ }
+
+ ident = icx_inkmask2char(devmask, 1);
+ bident = icx_inkmask2char(devmask, 0);
+ devchan = icx_noofinks(devmask);
+
+ if (verb) {
+ if (devtype == 0)
+ printf("Device type: input\n");
+ else if (devtype == 1)
+ printf("Device type: output\n");
+ else
+ printf("Device type: display\n");
+
+ printf("Colorspace: %s\n", ident);
+
+ printf("Curve parameters:\n");
+ for (j = 0; j < devchan; j++)
+ printf("off[%d] = %f, sca[%d] = %f, gam[%d] = %f\n",j,off[j],j,sca[j],j, gam[j]);
+ }
+
+ /* Write out the resulting calibration file */
+ {
+ int i, j, calres = 256; /* 256 steps in calibration */
+ cgats *ocg; /* output cgats structure */
+ time_t clk = time(0);
+ struct tm *tsp = localtime(&clk);
+ char *atm = asctime(tsp); /* Ascii time */
+ cgats_set_elem *setel; /* Array of set value elements */
+ int nsetel = 0;
+ char buf[200];
+
+ ocg = new_cgats(); /* Create a CGATS structure */
+ ocg->add_other(ocg, "CAL"); /* our special type is Calibration file */
+
+ ocg->add_table(ocg, tt_other, 0); /* Add a table for RAMDAC values */
+ ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Device Calibration Curves",NULL);
+ ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll synthcal", NULL);
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+ ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
+
+ if (devtype == 0)
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","INPUT", NULL);
+ else if (devtype == 1)
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL);
+ else
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","DISPLAY", NULL);
+
+ ocg->add_kword(ocg, 0, "COLOR_REP", ident, NULL);
+
+ if (profDesc != NULL)
+ ocg->add_kword(ocg, 0, "DESCRIPTION", profDesc, NULL);
+
+ sprintf(buf, "%s_I",bident);
+ ocg->add_field(ocg, 0, buf, r_t);
+ nsetel++;
+ for (j = 0; j < devchan; j++) {
+ inkmask imask = icx_index2ink(devmask, j);
+ sprintf(buf, "%s_%s",bident,icx_ink2char(imask));
+ ocg->add_field(ocg, 0, buf, r_t);
+ nsetel++;
+ }
+ if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL)
+ error("Malloc failed!");
+
+ for (i = 0; i < calres; i++) {
+ double vv = i/(calres-1.0);
+
+ setel[0].d = vv;
+ for (j = 0; j < devchan; j++) {
+ setel[j+1].d = off[j] + sca[j] * pow(vv, gam[j]);
+ if (setel[j+1].d < 0.0)
+ setel[j+1].d = 0.0;
+ else if (setel[j+1].d > 1.0)
+ setel[j+1].d = 1.0;
+ }
+
+ ocg->add_setarr(ocg, 0, setel);
+ }
+
+ free(setel);
+
+ if (ocg->write_name(ocg, outname))
+ error("Write error : %s",ocg->err);
+
+ ocg->del(ocg); /* Clean up */
+ }
+ return 0;
+}
+
+
diff --git a/spectro/synthread.c b/spectro/synthread.c
new file mode 100644
index 0000000..c4312f1
--- /dev/null
+++ b/spectro/synthread.c
@@ -0,0 +1,691 @@
+
+/*
+ * Argyll Color Correction System
+ * Synthetic device target chart reader
+ *
+ * Author: Graeme W. Gill
+ * Date: 10/7/2007
+ *
+ * Copyright 2002 - 2007 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ *
+ * Based on fakeread.c
+ */
+
+/*
+ Implements a synthetic RGB device response based on sRGB like
+ primaries.
+
+ */
+
+/*
+ * TTBD:
+ *
+ * Add non-linear mixing model
+ *
+ */
+
+
+#undef DEBUG
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/types.h>
+#include <time.h>
+#include <string.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "cgats.h"
+#include "xicc.h"
+#include "icc.h"
+
+void
+usage(char *mes) {
+ fprintf(stderr,"Synthetic device model test chart reader - Version %s\n",
+ ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ if (mes != NULL)
+ fprintf(stderr,"Error '%s'\n",mes);
+ fprintf(stderr,"usage: synthread [-v] [-s] [separation.icm] profile.[icc|mpp|ti3] outfile\n");
+ fprintf(stderr," -v Verbose mode\n");
+ fprintf(stderr," -p Use separation profile\n");
+ fprintf(stderr," -l Construct and output in Lab rather than XYZ\n");
+ fprintf(stderr," -i p1,p2,p3, Set input channel curve powers (default 1.0)\n");
+ fprintf(stderr," -k x1:y1,x2:y2,x3:y2 Set input channel inflection points (default 0.5,0.5)\n");
+ fprintf(stderr," -o p1,p2,p3, Set output channel curve powers (default 1.0)\n");
+ fprintf(stderr," -r level Add average random deviation of <level>%% to input device values (after sep.)\n");
+ fprintf(stderr," -R level Add average random deviation of <level>%% to output PCS values\n");
+ fprintf(stderr," -u Make random deviations have uniform distributions rather than normal\n");
+ fprintf(stderr," -b L,a,b Scale black point to target Lab value\n");
+ fprintf(stderr," [separation.icm] Device link separation profile\n");
+ fprintf(stderr," profile.[icc|mpp|ti3] ICC, MPP profile or TI3 to use\n");
+ fprintf(stderr," outfile Base name for input[ti1]/output[ti3] file\n");
+ exit(1);
+ }
+
+
+typedef struct {
+ int dolab; /* Combine and output in Lab space */
+ double ipow[3]; /* Input power applied */
+ double ibpp[3]; /* Input breakpoint location, -ve if none */
+ double ibpv[3]; /* Input breakpoint value, -ve if none */
+ double col[3][3]; /* sRGB additive colorant values in XYZ :- [out][in] */
+ double wnf[3]; /* White normalization factor */
+ double opow[3]; /* Output power */
+ double omax[3]; /* Output maximum that power operates into */
+} synthmodel;
+
+/* Symetrical power function */
+double spow(double val, double pp) {
+ if (val < 0.0)
+ return -pow(-val, pp);
+ else
+ return pow(val, pp);
+}
+
+/* Execute the device model */
+static void domodel(synthmodel *p, double *out, double *in) {
+ double tmp[3];
+ int i, j;
+
+ /* Input power */
+ for (j = 0; j < 3; j++)
+ tmp[j] = pow(in[j], p->ipow[j]);
+
+ /* Input breakpoint */
+ for (j = 0; j < 3; j++) {
+ if (p->ibpp[j] >= 0.0 && p->ibpv[j] >= 0.0) {
+ double b;
+ if (tmp[j] <= p->ibpp[j]) {
+ b = (tmp[j] - 0.0)/(p->ibpp[j] - 0.0);
+ tmp[j] = b * p->ibpv[j] + (1.0 - b) * 0.0;
+ } else {
+ b = (tmp[j] - p->ibpp[j])/(1.0 - p->ibpp[j]);
+ tmp[j] = b * 1.0 + (1.0 - b) * p->ibpv[j];
+ }
+ }
+ }
+
+ /* Lookup primary values, sum them, and then */
+ /* apply output power */
+ /* (We're not allowing for non-linear mixing yet) */
+ for (j = 0; j < 3; j++) {
+ out[j] = 0.0;
+ for (i = 0; i < 3; i++)
+ out[j] += p->col[j][i] * tmp[i];
+ out[j] = spow(out[j]/p->omax[j], p->opow[j]) * p->omax[j];
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int i, j, rv = 0;
+ int fa,nfa; /* current argument we're looking at */
+ int verb = 0; /* Verbose flag */
+ int dosep = 0; /* Use separation before profile */
+ int gfudge = 0; /* Do grey fudge, 1 = W->RGB, 2 = K->xxxK */
+ double rdlevel = 0.0; /* Random device average deviation level (0.0 - 1.0) */
+ double rplevel = 0.0; /* Random PCS average deviatio level (0.0 - 1.0) */
+ int unidist = 0; /* Use uniform distribution of errors */
+ double tbp[3] = { -1.0, 0.0, 0.0 }; /* Target black point */
+ static char sepname[500] = { 0 }; /* ICC separation profile */
+ static char inname[500] = { 0 }; /* Input cgats file base name */
+ static char outname[500] = { 0 }; /* Output cgats file base name */
+ cgats *icg; /* input cgats structure */
+ cgats *ocg; /* output cgats structure */
+ int nmask = 0; /* Test chart device colorant mask */
+ int nchan = 0; /* Test chart number of device chanels */
+ int npat; /* Number of patches */
+ int si; /* Sample id index */
+ int ti; /* Temp index */
+ int fi; /* Colorspace index */
+
+ synthmodel md; /* Synthetic model */
+
+ /* ICC separation device link profile */
+ icmFile *sep_fp = NULL; /* Color profile file */
+ icc *sep_icco = NULL; /* Profile object */
+ icmLuBase *sep_luo = NULL; /* Conversion object */
+ icColorSpaceSignature sep_ins, sep_outs; /* Type of input and output spaces */
+ int sep_inn; /* Number of input channels to separation */
+ inkmask sep_nmask = 0; /* Colorant mask for separation input */
+ double wp[3], bp[3]; /* ICC profile Lab white and black points */
+ double bpt[3][3]; /* Black point transform matrix (Lab->Lab) */
+
+
+ int inn, outn; /* Number of channels for conversion input, output */
+ icColorSpaceSignature ins, outs; /* Type of conversion input and output spaces */
+ int cnv_nmask = 0; /* Conversion input nmask */
+ time_t clk = time(0);
+ struct tm *tsp = localtime(&clk);
+ char *atm = asctime(tsp); /* Ascii time */
+ char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" };
+ char *labfname[3] = { "LAB_L", "LAB_A", "LAB_B" };
+
+ error_program = "Synthread";
+ if (argc < 1)
+ usage("Too few arguments");
+
+ /* Initialise the default model */
+ inn = 3;
+ ins = icSigRgbData;
+ outn = 3;
+ outs = icSigXYZData;
+
+ md.dolab = 0;
+
+ md.ipow[0] = 1.0;
+ md.ipow[1] = 1.0;
+ md.ipow[2] = 1.0;
+
+ md.ibpp[0] = -1.0;
+ md.ibpv[0] = -1.0;
+ md.ibpp[1] = -1.0;
+ md.ibpv[1] = -1.0;
+ md.ibpp[2] = -1.0;
+ md.ibpv[2] = -1.0;
+
+ md.col[0][0] = 0.412424; /* X from R */
+ md.col[0][1] = 0.357579; /* X from G */
+ md.col[0][2] = 0.180464; /* X from B */
+ md.col[1][0] = 0.212656; /* Y from R */
+ md.col[1][1] = 0.715158; /* Y from G */
+ md.col[1][2] = 0.0721856; /* Y from B */
+ md.col[2][0] = 0.0193324; /* Z from R */
+ md.col[2][1] = 0.119193; /* Z from G */
+ md.col[2][2] = 0.950444; /* Z from B */
+
+ md.opow[0] = 1.0;
+ md.opow[1] = 1.0;
+ md.opow[2] = 1.0;
+
+ md.omax[0] = 1.0;
+ md.omax[1] = 1.0;
+ md.omax[2] = 1.0;
+
+ /* Process the arguments */
+ for(fa = 1;fa < argc;fa++) {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else
+ {
+ if ((fa+1) < argc)
+ {
+ if (argv[fa+1][0] != '-')
+ {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage("Usage requested");
+
+ /* Verbose */
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V')
+ verb = 1;
+
+ /* Separation */
+ else if (argv[fa][1] == 'p' || argv[fa][1] == 'P')
+ dosep = 1;
+
+ /* Lab */
+ else if (argv[fa][1] == 'l' || argv[fa][1] == 'L')
+ md.dolab = 1;
+
+ /* Input curve power */
+ else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to -i");
+ if (sscanf(na, " %lf,%lf,%lf ", &md.ipow[0], &md.ipow[1], &md.ipow[2]) != 3)
+ usage("Argument to -i does not parse");
+ }
+
+ /* Input curve inflexction point */
+ else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to -k");
+ if (sscanf(na, " %lf:%lf,%lf:%lf,%lf:%lf ",
+ &md.ibpp[0], &md.ibpv[0], &md.ibpp[1],
+ &md.ibpv[1], &md.ibpp[2], &md.ibpv[2]) != 6)
+ usage("Argument to -k does not parse");
+ }
+
+ /* Output curve power */
+ else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to -o");
+ if (sscanf(na, " %lf,%lf,%lf ", &md.opow[0], &md.opow[1], &md.opow[2]) != 3)
+ usage("Argument to -o does not parse");
+ }
+
+ /* Uniform distrivuted errors */
+ else if (argv[fa][1] == 'u' || argv[fa][1] == 'U')
+ unidist = 1;
+
+ /* Random addition to device levels */
+ else if (argv[fa][1] == 'r') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to -r");
+ rdlevel = 0.01 * atof(na);
+ rand32(time(NULL)); /* Init seed randomly */
+ }
+
+ /* Random addition to PCS levels */
+ else if (argv[fa][1] == 'R') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to -R");
+ rplevel = 0.01 * atof(na);
+ rand32(time(NULL)); /* Init seed randomly */
+ }
+
+ /* Black point scale */
+ else if (argv[fa][1] == 'b' || argv[fa][1] == 'B') {
+ if (na == NULL) usage("Expect argument to -b");
+ fa = nfa;
+ if (sscanf(na, " %lf , %lf , %lf ",&tbp[0], &tbp[1], &tbp[2]) != 3)
+ usage("Couldn't parse argument to -b");
+ if (tbp[0] < 0.0 || tbp[0] > 100.0) usage("-b L* value out of range");
+ if (tbp[1] < -128.0 || tbp[1] > 128.0) usage("-b a* value out of range");
+ if (tbp[2] < -128.0 || tbp[2] > 128.0) usage("-b b* value out of range");
+ }
+
+ else
+ usage("Unrecognised flag");
+ }
+ else
+ break;
+ }
+
+ /* Get the file name argument */
+ if (dosep) {
+ if (fa >= argc || argv[fa][0] == '-') usage("Missing separation profile filename argument");
+ strcpy(sepname,argv[fa++]);
+ }
+
+ if (fa >= argc || argv[fa][0] == '-') usage("Missing basename argument");
+ strcpy(inname,argv[fa]);
+ strcat(inname,".ti1");
+ strcpy(outname,argv[fa]);
+ strcat(outname,".ti3");
+
+ /* Convert colorants to Lab, and scale white point */
+ if (md.dolab) {
+ double white[3] = { 100.0, 0, 0 };
+ double rot[3][3];
+
+printf("~1 switching to Lab\n");
+
+ for (i = 0; i < 3; i++) {
+ double val[3];
+
+ val[0] = md.col[0][i];
+ val[1] = md.col[1][i];
+ val[2] = md.col[2][i];
+printf("~1 prim XYZ %f %f %f -> ", val[0], val[1], val[2]);
+ icmXYZ2Lab(&icmD50, val, val);
+printf("Lab %f %f %f\n", val[0], val[1], val[2]);
+ md.col[0][i] = val[0];
+ md.col[1][i] = val[1];
+ md.col[2][i] = val[2];
+ }
+
+ /* Compute white sum */
+ for (i = 0; i < 3; i++) {
+ md.omax[i] = 0.0;
+ for (j = 0; j < 3; j++)
+ md.omax[i] += md.col[i][j];
+ }
+printf("~1 sum = %f %f %f\n", md.omax[0], md.omax[1], md.omax[2]);
+
+ /* Compute rotate and scale to map to white target */
+ icmRotMat(rot, md.omax, white);
+
+ /* Rotate and primaries to sum to white */
+ for (i = 0; i < 3; i++) {
+ double val[3];
+
+ val[0] = md.col[0][i];
+ val[1] = md.col[1][i];
+ val[2] = md.col[2][i];
+ icmMulBy3x3(val, rot, val);
+printf("~1 Scaled primary %f %f %f\n", val[0], val[1], val[2]);
+ md.col[0][i] = val[0];
+ md.col[1][i] = val[1];
+ md.col[2][i] = val[2];
+ }
+
+ /* Compute output maximum factors to set out power range */
+ for (i = 0; i < 3; i++) {
+ md.omax[i] = 0.0;
+ for (j = 0; j < 3; j++) {
+ if (i == 0)
+ md.omax[i] += md.col[i][j];
+ else {
+ if (fabs(md.col[i][j]) > md.omax[i])
+ md.omax[i] = fabs(md.col[i][j]);
+
+ }
+ }
+ }
+ } else {
+
+ /* Compute output maximum factors to set out power range */
+ for (i = 0; i < 3; i++) {
+ md.omax[i] = 0.0;
+ for (j = 0; j < 3; j++)
+ md.omax[i] += md.col[i][j];
+ }
+ }
+printf("~1 omax = %f %f %f\n", md.omax[0], md.omax[1], md.omax[2]);
+
+ /* Deal with separation */
+ if (dosep) {
+ if ((sep_fp = new_icmFileStd_name(sepname,"r")) == NULL)
+ error ("Can't open file '%s'",sepname);
+
+ if ((sep_icco = new_icc()) == NULL)
+ error ("Creation of ICC object failed");
+
+ /* Deal with ICC separation */
+ if ((rv = sep_icco->read(sep_icco,sep_fp,0)) == 0) {
+
+ /* Get a conversion object */
+ if ((sep_luo = sep_icco->get_luobj(sep_icco, icmFwd, icmDefaultIntent, icmSigDefaultData, icmLuOrdNorm)) == NULL) {
+ error ("%d, %s",sep_icco->errc, sep_icco->err);
+ }
+
+ /* Get details of conversion */
+ sep_luo->spaces(sep_luo, &sep_ins, &sep_inn, &sep_outs, NULL, NULL, NULL, NULL, NULL, NULL);
+ sep_nmask = icx_icc_to_colorant_comb(sep_ins, sep_icco->header->deviceClass);
+ }
+ }
+
+ /* Deal with input CGATS files */
+ icg = new_cgats(); /* Create a CGATS structure */
+ icg->add_other(icg, "CTI1"); /* our special input type is Calibration Target Information 1 */
+
+ if (icg->read_name(icg, inname))
+ error("CGATS file read error : %s",icg->err);
+
+ if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0)
+ error ("Input file isn't a CTI1 format file");
+ if (icg->ntables != 1 && icg->ntables != 2 && icg->ntables != 3)
+ error ("Input file doesn't contain one, two or three tables");
+
+ if ((npat = icg->t[0].nsets) <= 0)
+ error ("No sets of data");
+
+ /* Setup output cgats file */
+ ocg = new_cgats(); /* Create a CGATS structure */
+ ocg->add_other(ocg, "CTI3"); /* our special type is Calibration Target Information 3 */
+ ocg->add_table(ocg, tt_other, 0); /* Start the first table */
+
+ ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 3",NULL);
+ ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll synthread", NULL);
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+ ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
+
+ /* Assume a general output type device */
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL);
+
+ if ((ti = icg->find_kword(icg, 0, "SINGLE_DIM_STEPS")) >= 0)
+ ocg->add_kword(ocg, 0, "SINGLE_DIM_STEPS",icg->t[0].kdata[ti], NULL);
+
+ if ((ti = icg->find_kword(icg, 0, "COMP_GREY_STEPS")) >= 0)
+ ocg->add_kword(ocg, 0, "COMP_GREY_STEPS",icg->t[0].kdata[ti], NULL);
+
+ if ((ti = icg->find_kword(icg, 0, "MULTI_DIM_STEPS")) >= 0)
+ ocg->add_kword(ocg, 0, "MULTI_DIM_STEPS",icg->t[0].kdata[ti], NULL);
+
+ if ((ti = icg->find_kword(icg, 0, "FULL_SPREAD_PATCHES")) >= 0)
+ ocg->add_kword(ocg, 0, "FULL_SPREAD_PATCHES",icg->t[0].kdata[ti], NULL);
+
+ /* Fields we want */
+ ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t);
+
+ if ((si = icg->find_field(icg, 0, "SAMPLE_ID")) < 0)
+ error ("Input file doesn't contain field SAMPLE_ID");
+
+ /* Figure out the color space */
+ if ((fi = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
+ error ("Input file doesn't contain keyword COLOR_REP");
+
+ if ((nmask = icx_char2inkmask(icg->t[0].kdata[fi])) == 0)
+ error ("Input file keyword COLOR_REP has unknown value");
+
+ {
+ int i, j, ii;
+ int chix[ICX_MXINKS]; /* Device chanel indexes */
+ char *ident, *bident;
+ int nsetel = 0;
+ cgats_set_elem *setel; /* Array of set value elements */
+
+ nchan = icx_noofinks(nmask);
+ ident = icx_inkmask2char(nmask, 1);
+ bident = icx_inkmask2char(nmask, 0);
+
+ /* Sanity check what we're going to do */
+ if (dosep) {
+
+ /* Check if sep ICC input is compatible with .ti1 */
+ if (nmask == ICX_W && sep_ins == icSigRgbData)
+ gfudge = 1;
+ else if (nmask == ICX_K && sep_ins == icSigCmykData)
+ gfudge = 2;
+ else if (icx_colorant_comb_match_icc(nmask, sep_ins) == 0) {
+ error("Separation ICC device space '%s' dosen't match TI1 '%s'",
+ icm2str(icmColorSpaceSignature, sep_ins),
+ ident); /* Should free(). */
+ }
+
+ /* Check if separation ICC output is compatible with ICC/MPP/TI3 conversion */
+ if (sep_outs != ins)
+ error("Synthetic device space '%s' dosen't match Separation ICC '%s'",
+ icm2str(icmColorSpaceSignature, ins),
+ icm2str(icmColorSpaceSignature, sep_outs));
+ } else {
+ /* Check if synthetic device is compatible with .ti1 */
+ if (nmask == ICX_W && ins == icSigRgbData)
+ gfudge = 1;
+ else if (nmask == ICX_K && ins == icSigCmykData)
+ gfudge = 2; /* Should allow for other colorant combo's that include black */
+ else if (icx_colorant_comb_match_icc(nmask, ins) == 0) {
+ error("Synthetic device space '%s' dosen't match TI1 '%s'",
+ icm2str(icmColorSpaceSignature, ins),
+ ident); // Should free().
+ }
+ }
+
+ if ((ii = icg->find_kword(icg, 0, "TOTAL_INK_LIMIT")) >= 0)
+ ocg->add_kword(ocg, 0, "TOTAL_INK_LIMIT",icg->t[0].kdata[ii], NULL);
+
+ nsetel += 1; /* For id */
+ nsetel += nchan; /* For device values */
+ nsetel += 3; /* For XYZ/Lab */
+
+ for (j = 0; j < nchan; j++) {
+ int imask;
+ char fname[100];
+
+ imask = icx_index2ink(nmask, j);
+ sprintf(fname,"%s_%s",nmask == ICX_W || nmask == ICX_K ? "GRAY" : bident,
+ icx_ink2char(imask));
+
+ if ((ii = icg->find_field(icg, 0, fname)) < 0)
+ error ("Input file doesn't contain field %s",fname);
+ if (icg->t[0].ftype[ii] != r_t)
+ error ("Field %s is wrong type",fname);
+
+ ocg->add_field(ocg, 0, fname, r_t);
+ chix[j] = ii;
+ }
+
+ /* Add PCS fields */
+ for (j = 0; j < 3; j++) {
+ ocg->add_field(ocg, 0, md.dolab ? labfname[j] : xyzfname[j], r_t);
+ }
+
+ {
+ char fname[100];
+ sprintf(fname, md.dolab ? "%s_LAB" : "%s_XYZ", ident);
+ ocg->add_kword(ocg, 0, "COLOR_REP", fname, NULL);
+ }
+
+ if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL)
+ error("Malloc failed!");
+
+ /* Read all the test patches in, convert them, */
+ /* and write them out. */
+ for (i = 0; i < npat; i++) {
+ int k = 0;
+ char *id;
+ double odev[ICX_MXINKS], dev[ICX_MXINKS], sep[ICX_MXINKS], PCS[3];
+ xspect out;
+
+ id = ((char *)icg->t[0].fdata[i][si]);
+ for (j = 0; j < nchan; j++) {
+ double dv = *((double *)icg->t[0].fdata[i][chix[j]]) / 100.0;
+ odev[j] = dev[j] = sep[j] = dv;
+ }
+
+ if (gfudge) {
+ int nch;
+
+ if (dosep) /* Figure number of channels into conversion */
+ nch = sep_inn;
+ else
+ nch = inn;
+
+ if (gfudge == 1) { /* Convert W -> RGB */
+ double wval = dev[0];
+ for (j = 0; j < nch; j++)
+ dev[j] = sep[j] = wval;
+
+ } else { /* Convert K->xxxK */
+ int kch;
+ int inmask;
+ double kval = dev[0];
+
+ if (dosep) /* Figure number of channels into conversion */
+ inmask = sep_nmask;
+ else
+ inmask = cnv_nmask;
+
+ if (inmask == 0)
+ error("Input colorspace ambiguous - can't determine if it has black");
+
+ if ((kch = icx_ink2index(inmask, ICX_BLACK)) == -1)
+ error("Can't find black colorant for K fudge");
+ for (j = 0; j < nch; j++) {
+ if (j == kch)
+ dev[j] = sep[j] = kval;
+ else
+ dev[j] = sep[j] = 0.0;
+ }
+ }
+ }
+
+ if (dosep)
+ if (sep_luo->lookup(sep_luo, sep, dev) > 1)
+ error ("%d, %s",sep_icco->errc,sep_icco->err);
+
+ /* Add randomness and non-linearity (rdlevel is avg. dev.) */
+ /* Note dev/sep is 0-1.0 at this stage */
+ for (j = 0; j < inn; j++) {
+ double dv = sep[j];
+ if (rdlevel > 0.0) {
+ double rr;
+ if (unidist)
+ rr = d_rand(-2.0 * rdlevel, 2.0 * rdlevel);
+ else
+ rr = 1.2533 * rdlevel * norm_rand();
+ dv += rr;
+ if (dv < 0.0)
+ dv = 0.0;
+ else if (dv > 1.0)
+ dv = 1.0;
+ }
+ sep[j] = dv;
+ }
+
+ /* Do color conversion */
+ domodel(&md, PCS, sep);
+
+ if (tbp[0] >= 0) { /* Doing black point scaling */
+
+ for (j = 0; j < 3; j++)
+ PCS[j] -= wp[j];
+ icmMulBy3x3(PCS, bpt, PCS);
+ for (j = 0; j < 3; j++)
+ PCS[j] += wp[j];
+ }
+
+ setel[k++].c = id;
+
+ for (j = 0; j < nchan; j++)
+ setel[k++].d = 100.0 * odev[j];
+
+ if (md.dolab == 0) {
+ PCS[0] *= 100.0;
+ PCS[1] *= 100.0;
+ PCS[2] *= 100.0;
+ }
+
+ /* Add randomness (rplevel is avg. dev.) */
+ /* Note PCS is 0..100 XYZ or Lab at this point */
+ if (rplevel > 0.0) {
+ for (j = 0; j < 3; j++) {
+ double dv = PCS[j];
+ double rr;
+ if (unidist)
+ rr = 100.0 * d_rand(-2.0 * rplevel, 2.0 * rplevel);
+ else
+ rr = 100.0 * 1.2533 * rplevel * norm_rand();
+ dv += rr;
+
+ /* Don't let L*, X, Y or Z go negative */
+ if ((!md.dolab || j == 0) && dv < 0.0)
+ dv = 0.0;
+ PCS[j] = dv;
+ }
+ }
+
+ setel[k++].d = PCS[0];
+ setel[k++].d = PCS[1];
+ setel[k++].d = PCS[2];
+
+ ocg->add_setarr(ocg, 0, setel);
+ }
+
+ free(setel);
+ free(ident);
+ free(bident);
+ }
+
+ if (sep_luo != NULL) {
+ sep_luo->del(sep_luo);
+ sep_icco->del(sep_icco);
+ sep_fp->del(sep_fp);
+ }
+
+ if (ocg->write_name(ocg, outname))
+ error("Write error : %s",ocg->err);
+
+ ocg->del(ocg); /* Clean up */
+ icg->del(icg); /* Clean up */
+
+ return 0;
+}
+
+
diff --git a/spectro/usbio.c b/spectro/usbio.c
new file mode 100644
index 0000000..3db1535
--- /dev/null
+++ b/spectro/usbio.c
@@ -0,0 +1,585 @@
+
+ /* General USB I/O support */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 2006/22/4
+ *
+ * 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.
+ */
+
+/* These routines supliement the class code in ntio.c and unixio.c */
+/* with common and USB specific routines */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <signal.h>
+#if defined(UNIX)
+#include <termios.h>
+#include <errno.h>
+#include <unistd.h>
+#endif
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#else
+#include "sa_config.h"
+#endif
+#include "numsup.h"
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+
+#ifdef ENABLE_USB
+
+/* Counter set when we're in a USB read or write */
+/* Note - this isn't perfectly thread safe */
+int in_usb_rw = 0;
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Cancel token utility functions */
+
+/* Used by caller of icoms to init and uninit token */
+void usb_init_cancel(usb_cancelt *p) {
+
+ amutex_init(p->cmtx);
+
+#ifdef NATIVE_USB
+ p->hcancel = NULL;
+#else
+# ifdef USE_LIBUSB1
+ p->hcancel = NULL;
+# else
+ p->hcancel = (void *)-1;
+# endif
+#endif
+}
+
+void usb_uninit_cancel(usb_cancelt *p) {
+ amutex_del(p->cmtx);
+}
+
+/* Used by implementation */
+static void usb_lock_cancel(usb_cancelt *p) {
+ amutex_lock(p->cmtx);
+}
+
+static void usb_unlock_cancel(usb_cancelt *p) {
+ amutex_unlock(p->cmtx);
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Include the USB implementation dependent function implementations */
+#ifdef NATIVE_USB
+# ifdef NT
+# include "usbio_nt.c"
+# endif
+# if defined(__APPLE__)
+# include "usbio_ox.c"
+# endif
+# if defined(UNIX_X11)
+# include "usbio_lx.c"
+# endif
+#else /* Using libusb */
+# include "usbio_lusb.c"
+#endif
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* I/O routines supported by icoms - uses platform independent */
+/* USB routines implemented by code in usbio_*.c above */
+
+/* USB control message */
+static int
+icoms_usb_control(
+icoms *p,
+int requesttype, /* 8 bit request type (USB bmRequestType) */
+int request, /* 8 bit request code (USB bRequest) */
+int value, /* 16 bit value (USB wValue) */
+int index, /* 16 bit index (USB wIndex) */
+unsigned char *rwbuf, /* Write or read buffer */
+int rwsize, /* Bytes to read or write */
+double tout /* Timeout in seconds */
+) {
+ int rv = 0; /* Return value */
+ int c, rwbytes; /* Data bytes read or written */
+ long top; /* timeout in msec */
+
+ if (p->log->debug >= 8) {
+ a1logd(p->log, 8, "icoms_usb_control: message %02x, %02x %04x %04x %04x\n",
+ requesttype, request, value, index, rwsize);
+ if ((requesttype & IUSB_ENDPOINT_IN) == 0)
+ a1logd(p->log, 8, " writing data %s\n",icoms_tohex(rwbuf, rwsize));
+ }
+
+ if (!p->is_open) {
+ a1loge(p->log, ICOM_SYS, "icoms_usb_control: device not open\n");
+ return ICOM_SYS;
+ }
+
+ top = (int)(tout * 1000.0 + 0.5); /* Timout in msec */
+
+#ifdef QUIET_MEMCHECKERS
+ if (requesttype & IUSB_ENDPOINT_IN)
+ memset(rwbuf, 0, rwsize);
+#endif
+
+ /* Call back end implementation */
+ rv = icoms_usb_control_msg(p, &rwbytes, requesttype, request, value, index,
+ rwbuf, rwsize, top);
+ a1logd(p->log, 8, "icoms_usb_control: returning ICOM err 0x%x\n",rv);
+
+ if (p->log->debug >= 8 && (requesttype & IUSB_ENDPOINT_IN))
+ a1logd(p->log, 8, " read data %s\n",icoms_tohex(rwbuf, rwsize));
+
+ return rv;
+}
+
+/* - - - - - - - - - - - - - */
+/* USB Read/Write. Direction is set by ep. */
+/* Don't retry on a short read, return ICOM_SHORT. */
+static int
+icoms_usb_rw(icoms *p,
+ usb_cancelt *cancelt, /* Cancel handle */
+ int ep, /* End point address */
+ unsigned char *rbuf, /* Read/Write buffer */
+ int bsize, /* Bytes to read */
+ int *breadp, /* Bytes read/written */
+ double tout /* Timeout in seconds */
+) {
+ int lerr; /* Last error */
+ int bread, qa;
+ long top; /* Timeout period */
+ icom_usb_trantype type; /* bulk or interrupt */
+
+#ifdef QUIET_MEMCHECKERS
+ memset(rbuf, 0, bsize);
+#endif
+
+ if (!p->is_open) {
+ a1loge(p->log, ICOM_SYS, "icoms_usb_rw: device not initialised\n");
+ return ICOM_SYS;
+ }
+
+ if (p->EPINFO(ep).valid == 0) {
+ a1loge(p->log, ICOM_SYS, "icoms_usb_rw: invalid end point 0x%02x\n",ep);
+ return ICOM_SYS;
+ }
+
+ if (p->EPINFO(ep).type != ICOM_EP_TYPE_BULK
+ && p->EPINFO(ep).type != ICOM_EP_TYPE_INTERRUPT) {
+ a1loge(p->log, ICOM_SYS, "icoms_usb_rw: unhandled end point type %d\n",p->EPINFO(ep).type);
+ return ICOM_SYS;
+ }
+
+ if (p->EPINFO(ep).type == ICOM_EP_TYPE_BULK)
+ type = icom_usb_trantype_bulk;
+ else
+ type = icom_usb_trantype_interrutpt;
+
+ qa = bsize; /* For simpler tracing */
+
+ lerr = 0;
+ bread = 0;
+
+ top = (int)(tout * 1000 + 0.5); /* Timeout period in msecs */
+
+ /* Bug workaround - on some OS's for some devices */
+ if (p->uflags & icomuf_resetep_before_read
+ && (ep & IUSB_ENDPOINT_DIR_MASK) == IUSB_ENDPOINT_IN) {
+ msec_sleep(1); /* Let device recover ? */
+ p->usb_resetep(p, ep);
+ msec_sleep(1); /* Let device recover (ie. Spyder 3) */
+ }
+
+ /* Until data is all read/written, we get a short read/write, we time out, or the user aborts */
+// a1logd(p->log, 8, "icoms_usb_rw: read/write of %d bytes, timout %f\n",bsize,tout);
+ while (bsize > 0) {
+ int rv, rbytes;
+ int rsize = bsize > qa ? qa : bsize;
+
+// a1logd(p->log, 8, "icoms_usb_rw: read/write %d bytes this time\n",rsize);
+ rv = icoms_usb_transaction(p, cancelt, &rbytes, type, (unsigned char)ep, rbuf, rsize, top);
+ if (rv != 0 && rv != ICOM_SHORT) {
+ lerr = rv;
+ break;
+ } else { /* Account for bytes read/written */
+ bsize -= rbytes;
+ rbuf += rbytes;
+ bread += rbytes;
+ }
+ if (rbytes != rsize) {
+ lerr |= ICOM_SHORT;
+ break;
+ }
+ }
+
+ if (breadp != NULL)
+ *breadp = bread;
+
+ a1logd(p->log, 8, "icoms_usb_rw: returning %d bytes, ICOM err 0x%x\n",bread, lerr);
+
+ return lerr;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Static list so that all open USB/HID connections can be closed on a SIGKILL */
+static icoms *icoms_list = NULL;
+
+/* Clean up any open USB ports ready for exit on signal */
+static void icoms_cleanup() {
+ icoms *pp, *np;
+
+#if defined(UNIX)
+ /* This is a bit of a hack to compensate for the fact */
+ /* that a ^C will kill the program while ICANON is off. */
+ /* It's really better to restore the original attributes, */
+ /* even when USB is not compiled in. */
+ struct termios news;
+ if (tcgetattr(STDIN_FILENO, &news) >= 0) {
+ news.c_lflag |= (ICANON | ECHO);
+ tcsetattr(STDIN_FILENO,TCSANOW, &news);
+ }
+#endif
+
+ for (pp = icoms_list; pp != NULL; pp = np) {
+ np = pp->next;
+ a1logd(pp->log, 6, "icoms_cleanup: closing usb port 0x%x\n",pp);
+ /* There's a problem here if have more than one USB port */
+ /* open - win32 doesn't return from the system call. */
+ /* Should we depend on usb read/write routines to call cleanup ? */
+ pp->close_port(pp);
+ }
+}
+
+#ifdef NT
+void (__cdecl *usbio_int)(int sig) = SIG_DFL;
+void (__cdecl *usbio_term)(int sig) = SIG_DFL;
+#endif
+#ifdef UNIX
+void (*usbio_hup)(int sig) = SIG_DFL;
+void (*usbio_int)(int sig) = SIG_DFL;
+void (*usbio_term)(int sig) = SIG_DFL;
+#endif
+
+/* On something killing our process, deal with USB cleanup */
+static void icoms_sighandler(int arg) {
+ a1logd(g_log, 6, "icoms_sighandler: invoked with arg = %d\n",arg);
+ if (in_usb_rw != 0)
+ in_usb_rw = -1;
+ icoms_cleanup();
+ /* Call the existing handlers */
+#ifdef UNIX
+ if (arg == SIGHUP && usbio_hup != SIG_DFL && usbio_hup != SIG_IGN)
+ usbio_hup(arg);
+#endif /* UNIX */
+ if (arg == SIGINT && usbio_int != SIG_DFL && usbio_int != SIG_IGN)
+ usbio_int(arg);
+ if (arg == SIGTERM && usbio_term != SIG_DFL && usbio_term != SIG_IGN)
+ usbio_term(arg);
+
+ a1logd(g_log, 6, "icoms_sighandler: calling exit()\n");
+ exit(0);
+}
+
+/* - - - - - - - - - - - - - - - - - - - */
+
+/* Install the cleanup signal handlers */
+void usb_install_signal_handlers(icoms *p) {
+ if (icoms_list == NULL) {
+ a1logd(g_log, 6, "usb_install_signal_handlers: called\n");
+#if defined(UNIX)
+ usbio_hup = signal(SIGHUP, icoms_sighandler);
+#endif /* UNIX */
+ usbio_int = signal(SIGINT, icoms_sighandler);
+ usbio_term = signal(SIGTERM, icoms_sighandler);
+ }
+
+ /* Add it to our static list, to allow automatic cleanup on signal */
+ p->next = icoms_list;
+ icoms_list = p;
+ a1logd(g_log, 6, "usb_install_signal_handlers: done\n");
+}
+
+/* Delete an icoms from our static signal cleanup list */
+void usb_delete_from_cleanup_list(icoms *p) {
+
+ /* Find it and delete it from our static cleanup list */
+ if (icoms_list != NULL) {
+ if (icoms_list == p) {
+ icoms_list = p->next;
+ if (icoms_list == NULL) {
+#if defined(UNIX)
+ signal(SIGHUP, usbio_hup);
+#endif /* UNIX */
+ signal(SIGINT, usbio_int);
+ signal(SIGTERM, usbio_term);
+ }
+ } else {
+ icoms *pp;
+ for (pp = icoms_list; pp != NULL; pp = pp->next) {
+ if (pp->next == p) {
+ pp->next = p->next;
+ break;
+ }
+ }
+ }
+ }
+}
+
+/* ========================================================= */
+/* USB write/read "serial" imitation */
+
+/* Write the characters in the buffer out */
+/* Data will be written up to the terminating nul */
+/* Return relevant error status bits */
+static int
+icoms_usb_ser_write(
+icoms *p,
+char *wbuf,
+double tout)
+{
+ int len, wbytes;
+ long toc, i, top; /* Timout count, counter, timeout period */
+ int ep = p->wr_ep; /* End point */
+ icom_usb_trantype type; /* bulk or interrupt */
+ int retrv = ICOM_OK;
+
+ a1logd(p->log, 8, "\nicoms_usb_ser_write: writing '%s'\n",icoms_fix(wbuf));
+
+ if (!p->is_open) {
+ a1loge(p->log, ICOM_SYS, "icoms_usb_ser_write: device is not open\n");
+ return ICOM_SYS;
+ }
+
+ if (p->EPINFO(ep).valid == 0) {
+ a1loge(p->log, ICOM_SYS, "icoms_usb_ser_write: invalid end point 0x%02x\n",ep);
+ return ICOM_SYS;
+ }
+
+ if (p->EPINFO(ep).type != ICOM_EP_TYPE_BULK
+ && p->EPINFO(ep).type != ICOM_EP_TYPE_INTERRUPT) {
+ a1loge(p->log, ICOM_SYS, "icoms_usb_ser_write: unhandled end point type %d",p->EPINFO(ep).type);
+ return ICOM_SYS;
+ }
+
+ if (p->EPINFO(ep).type == ICOM_EP_TYPE_BULK)
+ type = icom_usb_trantype_bulk;
+ else
+ type = icom_usb_trantype_interrutpt;
+
+ len = strlen(wbuf);
+ tout *= 1000.0; /* Timout in msec */
+
+ top = (int)(tout + 0.5); /* Timeout period in msecs */
+ toc = (int)(tout/top + 0.5); /* Number of timout periods in timeout */
+ if (toc < 1)
+ toc = 1;
+
+ /* Until data is all written, we time out, or the user aborts */
+ for (i = toc; i > 0 && len > 0;) {
+ int c, rv;
+ a1logd(p->log, 8, "icoms_usb_ser_write: attempting to write %d bytes to usb top = %d, i = %d\n",len,top,i);
+
+ rv = icoms_usb_transaction(p, NULL, &wbytes, type, (unsigned char)ep, (unsigned char *)wbuf, len, top);
+ if (rv != ICOM_OK) {
+ if (rv != ICOM_TO) {
+ retrv |= rv;
+ break;
+ }
+ i--; /* timeout */
+ } else { /* Account for bytes written */
+ a1logd(p->log, 8, "icoms_usb_ser_write: wrote %d bytes\n",wbytes);
+ i = toc;
+ wbuf += wbytes;
+ len -= wbytes;
+ }
+ }
+ if (i <= 0) /* Must have timed out */
+ retrv |= ICOM_TO;
+
+ a1logd(p->log, 8, "icoms_usb_ser_write: returning ICOM err 0x%x\n",retrv);
+
+ return retrv;
+}
+
+
+/* Read characters into the buffer */
+/* Return string will be terminated with a nul */
+/* Read only in paket sized chunks, and retry if */
+/* the bytes requested aren'r read, untill we get a */
+/* timeout or a terminating char is read */
+static int
+icoms_usb_ser_read(icoms *p,
+char *rbuf, /* Buffer to store characters read */
+int bsize, /* Buffer size */
+char tc, /* Terminating characer */
+int ntc, /* Number of terminating characters */
+double tout) /* Time out in seconds */
+{
+ int j, rbytes;
+ long toc, i, top; /* Timout count, counter, timeout period */
+ char *rrbuf = rbuf; /* Start of return buffer */
+ int ep = p->rd_ep; /* End point */
+ icom_usb_trantype type; /* bulk or interrupt */
+ int retrv = ICOM_OK;
+
+#ifdef QUIET_MEMCHECKERS
+ memset(rbuf, 0, bsize);
+#endif
+
+ if (!p->is_open) {
+ a1loge(p->log, ICOM_SYS, "icoms_usb_ser_read: device is not open\n");
+ return ICOM_SYS;
+ }
+
+ if (p->EPINFO(ep).valid == 0) {
+ a1loge(p->log, ICOM_SYS, "icoms_usb_ser_read: invalid end point 0x%02x\n",ep);
+ return ICOM_SYS;
+ }
+
+ if (p->EPINFO(ep).type != ICOM_EP_TYPE_BULK
+ && p->EPINFO(ep).type != ICOM_EP_TYPE_INTERRUPT) {
+ a1loge(p->log, ICOM_SYS, "icoms_usb_ser_read: unhandled end point type %d",p->EPINFO(ep).type);
+ return ICOM_SYS;
+ }
+
+ if (p->EPINFO(ep).type == ICOM_EP_TYPE_BULK)
+ type = icom_usb_trantype_bulk;
+ else
+ type = icom_usb_trantype_interrutpt;
+
+ if (bsize < 3) {
+ a1loge(p->log, ICOM_SYS, "icoms_usb_ser_read given too small a buffer (%d)", bsize);
+ return ICOM_SYS;
+ }
+
+ for (i = 0; i < bsize; i++) rbuf[i] = 0;
+
+ tout *= 1000.0; /* Timout in msec */
+ bsize--; /* Allow space for null */
+
+ /* Have to do this in one go, because libusb has no way */
+ /* of timing out and returning the number of characters read */
+ /* up to the timeout, and it looses characters. */
+ top = (int)(tout + 0.5); /* Timeout period in msecs */
+ toc = (int)(tout/top + 0.5); /* Number of timout periods in timeout */
+ if (toc < 1)
+ toc = 1;
+
+ a1logd(p->log, 8, "\nicoms_usb_ser_read: end point 0x%x, read quanta %d\n",p->rd_ep,p->rd_qa);
+ /* Until data is all read, we time out, or the user aborts */
+ for (i = toc, j = 0; i > 0 && bsize > 1 && j < ntc ;) {
+ int c, rv;
+ int rsize = p->rd_qa < bsize ? p->rd_qa : bsize;
+
+ a1logd(p->log, 8, "icoms_usb_ser_read: attempting to read %d bytes from usb, top = %d, i = %d, j = %d\n",bsize > p->rd_qa ? p->rd_qa : bsize,top,i,j);
+ /* We read one read quanta at a time (usually 8 bytes), to avoid */
+ /* problems with libusb loosing characters whenever it times out. */
+ rv = icoms_usb_transaction(p, NULL, &rbytes, type, (unsigned char)ep, (unsigned char *)rbuf, rsize, top);
+ if (rv != 0 && rv != ICOM_SHORT) {
+ a1logd(p->log, 8, "icoms_usb_ser_read: read failed with 0x%x, rbuf = '%s'\n",rv,icoms_fix(rrbuf));
+ if (rv != ICOM_TO) {
+ retrv |= rv;
+ break;
+ }
+ i--; /* Timeout */
+ } else { /* Account for bytes read */
+ a1logd(p->log, 8, "icoms_usb_ser_read: read read %d bytes, rbuf = '%s'\n",rbytes,icoms_fix(rrbuf));
+ i = toc;
+ bsize -= rbytes;
+ while(rbytes) { /* Count termination characters */
+ if (*rbuf++ == tc)
+ j++;
+ rbytes--;
+ }
+ }
+ }
+
+ if (i <= 0) /* Must have timed out */
+ retrv |= ICOM_TO;
+
+ *rbuf = '\000';
+
+ a1logd(p->log, 8, "icoms_usb_ser_read: returning '%s' ICOM err 0x%x\n",icoms_fix(rrbuf),retrv);
+
+ return retrv;
+}
+
+
+/* ------------------------------------------------- */
+
+/* Set the usb port number and characteristics. */
+/* This may be called to re-establish a connection that has failed */
+/* return an icom error */
+static int
+icoms_set_usb_port(
+icoms *p,
+int config, /* Configuration */
+int wr_ep, /* "Serial" Write end point */
+int rd_ep, /* "Serial" Read end point */
+icomuflags usbflags, /* Any special handling flags */
+int retries, /* > 0 if we should retry set_configuration (100msec) */
+char **pnames /* List of process names to try and kill before opening */
+) {
+ a1logd(p->log, 8, "icoms_set_usb_port: About to set usb port characteristics\n");
+
+ if (p->port_type(p) == icomt_usb) {
+ int rv;
+
+ if (p->is_open)
+ p->close_port(p);
+
+ if ((rv = usb_open_port(p, config, wr_ep, rd_ep, usbflags, retries, pnames)) != ICOM_OK) {
+ return rv;
+ }
+
+ p->write = icoms_usb_ser_write;
+ p->read = icoms_usb_ser_read;
+
+ }
+ a1logd(p->log, 6, "icoms_set_usb_port: usb port characteristics set ok\n");
+
+#ifndef NATIVE_USB
+ /* libusb doesn't have any facility for re-directing its */
+ /* debug messages. Since we're moving away from it, */
+ /* ignore the problem. */
+ if (p->log->debug >= 8) { /* Could this go inside usb_open_port ? */
+# ifdef USE_LIBUSB1
+ libusb_set_debug(NULL, p->log->debug);
+# else
+ usb_set_debug(p->debug);
+# endif
+ }
+#endif /* NATIVE_USB */
+
+ return ICOM_OK;
+}
+
+/* ---------------------------------------------------------------------------------*/
+
+/* Set the USB specific icoms methods */
+void usb_set_usb_methods(
+icoms *p
+) {
+ p->set_usb_port = icoms_set_usb_port;
+ p->usb_control = icoms_usb_control;
+ p->usb_read = icoms_usb_rw;
+ p->usb_write = icoms_usb_rw;
+ p->usb_cancel_io = icoms_usb_cancel_io;
+ p->usb_resetep = icoms_usb_resetep;
+ p->usb_clearhalt = icoms_usb_clearhalt;
+}
+
+/* ---------------------------------------------------------------------------------*/
+
+#endif /* ENABLE_USB */
diff --git a/spectro/usbio.h b/spectro/usbio.h
new file mode 100644
index 0000000..5c982ae
--- /dev/null
+++ b/spectro/usbio.h
@@ -0,0 +1,247 @@
+
+#ifndef USBIO_H
+
+ /* General USB I/O support */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 2006/22/4
+ *
+ * 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.
+ */
+
+#ifdef ENABLE_USB
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+
+#ifdef NATIVE_USB
+
+/* Standard USB protocol defines */
+# include "iusb.h"
+
+/* Opaque structure to hide implementation details in icoms */
+
+# ifdef NT
+
+/* MSWin native USB context */
+struct usb_idevice {
+ /* icompath stuff: */
+ char *dpath; /* Device path */
+ int nconfig; /* Number of configurations */
+ int config; /* This config (always 1) */
+ int nifce; /* Number of interfaces */
+ usb_ep ep[32]; /* Information about each end point for general usb i/o */
+ /* Stuff setup when device is open: */
+ HANDLE handle;
+};
+
+# endif /* NT */
+
+# if defined(UNIX) && defined(__APPLE__)
+
+/* OS X structure version wrangling */
+
+/* usb_device_t - for communicating with the device. */
+#if defined (kIOUSBDeviceInterfaceID320)
+# define usb_device_t IOUSBDeviceInterface320
+# define DeviceInterfaceID kIOUSBDeviceInterfaceID320
+# define DeviceVersion 320
+#elif defined (kIOUSBDeviceInterfaceID300)
+# define usb_device_t IOUSBDeviceInterface300
+# define DeviceInterfaceID kIOUSBDeviceInterfaceID300
+# define DeviceVersion 300
+#elif defined (kIOUSBDeviceInterfaceID245)
+# define usb_device_t IOUSBDeviceInterface245
+# define DeviceInterfaceID kIOUSBDeviceInterfaceID245
+# define DeviceVersion 245
+#elif defined (kIOUSBDeviceInterfaceID197)
+# define usb_device_t IOUSBDeviceInterface197
+# define DeviceInterfaceID kIOUSBDeviceInterfaceID197
+# define DeviceVersion 197
+#elif defined (kIOUSBDeviceInterfaceID187)
+# define usb_device_t IOUSBDeviceInterface187
+# define DeviceInterfaceID kIOUSBDeviceInterfaceID187
+# define DeviceVersion 187
+#elif defined (kIOUSBDeviceInterfaceID182)
+# define usb_device_t IOUSBDeviceInterface182
+# define DeviceInterfaceID kIOUSBDeviceInterfaceID182
+# define DeviceVersion 182
+#else
+# error "Unknown kIOUSBDeviceInterface version"
+#endif
+
+/* usb_interface_t - for communicating with an interface in the device */
+#if defined (kIOUSBInterfaceInterfaceID300)
+# define usb_interface_t IOUSBInterfaceInterface300
+# define InterfaceInterfaceID kIOUSBInterfaceInterfaceID300
+# define InterfaceVersion 300
+#elif defined (kIOUSBInterfaceInterfaceID245)
+# define usb_interface_t IOUSBInterfaceInterface245
+# define InterfaceInterfaceID kIOUSBInterfaceInterfaceID245
+# define InterfaceVersion 245
+#elif defined (kIOUSBInterfaceInterfaceID220)
+# define usb_interface_t IOUSBInterfaceInterface220
+# define InterfaceInterfaceID kIOUSBInterfaceInterfaceID220
+# define InterfaceVersion 220
+#elif defined (kIOUSBInterfaceInterfaceID197)
+# define usb_interface_t IOUSBInterfaceInterface197
+# define InterfaceInterfaceID kIOUSBInterfaceInterfaceID197
+# define InterfaceVersion 197
+#elif defined (kIOUSBInterfaceInterfaceID190)
+# define usb_interface_t IOUSBInterfaceInterface190
+# define InterfaceInterfaceID kIOUSBInterfaceInterfaceID190
+# define InterfaceVersion 190
+#elif defined (kIOUSBInterfaceInterfaceID182)
+# define usb_interface_t IOUSBInterfaceInterface182
+# define InterfaceInterfaceID kIOUSBInterfaceInterfaceID182
+# define InterfaceVersion 182
+#else
+# error "Unknown kIOUSBInterfaceInterfaceID"
+#endif
+
+/* OS X native USB context */
+struct usb_idevice {
+ /* icompath stuff: */
+ int lid; /* Location ID */
+ io_object_t ioob; /* USB io registry object */
+ int nconfig; /* Number of configurations */
+ int config; /* This config (always 1) */
+ int nifce; /* Number of interfaces */
+ /* Stuff setup when device is open: */
+ usb_device_t **device; /* OS X USB device we've opened */
+ usb_interface_t **interfaces[32]; /* nifce interfaces */
+ CFRunLoopSourceRef cfsources[32]; /* Corresponding event sources */
+ pthread_t thread; /* RunLoop thread */
+ pthread_mutex_t lock; /* Protect cfrunloop and cond */
+ pthread_cond_t cond; /* Signal from thread that it's started */
+ IOReturn thrv; /* Thread return value */
+ CFRunLoopRef cfrunloop; /* RunLoop */
+ CFRunLoopSourceRef cfsource;/* Device event sources */
+};
+# endif /* OS X */
+
+# if defined(UNIX) && !defined(__APPLE__)
+
+/* Linux USB context */
+struct usb_idevice {
+ /* icompath stuff: */
+ char *dpath; /* Device path */
+ int nconfig; /* Number of configurations */
+ int config; /* This config (always 1) */
+ int nifce; /* Number of interfaces */
+ usb_ep ep[32]; /* Information about each end point for general usb i/o */
+ /* Stuff setup when device is open: */
+ int fd; /* Device file descriptor */
+ pthread_t thread; /* Reaper thread */
+ volatile int shutdown; /* Flag to tell reaper that we're closing the fd */
+ int sd_pipe[2]; /* pipe to signal sutdown */
+
+ /* These are simply to deal with the device going away: */
+ volatile int running; /* Reaper thread is running. Set to 0 on reap failure */
+ pthread_mutex_t lock; /* Protect reqs list */
+ struct _usbio_req *reqs; /* linked list of current reqs */
+};
+
+# endif /* Linux */
+
+#else /* !NATIVE_USB - Using libusb */
+
+# ifdef USE_LIBUSB1
+
+# include "libusb.h"
+# define usb_idevice libusb_device
+# define IUSB_ENDPOINT_DIR_MASK LIBUSB_ENDPOINT_DIR_MASK
+# define IUSB_ENDPOINT_IN LIBUSB_ENDPOINT_IN
+# define IUSB_ENDPOINT_OUT LIBUSB_ENDPOINT_OUT
+# define IUSB_REQ_RECIP_DEVICE LIBUSB_RECIPIENT_DEVICE
+# define IUSB_REQ_RECIP_INTERFACE LIBUSB_RECIPIENT_INTERFACE
+# define IUSB_REQ_RECIP_ENDPOINT LIBUSB_RECIPIENT_ENDPOINT
+# define IUSB_REQ_TYPE_STANDARD LIBUSB_REQUEST_TYPE_STANDARD
+# define IUSB_REQ_TYPE_CLASS LIBUSB_REQUEST_TYPE_CLASS
+# define IUSB_REQ_TYPE_VENDOR LIBUSB_REQUEST_TYPE_VENDOR
+# define IUSB_ENDPOINT_TYPE_MASK LIBUSB_TRANSFER_TYPE_MASK
+
+# else
+
+# include "usb.h"
+# define usb_idevice usb_device
+# define IUSB_ENDPOINT_DIR_MASK USB_ENDPOINT_DIR_MASK
+# define IUSB_ENDPOINT_IN USB_ENDPOINT_IN
+# define IUSB_ENDPOINT_OUT USB_ENDPOINT_OUT
+# define IUSB_REQ_RECIP_DEVICE USB_RECIP_DEVICE
+# define IUSB_REQ_RECIP_INTERFACE USB_RECIP_INTERFACE
+# define IUSB_REQ_RECIP_ENDPOINT USB_RECIP_ENDPOINT
+# define IUSB_REQ_TYPE_STANDARD USB_TYPE_STANDARD
+# define IUSB_REQ_TYPE_CLASS USB_TYPE_CLASS
+# define IUSB_REQ_TYPE_VENDOR USB_TYPE_VENDOR
+# define IUSB_ENDPOINT_TYPE_MASK USB_ENDPOINT_TYPE_MASK
+
+# endif
+#endif
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Transfer types for communicating to usb implementation */
+typedef enum {
+ icom_usb_trantype_command = 0,
+ icom_usb_trantype_interrutpt = 1,
+ icom_usb_trantype_bulk = 2
+} icom_usb_trantype;
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Cancelation token. */
+struct _usb_cancelt {
+ amutex cmtx;
+ void *hcancel; /* Pointer to implementation cancel handle */
+};
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* These routines suplement the class code in icoms_nt.c and icoms_ux.c */
+
+/* Add paths to USB connected instruments, to the existing */
+/* icompath paths in the icoms structure. */
+/* return icom error */
+int usb_get_paths(struct _icompaths *p);
+
+void usb_close_port(icoms *p);
+
+/* Set the USB specific icoms methods */
+void usb_set_usb_methods(icoms *p);
+
+/* Copy usb_idevice contents from icompaths to icom */
+/* return icom error */
+int usb_copy_usb_idevice(icoms *d, icompath *s);
+
+/* Cleanup and then free a usb_del_usb_idevice */
+void usb_del_usb_idevice(struct usb_idevice *dev);
+
+/* Cleanup any USB specific icoms info */
+void usb_del_usb(icoms *p);
+
+/* Install the cleanup signal handlers */
+/* (used inside usb_open_port(), hid_open_port() */
+void usb_install_signal_handlers(icoms *p);
+
+/* Delete an icoms from our static signal cleanup list */
+/* (used inside usb_close_port(), hid_close_port() */
+void usb_delete_from_cleanup_list(icoms *p);
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif /* ENABLE_USB */
+
+#define USBIO_H
+#endif /* USBIO_H */
diff --git a/spectro/usbio_lusb.c b/spectro/usbio_lusb.c
new file mode 100644
index 0000000..f78bcca
--- /dev/null
+++ b/spectro/usbio_lusb.c
@@ -0,0 +1,897 @@
+
+/* General USB I/O support, legacy Libusb 0.1/1.0 implementation, no longer */
+/* used by default, but can be configured in the Jamfile. */
+/* The corresponding libusb code is not distributed with the current source */
+/* though, and would have to be copied from ArgyllCMS V1.4.0 */
+
+/* This file is conditionaly #included into usbio.c */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 2006/22/4
+ *
+ * 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.
+ */
+
+/* To simplify error messages: */
+#ifdef USE_LIBUSB1
+# define USB_STRERROR(RV) libusb_strerror(RV)
+#else
+# define USB_STRERROR(RV) usb_strerror()
+#endif
+
+#ifdef USE_LIBUSB1
+# define usb_device libusb_device
+# define usb_device_descriptor libusb_device_descriptor
+# define usb_dev_handle libusb_device_handle
+# define usb_config_descriptor libusb_config_descriptor
+# define usb_strerror libusb_strerror
+#endif
+
+#if defined(__FreeBSD__) /* Shut spurious warnings up */
+# define CASTFIX (intptr_t)
+#else
+# define CASTFIX
+#endif
+
+/* Check a USB Vendor and product ID, and add the device */
+/* to the icoms path if it is supported. */
+/* (this is used to help implement usb_get_paths) */
+/* return icom error */
+static int usb_check_and_add(
+icompaths *p,
+struct usb_device *usbd
+) {
+ instType itype;
+ int nep; /* Number of end points */
+
+ struct usb_device_descriptor descriptor;
+
+#ifdef USE_LIBUSB1
+ enum libusb_error rv;
+
+ if ((rv = libusb_get_device_descriptor(usbd, &descriptor)) != LIBUSB_SUCCESS) {
+ a1loge(p->log, ICOM_SYS, "usb_check_and_add: failed with %d (%s)\n",
+ rv,USB_STRERROR(rv));
+ return ICOM_SYS;
+ }
+#else
+ descriptor = usbd->descriptor; /* Copy */
+#endif
+ a1logd(p->log, 6, "usb_check_and_add: called with VID 0x%x, PID 0x%x\n",descriptor.idVendor, descriptor.idProduct);
+
+#ifdef USE_LIBUSB1
+#if defined(NT) || defined(__APPLE__)
+ /* Ignore libusb1 HID driver capability */
+ {
+ struct libusb_config_descriptor *confdesc = NULL;
+
+ /* If not obviously HID, we need to fetch the config descriptor */
+ if (descriptor.bDeviceClass != LIBUSB_CLASS_HID
+ && (rv = libusb_get_config_descriptor(usbd, 0, &confdesc)) != LIBUSB_SUCCESS) {
+ /* This seems to happen for hubs etc., so ignore it */
+ a1logd(p->log, 6 , "usb_check_and_add: get conf desc. failed - device not reconized\n");
+ return ICOM_OK;
+ }
+
+ if (descriptor.bDeviceClass == LIBUSB_CLASS_HID
+ || (confdesc->bNumInterfaces > 0
+ && confdesc->interface[0]. num_altsetting > 0
+ && confdesc->interface[0].altsetting[0].bInterfaceClass == LIBUSB_CLASS_HID)) {
+ int i;
+ /* See if this devices is already in the list via the HID interface */
+ /* (This may not be 100% correct in the face of multiple instances
+ of the same device, if Windows allows different drivers for different
+ instances of the same device type.) */
+ for (i = 0; i < p->npaths; i++) {
+ if (p->paths[i]->vid == descriptor.idVendor
+ && p->paths[i]->pid == descriptor.idProduct)
+ break; /* Yes */
+ }
+ if (i < p->npaths) {
+ a1logd(p->log, 6, "Is an HID device and already added\n");
+ if (confdesc != NULL)
+ libusb_free_config_descriptor(confdesc);
+ return ICOM_OK;
+ }
+ }
+ if (confdesc != NULL)
+ libusb_free_config_descriptor(confdesc);
+ }
+#endif
+#endif
+
+ /* The i1pro2 is detected by checking the number of end points, */
+ /* so set this value in icom now by looking at the descriptor */
+ {
+#ifdef USE_LIBUSB1
+ struct libusb_config_descriptor *confdesc;
+ const struct libusb_interface_descriptor *ifd;
+
+ if (libusb_get_config_descriptor(usbd, 0, &confdesc) != LIBUSB_SUCCESS) {
+ /* This seems to happen for hubs etc., so ignore it */
+ a1logd(p->log, 6 , "usb_check_and_add: get conf desc. failed - device not reconized\n");
+ return ICOM_OK;
+ }
+
+ ifd = &confdesc->interface[0].altsetting[0];
+ nep = ifd->bNumEndpoints;
+ if (confdesc != NULL)
+ libusb_free_config_descriptor(confdesc);
+#else
+ struct usb_interface_descriptor *ifd;
+ ifd = &usbd->config[0].interface[0].altsetting[0];
+ nep = ifd->bNumEndpoints;
+#endif
+ }
+
+ if ((itype = inst_usb_match((unsigned int)descriptor.idVendor,
+ (unsigned int)descriptor.idProduct, nep)) != instUnknown) {
+ char pname[400];
+
+ a1logd(p->log, 2, "usb_check_and_add: found known instrument VID 0x%x, PID 0x%x\n",
+ descriptor.idVendor, descriptor.idProduct);
+
+ /* Create a path/identification */
+ /* (devnum doesn't seem valid ?) */
+#ifdef USE_LIBUSB1
+ libusb_ref_device(usbd); /* Keep it */
+ sprintf(pname,"usb:/bus%d/dev%d/ (%s)",libusb_get_bus_number(usbd),libusb_get_device_address(usbd), inst_name(itype));
+#else
+# if defined(UNIX)
+ sprintf(pname,"usb:/bus%d/dev%d (%s)",usbd->bus->location >> 24, usbd->devnum, inst_name(itype));
+# else
+ sprintf(pname,"usb:/bus%lu/dev%d (%s)",usbd->bus->location, usbd->devnum, inst_name(itype));
+# endif
+#endif
+
+ /* Add the path to the list */
+ p->add_usb(p, pname, descriptor.idVendor, descriptor.idProduct, nep, usbd, itype);
+ return ICOM_OK;
+ }
+ a1logd(p->log, 6 , "usb_check_and_add: device not reconized\n");
+
+ return ICOM_OK;
+}
+
+#ifdef USE_LIBUSB1
+
+/* Add paths of USB connected instruments */
+/* return icom error */
+int usb_get_paths(
+icompaths *p
+) {
+ ssize_t i, nlist;
+ struct libusb_device **list;
+
+ /* Scan the USB busses for instruments we recognise */
+ /* We're not expecting any of our instruments to be an interface on a device. */
+
+ /* Use the default context to avoid worying about versions */
+ /* of libusb1 that don't reference count them. (ie. would need */
+ /* copies in both icompaths and icoms) */
+
+ libusb_init(NULL); /* Use default context */
+
+ /* Enable lower level debugging of device finding */
+ if (p->log->debug >= 8)
+ libusb_set_debug(NULL, p->log->debug);
+
+ if ((nlist = libusb_get_device_list(NULL, &list)) < 0) {
+ a1loge(p->log, ICOM_SYS, "usb_get_paths: get_device_list failed with %d (%s)\n",
+ nlist,USB_STRERROR(nlist));
+ return ICOM_SYS;
+ }
+
+ a1logd(p->log, 6, "usb_get_paths: about to look through devices:\n");
+
+ for (i = 0; i < nlist; i++) {
+ usb_check_and_add(p, list[i]);
+ }
+
+ libusb_free_device_list(list, 1);
+
+ a1logd(p->log, 8, "usb_get_paths: returning %d paths and ICOM_OK\n",p->npaths);
+ return ICOM_OK;
+}
+
+#else /* !USE_LIBUSB1 */
+
+/* Add paths of USB connected instruments */
+/* return icom error */
+int usb_get_paths(
+icompaths *p
+) {
+ struct usb_bus *bus;
+ int rv;
+
+ /* Enable lower level debugging of device finding */
+ if (p->log->debug >= 8)
+ usb_set_debug(p->log->debug);
+
+ /* Scan the USB busses for instruments we recognise */
+ /* We're not expecting any of our instruments to be an interface on a device. */
+
+ usb_init();
+ if ((rv = usb_find_busses()) < 0) {
+ a1loge(p->log, ICOM_SYS, "usb_get_paths: find_busses failed with %d (%s)\n",
+ rv,USB_STRERROR(rv));
+ return ICOM_SYS;
+ }
+ if ((rv = usb_find_devices()) < 0) {
+ a1loge(p->log, ICOM_SYS, "usb_get_paths: usb_find_devices failed with %d (%s)\n",
+ rv,USB_STRERROR(rv));
+ return ICOM_SYS;
+ }
+
+ a1logd(p->log, 6, "usb_get_paths: about to look through busses:\n");
+
+ for (bus = usb_get_busses(); bus != NULL; bus = bus->next) {
+ struct usb_device *dev;
+ a1logd(p->log, 6, "usb_get_paths: about to look through devices:\n");
+ for (dev = bus->devices; dev != NULL; dev = dev->next) {
+ if ((rv = usb_check_and_add(p, dev)) != ICOM_OK)
+ return rv;
+ }
+ }
+ a1logd(p->log, 8, "usb_get_paths: returning %d paths and ICOM_OK\n",p->npaths);
+ return ICOM_OK;
+}
+#endif /* !USE_LIBUSB1 */
+
+/* Copy usb_idevice contents from icompaths to icom */
+/* return icom error */
+int usb_copy_usb_idevice(icoms *d, icompath *s) {
+ if (s->usbd == NULL) {
+ d->usbd = NULL;
+ return ICOM_OK;
+ }
+
+ d->usbd = s->usbd; /* Copy pointer */
+#ifdef USE_LIBUSB1
+ libusb_ref_device(d->usbd);
+#endif
+ return ICOM_OK;
+}
+
+/* Cleanup and then free a usb dev entry */
+void usb_del_usb_idevice(struct usb_idevice *usbd) {
+
+ if (usbd == NULL)
+ return;
+
+#ifdef USE_LIBUSB1
+ libusb_unref_device(usbd);
+#endif
+}
+
+/* Cleanup any USB specific icoms state */
+void usb_del_usb(icoms *p) {
+
+#ifdef USE_LIBUSB1
+ usb_del_usb_idevice(p->usbd);
+#endif /* USE_LIBUSB1 */
+}
+
+/* Close an open USB port */
+/* If we don't do this, the port and/or the device may be left in an unusable state. */
+void usb_close_port(icoms *p) {
+
+ a1logd(p->log, 8, "usb_close_port: called\n");
+
+ if (p->is_open && p->usbh != NULL) {
+ int iface;
+
+ for (iface = 0; iface < p->nifce; iface++) {
+#ifdef USE_LIBUSB1
+ libusb_release_interface(p->usbh, iface);
+#else
+ usb_release_interface(p->usbh, iface);
+#endif
+ }
+
+ /* Workaround for some bugs */
+ if (p->uflags & icomuf_reset_before_close) {
+#ifdef USE_LIBUSB1
+ libusb_reset_device(p->usbh);
+#else
+ usb_reset(p->usbh);
+#endif
+ }
+
+ /* Close as well, othewise we can't re-open */
+ {
+#ifdef USE_LIBUSB1
+ libusb_close(p->usbh);
+#else
+ usb_close(p->usbh);
+#endif
+ }
+ p->usbh = NULL;
+
+ a1logd(p->log, 8, "usb_close_port: port has been released and closed\n");
+ }
+ p->is_open = 0;
+
+ /* Find it and delete it from our static cleanup list */
+ usb_delete_from_cleanup_list(p);
+
+}
+
+/* Open a USB port for all our uses. */
+/* This always re-opens the port */
+/* return icom error */
+static int usb_open_port(
+icoms *p,
+int config, /* Configuration number */
+int wr_ep, /* Write end point */
+int rd_ep, /* Read end point */
+icomuflags usbflags,/* Any special handling flags */
+int retries, /* > 0 if we should retry set_configuration (100msec) */
+char **pnames /* List of process names to try and kill before opening */
+) {
+ int tries = 0;
+ a1logd(p->log, 8, "usb_open_port: Make sure USB port is open, tries %d\n",retries);
+
+ if (p->is_open)
+ p->close_port(p);
+
+ /* Make sure the port is open */
+ if (!p->is_open) {
+ struct usb_device_descriptor descriptor;
+#ifdef USE_LIBUSB1
+ const struct libusb_interface_descriptor *ifd;
+ struct libusb_config_descriptor *confdesc;
+#else
+ struct usb_interface_descriptor *ifd;
+#endif
+ int rv, i, iface;
+ kkill_nproc_ctx *kpc = NULL;
+
+ /* Do open retries */
+ for (tries = 0; retries >= 0; retries--, tries++) {
+
+ a1logd(p->log, 8, "usb_open_port: About to open USB port '%s'\n",p->name);
+
+ if (tries > 0) {
+ //msec_sleep(i_rand(50,100));
+ msec_sleep(77);
+ }
+ if (tries > 0 && pnames != NULL && kpc == NULL) {
+#if defined(__APPLE__) || defined(NT)
+ if ((kpc = kkill_nprocess(pnames, p->log)) == NULL)
+ a1logd(p->log, 1, "usb_open_port: kkill_nprocess returned error!\n");
+#endif /* __APPLE__ */
+ }
+
+#ifdef USE_LIBUSB1
+ if ((rv = libusb_open(p->usbd, &p->usbh)) != LIBUSB_SUCCESS)
+#else
+ if ((p->usbh = usb_open(p->usbd)) == NULL)
+#endif
+ {
+ a1logd(p->log, 8, "usb_open_port: open '%s' config %d failed (%s) (Permissions ?)\n",p->name,config,USB_STRERROR(rv));
+ if (retries <= 0) {
+ if (kpc != NULL)
+ kpc->del(kpc);
+ a1loge(p->log, ICOM_SYS, "usb_open_port: open '%s' config %d failed (%s) (Permissions ?)\n",p->name,config,USB_STRERROR(rv));
+ return ICOM_SYS;
+ }
+ continue;
+ } else if (p->debug)
+ a1logd(p->log, 2, "usb_open_port: open port '%s' succeeded\n",p->name);
+
+ /* Get a copy of the device descriptor so we can see device params */
+#ifdef USE_LIBUSB1
+ if (libusb_get_device_descriptor(p->usbd, &descriptor) != LIBUSB_SUCCESS) {
+ a1loge(p->log, ICOM_SYS, "usb_open_port: get device descriptor on '%s' failed with %d (%s)\n",p->name,rv,USB_STRERROR(rv));
+ return ICOM_SYS;
+ }
+#else
+ descriptor = p->usbd->descriptor; /* Copy */
+#endif
+
+ p->uflags = usbflags;
+
+ a1logd(p->log, 8, "usb_open_port: Number of configurations = %d\n",
+ descriptor.bNumConfigurations);
+ p->nconfig = descriptor.bNumConfigurations;
+ p->config = config;
+
+#if defined(UNIX_X11)
+ /* only call set_configuration on Linux if the device has more than one */
+ /* possible configuration, because Linux does a set_configuration by default, */
+ /* and two of them mess up instruments like the Spyder2 */
+
+ if (descriptor.bNumConfigurations > 1) {
+#endif
+
+ /* Can't skip this, as it is needed to setup the interface and end points on OS X */
+#ifdef USE_LIBUSB1
+ if ((rv = libusb_set_configuration(p->usbh, config)) < 0)
+#else
+ if ((rv = usb_set_configuration(p->usbh, config)) < 0)
+#endif
+ {
+ a1logd(p->log, 8, "usb_open_port: configuring '%s' to %d failed with %d (%s)\n",p->name,config,rv,USB_STRERROR(rv));
+ if (retries <= 0) {
+ if (kpc != NULL)
+ kpc->del(kpc);
+ a1loge(p->log, ICOM_SYS, "usb_open_port: configuring '%s' to %d failed with %d (%s)\n",p->name,config,rv,USB_STRERROR(rv));
+ return ICOM_SYS;
+ }
+ /* reset the port and retry */
+#ifdef USE_LIBUSB1
+ libusb_reset_device(p->usbh); // ~~999 ?????
+ libusb_close(p->usbh);
+#else
+ usb_reset(p->usbh);
+ usb_close(p->usbh);
+#endif
+ continue;
+ }
+#if defined(UNIX_X11)
+ } /* End of if bNumConfigurations > 1 */
+#endif
+
+ /* We're done */
+ break;
+ }
+
+ if (kpc != NULL)
+ kpc->del(kpc);
+
+ /* Claim all interfaces of this configuration */
+#ifdef USE_LIBUSB1
+ if ((rv = libusb_get_active_config_descriptor(p->usbd, &confdesc)) != LIBUSB_SUCCESS) {
+ a1loge(p->log, ICOM_SYS, "usb_open_port: get config desc. for '%s' failed with %d (%s)\n",p->name,rv,USB_STRERROR(rv));
+ return ICOM_SYS;
+ }
+ p->nifce = confdesc->bNumInterfaces;
+#else
+ p->nifce = p->usbd->config->bNumInterfaces;
+#endif
+
+// a1logv(p->log, 8, "usb_open_port: Number of interfaces = %d\n",p->nifce);
+
+ /* Claim all the interfaces */
+ for (iface = 0; iface < p->nifce; iface++) {
+ /* (Second parameter is bInterfaceNumber) */
+
+#ifdef USE_LIBUSB1
+ if ((rv = libusb_claim_interface(p->usbh, iface)) < 0) {
+ /* Detatch the existing interface if kernel driver is active. */
+ if (p->uflags & icomuf_detach
+ && libusb_kernel_driver_active(p->usbh, iface) == 1) {
+ libusb_detach_kernel_driver (p->usbh, iface);
+ if ((rv = libusb_claim_interface(p->usbh, iface)) < 0) {
+ a1loge(p->log, ICOM_SYS, "usb_open_port: Claiming USB port '%s' interface %d failed after detach with %d (%s)\n",p->name,iface,rv,USB_STRERROR(rv));
+ return ICOM_SYS;
+ }
+ } else {
+ a1loge(p->log, ICOM_SYS, "usb_open_port: Claiming USB port '%s' interface %d failed with %d (%s)\n",p->name,iface,rv,USB_STRERROR(rv));
+ return ICOM_SYS;
+ }
+ }
+#else
+ if ((rv = usb_claim_interface(p->usbh, iface)) < 0) {
+# if LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP == 1
+ /* Detatch the existing interface. */
+ if (p->uflags & icomuf_detach) {
+ usb_detach_kernel_driver_np(p->usbh, iface);
+ if ((rv = usb_claim_interface(p->usbh, iface)) < 0) {
+ a1loge(p->log, ICOM_SYS, "usb_open_port: Claiming USB port '%s' interface %d failed after detach with %d (%s)\n",p->name,iface,rv,USB_STRERROR(rv));
+ return ICOM_SYS;
+ }
+ }
+# else
+ a1loge(p->log, ICOM_SYS, "usb_open_port: Claiming USB port '%s' interface %d failed with %d (%s)\n",p->name,iface,rv,USB_STRERROR(rv));
+ return ICOM_SYS;
+# endif
+ }
+#endif
+
+ /* Fill in the end point details */
+#ifdef USE_LIBUSB1
+ ifd = &confdesc->interface[iface].altsetting[0];
+#else
+ ifd = &p->usbd->config[p->config-1].interface[iface].altsetting[0];
+#endif
+// a1logv(p->log, 8, "usb_open_port: Number of endpoints on iface %d = %d\n",iface, ifd->bNumEndpoints);
+ p->nep = ifd->bNumEndpoints;
+ for (i = 0; i < ifd->bNumEndpoints; i++) {
+ int ad = ifd->endpoint[i].bEndpointAddress;
+ p->EPINFO(ad).valid = 1;
+ p->EPINFO(ad).addr = ad;
+ p->EPINFO(ad).packetsize = ifd->endpoint[i].wMaxPacketSize;
+ p->EPINFO(ad).type = ifd->endpoint[i].bmAttributes & IUSB_ENDPOINT_TYPE_MASK;
+ /* Some I/F seem to hang if we do this, some seem to hang if we don't ! */
+ if (!(p->uflags & icomuf_no_open_clear))
+#ifdef USE_LIBUSB1
+ libusb_clear_halt(p->usbh, (unsigned char)ad);
+#else
+ usb_clear_halt(p->usbh, ad);
+#endif
+// a1logv(p->log, 8, "usb_open_port: ep %d: endpoint addr %02x pktsze %d, type %d\n",i,ad,ifd->endpoint[i].wMaxPacketSize,p->EPINFO(ad).type);
+ }
+
+#ifdef NEVER
+ /* Get the serial number */
+ {
+ int rv;
+ struct usb_device_descriptor descriptor;
+
+ a1logd(p->log, 8, "usb_open_port: About to get device serial number\n");
+# ifdef USE_LIBUSB1
+ if ((rv = libusb_get_device_descriptor(p->usbd, &descriptor)) != LIBUSB_SUCCESS) {
+ a1loge(p->log, ICOM_SYS, "usb_open_port: get_device_descriptor failed with %d USB port '%s'\n",rv,p->name);
+ return ICOM_SYS;
+ }
+# else
+ descriptor = dev->descriptor; /* Copy */
+# endif
+ if ((rv = libusb_get_string_descriptor_ascii(p->usbh, descriptor.iSerialNumber, p->serialno, 32)) <= 0) {
+ a1logd(p->log, 1, "usb_open_port: Failed to get device serial number %d (%s)\n",rv,USB_STRERROR(rv));
+ p->serialno[0] = '\000';
+ } else {
+ a1logd(p->log, 1, "usb_open_port: Device serial number = '%s'\n",p->serialno);
+ }
+ }
+#endif /* NEVER */
+ }
+
+ /* Set "serial" coms values */
+ p->wr_ep = wr_ep;
+ p->rd_ep = rd_ep;
+ p->rd_qa = p->EPINFO(rd_ep).packetsize;
+ if (p->rd_qa == 0)
+ p->rd_qa = 8;
+ a1logd(p->log, 8, "usb_open_port: 'serial' read quanta = packet size = %d\n",p->rd_qa);
+
+#ifdef USE_LIBUSB1
+ if (confdesc != NULL)
+ libusb_free_config_descriptor(confdesc);
+#endif
+
+ p->is_open = 1;
+ a1logd(p->log, 8, "usb_open_port: USB port is now open\n");
+ }
+
+ /* Install the cleanup signal handlers, and add to our cleanup list */
+ usb_install_signal_handlers(p);
+
+ return ICOM_OK;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Time out error return value */
+
+#ifdef USE_LIBUSB1
+#define USBIO_ERROR_TIMEOUT LIBUSB_ERROR_TIMEOUT
+#else
+# if defined(UNIX)
+#define USBIO_ERROR_TIMEOUT -ETIMEDOUT
+# else
+#define USBIO_ERROR_TIMEOUT -116 /* libusb-win32 code */
+# endif /* UNIX */
+#endif
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+#ifdef USE_LIBUSB1
+
+/* Callback functions */
+static void bulk_transfer_cb(struct libusb_transfer *transfer)
+{
+ int *completed = transfer->user_data;
+ *completed = 1;
+ /* caller interprets results and frees transfer */
+}
+
+/* Version of libusb1 sync to async code that returns the pointer */
+/* to the transfer structure so that the transfer can be cancelled */
+/* by another thread. */
+/* Return an icoms error code */
+static int do_sync_usb_transfer(
+ icoms *p,
+ struct libusb_device_handle *dev_handle,
+ usb_cancelt *cancelt,
+ int *transferred,
+ icom_usb_trantype ttype,
+ unsigned char endpoint,
+ unsigned char *buffer,
+ int length,
+ unsigned int timeout
+) {
+ enum libusb_transfer_type type;
+ struct libusb_transfer *transfer = libusb_alloc_transfer(0);
+ int completed = 0;
+ int rv;
+
+ if (!transfer) {
+ a1logd(p->log, 1, "do_sync_usb_transfer: transfer is NULL!\n");
+ return ICOM_SYS;
+ }
+
+ /* Translate icoms transfer type of libusb1 */
+ switch (ttype) {
+ case icom_usb_trantype_command:
+ type = LIBUSB_TRANSFER_TYPE_CONTROL;
+ break;
+ case icom_usb_trantype_interrutpt:
+ type = LIBUSB_TRANSFER_TYPE_INTERRUPT;
+ break;
+ case icom_usb_trantype_bulk:
+ type = LIBUSB_TRANSFER_TYPE_BULK;
+ break;
+ }
+
+ libusb_fill_bulk_transfer(transfer, dev_handle, endpoint, buffer, length,
+ bulk_transfer_cb, &completed, timeout);
+ transfer->type = type;
+
+ if ((rv = libusb_submit_transfer(transfer)) < 0) {
+ libusb_free_transfer(transfer);
+ a1logd(p->log, 1, "do_sync_usb_transfer: Submitting transfer failed with %d (%s)\n",rv,USB_STRERROR(rv));
+ if ((endpoint & IUSB_ENDPOINT_DIR_MASK) == IUSB_ENDPOINT_OUT)
+ return ICOM_USBW;
+ else
+ return ICOM_USBR;
+ }
+
+ if (cancelt != NULL) {
+ usb_lock_cancel(cancelt);
+ cancelt->hcancel = (void *)transfer;
+ usb_unlock_cancel(cancelt);
+ }
+
+ while (!completed) {
+ if ((rv = libusb_handle_events_check(NULL, &completed)) < 0) {
+ if (rv == LIBUSB_ERROR_INTERRUPTED)
+ continue; /* Retry */
+ /* Give up - cancel transfer and wait until complete */
+ libusb_cancel_transfer(transfer);
+ while (!completed) {
+ if (libusb_handle_events_check(NULL, &completed) < 0)
+ break;
+ }
+ if (cancelt != NULL) {
+ usb_lock_cancel(cancelt);
+ cancelt->hcancel = NULL;
+ usb_unlock_cancel(cancelt);
+ }
+ libusb_free_transfer(transfer);
+ a1loge(p->log, ICOM_SYS, "do_sync_usb_transfer: handle_events failed with %d (%s)\n",rv,USB_STRERROR(rv));
+ return ICOM_SYS;
+ }
+ }
+
+ *transferred = transfer->actual_length;
+ switch (transfer->status) {
+ case LIBUSB_TRANSFER_COMPLETED:
+ rv = ICOM_OK;
+ break;
+ case LIBUSB_TRANSFER_TIMED_OUT:
+ rv = ICOM_TO;
+ break;
+ case LIBUSB_TRANSFER_STALL:
+ case LIBUSB_TRANSFER_OVERFLOW:
+ case LIBUSB_TRANSFER_NO_DEVICE:
+ if ((endpoint & IUSB_ENDPOINT_DIR_MASK) == IUSB_ENDPOINT_OUT)
+ rv = ICOM_USBW;
+ else
+ rv = ICOM_USBR;
+ break;
+ case LIBUSB_TRANSFER_CANCELLED:
+ rv = ICOM_CANC;
+ break;
+ default:
+ a1loge(p->log, ICOM_SYS, "do_sync_usb_transfer: transfer faile with %d (%s)\n",rv,USB_STRERROR(rv));
+ rv = ICOM_SYS;
+ }
+
+ if (cancelt != NULL) {
+ usb_lock_cancel(cancelt);
+ cancelt->hcancel = NULL;
+ usb_unlock_cancel(cancelt);
+ }
+ libusb_free_transfer(transfer);
+
+ /* requested size wasn't transferred */
+ if (rv == ICOM_OK && *transferred != length)
+ rv = ICOM_SHORT;
+
+ return rv;
+}
+#endif /* USE_LIBUSB1 */
+
+/* Return icom error code */
+static int icoms_usb_control_msg(
+icoms *p,
+int *transferred,
+int requesttype, int request,
+int value, int index, unsigned char *bytes, int size,
+int timeout) {
+ int rv;
+
+ in_usb_rw++;
+ a1logd(p->log, 8, "icoms_usb_control_msg: type 0x%x, req 0x%x, size %d\n",requesttype,request,size);
+
+ if (transferred != NULL)
+ *transferred = 0;
+#ifdef USE_LIBUSB1
+ rv = libusb_control_transfer(p->usbh, (uint8_t)requesttype, (uint8_t)request,
+ (uint16_t)value, (uint16_t)index, bytes, (uint16_t)size, timeout);
+#else
+ rv = usb_control_msg(p->usbh, requesttype, request, value, index, (char *)bytes, size, timeout);
+#endif
+ if (in_usb_rw < 0) /* interrupt recurssion */
+ exit(0);
+
+ in_usb_rw--;
+
+ if (rv < 0) {
+ if (rv == USBIO_ERROR_TIMEOUT) { /* Not a timeout */
+ rv = ICOM_TO;
+ } else {
+ if (requesttype & IUSB_ENDPOINT_IN) /* Device to host */
+ rv = ICOM_USBR; /* Read error */
+ else
+ rv = ICOM_USBW; /* Write error */
+ }
+ } else {
+ if (transferred != NULL)
+ *transferred = rv;
+ if (rv != size) {
+ rv = ICOM_SHORT;
+ } else
+ rv = ICOM_OK;
+ }
+ a1logd(p->log, 8, "icoms_usb_control_msg: returning err 0x%x and %d bytes\n",rv, *transferred);
+ return rv;
+}
+
+/* Our versions of usblib read/write, that exit if a signal was caught */
+/* This is so that MSWindows works properly */
+/* return an icom error */
+static int icoms_usb_transaction(icoms *p, usb_cancelt *cancelt, int *xbytes,
+ icom_usb_trantype type, int ep, unsigned char *bytes, int size, int timeout) {
+ int rv;
+
+ in_usb_rw++;
+
+ a1logd(p->log, 8, "coms_usb_transaction: req type 0x%x ep 0x%x size %d\n",type,ep,size);
+#ifdef USE_LIBUSB1
+ rv = do_sync_usb_transfer(p, p->usbh, cancelt, xbytes, type,
+ (unsigned char)ep, bytes, size, timeout);
+#else
+ if (xbytes != NULL)
+ *xbytes = 0;
+ if (cancelt != NULL) {
+ usb_lock_cancel(cancelt);
+ cancelt->hcancel = (void *) CASTFIX ep;
+ usb_lock_cancel(cancelt);
+ }
+ if (type == icom_usb_trantype_interrutpt) {
+ if ((ep & IUSB_ENDPOINT_DIR_MASK) == IUSB_ENDPOINT_OUT)
+ rv = usb_interrupt_write(p->usbh, ep, (char *)bytes, size, timeout);
+ else
+ rv = usb_interrupt_read(p->usbh, ep, (char *)bytes, size, timeout);
+ } else { /* bulk */
+ if ((ep & IUSB_ENDPOINT_DIR_MASK) == IUSB_ENDPOINT_OUT)
+ rv = usb_bulk_write(p->usbh, ep, (char *)bytes, size, timeout);
+ else
+ rv = usb_bulk_read(p->usbh, ep, (char *)bytes, size, timeout);
+ }
+ if (cancelt != NULL) {
+ usb_lock_cancel(cancelt);
+ cancelt->hcancel = (void *)-1;
+ usb_lock_cancel(cancelt);
+ }
+ if (rv < 0) {
+ if (rv == USBIO_ERROR_TIMEOUT)
+ rv = ICOM_TO;
+ else {
+ if ((ep & IUSB_ENDPOINT_DIR_MASK) == IUSB_ENDPOINT_OUT)
+ rv = ICOM_USBW;
+ else
+ rv = ICOM_USBR;
+ }
+ } else {
+ if (xbytes != NULL)
+ *xbytes = rv;
+ if (rv != *xbytes)
+ rv = ICOM_SHORT;
+ else
+ rv = ICOM_OK;
+ }
+#endif
+ if (in_usb_rw < 0) /* Signal handler recursion error */
+ exit(0);
+
+ in_usb_rw--;
+
+ a1logd(p->log, 8, "coms_usb_transaction: returning err 0x%x and %d bytes\n",rv, *xbytes);
+
+ return rv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Cancel i/o in another thread */
+int icoms_usb_cancel_io(
+ icoms *p,
+ usb_cancelt *cancelt
+) {
+ int rv = 0;
+ usb_lock_cancel(cancelt);
+#ifdef USE_LIBUSB1
+ if (cancelt->hcancel != NULL) {
+ rv = libusb_cancel_transfer((struct libusb_transfer *)cancelt->hcancel);
+ if (rv == LIBUSB_ERROR_NOT_FOUND)
+ rv = 0;
+ }
+#else
+ if ((int) CASTFIX cancelt->hcancel >= 0) {
+// msec_sleep(1); /* Let device recover ? */
+ rv = usb_resetep(p->usbh, (int) CASTFIX cancelt->hcancel); /* Not reliable ? */
+// msec_sleep(1); /* Let device recover ? */
+ }
+#endif
+ usb_unlock_cancel(cancelt);
+
+ if (rv == 0)
+ return ICOM_OK;
+
+ a1logd(p->log, 1, "icoms_usb_cancel_io: failed with %d (%s)\n", rv,USB_STRERROR(rv));
+ return ICOM_SYS;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Reset and end point data toggle to 0 */
+int icoms_usb_resetep(
+ icoms *p,
+ int ep /* End point address */
+) {
+ int rv;
+#ifdef USE_LIBUSB1
+ rv = libusb_resetep(p->usbh, (unsigned char)ep); /* Is this the same though ? */
+#else
+ rv = usb_resetep(p->usbh, ep); /* Not reliable ? */
+#endif
+
+ if (rv == 0)
+ return ICOM_OK;
+
+ a1logd(p->log, 1, "icoms_usb_resetep: failed with %d (%s)\n", rv,USB_STRERROR(rv));
+ return ICOM_USBW;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Clear a halt on an end point */
+int icoms_usb_clearhalt(
+ icoms *p,
+ int ep /* End point address */
+) {
+ int rv;
+#ifdef USE_LIBUSB1
+ rv = libusb_clear_halt(p->usbh, (unsigned char)ep);
+#else
+ rv = usb_clear_halt(p->usbh, ep);
+#endif
+
+ if (rv == 0)
+ return ICOM_OK;
+
+ a1logd(p->log, 1, "icoms_usb_clearhalt: failed with %d (%s)\n", rv,USB_STRERROR(rv));
+ return ICOM_USBW;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - */
+
diff --git a/spectro/usbio_lx.c b/spectro/usbio_lx.c
new file mode 100644
index 0000000..359f483
--- /dev/null
+++ b/spectro/usbio_lx.c
@@ -0,0 +1,1069 @@
+
+/* General USB I/O support, Linux native implementation. */
+/* ("libusbx ? We don't need no stinking libusbx..." :-) */
+/* This file is conditionaly #included into usbio.c */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 2006/22/4
+ *
+ * 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.
+ */
+
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/usbdevice_fs.h>
+
+/* select() defined, but not poll(), so emulate poll() */
+#if defined(FD_CLR) && !defined(POLLIN)
+#include "pollem.h"
+#define poll_x pollem
+#else
+#include <sys/poll.h> /* Else assume poll() is native */
+#define poll_x poll
+#endif
+
+/* USB descriptors are little endian */
+
+/* Take a word sized return buffer, and convert it to an unsigned int */
+static unsigned int buf2uint(unsigned char *buf) {
+ unsigned int val;
+ val = buf[3];
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* Take a short sized return buffer, and convert it to an int */
+static unsigned int buf2ushort(unsigned char *buf) {
+ unsigned int val;
+ val = buf[1];
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* Take an int, and convert it into a byte buffer */
+static void int2buf(unsigned char *buf, int inv) {
+ buf[0] = (inv >> 0) & 0xff;
+ buf[1] = (inv >> 8) & 0xff;
+ buf[2] = (inv >> 16) & 0xff;
+ buf[3] = (inv >> 24) & 0xff;
+}
+
+/* Take a short, and convert it into a byte buffer */
+static void short2buf(unsigned char *buf, int inv) {
+ buf[0] = (inv >> 0) & 0xff;
+ buf[1] = (inv >> 8) & 0xff;
+}
+
+
+/* Check a USB Vendor and product ID by reading the device descriptors, */
+/* and add the device to the icoms path if it is supported. */
+/* Return icom nz error code on fatal error */
+int usb_check_and_add_fd(
+a1log *log,
+icompaths *pp, /* icompaths to add to, or if NULL */
+icompath *p, /* icompath to set. */
+char *dpath, /* path to device - may be NULL */
+int fd /* device file descriptor */
+) {
+ int rv;
+ unsigned char buf[IUSB_DESC_TYPE_DEVICE_SIZE];
+ unsigned vid, pid, nep10 = 0xffff;
+ unsigned int configix, nconfig, totlen;
+ instType itype;
+ struct usb_idevice *usbd = NULL;
+
+ a1logd(log, 6, "usb_check_and_add_fd: with fd %d\n",fd);
+
+ /* Read the device descriptor */
+ if ((rv = read(fd, buf, IUSB_DESC_TYPE_DEVICE_SIZE)) < 0
+ || rv != IUSB_DESC_TYPE_DEVICE_SIZE
+ || buf[0] != IUSB_DESC_TYPE_DEVICE_SIZE
+ || buf[1] != IUSB_DESC_TYPE_DEVICE) {
+ a1logd(log, 1, "usb_check_and_add: failed to read device descriptor\n");
+ return ICOM_OK;
+ }
+
+ /* Extract the vid and pid */
+ vid = buf2ushort(buf + 8);
+ pid = buf2ushort(buf + 10);
+ nconfig = buf[17];
+
+ a1logd(log, 6, "usb_check_and_add: checking vid 0x%04x, pid 0x%04x\n",vid,pid);
+
+ /* Do a preliminary match */
+ if ((itype = inst_usb_match(vid, pid, 0)) == instUnknown) {
+ a1logd(log, 6 , "usb_check_and_add: instrument not reconized\n");
+ return ICOM_OK;
+ }
+
+ /* Allocate an idevice so that we can fill in the end point information */
+ if ((usbd = (struct usb_idevice *) calloc(sizeof(struct usb_idevice), 1)) == NULL) {
+ a1loge(log, ICOM_SYS, "icoms: calloc failed!\n");
+ return ICOM_SYS;
+ }
+
+ usbd->nconfig = nconfig;
+
+ /* Read the configuration descriptors looking for the first configuration, first interface, */
+ /* and extract the number of end points */
+ for (configix = 0; configix < nconfig; configix++) {
+ int configno;
+ unsigned int ninfaces, inface, nep;
+ unsigned char *buf2, *bp, *zp;
+
+ /* Read the first 4 bytes to get the type and size */
+ if ((rv = read(fd, buf, 4)) < 0
+ || rv != 4
+ || buf[1] != IUSB_DESC_TYPE_CONFIG) {
+ a1logd(log, 1, "usb_check_and_add: failed to read device config\n");
+ free(usbd);
+ return ICOM_OK;
+ }
+
+ if ((totlen = buf2ushort(buf + 2)) < 6) {
+ a1logd(log, 1, "usb_check_and_add: config desc size strange\n");
+ free(usbd);
+ return ICOM_OK;;
+ }
+ if ((buf2 = calloc(1, totlen)) == NULL) {
+ a1loge(log, ICOM_SYS, "usb_check_and_add: calloc of descriptor failed!\n");
+ return ICOM_SYS;
+ }
+
+ memcpy(buf2, buf, 4); /* First 4 bytes read */
+ if ((rv = read(fd, buf2 + 4, totlen - 4)) < 0 /* Read the remainder */
+ || rv != (totlen - 4)) {
+ a1logd(log, 1, "usb_check_and_add: failed to read device config details\n");
+ free(buf2);
+ free(usbd);
+ return ICOM_SYS;
+ }
+
+ bp = buf2 + buf2[0]; /* Skip coniguration tag */
+ zp = buf2 + totlen; /* Past last bytes */
+
+ /* We are at the first configuration. */
+ /* Just read tags and keep track of where we are */
+ ninfaces = 0;
+ nep = 0;
+ usbd->nifce = buf2[4]; /* number of interfaces */
+ usbd->config = configno = buf2[5]; /* current configuration */
+ for (;bp < zp; bp += bp[0]) {
+ int ifaceno;
+ if ((bp + 1) >= zp)
+ break; /* Hmm - bodgy, give up */
+ if (bp[1] == IUSB_DESC_TYPE_INTERFACE) {
+ ninfaces++;
+ if ((bp + 2) >= zp)
+ break; /* Hmm - bodgy, give up */
+ ifaceno = bp[2]; /* Get bInterfaceNumber */
+ } else if (bp[1] == IUSB_DESC_TYPE_ENDPOINT) {
+ nep++;
+ if ((bp + 5) >= zp)
+ break; /* Hmm - bodgy */
+ /* At first config - */
+ /* record current nep and end point details */
+ if (configno == 1) {
+ int ad = bp[2];
+ nep10 = nep;
+ usbd->EPINFO(ad).valid = 1;
+ usbd->EPINFO(ad).addr = ad;
+ usbd->EPINFO(ad).packetsize = buf2ushort(bp + 4);
+ usbd->EPINFO(ad).type = bp[3] & IUSB_ENDPOINT_TYPE_MASK;
+ usbd->EPINFO(ad).interface = ifaceno;
+ a1logd(log, 6, "set ep ad 0x%x packetsize %d type %d\n",ad,usbd->EPINFO(ad).packetsize,usbd->EPINFO(ad).type);
+ }
+ }
+ /* Ignore other tags */
+ }
+ free(buf2);
+ }
+ if (nep10 == 0xffff) { /* Hmm. Failed to find number of end points */
+ a1logd(log, 1, "usb_check_and_add: failed to find number of end points\n");
+ free(usbd);
+ return ICOM_SYS;
+ }
+
+ a1logd(log, 6, "usb_check_and_add: found nep10 %d\n",nep10);
+
+ /* Found a known instrument ? */
+ if ((itype = inst_usb_match(vid, pid, nep10)) != instUnknown) {
+ char pname[400];
+
+ a1logd(log, 2, "usb_check_and_add: found instrument vid 0x%04x, pid 0x%04x\n",vid,pid);
+
+ /* Create a path/identification */
+ /* (devnum doesn't seem valid ?) */
+ if (dpath == NULL) {
+ sprintf(pname,"%s", inst_name(itype));
+ if ((usbd->dpath = strdup("no_path")) == NULL) {
+ a1loge(log, ICOM_SYS, "usb_check_and_add: strdup path failed!\n");
+ free(usbd);
+ return ICOM_SYS;
+ }
+ } else {
+ sprintf(pname,"%s (%s)", dpath, inst_name(itype));
+ if ((usbd->dpath = strdup(dpath)) == NULL) {
+ a1loge(log, ICOM_SYS, "usb_check_and_add: strdup path failed!\n");
+ free(usbd);
+ return ICOM_SYS;
+ }
+ }
+
+ /* Add the path and ep info to the list */
+ if (pp != NULL) {
+ if ((rv = pp->add_usb(pp, pname, vid, pid, nep10, usbd, itype)) != ICOM_OK)
+ return rv;
+ } else {
+ usbd->fd = fd;
+ if ((rv = icompath_set_usb(log, p, pname, vid, pid, nep10, usbd, itype)) != ICOM_OK)
+ return rv;
+ }
+ } else {
+ free(usbd);
+ }
+ return ICOM_OK;
+}
+
+/* Same as above, starting with the path */
+static int usb_check_and_add(
+icompaths *p,
+char *dpath
+) {
+ int fd;
+ int rv;
+
+ a1logd(p->log, 6, "usb_check_and_add: givem '%s'\n",dpath);
+
+ /* Open the device so that we can read it */
+ if ((fd = open(dpath, O_RDONLY)) < 0) {
+ a1logd(p->log, 1, "usb_check_and_add: failed to open '%s'\n",dpath);
+ return ICOM_OK;
+ }
+
+ rv = usb_check_and_add_fd(p->log, p, NULL, dpath, fd);
+
+ close(fd);
+ return rv;
+}
+
+/* Add paths to USB connected instruments */
+/* Return an icom error */
+int usb_get_paths(
+icompaths *p
+) {
+ int vid, pid;
+
+ a1logd(p->log, 6, "usb_get_paths: about to look through buses:\n");
+
+ {
+ int j;
+ char *paths[3] = { "/dev/bus/usb", /* current, from udev */
+ "/proc/bus/usb", /* old, deprecated */
+ "/dev" }; /* CONFIG_USB_DEVICE_CLASS, embeded, deprecated ? */
+
+ /* See what device names to look for */
+ for (j = 0; j < 3; j++) {
+ DIR *d1;
+ struct dirent *e1;
+ int rv, found = 0;
+
+ if ((d1 = opendir(paths[j])) == NULL)
+ continue;
+
+ while ((e1 = readdir(d1)) != NULL) {
+ if (e1->d_name[0] == '.')
+ continue;
+ found = 1;
+ if (j < 2) { /* Directory structure */
+ char path1[PATH_MAX];
+ char path2[PATH_MAX];
+ DIR *d2;
+ struct dirent *e2;
+ struct stat statbuf;
+
+ snprintf(path1, PATH_MAX, "%s/%s", paths[j], e1->d_name);
+
+ if ((d2 = opendir(path1)) == NULL)
+ continue;
+ while ((e2 = readdir(d2)) != NULL) {
+ if (e2->d_name[0] == '.')
+ continue;
+
+ snprintf(path2, PATH_MAX, "%s/%s/%s", paths[j], e1->d_name, e2->d_name);
+ a1logd(p->log, 8, "usb_get_paths: about to stat %s\n",path2);
+ if (stat(path2, &statbuf) == 0 && S_ISCHR(statbuf.st_mode)) {
+ found = 1;
+ if ((rv = usb_check_and_add(p, path2)) != ICOM_OK) {
+ closedir(d1);
+ return rv;
+ }
+ }
+ }
+ closedir(d2);
+ } else { /* Flat */
+ char path2[PATH_MAX];
+ struct stat statbuf;
+
+ /* Hmm. This will go badly wrong if this is a /dev/usbdev%d.%d_ep%d, */
+ /* since we're not expecting the end points to be separate devices. */
+ /* Maybe that's deprectated though... */
+ snprintf(path2, PATH_MAX, "%s/%s", paths[j], e1->d_name);
+ a1logd(p->log, 8, "usb_get_paths: about to stat %s\n",path2);
+ if (stat(path2, &statbuf) == 0 && S_ISCHR(statbuf.st_mode)) {
+ found = 1;
+ if ((rv = usb_check_and_add(p, path2)) != ICOM_OK) {
+ closedir(d1);
+ return rv;
+ }
+ }
+ }
+ }
+ closedir(d1);
+ if (found)
+ break;
+ }
+ }
+
+ a1logd(p->log, 8, "usb_get_paths: returning %d paths and ICOM_OK\n",p->npaths);
+ return ICOM_OK;
+}
+
+
+/* Copy usb_idevice contents from icompaths to icom */
+/* return icom error */
+int usb_copy_usb_idevice(icoms *d, icompath *s) {
+ int i;
+ if (s->usbd == NULL) {
+ d->usbd = NULL;
+ return ICOM_OK;
+ }
+ if ((d->usbd = calloc(sizeof(struct usb_idevice), 1)) == NULL) {
+ a1loge(d->log, ICOM_SYS, "usb_copy_usb_idevice: malloc\n");
+ return ICOM_SYS;
+ }
+ if ((d->usbd->dpath = strdup(s->usbd->dpath)) == NULL) {
+ a1loge(d->log, ICOM_SYS, "usb_copy_usb_idevice: malloc\n");
+ return ICOM_SYS;
+ }
+ /* Copy the ep info */
+ d->nconfig = s->usbd->nconfig;
+ d->nifce = s->usbd->nifce;
+ d->config = s->usbd->config;
+ for (i = 0; i < 32; i++)
+ d->ep[i] = s->usbd->ep[i]; /* Struct copy */
+ return ICOM_OK;
+}
+
+/* Cleanup and then free a usb dev entry */
+void usb_del_usb_idevice(struct usb_idevice *usbd) {
+
+ if (usbd == NULL)
+ return;
+
+ if (usbd->dpath != NULL)
+ free(usbd->dpath);
+ free(usbd);
+}
+
+/* Cleanup any USB specific icoms state */
+void usb_del_usb(icoms *p) {
+
+ usb_del_usb_idevice(p->usbd);
+}
+
+/* Close an open USB port */
+/* If we don't do this, the port and/or the device may be left in an unusable state. */
+void usb_close_port(icoms *p) {
+
+ a1logd(p->log, 6, "usb_close_port: called\n");
+
+ if (p->is_open && p->usbd != NULL) {
+ struct usbdevfs_urb urb;
+ unsigned char buf[8+IUSB_DESC_TYPE_DEVICE_SIZE];
+ int iface, rv;
+
+ /* Release all the interfaces */
+ for (iface = 0; iface < p->nifce; iface++)
+ ioctl(p->usbd->fd, USBDEVFS_RELEASEINTERFACE, &iface);
+
+ /* Workaround for some bugs - reset device on close */
+ if (p->uflags & icomuf_reset_before_close) {
+ if ((rv = ioctl(p->usbd->fd, USBDEVFS_RESET, NULL)) != 0) {
+ a1logd(p->log, 1, "usb_close_port: reset returned %d\n",rv);
+ }
+ }
+
+ if (p->usbd->running) { /* If reaper is still running */
+ unsigned char buf[1] = { 0 };
+
+ a1logd(p->log, 6, "usb_close_port: waking reaper thread to trigger exit\n");
+ p->usbd->shutdown = 1;
+
+ if (write(p->usbd->sd_pipe[1], buf, 1) < 1) {
+ a1logd(p->log, 1, "usb_close_port: writing to sd_pipe failed with '%s'\n", strerror(errno));
+ /* Hmm. We could be in trouble ? */
+ }
+ }
+ a1logd(p->log, 6, "usb_close_port: waiting for reaper thread\n");
+ pthread_join(p->usbd->thread, NULL); /* Wait for urb reaper thread to exit */
+ close(p->usbd->fd);
+ pthread_mutex_destroy(&p->usbd->lock);
+ close(p->usbd->sd_pipe[0]);
+ close(p->usbd->sd_pipe[1]);
+ free(p->usbd->dpath);
+ free(p->usbd);
+ p->usbd = NULL;
+
+ a1logd(p->log, 6, "usb_close_port: usb port has been released and closed\n");
+ }
+ p->is_open = 0;
+
+ /* Find it and delete it from our static cleanup list */
+ usb_delete_from_cleanup_list(p);
+}
+
+static void *urb_reaper(void *context); /* Declare */
+
+/* Open a USB port for all our uses. */
+/* This always re-opens the port */
+/* return icom error */
+static int usb_open_port(
+icoms *p,
+int config, /* Configuration number */
+int wr_ep, /* Write end point */
+int rd_ep, /* Read end point */
+icomuflags usbflags,/* Any special handling flags */
+int retries, /* > 0 if we should retry set_configuration (100msec) */
+char **pnames /* List of process names to try and kill before opening */
+) {
+ int rv, tries = 0;
+ a1logd(p->log, 8, "usb_open_port: Make sure USB port is open, tries %d\n",retries);
+
+ if (p->is_open)
+ p->close_port(p);
+
+ /* Make sure the port is open */
+ if (!p->is_open) {
+ int rv, i, iface;
+ kkill_nproc_ctx *kpc = NULL;
+
+ if (config != 1) {
+ /* Nothing currently needs it, so we haven't implemented it yet... */
+ a1loge(p->log, ICOM_NOTS, "usb_open_port: native driver cant handle config %d\n",config);
+ return ICOM_NOTS;
+ }
+
+ /* Do open retries */
+ for (tries = 0; retries >= 0; retries--, tries++) {
+
+ a1logd(p->log, 8, "usb_open_port: About to open USB port '%s'\n",p->usbd->dpath);
+
+ if (tries > 0) {
+ //msec_sleep(i_rand(50,100));
+ msec_sleep(77);
+ }
+
+ if ((rv = p->usbd->fd = open(p->usbd->dpath, O_RDWR)) < 0) {
+ a1logd(p->log, 8, "usb_open_port: open '%s' config %d failed (%d) (Permissions ?)\n",p->usbd->dpath,config,rv);
+ if (retries <= 0) {
+ if (kpc != NULL)
+ kpc->del(kpc);
+ a1loge(p->log, ICOM_SYS, "usb_open_port: open '%s' config %d failed (%d) (Permissions ?)\n",p->usbd->dpath,config,rv);
+ return ICOM_SYS;
+ }
+ continue;
+ } else if (p->debug)
+ a1logd(p->log, 2, "usb_open_port: open port '%s' succeeded\n",p->usbd->dpath);
+
+ p->uflags = usbflags;
+
+ /* We should only do a set configuration if the device has more than one */
+ /* possible configuration and it is currently not the desired configuration, */
+ /* but we should avoid doing a set configuration if the OS has already */
+ /* selected the configuration we want, since two set configs seem to */
+ /* mess up the Spyder2, BUT we can't do a get config because this */
+ /* messes up the i1pro-D. */
+
+ /* Linux set_configuration(1) by default, so we don't need to do anything */
+ p->cconfig = 1;
+
+ if (p->cconfig != config) {
+ if ((rv = ioctl(p->usbd->fd, USBDEVFS_SETCONFIGURATION, &config)) != 0) {
+ a1logd(p->log, 1, "icoms_usb_setconfig failed with %d\n",rv);
+ return ICOM_SYS;
+ }
+ p->cconfig = config;
+ a1logd(p->log, 6, "usb_open_port: set config %d OK\n",config);
+ }
+
+ /* We're done */
+ break;
+ }
+
+ if (kpc != NULL)
+ kpc->del(kpc);
+
+ /* Claim all the interfaces */
+ for (iface = 0; iface < p->nifce; iface++) {
+
+ if ((rv = ioctl(p->usbd->fd, USBDEVFS_CLAIMINTERFACE, &iface)) < 0) {
+ struct usbdevfs_getdriver getd;
+ getd.interface = iface;
+
+ a1logd(p->log, 1, "usb_open_port: Claiming USB port '%s' interface %d initally failed with %d\n",p->usbd->dpath,iface,rv);
+
+ /* Detatch the existing interface if kernel driver is active. */
+ if (p->uflags & icomuf_detach
+ && ioctl(p->usbd->fd, USBDEVFS_GETDRIVER, &getd) == 0) {
+ struct usbdevfs_ioctl cmd;
+ a1logd(p->log, 1, "usb_open_port: Attempting kernel detach\n");
+ cmd.ifno = iface;
+ cmd.ioctl_code = USBDEVFS_DISCONNECT;
+ cmd.data = NULL;
+ ioctl(p->usbd->fd, USBDEVFS_IOCTL, &cmd);
+ if ((rv = ioctl(p->usbd->fd, USBDEVFS_CLAIMINTERFACE, &iface)) < 0) {
+ a1loge(p->log, ICOM_SYS, "usb_open_port: Claiming USB port '%s' interface %d failed after detach with %d\n",p->usbd->dpath,iface,rv);
+ return ICOM_SYS;
+ }
+ } else {
+ a1loge(p->log, ICOM_SYS, "usb_open_port: Claiming USB port '%s' interface %d failed with %d\n",p->usbd->dpath,iface,rv);
+ return ICOM_SYS;
+ }
+ }
+ }
+
+ /* Clear any errors. */
+ /* (Some I/F seem to hang if we do this, some seem to hang if we don't !) */
+ /* The ColorMunki on Linux only starts every second time if we don't do this. */
+ if (!(p->uflags & icomuf_no_open_clear)) {
+ for (i = 0; i < 32; i++) {
+ if (!p->ep[i].valid)
+ continue;
+ p->usb_clearhalt(p, p->ep[i].addr);
+ }
+ }
+
+ /* Set "serial" coms values */
+ p->wr_ep = wr_ep;
+ p->rd_ep = rd_ep;
+ p->rd_qa = p->EPINFO(rd_ep).packetsize;
+ if (p->rd_qa == 0)
+ p->rd_qa = 8;
+ a1logd(p->log, 8, "usb_open_port: 'serial' read quanta = packet size = %d\n",p->rd_qa);
+
+ /* Start the reaper thread to handle URB completions */
+ if ((rv = pipe(p->usbd->sd_pipe)) < 0) {
+ a1loge(p->log, ICOM_SYS, "usb_open_port: creat pipe failed with %d\n",rv);
+ return ICOM_SYS;
+ }
+ pthread_mutex_init(&p->usbd->lock, NULL);
+
+ p->usbd->running = 1;
+ if ((rv = pthread_create(&p->usbd->thread, NULL, urb_reaper, (void*)p)) < 0) {
+ p->usbd->running = 0;
+ a1loge(p->log, ICOM_SYS, "usb_open_port: creating urb reaper thread failed with %s\n",rv);
+ return ICOM_SYS;
+ }
+
+ p->is_open = 1;
+ a1logd(p->log, 8, "usb_open_port: USB port is now open\n");
+ }
+
+ /* Install the cleanup signal handlers, and add to our cleanup list */
+ usb_install_signal_handlers(p);
+
+ return ICOM_OK;
+}
+
+/* -------------------------------------------------------------- */
+/* I/O structures */
+
+/* an icoms urb */
+typedef struct {
+ struct _usbio_req *req; /* Request we're part of */
+ int urbno; /* urb index within request */
+ struct usbdevfs_urb urb; /* Linux urb */
+} usbio_urb;
+
+/* an i/o request */
+typedef struct _usbio_req {
+
+ int nurbs; /* Number of urbs in urbs[] */
+ usbio_urb *urbs; /* Allocated */
+ volatile int nourbs; /* Number of outstanding urbs, 0 when done */
+ int cancelled; /* All the URB's have been cancelled */
+
+ pthread_mutex_t lock; /* Protect req & reaper access */
+ pthread_cond_t cond; /* Signal to thread waiting on req */
+
+ struct _usbio_req *next; /* Link to next in list */
+} usbio_req;
+
+/* - - - - - - - - - - - - - - - - - - - - - */
+
+/* Cancel a req's urbs from the last down to but not including thisurb. */
+/* return icom error */
+static int cancel_req(icoms *p, usbio_req *req, int thisurb) {
+ int i, rv = ICOM_OK;
+ for (i = req->nurbs-1; i > thisurb; i--) {
+ int ev;
+ // ~~99 can we skip done, errored or cancelled urbs ?
+ // Does it matter if there is a race between cancellers ? */
+ a1logd(p->log, 7, "cancel_req %d\n",i);
+ ev = ioctl(p->usbd->fd, USBDEVFS_DISCARDURB, &req->urbs[i].urb);
+ if (ev != 0 && ev != EINVAL) {
+ /* Hmmm */
+ a1loge(p->log, ICOM_SYS, "cancel_req: failed with %d\n",rv);
+ rv = ICOM_SYS;
+ }
+ req->urbs[i].urb.status = -ECONNRESET;
+ }
+ req->cancelled = 1; /* Don't cancel it again */
+ return ICOM_OK;
+}
+
+/* The reaper thread */
+static void *urb_reaper(void *context) {
+ icoms *p = (icoms *)context;
+ struct usbdevfs_urb *out = NULL;
+ int errc = 0;
+ int rv;
+ struct pollfd pa[2]; /* Poll array to monitor urb result or shutdown */
+
+ a1logd(p->log, 6, "urb_reaper: reap starting\n");
+
+ /* Wait for a URB, and signal the requester */
+ for (;;) {
+ usbio_urb *iurb;
+ usbio_req *req;
+
+ /* Setup to wait for serial output not block */
+ pa[0].fd = p->usbd->fd;
+ pa[0].events = POLLIN | POLLOUT;
+ pa[0].revents = 0;
+
+ pa[1].fd = p->usbd->sd_pipe[0];
+ pa[1].events = POLLIN;
+ pa[1].revents = 0;
+
+ /* Wait for fd to become ready or shutdown */
+ if ((rv = poll_x(pa, 2, -1)) < 0 || pa[1].revents || pa[0].revents == 0) {
+ a1logd(p->log, 6, "urb_reaper: poll returned %d and events %d %d\n",rv,pa[0].revents,pa[1].revents);
+ p->usbd->shutdown = 1;
+ break;
+ }
+
+ /* Not sure what this returns if there is nothing there */
+ rv = ioctl(p->usbd->fd, USBDEVFS_REAPURBNDELAY, &out);
+
+ if (rv < 0) {
+ a1logd(p->log, 2, "urb_reaper: reap failed with %d\n",rv);
+ if (errc++ < 5) {
+ continue;
+ }
+ p->usbd->shutdown = 1;
+ break;
+ }
+
+ errc = 0;
+
+ if (out == NULL) {
+ a1logd(p->log, 2, "urb_reaper: reap returned NULL URB\n");
+ continue;
+ }
+
+ /* Normal reap */
+ iurb = (usbio_urb *)out->usercontext;
+ req = iurb->req;
+
+ a1logd(p->log, 8, "urb_reaper: urb reap URB %d with status %d bytes %d, usrbs left %d\n",iurb->urbno, out->status, out->actual_length, req->nourbs-1);
+
+ pthread_mutex_lock(&req->lock); /* Stop requester from missing reap */
+ req->nourbs--; /* We're reaped one */
+
+ /* If urb failed or is done (but not cancelled), cancel all the following urbs */
+ if (req->nourbs > 0 && !req->cancelled
+ && ((out->actual_length < out->buffer_length)
+ || (out->status < 0 && out->status != -ECONNRESET))) {
+ a1logd(p->log, 6, "urb_reaper: reaper canceling failed or done urb's\n",rv);
+ if (cancel_req(p, req, iurb->urbno) != ICOM_OK) {
+ pthread_mutex_unlock(&req->lock);
+ /* Is this fatal ? Assume so for the moment ... */
+ break;
+ }
+ }
+ if (req->nourbs <= 0) /* Signal the requesting thread that we're done */
+ pthread_cond_signal(&req->cond);
+ pthread_mutex_unlock(&req->lock);
+ }
+
+ /* Clean up */
+ if (p->usbd->shutdown) {
+ usbio_req *req;
+
+ a1logd(p->log, 8, "urb_reaper: shutdown or too many failure\n");
+
+ /* Signal that any request should give up, and that the */
+ /* reaper thread is going to exit */
+ p->usbd->running = 0;
+
+ /* Go through the outstanding request list, and */
+ /* mark them as failed and signal them all */
+ pthread_mutex_lock(&p->usbd->lock);
+ req = p->usbd->reqs;
+ while(req != NULL) {
+ int i;
+
+ pthread_mutex_lock(&req->lock);
+ for (i = req->nourbs-1; i >= 0; i--) {
+ req->urbs[i].urb.status = ICOM_SYS;
+ }
+ req->nourbs = 0;
+ pthread_cond_signal(&req->cond);
+ pthread_mutex_unlock(&req->lock);
+ req = req->next;
+ }
+ pthread_mutex_unlock(&p->usbd->lock);
+ a1logd(p->log, 1, "urb_reaper: cleared requests\n");
+ }
+ p->usbd->running = 0;
+
+ a1logd(p->log, 6, "urb_reaper: thread done\n");
+ return NULL;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - */
+/* Our universal USB transfer function */
+static int icoms_usb_transaction(
+ icoms *p,
+ usb_cancelt *cancelt,
+ int *transferred,
+ icom_usb_trantype ttype, /* transfer type */
+ unsigned char endpoint, /* 0x80 for control write, 0x00 for control read */
+ unsigned char *buffer,
+ int length,
+ unsigned int timeout /* In msec */
+) {
+ int reqrv = ICOM_OK, rv = 0;
+ usbio_req req, **preq;
+ int type;
+ int remlen;
+ unsigned char *bp;
+ int xlength = 0;
+ int i;
+
+ in_usb_rw++;
+ a1logd(p->log, 8, "icoms_usb_transaction: req type 0x%x ep 0x%x size %d\n",ttype,endpoint,length);
+
+ if (!p->usbd->running) {
+ in_usb_rw--;
+ a1logv(p->log, 1, "icoms_usb_transaction: reaper thread is not running\n");
+ return ICOM_SYS;
+ }
+
+ /* Translate icoms transfer type of Linux */
+ switch (ttype) {
+ case icom_usb_trantype_command:
+ type = USBDEVFS_URB_TYPE_CONTROL;
+ break;
+ case icom_usb_trantype_interrutpt:
+ type = USBDEVFS_URB_TYPE_INTERRUPT;
+ break;
+ case icom_usb_trantype_bulk:
+ type = USBDEVFS_URB_TYPE_BULK;
+ break;
+ }
+
+ /* Setup the icom req and urbs */
+ req.urbs = NULL;
+ pthread_mutex_init(&req.lock, NULL);
+ pthread_cond_init(&req.cond, NULL);
+
+ /* Linux historically only copes with 16384 length urbs, */
+ /* so break up longer requests into multiple urbs */
+
+ req.cancelled = 0;
+ req.nourbs = req.nurbs = (length + (1 << 14)-1) >> 14;
+ if ((req.urbs = (usbio_urb *)calloc(sizeof(usbio_urb), req.nourbs)) == NULL) {
+ in_usb_rw--;
+ a1loge(p->log, ICOM_SYS, "icoms_usb_transaction: control transfer too big! (%d)\n",length);
+ return ICOM_SYS;
+ }
+
+ bp = buffer;
+ remlen = length;
+ for (i = 0; i < req.nurbs; i++) {
+ req.urbs[i].req = &req;
+ req.urbs[i].urbno = i;
+ /* Setup Linux URB */
+ req.urbs[i].urb.usercontext = &req.urbs[i];
+ req.urbs[i].urb.type = type;
+ if (type != USBDEVFS_URB_TYPE_CONTROL)
+ req.urbs[i].urb.endpoint = endpoint;
+ if (remlen > 16384)
+ req.urbs[i].urb.buffer_length = 16384;
+ else
+ req.urbs[i].urb.buffer_length = remlen;
+ req.urbs[i].urb.buffer = (void *)bp;
+ remlen -= req.urbs[i].urb.buffer_length;
+ bp += req.urbs[i].urb.buffer_length;
+ req.urbs[i].urb.status = -EINPROGRESS;
+ }
+a1logd(p->log, 8, "icoms_usb_transaction: reset req 0x%p nourbs to %d\n",&req,req.nourbs);
+
+ /* Add our request to the req list so that it can be cancelled on reap failure */
+ pthread_mutex_lock(&p->usbd->lock);
+ req.next = p->usbd->reqs;
+ p->usbd->reqs = &req;
+ pthread_mutex_unlock(&p->usbd->lock);
+
+ /* submit the URBs */
+ for (i = 0; i < req.nurbs; i++) {
+ if ((rv = ioctl(p->usbd->fd, USBDEVFS_SUBMITURB, &req.urbs[i].urb)) < 0) {
+ a1logd(p->log, 1, "coms_usb_transaction: Submitting urb to fd %d failed with %d\n",p->usbd->fd, rv);
+ req.urbs[i].urb.status = ICOM_SYS; /* Mark it as failed to submit */
+ req.nourbs--;
+ }
+ }
+
+ if (cancelt != NULL) {
+ usb_lock_cancel(cancelt);
+ cancelt->hcancel = (void *)&req;
+ usb_unlock_cancel(cancelt);
+ }
+
+ /* Wait for the reaper to wake us, or for a timeout, */
+ /* or for the reaper to die. */
+ pthread_mutex_lock(&req.lock);
+ if (req.nourbs > 0) {
+ struct timeval tv;
+ struct timespec ts;
+
+ // this is unduly complicated...
+ gettimeofday(&tv, NULL);
+ ts.tv_sec = tv.tv_sec + timeout/1000;
+ ts.tv_nsec = (tv.tv_usec + (timeout % 1000) * 1000) * 1000L;
+ if (ts.tv_nsec > 1000000000L) {
+ ts.tv_nsec -= 1000000000L;
+ ts.tv_sec++;
+ }
+
+ for(;;) { /* Ignore spurious wakeups */
+ if ((rv = pthread_cond_timedwait(&req.cond, &req.lock, &ts)) != 0) {
+ if (rv != ETIMEDOUT) {
+ pthread_mutex_unlock(&req.lock);
+ a1logd(p->log, 1, "coms_usb_transaction: pthread_cond_timedwait failed with %d\n",rv);
+ rv = ICOM_SYS;
+ goto done;
+ }
+
+ /* Timed out - cancel the remaining URB's */
+ a1logd(p->log, 8, "coms_usb_transaction: time out - cancel remaining URB's\n");
+ reqrv = ICOM_TO;
+ if (!req.cancelled && (rv = cancel_req(p, &req, -1)) != ICOM_OK) {
+ pthread_mutex_unlock(&req.lock);
+ reqrv = ICOM_SYS;
+ /* Since cancelling failed, we can't wait for them to be reaped */
+ goto done;
+ }
+
+ /* Wait for the cancelled URB's to be reaped */
+ for (;req.nourbs > 0;) { /* Ignore spurious wakeups */
+ if ((rv = pthread_cond_wait(&req.cond, &req.lock)) != 0) {
+ pthread_mutex_unlock(&req.lock);
+ a1logd(p->log, 1, "coms_usb_transaction: pthread_cond_wait failed with %d\n",rv);
+ reqrv = ICOM_SYS;
+ /* Waiting for reap failed, so give up */
+ goto done;
+ }
+ }
+ } else {
+ a1logd(p->log, 8, "coms_usb_transaction: reap - %d left\n",req.nourbs);
+ }
+ if (req.nourbs <= 0)
+ break; /* All urbs's are done */
+ }
+ }
+ pthread_mutex_unlock(&req.lock);
+
+ /* Compute the overall result by going through the urbs. */
+ for (i = 0; i < req.nurbs; i++) {
+ int stat = req.urbs[i].urb.status;
+ xlength += req.urbs[i].urb.actual_length;
+
+ if (stat == ICOM_SYS) { /* Submit or cancel failed */
+ reqrv = ICOM_SYS;
+ } else if (reqrv == ICOM_OK && stat < 0 && stat != -ECONNRESET) { /* Error result */
+ if ((endpoint & IUSB_ENDPOINT_DIR_MASK) == IUSB_ENDPOINT_OUT)
+ reqrv = ICOM_USBW;
+ else
+ reqrv = ICOM_USBR;
+ } else if (reqrv == ICOM_OK && stat == -ECONNRESET) { /* Cancelled */
+ reqrv = ICOM_CANC;
+ } else if (reqrv == ICOM_OK
+ && req.urbs[i].urb.actual_length < req.urbs[i].urb.buffer_length) {
+ /* Disregard any following urb's status - they are probably cancelled */
+ break;
+ }
+ /* reqrv == ICOM_TO will ignore urb status */
+ }
+
+ if (ttype == icom_usb_trantype_command)
+ xlength += IUSB_REQ_HEADER_SIZE; /* Account for header - linux doesn't */
+
+ /* requested size wasn't transferred ? */
+ if (reqrv == ICOM_OK && xlength != length)
+ reqrv = ICOM_SHORT;
+
+ if (transferred != NULL)
+ *transferred = xlength;
+
+done:;
+ if (cancelt != NULL) {
+ usb_lock_cancel(cancelt);
+ cancelt->hcancel = (void *)NULL;
+ usb_unlock_cancel(cancelt);
+ }
+
+ /* Remove our request from the list */
+ pthread_mutex_lock(&p->usbd->lock);
+ preq = &p->usbd->reqs;
+ while (*preq != &req && *preq != NULL) /* Find it */
+ preq = &((*preq)->next);
+ if (*preq != NULL)
+ *preq = (*preq)->next;
+ pthread_mutex_unlock(&p->usbd->lock);
+
+ if (req.urbs != NULL)
+ free(req.urbs);
+ pthread_cond_destroy(&req.cond);
+ pthread_mutex_destroy(&req.lock);
+
+ if (in_usb_rw < 0)
+ exit(0);
+
+ in_usb_rw--;
+
+ a1logd(p->log, 8, "coms_usb_transaction: returning err 0x%x and %d bytes\n",reqrv, xlength);
+
+ return reqrv;
+}
+
+
+/* Return error icom error code */
+static int icoms_usb_control_msg(
+icoms *p,
+int *transferred,
+int requesttype, int request,
+int value, int index, unsigned char *bytes, int size,
+int timeout) {
+ int reqrv = ICOM_OK;
+ int dirw = (requesttype & IUSB_REQ_DIR_MASK) == IUSB_REQ_HOST_TO_DEV ? 1 : 0;
+ unsigned char *buf;
+
+ a1logd(p->log, 8, "icoms_usb_control_msg: type 0x%x req 0x%x size %d\n",requesttype,request,size);
+
+ /* Allocate a buffer for the ctrl header + payload */
+ if ((buf = calloc(1, IUSB_REQ_HEADER_SIZE + size)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "icoms_usb_control_msg: calloc failed\n");
+ return ICOM_SYS;
+ }
+
+ /* Setup the control header */
+ buf[0] = requesttype;
+ buf[1] = request;
+ short2buf(buf + 2, value);
+ short2buf(buf + 4, index);
+ short2buf(buf + 6, size);
+
+ /* If it's a write, copy the write data into the buffer */
+ if (dirw)
+ memcpy(buf + IUSB_REQ_HEADER_SIZE, bytes, size);
+
+ reqrv = icoms_usb_transaction(p, NULL, transferred, icom_usb_trantype_command,
+ dirw ? 0x80 : 0x00, buf, IUSB_REQ_HEADER_SIZE + size, timeout);
+
+ /* If read, copy the data back */
+ if (!dirw)
+ memcpy(bytes, buf + IUSB_REQ_HEADER_SIZE, size);
+
+ if (transferred != NULL) /* Adjust for header size requested */
+ *transferred -= IUSB_REQ_HEADER_SIZE;
+
+ free(buf);
+
+ a1logd(p->log, 8, "icoms_usb_control_msg: returning err 0x%x and %d bytes\n",reqrv, *transferred);
+ return reqrv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Time out error return value */
+
+#define USBIO_ERROR_TIMEOUT -ETIMEDOUT
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Cancel i/o in another thread */
+int icoms_usb_cancel_io(
+ icoms *p,
+ usb_cancelt *cancelt
+) {
+ int rv = ICOM_OK;
+ a1logd(p->log, 8, "icoms_usb_cancel_io called\n");
+ usb_lock_cancel(cancelt);
+ if (cancelt->hcancel != NULL)
+ rv = cancel_req(p, (usbio_req *)cancelt->hcancel, -1);
+ usb_unlock_cancel(cancelt);
+
+ if (rv != ICOM_OK) /* Assume this could be because of faulty device */
+ rv = ICOM_USBW;
+
+ return rv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Reset and end point data toggle to 0 */
+int icoms_usb_resetep(
+ icoms *p,
+ int ep /* End point address */
+) {
+ int rv = ICOM_OK;
+
+ if ((rv = ioctl(p->usbd->fd, USBDEVFS_RESETEP, &ep)) != 0) {
+ a1logd(p->log, 1, "icoms_usb_resetep failed with %d\n",rv);
+ rv = ICOM_USBW;
+ }
+ return rv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Clear a halt on an end point */
+int icoms_usb_clearhalt(
+ icoms *p,
+ int ep /* End point address */
+) {
+ int rv = ICOM_OK;
+
+ if ((rv = ioctl(p->usbd->fd, USBDEVFS_CLEAR_HALT, &ep)) != 0) {
+ a1logd(p->log, 1, "icoms_usb_clearhalt failed with %d\n",rv);
+ rv = ICOM_USBW;
+ }
+ return rv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - */
+
diff --git a/spectro/usbio_nt.c b/spectro/usbio_nt.c
new file mode 100644
index 0000000..1711f0d
--- /dev/null
+++ b/spectro/usbio_nt.c
@@ -0,0 +1,898 @@
+
+/* General USB I/O support, MSWin native libusb0.sys implementation. */
+/* This file is conditionaly #included into usbio.c */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 2006/22/4
+ *
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <windows.h>
+#include <winioctl.h>
+#include <setupapi.h>
+#include <driver_api.h>
+
+#define DEBUG /* Turn on debug messages */
+
+#define LIBUSBW1_MAX_DEVICES 255
+#define LIBUSBW1_PATH_MAX 512
+#define LIBUSBW1_DEFAULT_TIMEOUT 5000
+
+/* USB descriptors are little endian */
+
+/* Take a word sized return buffer, and convert it to an unsigned int */
+static unsigned int buf2uint(unsigned char *buf) {
+ unsigned int val;
+ val = buf[3];
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* Take a short sized return buffer, and convert it to an int */
+static unsigned int buf2ushort(unsigned char *buf) {
+ unsigned int val;
+ val = buf[1];
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* Take an int, and convert it into a byte buffer */
+static void int2buf(unsigned char *buf, int inv) {
+ buf[0] = (inv >> 0) & 0xff;
+ buf[1] = (inv >> 8) & 0xff;
+ buf[2] = (inv >> 16) & 0xff;
+ buf[3] = (inv >> 24) & 0xff;
+}
+
+/* Take a short, and convert it into a byte buffer */
+static void short2buf(unsigned char *buf, int inv) {
+ buf[0] = (inv >> 0) & 0xff;
+ buf[1] = (inv >> 8) & 0xff;
+}
+
+/* Do a synchronous request. Return an ICOM error */
+static int do_sync_io(
+HANDLE handle,
+unsigned int ioctl,
+void *out, int outsz,
+void *in, int insz,
+int *retsz) {
+ OVERLAPPED olaps;
+ DWORD xlength;
+
+ memset(&olaps, 0, sizeof(OVERLAPPED));
+ if (retsz != NULL)
+ *retsz = 0;
+
+ if ((olaps.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL)
+ return ICOM_SYS;
+
+ if (!DeviceIoControl(handle, ioctl, out, outsz, in, insz, &xlength, &olaps)) {
+ if (GetLastError() != ERROR_IO_PENDING) {
+ CloseHandle(olaps.hEvent);
+ return ICOM_USBW;
+ }
+ if (!GetOverlappedResult(handle, &olaps, &xlength, TRUE)) {
+ CloseHandle(olaps.hEvent);
+ return ICOM_USBR;
+ }
+ }
+ CloseHandle(olaps.hEvent);
+ if (retsz != NULL)
+ *retsz = (int)xlength;
+
+ return ICOM_OK;
+}
+
+/* Add paths to USB connected instruments */
+/* Return an icom error */
+int usb_get_paths(
+icompaths *p
+) {
+ unsigned int vid, pid, nep10 = 0xffff;
+ unsigned int configix, nconfig, nifce;
+ instType itype;
+ struct usb_idevice *usbd = NULL;
+ int rv, retsz, i;
+
+ for (i = 0; i < LIBUSBW1_MAX_DEVICES; i++) {
+ libusb_request req;
+ char dpath[LIBUSBW1_PATH_MAX];
+ HANDLE handle;
+ unsigned char buf[IUSB_DESC_TYPE_DEVICE_SIZE];
+
+ _snprintf(dpath, LIBUSBW1_PATH_MAX - 1,"\\\\.\\libusb0-%04d", i+1);
+ a1logd(p->log, 6, "usb_get_paths opening device '%s'\n",dpath);
+
+ if ((handle = CreateFile(dpath, 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED,
+ NULL)) == INVALID_HANDLE_VALUE) {
+#ifdef DEBUG
+ a1logd(p->log, 8, "usb_get_paths failed to open device '%s'\n",dpath);
+#endif
+ continue;
+ }
+
+ /* Set kernel message debug */
+ if (p->log->debug >= 6) {
+ req.debug.level = LIBUSB_DEBUG_MAX;
+ req.timeout = LIBUSBW1_DEFAULT_TIMEOUT;
+ if (do_sync_io(handle, LIBUSB_IOCTL_SET_DEBUG_LEVEL,
+ &req, sizeof(libusb_request), NULL, 0, NULL)) {
+ a1logd(p->log, 1, "usb_get_paths: failed to set driver log leve\n");
+ } else {
+ a1logd(p->log, 1, "usb_get_paths: turned on kernel debug messages\n");
+ }
+ }
+
+ /* Read the device descriptor */
+ req.descriptor.type = IUSB_DESC_TYPE_DEVICE;
+ req.descriptor.recipient = IUSB_REQ_RECIP_DEVICE;
+ req.descriptor.index = 0;
+ req.descriptor.language_id = 0;
+ req.timeout = LIBUSBW1_DEFAULT_TIMEOUT;
+
+ if (do_sync_io(handle, LIBUSB_IOCTL_GET_DESCRIPTOR,
+ &req, sizeof(libusb_request),
+ buf, IUSB_DESC_TYPE_DEVICE_SIZE, &retsz) != ICOM_OK
+ || retsz != IUSB_DESC_TYPE_DEVICE_SIZE) {
+ a1logd(p->log, 1, "usb_get_paths: failed to read device descriptor '%s'\n",dpath);
+ CloseHandle(handle);
+ continue;
+ }
+
+ /* Extract the vid and pid */
+ vid = buf2ushort(buf + 8);
+ pid = buf2ushort(buf + 10);
+ nconfig = buf[17];
+
+ a1logd(p->log, 6, "usb_get_paths: checking vid 0x%04x, pid 0x%04x\n",vid,pid);
+
+ /* Do a preliminary match */
+ if ((itype = inst_usb_match(vid, pid, 0)) == instUnknown) {
+ a1logd(p->log, 6 , "usb_get_paths: instrument not reconized\n");
+ CloseHandle(handle);
+ continue;
+ }
+
+ /* Allocate an idevice so that we can fill in the end point information */
+ if ((usbd = (struct usb_idevice *) calloc(sizeof(struct usb_idevice), 1)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "usb_get_paths: calloc failed!\n");
+ CloseHandle(handle);
+ return ICOM_SYS;
+ }
+
+ usbd->nconfig = nconfig;
+
+ /* Read the configuration descriptors looking for the first configuration, first interface, */
+ /* and extract the number of end points for each configuration */
+ for (configix = 0; configix < nconfig; configix++) {
+ int configno, totlen;
+ unsigned char *buf2, *bp, *zp;
+ unsigned int ninfaces, inface, nep;
+
+ /* Read the configuration descriptor */
+ req.descriptor.type = IUSB_DESC_TYPE_CONFIG;
+ req.descriptor.recipient = IUSB_REQ_RECIP_DEVICE;
+ req.descriptor.index = configix;
+ req.descriptor.language_id = 0;
+ req.timeout = LIBUSBW1_DEFAULT_TIMEOUT;
+
+ if ((rv = do_sync_io(handle, LIBUSB_IOCTL_GET_DESCRIPTOR,
+ &req, sizeof(libusb_request),
+ buf, IUSB_DESC_TYPE_CONFIG_SIZE, &retsz)) != ICOM_OK
+ || retsz != IUSB_DESC_TYPE_CONFIG_SIZE) {
+ a1logd(p->log, 1, "usb_get_paths: failed to read configix %d descriptor\n",configix);
+ free(usbd);
+ CloseHandle(handle);
+ break;
+ }
+ nifce = buf[4]; /* number of interfaces */
+ configno = buf[5]; /* Configuration number */
+
+ if (configno != 1)
+ continue;
+
+ if ((totlen = buf2ushort(buf + 2)) < 6) {
+ a1logd(p->log, 1, "usb_get_paths: '%s' config desc size strange\n",dpath);
+ free(usbd);
+ CloseHandle(handle);
+ break;
+ }
+ if ((buf2 = calloc(1, totlen)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "usb_get_paths: calloc of descriptor failed!\n");
+ free(usbd);
+ CloseHandle(handle);
+ return ICOM_SYS;
+ }
+
+ /* Read the whole configuration descriptor */
+ req.descriptor.type = IUSB_DESC_TYPE_CONFIG;
+ req.descriptor.recipient = IUSB_REQ_RECIP_DEVICE;
+ req.descriptor.index = configix;
+ req.descriptor.language_id = 0;
+ req.timeout = LIBUSBW1_DEFAULT_TIMEOUT;
+
+ if (do_sync_io(handle, LIBUSB_IOCTL_GET_DESCRIPTOR,
+ &req, sizeof(libusb_request),
+ buf2, totlen, &retsz) != ICOM_OK
+ || retsz != totlen) {
+ a1logd(p->log, 1, "usb_get_paths: failed to read all configix %d descriptor\n",configix);
+ free(buf2);
+ free(usbd);
+ CloseHandle(handle);
+ break;
+ }
+
+ bp = buf2 + buf2[0]; /* Skip coniguration tag */
+ zp = buf2 + totlen; /* Past last bytes */
+
+ /* We are at the first configuration. */
+ /* Just read tags and keep track of where we are */
+ ninfaces = 0;
+ nep = 0;
+ usbd->nifce = buf2[4]; /* number of interfaces */
+ usbd->config = configno = buf2[5]; /* this configuration */
+ for (;bp < zp; bp += bp[0]) {
+ int ifaceno;
+ if ((bp + 1) >= zp)
+ break; /* Hmm - bodgy, give up */
+ if (bp[1] == IUSB_DESC_TYPE_INTERFACE) {
+ ninfaces++;
+ if ((bp + 2) >= zp)
+ break; /* Hmm - bodgy, give up */
+ ifaceno = bp[2]; /* Get bInterfaceNumber */
+ } else if (bp[1] == IUSB_DESC_TYPE_ENDPOINT) {
+ nep++;
+ if ((bp + 5) >= zp)
+ break; /* Hmm - bodgy */
+ /* At first config - */
+ /* record current nep and end point details */
+ if (configno == 1) {
+ int ad = bp[2];
+ nep10 = nep;
+ usbd->EPINFO(ad).valid = 1;
+ usbd->EPINFO(ad).addr = ad;
+ usbd->EPINFO(ad).packetsize = buf2ushort(bp + 4);
+ usbd->EPINFO(ad).type = bp[3] & IUSB_ENDPOINT_TYPE_MASK;
+ usbd->EPINFO(ad).interface = ifaceno;
+ a1logd(p->log, 6, "set ep ad 0x%x packetsize %d type %d\n",ad,usbd->EPINFO(ad).packetsize,usbd->EPINFO(ad).type);
+ }
+ }
+ /* Ignore other tags */
+ }
+ free(buf2);
+ }
+ if (nep10 == 0xffff) { /* Hmm. Failed to find number of end points */
+ a1logd(p->log, 1, "usb_get_paths: failed to find number of end points\n");
+ free(usbd);
+ CloseHandle(handle);
+ continue;
+ }
+
+ /* Check that we have an up to date kernel driver */
+ req.timeout = LIBUSBW1_DEFAULT_TIMEOUT;
+ if (do_sync_io(handle, LIBUSB_IOCTL_GET_VERSION,
+ &req, sizeof(libusb_request),
+ &req, sizeof(libusb_request), &retsz) != ICOM_OK
+ || retsz != sizeof(libusb_request)) {
+ a1logd(p->log, 1, "usb_get_paths: failed to read driver version info\n");
+ free(usbd);
+ CloseHandle(handle);
+ continue;
+ }
+ if (req.version.major < 1
+ || req.version.major == 1 && (req.version.minor < 2
+ || req.version.minor == 2 && req.version.micro < 6)) {
+ a1loge(p->log, ICOM_VER, "usb_get_paths: Must update %s System Driver to latest version!\n",inst_name(itype));
+ free(usbd);
+ CloseHandle(handle);
+ return ICOM_VER;
+ }
+ CloseHandle(handle);
+
+ /* Found a known instrument ? */
+ if ((itype = inst_usb_match(vid, pid, nep10)) != instUnknown) {
+ char pname[400];
+
+ a1logd(p->log, 1, "usb_get_paths: found instrument vid 0x%04x, pid 0x%04x\n",vid,pid);
+
+ /* Create a path/identification */
+ sprintf(pname,"%s (%s)", dpath + 4, inst_name(itype));
+
+ if ((usbd->dpath = strdup(dpath)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "usb_check_and_add: strdup path failed!\n");
+ free(usbd);
+ return ICOM_SYS;
+ }
+
+ /* Add the path and ep info to the list */
+ if ((rv = p->add_usb(p, pname, vid, pid, nep10, usbd, itype)) != ICOM_OK)
+ return rv;
+ } else {
+ free(usbd);
+ }
+ }
+
+ return ICOM_OK;
+}
+
+
+/* Copy usb_idevice contents from icompaths to icom */
+/* return icom error */
+int usb_copy_usb_idevice(icoms *d, icompath *s) {
+ int i;
+ if (s->usbd == NULL) {
+ d->usbd = NULL;
+ return ICOM_OK;
+ }
+ if ((d->usbd = calloc(sizeof(struct usb_idevice), 1)) == NULL) {
+ a1loge(d->log, ICOM_SYS, "usb_copy_usb_idevice: malloc\n");
+ return ICOM_SYS;
+ }
+ if ((d->usbd->dpath = strdup(s->usbd->dpath)) == NULL) {
+ a1loge(d->log, ICOM_SYS, "usb_copy_usb_idevice: malloc\n");
+ return ICOM_SYS;
+ }
+ /* Copy the current state & ep info */
+ d->nconfig = s->usbd->nconfig;
+ d->config = s->usbd->config;
+ d->nifce = s->usbd->nifce;
+ for (i = 0; i < 32; i++)
+ d->ep[i] = s->usbd->ep[i]; /* Struct copy */
+ return ICOM_OK;
+}
+
+/* Cleanup and then free a usb dev entry */
+void usb_del_usb_idevice(struct usb_idevice *usbd) {
+
+ if (usbd == NULL)
+ return;
+
+ if (usbd->dpath != NULL)
+ free(usbd->dpath);
+ free(usbd);
+}
+
+/* Cleanup any USB specific icoms state */
+void usb_del_usb(icoms *p) {
+
+ usb_del_usb_idevice(p->usbd);
+}
+
+/* Close an open USB port */
+/* If we don't do this, the port and/or the device may be left in an unusable state. */
+void usb_close_port(icoms *p) {
+
+ a1logd(p->log, 6, "usb_close_port: called\n");
+
+ if (p->is_open && p->usbd != NULL) {
+ int iface, rv;
+
+ /* Release all the interfaces */
+ for (iface = 0; iface < p->nifce; iface++) {
+ libusb_request req;
+
+ memset(&req, 0, sizeof(req));
+ req.intf.interface_number = iface;
+ req.timeout = LIBUSBW1_DEFAULT_TIMEOUT;
+
+ do_sync_io(p->usbd->handle, LIBUSB_IOCTL_RELEASE_INTERFACE,
+ &req, sizeof(libusb_request), NULL, 0, NULL);
+ }
+
+ /* Workaround for some bugs - reset device on close */
+ if (p->uflags & icomuf_reset_before_close) {
+ libusb_request req;
+
+ memset(&req, 0, sizeof(req));
+ req.timeout = LIBUSBW1_DEFAULT_TIMEOUT;
+
+ if ((rv = do_sync_io(p->usbd->handle, LIBUSB_IOCTL_RESET_DEVICE,
+ &req, sizeof(libusb_request), NULL, 0, NULL)) != ICOM_OK) {
+ a1logd(p->log, 1, "usb_close_port: reset returned %d\n",rv);
+ }
+ }
+ CloseHandle(p->usbd->handle);
+
+ free(p->usbd->dpath);
+ free(p->usbd);
+ p->usbd = NULL;
+
+ a1logd(p->log, 6, "usb_close_port: usb port has been released and closed\n");
+ }
+ p->is_open = 0;
+
+ /* Find it and delete it from our static cleanup list */
+ usb_delete_from_cleanup_list(p);
+}
+
+static void *urb_reaper(void *context); /* Declare */
+
+/* Open a USB port for all our uses. */
+/* This always re-opens the port */
+/* return icom error */
+static int usb_open_port(
+icoms *p,
+int config, /* Configuration number */
+int wr_ep, /* Write end point */
+int rd_ep, /* Read end point */
+icomuflags usbflags,/* Any special handling flags */
+int retries, /* > 0 if we should retry set_configuration (100msec) */
+char **pnames /* List of process names to try and kill before opening */
+) {
+ int rv, tries = 0;
+ a1logd(p->log, 8, "usb_open_port: Make sure USB port is open, tries %d\n",retries);
+
+ if (p->is_open)
+ p->close_port(p);
+
+ /* Make sure the port is open */
+ if (!p->is_open) {
+ int rv, i, iface;
+ kkill_nproc_ctx *kpc = NULL;
+ OSVERSIONINFO osver;
+
+ if (config != 1) {
+ /* Nothing currently needs it, so we haven't implemented it yet... */
+ a1loge(p->log, ICOM_NOTS, "usb_open_port: native driver cant handle config %d\n",config);
+ return ICOM_NOTS;
+ }
+
+ /* Do open retries */
+ for (tries = 0; retries >= 0; retries--, tries++) {
+
+ a1logd(p->log, 8, "usb_open_port: About to open USB port '%s'\n",p->usbd->dpath);
+
+ if (tries > 0) {
+ //msec_sleep(i_rand(50,100));
+ msec_sleep(77);
+ }
+
+ if ((p->usbd->handle = CreateFile(p->usbd->dpath, 0, 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED, NULL)) == INVALID_HANDLE_VALUE) {
+ a1logd(p->log, 8, "usb_open_port: open '%s' config %d failed (%d) (Device being used ?)\n",p->usbd->dpath,config,GetLastError());
+ if (retries <= 0) {
+ if (kpc != NULL)
+ kpc->del(kpc);
+ a1loge(p->log, ICOM_SYS, "usb_open_port: open '%s' config %d failed (%d) (Device being used ?)\n",p->usbd->dpath,config,GetLastError());
+ return ICOM_SYS;
+ }
+ continue;
+ } else if (p->debug)
+ a1logd(p->log, 2, "usb_open_port: open port '%s' succeeded\n",p->usbd->dpath);
+
+ p->uflags = usbflags;
+
+ /* We're done */
+ break;
+ }
+
+ if (kpc != NULL)
+ kpc->del(kpc);
+
+ /* We should only do a set configuration if the device has more than one */
+ /* possible configuration and it is currently not the desired configuration, */
+ /* but we should avoid doing a set configuration if the OS has already */
+ /* selected the configuration we want, since two set configs seem to */
+ /* mess up the Spyder2, BUT we can't do a get config because this */
+ /* messes up the i1pro-D. */
+
+ osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+ osver.dwMajorVersion = 5;
+ GetVersionEx(&osver);
+ if (osver.dwMajorVersion >= 6 && osver.dwMinorVersion >= 2) {
+ p->cconfig = 0; /* Need to do set_congfig(1) on Win8 */
+ } else {
+ p->cconfig = 1; /* Set by default to config 1 */
+ }
+
+ if (p->cconfig != config) {
+ libusb_request req;
+
+ memset(&req, 0, sizeof(libusb_request));
+ req.configuration.configuration = config;
+ req.timeout = LIBUSBW1_DEFAULT_TIMEOUT;
+
+ if ((rv = do_sync_io(p->usbd->handle, LIBUSB_IOCTL_SET_CONFIGURATION,
+ &req, sizeof(libusb_request), NULL, 0, NULL)) != ICOM_OK) {
+
+ a1loge(p->log, rv, "usb_open_port: Setting port '%s' to config %d failed with %d\n",p->usbd->dpath,config,rv);
+ return ICOM_SYS;
+ }
+ p->cconfig = config;
+ a1logd(p->log, 6, "usb_open_port: set config %d OK\n",config);
+ }
+
+ /* Claim all the interfaces */
+ for (iface = 0; iface < p->nifce; iface++) {
+ libusb_request req;
+
+ memset(&req, 0, sizeof(libusb_request));
+ req.intf.interface_number = iface;
+ req.timeout = LIBUSBW1_DEFAULT_TIMEOUT;
+
+ if ((rv = do_sync_io(p->usbd->handle, LIBUSB_IOCTL_CLAIM_INTERFACE,
+ &req, sizeof(libusb_request), NULL, 0, NULL)) != ICOM_OK) {
+
+ a1loge(p->log, rv, "usb_open_port: Claiming USB port '%s' interface %d failed with %d\n",p->usbd->dpath,iface,rv);
+ return ICOM_SYS;
+ }
+ }
+
+ /* Clear any errors */
+ /* (Some I/F seem to hang if we do this, some seem to hang if we don't !) */
+ if (!(p->uflags & icomuf_no_open_clear)) {
+ for (i = 0; i < 32; i++) {
+ if (!p->ep[i].valid)
+ continue;
+ p->usb_clearhalt(p, p->ep[i].addr);
+ }
+ }
+
+ /* Set "serial" coms values */
+ p->wr_ep = wr_ep;
+ p->rd_ep = rd_ep;
+ p->rd_qa = p->EPINFO(rd_ep).packetsize;
+ if (p->rd_qa == 0)
+ p->rd_qa = 8;
+ a1logd(p->log, 8, "usb_open_port: 'serial' read quanta = packet size = %d\n",p->rd_qa);
+
+ p->is_open = 1;
+ a1logd(p->log, 8, "usb_open_port: USB port is now open\n");
+ }
+
+ /* Install the cleanup signal handlers, and add to our cleanup list */
+ usb_install_signal_handlers(p);
+
+ return ICOM_OK;
+}
+
+/* -------------------------------------------------------------- */
+
+/* Our universal USB transfer function */
+static int icoms_usb_transaction(
+ icoms *p,
+ usb_cancelt *cancelt,
+ int *transferred,
+ icom_usb_trantype ttype, /* transfer type */
+ unsigned char endpoint, /* 0x80 for control write, 0x00 for control read */
+ unsigned char *buffer,
+ int length,
+ unsigned int timeout /* In msec */
+) {
+ int rv = ICOM_OK;
+ int dirw = (endpoint & IUSB_ENDPOINT_DIR_MASK) == IUSB_ENDPOINT_OUT ? 1 : 0;
+ libusb_request req;
+ OVERLAPPED olaps;
+ DWORD xlength = 0;
+
+ in_usb_rw++;
+
+ a1logd(p->log, 8, "icoms_usb_transaction: req type 0x%x ep 0x%x size %d\n",ttype,endpoint,length);
+
+ if (ttype != icom_usb_trantype_interrutpt
+ && ttype != icom_usb_trantype_bulk) {
+ /* We only handle interrupt & bulk, not control */
+ return ICOM_SYS;
+ }
+
+ memset(&olaps, 0, sizeof(olaps));
+
+ if ((olaps.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL)
+ return ICOM_SYS;
+
+ if (cancelt != NULL) {
+ usb_lock_cancel(cancelt);
+ cancelt->hcancel = (void *)&endpoint;
+ usb_unlock_cancel(cancelt);
+ }
+
+ memset(&req, 0, sizeof(libusb_request));
+ req.endpoint.endpoint = endpoint;
+
+ if (!DeviceIoControl(p->usbd->handle,
+ dirw ? LIBUSB_IOCTL_INTERRUPT_OR_BULK_WRITE
+ : LIBUSB_IOCTL_INTERRUPT_OR_BULK_READ,
+ &req, sizeof(libusb_request),
+ buffer,
+ length, &xlength, &olaps)) {
+ if (GetLastError() != ERROR_IO_PENDING) {
+ rv = dirw ? ICOM_USBW : ICOM_USBR;
+ goto done;
+ }
+
+ if (WaitForSingleObject(olaps.hEvent, timeout) == WAIT_TIMEOUT) {
+
+ /* Cancel the operation */
+ memset(&req, 0, sizeof(libusb_request));
+ req.endpoint.endpoint = endpoint;
+ req.timeout = LIBUSBW1_DEFAULT_TIMEOUT;
+ do_sync_io(p->usbd->handle, LIBUSB_IOCTL_ABORT_ENDPOINT,
+ &req, sizeof(libusb_request), NULL, 0, NULL);
+ rv = ICOM_TO;
+ }
+
+ if (!GetOverlappedResult(p->usbd->handle, &olaps, &xlength, TRUE)) {
+ if (rv == ICOM_OK) {
+ if (GetLastError() == ERROR_OPERATION_ABORTED)
+ rv = ICOM_CANC;
+ else
+ rv = dirw ? ICOM_USBW : ICOM_USBR;
+ }
+ }
+ }
+done:;
+ if (cancelt != NULL) {
+ usb_lock_cancel(cancelt);
+ cancelt->hcancel = (void *)NULL;
+ usb_unlock_cancel(cancelt);
+ }
+
+ CloseHandle(olaps.hEvent);
+
+ if (transferred != NULL)
+ *transferred = (int)xlength;
+
+ /* The requested size wasn't transferred */
+ if (rv == ICOM_OK && xlength != length)
+ rv = ICOM_SHORT;
+
+ if (in_usb_rw < 0)
+ exit(0);
+
+ in_usb_rw--;
+
+ a1logd(p->log, 8, "coms_usb_transaction: returning err 0x%x and %d bytes\n",rv, xlength);
+
+ return rv;
+}
+
+
+/* Our control message routine */
+/* Return error icom error code */
+static int icoms_usb_control_msg(
+icoms *p,
+int *transferred,
+int requesttype, int request,
+int value, int index, unsigned char *bytes, int size,
+int timeout) {
+ int rv = ICOM_OK;
+ int dirw = (requesttype & IUSB_REQ_DIR_MASK) == IUSB_REQ_HOST_TO_DEV ? 1 : 0;
+ libusb_request req;
+ unsigned char *obuf = (unsigned char *)&req;
+ int osize = sizeof(libusb_request);
+ unsigned char *ibuf = bytes;
+ int isize = size;
+ int ioctl = 0;
+ int retsz = 0;
+
+ a1logd(p->log, 8, "icoms_usb_control_msg: type 0x%x req 0x%x size %d\n",requesttype,request,size);
+
+ memset(&req, 0, sizeof(libusb_request));
+ req.timeout = timeout;
+
+ /* We need to treat each request type as a different IOCTL */
+ switch (requesttype & IUSB_REQ_TYPE_MASK) {
+
+ case IUSB_REQ_TYPE_STANDARD:
+
+ switch (request) {
+ case IUSB_REQ_GET_STATUS:
+ req.status.recipient = requesttype & IUSB_REQ_RECIP_MASK;
+ req.status.index = index;
+ ioctl = LIBUSB_IOCTL_GET_STATUS;
+ break;
+
+ case IUSB_REQ_CLEAR_FEATURE:
+ req.feature.recipient = requesttype & IUSB_REQ_RECIP_MASK;
+ req.feature.feature = value;
+ req.feature.index = index;
+ ioctl = LIBUSB_IOCTL_CLEAR_FEATURE;
+ break;
+
+ case IUSB_REQ_SET_FEATURE:
+ req.feature.recipient = requesttype & IUSB_REQ_RECIP_MASK;
+ req.feature.feature = value;
+ req.feature.index = index;
+ ioctl = LIBUSB_IOCTL_SET_FEATURE;
+ break;
+
+ case IUSB_REQ_GET_DESCRIPTOR:
+ req.descriptor.recipient = requesttype & IUSB_REQ_RECIP_MASK;
+ req.descriptor.type = (value >> 8) & 0xFF;
+ req.descriptor.index = value & 0xFF;
+ req.descriptor.language_id = index;
+ ioctl = LIBUSB_IOCTL_GET_DESCRIPTOR;
+ break;
+
+ case IUSB_REQ_SET_DESCRIPTOR:
+ req.descriptor.recipient = requesttype & IUSB_REQ_RECIP_MASK;
+ req.descriptor.type = (value >> 8) & 0xFF;
+ req.descriptor.index = value & 0xFF;
+ req.descriptor.language_id = index;
+ ioctl = LIBUSB_IOCTL_SET_DESCRIPTOR;
+ break;
+
+ case IUSB_REQ_GET_CONFIGURATION:
+ ioctl = LIBUSB_IOCTL_GET_CONFIGURATION;
+ break;
+
+ case IUSB_REQ_SET_CONFIGURATION:
+ req.configuration.configuration = value;
+ ioctl = LIBUSB_IOCTL_SET_CONFIGURATION;
+ break;
+
+ case IUSB_REQ_GET_INTERFACE:
+ req.intf.interface_number = index;
+ ioctl = LIBUSB_IOCTL_GET_INTERFACE;
+ break;
+
+ case IUSB_REQ_SET_INTERFACE:
+ req.intf.interface_number = index;
+ req.intf.altsetting_number = value;
+ ioctl = LIBUSB_IOCTL_SET_INTERFACE;
+ break;
+
+ default:
+ return ICOM_SYS;
+ }
+ break;
+
+ case IUSB_REQ_TYPE_VENDOR:
+ case IUSB_REQ_TYPE_CLASS:
+
+ req.vendor.type = (requesttype & IUSB_REQ_TYPE_MASK) >> IUSB_REQ_TYPE_SHIFT;
+ req.vendor.recipient = requesttype & IUSB_REQ_RECIP_MASK;
+ req.vendor.request = request;
+ req.vendor.value = value;
+ req.vendor.index = index;
+
+ if (dirw)
+ ioctl = LIBUSB_IOCTL_VENDOR_WRITE;
+ else
+ ioctl = LIBUSB_IOCTL_VENDOR_READ;
+ break;
+
+ case IUSB_REQ_TYPE_RESERVED:
+ default:
+ return ICOM_SYS;
+ }
+
+ /* If we're writing the data, append it to the req */
+ if (dirw) {
+ osize = sizeof(libusb_request) + size;
+ if ((obuf = calloc(1, osize)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "icoms_usb_control_msg: calloc failed\n");
+ return ICOM_SYS;
+ }
+
+ memcpy(obuf, &req, sizeof(libusb_request));
+ memcpy(obuf + sizeof(libusb_request), bytes, size);
+ ibuf = NULL;
+ isize = 0;
+ }
+
+ if ((rv = do_sync_io(p->usbd->handle, ioctl, obuf, osize, ibuf, isize, &retsz))
+ != ICOM_OK) {
+ if (dirw)
+ free(obuf);
+ return rv;
+ }
+
+ if (dirw) {
+ free(obuf);
+ retsz = size;
+ }
+
+ if (transferred != NULL) /* Adjust for header size requested */
+ *transferred = retsz;
+
+ a1logd(p->log, 8, "icoms_usb_control_msg: returning err 0x%x and %d bytes\n",rv, *transferred);
+ return rv;
+}
+
+/* Cancel i/o operation in another thread. */
+/* Only Vista has CancelIoEx that can cancel a single operation, */
+/* so we cancel the io to the end point, which will */
+/* acheive what we want. */
+int icoms_usb_cancel_io(
+ icoms *p,
+ usb_cancelt *cancelt
+) {
+ int rv = ICOM_OK;
+ usb_lock_cancel(cancelt);
+ if (cancelt->hcancel != NULL) {
+ libusb_request req;
+
+ memset(&req, 0, sizeof(libusb_request));
+ req.endpoint.endpoint = *((unsigned char *)cancelt->hcancel);
+ req.timeout = LIBUSBW1_DEFAULT_TIMEOUT;
+
+ if ((rv = do_sync_io(p->usbd->handle, LIBUSB_IOCTL_ABORT_ENDPOINT,
+ &req, sizeof(libusb_request), NULL, 0, NULL)) != ICOM_OK) {
+ a1logd(p->log, 1, "icoms_usb_cancel_io: failed with 0x%x\n",rv);
+ }
+ }
+ usb_unlock_cancel(cancelt);
+
+ return rv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Reset and end point data toggle to 0 */
+int icoms_usb_resetep(
+ icoms *p,
+ int ep /* End point address */
+) {
+ libusb_request req;
+ int rv = ICOM_OK;
+
+ memset(&req, 0, sizeof(libusb_request));
+ req.endpoint.endpoint = ep;
+ req.timeout = LIBUSBW1_DEFAULT_TIMEOUT;
+
+ if ((rv = do_sync_io(p->usbd->handle, LIBUSB_IOCTL_RESET_ENDPOINT, &req,
+ sizeof(libusb_request), NULL, 0, NULL)) != ICOM_OK) {
+ a1logd(p->log, 1, "icoms_usb_resetep failed with %d\n",rv);
+ return rv;
+ }
+ return rv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Clear a halt on an end point */
+/* (Actually does a resetep) */
+int icoms_usb_clearhalt(
+ icoms *p,
+ int ep /* End point address */
+) {
+ libusb_request req;
+ int rv = ICOM_OK;
+
+ memset(&req, 0, sizeof(libusb_request));
+ req.endpoint.endpoint = ep;
+ req.timeout = LIBUSBW1_DEFAULT_TIMEOUT;
+
+ if ((rv = do_sync_io(p->usbd->handle, LIBUSB_IOCTL_RESET_ENDPOINT, &req,
+ sizeof(libusb_request), NULL, 0, NULL)) != ICOM_OK) {
+ a1logd(p->log, 1, "icoms_usb_resetep failed with %d\n",rv);
+ return rv;
+ }
+ return rv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+#ifdef NEVER
+ libusb_request req;
+ unsigned char buf[1] = { 0xff };
+
+ memset(&req, 0, sizeof(libusb_request));
+ req.timeout = LIBUSBW1_DEFAULT_TIMEOUT;
+
+ if ((rv = do_sync_io(p->usbd->handle, LIBUSB_IOCTL_GET_CONFIGURATION,
+ &req, sizeof(libusb_request), buf, 1, NULL)) != ICOM_OK) {
+
+ a1logd(p->log, 1, "usb_open_port: Getting port '%s' configuration failed with %d\n",p->usbd->dpath,rv);
+ /* Ignore error */
+ } else {
+ a1logd(p->log, 1, "usb_open_port: current config = %d\n",(int)buf[0]);
+ }
+ config = buf[0];
+#endif // NEVER
+
diff --git a/spectro/usbio_ox.c b/spectro/usbio_ox.c
new file mode 100644
index 0000000..24498fb
--- /dev/null
+++ b/spectro/usbio_ox.c
@@ -0,0 +1,986 @@
+
+/* General USB I/O support, OS X native implementation. */
+/* This file is conditionaly #included into usbio.c */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 2006/22/4
+ *
+ * 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.
+ */
+
+#include <sys/time.h>
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/IOTypes.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/usb/IOUSBLib.h>
+#include <IOKit/IOCFPlugIn.h>
+//#include <IOKit/IOCFBundle.h>
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
+# include <objc/objc-auto.h>
+#endif
+
+/* Add paths to USB connected instruments */
+/* Return an icom error */
+int usb_get_paths(
+icompaths *p
+) {
+ kern_return_t kstat;
+ CFMutableDictionaryRef sdict; /* USB Device dictionary */
+ io_iterator_t mit; /* Matching itterator */
+ int vid, pid;
+ struct usb_idevice *usbd = NULL;
+
+ a1logd(p->log, 6, "usb_get_paths: about to look through devices:\n");
+
+ /* Get dictionary of USB devices */
+ if ((sdict = IOServiceMatching(kIOUSBDeviceClassName)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "usb_get_paths() IOServiceMatching returned a NULL dictionary\n");
+ return ICOM_SYS;
+ }
+
+ /* Init itterator to find matching types. Consumes sdict reference */
+ if ((kstat = IOServiceGetMatchingServices(kIOMasterPortDefault, sdict, &mit))
+ != KERN_SUCCESS) {
+ a1loge(p->log, ICOM_SYS, "usb_get_paths() IOServiceGetMatchingServices returned %d\n", kstat);
+ return ICOM_SYS;
+ }
+
+ /* Find all the matching USB devices */
+ for (;;) {
+ io_object_t ioob; /* USB object found */
+ io_iterator_t it1; /* Child level 1 */
+ CFMutableDictionaryRef usbprops = 0; /* USB Device properties */
+ CFNumberRef vref, pref; /* USB Vendor and Product ID propeties */
+ CFNumberRef nconfref; /* No configurations */
+ CFNumberRef nepref, lidpref; /* No ep's, Location ID properties */
+ unsigned int vid = 0, pid = 0, nep, tnep, nconfig = 0, lid = 0;
+ instType itype;
+
+ if ((ioob = IOIteratorNext(mit)) == 0)
+ break;
+
+ /* Get the two properies we need. */
+ if ((vref = IORegistryEntryCreateCFProperty(ioob, CFSTR(kUSBVendorID),
+ kCFAllocatorDefault,kNilOptions)) != 0) {
+ CFNumberGetValue(vref, kCFNumberIntType, &vid);
+ CFRelease(vref);
+ }
+ if ((pref = IORegistryEntryCreateCFProperty(ioob, CFSTR(kUSBProductID),
+ kCFAllocatorDefault,kNilOptions)) != 0) {
+ CFNumberGetValue(pref, kCFNumberIntType, &pid);
+ CFRelease(pref);
+ }
+
+ if ((nconfref = IORegistryEntryCreateCFProperty(ioob, CFSTR("bNumConfigurations"),
+ kCFAllocatorDefault,kNilOptions)) != 0) {
+ CFNumberGetValue(nconfref, kCFNumberIntType, &nconfig);
+ CFRelease(nconfref);
+ }
+
+ if ((lidpref = IORegistryEntryCreateCFProperty(ioob, CFSTR("locationID"),
+ kCFAllocatorDefault,kNilOptions)) != 0) {
+ CFNumberGetValue(lidpref, kCFNumberIntType, &lid);
+ CFRelease(lidpref);
+ }
+
+ a1logd(p->log, 6, "usb_check_and_add: checking vid 0x%04x, pid 0x%04x, lid 0x%x\n",vid,pid,lid);
+
+ /* Do a preliminary match */
+ if ((itype = inst_usb_match(vid, pid, 0)) == instUnknown) {
+ a1logd(p->log, 6 , "usb_check_and_add: 0x%04x 0x%04x not reconized\n",vid,pid);
+ IOObjectRelease(ioob); /* Release found object */
+ continue;
+ }
+
+ /* Allocate an idevice so that we can fill in the end point information */
+ if ((usbd = (struct usb_idevice *) calloc(sizeof(struct usb_idevice), 1)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "icoms: calloc failed!\n");
+ return ICOM_SYS;
+ }
+
+ usbd->nconfig = nconfig;
+ usbd->config = 1; /* We are only interested in config 1 */
+
+ /* Go through all the Interfaces, adding up the number of end points */
+ tnep = 0;
+ if ((kstat = IORegistryEntryCreateIterator(ioob, kIOServicePlane,
+ kIORegistryIterateRecursively, &it1)) != KERN_SUCCESS) {
+ IOObjectRelease(ioob);
+ IOObjectRelease(mit);
+ a1loge(p->log, kstat, "usb_check_and_add: IORegistryEntryCreateIterator() with %d\n",kstat);
+ return ICOM_SYS;
+ }
+ usbd->nifce = 0;
+ for (;;) {
+ io_object_t ch1; /* Child object 1 */
+ if ((ch1 = IOIteratorNext(it1)) == 0)
+ break;
+ if (IOObjectConformsTo(ch1, "IOUSBInterface")) {
+ unsigned int config = 0;
+
+ /* Get the configuration number */
+ if ((nconfref = IORegistryEntryCreateCFProperty(ch1, CFSTR(kUSBNumEndpoints),
+ kCFAllocatorDefault,kNilOptions)) != 0) {
+ CFNumberGetValue(nconfref, kCFNumberIntType, &config);
+ CFRelease(nconfref);
+ }
+
+ if (config != 1)
+ continue;
+
+ usbd->nifce++;
+
+ /* Get the number of end points */
+ if ((nepref = IORegistryEntryCreateCFProperty(ch1, CFSTR(kUSBNumEndpoints),
+ kCFAllocatorDefault,kNilOptions)) != 0) {
+ CFNumberGetValue(nepref, kCFNumberIntType, &nep);
+ CFRelease(nepref);
+ tnep += nep;
+ }
+ IOObjectRelease(ch1);
+ }
+ }
+ IOObjectRelease(it1);
+
+ if ((itype = inst_usb_match(vid, pid, tnep)) != instUnknown) {
+ int i;
+ char pname[400];
+ int rv;
+
+ /* If this device is HID, it will have already added to the paths list, */
+ /* so check for this and skip this device if it is already there. */
+ for (i = 0; i < p->npaths; i++) {
+ if (p->paths[i]->vid == vid
+ && p->paths[i]->pid == pid
+ && p->paths[i]->hidd != NULL
+ && p->paths[i]->hidd->lid == lid) {
+ a1logd(p->log, 1, "usb_check_and_add: Ignoring device because it is already in list as HID\n");
+ break;
+ }
+ }
+ if (i < p->npaths) {
+ IOObjectRelease(ioob); /* Release found object */
+ free(usbd);
+
+ } else {
+
+ a1logd(p->log, 1, "usb_check_and_add: found instrument vid 0x%04x, pid 0x%04x\n",vid,pid);
+ usbd->lid = lid;
+ usbd->ioob = ioob;
+
+ /* Create a path/identification */
+ sprintf(pname,"usb%d: (%s)", lid >> 20, inst_name(itype));
+
+ /* Add the path and ep info to the list */
+ if ((rv = p->add_usb(p, pname, vid, pid, tnep, usbd, itype)) != ICOM_OK) {
+ IOObjectRelease(ioob); /* Release found object */
+ free(usbd);
+ IOObjectRelease(mit); /* Release the itterator */
+ return rv;
+ }
+ }
+ } else {
+ IOObjectRelease(ioob); /* Release found object */
+ free(usbd);
+ }
+ }
+ IOObjectRelease(mit); /* Release the itterator */
+
+ a1logd(p->log, 8, "usb_get_paths: returning %d paths and ICOM_OK\n",p->npaths);
+ return ICOM_OK;
+}
+
+/* Copy usb_idevice contents from icompaths to icom */
+/* return icom error */
+int usb_copy_usb_idevice(icoms *d, icompath *s) {
+ int i;
+ if (s->usbd == NULL) {
+ d->usbd = NULL;
+ return ICOM_OK;
+ }
+ if ((d->usbd = calloc(sizeof(struct usb_idevice), 1)) == NULL) {
+ a1loge(d->log, ICOM_SYS, "usb_copy_usb_idevice: malloc\n");
+ return ICOM_SYS;
+ }
+ d->nconfig = s->usbd->nconfig;
+ d->config = s->usbd->config;
+ d->nifce = s->usbd->nifce;
+ d->usbd->ioob = s->usbd->ioob;
+ IOObjectRetain(d->usbd->ioob);
+ return ICOM_OK;
+}
+
+/* Cleanup and then free a usb dev entry */
+void usb_del_usb_idevice(struct usb_idevice *usbd) {
+
+ if (usbd == NULL)
+ return;
+
+ if (usbd->ioob != 0)
+ IOObjectRelease(usbd->ioob);
+
+ free(usbd);
+}
+
+/* Cleanup any USB specific icoms state */
+void usb_del_usb(icoms *p) {
+
+ usb_del_usb_idevice(p->usbd);
+}
+
+/* Clean up anything allocated/created in p->usbd, but don't free p->usbd itself */
+static void cleanup_device(icoms *p) {
+
+ if (p->usbd != NULL) {
+ if (p->usbd->device != NULL) { /* device is open */
+ int i;
+
+ /* Stop the RunLoop and wait for it */
+ if (p->usbd->cfrunloop != NULL) {
+
+ a1logd(p->log, 6, "usb_close_port: waiting for RunLoop thread to exit\n");
+ CFRunLoopStop(p->usbd->cfrunloop);
+ CFRelease(p->usbd->cfrunloop);
+ if (p->usbd->thread != NULL)
+ pthread_join(p->usbd->thread, NULL);
+ }
+
+ /* Release all the interfaces */
+ for (i = 0; i < p->usbd->nifce; i++) {
+ if (p->usbd->interfaces[i] != NULL) {
+ (*p->usbd->interfaces[i])->USBInterfaceClose(p->usbd->interfaces[i]);
+ (*p->usbd->interfaces[i])->Release(p->usbd->interfaces[i]);
+ }
+ }
+
+ /* Close the device */
+ (*(p->usbd->device))->USBDeviceClose(p->usbd->device);
+ (*(p->usbd->device))->Release(p->usbd->device);
+ }
+ pthread_cond_destroy(&p->usbd->cond);
+ pthread_mutex_destroy(&p->usbd->lock);
+
+ memset(p->usbd, 0, sizeof(struct usb_idevice));
+ }
+}
+
+/* Close an open USB port */
+/* If we don't do this, the port and/or the device may be left in an unusable state. */
+void usb_close_port(icoms *p) {
+
+ a1logd(p->log, 6, "usb_close_port: called\n");
+
+ if (p->is_open && p->usbd != NULL) {
+
+ /* Workaround for some bugs - reset device on close */
+ if (p->uflags & icomuf_reset_before_close) {
+ IOReturn rv;
+ if ((rv = (*(p->usbd->device))->ResetDevice(p->usbd->device)) != kIOReturnSuccess) {
+ a1logd(p->log, 1, "usb_close_port: ResetDevice failed with 0x%x\n",rv);
+ }
+ }
+
+ /* Close down and free everything in p->usbd */
+ cleanup_device(p);
+ if (p->usbd != NULL) {
+ free(p->usbd);
+ p->usbd = NULL;
+ }
+ a1logd(p->log, 6, "usb_close_port: usb port has been released and closed\n");
+ }
+ p->is_open = 0;
+
+ /* Find it and delete it from our static cleanup list */
+ usb_delete_from_cleanup_list(p);
+}
+
+static int icoms_usb_control_msg(icoms *p, int *transferred, int requesttype, int request,
+ int value, int index, unsigned char *bytes, int size, int timeout);
+static void *io_runloop(void *context);
+
+/* Open a USB port for all our uses. */
+/* This always re-opens the port */
+/* return icom error */
+static int usb_open_port(
+icoms *p,
+int config, /* Configuration number, (1 based) */
+int wr_ep, /* Write end point */
+int rd_ep, /* Read end point */
+icomuflags usbflags,/* Any special handling flags */
+int retries, /* > 0 if we should retry set_configuration (100msec) */
+char **pnames /* List of process names to try and kill before opening */
+) {
+ IOReturn rv;
+ int tries = 0;
+ a1logd(p->log, 8, "usb_open_port: Make sure USB port is open, tries %d\n",retries);
+
+ if (p->is_open)
+ p->close_port(p);
+
+ /* Make sure the port is open */
+ if (!p->is_open) {
+ kkill_nproc_ctx *kpc = NULL;
+
+ if (config != 1) {
+ /* Nothing currently needs it, so we haven't implemented it yet... */
+ a1loge(p->log, ICOM_NOTS, "usb_open_port: native driver cant handle config %d\n",config);
+ return ICOM_NOTS;
+ }
+
+ pthread_mutex_init(&p->usbd->lock, NULL);
+ pthread_cond_init(&p->usbd->cond, NULL);
+
+ /* Do open retries */
+ for (tries = 0; retries >= 0; retries--, tries++) {
+ IOCFPlugInInterface **piif = NULL;
+ SInt32 score;
+
+ a1logd(p->log, 8, "usb_open_port: About to open USB port '%s'\n",p->name);
+
+ if (tries > 0) {
+ //msec_sleep(i_rand(50,100));
+ msec_sleep(77);
+ }
+ if (tries > 0 && pnames != NULL && kpc == NULL) {
+ if ((kpc = kkill_nprocess(pnames, p->log)) == NULL) {
+ a1logd(p->log, 1, "kkill_nprocess returned error!\n");
+ }
+ }
+
+ if ((rv = IOCreatePlugInInterfaceForService(p->usbd->ioob,
+ kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID,
+ &piif, &score)) != kIOReturnSuccess || piif == NULL) {
+ a1loge(p->log, rv, "usb_open_port: Failed to get piif for "
+ "USB device '%s', result 0x%x, piif 0x%x\n",p->name,rv,piif);
+ return ICOM_SYS;
+ }
+
+ p->usbd->device = NULL;
+ if ((rv = (*piif)->QueryInterface(piif,
+ CFUUIDGetUUIDBytes(DeviceInterfaceID), (void *)&p->usbd->device))
+ != kIOReturnSuccess || p->usbd->device == NULL) {
+ a1loge(p->log, rv, "usb_open_port: QueryInterface '%s' failed with 0%x\n",p->name, rv);
+ (*piif)->Release(piif);
+ return ICOM_SYS;
+ }
+ (*piif)->Release(piif); /* delete intermediate object */
+
+ if ((rv = (*p->usbd->device)->USBDeviceOpenSeize(p->usbd->device)) != kIOReturnSuccess) {
+ a1logd(p->log, 8, "usb_open_port: open '%s' config %d failed (0x%x) (Device being used ?)\n",p->name,config,rv);
+ if (retries <= 0) {
+ if (kpc != NULL)
+ kpc->del(kpc);
+ a1loge(p->log, rv, "usb_open_port: open '%s' config %d failed (0x%x) (Device being used ?)\n",p->name, config, rv);
+ (*p->usbd->device)->Release(p->usbd->device);
+ return ICOM_SYS;
+ }
+ continue;
+ } else if (p->debug)
+ a1logd(p->log, 1, "usb_open_port: open port '%s' succeeded\n",p->name);
+
+ p->uflags = usbflags;
+
+ /* We're done retries */
+ break;
+ }
+
+ if (kpc != NULL)
+ kpc->del(kpc);
+
+ /* We should only do a set configuration if the device has more than one */
+ /* possible configuration and it is currently not the desired configuration, */
+ /* but we should avoid doing a set configuration if the OS has already */
+ /* selected the configuration we want, since two set configs seem to */
+ /* mess up the Spyder2, BUT we can't do a get config because this */
+ /* messes up the i1pro-D. */
+
+ /* OS X doesn't do a set_configuration() by default */
+ p->cconfig = 0;
+
+ if (p->cconfig != config) {
+ if ((rv = (*(p->usbd->device))->SetConfiguration(
+ p->usbd->device, config)) != kIOReturnSuccess) {
+ a1loge(p->log, rv, "usb_open_port: SetConfiguration failed with 0x%x\n",rv);
+ cleanup_device(p);
+ return ICOM_SYS;
+ }
+ p->cconfig = config;
+ a1logd(p->log, 6, "usb_open_port: SetConfiguration %d OK\n",config);
+ }
+
+ /* Get interfaces */
+ {
+ int i, j;
+ IOUSBFindInterfaceRequest req;
+ io_iterator_t ioit;
+ io_service_t iface;
+ IOCFPlugInInterface **pluginref = NULL;
+ SInt32 score;
+
+ req.bInterfaceClass =
+ req.bInterfaceSubClass =
+ req.bInterfaceProtocol =
+ req.bAlternateSetting = kIOUSBFindInterfaceDontCare;
+
+ if ((rv = (*(p->usbd->device))->CreateInterfaceIterator(
+ p->usbd->device, &req, &ioit)) != kIOReturnSuccess) {
+ a1loge(p->log, rv, "usb_open_port: CreateInterfaceIterator failed with 0x%x\n",rv);
+ cleanup_device(p);
+ return ICOM_SYS;
+ }
+ iface = IOIteratorNext(ioit);
+ for (i = 0; iface != 0; i++, iface = IOIteratorNext(ioit)) {
+ u_int8_t nep;
+
+ if ((rv = IOCreatePlugInInterfaceForService(iface,
+ kIOUSBInterfaceUserClientTypeID,
+ kIOCFPlugInInterfaceID,
+ &pluginref, &score)) != kIOReturnSuccess) {
+ a1loge(p->log, rv, "usb_open_port: IOCreatePlugInInterfaceForService failed with 0x%x\n",rv);
+ IOObjectRelease(iface);
+ IOObjectRelease(ioit);
+ cleanup_device(p);
+ return ICOM_SYS;
+ }
+ IOObjectRelease(iface);
+ if ((rv = (*pluginref)->QueryInterface(pluginref,
+ CFUUIDGetUUIDBytes(InterfaceInterfaceID),
+ (void *)&p->usbd->interfaces[i])) != kIOReturnSuccess) {
+ a1loge(p->log, rv, "usb_open_port: QueryInterface failed with 0x%x\n",rv);
+ (*pluginref)->Stop(pluginref);
+ IODestroyPlugInInterface(pluginref);
+ IOObjectRelease(ioit);
+ cleanup_device(p);
+ return ICOM_SYS;
+ }
+ (*pluginref)->Stop(pluginref);
+ IODestroyPlugInInterface(pluginref);
+
+ if ((rv = (*(p->usbd->interfaces[i]))->USBInterfaceOpen(
+ p->usbd->interfaces[i])) != kIOReturnSuccess) {
+ a1loge(p->log, rv, "usb_open_port: USBInterfaceOpen failed with 0x%x\n",rv);
+ IOObjectRelease(ioit);
+ cleanup_device(p);
+ return ICOM_SYS;
+ }
+
+ /* Get the end point details, and set reference to pipe no and interfece ix */
+ if ((rv = (*(p->usbd->interfaces[i]))->GetNumEndpoints(
+ p->usbd->interfaces[i], &nep)) != kIOReturnSuccess) {
+ a1loge(p->log, rv, "usb_open_port: GetNumEndpoints failed with 0x%x\n",rv);
+ IOObjectRelease(ioit);
+ cleanup_device(p);
+ return ICOM_SYS;
+ }
+ for (j = 0; j < nep; j++) {
+ UInt8 direction, pnumber, transferType, interval;
+ UInt16 maxPacketSize;
+ int ad;
+ if ((rv = (*(p->usbd->interfaces[i]))->GetPipeProperties(
+ p->usbd->interfaces[i], (UInt8)(j+1), &direction, &pnumber,
+ &transferType, &maxPacketSize, &interval)) != kIOReturnSuccess) {
+ a1loge(p->log, rv, "usb_open_port: GetPipeProperties failed with 0x%x\n",rv);
+ IOObjectRelease(ioit);
+ cleanup_device(p);
+ return ICOM_SYS;
+ }
+ ad = ((direction << IUSB_ENDPOINT_DIR_SHIFT) & IUSB_ENDPOINT_DIR_MASK)
+ | (pnumber & IUSB_ENDPOINT_NUM_MASK);
+ p->EPINFO(ad).valid = 1;
+ p->EPINFO(ad).addr = ad;
+ p->EPINFO(ad).packetsize = maxPacketSize;
+ p->EPINFO(ad).type = transferType & IUSB_ENDPOINT_TYPE_MASK;
+ p->EPINFO(ad).interface = i;
+ p->EPINFO(ad).pipe = j+1;
+ a1logd(p->log, 6, "set ep ad 0x%x packetsize %d type %d, if %d, pipe %d\n",ad,maxPacketSize,transferType & IUSB_ENDPOINT_TYPE_MASK,i,j);
+ }
+ }
+ p->usbd->nifce = i; /* Just in case.. */
+
+ IOObjectRelease(ioit);
+ }
+ {
+ /* Setup the RunLoop thread */
+ a1logd(p->log, 6, "usb_open_port: Starting RunLoop thread\n");
+ if ((rv = pthread_create(&p->usbd->thread, NULL, io_runloop, (void*)p)) < 0) {
+ a1loge(p->log, ICOM_SYS, "usb_open_port: creating RunLoop thread failed with %s\n",rv);
+ cleanup_device(p);
+ return ICOM_SYS;
+ }
+
+ /* Wait for the runloop thread to start and create a cfrunloop */
+ pthread_mutex_lock(&p->usbd->lock);
+ while (p->usbd->cfrunloop == NULL)
+ pthread_cond_wait(&p->usbd->cond, &p->usbd->lock);
+ pthread_mutex_unlock(&p->usbd->lock);
+ if (p->usbd->thrv != kIOReturnSuccess) { /* Thread failed */
+ pthread_join(p->usbd->thread, NULL);
+ cleanup_device(p);
+ return ICOM_SYS;
+ }
+ CFRetain(p->usbd->cfrunloop);
+ a1logd(p->log, 6, "usb_open_port: RunLoop thread started\n");
+ }
+
+ /* Clear any errors */
+ /* (Some I/F seem to hang if we do this, some seem to hang if we don't !) */
+ if (!(p->uflags & icomuf_no_open_clear)) {
+ int i;
+ for (i = 0; i < 32; i++) {
+ if (!p->ep[i].valid)
+ continue;
+ p->usb_clearhalt(p, p->ep[i].addr);
+ }
+ }
+
+ /* Set "serial" coms values */
+ p->wr_ep = wr_ep;
+ p->rd_ep = rd_ep;
+ p->rd_qa = p->EPINFO(rd_ep).packetsize;
+ if (p->rd_qa == 0)
+ p->rd_qa = 8;
+ a1logd(p->log, 8, "usb_open_port: 'serial' read quanta = packet size = %d\n",p->rd_qa);
+
+ p->is_open = 1;
+ a1logd(p->log, 8, "usb_open_port: USB port is now open\n");
+ }
+
+ /* Install the cleanup signal handlers, and add to our cleanup list */
+ usb_install_signal_handlers(p);
+
+ return ICOM_OK;
+}
+
+/* -------------------------------------------------------------- */
+
+/* The run loop thread */
+static void *io_runloop(void *context) {
+ icoms *p = (icoms *)context;
+ int i;
+
+ /* Register this thread with the Objective-C garbage collector */
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
+ objc_registerThreadWithCollector();
+#endif
+
+ a1logd(p->log, 6, "io_runloop: thread started\n");
+
+ p->usbd->cfrunloop = CFRunLoopGetCurrent(); /* Get this threads RunLoop */
+ CFRetain(p->usbd->cfrunloop);
+
+ /* Add a device event source */
+ if ((p->usbd->thrv = (*(p->usbd->device))->CreateDeviceAsyncEventSource(
+ p->usbd->device, &p->usbd->cfsource)) != kIOReturnSuccess) {
+ a1loge(p->log, p->usbd->thrv, "io_runloop: CreateDeviceAsyncEventSource failed with 0x%x\n",p->usbd->thrv);
+ } else {
+ CFRunLoopAddSource(p->usbd->cfrunloop, p->usbd->cfsource, kCFRunLoopDefaultMode);
+ }
+
+ /* Create an async event source for the interfaces */
+ for (i = 0; p->usbd->thrv == kIOReturnSuccess && i < p->usbd->nifce; i++) {
+ if ((p->usbd->thrv = (*(p->usbd->interfaces[i]))->CreateInterfaceAsyncEventSource(
+ p->usbd->interfaces[i], &p->usbd->cfsources[i])) != kIOReturnSuccess) {
+ a1loge(p->log, p->usbd->thrv, "io_runloop: CreateInterfaceAsyncEventSource failed with 0x%x\n",p->usbd->thrv);
+ } else {
+ /* Add it to the RunLoop */
+ CFRunLoopAddSource(p->usbd->cfrunloop, p->usbd->cfsources[i], kCFRunLoopDefaultMode);
+ }
+ }
+
+ /* Signal main thread that we've started */
+ pthread_mutex_lock(&p->usbd->lock);
+ pthread_cond_signal(&p->usbd->cond);
+ pthread_mutex_unlock(&p->usbd->lock);
+
+ /* Run the loop, or exit on error */
+ if (p->usbd->thrv == kIOReturnSuccess) {
+ CFRunLoopRun(); /* Run the loop and deliver events */
+ }
+
+ /* Delete the interfaces async event sources */
+ for (i = 0; i < p->usbd->nifce; i++) {
+ if (p->usbd->cfsources[i] != NULL) {
+ CFRunLoopRemoveSource(p->usbd->cfrunloop, p->usbd->cfsources[i], kCFRunLoopDefaultMode);
+ CFRelease(p->usbd->cfsources[i]);
+ }
+ }
+
+ /* Delete the devices event sources */
+ if (p->usbd->cfsource != NULL) {
+ CFRunLoopRemoveSource(p->usbd->cfrunloop, p->usbd->cfsource, kCFRunLoopDefaultMode);
+ CFRelease(p->usbd->cfsource);
+ }
+
+ CFRelease(p->usbd->cfrunloop);
+
+ a1logd(p->log, 6, "io_runloop: thread done\n");
+ return NULL;
+}
+
+/* I/O structures */
+
+typedef struct _usbio_req {
+ icoms *p;
+ int iix; /* Interface index */
+ UInt8 pno; /* pipe index */
+ volatile int done; /* Done flag */
+ pthread_mutex_t lock; /* Protect req & callback access */
+ pthread_cond_t cond; /* Signal to thread waiting on req */
+ int xlength; /* Bytes transferred */
+ IOReturn result; /* Result of transaction */
+} usbio_req;
+
+
+/* Async completion callback - called by RunLoop thread */
+static void io_callback(void *refcon, IOReturn result, void *arg0) {
+ usbio_req *req = (usbio_req *)refcon;
+
+ a1logd(req->p->log, 1, "io_callback: result 0x%x, length %d\n",result,(int)(long)arg0);
+
+ req->xlength = (int)(long)arg0;
+ req->result = result;
+ req->done = 1;
+ pthread_mutex_lock(&req->lock);
+ pthread_cond_signal(&req->cond);
+ pthread_mutex_unlock(&req->lock);
+}
+
+/* Our universal USB transfer function */
+static int icoms_usb_transaction(
+ icoms *p,
+ usb_cancelt *cancelt,
+ int *transferred,
+ icom_usb_trantype ttype, /* transfer type */
+ unsigned char endpoint, /* 0x80 for control write, 0x00 for control read */
+ unsigned char *buffer,
+ int length,
+ unsigned int timeout /* In msec */
+) {
+ int reqrv = ICOM_OK, rv = 0;
+ int dirw = (endpoint & IUSB_ENDPOINT_DIR_MASK) == IUSB_ENDPOINT_OUT ? 1 : 0;
+ usbio_req req;
+ IOReturn result;
+ int iix = p->EPINFO(endpoint).interface;
+ UInt8 pno = (UInt8)p->EPINFO(endpoint).pipe;
+
+ in_usb_rw++;
+
+ a1logd(p->log, 8, "icoms_usb_transaction: req type 0x%x ep 0x%x size %d\n",ttype,endpoint,length);
+
+ if (ttype != icom_usb_trantype_interrutpt
+ && ttype != icom_usb_trantype_bulk) {
+ /* We only handle interrupt & bulk, not control */
+ return ICOM_SYS;
+ }
+
+ req.p = p;
+ req.iix = iix;
+ req.pno = pno;
+ req.xlength = 0;
+ req.done = 0;
+ pthread_mutex_init(&req.lock, NULL);
+ pthread_cond_init(&req.cond, NULL);
+
+ if (dirw)
+ result = (*p->usbd->interfaces[iix])->WritePipeAsync(p->usbd->interfaces[iix],
+ pno, buffer, length, io_callback, &req);
+ else
+ result = (*p->usbd->interfaces[iix])->ReadPipeAsync(p->usbd->interfaces[iix],
+ pno, buffer, length, io_callback, &req);
+
+ if (result != kIOReturnSuccess) {
+ a1loge(p->log, ICOM_SYS, "icoms_usb_transaction: %sPipeAsync failed with 0x%x\n",dirw ? "Write" : "Read", result);
+ reqrv = ICOM_SYS;
+ goto done;
+ }
+
+ if (cancelt != NULL) {
+ usb_lock_cancel(cancelt);
+ cancelt->hcancel = (void *)&req;
+ usb_unlock_cancel(cancelt);
+ }
+
+ /* Wait for the callback to complete */
+ pthread_mutex_lock(&req.lock);
+ if (!req.done) {
+ struct timeval tv;
+ struct timespec ts;
+
+ // this is unduly complicated...
+ gettimeofday(&tv, NULL);
+ ts.tv_sec = tv.tv_sec + timeout/1000;
+ ts.tv_nsec = (tv.tv_usec + (timeout % 1000) * 1000) * 1000L;
+ if (ts.tv_nsec > 1000000000L) {
+ ts.tv_nsec -= 1000000000L;
+ ts.tv_sec++;
+ }
+
+ for(;;) { /* Ignore spurious wakeups */
+ if ((rv = pthread_cond_timedwait(&req.cond, &req.lock, &ts)) != 0) {
+ if (rv != ETIMEDOUT) {
+ a1logd(p->log, 1, "coms_usb_transaction: pthread_cond_timedwait failed with %d\n",rv);
+ (*p->usbd->interfaces[iix])->AbortPipe(p->usbd->interfaces[iix], pno);
+ req.result = kIOReturnAborted;
+ reqrv = ICOM_SYS;
+ break;
+ }
+
+ /* Timed out */
+ a1logd(p->log, 8, "coms_usb_transaction: time out - aborting io\n");
+ (*p->usbd->interfaces[iix])->AbortPipe(p->usbd->interfaces[iix], pno);
+ reqrv = ICOM_TO;
+ /* Wait for the cancelled io to be signalled */
+ if ((rv = pthread_cond_wait(&req.cond, &req.lock)) != 0) {
+ pthread_mutex_unlock(&req.lock);
+ a1logd(p->log, 1, "coms_usb_transaction: pthread_cond_wait failed with %d\n",rv);
+ req.result = kIOReturnAborted;
+ reqrv = ICOM_SYS;
+ break;
+ }
+ break;
+ }
+ if (req.done) /* Ignore spurious wakeups */
+ break;
+ }
+ }
+ pthread_mutex_unlock(&req.lock);
+
+ a1logd(p->log, 8, "coms_usb_transaction: completed with reqrv 0x%x and xlength %d\n",req.result,req.xlength);
+
+ /* If io was aborted, ClearPipeStall */
+ if (req.result == kIOReturnAborted) {
+#if defined(NEVER) && (InterfaceVersion > 182)
+ (*p->usbd->interfaces[iix])->ClearPipeStallBothEnds(p->usbd->interfaces[iix], pno);
+#else
+ (*p->usbd->interfaces[iix])->ClearPipeStall(p->usbd->interfaces[iix], pno);
+ icoms_usb_control_msg(p, NULL, IUSB_REQ_HOST_TO_DEV | IUSB_REQ_TYPE_STANDARD
+ | IUSB_REQ_RECIP_ENDPOINT, IUSB_REQ_CLEAR_FEATURE,
+ IUSB_FEATURE_EP_HALT, endpoint, NULL, 0, 200);
+#endif
+ if (reqrv == ICOM_OK) /* If not aborted for a known reason, must be cancelled */
+ reqrv = ICOM_CANC;
+ }
+
+ /* If normal completion - not timed out or aborted */
+ if (reqrv == ICOM_OK) { /* Completed OK */
+ if (req.result == kIOReturnSuccess) {
+ if (req.xlength != length)
+ reqrv = ICOM_SHORT;
+ } else {
+ if (dirw)
+ reqrv = ICOM_USBW;
+ else
+ reqrv = ICOM_USBR;
+ }
+ }
+
+ if (transferred != NULL)
+ *transferred = req.xlength;
+
+done:;
+ if (cancelt != NULL) {
+ usb_lock_cancel(cancelt);
+ cancelt->hcancel = (void *)NULL;
+ usb_unlock_cancel(cancelt);
+ }
+
+ pthread_cond_destroy(&req.cond);
+ pthread_mutex_destroy(&req.lock);
+
+ if (in_usb_rw < 0)
+ exit(0);
+
+ in_usb_rw--;
+
+ a1logd(p->log, 8, "coms_usb_transaction: returning err 0x%x and %d bytes\n",reqrv, req.xlength);
+
+ return reqrv;
+}
+
+
+/* Our control message routine */
+/* Return error icom error code */
+static int icoms_usb_control_msg(
+icoms *p,
+int *transferred,
+int requesttype, int request,
+int value, int index, unsigned char *bytes, int size,
+int timeout) {
+ int reqrv = ICOM_OK;
+ int dirw = (requesttype & IUSB_REQ_DIR_MASK) == IUSB_REQ_HOST_TO_DEV ? 1 : 0;
+ IOReturn rv;
+ IOUSBDevRequestTO req;
+
+ a1logd(p->log, 8, "icoms_usb_control_msg: type 0x%x req 0x%x size %d\n",requesttype,request,size);
+ bzero(&req, sizeof(req));
+ req.bmRequestType = requesttype;
+ req.bRequest = request;
+ req.wValue = value;
+ req.wIndex = index;
+ req.wLength = size;
+ req.pData = bytes;
+ req.completionTimeout = timeout;
+ req.noDataTimeout = timeout;
+
+ if (transferred != NULL)
+ *transferred = 0;
+
+ if ((rv = (*(p->usbd->device))->DeviceRequestTO(p->usbd->device, &req)) != kIOReturnSuccess) {
+ if (rv == kIOUSBTransactionTimeout) {
+ reqrv = ICOM_TO;
+ } else {
+ if (dirw)
+ reqrv = ICOM_USBW;
+ else
+ reqrv = ICOM_USBR;
+ }
+ } else {
+ if (transferred != NULL)
+ *transferred = req.wLenDone;
+ }
+
+ a1logd(p->log, 8, "icoms_usb_control_msg: returning err 0x%x and %d bytes\n",reqrv, transferred != NULL ? *transferred : -1);
+ return reqrv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Time out error return value */
+
+//#define USBIO_ERROR_TIMEOUT -ETIMEDOUT
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Cancel i/o in another thread */
+int icoms_usb_cancel_io(
+ icoms *p,
+ usb_cancelt *cancelt
+) {
+ int reqrv = ICOM_OK;
+
+ usb_lock_cancel(cancelt);
+ usbio_req *req = (usbio_req *)cancelt->hcancel;
+ if (req != NULL) {
+ IOReturn rv;
+
+ if ((rv = (*p->usbd->interfaces[req->iix])->AbortPipe(
+ p->usbd->interfaces[req->iix], req->pno)) != kIOReturnSuccess) {
+ reqrv = ICOM_USBW;
+ }
+ }
+ usb_unlock_cancel(cancelt);
+
+ return reqrv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Reset and end point data toggle to 0 */
+int icoms_usb_resetep(
+ icoms *p,
+ int ep /* End point address */
+) {
+ int reqrv = ICOM_OK;
+ int iix = p->EPINFO(ep).interface;
+ UInt8 pno = (UInt8)p->EPINFO(ep).pipe;
+ IOReturn rv;
+
+ if ((rv = (*p->usbd->interfaces[iix])->ResetPipe(
+ p->usbd->interfaces[iix], pno)) != kIOReturnSuccess) {
+ a1logd(p->log, 1, "icoms_usb_resetep failed with 0x%x\n",rv);
+ reqrv = ICOM_USBW;
+ }
+ return reqrv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Clear a halt on an end point */
+int icoms_usb_clearhalt(
+ icoms *p,
+ int ep /* End point address */
+) {
+ int reqrv = ICOM_OK;
+ int iix = p->EPINFO(ep).interface;
+ UInt8 pno = (UInt8)p->EPINFO(ep).pipe;
+ IOReturn rv;
+ int irv;
+
+#if defined(NEVER) && (InterfaceVersion > 182)
+ if ((rv = (*p->usbd->interfaces[iix])->ClearPipeStallBothEnds(
+ p->usbd->interfaces[iix], pno)) != kIOReturnSuccess) {
+ a1logd(p->log, 1, "icoms_usb_clearhalt failed with 0x%x\n",rv);
+ reqrv = ICOM_USBW;
+ }
+#else
+ if ((rv = (*p->usbd->interfaces[iix])->ClearPipeStall(
+ p->usbd->interfaces[iix], pno)) != kIOReturnSuccess) {
+ a1logd(p->log, 1, "icoms_usb_clearhalt failed with 0x%x\n",rv);
+ reqrv = ICOM_USBW;
+ }
+ if ((irv = icoms_usb_control_msg(p, NULL, IUSB_REQ_HOST_TO_DEV | IUSB_REQ_TYPE_STANDARD
+ | IUSB_REQ_RECIP_ENDPOINT, IUSB_REQ_CLEAR_FEATURE,
+ IUSB_FEATURE_EP_HALT, ep, NULL, 0, 200)) != ICOM_OK) {
+ a1logd(p->log, 1, "icoms_usb_clearhalt far end failed with 0x%x\n",irv);
+ reqrv = ICOM_USBW;
+ }
+#endif
+ return reqrv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+#ifdef NEVER
+ {
+ IOUSBFindInterfaceRequest req;
+ io_iterator_t ioit;
+
+ /* See if we can find any interfaces */
+ req.bInterfaceClass =
+ req.bInterfaceSubClass =
+ req.bInterfaceProtocol =
+ req.bAlternateSetting = kIOUSBFindInterfaceDontCare;
+
+ if ((rv = (*(p->usbd->device))->CreateInterfaceIterator(
+ p->usbd->device, &req, &ioit)) != kIOReturnSuccess) {
+ a1loge(p->log, rv, "usb_open_port: CreateInterfaceIterator failed with 0x%x\n",rv);
+ cleanup_device(p);
+ return ICOM_SYS;
+ }
+ if (IOIteratorNext(ioit) == 0) { /* Configure the device */
+ IOUSBConfigurationDescriptorPtr confdesc;
+
+ if ((rv = (*(p->usbd->device))->GetConfigurationDescriptorPtr(
+ p->usbd->device, config-1, &confdesc)) != kIOReturnSuccess) {
+ a1loge(p->log, rv, "usb_open_port: GetConfigurationDescriptorPtr failed with 0x%x\n",rv);
+ IOObjectRelease(ioit);
+ cleanup_device(p);
+ return ICOM_SYS;
+ }
+ if ((rv = (*(p->usbd->device))->SetConfiguration(
+ p->usbd->device, confdesc->bConfigurationValue)) != kIOReturnSuccess) {
+ a1loge(p->log, rv, "usb_open_port: SetConfiguration failed with 0x%x\n",rv);
+ IOObjectRelease(ioit);
+ cleanup_device(p);
+ return ICOM_SYS;
+ }
+ a1logd(p->log, 6, "usb_open_port: SetConfiguration %d OK\n",confdesc->bConfigurationValue);
+
+ } else { /* Some diagnostics */
+ UInt8 confno;
+ if ((rv = (*(p->usbd->device))->GetConfiguration(
+ p->usbd->device, &confno)) != kIOReturnSuccess) {
+ a1logd(p->log, 6, "usb_open_port: GetConfiguration failed with 0x%x\n",rv);
+ } else {
+ a1logd(p->log, 6, "usb_open_port: Device didn't need configuring - currently %d\n",confno);
+ }
+ }
+ IOObjectRelease(ioit);
+
+ }
+#endif /* NEVER */
diff --git a/spectro/vinflate.c b/spectro/vinflate.c
new file mode 100644
index 0000000..847fa28
--- /dev/null
+++ b/spectro/vinflate.c
@@ -0,0 +1,972 @@
+
+/*
+ inflate.c -- Not copyrighted 1992 by Mark Adler
+ version c10p1, 10 January 1993
+
+ Copied from gzip version 1.2.4 source, and modifed to work with
+ the VIZE installer flavour of DEFLATE by Graeme Gill, October 2007,
+ calling it vinflate.c to distinguish from the original.
+
+ The modifications include reading data 16 bits at a time, big endian,
+ aligning to 16 bits before a stored block, and backing out 16
+ bits at a time at the end of a block.
+
+ (Note that this fails on the latest Spyder2 setup.exe, while
+ the vinflate inside InstExpl.exe works. See
+ http://www.totalcmd.net/plugring/InstallExplorer.html
+ Or does this work now ?)
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/types.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* SALONEINSTLIB */
+#include <fcntl.h>
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* SALONEINSTLIB */
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(text) printf text ;
+#else
+#define DBG(text)
+#endif
+
+typedef unsigned char uch;
+typedef unsigned short ush;
+typedef unsigned int ulg;
+
+#define memzero(s, n) memset((void *)(s), 0, (n))
+
+/* Huffman code lookup table entry--this entry is four bytes for machines
+ that have 16-bit pointers (e.g. PC's in the small or medium model).
+ Valid extra bits are 0..13. e == 15 is EOB (end of block), e == 16
+ means that v is a literal, 16 < e < 32 means that v is a pointer to
+ the next table, which codes e - 16 bits, and lastly e == 99 indicates
+ an unused code. If a code with e == 99 is looked up, this implies an
+ error in the data. */
+struct huft {
+ unsigned char e; /* number of extra bits or operation */
+ unsigned char b; /* number of bits in this code or subcode */
+ union {
+ unsigned short n; /* literal, length base, or distance base */
+ struct huft *t; /* pointer to next level of table */
+ } v;
+};
+
+
+/* Interface to visetest.c */
+extern unsigned int vget_16bits();
+extern void vunget_16bits();
+extern int vwrite_output(unsigned char *buf, unsigned int len);
+
+/* Function prototypes */
+static int huft_build(unsigned *, unsigned, unsigned, ush *, ush *,
+ struct huft **, int *);
+static int huft_free(struct huft *);
+static int vinflate_codes(struct huft *, struct huft *, int, int);
+static int vinflate_stored(void);
+static int vinflate_fixed(void);
+static int vinflate_dynamic(void);
+static int vinflate_block(int *);
+int vinflate(void);
+
+/*
+ The inflate algorithm uses a sliding 32K byte window on the uncompressed
+ stream to find repeated byte strings. This is implemented here as a
+ circular buffer. The index is updated simply by incrementing and then
+ and'ing with 0x7fff (32K-1).
+ It is left to other modules to supply the 32K area. It is assumed
+ to be usable as if it were declared "uch slide[32768];" or as just
+ "uch *slide;" and then malloc'ed in the latter case.
+*/
+
+#define WSIZE 0x8000
+unsigned int wp; /* current position in slide */
+uch slide[32768];
+
+static int vflush_output(unsigned int w) {
+ wp = w;
+
+ if (wp == 0)
+ return 0;
+ if (vwrite_output(slide, wp))
+ return 1;
+ DBG(("Flushed %d byte sof ouput\n",wp))
+ wp = 0;
+
+ return 0;
+}
+
+
+/* Tables for deflate from PKZIP's appnote.txt. */
+static unsigned border[] = { /* Order of the bit length code lengths */
+ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+static ush cplens[] = { /* Copy lengths for literal codes 257..285 */
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
+ 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0};
+ /* note: see note #13 above about the 258 in this list. */
+static ush cplext[] = { /* Extra bits for literal codes 257..285 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
+ 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99}; /* 99==invalid */
+static ush cpdist[] = { /* Copy offsets for distance codes 0..29 */
+ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
+ 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
+ 8193, 12289, 16385, 24577};
+static ush cpdext[] = { /* Extra bits for distance codes */
+ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
+ 7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
+ 12, 12, 13, 13};
+
+
+
+/* Macros for inflate() bit peeking and grabbing.
+ The usage is:
+
+ NEEDBITS(j)
+ x = b & vmask_bits[j];
+ DUMPBITS(j)
+
+ where NEEDBITS makes sure that b has at least j bits in it, and
+ DUMPBITS removes the bits from b. The macros use the variable k
+ for the number of bits in b. Normally, b and k are register
+ variables for speed, and are initialized at the beginning of a
+ routine that uses these macros from a global bit buffer and count.
+
+ If we assume that EOB will be the longest code, then we will never
+ ask for bits with NEEDBITS that are beyond the end of the stream.
+ So, NEEDBITS should not read any more bytes than are needed to
+ meet the request. Then no bytes need to be "returned" to the buffer
+ at the end of the last block.
+
+ However, this assumption is not true for fixed blocks--the EOB code
+ is 7 bits, but the other literal/length codes can be 8 or 9 bits.
+ (The EOB code is shorter than other codes because fixed blocks are
+ generally short. So, while a block always has an EOB, many other
+ literal/length codes have a significantly lower probability of
+ showing up at all.) However, by making the first table have a
+ lookup of seven bits, the EOB code will be found in that first
+ lookup, and so will not require that too many bits be pulled from
+ the stream.
+ */
+
+ulg bb; /* bit buffer */
+unsigned bk; /* bits in bit buffer */
+
+ush vmask_bits[] = {
+ 0x0000,
+ 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff,
+ 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff
+};
+
+#ifdef NEVER
+#define NEEDBITS(n) { \
+ while(k < (n)) { \
+ unsigned int nv; \
+ nv = vget_16bits(); \
+ if (nv == 0x11111111) { \
+ printf("\nnoticed end\n"); \
+ /* return 2; */ \
+ } \
+ b |= ((ulg)nv) << k; \
+ k += 16; \
+ } \
+}
+#else
+#define NEXTBYTE() (uch)vget_16bits()
+//#define NEEDBITS(n) {while(k<(n)){b|=((ulg)NEXTBYTE())<<k;k+=16;}}
+#define NEEDBITS(n) { \
+ while(k < (n)) { \
+ unsigned int ttt; \
+ ttt = (ulg) (0xffff & vget_16bits()); \
+ b |= ttt << k; \
+ k += 16; \
+ } \
+}
+#endif
+#define DUMPBITS(n) {b>>=(n);k-=(n);}
+
+
+/*
+ Huffman code decoding is performed using a multi-level table lookup.
+ The fastest way to decode is to simply build a lookup table whose
+ size is determined by the longest code. However, the time it takes
+ to build this table can also be a factor if the data being decoded
+ is not very long. The most common codes are necessarily the
+ shortest codes, so those codes dominate the decoding time, and hence
+ the speed. The idea is you can have a shorter table that decodes the
+ shorter, more probable codes, and then point to subsidiary tables for
+ the longer codes. The time it costs to decode the longer codes is
+ then traded against the time it takes to make longer tables.
+
+ This results of this trade are in the variables vlbits and vdbits
+ below. vlbits is the number of bits the first level table for literal/
+ length codes can decode in one step, and vdbits is the same thing for
+ the distance codes. Subsequent tables are also less than or equal to
+ those sizes. These values may be adjusted either when all of the
+ codes are shorter than that, in which case the longest code length in
+ bits is used, or when the shortest code is *longer* than the requested
+ table size, in which case the length of the shortest code in bits is
+ used.
+
+ There are two different values for the two tables, since they code a
+ different number of possibilities each. The literal/length table
+ codes 286 possible values, or in a flat code, a little over eight
+ bits. The distance table codes 30 possible values, or a little less
+ than five bits, flat. The optimum values for speed end up being
+ about one bit more than those, so vlbits is 8+1 and vdbits is 5+1.
+ The optimum values may differ though from machine to machine, and
+ possibly even between compilers. Your mileage may vary.
+ */
+
+
+int vlbits = 9; /* bits in base literal/length lookup table */
+int vdbits = 6; /* bits in base distance lookup table */
+
+
+/* If BMAX needs to be larger than 16, then h and x[] should be ulg. */
+#define BMAX 16 /* maximum bit length of any code (16 for explode) */
+#define N_MAX 288 /* maximum number of codes in any set */
+
+
+unsigned hufts; /* track memory usage */
+
+/* Given a list of code lengths and a maximum table size, make a set of
+ tables to decode that set of codes. Return zero on success, one if
+ the given code set is incomplete (the tables are still built in this
+ case), two if the input is invalid (all zero length codes or an
+ oversubscribed set of lengths), and three if not enough memory. */
+/* return nz (2 ?) on error */
+static int huft_build(b, n, s, d, e, t, m)
+unsigned *b; /* code lengths in bits (all assumed <= BMAX) */
+unsigned n; /* number of codes (assumed <= N_MAX) */
+unsigned s; /* number of simple-valued codes (0..s-1) */
+ush *d; /* list of base values for non-simple codes */
+ush *e; /* list of extra bits for non-simple codes */
+struct huft **t; /* result: starting table */
+int *m; /* maximum lookup bits, returns actual */
+{
+ unsigned a; /* counter for codes of length k */
+ unsigned c[BMAX+1]; /* bit length count table */
+ unsigned f; /* i repeats in table every f entries */
+ int g; /* maximum code length */
+ int h; /* table level */
+ register unsigned i; /* counter, current code */
+ register unsigned j; /* counter */
+ register int k; /* number of bits in current code */
+ int l; /* bits per table (returned in m) */
+ register unsigned *p; /* pointer into c[], b[], or v[] */
+ register struct huft *q; /* points to current table */
+ struct huft r; /* table entry for structure assignment */
+ struct huft *u[BMAX]; /* table stack */
+ unsigned v[N_MAX]; /* values in order of bit length */
+ register int w; /* bits before this table == (l * h) */
+ unsigned x[BMAX+1]; /* bit offsets, then code stack */
+ unsigned *xp; /* pointer into x */
+ int y; /* number of dummy codes added */
+ unsigned z; /* number of entries in current table */
+
+
+ for (i = 0; i < BMAX; i++) {
+ u[i] = NULL;
+ x[i] = c[i] = 0;
+ }
+ x[i] = c[i] = 0;
+
+ for (i = 0; i < N_MAX; i++)
+ v[i] = 0;
+
+ /* Generate counts for each bit length */
+ memzero(c, sizeof(c));
+ p = b; i = n;
+ do {
+ c[*p]++; /* assume all entries <= BMAX */
+ p++; /* Can't combine with above line (Solaris bug) */
+ } while (--i);
+ if (c[0] == n) /* null input--all zero length codes */
+ {
+ *t = (struct huft *)NULL;
+ *m = 0;
+ return 0;
+ }
+
+
+ /* Find minimum and maximum length, bound *m by those */
+ l = *m;
+ for (j = 1; j <= BMAX; j++)
+ if (c[j])
+ break;
+ k = j; /* minimum code length */
+ if ((unsigned)l < j)
+ l = j;
+ for (i = BMAX; i; i--)
+ if (c[i])
+ break;
+ g = i; /* maximum code length */
+ if ((unsigned)l > i)
+ l = i;
+ *m = l;
+
+
+ /* Adjust last length count to fill out codes, if needed */
+ for (y = 1 << j; j < i; j++, y <<= 1)
+ if ((y -= c[j]) < 0) {
+ return 2; /* bad input: more codes than bits */
+ }
+ if ((y -= c[i]) < 0) {
+ return 2;
+ }
+ c[i] += y;
+
+
+ /* Generate starting offsets into the value table for each length */
+ x[1] = j = 0;
+ p = c + 1; xp = x + 2;
+ while (--i) { /* note that i == g from above */
+ *xp++ = (j += *p++);
+ }
+
+
+ /* Make a table of values in order of bit lengths */
+ p = b; i = 0;
+ do {
+ if ((j = *p++) != 0)
+ v[x[j]++] = i;
+ } while (++i < n);
+
+
+ /* Generate the Huffman codes and for each, make the table entries */
+ x[0] = i = 0; /* first Huffman code is zero */
+ p = v; /* grab values in bit order */
+ h = -1; /* no tables yet--level -1 */
+ w = -l; /* bits decoded == (l * h) */
+ u[0] = (struct huft *)NULL; /* just to keep compilers happy */
+ q = (struct huft *)NULL; /* ditto */
+ z = 0; /* ditto */
+
+ /* go through the bit lengths (k already is bits in shortest code) */
+ for (; k <= g; k++)
+ {
+ a = c[k];
+ while (a--)
+ {
+ /* here i is the Huffman code of length k bits for value *p */
+ /* make tables up to required level */
+ while (k > w + l)
+ {
+ h++;
+ w += l; /* previous table always l bits */
+
+ /* compute minimum size table less than or equal to l bits */
+ z = (z = g - w) > (unsigned)l ? l : z; /* upper limit on table size */
+ if ((f = 1 << (j = k - w)) > a + 1) /* try a k-w bit table */
+ { /* too few codes for k-w bit table */
+ f -= a + 1; /* deduct codes from patterns left */
+ xp = c + k;
+ while (++j < z) /* try smaller tables up to z bits */
+ {
+ if ((f <<= 1) <= *++xp)
+ break; /* enough codes to use up j bits */
+ f -= *xp; /* else deduct codes from patterns */
+ }
+ }
+ z = 1 << j; /* table entries for j-bit table */
+
+ /* allocate and link in new table */
+ if ((q = (struct huft *)malloc((z + 1)*sizeof(struct huft))) ==
+ (struct huft *)NULL)
+ {
+ if (h)
+ huft_free(u[0]);
+ return 3; /* not enough memory */
+ }
+ hufts += z + 1; /* track memory usage */
+ *t = q + 1; /* link to list for huft_free() */
+ *(t = &(q->v.t)) = (struct huft *)NULL;
+ u[h] = ++q; /* table starts after link */
+
+ /* connect to last table, if there is one */
+ if (h)
+ {
+ x[h] = i; /* save pattern for backing up */
+ r.b = (uch)l; /* bits to dump before this table */
+ r.e = (uch)(16 + j); /* bits in this table */
+ r.v.t = q; /* pointer to this table */
+ j = i >> (w - l); /* (get around Turbo C bug) */
+ u[h-1][j] = r; /* connect to last table */
+ }
+ }
+
+ /* set up table entry in r */
+ r.b = (uch)(k - w);
+ if (p >= v + n)
+ r.e = 99; /* out of values--invalid code */
+ else if (*p < s)
+ {
+ r.e = (uch)(*p < 256 ? 16 : 15); /* 256 is end-of-block code */
+ r.v.n = (ush)(*p); /* simple code is just the value */
+ p++; /* one compiler does not like *p++ */
+ }
+ else
+ {
+ if (e == NULL) {
+ return 2; /* Some sort of error */
+ }
+ r.e = (uch)e[*p - s]; /* non-simple--look up in lists */
+ r.v.n = d[*p++ - s];
+ }
+
+ /* fill code-like entries with r */
+ f = 1 << (k - w);
+ for (j = i >> w; j < z; j += f)
+ q[j] = r;
+
+ /* backwards increment the k-bit code i */
+ for (j = 1 << (k - 1); i & j; j >>= 1)
+ i ^= j;
+ i ^= j;
+
+ /* backup over finished tables */
+ while ((i & ((1 << w) - 1)) != x[h])
+ {
+ h--; /* don't need to update q */
+ w -= l;
+ }
+ }
+ }
+
+ /* Return true (1) if we were given an incomplete table */
+ return y != 0 && g != 1;
+}
+
+
+
+static int huft_free(t)
+struct huft *t; /* table to free */
+/* Free the malloc'ed tables built by huft_build(), which makes a linked
+ list of the tables it made, with the links in a dummy first entry of
+ each table. */
+{
+ register struct huft *p, *q;
+
+
+ /* Go through linked list, freeing from the malloced (t[-1]) address. */
+ p = t;
+ while (p != (struct huft *)NULL)
+ {
+ q = (--p)->v.t;
+ free((char*)p);
+ p = q;
+ }
+ return 0;
+}
+
+
+/* inflate (decompress) the codes in a deflated (compressed) block.
+ Return an error code or zero if it all goes ok. */
+static int vinflate_codes(tl, td, bl, bd)
+struct huft *tl, *td; /* literal/length and distance decoder tables */
+int bl, bd; /* number of bits decoded by tl[] and td[] */
+{
+ register unsigned e; /* table entry flag/number of extra bits */
+ unsigned n, d; /* length and index for copy */
+ unsigned w; /* current window position */
+ struct huft *t; /* pointer to table entry */
+ unsigned ml, md; /* masks for bl and bd bits */
+ register ulg b; /* bit buffer */
+ register unsigned k; /* number of bits in bit buffer */
+
+
+ /* make local copies of globals */
+ b = bb; /* initialize bit buffer */
+ k = bk;
+ w = wp; /* initialize window position */
+
+ /* inflate the coded data */
+ ml = vmask_bits[bl]; /* precompute masks for speed */
+ md = vmask_bits[bd];
+ for (;;) /* do until end of block */
+ {
+ NEEDBITS((unsigned)bl)
+ if (tl == NULL) {
+ DBG(("Huffman table is NULL\n"))
+ return 2;
+ }
+ if ((e = (t = tl + ((unsigned)b & ml))->e) > 16)
+ do {
+ if (e == 99) {
+ DBG(("Huffman table returned 99\n"))
+ return 1;
+ }
+ DUMPBITS(t->b)
+ e -= 16;
+ NEEDBITS(e)
+ } while ((e = (t = t->v.t + ((unsigned)b & vmask_bits[e]))->e) > 16);
+ DUMPBITS(t->b)
+ if (e == 16) /* then it's a literal */
+ {
+ slide[w++] = (uch)t->v.n;
+ if (w == WSIZE)
+ {
+ if (vflush_output(w)) {
+ DBG(("Buffer was unexpectedly large\n"))
+ return 1;
+ }
+ w = 0;
+ }
+ }
+ else /* it's an EOB or a length */
+ {
+ /* exit if end of block */
+ if (e == 15)
+ break;
+
+ /* get length of block to copy */
+ NEEDBITS(e)
+ n = t->v.n + ((unsigned)b & vmask_bits[e]);
+ DUMPBITS(e);
+
+ /* decode distance of block to copy */
+ NEEDBITS((unsigned)bd)
+ if ((e = (t = td + ((unsigned)b & md))->e) > 16)
+ do {
+ if (e == 99) {
+ DBG(("Huffman table returned 99\n"))
+ return 1;
+ }
+ DUMPBITS(t->b)
+ e -= 16;
+ NEEDBITS(e)
+ } while ((e = (t = t->v.t + ((unsigned)b & vmask_bits[e]))->e) > 16);
+ DUMPBITS(t->b)
+ NEEDBITS(e)
+ d = w - t->v.n - ((unsigned)b & vmask_bits[e]);
+ DUMPBITS(e)
+
+ /* do the copy */
+ do {
+ n -= (e = (e = WSIZE - ((d &= WSIZE-1) > w ? d : w)) > n ? n : e);
+#if !defined(NOMEMCPY) && !defined(DEBUG)
+ if (w - d >= e) /* (this test assumes unsigned comparison) */
+ {
+ memmove(slide + w, slide + d, e);
+ w += e;
+ d += e;
+ }
+ else /* do it slow to avoid memcpy() overlap */
+#endif /* !NOMEMCPY */
+ do {
+ slide[w++] = slide[d++];
+ } while (--e);
+ if (w == WSIZE)
+ {
+ if (vflush_output(w)) {
+ DBG(("Buffer was unexpectedly large\n"))
+ return 1;
+ }
+ w = 0;
+ }
+ } while (n);
+ }
+ }
+
+
+ /* restore the globals from the locals */
+ wp = w; /* restore global window pointer */
+ bb = b; /* restore global bit buffer */
+ bk = k;
+
+ /* done */
+ return 0;
+}
+
+
+
+static int vinflate_stored()
+/* "decompress" an inflated type 0 (stored) block. */
+{
+ unsigned n; /* number of bytes in block */
+ unsigned w; /* current window position */
+ register ulg b; /* bit buffer */
+ register unsigned k; /* number of bits in bit buffer */
+
+
+ /* make local copies of globals */
+ b = bb; /* initialize bit buffer */
+ k = bk;
+ w = wp; /* initialize window position */
+
+
+ /* go to 16 byte boundary */
+ n = k & 15;
+ DUMPBITS(n);
+
+
+ /* get the length and its complement */
+ NEEDBITS(16)
+ n = ((unsigned)b & 0xffff);
+ DUMPBITS(16)
+ NEEDBITS(16)
+ if (n != (unsigned)((~b) & 0xffff)) {
+ DBG(("Stored block length comlpement doesn't match\n"))
+ return 1; /* error in compressed data */
+ }
+ DUMPBITS(16)
+
+
+ /* read and output the compressed data */
+ while (n--)
+ {
+ NEEDBITS(8)
+ slide[w++] = (uch)b;
+ if (w == WSIZE)
+ {
+ if (vflush_output(w)) {
+ DBG(("Buffer was unexpectedly large\n"))
+ return 1;
+ }
+ w = 0;
+ }
+ DUMPBITS(8)
+ }
+
+
+ /* restore the globals from the locals */
+ wp = w; /* restore global window pointer */
+ bb = b; /* restore global bit buffer */
+ bk = k;
+ return 0;
+}
+
+
+
+/* decompress an inflated type 1 (fixed Huffman codes) block. We should
+ either replace this with a custom decoder, or at least precompute the
+ Huffman tables. */
+static int vinflate_fixed()
+{
+ int i; /* temporary variable */
+ struct huft *tl; /* literal/length code table */
+ struct huft *td; /* distance code table */
+ int bl; /* lookup bits for tl */
+ int bd; /* lookup bits for td */
+ unsigned l[288]; /* length list for huft_build */
+
+
+ /* set up literal table */
+ for (i = 0; i < 144; i++)
+ l[i] = 8;
+ for (; i < 256; i++)
+ l[i] = 9;
+ for (; i < 280; i++)
+ l[i] = 7;
+ for (; i < 288; i++) /* make a complete, but wrong code set */
+ l[i] = 8;
+ bl = 7;
+ if ((i = huft_build(l, 288, 257, cplens, cplext, &tl, &bl)) != 0)
+ return i;
+
+
+ /* set up distance table */
+ for (i = 0; i < 30; i++) /* make an incomplete code set */
+ l[i] = 5;
+ bd = 5;
+ if ((i = huft_build(l, 30, 0, cpdist, cpdext, &td, &bd)) > 1)
+ {
+ huft_free(tl);
+ return i;
+ }
+
+
+ /* decompress until an end-of-block code */
+ if (vinflate_codes(tl, td, bl, bd))
+ return 1;
+
+
+ /* free the decoding tables, return */
+ huft_free(tl);
+ huft_free(td);
+ return 0;
+}
+
+
+
+/* decompress an inflated type 2 (dynamic Huffman codes) block. */
+static int vinflate_dynamic()
+{
+ int i; /* temporary variables */
+ unsigned j;
+ unsigned l; /* last length */
+ unsigned m; /* mask for bit lengths table */
+ unsigned n; /* number of lengths to get */
+ struct huft *tl; /* literal/length code table */
+ struct huft *td; /* distance code table */
+ int bl; /* lookup bits for tl */
+ int bd; /* lookup bits for td */
+ unsigned nb; /* number of bit length codes */
+ unsigned nl; /* number of literal/length codes */
+ unsigned nd; /* number of distance codes */
+#ifdef PKZIP_BUG_WORKAROUND
+ unsigned ll[288+32]; /* literal/length and distance code lengths */
+#else
+ unsigned ll[286+30]; /* literal/length and distance code lengths */
+#endif
+ register ulg b; /* bit buffer */
+ register unsigned k; /* number of bits in bit buffer */
+
+
+ /* make local bit buffer */
+ b = bb;
+ k = bk;
+
+ /* read in table lengths */
+ NEEDBITS(5)
+ nl = 257 + ((unsigned)b & 0x1f); /* number of literal/length codes */
+ DUMPBITS(5)
+ NEEDBITS(5)
+ nd = 1 + ((unsigned)b & 0x1f); /* number of distance codes */
+ DUMPBITS(5)
+ NEEDBITS(4)
+ nb = 4 + ((unsigned)b & 0xf); /* number of bit length codes */
+ DUMPBITS(4)
+#ifdef PKZIP_BUG_WORKAROUND
+ if (nl > 288 || nd > 32)
+#else
+ if (nl > 286 || nd > 30) {
+#endif
+ DBG(("Bad block type nl = %d, nd = %d\n",nl,nd))
+ return 1; /* bad lengths */
+ }
+
+
+ /* read in bit-length-code lengths */
+ for (j = 0; j < nb; j++)
+ {
+ NEEDBITS(3)
+ ll[border[j]] = (unsigned)b & 7;
+ DUMPBITS(3)
+ }
+ for (; j < 19; j++)
+ ll[border[j]] = 0;
+
+
+ /* build decoding table for trees--single level, 7 bit lookup */
+ bl = 7;
+ if ((i = huft_build(ll, 19, 19, NULL, NULL, &tl, &bl)) != 0)
+ {
+ if (i == 1)
+ huft_free(tl);
+ DBG(("Incomplete code set\n"))
+ return i; /* incomplete code set */
+ }
+
+
+ /* read in literal and distance code lengths */
+ n = nl + nd;
+ m = vmask_bits[bl];
+ i = l = 0;
+ while ((unsigned)i < n)
+ {
+ NEEDBITS((unsigned)bl)
+ if (tl == NULL) {
+ DBG(("Huffman table is NULL\n"))
+ return 2;
+ }
+ j = (td = tl + ((unsigned)b & m))->b;
+ DUMPBITS(j)
+ j = td->v.n;
+ if (j < 16) /* length of code in bits (0..15) */
+ ll[i++] = l = j; /* save last length in l */
+ else if (j == 16) /* repeat last length 3 to 6 times */
+ {
+ NEEDBITS(2)
+ j = 3 + ((unsigned)b & 3);
+ DUMPBITS(2)
+ if ((unsigned)i + j > n) {
+ DBG(("Repeat length %d is bad\n",i))
+ return 1;
+ }
+ while (j--)
+ ll[i++] = l;
+ }
+ else if (j == 17) /* 3 to 10 zero length codes */
+ {
+ NEEDBITS(3)
+ j = 3 + ((unsigned)b & 7);
+ DUMPBITS(3)
+ if ((unsigned)i + j > n) {
+ DBG(("Repeat length %d is bad\n",i))
+ return 1;
+ }
+ while (j--)
+ ll[i++] = 0;
+ l = 0;
+ }
+ else /* j == 18: 11 to 138 zero length codes */
+ {
+ NEEDBITS(7)
+ j = 11 + ((unsigned)b & 0x7f);
+ DUMPBITS(7)
+ if ((unsigned)i + j > n) {
+ DBG(("Repeat length %d is bad\n",i))
+ return 1;
+ }
+ while (j--)
+ ll[i++] = 0;
+ l = 0;
+ }
+ }
+
+
+ /* free decoding table for trees */
+ huft_free(tl);
+
+
+ /* restore the global bit buffer */
+ bb = b;
+ bk = k;
+
+
+ /* build the decoding tables for literal/length and distance codes */
+ bl = vlbits;
+ if ((i = huft_build(ll, nl, 257, cplens, cplext, &tl, &bl)) != 0)
+ {
+ if (i == 1) {
+ huft_free(tl);
+ }
+ DBG(("Incomplete litteral tree\n"))
+ return i; /* incomplete code set */
+ }
+ bd = vdbits;
+ if ((i = huft_build(ll + nl, nd, 0, cpdist, cpdext, &td, &bd)) != 0)
+ {
+ if (i == 1) {
+// fprintf(stderr, " incomplete distance tree\n");
+#ifdef PKZIP_BUG_WORKAROUND
+ i = 0;
+ }
+#else
+ huft_free(td);
+ }
+ huft_free(tl);
+ DBG(("Incomplete code set\n"))
+ return i; /* incomplete code set */
+#endif
+ }
+
+
+ /* decompress until an end-of-block code */
+ if (vinflate_codes(tl, td, bl, bd)) {
+ DBG(("vinflate_codes failed\n"))
+ return 1;
+ }
+
+
+ /* free the decoding tables, return */
+ huft_free(tl);
+ huft_free(td);
+ return 0;
+}
+
+
+
+/* decompress an inflated block */
+static int vinflate_block(e)
+int *e; /* last block flag */
+{
+ unsigned t; /* block type */
+ register ulg b; /* bit buffer */
+ register unsigned k; /* number of bits in bit buffer */
+
+
+ /* make local bit buffer */
+ b = bb;
+ k = bk;
+
+
+ /* read in last block bit */
+ NEEDBITS(1)
+ *e = (int)b & 1;
+ DUMPBITS(1)
+
+
+ /* read in block type */
+ NEEDBITS(2)
+ t = (unsigned)b & 3;
+ DUMPBITS(2)
+
+
+ /* restore the global bit buffer */
+ bb = b;
+ bk = k;
+
+
+ /* inflate that block type */
+ if (t == 2)
+ return vinflate_dynamic();
+ if (t == 0)
+ return vinflate_stored();
+
+#ifdef NEVER
+ /* Apparently VISE doesn't use this */
+ if (t == 1) {
+ printf("WARNING: vinflate fixed found\n");
+ return vinflate_fixed();
+ }
+#endif
+
+ /* bad block type */
+ DBG(("Bad block type %d\n",t))
+ return 2;
+}
+
+/* decompress an inflated entry */
+/* return nz on error */
+int vinflate()
+{
+ int e; /* last block flag */
+ int r; /* result code */
+ unsigned h; /* maximum struct huft's malloc'ed */
+
+ /* initialize window, bit buffer */
+ wp = 0;
+ bk = 0;
+ bb = 0;
+
+ /* decompress until the last block */
+ h = 0;
+ do {
+ hufts = 0;
+ if ((r = vinflate_block(&e)) != 0)
+ return r;
+ if (hufts > h)
+ h = hufts;
+ } while (!e);
+
+ /* Undo too much lookahead. The next read will be byte aligned so we
+ * can discard unused bits in the last meaningful byte.
+ */
+ while (bk >= 16) {
+ bk -= 16;
+ vunget_16bits();
+ }
+
+ /* flush out slide */
+ if (vflush_output(wp)) {
+ DBG(("Buffer was unexpectedly large\n"))
+ return 1;
+ }
+
+ /* return success */
+ return 0;
+}
diff --git a/spectro/webwin.c b/spectro/webwin.c
new file mode 100644
index 0000000..d90239a
--- /dev/null
+++ b/spectro/webwin.c
@@ -0,0 +1,455 @@
+
+
+/*
+ * Argyll Color Correction System
+ * Web Display target patch window
+ *
+ * Author: Graeme W. Gill
+ * Date: 3/4/12
+ *
+ * Copyright 2013 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#ifdef NT
+# include <winsock2.h>
+#endif
+#ifdef UNIX
+# include <sys/types.h>
+# include <ifaddrs.h>
+# include <netinet/in.h>
+# include <arpa/inet.h>
+# ifdef __FreeBSD__
+# include <sys/socket.h>
+# endif /* __FreeBSD__ */
+#endif
+#include "copyright.h"
+#include "aconfig.h"
+#include "icc.h"
+#include "numsup.h"
+#include "cgats.h"
+#include "conv.h"
+#include "dispwin.h"
+#include "conv.h"
+#include "mongoose.h"
+
+#undef DEBUG
+//#define STANDALONE_TEST
+
+#ifdef DEBUG
+#define errout stderr
+# define debug(xx) fprintf(errout, xx )
+# define debug2(xx) fprintf xx
+# define debugr(xx) fprintf(errout, xx )
+# define debugr2(xx) fprintf xx
+# define debugrr(xx) fprintf(errout, xx )
+# define debugrr2(xx) fprintf xx
+# define debugrr2l(lev, xx) fprintf xx
+#else
+#define errout stderr
+# define debug(xx)
+# define debug2(xx)
+# define debugr(xx) if (p->ddebug) fprintf(errout, xx )
+# define debugr2(xx) if (p->ddebug) fprintf xx
+# define debugrr(xx) if (callback_ddebug) fprintf(errout, xx )
+# define debugrr2(xx) if (callback_ddebug) fprintf xx
+# define debugrr2l(lev, xx) if (callback_ddebug >= lev) fprintf xx
+#endif
+
+// A handler for the /ajax/get_messages endpoint.
+// Return a list of messages with ID greater than requested.
+static void ajax_get_messages(struct mg_connection *conn,
+ const struct mg_request_info *request_info) {
+char src_addr[20];
+
+// dispwin *p = (dispwin *)(request_info->user_data);
+ dispwin *p = (dispwin *)mg_get_user_data(conn);
+
+//sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
+
+// printf("ajax_messages query_string '%s'\n",request_info->query_string);
+
+ p->ccix++;
+
+ while(p->ncix == p->ccix && p->mg_stop == 0) {
+ msec_sleep(50);
+ }
+
+ mg_printf(conn,
+ "\r\n#%02X%02X%02X",
+ (int)(p->r_rgb[0] * 255.0 + 0.5),
+ (int)(p->r_rgb[1] * 255.0 + 0.5),
+ (int)(p->r_rgb[2] * 255.0 + 0.5));
+}
+
+/* Event handler */
+static void *webwin_ehandler(enum mg_event event,
+ struct mg_connection *conn) {
+// const struct mg_request_info *request_info) {
+ const struct mg_request_info *request_info = mg_get_request_info(conn);
+
+ if (event != MG_NEW_REQUEST) {
+ return NULL;
+ }
+#ifdef DEBUG
+ printf("Got event with uri = '%s'\n",request_info->uri);
+#endif
+ if (strcmp(request_info->uri, "/ajax/messages") == 0) {
+ ajax_get_messages(conn, request_info);
+ } else if (strcmp(request_info->uri, "/webdisp.js") == 0) {
+#ifndef NEVER
+ char *webdisp_js =
+ "\r\n"
+ "if (typeof XMLHttpRequest == \"undefined\") {\r\n"
+ " XMLHttpRequest = function () {\r\n"
+ " try { return new ActiveXObject(\"Msxml2.XMLHTTP.6.0\"); }\r\n"
+ " catch (e) {}\r\n"
+ " try { return new ActiveXObject(\"Msxml2.XMLHTTP.3.0\"); }\r\n"
+ " catch (e) {}\r\n"
+ " try { return new ActiveXObject(\"Microsoft.XMLHTTP\"); }\r\n"
+ " catch (e) {}\r\n"
+ " throw new Error(\"This browser does not support XMLHttpRequest.\");\r\n"
+ " };\r\n"
+ "}\r\n"
+ "\r\n"
+ "var ccolor = \"\";\r\n"
+ "var oXHR;\r\n"
+ "\r\n"
+ "function XHR_response() {\r\n"
+ " if (oXHR.readyState != 4)\r\n"
+ " return;\r\n"
+ "\r\n"
+ " if (oXHR.status != 200) {\r\n"
+ " return;\r\n"
+ " }\r\n"
+ " var rt = oXHR.responseText;\r\n"
+ " if (rt.charAt(0) == '\\r' && rt.charAt(1) == '\\n')\r\n"
+ " rt = rt.slice(2);\r\n"
+ " if (ccolor != rt) {\r\n"
+ " ccolor = rt;\r\n"
+ " document.body.style.background = ccolor;\r\n"
+ " }\r\n"
+ " oXHR.open(\"GET\", \"/ajax/messages?\" + document.body.style.background + \" \" + Math.random(), true);\r\n"
+ " oXHR.onreadystatechange = XHR_response;\r\n"
+ " oXHR.send();\r\n"
+ "}\r\n"
+ "\r\n"
+ "window.onload = function() {\r\n"
+ " ccolor = \"#808080\";\r\n"
+ " document.body.style.background = ccolor;\r\n"
+ "\r\n"
+ " oXHR = new XMLHttpRequest();\r\n"
+ " oXHR.open(\"GET\", \"/ajax/messages?\" + document.body.style.background, true);\r\n"
+ " oXHR.onreadystatechange = XHR_response;\r\n"
+ " oXHR.send();\r\n"
+ "};\r\n";
+ mg_write(conn, webdisp_js, strlen(webdisp_js));
+#else
+ return NULL; /* Read webdisp.js */
+#endif
+ } else {
+ mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+ "Cache-Control: no-cache\r\n"
+ "Content-Type: text/html\r\n\r\n"
+ "<html>\r\n"
+ "<head>\r\n"
+ "<title>ArgyllCMS Web Display</title>\r\n"
+ "<script src=\"webdisp.js\"></script>\r\n"
+ "</head>\r\n"
+ "</html>\r\n"
+ );
+ }
+
+// "<script type=\"text/javascript\"src=\"webdisp.js\"></script>"
+ return "yes";
+}
+
+/* ----------------------------------------------- */
+
+/* Get RAMDAC values. ->del() when finished. */
+/* Return NULL if not possible */
+static ramdac *webwin_get_ramdac(dispwin *p) {
+ debugr("webdisp doesn't have a RAMDAC\n");
+ return NULL;
+}
+
+/* Set the RAMDAC values. */
+/* Return nz if not possible */
+static int webwin_set_ramdac(dispwin *p, ramdac *r, int persist) {
+ debugr("webdisp doesn't have a RAMDAC\n");
+ return 1;
+}
+
+/* ----------------------------------------------- */
+/* Install a display profile and make */
+/* it the default for this display. */
+/* Return nz if failed */
+int webwin_install_profile(dispwin *p, char *fname, ramdac *r, p_scope scope) {
+ debugr("webdisp doesn't support installing profiles\n");
+ return 1;
+}
+
+/* Un-Install a display profile */
+/* Return nz if failed, */
+int webwin_uninstall_profile(dispwin *p, char *fname, p_scope scope) {
+ debugr("webdisp doesn't support uninstalling profiles\n");
+ return 1;
+}
+
+/* Get the currently installed display profile. */
+/* Return NULL if failed. */
+icmFile *webwin_get_profile(dispwin *p, char *name, int mxlen) {
+ debugr("webdisp doesn't support getting the current profile\n");
+ return NULL;
+}
+
+/* ----------------------------------------------- */
+
+/* Change the window color. */
+/* Return 1 on error, 2 on window being closed */
+static int webwin_set_color(
+dispwin *p,
+double r, double g, double b /* Color values 0.0 - 1.0 */
+) {
+ int j;
+
+ debugr("webwin_set_color called\n");
+
+ if (p->nowin)
+ return 1;
+
+ p->rgb[0] = r;
+ p->rgb[1] = g;
+ p->rgb[2] = b;
+
+ for (j = 0; j < 3; j++) {
+ if (p->rgb[j] < 0.0)
+ p->rgb[j] = 0.0;
+ else if (p->rgb[j] > 1.0)
+ p->rgb[j] = 1.0;
+ p->r_rgb[j] = p->rgb[j];
+ }
+
+ /* This is probably not actually thread safe... */
+ p->ncix++;
+
+ while(p->ncix != p->ccix) {
+ msec_sleep(50);
+ }
+
+ /* Allow some time for the display to update before */
+ /* a measurement can take place. This allows time for */
+ /* the browser to update the background color, the CRT */
+ /* refresh or LCD processing/update time, + */
+ /* display settling time (quite long for smaller LCD changes). */
+ msec_sleep(200);
+
+ return 0;
+}
+
+/* ----------------------------------------------- */
+/* Set an update delay, and return the previous value */
+/* Value can be set to zero, but othewise will be forced */
+/* to be >= min_update_delay */
+static int webwin_set_update_delay(
+dispwin *p,
+int update_delay) {
+ int cval = p->update_delay;
+ p->update_delay = update_delay;
+ if (update_delay != 0 && p->update_delay < p->min_update_delay)
+ p->update_delay = p->min_update_delay;
+ return cval;
+}
+
+/* ----------------------------------------------- */
+/* Set the shell set color callout */
+void webwin_set_callout(
+dispwin *p,
+char *callout
+) {
+ debugr2((errout,"webwin_set_callout called with '%s'\n",callout));
+
+ p->callout = strdup(callout);
+}
+
+/* ----------------------------------------------- */
+/* Destroy ourselves */
+static void webwin_del(
+dispwin *p
+) {
+
+ debugr("webwin_del called\n");
+
+ if (p == NULL)
+ return;
+
+ p->mg_stop = 1;
+ mg_stop((struct mg_context *)p->pcntx);
+
+ if (p->name != NULL)
+ free(p->name);
+ if (p->description != NULL)
+ free(p->description);
+ if (p->callout != NULL)
+ free(p->callout);
+
+ free(p);
+}
+
+/* ----------------------------------------------- */
+
+/* Create a web display test window, default grey */
+dispwin *new_webwin(
+int webdisp, /* Port number */
+double width, double height, /* Width and height in mm */
+double hoff, double voff, /* Offset from center in fraction of screen, range -1.0 .. 1.0 */
+int nowin, /* NZ if no window should be created - RAMDAC access only */
+int blackbg, /* NZ if whole screen should be filled with black */
+int verb, /* NZ for verbose prompts */
+int ddebug /* >0 to print debug statements to stderr */
+) {
+ dispwin *p = NULL;
+ char *cp;
+ struct mg_context *mg;
+ const char *options[3];
+ char port[50];
+
+ debug("new_webwin called\n");
+
+ if ((p = (dispwin *)calloc(sizeof(dispwin), 1)) == NULL) {
+ if (ddebug) fprintf(stderr,"new_webwin failed because malloc failed\n");
+ return NULL;
+ }
+
+ /* !!!! Make changes in dispwin.c as well !!!! */
+ p->name = strdup("Web Window");
+ p->nowin = nowin;
+ p->native = 0;
+ p->blackbg = blackbg;
+ p->ddebug = ddebug;
+ p->get_ramdac = webwin_get_ramdac;
+ p->set_ramdac = webwin_set_ramdac;
+ p->install_profile = webwin_install_profile;
+ p->uninstall_profile = webwin_uninstall_profile;
+ p->get_profile = webwin_get_profile;
+ p->set_color = webwin_set_color;
+ p->set_update_delay = webwin_set_update_delay;
+ p->set_callout = webwin_set_callout;
+ p->del = webwin_del;
+
+ p->rgb[0] = p->rgb[1] = p->rgb[2] = 0.5; /* Set Grey as the initial test color */
+
+ p->min_update_delay = 20;
+
+ if ((cp = getenv("ARGYLL_MIN_DISPLAY_UPDATE_DELAY_MS")) != NULL) {
+ p->min_update_delay = atoi(cp);
+ if (p->min_update_delay < 20)
+ p->min_update_delay = 20;
+ if (p->min_update_delay > 60000)
+ p->min_update_delay = 60000;
+ debugr2((errout, "new_webwin: Minimum display update delay set to %d msec\n",p->min_update_delay));
+ }
+
+ p->update_delay = DISPLAY_UPDATE_DELAY; /* Default update delay */
+ if (p->update_delay < p->min_update_delay)
+ p->update_delay = p->min_update_delay;
+
+ p->ncix = 1;
+
+ /* Basic object is initialised, so create a web server */
+
+ options[0] = "listening_ports";
+ sprintf(port,"%d", webdisp);
+ options[1] = port;
+ options[2] = NULL;
+
+ mg = mg_start(&webwin_ehandler, (void *)p, options);
+ p->pcntx = (void *)mg;
+
+//printf("Domain = %s'\n",mg_get_option(mg, "authentication_domain"));
+
+ /* Create a suitable description */
+#if NT
+ {
+ char szHostName[255];
+ struct hostent *host_entry;
+ char *localIP;
+ char buf[1000];
+
+ /* We assume WinSock has been started by mongoose */
+
+ // Get the local hostname
+ gethostname(szHostName, 255);
+ host_entry=gethostbyname(szHostName);
+ /* Get first entry */
+ localIP = inet_ntoa(*(struct in_addr *)*host_entry->h_addr_list);
+
+ sprintf(buf,"Web Window at http://%s:%d",localIP,webdisp);
+ p->description = strdup(buf);
+
+ if (verb)
+ printf("Created web server at 'http://%s:%d', now waiting for browser to connect\n",localIP,webdisp);
+ }
+#else
+ {
+ struct ifaddrs * ifAddrStruct=NULL;
+ struct ifaddrs * ifa=NULL;
+ void *tmpAddrPtr=NULL;
+ char abuf[INET_ADDRSTRLEN] = "";
+ char abuf6[INET6_ADDRSTRLEN] = "";
+ char *addr = abuf;
+ char buf[1000];
+
+ getifaddrs(&ifAddrStruct);
+
+ /* Stop at the first non local adderss */
+ for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
+#ifdef AF_INET6
+ if (ifa->ifa_addr->sa_family==AF_INET) { /* IP4 ? */
+#endif
+ if (strncmp(ifa->ifa_name, "lo",2) == 0 || abuf[0] != '\000')
+ continue;
+ tmpAddrPtr=&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
+ inet_ntop(AF_INET, tmpAddrPtr, abuf, INET_ADDRSTRLEN);
+// printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer);
+#ifdef AF_INET6
+ } else if (ifa->ifa_addr->sa_family==AF_INET6) { /* IP6 ? */
+ if (strncmp(ifa->ifa_name, "lo",2) == 0 || abuf6[0] != '\000')
+ continue;
+ tmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
+ inet_ntop(AF_INET6, tmpAddrPtr, abuf6, INET6_ADDRSTRLEN);
+// printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer);
+ }
+#endif
+ }
+ if (ifAddrStruct!=NULL)
+ freeifaddrs(ifAddrStruct);
+ if (addr[0] == '\000')
+ addr = abuf6;
+ if (addr[0] == '\000')
+ addr = "Unknown";
+
+ sprintf(buf,"Web Window at http://%s:%d",addr,webdisp);
+ p->description = strdup(buf);
+
+ if (verb)
+ printf("Created web server at 'http://%s:%d', now waiting for browser to connect\n",addr,webdisp);
+ }
+#endif
+
+ /* Wait for the web server to connect */
+ debugr("new_webwin: waiting for web browser to connect\n");
+ while(p->ccix == 0) {
+ msec_sleep(50);
+ }
+
+ debugr("new_webwin: return sucessfully\n");
+
+ return p;
+}
+
diff --git a/spectro/webwin.h b/spectro/webwin.h
new file mode 100644
index 0000000..b298397
--- /dev/null
+++ b/spectro/webwin.h
@@ -0,0 +1,31 @@
+
+#ifndef WEBWIN_H
+
+/*
+ * Argyll Color Correction System
+ * Web Display target patch window
+ *
+ * Author: Graeme W. Gill
+ * Date: 3/4/12
+ *
+ * Copyright 2013 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+
+/* Create a web display test window, default grey */
+dispwin *new_webwin(
+int webdisp, /* Port number */
+double width, double height, /* Width and height in mm */
+double hoff, double voff, /* Offset from center in fraction of screen, range -1.0 .. 1.0 */
+int nowin, /* NZ if no window should be created - RAMDAC access only */
+int blackbg, /* NZ if whole screen should be filled with black */
+int verb, /* NZ for verbose prompts */
+int ddebug /* >0 to print debug statements to stderr */
+);
+
+#define WEBWIN_H
+#endif /* WEBWIN_H */
diff --git a/spectro/xdg_bds.c b/spectro/xdg_bds.c
new file mode 100644
index 0000000..9d81291
--- /dev/null
+++ b/spectro/xdg_bds.c
@@ -0,0 +1,1088 @@
+
+ /* XDG Base Directory Specifications support library. */
+ /* Implements equivalent cross platform functionality too. */
+
+/*************************************************************************
+ Copyright 2011, 2013 Graeme W. Gill
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+
+ *************************************************************************/
+
+/*
+ This function provides support for the XDG Base Directory Specifications
+ in a cross platform compatible way.
+
+ [ Note that for MSWin each path in a set is separated by a ';' character.
+ and that DATA and CONF will be in the same directory. ]
+
+ The following paths are used for each of the 5 XDG concepts, listed in order
+ of priority:
+
+ Per user application related data.
+
+ Per user application configuration settings.
+
+ Per user application cache storage area.
+
+ Local system wide application related data.
+
+ Local system wide application configuration settings.
+
+ Unix:
+ $XDG_DATA_HOME
+ $HOME/.local/share
+
+ $XDG_CONF_HOME
+ $HOME/.config
+
+ $XDG_CACHE_HOME
+ $HOME/.cache
+
+ $XDG_DATA_DIRS
+ /usr/local/share:/usr/share
+
+ $XDG_CONF_DIRS
+ /etc/xdg
+
+ OS X:
+ $XDG_DATA_HOME
+ $HOME/Library/Application Support
+
+ $XDG_CONF_HOME
+ $HOME/Library/Preferences
+
+ $XDG_CACHE_HOME
+ $HOME/Library/Caches
+
+ $XDG_DATA_DIRS
+ /Library/Application Support
+
+ $XDG_CONF_DIRS
+ /Library/Preferences
+
+ MSWin:
+ $XDG_DATA_HOME
+ $APPDATA
+ $HOME/.local/share
+
+ $XDG_CONF_HOME
+ $APPDATA
+ $HOME/.config
+
+ $XDG_CACHE_HOME
+ $APPDATA/Cache
+ $HOME/.cache
+
+ $XDG_DATA_DIRS
+ $ALLUSERSPROFILE
+
+ $XDG_CONF_DIRS
+ $ALLUSERSPROFILE
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <math.h>
+#include <time.h>
+#include <signal.h>
+#ifndef NT
+# include <unistd.h>
+# include <glob.h>
+#else
+# include <io.h>
+# include <direct.h>
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "numsup.h"
+#include "conv.h"
+#include "aglob.h"
+#include "xdg_bds.h"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBGA g_log, 0
+#define DBG(xxx) a1logd xxx ;
+#else
+#define DBG(xxx)
+#endif /* DEBUG */
+
+#ifdef NT
+# define stat _stat
+# define mode_t int
+# define mkdir(A,B) _mkdir(A)
+# define mputenv _putenv
+# define unlink _unlink
+# define rmdir _rmdir
+#else
+/* UNIX putenv is a pain.. */
+static void mputenv(char *ss) {
+ int ll = strlen(ss);
+ ss = strdup(ss);
+ if (ll > 0 && ss[ll-1]== '=') {
+ ss[ll-1] = '\000';
+ unsetenv(ss);
+ } else {
+ putenv(ss);
+ }
+}
+#endif
+
+/* Allocate a copy of the string, and normalize the */
+/* path separator to '/' */
+
+/* Append a string. Free in. Return NULL on error. */
+static char *append(char *in, char *app) {
+ char *rv;
+
+ if ((rv = malloc(strlen(in) + strlen(app) + 1)) == NULL) {
+ a1loge(g_log, 1, "xdg_bds: append malloc failed\n");
+ free(in);
+ return NULL;
+ }
+ strcpy(rv, in);
+ strcat(rv, app);
+ free(in);
+
+ return rv;
+}
+
+/* Append a ':' or ';' then a string. Free in. Return NULL on error. */
+static char *cappend(char *in, char *app) {
+ int inlen;
+ char *rv;
+
+ inlen = strlen(in);
+
+ if ((rv = malloc(inlen + 1 + strlen(app) + 1)) == NULL) {
+ a1loge(g_log, 1, "xdg_bds: cappend malloc failed\n");
+ free(in);
+ return NULL;
+ }
+ strcpy(rv, in);
+ if (inlen > 1)
+ strcat(rv, SSEPS);
+ strcat(rv, app);
+ free(in);
+
+ return rv;
+}
+
+/* Append a '/' then a string. Free in. Return NULL on error. */
+static char *dappend(char *in, char *app) {
+ int inlen;
+ char *rv;
+
+ inlen = strlen(in);
+
+ if ((rv = malloc(inlen + 1 + strlen(app) + 1)) == NULL) {
+ a1loge(g_log, 1, "xdg_bds: dappend malloc failed\n");
+ free(in);
+ return NULL;
+ }
+ strcpy(rv, in);
+ if (inlen > 1 && in[inlen-1] != '/')
+ strcat(rv, "/");
+ strcat(rv, app);
+ free(in);
+
+ return rv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Do internal cleanup */
+static void xdg_ifree(char ***paths, char **fnames, int nopaths) {
+ int i;
+
+ if (paths != NULL) {
+ if (*paths != NULL) {
+ for (i = 0; i < nopaths; i++) {
+ if ((*paths)[i] != NULL)
+ free ((*paths)[i]);
+ }
+ }
+ free(*paths);
+ *paths = NULL;
+ }
+ if (fnames != NULL) {
+ for (i = 0; i < nopaths; i++) {
+ if (fnames[i] != NULL)
+ free (fnames[i]);
+ }
+ free(fnames);
+ }
+}
+
+/* Free a return value */
+void xdg_free(char **paths, int nopaths) {
+ int i;
+
+ if (paths != NULL) {
+ for (i = 0; i < nopaths; i++) {
+ if (paths[i] != NULL)
+ free (paths[i]);
+ }
+ free(paths);
+ }
+}
+
+/* Return the number of matching full paths to the given subpath for the */
+/* type of storage and access required. Return 0 if there is an error. */
+/* The files are always unique (ie. the first match to a given filename */
+/* in the possible XDG list of directories is returned, and files with */
+/* the same name in other XDG directories are ignored) */
+/* Wildcards should not be used for xdg_write. */
+/* The list should be free'd using xdg_free() after use. */
+/* XDG environment variables and the subpath are assumed to be using */
+/* the '/' path separator. Multiple read paths are separated by SSEP */
+/* When "xdg_write", the necessary path to the file will be created. */
+/* If we're running as sudo and are creating a user dir/file, */
+/* we drop to using the underlying SUDO_UID/GID. If we are creating a */
+/* local system dir/file as sudo and have dropped to the SUDO_UID/GID, */
+/* then revert back to root uid/gid. */
+int xdg_bds(
+ xdg_error *er, /* Return an error code */
+ char ***paths, /* Retun array pointers to paths */
+ xdg_storage_type st, /* Specify the storage type */
+ xdg_op_type op, /* Operation type */
+ xdg_scope sc, /* Scope if write */
+ char *pfname /* Sub-path and file name(s) */
+) {
+ char *path = NULL; /* Directory paths to search, separated by ':' or ';' */
+ char **fnames = NULL; /* Filename component of each path being returned */
+ int npaths = 0; /* Number of paths being returned */
+ int napaths = 0; /* Number of paths allocated */
+
+ DBG((DBGA,"xdg_bds called with st %s, op %s, sc %s, pfnames '%s'\n",
+ st == xdg_data ? "data" : st == xdg_conf ? "config" : st == xdg_cache ? "cache" : "unknown",
+ op == xdg_write ? "write" : op == xdg_read ? "read" : "unknown",
+ sc == xdg_user ? "user" : sc == xdg_local ? "local" : "unknown",
+ pfname))
+
+ *paths = NULL;
+
+ /* Initial, empty path */
+ if ((path = strdup("")) == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ xdg_ifree(paths, fnames, npaths);
+ return 0;
+ }
+
+ /* Create a set of ':'/';' separated search paths */
+
+ /* User scope */
+ if (op == xdg_read || sc == xdg_user) {
+ if (st == xdg_data) {
+ char *xdg, *home;
+ if ((xdg = getenv("XDG_DATA_HOME")) != NULL) {
+ if ((path = cappend(path, xdg)) == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ return 0;
+ }
+#ifdef NT
+ } else if (getenv("HOME") == NULL && (xdg = getenv("APPDATA")) != NULL) {
+ if ((path = cappend(path, xdg)) == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ return 0;
+ }
+#endif
+ } else {
+ if ((home = getenv("HOME")) == NULL
+#ifdef NT
+ && (home = getenv("APPDATA")) == NULL
+#endif
+ ) {
+ if (er != NULL) *er = xdg_nohome;
+ free(path);
+ DBG((DBGA,"no $HOME\n"))
+ return 0;
+ }
+ if ((path = cappend(path, home)) == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ return 0;
+ }
+#ifdef NT
+ if (getenv("HOME") != NULL)
+ path = dappend(path, ".local/share");
+#else
+#ifdef __APPLE__
+ path = dappend(path, "Library/Application Support");
+#else /* Unix, Default */
+ path = dappend(path, ".local/share");
+#endif
+#endif
+ if (path == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ return 0;
+ }
+ }
+ } else if (st == xdg_conf) {
+ char *xdg, *home;
+ if ((xdg = getenv("XDG_CONF_HOME")) != NULL) {
+ if ((path = cappend(path, xdg)) == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ return 0;
+ }
+#ifdef NT
+ } else if (getenv("HOME") == NULL && (xdg = getenv("APPDATA")) != NULL) {
+ if ((path = cappend(path, xdg)) == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ return 0;
+ }
+#endif
+ } else {
+ if ((home = getenv("HOME")) == NULL
+#ifdef NT
+ && (home = getenv("APPDATA")) == NULL
+#endif
+ ) {
+ if (er != NULL) *er = xdg_nohome;
+ free(path);
+ DBG((DBGA,"no $HOME\n"))
+ return 0;
+ }
+ if ((path = cappend(path, home)) == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ return 0;
+ }
+#ifdef NT
+ if (getenv("HOME") != NULL)
+ path = dappend(path, ".config");
+#else
+#ifdef __APPLE__
+ path = dappend(path, "Library/Preferences");
+#else /* Unix, Default */
+ path = dappend(path, ".config");
+#endif
+#endif
+ if (path == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ return 0;
+ }
+ }
+ } else if (st == xdg_cache) {
+ char *xdg, *home;
+ if ((xdg = getenv("XDG_CACHE_HOME")) != NULL) {
+ if ((path = cappend(path, xdg)) == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ return 0;
+ }
+#ifdef NT
+ } else if (getenv("HOME") == NULL && (xdg = getenv("APPDATA")) != NULL) {
+ if ((path = cappend(path, xdg)) == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ return 0;
+ }
+ if ((path = dappend(path, "Cache")) == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ return 0;
+ }
+#endif
+ } else {
+ if ((home = getenv("HOME")) == NULL
+#ifdef NT
+ && (home = getenv("APPDATA")) == NULL
+#endif
+ ) {
+ if (er != NULL) *er = xdg_nohome;
+ free(path);
+ DBG((DBGA,"no $HOME\n"))
+ return 0;
+ }
+ if ((path = cappend(path, home)) == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ return 0;
+ }
+#ifdef NT
+ if (getenv("HOME") != NULL)
+ path = dappend(path, ".cache");
+ else
+ path = dappend(path, "Cache");
+#else
+#ifdef __APPLE__
+ path = dappend(path, "Library/Caches");
+#else /* Unix, Default */
+ path = dappend(path, ".cache");
+#endif
+#endif
+ if (path == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ return 0;
+ }
+ }
+ }
+ }
+ /* Local system scope */
+ if (op == xdg_read || sc == xdg_local) {
+ char *xdg;
+ if (st == xdg_data) {
+ if ((xdg = getenv("XDG_DATA_DIRS")) != NULL) {
+ if ((path = cappend(path, xdg)) == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ return 0;
+ }
+ } else {
+#ifdef NT
+ /*
+ QT uses $COMMON_APPDATA expected to be
+ C:\Documents and Settings\All Users\Application Data\
+ while others use $CommonAppData.
+ Both seem poorly supported,
+ */
+ char *home;
+ if ((home = getenv("ALLUSERSPROFILE")) == NULL
+ ) {
+ if (er != NULL) *er = xdg_noalluserprofile;
+ free(path);
+ DBG((DBGA,"no $ALLUSERSPROFILE\n"))
+ return 0;
+ }
+ path = cappend(path, home);
+#else
+#ifdef __APPLE__
+ path = cappend(path, "/Library");
+#else
+ path = cappend(path, "/usr/local/share:/usr/share");
+#endif
+#endif
+ if (path == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ return 0;
+ }
+ }
+ } else if (st == xdg_conf) {
+ if ((xdg = getenv("XDG_CONF_DIRS")) != NULL) {
+ if ((path = cappend(path, xdg)) == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ return 0;
+ }
+ } else {
+#ifdef NT
+ char *home;
+ if ((home = getenv("ALLUSERSPROFILE")) == NULL
+ ) {
+ if (er != NULL) *er = xdg_noalluserprofile;
+ free(path);
+ DBG((DBGA,"no $ALLUSERSPROFILE\n"))
+ return 0;
+ }
+ path = cappend(path, home);
+#else
+#ifdef __APPLE__
+ path = cappend(path, "/Library/Preferences");
+#else
+ path = cappend(path, "/etc/xdg");
+#endif
+#endif
+ if (path == NULL) {
+ if (er != NULL) *er = xdg_alloc;
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ return 0;
+ }
+ }
+ }
+ }
+
+#ifdef NT
+ /* Replace all backslashes with forward slashes */
+ {
+ char *cp;
+ for (cp = path; *cp != '\000'; cp++) {
+ if (*cp == '\\')
+ *cp = '/';
+ }
+ }
+#endif
+
+ DBG((DBGA,"Paths to search '%s'\n",path));
+
+ /* Hmm. */
+ if (strlen(path) == 0) {
+ free(path);
+ if (er != NULL) *er = xdg_nopath;
+ *paths = NULL;
+ return 0;
+ }
+
+ {
+ char *spath = NULL; /* sub path out of paths */
+ char *cp, *ep;
+
+ /* For each search path */
+ for (cp = path; *cp != '\000';) {
+ char *sname = NULL; /* sub name out of pfnames */
+ char *ncp, *nep;
+
+ /* Copy search path */
+ if ((ep = strchr(cp, SSEP)) == NULL)
+ ep = cp + strlen(cp);
+ if ((ep - cp) == 0) {
+ free(path);
+ if (er != NULL) *er = xdg_mallformed;
+ xdg_ifree(paths, fnames, npaths);
+ return 0;
+ }
+ if ((spath = (char *)malloc(ep - cp + 1)) == NULL) {
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ free(path);
+ if (er != NULL) *er = xdg_alloc;
+ xdg_ifree(paths, fnames, npaths);
+ return 0;
+ }
+ memmove(spath, cp, ep - cp);
+ spath[ep - cp] = '\000';
+
+ /* For each filename part */
+ for (ncp = pfname; *ncp != '\000';) {
+ int rlen = 0; /* Number of chars of search path up to subpath & filename */
+ char *pp;
+ char *schpath; /* Path to search */
+
+ /* Copy filename path */
+ if ((nep = strchr(ncp, SSEP)) == NULL)
+ nep = ncp + strlen(ncp);
+ if ((nep - ncp) == 0) {
+ free(spath);
+ free(path);
+ if (er != NULL) *er = xdg_mallformed;
+ xdg_ifree(paths, fnames, npaths);
+ return 0;
+ }
+ if ((sname = (char *)malloc(nep - ncp + 1)) == NULL) {
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ free(spath);
+ free(path);
+ if (er != NULL) *er = xdg_alloc;
+ xdg_ifree(paths, fnames, npaths);
+ return 0;
+ }
+ memmove(sname, ncp, nep - ncp);
+ sname[nep - ncp] = '\000';
+
+ /* append subpath & subname */
+ if ((schpath = strdup(spath)) == NULL
+ || (schpath = dappend(schpath, sname)) == NULL) {
+ free(sname);
+ free(spath);
+ free(path);
+ if (er != NULL) *er = xdg_alloc;
+ xdg_ifree(paths, fnames, npaths);
+ return 0;
+ }
+ DBG((DBGA,"Full path to check '%s'\n",schpath));
+
+ /* Figure out where the filename starts */
+ if ((pp = strrchr(schpath, '/')) == NULL)
+ rlen = 0;
+ else
+ rlen = pp - schpath + 1;
+
+ if (op == xdg_read) {
+ char *fpath; /* Full path of matched */
+ aglob gg; /* Glob structure */
+
+ /* Setup the file glob */
+ if (aglob_create(&gg, schpath)) {
+ free(schpath);
+ free(sname);
+ free(spath);
+ free(path);
+ if (er != NULL) *er = xdg_alloc;
+ xdg_ifree(paths, fnames, npaths);
+ return 0;
+ }
+
+ /* While we have matching filenames */
+ DBG((DBGA,"Getting glob results for '%s'\n",schpath))
+ free(schpath);
+ for (;;) {
+ int i;
+
+ if ((fpath = aglob_next(&gg)) == NULL) {
+ if (gg.merr) { /* Malloc error */
+ free(sname);
+ free(spath);
+ free(path);
+ aglob_cleanup(&gg);
+ if (er != NULL) *er = xdg_alloc;
+ xdg_ifree(paths, fnames, npaths);
+ return 0;
+ }
+ break; /* No more matches */
+ }
+ DBG((DBGA,"Found match with '%s'\n",fpath))
+
+ /* Check that this one hasn't already been found */
+ /* in a different search directory */
+ for (i = 0; i < npaths; i++) {
+ if (strcmp(fpath + rlen, fnames[i]) == 0) {
+ /* Already been found earlier - ignore it */
+ break;
+ }
+ }
+ if (i < npaths) {
+ free(fpath);
+ DBG((DBGA,"Ignoring it because it's already in list\n"))
+ continue; /* Ignore it */
+ }
+
+ /* Found a file, so append it to the list */
+ if (npaths >= napaths) { /* Need more space in arrays */
+ napaths = napaths * 2 + 1;
+ if ((*paths = realloc(*paths, sizeof(char *) * napaths)) == NULL
+ || (fnames = realloc(fnames, sizeof(char *) * napaths)) == NULL) {
+ a1loge(g_log, 1, "xdg_bds: realloc failed\n");
+ free(fpath);
+ free(sname);
+ free(spath);
+ free(path);
+ aglob_cleanup(&gg);
+ if (er != NULL) *er = xdg_alloc;
+ xdg_ifree(paths, fnames, npaths);
+ return 0;
+ }
+ }
+ if (((*paths)[npaths] = strdup(fpath)) == NULL) {
+ a1loge(g_log, 1, "xdg_bds: strdup failed\n");
+ free(fpath);
+ free(sname);
+ free(spath);
+ free(path);
+ aglob_cleanup(&gg);
+ if (er != NULL) *er = xdg_alloc;
+ xdg_ifree(paths, fnames, npaths);
+ return 0;
+ }
+ /* The non-searchpath part of the name found */
+ if ((fnames[npaths] = strdup(fpath + rlen)) == NULL) {
+ a1loge(g_log, 1, "xdg_bds: strdup failed\n");
+ free((*paths)[npaths]);
+ free(fpath);
+ free(sname);
+ free(spath);
+ free(path);
+ aglob_cleanup(&gg);
+ if (er != NULL) *er = xdg_alloc;
+ xdg_ifree(paths, fnames, npaths);
+ return 0;
+ }
+ free(fpath);
+ fpath = NULL;
+ npaths++;
+ }
+ aglob_cleanup(&gg);
+
+ /* Fall through to next search path */
+
+ } else { /* op == xdg_write */
+ char *pp = schpath;
+ struct stat sbuf;
+ mode_t mode = 0700; /* Default directory mode */
+
+ if (sc == xdg_user)
+ mode = 0700; /* Default directory mode for user */
+ else
+ mode = 0755; /* Default directory mode local system shared */
+#ifndef NT
+ /* If we're creating a user dir/file and running as root sudo */
+ if (sc == xdg_user && geteuid() == 0) {
+ char *uids, *gids;
+ int uid, gid;
+ DBG((DBGA,"We're setting a user dir/file running as root\n"))
+
+ if ((uids = getenv("SUDO_UID")) != NULL
+ && (gids = getenv("SUDO_GID")) != NULL) {
+ uid = atoi(uids);
+ gid = atoi(gids);
+ if (setegid(gid) || seteuid(uid)) {
+ DBG((DBGA,"seteuid or setegid failed\n"))
+ } else {
+ DBG((DBGA,"Set euid %d and egid %d\n",uid,gid))
+ }
+ }
+ /* If setting local system dir/file and not effective root, but sudo */
+ } else if (sc == xdg_local && getuid() == 0 && geteuid() != 0) {
+ if (getenv("SUDO_UID") != NULL
+ && getenv("SUDO_GID") != NULL) {
+ DBG((DBGA,"We're setting a local system dir/file with uid = 0 && euid != 0\n"))
+ setegid(getgid());
+ seteuid(getuid());
+ DBG((DBGA,"Set euid %d, egid %d\n",geteuid(),getegid()))
+ }
+ }
+#endif /* !NT */
+
+#ifdef NT
+ if (*pp != '\000' /* Skip drive number */
+ && ((*pp >= 'a' && *pp <= 'z') || (*pp >= 'A' && *pp <= 'Z'))
+ && pp[1] == ':')
+ pp += 2;
+#endif
+ if (*pp == '/')
+ pp++; /* Skip root directory */
+
+ /* Check each directory in hierarchy, and */
+ /* create it if it doesn't exist. */
+ DBG((DBGA,"About to check & create whole path '%s'\n",schpath))
+ for (;pp != NULL && *pp != '\000';) {
+ if ((pp = strchr(pp, '/')) != NULL) {
+ *pp = '\000';
+ DBG((DBGA,"Checking path '%s'\n",schpath))
+ if (stat(schpath,&sbuf) != 0) {
+ /* Doesn't exist */
+ DBG((DBGA,"Path '%s' doesn't exist - creating it\n",schpath))
+ if (mkdir(schpath, mode) != 0) {
+ DBG((DBGA,"mkdir failed - giving up on this one\n"))
+ break;
+ }
+ } else {
+ mode = sbuf.st_mode;
+ }
+ *pp = '/';
+ pp++;
+ }
+ }
+
+ /* If we got to the end of the hierarchy, */
+ /* then the path looks good to write to, */
+ /* so create a list of one and we're done */
+ if (pp == NULL || *pp == '\000') {
+
+ if ((*paths = malloc(sizeof(char *))) == NULL) {
+ a1loge(g_log, 1, "xdg_bds: malloc failed\n");
+ free(schpath);
+ free(sname);
+ free(spath);
+ free(path);
+ if (er != NULL) *er = xdg_alloc;
+ return 0;
+ }
+ if (((*paths)[npaths] = schpath) == NULL) {
+ free(sname);
+ free(spath);
+ free(path);
+ if (er != NULL) *er = xdg_alloc;
+ free(*paths);
+ return 0;
+ }
+ npaths++;
+ DBG((DBGA,"Returning 0: '%s'\n",(*paths)[0]))
+ free(sname);
+ free(spath);
+ free(path);
+ return npaths;
+ }
+ }
+
+ /* Move on to the next name part */
+ free(sname); sname = NULL;
+ if (*nep == SSEP)
+ ncp = nep+1;
+ else
+ ncp = nep;
+ }
+
+ /* Move on to the next search path */
+ free(spath); spath = NULL;
+ if (*ep == SSEP)
+ cp = ep+1;
+ else
+ cp = ep;
+ }
+ }
+
+ /* We're done looking through search paths */
+ free(path);
+
+ if (npaths == 0) { /* Didn't find anything */
+ if (er != NULL) *er = xdg_nopath;
+ xdg_ifree(paths, fnames, npaths);
+ } else {
+ xdg_ifree(NULL, fnames, npaths);
+#ifdef DEBUG
+ {
+ int i;
+ a1logd(DBGA,"Returning list\n");
+ for (i = 0; i < npaths; i++)
+ a1logd(DBGA," %d: '%s'\n",i,(*paths)[i]);
+ }
+#endif
+ }
+ return npaths;
+}
+
+/* Return a string corresponding to the error value */
+char *xdg_errstr(xdg_error er) {
+ switch (er) {
+ case xdg_ok:
+ return "OK";
+ case xdg_alloc:
+ return "memory allocation failed";
+ case xdg_nohome:
+ return "There is no $HOME";
+ case xdg_noalluserprofile:
+ return "Theres no $ALLUSERSPROFILE is no $ALLUSERSPROFILE";
+ case xdg_nopath:
+ return "There is no resulting path";
+ case xdg_mallformed:
+ return "Malfomed path fount";
+ default:
+ return "unknown";
+ }
+}
+
+
+/* ---------------------------------------------------------------- */
+#ifdef STANDALONE_TEST
+/* test code */
+
+/* Return nz on error */
+static int touch(char *name) {
+ FILE *fp;
+
+ if ((fp = fopen(name,"w")) == NULL)
+ return 1;
+
+ if (fclose(fp))
+ return 1;
+
+ return 0;
+}
+
+/* Check a file can be opened */
+/* Return nz on error */
+static int check(char *name) {
+ FILE *fp;
+
+ if ((fp = fopen(name,"r")) == NULL)
+ return 1;
+
+ if (fclose(fp))
+ return 1;
+
+ return 0;
+}
+
+/* Delete a path and a file */
+/* Return nz on error */
+static int delpath(char *path, int depth) {
+ int i;
+ char *pp;
+
+ for (i = 0; i < depth;) {
+// a1logd(DBGA,"deleting '%s'\n",path);
+ if (i == 0) {
+ if (unlink(path)) {
+// a1logd(DBGA,"unlink '%s' failed\n",path);
+ return 1;
+ }
+ } else {
+ if (rmdir(path)) {
+// a1logd(DBGA,"rmdir '%s' failed\n",path);
+ return 1;
+ }
+ }
+ i++;
+ if (i == depth)
+ return 0;
+
+ if ((pp = strrchr(path, '/')) == NULL)
+ return 0;
+ *pp = '\000';
+ }
+ return 0;
+}
+
+/* Run a test */
+static int runtest(
+ xdg_storage_type st, /* Specify the storage type */
+ xdg_scope sc, /* Scope if write */
+ char *pfname, /* Sub-path and file name */
+ char *env, /* Environment variable being set */
+ char *envv, /* Value to set it to */
+ char *defv, /* default variable needed for read */
+ int depth /* Cleanup depth */
+) {
+ xdg_error er;
+ char *xval;
+ char **paths;
+ int nopaths;
+ char buf[200];
+
+ if ((xval = getenv(env)) != NULL) /* Save value before mods */
+ xval = strdup(xval);
+ if (*env != '\000') { /* If it is to be set */
+ sprintf(buf, "%s=%s",env,envv);
+ mputenv(buf);
+ }
+
+ printf("\nTesting Variable %s\n",env);
+ if ((nopaths = xdg_bds(&er, &paths, st, xdg_write, sc, pfname)) == 0) {
+ printf("Write test failed with %s\n",xdg_errstr(er));
+ return 1;
+ }
+ printf("Create %s %s returned '%s'\n",
+ st == xdg_data ? "Data" : st == xdg_data ? "Conf" : "Cache",
+ sc == xdg_data ? "User" : "Local", paths[0]);
+ if (touch(paths[0])) {
+ printf("Creating file %s failed\n",paths[0]);
+ return 1;
+ }
+ if (check(paths[0])) {
+ printf("Checking file %s failed\n",paths[0]);
+ return 1;
+ }
+
+ if (sc == xdg_local && *env != '\000') { /* Add another path */
+ sprintf(buf, "%s=xdgtestXXX%c%s",env,SSEP,envv);
+ mputenv(buf);
+ }
+
+ if (defv != NULL) {
+ sprintf(buf, "%s=xdg_NOT_%s",defv,defv);
+ mputenv(buf);
+ }
+ xdg_free(paths, nopaths);
+
+ if ((nopaths = xdg_bds(&er, &paths, st, xdg_read, sc, pfname)) < 1) {
+ printf("Read test failed with %s\n",xdg_errstr(er));
+ return 1;
+ }
+ printf(" Read %s %s returned '%s'\n",
+ st == xdg_data ? "Data" : st == xdg_data ? "Conf" : "Cache",
+ sc == xdg_data ? "User" : "Local",paths[0]);
+ if (check(paths[0])) {
+ printf("Checking file %s failed\n",paths[0]);
+ return 1;
+ }
+ if (delpath(paths[0], depth)) {
+ printf("Warning: Deleting file %s failed\n",paths[0]);
+ }
+ xdg_free(paths, nopaths);
+
+ /* Restore variables value */
+ if (xval == NULL)
+ sprintf(buf, "%s=",env);
+ else
+ sprintf(buf, "%s=%s",env,xval);
+ mputenv(buf);
+
+ if (defv != NULL) {
+ sprintf(buf, "%s=",defv);
+ mputenv(buf);
+ }
+
+ return 0;
+}
+
+typedef struct {
+ xdg_storage_type st; /* Storage type */
+ xdg_scope sc; /* Scope if write */
+ char *defv[2]; /* Default variables needed for user & local tests on read */
+ char *envn[10]; /* Environment variable name to set */
+} testcase;
+
+int
+main() {
+ char buf1[200], buf2[200];
+ int i, j;
+
+#ifdef NT
+ testcase cases[5] = {
+ { xdg_data, xdg_user, {"ALLUSERSPROFILE", NULL},
+ { "XDG_DATA_HOME", "APPDATA", "HOME", "APPDATA", NULL } },
+ { xdg_conf, xdg_user, {"ALLUSERSPROFILE", NULL},
+ { "XDG_CONF_HOME", "APPDATA", "HOME", "APPDATA", NULL } },
+ { xdg_cache, xdg_user, {NULL, NULL},
+ { "XDG_CACHE_HOME", "APPDATA", "HOME", "APPDATA", NULL } },
+ { xdg_data, xdg_local, {NULL, "HOME"},
+ { "XDG_DATA_DIRS", "ALLUSERSPROFILE", NULL } },
+ { xdg_conf, xdg_local, {NULL, "HOME"},
+ { "XDG_CONF_DIRS", "ALLUSERSPROFILE", NULL } }
+ };
+#else /* Apple, Unix, Default */
+ testcase cases[5] = {
+ { xdg_data, xdg_user, {NULL, NULL},
+ { "XDG_DATA_HOME", "HOME", NULL } },
+ { xdg_conf, xdg_user, {NULL, NULL},
+ { "XDG_CONF_HOME", "HOME", NULL } },
+ { xdg_cache, xdg_user, {NULL, NULL},
+ { "XDG_CACHE_HOME", "HOME", NULL } },
+ { xdg_data, xdg_local, {NULL, "HOME"},
+ { "XDG_DATA_DIRS", "", NULL } },
+ { xdg_conf, xdg_local, {NULL, "HOME"},
+ { "XDG_CONF_DIRS", "", NULL } }
+ };
+#endif
+
+ /* First clear all the environment variables */
+ for (i = 0; i < 5; i++) {
+ for (j = 0; ;j++) {
+ if (cases[i].envn[j] == NULL)
+ break;
+ sprintf(buf1, "%s=",cases[i].envn[j]);
+ mputenv(buf1);
+ }
+ }
+
+ /* Then run all the tests */
+ for (i = 0; i < 5; i++) {
+ for (j = 0; ;j++) {
+ if (cases[i].envn[j] == NULL)
+ break;
+ sprintf(buf1, "xdgtest%d",i);
+ sprintf(buf2, "application/%s",cases[i].st == xdg_data ? "data" :
+ cases[i].st == xdg_conf ? "config" : "cache");
+ if (runtest(cases[i].st, cases[i].sc, buf2, cases[i].envn[j],buf1,
+ cases[i].defv[cases[i].sc == xdg_user ? 0 : 1],9))
+ exit(1);
+ }
+ }
+
+ printf("Test completed OK\n");
+
+ return 0;
+}
+
+#endif /* STANDALONE_TEST */
+
+
diff --git a/spectro/xdg_bds.h b/spectro/xdg_bds.h
new file mode 100644
index 0000000..5c29790
--- /dev/null
+++ b/spectro/xdg_bds.h
@@ -0,0 +1,115 @@
+
+#ifndef XDG_BDS_H
+
+ /* XDG Base Directory Specifications support library */
+
+/*************************************************************************
+ Copyright 2011, 2013 Graeme W. Gill
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+
+ *************************************************************************/
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/* Which type of storage */
+typedef enum {
+ xdg_data,
+ xdg_conf,
+ xdg_cache /* Note there is no xdg_local cache */
+} xdg_storage_type;
+
+/* What operation is being performed */
+typedef enum {
+ xdg_write, /* Create or write */
+ xdg_read /* Read */
+} xdg_op_type;
+
+/* What scope to write to */
+/* (For write only. Read always searches */
+/* the user context then the local system context.) */
+typedef enum {
+ xdg_user, /* User context */
+ xdg_local /* Local system wide context */
+} xdg_scope;
+
+/* An error code */
+typedef enum {
+ xdg_ok = 0,
+ xdg_alloc, /* A memory allocation failed */
+ xdg_nohome, /* There is no $HOME */
+ xdg_noalluserprofile, /* There is no $ALLUSERSPROFILE */
+ xdg_nopath, /* There is no resulting path */
+ xdg_mallformed /* Malfomed path */
+} xdg_error;
+
+#ifdef NT
+#define SSEP ';' /* Since ':' is used for drive letter */
+#define SSEPS ";"
+#else
+#define SSEP ':'
+#define SSEPS ":"
+#endif
+
+ /* ONLY use this for xdg_data type */
+#ifdef __APPLE__ /* fudge to assist OS X migration from */
+#define XDG_FUDGE SSEPS "../" /* Library/color to Library/Application Support/ArgyllCMS */
+#else
+#define XDG_FUDGE SSEPS
+#endif
+
+/* Return the number of matching full paths to the given subpath for the */
+/* type of storage and access required. Return 0 if there is an error. */
+/* The files are always unique (ie. the first match to a given filename */
+/* in the possible XDG list of directories is returned, and files with */
+/* the same name in other XDG directories are ignored) */
+/* Wildcards should only be for the filename portion, and not be used for xdg_write. */
+/* The list should be free'd using xdg_free() after use. */
+/* XDG environment variables and the subpath are assumed to be using */
+/* the '/' path separator. Multiple read paths are separated by SSEP */
+/* When "xdg_write", the necessary path to the file will be created. */
+/* If we're running as sudo and are creating a user dir/file, */
+/* we drop to using the underlying SUDO_UID/GID. If we are creating a */
+/* local system dir/file as sudo and have dropped to the SUDO_UID/GID, */
+/* then revert back to root uid/gid. */
+int xdg_bds(
+ xdg_error *er, /* Return an error code */
+ char ***paths, /* Return array of pointers to paths */
+ xdg_storage_type st, /* Specify the storage type */
+ xdg_op_type op, /* Operation type */
+ xdg_scope sc, /* Scope if write */
+ char *spath /* Sub-path and file name or file pattern */
+);
+
+/* Free the list */
+void xdg_free(char **paths, int nopaths);
+
+/* Return a string corresponding to the error value */
+char *xdg_errstr(xdg_error er);
+
+
+#define XDG_BDS_H
+#endif /* XDG_BDS_H */
+
+#ifdef __cplusplus
+ }
+#endif
+