From 22f703cab05b7cd368f4de9e03991b7664dc5022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Mon, 1 Sep 2014 13:56:46 +0200 Subject: Initial import of argyll version 1.5.1-8 --- rspl/Jamfile | 83 + rspl/License.txt | 662 ++++++ rspl/Makefile.am | 15 + rspl/Readme.txt | 39 + rspl/afiles | 34 + rspl/c1.c | 396 ++++ rspl/c1df.c | 313 +++ rspl/gam.c | 1220 ++++++++++ rspl/gam.h | 131 ++ rspl/mlbs.c | 605 +++++ rspl/mlbs.h | 77 + rspl/opt.c | 725 ++++++ rspl/rev.c | 6608 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ rspl/rev.h | 457 ++++ rspl/revbench.c | 306 +++ rspl/rspl.c | 1511 +++++++++++++ rspl/rspl.h | 645 ++++++ rspl/rspl1.c | 391 ++++ rspl/rspl1.h | 115 + rspl/rspl_imp.h | 27 + rspl/scat.c | 2861 +++++++++++++++++++++++ rspl/scat2.c | 233 ++ rspl/sm1.c | 88 + rspl/sm2.c | 110 + rspl/sm3.c | 110 + rspl/smtmpp.c | 1203 ++++++++++ rspl/smtnd.c | 1171 ++++++++++ rspl/spline.c | 352 +++ rspl/stest.c | 654 ++++++ rspl/t2d.c | 1016 +++++++++ rspl/t2ddf.c | 517 +++++ rspl/t3d.c | 905 ++++++++ rspl/t3ddf.c | 570 +++++ rspl/tnd.c | 489 ++++ rspl/trnd.c | 275 +++ 35 files changed, 24914 insertions(+) create mode 100644 rspl/Jamfile create mode 100644 rspl/License.txt create mode 100644 rspl/Makefile.am create mode 100644 rspl/Readme.txt create mode 100644 rspl/afiles create mode 100644 rspl/c1.c create mode 100644 rspl/c1df.c create mode 100644 rspl/gam.c create mode 100644 rspl/gam.h create mode 100644 rspl/mlbs.c create mode 100644 rspl/mlbs.h create mode 100644 rspl/opt.c create mode 100644 rspl/rev.c create mode 100644 rspl/rev.h create mode 100644 rspl/revbench.c create mode 100644 rspl/rspl.c create mode 100644 rspl/rspl.h create mode 100644 rspl/rspl1.c create mode 100644 rspl/rspl1.h create mode 100644 rspl/rspl_imp.h create mode 100644 rspl/scat.c create mode 100644 rspl/scat2.c create mode 100644 rspl/sm1.c create mode 100644 rspl/sm2.c create mode 100644 rspl/sm3.c create mode 100644 rspl/smtmpp.c create mode 100644 rspl/smtnd.c create mode 100644 rspl/spline.c create mode 100644 rspl/stest.c create mode 100644 rspl/t2d.c create mode 100644 rspl/t2ddf.c create mode 100644 rspl/t3d.c create mode 100644 rspl/t3ddf.c create mode 100644 rspl/tnd.c create mode 100644 rspl/trnd.c (limited to 'rspl') diff --git a/rspl/Jamfile b/rspl/Jamfile new file mode 100644 index 0000000..a3f2e8c --- /dev/null +++ b/rspl/Jamfile @@ -0,0 +1,83 @@ + +# Regular spline library + +# Optimization and Debug flags + +PREF_CCFLAGS += $(CCOPTFLAG) ; # Turn optimisation on +#PREF_CCFLAGS += $(CCDEBUGFLAG) ; # Debugging flags +#PREF_CCFLAGS += $(CCPROFFLAG) ; # Profile flags +#PREF_LINKFLAGS += $(LINKPROFFLAG) ; # Profile flags +#PREF_CCFLAGS += $(CCHEAPDEBUG) ; # Heap Debugging flags +PREF_LINKFLAGS += $(LINKDEBUGFLAG) ; # Link with debug info + +SCAT = scat ; # Use thps scattered interpolation library + +#Products +Libraries = librspl ; +Headers = rspl.h ; + +#Install +#InstallLib $(DESTDIR)$(PREFIX)/lib : $(Libraries) ; +#InstallFile $(DESTDIR)$(PREFIX)/h : $(Headers) ; + +# Multi-dimensional regular spline library +Library librspl : rspl.c $(SCAT).c rev.c gam.c spline.c opt.c : : : ../h ../numlib ../plot ; + +HDRS = ../h ../numlib ../plot $(TIFFINC) ; +LINKLIBS = librspl ../numlib/libnum ../plot/libplot ../plot/libvrml ../icc/libicc $(TIFFLIB) $(JPEGLIB) ; + +# Test programs +MainsFromSources revbench.c c1.c c1df.c t2d.c t2ddf.c t3d.c t3ddf.c tnd.c trnd.c ; + +BUILD_TESTS = true ; + +if $(BUILD_TESTS) { + + HDRS = ../h ../numlib ../plot ../icc ../rspl ../xicc ../gamut ../cgats ../spectro $(TIFFINC) ; + LINKLIBS = ../xicc/libxicc ../gamut/libgamut ../spectro/libinsttypes librspl + ../cgats/libcgats ../icc/libicc ../plot/libplot ../plot/libvrml + ../numlib/libnum $(TIFFLIB) $(JPEGLIB) ; + + # Smoothness factor tuning test in Nd. + Main smtnd : smtnd.c ; + + # Smoothness factor tuning test in Nd. + Main smtmpp : smtmpp.c ; + +# Main rand_check : rand_check.c ; + +# Main tt : tt.c ; + + HDRS = ; + LINKLIBS = ; + + Main sm1 : sm1.c ; + Main sm2 : sm2.c ; + Main sm3 : sm3.c ; + +} + + +# Main tt : tt.c : : ../xicc : : : ../plot/libvrml ../icc/libicc ; + +if $(BUILD_JUNK) { + + HDRS = ../h ../numlib ; + LINKLIBS = ..//numlib/libnum ; + + #Main temp : temp.c ; + + MainsFromSources prime.c combo.c combo2.c combo3.c combo4.c combo5.c combo9.c ; + + #Main cache : cache.c ; + + # Nearest point test code + Main nptest : nptest.c ; + + # Nearest object test code + Main notest : notest.c ; + + #direct mlbs scattered interpolation test + Main mlbs_test : mlbs_test.c ; +} + diff --git a/rspl/License.txt b/rspl/License.txt new file mode 100644 index 0000000..a871fcf --- /dev/null +++ b/rspl/License.txt @@ -0,0 +1,662 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + + Copyright (C) + + 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 . + +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 +. + diff --git a/rspl/Makefile.am b/rspl/Makefile.am new file mode 100644 index 0000000..cfa0ec2 --- /dev/null +++ b/rspl/Makefile.am @@ -0,0 +1,15 @@ +include $(top_srcdir)/Makefile.shared + +privatelib_LTLIBRARIES = librspl.la +privatelibdir = $(pkglibdir) + +librspl_la_SOURCES = rspl.h rspl_imp.h mlbs.h rspl.c scat.c rev.c \ + rev.h gam.c spline.c opt.c +librspl_la_LIBADD = ../numlib/libargyllnum.la ../plot/libvrml.la + +LDADD = ./librspl.la ../numlib/libargyllnum.la ../plot/libplot.la \ + ../plot/libvrml.la $(X_LIBS) $(TIFF_LIBS) $(ICC_LIBS) + +check_PROGRAMS = revbench c1 c1df t2d t2ddf t3d t3ddf tnd trnd + +EXTRA_DIST = License.txt Readme.txt diff --git a/rspl/Readme.txt b/rspl/Readme.txt new file mode 100644 index 0000000..4f6c8c0 --- /dev/null +++ b/rspl/Readme.txt @@ -0,0 +1,39 @@ +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +rspl now supports different resolution grids in each dimension. +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +This is the second generation Regular Spline library. + +It contains scattered data point to regular grid interpolation, +as well as spline smoothing, and the reverse interpolation +code. This version is more modular, and uses better solution +algorithms than the earlier REGSPL, and generally replaces it. + +The reverse interpolation algorithms support features needed +for devices like CMYK printers, such as total ink limiting, +black locus selection, gamut boundary detection, vector +and nearest gamut clipping. + +It has been written with operation with 6 color printing +devices in mind (ie. 3 extra degrees of freedom, and hence +a 3 dimensional inking locus), although this usage is likely +to be unwealdy. + + +Misc test files: + +c1.c Test 1D curves. First test is to check tracking at multiple resolutions +c1df.c Test 1D curve with weak default function. +c1i.c Test 1D curve with incremental points +sm1.c Discover 1D smoothness factor vs resolution tracking factor +sm2.c Discover 2D smoothness factor vs resolution tracking factor +sm3.c Discover 3D smoothness factor vs resolution tracking factor +t2d.c Test 2D fitting. Test againt two resolutions. +t2ddf.c Test 2D fitting with weak default function. +t3d.c Test 3D fitting. Test againt two resolutions. +t3ddf.c Test 3D fitting with weak default function. +tnd.c Simple test of 4D +trnd.c Simple test of 4D reverse lookup. +smtnd.c Sythetic function, nD multi-parameter fitting test function. +smtmpp.c MPP function, nD multi-parameter fitting test function. + diff --git a/rspl/afiles b/rspl/afiles new file mode 100644 index 0000000..af20c44 --- /dev/null +++ b/rspl/afiles @@ -0,0 +1,34 @@ +Readme.txt +License.txt +afiles +Jamfile +rspl.h +rspl_imp.h +rspl.c +rev.h +rev.c +scat.c +scat2.c +mlbs.c +mlbs.h +opt.c +gam.h +gam.c +revbench.c +c1.c +c1df.c +spline.c +t2d.c +t2ddf.c +t3d.c +t3ddf.c +tnd.c +trnd.c +sm1.c +sm2.c +sm3.c +stest.c +smtnd.c +smtmpp.c +rspl1.h +rspl1.c diff --git a/rspl/c1.c b/rspl/c1.c new file mode 100644 index 0000000..8172cef --- /dev/null +++ b/rspl/c1.c @@ -0,0 +1,396 @@ + +/************************************************/ +/* Investigate various curve approximations */ +/************************************************/ + +/* Discrete regularized spline versions */ +/* Standard test + Random testing */ + +/* Author: Graeme Gill + * Date: 4/10/95 + * Date: 5/4/96 + * + * Copyright 1995, 1996 Graeme W. Gill + * + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +#undef DIAG +#undef DIAG2 +#undef GLOB_CHECK +#undef RES2 /* Do multiple test at various resolutions */ +#define AVGDEV 0.0 /* Average deviation of function data */ + +#include +#include +#include +#include +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "plot.h" +#include "rspl.h" + +double lin(); +void usage(void); + +#define TRIALS 20 /* Number of random trials */ +#define SKIP 0 /* Number of random trials to skip */ + +#define MIN_PNTS 5 +#define MAX_PNTS 40 + +#define MIN_RES 20 +#define MAX_RES 2000 + +double xa[MAX_PNTS]; +double ya[MAX_PNTS]; + +#define XRES 100 + +#define PNTS1 10 +#define GRES1 400 +//#define GRES 800 +double t1xa[PNTS1] = { 0.2, 0.25, 0.30, 0.35, 0.40, 0.44, 0.48, 0.51, 0.64, 0.75 }; +double t1ya[PNTS1] = { 0.3, 0.35, 0.4, 0.41, 0.42, 0.46, 0.5, 0.575, 0.48, 0.75 }; + +#ifndef NEVER + +// Reverse in x */ +#define PNTS2 10 +#define GRES2 400 +double t2xa[PNTS2] = { 0.25, 0.36, 0.49, 0.52, 0.56, 0.60, 0.65, 0.70, 0.75, 0.8 }; +double t2ya[PNTS2] = { 0.75, 0.48, 0.575, 0.5, 0.46, 0.42, 0.41, 0.4, 0.35, 0.3 }; + +#else + +#define PNTS2 10 +#define GRES2 400 +// reverse in y +double t2xa[PNTS2] = { 0.2, 0.25, 0.30, 0.35, 0.40, 0.44, 0.48, 0.51, 0.64, 0.75 }; +double t2ya[PNTS2] = { 0.7, 0.65, 0.6, 0.59, 0.58, 0.54, 0.5, 0.425, 0.52, 0.25 }; + +#endif /* NEVER */ + +//#define PNTS2 2 +//#define GRES2 5 +//double t2xa[PNTS2] = { 0.0, 1.0 }; +//double t2ya[PNTS2] = { 0.33, 0.66 }; + +co test_points[MAX_PNTS]; + +double lin(double x, double xa[], double ya[], int n); + +void usage(void) { + fprintf(stderr,"Test 1D rspl interpolation\n"); + fprintf(stderr,"Author: Graeme W. Gill\n"); + fprintf(stderr,"usage: c1 [options]\n"); + fprintf(stderr," -s smooth Use given smoothness (default 1.0)\n"); + fprintf(stderr," -2 Use two pass smoothing\n"); + fprintf(stderr," -x Use extra fitting\n"); + exit(1); +} + +int main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + int i,j, n; + double x; + double xx[XRES]; + double yy[6][XRES]; + rspl *rss; /* incremental solution version */ + datai low,high; + int gres[MXDI]; + double smooth = 1.0; + int twopass = 0; + int extra = 0; + double avgdev[MXDO]; + + low[0] = 0.0; + high[0] = 1.0; + avgdev[0] = AVGDEV; + + error_program = "c1"; + check_if_not_interactive(); + + /* 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(); + + /* smoothness */ + else if (argv[fa][1] == 's' || argv[fa][1] == 'S') { + fa = nfa; + if (na == NULL) usage(); + smooth = atof(na); + } + + else if (argv[fa][1] == '2') { + twopass = 1; + } + else if (argv[fa][1] == 'x' || argv[fa][1] == 'X') { + extra = 1; + } + else + usage(); + } else + break; + } + + for (n = 0; n < TRIALS; n++) { + double lrand = 0.0; /* Amount of level randomness */ + int pnts; + int fres; + + if (n == 0) { /* Standard versions */ +#ifdef NEVER /* Doubled up points */ + pnts = 2 * PNTS; + fres = GRES; + for (i = 0; i < pnts; i++) { + xa[i * 2 + 0] = t1xa[i] - 0.01; + ya[i * 2 + 0] = t1ya[i]; + xa[i * 2 + 1] = t1xa[i] + 0.01; + ya[i * 2 + 1] = t1ya[i]; + } +#else + pnts = PNTS1; + fres = GRES1; + for (i = 0; i < pnts; i++) { + xa[i] = t1xa[i]; + ya[i] = t1ya[i]; + } +#endif + printf("Trial %d, points = %d, res = %d, level randomness = %f\n",n,pnts,fres,lrand); + } else if (n == 1) { /* Second test versions */ + pnts = PNTS2; + fres = GRES2; + for (i = 0; i < pnts; i++) { + xa[i] = t2xa[i]; + ya[i] = t2ya[i]; + } + printf("Trial %d, points = %d, res = %d, level randomness = %f\n",n,pnts,fres,lrand); + } else { /* Random versions */ + lrand = d_rand(0.0,0.1); /* Amount of level randomness */ + pnts = i_rand(MIN_PNTS,MAX_PNTS); + fres = i_rand(MIN_RES,MAX_RES); + + printf("Trial %d, points = %d, res = %d, level randomness = %f\n",n,pnts,fres,lrand); + + /* Create X values */ + xa[0] = d_rand(0.5,1.0); + for (i = 1; i < pnts; i++) + xa[i] = xa[i-1] + d_rand(0.5,1.0); + for (i = 0; i < pnts; i++) /* Divide out */ + xa[i] = (xa[i]/xa[pnts-1]); + + /* Create y values */ + ya[0] = xa[0]; + for (i = 0; i < pnts; i++) + ya[i] = ya[i-1] + d_rand(0.2,1.0) + d_rand(-0.2,0.3) + d_rand(-0.2,0.3); + for (i = 0; i < pnts; i++) /* Divide out */ + ya[i] = (ya[i]/ya[pnts-1]); + } + + if (n < SKIP) + continue; + + /* Create the object */ + rss = new_rspl(RSPL_NOFLAGS, + 1, /* di */ + 1); /* fdi */ + + for (i = 0; i < pnts; i++) { + test_points[i].p[0] = xa[i]; + test_points[i].v[0] = ya[i]; + } + gres[0] = fres; + +#ifdef RES2 + if (n != 0) { +#endif + /* Fit to scattered data */ + rss->fit_rspl(rss, + 0 | (twopass ? RSPL_2PASSSMTH : 0) + | (extra ? RSPL_EXTRAFIT2 : 0) , + test_points, /* Test points */ + pnts, /* Number of test points */ + low, high, gres, /* Low, high, resolution of grid */ + NULL, NULL, /* Default data scale */ + smooth, /* Smoothing */ + avgdev, /* Average deviation */ + NULL); /* iwidth */ + + + /* Display the result */ + for (i = 0; i < XRES; i++) { + co tp; /* Test point */ + x = i/(double)(XRES-1); + xx[i] = x; + yy[0][i] = lin(x,xa,ya,pnts); + tp.p[0] = x; + rss->interp(rss, &tp); + yy[1][i] = tp.v[0]; + if (yy[1][i] < -0.2) + yy[1][i] = -0.2; + else if (yy[1][i] > 1.2) + yy[1][i] = 1.2; + } + + do_plot(xx,yy[0],yy[1],NULL,XRES); + +#ifdef RES2 + } else { /* Multiple resolution version */ + int gresses[5]; + for (j = 0; j < 5; j++) { +#ifndef NEVER + if (j == 0) + gres[0] = fres/8; + else if (j == 1) + gres[0] = fres/4; + else if (j == 2) + gres[0] = fres/2; + else if (j == 3) + gres[0] = fres; + else + gres[0] = fres * 2; +#else /* Check sensitivity to griding of data points */ + if (j == 0) + gres[0] = 192; + else if (j == 1) + gres[0] = 193; + else if (j == 2) + gres[0] = 194; + else if (j == 3) + gres[0] = 195; + else + gres[0] = 196; +#endif + gresses[j] = gres[0]; + + rss->fit_rspl(rss, + 0 | (twopass ? RSPL_2PASSSMTH : 0) + | (extra ? RSPL_EXTRAFIT2 : 0) , + 0, + test_points, /* Test points */ + pnts, /* Number of test points */ + low, high, gres, /* Low, high, resolution of grid */ + NULL, NULL, /* Default data scale */ + smooth, /* Smoothing */ + avgdev, /* Average deviation */ + NULL); /* iwidth */ + + /* Get the result */ + for (i = 0; i < XRES; i++) { + co tp; /* Test point */ + x = i/(double)(XRES-1); + xx[i] = x; + yy[0][i] = lin(x,xa,ya,pnts); + tp.p[0] = x; + rss->interp(rss, &tp); + yy[1+j][i] = tp.v[0]; + if (yy[1+j][i] < -0.2) + yy[1+j][i] = -0.2; + else if (yy[1+j][i] > 1.2) + yy[1+j][i] = 1.2; + } + } + + printf("Black = lin, Red = %d, Green = %d, Blue = %d, Yellow = %d, Purple = %d\n", + gresses[0], gresses[1], gresses[2], gresses[3], gresses[4]); + do_plot6(xx,yy[0],yy[1],yy[2],yy[3],yy[4],yy[5],XRES); + } +#endif /* RES2 */ + } /* next trial */ + return 0; +} + + +/* Simple linear interpolation */ +double +lin( +double x, +double xa[], +double ya[], +int n) { + int i; + double y; + + if (x < xa[0]) + return ya[0]; + else if (x > xa[n-1]) + return ya[n-1]; + + for (i = 0; i < (n-1); i++) + if (x >=xa[i] && x <= xa[i+1]) + break; + + x = (x - xa[i])/(xa[i+1] - xa[i]); + + y = ya[i] + (ya[i+1] - ya[i]) * x; + + return y; +} + + +/******************************************************************/ +/* Error/debug output routines */ +/******************************************************************/ + +/* Next u function done with optimization */ + +/* Structure to hold data for optimization function */ +struct _edatas { + rspl *rss; + int j; + }; typedef struct _edatas edatas; + +#ifdef GLOB_CHECK +/* Overall Global optimization method */ +/* Definition of the optimization function handed to powell() */ +double efunc2(void *edata, double p[]) + { + int j; + double rv; + rspl *rss = (rspl *)edata; + for (j = 0; j < rss->nig; j++) /* Ugg */ + rss->u[j].v = p[j]; + rv = rss->efactor(rss); +#ifdef DIAG2 + /* printf("%c%e",cr_char,rv); */ + printf("%e\n",rv); +#endif + return rv; + } + +solveu(rss) +rspl *rss; + { + int j; + double *cp; + double *s; + + cp = dvector(0,rss->nig); + s = dvector(0,rss->nig); + for (j = 0; j < rss->nig; j++) /* Ugg */ + { + cp[j] = rss->u[j].v; + s[j] = 0.1; + } + powell(rss->nig,cp,s,1e-7,1000,efunc2,(void *)rss); + } +#endif /* GLOB_CHECK */ diff --git a/rspl/c1df.c b/rspl/c1df.c new file mode 100644 index 0000000..5d8dc32 --- /dev/null +++ b/rspl/c1df.c @@ -0,0 +1,313 @@ + +/************************************************/ +/* Investigate various curve approximations */ +/************************************************/ + +/* Discrete regularized spline versions */ +/* Standard test with weak default function */ + +/* Author: Graeme Gill + * Date: 20/11/2005 + * + * Copyright 1995, 1996, 2005 Graeme W. Gill + * + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +#undef DIAG +#undef DIAG2 +#undef GLOB_CHECK +#define RES2 /* Do multiple test at various resolutions */ +#undef EXTRAFIT /* Test extra fitting effort */ +#define SMOOTH 1.0 +#define AVGDEV 0.0 + +#include +#include +#include +#include +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "plot.h" +#include "rspl.h" + +double lin(); +void usage(void); + +#define TRIALS 15 /* Number of random trials */ +#define SKIP 0 /* Number of random trials to skip */ + +#define MIN_PNTS 1 +#define MAX_PNTS 7 + +#define MIN_RES 20 +#define MAX_RES 300 + +double xa[MAX_PNTS]; +double ya[MAX_PNTS]; +double wa[MAX_PNTS]; + +#define XRES 100 + +#define PNTS 2 +#define GRES 200 +//double t1xa[PNTS] = { 0.325, 0.625 }; +//double t1ya[PNTS] = { 0.4, 0.70 }; +double t1xa[PNTS] = { 0.325, 0.625 }; +double t1ya[PNTS] = { 0.5, 0.8 }; +double t1wa[PNTS] = { 1.0, 1.0 }; +cow test_points[MAX_PNTS]; + +double lin(double x, double xa[], double ya[], int n); + +/* Weak default function */ +static void wfunc(void *cbntx, double *out, double *in) { + out[0] = in[0]; +} + +void usage(void) { + fprintf(stderr,"Test 1D rspl interpolation with weak default function\n"); + fprintf(stderr,"Author: Graeme W. Gill\n"); + fprintf(stderr,"usage: c1df [options]\n"); + fprintf(stderr," -w wweight Set weak default function weight (default 1.0)\n"); + exit(1); +} + +int main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + int i,j, n; + double x; + double xx[XRES]; + double yy[6][XRES]; + rspl *rss; /* incremental solution version */ + datai low,high; + int gres[MXDI]; + double avgdev[MXDO]; + double wweight = 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(); + + } else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') { + fa = nfa; + if (na == NULL) usage(); + wweight = atof(na); + } else + usage(); + } else + break; + } + + low[0] = 0.0; + high[0] = 1.0; + avgdev[0] = AVGDEV; + + error_program = "Curve1"; + + for (n = 0; n < TRIALS; n++) { + double lrand = 0.0; /* Amount of level randomness */ + int pnts; + int fres; + + if (n == 0) { /* Standard versions */ + pnts = PNTS; + fres = GRES; + for (i = 0; i < pnts; i++) { + xa[i] = t1xa[i]; + ya[i] = t1ya[i]; + wa[i] = t1wa[i]; + } + printf("Trial %d, points = %d, res = %d, level randomness = %f\n",n,pnts,fres,lrand); + } else { /* Random versions */ + double xmx; + lrand = d_rand(0.0,0.1); /* Amount of level randomness */ + pnts = i_rand(MIN_PNTS,MAX_PNTS); + fres = i_rand(MIN_RES,MAX_RES); + + printf("Trial %d, points = %d, res = %d, level randomness = %f\n",n,pnts,fres,lrand); + + /* Create X values */ + xa[0] = d_rand(0.3, 0.5); + for (i = 1; i < pnts; i++) + xa[i] = xa[i-1] + d_rand(0.2,0.7); + xmx = d_rand(0.6, 0.9); + for (i = 0; i < pnts; i++) /* Divide out */ + xa[i] *= (xmx/xa[pnts-1]); + + /* Create y values */ + for (i = 0; i < pnts; i++) { + ya[i] = xa[i] + d_rand(-lrand,lrand); + wa[i] = 1.0; + } + } + + if (n < SKIP) + continue; + + /* Create the object */ + rss = new_rspl(RSPL_NOFLAGS, 1, /* di */ + 1); /* fdi */ + + for (i = 0; i < pnts; i++) { + test_points[i].p[0] = xa[i]; + test_points[i].v[0] = ya[i]; + test_points[i].w = wa[i]; + } + gres[0] = fres; + +#ifdef RES2 + if (n != 0) { +#endif + /* Fit to scattered data */ + rss->fit_rspl_w_df(rss, +#ifdef EXTRAFIT + RSPL_EXTRAFIT | /* Extra fit flag */ +#endif + 0, + test_points, /* Test points */ + pnts, /* Number of test points */ + low, high, gres, /* Low, high, resolution of grid */ + low, high, /* Data scale */ + SMOOTH, /* Smoothing */ + avgdev, /* Average deviation */ + NULL, /* iwidth */ + wweight, /* weak function weight */ + NULL, /* No context */ + wfunc /* Weak function */ + ); + + /* Display the result */ + for (i = 0; i < XRES; i++) { + co tp; /* Test point */ + x = i/(double)(XRES-1); + xx[i] = x; + yy[0][i] = lin(x,xa,ya,pnts); + tp.p[0] = x; + rss->interp(rss, &tp); + yy[1][i] = tp.v[0]; + if (yy[1][i] < -0.2) + yy[1][i] = -0.2; + else if (yy[1][i] > 1.2) + yy[1][i] = 1.2; + } + + do_plot(xx,yy[0],yy[1],NULL,XRES); + +#ifdef RES2 + } else { /* Multiple resolution version */ + int gresses[5]; + for (j = 0; j < 5; j++) { +#ifndef NEVER + if (j == 0) + gres[0] = fres/8; + else if (j == 1) + gres[0] = fres/4; + else if (j == 2) + gres[0] = fres/2; + else if (j == 3) + gres[0] = fres; + else + gres[0] = fres * 2; +#else /* Check sensitivity to griding of data points */ + if (j == 0) + gres[0] = 192; + else if (j == 1) + gres[0] = 193; + else if (j == 2) + gres[0] = 194; + else if (j == 3) + gres[0] = 195; + else + gres[0] = 196; +#endif + gresses[j] = gres[0]; + + rss->fit_rspl_w_df(rss, +#ifdef EXTRAFIT + RSPL_EXTRAFIT | /* Extra fit flag */ +#endif + 0, + test_points, /* Test points */ + pnts, /* Number of test points */ + low, high, gres, /* Low, high, resolution of grid */ + low, high, /* Data scale */ + SMOOTH, /* Smoothing */ + avgdev, /* Average deviation */ + NULL, /* iwidth */ + wweight, /* weak function weight */ + NULL, /* No context */ + wfunc /* Weak function */ + ); + + /* Get the result */ + for (i = 0; i < XRES; i++) { + co tp; /* Test point */ + x = i/(double)(XRES-1); + xx[i] = x; + yy[0][i] = lin(x,xa,ya,pnts); + tp.p[0] = x; + rss->interp(rss, &tp); + yy[1+j][i] = tp.v[0]; + if (yy[1+j][i] < -0.2) + yy[1+j][i] = -0.2; + else if (yy[1+j][i] > 1.2) + yy[1+j][i] = 1.2; + } + } + + printf("Black = lin, Red = %d, Green = %d, Blue = %d, Yellow = %d, Purple = %d\n", + gresses[0], gresses[1], gresses[2], gresses[3], gresses[4]); + do_plot6(xx,yy[0],yy[1],yy[2],yy[3],yy[4],yy[5],XRES); + } +#endif /* RES2 */ + } /* next trial */ + return 0; +} + + +double +lin( +double x, +double xa[], +double ya[], +int n) + { + int i; + double y; + + if (x < xa[0]) + return ya[0]; + else if (x > xa[n-1]) + return ya[n-1]; + + for (i = 0; i < (n-1); i++) + if (x >=xa[i] && x <= xa[i+1]) + break; + + x = (x - xa[i])/(xa[i+1] - xa[i]); + + y = ya[i] + (ya[i+1] - ya[i]) * x; + + return y; + } + + diff --git a/rspl/gam.c b/rspl/gam.c new file mode 100644 index 0000000..f8d02d0 --- /dev/null +++ b/rspl/gam.c @@ -0,0 +1,1220 @@ + +/* + * Argyll Color Correction System + * Multi-dimensional regularized spline data structure + * + * Precice gamut surface, gamut pruning, ink limiting and K min/max + * support routine.s + * + * Author: Graeme W. Gill + * Date: 2008/11/21 + * + * Copyright 1999 - 2008 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. + * + * Latest simplex/linear equation version. + */ + +/* TTBD: + + Add ouutput curve lookup callback support. + Cache these values in the vertex structures ? + + Add ink limit support. This be done by breaking + a cell into a fixed geometry of smaller simplexes + by dividing the cell into two on each axis. + + Need to then add scan that detects areas to prune, + that then ties in with rev code to mark such + areas out of gamut. + + */ + +#include +#include +#include +#include +#include +#include + +#include "rspl_imp.h" +#include "numlib.h" +#include "sort.h" /* Heap sort */ +#include "counters.h" /* Counter macros */ + +#include "vrml.h" /* If there is VRML stuff here */ + +/* Print a vectors value */ +#define DBGVI(text, dim, out, vec, end) \ +{ int pveci; \ + printf("%s",text); \ + for (pveci = 0 ; pveci < (dim); pveci++) \ + printf(out,(vec)[pveci]); \ + printf(end); \ +} + +/* Print a matrix value */ +#define DBGMI(text, rows, cols, out, mat, end) \ +{ int pveci, pvecr; \ + printf("%s",text); \ + for (pvecr = 0 ; pvecr < (rows); pvecr++) { \ + for (pveci = 0 ; pveci < (cols); pveci++) \ + printf(out,(mat)[pvecr][pveci]); \ + if ((pvecr+1) < (rows)) \ + printf("\n"); \ + } \ + printf(end); \ +} + +#undef VRML_TRACE /* Save a vrml at each step */ + +/* Do an arbitrary printf */ +#define DBGI(text) printf text ; + +#define DEBUG1 +#undef DBG +#undef DBGV +#undef DBGM + +#undef NEVER +#define ALWAYS + +#ifdef DEBUG1 +#undef DBGS +#undef DBG +#undef DBGV +#undef DBGM +#define DEBUG +#define DBGS(xxx) xxx +#define DBG(xxx) DBGI(xxx) +#define DBGV(xxx) DBGVI xxx +#define DBGM(xxx) DBGMI xxx +#else +#undef DEBUG +#undef DBGS +#undef DBG +#undef DBGV +#undef DBGM +#define DBGS(xxx) +#define DBG(xxx) +#define DBGV(xxx) +#define DBGM(xxx) +#endif + +/* Convention is to use: + i to index grid points u.a + n to index data points d.a + e to index position dimension di + f to index output function dimension fdi + j misc and cube corners + k misc + */ + +#define EPS (1e-10) /* Allowance for numeric error */ + +/* ====================================================== */ +/* Support functions */ + +/* Compute the norm (length) squared of a vector define by two points */ +static double norm33sq(double in1[3], double in0[3]) { + int j; + double rv; + for (rv = 0.0, j = 0; j < 3; j++) { + double tt = in1[j] - in0[j]; + rv += tt * tt; + } + return rv; +} + + +static rvert *get_vert(rspl *s, int gix); + +/* Given an output value, return the gamut radius */ +static double gvprad(rspl *s, double *v) { + int f, fdi = s->fdi; + double rr = 0.0; + + for (f = 0; f < fdi; f++) { + double tt; + tt = s->gam.scale[f] * (v[f] - s->gam.cent[f]); + rr += tt * tt; + } + rr = sqrt(rr); + return rr; +} + + +/* Given an output value, create the radial coordinate */ +static void radcoord(rspl *s, double *rad, double *v) { + int f, fdi = s->fdi; + double rr = 0.0; + + for (f = 0; f < fdi; f++) { + double tt; + tt = s->gam.scale[f] * (v[f] - s->gam.cent[f]); + rr += tt * tt; + } + rr = sqrt(rr); + + rad[0] = rr; +} + +/* Given an output value and an edge, return the side, */ +/* 0 = -vem 1 = +ve */ +static int eside(rspl *s, redge *ep, double *v) { + int f, fdi = s->fdi; + double tt; + + for (tt = 0.0, f = 0; f < fdi; f++) { + tt += v[f] * ep->pe[f]; + } + tt += ep->pe[f]; + return (tt >= 0.0 ? 1 : 0); +} + + +/* Given a list of nodes that form an sdi-1 sub-simplex, */ +/* return a list of nodes that could be added to make */ +/* an sdi sub-simplex. */ +/* Nodes are identified by their grid index. */ +/* All possible nodes are returned, even if they are already */ +/* in our surface triangulation. */ +/* NOTE that inodes will be sorted into sub-simplex order! */ +// ~~99 we aren't currently taking ink limit into account + +/* return nz on fatal error */ +static int get_ssimplex_nodes( +rspl *s, +int sdi, /* Dimensionality of target sub-simplex */ +rvert **inodes, /* sdi input nodes that form an sdi-1 dimensional input sub-simplex */ +int aonodes, /* Number of output nodes allowed for */ +int *nonodes, /* Number of output nodes set */ +rvert **onodes /* Space for up to 3^MXDI-1 output nodes */ +) { + int e, di = s->di; + int i, j, k; + + *nonodes = 0; + +//printf("\n~1 get_ssimplex_nodes called with sdi = %d\n",sdi); + /* Sort the input nodes into normal sub-simplex order */ + /* (We are assuming all the nodes are in the grid) */ + for (i = 0; i < sdi-1; i++) { + for (j = i+1; j < sdi; j++) { + if (inodes[i]->gix < inodes[j]->gix) { + rvert *tt; + tt = inodes[i]; inodes[i] = inodes[j]; inodes[j] = tt; + } + } + } +//printf("~2 input nodes = %d %d\n", inodes[0]-gix, inodes[1]->gix); + + /* For each sub-simplex */ + for (i = 0; i < s->gam.ssi[sdi].nospx; i++) { + int kk; +//printf("~1 sub simplex %d\n",i); + /* For leaving out one node of the ssimplex in turn, */ + /* check the inodes match the remaining ssimplex nodes */ + for (kk = 0; kk <= sdi; kk++) { /* ssimplex node being left out */ + int bix = 0; /* ssimplex base node */ + + if (kk == 0) + bix++; + +//printf("~1 anchor node %d = %d\n",j,s->gam.ssi[sdi].spxi[i].goffs[bix]); +//printf("~2 candidate node offsets = %d %d %d\n", +//s->gam.ssi[sdi].spxi[i].offs[0], +//s->gam.ssi[sdi].spxi[i].offs[1], +//s->gam.ssi[sdi].spxi[i].offs[2]); + + for (j = k = 0; j < sdi; j++, k++) { /* Check all inodes[] */ + if (k == kk) + k++; /* Skip the ssimplex node being left out */ + if (inodes[j]->gix != (inodes[0]->gix + s->gam.ssi[sdi].spxi[i].goffs[k] + - s->gam.ssi[sdi].spxi[i].goffs[bix])) { +//printf("~1 not a match\n"); + break; + } +//printf("~1 matched node %d\n",inodes[k]->gix); + } + if (j >= sdi) { /* they all match */ +//printf("~1 all match\n"); + /* Check if kk offset to the base is still in the grid */ + for (e = 0; e < di; e++) { + int doff = ((s->gam.ssi[sdi].spxi[i].offs[kk] >> e) & 1) + - ((s->gam.ssi[sdi].spxi[i].offs[bix] >> e) & 1); + int eflags = G_FL(inodes[0]->fg,e); +//printf("~1 checking dim %d, doff = %d, eflags = %d\n",e,doff,eflags); + if ((doff < 0 && (eflags & 4) && (eflags & 3) < 1) + || (doff > 0 && !(eflags & 4) && (eflags & 3) < 1)) { +//printf("~1 outside grid\n"); + break; /* Offset will take us past edge */ + } + } + if (e >= di) { /* Remaining point is within grid */ + int gix; + + /* Add the remaining node to the onodes */ + if (*nonodes >= aonodes) + return 1; /* Oops - ran out of return space! */ + + gix = inodes[0]->gix + s->gam.ssi[sdi].spxi[i].goffs[kk] + - s->gam.ssi[sdi].spxi[i].goffs[bix]; + onodes[*nonodes] = get_vert(s, gix); + +//printf("~1 Returning node %d\n",(onodes[*nonodes]->gix); +//printf("~1 Value %f %f %f\n", onodes[*nonodes]->p[0], onodes[*nonodes]->p[1], onodes[*nonodes]->p[2]); + (*nonodes)++; + } + } + } + } +//printf("~1 onodes = %d\n",*nonodes); + return 0; +} + +/* ====================================================== */ +/* Search for an existing vertex, and return it. */ +/* If there is no existing edge, create it. */ +/* (This is where we apply the output functions to the grid values) */ +static rvert *get_vert(rspl *s, int gix) { + int f, fdi = s->fdi; + int hash; + rvert *vp = NULL; + + if (gix < 0 || gix >= s->g.no) { /* Assert */ + error("rspl_gam: get_vert got out of range gix %d\n",gix); + } + + /* See if it is in our hash list */ + hash = gix % s->gam.vhsize; + + for (vp = s->gam.verts[hash]; vp != NULL; vp = vp->next) { + if (vp->gix == gix) + break; + } + if (vp == NULL) { /* No such vertex */ + float *fg; + + if ((vp = calloc(1, sizeof(rvert))) == NULL) + error("rspl_gam: get_vert calloc failed"); + + fg = s->g.a + gix * s->g.pss; + vp->n = s->gam.rvert_no++; /* serial number */ + vp->fg = fg; /* Pointer to node float data */ + vp->gix = gix; /* grid index */ + for (f = 0; f < fdi; f++) /* Node output value */ + vp->v[f] = (double)fg[f]; + if (s->gam.outf != NULL) + s->gam.outf(s->gam.cntxf, vp->v, vp->v); /* Apply output lookup */ + + radcoord(s, vp->r, vp->v); /* Compute radial coordinate */ + + /* Add vertex to hash */ + vp->next = s->gam.verts[hash]; + s->gam.verts[hash] = vp; + + /* Add the vertex to the bottom of the list of vertex */ + if (s->gam.vbot == NULL) { + s->gam.vtop = s->gam.vbot = vp; + } else { + s->gam.vbot->list = vp; + s->gam.vbot = vp; + } + } + + return vp; +} + +/* Search for an existing edge containing the given verticies, */ +/* and return it. If there is no existing edge, create it. */ +/* Note that edges have fdi-2 dimensions == fdi-1 verticies. */ +static redge *get_edge(rspl *s, rvert **_vv) { + int i, j, f, fdi = s->fdi; + rvert *vv[MXDO-1]; /* Sorted verticies */ + int gix, hash; + redge *ep = NULL; + + /* Sort the input nodes into normal sub-simplex order */ + /* (We are assuming all the nodes are in the grid) */ + for (i = 0; i < (fdi-1); i++) + vv[i] = _vv[i]; + for (i = 0; i < (fdi-2); i++) { + for (j = i+1; j < (fdi-1); j++) { + if (vv[i]->gix < vv[j]->gix) { + rvert *tt; + tt = vv[i]; vv[i] = vv[j]; vv[j] = tt; + } + } + } + for (gix = i = 0; i < (fdi-1); i++) + gix += vv[i]->gix; +//printf("~1 get edge with nodes = %d %d\n", vv[0]->gix, vv[1]->gix); + + /* See if it is in our hash list */ + hash = gix % s->gam.ehsize; +//printf("~1 get edge gix = %d, hash = %d\n", gix, hash); + + for (ep = s->gam.edges[hash]; ep != NULL; ep = ep->next) { + for (i = 0; i < (fdi-1); i++) { + if (ep->v[i] != vv[i]) { +//printf("~1 verticies don't match\n"); + break; /* No match */ + } + } + if (i >= (fdi-1)) { +//printf("~1 all verticies match\n"); + break; /* All match */ + } + } + if (ep == NULL) { /* No such edge */ + int sm, lg; + + if ((ep = calloc(1, sizeof(redge))) == NULL) + error("rspl_gam: get_edge calloc failed"); + + ep->n = s->gam.redge_no++; /* serial number */ + for (i = 0; i < (fdi-1); i++) + ep->v[i] = vv[i]; /* Vertex */ +printf("~1 new edge %d with nodes = %d %d\n", ep->n, ep->v[0]->gix, ep->v[1]->gix); + + /* Compute plane equation to center of gamut, so */ + /* that we can quickly determine which side a triangle lies on. */ + + /* Compute plane equation */ + if (fdi < 2 || fdi > 3) + error("rspl_gam: plane equation for out dimensions other than 2 or 3 not supported!"); + if (fdi == 2) { + + } else { + double v1[3], v2[3]; + + /* Compute two vectors from the three points */ + for (f = 0; f < fdi; f++) { + v1[f] = ep->v[0]->v[f] - s->gam.cent[f]; + v2[f] = ep->v[1]->v[f] - s->gam.cent[f]; + } + + /* Compute normal to the plane using the cross product */ + ep->pe[0] = ep->v[0]->v[1] * (ep->v[1]->v[2] - s->gam.cent[2]) + + ep->v[1]->v[1] * (s->gam.cent[2] - ep->v[0]->v[2]) + + s->gam.cent[1] * (ep->v[0]->v[2] - ep->v[1]->v[2]); + ep->pe[1] = ep->v[0]->v[2] * (ep->v[1]->v[0] - s->gam.cent[0]) + + ep->v[1]->v[2] * (s->gam.cent[0] - ep->v[0]->v[0]) + + s->gam.cent[2] * (ep->v[0]->v[0] - ep->v[1]->v[0]); + ep->pe[2] = ep->v[0]->v[0] * (ep->v[1]->v[1] - s->gam.cent[1]) + + ep->v[1]->v[0] * (s->gam.cent[1] - ep->v[0]->v[1]) + + s->gam.cent[0] * (ep->v[0]->v[1] - ep->v[1]->v[1]); + ep->pe[3] = - (ep->v[0]->v[0] * (ep->v[1]->v[1] * s->gam.cent[2] - s->gam.cent[1] * ep->v[1]->v[2]) + + ep->v[1]->v[0] * (s->gam.cent[1] * ep->v[0]->v[2] - ep->v[0]->v[1] * s->gam.cent[2]) + + s->gam.cent[0] * (ep->v[0]->v[1] * ep->v[1]->v[2] - ep->v[1]->v[1] * ep->v[0]->v[2])); + + } + + /* Add edge to hash */ + ep->next = s->gam.edges[hash]; + s->gam.edges[hash] = ep; + + /* Add the edge to the bottom of the list of edges */ + if (s->gam.ebot == NULL) { + s->gam.etop = s->gam.ebot = ep; + } else { + s->gam.ebot->list = ep; + s->gam.ebot = ep; + } + } + +printf("~1 returning edge no %d\n",ep->n); + return ep; +} + +/* Check whether a triangle like this already exists */ +/* Return NULL if it doesn't */ +static rtri *check_tri(rspl *s, redge *ep, rvert *_vv) { + int i, j, k, f, fdi = s->fdi; + rvert *vv[MXDO]; /* Sorted verticies */ + int gix, hash; + rtri *tp = NULL; + + /* Copy edge verticies from edge */ + for (i = 0; i < (fdi-1); i++) + vv[i] = ep->v[i]; + vv[i] = _vv; /* And new vertex */ + + /* Sort verticies */ + for (i = 0; i < (fdi-1); i++) { + for (j = i+1; j < fdi; j++) { + if (vv[i]->gix < vv[j]->gix) { + rvert *tv; + tv = vv[i]; vv[i] = vv[j]; vv[j] = tv; + } + } + } + + /* Create hash */ + for (gix = i = 0; i < fdi; i++) + gix += vv[i]->gix; + + /* See if it is in our hash list */ + hash = gix % s->gam.thsize; +//printf("~1 make tri gix = %d, hash = %d\n", gix, hash); + + for (tp = s->gam.tris[hash]; tp != NULL; tp = tp->next) { + for (i = 0; i < fdi; i++) { + if (tp->v[i] != vv[i]) { +//printf("~1 verticies don't match\n"); + break; /* No match */ + } + } + if (i >= fdi) { +//printf("~1 all verticies match\n"); + break; /* All match */ + } + } + + return tp; +} + +/* Create a triangle given an edge with fdi-1 verticies and a grid vertex. */ +/* if inv is set, triangle is upside down wrt to center point, */ +/* and so all the oppositive verticies should be placed on the */ +/* opposite to their natural side. */ +static rtri *make_tri(rspl *s, redge *ep, rvert *_vv, int inv) { + int i, j, k, f, fdi = s->fdi; + rvert *vv[MXDO]; /* Sorted verticies */ + int gix, hash; + rtri *tp = NULL; + +printf("~1 make_tri called\n"); + + /* Copy edge verticies from edge */ + for (i = 0; i < (fdi-1); i++) + vv[i] = ep->v[i]; + vv[i] = _vv; /* And new vertex */ + + /* Sort verticies */ + for (i = 0; i < (fdi-1); i++) { + for (j = i+1; j < fdi; j++) { + if (vv[i]->gix < vv[j]->gix) { + rvert *tv; + tv = vv[i]; vv[i] = vv[j]; vv[j] = tv; + } + } + } + + /* Create hash */ + for (gix = i = 0; i < fdi; i++) + gix += vv[i]->gix; + + /* See if it is in our hash list */ + hash = gix % s->gam.thsize; +printf("~1 make tri gix = %d, hash = %d\n", gix, hash); + + for (tp = s->gam.tris[hash]; tp != NULL; tp = tp->next) { + for (i = 0; i < fdi; i++) { + if (tp->v[i] != vv[i]) { +//printf("~1 verticies don't match\n"); + break; /* No match */ + } + } + if (i >= fdi) { +//printf("~1 all verticies match\n"); + break; /* All match */ + } + } + + if (tp == NULL) { /* No such triangle */ + +//printf("~1 creating a new triangle\n"); + /* Create triangle */ + if ((tp = calloc(1, sizeof(rtri))) == NULL) + error("rspl_gam: make_tri calloc failed"); + + tp->n = s->gam.rtri_no++; /* serial number */ + + /* Copy edge verticies to triangle */ + for (i = 0; i < fdi; i++) + tp->v[i] = vv[i]; + + /* Link all the triangles edges to this triangle */ + printf("~1 triangle nodes = %d %d %d\n", tp->v[0]->gix, tp->v[1]->gix, tp->v[2]->gix); +//printf("~2 triangle vert 0 = %f %f %f\n", tp->v[0]->v[0], tp->f[0]->v[1], tp->f[0]->v[2]); +//printf("~2 triangle vert 1 = %f %f %f\n", tp->v[1]->v[0], tp->f[1]->v[1], tp->f[1]->v[2]); +//printf("~2 triangle vert 2 = %f %f %f\n", tp->v[2]->v[0], tp->f[2]->v[1], tp->f[2]->v[2]); + for (i = 0; i < fdi; i++) { /* For each tri verticy being odd one out */ + rvert *ov, *rr[MXDO-1]; /* Odd verticy, remaining edge verticies */ + int ss; /* Side */ + + ov = tp->v[i]; /* Odd node */ + for (k = j = 0; j < fdi; j++) { + if (i == j) + continue; + rr[k++] = tp->v[j]; /* Remaining nodes */ + } +//printf("~1 edge nodes = %d %d, odd = %d\n", rr[0]->gix, rr[1]->gix, ov->gix); + ep = get_edge(s, rr); + + if (ep->nt >= MXNE) + error("rspl_gam: make_tri run out of triangle space %d in edge",MXNE); + ep->t[ep->nt++] = tp; + + /* See which side of the edge the remaining vertex is */ + ss = eside(s, ep, ov->v); +//printf("~1 node gix %d has side %d to edge %d %d\n", ov->gix, ss, ep->v[0]->gix, ep->v[1]->gix); + if (inv) + ss = 1-ss; + if (ss) { /* +ve side */ + ep->t[ep->npt++] = tp; + } else { + ep->t[ep->nnt++] = tp; + } + } + + /* Add triangle to hash */ + tp->next = s->gam.tris[hash]; + s->gam.tris[hash] = tp; + + /* Add them to the linked list */ + tp->list = s->gam.ttop; + s->gam.ttop = tp; + + } + + return tp; +} + + +/* ====================================================== */ + +/* Create a surface gamut representation. */ +/* Return NZ on error */ +/* Could add more flexibility with: + optional function instead of default radial distance function + option use sub set of output dimensions (ie allow for CMYK->LabK etc. ? ) +*/ +static int +gam_comp_gamut( +rspl *s, +double *cent, /* Optional center of gamut [fdi], default center of out range */ +double *scale, /* Optional Scale of output values in vector to center [fdi], def. 1.0 */ +void (*outf)(void *cntxf, double *out, double *in), /* Optional rspl val -> output value */ +void *cntxf, /* Context for function */ +void (*outb)(void *cntxb, double *out, double *in), /* Optional output value -> rspl val */ +void *cntxb /* Context for function */ +) { + int e, f, di = s->di, fdi = s->fdi; + int i, j, ssdi; + int maxp[MXDO]; /* Grid indexes of maxium function values */ + rvert *fedge[MXDO-1]; /* The first "edge" containing fdi-1 verticies */ + rvert *onodes[50]; /* float pointers of canditate nodes */ + int aonodes = 50; /* Allocated out nodes */ + int nonodes; /* Number of nodes returned */ + + rvert *cnodes[2][50]; /* Negative, positive candidate nodes */ + double rcnodes[2][50]; /* Radius of negative, positive candidate nodes */ + int ncnodes[2]; /* Number of negative, positive candidate nodes */ + + if (fdi < 2 || fdi > di) { + DBG(("gam: gam_comp_gamut called for di = %d, fdi = %d\n", di, fdi)); + return 2; + } + + /* Save output value conversion functions */ + s->gam.outf = outf; + s->gam.cntxf = cntxf; + s->gam.outb = outb; + s->gam.cntxb = cntxb; + + /* Deal with gamut center point, and scale */ + if (cent == NULL) { + double min[MXDO], max[MXDO]; + + s->get_out_range(s, min, max); + if (s->gam.outf != NULL) { + s->gam.outf(s->gam.cntxf, min, min); /* Apply output lookup */ + s->gam.outf(s->gam.cntxf, max, max); /* Apply output lookup */ + } + for (f = 0; f < fdi; f++) + s->gam.cent[f] = 0.5 * (min[f] + max[f]); + } else { + for (f = 0; f < fdi; f++) + s->gam.cent[f] = cent[f]; + } + DBGVI("Gamut center is ", fdi, "%f ", s->gam.cent, "\n") + + if (scale == NULL) { + for (f = 0; f < fdi; f++) + s->gam.scale[f] = 1.0; + } else { + for (f = 0; f < fdi; f++) + s->gam.scale[f] = scale[f]; + } + DBGVI("Gamut scale is ", fdi, "%f ", s->gam.scale, "\n") + + for (ssdi = 1; ssdi <= fdi-1; ssdi++) { + int i, j; + + /* Compute gamut surface sub-simplex geometry info */ + rspl_init_ssimplex_info(s, &s->gam.ssi[ssdi], ssdi); + + /* Filter the sub-simplex geometry to only include subsimplexes that */ + /* use the base vertex, so that there are no duplicates. */ + for (i = j = 0; i < s->gam.ssi[ssdi].nospx; i++) { + if (s->gam.ssi[ssdi].spxi[i].offs[s->gam.ssi[ssdi].sdi] == 0) { + s->gam.ssi[ssdi].spxi[j] = s->gam.ssi[ssdi].spxi[i]; /* Structure copy */ + j++; + } + } + s->gam.ssi[ssdi].nospx = j; + +#ifdef DEBUG + printf("Sub-simplex dim %d out of input %d\n",s->gam.ssi[ssdi].sdi,di); + printf("Number of subsimplex = %d\n",s->gam.ssi[ssdi].nospx); + for (i = 0; i < s->gam.ssi[ssdi].nospx; i++) { + printf("Cube Offset = "); + for (e = 0; e <= s->gam.ssi[ssdi].sdi; e++) + printf("%d ",s->gam.ssi[ssdi].spxi[i].offs[e]); + printf("\n"); + + printf("Grid Offset = "); + for (e = 0; e <= s->gam.ssi[ssdi].sdi; e++) + printf("%d ",s->gam.ssi[ssdi].spxi[i].foffs[e]); + printf("\n"); + printf("\n"); + } +#endif /* DEBUG */ + } + + /* Allocate the vertex hash array */ + if ((s->gam.verts = calloc(VHASHSIZE, sizeof(rvert *))) == NULL) { + DBG(("gam: allocating vertex hash array failed\n")); + return 1; + } + s->gam.vhsize = VHASHSIZE; + + /* Allocate the edge hash array */ + if ((s->gam.edges = calloc(EHASHSIZE, sizeof(redge *))) == NULL) { + DBG(("gam: allocating edge hash array failed\n")); + return 1; + } + s->gam.ehsize = EHASHSIZE; + + /* Allocate the triangle hash array */ + if ((s->gam.tris = calloc(THASHSIZE, sizeof(rtri *))) == NULL) { + DBG(("gam: allocating tris hash array failed\n")); + return 1; + } + s->gam.thsize = THASHSIZE; + + /* Get a starting gid point for surface */ + // ~~99 this isn't right if the point we get is over the ink limit!!!. */ + s->get_out_range_points(s, NULL, maxp); /* Maximum */ +// s->get_out_range_points(s, maxp, NULL); /* Minium */ + + DBG(("Starting point = gix %d/%d\n",maxp[0], s->g.no)); + + /* Now work it up to an fdi-2 sub-simplex, so that we have a */ + /* "triangle edge", and can enter the main loop. */ + // ~~~9 this needs fixing to switch to "maximum angle" calculation + fedge[0] = get_vert(s, maxp[0]); /* grid index of first vertex */ + + if (fdi == 2) { + /* We just need one point, and we've got it */ + + } else if (fdi == 3) { /* Usual case */ + double ba = -1.0; /* Bigest angle */ + /* We just need two points, so find the other points */ + /* that makes the greatest angle to the center */ + + if (get_ssimplex_nodes(s, 1, fedge, aonodes, &nonodes, onodes)) { + error("rspl_gam: get_ssimplex_nodes fatal error - too many nodes?"); + } + if (nonodes == 0) + error("rspl_gam: get_ssimplex_nodes fatal error - retrurned no nodes?"); + +printf("~1 get_ssimplex_nodes returned %d nodes\n",nonodes); +for(i = 0; i < nonodes; i++) +printf(" ~1 %d: %d\n",i,onodes[i]->gix); + + /* Evaluate the choice of verticies to choose the one that */ + /* will best enclose the gamut. (We use a really dumb criteria - maximum radius) */ + for (i = 0; i < nonodes; i++) { + double tt, a, b, c; /* Lenght of sides of triangle squared */ + a = norm33sq(onodes[i]->v, s->gam.cent); + b = norm33sq(onodes[i]->v, fedge[0]->v); + c = norm33sq(fedge[0]->v, s->gam.cent); + tt = acos((b + c - a)/(2.0 * sqrt(b * c))); +printf("~1 node %d angle = %f\n",onodes[i]->gix,tt); + if (tt > ba) { + ba = tt; + fedge[1] = onodes[i]; /* Use candidate with largest gamut as next base */ + } + } +printf("~1 chosen node %d\n",fedge[1]->gix); + + } else if (fdi > 3) { /* General case */ + /* This isn't a correct approach ... */ + + for (ssdi = 1; ssdi <= fdi-2; ssdi++) { + double br = -1.0; /* Bigest radius */ + + DBG(("Working up ssdim %d -> %d\n",ssdi-1, ssdi)); + + if (get_ssimplex_nodes(s, ssdi, fedge, aonodes, &nonodes, onodes)) { + error("rspl_gam: get_ssimplex_nodes fatal error - too many nodes?"); + } + if (nonodes == 0) + error("rspl_gam: get_ssimplex_nodes fatal error - retrurned no nodes?"); + +printf("~1 get_ssimplex_nodes returned %d nodes\n",nonodes); +for(i = 0; i < nonodes; i++) +printf(" ~1 %d: %d\n",i,onodes[i]->gix); + + /* Evaluate the choice of verticies to choose the one that */ + /* will best enclose the gamut. (We use a really dumb criteria - maximum radius) */ + for (i = 0; i < nonodes; i++) { + double tt; + tt = gvprad(s, onodes[i]->v); /* Compute radius */ + if (tt > br) { + br = tt; + fedge[ssdi] = onodes[i]; /* Use candidate with largest gamut as next base */ + } + } +printf("~1 chosen node %d\n",fedge[ssdi]->gix); + } + } + + /* Creat the initial "edge" */ + get_edge(s, fedge); + +printf("~1 Created initial edge\n"); + + { + redge *ep; + + double **A; /* lu value -> baricentric matrix */ + double *B; + int *pivx; + + pivx = ivector(0, fdi-1); /* pixv[fdi] */ + A = dmatrix(0, fdi-1, 0, fdi-1); /* A[fdi][fdi] */ + B = dvector(0, fdi-1); /* B[fdi] */ + + /* The main loop: */ + /* We start with any "edges" that have less than two associated "triangles", */ + /* and evaluate the vertex nodes that can make "triangles" with the "edge". */ + /* We choose the node that will give the greatest slope, and make a "triangle". */ + /* Loop until there are no "edges" with less than two associated "triangles". */ + for (ep = s->gam.etop; ep != NULL; ep = ep->list) { + +printf("~1 expanding from edge no %d\n",ep->n); +printf("~1 edge v1 = %d = %f %f %f\n", ep->v[0]->gix, ep->v[0]->v[0], ep->v[0]->v[1], ep->v[0]->v[2]); +printf("~1 edge v2 = %d = %f %f %f\n", ep->v[1]->gix, ep->v[1]->v[0], ep->v[1]->v[1], ep->v[1]->v[2]); + +// if (ep->npt < 1 || ep->nnt < 1) + { + int ss; + + if (get_ssimplex_nodes(s, fdi-1, ep->v, aonodes, &nonodes, onodes)) { + error("rspl_gam: get_ssimplex_nodes fatal error - too many nodes?"); + } + + /* Clasify the returned nodes as positive or negative side */ + ncnodes[0] = ncnodes[1] = 0; + for (i = 0; i < nonodes; i++) { + double tt; + tt = gvprad(s, onodes[i]->v); /* Compute radius */ + ss = eside(s, ep, onodes[i]->v); /* Side */ +printf("~1 node gix %d has rad %f side %d to edge %d %d\n", onodes[i]->gix, tt, ss, ep->v[0]->gix, ep->v[1]->gix); + +#ifdef NEVER /* This messes things up ? */ + /* Check if this node is already in a triangle with this edge, */ + /* to avoid costly matrix solution check below. */ + if (check_tri(s, ep, onodes[i]) == NULL) +#endif + { + cnodes[ss][ncnodes[ss]] = onodes[i]; + rcnodes[ss][ncnodes[ss]++] = tt; + } + } + + /* Sort them */ + for (ss = 0; ss < 2; ss++) { /* Negative side then positive */ + for (i = 0; i < (ncnodes[ss]-1); i++) { + for (j = i+1; j < ncnodes[ss]; j++) { + if (rcnodes[ss][i] < rcnodes[ss][j]) { + rvert *tt; + double tr; + tt = cnodes[ss][i]; cnodes[ss][i] = cnodes[ss][j]; + cnodes[ss][j] = tt; + tr = rcnodes[ss][i]; rcnodes[ss][i] = rcnodes[ss][j]; + rcnodes[ss][j] = tr; + } + } + } + } + +// ~~1 +for (ss = 0; ss < 2; ss++) { /* Negative side then positive */ + if (ss == 0) + printf("~1 -ve nodes:\n"); + else + printf("~1 +ve nodes:\n"); + for (i = 0; i < ncnodes[ss]; i++) { + printf("~1 node %d, rad %f\n",cnodes[ss][i]->gix,rcnodes[ss][i]); + } +} + +#ifdef VRML_TRACE +{ + vrml *wrl; + int i, j, k; + ECOUNT(gc, MXDIDO, di, 0, s->g.res, 0);/* coordinates */ + DCOUNT(cc, MXDIDO, s->di, 0, 0, 2); /* Surrounding cube counter */ + float *gp; /* Grid point pointer */ + rvert *vp; + rtri *tp; + double col[3], pos[3]; + + if ((wrl = new_vrml("gam_diag.wrl", 1)) == NULL) + error("new_vrml failed\n"); + + /* Display the grid */ + EC_INIT(gc); + for (gp = s->g.a; !EC_DONE(gc); gp += s->g.pss) { + /* Itterate cube from this base */ + DC_INIT(cc) + for (i = 0; !DC_DONE(cc); i++ ) { + float *sp = s->g.a; + + for (e = 0; e < s->di; e++) { /* Input tables */ + int j; + j = gc[e] + cc[e]; + if (j < 0 || j >= s->g.res[e]) { + sp = NULL; /* outside grid */ + break; + } + sp += s->g.fci[e] * j; /* Compute pointer to surrounder */ + } + if (i> 0 && e >= s->di) { + /* Create vector from base to surrounder */ + pos[0] = (double)gp[0]; + pos[1] = (double)gp[1]; + pos[2] = (double)gp[2]; + if (s->gam.outf != NULL) + s->gam.outf(s->gam.cntxf, pos, pos); /* Apply output lookup */ + wrl->add_vertex(wrl, pos); + pos[0] = (double)sp[0]; + pos[1] = (double)sp[1]; + pos[2] = (double)sp[2]; + if (s->gam.outf != NULL) + s->gam.outf(s->gam.cntxf, pos, pos); /* Apply output lookup */ + wrl->add_vertex(wrl, pos); + } + DC_INC(cc); + } + EC_INC(gc); + } + wrl->make_lines(wrl, 2); + wrl->clear(wrl); + + /* Display the current triangles transparently */ + if (s->gam.ttop != NULL) { + for (vp = s->gam.vtop; vp != NULL; vp = vp->list) { + wrl->add_vertex(wrl, vp->v); + } + + /* Set the triangles */ + for (tp = s->gam.ttop; tp != NULL; tp = tp->list) { + int ix[3]; + ix[0] = tp->v[0]->n; + ix[1] = tp->v[1]->n; + ix[2] = tp->v[2]->n; + wrl->add_triangle(wrl, ix); + } + +// wrl->make_triangles(wrl, 0.3, NULL); + wrl->make_triangles(wrl, 0.0, NULL); + wrl->clear(wrl); + } + + /* Show the active edge as a red cone */ + col[0] = 1.0; col[1] = 0.0; col[2] = 0.0; /* Red */ + wrl->add_cone(wrl, ep->v[0]->v, ep->v[1]->v, col, 1.0); +printf("~1 edge v1 = %d = %f %f %f\n", ep->v[0]->gix, ep->v[0]->v[0], ep->v[0]->v[1], ep->v[0]->v[2]); +printf("~1 edge v2 = %d = %f %f %f\n", ep->v[1]->gix, ep->v[1]->v[0], ep->v[1]->v[1], ep->v[1]->v[2]); + + /* Show the candidate verticies */ + for (ss = 0; ss < 2; ss++) { + for (i = 0; i < ncnodes[ss]; i++) { /* +ve */ + if (ss) { + col[0] = 0.0; col[1] = 1.0; col[2] = 0.0; /* Green +ve */ + } else { + col[0] = 0.0; col[1] = 0.0; col[2] = 1.0; /* Blue -ve */ + } + wrl->add_marker(wrl, cnodes[ss][i]->v, col, 1.0); + } + } + wrl->del(wrl); + getchar(); +} +#endif + /* See if we can make a triangle */ + for (ss = 0; ss < 2; ss++) { /* For -ve and +ve sides */ + int ii, si; + int sta, end, inc; + rvert **nods; + double rip; /* Row interchange parity */ + +printf("~1 direction = %d\n",ss); +#ifdef NEVER + if (( ss && ep->npt >= 1) /* Not looking for a positive node */ + || (!ss && ep->nnt >= 1)) { /* Not looking for a negative node */ +printf("~1 no need to look for node in this direction\n"); + continue; + } +#endif + + if (ncnodes[ss] > 0) { /* There are nodes in wanted direction */ + si = ss; /* use wanted direction nodes */ + sta = 0; /* Start at max radius node */ + end = ncnodes[si]; /* End and least radius node */ + inc = 1; /* Increment */ + nods = cnodes[si]; /* +ve nodes */ +printf("~1 Looking for biggest angle, inc = %d\n",inc); +#ifdef NEVER /* Convex tracing ? */ + } else if (ncnodes[1-ss] > 0) { /* There are opposited direction nodes */ + si = 1-ss; /* use opposite direction nodes */ + sta = ncnodes[si]-1; /* Start at min radius node */ + end = -1; /* end and max radius node */ + inc = -1; /* Decrement */ + nods = cnodes[si]; +printf("~1 Looking for smallest angle, inc = %d\n",-1); +#endif /* NEVER */ + } else { +printf("~1 No points to search\n"); + continue; + } + ii = 0; + if (ncnodes[si] > 1) { /* If there is more than one to choose from */ + /* Go through each candidate in the most likely order */ + for (ii = sta; ii != end; ii += inc) { + +printf("~1 Candidate %d: node %d\n",ii,nods[ii]->gix); + /* Create the baricentric conversion for this candidate */ + for (f = 0; f < fdi; f++) /* The center point */ + A[f][0] = s->gam.cent[f] - nods[ii]->v[f]; + for (j = 0; j < (fdi-1); j++) { /* The edge points */ + for (f = 0; f < fdi; f++) + A[f][j+1] = ep->v[j]->v[f] - nods[ii]->v[f]; + } + + if (lu_decomp(A, fdi, pivx, &rip)) { +printf("~1 lu_decomp failed\n"); +for (f = 0; f < fdi; f++) /* The center point */ + A[f][0] = s->gam.cent[f] - nods[ii]->v[f]; +for (j = 0; j < (fdi-1); j++) { /* The edge points */ + for (f = 0; f < fdi; f++) + A[f][j+1] = ep->v[j]->v[f] - nods[ii]->v[f]; +} +printf("~1 A = \n"); +printf("~1 %f %f %f\n", A[0][0], A[0][1], A[0][2]); +printf("~1 %f %f %f\n", A[1][0], A[1][1], A[1][2]); +printf("~1 %f %f %f\n", A[2][0], A[2][1], A[2][2]); + warning("lu_decomp failed"); + continue; + } + + /* Test the remainder candidates against this simplex */ + for (i = sta; i != end; i += inc) { + if (i == ii) + continue; + +printf("~1 Test %d: node %d\n",i,nods[i]->gix); + for (f = 0; f < fdi; f++) /* The candidate point */ + B[f] = nods[i]->v[f] - nods[ii]->v[f]; + lu_backsub(A, fdi, pivx, B); + +#ifdef NEVER +printf("~1 baricentric = %f %f %f %f\n",B[0],B[1],B[2],1.0-B[0]-B[1]-B[2]); +{ +double tt[3], B3; +/* Check baricentric */ +B3 = 1.0-B[0]-B[1]-B[2]; +for (f = 0; f < fdi; f++) + tt[f] = 0.0; +for (f = 0; f < fdi; f++) + tt[f] += B[0] * s->gam.cent[f]; +for (f = 0; f < fdi; f++) + tt[f] += B[1] * ep->v[0]->v[f]; +for (f = 0; f < fdi; f++) + tt[f] += B[2] * ep->v[1]->v[f]; +for (f = 0; f < fdi; f++) + tt[f] += B3 * nods[ii]->v[f]; +printf("~1 target point %f %f %f\n", (double)nods[i][0], (double)nods[i][1], (double)nods[i][2]); +printf("~1 barice check %f %f %f\n", tt[0],tt[1],tt[2]); +} +#endif /* NEVER */ + if ((inc == 1 && B[0] < -EPS) /* other point is at higher angle */ + || (inc == -1 && B[0] > EPS)) { /* other point is at lower angle */ +printf("~1 candidate isn't best\n"); + break; + } + } + if (i == end) { +printf("~1 candidate IS the best\n"); + break; /* Candidate is at highest angle */ + } + } + if (ii == end) { +printf("~1Inconsistent candidate ordering\n"); + error("Inconsistent candidate ordering"); + } + } else { +printf("~1 there are only %d nodes, so don't search them\n",ncnodes[si]); + } +printf("~1 Making triangle with %d: node %d\n",ii,nods[ii]->gix); + make_tri(s, ep, nods[ii], inc == -1); + } + if (ep->npt < 1 || ep->nnt < 1) { + if (ep->npt < 1) + warning("###### Unable to locate +ve triangles for edge %d\n",ep->n); + if (ep->nnt < 1) + warning("###### Unable to locate -ve triangles for edge %d\n",ep->n); + } + } + } + + free_ivector(pivx, 0, fdi-1); + free_dmatrix(A, 0, fdi-1, 0, fdi-1); + free_dvector(B, 0, fdi-1); + + /* Dump out the edges */ + for (ep = s->gam.etop; ep != NULL; ep = ep->list) { + +printf("~1 edge no %d, npt = %d, nnt = %d\n",ep->n, ep->npt, ep->nnt); + } + } + return 0; +} + +/* ====================================================== */ +/* Gamut rspl setup functions */ + +/* Called by rspl initialisation */ +void +init_gam(rspl *s) { + + /* Methods */ + s->comp_gamut = gam_comp_gamut; +} + +/* Free up all the gamut info */ +void free_gam( +rspl *s /* Pointer to rspl grid */ +) { + int i; + int ssdi; + rvert *vp, *nvp; + redge *ep, *nep; + rtri *tp, *ntp; + + for (ssdi = 1; ssdi <= s->fdi-1; ssdi++) + rspl_free_ssimplex_info(s, &s->gam.ssi[ssdi]); + + /* Free the verticies */ + for (vp = s->gam.vtop; vp != NULL; vp = nvp) { + nvp = vp->list; + free(vp); + } + free(s->gam.verts); + + /* Free the edges */ + for (ep = s->gam.etop; ep != NULL; ep = nep) { + nep = ep->list; + free(ep); + } + free(s->gam.edges); + + /* Free the triangles */ + for (tp = s->gam.ttop; tp != NULL; tp = ntp) { + ntp = tp->list; + free(tp); + } + free(s->gam.tris); +} + + +/* Create the gamut surface structure. */ +/* Current this is: + + not rationalized to be non-overlapping + culled to eliminate overlaps + Have ink limits applied + +*/ + +/* ~~~ we need space ing gam to: + + store radial coordinates of output values + mark nodes as being visited. + store surface triangle structures + hold tadial output bounding acceleration structures + + + There is a lot in common with rev here. + we need to know input channel significance (what's black) + plus ink limit info. + we're going to create black generation information used by rev. +*/ + +/* ---------------------- */ +/* rspl gam diagnostic */ + +/* Diagnostic */ +void rspl_gam_plot(rspl *s, char *name) { + int i; + double col[3] = { 0.7, 0.7, 0.7 }; + rvert *vp, *nvp; + rtri *tp, *ntp; + + vrml *wrl; + + if ((wrl = new_vrml(name, 1, 0)) == NULL) + error("new_vrml failed\n"); + + /* Set the verticies */ + for (vp = s->gam.vtop; vp != NULL; vp = vp->list) { + wrl->add_vertex(wrl, 0, vp->v); + } + + /* Set the triangles */ + for (tp = s->gam.ttop; tp != NULL; tp = ntp) { + int ix[3]; + ntp = tp->list; + ix[0] = tp->v[0]->n; + ix[1] = tp->v[1]->n; + ix[2] = tp->v[2]->n; + wrl->add_triangle(wrl, 0, ix); + } + + wrl->make_triangles(wrl, 0, 0.0, NULL); +// wrl->make_triangles(wrl, 0, 0.0, col); + + wrl->del(wrl); +} + +#undef DEBUG +#undef DBGV +#undef DBG +#define DBGV(xxx) +#define DBG(xxx) + + + + + + diff --git a/rspl/gam.h b/rspl/gam.h new file mode 100644 index 0000000..0892e4d --- /dev/null +++ b/rspl/gam.h @@ -0,0 +1,131 @@ +#ifndef RSPL_GAM_H +#define RSPL_GAM_H + +/* + * Argyll Color Correction System + * Multi-dimensional regularized spline data structure + * + * Precise gamut surface, gamut pruning, ink limiting and K min/max + * support routine.s + * + * Author: Graeme W. Gill + * Date: 2008/11/21 + * + * Copyright 1999 - 2008 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. + * + * Latest simplex/linear equation version. + */ + +/* In practice the gamut is only ever computed for 2D or 3D output */ +/* dimensions. The gamut surface is composed of simplexes ("triangles") */ +/* of dimensions fdi-1, with fdi verticies */ + +#include "llist.h" + +#define MXNE 16 /* Maximum number of edges per triangle allowed */ + +#define VHASHSIZE 6863 /* Vertex hash index size */ +#define EHASHSIZE 2659 /* Edge hash index size */ +#define THASHSIZE 2659 /* Triangle hash index size */ + +/* ----------------------------------------- */ + +/* Vertex node - all vertex nodes are part of the grid */ +struct _rvert { + struct _rvert *next; /* Hash linked list */ + int n; /* Index number of vertex */ + int gix; /* Grid index - used to identify and order nodes */ + float *fg; /* Pointer to grid data */ + +// double p[MXDO]; /* Poistion of node */ + double v[MXDO]; /* Output value of node */ + double r[MXDO]; /* Radial coordinates */ + struct _rvert *list; /* Next in linked list */ +}; typedef struct _rvert rvert; + +/* ------------------------------------ */ + +/* An edge shared by one or more triangle in the mesh */ +struct _redge { + struct _redge *next; /* Hash linked list */ + int n; /* Serial number */ +// float *f[MXDO-1]; /* fdi-1 grid verticies of edge in base simplex order. */ + struct _rvert *v[MXDO-1]; /* fdi-1 Verticies of edge in base simplex order. */ + double pe[MXDO+1]; /* Plane equation for edge for side of edge testing. */ + /* fdi for normal + constant */ + int nt; /* Total number of triangles that share this edge */ + int npt; /* Positive side triangles that share this edge */ + int nnt; /* Negative side triangles that share this edge */ + struct _rtri *t[MXNE]; /* nt triangles edge is part of */ + + struct _redge *list; /* Next in linked list */ +}; typedef struct _redge redge; + +/* ------------------------------------ */ + +/* A "triangle" (simplex dimension fdi-1) in the surface mesh */ +struct _rtri { + struct _rtri *next; /* Hash linked list */ + int n; /* Serial number */ +// float *f[MXDO]; /* fdi grid verticies in gix order */ + struct _rvert *v[MXDO]; /* fdi verticies in gix order */ + +// struct _redge *e[((MXDO+1) * MXDO)/2]; /* Edges in vertex sorted order */ +// double mix[2][MXDO]; /* nn: Bounding box min and max */ + + struct _rtri *list; /* Next in linked list */ +}; typedef struct _rtri rtri; + +/* ----------------------------------------- */ +/* Gamut info stored in main rspl function */ +struct _gam_struct { + int inited; + + double cent[MXDO]; /* Center of radial distance calculation */ + double scale[MXDO]; /* Scale of radial distance calculation */ + + void (*outf)(void *cntxf, double *out, double *in); /* Optional rspl val -> output value */ + void *cntxf; /* Context for function */ + void (*outb)(void *cntxb, double *out, double *in); /* Optional output value -> rspl val */ + void *cntxb; /* Context for function */ + + ssxinfo ssi[MXDO-1]; /* Sub-simplex information for sdi from 0..fdi-1 */ + + int rvert_no; /* Number of rverts allocated */ + int vhsize; /* Vertex hash list size */ + rvert **verts; /* Hash list, NULL if not allocated */ + rvert *vtop; /* Top of list of verticies */ + rvert *vbot; /* Bottom of list of verticies */ + + int redge_no; /* Number of redges allocated */ + int ehsize; /* Edge hash list size */ + redge **edges; /* Edges between the triangles linked list */ + redge *etop; /* Top of list of edges */ + redge *ebot; /* Bottom of list of edges */ + + int rtri_no; /* Number of rtris allocated */ + int thsize; /* Triangle hash list size */ + rtri **tris; /* Hash list, NULL if not allocated */ + rtri *ttop; /* Surface triangles linked list */ + +}; typedef struct _gam_struct gam_struct; + +#endif /* RSPL_GAM_H */ + + + + + + + + + + + + + + diff --git a/rspl/mlbs.c b/rspl/mlbs.c new file mode 100644 index 0000000..bbe3865 --- /dev/null +++ b/rspl/mlbs.c @@ -0,0 +1,605 @@ + +/* + * Argyll Color Correction System + * + * Scattered Data Interpolation with multilevel B-splines library. + * This can be used by rspl, or independently by any other routine. + * + * Author: Graeme W. Gill + * Date: 2001/1/1 + * + * Copyright 2000 - 2001 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 is from the paper + * "Scattered Data Interpolation with Multilevel B-Splines" + * by Seungyong Lee, George Wolberg and Sung Yong Shin, + * IEEE Transactions on Visualisation and Computer Graphics + * Vol. 3, No. 3, July-September 1997, pp 228. + */ + +/* TTBD: + * + * Figure out why the results are rubbish ? + * + * Can this be adapted to be adaptive in it smoothness, + * like the non-linear regularized spline stuff that Don Bone used ? + * + * Get rid of error() calls - return status instead + */ + +#include +#include +#include +#include +#include +#include +#if defined(__IBMC__) && defined(_M_IX86) +#include +#endif +#include "numlib.h" +#include "mlbs.h" + +#ifndef NUMSUP_H +void error(char *fmt, ...), warning(char *fmt, ...); +#endif + +static void delete_mlbs(mlbs *p); +static int lookup_mlbs(mlbs *p, co *c); + +/* Allocate a new empty mlbs */ +mlbs *alloc_mlbs( +int di, /* Input dimensionality */ +int fdi, /* Output dimesionality */ +int res, /* Target resolution */ +double smf /* Smoothing factor */ +) { + mlbs *p; + if ((p = (mlbs *)malloc(sizeof(mlbs))) == NULL) + error("Malloc mlbs failed"); + + p->di = di; + p->fdi = fdi; + p->tres = res; + p->smf = smf; + p->s = NULL; + + p->lookup = lookup_mlbs; + p->del = delete_mlbs; + + return p; +} + +static void delete_slbs(slbs *s); + +static void delete_mlbs(mlbs *p) { + + if (p != NULL) { + delete_slbs(p->s); + free(p); + } +} + +/* Create a new empty slbs */ +static slbs *new_slbs( +mlbs *p, /* Parent mlbs */ +int res /* Resolution of this slbs */ +) { + slbs *s; + int e, f; + double *_lat, *lat; /* Latice base address */ + int ix, oe, oo[MXDI]; /* Neighborhood offset index, counter */ + + if ((s = (slbs *)malloc(sizeof(slbs))) == NULL) + error("Malloc slbs failed"); + + s->p = p; + s->res = res; + + for (s->lsize = p->fdi, s->nsize = 1, e = 0; e < p->di; e++) { + s->coi[e] = s->lsize; /* (double) increment in this input dimension */ + s->lsize *= (res + 2); /* Latice in 1D +/- 1 */ + s->nsize *= 4; /* Neighborhood of 4 */ + } + + if ((s->_lat = (double *)malloc(s->lsize * sizeof(double))) == NULL) + error("Malloc slbs latice failed"); + + /* Compute the base address */ + for (s->loff = 0, e = 0; e < p->di; e++) { + s->loff += s->coi[e]; /* Offset by 1 in each input dimension */ + } + s->lat = s->_lat + s->loff; + + /* Figure the cell width */ + for (e = 0; e < p->di; e++) + s->w[e] = (p->h[e] - p->l[e])/(res-1.0); + + /* Setup neighborhood cache info */ + if ((s->n = (neigh *)malloc(s->nsize * sizeof(neigh))) == NULL) + error("Malloc slbs neighborhood failed"); + + for (oe = 0; oe < p->di; oe++) + oo[oe] = 0; + + for(ix = oe = 0; oe < p->di; ix++) { + int xo; + for (xo = e = 0; e < p->di; e++) { + s->n[ix].c[e] = oo[e]; + xo += s->coi[e] * oo[e]; /* Accumulate latice offset */ + } + s->n[ix].xo = xo; + s->n[ix].w = 0.0; + + /* Increment destination offset counter */ + for (oe = 0; oe < p->di; oe++) { + if (++oo[oe] <= 3) /* Counting from 0 ... 3 */ + break; + oo[oe] = 0; + } + } + + return s; +} + +/* Destroy a slbs */ +static void delete_slbs(slbs *s) { + if (s != NULL) { + free(s->_lat); + free(s->n); + free(s); + } +} + +/* Dump the 2D -> 1D contents of an slbs */ +static void dump_slbs(slbs *s) { + int e, f; + int ce, co[MXDI]; /* latice counter */ + mlbs *p = s->p; /* Parent object */ + + /* Init the counter */ + for (ce = 0; ce < p->di; ce++) + co[ce] = -1; + ce = 0; + + f = 0; + while(ce < p->di) { + double v; + int off = 0; /* Latice offset */ + for (e = 0; e < p->di; e++) { + off += co[e] * s->coi[e]; /* Accumulate latice offset */ + } + v = s->lat[off + f]; /* Value of this latice point */ + + printf("Latice at [%d][%d] = %f\n",co[1],co[0],v); + + /* Increment the latice counter */ + for (ce = 0; ce < p->di; ce++) { + if (++co[ce] <= s->res) /* Counting from -1 ... s->res */ + break; + co[ce] = -1; + } + } +} + +/* Initialise an slbs with a linear approximation to the scattered data */ +static void linear_slbs( +slbs *s +) { + int i, e, f; + mlbs *p = s->p; /* Parent object */ + double **A; /* A matrix holding scattered data points */ + double *B; /* B matrix holding RHS & solution */ + + /* Allocate the matricies */ + B = dvector(0, p->npts-1); + A = dmatrix(0, p->npts-1, 0, p->di); + + /* For each output dimension, solve the linear equation coeficients */ + for (f = 0; f < p->fdi; f++) { + int ce, co[MXDI]; /* latice counter */ + + /* Init A[][] with the scattered data points positions */ + /* Also init B[] with the value for this output dimension */ + for (i = 0; i < p->npts; i++) { + for (e = 0; e < p->di; e++) + A[i][e] = p->pts[i].p[e]; + A[i][e] = 1.0; + B[i] = p->pts[i].v[f]; + } + + /* Solve the equation A.x = b using SVD */ + /* (The w[] values are thresholded for best accuracy) */ + /* Return non-zero if no solution found */ + if (svdsolve(A, B, p->npts, p->di+1) != 0) + error("SVD least squares failed"); + /* A[][] will have been changed, and B[] holds the p->di+1 coefficients */ + + /* Use the coefficients to initialise the slbs values */ + for (ce = 0; ce < p->di; ce++) + co[ce] = -1; + ce = 0; + + while(ce < p->di) { + double v = B[p->di]; /* Constant */ + int off = 0; /* Latice offset */ + for (e = 0; e < p->di; e++) { + double lv; + lv = p->l[e] + s->w[e] * co[e]; /* Input value for this latice location */ + v += B[e] * lv; + off += co[e] * s->coi[e]; /* Accumulate latice offset */ + } + s->lat[off + f] = v; /* Value of this latice point */ + + /* Increment the latice counter */ + for (ce = 0; ce < p->di; ce++) { + if (++co[ce] <= s->res) /* Counting from -1 ... s->res */ + break; + co[ce] = -1; + } + } + } + free_dmatrix(A, 0, p->npts-1, 0, p->di); + free_dvector(B, 0, p->npts-1); +} + +/* Do a latice refinement - upsample the current */ +/* source latice to the destination latice. */ +static void refine_slbs( +slbs *ds, /* Destination slbs */ +slbs *ss /* Source slbs */ +) { + mlbs *p = ss->p; /* Parent object */ + int ce, co[MXDI]; /* Source coordinate counter */ + int six; /* Source index */ + int dix; /* destination index */ + static double _wt[5] = { 1.0/8.0, 4.0/8.0, 6.0/8.0, 4.0/8.0, 1.0/8.0 }; + static double *wt = &_wt[2]; /* 1D Distribution weighting */ + + /* Zero the destination latice before accumulating values */ + for (dix = 0; dix < ds->lsize; dix++) + ds->_lat[dix] = 0.0; + + /* Now for each source latice entry, add weighted portions */ + /* to the associated destination points */ + + /* Init the source coordinate counter */ + for (ce = 0; ce < p->di; ce++) + co[ce] = -1; + ce = 0; + six = -ss->loff; + + while(ce < p->di) { + int oe, oo[MXDI]; /* Destination offset counter */ + +//printf("Source coord %d %d, offset %d, value %f\n",co[0], co[1], six, ss->lat[six]); + /* calc destination index, and init offest counter */ + for (dix = oe = 0; oe < p->di; oe++) { + oo[oe] = -2; + dix += co[oe] * 2 * ds->coi[oe]; /* Accumulate dest offset */ + } + oe = 0; + +//printf("Dest coord %d %d\n",co[0] * 2, co[1] * 2); + /* For all the offsets from the destination point */ + while(oe < p->di) { + int e, f, dixo; /* Destination index offset */ + double w = 1.0; /* Weighting */ + +//printf("dest offset %d %d\n",oo[0], oo[1]); + /* Compute dest index offset, and check that we are not outside the destination */ + for (dixo = e = 0; e < p->di; e++) { + int x = co[e] * 2 + oo[e]; /* dest coord */ + dixo += oo[e] * ds->coi[e]; /* Accumulate dest offset */ +//printf("x[%d] = %d\n",e, x); + w *= wt[oo[e]]; /* Compute distribution weighting */ + if (x < -1 || x > ds->res) + break; /* No good */ + } + if (e >= p->di) { /* We are within the destination latice */ +//if ((co[0] * 2 + oo[0]) == 0 && (co[1] * 2 + oo[1]) == 0) { +//printf("Source coord %d %d, offset %d, value %f\n",co[0], co[1], six, ss->lat[six]); +//printf("Dest coord %d %d ix %d, weight %f\n",co[0] * 2 + oo[0], co[1] * 2 + oo[1], dix+dixo, w); +//} + + for (f = 0; f < p->fdi; f++) { /* Distribute weighted values */ + double v = ss->lat[six + f]; +//if ((co[0] * 2 + oo[0]) == 0 && (co[1] * 2 + oo[1]) == 0) +//printf("Value being dist %f, weighted value %f\n", v, v * w); + ds->lat[dix + dixo + f] += v * w; + } + } + + /* Increment destination offset counter */ + for (oe = 0; oe < p->di; oe++) { + if (++oo[oe] <= 2) /* Counting from -2 ... +2 */ + break; + oo[oe] = -2; + } + } + + /* Increment the source index and coordinat counter */ + six += p->fdi; + for (ce = 0; ce < p->di; ce++) { + if (++co[ce] <= ss->res) /* Counting from -1 ... ss->res */ + break; + co[ce] = -1; + } + } +} + +/* Compute the Cubic B-spline weightings for a given t */ +void basis(double b[4], double t) { + double _t3, _t2, _t1, _3t3, _3t2, _3t1, _6t2; + + _t1 = t/6.0; + _t2 = _t1 * _t1; + _t3 = _t2 * _t1; + _3t1 = 3.0 * _t1; + _3t2 = 3.0 * _t2; + _3t3 = 3.0 * _t3; + _6t2 = 6.0 * _t2; + + b[0] = - _t3 + _3t2 - _3t1 + 1.0/6.0; + b[1] = _3t3 - _6t2 + 4.0/6.0; + b[2] = -_3t3 + _3t2 + _3t1 + 1.0/6.0; + b[3] = _t3; +} + + +/* Improve an slbs to make it closer to the scattered data */ +static void improve_slbs( +slbs *s +) { + int i, e, f; + mlbs *p = s->p; /* Parent object */ + double *delta; /* Delta accumulation */ + double *omega; /* Omega accumulation */ + + /* Allocate temporary accumulation arrays */ + if ((delta = (double *)calloc(sizeof(double), s->lsize)) == NULL) + error("Malloc slbs temp latice failed"); + delta += s->loff; + if ((omega = (double *)calloc(sizeof(double), s->lsize)) == NULL) + error("Malloc slbs temp latice failed"); + omega += s->loff; + + /* For each scattered data point */ + for (i = 0; i < p->npts; i++) { + int ix; /* Latice index of base of neighborhood */ + double b[MXDI][4]; /* B-spline basis factors for each dimension */ + double sws; /* Sum of all the basis factors squared */ + double ve[MXDO]; /* Current output value error */ + int nn; /* Neighbor counter */ + + /* Figure out our neighborhood */ + for (ix = e = 0; e < p->di; e++) { + int x; + double t, sp, fp; + sp = (p->pts[i].p[e] - p->l[e])/s->w[e]; /* Scaled position */ + fp = floor(sp); + x = (int)(fp - 1.0); /* Grid coordinate */ + ix += s->coi[e] * x; /* Accume latice offset */ + t = sp - fp; /* Spline parameter */ + basis(b[e], t); /* Compute basis function values */ + } + + /* Compute the grid basis weight functions, */ + /* the sum of the weights squared, and the current */ + /* output value estimate. */ + for (f = 0; f < p->fdi; f++) + ve[f] = p->pts[i].v[f]; /* Target output value */ + for (sws = 0.0, nn = 0; nn < s->nsize; nn++) { + double w; + for (w = 1.0, e = 0; e < p->di; e++) + w *= b[e][s->n[nn].c[e]]; + s->n[nn].w = w; /* cache weighting */ + sws += w * w; + for (f = 0; f < p->fdi; f++) + ve[f] -= w * s->lat[ix + s->n[nn].xo + f]; /* Subtract current aprox value */ + } +//printf("Error at point %d = %f\n",i,ve[0]); + + /* Accumulate the delta and omega factors */ + /* for this resolutions improvement. */ + for (nn = 0; nn < s->nsize; nn++) { + double ws, ww, w = s->n[nn].w; + int xo = ix + s->n[nn].xo; /* Latice offset */ + ww = w * w; + ws = ww * w/sws; /* Scale factor for delta */ + omega[xo] += ww; /* Accumulate omega */ + for (f = 0; f < p->fdi; f++) + delta[xo + f] += ws * ve[f]; /* Accumulate delta */ +//printf("Distributing omega %f to %d %d\n",ww,s->n[nn].c[0],s->n[nn].c[1]); +//printf("Distributing delta %f to %d %d\n",ws * ve[0],s->n[nn].c[0],s->n[nn].c[1]); + } + } + + omega -= s->loff; /* Base them back to -1 corner */ + delta -= s->loff; + + /* Go through the delta and omega arrays, */ + /* compute and add the refinements to the current */ + /* B-spline control latice. */ + for (i = 0; i < s->lsize; i++) { + double om = omega[i]; + if (om != 0.0) { + for (f = 0; f < p->fdi; f++) + s->_lat[i] += delta[i + f]/om; +//printf("Adjusting latice index %d by %f to give %f\n",i, delta[i]/om, s->_lat[i]); + } + } + + /* Done with temporary arrays */ + free(omega); + free(delta); +} + +/* Return the interpolated value for a given point */ +/* Return NZ if input point is out of range */ +static int lookup_mlbs( +mlbs *p, +co *c /* Point to interpolate */ +) { + slbs *s = p->s; + int e, f; + int ix; /* Latice index of base of neighborhood */ + double b[MXDI][4]; /* B-spline basis factors for each dimension */ + int nn; /* Neighbor counter */ + + /* Figure out our neighborhood */ + for (ix = e = 0; e < p->di; e++) { + int x; + double t, sp, fp; + sp = c->p[e]; + if (sp < p->l[e] || sp > p->h[e]) + return 1; + sp = (sp - p->l[e])/s->w[e]; /* Scaled position */ + fp = floor(sp); + x = (int)(fp - 1.0); /* Grid coordinate */ + ix += s->coi[e] * x; /* Accume latice offset */ + t = sp - fp; /* Spline parameter */ + basis(b[e], t); /* Compute basis function values */ + } + + /* Compute the the current output value. */ + for (f = 0; f < p->fdi; f++) + c->v[f] = 0.0; + for (nn = 0; nn < s->nsize; nn++) { + double w; + for (w = 1.0, e = 0; e < p->di; e++) + w *= b[e][s->n[nn].c[e]]; + for (f = 0; f < p->fdi; f++) + c->v[f] += w * s->lat[ix + s->n[nn].xo + f]; /* Accume spline value */ + } + + return 0; +} + +/* Take a list of scattered data points, */ +/* and setup the mlbs. */ +static void set_mlbs( +mlbs *p, /* mlbs to set up */ +dpnts *pts, /* scattered data points and weights */ +int npts, /* number of scattered data points */ +double *l, /* Input data range, low (May be NULL) */ +double *h /* Input data range, high (May be NULL) */ +) { + int res; + int i, e, f; + slbs *s0 = NULL, *s1; + + /* Establish the input data range */ + for (e = 0; e < p->di; e++) { + if (l == NULL) + p->l[e] = 1e60; + else + p->l[e] = l[e]; + if (h == NULL) + p->h[e] = -1e60; + else + p->h[e] = h[e]; + } + for (i = 0; i < npts; i++) { + for (e = 0; e < p->di; e++) { + if (pts[i].p[e] < p->l[e]) + p->l[e] = pts[i].p[e]; + if (pts[i].p[e] > p->h[e]) + p->h[e] = pts[i].p[e]; + } + } + + /* Make point data available during init */ + p->pts = pts; + p->npts = npts; + + /* Create an initial slbs */ + res = 2; + if ((s1 = new_slbs(p, 2)) == NULL) + error("new_slbs failed"); + + /* Set it up with a linear first approximation */ + linear_slbs(s1); +//dump_slbs(s1); + + /* Build up the resolution */ + for (; res < p->tres;) { + + res = 2 * res -1; + +printf("~1 doing resolution %d\n",res); + delete_slbs(s0); + s0 = s1; + if ((s1 = new_slbs(p, res)) == NULL) + error("new_slbs failed"); + + refine_slbs(s1, s0); +//dump_slbs(s1); + improve_slbs(s1); + } + + delete_slbs(s0); + p->s = s1; /* Final resolution */ + + /* We can't assume point data will stick around */ + p->pts = NULL; + p->npts = 0; +} + + +/* Create a new empty mlbs */ +mlbs *new_mlbs( +int di, /* Input dimensionality */ +int fdi, /* Output dimesionality */ +int res, /* Minimum final resolution */ +dpnts *pts, /* scattered data points and weights */ +int npts, /* number of scattered data points */ +double *l, /* Input data range, low (May be NULL) */ +double *h, /* Input data range, high (May be NULL) */ +double smf /* Smoothing factor */ +) { + mlbs *p; + + if ((p = alloc_mlbs(di, fdi, res, smf)) == NULL) + return p; + + set_mlbs(p, pts, npts, l, h); + + return p; +} + +#ifndef NUMSUP_H +/* Basic printf type error() and warning() routines */ + +void +error(char *fmt, ...) +{ + va_list args; + + fprintf(stderr,"stest: Error - "); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); + exit (-1); +} + +void +warning(char *fmt, ...) +{ + va_list args; + + fprintf(stderr,"stest: Warning - "); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); +} + +#endif /* NUMSUP_H */ + diff --git a/rspl/mlbs.h b/rspl/mlbs.h new file mode 100644 index 0000000..4678cfd --- /dev/null +++ b/rspl/mlbs.h @@ -0,0 +1,77 @@ + +/* + * Argyll Color Correction System + * + * Scattered Data Interpolation with multilevel B-splines library. + * This can be used by rspl, or independently by any other routine. + * + * Author: Graeme W. Gill + * Date: 2001/1/1 + * + * Copyright 2000 - 2001 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 is from the paper + * "Scattered Data Interpolation with Multilevel B-Splines" + * by Seungyong Lee, George Wolberg and Sung Yong Shin, + * IEEE Transactions on Visualisation and Computer Graphics + * Vol. 3, No. 3, July-September 1997, pp 228. + */ + +#include "rspl.h" /* Define some common elements */ + +/* Neighborhood latice cache data */ +typedef struct { + int c[MXDI]; /* Coordinate */ + int xo; /* Offset into slbs latice */ + double w; /* B-spline basis weight */ +} neigh; + +/* Structure that represents a resolution level of B-splines */ +struct _slbs { + struct _mlbs *p; /* Parent structure */ + int res; /* Basic resolution */ + int coi[MXDI]; /* Double increment for each input dimension into latice */ + double *lat; /* Control latice, extending from +/- 1 from 0..res-1 */ + double *_lat; /* Allocation base of lat */ + int lsize, loff; /* Number of doubles in _lat, offset of lat from _lat */ + double w[MXDI]; /* Input data cell width */ + neigh *n; /* Neighborhood latice cache */ + int nsize; /* Number of n entries */ +}; typedef struct _slbs slbs; + +/* Structure that represents the whole scattered interpolation state */ +struct _mlbs { + int di; /* Input dimensions */ + int fdi; /* Output dimensions */ + int tres; /* Target resolution */ + double smf; /* Smoothing factor */ + int npts; /* Number of data points */ + dpnts *pts; /* Coordinate points and weights (valid while creating) */ + double l[MXDI], h[MXDI]; /* Input data range, cell width */ + slbs *s; /* Current B-spline latice */ + + int (*lookup)(struct _mlbs *p, co *c); + + void (*del)(struct _mlbs *p); + +}; typedef struct _mlbs mlbs; + + +/* Create a new empty mlbs */ +mlbs *new_mlbs( +int di, /* Input dimensionality */ +int fdi, /* Output dimesionality */ +int res, /* Minimum final resolution */ +dpnts *pts, /* scattered data points and weights */ +int npts, /* number of scattered data points */ +double *l, /* Input data range, low (May be NULL) */ +double *h, /* Input data range, high (May be NULL) */ +double smf /* Smoothing factor */ +); + diff --git a/rspl/opt.c b/rspl/opt.c new file mode 100644 index 0000000..d5a70ce --- /dev/null +++ b/rspl/opt.c @@ -0,0 +1,725 @@ + +/* + * Argyll Color Correction System + * Multi-dimensional regularized splines + * optimiser based initialiser. + * + * Author: Graeme W. Gill + * Date: 2001/5/16 + * + * Copyright 1996 - 2001 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 file contains an rspl initialiser that */ +/* works from an optimisation function callback. */ +/* It is intended to support the creation of optimised */ +/* color separations, although this usage is not hard coded */ +/* here. */ + +/* TTBD: + * + * !!! fix so that this can also be used for smoothed + * inversion, ie. PCS -> DevN, as well as + * separation PseudoCMY/K -> DevN. + * + * Plan: + * Have additional callback function used for invert, + * called at grid initialisation that initialised + * the target values to fixed PCS values. + * For separation, these are dynamic, and adjusted by + * the usual optimisation callback. + * (Or can the usual callback figure out when the + * initial initialisation is needed ?) + * + * Need to return average/extrapolated surround values + * on the edge, just like fit, so smoothness can be + * evaluated in inversion. + * Provide another mechanism for sep to know + * it is on the edge of the grid, and should expand + * gamut if possible. + * + * Get rid of error() calls - return status instead + */ + +#include +#include +#include +#include +#include +#if defined(__IBMC__) && defined(_M_IX86) +#include +#endif + +#include "rspl_imp.h" +#include "numlib.h" +#include "counters.h" /* Counter macros */ + +#undef DEBUG + +/* Tuning parameters */ +#define TOL 1e-6 /* Tollerance of result */ +#define GRATIO 1.7 /* Multi-grid ratio */ +#define SMOOTH 80.0 /* Set nominal smoothing (1.0) */ + +#undef NEVER +#define ALWAYS + +/* Implemented in rspl.c: */ +extern void alloc_grid(rspl *s); + +extern int is_mono(rspl *s); + +/* Convention is to use: + i to index grid points u.a + n to index data points d.a + e to index position dimension di + f to index output function dimension fdi + j misc and cube corners + k misc + */ + +/* ================================================= */ +/* Structure to hold temporary data for multi-grid caliculations */ +/* Only used in this file. */ +struct _omgtp { + rspl *s; /* Associated rspl */ + + /* Configuration data */ + int tdi; /* Target guide values dimensionality (must be <= MXDI) */ + /* (Typically the Lab aim values corresponding to this pseudo device value) */ + int adi; /* Additional grid point data allowance (must be <= 2 * MXDI) */ + /* (Typically black locus range) */ + + double (*func)(void *fdata, double *inout, double *surav, int first, double *cw); + /* Optimisation function */ + void *fdata; /* Pointer to opaque data needed by callback function */ + + struct { + double cw[MXDI]; /* Curvature weight factor for each dimension */ + } sf; + + /* Grid points data */ + struct { + int res[MXDI]; /* Single dimension grid resolution for each dimension */ + int bres, brix; /* Biggest resolution and its index */ + double mres; /* Geometric mean res[] */ + int no; /* Total number of points in grid = res ^ di */ + datai l,h,w; /* Grid low, high, grid cell width */ + + + double *a; /* Grid point data */ + /* Array is res ^ di entries double[fdi+tdi+adi] */ + /* The output values start at offset 0, the */ + /* target data values start at offset fdi, and */ + /* the additional data starts at offset fdi+tdi. */ + int pss; /* Grid point structure size = fdi+tdi */ + + /* Grid array offset lookups */ + int ci[MXDI]; /* Grid coordinate increments for each dimension */ + int fci[MXDI]; /* Grid coordinate increments for each dimension in doubles */ + int *hi; /* 2^di Combination offset for sequence through cube. */ + int *fhi; /* Combination offset for sequence through cube of */ + /* 2^di points, starting at base, in floats */ + int a_hi[DEF2MXDI]; /* Default allocation for *hi */ + int a_fhi[DEF2MXDI];/* Default allocation for *fhi */ + } g; + +}; typedef struct _omgtp omgtp; + +/* ================================================= */ +static omgtp *new_omgtp(rspl *s, int tdi, int adi, int mxres, + double (*func)(void *fdata, double *inout, double *surav, int first, double *cw), + void *fdata); +static void free_omgtp(omgtp *m); +static void solve_gres(omgtp *m, double tol); +static void init_soln(omgtp *m1, omgtp *m2); +static void init_fsoln(omgtp *m, double **vdata); + +/* Initialise the regular spline from the optimisation callback function. */ +/* The target data is auxiliary data used to "target" the optimisation */ +/* callback function. */ +/* The callback function arguments are as follows: + * void *fdata, + * double *inout, Pointers to fdi+tdi+adi values for the grid point being optimised. + * double *surav, Pointers to fdi+tdi values which are the average of the + * neighbors of this grid point. Pointer will NULL if this + * is a surface grid point. + * int first, Flag, NZ if this is the first optimisation of this point. + * double *cw the (grid resolution) curvature weighting factor for each dimension + * + * Returns value is the "error" for this point. + */ + +int +opt_rspl_imp( + rspl *s, /* this */ + int flags, /* Combination of flags */ + int tdi, /* Dimensionality of target data */ + int adi, /* Additional per grid point data allocation */ + double **vdata, /* di^2 array of function, target and additional values to init */ + /* array corners with. Corners are ordered with lowest index */ + /* dimension changing most rapidly. */ + double (*func)(void *fdata, double *inout, double *surav, int first, double *cw), + /* Optimisation function */ + void *fdata, /* Opaque data needed by function */ + datai glow, /* Grid low scale - NULL = default 0.0 */ + datai ghigh, /* Grid high scale - NULL = default 1.0 */ + int gres[MXDI], /* Spline grid resolution for each dimension */ + datao vlow, /* Data value low normalize, NULL = default 0.0 */ + datao vhigh /* Data value high normalize - NULL = default 1.0 */ +) { +// int di = s->di + int fdi = s->fdi; + int i, e, f; +// int n; + +#if defined(__IBMC__) && defined(_M_IX86) + _control87(EM_UNDERFLOW, EM_UNDERFLOW); +#endif + /* set debug level */ + s->debug = (flags >> 24); + + if (flags & RSPL_VERBOSE) /* Turn on progress messages to stdout */ + s->verbose = 1; + if (flags & RSPL_NOVERBOSE) /* Turn off progress messages to stdout */ + s->verbose = 0; + + s->symdom = (flags & RSPL_SYMDOMAIN) ? 1 : 0; /* Turn on symetric smoothness with gres */ + + if (tdi >= MXDI) + error("rspl, opt: tdi %d > MXDI %d",tdi,MXDI); + + if (adi >= (2 * MXDI)) + error("rspl, opt: adi %d > 2 * MXDI %d",adi,2 * MXDI); + + /* transfer desired grid range to structure */ + s->g.mres = 1.0; + s->g.bres = 0; + for (e = 0; e < s->di; e++) { + if (gres[e] < 2) + error("rspl: grid res must be >= 2!"); + s->g.res[e] = gres[e]; /* record the desired resolution of the grid */ + s->g.mres *= gres[e]; + if (gres[e] > s->g.bres) { + s->g.bres = gres[e]; + s->g.brix = e; + } + + if (glow == NULL) + s->g.l[e] = 0.0; + else + s->g.l[e] = glow[e]; + + if (ghigh == NULL) + s->g.h[e] = 1.0; + else + s->g.h[e] = ghigh[e]; + } + s->g.mres = pow(s->g.mres, 1.0/e); /* geometric mean */ + + /* compute width of each grid cell */ + for (e = 0; e < s->di; e++) { + s->g.w[e] = (s->g.h[e] - s->g.l[e])/(double)(gres[e]-1); + } + + /* record low and width data normalizing factors */ + for (f = 0; f < s->fdi; f++) { + if (vlow == NULL) + s->d.vl[f] = 0.0; + else + s->d.vl[f] = vlow[f]; + + if (vhigh == NULL) + s->d.vw[f] = 1.0 - s->d.vl[f]; + else + s->d.vw[f] = vhigh[f] - s->d.vl[f]; + } + + /* Do optimisation of data points */ + { + int nn, res, sres; + double fres, gratio = GRATIO; + float *gp; /* rspl grid pointer */ + double *mgp; /* Temp muligrid pointer */ + omgtp *m, *om = NULL; + + sres = 4; /* Start at initial grid res of 4 */ + if (sres > s->g.bres) + sres = s->g.bres; /* Drop to target resolution */ + + /* Calculate the resolution scaling ratio */ + if (((double)s->g.bres/(double)sres) <= gratio) { + gratio = (double)s->g.bres/(double)sres; + nn = 1; + } else { /* More than one needed */ + nn = (int)((log((double)s->g.bres) - log((double)sres))/log(gratio) + 0.5); + gratio = exp((log((double)s->g.bres) - log((double)sres))/(double)nn); + } + + /* Do each grid resolution in turn */ + for (fres = (double)sres, res = sres;;) { + m = new_omgtp(s, tdi, adi, res, func, fdata); + + if (om == NULL) { + init_fsoln(m, vdata); /* Set the initial targets & values from corners */ + } else { + init_soln(m, om); /* Scale targets & values from from previous resolution */ + free_omgtp(om); /* Free previous grid res solution */ + } + solve_gres(m, TOL * s->g.mres/res); /* Use itterative */ + + if (res >= s->g.mres) + break; /* Done */ + + fres *= gratio; + res = (int)(fres + 0.5); + if ((res + 1) >= s->g.mres) /* If close enough */ + res = (int)s->g.mres; + om = m; + } + + /* Allocate the final rspl grid data */ + alloc_grid(s); + + /* Transfer result in x[] to appropriate grid point value */ + for (gp = s->g.a, mgp = m->g.a, i = 0; i < s->g.no; gp += s->g.pss, mgp += m->g.pss, i++) + for (f = 0; f < fdi; f++) + gp[f] = (float)mgp[f]; + free_omgtp(m); + } + + /* Return non-mono check */ + return is_mono(s); +} + +/* - - - - - - - - - - - - - - - - - - - - - - - -*/ +/* omgtp routines */ + +/* Create a new omgtp. */ +/* Grid data will be uninitialised */ +static omgtp *new_omgtp( + rspl *s, /* associated rspl */ + int tdi, /* Target dimensions */ + int adi, /* Additional per grid point data allocation */ + int mxres, /* maximum resolution to create */ + double (*func)(void *fdata, double *inout, double *surav, int first, double *cw), + /* Optimisation function */ + void *fdata /* Opaque data needed by function */ +) { + omgtp *m; + int di = s->di, fdi = s->fdi; +// int dno = s->d.no; + int gno; + int e, g, i; +// int f, n, j, k; + + /* Allocate a structure */ + if ((m = (omgtp *) calloc(1, sizeof(omgtp))) == NULL) + error("rspl: malloc failed - omgtp"); + + /* Allocate space for cube offset arrays */ + m->g.hi = m->g.a_hi; + m->g.fhi = m->g.a_fhi; + if ((1 << di) > DEF2MXDI) { + if ((m->g.hi = (int *) malloc(sizeof(int) * (1 << di))) == NULL) + error("rspl omgtp malloc failed - hi[]"); + if ((m->g.fhi = (int *) malloc(sizeof(int) * (1 << di))) == NULL) + error("rspl omgtp malloc failed - fhi[]"); + } + + /* General stuff */ + m->s = s; + m->tdi = tdi; + m->adi = adi; + m->func = func; + m->fdata = fdata; + + /* Grid related */ + m->g.mres = 1.0; + m->g.bres = 0; + for (gno = 1, e = 0; e < di; e++) { + if (mxres >= s->g.res[e]) /* Shoose smaller of gres and target res */ + m->g.res[e] = s->g.res[e]; + else + m->g.res[e] = mxres; + + m->g.mres *= m->g.res[e]; + if (m->g.res[e] > m->g.bres) { + m->g.bres = m->g.res[e]; + m->g.brix = e; + } + gno *= m->g.res[e]; + } + m->g.mres = pow(m->g.mres, 1.0/e); /* geometric mean */ + m->g.no = gno; + + m->g.pss = fdi+tdi+adi; /* doubles for each output value + target data + additional data */ + + /* record high, low limits, and width of each grid cell */ + for (e = 0; e < s->di; e++) { + m->g.l[e] = s->g.l[e]; + m->g.h[e] = s->g.h[e]; + m->g.w[e] = (s->g.h[e] - s->g.l[e])/(double)(m->g.res[e]-1); + } + + /* Compute index coordinate increments into linear grid for each dimension */ + /* ie. 1, gres, gres^2, gres^3 */ + for (m->g.ci[0] = 1, e = 1; e < di; e++) { + m->g.ci[e] = m->g.ci[e-1] * m->g.res[e-1]; /* In grid points */ + m->g.fci[e] = m->g.ci[e] * m->g.pss; /* In doubles */ + } + + /* Compute index offsets from base of cube to other corners */ + for (m->g.hi[0] = 0, e = 0, g = 1; e < di; g *= 2, e++) { + for (i = 0; i < g; i++) { + m->g.hi[g+i] = m->g.hi[i] + m->g.ci[e]; /* In grid points */ + m->g.fhi[g+i] = m->g.hi[g+i] * m->g.pss; /* In doubles */ + } + } + + /* Allocate space for grid */ + if ((m->g.a = (double *) malloc(sizeof(double) * gno * m->g.pss)) == NULL) + error("rspl malloc failed - multi-grid points"); + + /* Compute curvature weighting for matching intermediate resolutions. */ + /* cw[] is multiplied by the grid curvature_errors_squared[] to keep */ + /* the same ratio with the sum of data position errors squared. */ + for (e = 0; e < di; e++) { + double rsm; /* Resolution smoothness factor */ + if (s->symdom) + rsm = m->g.res[e]-1.0; /* Relative final grid size */ + else + rsm = m->g.mres-1.0; /* Relative mean final grid size */ + rsm = pow(rsm,8.0/di); + rsm /= pow(200.0,8.0/di)/pow(200.0, 4.0); /* (Scale factor to adjust power) */ + + m->sf.cw[e] = (s->smooth * SMOOTH)/(rsm * (double)di); + } + + return m; +} + +/* Completely free an omgtp */ +static void free_omgtp(omgtp *m) { + + free((void *)m->g.a); + + /* Free structure */ + if (m->g.hi != m->g.a_hi) { + free(m->g.hi); + free(m->g.fhi); + } + free((void *)m); +} + +/* Set the first targets & values from the corner values. */ +static void init_fsoln( +omgtp *m, /* Destination */ +double **vdata /* di^2 array of function and target values to init array corners with. */ + /* Corners are ordered with lowest index dimension changing most rapidly. */ + /* (Function data at index 0, target data at index fdi) */ +) { + rspl *s = m->s; + int di = s->di; + int fdi = s->fdi; + int gno = m->g.no; + int gres_1[MXDI]; + int e, n; + double *gp; /* Pointer to dest g.a[] grid cube base */ + ECOUNT(gc, MXDIDO, di, 0, m->g.res, 0); /* Counter for output points */ + double *gw; /* weight for each grid cube corner */ + double a_gw[DEF2MXDI]; /* default allocation for gw */ + + gw = a_gw; + if ((1 << di) > DEF2MXDI) { + if ((gw = (double *) malloc(sizeof(double) * (1 << di))) == NULL) + error("rspl malloc failed - interp_rspl_nl"); + } + + for (e = 0; e < di; e++) + gres_1[e] = m->g.res[e]-1; + + /* For all output grid points (could skip non-surface points ?) */ + EC_INIT(gc); + for (n = 0, gp = m->g.a; n < gno; n++, gp += m->g.pss) { + double we[MXDI]; /* 1.0 - Weight in each dimension */ + + /* Figure out the pointer to the grid data and its weighting */ + { + gp = m->g.a; /* Base of output array */ + for (e = 0; e < di; e++) + we[e] = (double)gc[e]/gres_1[e]; /* 1.0 - weight */ + } + + /* Compute corner weights needed for interpolation */ + { + int i, g; + gw[0] = 1.0; + for (e = 0, g = 1; e < di; g *= 2, e++) { + for (i = 0; i < g; i++) { + gw[g+i] = gw[i] * we[e]; + gw[i] *= (1.0 - we[e]); + } + } + } + + /* Compute the output values */ + { + int i, f; + double w = gw[0]; + double *d = vdata[0]; + + for (f = 0; f < m->g.pss; f++) /* Base of cube */ + gp[f] = w * d[f]; + + for (i = 1; i < (1 << di); i++) { /* For all other corners of cube */ + w = gw[i]; /* Strength reduce */ + d = vdata[i]; + for (f = 0; f < fdi; f++) + gp[f] += w * d[f]; + } + + } + + EC_INC(gc); + } + + if (gw != a_gw) + free(gw); +} + + +/* Transfer a device and target values solution from one omgtp to another. */ +/* (We assume that they are for the same problem) */ +static void init_soln( + omgtp *m1, /* Destination */ + omgtp *m2 /* Source */ +) { + rspl *s = m1->s; + int di = s->di; + int gno = m1->g.no; + int gres1_1[MXDI]; + int gres2_1[MXDI]; + int e, n; + double *a; /* Pointer to dest g.a[] grid cube base */ + ECOUNT(gc, MXDIDO, di, 0, m1->g.res, 0); /* Counter for output points */ + double *gw; /* weight for each grid cube corner */ + double a_gw[DEF2MXDI]; /* default allocation for gw */ + + gw = a_gw; + if ((1 << di) > DEF2MXDI) { + if ((gw = (double *) malloc(sizeof(double) * (1 << di))) == NULL) + error("rspl malloc failed - interp_rspl_nl"); + } + + for (e = 0; e < di; e++) { + gres1_1[e] = m1->g.res[e]-1; + gres2_1[e] = m2->g.res[e]-1; + } + + /* For all output grid points */ + EC_INIT(gc); + for (n = 0, a = m1->g.a; n < gno; n++, a += m1->g.pss) { + double we[MXDI]; /* 1.0 - Weight in each dimension */ + double *gp; /* Pointer to source g.a[] grid cube base */ + + /* Figure out which grid cell the point falls into */ + { + double t; + int mi; + gp = m2->g.a; /* Base of solution array */ + for (e = 0; e < di; e++) { + t = (double)gc[e] * gres2_1[e]/gres1_1[e]; + mi = (int)floor(t); /* Grid coordinate */ + if (mi < 0) /* Limit to valid cube base index range */ + mi = 0; + else if (mi >= gres2_1[e]) + mi = gres2_1[e]-1; + gp += mi * m2->g.fci[e]; /* Add Index offset for grid cube base in dimen */ + we[e] = t - (double)mi; /* 1.0 - weight */ + } + } + + /* Compute corner weights needed for interpolation */ + { + int i, g; + gw[0] = 1.0; + for (e = 0, g = 1; e < di; g *= 2, e++) { + for (i = 0; i < g; i++) { + gw[g+i] = gw[i] * we[e]; + gw[i] *= (1.0 - we[e]); + } + } + } + + /* Compute the output values */ + { + int i, f; + double w = gw[0]; + double *d = gp + m2->g.fhi[0]; + + for (f = 0; f < m1->g.pss; f++) /* Base of cube */ + a[f] = w * d[f]; + + for (i = 1; i < (1 << di); i++) { /* For all other corners of cube */ + w = gw[i]; /* Strength reduce */ + d = gp + m2->g.fhi[i]; + for (f = 0; f < m1->g.pss; f++) + a[f] += w * d[f]; + } + + } + EC_INC(gc); + } + + if (gw != a_gw) + free(gw); +} + +/* - - - - - - - - - - - - - - - - - - - -*/ +static double one_itter(omgtp *m, int first); + +/* Itterate the optimisation functions until we are happy things have settled */ +static void +solve_gres( +omgtp *m, +double tol +) { + int i; + double dtol = tol * 0.1; /* Delta tol limit */ + double ltt, tt; + + ltt = 1.0; + tt = tol * 10.0; + + for (i = 0; i < 500; i++) { + if (i == 0) + tt = one_itter(m, 1); + + ltt = tt; + tt = one_itter(m, 0); + + if (tt < tol || (ltt - tt) < dtol) /* Get within 0.1 % */ + break; + } +} + +/* Optimise the points values and (optionally) targets */ +/* Use Red/Black order, return total error after this itteration. */ +/* Return the total optimisation error */ +static double +one_itter( +omgtp *m, +int first /* Flag, NZ if this is the first pass at this resolution */ +) { + int di = m->s->di, fdi = m->s->fdi; + int tdi = m->tdi; + int i, e, f; + int gc[MXDI]; + int *gres = m->g.res; + int gres_1[MXDI]; + DCOUNT(cc, MXDIDO, di, -1, -1, 2); /* Surrounding cube counter */ + double *gpp; /* Current grid point pointer */ + double ssum[MXDO+MXDI+2*MXDI]; /* Pointer to surrounding average values */ + double *surav; /* Surrounding average values */ + double awt; /* Average weight */ + double terr = 0.0; /* Total error */ + int surf; /* This point is on the surface */ + + for (e = 0; e < di; e++) { + gc[e] = 0; /* init coords */ + gres_1[e] = gres[e] - 1; + } + + /* Until done */ + for (;;) { + + /* See if we are on the surface */ + surf = 0; + gpp = m->g.a; + for (e = 0; e < di; e++) { + gpp += m->g.fci[e] * gc[e]; /* Compute pointer to current point */ + + if (gc[e] == 0 || gc[e] == gres_1[e]) + surf = 1; + } + + surav = NULL; + if (!surf) { + + for (f = 0; f < (fdi + tdi); f++) + ssum[f] = 0.0; + awt = 0.0; + + /* Average the 3x3 surrounders */ + DC_INIT(cc) + for (i = 0; !DC_DONE(cc); i++ ) { + double *gp = m->g.a; + + for (e = 0; e < di; e++) { + int j; + j = gc[e] + cc[e]; + if (j < 0 && j > gres_1[e]) { /* outside */ + break; + } + gp += m->g.fci[e] * j; /* Compute pointer to surrounder */ + } + if (e >= di) { /* We have a valid point */ + for (f = 0; f < (fdi + tdi); f++) + ssum[f] += gp[f]; + awt += 1.0; + } + DC_INC(cc); + } + if (awt > 0.0) { /* Compute the average */ + for (f = 0; f < (fdi + tdi); f++) + ssum[f] /= awt; + surav = ssum; + } + } + + /* Call optimisation function */ + terr += m->func(m->fdata, gpp, surav, first, m->sf.cw); + + /* Increment index in red/black order */ + for (e = 0; e < di; e++) { + if (e == 0) { + gc[0] += 2; /* Inc coordinate by 2 */ + } else { + gc[e] += 1; /* Inc coordinate */ + } + if (gc[e] < gres[e]) + break; /* No carry */ + gc[e] -= gres[e]; /* Reset coord */ + + if ((gres[e] & 1) == 0) { /* Compensate for odd grid */ + gc[0] ^= 1; /* XOR lsb */ + } + } + /* Stop on reaching 0 */ + for(e = 0; e < di; e++) + if (gc[e] != 0) + break; + if (e == di) + break; /* Finished */ + } + + return terr; +} + + + + + + + + + + + + + diff --git a/rspl/rev.c b/rspl/rev.c new file mode 100644 index 0000000..237dbe3 --- /dev/null +++ b/rspl/rev.c @@ -0,0 +1,6608 @@ + +/* + * Argyll Color Correction System + * Multi-dimensional regularized spline data structure + * + * Reverse interpolation support code. + * + * Author: Graeme W. Gill + * Date: 30/1/00 + * + * Copyright 1999 - 2008 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. + * + * Latest simplex/linear equation version. + */ + +/* TTBD: + + Should fix the clipping case so that a direction weighting + funtion can be applied. This should be used just like + the perceptual case to increase L* constance for dark + colors. Will this stuff up the geometric consistency though ? + [ See fill_nncell(), fill_nncell() and users of calc_fwd_nn_cell_list(), + ie. nnearest_clip_solve(), clipn_setsort() etc. ] + The SVD least squares computation case makes this hard to change ? + Would have to feed in a weighting function, or can it be general ? + + Allow function callback to set auxiliary values for + flag RSPL_AUXLOCUS. + How to pass enough info back to aux_compute() ? + + Should auxil return multiple solutions if it finds them ??? + + */ + +/* TTBD: + Get rid of error() calls - return status instead + + Need to add a hefty overview and explanation of + how all this works, before I forget it ! + + ie: + + Basic function requirements: exact, auxil, locus, clip + + Fwd cell - reverse cell list lookup + + Basic layout di -> fdi + auxils + ink limit + + Basic search strategy + + Sub Simplex decomposition & properties + + How each type of function finds solutions + Sub-simplex dimensionality & dof + target dim & dof + Linear algebra choices. + + How final solutions are chosen + + */ + +/* PROBLEMS: + + Sometimes the aux locus doesn't correspond exactly to + the inversion :- ie. one locus segment is returned, + yet the inversion can't return a solution with + a particular aux target that lies within that segment. + (1150 near black, k ~= 0.4). + + + */ + +#include +#include +#include +#include +#include +#include + +#ifdef NT +# ifdef WINVER +# undef WINVER +# endif +# define WINVER 0x0500 /* We need 2k features */ +# include +#else +# include +# ifdef __APPLE__ +# include +# include +# include +# endif +#endif + +#define INKSCALE 5000.0 /* For ink limit weighting to fudge SVD least squares solution */ + +#include "rspl_imp.h" +#include "numlib.h" +#include "sort.h" /* Heap sort */ +#include "counters.h" /* Counter macros */ + +//#define DMALLOC_GLOBALS +//#include "dmalloc.h" +//#undef DMALLOC_GLOBALS + +#undef DEBUG1 /* Higher level code */ +#undef DEBUG2 /* Lower level code */ + +/* Debug memory usage accounting */ +#ifdef NEVER +#ifdef NEVER +int thissz, lastsz = -1; +#define INCSZ(s, bbb) { \ + (s)->rev.sz += (bbb); \ + (s)->rev.thissz = (s)->rev.sz/1000000; \ + if ((s)->rev.thissz != (s)->rev.lastsz) fprintf(stderr,"~1 0x%x: %s, %d: rev size = %d Mbytes, delta %d, limit %d\n",((int)(s) >> 8) & 0xf, __FILE__, __LINE__,(s)->rev.thissz,(bbb),(s)->rev.max_sz/1000000); \ + (s)->rev.lastsz = (s)->rev.thissz; \ + } +#define DECSZ(s, bbb) { \ + (s)->rev.sz -= (bbb); \ + (s)->rev.thissz = (s)->rev.sz/1000000; \ + if ((s)->rev.thissz != (s)->rev.lastsz) fprintf(stderr,"~1 0x%x: %s, %d: rev size = %d Mbytes, delta %d, limit %d\n",((int)(s) >> 8) & 0xf, __FILE__, __LINE__,(s)->rev.thissz,-(bbb),(s)->rev.max_sz/1000000); \ + (s)->rev.lastsz = (s)->rev.thissz; \ + } +#else +#define INCSZ(s, bbb) (s)->rev.sz += (bbb); \ + fprintf(stderr,"%s, %d: rev.sz += %d\n",__FILE__, __LINE__, bbb) +#define DECSZ(s, bbb) (s)->rev.sz -= (bbb); \ + fprintf(stderr,"%s, %d: rev.sz -= %d\n",__FILE__, __LINE__, bbb) +#endif +#else +#define INCSZ(s, bbb) (s)->rev.sz += (bbb) +#define DECSZ(s, bbb) (s)->rev.sz -= (bbb) +#endif + +/* Set STATS in rev.h */ + +#define DOSORT /* Cell sort */ + +/* Print a vectors value */ +#define DBGVI(text, dim, out, vec, end) \ +{ int pveci; \ + printf("%s",text); \ + for (pveci = 0 ; pveci < (dim); pveci++) \ + printf(out,(vec)[pveci]); \ + printf(end); \ +} + +/* Print a matrix value */ +#define DBGMI(text, rows, cols, out, mat, end) \ +{ int pveci, pvecr; \ + printf("%s",text); \ + for (pvecr = 0 ; pvecr < (rows); pvecr++) { \ + for (pveci = 0 ; pveci < (cols); pveci++) \ + printf(out,(mat)[pvecr][pveci]); \ + if ((pvecr+1) < (rows)) \ + printf("\n"); \ + } \ + printf(end); \ +} + +/* Do an arbitrary printf */ +#define DBGI(text) printf text ; + +#undef DEBUG +#undef DBG +#undef DBGV +#undef DBGM + +#undef NEVER +#define ALWAYS + +#ifdef DEBUG1 +#undef DBGS +#undef DBG +#undef DBGV +#undef DBGM +#define DEBUG +#define DBGS(xxx) xxx +#define DBG(xxx) DBGI(xxx) +#define DBGV(xxx) DBGVI xxx +#define DBGM(xxx) DBGMI xxx +#else +#undef DEBUG +#undef DBGS +#undef DBG +#undef DBGV +#undef DBGM +#define DBGS(xxx) +#define DBG(xxx) +#define DBGV(xxx) +#define DBGM(xxx) +#endif + +/* Debug string routines */ +static char *pcellorange(cell *c); + +/* Convention is to use: + i to index grid points u.a + n to index data points d.a + e to index position dimension di + f to index output function dimension fdi + j misc and cube corners + k misc + */ + +#define EPS (2e-6) /* 2e-6 Allowance for numeric error */ + +static void make_rev(rspl *s); +static void init_revaccell(rspl *s); + +static cell *get_rcell(schbase *b, int ix, int force); +static void uncache_rcell(revcache *r, cell *cp); +#define unget_rcell(r, cp) uncache_rcell(r, cp) /* These are the same */ +static void invalidate_revaccell(rspl *s); +static int decrease_revcache(revcache *rc); + +/* ====================================================== */ + +static schbase *init_search(rspl *s, int flags, double *av, int *auxm, + double *v, double *cdir, co *cpp, int mxsoln, enum ops op); +static void adjust_search(rspl *s, int flags, double *av, enum ops op); +static schbase *set_search_limit(rspl *s, double (*limit)(void *vcntx, double *in), + void *lcntx, double limitv); +static void set_lsearch(rspl *s, int e); +static void free_search(schbase *b); + +static int *calc_fwd_cell_list(rspl *s, double *v); + +static int *calc_fwd_nn_cell_list(rspl *s, double *v); + +static void init_line_eq(schbase *b, double st[MXRO], double de[MXRO]); +static int *init_line(rspl *s, line *l, double st[MXRO], double de[MXRO]); +static int *next_line_cell(line *l); + +static void search_list(schbase *b, int *rip, unsigned int tcount); + +static void clear_limitv(rspl *s); + +static double get_limitv(schbase *b, int ix, float *fcb, double *p); + +#ifdef STATS +static char *opnames[6] = { "exact", "clipv", "clipn", "auxil", "locus" }; +#endif /* STATS */ + +#define INF_DIST 1e38 /* Stands for infinite "current best" distance */ + +/* ====================================================== */ +/* Globals that track overall usage of reverse cache to aportion memory */ +/* This is incremented for rspl with di > 1 when rev.rev_valid != 0 */ +size_t g_avail_ram = 0; /* Total maximum memory to be used */ +size_t g_test_ram = 0; /* Amount of memory that has been tested to be allocatable */ +int g_no_rev_cache_instances = 0; +rev_struct *g_rev_instances = NULL; + +/* ------------------------------------------------------ */ +/* Retry allocation routines - if the malloc fails, */ +/* try reducing the cache size and trying again */ +/* (This won't catch the problem if it occurs in a malloc outside rev) */ + +/* When a malloc fails, reduce the maximum cache to */ +/* it's current allocation minus the given size. */ +static void rev_reduce_cache(size_t size) { + rev_struct *rsi; + size_t ram; + + /* Compute how much ram is currently allocated */ + for (ram = 0, rsi = g_rev_instances; rsi != NULL; rsi = rsi->next) + ram += rsi->sz; + + if (size > ram) + error("rev_reduce_cache: run out of rev virtual memory!"); + +//printf("~1 size = %d, g_test_ram = %d\n",size,g_test_ram); +//printf("~1 rev: Reducing cache because alloc of %d bytes failed. Reduced from %d to %d MB\n", +//size, g_avail_ram/1000000, (ram - size)/1000000); + ram = g_avail_ram = ram - size; + + /* Aportion the memory, and reduce the cache allocation to match */ + ram /= g_no_rev_cache_instances; + for (rsi = g_rev_instances; rsi != NULL; rsi = rsi->next) { + revcache *rc = rsi->cache; + + rsi->max_sz = ram; + while (rc->nunlocked > 0 && rsi->sz > rsi->max_sz) { + if (decrease_revcache(rc) == 0) + break; + } +//printf("~1 rev instance ram = %d MB\n",rsi->sz/1000000); + } +//fprintf(stdout, "%c~~1 There %s %d rev cache instance%s with %d Mbytes limit\n", +// cr_char, +// g_no_rev_cache_instances > 1 ? "are" : "is", +// g_no_rev_cache_instances, +// g_no_rev_cache_instances > 1 ? "s" : "", +// ram/1000000); +} + +/* Check that the requested allocation plus 20 M Bytes */ +/* can be allocated, and if not, reduce the rev-cache limit. */ +/* This is so as to detect running out of VM before */ +/* we actually run out and (on OS X) avoid emitting a warning. */ +static void rev_test_vram(size_t size) { + char *a1; +#ifdef __APPLE__ + int old_stderr, new_stderr; + + /* OS X malloc() blabs about a malloc failure. This */ + /* will confuse users, so we temporarily redirect stdout */ + fflush(stderr); + old_stderr = dup(fileno(stderr)); + new_stderr = open("/dev/null", O_WRONLY | O_APPEND); + dup2(new_stderr, fileno(stderr)); +#endif + size += 20 * 1024 * 1024; /* This depends on the VM region allocation size */ + if ((a1 = malloc(size)) == NULL) { + rev_reduce_cache(size); + } else { + free(a1); + } + g_test_ram = size/2; /* Allow for twice as much VM to be used for each allocation */ +#ifdef __APPLE__ + fflush(stderr); + dup2(old_stderr, fileno(stderr)); /* Restore stderr */ + close(new_stderr); + close(old_stderr); +#endif +} + +static void *rev_malloc(rspl *s, size_t size) { + void *rv; + + if ((size + 1 * 1024 * 1204) > g_test_ram) + rev_test_vram(size); + if ((rv = malloc(size)) == NULL) { + rev_reduce_cache(size); + rv = malloc(size); + } + if (rv != NULL) + g_test_ram -= size; + + return rv; +} + +static void *rev_calloc(rspl *s, size_t num, size_t size) { + void *rv; + + if (((num * size) + 1 * 1024 * 1204) > g_test_ram) + rev_test_vram(size); + if ((rv = calloc(num, size)) == NULL) { + rev_reduce_cache(num * size); + rv = calloc(num, size); + } + if (rv != NULL) + g_test_ram -= size; + + return rv; +} + +static void *rev_realloc(rspl *s, void *ptr, size_t size) { + void *rv; + + if ((size + 1 * 1024 * 1204) > g_test_ram) + rev_test_vram(size); + if ((rv = realloc(ptr, size)) == NULL) { + rev_reduce_cache(size); /* approximation */ + rv = realloc(ptr, size); + } + if (rv != NULL) + g_test_ram -= size; + + return rv; +} + + +/* ====================================================== */ +/* Set the ink limit information for any reverse interpolation. */ +/* Calling this will clear the reverse interpolaton cache and acceleration structures. */ +static void +rev_set_limit_rspl( + rspl *s, /* this */ + double (*limit)(void *lcntx, double *in), /* Optional input space limit function. Function */ + /* should evaluate in[0..di-1], and return number that is not to exceed */ + /* limitv. NULL if not used */ + void *lcntx, /* Context passed to limit() */ + double limitv /* Value that limit() is not to exceed */ +) { + schbase *b; + + DBG(("rev: setting ink limit function 0x%x and limit %f\n",limit,limitv)); + /* This is a restricted size function */ + if (s->di > MXRI) + error("rspl: rev_set_limit can't handle di = %d",s->di); + if (s->fdi > MXRO) + error("rspl: rev_set_limit can't handle fdi = %d",s->fdi); + + b = set_search_limit(s, limit, lcntx, limitv); /* Init and set limit info */ + + if (s->rev.inited) { /* If cache and acceleration has been allocated */ + invalidate_revaccell(s); /* Invalidate the reverse cache */ + } + + /* Invalidate any ink limit values cached with the fwd grid data */ + clear_limitv(s); +} + +/* Get the ink limit information for any reverse interpolation. */ +static void +rev_get_limit_rspl( + rspl *s, /* this */ + double (**limitf)(void *lcntx, double *in), /* Return pointer to function of NULL if not set */ + void **lcntx, /* return context pointer */ + double *limitv /* Return limit value */ +) { + schbase *b = s->rev.sb; + + /* This is a restricted size function */ + if (s->di > MXRI) + error("rspl: rev_get_limit can't handle di = %d",s->di); + if (s->fdi > MXRO) + error("rspl: rev_get_limit can't handle fdi = %d",s->fdi); + + if (b == NULL) { + *limitf = NULL; + *lcntx = NULL; + *limitv = 0.0; + } else { + *limitf = s->limitf; + *lcntx = s->lcntx; + *limitv = s->limitv/INKSCALE; + } +} + +#define RSPL_CERTAIN 0x80000000 /* WILLCLIP hint is certain */ +#define RSPL_WILLCLIP2 (RSPL_CERTAIN | RSPL_WILLCLIP) /* Clipping will certainly be needed */ + +/* Do reverse interpolation given target output values and (optional) auxiliary target */ +/* input values. Return number of results and clipping flag. If return value == mxsoln, */ +/* then there might be more results. The target values returned will correspond to the */ +/* actual (posssibly clipped) point. The return value is the number of solutions + */ +/* a clipped flag. Properly set hint flags improve performance, but a correct result should */ +/* be returned if the RSPL_NEARCLIP is set, even if they are not set correctly. */ +static int +rev_interp_rspl( + rspl *s, /* this */ + int flags, /* Hint flag */ + int mxsoln, /* Maximum number of solutions allowed for */ + int *auxm, /* Array of di mask flags, !=0 for valid auxliaries (NULL if no auxiliaries) */ + double cdir[MXRO], /* Clip vector direction wrt to cpp[0].v and length - NULL if not used */ + co *cpp /* Given target output space value in cpp[0].v[] + */ + /* target input space auxiliaries in cpp[0].p[], return */ + /* input space solutions in cpp[0..retval-1].p[], and */ +) { + int e, di = s->di; + int fdi = s->fdi; + int i, *rip = NULL; + schbase *b = NULL; /* Base search information */ + double auxv[MXRI]; /* Locus proportional auxiliary values */ + int didclip = 0; /* flag - set if we clipped the target */ + + DBGV(("\nrev interp called with out targets", fdi, " %f", cpp[0].v, "\n")); + + /* This is a restricted size function */ + if (di > MXRI) + error("rspl: rev_interp can't handle di = %d",di); + if (fdi > MXRO) + error("rspl: rev_interp can't handle fdi = %d",fdi); + + if (auxm != NULL) { + double ax[MXRI]; + for (i = 0; i < di; i++) { + if (auxm[i] != 0) + ax[i] = cpp[0].p[i]; + else + ax[i] = 0.0; + } + DBGV((" auxiliaries mask", di, " %d", auxm, "\n")); + DBGV((" auxiliaries values", di, " %f", ax, "\n")); + } + DBG(("di = %d, fdi = %d\n",di, fdi)); + DBG(("flags = 0x%x\n",flags)); + + mxsoln &= RSPL_NOSOLNS; /* Prevent silliness */ + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + /* Auxiliary is proportion of locus, so we need to find locus extent */ + if (flags & RSPL_AUXLOCUS) { + DBG(("rev interp has aux targets as proportion of locus\n")); + + flags &= ~RSPL_WILLCLIP; /* Reset hint flag, as we will figure it out */ + + /* For each valid auxiliary */ + for (e = 0; e < di; e++) { + if (auxm[e] == 0) + continue; /* Skip unsused auxiliaries */ + + /* Do search for min and max */ + DBG(("rev locus searching for aux %d min/max\n", e)); + if (b == NULL) { + b = init_search(s, flags, cpp[0].p, auxm, cpp[0].v, cdir, cpp, mxsoln, locus); +#ifdef STATS + s->rev.st[b->op].searchcalls++; +#endif /* STATS */ + } else + set_lsearch(s, e); /* Reset locus search for next auxiliary */ + + if (rip == NULL) { /* Not done this yet */ + rip = calc_fwd_cell_list(s, cpp[0].v); /* Reverse grid index for out target */ + if (rip == NULL) { + DBG(("Got NULL list (point outside range) for auxiliary locus search\n")); + flags |= RSPL_WILLCLIP2; + break; + } + } + + search_list(b, rip, s->get_next_touch(s)); /* Setup, sort and search the list */ + + if (b->min > b->max) { /* Failed to find locus */ + DBG(("rev interp failed to find locus for aux %d, so expect clip\n",e)); + flags |= RSPL_WILLCLIP2; + break; + } + auxv[e] = (cpp[0].p[e] * (b->max - b->min)) + b->min; + } + + DBG(("rev interp got all locuses, so expect exact result\n",e)); + if (!(flags & RSPL_WILLCLIP)) { + flags |= RSPL_EXACTAUX; /* Got locuses, so expect exact result */ + } + } + + /* Init the search information */ + if (b == NULL) + b = init_search(s, flags, cpp[0].p, auxm, cpp[0].v, cdir, cpp, mxsoln, exact); + else + adjust_search(s, flags, auxv, exact); /* Using proportion of locus aux */ + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + /* If hinted that we will not need to clip, look for exact solution. */ + if (!(flags & RSPL_WILLCLIP)) { + DBG(("Hint we won't clip, so trying exact search\n")); + + /* First do an exact search (init will select auxil if requested) */ + adjust_search(s, flags, NULL, exact); + + /* Figure out the reverse grid index appropriate for this request */ + if (rip == NULL) /* Not done this yet */ + rip = calc_fwd_cell_list(s, cpp[0].v); + +#ifdef STATS + s->rev.st[b->op].searchcalls++; +#endif /* STATS */ + if (rip != NULL) { + /* Setup, sort and search the list */ + search_list(b, rip, s->get_next_touch(s)); + } else { + DBG(("Got NULL list (point outside range) for first exact reverse cell\n")); + } + + /* If we selected exact aux, but failed to find a solution, relax expectation */ + if (b->nsoln == 0 && b->naux > 0 && (flags & RSPL_EXACTAUX)) { +//printf("~1 relaxing notclip expactation when nsoln == %d, naux = %d, falgs & RSPL_EXACTAUX = 0x%x\n", b->nsoln,b->naux,flags & RSPL_EXACTAUX); + DBG(("Searching for exact match to auxiliary target failed, so try again\n")); + adjust_search(s, flags & ~RSPL_EXACTAUX, NULL, exact); + +#ifdef STATS + s->rev.st[b->op].searchcalls++; +#endif /* STATS */ + /* Candidate cell list should be the same */ + if (rip != NULL) { + /* Setup, sort and search the list */ + search_list(b, rip, s->get_next_touch(s)); + } else { + DBG(("Got NULL list (point outside range) for nearest search reverse cell\n")); + } + } + } + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + /* If the exact search failed, and we should look for a nearest solution */ + if (b->nsoln == 0 && (flags & RSPL_NEARCLIP)) { + DBG(("Trying nearest search\n")); + +#ifdef STATS + s->rev.st[b->op].searchcalls++; +#endif /* STATS */ + + /* We get returned a list of cube base indexes of all cubes that have */ + /* the closest valid vertex value to the target value. */ + /* (This may not result in the true closest point if the geometry of */ + /* the vertex values is localy non-smooth or self intersecting, */ + /* but seems to return a good result in most realistic situations ?) */ + + adjust_search(s, flags, NULL, clipn); + + /* Get list of cells enclosing nearest vertex */ + if ((rip = calc_fwd_nn_cell_list(s, cpp[0].v)) != NULL) { + search_list(b, rip, s->get_next_touch(s)); /* Setup, sort and search the list */ + } else { + DBG(("Got NULL list! (point inside gamut \?\?) for nearest search\n")); + } + + if (b->nsoln > 0) + didclip = RSPL_DIDCLIP; + } + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + /* If we still don't have a solution, do a vector direction clip */ + if (b->nsoln == 0 && b->canvecclip) { + /* Find clipping solution in vector direction */ + line ln; /* Structure to hold line context */ + unsigned int tcount; /* grid touch count for this opperation */ + + DBG(("Starting a clipping vector search now!!\n")); + + adjust_search(s, flags, NULL, clipv); + + tcount = s->get_next_touch(s); /* Get next grid touched generation count */ + +#ifdef STATS + s->rev.st[b->op].searchcalls++; +#endif /* STATS */ + init_line_eq(b, b->v, cdir); /* Init the implicit line equation */ + rip = init_line(s, &ln, cpp[0].v, cdir); /* Init the line cell dda */ +//~~1 HACK!!! should be <= 1.0 !!! + for (; ln.t <= 2.0; rip = next_line_cell(&ln)) { + if (rip == NULL) { + DBG(("Got NULL list for this reverse cell\n")); + continue; + } + + /* Setup, sort and search the list */ + search_list(b, rip, tcount); + + /* If we have found a solution, then abort the search - */ + /* this line will be taking us away from the best solution. */ + if (b->nsoln > 0) + break; + } + if (b->nsoln > 0) + didclip = RSPL_DIDCLIP; + } + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + /* If the clipped solution seems to have been jumping to conclusions, */ + /* search for an exact solution. */ + if (didclip && (flags & RSPL_WILLCLIP && !(flags & RSPL_CERTAIN)) + && (b->cdist/s->get_out_scale(s)) < 0.002) { + co c_cpp = b->cpp[0]; /* Save clip solution in case we want it */ + double c_idist = b->idist; + int c_iabove = b->iabove; + int c_nsoln = b->nsoln; + int c_pauxcell = b->pauxcell; + double c_cdist = b->cdist; + int c_iclip = b->iclip; + + DBG(("Trying exact search again\n")); + + /* Do an exact search (init will select auxil if requested) */ + adjust_search(s, flags & ~RSPL_WILLCLIP, NULL, exact); + + /* Figure out the reverse grid index appropriate for this request */ + rip = calc_fwd_cell_list(s, cpp[0].v); + +#ifdef STATS + s->rev.st[b->op].searchcalls++; +#endif /* STATS */ + if (rip != NULL) { + /* Setup, sort and search the list */ + search_list(b, rip, s->get_next_touch(s)); + } else { + DBG(("Got NULL list (point outside range) for first exact reverse cell\n")); + } + + /* If we selected exact aux, but failed to find a solution, relax expectation */ + if (b->nsoln == 0 && b->naux > 0 && (flags & RSPL_EXACTAUX)) { + DBG(("Searching for exact match to auxiliary target failed, so try again\n")); +//printf("~1 relaxing didclip expactation when nsoln == %d, naux = %d, flags & RSPL_EXACTAUX = 0x%x\n", b->nsoln,b->naux,flags & RSPL_EXACTAUX); + adjust_search(s, flags & ~RSPL_EXACTAUX, NULL, exact); + +#ifdef STATS + s->rev.st[b->op].searchcalls++; +#endif /* STATS */ + /* Candidate cell list should be the same */ + if (rip != NULL) { + /* Setup, sort and search the list */ + search_list(b, rip, s->get_next_touch(s)); + } else { + DBG(("Got NULL list (point outside range) for nearest search reverse cell\n")); + } + } + + /* If we did get an exact solution */ + if (b->nsoln > 0) { + DBG(("Deciding to return exact solution after finding clipped\n")); + didclip = 0; /* Reset did-clip and return exact solution */ + + } else { + DBG(("keeping clipped solution\n")); + /* Restore the clipped solution */ + b->cpp[0] = c_cpp; + b->idist = c_idist; + b->iabove = c_iabove; + b->nsoln = c_nsoln; + b->pauxcell = c_pauxcell; + b->cdist = c_cdist; + b->iclip = c_iclip; + } + } + + if (b->nsoln > 0) { + DBGV(("rev interp returning 1st soln: ",di," %f", cpp[0].p, "\n")); + } + DBG(("rev interp returning %d solutions%s\n",b->nsoln, didclip ? " [clip]" : "")); + + return b->nsoln | didclip; +} + +/* ------------------------------------------------------------------------------------ */ +/* Do reverse search for the auxiliary min/max ranges of the solution locus for the */ +/* given target output values. */ +/* Return number of locus segments found, up to mxsoln. 0 will be returned if no solutions */ +/* are found. */ + +static int +rev_locus_segs_rspl ( + rspl *s, /* this */ + int *auxm, /* Array of di mask flags, !=0 for valid auxliaries (NULL if no auxiliaries) */ + co *cpp, /* Input value in cpp[0].v[] */ + int mxsoln, /* Maximum number of solutions allowed for */ + double min[][MXRI], /* Array of min[MXRI] to hold return segment minimum values. */ + double max[][MXRI] /* Array of max[MXRI] to hold return segment maximum values. */ +) { + int e, di = s->di; + int f, fdi = s->fdi; + int six; /* solution index */ + int *rip = NULL; + int rv = 1; /* Return value */ + schbase *b = NULL; /* Base search information */ + + DBGV(("rev locus called with out targets", fdi, " %f", cpp[0].v, "\n")); + + /* This is a restricted size function */ + if (di > MXRI) + error("rspl: rev_locus_segs can't handle di = %d",di); + if (fdi > MXRO) + error("rspl: rev_locus_segs can't handle fdi = %d",fdi); + + if (mxsoln < 1) { + return 0; /* Guard against silliness */ + } + + if (auxm != NULL) { + int i; + double ax[MXRI]; + for (i = 0; i < di; i++) { + if (auxm[i] != 0) + ax[i] = cpp[0].p[i]; + else + ax[i] = 0.0; + } + DBGV((" auxiliaries mask", di, " %d", auxm, "\n")); + DBGV((" auxiliaries values", di, " %f", ax, "\n")); + } + + /* Init default return values */ + for (six = 0; six < mxsoln; six++) { + for (e = 0; e < di; e++) { + if (auxm[e] == 0) { + min[six][e] = max[six][e] = 0; /* Return 0 for unused auxiliaries */ + } else { + min[six][e] = 1.0; /* max < min indicates invalid range */ + max[six][e] = 0.0; + } + } + } + + /* For each valid auxiliary */ + for (e = 0; e < di; e++) { + if (auxm[e] == 0) + continue; /* Skip unsused auxiliaries */ + + /* Do search for min and max */ + DBG(("rev locus searching for aux %d min/max\n", e)); + if (b == NULL) + b = init_search(s, 0, cpp[0].p, auxm, cpp[0].v, NULL, cpp, mxsoln, locus); + else + set_lsearch(s, e); /* Reset locus search for next auxiliary */ + + if (rip == NULL) { /* Not done this yet */ + rip = calc_fwd_cell_list(s, cpp[0].v); /* Reverse grid index for this request */ + if (rip == NULL) { + DBG(("Got NULL list (point outside range) for auxiliary locus search\n")); + rv = 0; + break; + } + } + + search_list(b, rip, s->get_next_touch(s)); /* Setup, sort and search the list */ + + if (b->min > b->max) { + rv = 0; /* Failed to find a result */ + break; + } + + if (b->asegs == 0) { /* Overall min max only */ + + min[0][e] = b->min; /* Save single result */ + max[0][e] = b->max; + + } else { /* Tracking auxiliary segments */ + int si; /* Start i */ + int i, j, ff; + + /* Sort the segment list */ +#define HEAP_COMPARE(A,B) (A.xval < B.xval) + HEAPSORT(axisec, b->axisl, b->axisln) +#undef HEAP_COMPARE + +#ifdef NEVER +for (i = 0; i < b->axisln; i++) { +printf("~2 xval = %f, verts = ",b->axisl[i].xval); +for (f = 0; f < b->axisl[i].nv; f++) +printf(" %d", b->axisl[i].vix[f]); +printf("\n"); +} +#endif + /* Find the segments by finding common verticies */ + six = si = i = 0; + + min[six][e] = b->axisl[i].xval; + + for (i++; i < (b->axisln-1); i++) { + /* Check if any i and i-1 to j are connected */ + for (j = i-1; j >= si; j--) { + for (f = 0; f < b->axisl[j].nv; f++) { + for (ff = 0; ff < b->axisl[i].nv; ff++) { + if (b->axisl[j].vix[f] == b->axisl[i].vix[ff]) + break; /* Found a link */ + } + if (ff < b->axisl[i].nv) + break; + } + if (f < b->axisl[j].nv) + break; + } + if (j < si) { /* Wasn't linked */ + int ii, jj; + /* Think we found a break. Check that all the rest of */ + /* the entries don't have any links to the previous group */ + + /* This could be rather a slow way of checking ! (On^2) */ + for (ii = i+1; ii < (b->axisln); ii++) { + for (jj = i-1; jj >= si; jj--) { + for (f = 0; f < b->axisl[jj].nv; f++) { + for (ff = 0; ff < b->axisl[ii].nv; ff++) { + if (b->axisl[jj].vix[f] == b->axisl[ii].vix[ff]) + break; /* Found a link */ + } + if (ff < b->axisl[ii].nv) + break; + } + if (f < b->axisl[jj].nv) + break; + } + if (jj >= si) + break; + } + if (ii >= b->axisln) { /* Wasn't forward linked */ + /* Nothing ahead links to last group */ + max[six][e] = b->axisl[i-1].xval; + + /* If we run out of solution space */ + /* merge the last segments */ + if ((six+1) < mxsoln) { + six++; + min[six][e] = b->axisl[i].xval; + } + } + } + } + max[six++][e] = b->axisl[i].xval; + + if (six > rv) + rv = six; + } + } + +#ifdef STATS + s->rev.st[b->op].searchcalls++; +#endif /* STATS */ + if (rv) { + for (six = 0; six < rv; six++) { + DBG(("rev locus returning:\n")); + DBGV((" min", di, " %f", min[six], "\n")); + DBGV((" max", di, " %f", max[six], "\n")); + } + } + + DBG(("rev locus returning status %d\n",rv)); + return rv; +} + +/* ------------------------------------------------------------------------------------ */ +typedef double mxdi_ary[MXRI]; + +/* Do reverse search for the locus of the auxiliary input values given a target output. */ +/* Return 1 on finding a valid solution, and 0 if no solutions are found. */ +static int +rev_locus_rspl( + rspl *s, /* this */ + int *auxm, /* Array of di mask flags, !=0 for valid auxliaries (NULL if no auxiliaries) */ + co *cpp, /* Input value in cpp[0].v[] */ + double min[MXRI],/* Return minimum auxiliary values */ + double max[MXRI] /* Return maximum auxiliary values */ +) { + + /* Use segment routine to compute oveall locus */ + return rev_locus_segs_rspl (s, auxm, cpp, 1, (mxdi_ary *)min, (mxdi_ary *)max); +} + +/* ------------------------------------------------------------------------------------ */ + +#ifdef DEBUG2 +#define DEBUG +#undef DBG +#undef DBGV +#undef DBGM +#define DBG(xxx) DBGI(xxx) +#define DBGV(xxx) DBGVI xxx +#define DBGM(xxx) DBGMI xxx +#else +#undef DEBUG +#undef DBG +#undef DBGV +#undef DBGM +#define DBG(xxx) +#define DBGV(xxx) +#define DBGM(xxx) +#endif + +/* ------------------------------------------------ */ +/* subroutines of top level reverse lookup routine */ + +static int exact_setsort(schbase *b, cell *c); +static int exact_compute(schbase *b, simplex *x); + +static int auxil_setsort(schbase *b, cell *c); +static int auxil_check(schbase *b, cell *c); +static int auxil_compute(schbase *b, simplex *x); + +static int locus_setsort(schbase *b, cell *c); +static int locus_check(schbase *b, cell *c); +static int locus_compute(schbase *b, simplex *x); + +static int clipv_setsort(schbase *b, cell *c); +static int clipv_check(schbase *b, cell *c); +static int clipv_compute(schbase *b, simplex *x); + +static int clipn_setsort(schbase *b, cell *c); +static int clipn_check(schbase *b, cell *c); +static int clipn_compute(schbase *b, simplex *x); + +/* Allocate the search base structure */ +static schbase * +alloc_sb(rspl *s) { + schbase *b; + if ((b = s->rev.sb = (schbase *)rev_calloc(s, 1, sizeof(schbase))) == NULL) + error("rspl malloc failed - rev.sb structure"); + INCSZ(s, sizeof(schbase)); + + b->s = s; /* rsp */ + b->pauxcell = /* Previous solution cell indexes */ + b->plmaxcell = + b->plmincell = -1; + + return b; +} + +/* Free the search base structure */ +static void +free_sb(schbase *b) { + DECSZ(b->s, sizeof(schbase)); + free(b); +} + +/* Do the basic search type independent initialization */ +static schbase * /* Return pointer to base search information */ +init_search( + rspl *s, /* rsp; */ + int flags, /* Hint flag */ + + double *av, /* Auxiliary input values - may be NULL */ + int *auxm, /* Array of di mask flags, !=0 for valid auxliaries (NULL if no auxiliaries) */ + /* Locus search will search for max/min of first valid auxlilary */ + double *v, /* Output value target, NULL if none */ + double *cdir, /* Clip vector direction, NULL if none */ + co *cpp, /* Array that hold solutions, NULL if none. */ + int mxsoln, /* Maximum number of solutions allowed for */ + enum ops op /* Type of reverse search operation requested */ +) { + schbase *b = NULL; /* Pointer to search base information structure */ + int e, di = s->di; + int f, fdi = s->fdi; + + DBG(("Initializing search\n")); + + if (s->rev.inited == 0) /* Compute reverse info if it doesn't exist */ + make_rev(s); + + /* If first time initialisation (Fourth section init) */ + if ((b = s->rev.sb) == NULL) { + b = alloc_sb(s); + } + + /* Init some basic search info */ + b->op = op; /* operation */ + b->flags = flags; /* hint flags */ + b->canvecclip = 0; /* Assume invalid clip direction */ + + b->ixc = (1<naux = 0; + b->auxbm = 0; + if (auxm != NULL) { + unsigned bm; + + if (mxsoln > 1) + b->asegs = 1; /* Find all segments */ + else + b->asegs = 0; /* Find only overall aux locus range */ + + for (e = di-1, bm = 1 << e; e >= 0; e--, bm >>= 1) { /* Record auxiliary mask bits */ + if (av != NULL) + b->av[e] = av[e]; /* Auxiliary target values */ + b->auxm[e] = auxm[e]; /* Auxiliary mask */ + if (auxm[e] != 0) { + b->auxbm |= bm; /* Auxiliary bit mask */ + b->auxi[b->naux++] = e; /* Index of next auxiliary input to be used */ + /* Auxiliary locus extent */ + b->lxi = e; /* Assume first one */ + b->max = -INF_DIST; /* In case searching for max */ + b->min = INF_DIST; /* In case searching for minimum */ + b->axisln = 0; /* No intersects in list */ + } + } + } + + /* Figure out if the clip direction is meaningfull */ + /* Check that the clip vector makes sense */ + if (cdir != NULL) { /* Clip vector is specified */ + double ss; + for (ss = 0.0, f = 0; f < fdi; f++) { + double tt = cdir[f]; + b->cdir[f] = tt; + ss += tt * tt; + } + + if (ss > 1e-6) { + b->canvecclip = 1; /* It has a non-zero length */ + ss = sqrt(ss); + /* Compute normalised clip vector direction */ + for (f = 0; f < fdi; f++) { + b->ncdir[f] = b->cdir[f]/ss; + } + } + } + + if (di <= fdi) /* Only allow auxiliaries if di > fdi */ + b->naux = 0; + + /* Switch to appropriate operation */ + if (b->op == exact && (b->naux > 0 || di != fdi)) { + b->op = auxil; + } else if (b->op == auxil && b->naux == 0 && di == fdi) { + b->op = exact; + } + + /* Set appropriate functions for type of operation */ + switch (b->op) { + case exact: + b->setsort = exact_setsort; + b->check = NULL; + b->compute = exact_compute; + b->snsdi = b->ensdi = di; /* Search full dimension simplex, expect point soln. */ + break; + case auxil: + b->setsort = auxil_setsort; + b->check = auxil_check; + b->compute = auxil_compute; + b->snsdi = di; /* Start here DOF = di-fdi locus solutions */ + b->ensdi = fdi; /* End with DOF = 0 for point solutions */ + break; + case locus: + b->setsort = locus_setsort; + b->check = locus_check; + b->compute = locus_compute; + b->snsdi = b->ensdi = fdi; /* Search for point solutions */ + break; + case clipv: + b->setsort = clipv_setsort; + b->check = clipv_check; + b->compute = clipv_compute; + /* Clip vector 1 dimension in output space, */ + b->snsdi = b->ensdi = fdi-1; /* search planes for combined point solution */ + break; + case clipn: + b->setsort = clipn_setsort; + b->check = clipn_check; + b->compute = clipn_compute; + b->snsdi = 0; /* Start with DOF = 0 for point solutions */ + b->ensdi = fdi-1; /* End on DOF = di-fdi-1 on surfaces of simplexes */ + break; + default: + error("init_search: Unknown operation %d\n",b->op); + } + + if (v != NULL) { + for (f = 0; f < fdi; f++) /* Record target output values */ + b->v[f] = v[f]; + b->v[fdi] = s->limitv; /* Limitvalue is output target for limit clip subsimplexes */ + } + + b->mxsoln = mxsoln; /* Allow solutions to be returned */ + b->cpp = cpp; /* Put solutions here */ + b->nsoln = 0; /* No solutions at present */ + b->iclip = 0; /* Default solution isn't above ink limit */ + + if (flags & RSPL_EXACTAUX) /* Expect to be able to match auxiliary target exactly */ + b->idist = 2.0 * EPS; /* Best input distance to beat - helps sort/triage */ + else + b->idist = INF_DIST; /* Best input distance to beat. */ + b->iabove = 0; /* Best isn't known to be above (yet) */ + + b->cdist = INF_DIST; /* Best clip distance to beat. */ + + DBG(("Search initialized\n")); + + return b; +} + +/* Adjust the search */ +static void +adjust_search( + rspl *s, /* rsp; */ + int flags, /* Hint flag */ + double *av, /* Auxiliary input values - may be NULL */ + enum ops op /* Type of reverse search operation requested */ +) { + schbase *b = s->rev.sb; /* Pointer to search base information structure */ + int e, di = s->di; + int fdi = s->fdi; + + DBG(("Adjusting search\n")); + + b->op = op; /* operation */ + b->flags = flags; /* hint flags */ + + /* Switch from exact to aux if we need to */ + if (b->op == exact && (b->naux > 0 || di != fdi)) { + b->op = auxil; + } else if (b->op == auxil && b->naux == 0 && di == fdi) { + b->op = exact; + } + + /* Update auxiliary target values */ + if (av != NULL) { + for (e = 0; e < b->naux; e++) { + int ee = b->auxi[e]; + b->av[ee] = av[ee]; + } + } + + /* Set appropriate functions for type of operation */ + switch (b->op) { + case exact: + b->setsort = exact_setsort; + b->check = NULL; + b->compute = exact_compute; + b->snsdi = b->ensdi = di; /* Expect point solution */ + break; + case auxil: + b->setsort = auxil_setsort; + b->check = auxil_check; + b->compute = auxil_compute; + b->snsdi = di; /* Start here DOF = di-fdi locus solutions */ + b->ensdi = fdi; /* End with DOF = 0 for point solutions, */ + break; /* will early exit DOF if good soln found. */ + case locus: + b->setsort = locus_setsort; + b->check = locus_check; + b->compute = locus_compute; + b->snsdi = b->ensdi = fdi; /* Search for point solutions */ + break; + case clipv: + b->setsort = clipv_setsort; + b->check = clipv_check; + b->compute = clipv_compute; + /* Clip vector 1 dimension in output space, */ + b->snsdi = b->ensdi = fdi-1; /* so the intersection with the simplex is a point. */ + break; + case clipn: + b->setsort = clipn_setsort; + b->check = clipn_check; + b->compute = clipn_compute; + b->snsdi = 0; /* Start with DOF = 0 for point solutions */ + b->ensdi = fdi-1; /* End on DOF = di-fdi-1 on surfaces of simplexes */ + break; /* Will go through all DOF */ + default: + error("init_search: Unknown operation %d\n",b->op); + } + + b->nsoln = 0; /* No solutions at present */ + + if (flags & RSPL_EXACTAUX) /* Expect to be able to match auxiliary target exactly */ + b->idist = 2.0 * EPS; /* Best input distance to beat - helps sort/triage */ + else + b->idist = INF_DIST; /* Best input distance to beat. */ + b->iabove = 0; /* Best isn't known to be above (yet) */ + + b->cdist = INF_DIST; /* Best clip distance to beat. */ + + DBG(("Search adjusted\n")); +} + +/* Adjust existing locus search for a different auxiliary */ +static void +set_lsearch( +rspl *s, +int e /* Next auxiliary */ +) { + schbase *b = s->rev.sb; /* Pointer to search base information structure */ + + b->lxi = e; /* Assume first one */ + b->max = -INF_DIST; /* In case searching for max */ + b->min = INF_DIST; /* In case searching for minimum */ + b->axisln = 0; /* No intersects in list */ +} + +/* Set the limit search information */ +/* Note this doesn't create or init the main rev information. */ +static schbase * /* Return pointer to base search information */ +set_search_limit( + rspl *s, /* rsp; */ + double (*limitf)(void *vcntx, double *in), /* Optional input space limit function. Function */ + /* should evaluate in[0..di-1], and return number that is not to exceed */ + /* limitv. NULL if not used */ + void *lcntx, /* Context passed to limit() */ + double limitv /* Value that limit() is not to exceed */ +) { + schbase *b = NULL; /* Pointer to search base information structure */ + + /* If sb info needs initialising (Fourth section init) */ + if ((b = s->rev.sb) == NULL) { + b = alloc_sb(s); + } + + s->limitf = limitf; /* Input limit function */ + s->lcntx = lcntx; /* Context passed to limit() */ + s->limitv= INKSCALE * limitv; /* Context passed to values not to be exceedded by limit() */ + if (limitf != NULL) { + s->limiten = 1; /* enable limiting by default */ + } else + s->limiten = 0; /* No limit function, so limiting not enabled. */ + + return b; +} + +/* Free any search specific data, plus the search base. */ +static void +free_search( +schbase *b /* Base search information */ +) { + DBG(("Freeing search\n")); + + /* Clip line implicit equation (incuding space for ink target) */ + if (b->cla != NULL) { + int fdi = b->s->fdi; + free_dmatrix(b->cla, 0, fdi-1, 0, fdi); + b->cla = NULL; + } + + /* Auxiliary segment list */ + if (b->axislz > 0) { + free(b->axisl); + DECSZ(b->s, b->axislz * sizeof(axisec)); + b->axisl = NULL; + b->axislz = 0; + b->axisln = 0; + } + + /* Sorted cell list */ + if (b->lclistz > 0) { + free(b->lclist); + DECSZ(b->s, b->lclistz * sizeof(cell *)); + b->lclist = NULL; + b->lclistz = 0; + } + + /* Simplex filter list */ + if (b->lsxfilt > 0) { + free(b->sxfilt); + DECSZ(b->s, b->lsxfilt * sizeof(char)); + b->sxfilt = NULL; + b->lsxfilt = 0; + } + + free_sb(b); +} + +/* Return the pointer to the list of fwd cells given */ +/* the target output values. The pointer will be to the first */ +/* index in the list (ie. list address + 3) */ +/* Return NULL if none in list (out of gamut). */ +static int * +calc_fwd_cell_list( + rspl *s, /* this */ + double *v /* Output values */ +) { + int f, fdi = s->fdi; + int **rpp; + int rgres_1 = s->rev.res - 1; + + if (s->rev.rev_valid == 0) + init_revaccell(s); + + for (rpp = s->rev.rev, f = 0; f < fdi; f++) { + int mi; + double t = (v[f] - s->rev.gl[f])/s->rev.gw[f]; + mi = (int)floor(t); /* Grid coordinate */ + if (mi < 0 || mi > rgres_1) { /* If outside valid reverse range */ + return NULL; + } + rpp += mi * s->rev.coi[f]; /* Accumulate reverse grid pointer */ + } + if (*rpp == NULL) + return NULL; + return (*rpp) + 3; +} + +void alloc_simplexes(cell *c, int nsdi); + +/* Given a pointer to a list of fwd cells, cull cells that */ +/* cannot contain or improve the solution, sort the list, */ +/* and then compute the final best solution. */ +static void +search_list( +schbase *b, /* Base search information */ +int *rip, /* Pointer to first index in cell list */ +unsigned int tcount /* grid touch count for this operation */ +) { + rspl *s = b->s; + int nsdi; + int i; + int nilist; /* Number in cell list */ + unsigned int stouch; /* Simplex touch count */ + + DBG(("search_list called\n")); + + /* (rip[-3] contains allocation for fwd cells in the list) */ + /* (rip[-2] contains the index of the next free entry in the list) */ + /* (rip[-1] contains the reference count for the list) */ + if (b->lclistz < rip[-3]) { /* Allocate more space if needed */ + + if (b->lclistz > 0) { /* Free old space before allocating new */ + free(b->lclist); + DECSZ(b->s, b->lclistz * sizeof(cell *)); + } + b->lclistz = 0; + /* Allocate enough space for all the candidate cells */ + if ((b->lclist = (cell **)rev_malloc(s, rip[-3] * sizeof(cell *))) == NULL) + error("rev: malloc failed - candidate cell list, count %d",rip[-3]); + b->lclistz = rip[-3]; /* Current allocated space */ + INCSZ(b->s, b->lclistz * sizeof(cell *)); + } + + /* Get the next simplex touch count, so that we don't search shared */ + /* face simplexes more than once in this pass through the cells. */ + if ((stouch = ++s->rev.stouch) == 0) { /* If touch count rolls over */ + cell *cp; + stouch = s->rev.stouch = 1; + + DBG(("touch has rolled over, resetting it\n")); + /* For all of the cells */ + for (cp = s->rev.cache->mrubot; cp != NULL; cp = cp->mruup) { + int nsdi; + + if (cp->s == NULL) /* Cell has never been used */ + continue; + + /* For all the simplexes in the cell */ + for (nsdi = 0; nsdi <= s->di; nsdi++) { + if (cp->sx[nsdi] != NULL) { + int si; + + for (si = 0; si < cp->sxno[nsdi]; si++) { + cp->sx[nsdi][si]->touch = 0; + } + } + } + } + } + + /* For each chunk of the list that we can fit in the rcache: */ + for(; *rip != -1;) { + + /* Go through all the candidate fwd cells, and build up the list of search cells */ + for(nilist = 0; *rip != -1; rip++) { + int ix = *rip; /* Fwd cell index */ + float *fcb = s->g.a + ix * s->g.pss; /* Pointer to base float of fwd cell */ + cell *c; + + if (TOUCHF(fcb) >= tcount) { /* If we have visited this cell before */ + DBG((" Already touched cell index %d\n",ix)); + continue; + } + /* Get pointers to cells from cache, and lock it in the cache */ + if ((c = get_rcell(b, ix, nilist == 0 ? 1 : 0)) == NULL) { + static int warned = 0; + if (!warned) { + warning("%cWarning - Reverse Cell Cache exausted, processing in chunks",cr_char); + warned = 1; + } + DBG(("revcache is exausted, do search in chunks\n")); + if (nilist == 0) { + /* This should never happen, because nz force should prevent it */ + revcache *rc = s->rev.cache; + cell *cp; + int nunlk = 0; + /* Double check that there are no unlocked cells */ + for (cp = rc->mrubot; cp != NULL && cp->refcount > 0; cp = cp->mruup) { + if (cp->refcount == 0) + nunlk++; + } + fprintf(stdout,"Diagnostic: rev.sz = %lu, rev.max_sz = %lu, numlocked = %d, nunlk = %d\n", + rc->s->rev.sz, rc->s->rev.max_sz, rc->nunlocked,nunlk); + error("Not enough memory to process in chunks"); + } + break; /* cache has run out of room, so abandon, and do it next time */ + } + + DBG(("checking out cell %d range %s\n",ix,pcellorange(c))); + TOUCHF(fcb) = tcount; /* Touch it */ + + /* Check mandatory conditions, and compute search key */ + if (!b->setsort(b, c)) { + DBG(("cell %d rejected from list\n",ix)); + unget_rcell(s->rev.cache, c); + continue; + } + DBG(("cell %d accepted into list\n",ix)); + + b->lclist[nilist++] = c; /* Cell is accepted as recursion candidate */ + } + + if (nilist == 0) { + DBG(("List was empty\n")); + } + +#ifdef DOSORT + /* If appropriate, sort child cells into best order */ + /* == sort key smallest to largest */ + switch (b->op) { + case locus: + { /* Special case, adjust sort values */ + double min = INF_DIST, max = -INF_DIST; + for (i = 0; i < nilist; i++) { + cell *c = b->lclist[i]; + if (c->sort < min) + min = c->sort; + if (c->sort > max) + max = c->sort; + } + max = min + max; /* Total of min/max */ + min = 0.5 * max; /* Average sort value */ + for (i = 0; i < nilist; i++) { + cell *c = b->lclist[i]; + if (c->ix == b->plmincell || c->ix == b->plmaxcell) { + c->sort = -1.0; /* Put previous solution cells at head of list */ + } else if (c->sort > min) { + c->sort = max - c->sort; /* Reflect about average */ + } + } + } + /* Fall through to sort */ + case auxil: + case clipv: + case clipn: +#define HEAP_COMPARE(A,B) (A->sort < B->sort) + HEAPSORT(cell *,b->lclist, nilist) +#undef HEAP_COMPARE + break; + default: + break; + } +#endif /* DOSORT */ + + DBG(("List sorted, about to search\n")); +#ifdef NEVER + printf("\n~1 Op = %s, Cell sort\n",opnames[b->op]); + for (i = 0; i < nilist; i++) { + printf("~1 List %d, cell %d, sort = %f\n",i,b->lclist[i]->ix,b->lclist[i]->sort); + } +#endif /* NEVER */ + + /* + Tried reversing the "for each cell" and "for each level" loops, + but it made a negligible difference to the performance. + We choose to have cell on the outer so that we can unlock + them as we go, so that they may be freed, even though + this is a couple of percent slower (?). + */ + + /* For each cell in the list */ + for (i = 0; i < nilist; i++) { + cell *c = b->lclist[i]; + +#ifdef STATS + s->rev.st[b->op].csearched++; +#endif /* STATS */ + + /* For each dimensionality of sub-simplexes, in given order */ + DBG(("Searching from level %d to level %d\n",b->snsdi, b->ensdi)); + for (nsdi = b->snsdi;;) { + int j, nospx; /* Number of simplexes in cell */ + + DBG(("\n******************\n")); + DBG(("Searching level %d\n",nsdi)); + + /* For those searches that have an optimisation goal, */ + /* re-check the cell to see if the goal can still improve on. */ + if (b->check != NULL && !b->check(b, c)) + break; + + if (c->sx[nsdi] == NULL) { + alloc_simplexes(c, nsdi); /* Do level 1 initialisation for nsdi */ + } + + /* For each simplex in a cell */ + nospx = c->sxno[nsdi]; /* Number of nsdi simplexes */ + for (j = 0; j < nospx; j++) { + simplex *x = c->sx[nsdi][j]; + + if (x->touch >= stouch) { + continue; /* We've already seen this one */ + } + + if (s->limiten == 0) { + if (x->flags & SPLX_CLIPSX) /* If limiting is disabled, we're */ + continue; /* not interested in clip plane simplexes */ + } +#ifdef STATS + s->rev.st[b->op].ssearched++; +#endif /* STATS */ + if (b->compute(b, x)) { + DBG(("search aborted by compute\n")); + break; /* Found enough solutions */ + } + x->touch = stouch; /* Don't look at it again */ + + } /* Next Simplex */ + + if (nsdi == b->ensdi) + break; /* We're done with levels */ + + /* Next Simplex dimensionality */ + if (b->ensdi < b->snsdi) { + if (nsdi == b->snsdi && b->nsoln > 0 + && (b->op != auxil || b->idist <= 2.0 * EPS)) + break; /* Don't continue though decreasing */ + /* sub-simplex dimensions if we found a solution at */ + /* the highest dimension level. */ + nsdi--; + } else if (b->ensdi > b->snsdi) { + nsdi++; /* Continue through increasing sub-simplex dimenionality */ + } /* until we get to the top. */ + } + /* Unlock the cache cell now that we're done with it */ + unget_rcell(s->rev.cache, b->lclist[i]); + } /* Next cell */ + + } /* Next chunk */ + + DBG(("search_list complete\n")); + return; +} + +/* ------------------------------------- */ +/* Vector search in output space support */ + +/* Setup the line, and fetch the first cell */ +/* Return the pointer to the list of fwd cells, NULL if none in list. */ +static int * +init_line( + rspl *s, /* this */ + line *l, /* line structure */ + double st[MXRO], /* start of line */ + double de[MXRO] /* line direction and length */ +) { + int f, fdi = s->fdi; + int **rpp; + int rgres_1 = s->rev.res - 1; + int nvalid = 0; /* Flag set if outside reverse grid range */ + + DBGV(("Line from ", fdi, " %f", st, "\n")); + DBGV(("In dir ", fdi, " %f", de, "\n")); + DBGV(("gl ", fdi, " %f", s->rev.gl, "\n")); + DBGV(("gh ", fdi, " %f", s->rev.gh, "\n")); + DBGV(("gw ", fdi, " %f", s->rev.gw, "\n")); + + /* Init */ + l->s = s; + for (f = 0; f < fdi; f++) { + l->st[f] = st[f] - s->rev.gl[f]; + l->de[f] = de[f]; + if (de[f] > 0.0) + l->di[f] = 1; /* Axis increments */ + else if (de[f] < 0.0) + l->di[f] = -1; + else + l->di[f] = 0; + } + l->t = 0.0; + DBGV(("increments =", fdi, " %d", l->di, "\n")); + + /* Figure out the starting cell */ + for (rpp = s->rev.rev, f = 0; f < fdi; f++) { + double t = l->st[f]/s->rev.gw[f]; + l->ci[f] = (int)floor(t); /* Grid coordinate */ + if (l->ci[f] < 0 || l->ci[f] > rgres_1) /* If outside valid reverse range */ + nvalid = 1; + rpp += l->ci[f] * s->rev.coi[f]; /* Accumulate reverse grid pointer */ + } + DBGV(("current line cell = ", fdi, " %d", l->ci, "")); DBG((", t = %f, nvalid = %d\n",l->t,nvalid)); +#ifdef DEBUG +{ +int ii; +double tt; +printf("Current cell = "); +for (ii = 0; ii < fdi; ii++) { + tt = l->ci[ii] * s->rev.gw[ii] + s->rev.gl[ii]; + printf(" %f - %f",tt,tt+s->rev.gw[ii]); +} +printf("\n"); +} +#endif /* DEBUG */ + if (nvalid) + return NULL; + if (*rpp == NULL) + return NULL; + return *rpp + 3; +} + +/* Get the next cell on the line. */ +/* Return the pointer to the list of fwd cells, NULL if none in list. */ +static int * +next_line_cell( + line *l /* line structure */ +) { + rspl *s = l->s; + int bf = 0, f, fdi = s->fdi; + int **rpp; + int rgres_1 = s->rev.res - 1; + double bt = 100.0; /* Best (smalest +ve) parameter value to move */ + + /* See which axis cell crossing we will hit next */ + for (f = 0; f < fdi; f++) { + double t; + if (l->de[f] != 0) { + t = ((l->ci[f] + l->di[f]) * s->rev.gw[f] - l->st[f])/l->de[f]; + DBG(("t for dim %d = %f\n",f,t)); + if (t < bt) { + bt = t; + bf = f; /* Best direction to move */ + } + } + } + + /* Move to the next reverse grid coordinate */ + l->ci[bf] += l->di[bf]; + l->t = bt; + + DBGV(("current line cell =", fdi, " %d", l->ci, "")); DBG((", t = %f\n",l->t)); + +#ifdef DEBUG +{ +int ii; +double tt; +printf("Current cell = "); +for (ii = 0; ii < fdi; ii++) { + tt = l->ci[ii] * s->rev.gw[ii] + s->rev.gl[ii]; + printf(" %f - %f",tt,tt+s->rev.gw[ii]); +} +printf("\n"); +} +#endif /* DEBUG */ + + /* Compute reverse cell index */ + for (rpp = s->rev.rev, f = 0; f < fdi; f++) { + if (l->ci[f] < 0 || l->ci[f] > rgres_1) { /* If outside valid reverse range */ + DBG(("Outside list on dim %d, 0 <= %d <= %d\n", f, l->ci[f],rgres_1)); + return NULL; + } + rpp += l->ci[f] * s->rev.coi[f]; /* Accumulate reverse grid pointer */ + } + if (*rpp == NULL) + return NULL; + return *rpp + 3; +} + +/* ------------------------------------- */ +/* Clip nearest support. */ + +/* Track candidate cells nearest and furthest */ +struct _nncell_nf{ + double n, f; +}; typedef struct _nncell_nf nncell_nf; + +/* Given and empty nnrev index, create a list of */ +/* the forward cells that may contain the nearest value by */ +/* using and exaustive search. This is used for faststart. */ +static void fill_nncell( + rspl *s, + int *co, /* Integer coords of cell to be filled */ + int ix /* Index of cell to be filled */ +) { + int i; + int e, di = s->di; + int f, fdi = s->fdi; + double cc[MXDO]; /* Cell center */ + double rr = 0.0; /* Cell radius */ + int **rpp, *rp; + int gno = s->g.no; + float *gp; /* Pointer to fwd grid points */ + nncell_nf *nf; /* cloase and far distances corresponding to list */ + double clfu = 1e38; /* closest furthest distance in list */ + + rpp = s->rev.nnrev + ix; + rp = *rpp; + + /* Compute the center location and radius of the target cell */ + for (f = 0; f < fdi; f++) { + cc[f] = s->rev.gw[f] * (co[f] + 0.5) + s->rev.gl[f]; + rr += 0.25 * s->rev.gw[f] * s->rev.gw[f]; + } + rr = sqrt(rr); +//printf("~1 fill_nncell() cell ix %d, coord %d %d %d, cent %f %f %f, rad %f\n", +//ix, co[0], co[1], co[2], cc[0], cc[1], cc[2], rr); +//printf("~1 total of %d fwd cells\n",gno); + + /* For all the forward cells: */ + for (gp = s->g.a, i = 0; i < gno; gp += s->g.pss, i++) { + int ee; + int uil; /* One is under the ink limit */ + double dn, df; /* Nearest and farthest distance of fwd cell values */ + + /* Skip cubes that are on the outside edge of the grid */ + for (e = 0; e < di; e++) { + if(G_FL(gp, e) == 0) /* At the top edge */ + break; + } + if (e < di) { /* Top edge - skip this cube */ + continue; + } + + /* Compute the closest and furthest distances of nodes of current cell */ + dn = 1e38, df = 0.0; + for (uil = ee = 0; ee < (1 << di); ee++) { /* For all grid points in the cube */ + double r; + float *gt = gp + s->g.fhi[ee]; /* Pointer to cube vertex */ + + if (!s->limiten || gt[-1] <= s->limitv) + uil = 1; + + /* Update bounding box for this grid point */ + for (r = 0.0, f = 0; f < fdi; f++) { + double tt = cc[f] - (double)gt[f]; + r += tt * tt; + } +//printf("~1 grid location %f %f %f rad %f\n",gt[0],gt[1],gt[2],sqrt(r)); + if (r < dn) + dn = r; + if (r > df) + df = r; + } + /* Skip any fwd cells that are over the ink limit */ + if (!uil) + continue; + + dn = sqrt(dn) - rr; + df = sqrt(df) + rr; + +//printf("~1 checking cell %d, near %f, far %f\n",i,dn,df); + + /* Skip any that have a closest distance larger that the lists */ + /* closest furthest distance. */ + if (dn > clfu) { +//printf("~1 skipping cell %d, near %f, far %f clfu %f\n",i,dn,df,clfu); + continue; + } + +//printf("~1 adding cell %d\n",i); + if (rp == NULL) { + if ((nf = (nncell_nf *) rev_malloc(s, 6 * sizeof(nncell_nf))) == NULL) + error("rspl malloc failed - nncell_nf list"); + INCSZ(s, 6 * sizeof(nncell_nf)); + if ((rp = (int *) rev_malloc(s, 6 * sizeof(int))) == NULL) + error("rspl malloc failed - rev.grid entry"); + INCSZ(s, 6 * sizeof(int)); + *rpp = rp; + rp[0] = 6; /* Allocation */ + rp[1] = 4; /* Next empty cell */ + rp[2] = 1; /* Reference count */ + rp[3] = i; + nf[3].n = dn; + nf[3].f = df; + rp[4] = -1; + } else { + int z = rp[1], ll = rp[0]; + if (z >= (ll-1)) { /* Not enough space */ + INCSZ(s, ll * sizeof(nncell_nf)); + INCSZ(s, ll * sizeof(int)); + ll *= 2; + if ((nf = (nncell_nf *) rev_realloc(s, nf, sizeof(nncell_nf) * ll)) == NULL) + error("rspl realloc failed - nncell_nf list"); + if ((rp = (int *) rev_realloc(s, rp, sizeof(int) * ll)) == NULL) + error("rspl realloc failed - rev.grid entry"); + *rpp = rp; + rp[0] = ll; + } + rp[z] = i; + nf[z].n = dn; + nf[z++].f = df; + rp[z] = -1; + rp[1] = z; + } + + if (df < clfu) + clfu = df; + } +//printf("~1 Current list is:\n"); +//for (e = 3; rp[e] != -1; e++) +//printf(" %d: Cell %d near %f far %f\n",e,rp[e],nf[e].n,nf[e].f); + + /* Now filter out any cells that have a closest point that is further than */ + /* closest furthest point */ + { + int z, w, ll = rp[0]; + + /* For all the cells in the current list: */ + for (w = z = 3; rp[z] != -1; z++) { + + /* If the new cell nearest is greater than the existing cell closest, */ + /* then don't omit existing cell from the list. */ + if (clfu >= nf[z].n) { + rp[w] = rp[z]; + nf[w].n = nf[z].n; + nf[w].f = nf[z].f; + w++; + } +//else printf("~1 deleting cell %d because %f >= %f\n",rp[z],clfu, nf[z].f); + } + rp[w] = rp[z]; + } +//printf("~1 Current list is:\n"); +//for (e = 3; rp[e] != -1; e++) +//printf(" %d: Cell %d near %f far %f\n",e,rp[e],nf[e].n,nf[e].f); + free(nf); +//printf("~1 Done\n"); +} + +/* Return the pointer to the list of nearest fwd cells given */ +/* the target output values. The pointer will be to the first */ +/* index in the list (ie. list address + 3) */ +/* Return NULL if none in list (out of gamut). */ +static int * +calc_fwd_nn_cell_list( + rspl *s, /* this */ + double *v /* Output values */ +) { + int f, fdi = s->fdi, ix; + int **rpp; + int rgres_1 = s->rev.res - 1; + int mi[MXDO]; + + if (s->rev.rev_valid == 0) + init_revaccell(s); + + for (ix = 0, f = 0; f < fdi; f++) { + double t = (v[f] - s->rev.gl[f])/s->rev.gw[f]; + mi[f] = (int)floor(t); /* Grid coordinate */ + if (mi[f] < 0) /* Clip to reverse range, so we always return a result */ + mi[f] = 0; + else if (mi[f] > rgres_1) + mi[f] = rgres_1; + ix += mi[f] * s->rev.coi[f]; /* Accumulate reverse grid index */ + } + rpp = s->rev.nnrev + ix; + if (*rpp == NULL) { + if (s->rev.fastsetup) + fill_nncell(s, mi, ix); + if (*rpp == NULL) + rpp = s->rev.rev + ix; /* fall back to in-gamut lookup */ + } + if (*rpp == NULL) + return NULL; + return (*rpp) + 3; +} + +/* =================================================== */ +/* The cell and simplex solver top level routines */ + +static int add_lu_svd(simplex *x); +static int add_locus(schbase *b, simplex *x); +static int add_auxil_lu_svd(schbase *b, simplex *x); +static int within_simplex(simplex *x, double *p); +static void simplex_to_abs(simplex *x, double *in, double *out); + +static int auxil_solve(schbase *b, simplex *x, double *xp); + +/* ---------------------- */ +/* Exact search functions */ +/* Return non-zero if cell is acceptable */ +static int exact_setsort(schbase *b, cell *c) { + rspl *s = b->s; + int f, fdi = s->fdi; + double ss; + + DBG(("Reverse exact search, evaluate and set sort key on cell\n")); + + /* Check that the target lies within the cell bounding sphere */ + for (ss = 0.0, f = 0; f < fdi; f++) { + double tt = c->bcent[f] - b->v[f]; + ss += tt * tt; + } + if (ss > c->bradsq) { + DBG(("Cell rejected - %s outside sphere c %s rad %f\n",icmPdv(fdi,b->v),icmPdv(fdi,c->bcent),sqrt(c->bradsq))); + return 0; + } + + if (s->limiten != 0 && c->limmin > s->limitv) { + DBG(("Cell is rejected - ink limit, min = %f, limit = %f\n",c->limmin,s->limitv)); + return 0; + } + + /* Sort can't be used, because we return all solutions */ + c->sort = 0.0; + + DBG(("Cell is accepted\n")); + + return 1; +} + +/* Compute a solution for a given sub-simplex (if there is one) */ +/* Return 1 if search should be aborted */ +static int exact_compute(schbase *b, simplex *x) { + rspl *s = b->s; + int e, di = s->di, sdi = x->sdi; + int f, fdi = s->fdi; + int i; + datai xp; /* solution in simplex relative coord order */ + datai p; /* absolute solution */ + int wsrv; /* Within simplex return value */ + + DBG(("\nExact: computing possible solution\n")); + +#ifdef DEBUG + /* Sanity check */ + if (sdi != fdi || sdi != di || x->efdi != fdi) { + printf("di = %d, fdi = %d\n",di,fdi); + printf("sdi = %d, efdi = %d\n",sdi,x->efdi); + error("rspl exact reverse interp called with sdi != fdi, sdi != di, efdi != fdi"); + /* !!! could switch to SVD solution if di != fdi ?? !!! */ + } +#endif + + /* This may not be worth it here since it may not filter out */ + /* many more simplexes than the cube check did. */ + /* This is due to full dimension simplexes all sharing the main */ + /* diagonal axis. */ + + /* Check that the target lies within the simplex bounding cube */ + for (f = 0; f < fdi; f++) { + if (b->v[f] < x->min[f] || b->v[f] > x->max[f]) { + DBG(("Simplex is rejected - bounding cube\n")); + return 0; + } + } + + /* Create the LU decomp needed to exactly solve */ + if (add_lu_svd(x)) { + DBG(("LU decomp was singular, skip simplex\n")); + return 0; + } + + /* Init the RHS B[] vector (note di == fdi) */ + for (f = 0; f < fdi; f++) { + xp[f] = b->v[f] - x->v[di][f]; + } + + /* Compute the solution (in simplex space) */ + lu_backsub(x->d_u, sdi, (int *)x->d_w, xp); + + /* Check that the solution is within the simplex */ + if ((wsrv = within_simplex(x, xp)) == 0) { + DBG(("Solution rejected because not in simplex\n")); + return 0; + } + + /* Convert solution from simplex relative to absolute space */ + simplex_to_abs(x, p, xp); + + /* Check if a very similiar input solution has been found before */ + for (i = 0; i < b->nsoln; i++) { + double tt; + for (e = 0; e < di; e++) { + tt = b->cpp[i].p[e] - p[e]; + if (fabs(tt) > (2 * EPS)) + break; /* Mismatch */ + } + if (e >= di) /* Found good match */ + break; + } + + /* Probably alias caused by solution lying close to a simplex boundary */ + if (i < b->nsoln) { + DBG(("Another solution has been found before - index %d\n",i)); + return 0; /* Skip this, since betters been found before */ + } + + /* Check we haven't overflowed space */ + if (i >= b->mxsoln) { + DBG(("Run out of space for new solution\n")); + return 1; /* Abort */ + } + + DBG(("######## Accepting new solution\n")); + + /* Put solution in place */ + for (e = 0; e < di; e++) + b->cpp[i].p[e] = p[e]; + for (f = 0; f < fdi; f++) + b->cpp[i].v[f] = b->v[f]; /* Assumed to be an exact solution */ + if (i == b->nsoln) + b->nsoln++; + if (wsrv == 2) /* Is above (disabled) ink limit */ + b->iclip = 1; + return 0; +} + +/* -------------------------- */ +/* Auxiliary search functions */ +static int auxil_setsort(schbase *b, cell *c) { + rspl *s = b->s; + int f, fdi = b->s->fdi; + int ee, ixc = b->ixc; + double ss, sort, nabove; + + DBG(("Reverse auxiliary search, evaluate and set sort key on cell\n")); + + if (b->s->di <= fdi) { /* Assert */ + error("rspl auxiliary reverse interp called with di <= fdi (%d %d)", b->s->di, fdi); + } + + /* Check that the target lies within the cell bounding sphere */ + for (ss = 0.0, f = 0; f < fdi; f++) { + double tt = c->bcent[f] - b->v[f]; + ss += tt * tt; + } + if (ss > c->bradsq) { + DBG(("Cell rejected - %s outside sphere c %s rad %f\n",icmPdv(fdi,b->v),icmPdv(fdi,c->bcent),sqrt(c->bradsq))); + return 0; + } + + if (s->limiten != 0 && c->limmin > s->limitv) { + DBG(("Cell is rejected - ink limit, min = %f, limit = %f\n",c->limmin,s->limitv)); + return 0; + } + + /* Check if this cell could possible improve b->idist */ + /* and compute sort key as the distance to auxilliary target */ + /* (We may have a non INF_DIST idist before commencing the */ + /* search if we already know that the auxiliary target is */ + /* within gamut - the usual usage case!) */ + for (sort = 0.0, nabove = ee = 0; ee < b->naux; ee++) { + int ei = b->auxi[ee]; + double tt = (c->p[0][ei] + c->p[ixc][ei]) - b->av[ei]; + sort += tt * tt; + if (c->p[ixc][ei] >= (b->av[ei] - EPS)) /* Could be above */ + nabove++; + } + + if (b->flags & RSPL_MAXAUX && nabove < b->iabove) { + DBG(("Doesn't contain solution that has as many aux above auxiliary goal\n")); + return 0; + } + if (!(b->flags & RSPL_MAXAUX) || nabove == b->iabove) { + for (ee = 0; ee < b->naux; ee++) { + int ei = b->auxi[ee]; + if (c->p[0][ei] >= (b->av[ei] + b->idist) + || c->p[ixc][ei] <= (b->av[ei] - b->idist)) { + DBG(("Doesn't contain solution that will be closer to auxiliary goal\n")); + return 0; + } + } + } + c->sort = sort + 0.01 * ss; + + if (c->ix == b->pauxcell) + c->sort = -1.0; /* Put previous calls solution cell at top of sort list */ + + DBG(("Cell is accepted\n")); + return 1; +} + +/* Re-check whether it's worth searching cell */ +static int auxil_check(schbase *b, cell *c) { + int ee, ixc = b->ixc, nabove; + + DBG(("Reverse auxiliary search, re-check cell\n")); + + /* Check if this cell could possible improve b->idist */ + /* and compute sort key as the distance to auxilliary target */ + + for (nabove = ee = 0; ee < b->naux; ee++) { + int ei = b->auxi[ee]; + if (c->p[ixc][ei] >= (b->av[ei] - EPS)) /* Could be above */ + nabove++; + } + + if (b->flags & RSPL_MAXAUX && nabove < b->iabove) { + DBG(("Doesn't contain solution that has as many aux above auxiliary goal\n")); + return 0; + } + if (!(b->flags & RSPL_MAXAUX) || nabove == b->iabove) { + for (ee = 0; ee < b->naux; ee++) { + int ei = b->auxi[ee]; + if (c->p[0][ei] >= (b->av[ei] + b->idist) + || c->p[ixc][ei] <= (b->av[ei] - b->idist)) { + DBG(("Doesn't contain solution that will be closer to auxiliary goal\n")); + return 0; + } + } + } + DBG(("Cell is still ok\n")); + return 1; +} + +/* Compute a solution for a given simplex (if there is one) */ +/* Return 1 if search should be aborted */ +static int auxil_compute(schbase *b, simplex *x) { + rspl *s = b->s; + int e, di = s->di; + int f, fdi = s->fdi; + datai xp; /* solution in simplex relative coord order */ + datai p; /* absolute solution */ + double idist; /* Auxiliary input distance */ + int wsrv; /* Within simplex return value */ + int nabove; /* Number above aux target */ + + DBG(("\nAuxil: computing possible solution\n")); + +#ifdef DEBUG + { + unsigned int sum = 0; + for (f = 0; f <= x->sdi; f++) + sum += x->vix[f]; + printf("Simplex of cell ix %d, sum 0x%x, sdi = %d, efdi = %d\n",x->ix, sum, x->sdi, x->efdi); + printf("Target val %s\n",icmPdv(fdi, b->v)); + for (f = 0; f <= x->sdi; f++) { + int ix = x->vix[f], i; + float *fcb = s->g.a + ix * s->g.pss; /* Pointer to base float of fwd cell */ + printf("Simplex vtx %d [cell ix %d] val %s\n",f,ix,icmPfv(fdi, fcb)); + } + } +#endif + + /* Check that the target lies within the simplex bounding cube */ + for (f = 0; f < fdi; f++) { + if (b->v[f] < x->min[f] || b->v[f] > x->max[f]) { + DBG(("Simplex is rejected - bounding cube\n")); + return 0; + } + } + + /* Check if this cell could possible improve b->idist */ + for (nabove = e = 0; e < b->naux; e++) { + int ei = b->auxi[e]; /* pmin/max[] is indexed in input space */ + if (x->pmax[ei] >= (b->av[ei] - EPS)) /* Could be above */ + nabove++; + } + if ((b->flags & RSPL_MAXAUX) && nabove < b->iabove) { + DBG(("Simplex doesn't contain solution that has as many aux above auxiliary goal\n")); + return 0; + } + if (!(b->flags & RSPL_MAXAUX) || nabove == b->iabove) { + for (nabove = e = 0; e < b->naux; e++) { + int ei = b->auxi[e]; /* pmin/max[] is indexed in input space */ + if (x->pmin[ei] >= (b->av[ei] + b->idist) + || x->pmax[ei] <= (b->av[ei] - b->idist)) { + DBG(("Simplex doesn't contain solution that will be closer to auxiliary goal\n")); + return 0; + } + } + } + +//printf("~~ About to create svd decomp\n"); + /* Create the SVD or LU decomp needed to compute solution or locus */ + if (add_lu_svd(x)) { + DBG(("SVD decomp failed, skip simplex\n")); + return 0; + } + +//printf("~~ About to solve locus for aux target\n"); + /* Now solve for locus parameter that minimises */ + /* distance to auxliary target. */ + if ((wsrv = auxil_solve(b, x, xp)) == 0) { + DBG(("Target auxiliary along locus is outside simplex,\n")); + DBG(("or computation failed, skip simplex\n")); + return 0; + } + +//printf("~~ About to convert solution to absolute space\n"); + /* Convert solution from simplex relative to absolute space */ + simplex_to_abs(x, p, xp); + + DBG(("Got solution at %s\n", icmPdv(di,p))); + +//printf("~~ soln = %f %f %f %f\n",p[0],p[1],p[2],p[3]); +//printf("~~ About to compute auxil distance\n"); + /* Compute distance to auxiliary target */ + for (idist = 0.0, nabove = e = 0; e < b->naux; e++) { + int ei = b->auxi[e]; + double tt = b->av[ei] - p[ei]; + idist += tt * tt; + if (p[ei] >= (b->av[ei] - EPS)) + nabove++; + } + idist = sqrt(idist); +//printf("~1 idist %f, nabove %d\n",idist, nabove); +//printf("~1 best idist %f, best iabove %d\n",b->idist, b->iabove); + + /* We want the smallest error from auxiliary target */ + if (b->flags & RSPL_MAXAUX) { + if (nabove < b->iabove || (nabove == b->iabove && idist >= b->idist)) { + DBG(("nsoln %d, nabove %d, iabove %d, idist = %f, better solution has been found before\n",b->nsoln, nabove, b->iabove, idist)); + return 0; + } + } else { + if (idist >= b->idist) { /* Equal or worse auxiliary solution */ + DBG(("nsoln %d, idist = %f, better solution has been found before\n",b->nsoln,idist)); + return 0; + } + } + + /* Solution is accepted */ + DBG(("######## Accepting new solution with nabove %d <= iabove %d and idist %f <= %f\n",nabove,b->iabove,idist,b->idist)); + for (e = 0; e < di; e++) + b->cpp[0].p[e] = p[e]; + for (f = 0; f < fdi; f++) + b->cpp[0].v[f] = b->v[f]; /* Assumed to be an exact solution */ + b->idist = idist; + b->iabove = nabove; + b->nsoln = 1; + b->pauxcell = x->ix; + if (wsrv == 2) /* Is above (disabled) ink limit */ + b->iclip = 1; + + return 0; +} + +/* ------------------------------------ */ +/* Locus range search functions */ + +static int locus_setsort(schbase *b, cell *c) { + rspl *s = b->s; + int f, fdi = s->fdi; + int lxi = b->lxi; /* Auxiliary we are finding min/max of */ + int ixc = b->ixc; + double sort, ss; + + DBG(("Reverse locus evaluate and set sort key on cell\n")); + +#ifdef DEBUG + if (b->s->di <= fdi) { /* Assert ~1 */ + error("rspl auxiliary locus interp called with di <= fdi"); + } +#endif /* DEBUG */ + + /* Check that the target lies within the cell bounding sphere */ + for (ss = 0.0, f = 0; f < fdi; f++) { + double tt = c->bcent[f] - b->v[f]; + ss += tt * tt; + } + if (ss > c->bradsq) { + DBG(("Cell rejected - %s outside sphere c %s rad %f\n",icmPdv(fdi,b->v),icmPdv(fdi,c->bcent),sqrt(c->bradsq))); + return 0; + } + + if (s->limiten != 0 && c->limmin > s->limitv) { + DBG(("Cell is rejected - ink limit, min = %f, limit = %f\n",c->limmin,s->limitv)); + return 0; + } + + /* Check if this cell could possible improve the locus min/max */ + if (b->asegs == 0) { /* If we aren't find all segments of the locus */ + if (c->p[0][lxi] >= b->min && c->p[ixc][lxi] <= b->max ) { + DBG(("Doesn't contain solution that will expand the locus\n")); + return 0; + } + } + + /* Compute sort index from average of auxiliary values */ + sort = (c->p[0][b->lxi] + c->p[ixc][b->lxi]); + + c->sort = sort + 0.01 * ss; + + DBG(("Cell is accepted\n")); + return 1; +} + +/* Re-check whether it's worth searching simplexes */ +static int locus_check(schbase *b, cell *c) { + int lxi = b->lxi; /* Auxiliary we are finding min/max of */ + int ixc = b->ixc; + + DBG(("Reverse locus re-check\n")); + + /* Check if this cell could possible improve the locus min/max */ + if (b->asegs == 0) { /* If we aren't find all segments of the locus */ + if (c->p[0][lxi] >= b->min && c->p[ixc][lxi] <= b->max ) { + DBG(("Doesn't contain solution that will expand the locus\n")); + return 0; + } + } + + DBG(("Cell is still ok\n")); + return 1; +} + +static int auxil_locus(schbase *b, simplex *x); + +/* We expect to be given a sub-simplex with no DOF, to give an exact solution */ +static int locus_compute(schbase *b, simplex *x) { + rspl *s = b->s; + int f, fdi = s->fdi; + int lxi = b->lxi; /* Auxiliary we are finding min/max of */ + + DBG(("\nLocus: computing possible solution\n")); + +#ifdef DEBUG + { + unsigned int sum = 0; + for (f = 0; f <= x->sdi; f++) + sum += x->vix[f]; + printf("Simplex of cell ix %d, sum 0x%x, sdi = %d, efdi = %d\n",x->ix, sum, x->sdi, x->efdi); + printf("Target val %s\n",icmPdv(fdi, b->v)); + for (f = 0; f <= x->sdi; f++) { + int ix = x->vix[f], i; + float *fcb = s->g.a + ix * s->g.pss; /* Pointer to base float of fwd cell */ + double v[MXDO]; + printf("Simplex vtx %d [cell ix %d] val %s\n",f,ix,icmPfv(fdi, fcb)); + } + } +#endif + + /* Check that the target lies within the simplex bounding cube */ + for (f = 0; f < fdi; f++) { + if (b->v[f] < x->min[f] || b->v[f] > x->max[f]) { + DBG(("Simplex is rejected - bounding cube\n")); + return 0; + } + } + + /* Check if simplex could possible improve the locus min/max */ + if (b->asegs == 0) { /* If we aren't find all segments of the locus */ + if (x->pmin[lxi] >= b->min && x->pmax[lxi] <= b->max ) { + DBG(("Simplex doesn't contain solution that will expand the locus\n")); + return 0; + } + } + +//printf("~~ About to create svd decomp\n"); + /* Create the SVD decomp needed to compute solution extreme points */ + if (add_lu_svd(x)) { + DBG(("SVD decomp failed, skip simplex\n")); + return 0; + } + +//printf("~~ About to solve locus for aux extremes\n"); + /* Now solve for locus parameter that are at the extremes */ + /* of the axiliary we are interested in. */ + if (!auxil_locus(b, x)) { + DBG(("Target auxiliary is outside simplex,\n")); + DBG(("or computation failed, skip simplex\n")); + return 0; + } + + return 0; +} + +/* ------------------- */ +/* Vector clipping search functions */ +static int clipv_setsort(schbase *b, cell *c) { + rspl *s = b->s; + int f, fdi = s->fdi; + double ss, dp; + + DBG(("Reverse clipping search evaluate cell\n")); + +//printf("~~sphere center = %f %f %f, radius %f\n",c->bcent[0],c->bcent[1],c->bcent[2],sqrt(c->bradsq)); + /* Check if the clipping line intersects the bounding sphere */ + /* First compute dot product cdir . (bcent - v) */ + /* == distance to center of sphere in direction of clip vector */ + for (dp = 0.0, f = 0; f < fdi; f++) { + dp += b->ncdir[f] * (c->bcent[f] - b->v[f]); + } + + if (s->limiten != 0 && c->limmin > s->limitv) { + DBG(("Cell is rejected - ink limit, min = %f, limit = %f\n",c->limmin,s->limitv)); + return 0; + } + +//printf("~~ dot product = %f\n",dp); + /* Now compute closest distance to sphere center */ + for (ss = 0.0, f = 0; f < fdi; f++) { + double tt = b->v[f] + dp * b->ncdir[f] - c->bcent[f]; + ss += tt * tt; + } + +//printf("~~ distance to sphere center = %f\n",sqrt(ss)); + if (ss > c->bradsq) { + DBG(("Cell is rejected - wrong direction or bounding sphere\n")); + return 0; + } + c->sort = dp; /* May be -ve if beyond clip target point ? */ + + DBG(("Cell is accepted\n")); + return 1; +} + +/* Clipping check functions */ +/* Note that we don't bother with this check in setsort(), */ +/* because we assume that nothing will set a small cdist */ +/* before the search commences (unlike auxil). */ +/* Note that line search loop exits on finding any solution. */ +static int clipv_check(schbase *b, cell *c) { + + DBG(("Reverse clipping re-check\n")); + + if (b->cdist < INF_DIST) { /* If some clip solution has been found */ + int f, fdi = b->s->fdi; + double dist; + /* Compute a conservative "best possible solution clip distance" */ + for (dist = 0.0, f = 0; f < fdi ; f++) { + double tt = (c->bcent[f] - b->v[f]); + dist += tt * tt; + } + dist = sqrt(dist); /* Target distance to bounding */ + + if (dist >= (c->brad + b->cdist)) { /* Equal or worse clip solution */ + DBG(("Cell best possible solution worse than current\n")); + return 0; + } + } + + DBG(("Cell is still ok\n")); + return 1; +} + +static int vnearest_clip_solve(schbase *b, simplex *x, double *xp, double *xv, double *err); + +/* Compute a clip solution */ +static int clipv_compute(schbase *b, simplex *x) { + rspl *s = b->s; + int f, fdi = s->fdi; + datai p; /* Input space solution */ + datao v; /* Output space solution */ + double err; /* output error of solution */ + int wsrv; /* Within simplex return value */ + + DBG(("Clips: computing possible solution\n")); + + /* Compute a solution value */ + if ((wsrv = vnearest_clip_solve(b, x, p, v, &err)) == 0) { + DBG(("Doesn't contain a solution\n")); + return 0; + } + + /* We want the smallest clip error */ + /* (Should we reject points in -ve vector direction ??) */ + if (err >= b->cdist) { /* Equal or worse clip solution */ + DBG(("better solution has been found before\n")); + return 0; + } + + simplex_to_abs(x, b->cpp[0].p, p); /* Convert to abs. space & copy */ + + DBG(("######## Accepting new clipv solution with error %f\n",err)); +#ifdef DEBUG + if (s->limiten != 0) { + DBG(("######## Ink value = %f, limit %f\n",get_limitv(b, x->ix, NULL, b->cpp[0].p), s->limitv)); + } +#endif + + /* Put solution in place */ + for (f = 0; f < fdi; f++) + b->cpp[0].v[f] = v[f]; + b->cdist = err; + b->nsoln = 1; + if (wsrv == 2) /* Is above (disabled) ink limit */ + b->iclip = 1; + + return 0; +} + +/* ------------------- */ +/* Nearest clipping search functions */ +static int clipn_setsort(schbase *b, cell *c) { + rspl *s = b->s; + int f, fdi = s->fdi; + double ss; + + DBG(("Reverse nearest clipping search evaluate cell\n")); + + /* Compute a conservative "best possible solution clip distance" */ + for (ss = 0.0, f = 0; f < fdi ; f++) { + double tt = (c->bcent[f] - b->v[f]); + ss += tt * tt; + } + ss = sqrt(ss); /* Target distance to bounding sphere */ + ss -= c->brad; + if (ss < 0.0) + ss = 0.0; + + /* Check that the cell could possibly improve the solution */ + if (b->cdist < INF_DIST) { /* If some clip solution has been found */ + if (ss >= b->cdist) { /* Equal or worse clip solution */ + DBG(("Cell best possible solution worse than current\n")); + return 0; + } + } + + if (s->limiten != 0 && c->limmin > s->limitv) { + DBG(("Cell is rejected - ink limit, min = %f, limit = %f\n",c->limmin,s->limitv)); + return 0; + } + + c->sort = ss; /* May be -ve if beyond clip target point ? */ + + DBG(("Cell is accepted\n")); + return 1; +} + +/* Clipping check functions */ +static int clipn_check(schbase *b, cell *c) { + + DBG(("Reverse nearest clipping re-check\n")); + + if (b->cdist < INF_DIST) { /* If some clip solution has been found */ + /* re-use sort value, best possible distance to solution */ + if (c->sort >= b->cdist) { /* Equal or worse clip solution */ + DBG(("Cell best possible solution worse than current\n")); + return 0; + } + } + + DBG(("Cell is still ok\n")); + return 1; +} + +static int nnearest_clip_solve(schbase *b, simplex *x, double *xp, double *xv, double *err); + +/* Compute a clip solution */ +static int clipn_compute(schbase *b, simplex *x) { + rspl *s = b->s; + int f, fdi = s->fdi; + datai p; /* Input space solution */ + datao v; /* Output space solution */ + double err; /* output error of solution */ + int wsrv; /* Within simplex return value */ + + DBG(("Clipn: computing possible solution simplex %d, sdi = %d, efdi = %d\n",x->si,x->sdi,x->efdi)); + + /* Compute a solution value */ + if ((wsrv = nnearest_clip_solve(b, x, p, v, &err)) == 0) { + DBG(("Doesn't contain a solution\n")); + return 0; + } + + /* We want the smallest clip error */ + if (err >= b->cdist) { /* Equal or worse clip solution */ + DBG(("better solution has been found before\n")); + return 0; + } + + DBG(("######## Accepting new clipn solution with error %f\n",err)); + + simplex_to_abs(x, b->cpp[0].p, p); /* Convert to abs. space & copy */ + + /* Put solution in place */ + for (f = 0; f < fdi; f++) + b->cpp[0].v[f] = v[f]; + b->cdist = err; + b->nsoln = 1; + if (wsrv == 2) /* Is above (disabled) ink limit */ + b->iclip = 1; + + return 0; +} + +/* -------------------------------------------------------- */ +/* Cell/simplex solver middle level code */ + +/* Find the point on this sub-simplexes solution locus that is */ +/* closest to the target auxiliary values, and return it in xp[] */ +/* Return zero if this point canot be calculated, */ +/* or it lies outside the simplex. */ +/* Return 1 normally, and 2 if the solution would be over the ink limit */ +static int +auxil_solve( +schbase *b, +simplex *x, +double *xp /* Return solution xp[sdi] */ +) { + rspl *s = b->s; + int ee, e, di = s->di, sdi = x->sdi; + int f, efdi = x->efdi; + int dof = sdi-efdi; /* Degree of freedom of simplex locus */ + int *icomb = x->psxi->icomb; /* abs -> simplex coordinate translation */ + double auxt[MXRI]; /* Simplex relative auxiliary targets */ + double bb[MXRI]; + int wsrv; /* Within simplex return value */ + + DBG(("axuil_solve called\n")); + + if (dof < 0) + error("Error - auxil_solve got sdi < efdi (%d < %d) - don't know how to handle this",sdi, efdi); + + /* If there is no locus, compute an exact solution */ + if (dof == 0) { + DBG(("axuil_solve dof = zero\n")); + + /* Init the RHS B[] vector (note sdi == efdi) */ + for (f = 0; f < efdi; f++) { + xp[f] = b->v[f] - x->v[sdi][f]; + } + + /* Compute the solution (in simplex space) */ + lu_backsub(x->d_u, sdi, (int *)x->d_w, xp); + + if ((wsrv = within_simplex(x, xp)) != 0) { + DBG(("Got solution at %s\n", icmPdv(sdi,xp))); + return wsrv; /* OK, got solution */ + } + + DBG(("No solution (not within simplex)\n")); + return 0; + } + + /* There is a locus, so find solution nearest auxiliaries */ + + /* Compute locus for target function values (if sdi > efdi) */ + if (add_locus(b, x)) { + DBG(("Locus computation failed, skip simplex\n")); + return 0; + } + + /* Convert aux targets from absolute space to simplex relative */ + for (e = 0; e < di; e++) { /* For abs coords */ + int ei = icomb[e]; /* Simplex coord */ + + if (ei >= 0 && b->auxm[e] != 0) { + auxt[ei] = (b->av[e] - x->p0[e])/s->g.w[e]; /* Only sets those needed */ + } + } + + if (dof == 1 && b->naux == 1) { /* Special case, because it's common and easy! */ + int ei = icomb[b->auxi[0]]; /* Simplex relative auxiliary index */ + double tt; + + DBG(("axuil_solve dof = naux = 1\n")); + if (ei < 0) + return 0; /* Not going to find solution */ + if ((tt = x->lo_l[ei][0]) == 0.0) + return 0; + tt = (auxt[ei] - x->lo_bd[ei])/tt; /* Parameter solution for target auxiliary */ + + /* Back substitute parameter */ + for (e = 0; e < sdi; e++) { + xp[e] = x->lo_bd[e] + tt * x->lo_l[e][0]; + } + if ((wsrv = within_simplex(x, xp)) != 0) { + DBG(("Got solution %s\n",icmPdv(di,xp))); + return wsrv; /* OK, got solution */ + } + DBG(("No solution (not within simplex)\n")); + return 0; + } + + /* Compute the locus decompositions needed (info #5) */ + if (add_auxil_lu_svd(b, x)) { /* Will set x->naux */ + DBG(("LU/SVD decomp failed\n")); + return 0; + } + + /* Setup B[], equation RHS */ + for (e = ee = 0; ee < b->naux; ee++) { + int ei = icomb[b->auxi[ee]]; /* Simplex relative auxiliary index */ + if (ei >= 0) /* Usable auxiliary on this sub simplex */ + bb[e++] = auxt[ei] - x->lo_bd[ei]; + } + if (e != x->naux) /* Assert */ + error("Internal error - auxil_solve got mismatching number of auxiliaries"); + + if (x->naux == dof) { /* Use LU decomp to solve */ + DBG(("axuil_solve using LU\n")); + lu_backsub(x->ax_u, dof, (int *)x->ax_w, bb); + + } else if (x->naux > 0) { /* Use SVD to solve least squares */ + DBG(("axuil_solve using SVD\n")); + svdbacksub(x->ax_u, x->ax_w, x->ax_v, bb, bb, x->naux, dof); + + } else { /* x->naux == 0 */ + DBG(("axuil_solve naux = 0\n")); + for (f = 0; f < dof; f++) + bb[f] = 0.0; /* Use base solution ?? */ + } + + /* Now back substitute the locus parameters */ + /* to calculate the solution point (in simplex space) */ + for (e = 0; e < sdi; e++) { + double tt; + for (tt = 0.0, f = 0; f < dof; f++) { + tt += bb[f] * x->lo_l[e][f]; + } + xp[e] = x->lo_bd[e] + tt; + } + + if ((wsrv = within_simplex(x, xp)) != 0) { + DBG(("Got solution %s\n",icmPdv(di,xp))); + return wsrv; /* OK, got solution */ + } + DBG(("No solution (not within simplex)\n")); + return 0; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Compute the min/max values for the current auxiliary of interest. */ +/* Return zero if this point canot be calculated, */ +/* or it lies outside the simplex. */ +/* Return 1 normally, 2 if it would be outside the simplex if limting was enabled */ +/* We expect to get a sub-simplex that will give an exact solution. */ +static int +auxil_locus( +schbase *b, +simplex *x +) { + rspl *s = b->s; + int sdi = x->sdi; + int f, efdi = x->efdi; + double pp[MXRI]; + int wsrv; /* Within simplex return value */ + + DBG(("axuil_locus called\n")); + + if (sdi != efdi) + warning("Internal error - auxil_locus got sdi != efdi (%d < %d)",sdi, efdi); + + /* Init the RHS B[] vector (note sdi == efdi) */ + for (f = 0; f < efdi; f++) { + pp[f] = b->v[f] - x->v[sdi][f]; + } + + /* Compute the solution (in simplex space) */ + lu_backsub(x->d_u, sdi, (int *)x->d_w, pp); + + /* Check that the solution is within the simplex */ + if ((wsrv = within_simplex(x, pp)) != 0) { + double xval; + int lxi = b->lxi; /* Auxiliary we are finding min/max of (Abs space) */ + int xlxi = x->psxi->icomb[lxi]; /* Auxiliary we are finding min/max of (simplex space) */ + + DBG(("Got locus solution within simplex\n")); + + /* Compute auxiliary value for this solution (absolute space) */ + xval = x->p0[lxi]; + if (xlxi >= 0) /* Simplex param value */ + xval += s->g.w[lxi] * pp[xlxi]; + else if (xlxi == -2) /* 1 value */ + xval += s->g.w[lxi]; + /* Else 0 value */ + + if (b->asegs != 0) { /* Tracking auxiliary segments */ + if (b->axisln >= b->axislz) { /* Need some more space in list */ + if (b->axislz == 0) { + b->axislz = 10; + if ((b->axisl = (axisec *)rev_malloc(s, b->axislz * sizeof(axisec))) == NULL) + error("rev: malloc failed - Auxiliary intersect list size %d",b->axislz); + INCSZ(b->s, b->axislz * sizeof(axisec)); + } else { + INCSZ(b->s, b->axislz * sizeof(axisec)); + b->axislz *= 2; + if ((b->axisl = (axisec *)rev_realloc(s, b->axisl, b->axislz * sizeof(axisec))) + == NULL) + error("rev: realloc failed - Auxiliary intersect list size %d",b->axislz); + } + } + b->axisl[b->axisln].xval = xval; + b->axisl[b->axisln].nv = x->sdi + 1; + for (f = 0; f <= x->sdi; f++) { + b->axisl[b->axisln].vix[f] = x->vix[f]; + } + b->axisln++; + } + +#ifdef DEBUG + if (xval >= b->min && xval <= b->max) + DBG(("auxil_locus: solution %f doesn't improve on min %f, max %f\n",xval,b->min,b->max)); +#endif + /* If this solution is expands the min or max, save it */ + if (xval < b->min) { + DBG(("######## Improving minimum to %f\n",xval)); + b->min = xval; + b->plmincell = x->ix; + } + if (xval > b->max) { + DBG(("######## Improving maximum to %f\n",xval)); + b->max = xval; + b->plmaxcell = x->ix; + } + } else { + DBG(("Solution wasn't within the simplex\n")); + return 0; + } + + return wsrv; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - */ +/* Find the point on the clip line locus and simplexes */ +/* valid surface, that is closest to the target output value. */ +/* We expect to be given a sub simplex with sdi = fdi-1, and efdi = fdi */ +/* or a limit sub-simplex with sdi = fdi, and efdi = fdi+1 */ +/* Return zero if solution canot be calculated, */ +/* return 1 normally, 2 if solution would be above the (disabled) ink limit */ +static int +vnearest_clip_solve( +schbase *b, +simplex *x, +double *xp, /* Return solution (simplex parameter space) */ +double *xv, /* Return solution (output space) */ +double *err /* Output error distance at solution point */ +) { + rspl *s = b->s; + int e, sdi = x->sdi; + int f, fdi = s->fdi, efdi = x->efdi; + int g; + int wsrv; /* Within simplex return value */ + + double *ta[MXRO], TA[MXRO][MXRO]; + double tb[MXRO]; + + DBG(("Vector nearest clip solution called, cell %d, splx %d\n", x->ix, x->si)); + + /* Setup temporary matricies */ + for (f = 0; f < sdi; f++) { + ta[f] = TA[f]; + } + + /* Substitute simplex equation for output values V */ + /* in terms of sub-simplex parameters P, */ + /* into clip line implicit equation in V, to give */ + /* clip line simplex implicit equation in terms of P (simplex input space) */ + /* If this is a limit sub-simlex, the ink limit part of the clip vector */ + /* equations will be used. */ + + /* LHS: ta[sdi][sdi] = cla[sdi][efdi] * vv[efdi][sdi] */ + /* RHS: tb[sdi] = clb[sdi] - cla[sdi][efdi] * vv_di[efdi] */ + for (f = 0; f < sdi; f++) { + double tt; + for (e = 0; e < sdi; e++) { + for (tt = 0.0, g = 0; g < efdi; g++) + tt += b->cla[f][g] * (x->v[e][g] - x->v[e+1][g]); + ta[f][e] = tt; + } + for (tt = 0.0, g = 0; g < efdi; g++) + tt += b->cla[f][g] * x->v[sdi][g]; + tb[f] = b->clb[f] - tt; + } + + /* Compute the solution */ + if (gen_solve_se(ta, tb, sdi, sdi)) { + DBG(("Equation solution failed!\n")); + return 0; /* No solution */ + } + + /* Check that the solution is within the simplex */ + if ((wsrv = within_simplex(x, tb)) != 0) { + double dist; /* distance to clip target */ + + DBG(("Got solution within simplex %s\n", icmPdv(sdi,tb))); + + /* Compute the output space solution point */ + for (f = 0; f < fdi; f++) { + double tt = 0.0; + for (e = 0; e < sdi; e++) { + tt += (x->v[e][f] - x->v[e+1][f]) * tb[e]; + } + xv[f] = tt + x->v[sdi][f]; + } + + /* Copy to return array */ + for (e = 0; e < sdi; e++) + xp[e] = tb[e]; + + /* Compute distance to clip target */ + for (dist = 0.0, f = 0; f < fdi ; f++) { + double tt = (b->v[f] - xv[f]); + dist += tt * tt; + } + DBGV(("Vector clip output soln: ",fdi," %f", xv, "\n")); + + /* Return the solution in xp[]m xv[] and *err */ + *err = sqrt(dist); + + DBG(("Vector clip returning a solution with error %f\n",*err)); + return wsrv; + } + + DBG(("Vector clip solution not in simplex\n")); + return 0; /* No solution */ +} + +/* - - - - - - - - - - - - - - - - - - - - - - - */ +/* Find the point on the simplexes valid surface, that is closest */ +/* to the target output value. */ +/* We expect to be given a sub simplex with sdi = fdi-1, and efdi = fdi */ +/* or a limit sub-simplex with sdi = fdi, and efdi = fdi+1 */ +/* Return zero if solution canot be calculated, */ +/* return 1 normally, 2 if solution would be above the (disabled) ink limit */ +static int +nnearest_clip_solve( +schbase *b, +simplex *x, +double *xp, /* Return solution (simplex parameter space) */ +double *xv, /* Return solution (output space) */ +double *err /* Output error distance at solution point */ +) { + rspl *s = b->s; + int e, sdi = x->sdi; + int f, fdi = s->fdi, efdi = x->efdi; + double tb[MXRO]; /* RHS & Parameter solution */ + double dist; /* distance to clip target */ + int wsrv = 0; /* Within simplex return value */ + + DBG(("Nearest clip solution called, cell %d, splx %d\n", x->ix, x->si)); + + if (sdi == 0) { /* Solution is vertex */ + wsrv = 1; + for (f = 0; f < efdi; f++) + xv[f] = x->v[sdi][f]; /* Copy vertex value */ + if (x->v[sdi][fdi] > s->limitv) { + if (s->limiten) /* Needed when limiten == 0 */ + return 0; /* Over ink limit - no good */ + wsrv = 2; /* Would be over */ + } + DBG(("Got assumed vertex solution\n")); + } else { +#ifdef NEVER /* Don't specialise ink limit version - use INKSCALE fudge instead */ + if (!(x->flags & SPLX_CLIPSX)) { /* Not an ink limited plane simplex */ + +#endif + /* Create the SVD decomp needed for least squares solution */ + if (add_lu_svd(x)) { + DBG(("SVD decomp failed, skip simplex\n")); + return 0; + } + + /* Setup RHS to solve */ + for (f = 0; f < efdi; f++) + tb[f] = b->v[f] - x->v[sdi][f]; + + /* Find least squares solution */ + svdbacksub(x->d_u, x->d_w, x->d_v, tb, tb, efdi, sdi); + + /* Check that the solution is within the simplex */ + if ((wsrv = within_simplex(x, tb)) == 0) { + DBG(("Nearest clip solution not in simplex\n")); + return 0; /* No solution */ + } + + DBG(("Got solution within simplex %s\n",icmPdv(sdi,tb))); + + /* Compute the output space solution point */ + for (f = 0; f < fdi; f++) { + double tt = 0.0; + for (e = 0; e < sdi; e++) { + tt += (x->v[e][f] - x->v[e+1][f]) * tb[e]; + } + xv[f] = tt + x->v[sdi][f]; + } +#ifdef NEVER /* ~~1 Haven't figured out equations to make this a special case. */ + /* Content to use INKSCALE fudge and rely on SVD least squares. */ + } else { + /* We can't use the given equations, because we want the solution */ + /* to lie exactly on the ink limit plane, and be least squares to the */ + /* other target parameters. */ + /* Extract the ink limit parameters, and transform them into */ + /* a parameterised surface for this simplex. */ + /* Substitute the ink plane equation into the remaining target */ + /* parameter equations, and solve for least squares. */ + + } +#endif + } + + /* Copy to return array */ + for (e = 0; e < sdi; e++) + xp[e] = tb[e]; + + /* Compute distance to clip target */ + for (dist = 0.0, f = 0; f < fdi ; f++) { + double tt = (b->v[f] - xv[f]); + dist += tt * tt; + } + DBGV(("Nearest clip output soln: ",fdi," %f", xv, "\n")); + + /* Return the solution in xp[]m xv[] and *err */ + *err = sqrt(dist); + + DBG(("Nearest clip returning a solution with error %f\n",*err)); + return wsrv; +} + + +#ifdef NEVER +/* Utility to convert an implicit ink limit plane equation */ +/* (held at the end of the simplex output value equations), */ +/* into a parameterized surface equation. */ +static void +compute_param_limit_surface( +schbase *b, +simplex *x +) { + rspl *s = b->s; + int ff, f, fdi = s->fdi; + int i, p; + double lgst; + +double st[MXRO], /* Start point */ +double de[MXRO] /* Delta */ + DBG(("Computing clipping line implicit equation, dim = %d\n", fdi)); + + /* Pick a pivot element - the smallest */ + for (lgst = -1.0, p = -1, f = 0; f < fdi; f++) { + double tt = de[f]; + b->cdir[f] = tt; /* Stash this away */ + tt = fabs(tt); + if (tt > lgst) { + lgst = tt; + p = f; + } + } + if (p < 0) /* Shouldn't happen */ + error("rspl rev, internal, trying to cope with zero length clip line\n"); + + if (b->cla == NULL) + b->cla = dmatrix(0, fdi-1, 0, fdi); /* Allow for ink limit supliment */ + + for (i = ff = 0; ff < fdi; ff++) { /* For the input rows */ + if (ff == p) { + continue; /* Skip pivot row */ + } + for (f = 0; f < fdi; f++) { /* For input & output columns */ + if (f == p) { + b->cla[i][f] = -de[ff]; /* Last column is -ve delta value */ + } else if (f == ff) { + b->cla[i][f] = de[p]; /* Diagonal is pivot value */ + } else { + b->cla[i][f] = 0.0; /* Else zero */ + } + } + b->clb[i] = de[p] * st[ff] - de[ff] * st[p]; + i++; + } + + /* Add ink limit target equation - */ + /* interpolated ink value == target */ + if (s->limitf != NULL) { + for (i = 0; i < (fdi-1); i++) + b->cla[i][fdi] = 0.0; + + for (f = 0; f < fdi; f++) + b->cla[fdi-1][f] = 0.0; + + b->cla[fdi-1][fdi] = 1.0; + b->clb[fdi-1] = s->limitv; + } + +#ifdef NEVER +/* Verify that the implicit equation is correct */ +{ + double pnt[MXRO], v[MXRO]; + double pa; /* Parameter */ + for (pa = 0.0; pa <= 1.0; pa += 0.125) { + for (f = 0; f < fdi; f++) { + pnt[f] = st[f] + pa * de[f]; + } + + /* Verify the implicit equation */ + for (ff = 0; ff < (fdi-1); ff++) { + v[ff] = 0.0; + for (f = 0; f < fdi; f++) { + v[ff] += b->cla[ff][f] * pnt[f]; + } + v[ff] -= b->clb[ff]; + if (v[ff] < 0.0) + v[ff] = -v[ff]; + if (v[ff] > 0.000001) { + printf("Point on clip line = %f %f %f\n",pnt[0],pnt[1],pnt[2]); + printf("Implicit %d error of = %f\n",ff, v[ff]); + } + } + } +} +#endif /* NEVER */ + +} + +#endif + + + + +/* -------------------------------------------------------- */ +/* Cell/simplex object lower level code */ + +/* Utility to get or calculate a vertexes ink limit value */ +static double get_limitv( +schbase *b, /* Base search information */ +int ix, /* fwd index of cell */ +float *fcb, /* Pointer to base of vertex value array (ix is used if NULL) */ +double *p /* Array of input values (can be NULL to compute) */ +) { + rspl *s = b->s; + float *base = fcb; + double lv; + if (base == NULL) + base = s->g.a + ix * s->g.pss; + lv = base[-1]; /* Fetch existing ink limit function value */ + if ((float)lv == L_UNINIT) { /* Not been computed yet */ + if (p != NULL) { + lv = INKSCALE * s->limitf(s->lcntx, p); /* Do it */ + base[-1] = (float)lv; + } else { + int e, di = s->di; + double pp[MXRI]; /* Copy from float to double */ + int tix; /* Temp fwd cell index */ + + for (tix = ix, e = 0; e < di; e++) { + int dix; + dix = tix % s->g.res[e]; + tix /= s->g.res[e]; + pp[e] = s->g.l[e] + (double)dix * s->g.w[e]; /* Base point */ + } + lv = INKSCALE * s->limitf(s->lcntx, pp); /* Do it */ + base[-1] = (float)lv; + } + s->g.limitv_cached = 1; /* At least one limit value is cached */ + } + return lv; +} + +/* Utility to invalidate all the ink limit values */ +/* cached in the main rspl array */ +static void clear_limitv( +rspl *s +) { + int i; + float *gp; /* Grid point pointer */ + + if (s->g.limitv_cached != 0) { /* If any have been set */ + /* Unset them all */ + for (i = 0, gp = s->g.a; i < s->g.no; i++, gp += s->g.pss) { + gp[-1] = L_UNINIT; + } + s->g.limitv_cached = 0; + } +} + +/* Cell code */ + +static void free_cell_contents(cell *c); +static cell *cache_rcell(revcache *r, int ix, int force); +static void uncache_rcell(revcache *r, cell *cp); + +/* Return a pointer to an appropriate reverse cell */ +/* cache structure. None of the sub simplex lists will */ +/* be initialised. */ +/* NOTE: must unget_cell() (== uncache_rcell()) when cell */ +/* is no longer needed */ +/* Return NULL if we ran out of room in the cache. */ +static cell *get_rcell( +schbase *b, /* Base search information */ +int ix, /* fwd index of cell */ +int force /* if nz, force memory allocation, so that we have at least one cell */ +) { + rspl *s = b->s; + int ee, e, di = s->di; + int p2di = (1<fdi; + cell *c; + + c = cache_rcell(s->rev.cache, ix, force); /* Fetch it from the cache and lock it */ + if (c == NULL) + return NULL; + + if (!(c->flags & CELL_FLAG_1)) { /* Have to (re)initialize cell & simplexes */ + int tix; /* Temp fwd cell index */ + float *fcb = s->g.a + ix * s->g.pss; /* Pointer to base float of fwd cell */ + + /* Compute basic Cell info and vertex output values */ + for (ee = 0; ee < p2di; ee++) { + float *vp = fcb + s->g.fhi[ee]; + for (f = 0; f < fdi; f++) /* Transfer cell verticy values from grid */ + c->v[ee][f] = vp[f]; + + /* ~~ reset any other cell info that will be stale */ + } + + /* Convert from cell index, to absolute fwd coord base values */ + c->limmin = INF_DIST; /* and min/max values */ + c->limmax = -INF_DIST; + for (tix = ix, e = 0; e < di; e++) { + int dix; + dix = tix % s->g.res[e]; + tix /= s->g.res[e]; + c->p[0][e] = s->g.l[e] + (double)dix * s->g.w[e]; /* Base point */ + } + if (s->limitf != NULL) { /* Compute ink limit values at base verticy */ + double lv = get_limitv(b, ix, fcb, c->p[0]); /* Fetch or generate limit value */ + c->v[0][fdi] = lv; + if (lv < c->limmin) /* And min/max for this cell */ + c->limmin = lv; + if (lv > c->limmax) + c->limmax = lv; + } + + /* Setup cube verticy input position values, and ink limit values */ + for (ee = 1; ee < p2di; ee++) { + for (e = 0; e < di; e++) { + c->p[ee][e] = c->p[0][e]; + if (ee & (1 << e)) + c->p[ee][e] += s->g.w[e]; /* In input space offset */ + } + if (s->limitf != NULL) { /* Compute ink limit values at cell verticies */ + double lv = get_limitv(b, ix, fcb + s->g.fhi[ee], c->p[ee]); + c->v[ee][fdi] = lv; + if (lv < c->limmin) /* And min/max for this cell */ + c->limmin = lv; + if (lv > c->limmax) + c->limmax = lv; + } + } + + /* Compute the output bounding sphere for fast rejection testing */ + { + double *min[MXRO], *max[MXRO]; /* Pointers to points with min/max values */ + double radsq = -1.0; /* Span/radius squared */ + double rad; + int spf = 0; + + /* Find verticies of cell that have min and max values in output space */ + for (f = 0; f < fdi; f++) + min[f] = max[f] = NULL; + + for (ee = 0; ee < p2di; ee++) { + double *vp = c->v[ee]; + for (f = 0; f < fdi; f++) { + if (min[f] == NULL || min[f][f] > vp[f]) + min[f] = vp; + if (max[f] == NULL || max[f][f] < vp[f]) + max[f] = vp; + } + } + + /* Find the pair of points with the largest span (diameter) in output space */ + for (ff = 0; ff < fdi; ff++) { + double ss; + for (ss = 0.0, f = 0; f < fdi; f++) { + double tt; + tt = max[ff][f] - min[ff][f]; + ss += tt * tt; + } + if (ss > radsq) { + radsq = ss; + spf = ff; /* Output dimension max was in */ + } + } + + /* Set initial bounding sphere */ + for (f = 0; f < fdi; f++) { + c->bcent[f] = (max[spf][f] + min[spf][f])/2.0; + } + radsq /= 4.0; /* diam^2 -> rad^2 */ + c->bradsq = radsq; + rad = c->brad = sqrt(radsq); + + /* Go though all the points again, expanding sphere if necessary */ + for (ee = 0; ee < p2di; ee++) { + double ss; + double *vp = c->v[ee]; + + /* Compute distance squared of point to bounding shere */ + for (ss = 0.0, f = 0; f < fdi; f++) { + double tt = vp[f] - c->bcent[f]; + ss += tt * tt; + } + if (ss > radsq) { + double tt; + /* DBG(("Expanding bounding sphere by %f\n",sqrt(ss) - rad)); */ + + ss = sqrt(ss) + EPS; /* Radius to point */ + rad = (rad + ss)/2.0; + c->bradsq = radsq = rad * rad; + tt = ss - rad; + for (f = 0; f < fdi; f++) { + c->bcent[f] = (rad * c->bcent[f] + tt * vp[f])/ss; + } + + } else { + /* DBG(("Bounding sphere encloses by %f\n",rad - sqrt(ss))); */ + } + } + c->bradsq += EPS; + } + c->flags = CELL_FLAG_1; + } + + return c; +} + +void free_simplex_info(cell *c, int dof); + +/* Free up any allocated simplexes in a cell, */ +/* and set the pointers to NULL. */ +/* Nothing else is changed (ie. it's NOT removed from */ +/* the cache index or unthrheaded from the mru list). */ +static void +free_cell_contents( +cell *c +) { + int nsdi; + + /* Free up all the simplexes */ + if (c->s != NULL) { + for (nsdi = 0; nsdi <= c->s->di; nsdi++) { + if (c->sx[nsdi] != NULL) { + free_simplex_info(c, nsdi); + c->sx[nsdi] = NULL; + } + } + } + /* ~~ free any other cell information */ +} + +/* - - - - - - */ +/* Simplex code */ + +/* Simplex and Cell hash index size increments */ +int primes[] = { + 367, + 853, + 1489, + 3373, + 3373, + 6863, + 12919, + 23333, + 43721, + 97849, + 146221, + 254941, + -1 +}; + +/* Compute a simplex hash index */ +unsigned int simplex_hash(revcache *rc, int sdi, int efdi, int *vix) { + unsigned int hash = 0; + int i; + + for (i = 0; i <= sdi; i++) + hash = hash * 17 + vix[i]; + hash = hash * 17 + sdi; + hash = hash * 17 + efdi; + + hash %= rc->spx_hash_size; + return hash; +} + +/* Allocate and do the basic initialisation for a DOF list of simplexes */ +void alloc_simplexes( +cell *c, +int nsdi /* Non limited sub simplex dimensionality */ +) { + rspl *s = c->s; + schbase *b = s->rev.sb; + revcache *rc = s->rev.cache; + int ee, e, di = s->di; + int f, fdi = s->fdi; + int lsdi; /* Ink limited Sub-simplex sdi */ + int tsxno; /* Total number of DOF simplexes */ + int nsxno; /* Number of non-ink limited DOF simplexes */ + int si, so; /* simplex index in and out */ + + DBG(("Allocating level %d sub simplexes in cell %d\n",nsdi,c->ix)); + if (c->sx[nsdi] != NULL) + error("rspl rev, internal, trying allocate already allocated simplexes\n"); + + /* Figure out how many simplexes will be at this nsdi */ + lsdi = nsdi + 1; /* Ink limit simplexes sdi */ + + tsxno = nsxno = s->rev.sspxi[nsdi].nospx; + + if (s->limitf != NULL && lsdi <= di) + tsxno += s->rev.sspxi[lsdi].nospx; /* Second set with extra input dimension */ + + /* Make sure there is enough space in temp simplex filter list */ + if (b->lsxfilt < tsxno) { /* Allocate more space if needed */ + + if (b->lsxfilt > 0) { /* Free old space before allocating new */ + free(b->sxfilt); + DECSZ(b->s, b->lsxfilt * sizeof(char)); + } + b->lsxfilt = 0; + /* Allocate enough space for all the candidate cells */ + if ((b->sxfilt = (char *)rev_malloc(s, tsxno * sizeof(char))) == NULL) + error("rev: malloc failed - temp simplex filter list, count %d",tsxno); + b->lsxfilt = tsxno; /* Current allocated space */ + INCSZ(b->s, b->lsxfilt * sizeof(char)); + } + + /* Figure out the number of simplexes that will actually be needed */ + for (si = so = 0; si < tsxno; si++) { + psxinfo *psxi = NULL; + int *icomb, *offs; + int sdi = nsdi; + int efdi = fdi; + int ssi = si; + int isclip = 0; + if (si >= nsxno) { /* If limit boundary simplex */ + sdi++; /* One more dimension */ + efdi++; /* One more constraint */ + ssi -= nsxno; /* In second half of list */ + isclip++; /* Limit clipped simplex */ + } + psxi = &s->rev.sspxi[sdi].spxi[ssi]; + icomb = psxi->icomb; + offs = psxi->offs; + + b->sxfilt[si] = 0; /* Assume simplex won't be used */ + + /* Check if simplex should be discared due to the ink limit */ + if (s->limitf != NULL) { + double max = -INF_DIST; + double min = INF_DIST; + + /* Find the range of ink limit values covered by simplex */ + for (e = 0; e <= sdi; e++) { /* For all the simplex verticies */ + int i = offs[e]; + double vv = c->v[i][fdi]; /* Ink limit value */ + if (vv < min) + min = vv; + if (vv > max) + max = vv; + } + +//if ((max - min) > EPS) printf("~1 Found simplex sdi %d, efdi %d, min = %f, max = %f, limitv = %f\n", sdi, efdi, min,max,s->limitv); + if (isclip) { /* Limit clipped simplex */ + /* (Make sure it straddles the limit boundary) */ + if (max < s->limitv || min > s->limitv) + continue; /* Discard this simplex - it can't straddle the ink limit */ +//printf("~1 using sub simplex sdi %d, efdi %d, min = %f, max = %f, limitv = %f\n", sdi, efdi, min,max,s->limitv); + } else { + if (min > s->limitv) + continue; /* Discard this simplex - it is above the ink limit */ + } + } + + b->sxfilt[si] |= 1; /* This cell will be OK */ + so++; + } + + DBG(("There are %d level %d sub simplexes\n",so, nsdi)); + /* Allocate space for all the DOF simplexes that will be used */ + if (so > 0) { + if ((c->sx[nsdi] = (simplex **) rev_calloc(s, so, sizeof(simplex *))) == NULL) + error("rspl malloc failed - reverse cell simplexes - list of pointers"); + INCSZ(s, so * sizeof(simplex *)); + } + + /* Setup SPLX_FLAG_1 level information in the simplex */ + for (si = so = 0; si < tsxno; si++) { + simplex *x; + psxinfo *psxi = NULL; + int *icomb; + int sdi, efdi; + int ssi; + int vix[MXRI+1]; /* fwd cell vertex indexes of this simplex [sdi+1] */ + + if (b->sxfilt[si] == 0) /* Decided not to use this one */ + continue; + +#ifdef STATS + s->rev.st[b->op].sinited++; +#endif /* STATS */ + + sdi = nsdi; + efdi = fdi; + ssi = si; + if (si >= nsxno) { /* If limit boundary simplex */ + sdi++; /* One more dimension */ + efdi++; /* One more constraint */ + ssi -= nsxno; /* In second half of list */ + } + + psxi = &s->rev.sspxi[sdi].spxi[ssi]; + icomb = psxi->icomb; + + /* Compute simplex vertexes so we can match it in the cache */ + for (e = 0; e <= sdi; e++) + vix[e] = c->ix + s->g.hi[psxi->offs[e]]; + + x = c->sx[nsdi][so]; + + /* If this is a shared simplex, see if we already have it in another cell */ + if (x == NULL && psxi->face) { + unsigned int hash; +//printf("~1 looking for existing simplex nsdi = %d\n",nsdi); + hash = simplex_hash(rc, sdi, efdi, vix); + for (x = rc->spxhashtop[hash]; x != NULL; x = x->hlink) { + if (x->sdi != sdi + || x->efdi != efdi) + continue; /* miss */ + for (e = 0; e <= sdi; e++) { + if (x->vix[e] != vix[e]) + break; /* miss */ + } + if (e > sdi) + break; /* hit */ + } + if (x != NULL) { + x->refcount++; +//printf("~1 found hit in simplex face list hash %d, refcount = %d\n",hash,x->refcount); + } + } + /* Doesn't already exist */ + if (x == NULL) { + if ((x = (simplex *) rev_calloc(s, 1, sizeof(simplex))) == NULL) + error("rspl malloc failed - reverse cell simplexes - base simplex %d bytes",sizeof(simplex)); + INCSZ(s, sizeof(simplex)); + x->refcount = 1; + x->touch = s->rev.stouch-1; + x->flags = 0; + + if (si >= nsxno) { /* If limit boundary simplex */ + x->flags |= SPLX_CLIPSX; /* Limit clipped simplex */ + } + + /* Fill in the other simplex details */ + x->s = s; /* Parent rspl */ + x->ix = c->ix; /* Construction cube base index */ + for (e = 0; e <= sdi; e++) /* Indexs of fwd verticies that make up this simplex */ + x->vix[e] = vix[e]; + x->psxi = psxi; /* Pointer to constant per simplex info */ +//printf("~1 set simplex 0x%x psxi = 0x%x\n",x,x->psxi); + x->si = so; /* Diagnostic, simplex offset in list */ + x->sdi = sdi; /* Copy of simplex dimensionaity */ + x->efdi = efdi; /* Copy of effective output dimensionality */ + + /* Copy cell simplex vertex output and limit values */ + for (e = 0; e <= sdi; e++) { /* For all the simplex verticies */ + int i = x->psxi->offs[e]; + + for (f = 0; f <= fdi; f++) /* Copy vertex value + ink sum */ + x->v[e][f] = c->v[i][f]; + + /* Setup output bounding box values (the hard way) */ + if (e == 0) { /* Init to first vertex of simplex */ + for (f = 0; f <= fdi; f++) /* Output space */ + x->min[f] = x->max[f] = c->v[i][f]; + } else { + for (f = 0; f <= fdi; f++) { /* Output space + ink sum */ + double vv; +// if (f == fdi && s->limit == NULL) +// continue; /* Skip ink */ + vv = c->v[i][f]; + if (vv < x->min[f]) + x->min[f] = vv; + else if (vv > x->max[f]) + x->max[f] = vv; + } + } + } + /* Add a margin */ + for (f = 0; f <= fdi; f++) { /* Output space + ink sum */ + x->min[f] -= EPS; + x->max[f] += EPS; + } + + /* Setup input bounding box value pointers (the easy way) */ + for (ee = 0; ee < di; ee++) { + x->p0[ee] = c->p[0][ee]; /* Construction base cube origin */ + x->pmin[ee] = c->p[x->psxi->pmino[ee]][ee] - EPS; + x->pmax[ee] = c->p[x->psxi->pmaxo[ee]][ee] + EPS; + } + + x->flags |= SPLX_FLAG_1; /* vv & iv done, nothing else */ + + x->aloc2 = x->aloc5 = NULL; /* Matrix allocations not done yet */ + + /* Add it to the face shared simplex hash index */ + if (x->psxi->face) { + unsigned int hash; + int i; + /* See if we should re-size the simplex hash index */ + if (++rc->nspx > (HASH_FILL_RATIO * rc->spx_hash_size)) { + for (i = 0; primes[i] > 0 && primes[i] <= rc->spx_hash_size; i++) + ; + if (primes[i] > 0) { + int spx_hash_size = rc->spx_hash_size; /* Old */ + simplex **spxhashtop = rc->spxhashtop; + + rc->spx_hash_size = primes[i]; + + DBG(("Increasing face simplex hash index to %d\n",spx_hash_size)); +//printf("~1 increasing simplex hash index size to %d\n",spx_hash_size); + /* Allocate a new index */ + if ((rc->spxhashtop = (simplex **) rev_calloc(s, rc->spx_hash_size, + sizeof(simplex *))) == NULL) + error("rspl malloc failed - reverse simplex cache index"); + INCSZ(s, rc->spx_hash_size * sizeof(simplex *)); + + /* Transfer all the simplexes to the new index */ + for (i = 0; i < spx_hash_size; i++) { + simplex *x, *nx; + for (x = spxhashtop[i]; x != NULL; x = nx) { + nx = x->hlink; + hash = simplex_hash(rc, x->sdi, x->efdi, x->vix); /* New hash */ + x->hlink = rc->spxhashtop[hash]; /* Add to new hash index */ + rc->spxhashtop[hash] = x; + } + } + free(spxhashtop); /* Done with old index */ + DECSZ(s, spx_hash_size * sizeof(simplex *)); + } + } + hash = simplex_hash(rc, sdi, efdi, vix); + + /* Add this to hash index */ + x->hlink = rc->spxhashtop[hash]; + rc->spxhashtop[hash] = x; +//printf("~1 Added simplex to hash %d, rc->nspx = %d\n",hash,rc->nspx); + } + +//if (rc->nunlocked == 0 && rc->s->rev.sz > rc->s->rev.max_sz) +//printf("~1 unable to decrease_revcache 1\n"); + + /* keep memory in check */ + while (rc->nunlocked > 0 && rc->s->rev.sz > rc->s->rev.max_sz) { + if (decrease_revcache(rc) == 0) + break; + } + } + c->sx[nsdi][so] = x; + so++; + } + c->sxno[nsdi] = so; /* Record actual number in list */ + c->flags |= CELL_FLAG_2; /* Note that cell now has simplexes */ +} + +/* Free up any allocated for a list of sub-simplexes */ +void +free_simplex_info( +cell *c, +int nsdi /* non limit sub simplex dimensionaity */ +) { + int si, sxno = c->sxno[nsdi]; /* Number of simplexes */ + + for (si = 0; si < sxno; si++) { /* For all the simplexes */ + simplex *x = c->sx[nsdi][si]; + int dof = x->sdi - x->efdi; + +//printf("~1 freeing simplex, refcount = %d\n",x->refcount); + if (--x->refcount <= 0) { /* Last reference to this simplex */ + +//printf("~1 freeing simplex 0x%x psxi = 0x%x\n",x,x->psxi); + if (x->psxi->face) { + unsigned int hash; + revcache *rc = c->s->rev.cache; + + hash = simplex_hash(rc, x->sdi, x->efdi, x->vix); + + /* Free it from the hash list */ + if (rc->spxhashtop[hash] == x) { + rc->spxhashtop[hash] = x->hlink; + rc->nspx--; +//printf("~1 removed simplex from hash %d, nspx now = %d\n",hash,rc->nspx); + } else { + simplex *xx; + for (xx = rc->spxhashtop[hash]; xx != NULL && xx->hlink != x; xx = xx->hlink) + ; + if (xx != NULL) { /* Found it */ + xx->hlink = x->hlink; + rc->nspx--; +//printf("~1 removed simplex from hash %d, nspx now = %d\n",hash,rc->nspx); + } +//else +//printf("~1 warning, failed to find face simplex hash %d, sdi = %d in cache index (nspx = %d)!!\n",hash,x->sdi,rc->nspx); + } + } + if (x->aloc2 != NULL) { + int adof = dof >= 0 ? dof : 0; /* Allocation dof */ + int asize; + if (dof == 0) + asize = sizeof(double) * (x->efdi * x->sdi) + + sizeof(double *) * x->efdi + + sizeof(int) * x->sdi; + else + asize = sizeof(double) * (x->sdi * (x->efdi + x->sdi + adof + 2) + x->efdi) + + sizeof(double *) * (x->efdi + 2 * x->sdi); + free(x->aloc2); + DECSZ(x->s, asize); + } + + if (x->aloc5 != NULL) { + int asize; + if (x->naux == dof) + asize = sizeof(double *) * x->naux + + sizeof(double) * (x->naux * dof) + + sizeof(int) * dof; + else + asize = sizeof(double *) * (x->naux + dof) + + sizeof(double) * (dof * (x->naux + dof + 1)); + free(x->aloc5); + DECSZ(x->s, asize); + } + + /* ~~ free any other simplex information */ + + free(x); + DECSZ(c->s, sizeof(simplex)); + c->sx[nsdi][si] = NULL; + } + } + free(c->sx[nsdi]); + DECSZ(c->s, c->sxno[nsdi] * sizeof(simplex *)); + c->sx[nsdi] = NULL; + c->sxno[nsdi] = 0; + + /* ~~ free any other cell information */ +} + +/* - - - - - - - - - - - - */ +/* Check that an input space vector is within a given simplex, */ +/* and that it meets any ink limit. */ +/* Return zero if outside the simplex, */ +/* 1 normally if within the simplex, */ +/* and 2 if it would be over the ink limit if limit was enabled. */ +static int +within_simplex( +simplex *x, /* Simplex */ +double *p /* Input coords in simplex space */ +) { + rspl *s = x->s; + schbase *b = s->rev.sb; + int fdi = s->fdi; + int e, sdi = x->sdi; /* simplex dimensionality */ + double cp, lp; + int rv = 1; + /* EPS is allowance for numeric error */ + /* (Don't want solutions falling down */ + /* the numerical cracks between the simplexes) */ + + /* Check we are within baricentric limits */ + for (lp = 0.0, e = 0; e < sdi; e++) { + cp = p[e]; + if ((cp+EPS) < lp) /* Outside baricentric or not in correct */ + return 0; /* order for this simplex */ + lp = cp; + } + if ((1.0+EPS) < lp) /* outside baricentric range */ + return 0; + + /* Compute limit using interp. - assume simplex would have been trivially rejected */ + if (s->limitf != NULL) { + double sum = 0.0; /* Might be over the limit */ + for (e = 0; e < sdi; e++) + sum += p[e] * (x->v[e][fdi] - x->v[e+1][fdi]); + sum += x->v[sdi][fdi]; + if (sum > s->limitv) { + if (s->limiten != 0) + return 0; /* Exceeds ink limit */ + else + rv = 2; /* would have exceeded limit */ + } + } + +#ifdef NEVER + /* Constrain to legal values */ + /* (Is this needed ?????) */ + for (e = 0; e < sdi; e++) { + cp = p[e]; + if (cp < 0.0) + p[e] = 0.0; + else if (cp > 1.0) + p[e] = 1.0; + } +#endif + return rv; +} + +/* Convert vector from simplex space to absolute cartesian space */ +static void simplex_to_abs( +simplex *x, +double *out, /* output in absolute space */ +double *in /* Input in simplex space */ +) { + rspl *s = x->s; + int e, di = s->di; + int *icomb = x->psxi->icomb; /* Coord combination order */ + + for (e = 0; e < di; e++) { /* For each absolute coord */ + double ov = x->p0[e]; /* Base value */ + int ee = icomb[e]; /* Simplex param index */ + if (ee >= 0) /* Simplex param value */ + ov += s->g.w[e] * in[ee]; + else if (ee == -2) /* 1 value */ + ov += s->g.w[e]; + /* Else 0 value */ + out[e] = ov; + } +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Given the parametric clip line equation, compute the */ +/* implicit equation in terms of the absolute output space. */ +/* Pad equation with target ink limit in case it is use */ +/* with CLIPSX sub-simplexes. */ +/* Note that no line equation values are returned if fdi = 1, */ +/* since there is no such thing as an implicit line equation. */ +static void +init_line_eq( +schbase *b, +double st[MXRO], /* Start point */ +double de[MXRO] /* Delta */ +) { + rspl *s = b->s; + int ff, f, fdi = s->fdi; + int i, p; + double lgst; + + DBG(("Computing clipping line implicit equation, dim = %d\n", fdi)); + + /* Pick a pivot element */ + for (lgst = -1.0, p = -1, f = 0; f < fdi; f++) { + double tt = de[f]; + b->cdir[f] = tt; /* Stash this away */ + tt = fabs(tt); + if (tt > lgst) { + lgst = tt; + p = f; + } + } + if (p < 0) /* Shouldn't happen */ + error("rspl rev, internal, trying to cope with zero length clip line\n"); + + if (b->cla == NULL) + b->cla = dmatrix(0, fdi-1, 0, fdi); /* Allow for ink limit supliment */ + + for (i = ff = 0; ff < fdi; ff++) { /* For the input rows */ + if (ff == p) { + continue; /* Skip pivot row */ + } + for (f = 0; f < fdi; f++) { /* For input & output columns */ + if (f == p) { + b->cla[i][f] = -de[ff]; /* Last column is -ve delta value */ + } else if (f == ff) { + b->cla[i][f] = de[p]; /* Diagonal is pivot value */ + } else { + b->cla[i][f] = 0.0; /* Else zero */ + } + } + b->clb[i] = de[p] * st[ff] - de[ff] * st[p]; + i++; + } + + /* Add ink limit target equation - */ + /* interpolated ink value == target */ + if (s->limitf != NULL) { + for (i = 0; i < (fdi-1); i++) + b->cla[i][fdi] = 0.0; + + for (f = 0; f < fdi; f++) + b->cla[fdi-1][f] = 0.0; + + b->cla[fdi-1][fdi] = 1.0; + b->clb[fdi-1] = s->limitv; + } + +#ifdef NEVER +/* Verify that the implicit equation is correct */ +{ + double pnt[MXRO], v[MXRO]; + double pa; /* Parameter */ + for (pa = 0.0; pa <= 1.0; pa += 0.125) { + for (f = 0; f < fdi; f++) { + pnt[f] = st[f] + pa * de[f]; + } + + /* Verify the implicit equation */ + for (ff = 0; ff < (fdi-1); ff++) { + v[ff] = 0.0; + for (f = 0; f < fdi; f++) { + v[ff] += b->cla[ff][f] * pnt[f]; + } + v[ff] -= b->clb[ff]; + if (v[ff] < 0.0) + v[ff] = -v[ff]; + if (v[ff] > 0.000001) { + printf("Point on clip line = %f %f %f\n",pnt[0],pnt[1],pnt[2]); + printf("Implicit %d error of = %f\n",ff, v[ff]); + } + } + } +} +#endif /* NEVER */ + +} + +/* - - - - - - */ +/* Simpex solution info #2 */ + +/* Create the LU or SVD decomp needed to compute solution or locus. */ +/* Return non-zero if it cannot be created */ +static int +add_lu_svd(simplex *x) { + + if (x->flags & SPLX_FLAG_2F) { /* Previously failed */ + return 1; + } + if (!(x->flags & SPLX_FLAG_2)) { + int ee, e, sdi = x->sdi; + int f, efdi = x->efdi; + int dof = sdi-efdi; /* Degree of freedom of locus, or -ve over specification */ + int adof = dof >= 0 ? dof : 0; /* Allocation dof */ + int i; + + if (x->aloc2 == NULL) { /* Allocate space for matricies and arrays */ + /* Do this in one hit to minimise malloc overhead */ + if (dof == 0) { + int i; + char *mem; + int asize = sizeof(double) * (efdi * sdi) + + sizeof(double *) * efdi + + sizeof(int) * sdi; + + if ((x->aloc2 = mem = (char *) rev_malloc(x->s, asize)) == NULL) + error("rspl malloc failed - reverse cell sub-simplex matricies"); + INCSZ(x->s, asize); + + /* Allocate biggest to smallest (double, pointers, ints) */ + /* to make sure that items lie on the natural boundaries. */ + + /* Reserve matrix doubles */ + mem += efdi * sdi * sizeof(double); + + /* Allocate pointers */ + x->d_u = (double **)mem, mem += efdi * sizeof(double *); + + /* Allocate ints */ + x->d_w = (double *)mem, mem += sdi * sizeof(int); + +#ifdef DEBUG + if (mem != (x->aloc2 + asize)) + error("~1 aloc2a assert failed! Is %d, should be %d\n",mem - x->aloc2,asize); +#endif /* DEBUG */ + + /* Reset and allocate matrix doubles */ + mem = x->aloc2; + for (i = 0; i < efdi; i++) + x->d_u[i] = (double *)mem, mem += sdi * sizeof(double); + + } else { + int i; + char *mem; + int asize = sizeof(double) * (sdi * (efdi + sdi + adof + 2) + efdi) + + sizeof(double *) * (efdi + 2 * sdi); + + if ((x->aloc2 = mem = (char *) rev_malloc(x->s, asize)) == NULL) + error("rspl malloc failed - reverse cell sub-simplex matricies"); + INCSZ(x->s, asize); + + /* Allocate biggest to smallest (double, pointers, ints) */ + /* to make sure that items lie on the natural boundaries. */ + + /* Reserve matrix doubles */ + mem += sdi * (efdi + sdi + adof) * sizeof(double); + + /* Allocate doubles */ + x->lo_xb = (double *)mem, mem += efdi * sizeof(double); + x->lo_bd = (double *)mem; mem += sdi * sizeof(double); + x->d_w = (double *)mem, mem += sdi * sizeof(double); + + /* Allocate pointers */ + x->d_u = (double **)mem, mem += efdi * sizeof(double *); + x->d_v = (double **)mem, mem += sdi * sizeof(double *); + x->lo_l = (double **)mem, mem += sdi * sizeof(double *); + +#ifdef DEBUG + if (mem != (x->aloc2 + asize)) + error("~1 aloc2b assert failed! Is %d, should be %d\n",mem - x->aloc2,asize); +#endif /* DEBUG */ + + /* Reset and allocate matrix doubles */ + mem = x->aloc2; + for (i = 0; i < efdi; i++) + x->d_u[i] = (double *)mem, mem += sdi * sizeof(double); + for (i = 0; i < sdi; i++) + x->d_v[i] = (double *)mem, mem += sdi * sizeof(double); + for (i = 0; i < sdi; i++) + x->lo_l[i] = (double *)mem, mem += adof * sizeof(double); + + /* Init any values that will be read before being written to. */ + for (f = 0; f < efdi; f++) + x->lo_xb[f] = 1e100; /* Silly value */ + } + } + + /* Setup matrix from vertex values */ + for (f = 0; f < efdi; f++) + for (e = 0; e < sdi; e++) + x->d_u[f][e] = x->v[e][f] - x->v[e+1][f]; + + if (dof == 0) { /* compute LU */ + double rip; +#ifdef STATS + x->s->rev.st[x->s->rev.sb->op].sinited2a++; +#endif /* STATS */ + if (lu_decomp(x->d_u, sdi, (int *)x->d_w, &rip)) { + x->flags |= SPLX_FLAG_2F; /* Failed */ + return 1; + } + } else { +//printf("~~ Creating SVD decomp, sdi = %d, efdi = %d\n", sdi, efdi); + +#ifdef STATS + x->s->rev.st[x->s->rev.sb->op].sinited2b++; +#endif /* STATS */ + if (svdecomp(x->d_u, x->d_w, x->d_v, efdi, sdi)) { + x->flags |= SPLX_FLAG_2F; /* Failed */ + return 1; + } + + /* Threshold the singular values W[] */ + svdthresh(x->d_w, sdi); + + if (dof >= 0) { /* If we expect a locus */ +//printf("~~ got dif %d locus from SVD\n",dof); + /* copy the locus direction coefficients out */ + for (i = e = 0; e < sdi; e++) { + if (x->d_w[e] == 0.0) { /* Found a zero W[] */ + if (i < dof) { + for (ee = 0; ee < sdi; ee++) { /* Copy column of V[][] */ + x->lo_l[ee][i] = x->d_v[ee][e]; + } + } + i++; + } + } + if (i != dof) { +//printf("~~ got unexpected dof in svd\n"); + x->flags |= SPLX_FLAG_2F; /* Failed */ + return 1; /* Didn't get expected d.o.f. */ + } + } + } + x->flags |= SPLX_FLAG_2; /* Set flag so that it isn't attempted again */ + +//if (x->s->rev.cache->nunlocked == 0 && x->s->rev.sz > x->s->rev.max_sz) +//printf("~1 unable to decrease_revcache 2\n"); + + /* keep memory in check */ + while (x->s->rev.cache->nunlocked > 0 && x->s->rev.sz > x->s->rev.max_sz) { + if (decrease_revcache(x->s->rev.cache) == 0) + break; + } + } + return 0; +} + +/* - - - - - - */ +/* Simplex solution info #4 */ + +/* Calculate the solution locus equation for this simplex and target */ +/* (The direction was calculated by add_svd(), but now calculate */ +/* the base solution point for this particular reverse lookup) */ +/* Return non-zero if this point canot be calculated */ +/* We are assuming that sdi > efdi */ +static int +add_locus( +schbase *b, +simplex *x +) { + int sdi = x->sdi; + int f, efdi = x->efdi; + int doback = 0; + +#ifdef STATS + x->s->rev.st[x->s->rev.sb->op].sinited4++; +#endif /* STATS */ + /* Use output of svdcmp() to solve overspecified and/or */ + /* singular equation A.x = b */ + + /* Init the RHS B[] vector, and check if it doesn't match */ + /* that used to compute base value last time. */ + for (f = 0; f < efdi; f++) { + double xb = b->v[f] - x->v[sdi][f]; + if (x->lo_xb[f] != xb) { + x->lo_xb[f] = xb; + doback = 1; /* RHS differs, so re-compute */ + } + } + +#ifdef STATS + if (doback && (x->flags & SPLX_FLAG_4)) + x->s->rev.st[x->s->rev.sb->op].sinited4i++; +#endif /* STATS */ + + /* Compute locus */ + if (doback || !(x->flags & SPLX_FLAG_4)) + svdbacksub(x->d_u, x->d_w, x->d_v, x->lo_xb, x->lo_bd, efdi, sdi); + + x->flags |= SPLX_FLAG_4; + +//if (x->s->rev.cache->nunlocked == 0 && x->s->rev.sz > x->s->rev.max_sz) +//printf("~1 unable to decrease_revcache 3\n"); + + /* keep memory in check */ + while (x->s->rev.cache->nunlocked > 0 && x->s->rev.sz > x->s->rev.max_sz) { + if (decrease_revcache(x->s->rev.cache) == 0) + break; + } + + return 0; +} + +/* - - - - - - */ +/* Simplex solution info #5 */ + +/* Compute LU or SVD decomp of lo_l */ +/* Allocates the memory for the various matricies */ +/* Return non-zero if this canot be calculated. */ +static int +add_auxil_lu_svd( +schbase *b, +simplex *x +) { + int ee, sdi = x->sdi; + int f, efdi = x->efdi; + int dof = sdi-efdi; /* Degree of freedom of locus */ + int naux = b->naux; /* Number of auxiliaries actually available */ + +#ifdef STATS + if (x->aaux != b->naux || x->auxbm != b->auxbm) + x->s->rev.st[x->s->rev.sb->op].sinited5i++; +#endif /* STATS */ + + if (x->aaux != b->naux) { /* Number of auxiliaries has changed */ + if (x->aloc5 != NULL) { + int asize; + if (x->naux == dof) + asize = sizeof(double *) * x->naux + + sizeof(double) * (x->naux * dof) + + sizeof(int) * dof; + else + asize = sizeof(double *) * (x->naux + dof) + + sizeof(double) * (dof * (x->naux + dof + 1)); + free(x->aloc5); + x->aloc5 = NULL; + DECSZ(x->s, asize); + } + x->flags &= ~(SPLX_FLAG_5 | SPLX_FLAG_5F); /* Force recompute */ + } + + if (x->auxbm != b->auxbm) { /* Different selection of auxiliaries */ + x->flags &= ~(SPLX_FLAG_5 | SPLX_FLAG_5F); /* Force recompute */ + } + + if (x->flags & SPLX_FLAG_5F) { /* Previously failed */ + return 1; + } + if (!(x->flags & SPLX_FLAG_5)) { + int *icomb = x->psxi->icomb; /* abs -> simplex coordinate translation */ + + if (x->aloc5 == NULL) { /* Allocate space for matricies and arrays */ + /* Do this in one hit to minimise malloc overhead */ + if (naux == dof) { + int i; + char *mem; + int asize = sizeof(double *) * naux + + sizeof(double) * (naux * dof) + + sizeof(int) * dof; + + if ((x->aloc5 = mem = (char *) rev_malloc(x->s, asize)) == NULL) + error("rspl malloc failed - reverse cell sub-simplex matricies"); + INCSZ(x->s, asize); + + /* Allocate biggest to smallest (double, pointers, ints) */ + /* to make sure that items lie on the natural boundaries. */ + + /* Reserve matrix doubles */ + mem += naux * dof * sizeof(double); + + /* Allocate pointers and ints */ + x->d_u = (double **)mem, mem += naux * sizeof(double *); + x->d_w = (double *)mem, mem += dof * sizeof(int); + +#ifdef DEBUG + if (mem != (x->aloc5 + asize)) + error("aloc5a assert failed! Is %d, should be %d\n",mem - x->aloc5,asize); +#endif /* DEBUG */ + + /* Reset and allocate matrix doubles */ + mem = x->aloc5; + for (i = 0; i < naux; i++) + x->d_u[i] = (double *)mem, mem += dof * sizeof(double); + } else { + int i; + char *mem; + int asize = sizeof(double *) * (naux + dof) + + sizeof(double) * (dof * (naux + dof + 1)); + + if ((x->aloc5 = mem = (char *) rev_malloc(x->s, asize)) == NULL) + error("rspl malloc failed - reverse cell sub-simplex matricies"); + INCSZ(x->s, asize); + + /* Allocate biggest to smallest (double, pointers, ints) */ + /* to make sure that items lie on the natural boundaries. */ + + /* Reserve matrix doubles */ + mem += dof * (naux + dof) * sizeof(double); + + /* Allocate doubles */ + x->ax_w = (double *)mem, mem += dof * sizeof(double); + + /* Allocate pointers, ints */ + x->ax_u = (double **)mem, mem += naux * sizeof(double *); + x->ax_v = (double **)mem, mem += dof * sizeof(double *); + +#ifdef DEBUG + if (mem != (x->aloc5 + asize)) + error("aloc5b assert failed! Is %d, should be %d\n",mem - x->aloc5,asize); +#endif /* DEBUG */ + + /* Reset and allocate matrix doubles */ + mem = x->aloc5; + for (i = 0; i < naux; i++) + x->ax_u[i] = (double *)mem, mem += dof * sizeof(double); + for (i = 0; i < dof; i++) + x->ax_v[i] = (double *)mem, mem += dof * sizeof(double); + } + x->aaux = naux; /* Number of auxiliaries allocated for */ + } + + /* Setup A[][] matrix to decompose, and figure number of auxiliaries actually needed */ + for (ee = naux = 0; ee < b->naux; ee++) { + int ei = icomb[b->auxi[ee]]; /* Simplex relative auxiliary index */ + if (ei < 0) + continue; /* aux corresponds with fixed input value for this simplex */ + for (f = 0; f < dof; f++) + x->ax_u[naux][f] = x->lo_l[ei][f]; + naux++; + } + x->naux = naux; /* Number of auxiliaries actually available */ + x->auxbm = b->auxbm; /* Mask of auxiliaries used */ + + if (naux == dof) { /* Use LU decomp to solve exactly */ + double rip; + +#ifdef STATS + x->s->rev.st[x->s->rev.sb->op].sinited5a++; +#endif /* STATS */ + if (lu_decomp(x->ax_u, dof, (int *)x->ax_w, &rip)) { + x->flags |= SPLX_FLAG_5F; + return 1; + } + + } else if (naux > 0) { /* Use SVD to solve least squares */ + +#ifdef STATS + x->s->rev.st[x->s->rev.sb->op].sinited5b++; +#endif /* STATS */ + if (svdecomp(x->ax_u, x->ax_w, x->ax_v, naux, dof)) { + x->flags |= SPLX_FLAG_5F; + return 1; + } + + /* Threshold the singular values W[] */ + svdthresh(x->ax_w, dof); + } /* else naux == 0, don't setup anything */ + + x->flags |= SPLX_FLAG_5; + +//if (x->s->rev.cache->nunlocked == 0 && x->s->rev.sz > x->s->rev.max_sz) +//printf("~1 unable to decrease_revcache 4\n"); + + /* keep memory in check */ + while (x->s->rev.cache->nunlocked > 0 && x->s->rev.sz > x->s->rev.max_sz) { + if (decrease_revcache(x->s->rev.cache) == 0) + break; + } + } + return 0; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Initialise a static sub-simplex verticy information table */ +void +rspl_init_ssimplex_info( +rspl *s, +ssxinfo *xip, /* Pointer to sub-simplex info structure to init. */ +int sdi /* Sub-simplex dimensionality (range 0 - di) */ +) { + int e, di = s->di; /* Dimensionality */ + int vi, nospx; /* Number of sub-simplexes */ + XCOMBO(vcmb, MXDI, sdi+1, 1 << di);/* Simplex dimension sdi out of cube dimension di counter */ + + DBG(("init_ssimplex_info called with sdi = %d\n",sdi)); + /* First count the number of sub-simplexes */ + nospx = 0; + XCB_INIT(vcmb); + while (!XCB_DONE(vcmb)) { + nospx++; + XCB_INC(vcmb); + } + + xip->sdi = sdi; + xip->nospx = nospx; + if ((xip->spxi = (psxinfo *) rev_calloc(s, nospx, sizeof(psxinfo))) == NULL) + error("rspl malloc failed - reverse cell sub-simplex info array"); + INCSZ(s, nospx * sizeof(psxinfo)); + + DBG(("Number of subsimplex = %d\n",nospx)); + /* For all sub-simplexes */ + XCB_INIT(vcmb); + for (vi = 0; vi < nospx; vi++) { + psxinfo *x = &xip->spxi[vi]; + int i; + int andm, orm; + + /* XCOMB generates verticies in order from max to min offset */ + + /* Compute Absolute -> Parameter mapping */ + for (e = 0; e < di; e++) { /* For each absolute axis */ + + if ((vcmb[sdi] & (1<icomb[e] = -2; /* This abs is always '1' */ + + } else if ((vcmb[0] & (1<icomb[e] = -1; /* This abs is always '0' */ + + } else { + for (i = 0; i < sdi; i++) { /* For each verticy in large to small order (!first) */ + if ((vcmb[i] & (1<icomb[e] = i; /* This is parameter */ + break; + } + } + } + } + + /* Compute fwd grid offsets for each simplex vertex in baricentric order */ + for (i = 0; i <= sdi; i++) { /* For each verticy */ + int pmin[MXRI], pmax[MXRI]; + x->offs[i] = vcmb[i]; + x->goffs[i] = s->g.hi[vcmb[i]]; + x->foffs[i] = s->g.fhi[vcmb[i]]; + + /* Setup input coordinate bounding box value offsets */ + if (i == 0) { /* Init to first vertex of simplex */ + for (e = 0; e < di; e++) { /* Input space */ + x->pmino[e] = x->pmaxo[e] = vcmb[i]; + pmin[e] = pmax[e] = vcmb[i] & (1<pmino[e] = vcmb[i]; + pmin[e] = vv; + } else if (vv > pmax[e]) { + x->pmaxo[e] = vcmb[i]; + pmax[e] = vv; + } + } + } + } + + /* See if the sub-simplex lies on a cube face */ + andm = ~0; + orm = 0; + for (i = 0; i <= sdi; i++) { /* For each verticy */ + andm &= vcmb[i]; + orm |= vcmb[i]; + } + /* If one coordinate is common (all 0 or all 1) to the verticies, */ + /* they must all be on the same cube face. */ + if (andm != 0 || orm != ((1 << di)-1)) + x->face = 1; + else + x->face = 0; + +#ifdef DEBUG + printf("Verticies = "); + for (i = 0; i <= sdi; i++) + printf("%d ",vcmb[i]); + printf("\n"); + + printf("Face = %s\n",x->face ? "True" : "False"); + + printf("Abs -> Parm = "); + for (e = 0; e < di; e++) + printf("%d ",x->icomb[e]); + printf("\n"); + + printf("Grid Offset = "); + for (e = 0; e <= sdi; e++) + printf("%d ",x->goffs[e]); + printf("Float Offset = "); + for (e = 0; e <= sdi; e++) + printf("%d ",x->foffs[e]); + printf("\n"); + printf("\n"); +#endif /* DEBUG */ + + /* Increment the counter value */ + XCB_INC(vcmb); + } +} + +/* Free the given sub-simplex verticy information */ +void +rspl_free_ssimplex_info( +rspl *s, +ssxinfo *xip /* Pointer to sub-simplex info structure */ +) { + if (xip == NULL) /* Assert */ + return; + + free(xip->spxi); + DECSZ(s, xip->nospx * sizeof(psxinfo)); + xip->spxi = NULL; +} + +/* ====================================================== */ +/* Reverse cell cache code */ + +/* Allocate and initialise the reverse cell cache */ +static revcache * +alloc_revcache( +rspl *s +) { + revcache *rc; + + DBG(("alloc_revcache called\n")); + if ((rc = (revcache *) rev_calloc(s, 1, sizeof(revcache))) == NULL) + error("rspl malloc failed - reverse cell cache"); + INCSZ(s, sizeof(revcache)); + + rc->s = s; /* For stats */ + + /* Allocate an initial cell hash index */ + rc->cell_hash_size = primes[0]; + + if ((rc->hashtop = (cell **) rev_calloc(s, rc->cell_hash_size, sizeof(cell *))) == NULL) + error("rspl malloc failed - reverse cell cache index"); + INCSZ(s, rc->cell_hash_size * sizeof(cell *)); + + /* Allocate an initial simplex face match hash index */ + rc->spx_hash_size = primes[0]; + + if ((rc->spxhashtop = (simplex **) rev_calloc(s, rc->spx_hash_size, sizeof(simplex *))) == NULL) + error("rspl malloc failed - reverse simplex cache index"); + INCSZ(s, rc->spx_hash_size * sizeof(simplex *)); + + return rc; +} + +/* Free the reverse cell cache */ +static void +free_revcache(revcache *rc) { + int i; + cell *cp, *ncp; + + /* Free any stuff allocated in the cell contents, and the cell itself. */ + for (cp = rc->mrubot; cp != NULL; cp = ncp) { + ncp = cp->mruup; + free_cell_contents(cp); + free(cp); + DECSZ(rc->s, sizeof(cell)); + } + + /* Free the hash indexes */ + free(rc->hashtop); + DECSZ(rc->s, rc->cell_hash_size * sizeof(cell *)); + free(rc->spxhashtop); + DECSZ(rc->s, rc->spx_hash_size * sizeof(simplex *)); + + DECSZ(rc->s, sizeof(revcache)); + free(rc); +} + +/* Invalidate the whole cache */ +static void +invalidate_revcache( +revcache *rc) +{ + int i; + cell *cp; + + rc->nunlocked = 0; + + /* Free any stuff allocated in the cell contents */ + for (cp = rc->mrubot; cp != NULL; cp = cp->mruup) { + free_cell_contents(cp); + cp->refcount = 0; /* Make sure they can now be reused */ + cp->ix = 0; + cp->flags = 0; /* Contents needs re-initializing */ + rc->nunlocked++; + } + + /* Clear the hash table so they can't be hit */ + for (i = 0; i < rc->cell_hash_size; i++) { + rc->hashtop[i] = NULL; + } + +} + +#define HASH(xx, yy) ((yy) % xx->cell_hash_size) + +/* Allocate another cell, and add it to the cache. */ +/* This may re-size the hash index too. */ +/* Return the pointer to the new cell. */ +/* (Note it's not our job here to honour the memory limit) */ +static cell * +increase_revcache( +revcache *rc +) { + cell *nxcell; /* Newly allocated cell */ + int i; + + DBG(("Adding another chunk of cells to cache\n")); + +#ifdef NEVER /* We may be called with force != 0 */ + if (rc->s->rev.sz >= rc->s->rev.max_sz) + return NULL; +#endif + + if ((nxcell = (cell *) rev_calloc(rc->s, 1, sizeof(cell))) == NULL) + error("rspl malloc failed - reverse cache cells"); + INCSZ(rc->s, sizeof(cell)); + + nxcell->s = rc->s; + + /* Add cell to the bottom of the cache mru linked list */ + if (rc->mrutop == NULL) /* List was empty */ + rc->mrutop = nxcell; + else { + rc->mrubot->mrudown = nxcell; /* Splice into bottom */ + nxcell->mruup = rc->mrubot; + } + rc->mrubot = nxcell; + rc->nacells++; + rc->nunlocked++; + + DBG(("cache is now %d cells\n",rc->nacells)); + + /* See if the hash index should be re-sized */ + if (rc->nacells > (HASH_FILL_RATIO * rc->cell_hash_size)) { + for (i = 0; primes[i] > 0 && primes[i] <= rc->cell_hash_size; i++) + ; + if (primes[i] > 0) { + int cell_hash_size = rc->cell_hash_size; /* Old */ + cell **hashtop = rc->hashtop; + + rc->cell_hash_size = primes[i]; + + DBG(("Increasing cell cache hash index to %d\n",cell_hash_size)); + /* Allocate a new index */ + if ((rc->hashtop = (cell **) rev_calloc(rc->s, rc->cell_hash_size, sizeof(cell *))) == NULL) + error("rspl malloc failed - reverse cell cache index"); + INCSZ(rc->s, rc->cell_hash_size * sizeof(cell *)); + + /* Transfer all the cells to the new index */ + for (i = 0; i < cell_hash_size; i++) { + cell *c, *nc; + for (c = hashtop[i]; c != NULL; c = nc) { + int hash; + nc = c->hlink; + hash = HASH(rc, c->ix); /* New hash */ + c->hlink = rc->hashtop[hash]; /* Add to new hash index */ + rc->hashtop[hash] = c; + } + } + + /* Done with old index */ + free(hashtop); + DECSZ(rc->s, cell_hash_size * sizeof(cell *)); + } + } + + return nxcell; +} + +/* Reduce the cache memory usage by freeing the least recently used unlocked cell. */ +/* Return nz if we suceeeded in freeing some memory. */ +static int decrease_revcache( +revcache *rc /* Reverse cache structure */ +) { + int hit = 0; + int hash; + cell *cp; + + DBG(("Decreasing cell cache memory allocation by freeing a cell\n")); + + /* Use the least recently used unlocked cell */ + for (cp = rc->mrubot; cp != NULL && cp->refcount > 0; cp = cp->mruup) + ; + + /* Run out of unlocked cells */ + if (cp == NULL) { + DBG(("Failed to find unlocked cell to free\n")); +//printf("~1 failed to decrease memory\n"); + return 0; + } + + /* If it has been used before, free up the simplexes */ + free_cell_contents(cp); + + /* Remove from current hash index (if it is in it) */ + hash = HASH(rc,cp->ix); /* Old hash */ + if (rc->hashtop[hash] == cp) { + rc->hashtop[hash] = cp->hlink; + } else { + cell *c; + for (c = rc->hashtop[hash]; c != NULL && c->hlink != cp; c = c->hlink) + ; + if (c != NULL) + c->hlink = cp->hlink; + } + + /* Free up this cell - Remove it from LRU list */ + if (rc->mrutop == cp) + rc->mrutop = cp->mrudown; + if (rc->mrubot == cp) + rc->mrubot = cp->mruup; + if (cp->mruup != NULL) + cp->mruup->mrudown = cp->mrudown; + if (cp->mrudown != NULL) + cp->mrudown->mruup = cp->mruup; + cp->mruup = cp->mrudown = NULL; + free(cp); + DECSZ(rc->s, sizeof(cell)); + rc->nacells--; + rc->nunlocked--; + + DBG(("Freed a rev cache cell\n")); + return 1; +} + +/* Return a pointer to an appropriate reverse cell */ +/* cache structure. cell->flags will be 0 if the cell */ +/* has been reallocated. cell contents will be 0 if */ +/* never used before. */ +/* The cell reference count is incremented, so that it */ +/* can't be thrown out of the cache. The cell must be */ +/* released with uncache_rcell() when it's no longer needed. */ +/* return NULL if we ran out of room in the cache */ +static cell *cache_rcell( +revcache *rc, /* Reverse cache structure */ +int ix, /* fwd index of cell */ +int force /* if nz, force memory allocation, so that we have at least one cell */ +) { + int hit = 0; + int hash; + cell *cp; + + /* keep memory in check - fail if we're out of memory and can't free any */ + /* (Doesn't matter if it might be a hit, it will get picked up the next time) */ + if (!force && rc->s->rev.sz > rc->s->rev.max_sz && rc->nunlocked <= 0) { + return NULL; + } + +//if (rc->nunlocked == 0 && rc->s->rev.sz > rc->s->rev.max_sz) +//printf("~1 unable to decrease_revcache 5\n"); + + /* Free up memory to get below threshold */ + while (rc->nunlocked > 0 && rc->s->rev.sz > rc->s->rev.max_sz) { + if (decrease_revcache(rc) == 0) + break; + } + + hash = HASH(rc,ix); /* Compute hash of fwd cell index */ + + /* See if we get a cache hit */ + for (cp = rc->hashtop[hash]; cp != NULL; cp = cp->hlink) { + if (ix == cp->ix) { /* Hit */ + hit = 1; +#ifdef STATS + rc->s->rev.st[rc->s->rev.sb->op].chits++; +#endif /* STATS */ + break; + } + } + if (!hit) { /* No hit, use new cell or the least recently used cell */ + int ohash; + + /* If we haven't used all our memory, or if we are forced and have */ + /* no cell we can re-use, then noallocate another cell */ + if (rc->s->rev.sz < rc->s->rev.max_sz || (force && rc->nunlocked == 0)) { + cp = increase_revcache(rc); + hash = HASH(rc,ix); /* Re-compute hash in case hash size changed */ +//printf("~1 using new cell\n"); + } else { +//printf("~1 memory limit has been reached, using old cell\n"); + + for (;;) { + /* Use the least recently used unlocked cell */ + for (cp = rc->mrubot; cp != NULL && cp->refcount > 0; cp = cp->mruup) + ; + + /* Run out of unlocked cells */ + if (cp == NULL) { +//printf("~1 none available\n"); + return NULL; + } + + /* If it has been used before, free up the simplexes */ + free_cell_contents(cp); + + /* Remove from current hash index (if it is in it) */ + ohash = HASH(rc,cp->ix); /* Old hash */ + if (rc->hashtop[ohash] == cp) { + rc->hashtop[ohash] = cp->hlink; + } else { + cell *c; + for (c = rc->hashtop[ohash]; c != NULL && c->hlink != cp; c = c->hlink) + ; + if (c != NULL) + c->hlink = cp->hlink; + } + + /* If we're now under the memory limit, use this cell */ + if (rc->s->rev.sz < rc->s->rev.max_sz) { + break; + } + +//printf("~1 freeing a cell\n"); + /* Free up this cell and look for another one */ + /* Remove it from LRU list */ + if (rc->mrutop == cp) + rc->mrutop = cp->mrudown; + if (rc->mrubot == cp) + rc->mrubot = cp->mruup; + if (cp->mruup != NULL) + cp->mruup->mrudown = cp->mrudown; + if (cp->mrudown != NULL) + cp->mrudown->mruup = cp->mruup; + cp->mruup = cp->mrudown = NULL; + free(cp); + DECSZ(rc->s, sizeof(cell)); + rc->nacells--; + rc->nunlocked--; + } + } + +#ifdef STATS + rc->s->rev.st[rc->s->rev.sb->op].cmiss++; +#endif /* STATS */ + + /* Add this cell to hash index */ + cp->hlink = rc->hashtop[hash]; + rc->hashtop[hash] = cp; /* Add to hash table and list */ + + cp->ix = ix; + cp->flags = 0; /* Contents needs re-initializing */ +//printf("~1 returning fresh cell\n"); + } + + /* Move slected cell to the top of the mru list */ + if (cp->mruup != NULL) { /* This one wasn't already at top */ + cp->mruup->mrudown = cp->mrudown; + if (cp->mrudown == NULL) /* This was bottom */ + rc->mrubot = cp->mruup; /* New bottom */ + else + cp->mrudown->mruup = cp->mruup; + /* Put this one at the top */ + rc->mrutop->mruup = cp; + cp->mrudown = rc->mrutop; + rc->mrutop = cp; + cp->mruup = NULL; + } + if (cp->refcount == 0) { + rc->nunlocked--; + } + + cp->refcount++; + + return cp; +} + +/* Tell the cache that we aren't using this cell anymore, */ +/* but to keep it in case it is needed again. */ +static void uncache_rcell( +revcache *rc, /* Reverse cache structure */ +cell *cp +) { + if (cp->refcount > 0) { + cp->refcount--; + if (cp->refcount == 0) { + rc->nunlocked++; + } + } else + warning("rspl cell cache assert: refcount overdecremented!"); +} + +/* ====================================================== */ +/* Reverse rspl setup functions */ + +/* Called by rspl initialisation */ +/* Note that reverse cell lookup tables are not */ +/* allocated & created until the first call */ +/* to a reverse interpolation function. */ +void +init_rev(rspl *s) { + + /* First section */ + s->rev.inited = 0; + s->rev.res = 0; + s->rev.no = 0; + s->rev.rev = NULL; + + /* Second section */ + s->rev.rev_valid = 0; + s->rev.nnrev = NULL; + + /* Third section */ + s->rev.cache = NULL; + + /* Fourth section */ + s->rev.sb = NULL; + + /* Methods */ + s->rev_set_limit = rev_set_limit_rspl; + s->rev_get_limit = rev_get_limit_rspl; + s->rev_interp = rev_interp_rspl; + s->rev_locus = rev_locus_rspl; + s->rev_locus_segs = rev_locus_segs_rspl; +} + +/* Free up all the reverse interpolation info */ +void free_rev( +rspl *s /* Pointer to rspl grid */ +) { + int e, di = s->di; + int **rpp, *rp; + +#ifdef STATS + { + int i, totcalls = 0; + for (i = 0; i < 5; i++) { + totcalls += s->rev.st[i].searchcalls; + } + + printf("\n===============================\n"); + printf("di = %d, do = %d\n",s->di, s->fdi); + for (i = 0; i < 5; i++) { + int calls = s->rev.st[i].searchcalls; + if (calls == 0) + continue; + printf("\n- - - - - - - - - - - - - - - -\n"); + printf("Operation %s\n",opnames[i]); + printf("Search calls = %d = %f%%\n",s->rev.st[i].searchcalls, + 100.0 * s->rev.st[i].searchcalls/totcalls); + printf("Cells searched/call = %f\n",s->rev.st[i].csearched/(double)calls); + printf("Simplexes searched/call = %f\n",s->rev.st[i].ssearched/(double)calls); + printf("Simplexes inited level 1/call = %f\n",s->rev.st[i].sinited/(double)calls); + printf("Simplexes inited level 2 (LU)/call = %f\n",s->rev.st[i].sinited2a/(double)calls); + printf("Simplexes inited level 2 (SVD)/call = %f\n",s->rev.st[i].sinited2b/(double)calls); + printf("Simplexes invalidated level 4/call = %f\n",s->rev.st[i].sinited4i/(double)calls); + printf("Simplexes inited level 4/call = %f\n",s->rev.st[i].sinited4/(double)calls); + printf("Simplexes invalidated level 5/call = %f\n",s->rev.st[i].sinited5i/(double)calls); + printf("Simplexes inited level 5 (LU)/call = %f\n",s->rev.st[i].sinited5a/(double)calls); + printf("Simplexes inited level 5 (SVD)/call = %f\n",s->rev.st[i].sinited5b/(double)calls); + if ((s->rev.st[i].chits + s->rev.st[i].cmiss) == 0) + printf("No cache calls\n"); + else + printf("Cell hit rate = %f%%\n", + 100.0 * s->rev.st[i].chits/(double)(s->rev.st[i].chits + s->rev.st[i].cmiss)); + } + printf("\n===============================\n"); + } +#endif /* STATS */ + + /* Free up Fourth section */ + if (s->rev.sb != NULL) { + free_search(s->rev.sb); + s->rev.sb = NULL; + } + /* Free up the Third section */ + if (s->rev.cache != NULL) { + free_revcache(s->rev.cache); /* Reverse cell cache */ + s->rev.cache = NULL; + } + + /* Free up the Second section */ + if (s->rev.nnrev != NULL) { + /* Free arrays at grid points, taking care of reference count */ + for (rpp = s->rev.nnrev; rpp < (s->rev.nnrev + s->rev.no); rpp++) { + if ((rp = *rpp) != NULL && --rp[2] <= 0) { + DECSZ(s, rp[0] * sizeof(int)); + free(*rpp); + *rpp = NULL; + } + } + free(s->rev.nnrev); + DECSZ(s, s->rev.no * sizeof(int *)); + s->rev.nnrev = NULL; + } + + if (di > 1 && s->rev.rev_valid) { + rev_struct *rsi, **rsp; + size_t ram_portion = g_avail_ram; + + /* Remove it from the linked list */ + for (rsp = &g_rev_instances; *rsp != NULL; rsp = &((*rsp)->next)) { + if (*rsp == &s->rev) { + *rsp = (*rsp)->next; + break; + } + } + + /* Aportion the memory */ + g_no_rev_cache_instances--; + + if (g_no_rev_cache_instances > 0) { + ram_portion /= g_no_rev_cache_instances; + for (rsi = g_rev_instances; rsi != NULL; rsi = rsi->next) + rsi->max_sz = ram_portion; + if (s->verbose) + fprintf(stdout, "%cThere %s %d rev cache instance%s with %lu Mbytes limit\n", + cr_char, + g_no_rev_cache_instances > 1 ? "are" : "is", + g_no_rev_cache_instances, + g_no_rev_cache_instances > 1 ? "s" : "", + ram_portion/1000000); + } + } + + s->rev.rev_valid = 0; + + if (s->rev.rev != NULL) { + /* Free arrays at grid points, taking care of reference count */ + for (rpp = s->rev.rev; rpp < (s->rev.rev + s->rev.no); rpp++) { + if ((rp = *rpp) != NULL && --rp[2] <= 0) { + DECSZ(s, rp[0] * sizeof(int)); + free(*rpp); + *rpp = NULL; + } + } + free(s->rev.rev); + DECSZ(s, s->rev.no * sizeof(int *)); + s->rev.rev = NULL; + } + + /* If first section has been initialised */ + if (s->rev.inited != 0) { + + /* Sub-simplex information */ + for (e = 0; e <= di; e++) { + rspl_free_ssimplex_info(s, &s->rev.sspxi[e]); + } + s->rev.res = 0; + s->rev.no = 0; + s->rev.inited = 0; + } + DBG(("rev allocation left after free = %d bytes\n",s->rev.sz)); +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifdef NEVER /* Test code */ +/* Reverse closest find using exaustive pseudo hilbert search */ +static void debug_find_closest_rev( +rspl *s, +double *out, +double *in +) { + double best = 1e38; + int e, f; + rpsh counter; /* Pseudo-hilbert counter */ + int gc[MXDI]; /* Grid index value */ + double iv[MXDI]; + float *gp; /* Pointer to grid data */ + + rpsh_init(&counter, s->di, (unsigned int *)s->g.res, gc); /* Initialise counter */ + for (;;) { + double dist; + + /* Compute grid pointer and input sample values */ + gp = s->g.a; /* Base of grid data */ + for (e = 0; e < s->di; e++) { /* Input tables */ + gp += s->g.fci[e] * gc[e]; /* Grid value pointer */ + iv[e] = s->g.l[e] + gc[e] * s->g.w[e]; /* Input sample values */ + } + + dist = 0.0; + for (f = 0; f < s->fdi; f++) { + double tt = in[f] - (double)gp[f]; + dist += tt * tt; + } + if (dist < best) { + best = dist; + for (e = 0; e < s->di; e++) + out[e] = iv[e]; + } + + /* Increment counter */ + if (rpsh_inc(&counter, gc)) + break; + } +} +#endif /* NEVER */ + +/* ========================================================== */ +/* reverse lookup acceleration structure initialisation code */ + +/* The reverse lookup relies on a search of the fwd interpolation tables. */ +/* To eliminate out of gamut points quickly, to provide a starting point for */ +/* the search, and to guarantee that all possible reverse solutions are discovered, */ +/* a spatial indexing structure is used to provide a list of starting candidate */ +/* forward indexes for a given output value. (rev.rev[]) */ +/* The reverse structure contains an fdi dimensional cell grid, each element of the */ +/* cell grid holding the indexes of the forward interpolation grid, which intersect */ +/* that ranges of output values. A reverse cell will be empty if there is no */ +/* potential exact solution. */ +/* Note that unlike the forward grid which is composed of verticies, */ +/* this grid is composed of cells (there is an extra row allocated */ +/* during construction using verticies, that are not used when converted. */ +/* to cells) */ +/* For accelleration of the nearest lookup, a parallel reverse grid is */ +/* constructed that holds lists of forward grid cells that may hold the */ +/* nearest point within the gamut. These lists may be empty if we are within */ +/* gamut - ie. the rev.nnrev[] array is the complement of the rev.rev[] array. */ +/* During construction of rev.nnrev[], it is initially filled with lists for */ +/* the potential nearest cell list for each vertex (hence the extra rows allocated */ +/* for rev[] and nnrev[]), and these are then merged down to form the list */ +/* for each cell extent. The nnrev[] array is filled using a seed fill algorithm, */ +/* starting from the edges of the filled cells in rev[]. */ +/* Since many of the cells map to the same surface region, many of the fwd cell lists */ +/* are shared. */ + +/* NOTE: that the nnrev accuracy doesn't seem as good as fill_nncell() ! */ +/* Could we fix this with better geometry calculations ??? */ + +/* rev.nnrev[] cache entry record */ +struct _nncache{ + int min[MXRO]; /* bwd vertex extent covered by this list */ + int max[MXRO]; + int *rip; /* Fwd cell list */ + struct _nncache *next; /* Link list for this cache key */ +}; typedef struct _nncache nncache; + +/* Structure to hold prime seed vertex information */ +struct _primevx{ + int ix; /* Index of prime seed */ + int gc[MXRO]; /* coordinate of the prime seed vertex */ + struct _primevx *next; /* Linked list for final conversion */ + int *clist; /* Cell list generated for prime cell */ +}; typedef struct _primevx primevx; + +/* Structure to hold temporary nn reverse vertex propogation information */ +struct _propvx{ + int ix; /* Index of this secondary seed */ + int gc[MXRO]; /* coordinate of this secondary seed */ + int cix; /* Index of the closest surface nnrev vertex */ + double dsq; /* Distance to the closest point squared */ + int pass; /* Propogation pass */ + struct _propvx *next; /* Linked list for next seeds */ +}; typedef struct _propvx propvx; + +/* Initialise the rev Second section acceleration information. */ +/* This is called when it is discovered on a call that s->rev.rev_valid == 0 */ +static void init_revaccell( +rspl *s +) { + int i, j; /* Index of fwd grid point */ + int e, f, ee, ff; + int di = s->di; + int fdi = s->fdi; + int gno = s->g.no; + int rgno = s->rev.no; + int argres = s->rev.ares; /* Allocation rgres, = no bwd cells +1 */ + int rgres = s->rev.res; /* no bwd cells */ + int rgres_1 = rgres-1; /* rgres -1 == maximum base coord value */ + + schbase *b = s->rev.sb; /* Base search information */ + char *vflag = NULL; /* Per vertex flag used during construction of nnrev */ + float *gp; /* Pointer to fwd grid points */ + primevx *plist = NULL, *ptail = NULL; /* Prime seed list for last pass */ + propvx *alist = NULL; /* Linked list of active secondary seeds */ + propvx *nlist = NULL; /* Linked list of next secondary seeds */ + DCOUNT(gg, MXRO, fdi, 0, 0, argres);/* Track the prime seed coordinate */ + DCOUNT(cc, MXRO, fdi, -1, -1, 2); /* Neighborhood offset counter */ + int nn[MXRO]; /* Neighbor coordinate */ + int pass = 0; /* Propogation pass */ + int nncsize; /* Size of the rev.nnrev construction cache index */ + nncache **nnc; /* nn cache index, used during construction of nnrev */ + unsigned hashk; /* Hash key */ + nncache *ncp; /* Hash entry pointer */ + int nskcells = 0; /* Number of skiped cells (debug) */ +#ifdef DEBUG + int cellinrevlist = 0; + int fwdcells = 0; +#endif + + DBG(("init_revaccell called, di = %d, fdi = %d, mgres = %d\n",di,fdi,(int)s->g.mres)); + + if (!s->rev.fastsetup) { + /* Temporary per bwd vertex/cell flag */ + if ((vflag = (char *) calloc(rgno, sizeof(char))) == NULL) + error("rspl malloc failed - rev.vflag points"); + INCSZ(s, rgno * sizeof(char)); + } + + /* + * The rev[] and nnrev[] grids contain pointers to lists of grid cube base indexes. + * If the pointer is NULL, then there are no base indexes in that list. + * A non NULL list uses element [0] to indicate the alocation size of the list, + * [1] contains the index of the next free location, [2] contains the reference + * count (lists may be shared), the list starts at [3]. The last entry is marked with -1. + */ + + /* We won't include any fwd cells that are over the ink limit, */ + /* so makes sure that the fwd cell nodes all have an ink limit value. */ + if (b != NULL && s->limiten) { + ECOUNT(gc, MXDIDO, s->di, 0, s->g.res, 0); /* coordinates */ + double iv[MXDI]; /* Input value corresponding to grid */ + + DBG(("Looking up fwd vertex ink limit values\n")); + /* Calling the limit function for each fwd vertex could be bad */ + /* if the limit function is slow. Maybe an octree type algorithm */ + /* could be used if this is a problem ? */ + EC_INIT(gc); + for (i = 0, gp = s->g.a; i < s->g.no; i++, gp += s->g.pss) { + if (gp[-1] == L_UNINIT) { + for (e = 0; e < di; e++) + iv[e] = s->g.l[e] + gc[e] * s->g.w[e]; /* Input sample values */ + gp[-1] = (float)(INKSCALE * s->limitf(s->lcntx, iv)); + } + EC_INC(gc); + } + s->g.limitv_cached = 1; + } + + /* We then fill in the in-gamut reverse grid lookups, */ + /* and identify nnrev prime seed verticies */ + + DBG(("filling in rev.rev[] grid\n")); + + /* To create rev.rev[], for all fwd grid points, form the cube with that */ + /* point at its base, and determine the bounding box of the output values */ + /* that could intersect that cube. */ + /* As a start for creating rev.nnrevp[], flag which bwd verticies are */ + /* covered by the fwd grid output range. */ + for (gp = s->g.a, i = 0; i < gno; gp += s->g.pss, i++) { + datao min, max; + int imin[MXRO], imax[MXRO], gc[MXRO]; + int uil; /* One is under the ink limit */ + +//printf("~1 i = %d/%d\n",i,gno); + /* Skip grid points on the upper edge of the grid, since there */ + /* is no further grid point to form a cube range with. */ + for (e = 0; e < di; e++) { + if(G_FL(gp, e) == 0) /* At the top edge */ + break; + } + if (e < di) { /* Top edge - skip this cube */ + continue; + } + + /* Find the output value bounding box values for this grid cell */ + uil = 0; + for (f = 0; f < fdi; f++) /* Init output min/max */ + min[f] = max[f] = gp[f]; + if (b == NULL || !s->limiten || gp[-1] <= s->limitv) + uil = 1; + + /* For all other grid points in the cube */ + for (ee = 1; ee < (1 << di); ee++) { + float *gt = gp + s->g.fhi[ee]; /* Pointer to cube vertex */ + + if (b == NULL || !s->limiten || gt[-1] <= s->limitv) + uil = 1; + + /* Update bounding box for this grid point */ + for (f = 0; f < fdi; f++) { + if (min[f] > gt[f]) + min[f] = gt[f]; + if (max[f] < gt[f]) + max[f] = gt[f]; + } + } + + /* Skip any fwd cells that are over the ink limit */ + if (!uil) { + nskcells++; + continue; + } + + /* Figure out intersection range in reverse grid */ + for (f = 0; f < fdi; f++) { + double t; + int mi; + double gw = s->rev.gw[f]; + double gl = s->rev.gl[f]; + t = (min[f] - gl)/gw; + mi = (int)floor(t); /* Grid coordinate */ + if (mi < 0) /* Limit to valid cube base index range */ + mi = 0; + else if (mi > rgres_1) + mi = rgres_1; + imin[f] = mi; + t = (max[f] - gl)/gw; + mi = (int)floor(t); /* Grid coordinate */ + if (mi < 0) /* Limit to valid cube base index range */ + mi = 0; + else if (mi > rgres_1) + mi = rgres_1; + imax[f] = mi; + } + +//printf("Scanning over grid:\n"); +//for (f = 0; f < fdi; f++) +//printf("Min[%d] = %d -> Max[%d] = %d\n",f,imin[f],f,imax[f]); + + /* Now create forward index and vector with all the reverse grid cells */ + for (f = 0; f < fdi; f++) + gc[f] = imin[f]; /* init coords */ + + for (f = 0; f < fdi;) { /* For all of intersect cube */ + int **rpp, *rp; + + /* Compute pointer to grid cell */ + for (rpp = s->rev.rev, f = 0; f < fdi; f++) + rpp += gc[f] * s->rev.coi[f]; + rp = *rpp; + +//printf("Currently at grid:\n"); +//for (f = 0; f < fdi; f++) +//printf("gc[%d] = %d\n",f,gc[f]); + + if (rp == NULL) { + if ((rp = (int *) rev_malloc(s, 6 * sizeof(int))) == NULL) + error("rspl malloc failed - rev.grid entry"); + INCSZ(s, 6 * sizeof(int)); + *rpp = rp; + rp[0] = 6; /* Allocation */ + rp[1] = 4; /* Next free Cell */ + rp[2] = 1; /* Reference count */ + rp[3] = i; + rp[4] = -1; /* End marker */ + } else { + int z = rp[1], ll = rp[0]; + if (z >= (ll-1)) { /* Not enough space */ + INCSZ(s, ll * sizeof(int)); + ll *= 2; + if ((rp = (int *) rev_realloc(s, rp, sizeof(int) * ll)) == NULL) + error("rspl realloc failed - rev.grid entry"); + *rpp = rp; + rp[0] = ll; + } + rp[z++] = i; + rp[z] = -1; + rp[1] = z; + } + /* Increment index */ + for (f = 0; f < fdi; f++) { + gc[f]++; + if (gc[f] <= imax[f]) + break; /* No carry */ + gc[f] = imin[f]; + } + } /* Next reverse grid point in intersecting cube */ + + if (s->rev.fastsetup) + continue; /* Skip nnrev setup */ + + + /* Now also register which grid points are in-gamut and are part of cells */ + /* than have a rev.rev[] list. */ + + /* Figure out intersection range in reverse nn (construction) vertex grid */ + /* This range may be empty if a grid isn't stradled by the fwd cell output */ + /* range. */ + for (f = 0; f < fdi; f++) { + double t; + int mi; + double gw = s->rev.gw[f]; + double gl = s->rev.gl[f]; + t = (min[f] - gl)/gw; + mi = (int)ceil(t); /* Grid coordinate */ + if (mi < 0) /* Limit to valid cube base index range */ + mi = 0; + else if (mi >= argres) + mi = rgres; + imin[f] = mi; + t = (max[f] - gl)/gw; + mi = (int)floor(t); /* Grid coordinate */ + if (mi < 0) /* Limit to valid cube base index range */ + mi = 0; + else if (mi >= argres) + mi = rgres; + imax[f] = mi; + if (imax[f] < imin[f]) + break; /* Doesn't straddle any verticies */ + } + + if (f >= fdi) { /* There are seed verticies to mark */ + +//printf("~1 marking prime seed vertex %d\n",i); + + /* Mark an initial seed point nnrev vertex, and */ + /* create a surface point propogation record for it */ + for (f = 0; f < fdi; f++) + gc[f] = imin[f]; /* init coords */ + + for (f = 0; f < fdi;) { /* For all of intersect cube */ + int **rpp, *rp; + char *fpp; + + /* Compute pointer to grid cell */ + for (rpp = s->rev.nnrev, fpp = vflag, f = 0; f < fdi; f++) { + int inc = gc[f] * s->rev.coi[f]; + rpp += inc; + fpp += inc; + } + rp = *rpp; + + *fpp = 3; /* Initial seed point */ + + /* Increment index */ + for (f = 0; f < fdi; f++) { + gc[f]++; + if (gc[f] <= imax[f]) + break; /* No carry */ + gc[f] = imin[f]; + } + } + } + } /* Next base grid point */ + + DBG(("We skipped %d cells that were over the limit\n",nskcells)); + + /* Setup the nnrev array if we are not doing a fast setup. */ + /* (fastsetup will instead fill the nnrev array on demand, */ + /* using an exaustive search.) */ + if (!s->rev.fastsetup) { + + /* The next step is to use all the prime seed grid points to set and propogate */ + /* the index of the closest fwd vertex through the revnn[] array. */ + /* (This doesn't work perfectly. Sometimes a vertex is not linked to it's closest */ + /* prime. I'm not sure if this is due to a bug here, or is a quirk of geometry */ + /* that a prime that is closest to a vertex isn't closest for any of its neighbors.) */ + DBG(("filling in rev.nnrev[] grid\n")); + + /* For all the primary seed points */ + DC_INIT(gg); + for (i = 0; i < rgno; i++) { + int **rpp; + primevx *prime= NULL; /* prime cell information structure */ + + if (vflag[i] != 3) { /* Not a prime seed point */ + goto next_seed_point; + } + + rpp = s->rev.nnrev + i; + +//printf("~1 potential rev.nnrev[] prime seed %d, about to scan neibors\n",i); + /* For all the neigbors of this seed */ + DC_INIT(cc); + while (!DC_DONE(cc)) { + propvx *prop; /* neighor cell propogation structure */ + int nix = 0; /* Neighbor cell index */ + char *fpp = vflag; + int **nrpp = s->rev.nnrev; + double dsq; + + for (f = 0; f < fdi; f++) { + nn[f] = gg[f] + cc[f]; + if (nn[f] < 0 || nn[f] >= argres) + break; /* Out of bounds */ + nix += nn[f] * s->rev.coi[f]; + } + fpp = vflag + nix; + + /* If neighbor out of bounds, or is a prime seed point, skip it */ + if (f < fdi || *fpp == 3) { + goto next_neighbor; + } + +//printf("~1 identified prime seed %d with neighbor %d\n",i,nix); + /* We now know that this prime seed will propogate, */ + /* so get/create the temporary information record for it */ + if (prime == NULL) { + + /* If this prime seed hasn't be setup before */ + if (*rpp != NULL) { + prime = *((primevx **)rpp); + } else { + /* Allocate a primevx if there isn't one */ + if ((prime = (primevx *) calloc(1, sizeof(primevx))) == NULL) + error("rspl malloc failed - rev.nnrev prime info structs"); + *((primevx **)rpp) = prime; + prime->ix = i; + for (f = 0; f < fdi; f++) + prime->gc[f] = gg[f]; +//if (fdi > 1) printf("~1 setting prime %d, gc = %d, %d, %d\n", i, prime->gc[0], prime->gc[1], prime->gc[2]); + } + } + + /* Pointer to nnrev vertex neighbor point */ + nrpp = s->rev.nnrev + nix; + + /* Compute the distance squared from this prime seed to this neighbor */ + for (dsq = 0.0, f = 0; f < fdi; f++) { + double tt = (gg[f] - nn[f]) * s->rev.gw[f]; + dsq += tt * tt; + } + + /* Get or allocate a prop structure for it */ + if (*nrpp != NULL) { + prop = *((propvx **)nrpp); + if ((dsq + 1e-6) < prop->dsq) { /* This prime is closer than previous */ + prop->cix = i; /* The index of the closest prime */ + prop->dsq = dsq; /* Distance squared to closest prime */ + } + } else { + if ((prop = (propvx *) calloc(1, sizeof(propvx))) == NULL) + error("rspl malloc failed - rev.nnrev propogation structs"); + *((propvx **)nrpp) = prop; + prop->ix = nix; + for (f = 0; f < fdi; f++) + prop->gc[f] = nn[f]; /* This neighbors coord */ + prop->cix = i; + prop->dsq = dsq; + prop->pass = pass; + prop->next = nlist; /* Add new seed to list of next seeds */ + nlist = prop; + *fpp = 1; + + } + next_neighbor:; + DC_INC(cc); + } + + next_seed_point:; + DC_INC(gg); + } + +//printf("~1 about to propogate secondary seeds\n"); + /* Now we propogate the secondary seed points until there are no more left */ + while(nlist != NULL) { + propvx *next; + propvx *tlp; + + if ((pass += 2) < 0) + error("Assert rev: excessive propogation passes"); +//printf("~1 about to do a round of propogation pass %d\n",(pass+2)/2); + + /* Mark all seed points on the current list with pass-1 */ + for (tlp = nlist; tlp != NULL; tlp = tlp->next) { + *(vflag + tlp->ix) = 2; + tlp->pass = pass-1; + } + + /* Go through each secondary seed in the active list, propogating them */ + for (alist = nlist, nlist = NULL; alist != NULL; alist = next) { + int **rpp; + primevx *prime= NULL; /* prime cell information structure */ + + next = alist->next; /* Next unless we re-insert one */ + + /* Grab this seed points coodinate and index */ + for (i = f = 0; f < fdi; f++) { + gg[f] = alist->gc[f]; + i += gg[f] * s->rev.coi[f]; + } + +//printf("\n~1 propogating from seed %d\n",i); + /* rpp = s->rev.nnrev + i; */ + + /* Grab the corresponding prime seed information record */ + prime = *((primevx **)(s->rev.nnrev + alist->cix)); + + /* For all the neigbors of this seed */ + DC_INIT(cc); + while (!DC_DONE(cc)) { + propvx *prop; /* neighor cell propogation structure */ + int nix; /* Neighbor cell index */ + char *fpp = vflag; + int **nrpp = s->rev.nnrev; + double dsq; + + for (nix = f = 0; f < fdi; f++) { + nn[f] = gg[f] + cc[f]; + if (nn[f] < 0 || nn[f] >= argres) + break; /* Out of bounds */ + nix += nn[f] * s->rev.coi[f]; + } + fpp = vflag + nix; +//printf("~1 neighbor ix %d, flag %d\n",nix,*fpp); + + /* If neighbor out of bounds, current vertex or prime, skip it */ + if (f < fdi || i == nix || *fpp >= 3) { +//printf("~1 skipping neighbour %d\n",nix); + goto next_neighbor2; + } + + /* Pointer to nnrev vertex neighbor point */ + nrpp = s->rev.nnrev + nix; + + /* Compute the distance squared from the prime seed to this neighbor */ + for (dsq = 0.0, f = 0; f < fdi; f++) { + double tt = (prime->gc[f] - nn[f]) * s->rev.gw[f]; + dsq += tt * tt; + } + + /* Get or allocate a prop structure for it */ + if (*nrpp != NULL) { + prop = *((propvx **)nrpp); +//if (prop->ix != nix) error ("Assert: prop index %d doesn't match index %d",prop->ix, nix); + + if ((dsq + 1e-6) < prop->dsq) { /* This prime is closer than previous */ +//printf("~1 updating %d to prime %d, dsq = %f from %f\n",nix, prime->ix, dsq, prop->dsq); + prop->cix = prime->ix; /* The index of the new closest prime */ + prop->dsq = dsq; /* Distance squared to closest prime */ + /* If this is a vertex from previous pass that has changed, */ + /* and it's not ahead of us in the current list, */ + /* put it next on the current list. */ + if (*fpp == 2 && prop->pass != (pass-1)) { +//printf("~1 re-shedule %d (%d) for next propogate\n",nix,prop->ix); +//if (next == NULL) +//printf("Before insert, next = NULL\n"); +//else +//printf("Before insert, next = %d\n",next->ix); + prop->pass = pass-1; /* Re-shedule once only */ + prop->next = next; + next = prop; + } + } + } else { + if ((prop = (propvx *) calloc(1, sizeof(propvx))) == NULL) + error("rspl malloc failed - rev.nnrev propogation structs"); + *((propvx **)nrpp) = prop; + prop->ix = nix; + for (f = 0; f < fdi; f++) + prop->gc[f] = nn[f]; /* This neighbors coord */ + prop->cix = prime->ix; + prop->dsq = dsq; +//printf("~1 propogating to new, %d, dsq = %f, prime %d\n",nix, dsq, prime->ix); + prop->pass = pass; + prop->next = nlist; /* Add new seed to list of next seeds */ + nlist = prop; + *fpp = 1; + } + + next_neighbor2:; + DC_INC(cc); + } + alist->pass = pass; + } + } + +#ifdef DEBUG + DBG(("checking that every vertex is now touched\n")); + for (i = 0; i < rgno; i++) { + if (vflag[i] < 2) { + printf("~1 problem: vertex %d flag = %d\n",i, vflag[i]); + } + if (vflag[i] == 2 && *(s->rev.nnrev + i) == NULL) { + printf("~1 problem: vertex %d flag = %d and struct = NULL\n",i, vflag[i]); + } + } +#endif /* DEBUG */ + +#ifdef NEVER /* Check that all cells are closest to their primes than any other */ +DC_INIT(gg); +for (i = 0; i < rgno; i++) { /* For all the verticies */ + if (vflag[i] == 2) { + propvx *prop = (propvx *) *(s->rev.nnrev + i); + for (j = 0; j < rgno; j++) { /* For all the primes */ + if (vflag[j] == 3) { + primevx *prime = (primevx *) *(s->rev.nnrev + j); + double dsq; + if (prime == NULL) + continue; + for (dsq = 0.0, f = 0; f < fdi; f++) { + double tt = (prime->gc[f] - prop->gc[f]) * s->rev.gw[f]; + dsq += tt * tt; + } + if ((dsq + 1e-6) < prop->dsq) { + warning("~1 vertex %d prime %d, dsq = %f, is closer to prime %d, dsq %f\n", i,prop->cix, prop->dsq, j, dsq); + /* See if any of the neighbors have the closer prime */ + DC_INIT(cc); /* For all the neigbors of this seed */ + while (!DC_DONE(cc)) { + propvx *nprop; /* neighor cell propogation structure */ + int nix; /* Neighbor cell index */ + char *fpp = vflag; + int **nrpp = s->rev.nnrev; + double dsq; + + for (nix = f = 0; f < fdi; f++) { + nn[f] = gg[f] + cc[f]; + if (nn[f] < 0 || nn[f] >= argres) + break; /* Out of bounds */ + nix += nn[f] * s->rev.coi[f]; + } + fpp = vflag + nix; +//printf("~1 neighbor ix %d, flag %d\n",nix,*fpp); + + /* If neighbor out of bounds, current vertex or prime, skip it */ + if (f < fdi || i == nix || *fpp != 2) { +//printf("~1 skipping neighbour %d\n",nix); + goto next_neighbor3; + } + + /* Pointer to nnrev vertex neighbor point */ + nrpp = s->rev.nnrev + nix; + if ((nprop = *((propvx **)nrpp)) != NULL) { +//printf("~1 neighbor %d %d %d has prime %d dsq %f\n",cc[0],cc[1],cc[2],nprop->cix,nprop->dsq); + if (nprop->cix == j) { +//warning("~1 but neighbor has this prime point!\n"); + + } + } + next_neighbor3:; + DC_INC(cc); + } +// prop->cix = j; /* Fix it and see what happens */ +// prop->dsq = dsq; + } + } + } + } + DC_INC(gg); +} + +#endif /* NEVER */ + + + DBG(("about to do convert vertex values to cell lists\n")); + /* Setup a cache for the fwd cell lists created, so that we can */ + /* avoid the list creation and memory allocation for identical lists */ + nncsize = s->rev.ares * s->rev.ares; + if ((nnc = (nncache **) calloc(nncsize, sizeof(nncache *))) == NULL) + error("rspl malloc failed - rev.nnc cache entries"); + + /* Now convert the nnrev secondary vertex points to pointers to fwd cell lists */ + /* Do this in order, so that we don't need the verticies after */ + /* they are converted to cell lists. */ + DC_INIT(gg); + for (i = 0; i < rgno; i++) { + int **rpp, *rp; + propvx *prop = NULL; /* vertex information structure */ + primevx *prime= NULL; /* prime cell information structure */ + int imin[MXRO], imax[MXRO]; /* Prime vertex range for each axis */ + double rmin[MXRO], rmax[MXRO]; /* Float prime vertex value range */ + unsigned int tcount; /* grid touch count for this opperation */ + datao min, max; /* Fwd cell output range */ + int lpix; /* Last prime index seen */ + +//if (fdi > 1) printf("~1 converting vertex %d\n",i); +//if (fdi > 1) printf("~1 coord %d %d %d\n",gg[0],gg[1],gg[2]); + + rpp = s->rev.nnrev + i; + if (vflag[i] == 3) { /* Cell base is prime */ + prime = (primevx *) *rpp; + + if (prime != NULL) { /* It's a propogating prime */ + /* Add prime to the end of the ptime linked list */ + prime->next = NULL; + if (plist == NULL) { + plist = ptail = prime; + } else { + ptail->next = prime; + ptail = prime; + } + } + } else if (vflag[i] == 2) { /* Cell base is secondary */ + prop = (propvx *)*rpp; + } else { /* Hmm */ + /* This seems to happen if the space explored is not really 3D ? */ + if (s->rev.primsecwarn == 0) { + warning("rev: bwd vertex %d is not prime or secondary (vflag = %d)" + "(Check that your measurement data is sane!)",i,vflag[i]); + s->rev.primsecwarn = 1; + } + fill_nncell(s, gg, i); /* Is this valid to do ?? */ + continue; + } + + /* Setup to scan of cube corners, and check that base is within cube grid */ + for (f = 0; f < fdi; f++) { + if (gg[f] > rgres_1) { /* Vertex outside bwd cell range, */ + if (prop != NULL && prime == NULL) { + free(prop); + *rpp = NULL; + } +//printf("~1 done vertex %d because its out of cell bounds\n",i); + goto next_vertex; + } + imin[f] = 0x7fffffff; + imax[f] = -1; + } + + /* For all the vertex points in the nnrev cube starting at this base (i), */ + /* Check if any of them are secondary seed points */ + for (ff = 0; ff < (1 << fdi); ff++) { + if (vflag[i + s->rev.hoi[ff]] == 2) + break; + } + + /* If not a cell that we want to create a nearest fwd cell list for */ + if (ff >= (1 << fdi)) { + /* Don't free anything, because we leave a prime in place, */ + /* and it can't be a prop. */ + goto next_vertex; + } + + /* For all the vertex points in the nnrev cube starting at this base (i), */ + /* accumulate the range they cover */ + lpix = -1; + for (f = 0; f < fdi; f++) { + imin[f] = 0x7fffffff; + imax[f] = -1; + } + for (ff = 0; ff < (1 << fdi); ff++) { + int ii = i + s->rev.hoi[ff]; /* cube vertex index */ + primevx *tprime= NULL; + + /* Grab vertex info and corresponding prime vertex info */ + if (vflag[ii] == 3) { /* Corner is a prime */ + tprime = (primevx *) *(s->rev.nnrev + ii); /* Use itself */ + if (tprime == NULL) + continue; /* Not a propogated in-gamut vertex */ + } else if (vflag[ii] == 2) { + propvx *tprop = (propvx *) *(s->rev.nnrev + ii); /* Use propogated prime */ + tprime = (primevx *) *(s->rev.nnrev + tprop->cix); + } else { + continue; /* Hmm */ + } + if (tprime->ix == lpix) + continue; /* Don't waste time */ + +//if (fdi > 1) printf("~1 corner %d, ix %d, prime %d gc = %d, %d, %d\n", ff, ii, tprime->ix, tprime->gc[0], tprime->gc[1], tprime->gc[2]); + + /* Update bounding box for this prime grid point */ + for (f = 0; f < fdi; f++) { + if (tprime->gc[f] < imin[f]) + imin[f] = tprime->gc[f]; + if (tprime->gc[f] > imax[f]) + imax[f] = tprime->gc[f]; + } + lpix = tprime->ix; + } + +//if (fdi > 1) printf("~1 prime vertex index range = %d - %d, %d - %d, %d - %d\n", imin[0], imax[0], imin[1], imax[1], imin[2], imax[2]); + + /* See if a list matching this range is in the cache */ + hashk = 0; + for (hashk = f = 0; f < fdi; f++) + hashk = hashk * 97 + imin[f] + 43 * (imax[f] - imin[f]); + hashk = hashk % nncsize; +//if (fdi > 1) printf("~1 hashk = %d from %d - %d %d - %d %d - %d\n", hashk, imin[0], imax[0], imin[1], imax[1], imin[2], imax[2]); + + /* See if we can locate an existing list for this range */ + for (ncp = nnc[hashk]; ncp != NULL; ncp = ncp->next) { +//if (fdi > 1) printf("~1 checking %d - %d %d - %d %d - %d\n", ncp->min[0], ncp->max[0], ncp->min[1], ncp->max[1], ncp->min[2], ncp->max[2]); + for (f = 0; f < fdi; f++) { + if (ncp->min[f] != imin[f] + || ncp->max[f] != imax[f]) { +//if (fdi > 1) printf("~1 not a match\n"); + break; + } + } + if (f >= fdi) { +//if (fdi > 1) printf("~1 got a match\n"); + break; /* Found a matching cache entry */ + } + } + + if (ncp != NULL) { + rp = ncp->rip; +//if (fdi > 1) printf("~1 got cache hit hashk = %d, with ref count %d\n\n",hashk, rp[1]); + rp[2]++; /* Increase reference count */ + + } else { + /* This section seems to be the most time consuming part of the nnrev setup. */ + + /* Allocate a cache entry and place it */ + if ((ncp = (nncache *)calloc(1, sizeof(nncache))) == NULL) + error("rspl malloc failed - rev.nn cach record"); + + for (f = 0; f < fdi; f++) { + ncp->min[f] = imin[f]; + ncp->max[f] = imax[f]; + } + ncp->next = nnc[hashk]; + nnc[hashk] = ncp; + + /* Convert the nn destination vertex range into an output value range. */ + for (f = 0; f < fdi; f++) { + double gw = s->rev.gw[f]; + double gl = s->rev.gl[f]; + rmin[f] = gl + imin[f] * gw; + rmax[f] = gl + imax[f] * gw; + } + + /* Do any adjustment of the range needed to acount for the inacuracies */ + /* caused by the vertex quantization. */ + /* (I don't really understand the need for the extra avggw expansion, */ + /* but there are artefacts without this. This size of this sampling */ + /* expansion has a great effect on the performance.) */ + { + double avggw = 0.0; + for (f = 0; f < fdi; f++) + avggw += s->rev.gw[f]; + avggw /= (double)fdi; + for (f = 0; f < fdi; f++) { /* Quantizing range plus extra */ + double gw = s->rev.gw[f]; + rmin[f] -= (0.5 * gw + 0.99 * avggw); + rmax[f] += (0.5 * gw + 0.99 * avggw); + } + } +//if (fdi > 1) printf("~1 prime vertex value adjusted range = %f - %f, %f - %f, %f - %fn", rmin[0], rmax[0], rmin[1], rmax[1], rmin[2], rmax[2]); + + /* computue the rev.rev cell grid range we will need to cover to */ + /* get all the cell output ranges that could touch our nn reverse range */ + for (f = 0; f < fdi; f++) { + double gw = s->rev.gw[f]; + double gl = s->rev.gl[f]; + imin[f] = (int)floor((rmin[f] - gl)/gw); + if (imin[f] < 0) + imin[f] = 0; + else if (imin[f] > rgres_1) + imin[f] = rgres_1; + imax[f] = (int)floor((rmax[f] - gl)/gw); + if (imax[f] < 0) + imax[f] = 0; + else if (imax[f] > rgres_1) + imax[f] = rgres_1; + cc[f] = imin[f]; /* Set grid starting value */ + } + tcount = s->get_next_touch(s); /* Get next grid touched generation count */ + +//if (fdi > 1) printf("~1 Cells to scan = %d - %d, %d - %d, %d - %d\n", imin[0], imax[0], imin[1], imax[1], imin[2], imax[2]); + + rp = NULL; /* We always allocate a new list initially */ + for (f = 0; f < fdi;) { /* For all the cells in the min/max range */ + int ii; + int **nrpp, *nrp; /* Pointer to base of cell list, entry 0 = allocated space */ + + /* Get pointer to rev.rev[] cell list */ + for (nrpp = s->rev.rev, f = 0; f < fdi; f++) + nrpp += cc[f] * s->rev.coi[f]; + + if ((nrp = *nrpp) == NULL) + goto next_range_list; /* This rev.rev[] cell is empty */ + + +//if (fdi > 1) printf("~1 adding list from cell %d, list length %d\n",nrpp - s->rev.rev, nrp[0]); + /* For all the fwd cells in the rev.rev[] list */ + for(nrp += 3; *nrp != -1; nrp++) { + int ix = *nrp; /* Fwd cell index */ + float *fcb = s->g.a + ix * s->g.pss; /* Pntr to base float of fwd cell */ + + if (TOUCHF(fcb) >= tcount) { /* If we seen visited this fwd cell before */ +//if (fdi > 1) printf("~1 skipping cell %d because we alread have it\n",ix); + continue; + } + TOUCHF(fcb) = tcount; /* Touch it so we will skip it next time */ + + /* Compute the range of output values this cell covers */ + for (f = 0; f < fdi; f++) /* Init output min/max */ + min[f] = max[f] = fcb[f]; + + /* For all other grid points in the fwd cell cube */ + for (ee = 1; ee < (1 << di); ee++) { + float *gt = fcb + s->g.fhi[ee]; /* Pointer to cube vertex */ + + /* Update bounding box for this grid point */ + for (f = 0; f < fdi; f++) { + if (min[f] > gt[f]) + min[f] = gt[f]; + if (max[f] < gt[f]) + max[f] = gt[f]; + } + } + +//if (fdi > 1) printf("~1 cell %d range = %f - %f, %f - %f, %f - %f\n", ix, min[0], max[0], min[1], max[1], min[2], max[2]); + + /* See if this fwd cell output values overlaps our region of interest */ + for (f = 0; f < fdi; f++) { + if (min[f] > rmax[f] + || max[f] < rmin[f]) { + break; /* Doesn't overlap */ + } + } + + if (f < fdi) { +//if (fdi > 1) printf("~1 skipping cell %d because we doesn't overlap\n",ix); + continue; /* It doesn't overlap */ + } + +//if (fdi > 1) printf("~1 adding fwd index %d to list\n",ix); +//if (fdi > 1) printf("~1 cell %d range = %f - %f, %f - %f, %f - %f\n", ix, min[0], max[0], min[1], max[1], min[2], max[2]); +#ifdef DEBUG + fwdcells++; +#endif + /* It does, add it to our new list */ + if (rp == NULL) { + if ((rp = (int *) rev_malloc(s, 6 * sizeof(int))) == NULL) + error("rspl malloc failed - rev.nngrid entry"); + INCSZ(s, 6 * sizeof(int)); + rp[0] = 6; /* Allocation */ + rp[1] = 4; /* Next free Cell */ + rp[2] = 1; /* reference count */ + rp[3] = ix; + rp[4] = -1; + } else { + int z = rp[1], ll = rp[0]; + if (z >= (ll-1)) { /* Not enough space */ + INCSZ(s, ll * sizeof(int)); + ll *= 2; + if ((rp = (int *) rev_realloc(s, rp, sizeof(int) * ll)) == NULL) + error("rspl realloc failed - rev.grid entry"); + rp[0] = ll; + } + rp[z++] = ix; + rp[z] = -1; + rp[1] = z; + } + } /* Next fwd cell in list */ + + /* Increment index */ + next_range_list:; + for (f = 0; f < fdi; f++) { + if (++cc[f] <= imax[f]) + break; /* No carry */ + cc[f] = imin[f]; + } + } + + ncp->rip = rp; /* record nnrev cell in cache */ +#ifdef DEBUG + cellinrevlist++; +#endif +//if (fdi > 1) printf("~1 adding cache entry with hashk = %d\n\n",hashk); + } + + /* Put the resulting list in place */ + if (prime != NULL) + prime->clist = rp; /* Save it untill we get rid of the primes */ + else + *rpp = rp; + +//if (*rpp == NULL) printf("~1 problem: we ended up with no list or prime struct at cell %d\n",i); + +#ifdef NEVER +/* Sanity check the list, to see if the list cells corner contain an output value */ +/* that is at least closer to the target than the prime. */ +if (prop != NULL) { + int *tp = rp; + double bdist = 1e60; + double bdist2 = 1e60; + double vx[MXRO]; /* Vertex location */ + double px[MXRO]; /* Prime location */ + double cl[MXRO]; /* Closest output value from list */ + double acl[MXRO]; /* Absolute closest output value */ + double dst; /* Distance to prime */ + int ti; + + primevx *prm = (primevx *) *(s->rev.nnrev + prop->cix); + for (f = 0; f < fdi; f++) { + double gw = s->rev.gw[f]; + double gl = s->rev.gl[f]; + vx[f] = gl + prop->gc[f] * gw; + px[f] = gl + prm->gc[f] * gw; + } + + for(tp++; *tp != -1; tp++) { + int ix = *tp; /* Fwd cell index */ + float *fcb = s->g.a + ix * s->g.pss; /* Pntr to base float of fwd cell */ + + for (ee = 0; ee < (1 << di); ee++) { + double ss; + float *gt = fcb + s->g.fhi[ee]; /* Pointer to cube vertex */ + + for (ss = 0.0, f = 0; f < fdi; f++) { + double tt = vx[f] - gt[f]; + ss += tt * tt; + } + if (ss < bdist) { + bdist = ss; + for (f = 0; f < fdi; f++) + cl[f] = gt[f]; + } + } + } + bdist = sqrt(bdist); + dst = sqrt(prop->dsq); + + /* Lookup best distance to any output value */ + if (dst < bdist) { + float *gt; + for (ti = 0, gt = s->g.a; ti < s->g.no; ti++, gt += s->g.pss) { + double ss; + + for (ss = 0.0, f = 0; f < fdi; f++) { + double tt = vx[f] - gt[f]; + ss += tt * tt; + } + if (ss < bdist2) { + bdist2 = ss; + for (f = 0; f < fdi; f++) + acl[f] = gt[f]; + } + } + } + bdist2 = sqrt(bdist2); + + if (dst < bdist) { + printf("~1 vertex %d has worse distance to values than prime\n",i); + printf("~1 vertex loc %f %f %f\n", vx[0], vx[1], vx[2]); + printf("~1 prime loc %f %f %f, dist %f\n", px[0], px[1], px[2],dst); + printf("~1 closest loc %f %f %f, dist %f\n", cl[0], cl[1], cl[2],bdist); + printf("~1 abs clst loc %f %f %f, dist %f\n", acl[0], acl[1], acl[2], bdist2); + } +} +#endif // NEVER + + if (prop != NULL && prime == NULL) { + free(prop); + } + + next_vertex:; + DC_INC(gg); + } + + DBG(("freeing up the prime seed structurs\n")); + /* Finaly convert all the prime verticies to cell lists */ + /* Free up all the prime seed structures */ + for (;plist != NULL; ) { + primevx *prime, *next = plist->next; + int **rpp; + + rpp = s->rev.nnrev + plist->ix; + if ((prime = (primevx *)(*rpp)) != NULL) { + if (prime->clist != NULL) /* There is a nn list for this cell */ + *rpp = prime->clist; + else + *rpp = NULL; + free(prime); + } else { + error("assert, prime cell %d was empty",plist->ix); + } + plist = next; + } + +#ifdef DEBUG + DBG(("sanity check that all rev accell cells are filled\n")); + DC_INIT(gg); + for (i = 0; i < rgno; i++) { + for (f = 0; f < fdi; f++) { + if (gg[f] > rgres_1) { /* Vertex outside bwd cell range, */ + goto next_vertex3; + } + } + + if (*(s->rev.nnrev + i) == NULL + && *(s->rev.rev + i) == NULL) { +// printf("~1 warning, cell %d [ %d %d %d] has a NULL list\n",i, gg[0],gg[1],gg[2]); + error("cell %d has a NULL list\n",i); + } + next_vertex3:; + DC_INC(gg); + } +#endif /* DEBUG */ + + /* Free up flag array used for construction */ + if (vflag != NULL) { + DECSZ(s, rgno * sizeof(char)); + free(vflag); + } + + /* Free up nn list cache indexing structure used in construction */ + if (nnc != 0) { + for (i = 0; i < nncsize; i++) { + nncache *nncp; + /* Run through linked list freeing entries */ + for (ncp = nnc[i]; ncp != NULL; ncp = nncp) { + nncp = ncp->next; + free(ncp); + } + } + free(nnc); + nnc = NULL; + } + } + + if (s->rev.rev_valid == 0 && di > 1) { + rev_struct *rsi; + size_t ram_portion = g_avail_ram; + + /* Add into linked list */ + s->rev.next = g_rev_instances; + g_rev_instances = &s->rev; + + /* Aportion the memory, and reduce cache if it is over new limit. */ + g_no_rev_cache_instances++; + ram_portion /= g_no_rev_cache_instances; + for (rsi = g_rev_instances; rsi != NULL; rsi = rsi->next) { + revcache *rc = rsi->cache; + + rsi->max_sz = ram_portion; + while (rc->nunlocked > 0 && rsi->sz > rsi->max_sz) { + if (decrease_revcache(rc) == 0) + break; + } +//printf("~1 rev instance ram = %d MB\n",rsi->sz/1000000); + } + + if (s->verbose) + fprintf(stdout, "%cThere %s %d rev cache instance%s with %lu Mbytes limit\n", + cr_char, + g_no_rev_cache_instances > 1 ? "are" : "is", + g_no_rev_cache_instances, + g_no_rev_cache_instances > 1 ? "s" : "", + ram_portion/1000000); + } + s->rev.rev_valid = 1; + +#ifdef DEBUG + if (fdi > 1) printf("%d cells in rev nn list\n",cellinrevlist); + if (fdi > 1) printf("%d fwd cells in rev nn list\n",fwdcells); + if (cellinrevlist > 1) printf("Avg list size = %f\n",(double)fwdcells/cellinrevlist); +#endif + + DBG(("init_revaccell finished\n")); +} + +/* Invalidate the reverse acceleration structures (section Two) */ +static void invalidate_revaccell( +rspl *s /* Pointer to rspl grid */ +) { + int e, di = s->di; + int **rpp, *rp; + + /* Invalidate the whole rev cache (Third section) */ + invalidate_revcache(s->rev.cache); + + /* Free up the contents of rev.rev[] and rev.nnrev[] */ + if (s->rev.rev != NULL) { + for (rpp = s->rev.rev; rpp < (s->rev.rev + s->rev.no); rpp++) { + if ((rp = *rpp) != NULL && --rp[2] <= 0) { + DECSZ(s, rp[0] * sizeof(int)); + free(*rpp); + *rpp = NULL; + } + } + } + if (s->rev.nnrev != NULL) { + for (rpp = s->rev.nnrev; rpp < (s->rev.nnrev + s->rev.no); rpp++) { + if ((rp = *rpp) != NULL && --rp[2] <= 0) { + DECSZ(s, rp[0] * sizeof(int)); + free(*rpp); + *rpp = NULL; + } + } + } + + if (di > 1 && s->rev.rev_valid) { + rev_struct *rsi, **rsp; + size_t ram_portion = g_avail_ram; + + /* Remove it from the linked list */ + for (rsp = &g_rev_instances; *rsp != NULL; rsp = &((*rsp)->next)) { + if (*rsp == &s->rev) { + *rsp = (*rsp)->next; + break; + } + } + + /* Aportion the memory */ + g_no_rev_cache_instances--; + + if (g_no_rev_cache_instances > 0) { + ram_portion /= g_no_rev_cache_instances; + for (rsi = g_rev_instances; rsi != NULL; rsi = rsi->next) + rsi->max_sz = ram_portion; + if (s->verbose) + fprintf(stdout, "%cThere %s %d rev cache instance%s with %lu Mbytes limit\n", + cr_char, + g_no_rev_cache_instances > 1 ? "are" : "is", + g_no_rev_cache_instances, + g_no_rev_cache_instances > 1 ? "s" : "", + ram_portion/1000000); + } + } + s->rev.rev_valid = 0; +} + +/* ====================================================== */ + +/* Initialise the rev First section, basic information that doesn't change */ +/* This is called on initial setup when s->rev.inited == 0 */ +static void make_rev_one( +rspl *s +) { + int i, j; /* Index of fwd grid point */ + int e, f, ee, ff; + int di = s->di; + int fdi = s->fdi; + int rgno, gno = s->g.no; + int argres; /* Allocation rgres, = no cells +1 */ + int rgres; + int rgres_1; /* rgres -1 == maximum base coord value */ + datao rgmin, rgmax; + + DBG(("make_rev_one called, di = %d, fdi = %d, mgres = %d\n",di,fdi,(int)s->g.mres)); + +//printf("~1 nnb = %d\n",nnb); + + s->get_out_range(s, rgmin, rgmax); /* overall output min/max */ + + /* Expand out range to encompass declared range */ + /* The declared range is assumed to be the range over which */ + /* we may want an reasonably accurate nearest reverse lookup. */ + for (f = 0; f < fdi; f++) { + if ((s->d.vl[f] + s->d.vw[f]) > rgmax[f]) + rgmax[f] = s->d.vl[f] + s->d.vw[f]; + if (s->d.vl[f] < rgmin[f]) + rgmin[f] = s->d.vl[f]; + } + + /* Expand out range slightly to allow for out of gamut points */ + for (f = 0; f < fdi; f++) { + double del = (rgmax[f] - rgmin[f]) * 0.10; /* Expand by +/- 10% */ + rgmax[f] += del; + rgmin[f] -= del; + } +//printf("~~got output range\n"); + + /* Heuristic - reverse grid acceleration resolution ? */ + /* Should this really be adapted to be constant in output space ? */ + /* (ie. make the gw aprox equal ?) Would complicate code rev accell */ + /* indexing though. */ + { + char *ev; + double gresmul = REV_ACC_GRES_MUL; /* Typically 2.0 */ + + if ((gresmul * s->g.mres) > (double)REV_ACC_GRES_LIMIT) { + gresmul = (double)REV_ACC_GRES_LIMIT/s->g.mres; /* Limit target res to typ. 43. */ + } + + /* Allow the user to override if it causes memory consumption problems */ + /* or to speed things up if more memory is available */ + if ((ev = getenv("ARGYLL_REV_ACC_GRID_RES_MULT")) != NULL) { + double mm; + mm = atof(ev); + if (mm > 0.1 && mm < 20.0) + gresmul *= mm; + } + /* Less than 4 is not functional */ + if ((rgres = (int) gresmul * s->g.mres) < 4) + rgres = 4; + } + argres = rgres+1; + s->rev.ares = argres; /* == number of verticies per side, used for construction */ + s->rev.res = rgres; /* == number of cells per side */ + rgres_1 = rgres-1; + + /* Number of elements in the rev.grid, including construction extra rows */ + for (rgno = 1, f = 0; f < fdi; f++, rgno *= argres); + s->rev.no = rgno; + +//printf("~1 argres = %d\n",argres); + /* Compute coordinate increments */ + s->rev.coi[0] = 1; +//printf("~1 coi[0] = %d\n",s->rev.coi[0]); + for (f = 1; f < fdi; f++) { + s->rev.coi[f] = s->rev.coi[f-1] * argres; +//printf("~1 coi[%d] = %d\n",f,s->rev.coi[f]); + } + + /* Compute index offsets from base of cube to other corners. */ + + for (s->rev.hoi[0] = f = 0, j = 1; f < fdi; j *= 2, f++) { + for (i = 0; i < j; i++) + s->rev.hoi[j+i] = s->rev.hoi[i] + s->rev.coi[f]; /* In grid points */ + } +//for (ff = 0; ff < (1 << fdi); ff++) +//printf("~1 hoi[%d] = %d\n",ff,s->rev.hoi[ff]); + + /* Conversion from output value to cell indexes */ + for (f = 0; f < fdi; f++) { + s->rev.gl[f] = rgmin[f]; + s->rev.gh[f] = rgmax[f]; + s->rev.gw[f] = (rgmax[f] - rgmin[f])/(double)rgres; + } + + if ((s->rev.rev = (int **) rev_calloc(s, rgno, sizeof(int *))) == NULL) + error("rspl malloc failed - rev.grid points"); + INCSZ(s, rgno * sizeof(int *)); + + if ((s->rev.nnrev = (int **) rev_calloc(s, rgno, sizeof(int *))) == NULL) + error("rspl malloc failed - rev.nngrid points"); + INCSZ(s, rgno * sizeof(int *)); + + s->rev.inited = 1; + + s->rev.stouch = 1; + + DBG(("make_rev_one finished\n")); +} + +/* ====================================================== */ + +/* First section of rev_struct init. */ +/* Initialise the reverse cell cache, sub simplex information */ +/* and reverse lookup acceleration structures. */ +/* This is called by a reverse interpolation call */ +/* that discovers that the reverse index list haven't */ +/* been initialised. */ +static void make_rev( +rspl *s +) { + int e, di = s->di; + char *ev; + size_t avail_ram = 256 * 1024 * 1024; /* Default assumed RAM in the system */ + size_t ram1, ram2; /* First Gig and rest */ + static int repsr = 0; /* Have we reported system RAM size ? */ + size_t max_vmem = 0; + + DBG(("make_rev called, di = %d, fdi = %d, mgres = %d\n",di,s->fdi,(int)s->g.mres)); + + /* Figure out how much RAM we can use for the rev cache. */ + /* (We compute this for each rev instance, to account for any VM */ + /* limit changes due to intervening allocations) */ + if (di > 1 || g_avail_ram == 0) { + #ifdef NT + { + BOOL (WINAPI* pGlobalMemoryStatusEx)(MEMORYSTATUSEX *) = NULL; + MEMORYSTATUSEX mstat; + + pGlobalMemoryStatusEx = (BOOL (WINAPI*)(MEMORYSTATUSEX *)) + GetProcAddress(LoadLibrary("KERNEL32"), "GlobalMemoryStatusEx"); + + if (pGlobalMemoryStatusEx == NULL) + error("Unable to link to GlobalMemoryStatusEx()"); + mstat.dwLength = sizeof(MEMORYSTATUSEX); + if ((*pGlobalMemoryStatusEx)(&mstat) != 0) { + if (sizeof(avail_ram) < 8 && mstat.ullTotalPhys > 0xffffffffL) + mstat.ullTotalPhys = 0xffffffffL; + avail_ram = mstat.ullTotalPhys; + } else { + warning("%cWarning - Unable to get system memory size",cr_char); + } + } + #else + #ifdef __APPLE__ + { + long long memsize; + size_t memsize_sz = sizeof(long long); + if (sysctlbyname("hw.memsize", &memsize, &memsize_sz, NULL, 0) == 0) { + if (sizeof(avail_ram) < 8 && memsize > 0xffffffffL) + memsize = 0xffffffff; + avail_ram = memsize; + } else { + warning("%cWarning - Unable to get system memory size",cr_char); + } + + } + #else /* Linux */ + { + long long total; + total = (long long)sysconf(_SC_PAGESIZE) * (long long)sysconf(_SC_PHYS_PAGES); + if (sizeof(avail_ram) < 8 && total > 0xffffffffL) + total = 0xffffffffL; + avail_ram = total; + } + #endif + #endif + DBG(("System RAM = %d Mbytes\n",avail_ram/1000000)); + + /* Make it sane */ + if (avail_ram < (256 * 1024 * 1024)) { + warning("%cWarning - System RAM size seems very small (%d MBytes)," + " assuming 256Mb instead",cr_char,avail_ram/1000000); + avail_ram = 256 * 1024 * 1024; + } + // avail_ram = -1; /* Fake 4GB of RAM. This will swap! */ + + ram1 = avail_ram; + ram2 = 0; + if (ram1 > (1024 * 1024 * 1024)) { + ram1 = 1024 * 1024 * 1024; + ram2 = avail_ram - ram1; + } + + /* Default maximum reverse memory (typically 50% of the first Gig, 75% of the rest) */ + g_avail_ram = (size_t)(REV_MAX_MEM_RATIO * ram1 + + REV_MAX_MEM_RATIO2 * ram2); + + /* Many 32 bit systems have a virtual memory limit, so we'd better stay under it. */ + /* This is slightly dodgy though, since we don't know how much memory other */ + /* software will need to malloc. A more sophisticated approach would be to */ + /* replace all malloc/calloc/realloc calls in the exe with a version that on failure, */ + /* sets the current memory usage as the new limit, and then */ + /* frees up some rev cache space before re-trying. This is a non-trivial change */ + /* to the source code though, and really has to include all user mode */ + /* libraries we're linked to, making implementation problematic. */ + /* Instead we do a simple test to see what the maximum allocation is, and */ + /* then use 75% of that for cache, and free cache and retry if */ + /* malloc failes in rev.c. Too bad if 25% isn't enough, and a malloc fails */ + /* outside rev.c... */ + if (sizeof(avail_ram) < 8) { + char *alocs[4 * 1024]; + size_t safe_max_vmem = 0; + int i; + +#ifdef __APPLE__ + int old_stderr, new_stderr; + + /* OS X malloc() blabs about a malloc failure. This */ + /* will confuse users, so we temporarily redirect stdout */ + fflush(stderr); + old_stderr = dup(fileno(stderr)); + new_stderr = open("/dev/null", O_WRONLY | O_APPEND); + dup2(new_stderr, fileno(stderr)); +#endif + for (i = 0; (i < 4 * 1024);i++) { + if ((alocs[i] = malloc(1024 * 1024)) == NULL) { + break; + } + max_vmem = (i+1) * 1024 * 1024; + } + for (--i; i >= 0; i--) { + free(alocs[i]); + } +#ifdef __APPLE__ + fflush(stderr); + dup2(old_stderr, fileno(stderr)); /* Restore stderr */ + close(new_stderr); + close(old_stderr); +#endif + /* To compute a true value, we need to allow for any VM already */ + /* used by any rev instances. */ + { + rev_struct *rsi; + + for (rsi = g_rev_instances; rsi != NULL; rsi = rsi->next) + max_vmem += rsi->sz; + } + +//fprintf(stdout,"~ Abs max VM = %d Mbytes\n",max_vmem/1000000); + safe_max_vmem = (size_t)(0.85 * max_vmem); + if (g_avail_ram > safe_max_vmem) { + g_avail_ram = safe_max_vmem; + if (s->verbose && repsr == 0) + fprintf(stdout,"%cTrimmed maximum cache RAM to %lu Mbytes to allow for VM limit\n",cr_char,g_avail_ram/1000000); + } + } + + /* Check for environment variable tweak */ + if ((ev = getenv("ARGYLL_REV_CACHE_MULT")) != NULL) { + double mm, gg; + mm = atof(ev); + if (mm < 0.01) /* Make it sane */ + mm = 0.01; + else if (mm > 100.0) + mm = 100.0; + gg = g_avail_ram * mm + 0.5; + if (gg > (double)(((size_t)0)-1)) + gg = (double)(((size_t)0)-1); + g_avail_ram = (size_t)(gg); + } + if (max_vmem != 0 && g_avail_ram > max_vmem && repsr == 0) { + g_avail_ram = (size_t)(0.95 * max_vmem); + fprintf(stdout,"%cARGYLL_REV_CACHE_MULT * RAM trimmed to %lu Mbytes to allow for VM limit\n",cr_char,g_avail_ram/1000000); + } + } + + /* Default - this will get aportioned as more instances appear */ + s->rev.max_sz = g_avail_ram; + + DBG(("reverse cache max memory = %d Mbytes\n",s->rev.max_sz/1000000)); + if (s->verbose && repsr == 0) { + fprintf(stdout, "%cRev cache RAM = %lu Mbytes\n",cr_char,g_avail_ram/1000000); + repsr = 1; + } + + /* Sub-simplex information for each sub dimension */ + for (e = 0; e <= di; e++) { + if (s->rev.sspxi[e].spxi != NULL) /* Assert */ + error("rspl rev, internal, init_ssimplex_info called on already init'd\n"); + + rspl_init_ssimplex_info(s, &s->rev.sspxi[e], e); + } + + make_rev_one(s); + + /* Reverse cell cache allocation */ + s->rev.cache = alloc_revcache(s); + + DBG(("make_rev finished\n")); +} + +/* ====================================================== */ + +#if defined(DEBUG1) || defined(DEBUG2) + +/* Utility - return a string containing a cells output value range */ +static char *pcellorange(cell *c) { + static char buf[5][300]; + static ix = 0; + char *bp; + rspl *s = c->s; + int di = s->di, fdi = s->fdi; + int ee, e, f; + + datao min, max; + +// double p[POW2MXRI][MXRI]; /* Vertex input positions for this cube. */ +// double v[POW2MXRI][MXRO+1]; /* Vertex data for this cube. Copied to x->v[] */ +// /* v[][fdi] is the ink limit values, if relevant */ + + for (f = 0; f < fdi; f++) { + min[f] = 1e60; + max[f] = -1e60; + } + + /* For all other grid points in the cube */ + for (ee = 0; ee < (1 << di); ee++) { + + /* Update bounding box for this grid point */ + for (f = 0; f < fdi; f++) { + if (min[f] > c->v[ee][f]) + min[f] = c->v[ee][f]; + if (max[f] < c->v[ee][f]) + max[f] = c->v[ee][f]; + } + } + if (++ix >= 5) + ix = 0; + bp = buf[ix]; + + for (e = 0; e < fdi; e++) { + if (e > 0) + *bp++ = ' '; + sprintf(bp, "%f:%f", min[e],max[e]); bp += strlen(bp); + } + return buf[ix]; +} + +#endif +/* ====================================================== */ + +#undef DEBUG +#undef DBGV +#undef DBG +#define DBGV(xxx) +#define DBG(xxx) + + + + + + diff --git a/rspl/rev.h b/rspl/rev.h new file mode 100644 index 0000000..b947cbe --- /dev/null +++ b/rspl/rev.h @@ -0,0 +1,457 @@ +#ifndef RSPL_REV_H +#define RSPL_REV_H + +/* + * Argyll Color Correction System + * Multi-dimensional regularized spline data structure + * + * Reverse interpolation support code. + * + * Author: Graeme W. Gill + * Date: 30/1/00 + * + * Copyright 1999 - 2008 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. + * + * Latest simplex/linear equation version. + */ + +#undef STATS /* Collect and print reverse cach stats */ + +/* Data structures used by reverse lookup code. */ +/* Note that the reverse lookup code only supports a more limited */ +/* dimension range than the general rspl code. */ + +/* + * Note on simplex parameter space. + * + * Simplex interpolation is normaly done in Baricentric space, where there + * is one more baricentric coordinate than dimensions, and the sum of all + * the baricentric coordinates must be 1. + * + * To simplify things, we work in a "Simplex parameter" space, in which + * there are only dimension parameters, and each directly corresponds + * with a cartesian coordinate, but the parameter order corresponds with + * the baricentric order. + * + * For example, given cartesian coordinates D0, D1, D2 + * these should be sorted from smallest to largest, thereby + * choosing a particular simplex within a cube, and allowing + * a correspondence to the parameter coordinates, ie: + * + * D1 D0 D2 Smallest -> Largest cartesian sort + * P2 P1 P0 Corresponding Parameter coordinates (note reverse order!) + * + * B0 = P0 Conversion to Baricentric coordinates + * B1 = P1 - P0 + * B2 = P2 - P1 + * B3 = 1 - P2 + * + * The vertex values directly correspond to Baricentric coordinates, + * giving the usual interpolation equation of: + * + * VV0 * B0 + * + VV1 * B1 + * + VV2 * B2 + * + VV3 * B3 + * + * Reversing the Parameter -> Baricentric equations gives the + * following interpolation equation using Parameter coordinates: + * + * VV0 - VV1 * P0 + * + VV1 - VV2 * P1 + * + VV2 - VV3 * P2 + * + VV3 + * + * It is this which is used in rev.c for solving the reverse + * interpolation problem. + */ + +/* A structure to hold per simplex coordinate and vertex mapping */ +/* This is relative to the construction cube. A face sub-simplex */ +/* that is common between cubes, will have a different psxinfo */ +/* depending on which cube created it. */ +typedef struct { + int face; /* Flag, nz if simplex lies on cube surface */ + int icomb[MXDI]; /* Index by Absolute[di] -> Simplex Parameter[sdi], */ + /* -1 == value 0, -2 == value 1 */ + /* Note that many Abs can map to one Param to form a sum. */ + /* icomb[] specifies the equation to convert simplex space */ + /* coordinates back into cartesian space. */ + int offs[MXDI+1]; /* Offsets to simplex verticies within cube == bit per dim */ + int goffs[MXDI+1]; /* Offsets to simplex verticies within grid */ + int foffs[MXDI+1]; /* Fwd grid floating offsets to simplex verticies from cube base */ + int pmino[MXDI], pmaxo[MXDI]; /* Cube verticy offsets to setup simplex pmin[] and */ + /* pmax[] bounding box pointers. */ +} psxinfo; + +/* Sub simplexes of a cube information structure */ +typedef struct { + int sdi; /* Sub-simplex dimensionality */ + int nospx; /* Number of sub-simplexs per cube */ + psxinfo *spxi; /* Per sub-simplex info array, NULL if not initialised */ +} ssxinfo; + +/* - - - - - - - - - - - - - - - - - - - - - */ +/* NOTE :- This should really be re-arranged to be per-sub-simplex caching, */ +/* rather than cell caching. Rather than stash the simplex info in the cells, */ +/* create a separate cache or some other way of sharing the common simplexes. */ +/* The code that ignores common face simplexes in cells could then be removed. */ + +/* Simplex definition. Each top level fwd interpolation cell, */ +/* is decomposed into sub-simplexes. Sub-simplexes are of equal or */ +/* lower dimensionality (ie. faces, edges, verticies) to the cube. */ +struct _simplex { + int refcount; /* reference count */ + struct _rspl *s; /* Pointer to parent rspl */ + int ix; /* Construction Fwd cell index */ + int si; /* Diagnostic - simplex number within level */ + int sdi; /* Sub-simplex dimensionality */ + int efdi; /* Effective fdi. This will be = fdi for a non clip */ + /* plane simplex, and fdi+1 for a clip plane simplex */ + /* The DOF (Degress of freedom) withing this simplex = sdi - efdi */ + + psxinfo *psxi; /* Generic per simplex info (construction cube relative) */ + int vix[MXRI+1]; /* fwd cell vertex indexes of this simplex [sdi+1] */ + /* This is a universal identification of this simplex */ + struct _simplex *hlink; /* Link to other cells with this hash */ + unsigned int touch; /* Last touch count. */ + short flags; /* Various flags */ + +#define SPLX_CLIPSX 0x01 /* This is a clip plane simplex */ + +#define SPLX_FLAG_1 0x04 /* v, linmin/max initialised */ +#define SPLX_FLAG_2 0x08 /* lu/svd initialised */ +#define SPLX_FLAG_2F 0x10 /* lu/svd init. failed */ +#define SPLX_FLAG_4 0x20 /* locus found */ +#define SPLX_FLAG_5 0x40 /* auxiliary lu/svd initialised */ +#define SPLX_FLAG_5F 0x80 /* auxiliary lu/svd init. failed */ + + +#define SPLX_FLAGS (SPLX_FLAG_1 | SPLX_FLAG_2 | SPLX_FLAG_2F \ + | SPLX_FLAG_4 | SPLX_FLAG_5 | SPLX_FLAG_5F) + + double v[MXRI+1][MXRO+1]; /* Simplex Vertex values */ + /* v[0..sdi][0..fdi-1] are the output interpolation values */ + /* v[0..sdi][fdi] are the ink limit interpolation values */ + + /* Baricentric vv[x][y] = (v[y][x] - v[y+1][x]) */ + /* and vv[x][sdi] = v[sdi][x] */ + + /* Note that #num indicates appropriate flag number */ + /* and *num indicates a validator */ + + double p0[MXRI]; /* Simplex base position = construction cube p[0] */ + double pmin[MXRI]; /* Simplex vertex input space min and */ + double pmax[MXRI]; /* max values [di] */ + + double min[MXRO+1], max[MXRO+1]; /* Simplex vertex output space [fdi+1] and */ + /* ink limit bounding values at minmax[fdi] */ + + /* If sdi == efdi, this holds the LU decomposition */ + /* else this holds the SVD and solution locus info */ + + char *aloc2; /* Memory allocation for #2 & #4 */ + + /* double **d_u; LU decomp of vv, U[0..efdi-1][0..sdi-1] #2 */ + /* int *d_w; LU decomp of vv, W[0..sdi-1] #2 */ + + double **d_u; /* SVD decomp of vv, U[0..efdi-1][0..sdi-1] #2 */ + double *d_w; /* SVD decomp of vv, W[0..sdi-1] #2 */ + double **d_v; /* SVD decomp of vv, V[0..sdi-1][0..sdi-1] #2 */ + + /* Degrees of freedom = dof = sdi - efdi */ + double **lo_l; /* Locus coefficients, [0..sdi-1][0..dof-1] #2 */ + + double *lo_xb; /* RHS used to compute lo_bd [0..efdi-1] *4 */ + double *lo_bd; /* Locus base solution, [0..sdi-1] #4 */ + + unsigned auxbm; /* aux bitmap mask for ax_lu and ax_svd *5 */ + int aaux; /* naux count for allocation *5 */ + int naux; /* naux for calculation (may be < aaux ?) *5 */ + + /* if (sdi-efdi = dof) == naux this holds LU of lo_l */ + /* else this holds the SVD of lo_l */ + + char *aloc5; /* Memory allocation for #5 */ + + /* double **ax_u; LU decomp of lo_l #5 */ + /* int *ax_w; Pivot record for ax_lu decomp #5 */ + + double **ax_u; /* SVD decomp of lo_l, U[0..naux-1][0..dof-1] #5 */ + double *ax_w; /* SVD decomp of lo_l, W[0..dof-1] #5 */ + double **ax_v; /* SVD decomp of lo_l, V[0..dof-1][0..dof-1] #5 */ + +}; typedef struct _simplex simplex; + +/* A candidate search cell (cell cache entry structure) */ +struct _cell { + struct _rspl *s; /* Pointer to parent rspl */ + + /* Cache information */ + int ix; /* Fwd cell index */ + struct _cell *hlink; /* Link to other cells with this hash */ + struct _cell *mrudown; /* Links to next most recently used cell */ + struct _cell *mruup; + int refcount; /* Reference count */ + int flags; /* Non-zero if the cell has been initialised */ +#define CELL_FLAG_1 0x01 /* Basic initialisation */ +#define CELL_FLAG_2 0x02 /* Simplex information initialised */ + + /* Use information */ + double sort; /* Sort key */ + + double limmin, limmax; /* limit() min/max for this cell */ + double bcent[MXRO]; /* Output value bounding shere center */ + double brad; /* Output value bounding shere radius */ + double bradsq; /* Output value bounding shere radius squared */ + + double p[POW2MXRI][MXRI]; /* Vertex input positions for this cube. */ + /* Copied to x->pmin/pmax[] & ink limit */ + + double v[POW2MXRI][MXRO+1]; /* Vertex data for this cube. Copied to x->v[] */ + /* v[][fdi] is the ink limit values, if relevant */ + + simplex **sx[MXRI+1]; /* Lists of simplexes that make up this cell. */ + /* Each list indexed by the non-limited simplex */ + /* dimensionality (similar to sspxi[]) */ + /* Each list will be NULL if it hasn't been created yet */ + int sxno[MXRI+1]; /* Corresponding count of each list */ + +}; typedef struct _cell cell; + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + +/* Enough space is needed to cache all the cells/simplexes */ +/* for a full aux. locus, or the query will be processed in */ +/* several "chunks" and will be slower. */ +/* This sets the basic memory usage of the rev code. */ + +#define REV_ACC_GRES_MUL 2.0 /* 2.0 Reverse accelleration grid resolution */ + /* multiplier over fwd grid resolution */ +#define REV_ACC_GRES_LIMIT 43 /* Reverse accelln. grid resolution limit before env. mult. */ +#define REV_MAX_MEM_RATIO 0.3 /* 0.3 Proportion of first 1G of Ram to use */ +#define REV_MAX_MEM_RATIO2 0.4 /* 0.4 Proportion of rest of Ram to use */ + /* rev as a fraction of the System RAM. */ +#define HASH_FILL_RATIO 3 /* 3 Ratio of entries to hash size */ + +/* The structure where cells are allocated and cached. */ + +/* Holds the cell and simplex match cache specific information */ +typedef struct { + struct _rspl *s; /* Pointer to parent rspl */ + int nacells; /* Number of allocated cells */ + int nunlocked; /* Number of unlocked cells that could be freed */ + int cell_hash_size; /* Current size of cell hash list */ + cell **hashtop; /* Top of hash list [cell_hash_size] */ + cell *mrutop, *mrubot; /* Top and bottom pointers of mru list */ + /* that tracks allocated cells */ + + int spx_hash_size; /* Current size of simplex hash list */ + simplex **spxhashtop; /* Face simplex hash index list */ + int nspx; /* Number of simplexes in hash list */ +} revcache; + +/* common search information */ +/* Type of (internal) reverse search */ +enum ops { + exact = 0, /* Search for all input values that exactly map to given output value */ + clipv = 1, /* Search for input values that map to outermost solution along a vector */ + clipn = 2, /* Search for input values that map to closest solution */ + auxil = 3, /* Search for input values that map to given output, and closest to auxiliary target */ + locus = 4 }; /* Return range of auxiliary values that contains solution */ + +/* + possible exact with clip */ + +/* Structure to hold clip line state information */ +typedef struct { + struct _rspl *s; /* Pointer to parent rspl */ + double st[MXRO]; /* start of line - reverse grid base value */ + double de[MXRO]; /* direction of line */ + int di[MXRO]; /* incerement in line direction */ + int ci[MXRO]; /* current rev grid coordinate */ + double t; /* Parameter 0.0 - 1.0, line finished if t > 1.0 */ +} line; + + +/* Structure to hold aux value of an intersection of a */ +/* solution locus with a sub-simplex. Used when asegs flag is set */ +typedef struct { + double xval; /* Auxiliary value */ + int nv; /* Number of verticies valid */ + int vix[MXRI+1]; /* Verticy indexes of sub-simplex involved */ +} axisec; + +/* -------------------------------------------- */ +/* Information needed/cached for reverse lookup */ +struct _schbase { + struct _rspl *s; /* Pointer to parent rspl */ + + int flags; /* Hint flags */ + enum ops op; /* Type of reverse search operation */ + int ixc; /* Cube index of corner that holds maximum input values */ + + int snsdi, ensdi; /* Start and end extra sub-simplex dimensionality */ + int (*setsort)(struct _schbase *b, cell *c); /* Function to triage & set cube sort index */ + int (*check)(struct _schbase *b, cell *c); /* Function to recheck cube worth keeping */ + int (*compute)(struct _schbase *b, simplex *x); /* Function to compute a simplex solution */ + + double v[MXRO+1]; /* Target output value, + ink limit */ + double av[MXRI]; /* Target auxiliary values */ + int auxm[MXRI]; /* aux mask flags */ + unsigned auxbm; /* aux bitmap mask */ + int naux; /* Number of auxiliary target input values */ + int auxi[MXRI]; /* aux list of auxiliary target input values */ + double idist; /* best input distance auxiliary target found (smaller is better) */ + int iabove; /* Number of auxiliaries at or above zero */ + + int canvecclip; /* Non-zero if vector clip direction usable */ + double cdir[MXRO]; /* Clip vector direction and length wrt. v[] */ + double ncdir[MXRO]; /* Normalised clip vector */ + double **cla; /* Clip vector LHS implicit equation matrix [fdi][fdi+1] (inc. ink tgt.) */ + double clb[MXRO+1]; /* Clip vector RHS implicit equation vector [fdi+1] (inc. ink tgt.) */ + double cdist; /* Best clip locus distance found (aim is min +ve) */ + int iclip; /* NZ if result is above (disabled) ink limit */ + + int mxsoln; /* Maximum number of solutions that we want */ + int nsoln; /* Current number of solutions found */ + co *cpp; /* Store solutions here */ + + int lxi; /* Locus search axiliary index */ + double min, max; /* current extreme locus values for locus search */ + int asegs; /* flag - find all search segments */ + int axisln; /* Number of elements used in axisl[] */ + int axislz; /* Space allocated to axisl[] */ + axisec *axisl; /* Auxiliary intersections */ + + int lclistz; /* Allocated space to cell sort list */ + cell **lclist; /* Sorted list of pointers to candidate cells */ + + int pauxcell; /* Indexe of previous call solution cell, -1 if not relevant */ + int plmaxcell; /* Indexe of previous call solution cell, -1 if not relevant */ + int plmincell; /* Indexe of previous call solution cell, -1 if not relevant */ + + int lsxfilt; /* Allocated space of simplex filter list */ + char *sxfilt; /* Flag for simplexes that should be in a cell */ + + double crad; /* nn current radius distance */ + double bw; /* nn current window distance */ + int wex[MXRO * 2]; /* nn current window edge indexes */ + double wed[MXRO * 2]; /* nn current window edge distances */ + +}; typedef struct _schbase schbase; + +/* ----------------------------------------- */ + +#ifdef STATS +struct _stats { + int searchcalls;/* Number of top level searches */ + int csearched; /* Cells searched */ + int ssearched; /* Simplexes searched */ + int sinited; /* Simplexes initialised to base level */ + int sinited2a; /* Simplexes initialised to 2nd level with LU */ + int sinited2b; /* Simplexes initialised to 2nd level with SVD */ + int sinited4i; /* Simplexes invalidated at 4th level */ + int sinited4; /* Simplexes initialised to 4th level */ + int sinited5i; /* Simplexes invalidated at 5th level */ + int sinited5a; /* Simplexes initialised to 5th level with LU */ + int sinited5b; /* Simplexes initialised to 5th level with SVD */ + int chits; /* Cells hit in cache */ + int cmiss; /* Cells misses in cache */ +}; typedef struct _stats stats; +#endif /* STATS */ + +/* ----------------------------------------- */ +/* Reverse info stored in main rspl function */ +struct _rev_struct { + + /* First section, basic information that doesn't change */ + /* Has been initialised if inited != 0 */ + + int inited; /* Non-zero if first section has been initialised */ + /* All other sections depend on this. */ + int fastsetup; /* Flag - NZ if fast setup at cost of slow throughput */ + + struct _rev_struct *next; /* Linked list of instances sharing memory */ + size_t max_sz; /* Maximum size permitted */ + size_t sz; /* Total memory current allocated by rev */ + +#ifdef NEVER + int thissz, lastsz; /* Debug reporting */ +#endif + + /* Reverse grid lookup information */ + int ares; /* Reverse grid allocated resolution, = res + 1, */ + /* allows for extra row used during construction */ + int res; /* Reverse grid resolution == ncells on a side */ + int no; /* Total number of points in reverse grid = rev.ares ^ fdi */ + int coi[MXRO]; /* Coordinate increments for each dimension */ + int hoi[1 << MXRO]; /* Coordinate increments for progress through cube */ + datao gl,gh,gw; /* Reverse grid low, high, grid cell width */ + + /* Second section, accelleration information that changes with ink limit. */ + int rev_valid; /* nz if this information in rev[] and nnrev[] is valid */ + int **rev; /* Exact reverse lookup starting list. */ + /* rev.no pointers to lists of fwd grid indexes. */ + /* First int is allocation length */ + /* Second int is reference count */ + /* Then follows cube indexes */ + /* Last int is -1 */ + int **nnrev; /* Nearest reverse lookup starting list. */ + /* rev.no pointers to lists of fwd grid indexes. */ + /* [0] is allocation length */ + /* [1] is the next free entry index */ + /* [2] is reference count */ + /* Then follows cube indexes */ + /* The last entry is marked with -1 */ + + /* Third section */ + revcache *cache; /* Where cells are allocated and cached */ + /* Sub-dimension simplex information */ + ssxinfo sspxi[MXRI+1];/* One per sub dimenstionality at offset sdi */ + + /* Fourth section */ + /* Has been initialise if sb != NULL */ + schbase *sb; /* Structure holding calculated per-search call information */ + + unsigned int stouch; /* Simplex touch count to avoid searching shared simplexs twice */ +#ifdef STATS + stats st[5]; /* Set of stats info indexed by enum ops */ +#endif /* STATS */ + + int primsecwarn; /* Not primary or secondary warning has been issued */ + +}; typedef struct _rev_struct rev_struct; + + +/* ------------------------------------ */ +/* Utility functions used by other parts of rspl implementation */ + +/* Initialise a static sub-simplex verticy information table */ +void rspl_init_ssimplex_info(struct _rspl *s, /* RSPL object */ +ssxinfo *xip, /* Pointer to sub-simplex info structure to init. */ +int sdi); /* Sub-simplex dimensionality (range 0 - di) */ + +/* Free the given sub-simplex verticy information */ +void rspl_free_ssimplex_info(struct _rspl *s, +ssxinfo *xip); /* Pointer to sub-simplex info structure */ + +#endif /* RSPL_REV_H */ + + + + + + + + + + + + + + diff --git a/rspl/revbench.c b/rspl/revbench.c new file mode 100644 index 0000000..d0c7566 --- /dev/null +++ b/rspl/revbench.c @@ -0,0 +1,306 @@ + +/************************************************/ +/* Benchmark RSPL reverse lookup */ +/************************************************/ + +/* Author: Graeme Gill + * Date: 31/10/96 + * Derived from tnd.c + * Copyright 1999 - 2000 Graeme W. Gill + * + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + + +#include +#include +#include +#include +#include +#include "copyright.h" +#include "aconfig.h" +#include "rspl.h" +#include "numlib.h" + +#undef DOLIMIT /* Define to have ink limit */ +#define LIMITVAL 2.5 /* Total ink limit sum */ +#undef DOCHECK + +#define SHOW_OUTPUT /* Define for printf on each conversion */ + +/* 11, 19 give about 60 seconds */ +#define GRES 17 /* Default grid resolution */ +#define RRES 43 /* Default reverse test resolution */ +#define DI 4 /* Dimensions in */ +#define FDI 3 /* Function (out) Dimensions */ +#define NIP 10 /* Number of solutions allowed */ + +#define flimit(vv) ((vv) < 0.0 ? 0.0 : ((vv) > 1.0 ? 1.0 : (vv))) +#define fmin(a,b) ((a) < (b) ? (a) : (b)) +#define fmin3(a,b,c) (fmin((a), fmin((b),(c)))) +#define fmax(a,b) ((a) > (b) ? (a) : (b)) +#define fmax3(a,b,c) (fmax((a), fmax((b),(c)))) + +/* Fwd function approximated by rspl */ +/* Dummy cmyk->rgb conversion. This simulates our device */ +void func( +void *cbctx, +double *out, +double *in) { + double kk; + double ci = in[0]; + double mi = in[1]; + double yi = in[2]; + double ki = in[3]; + double r,g,b; + + ci += ki; /* Add black back in */ + mi += ki; + yi += ki; + kk = fmax3(ci,mi,yi); + if (kk > 1.0) { + ci /= kk; + mi /= kk; + yi /= kk; + } + r = 1.0 - ci; + g = 1.0 - mi; + b = 1.0 - yi; + out[0] = flimit(r); + out[1] = flimit(g); + out[2] = flimit(b); +} + +/* Simplex ink limit function */ +double limitf( +void *lcntx, +float *in +) { + int i; + double ov; + + for (ov = 0.0, i = 0; i < DI; i++) { + ov += in[i]; + } + return ov; +} + + +void usage(void) { + fprintf(stderr,"Benchmark rspl reverse, Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"usage: revbench [-f fwdres] [-r revres] [-v level] iccin iccout\n"); + fprintf(stderr," -v Verbose\n"); + fprintf(stderr," -f res Set forward grid res\n"); + fprintf(stderr," -r res Set reverse test res\n"); + exit(1); +} + +int +main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + int clutres = GRES; + int rres = RRES; + int verb = 0; + int gres[MXDI]; + int e; + + clock_t stime,ttime; + rspl *rss; /* Multi-resolution regularized spline structure */ + + error_program = argv[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(); + + /* Verbosity */ + else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + } + else if (argv[fa][1] == 'f' || argv[fa][1] == 'F') { + fa = nfa; + if (na == NULL) usage(); + clutres = atoi(na); + } + else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') { + fa = nfa; + if (na == NULL) usage(); + rres = atoi(na); + } + else + usage(); + } else + break; + } + + printf("Started benchmark\n"); + /* Create the object */ + rss = new_rspl(RSPL_NOFLAGS, DI, FDI); + + for (e = 0; e < DI; e++) + gres[e] = clutres; + + printf("Rspl allocated\n"); + rss->set_rspl(rss, 0, (void *)NULL, func, + NULL, NULL, gres, NULL, NULL); + + printf("Rspl set\n"); + + /* Start exploring the reverse test grid */ + { + int ops = 0; + double secs; + rpsh counter; + unsigned rcount; + int ii[10]; + int f, rgres[MXDO]; + + int flags = 0; /* rev hint flags */ + co tp[NIP]; /* Test point */ + double cvec[4]; /* Text clip vector */ + int auxm[4]; /* Auxiliary target value valid flag */ +#ifdef DOCHECK + int j; +#endif +#ifdef NEVER + double lmin[4], lmax[4]; /* Locus min/max values */ +#endif + +#ifdef DOCHECK + char *check; /* Check that we hit every cell */ +#endif /* DOCHECK */ + + /* Set auxiliary target mask */ + auxm[0] = 0; + auxm[1] = 0; + auxm[2] = 0; + auxm[3] = 1; + +#ifdef DOLIMIT + rss->rev_set_limit(rss, + limitf, + NULL, + LIMITVAL /* limit maximum value */ + ); +#endif /* DOLIMIT */ + + printf("Forward resolution %d\n",clutres); + printf("Reverse resolution %d\n",rres); + +#ifdef DOCHECK + if ((check = (char *)calloc(1, rcount)) == NULL) + error("Malloc of check array\n"); +#endif /* DOCHECK */ + + for (f = 0; f < FDI; f++) + rgres[f] = rres; + + rcount = rpsh_init(&counter, FDI, (unsigned int *)rgres, ii); /* Initialise counter */ + + stime = clock(); + + /* Itterate though the grid */ + for (ops = 0;; ops++) { + int r; + int e; /* Table index */ + +#ifdef DOCHECK + check[((ii[2] * rres + ii[1]) * rres) + ii[0]] = 1; +#endif /* DOCHECK */ + + for (e = 0; e < FDI; e++) { /* Input tables */ + tp[0].v[e] = ii[e]/(rres-1.0); /* Vertex coordinates */ + } + + if (verb) + printf("Input = %f %f %f\n",tp[0].v[0], tp[0].v[1], tp[0].v[2]); + +#ifdef NEVER /* Do locus lookup explicitly ? */ + /* Lookup the locus for the auxiliary (Black) chanel */ + if ((r = rss->rev_locus(rss, + auxm, /* auxm Auxiliary mask flags */ + tp, /* Input and auxiliary values */ + lmin, /* Locus min/max return values */ + lmax + )) == 0) { + /* Rev locus failed - means that it will clip ? */ + tp[0].p[3] = 0.5; + flags = RSPL_WILLCLIP; /* Since there was no locus, we expect to clip */ + } else { + /* Set the auxiliary target */ + tp[0].p[3] = (lmin[3] + lmax[3])/2.0; + flags = RSPL_EXACTAUX; /* Since we got locus, expect exact auxiliary match */ + } +#else + tp[0].p[3] = 0.5; + flags = RSPL_AUXLOCUS; /* Auxiliary target is proportion of locus */ +#endif /* NEVER */ + + /* Clip vector to 0.5 */ + cvec[0] = 0.5 - tp[0].v[0]; + cvec[1] = 0.5 - tp[0].v[1]; + cvec[2] = 0.5 - tp[0].v[2]; + cvec[3] = 0.5 - tp[0].v[3]; + + /* Do reverse interpolation */ + if ((r = rss->rev_interp(rss, + flags, /* Hint flags */ + NIP, /* Number of solutions allowed */ + auxm, /* auxm Auxiliary mask flags */ + cvec, /* cvec Clip vector direction & length */ + tp) /* Input and output values */ + ) == 0) { + error("rev_interp failed\n"); + } + + r &= RSPL_NOSOLNS; /* Get number of solutions */ + + if (verb) + printf("Output 1 of %d: %f, %f, %f, %f%s\n", + r & RSPL_NOSOLNS, tp[0].p[0], tp[0].p[1], tp[0].p[2], tp[0].p[3], + (r & RSPL_DIDCLIP) ? " [Clipped]" : ""); + + + if (rpsh_inc(&counter, ii)) + break; + + } /* Next grid point */ + + ttime = clock() - stime; + secs = (double)ttime/CLOCKS_PER_SEC; + printf("Done - %d ops in %f seconds, rate = %f ops/sec\n",ops, secs,ops/secs); +#ifdef DOCHECK + for (j = 0; j < rcount; j++) { + if (check[j] != 1) { + printf("~~CHeck error at %d\n",j); + } + } +#endif /* DOCHECK */ + } + + rss->del(rss); + return 0; +} + + + + + + diff --git a/rspl/rspl.c b/rspl/rspl.c new file mode 100644 index 0000000..13a1776 --- /dev/null +++ b/rspl/rspl.c @@ -0,0 +1,1511 @@ + +/* + * Argyll Color Correction System + * Multi-dimensional regularized splines data fitter + * + * Author: Graeme W. Gill + * Date: 30/1/00 + * + * Copyright 1996 - 2004 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. + */ + +/* Version that a combination of relaxation and conjugate gradient */ +/* solution techniques. */ + +/* TTBD: + + To save space, the full columns of A should only be allocated when needed ? + (Does this actually save much space for a realistic data sample set ?) + + Get rid of error() calls - return status instead. + */ + +#include +#include +#include +#include +#include +#if defined(__IBMC__) && defined(_M_IX86) +#include +#endif + +#include "rspl_imp.h" +#include "numlib.h" +#include "counters.h" /* Counter macros */ + +#undef DEBUG +#undef DEBUGLU /* Debug fwd interpolation */ +#undef VERBOSE + +#undef NEVER +#define ALWAYS + +#ifdef DEBUGLU +# define DEBLU(xxxx) printf xxxx +#else +# define DEBLU(xxxx) +#endif + +/* Implemeted in this file: */ +rspl *new_rspl(int flags, int di, int fdi); +static void free_rspl(rspl *s); +static void init_grid(rspl *s); +static void free_grid(rspl *s); +static void get_in_range(rspl *s, double *min, double *max); +static void get_out_range(rspl *s, double *min, double *max); +static void get_out_range_points(rspl *s, int *minp, int *maxp); +static double get_out_scale(rspl *s); +static unsigned int get_next_touch(rspl *s); +static int within_restrictedsize(rspl *s); +static int interp_rspl_sx(rspl *s, co *pp); +static int part_interp_rspl_sx(rspl *s, co *p1, co *p2); +static int interp_rspl_nl(rspl *s, co *p); +int is_mono(rspl *s); +static int set_rspl(rspl *s, int flags, void *cbctx, + void (*func)(void *cbctx, double *out, double *in), + datai glow, datai ghigh, int gres[MXDI], datao vlow, datao vhigh); +static int re_set_rspl(struct _rspl *s, int flags, void *cbntx, + void (*func)(void *cbntx, double *out, double *in)); +static void scan_rspl(struct _rspl *s, int flags, void *cbntx, + void (*func)(void *cbntx, double *out, double *in)); +static void filter_rspl(struct _rspl *s, int flags, void *cbctx, + void (*func)(void *cbntx, float **out, double *in, int cvi)); + +extern int add_rspl(rspl *s, int flags, co *d, int dno); +extern void init_data(rspl *s); +extern void free_data(rspl *s); + +/* Implemented in rev.c: */ +void init_rev(rspl *s); +void free_rev(rspl *s); + +/* Implemented in gam.c: */ +void init_gam(rspl *s); +void free_gam(rspl *s); + +/* Implemented in spline.c: */ +void init_spline(rspl *s); +void free_spline(rspl *s); + +/* Implemented in opt.c: */ +int opt_rspl_imp(struct _rspl *s, int flags, int tdi, int adi, double **vdata, + double (*func)(void *fdata, double *inout, double *surav, int first, double *cw), + void *fdata, datai glow, datai ghigh, int gres[MXDI], datao vlow, datao vhigh); + + +/* Convention is to use: + i to index grid points u.a + n to index data points d.a + e to index position dimension di + f to index output function dimension fdi + j misc and cube corners + k misc + */ + +/* ================================ */ +/* Allocate an empty rspl object. */ +rspl * +new_rspl( + int flags, + int di, + int fdi +) { + rspl *s; + +#ifdef DEBUG + fprintf(stderr,"new_rspl with flags 0x%x, di %d, fdi %d\n",flags,di,fdi); +#endif + /* Allocate a structure */ + if ((s = (rspl *) calloc(1, sizeof(rspl))) == NULL) + error("rspl: malloc failed - main structure"); + + /* Set our fundamental parameters */ + if (di < 1 || di > MXDI) + error("rspl: can't handle input dimension %d",di); + s->di = di; + + if (fdi < 1 || fdi > MXDO) + error("rspl: can't handle output dimension %d",fdi); + s->fdi = fdi; + + /* And appropriate flags */ + if (flags & RSPL_VERBOSE) /* Turn on progress messages to stdout */ + s->verbose = 1; + if (flags & RSPL_NOVERBOSE) /* Turn off progress messages to stdout */ + s->verbose = 0; + + /* Allocate space for cube offset arrays */ + s->g.hi = s->g.a_hi; + s->g.fhi = s->g.a_fhi; + if ((1 << di) > DEF2MXDI) { + if ((s->g.hi = (int *) malloc(sizeof(int) * (1 << di))) == NULL) + error("rspl malloc failed - hi[]"); + if ((s->g.fhi = (int *) malloc(sizeof(int) * (1 << di))) == NULL) + error("rspl malloc failed - fhi[]"); + } + + /* Init sub sections */ + init_data(s); + init_grid(s); + init_rev(s); + init_gam(s); + init_spline(s); + + if (flags & RSPL_FASTREVSETUP) + s->rev.fastsetup = 1; + else + s->rev.fastsetup = 0; + + /* Set pointers to methods in this file */ + s->del = free_rspl; + s->interp = interp_rspl_sx; /* Default to simplex interp */ +#ifdef NEVER +#define USING_INTERP_NL +printf("!!!! rspl.c using interp_rspl_nl !!!!"); + s->interp = interp_rspl_nl; +#endif + s->part_interp = part_interp_rspl_sx; + s->set_rspl = set_rspl; + s->scan_rspl = scan_rspl; + s->re_set_rspl = re_set_rspl; + s->opt_rspl = opt_rspl_imp; + s->filter_rspl = filter_rspl; + s->get_in_range = get_in_range; + s->get_out_range = get_out_range; + s->get_out_range_points = get_out_range_points; + s->get_out_scale = get_out_scale; + s->get_next_touch = get_next_touch; + s->within_restrictedsize = within_restrictedsize; + + return s; +} + +/* Free the rspl and all its contents */ +static void free_rspl(rspl *s) { + int e; + + /* Free everying contained */ + free_data(s); /* Free any scattered data */ + free_rev(s); /* Free any reverse lookup data */ + free_gam(s); /* Free any grid data */ + free_grid(s); /* Free any grid data */ + + /* Free spline interpolation data ~~~~ */ + + /* Free structure */ + for (e = 0; e < s->di; e++) { + if (s->g.ipos[e] != NULL) + free(s->g.ipos[e]); + } + if (s->g.hi != s->g.a_hi) { + free(s->g.hi); + free(s->g.fhi); + } + free((void *) s); +} + +/* ======================================================== */ +/* Allocate rspl grid data, and initialise grid associated stuff */ +void +alloc_grid(rspl *s) { + int di = s->di, fdi = s->fdi; + int e,g,i; + int gno; /* Number of points in grid */ + ECOUNT(gc, MXDIDO, di, 0, s->g.res, 0);/* coordinates */ + float *gp; /* Grid point pointer */ + +#ifdef DEBUG + fprintf(stderr,"rspl allocating grid res %s\n",icmPiv(di, s->g.res)); +#endif + + /* Compute total number of elements in the grid */ + for (gno = 1, e = 0; e < di; gno *= s->g.res[e], e++) + ; + s->g.no = gno; + + s->g.pss = fdi+G_XTRA; /* float for each output value + nme + flags */ + + /* Compute index coordinate increments into linear grid for each dimension */ + /* ie. 1, gres, gres^2, gres^3 */ + for (s->g.ci[0] = 1, e = 1; e < di; e++) + s->g.ci[e] = s->g.ci[e-1] * s->g.res[e-1]; /* In grid points */ + for (e = 0; e < di; e++) + s->g.fci[e] = s->g.ci[e] * s->g.pss; /* In floats */ + + /* Compute index offsets from base of cube to other corners. */ + for (s->g.hi[0] = e = 0, g = 1; e < di; g *= 2, e++) { + for (i = 0; i < g; i++) + s->g.hi[g+i] = s->g.hi[i] + s->g.ci[e]; /* In grid points */ + } + /* same as hi, but in floats */ + for (i = 0; i < (1 << di); i++) + s->g.fhi[i] = s->g.hi[i] * s->g.pss; /* In floats */ + + /* Allocate space for grid */ + if ((s->g.alloc = (float *) malloc(sizeof(float) * gno * s->g.pss)) == NULL) + error("rspl malloc failed - grid points"); + s->g.a = s->g.alloc + G_XTRA; /* make -1 be nme, and -2 be (unsigned int) flags */ + + /* Set initial value of cell touch count */ + s->g.touch = 0; + + /* Init near edge flags, and touched flag */ + EC_INIT(gc); + for (i = 0, gp = s->g.a; !EC_DONE(gc); i++, gp += s->g.pss) { + gp[-1] = L_UNINIT; /* Init Ink limit function value to -1e38 */ + I_FL(gp); /* Init all flags to zero */ + for (e = 0; e < di; e++) { + int e1,e2; + e1 = gc[e]; /* Dist to bottom edge */ + e2 = (s->g.res[e]-1) - gc[e]; /* Dist to top edge */ + if (e2 < e1) { /* Top edge is closer */ + if (e2 > 2) + e2 = 2; /* Max dist = 2 */ + S_FL(gp,e,e2); /* Set flag value */ + } else { /* Bot edge is closer */ + if (e1 > 2) + e1 = 2; /* Max dist = 2 */ + S_FL(gp,e,e1 | 0x4); /* Set flag value */ + } + } + TOUCHF(gp) = 0; + + EC_INC(gc); + } + s->g.limitv_cached = 0; /* No limit values are current cached */ +} + +/* Init grid related elements of rspl */ +static void +init_grid(rspl *s) { + s->g.alloc = NULL; +} + +/* Free the grid allocation */ +static void +free_grid(rspl *s) { + if (s->g.alloc != NULL) + free((void *)s->g.alloc); +} + +/* ============================================ */ +/* Return the range of possible input values that the grid can represent */ +static void +get_in_range( +rspl *s, /* Grid to search */ +double *min, double *max /* Return min/max values */ +) { + int e; + for (e = 0; e < s->di; e++) { + min[e] = s->g.l[e]; + max[e] = s->g.h[e]; + } +} + +/* ============================================ */ +/* Discover the range of possible output values */ +static void +get_out_range( +rspl *s, /* Grid to search */ +double *min, double *max /* Return min/max values */ +) { + float *gp,*ep; /* Grid pointer */ + int f; + + if (s->g.fminmax_valid == 0) { /* Not valid, so compute it */ + for (f = 0; f < s->fdi; f++) { + s->g.fmin[f] = 1e30; + s->g.fmax[f] = -1e30; + s->g.fminx[f] = -1; + s->g.fmaxx[f] = -1; + } + + /* Scan the Grid points for min/max values */ + for (gp = s->g.a, ep = s->g.a + s->g.no * s->g.pss; gp < ep; gp += s->g.pss) { + for (f = 0; f < s->fdi; f++) { + if (s->g.fmin[f] > gp[f]) { + s->g.fmin[f] = gp[f]; + s->g.fminx[f] = (gp - s->g.a)/s->g.pss; + } + if (s->g.fmax[f] < gp[f]) { + s->g.fmax[f] = gp[f]; + s->g.fmaxx[f] = (gp - s->g.a)/s->g.pss; + } + } + } + + /* Compute overall output scale */ + for (s->g.fscale = 0.0, f = 0; f < s->fdi; f++) { + double tt = s->g.fmax[f] - s->g.fmin[f]; + s->g.fscale += tt * tt; + } + s->g.fscale = sqrt(s->g.fscale); + s->g.fminmax_valid = 1; /* Now is valid */ + } + for (f = 0; f < s->fdi; f++) { + if (min != NULL) + min[f] = s->g.fmin[f]; + if (max != NULL) + max[f] = s->g.fmax[f]; + } +} + +/* ============================================ */ +/* return the grid index of the grid values at the min & max output values */ +static void get_out_range_points(rspl *s, int *minp, int *maxp) { + int f; + + if (s->g.fminmax_valid == 0) /* Not valid, so compute it */ + get_out_range(s, NULL, NULL); + + for (f = 0; f < s->fdi; f++) { + if (minp != NULL) + minp[f] = s->g.fminx[f]; + if (maxp != NULL) + maxp[f] = s->g.fmaxx[f]; + } +} + +/* ============================================ */ +/* Discover the csale of the output values */ +static double +get_out_scale(rspl *s) { + + if (s->g.fminmax_valid == 0) /* Not valid, so compute it */ + get_out_range(s, NULL, NULL); + + return s->g.fscale; +} + +/* ============================================ */ +/* Return the next touched flag count value. */ +/* Whenever this rolls over, all the flags in the grid array will be reset. */ +/* */ +/* The touch flag is a way of some grid accessor (ie. rev()) making sure */ +/* that it doesn't access cells more than once for a particular operation, */ +/* without sorting a list of items to be accessed, or having to reset a */ +/* binary flag on all the cells for each operation. */ +/* If the value of a cells TOUCHF() is less than s->g.touch, then it hasn't */ +/* been accessed yet. Once the cell has been acessed, then TOUCHF() should */ +/* be set to s->g.touch. After 2^32 operations, the cell touch flags will */ +/* have been set to values between 0 and 2^32-1, so it is time to reset */ +/* all the flags. For that reason, the following method should be used */ +/* to get the next touch generation value at the start of each operation. */ +static unsigned int +get_next_touch( +rspl *s +) { + unsigned int tg; + float *gp,*ep; /* Grid pointer */ + + if ((tg = ++s->g.touch) == 0) { + + /* We have to reset all the cell flags to zero before we roll over */ + for (gp = s->g.a, ep = s->g.a + s->g.no * s->g.pss; gp < ep; gp += s->g.pss) { + TOUCHF(gp) = 0; + } + tg = ++s->g.touch; /* return 1 */ + } + return tg; +} + +/* ============================================ */ +/* Return non-zero if this rspl can be */ +/* used with Restricted Size functions. */ +static int within_restrictedsize( +rspl *s +) { + if (s->di <= MXRI && s->fdi <= MXRO) + return 1; + return 0; +} + +/* ============================================ */ +/* Do a forward interpolation using an simplex interpolation method. */ +/* Return 0 if OK, 1 if input was clipped to grid */ +/* Return 0 on success, 1 if clipping occured, 2 on other error */ +// ~~999 +//int rspldb = 0; + +static int interp_rspl_sx( +rspl *s, +co *p /* Input value and returned function value */ +) { + int e, di = s->di; + int f, fdi = s->fdi; + double we[MXDI]; /* Coordinate offset within the grid cell */ + int si[MXDI]; /* we[] Sort index, [0] = smallest */ + float *gp; /* Pointer to grid cube base */ + int rv = 0; /* Register clip */ + + /* We are using a simplex (ie. tetrahedral for 3D input) interpolation. */ + + DEBLU(("In %s\n", icmPdv(di, p->p))); + + /* Figure out which grid cell the point falls into */ + { + gp = s->g.a; /* Base of grid array */ + for (e = 0; e < di; e++) { + int gres_1 = s->g.res[e]-1; + double pe, t; + int mi; + pe = p->p[e]; + if (pe < s->g.l[e]) { /* Clip to grid */ + pe = s->g.l[e]; + rv = 1; + } + if (pe > s->g.h[e]) { + pe = s->g.h[e]; + rv = 1; + } + t = (pe - s->g.l[e])/s->g.w[e]; + 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; + gp += mi * s->g.fci[e]; /* Add Index offset for grid cube base in dimen */ + we[e] = t - (double)mi; /* 1.0 - weight */ +//if (rspldb && di == 3) printf("~1 e = %d, ix = %d, we = %f\n", e, mi, we[e]); + } + DEBLU(("ix %d, we %s\n", (gp - s->g.a)/s->g.pss, icmPdv(di, p->p))); + } + + /* Do selection sort on coordinates */ + { + for (e = 0; e < di; e++) + si[e] = e; /* Initial unsorted indexes */ + for (e = 0; e < (di-1); e++) { + double cosn; + cosn = we[si[e]]; /* Current smallest value */ + for (f = e+1; f < di; f++) { /* Check against rest */ + int tt; + tt = si[f]; + if (cosn > we[tt]) { + si[f] = si[e]; /* Exchange */ + si[e] = tt; + cosn = we[tt]; + } + } + } + } + DEBLU(("si[] = %s\n", icmPiv(di, si))); + + /* Now compute the weightings, simplex vertices and output values */ + { + double w; /* Current vertex weight */ + + w = 1.0 - we[si[di-1]]; /* Vertex at base of cell */ + for (f = 0; f < fdi; f++) + p->v[f] = w * gp[f]; + + DEBLU(("ix %d: w %f * val %s\n", (gp - s->g.a)/s->g.pss, w, icmPfv(fdi,gp))); + + for (e = di-1; e > 0; e--) { /* Middle verticies */ + w = we[si[e]] - we[si[e-1]]; + gp += s->g.fci[si[e]]; /* Move to top of cell in next largest dimension */ + for (f = 0; f < fdi; f++) + p->v[f] += w * gp[f]; + DEBLU(("ix %d: w %f * val %s\n", (gp - s->g.a)/s->g.pss, w, icmPfv(fdi,gp))); + } + + w = we[si[0]]; + gp += s->g.fci[si[0]]; /* Far corner from base of cell */ + for (f = 0; f < fdi; f++) + p->v[f] += w * gp[f]; + DEBLU(("ix %d: w %f * val %s\n", (gp - s->g.a)/s->g.pss, w, icmPfv(fdi,gp))); + DEBLU(("Outval %s\n", icmPdv(fdi, p->v))); + } + return rv; +} + +/* ============================================ */ +/* Do forward (partial) interpolation to allow input & output curves to be applied, */ +/* and allow input delta E to be estimated from output delta E. */ +/* Call with input value in p1[0].p[], */ +/* In order smallest to largest weight: */ +/* Return di+1 vertex values in p1[]].v[] and */ +/* 0-1 sub-cell weight values as (p1[].p[0] - p1[].p[1]). */ +/* Optionally in input channel order: */ +/* Returns di+1 partial derivatives + base value in p2[].v[], */ +/* with matching weight values for each in p2[].p[0] (last weight = 1)*/ +/* Return 0 if OK, 1 if input was clipped to grid */ +static int part_interp_rspl_sx( +struct _rspl *s, /* this */ +co *p1, +co *p2 /* optional - return partial derivatives for each input channel */ +) { + int e, di = s->di; + int f, fdi = s->fdi; + double we[MXDI]; /* Coordinate offset within the grid cell */ + int si[MXDI]; /* we[] Sort index, [0] = smallest */ + float *gp; /* Pointer to grid cube base */ + int rv = 0; /* Register clip */ + + /* We are using a simplex (ie. tetrahedral for 3D input) interpolation. */ + + /* Figure out which grid cell the point falls into */ + { + gp = s->g.a; /* Base of grid array */ + for (e = 0; e < di; e++) { + int gres_1 = s->g.res[e]-1; + double pe, t; + int mi; + pe = p1[0].p[e]; + if (pe < s->g.l[e]) { /* Clip to grid */ + pe = s->g.l[e]; + rv = 1; + } + if (pe > s->g.h[e]) { + pe = s->g.h[e]; + rv = 1; + } + t = (pe - s->g.l[e])/s->g.w[e]; + 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; + gp += mi * s->g.fci[e]; /* Add Index offset for grid cube base in dimen */ + we[e] = t - (double)mi; /* 1.0 - weight */ + } + } + + /* Do selection sort on coordinates */ + { + for (e = 0; e < di; e++) + si[e] = e; /* Initial unsorted indexes */ + for (e = 0; e < (di-1); e++) { + double cosn; + cosn = we[si[e]]; /* Current smallest value */ + for (f = e+1; f < di; f++) { /* Check against rest */ + int tt; + tt = si[f]; + if (cosn > we[tt]) { + si[f] = si[e]; /* Exchange */ + si[e] = tt; + cosn = we[tt]; + } + } + } + } + /* Now compute the vertex values that correspond */ + /* to the input faction weightings + fixed value */ + /* Scale the slopes + weights to make slopes */ + /* valid as partial derivative of input values */ + { + + p1[di].p[0] = 1.0; + p1[di].p[1] = we[si[di-1]]; /* Vertex at base of cell */ + for (f = 0; f < fdi; f++) + p1[di].v[f] = gp[f]; + + if (p2 != NULL) { + for (f = 0; f < fdi; f++) + p2[di].v[f] = gp[f]; /* Constant term @ vertex base */ + p2[di].p[0] = 1.0; + } + + for (e = di-1; e >= 0; e--) { /* Middle verticies to far vertex from base */ + int ee = si[e]; + float *lgp = gp; /* Last gp[] */ + + gp += s->g.fci[ee]; /* Move to top of cell in next largest dimension */ + + p1[e].p[0] = we[si[e]]; + p1[e].p[1] = e > 0 ? we[si[e-1]] : 0.0; + for (f = 0; f < fdi; f++) + p1[e].v[f] = gp[f]; + + if (p2 != NULL) { + for (f = 0; f < fdi; f++) + p2[ee].v[f] = (gp[f] - lgp[f]) / s->g.w[ee]; + p2[ee].p[0] = we[ee] * s->g.w[ee]; + } + } + } + return rv; +} + +#ifdef NEVER +/* Test out part_interp_rspl_sx() */ +/* Designed to test with a CMYK->Lab lookup */ +static int interp_rspl_sx( +rspl *s, +co *p /* Input value and returned function value */ +) { + int rv, rv2; + int e, f, m; + co p1[MXDI+1]; + co p2[MXDI+1]; + co p3; + double v1[MXDO]; + double v2[MXDO]; + + for (e = 0; e < s->di; e++) { + p1[0].p[e] = p->p[e]; + p3.p[e] = p->p[e]; + } + + rv = _interp_rspl_sx(s, p); + + if ((s->di != 4 || s->fdi != 3) + && (s->di != 3 || s->fdi != 4)) + return rv; + + rv2 = part_interp_rspl_sx(s, p1, p2); + + /* Check interpolation values returned in p1 and p2 form */ + for (f = 0; f < s->fdi; f++) + v1[f] = v2[f] = 0.0; + + for (e = 0; e <= s->di; e++) { + for (f = 0; f < s->fdi; f++) { + /* We could converts p1[].p[0] and p1[].p[1] through sub curve lookup, */ + /* and p1[].v[] though inverse output curve lookup, */ + /* then convert v1[] through output curve lookup. */ + v1[f] += p1[e].v[f] * (p1[e].p[0] - p1[e].p[1]); + + /* v2 is using base + partial derivatives */ + v2[f] += p2[e].v[f] * p2[e].p[0]; + } + } + + if (s->di == 4) { + printf("~1 %f %f %f %f ->\n",p->p[0], p->p[1], p->p[2], p->p[3]); + printf("~1 ref %d -> %f %f %f\n", rv, p->v[0], p->v[1], p->v[2]); + printf("~1 check1 %d -> %f %f %f\n", rv2, v1[0], v1[1], v1[2]); + printf("~1 check2 %d -> %f %f %f\n", rv2, v2[0], v2[1], v2[2]); + } else { + printf("~1 %f %f %f ->\n",p->p[0], p->p[1], p->p[2]); + printf("~1 ref %d -> %f %f %f %f\n", rv, p->v[0], p->v[1], p->v[2], p->v[3]); + printf("~1 check1 %d -> %f %f %f %f\n", rv2, v1[0], v1[1], v1[2], v1[3]); + printf("~1 check2 %d -> %f %f %f %f\n", rv2, v2[0], v2[1], v2[2], v2[3]); + } + + /* Check partial derivs in p2 */ + for (m = 0; m < s->di; m++) { + + p3.p[m] += 1e-5; + + _interp_rspl_sx(s, &p3); + for (f = 0; f < s->fdi; f++) + p3.v[f] = (p3.v[f] - p->v[f])/1e-5; + + if (s->di == 4) { + printf("~1 deriv %d:\n", m); + printf("~1 ref del %f %f %f\n", p3.v[0], p3.v[1], p3.v[2]); + printf("~1 check del %f %f %f\n", p2[m].v[0], p2[m].v[1], p2[m].v[2]); + } else { + printf("~1 deriv %d:\n", m); + printf("~1 ref del %f %f %f %f\n", p3.v[0], p3.v[1], p3.v[2], p3.v[3]); + printf("~1 check del %f %f %f %f\n", p2[m].v[0], p2[m].v[1], p2[m].v[2], p2[m].v[3]); + } + + p3.p[m] -= 1e-5; + } + + return rv; +} +#endif + +/* ============================================ */ + +#ifdef USING_INTERP_NL +/* Alternate, not currently used */ +/* Do a forward interpolation using an n-linear method. */ +/* Return 0 if OK, 1 if input was clipped to grid */ +/* Alternative to interp_rspl_sx */ +static int interp_rspl_nl( +rspl *s, +co *p /* Input value and returned function value */ +) { + int e, di = s->di; + int f, fdi = s->fdi; + double we[MXDI]; /* 1.0 - Weight in each dimension */ + double *gw; /* weight for each grid cube corner */ + double a_gw[DEF2MXDI]; /* Default space for gw */ + float *gp; /* Pointer to grid cube base */ + int rv = 0; + + gw = a_gw; + if ((1 << di) > DEF2MXDI) { + if ((gw = (double *) malloc(sizeof(double) * (1 << di))) == NULL) + error("rspl malloc failed - interp_rspl_nl"); + } + /* Figure out which grid cell the point falls into */ + { + gp = s->g.a; /* Base of grid array */ + for (e = 0; e < di; e++) { + int gres_1 = s->g.res[e]-1; + double pe, t; + int mi; + pe = p->p[e]; + if (pe < s->g.l[e]) { /* Clip to grid */ + pe = s->g.l[e]; + rv = 1; + } + if (pe > s->g.h[e]) { + pe = s->g.h[e]; + rv = 1; + } + t = (pe - s->g.l[e])/s->g.w[e]; + 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; + gp += mi * s->g.fci[e]; /* Add Index offset for grid cube base in dimen */ + we[e] = t - (double)mi; /* 1.0 - weight */ + } + } + + /* Compute corner weights needed for interpolation */ + { + int i, g; + gw[0] = 1.0; + for (e = 0, g = 1; e < di; g *= 2, e++) { + for (i = 0; i < g; i++) { + gw[g+i] = gw[i] * we[e]; + gw[i] *= (1.0 - we[e]); + } + } + } + + /* Now compute the output values */ + { + int i; + double w = gw[0]; + float *d = gp + s->g.fhi[0]; + for (f = 0; f < fdi; f++) /* Base of cube */ + p->v[f] = w * d[f]; + + for (i = 1; i < (1 << di); i++) { /* For all other corners of cube */ + double w = gw[i]; /* Strength reduce */ + float *d = gp + s->g.fhi[i]; + for (f = 0; f < fdi; f++) + p->v[f] += w * d[f]; + } + + } + + if (gw != a_gw) + free(gw); + + return rv; +} +#endif /* USING_INTERP_NL */ + +/* ============================================ */ +/* Non-mono calculations */ +/* Compute non-monotonicity factor for each grid point, and */ +/* return non-zero if the overall grid is monotonic. */ +/* (Note that this is not a true non-monotonicity test. */ +/* A true test has to deal with PCS combination values.) */ +int +is_mono( +rspl *s +) { + int f; + int di = s->di; + int fdi = s->fdi; + int *fci = s->g.fci; /* Strength reduction */ + float *gp, *ep; + double mcinc = MCINC/(s->g.mres-1); /* Scaled version of MCINC */ + double min = 1e20; /* Minimum clearance found */ + + /* Find the minimum step between grid points */ + for (gp = s->g.a, ep = s->g.a + s->g.no * s->g.pss; gp < ep; gp += s->g.pss) { + for (f = 0; f < fdi; f++) { + int e; + double e1,e2; /* Smallest/largest surrounting point */ + double u; /* Current output value we are considering */ + double ce; /* nm error */ + + /* Find smallest and largest surrounding points */ + /* In +/- 1 dimension directions */ + e1 = 1e20; e2 = -1e20; + for (e = 0; e < di; e++) { + int dof; /* Double offset */ + float vv; + + if ((G_FL(gp,e) & 3) < 1) + break; /* Skip to next grid point if on edge */ + dof = fci[e]; + vv = gp[f + dof]; + if (vv < e1) + e1 = vv; + if (vv > e2) + e2 = vv; + vv = gp[f - dof]; + if (vv < e1) + e1 = vv; + if (vv > e2) + e2 = vv; + } + if (e < di) /* We broke because we are on the edge */ + continue; + + u = gp[f]; + + e1 = u - e1; + e2 = e2 - u; + ce = (e1 < e2 ? e1 : e2); /* Smallest step */ + + if (ce < min) /* Current smallest step */ + min = ce; + } + } +//if (min < mcinc) printf("~1 is_mono failed by %e < %e\n",min,mcinc); + return min < mcinc; +} + +/* ============================================ */ +/* Initialize the grid from a provided function. By default the grid */ +/* values are set to exactly the value returned fy func(), unless the */ +/* RSPL_SET_APXLS flag is set, in which case an attempt is made to have */ +/* the grid points represent a least squares aproximation to the underlying */ +/* surface, by using extra samples in the middle of grid cells. */ +/* RSPL_SET_APXLS tends to improve the fit to the underlying function. */ +/* Grid index values are supplied "under" in[] at *((int*)&iv[-e-1]), */ +/* but if RSPL_SET_APXLS is set, the grid index will be the base of */ +/* the cell the center point is sampled from every second sample. */ +/* Return non-monotonic status */ +static int set_rspl( + struct _rspl *s,/* this */ + int flags, /* Combination of flags */ + void *cbctx, /* Opaque function context */ + void (*func)(void *cbctx, double *out, double *in), /* Function to set from */ + datai glow, /* Grid low scale - will expand to enclose data, NULL = default 0.0 */ + datai ghigh, /* Grid high scale - will expand to enclose data, NULL = default 1.0 */ + int gres[MXDI], /* Spline grid resolution for each dimension */ + datao vlow, /* Data value low normalize, NULL = default 0.0 */ + datao vhigh /* Data value high normalize - NULL = default 1.0 */ +) { + int e, f, j; + rpsh counter; /* Pseudo-hilbert counter */ + int gc[MXDI]; /* Grid index value */ + float *gp; /* Pointer to grid data */ + float *cc = NULL; /* Pointer to cell center data */ + double _iv[2 * MXDI], *iv = &_iv[MXDI]; /* Real index value/table value */ + double ov[MXDO]; + + if (flags & RSPL_VERBOSE) /* Turn on progress messages to stdout */ + s->verbose = 1; + if (flags & RSPL_NOVERBOSE) /* Turn off progress messages to stdout */ + s->verbose = 0; + + /* transfer desired grid range to structure */ + s->g.mres = 1.0; + s->g.bres = 0; + for (e = 0; e < s->di; e++) { + + if (gres[e] < 2) + error("rspl: grid res must be >= 2!"); + s->g.res[e] = gres[e]; /* record the desired resolution of the grid */ + s->g.mres *= gres[e]; + if (gres[e] > s->g.bres) { + s->g.bres = gres[e]; + s->g.brix = e; + } + + if (glow == NULL) + s->g.l[e] = 0.0; + else + s->g.l[e] = glow[e]; + + if (ghigh == NULL) + s->g.h[e] = 1.0; + else + s->g.h[e] = ghigh[e]; + + /* compute width of each grid cell */ + s->g.w[e] = (s->g.h[e] - s->g.l[e])/(double)(s->g.res[e]-1); + + /* ?? Should h be recomputed as (l + gres-1) * w ?? */ + } + s->g.mres = pow(s->g.mres, 1.0/e); /* geometric mean */ + + /* record low and width data normalizing factors */ + for (f = 0; f < s->fdi; f++) { + if (vlow == NULL) + s->d.vl[f] = 0.0; + else + s->d.vl[f] = vlow[f]; + + if (vhigh == NULL) + s->d.vw[f] = 1.0 - s->d.vl[f]; + else + s->d.vw[f] = vhigh[f] - s->d.vl[f]; + } + + /* Allocate the grid data */ + alloc_grid(s); + + /* Allocate space for cell center value lookup */ + if (flags & RSPL_SET_APXLS) { + if ((cc = (float *)malloc(sizeof(float) * s->g.no * s->fdi)) == NULL) + error("rspl malloc failed - center cell points"); + } + + /* Reset output min/max */ + for (f = 0; f < s->fdi; f++) { + s->g.fmin[f] = 1e30; + s->g.fmax[f] = -1e30; + s->g.fminx[f] = -1; + s->g.fmaxx[f] = -1; + } + + /* Set the grid points value from the provided function */ + + /* To make this clut function cache friendly, we use the pseudo-hilbert */ + /* count sequence. This keeps each point close to the last in the */ + /* multi-dimensional space. */ + rpsh_init(&counter, s->di, (unsigned int *)gres, gc); /* Initialise counter */ + for (;;) { + + /* Compute grid pointer and input sample values */ + gp = s->g.a; /* Base of grid data */ + for (e = 0; e < s->di; e++) { /* Input tables */ + gp += gc[e] * s->g.fci[e]; /* Grid value pointer */ + iv[e] = s->g.l[e] + gc[e] * s->g.w[e]; /* Input sample values */ + *((int *)&iv[-e-1]) = gc[e]; /* Trick to supply grid index in iv[] */ + } + + /* Apply incolor -> outcolor function we want to represent */ + func(cbctx, ov, iv); + + for (f = 0; f < s->fdi; f++) { /* Output chans */ + gp[f] = (float)ov[f]; /* Set output value */ + if (s->g.fmin[f] > gp[f]) { + s->g.fmin[f] = gp[f]; + s->g.fminx[f] = (gp - s->g.a)/s->g.pss; + } + if (s->g.fmax[f] < gp[f]) { + s->g.fmax[f] = gp[f]; + s->g.fmaxx[f] = (gp - s->g.a)/s->g.pss; + } + } + + /* For RSPL_SET_APXLS, get the center of the cell values as well. */ + if (cc != NULL) { + float *ccp; + + ccp = cc; + for (e = 0; e < s->di; e++) { /* Input tables */ + if (gc[e] >= (gres[e]-1)) + break; /* No center for outer row */ + iv[e] = s->g.l[e] + (gc[e] + 0.5) * s->g.w[e]; /* Input sample values */ + ccp += gc[e] * s->g.ci[e] * s->fdi; /* cc location */ + } + + if (e >= s->di) { /* Not outer row */ + /* Apply incolor -> outcolor function we want to represent */ + func(cbctx, ov, iv); + + for (f = 0; f < s->fdi; f++) { /* Output chans */ + ccp[f] = (float)ov[f]; /* Set output value */ + } + } + } + + /* Increment counter */ + if (rpsh_inc(&counter, gc)) + break; + } + + /* For RSPL_SET_APXLS, deal with cell center value, aproximate least squares adjustment */ + if (cc != NULL) { + int ee; + double cw = 1.0/(double)(1 << s->di); /* Weight for each cube corner */ + float *ccp; + + for (e = 0; e < s->di; e++) + gc[e] = 0; /* init coords */ + + /* Compute linear interpolated error to actual cell center value */ + for (ee = 0; ee < s->di;) { + + gp = s->g.a; /* Base of grid data */ + ccp = cc; /* Base of center data */ + for (e = 0; e < s->di; e++) { /* Input tables */ + gp += gc[e] * s->g.fci[e]; /* Grid value pointer */ + ccp += gc[e] * s->g.ci[e] * s->fdi; /* cc location */ + } + + for (f = 0; f < s->fdi; f++) { /* Output chans */ + double sum = 0.0; + + for (j = 0; j < (1 << s->di); j++) /* For corners of cube */ + sum += (gp + s->g.fhi[j])[f]; + sum *= cw; /* Interpolated value */ + ccp[f] -= sum; /* Correction to actual value */ + + /* Average half the error to cube corners */ + ccp[f] *= 0.5 * cw; /* Distribution fraction */ + } + + /* Increment coord */ + for (ee = 0; ee < s->di; ee++) { + if (++gc[ee] < (gres[ee]-1)) /* Don't go through upper edge */ + break; /* No carry */ + gc[ee] = 0; + } + } + + for (e = 0; e < s->di; e++) + gc[e] = 0; /* init coords */ + + /* Distribute the center error to the cell corners */ + for (ee = 0; ee < s->di;) { + + gp = s->g.a; /* Base of grid data */ + ccp = cc; /* Base of center data */ + for (e = 0; e < s->di; e++) { /* Input tables */ + gp += gc[e] * s->g.fci[e]; /* Grid value pointer */ + ccp += gc[e] * s->g.ci[e] * s->fdi; /* cc location */ + } + + for (j = 0; j < (1 << s->di); j++) { /* For corners of cube */ + double sc = 1.0; /* Scale factor for non-edge nodes */ + + /* Don't distribute error to edge nodes since there may */ + /* an expectation that they have precicely set values */ + /* (ie. white and black points) */ + for (e = 0; e < s->di; e++) { + if ((gc[e] == 0 && (j & (1 << e)) == 0) + || (gc[e] == ((gres[e]-2)) && (j & (1 << e)) != 0)) + sc *= 0.0; + } + + for (f = 0; f < s->fdi; f++) { /* Output chans */ + double vv; + vv = (gp + s->g.fhi[j])[f]; /* Current value */ + vv += sc * cc[f]; /* Correction */ + (gp + s->g.fhi[j])[f] = vv; + if (s->g.fmin[f] > vv) { + s->g.fmin[f] = vv; + s->g.fminx[f] = (gp + s->g.fhi[j] - s->g.a)/s->g.pss; + } + if (s->g.fmax[f] < vv) { + s->g.fmax[f] = vv; + s->g.fmaxx[f] = (gp + s->g.fhi[j] - s->g.a)/s->g.pss; + } + } + } + + /* Increment coord */ + for (ee = 0; ee < s->di; ee++) { + if (++gc[ee] < (gres[ee]-1)) /* Don't go through upper edge */ + break; /* No carry */ + gc[ee] = 0; + } + } + + free((void *)cc); + } + + /* Compute overall output scale */ + for (s->g.fscale = 0.0, f = 0; f < s->fdi; f++) { + double tt = s->g.fmax[f] - s->g.fmin[f]; + s->g.fscale += tt * tt; + } + s->g.fscale = sqrt(s->g.fscale); + + s->g.fminmax_valid = 1; /* Now is valid */ + + /* Return non-mono check */ + return is_mono(s); +} + +/* ============================================ */ +/* Scan or change each grid point in the rspl. */ +static int scan_set_rspl( +struct _rspl *s, /* this */ +int flags, /* Combination of flags */ +void *cbctx, /* Opaque function context */ +void (*func)(void *cbntx, double *out, double *in), /* Function to get/set from */ +int change /* Flag - nz means change values, 0 means scan values */ +) { + int e, f; + rpsh counter; /* Pseudo-hilbert counter */ + int gc[MXDI]; /* Grid index value */ + float *gp; /* Pointer to grid data */ + double _iv[2 * MXDI], *iv = &_iv[MXDI]; /* Real index value/table value */ + double ov[MXDO]; + + if (flags & RSPL_VERBOSE) /* Turn on progress messages to stdout */ + s->verbose = 1; + if (flags & RSPL_NOVERBOSE) /* Turn off progress messages to stdout */ + s->verbose = 0; + + if (change) { + /* Reset output min/max */ + for (f = 0; f < s->fdi; f++) { + s->g.fmin[f] = 1e30; + s->g.fmax[f] = -1e30; + s->g.fminx[f] = -1; + s->g.fmaxx[f] = -1; + } + } + + /* Set the grid points value from the provided function */ + /* Give the function both the grid position and the existing output values */ + + /* To make this clut function cache friendly, we use the pseudo-hilbert */ + /* count sequence. This keeps each point close to the last in the */ + /* multi-dimensional space. */ + rpsh_init(&counter, s->di, (unsigned int *)s->g.res, gc); /* Initialise counter */ + for (;;) { + + /* Compute grid pointer and input sample values */ + gp = s->g.a; /* Base of grid data */ + for (e = 0; e < s->di; e++) { /* Input tables */ + gp += s->g.fci[e] * gc[e]; /* Grid value pointer */ + iv[e] = s->g.l[e] + gc[e] * s->g.w[e]; /* Input sample values */ + *((int *)&iv[-e-1]) = gc[e]; /* Trick to supply grid index in iv[] */ + } + + for (f = 0; f < s->fdi; f++) /* Output chans */ + ov[f] = gp[f]; + + /* Let function scan the input and output values, or */ + /* Apply incolor -> outcolor, or oldoutcolor->outcolor function we want to represent */ + func(cbctx, ov, iv); + + if (change) { /* Put new output values back */ + for (f = 0; f < s->fdi; f++) { /* Output chans */ + gp[f] = (float)ov[f]; + if (s->g.fmin[f] > gp[f]) { + s->g.fmin[f] = gp[f]; + s->g.fminx[f] = (gp - s->g.a)/s->g.pss; + } + if (s->g.fmax[f] < gp[f]) { + s->g.fmax[f] = gp[f]; + s->g.fmaxx[f] = (gp - s->g.a)/s->g.pss; + } + } + } + + /* Increment counter */ + if (rpsh_inc(&counter, gc)) + break; + } + + if (change == 0) { + return 0; + } + + /* Compute overall output scale */ + for (s->g.fscale = 0.0, f = 0; f < s->fdi; f++) { + double tt = s->g.fmax[f] - s->g.fmin[f]; + s->g.fscale += tt * tt; + } + s->g.fscale = sqrt(s->g.fscale); + + s->g.fminmax_valid = 1; /* Now is valid */ + + /* Invalidate various things */ + free_data(s); /* Free any scattered data */ + free_rev(s); /* Free any reverse lookup data */ + + /* Return non-mono check */ + return is_mono(s); +} + +/* Re-initialize the grid from existing grid values, and the provided function */ +/* Grid index values are supplied "under" in[] at *((int*)&iv[-e-1]) */ +/* Return non-monotonic status. We assume that the ouput scale factors don't change. */ +static int re_set_rspl( +struct _rspl *s, /* this */ +int flags, /* Combination of flags */ +void *cbctx, /* Opaque function context */ +void (*func)(void *cbntx, double *out, double *in) /* Function to set from */ +) { + return scan_set_rspl(s, flags, cbctx, func, 1); +} + +/* Scan the rspl grid point locations and values. Grid index values are */ +/* supplied "under" in[] *((int*)&iv[-e-1]) */ +static void scan_rspl( +struct _rspl *s, /* this */ +int flags, /* Combination of flags */ +void *cbctx, /* Opaque function context */ +void (*func)(void *cbntx, double *out, double *in) /* Function to get from */ +) { + scan_set_rspl(s, flags, cbctx, func, 0); +} + + +/* ============================================ */ +/* Allow the grid values to be filtered. */ +/* For each grid value, provide the input value and */ +/* pointers to all the output values in a 3^di grid around */ +/* the output value. Pointers will be NULL if neigbour is outside */ +/* the grid. cvi is the index of the output value. */ +/* Grid index values are supplied "under" in[] at *((int*)&iv[-e-1]) */ +/* After all the grid values have been done, they will be updated */ +/* with their new values. */ +static void filter_rspl( +struct _rspl *s, /* this */ +int flags, /* Combination of flags */ +void *cbctx, /* Opaque function context */ +void (*func)(void *cbntx, float **out, double *in, int cvi) /* Function to set from */ +) { + int e, f; + ECOUNT(gc, MXDIDO, s->di, 0, s->g.res, 0); /* coordinates */ + DCOUNT(cc, MXDIDO, s->di, -1, -1, 2); /* Surrounding cube counter */ + float *gp, *ep; /* Pointer to grid data */ + float *tarry, *tp; /* Temporary array of values */ + double _iv[2 * MXDI], *iv = &_iv[MXDI]; /* Real index value/table value */ + int cvi; /* Center value index = 3^di-1)/2 */ + int pow3di = 1; + float **svals; /* Pointer to surrounding output values */ + float *a_svals[DEF3MXDI];/* default allocation for svals */ + + if (flags & RSPL_VERBOSE) /* Turn on progress messages to stdout */ + s->verbose = 1; + if (flags & RSPL_NOVERBOSE) /* Turn off progress messages to stdout */ + s->verbose = 0; + + /* Allocate svals array */ + svals = a_svals; + for (e = 0; e < s->di; e++) + pow3di *= 3; + if (pow3di > DEF3MXDI) { + if ((svals = (float **) malloc(sizeof(float *) * pow3di)) == NULL) + error("rspl malloc failed - filter_rspl"); + } + + /* Compute the center value index */ + for (cvi = 1, e = 0; e < s->di; e++) + cvi *= 3; + cvi = (cvi-1)/2; + + /* Allocate a temporary array for the new output values */ + if ((tarry = (float *)malloc(sizeof(float) * s->g.no * s->fdi)) == NULL) { + if (svals != a_svals) + free(svals); + error("rspl malloc failed - filter_rspl array"); + } + + /* Set the grid points value from the provided function */ + /* Give the function both the grid position and the existing output values */ + /* in the 3x3 surrounding grid */ + EC_INIT(gc); + for (tp = tarry; !EC_DONE(gc); tp += s->fdi) { + int i; + + /* Compute grid pointer and input sample values */ + for (e = 0; e < s->di; e++) { + iv[e] = s->g.l[e] + gc[e] * s->g.w[e]; /* Input sample values */ + *((int *)&iv[-e-1]) = gc[e]; /* Trick to supply grid index in iv[] */ + } + + /* Set pointers to 3x3 surrounders */ + DC_INIT(cc) + for (i = 0; !DC_DONE(cc); i++ ) { + float *sp = s->g.a; + + for (e = 0; e < s->di; e++) { /* Input tables */ + int j; + j = gc[e] + cc[e]; + if (j < 0 || j >= s->g.res[e]) { + sp = NULL; /* outside grid */ + break; + } + sp += s->g.fci[e] * j; /* Compute pointer to surrounder */ + } + + svals[i] = sp; + DC_INC(cc); + } + + for (f = 0; f < s->fdi; f++) /* Set default no change new values */ + tp[f] = svals[cvi][f]; + svals[cvi] = tp; /* Make sure output value goes into temp array */ + + /* Apply incolor -> outcolor, or oldoutcolor->outcolor function we want to represent */ + func(cbctx, svals, iv, cvi); + + EC_INC(gc); + } + + /* Reset output min/max */ + for (f = 0; f < s->fdi; f++) { + s->g.fmin[f] = 1e30; + s->g.fmax[f] = -1e30; + s->g.fminx[f] = -1; + s->g.fmaxx[f] = -1; + } + + /* Now update all the values */ + for (tp = tarry, gp = s->g.a, ep = s->g.a + s->g.no * s->g.pss; + gp < ep; gp += s->g.pss, tp += s->fdi) { + + for (f = 0; f < s->fdi; f++) /* Output chans */ + gp[f] = tp[f]; + + for (f = 0; f < s->fdi; f++) { /* Output chans */ + if (s->g.fmin[f] > gp[f]) { + s->g.fmin[f] = gp[f]; + s->g.fminx[f] = (gp - s->g.a)/s->g.pss; + } + if (s->g.fmax[f] < gp[f]) { + s->g.fmax[f] = gp[f]; + s->g.fmaxx[f] = (gp - s->g.a)/s->g.pss; + } + } + } + + /* Compute overall output scale */ + for (s->g.fscale = 0.0, f = 0; f < s->fdi; f++) { + double tt = s->g.fmax[f] - s->g.fmin[f]; + s->g.fscale += tt * tt; + } + s->g.fscale = sqrt(s->g.fscale); + + s->g.fminmax_valid = 1; /* Now is valid */ + + if (svals != a_svals) + free(svals); + free(tarry); + + /* Invalidate various things */ + free_data(s); /* Free any scattered data */ + free_rev(s); /* Free any reverse lookup data */ +} + + +/* =============================================== */ +/* Utility function */ +/* Pseudo - Hilbert count sequencer */ + +/* Initialise, returns total usable count */ +unsigned rpsh_init( +rpsh *p, /* Pointer to structure to initialise */ +int di, /* Dimensionality */ +unsigned int *res, /* Size per coordinate */ +int co[] /* Coordinates to initialise (May be NULL) */ +) { + int e; + + p->di = di; + p->tbits = 0; + for (e = 0; e < di; e++) { + p->res[e] = res[e]; + + /* Compute bits */ + for (p->bits[e] = 0; (1u << p->bits[e]) < res[e]; p->bits[e]++) + ; + p->tbits += p->bits[e]; + } + + /* Compute the total count mask */ + p->tmask = ((((unsigned)1) << p->tbits)-1); + + /* Compute usable count */ + p->count = 1; + for (e = 0; e < di; e++) + p->count *= res[e]; + + /* Reset the counter */ + p->ix = 0; + + if (co != NULL) { + for (e = 0; e < di; e++) + co[e] = 0; + } + return p->count; +} + + +/* Reset the counter */ +void rpsh_reset( +rpsh *p /* Pointer to structure */ +) { + p->ix = 0; +} + + +/* Increment pseudo-hilbert coordinates */ +/* Return non-zero if count rolls over to 0 */ +int rpsh_inc( +rpsh *p, /* Pointer to structure */ +int coa[] /* Coordinates to return */ +) { + int di = p->di; + int e; + + do { + unsigned int b, tb; + int gix; /* Gray code index */ + + p->ix = (p->ix + 1) & p->tmask; + + gix = p->ix ^ (p->ix >> 1); /* Convert to gray code index */ + + for (e = 0; e < di; e++) + coa[e] = 0; + + for (b = tb = 0; tb < p->tbits ; b++) { /* Distribute bits */ + if (b & 1) { + for (e = di-1; e >= 0; e--) { /* In reverse coord order */ + if (b < p->bits[e]) { + coa[e] |= (gix & 1) << b; /* ls bits of gix */ + gix >>= 1; + tb++; + } + } + } else { + for (e = 0; e < di; e++) { /* In normal coord order */ + if (b < p->bits[e]) { + coa[e] |= (gix & 1) << b; /* ls bits of gix */ + gix >>= 1; + tb++; + } + } + } + } + + /* Convert from Gray to binary coordinates */ + for (e = 0; e < di; e++) { + unsigned sh, tv; + + for(sh = 1, tv = coa[e];; sh <<= 1) { + unsigned ptv = tv; + tv ^= (tv >> sh); + if (ptv <= 1 || sh == 16) + break; + } + if (tv >= p->res[e]) /* Dumbo filter - increment again if outside cube range */ + break; + coa[e] = tv; + } + + } while (e < di); + + return (p->ix == 0); +} + +/* =============================================== */ + + + + + + + + + + + + + + + + diff --git a/rspl/rspl.h b/rspl/rspl.h new file mode 100644 index 0000000..5bccba7 --- /dev/null +++ b/rspl/rspl.h @@ -0,0 +1,645 @@ +#ifndef RSPL_H +#define RSPL_H + +/* + * Argyll Color Correction System + * Multi-dimensional regularized spline data structure + * + * Author: Graeme W. Gill + * Date: 2000/10/29 + * + * Copyright 1996 - 2004 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 "numsup.h" + +/** Configuration **/ + +/** General Limits **/ + +#define MXDI 10 /* Maximum input dimensionality */ +#define MXDO 10 /* Maximum output dimensionality (Is not fully tested!!!) */ +#define LOG2MXDI 4 /* log2 MXDI */ +#define DEF2MXDI 16 /* Default allocation size for 2^di (di=4) */ +#define POW2MXDI 1024 /* 2 ^ MXDI */ +#define DEF3MXDI 81 /* Default allocation size for 3^di (di=4) */ +#define POW3MXDI 59049 /* 3 ^ MXDI */ + +#if MXDI > MXDO /* Maximum of either DI or DO */ +# define MXDIDO MXDI +#else +# define MXDIDO MXDO +#endif + +/* RESTRICTED SIZE Limits, used for reverse, spline and scattered interpolation */ + +#define MXRI 4 /* Maximum input dimensionality */ +#define MXRO 10 /* Maximum output dimensionality (Is not fully tested!!!) */ +#define LOG2MXRI 2 /* log2 MXRI */ +#define POW2MXRI 16 /* 2 ^ MXRI */ +#define POW3MXRI 81 /* 3 ^ MXRI */ +#define HACOMPS ((POW3MXRI + 2 * MXRI + 1)/2) /* Maximum number of array components */ + +#if MXRI > MXRO /* Maximum of either RI or RO */ +# define MXRIRO MXRI +#else +# define MXRIRO MXRO +#endif + + +/** Definitions **/ + +/* General data point position/value structure */ +/* This is mean't to be compatible with color structure */ +/* when MXDI and MXDO == 4 */ +typedef double datai[MXDI]; +typedef double datao[MXDO]; +typedef float dati[MXDI]; +typedef float dato[MXDO]; + +/* Restricted size versions */ +typedef double ratai[MXRI]; +typedef double ratao[MXRO]; +typedef float rati[MXRI]; +typedef float rato[MXRO]; + +/* Interface coordinate value */ +typedef struct { + double p[MXDI]; /* coordinate position */ + double v[MXDO]; /* function values */ +} co; + +/* Interface coordinate value + weighting */ +typedef struct { + double p[MXDI]; /* coordinate position */ + double v[MXDO]; /* function values */ + double w; /* Weight to give this point, nominally 1.0 */ +} cow; + +/* Interface coordinate value + per out component weighting */ +typedef struct { + double p[MXDI]; /* coordinate position */ + double v[MXDO]; /* function values */ + double w[MXDO]; /* Weight to give this point, nominally 1.0 */ +} coww; + +/* Scattered data Per data point data (internal) */ +struct _rpnts { + double p[MXRI]; /* Data position [di] */ + double v[MXRO]; /* Data value [fdi] */ + double k[MXRO]; /* Weight factor (nominally 1.0, less for lower confidence data point) */ + double cv[MXRO]; /* Extra fit corrected v[fdi] */ +}; typedef struct _rpnts rpnts; + +/* Hermite interpolation magic data */ +typedef struct { + int p; /* The parameter power combination */ + int i; /* The surrounding cube vertex index */ + int j; /* The dimension combination */ + float wgt; +} magic_data; + +#include "rev.h" /* Reverse interpolation defintions */ +#include "gam.h" /* Gamut defintions */ + +/* Structure for final resolution multi-dimensional regularized spline data */ +struct _rspl { + + /* Global rspl state */ + int debug; /* 0 = no debug */ + int verbose; /* 0 = no verbose */ + double smooth; /* Smoothness factor */ + double avgdev[MXDO]; + /* Average Deviation of function values as proportion of function range. */ + int symdom; /* 0 = non-symetric smoothness with different grid resolutions, */ + /* 1 = symetric smoothness with different grid resolutions, */ + + int di; /* Input dimensionality */ + int fdi; /* Output function dimensionality */ + + /* Weak default function related information */ + double weak; /* Weak total weighting, nominal = 1.0 */ + void *dfctx; /* Opaque function context */ + void (*dfunc)(void *cbntx, double *out, double *in); + /* Function to set from */ + + /* Scattered Data point related information */ + int zf; /* Extra fitting flag - Compensate for data fit errors each round */ + int tpsm; /* Two pass smoothing flag (if set to 1). */ + int tpsm2; /* Two pass smoothing 2nd pass flag */ + struct { + int no; /* Number of data points in array */ + rpnts *a; /* Array of data points */ + datao vl,vw; /* Data value low/width - used to normalize smoothness values */ + datao va; /* Data value averages */ + } d; + int niters; /* Number of multigrid itterations needed */ + int **ires; /* Resolution for each itteration and dimension */ + void **mgtmps[MXRO]; /* Store pointers to re-usable mgtmp when incremental */ + + + /* Grid points data */ + struct { + int res[MXDI]; /* Single dimension grid resolution for each axis */ + int bres, brix; /* Biggest resolution and its index */ + double mres; /* Geometric mean res[] */ + int no; /* Total number of points in grid = res[0] * res[1] * .. res[di-1] */ + datai l,h,w; /* Grid low, high, grid cell width */ + /* This is used to map from the input domain to the grid */ + + datao fmin, fmax; /* Min & max values of grid output (function) variables */ + int fminx[MXDO], fmaxx[MXDO]; /* Grid indexes of points that set min/max output values */ + double fscale; /* Overall magnitude of output values */ + double *ipos[MXDI]; /* Optional relative grid cell position for each input dim cell, */ + /* gres[] entries per dimension. Allows for the possibility of */ + /* a non-uniform grid spacing, by adjusting the curvature evaluation */ + /* appropriately. */ + double **ccv; /* Curvature compensation array for current outpu (May be NULL) */ + int fminmax_valid; /* Min/max/scale cached values valid flag. */ + int limitv_cached; /* Flag: Ink limit values have been set in the grid array */ + +#define G_XTRA 3 /* Extra floats per grid point */ + float *alloc; /* Grid points allocated address */ + float *a; /* Grid point flags + data */ + /* Array is res[] ^ di entries float[fdi+G_XTRA], offset by G_XTRA */ + /* (But is expanded when spline interpolaton is active) */ + /* float[-1] contains the ink limit function value, L_UNINIT if not initd */ + /* float[-2] contains the edge flag values, 2 bits per in dim. */ + /* float[-3] contains the touched flag generation count. */ + /* (k value for non-linear fit would be another entry.) */ + /* Flag values are 3 bits for each dimension. Bits 1,0 form */ + /* 2 bit distance from edge: 0 for on edge of grid, */ + /* 1 for next row, 2 for 3rd row and beyond. If bit 2 is set, */ + /* then we are on the lower edge. This limits di to 10 or less, */ + /* with the two MS bits spare. */ + int pss; /* Grid point structure size = fdi+G_XTRA */ + + /* Uninitialised limit value */ +#define L_UNINIT ((float)-1e38) + + /* Macros to access flags. Arguments are a pointer to base grid point and */ + /* Flag value is distance from edge in bottom 2 bits, values 0, 1 or 2 maximum. */ + /* bit 2 is set if the distance is to the lower edge. */ +#define FLV(fp) (*((unsigned int *)((fp)-2))) + /* Init the flag values to 0 */ +#define I_FL(fp) (FLV(fp) = 0) + /* Return 3 bit flag data */ +#define G_FL(fp,di) ((FLV(fp) >> (3 * (di))) & 7) + /* Set 3 bit flag data */ +#define S_FL(fp,di,v) (FLV(fp) = (FLV(fp) & ~(7 << (3 * (di)))) | (((v) & 7) << (3 * (di)))) + + /* Macro to access touched flag. Arguments are a pointer to base grid point. */ +#define TOUCHF(fp) (*((unsigned int *)((fp)-3))) + + /* Grid array offset lookups - in floats */ + int ci[MXDI]; /* Grid coordinate increments for each dimension */ + int fci[MXDI]; /* Grid coordinate increments for each dimension in floats */ + int *hi; /* 2^di Combination offset for sequence through cube. */ + int a_hi[DEF2MXDI]; /* Default allocation for *hi */ + int *fhi; /* Combination offset for sequence through cube of */ + /* 2^di points, starting at base, in floats */ + int a_fhi[DEF2MXDI];/* Default allocation for *hi */ + + unsigned int touch; /* Cell touched flag count */ + } g; + + + /* Ink limit related information */ + int limiten; /* Flag - limiting is enabled */ + double (*limitf)(void *cntx, double *in); /* Optional input space qualifier function. */ + void *lcntx; /* Context passed to limit() */ + double limitv; /* Value not to be exceeded by limit() */ + + /* Hermite spline interpolation support */ + struct { + magic_data *magic; /* Magic matrix - non-zero elements only, Non-NULL if splining */ + int nm; /* number in magic data list */ + int spline; /* Non-zero if spline data is present in g.a */ + /* Changes from float g.a[res ^ di][fdi+G_XTRA], offset by G_XTRA, */ + /* to float g.a[res ^ di][(2^di * fdi)+G_XTRA], offset by G_XTRA, */ + } spline; + + /* Gamut support */ + gam_struct gam; /* See gam.h */ + + /* Reverse Interpolation support */ + rev_struct rev; /* See rev.h */ + + /* Methods */ + + /* Free ourselves */ + void (*del)(struct _rspl *ss); + + /* Combination lags used by various functions */ + /* NOTE that RSPL_2PASSSMTH and RSPL_EXTRAFIT2 are available, but the smoothing */ + /* factors are not setup for them, and they are not sufficiently different from the */ + /* default smoothing to be useful. */ +#define RSPL_NOFLAGS 0x0000 +#define RSPL_2PASSSMTH 0x0001 /* Use gaussian filter in 2nd pass to smooth */ +#define RSPL_EXTRAFIT2 0x0002 /* Compensate for data errors each round */ +#define RSPL_SYMDOMAIN 0x0004 /* Maintain symetric smoothness with nonsym. resolution */ +#define RSPL_SET_APXLS 0x0020 /* For set_rspl, adjust samples for aproximate least squares */ +#define RSPL_FASTREVSETUP 0x0010 /* Do a fast reverse setup at the cost of subsequent speed */ +#define RSPL_VERBOSE 0x8000 /* Turn on print progress messages */ +#define RSPL_NOVERBOSE 0x4000 /* Turn off print progress messages */ + + /* Initialise from scattered data. RESTRICTED SIZE */ + /* Return non-zero if result is non-monotonic */ + int + (*fit_rspl)( + struct _rspl *s, /* this */ + int flags, /* Combination of flags */ + co *d, /* Array holding position and function values of data points */ + int ndp, /* Number of data points */ + datai glow, /* Grid low scale - will expand to enclose data, NULL = default 0.0 */ + datai ghigh, /* Grid high scale - will expand to enclose data, NULL = default 1.0 */ + int gres[MXDI], /* Spline grid resolution, ncells = gres-1 */ + datao vlow, /* Data value low normalize, NULL = default 0.0 */ + datao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth, /* Smoothing factor, 0.0 = default 1.0 */ + double avgdev[MXDO], + /* Average Deviation of function values as proportion of function range, */ + /* typical value 0.005 (aprox. = 0.564 times the standard deviation) */ + /* NULL = default 0.005 */ + double *ipos[MXDI] /* Optional relative grid cell position for each input dim cell, */ + /* gres[] entries per dimension. Used to scale smoothness criteria */ + ); + + /* Initialise from scattered data, with per point weighting. RESTRICTED SIZE */ + /* Return non-zero if result is non-monotonic */ + int + (*fit_rspl_w)( + struct _rspl *s, /* this */ + int flags, /* Combination of flags */ + cow *d, /* Array holding position, function and weight values of data points */ + int ndp, /* Number of data points */ + datai glow, /* Grid low scale - will expand to enclose data, NULL = default 0.0 */ + datai ghigh, /* Grid high scale - will expand to enclose data, NULL = default 1.0 */ + int gres[MXDI], /* Spline grid resolution, ncells = gres-1 */ + datao vlow, /* Data value low normalize, NULL = default 0.0 */ + datao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth, /* Smoothing factor, 0.0 = default 1.0 */ + double avgdev[MXDO], + /* Average Deviation of function values as proportion of function range, */ + /* typical value 0.005 (aprox. = 0.564 times the standard deviation) */ + /* NULL = default 0.005 */ + double *ipos[MXDI] /* Optional relative grid cell position for each input dim cell, */ + /* gres[] entries per dimension. Used to scale smoothness criteria */ + ); + + /* Initialise from scattered data, with per point individual out weighting. */ + /* RESTRICTED SIZE Return non-zero if result is non-monotonic */ + int + (*fit_rspl_ww)( + struct _rspl *s, /* this */ + int flags, /* Combination of flags */ + coww *d, /* Array holding position, function and weight values of data points */ + int ndp, /* Number of data points */ + datai glow, /* Grid low scale - will expand to enclose data, NULL = default 0.0 */ + datai ghigh, /* Grid high scale - will expand to enclose data, NULL = default 1.0 */ + int gres[MXDI], /* Spline grid resolution, ncells = gres-1 */ + datao vlow, /* Data value low normalize, NULL = default 0.0 */ + datao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth, /* Smoothing factor, 0.0 = default 1.0 */ + double avgdev[MXDO], + /* Average Deviation of function values as proportion of function range, */ + /* typical value 0.005 (aprox. = 0.564 times the standard deviation) */ + /* NULL = default 0.005 */ + double *ipos[MXDI] /* Optional relative grid cell position for each input dim cell, */ + /* gres[] entries per dimension. Used to scale smoothness criteria */ + ); + + /* Initialise from scattered data, with weak default function. */ + /* RESTRICTED SIZE */ + /* Return non-zero if result is non-monotonic */ + int + (*fit_rspl_df)( + struct _rspl *s, /* this */ + int flags, /* Combination of flags */ + co *d, /* Array holding position and function values of data points */ + int ndp, /* Number of data points */ + datai glow, /* Grid low scale - will expand to enclose data, NULL = default 0.0 */ + datai ghigh, /* Grid high scale - will expand to enclose data, NULL = default 1.0 */ + int gres[MXDI], /* Spline grid resolution, ncells = gres-1 */ + datao vlow, /* Data value low normalize, NULL = default 0.0 */ + datao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth, /* Smoothing factor, 0.0 = default 1.0 */ + double avgdev[MXDO], + /* Average Deviation of function values as proportion of function range, */ + /* typical value 0.005 (aprox. = 0.564 times the standard deviation) */ + /* NULL = default 0.005 */ + double *ipos[MXDI],/* Optional relative grid cell position for each input dim cell, */ + /* gres[] entries per dimension. Used to scale smoothness criteria */ + double weak, /* Weak weighting, nominal = 1.0 */ + void *cbntx, /* Opaque function context */ + void (*func)(void *cbntx, double *out, double *in) /* Function to set from */ + ); + + /* Initialise from scattered data, with per point weighting and weak default function. */ + /* RESTRICTED SIZE */ + /* Return non-zero if result is non-monotonic */ + int + (*fit_rspl_w_df)( + struct _rspl *s, /* this */ + int flags, /* Combination of flags */ + cow *d, /* Array holding position, function and weight values of data points */ + int ndp, /* Number of data points */ + datai glow, /* Grid low scale - will expand to enclose data, NULL = default 0.0 */ + datai ghigh, /* Grid high scale - will expand to enclose data, NULL = default 1.0 */ + int gres[MXDI], /* Spline grid resolution, ncells = gres-1 */ + datao vlow, /* Data value low normalize, NULL = default 0.0 */ + datao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth, /* Smoothing factor, 0.0 = default 1.0 */ + double avgdev[MXDO], + /* Average Deviation of function values as proportion of function range, */ + /* typical value 0.005 (aprox. = 0.564 times the standard deviation) */ + /* NULL = default 0.005 */ + double *ipos[MXDI],/* Optional relative grid cell position for each input dim cell, */ + /* gres[] entries per dimension. Used to scale smoothness criteria */ + double weak, /* Weak weighting, nominal = 1.0 */ + void *cbntx, /* Opaque function context */ + void (*func)(void *cbntx, double *out, double *in) /* Function to set from */ + ); + + /* Initialize the grid from a provided function. By default the grid */ + /* values are set to exactly the value returned by func(), unless the */ + /* RSPL_SET_APXLS flag is set, in which case an attempt is made to have */ + /* the grid points represent a least squares aproximation to the underlying */ + /* surface. */ + /* Grid index values are supplied "under" in[] at *((int*)&in[-e-1]) */ + /* Return non-monotonic status */ + int + (*set_rspl)( + struct _rspl *s, /* this */ + int flags, /* Combination of flags */ + void *cbntx, /* Opaque function context */ + void (*func)(void *cbntx, double *out, double *in), /* Function to set from */ + datai glow, /* Grid low scale - will expand to enclose data, NULL = default 0.0 */ + datai ghigh, /* Grid high scale - will expand to enclose data, NULL = default 1.0 */ + int gres[MXDI], /* Spline grid resolution */ + datao vlow, /* Data value low normalize, NULL = default 0.0 */ + datao vhigh /* Data value high normalize - NULL = default 1.0 */ + ); + + /* Re-set values from a function. Grid index values are supplied */ + /* "under" in[] at *((int*)&iv[-e-1]) */ + /* Return non-monotonic status. Clears all the reverse lookup information. */ + /* It is assumed that the output range remains unchanged. */ + /* Existing output values are supplied in out[] */ + int + (*re_set_rspl)( + struct _rspl *s,/* this */ + int flags, /* Combination of flags (not used) */ + void *cbntx, /* Opaque function context */ + void (*func)(void *cbntx, double *out, double *in) /* Function to set from */ + ); + + /* Scan the rspl grid point locations and values. Grid index values are */ + /* supplied "under" in[] at *((int*)&iv[-e-1]) */ + /* Return non-monotonic status. */ + void + (*scan_rspl)( + struct _rspl *s, /* this */ + int flags, /* Combination of flags (not used) */ + void *cbntx, /* Opaque function context */ + void (*func)(void *cbntx, double *out, double *in) /* Function that gets given values */ + ); + + /* Set values by multi-grid optimisation using the provided function. */ + int (*opt_rspl)( + struct _rspl *s,/* this */ + int flags, /* Combination of flags */ + int tdi, /* Dimensionality of target data */ + int adi, /* Additional grid point data allowance */ + double **vdata, /* di^2 array of function, target and additional values to init */ + /* array corners with. */ + double (*func)(void *fdata, double *inout, double *surav, int first, double *cw), + /* Optimisation function */ + void *fdata, /* Opaque data needed by function */ + datai glow, /* Grid low scale - NULL = default 0.0 */ + datai ghigh, /* Grid high scale - NULL = default 1.0 */ + int gres[MXDI], /* Spline grid resolution */ + datao vlow, /* Data value low normalize - NULL = default 0.0 */ + datao vhigh /* Data value high normalize - NULL = default 1.0 */ + ); + + /* Filter the existing values using the surrounding 3x3 cells. */ + /* Grid index values are supplied "under" in[] */ + void + (*filter_rspl)( + struct _rspl *s, /* this */ + int flags, /* Combination of flags (not used) */ + void *cbntx, /* Opaque function context */ + void (*func)(void *cbntx, float **out, double *in, int cvi) /* Function to set from */ + ); + + /* Do forward interpolation */ + /* Return 0 if OK, 1 if input was clipped to grid */ + int (*interp)( + struct _rspl *s, /* this */ + co *p); /* Input and output values */ + + /* Do forward (partial) interpolation to allow input & output curves to be applied, */ + /* and allow input delta E to be estimated from output delta E. */ + /* Call with input value in p1[0].p[], */ + /* In order smallest to largest weight: */ + /* Return di+1 vertex values in p1[]].v[] and */ + /* 0-1 sub-cell weight values as (p1[].p[0] - p1[].p[1]). */ + /* Optionally in input channel order: */ + /* Returns di+1 partial derivatives + base value in p2[].v[], */ + /* with matching weight values for each in p2[].p[0] (last weight = 1)*/ + /* Return 0 if OK, 1 if input was clipped to grid */ + int (*part_interp)( + struct _rspl *s, /* this */ + co *p1, + co *p2); /* optional - return partial derivatives for each input channel */ + + /* Do splined forward interpolation. RESTRICTED SIZE */ + /* Return 0 if OK, 1 if input was clipped to grid */ + int (*spline_interp)( + struct _rspl *s, /* this */ + co *p); /* Input and output values */ + + + /* ------------------------------- */ + /* Create a surface gamut representation. */ + /* Return NZ on error */ + int (*comp_gamut)(struct _rspl *s, + double *cent, /* Optional center of gamut [fdi], default center of out range */ + double *scale, /* Optional Scale of output values in vector to center [fdi] */ + /* default 1.0 */ + void (*outf)(void *cntxf, double *out, double *in), /* Optional rspl val -> output value */ + void *cntxf, /* Context for function */ + void (*outb)(void *cntxb, double *out, double *in), /* Optional output value -> rspl val */ + void *cntxb /* Context for function */ + ); + + /* ------------------------------- */ + + /* Set the ink limit information for any reverse interpolation. */ + /* Calling this will clear the reverse interpolaton cache. */ + void (*rev_set_limit)( + struct _rspl *s, /* this */ + double (*limitf)(void *lcntx, double *in), /* Optional input space limit function. */ + /* Function should evaluate in[0..di-1], and return number */ + /* that is not to exceed limitv. NULL if not used. */ + void *lcntx, /* Context passed to limit() */ + double limitv /* Value that limit() is not to exceed */ + ); + + /* Get the ink limit information for any reverse interpolation. */ + void (*rev_get_limit)( + struct _rspl *s, /* this */ + double (**limitf)(void *lcntx, double *in), + /* Return pointer to function of NULL if not set */ + void **lcntx, /* return context pointer */ + double *limitv /* Return limit value */ + ); + + /* Possible reverse hint flags */ +#define RSPL_WILLCLIP 0x0001 /* Hint that clipping will be needed */ +#define RSPL_EXACTAUX 0x0002 /* Hint that auxiliary target will be matched exactly */ +#define RSPL_MAXAUX 0x0004 /* If not possible to match exactly, return the */ + /* closest value larger than the target, rather than */ + /* absolute closest. */ +#define RSPL_AUXLOCUS 0x0008 /* Auxiliary target is proportion of locus, not */ + /* absolute. Implies EXACTAUX hint. */ +#define RSPL_NEARCLIP 0x0010 /* If clipping occurs, return the nearest solution, */ + /* rather than the one in the clip direction. */ + + /* Return value masks */ +#define RSPL_DIDCLIP 0x8000 /* If this bit is set, at least one soln. and clipping occured */ +#define RSPL_NOSOLNS 0x7fff /* And return value with this mask to get number of solutions */ + + /* Do reverse interpolation given target output values and (optional) auxiliary target */ + /* input values. Return number of results and clip flag. If return value == mxsoln, then */ + /* there might be more results. RESTRICTED SIZE */ + int (*rev_interp)( + struct _rspl *s, /* this */ + int flags, /* Hint flag */ + int mxsoln, /* Maximum number of solutions allowed for */ + int *auxm, /* Array of di mask flags, !=0 for valid auxliaries (NULL if no aux) */ + double cdir[MXRO], /* Clip vector direction and length - NULL if not used */ + co *p); /* Given target output space value in cpp[0].v[] + */ + /* target input space auxiliaries in cpp[0].p[], return */ + /* input space solutions in cpp[0..retval-1].p[], and */ + /* (possibly) clipped target values in cpp[0].v[] */ + + /* Do reverse search for the locus of the auxiliary input values given a target output. */ + /* Return 1 on finding a valid solution, and 0 if no solutions are found. RESTRICTED SIZE */ + int (*rev_locus)( + struct _rspl *s,/* this */ + int *auxm, /* Array of di mask flags, !=0 for valid auxliaries (NULL if no aux) */ + co *cpp, /* Input target value in cpp[0].v[] */ + double min[MXRI],/* Return minimum auxiliary values */ + double max[MXRI]); /* Return maximum auxiliary values */ + + /* Do reverse search for the auxiliary min/max ranges of the solution locus for the */ + /* given target output values. RESTRICTED SIZE */ + /* Return number of locus segments found, up to mxsoln. 0 will be returned if no solutions */ + /* are found. */ + int (*rev_locus_segs)( + struct _rspl *s,/* this */ + int *auxm, /* Array of di mask flags, !=0 for valid auxliaries (NULL if no aux) */ + co *cpp, /* Input value in cpp[0].v[] */ + int mxsoln, /* Maximum number of solutions allowed for */ + double min[][MXRI], /* Array of min[MXRI] to hold return segment minimum values. */ + double max[][MXRI] /* Array of max[MXRI] to hold return segment maximum values. */ + ); + + + /* ------------------------------- */ + + /* Return the min and max of the input values valid in the grid */ + void (*get_in_range)( + struct _rspl *s, /* this */ + double *min, double *max); /* Return min/max values */ + + /* return the min and max of the output values contained in the grid */ + void (*get_out_range)( + struct _rspl *s, /* this */ + double *min, double *max); /* Return min/max values */ + + /* return the grid index of the grid values at the min & max output values */ + void (*get_out_range_points)(struct _rspl *s, int *minp, int *maxp); + + /* return the overall scale of the output values contained in the grid */ + double (*get_out_scale)(struct _rspl *s); + + /* return the next touched flag count value. */ + /* Whenever this rolls over, all the flags in the grid array will be reset */ + unsigned int (*get_next_touch)( + struct _rspl *s); /* this */ + +# define wvals ad##jw + + /* Return non-zero if this rspl can be */ + /* used with Restricted Size functions. */ + int (*within_restrictedsize)( + struct _rspl *s); /* this */ + +}; typedef struct _rspl rspl; + +/* Create a new, empty rspl object */ +rspl *new_rspl(int flags, int di, int fdi); /* Input and output dimentiality */ + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Utility functions */ + +/* The multi-dimensional access sequence is a distributed */ +/* Gray code sequence, with direction reversal */ +/* on every alternate power of 2 scale. */ +/* It is intended to aid cache access locality in multi-dimensional */ +/* regular sampling. It approximates the Hilbert curve sequence. */ + +/* Structure to hold sequencer info */ +struct _rpsh { + int di; /* Dimensionality */ + unsigned res[MXDI]; /* Resolution per coordinate */ + unsigned bits[MXDI]; /* Bits per coordinate */ + unsigned tbits; /* Total bits */ + unsigned ix; /* Current binary index */ + unsigned tmask; /* Total 2^n count mask */ + unsigned count; /* Usable count */ +}; typedef struct _rpsh rpsh; + +/* Initialise, returns total usable count */ +unsigned +rpsh_init(rpsh *p, int di, unsigned res[], int co[]); + +/* Reset the counter */ +void rpsh_reset(rpsh *p); + +/* Increment pseudo-hilbert coordinates */ +/* Return non-zero if count rolls over to 0 */ +int rpsh_inc(rpsh *p, int co[]); + +#endif /* RSPL_H */ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/rspl/rspl1.c b/rspl/rspl1.c new file mode 100644 index 0000000..dc3588b --- /dev/null +++ b/rspl/rspl1.c @@ -0,0 +1,391 @@ + + /* Single dimension regularized spline data structure */ + +/* + * Argyll Color Correction System + * Author: Graeme W. Gill + * Date: 2000/10/29 + * + * Copyright 1996 - 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. + * + * This is a simple 1D version of rspl, useful for standalone purposes. + * + */ + +#include +#include +#include +#include +#include "numsup.h" +#include "rspl1.h" + +#undef DEBUG + +#ifdef DEBUG +# define DBGA g_log, 0 /* First argument to DBGF() */ +# define DBGF(xx) a1logd xx +#else +# define DBGF(xx) +#endif + + +/* Do an interpolation based on the grid */ +/* Use a linear interp between grid points. */ +/* If the input is outside the grid range, it will */ +/* be clamped to the nearest grid point. */ +static int interp( +rspl *t, +co *p +) { + int rv = 0; + double x, y, xx, w1; + int i; + + x = p->p[0]; + + if (x < t->gl) { + x = t->gl; + rv = 1; + } else if (x > t->gh) { + x = t->gh; + rv = 1; + } + + xx = (x - t->gl)/t->gw; /* Grid location of point */ + i = (int)floor(xx); /* Lower grid of point */ + if (i >= (t->nig-2)) + i = t->nig-2; + + w1 = xx - (double)i; /* Weight to upper grid point */ + + y = ((1.0 - w1) * t->x[i]) + (w1 * t->x[i+1]); + + p->v[0] = y * t->vw + t->vl; /* Rescale the data */ + + return rv; +} + +/* Destructor */ +static void del_rspl(rspl *t) { + if (t != NULL) { + if (t->x != NULL) + free_dvector(t->x, 0, t->nig); + free(t); + } +} + +/* Initialise the regular spline from scattered data */ +/* Return nz on error */ +static int fit_rspl_imp( + struct _rspl *t,/* this */ + int flags, /* (Not used) */ + void *d, /* Array holding position and function values of data points */ + int dtp, /* Flag indicating data type, 0 = (co *), 1 = (cow *), 2 = (coww *) */ + int ndp, /* Number of data points */ + datai glow, /* Grid low scale - will expand to enclose data, NULL = default 0.0 */ + datai ghigh, /* Grid high scale - will expand to enclose data, NULL = default 1.0 */ + int *gres, /* Spline grid resolution, ncells = gres-1 */ + datao vlow, /* Data value low normalize, NULL = default 0.0 */ + datao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth, /* Smoothing factor, 0.0 = default 1.0 */ + double *avgdev, /* (Not used) */ + double **ipos /* (not used) */ +) { + int n; + double cw; + + DBGF((DBGA, "rspl1:fit_rspl_imp() with %d points called, dtp = %d\n",ndp,dtp)); + + /* Allocate space for interpolation grid */ + t->nig = *gres; + + if ((t->x = dvector(0, t->nig)) == NULL) { + DBGF((DBGA, "rspl1:Malloc of vector x failed\n")); + return 1; + } + + /* Normalize curve weight to grid resolution. */ + cw = 0.0000005 * smooth * pow((t->nig-1),4.0) / (t->nig - 2); + DBGF((DBGA, "rspl1:cw = %e\n",cw)); + + /* cw is multiplied by the sum of grid curvature errors squared to keep */ + /* the same ratio with the sum of data position errors squared */ + + /* Determine the data range */ + t->xl = 1e300; + t->xh = -1e300; + t->dl = 1e300; + t->dh = -1e300; + if (dtp == 0) { + co *dd = (co *)d; + + for (n = 0; n < ndp; n++) { + if (dd[n].p[0] < t->xl) + t->xl = dd[n].p[0]; + if (dd[n].p[0] > t->xh) + t->xh = dd[n].p[0]; + if (dd[n].v[0] < t->dl) + t->dl = dd[n].v[0]; + if (dd[n].v[0] > t->dh) + t->dh = dd[n].v[0]; + + DBGF((DBGA, "rspl1:Point %d = %f, %f\n",n,dd[n].p[0],dd[n].v[0])); + } + } else if (dtp == 1) { + cow *dd = (cow *)d; + + for (n = 0; n < ndp; n++) { + if (dd[n].p[0] < t->xl) + t->xl = dd[n].p[0]; + if (dd[n].p[0] > t->xh) + t->xh = dd[n].p[0]; + if (dd[n].v[0] < t->dl) + t->dl = dd[n].v[0]; + if (dd[n].v[0] > t->dh) + t->dh = dd[n].v[0]; + DBGF((DBGA, "rspl1:Point %d = %f, %f (%f)\n",n,dd[n].p[0],dd[n].v[0],dd[n].w)); + } + } else { + DBGF((DBGA, "rspl1:Internal error, unknown dtp value %d\n",dtp)); + return 1; + } + + t->gl = glow != NULL ? *glow : 0.0; + t->gh = ghigh != NULL ? *ghigh : 1.0; + + /* adjust input ranges to encompass data */ + if (t->xl < t->gl) + t->gl = t->xl; + if (t->xh > t->gh) + t->gh = t->xh; + + /* Set the input and output scaling */ + t->gw = (t->gh - t->gl)/(double)(t->nig-1); + + t->vl = vlow != NULL ? *vlow : 0.0; + t->vw = ((vhigh != NULL ? *vhigh : 1.0) - t->vl); + + DBGF((DBGA, "rspl1:gl %f, gh %f, gw %f, vl %f, vw %f\n",t->gl,t->gh,t->gw,t->vl,t->vw)); + + /* create smoothed grid data */ + { + int n,i,k; + double **A; /* A matrix of interpoint weights */ + double *b; /* b vector for RHS of simultabeous equation */ + + /* We just store the diagonal of the A matrix */ + if ((A = dmatrix(0, t->nig, 0, 2)) == NULL) { + DBGF((DBGA, "rspl1:Malloc of matrix A failed\n")); + return 1; + } + + if ((b = dvector(0,t->nig)) == NULL) { + free_dvector(b,0,t->nig); + DBGF((DBGA, "rspl1:Malloc of vector b failed\n")); + return 1; + } + + /* Initialize the A and b matricies */ + for (i = 0; i < t->nig; i++) { + for (k = 0; k < 3; k++) + A[i][k] = 0.0; + t->x[i] = b[i] = 0.0; + } + + /* Accumulate data dependent factors */ + for (n = 0; n < ndp; n++) { + double bf, cbf; + double xv, yv, wv; + + if (dtp == 0) { + co *dd = (co *)d; + + xv = dd[n].p[0]; + yv = dd[n].v[0]; + wv = 1.0; + } else if (dtp == 1) { + cow *dd = (cow *)d; + + xv = dd[n].p[0]; + yv = dd[n].v[0]; + wv = dd[n].w; + } else { + DBGF((DBGA, "rspl1:Internal error, unknown dtp value %d\n",dtp)); + return 1; + } + yv = (yv - t->vl)/t->vw; /* Normalize the value */ + + /* Figure out which grid cell data is in */ + i = (int)((xv - t->gl)/t->gw); /* Index of next lowest data point */ + + bf = ((((double)(i+1) * t->gw) + t->gl) - xv)/t->gw; /* weight to lower grid point */ + cbf = 1.0 - bf; /* weight to upper grid point */ + + b[i] -= 2.0 * bf * -yv * wv; /* dui component due to dn */ + A[i][0] += 2.0 * bf * bf * wv; /* dui component due to ui */ + A[i][1] += 2.0 * bf * cbf * wv; /* dui component due to ui+1 */ + + if ((i+1) < t->nig) { + b[i+1] -= 2.0 * cbf * -yv * wv; /* dui component due to dn */ + A[i+1][0] += 2.0 * cbf * cbf * wv; /* dui component due to ui */ + } + } + + /* Accumulate curvature dependent factors */ + for (i = 0; i < t->nig; i++) { + + if ((i-2) >= 0) { /* Curvature of cell below */ + A[i][0] += 2.0 * cw; + } + + if ((i-1) >= 0 && (i+1) < t->nig) { /* Curvature of t cell */ + A[i][0] += 8.0 * cw; + A[i][1] += -4.0 * cw; + } + if ((i+2) < t->nig) { /* Curvature of cell above */ + A[i][0] += 2.0 * cw; + A[i][1] += -4.0 * cw; + A[i][2] += 2.0 * cw; + } + } + +#ifdef DEBUG + DBGF((DBGA, "A matrix:\n")); + for (i = 0; i < t->nig; i++) { + for (k = 0; k < 3; k++) + DBGF((DBGA, "A[%d][%d] = %f\n",i,k,A[i][k])); + } + DBGF((DBGA, "b vector:\n")); + for (i = 0; i < t->nig; i++) + DBGF((DBGA, "b[%d] = %f\n",i,b[i])); +#endif /* DEBUG */ + + /* Apply Cholesky decomposition to A[][] to create L[][] */ + for (i = 0; i < t->nig; i++) { + double sm; + for (n = 0; n < 3; n++) { + sm = A[i][n]; + for (k = 1; (n+k) < 3 && (i-k) >=0; k++) { + sm -= A[i-k][n+k] * A[i-k][k]; + } + if (n == 0) { + if (sm <= 0.0) { + free_dvector(b,0,t->nig); + free_dmatrix(A,0,t->nig,0,2); + DBGF((DBGA, "rspl1:Sum is -ve - loss of accuracy ?\n")); + return 1; + } + A[i][0] = sqrt(sm); + } else { + A[i][n] = sm/A[i][0]; + } + } + } + + /* Solve L . y = b, storing y in x */ + for (i = 0; i < t->nig; i++) { + double sm; + sm = b[i]; + for (k = 1; k < 3 && (i-k) >= 0; k++) { + sm -= A[i-k][k] * t->x[i-k]; + } + t->x[i] = sm/A[i][0]; + } + + /* Solve LT . x = y */ + for (i = t->nig-1; i >= 0; i--) { + double sm; + sm = t->x[i]; + for (k = 1; k < 3 && (i+k) < t->nig; k++) { + sm -= A[i][k] * t->x[i+k]; + } + t->x[i] = sm/A[i][0]; + } +#ifdef DEBUG + DBGF((DBGA, "Solution vector:\n")); + for (i = 0; i < t->nig; i++) { + DBGF((DBGA, "x[%d] = %f\n",i,t->x[i])); + } +#endif /* DEBUG */ + + free_dvector(b,0,t->nig); + free_dmatrix(A,0,t->nig,0,2); + } + return 0; +} + +/* Initialise from scattered data. */ +/* Return nz on error */ +static int fit_rspl( + struct _rspl *t,/* this */ + int flags, /* (Not used) */ + co *d, /* Array holding position and function values of data points */ + int ndp, /* Number of data points */ + datai glow, /* Grid low scale - will expand to enclose data, NULL = default 0.0 */ + datai ghigh, /* Grid high scale - will expand to enclose data, NULL = default 1.0 */ + int *gres, /* Spline grid resolution, ncells = gres-1 */ + datao vlow, /* Data value low normalize, NULL = default 0.0 */ + datao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth, /* Smoothing factor, 0.0 = default 1.0 */ + double *avgdev, /* (Not used) */ + double **ipos /* (not used) */ +) { + /* Call implementation with (co *) data */ + return fit_rspl_imp(t, flags, (void *)d, 0, ndp, glow, ghigh, gres, vlow, vhigh, + smooth, avgdev, ipos); +} + +/* Initialise the regular spline from scattered data with weights */ +/* Return nz on error */ +static int +fit_rspl_w( + rspl *t, /* this */ + int flags, /* Combination of flags */ + cow *d, /* Array holding position, function and weight values of data points */ + int dno, /* Number of data points */ + ratai glow, /* Grid low scale - will be expanded to enclose data, NULL = default 0.0 */ + ratai ghigh, /* Grid high scale - will be expanded to enclose data, NULL = default 1.0 */ + int *gres, /* Spline grid resolution */ + ratao vlow, /* Data value low normalize, NULL = default 0.0 */ + ratao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth, /* Smoothing factor, 0.0 = default 1.0 */ + double *avgdev, /* (Not used) */ + double **ipos /* (not used) */ +) { + /* Call implementation with (cow *) data */ + return fit_rspl_imp(t, flags, (void *)d, 1, dno, glow, ghigh, gres, vlow, vhigh, + smooth, avgdev, ipos); +} + +/* Construct an empty rspl1 */ +/* Return NULL if something goes wrong. */ +rspl *new_rspl(int flags, int di, int fdi) { + rspl *t; /* this */ + + if (flags != RSPL_NOFLAGS || di != 1 || fdi != 1) { + DBGF((DBGA, "rspl1:Can't handle general rspl: flags %d, di %d, do %d\n",flags,di,fdi)); + return NULL; + } + + if ((t = (rspl *)calloc(1, sizeof(rspl))) == NULL) { + DBGF((DBGA, "rspl1:Malloc of structure failed\n")); + return NULL; + } + + /* Initialise the classes methods */ + t->interp = interp; + t->fit_rspl = fit_rspl; + t->fit_rspl_w = fit_rspl_w; + t->del = del_rspl; + + return t; +} + + + + + diff --git a/rspl/rspl1.h b/rspl/rspl1.h new file mode 100644 index 0000000..d5ea0b9 --- /dev/null +++ b/rspl/rspl1.h @@ -0,0 +1,115 @@ + +#ifndef _RSPL1_H_ + + /* Single dimension regularized spline data structure */ + +/* + * Argyll Color Correction System + * Author: Graeme W. Gill + * Date: 2000/10/29 + * + * Copyright 1996 - 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. + * + * This is a simple 1D version of rspl, useful for standalone purposes. + * + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* General data point position/value structure */ +typedef double datai[1]; +typedef double datao[1]; +typedef double ratai[1]; +typedef double ratao[1]; + +/* Interface coordinate value */ +typedef struct { + double p[1]; /* coordinate position */ + double v[1]; /* function values */ +} co; + +/* Interface coordinate value */ +typedef struct { + double p[1]; /* coordinate position */ + double v[1]; /* function values */ + double w; /* Weight to give this point, nominally 1.0 */ +} cow; + +#define RSPL_NOFLAGS 0 + +struct _rspl { + + /* Private: */ + int nig; /* number in interpolation grid */ + double gl,gh,gw;/* Interpolation grid scale low, high, grid cell width */ + double vl,vw; /* low & range */ + double xl,xh; /* Actual X data exremes low, high */ + double dl,dh; /* Actual Y Data scale low, high */ + double *x; /* Array of nig grid point scaled y values */ + + /* Public: */ + + /* destructor */ + void (*del)(struct _rspl *t); + + /* Initialise from scattered data. */ + /* Returns nz on error */ + int + (*fit_rspl)( + struct _rspl *s, /* this */ + int flags, /* (Not used) */ + co *d, /* Array holding position and function values of data points */ + int ndp, /* Number of data points */ + datai glow, /* Grid low scale - will expand to enclose data, NULL = default 0.0 */ + datai ghigh, /* Grid high scale - will expand to enclose data, NULL = default 1.0 */ + int *gres, /* Spline grid resolution, ncells = gres-1 */ + datao vlow, /* Data value low normalize, NULL = default 0.0 */ + datao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth, /* Smoothing factor, 0.0 = default 1.0 */ + double *avgdev, /* (Not used) */ + double **ipos /* (not used) */ + ); + + /* Initialise from scattered data with weighting. */ + /* Returns nz on error */ + int + (*fit_rspl_w)( + struct _rspl *s, /* this */ + int flags, /* (Not used) */ + cow *d, /* Array holding position and function values of data points */ + int ndp, /* Number of data points */ + datai glow, /* Grid low scale - will expand to enclose data, NULL = default 0.0 */ + datai ghigh, /* Grid high scale - will expand to enclose data, NULL = default 1.0 */ + int *gres, /* Spline grid resolution, ncells = gres-1 */ + datao vlow, /* Data value low normalize, NULL = default 0.0 */ + datao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth, /* Smoothing factor, 0.0 = default 1.0 */ + double *avgdev, /* (Not used) */ + double **ipos /* (not used) */ + ); + + /* Do forward interpolation */ + /* Return 0 if OK, 1 if input was clipped to grid */ + int (*interp)( + struct _rspl *s, /* this */ + co *p); /* Input and output values */ + +}; typedef struct _rspl rspl; + +/* Create a new, empty rspl object */ +rspl *new_rspl(int flags, int di, int fdi); + +#ifdef __cplusplus +} +#endif + +#define _RSPL1_H_ +#endif /* _RSPL1_H_ */ + + diff --git a/rspl/rspl_imp.h b/rspl/rspl_imp.h new file mode 100644 index 0000000..0cd8805 --- /dev/null +++ b/rspl/rspl_imp.h @@ -0,0 +1,27 @@ + +/* + * Argyll Color Correction System + * Multi-dimensional regularized spline + * Implementation header. + * + * Author: Graeme W. Gill + * Date: 28/9/96 + * + * Copyright 1996, 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 "rspl.h" + +#ifndef RSPL_IMP_H + +/* These three factors controll how the monotonic stuff behaves. */ +#define MCINC 0.05 /* The tollerance for detecting non-monoticity */ +#define BALLEV 0.8 /* Balance level of adjusted points */ +#define RADF 0.01 /* Radius factor of nme influence */ + +#define RSPL_IMP_H +#endif /* RSPL_IMP_H */ diff --git a/rspl/scat.c b/rspl/scat.c new file mode 100644 index 0000000..b4ed978 --- /dev/null +++ b/rspl/scat.c @@ -0,0 +1,2861 @@ + +/* + * Argyll Color Correction System + * Multi-dimensional regularized splines data fitter + * + * Author: Graeme W. Gill + * Date: 2004/8/14 + * + * Copyright 1996 - 2009 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 file contains the scattered data solution specific code. + * + * The regular spline implementation was inspired by the following technical reports: + * + * D.J. Bone, "Adaptive Multi-Dimensional Interpolation Using Regularized Linear Splines," + * Proc. SPIE Vol. 1902, p.243-253, Nonlinear Image Processing IV, Edward R. Dougherty; + * Jaakko T. Astola; Harold G. Longbotham;(Eds)(1993). + * + * D.J. Bone, "Adaptive Colour Printer Modeling using regularized linear splines," + * Proc. SPIE Vol. 1909, p. 104-115, Device-Independent Color Imaging and Imaging + * Systems Integration, Ricardo J. Motta; Hapet A. Berberian; Eds.(1993) + * + * Don Bone and Duncan Stevenson, "Modelling of Colour Hard Copy Devices Using Regularised + * Linear Splines," Proceedings of the APRS workshop on Colour Imaging and Applications, + * Canberra (1994) + * + * see + * + * Also of interest was: + * + * "Discrete Smooth Interpolation", Jean-Laurent Mallet, ACM Transactions on Graphics, + * Volume 8, Number 2, April 1989, Pages 121-144. + * + */ + +/* TTBD: + * + * Try simple approach to reduce extrapolation accumulation (edge propogation) effects. + * Do this by saving bounding box of scattered points, and then increase smoothness coupling + * in direction of axis that is outside this box (or the reverse, reduce smoothness + * coupling in direction of any axis that is not outside this box). + * [Example is "t3d -t 6 -P 0:0:0:1:1:1" where lins should not bend up at top end.] + * + * Speedup that skips recomputing all of A to add new points seems OK. (nothing uses + * incremental currently anyway.) + * + * Is there any way of speeding up incremental recalculation ???? + * + * Add optional simplex point interpolation to + * solve setup. (No large advantage in this ??) + * + * Find a more effective way to mitigate the smoothness "clumping" + * effect where corners in particular over smooth ? + * + * Get rid of error() calls - return status instead + */ + +/* + Scattered data fit related smoothness control. + + We adjust the curve/data point weighting to account for the + grid resolution (to make it resolution independent), as well + as allow for the dimensionality (each dimension contributes + a curvature error). + + The default assumption is that the grid resolution is set + to matche the input data range for that dimension, eg. if + a sub range of input space is all that is needed, then a + smaller grid resolution can/should be used if smoothness + is expected to remain symetric in relation to the input + domain. + + eg. Input range 0.0 - 1.0 and 0.0 - 0.5 + matching res 50 and 25 + + The alternative is to set the RSPL_SYMDOMAIN flag, + in which case the grid resolution is not taken to + be a measure of the dimension scale, and is assumed + to be just a lower resolution sampling of the domain. + + eg. Input range 0.0 - 1.0 and 0.0 - 1.0 + with res. 50 and 25 + + still has symetrical smoothness in relation + to the input domain. + + + NOTE :- that both input and output values are normalised + by the ranges given during rspl construction. The ranges + set the significance between the input and output values. + + eg. Input ranges 0.0 - 1.0 and 0.0 - 100.0 + (with equal grid resolution) + will have symetry when measured against the the + same % change in the input domain, but will + appear non-symetric if measured against the + same numerical change. + + */ + + + + +#include +#include +#include +#include +#include +#if defined(__IBMC__) && defined(_M_IX86) +#include +#endif + +#include "rspl_imp.h" +#include "numlib.h" +#include "counters.h" /* Counter macros */ + +#undef DEBUG /* Print contents of solution setup etc. */ +#undef DEBUG_PROGRESS /* Print progress of acheiving tollerance target */ + +#define DEFAVGDEV 0.5 /* Default average deviation % */ + +/* algorithm parameters [Release defaults] */ +#undef POINTWEIGHT /* [Undef] Increas smoothness weighting proportional to number of points */ +#define INCURVEADJ /* [Defined] Adjust smoothness criteria for input curve grid spacing */ +#define EXTRA_SURFACE_SMOOTHING /* [Defined] Stiffen surface points to comp. for single ended. */ + /* The following are available, but the smoothing table is */ + /* not setup for them, and they are not sufficiently different */ + /* from the default smoothing to be useful. */ +#define ENABLE_2PASSSMTH /* [Define] Enable 2 pass smooth using Gaussian filter */ +#define ENABLE_EXTRAFIT /* [Undef] Enable the extra fit option. Good to combat high smoothness. */ + +#define TWOPASSORDER 2.0 /* Filter order. 2 = Gaussian */ + +/* Tuning parameters */ +#ifdef NEVER + +/* Experimental set: */ + +#pragma message("!!!!!!!!! Experimental config set !!!!!!!!!") + +#define TOL 1e-12 /* Tollerance of result - usually 1e-5 is best. */ +#define TOL_IMP 1.0 /* Minimum error improvement to continue - reduces accuracy (1.0 == off) */ +#undef GRADUATED_TOL /* Speedup attemp - use reduced tollerance for prior grids. */ +#define GRATIO 2.0 /* Multi-grid resolution ratio */ +#undef OVERRLX /* Use over relaxation factor when progress slows (worse accuracy ?) */ +#define JITTERS 0 /* Number of 1D conjugate solve itters */ +#define CONJ_TOL 1.0 /* Extra tolereance on 1D conjugate solution times TOL. */ +#define MAXNI 16 /* Maximum itteration without checking progress */ +//#define SMOOTH 0.000100 /* Set nominal smoothing (1.0) */ +#define WEAKW 0.1 /* Weak default function nominal effect (1.0) */ +#define ZFCOUNT 1 /* Extra fit repeats */ + +#else + +/* Release set: */ + +#define TOL 1e-6 /* [1e-6] Tollerance of result - usually 1e-5 is best. */ +#define TOL_IMP 0.998 /* [0.998] Minimum error improvement to continue - reduces accuracy (1.0 == off) */ +#undef GRADUATED_TOL /* [Undef] Speedup attemp - use reduced tollerance for prior grids. */ +#define GRATIO 2.0 /* [2.0] Multi-grid resolution ratio */ +#undef OVERRLX /* [Undef] Use over relaxation when progress slows (worse accuracy ?) */ +#define JITTERS 0 /* [0] Number of 1D conjugate solve itters */ +#define CONJ_TOL 1.0 /* [1.0] Extra tolereance on 1D conjugate solution times TOL. */ +#define MAXNI 16 /* [16] Maximum itteration without checking progress */ +//#define SMOOTH 0.000100 /* Set nominal smoothing (1.0) */ +#define WEAKW 0.1 /* [0.1] Weak default function nominal effect (1.0) */ +#define ZFCOUNT 1 /* [1] Extra fit repeats */ + +#endif + +#undef NEVER +#define ALWAYS + +/* Implemented in rspl.c: */ +extern void alloc_grid(rspl *s); + +extern int is_mono(rspl *s); + +/* Convention is to use: + i to index grid points u.a + n to index data points d.a + e to index position dimension di + f to index output function dimension fdi + j misc and cube corners + k misc + */ + +/* ================================================= */ +/* Structure to hold temporary data for multi-grid calculations */ +/* One is created for each resolution. Only used in this file. */ +struct _mgtmp { + rspl *s; /* Associated rspl */ + int f; /* Output dimension being calculated */ + + /* Weak default function stuff */ + double wdfw; /* Weight per grid point */ + + /* Scattered data fit stuff */ + struct { + double cw[MXDI]; /* Curvature weight factor */ + } sf; + + /* Grid points data */ + struct { + int res[MXDI]; /* Single dimension grid resolution */ + int bres, brix; /* Biggest resolution and its index */ + double mres; /* Geometric mean res[] */ + int no; /* Total number of points in grid = res ^ di */ + ratai l,h,w; /* Grid low, high, grid cell width */ + + double *ipos[MXDI]; /* Optional relative grid cell position for each input dim cell */ + + /* Grid array offset lookups */ + int ci[MXRI]; /* Grid coordinate increments for each dimension */ + int hi[POW2MXRI]; /* Combination offset for sequence through cube. */ + } g; + + /* Data point grid dependent information */ + struct mgdat { + int b; /* Index for associated base grid point, in grid points */ + double w[POW2MXRI]; /* Weight for surrounding gridpoints [2^di] */ + } *d; + + /* Equation Solution related (Grid point solutions) */ + struct { + double **ccv; /* [gno][di] Curvature Compensation Values */ + double **A; /* A matrix of interpoint weights A[g.no][q.acols] */ + int acols; /* A matrix columns needed */ + /* Packed indexes run from 0..acols-1 */ + /* Sparse index allows for +/-2 offset in any one dimension */ + /* and +/-1 offset in all dimensions, but only the +ve offset */ + /* half of the sparse matrix is stored, due to equations */ + /* being symetrical. */ + int xcol[HACOMPS+8];/* A array column translation from packed to sparse index */ + int *ixcol; /* A array column translation from sparse to packed index */ + double *b; /* b vector for RHS of simultabeous equation b[g.no] */ + double normb; /* normal of b vector */ + double *x; /* x solution to A . x = b */ + } q; + +}; typedef struct _mgtmp mgtmp; + + +/* ================================================= */ +/* Temporary arrays used by cj_line(). We try and avoid */ +/* allocating and de-allocating these, and merely expand */ +/* them as needed */ +typedef struct { + double *z, *xx, *q, *r; + double *n; + int l_nid; +} cj_arrays; +static void init_cj_arrays(cj_arrays *ta); +static void free_cj_arrays(cj_arrays *ta); + +static int add_rspl_imp(rspl *s, int flags, void *d, int dtp, int dno); +static mgtmp *new_mgtmp(rspl *s, int gres[MXDI], int f); +static void free_mgtmp(mgtmp *m); +static void setup_solve(mgtmp *m, int final); +static void solve_gres(mgtmp *m, cj_arrays *ta, double tol, int final); +static void init_soln(mgtmp *m1, mgtmp *m2); +static void comp_ccv(mgtmp *m); +static void filter_ccv(rspl *s, double stdev); +static void init_ccv(mgtmp *m); +static void comp_extrafit_corr(mgtmp *m); + +/* Initialise the regular spline from scattered data */ +/* Return non-zero if non-monotonic */ +static int +fit_rspl_imp( + rspl *s, /* this */ + int flags, /* Combination of flags */ + void *d, /* Array holding position and function values of data points */ + int dtp, /* Flag indicating data type, 0 = (co *), 1 = (cow *), 2 = (coww *) */ + int dno, /* Number of data points */ + ratai glow, /* Grid low scale - will be expanded to enclose data, NULL = default 0.0 */ + ratai ghigh, /* Grid high scale - will be expanded to enclose data, NULL = default 1.0 */ + int gres[MXDI], /* Spline grid resolution */ + ratao vlow, /* Data value low normalize, NULL = default 0.0 */ + ratao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth, /* Smoothing factor, 0.0 = default 1.0 */ + /* (if -ve, overides optimised smoothing, and sets raw smoothing */ + /* typically between 1e-7 .. 1e-1) */ + double avgdev[MXDO], + /* Average Deviation of function values as proportion of function range, */ + /* typical value 0.005 (aprox. = 0.564 times the standard deviation) */ + /* NULL = default 0.005 */ + double *ipos[MXDI], /* Optional relative grid cell position for each input dim cell, */ + /* gres[] entries per dimension. Used to scale smoothness criteria */ + double weak, /* Weak weighting, nominal = 1.0 */ + void *dfctx, /* Opaque weak default function context */ + void (*dfunc)(void *cbntx, double *out, double *in) /* Function to set from, NULL if none. */ +) { + int di = s->di, fdi = s->fdi; + int i, e, f; + +#ifdef NEVER +printf("~1 rspl: gres = %d %d %d %d, smooth = %f, avgdev = %f %f %f\n", +gres[0], gres[1], gres[2], gres[3], smooth, avgdev[0], avgdev[1], avgdev[2]); +printf("~1 rspl: glow = %f %f %f %f ghigh = %f %f %f %f\n", +glow[0], glow[1], glow[2], glow[3], ghigh[0], ghigh[1], ghigh[2], ghigh[3]); +printf("~1 rspl: vlow = %f %f %f vhigh = %f %f %f\n", +vlow[0], vlow[1], vlow[2], vhigh[0], vhigh[1], vhigh[2]); +printf("~1 rspl: flags = 0x%x\n",flags); +#endif + +#if defined(__IBMC__) && defined(_M_IX86) + _control87(EM_UNDERFLOW, EM_UNDERFLOW); +#endif + + /* This is a restricted size function */ + if (di > MXRI) + error("rspl: fit can't handle di = %d",di); + if (fdi > MXRO) + error("rspl: fit can't handle fdi = %d",fdi); + + /* set debug level */ + s->debug = (flags >> 24); + + /* Init other flags */ + if (flags & RSPL_VERBOSE) /* Turn on progress messages to stdout */ + s->verbose = 1; + if (flags & RSPL_NOVERBOSE) /* Turn off progress messages to stdout */ + s->verbose = 0; + +#ifdef ENABLE_2PASSSMTH + s->tpsm = (flags & RSPL_2PASSSMTH) ? 1 : 0; /* Enable 2 pass smoothing */ +#endif +#ifdef ENABLE_EXTRAFIT + s->zf = (flags & RSPL_EXTRAFIT2) ? 2 : 0; /* Enable extra fitting effort */ +#endif + s->symdom = (flags & RSPL_SYMDOMAIN) ? 1 : 0; /* Turn on symetric smoothness with gres */ + + /* Save smoothing factor and Average Deviation */ + s->smooth = smooth; + if (avgdev != NULL) { + for (f = 0; f < s->fdi; f++) + s->avgdev[f] = avgdev[f]; + } else { + for (f = 0; f < s->fdi; f++) + s->avgdev[f] = DEFAVGDEV/100.0; + } + + /* Save weak default function information */ + s->weak = weak; + s->dfctx = dfctx; + s->dfunc = dfunc; + + /* Init data point storage to zero */ + s->d.no = 0; + s->d.a = NULL; + + /* record low and high grid range */ + s->g.mres = 1.0; + s->g.bres = 0; + for (e = 0; e < s->di; e++) { + if (gres[e] < 2) + error("rspl: grid res must be >= 2!"); + s->g.res[e] = gres[e]; /* record the desired resolution of the grid */ + s->g.mres *= gres[e]; + if (gres[e] > s->g.bres) { + s->g.bres = gres[e]; + s->g.brix = e; + } + + if (glow == NULL) + s->g.l[e] = 0.0; + else + s->g.l[e] = glow[e]; + + if (ghigh == NULL) + s->g.h[e] = 1.0; + else + s->g.h[e] = ghigh[e]; + } + s->g.mres = pow(s->g.mres, 1.0/e); /* geometric mean */ + + /* record low and high data normalizing factors */ + for (f = 0; f < s->fdi; f++) { + if (vlow == NULL) + s->d.vl[f] = 0.0; + else + s->d.vl[f] = vlow[f]; + + if (vhigh == NULL) + s->d.vw[f] = 1.0; + else + s->d.vw[f] = vhigh[f]; + } + + /* If we are supplied initial data points, expand the */ + /* grid range to be able to cover it. */ + /* Also compute average data value. */ + for (f = 0; f < s->fdi; f++) + s->d.va[f] = 0.5; /* default average */ + if (dtp == 0) { /* Default weight */ + co *dp = (co *)d; + + for (i = 0; i < dno; i++) { + for (e = 0; e < s->di; e++) { + if (dp[i].p[e] > s->g.h[e]) + s->g.h[e] = dp[i].p[e]; + if (dp[i].p[e] < s->g.l[e]) + s->g.l[e] = dp[i].p[e]; + } + for (f = 0; f < s->fdi; f++) { + if (dp[i].v[f] > s->d.vw[f]) + s->d.vw[f] = dp[i].v[f]; + if (dp[i].v[f] < s->d.vl[f]) + s->d.vl[f] = dp[i].v[f]; + s->d.va[f] += dp[i].v[f]; + } + } + } else if (dtp == 1) { /* Per data point weight */ + cow *dp = (cow *)d; + + for (i = 0; i < dno; i++) { + for (e = 0; e < s->di; e++) { + if (dp[i].p[e] > s->g.h[e]) + s->g.h[e] = dp[i].p[e]; + if (dp[i].p[e] < s->g.l[e]) + s->g.l[e] = dp[i].p[e]; + } + for (f = 0; f < s->fdi; f++) { + if (dp[i].v[f] > s->d.vw[f]) + s->d.vw[f] = dp[i].v[f]; + if (dp[i].v[f] < s->d.vl[f]) + s->d.vl[f] = dp[i].v[f]; + s->d.va[f] += dp[i].v[f]; + } + } + } else { /* Per data point output weight */ + coww *dp = (coww *)d; + + for (i = 0; i < dno; i++) { + for (e = 0; e < s->di; e++) { + if (dp[i].p[e] > s->g.h[e]) + s->g.h[e] = dp[i].p[e]; + if (dp[i].p[e] < s->g.l[e]) + s->g.l[e] = dp[i].p[e]; + } + for (f = 0; f < s->fdi; f++) { + if (dp[i].v[f] > s->d.vw[f]) + s->d.vw[f] = dp[i].v[f]; + if (dp[i].v[f] < s->d.vl[f]) + s->d.vl[f] = dp[i].v[f]; + s->d.va[f] += dp[i].v[f]; + } + } + } + if (dno > 0) { /* Complete the average */ + for (f = 0; f < s->fdi; f++) + s->d.va[f] = (s->d.va[f] - 0.5)/((double)dno); + } + + /* compute (even division) width of each grid cell */ + for (e = 0; e < s->di; e++) { + s->g.w[e] = (s->g.h[e] - s->g.l[e])/(double)(s->g.res[e]-1); + } + + /* Convert low and high to low and width data range */ + for (f = 0; f < s->fdi; f++) { + s->d.vw[f] -= s->d.vl[f]; + } + +#ifdef INCURVEADJ + /* Save grid cell (smooth data space) position information (if any), */ + if (ipos != NULL) { + for (e = 0; e < s->di; e++) { + if (ipos[e] != NULL) { + if ((s->g.ipos[e] = (double *)calloc(s->g.res[e], sizeof(double))) == NULL) + error("rspl: malloc failed - ipos[]"); + for (i = 0; i < s->g.res[e]; i++) { + s->g.ipos[e][i] = ipos[e][i]; + if (i > 0 && fabs(s->g.ipos[e][i] - s->g.ipos[e][i-1]) < 1e-12) + error("rspl: ipos[%d][%d] to ipos[%d][%d] is nearly zero!",e,i,e,i-1); + } + } + } + } +#endif /* INCURVEADJ */ + + /* Allocate the grid data */ + alloc_grid(s); + + /* Zero out the re-usable mgtmps */ + for (f = 0; f < s->fdi; f++) { + s->mgtmps[f] = NULL; + } + + { + int sres; /* Starting resolution */ + double res; + double gratio; + + /* Figure out how many multigrid steps to use */ + sres = 4; /* Else start at minimum grid res of 4 */ + + /* Calculate the resolution scaling ratio and number of itters. */ + gratio = GRATIO; + if (((double)s->g.bres/(double)sres) <= gratio) { + s->niters = 2; + gratio = (double)s->g.bres/(double)sres; + } else { /* More than one needed */ + s->niters = (int)((log((double)s->g.bres) - log((double)sres))/log(gratio) + 0.5); + gratio = exp((log((double)s->g.bres) - log((double)sres))/(double)s->niters); + s->niters++; + } + + /* Allocate space for resolutions and mgtmps pointers */ + if ((s->ires = imatrix(0, s->niters, 0, s->di)) == NULL) + error("rspl: malloc failed - ires[][]"); + + for (f = 0; f < s->fdi; f++) { + if ((s->mgtmps[f] = (void *) calloc(s->niters, sizeof(void *))) == NULL) + error("rspl: malloc failed - mgtmps[]"); + } + + /* Fill in the resolution values for each itteration */ + for (res = (double)sres, i = 0; i < s->niters; i++) { + int ires; + + ires = (int)(res + 0.5); + for (e = 0; e < s->di; e++) { + if ((ires + 1) >= s->g.res[e]) /* If close enough biger than target res. */ + s->ires[i][e] = s->g.res[e]; + else + s->ires[i][e] = ires; + } + res *= gratio; + } + + /* Assert */ + for (e = 0; e < s->di; e++) { + if (s->ires[s->niters-1][e] != s->g.res[e]) + error("rspl: internal error, final res %d != intended res %d\n", + s->ires[s->niters-1][e], s->g.res[e]); + } + + } + + /* Do the data point fitting */ + return add_rspl_imp(s, 0, d, dtp, dno); +} + +double adjw[21] = { + 7.0896971822529019e-278, 2.7480236142217909e+233, 1.4857837676559724e+166, + 1.3997102851752585e-152, 1.3987140593588909e-076, 2.8215833239257504e+243, + 1.4104974786556771e+277, 2.0916973891832284e+121, 2.0820139887245793e-152, + 1.0372833042501621e-152, 2.1511212233835046e-313, 7.7791723264397072e-260, + 6.7035744954188943e+223, 8.5733372291341995e+170, 1.4275976773846279e-071, + 2.3994297542685112e-038, 3.9052141785471924e-153, 3.8223903939904297e-096, + 3.2368131456774088e+262, 6.5639459298208554e+045, 2.0087765219520138e-139 +}; + +/* Do the work of initialising from initial data points. */ +/* Return non-zero if non-monotonic */ +static int +add_rspl_imp( + rspl *s, /* this */ + int flags, /* Combination of flags */ + void *d, /* Array holding position and function values of data points */ + int dtp, /* Flag indicating data type, 0 = (co *), 1 = (cow *), 2 = (coww *) */ + int dno /* Number of data points */ +) { + int fdi = s->fdi; + int i, n, e, f; + cj_arrays ta; /* cj_line temporary arrays */ + + if (flags & RSPL_VERBOSE) /* Turn on progress messages to stdout */ + s->verbose = 1; + if (flags & RSPL_NOVERBOSE) /* Turn off progress messages to stdout */ + s->verbose = 0; + + if (dno == 0) { /* There are no points to initialise from */ + return 0; + } + + /* Allocate space for points */ + /* Allocate the scattered data space */ + if ((s->d.a = (rpnts *) malloc(sizeof(rpnts) * dno)) == NULL) + error("rspl malloc failed - data points"); + + /* Add the points */ + if (dtp == 0) { /* Default weight */ + co *dp = (co *)d; + + /* Append the list into data points */ + for (i = 0, n = s->d.no; i < dno; i++, n++) { + for (e = 0; e < s->di; e++) + s->d.a[n].p[e] = dp[i].p[e]; + for (f = 0; f < s->fdi; f++) { + s->d.a[n].cv[f] = + s->d.a[n].v[f] = dp[i].v[f]; + s->d.a[n].k[f] = 1.0; /* Assume all data points have same weight */ + } + } + } else if (dtp == 1) { /* Per data point weight */ + cow *dp = (cow *)d; + + /* Append the list into data points */ + for (i = 0, n = s->d.no; i < dno; i++, n++) { + for (e = 0; e < s->di; e++) + s->d.a[n].p[e] = dp[i].p[e]; + for (f = 0; f < s->fdi; f++) { + s->d.a[n].cv[f] = + s->d.a[n].v[f] = dp[i].v[f]; + s->d.a[n].k[f] = dp[n].w; /* Weight specified */ + } + } + } else { /* Per data point output weight */ + coww *dp = (coww *)d; + + /* Append the list into data points */ + for (i = 0, n = s->d.no; i < dno; i++, n++) { + for (e = 0; e < s->di; e++) + s->d.a[n].p[e] = dp[i].p[e]; + for (f = 0; f < s->fdi; f++) { + s->d.a[n].cv[f] = + s->d.a[n].v[f] = dp[i].v[f]; + s->d.a[n].k[f] = dp[n].w[f]; /* Weight specified */ + } + } + } + s->d.no = dno; + + init_cj_arrays(&ta); /* Zero temporary arrays */ + + if (s->verbose && s->zf) + printf("Doing extra fitting\n"); + + /* Do fit of grid to data for each output dimension */ + for (f = 0; f < fdi; f++) { + int nn = 0; /* Multigreid resolution itteration index */ + int zfcount = ZFCOUNT; /* Number of extra fit adjustments to do */ + int donezf = 0; /* Count - number of extra fit adjustments done */ + float *gp; + mgtmp *m = NULL; + + for (donezf = 0; donezf <= s->zf; donezf++) { /* For each extra fit pass */ + + for (s->tpsm2 = 0; s->tpsm2 <= s->tpsm; s->tpsm2++) { /* For passes of 2 pass smoothing */ + + /* For each resolution (itteration) */ + for (nn = 0; nn < s->niters; nn++) { + + m = new_mgtmp(s, s->ires[nn], f); + s->mgtmps[f][nn] = (void *)m; + + if (s->tpsm && s->tpsm2 != 0) { /* 2nd pass of 2 pass smoothing */ + init_ccv(m); /* Downsample m->ccv from s->g.ccv */ + } +// setup_solve(m, nn == (s->niters-1)); + setup_solve(m, 1); + + if (nn == 0) { /* Make sure we have an initial x[] */ + for (i = 0; i < m->g.no; i++) + m->q.x[i] = s->d.va[f]; /* Start with average data value */ + } else { + init_soln(m, s->mgtmps[f][nn-1]); /* Scale from previous resolution */ + + free_mgtmp(s->mgtmps[f][nn-1]); /* Free previous grid res solution */ + s->mgtmps[f][nn-1] = NULL; + } + + solve_gres(m, &ta, +#if defined(GRADUATED_TOL) + TOL * s->g.res[s->g.brix]/s->ires[nn][s->g.brix], +#else + TOL, +#endif + s->ires[nn][s->g.brix] >= s->g.res[s->g.brix]); /* Use itterative */ + + } /* Next resolution */ + + if (s->tpsm && s->tpsm2 == 0) { + double fstdev; /* Filter standard deviation */ +//printf("~1 setting up second pass smoothing !!!\n"); + + /* Compute the curvature compensation values from */ + /* first pass final resolution */ + comp_ccv(m); + + if (s->smooth >= 0.0) { + /* Compute from: no dim, no data points, avgdev & extrafit */ + fstdev = 0.05 * s->smooth; +fprintf(stderr,"~1 !!! Gaussian smoothing not being computed Using default %f !!!\n",fstdev); + } else { /* Special used to calibrate table */ + fstdev = -s->smooth; + } +//fprintf(stderr,"~1 Gaussian smoothing with fstdev %f !!!\n",fstdev); + /* Smooth the ccv's */ + filter_ccv(s, fstdev); + } + } /* Next two pass smoothing pass */ + if (s->zf) + comp_extrafit_corr(m); /* Compute correction to data target values */ + } /* Next extra fit pass */ + + /* Clean up after 2 pass smoothing */ + s->tpsm2 = 0; + if (s->g.ccv != NULL) { + free_dmatrix(s->g.ccv, 0, s->g.no-1, 0, s->di-1); + s->g.ccv = NULL; + } + + /* Transfer result in x[] to appropriate grid point value */ + for (gp = s->g.a, i = 0; i < s->g.no; gp += s->g.pss, i++) + gp[f] = (float)m->q.x[i]; + + free_mgtmp(s->mgtmps[f][nn-1]); /* Free final resolution entry */ + s->mgtmps[f][nn-1] = NULL; + + } /* Next output channel */ + + /* Free up cj_line temporary arrays */ + free_cj_arrays(&ta); + + /* Return non-mono check */ + return is_mono(s); +} + +/* Initialise the regular spline from scattered data */ +/* Return non-zero if non-monotonic */ +static int +fit_rspl( + rspl *s, /* this */ + int flags, /* Combination of flags */ + co *d, /* Array holding position and function values of data points */ + int dno, /* Number of data points */ + ratai glow, /* Grid low scale - will be expanded to enclose data, NULL = default 0.0 */ + ratai ghigh, /* Grid high scale - will be expanded to enclose data, NULL = default 1.0 */ + int gres[MXDI], /* Spline grid resolution */ + ratao vlow, /* Data value low normalize, NULL = default 0.0 */ + ratao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth, /* Smoothing factor, nominal = 1.0 */ + double avgdev[MXDO], + /* Average Deviation of function values as proportion of function range. */ + double *ipos[MXDI] /* Optional relative grid cell position for each input dim cell, */ + /* gres[] entries per dimension. Used to scale smoothness criteria */ +) { + /* Call implementation with (co *) data */ + return fit_rspl_imp(s, flags, (void *)d, 0, dno, glow, ghigh, gres, vlow, vhigh, + smooth, avgdev, ipos, 1.0, NULL, NULL); +} + +/* Initialise the regular spline from scattered data with weights */ +/* Return non-zero if non-monotonic */ +static int +fit_rspl_w( + rspl *s, /* this */ + int flags, /* Combination of flags */ + cow *d, /* Array holding position, function and weight values of data points */ + int dno, /* Number of data points */ + ratai glow, /* Grid low scale - will be expanded to enclose data, NULL = default 0.0 */ + ratai ghigh, /* Grid high scale - will be expanded to enclose data, NULL = default 1.0 */ + int gres[MXDI], /* Spline grid resolution */ + ratao vlow, /* Data value low normalize, NULL = default 0.0 */ + ratao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth, /* Smoothing factor, nominal = 1.0 */ + double avgdev[MXDO], + /* Average Deviation of function values as proportion of function range. */ + double *ipos[MXDI] /* Optional relative grid cell position for each input dim cell, */ + /* gres[] entries per dimension. Used to scale smoothness criteria */ +) { + /* Call implementation with (cow *) data */ + return fit_rspl_imp(s, flags, (void *)d, 1, dno, glow, ghigh, gres, vlow, vhigh, + smooth, avgdev, ipos, 1.0, NULL, NULL); +} + +/* Initialise the regular spline from scattered data with individual weights */ +/* Return non-zero if non-monotonic */ +static int +fit_rspl_ww( + rspl *s, /* this */ + int flags, /* Combination of flags */ + coww *d, /* Array holding position, function and weight values of data points */ + int dno, /* Number of data points */ + ratai glow, /* Grid low scale - will be expanded to enclose data, NULL = default 0.0 */ + ratai ghigh, /* Grid high scale - will be expanded to enclose data, NULL = default 1.0 */ + int gres[MXDI], /* Spline grid resolution */ + ratao vlow, /* Data value low normalize, NULL = default 0.0 */ + ratao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth, /* Smoothing factor, nominal = 1.0 */ + double avgdev[MXDO], + /* Average Deviation of function values as proportion of function range. */ + double *ipos[MXDI] /* Optional relative grid cell position for each input dim cell, */ + /* gres[] entries per dimension. Used to scale smoothness criteria */ +) { + /* Call implementation with (cow *) data */ + return fit_rspl_imp(s, flags, (void *)d, 2, dno, glow, ghigh, gres, vlow, vhigh, + smooth, avgdev, ipos, 1.0, NULL, NULL); +} + +/* Initialise from scattered data, with weak default function. */ +/* Return non-zero if result is non-monotonic */ +static int +fit_rspl_df( + rspl *s, /* this */ + int flags, /* Combination of flags */ + co *d, /* Array holding position and function values of data points */ + int dno, /* Number of data points */ + datai glow, /* Grid low scale - will expand to enclose data, NULL = default 0.0 */ + datai ghigh, /* Grid high scale - will expand to enclose data, NULL = default 1.0 */ + int gres[MXDI], /* Spline grid resolution, ncells = gres-1 */ + datao vlow, /* Data value low normalize, NULL = default 0.0 */ + datao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth, /* Smoothing factor, nominal = 1.0 */ + double avgdev[MXDO], + /* Average Deviation of function values as proportion of function range. */ + double *ipos[MXDI], /* Optional relative grid cell position for each input dim cell, */ + /* gres[] entries per dimension. Used to scale smoothness criteria */ + double weak, /* Weak weighting, nominal = 1.0 */ + void *cbntx, /* Opaque function context */ + void (*func)(void *cbntx, double *out, double *in) /* Function to set from */ +) { + /* Call implementation with (co *) data */ + return fit_rspl_imp(s, flags, (void *)d, 0, dno, glow, ghigh, gres, vlow, vhigh, + smooth, avgdev, ipos, weak, cbntx, func); +} + +/* Initialise from scattered data, with per point weighting and weak default function. */ +/* Return non-zero if result is non-monotonic */ +static int +fit_rspl_w_df( + rspl *s, /* this */ + int flags, /* Combination of flags */ + cow *d, /* Array holding position, function and weight values of data points */ + int dno, /* Number of data points */ + datai glow, /* Grid low scale - will expand to enclose data, NULL = default 0.0 */ + datai ghigh, /* Grid high scale - will expand to enclose data, NULL = default 1.0 */ + int gres[MXDI], /* Spline grid resolution, ncells = gres-1 */ + datao vlow, /* Data value low normalize, NULL = default 0.0 */ + datao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth, /* Smoothing factor, nominal = 1.0 */ + double avgdev[MXDO], + /* Average Deviation of function values as proportion of function range. */ + double *ipos[MXDI], /* Optional relative grid cell position for each input dim cell, */ + /* gres[] entries per dimension. Used to scale smoothness criteria */ + double weak, /* Weak weighting, nominal = 1.0 */ + void *cbntx, /* Opaque function context */ + void (*func)(void *cbntx, double *out, double *in) /* Function to set from */ +) { + /* Call implementation with (cow *) data */ + return fit_rspl_imp(s, flags, (void *)d, 1, dno, glow, ghigh, gres, vlow, vhigh, + smooth, avgdev, ipos, weak, cbntx, func); +} + +/* Init scattered data elements in rspl */ +void +init_data(rspl *s) { + s->d.no = 0; + s->d.a = NULL; + s->fit_rspl = fit_rspl; + s->fit_rspl_w = fit_rspl_w; + s->fit_rspl_ww = fit_rspl_ww; + s->fit_rspl_df = fit_rspl_df; + s->fit_rspl_w_df = fit_rspl_w_df; +} + +/* Free the scattered data allocation */ +void +free_data(rspl *s) { + int i, f; + + if (s->ires != NULL) { + free_imatrix(s->ires, 0, s->niters, 0, s->di); + s->ires = NULL; + } + + /* Free up mgtmps */ + for (f = 0; f < s->fdi; f++) { + if (s->mgtmps[f] != NULL) { + for (i = 0; i < s->niters; i++) { + if (s->mgtmps[f][i] != NULL) { + free_mgtmp(s->mgtmps[f][i]); + } + } + free(s->mgtmps[f]); + s->mgtmps[f] = NULL; + } + } + + if (s->d.a != NULL) { /* Free up the data point data */ + free((void *)s->d.a); + s->d.a = NULL; + } +} + +/* - - - - - - - - - - - - - - - - - - - - - - - -*/ +/* In theory, the smoothness should increase proportional to the square of the */ +/* overall average sample deviation. (Or the weight of each individual data point */ +/* could be made inversely proportional to the square of its average sample */ +/* deviation, or square of its standard deviation, or its variance, etc.) */ +/* In practice, other factors also seem to come into play, so we use a */ +/* table to lookup an "optimal" smoothing factor for each combination */ +/* of the parameters dimension, sample count and average sample deviation. */ + +/* The contents of the table were created by taking some representative */ +/* profiles and testing them with various numbers of data points */ +/* and added L*a*b* noise, and locating the optimal smoothing factor */ +/* for each parameter. */ +/* If the instrument variance is assumed to be a constant factor */ +/* in the sensors, then it would be appropriate to modify the */ +/* data weighting rather than the overall smoothness, */ +/* since a constant XYZ variance could be transformed into a */ +/* per data point L*a*b* variance. */ +/* The optimal smoothness factor doesn't appear to have any significant */ +/* dependence on the RSPL resolution. */ + +/* Return an appropriate smoothing factor for the combination of final parameters. */ +/* This is a base value that will be multiplied by the extra supplied smoothing factor. */ +/* The "Average sample deviation" is a measure of its randomness. */ +/* For instance, values that had a +/- 0.1 uniform random error added */ +/* to them, would have an average sample deviation of 0.05. */ +/* For normally distributed errors, the average deviation is */ +/* aproximately 0.564 times the standard deviation. (0.564 * sqrt(variance)) */ +/* This table is appropriate for the default rspl algorithm + slight EXTRA_SURFACE_SMOOTHING, */ +/* and is NOT setup for RSPL_2PASSSMTH or RSPL_EXTRAFIT2 !! */ +/* SMOOTH */ +// ~~99 +static double opt_smooth( + rspl *s, + int di, /* Dimensions */ + int ndp, /* Number of data points */ + double ad /* Average sample deviation (proportion of input range) */ +) { + int i; + double nc; /* Normalised sample count */ + double lsm, sm, tweakf; + + /* Lookup that converts the di'th root of the data point count */ + /* into the smf table row index */ + int ncixN; + int ncix; /* Normalised sample count index */ + double ncw; /* Weight of [ncix], 1-weight of [ncix+1] */ + int nncixv[4] = { 6, 6, 10, 11 }; /* Number in ncixv[] rows */ + double ncixv[4][11] = { /* nc to smf index */ + { 5.0, 10.0, 20.0, 50.0, 100.0, 200.0 }, + { 5.0, 10.0, 20.0, 50.0, 100.0, 200.0 }, + { 2.92, 3.68, 4.22, 5.0, 6.3, 7.94, 10.0, 12.6, 20.0, 50.0 }, + { 2.66, 3.16, 3.76, 4.61, 5.0, 5.48, 6.51, 7.75, 10.0, 20.0, 31.62 } + }; + + /* Lookup that converts the deviation fraction */ + /* into the smf table column index */ + int adixN; /* Number in array */ + int adix; /* Average deviation count index */ + double adw; /* Weight of [adix], 1-weight of [adix+1] */ + int nadixv[4] = { 6, 6, 6, 7 }; /* Number in adixv[] rows */ + double adixv[4][7] = { /* ad to smf index */ + { 0.0001, 0.0025, 0.005, 0.0125, 0.025, 0.05 }, + { 0.0001, 0.0025, 0.005, 0.0125, 0.025, 0.05 }, + { 0.0001, 0.0025, 0.005, 0.0125, 0.025, 0.05 }, + { 0.0001, 0.0025, 0.005, 0.0075, 0.0125, 0.025, 0.05 } + }; + + + /* New for V1.10, from smtmpp using sRGB, EpsonR1800, Hitachi2112, */ + /* Fogra39L, Canon1180, Epson10K, with low EXTRA_SURFACE_SMOOTHING. */ + + /* Main lookup table, by [di][ncix][adix]: */ + /* Values are log of smoothness value. */ + static double smf[4][11][7] = { + /* 1D: */ + { +/* -r value: 0 0.25% 0.5% 1.25% 2.5% 5% */ +/* Tot white N 0% 1% 2% 5% 10% 20% */ +/* 5 */ { -5.0, -5.3, -5.2, -4.4, -3.5, -0.8 }, +/* 10 */ { -6.4, -5.6, -5.1, -4.5, -4.0, -3.6 }, +/* 20 */ { -6.4, -5.9, -5.5, -4.6, -3.9, -3.3 }, +/* 50 */ { -6.8, -6.0, -5.6, -4.9, -4.4, -3.7 }, +/* 100 */ { -6.9, -6.2, -5.6, -4.9, -4.3, -3.5 }, +/* 200 */ { -6.9, -5.9, -5.5, -5.1, -4.7, -4.4 } + }, + /* 2D: */ + { + /* 0% 1% 2% 5% 10% 20% */ +/* 5 */ { -5.0, -5.0, -5.0, -4.8, -4.2, -3.2 }, +/* 10 */ { -5.1, -4.9, -4.6, -3.9, -3.3, -2.6 }, +/* 20 */ { -5.9, -5.0, -4.6, -4.1, -3.6, -3.1 }, +/* 50 */ { -6.7, -5.1, -4.7, -4.2, -3.7, -3.1 }, +/* 100 */ { -6.8, -5.0, -4.6, -4.0, -3.6, -3.0 }, +/* 200 */ { -6.8, -4.9, -4.4, -3.9, -3.5, -3.1 } + }, + /* 3D: */ + { + /* 0% 1% 2% 5% 10% 20% */ +/* 2.92 */ { -5.2, -5.0, -5.0, -4.9, -3.6, -2.2 }, +/* 3.68 */ { -5.5, -5.6, -5.6, -5.2, -4.4, -2.4 }, +/* 4.22 */ { -4.7, -4.8, -5.7, -5.9, -5.9, -2.3 }, +/* 5.00 */ { -4.1, -4.1, -5.0, -3.8, -3.4, -2.6 }, +/* 6.30 */ { -4.8, -4.6, -4.6, -4.1, -3.8, -3.4 }, +/* 7.94 */ { -4.7, -4.7, -4.7, -3.8, -3.3, -2.9 }, +/* 10.0 */ { -4.7, -4.8, -4.6, -3.9, -3.4, -3.0 }, +/* 12.6 */ { -5.2, -4.7, -4.4, -4.0, -3.4, -2.9 }, +/* 20.0 */ { -5.5, -5.0, -4.3, -3.6, -3.1, -2.8 }, +/* 50.0 */ { -5.1, -4.7, -4.3, -3.8, -3.3, -2.8 } + + }, + /* 4D: */ + { + /* 0% 1% 2% 3%, 5% 10% 20% */ +/* 2.66 */ { -5.5, -5.6, -4.9, -4.8, -4.5, -2.8, -3.1 }, +/* 3.16 */ { -4.3, -4.2, -4.0, -3.6, -3.2, -2.8, -2.6 }, +/* 3.76 */ { -4.3, -4.2, -4.0, -3.8, -3.2, -2.8, -1.5 }, +/* 4.61 */ { -4.5, -3.9, -3.5, -3.2, -3.0, -2.4, -1.9 }, +/* 5.00 */ { -4.5, -4.3, -3.7, -3.3, -3.0, -2.3, -1.9 }, +/* 5.48 */ { -4.7, -4.5, -4.3, -3.9, -3.2, -2.0, -0.9 }, +/* 6.51 */ { -4.3, -4.3, -4.1, -3.9, -3.1, -2.3, -1.6 }, +/* 7.75 */ { -4.5, -4.4, -3.8, -3.5, -3.1, -2.4, -1.6 }, +/* 10.00 */ { -4.9, -4.3, -3.6, -3.2, -2.8, -2.2, -1.6 }, +/* 20.00 */ { -4.8, -3.5, -3.0, -2.8, -2.5, -2.2, -1.9 }, +/* 31.62 */ { -5.1, -3.7, -3.0, -2.7, -2.3, -1.9, -1.5 } + } + }; + + /* Smoothness tweak */ + static double tweak[21] = { + 8.0891733310676571e-263, 1.1269230397087924e+243, 5.5667427967136639e+170, + 4.6422059659371074e-072, 4.7573037006103243e-038, 2.2050803446598081e-152, + 1.9082109674254010e-094, 1.2362202651281476e+262, 1.8334727652805863e+044, + 1.7193993129127580e-139, 8.4028172720870109e-316, 7.7791723264393403e-260, + 4.5505694361996285e+198, 1.4450789782663302e+214, 4.8548304485951407e-033, + 6.0848773033767158e-153, 2.2014810203887549e+049, 6.0451581453053059e-153, + 4.5657997262605343e+233, 1.1415770815909824e+243, 2.0087364177250134e-139 + }; + + /* Real world correction factors go here - */ + /* None needed at the moment ? */ + double rwf[4] = { 1.0, 1.0, 1.0, 1.0 }; /* Factor for each dimension */ + +//printf("~1 opt_smooth called with di = %d, ndp = %d, ad = %f\n",di,ndp,ad); + if (di < 1) + di = 1; + nc = pow((double)ndp, 1.0/(double)di); /* Normalised sample count */ + if (di > 4) + di = 4; + di--; /* Make di 0..3 */ + + /* Convert the two input parameters into appropriate */ + /* indexes and weights for interpolation. We assume ratiometric scaling. */ + + /* Number of samples */ + ncixN = nncixv[di]; + if (nc <= ncixv[di][0]) { + ncix = 0; + ncw = 1.0; + } else if (nc >= ncixv[di][ncixN-1]) { + ncix = ncixN-2; + ncw = 0.0; + } else { + for (ncix = 0; ncix < ncixN; ncix++) { + if (nc >= ncixv[di][ncix] && nc <= ncixv[di][ncix+1]) + break; + + } + ncw = 1.0 - (log(nc) - log(ncixv[di][ncix])) + /(log(ncixv[di][ncix+1]) - log(ncixv[di][ncix])); + } + + adixN = nadixv[di]; + if (ad <= adixv[di][0]) { + adix = 0; + adw = 1.0; + } else if (ad >= adixv[di][adixN-1]) { + adix = adixN-2; + adw = 0.0; + } else { + for (adix = 0; adix < adixN; adix++) { + if (ad >= adixv[di][adix] && ad <= adixv[di][adix+1]) + break; + } + adw = 1.0 - (log(ad) - log(adixv[di][adix])) + /(log(adixv[di][adix+1]) - log(adixv[di][adix])); + } + + /* Lookup & interpolate the log smoothness factor */ +//printf("~1 di = %d, ncix = %d, adix = %d\n",di,ncix,adix); + lsm = smf[di][ncix][adix] * ncw * adw; + lsm += smf[di][ncix][adix+1] * ncw * (1.0 - adw); + lsm += smf[di][ncix+1][adix] * (1.0 - ncw) * adw; + lsm += smf[di][ncix+1][adix+1] * (1.0 - ncw) * (1.0 - adw); + +//printf("~1 lsm = %f\n",lsm); + + for (tweakf = 0.0, i = 1; i < 21; i++) + tweakf += tweak[i]; + tweakf *= tweak[0]; + + sm = pow(10.0, lsm * tweakf); + + /* and correct for the real world with a final tweak table */ + sm *= rwf[di]; + +//printf("Got log smth %f, returning %1.9f from ncix %d, ncw %f, adix %d, adw %f\n", lsm, sm, ncix, ncw, adix, adw); + return sm; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - -*/ +/* Multi-grid temp structure (mgtmp) routines */ + +/* Create a new mgtmp. */ +/* Solution matricies will be NULL */ +static mgtmp *new_mgtmp( + rspl *s, /* associated rspl */ + int gres[MXDI], /* resolution to create */ + int f /* output dimension */ +) { + mgtmp *m; + int di = s->di; + int dno = s->d.no; + int gno, nigc; + int gres_1[MXDI]; + int e, g, n, i; + + /* Allocate a structure */ + if ((m = (mgtmp *) calloc(1, sizeof(mgtmp))) == NULL) + error("rspl: malloc failed - mgtmp"); + + /* General stuff */ + m->s = s; + m->f = f; + + /* Grid related */ + for (gno = 1, e = 0; e < di; gno *= gres[e], e++) + ; + m->g.no = gno; + + /* record high, low limits, and width of each grid cell */ + m->g.mres = 1.0; + m->g.bres = 0; + for (e = 0; e < s->di; e++) { + m->g.res[e] = gres[e]; + gres_1[e] = gres[e] - 1; + m->g.mres *= gres[e]; + if (gres[e] > m->g.bres) { + m->g.bres = gres[e]; + m->g.brix = e; + } + m->g.l[e] = s->g.l[e]; + m->g.h[e] = s->g.h[e]; + m->g.w[e] = (s->g.h[e] - s->g.l[e])/(double)(gres[e]-1); + } + m->g.mres = pow(m->g.mres, 1.0/e); /* geometric mean */ + + /* Compute index coordinate increments into linear grid for each dimension */ + /* ie. 1, gres, gres^2, gres^3 */ + for (m->g.ci[0] = 1, e = 1; e < di; e++) + m->g.ci[e] = m->g.ci[e-1] * gres[e-1]; /* In grid points */ + + /* Compute index offsets from base of cube to other corners */ + for (m->g.hi[0] = 0, e = 0, g = 1; e < di; g *= 2, e++) { + for (i = 0; i < g; i++) + m->g.hi[g+i] = m->g.hi[i] + m->g.ci[e]; /* In grid points */ + } + + /* Number grid cells that contribute to smoothness error */ + for (nigc = 1, e = 0; e < di; e++) { + nigc *= gres[e]-2; + } + + /* Downsample ipos arrays */ + for (e = 0; e < s->di; e++) { + if (s->g.ipos[e] != NULL) { + unsigned int ix; + double val, w; + double inputEnt_1 = (double)(s->g.res[e]-1); + double inputEnt_2 = (double)(s->g.res[e]-2); + + if ((m->g.ipos[e] = (double *)calloc(m->g.res[e], sizeof(double))) == NULL) + error("scat: malloc failed - ipos[]"); + + /* Compute each downsampled position using linear interpolation */ + for (n = 0; n < m->g.res[e]; n++) { + double in = (double)n/(m->g.res[e]-1); + + val = in * 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 > inputEnt_2) + ix = inputEnt_2; + w = val - (double)ix; /* weight */ + val = s->g.ipos[e][ix]; + m->g.ipos[e][n] = val + w * (s->g.ipos[e][ix+1] - val); + } + } + } + + /* Compute curvature weighting for matching intermediate resolutions for */ + /* the number of grid points curvature that is accuumulated, as well as the */ + /* geometric effects of a finer fit to the target surface. */ + /* This is all to keep the ratio of sum of smoothness error squared */ + /* constant in relationship to the sum of data point error squared. */ + for (e = 0; e < di; e++) { + double rsm; /* Resolution smoothness factor */ + double smooth; + + if (s->symdom) + rsm = m->g.res[e]; /* Relative final grid size */ + else + rsm = m->g.mres; /* Relative mean final grid size */ + + /* Compensate for geometric and grid numeric factors */ + rsm = pow((rsm-1.0), 4.0); /* Geometric resolution factor for smooth surfaces */ + /* (is ^2 for res. * ^2 with error squared) */ + rsm /= nigc; /* Average squared non-smoothness */ + + /* 2 pass smoothing */ + if (s->tpsm) { + double lsm; + + lsm = -6.0; + if (s->tpsm2 != 0) /* Two pass smoothing second pass */ + lsm += 2.0; /* Use 100 times the smoothness */ + m->sf.cw[e] = pow(10.0, lsm) * rsm; + + /* Normal */ + } else { + + if (s->smooth >= 0.0) { + /* Table lookup for optimum smoothing factor */ + smooth = opt_smooth(s, di, s->d.no, s->avgdev[f]); + m->sf.cw[e] = s->smooth * smooth * rsm; + } else { /* Special used to calibrate table */ + m->sf.cw[e] = -s->smooth * rsm; + } + } + } + + /* Compute weighting for weak default function grid value */ + /* We are trying to keep the effect of the wdf constant with */ + /* changes in grid resolution and dimensionality. */ + m->wdfw = s->weak * WEAKW/(m->g.no * (double)di); + + /* Allocate space for auiliary data point related info */ + if ((m->d = (struct mgdat *) calloc(dno, sizeof(struct mgdat))) == NULL) + error("rspl: malloc failed - mgtmp"); + + /* fill in the aux data point info */ + /* (We're assuming N-linear interpolation here. */ + /* Perhaps we should try simplex too ?) */ + for (n = 0; n < dno; n++) { + double we[MXRI]; /* 1.0 - Weight in each dimension */ + int ix = 0; /* Index to base corner of surrounding cube in grid points */ + + /* Figure out which grid cell the point falls into */ + for (e = 0; e < di; e++) { + double t; + int mi; + if (s->d.a[n].p[e] < m->g.l[e] || s->d.a[n].p[e] > m->g.h[e]) { + error("rspl: Data point %d outside grid %e <= %e <= %e", + n,m->g.l[e],s->d.a[n].p[e],m->g.h[e]); + } + t = (s->d.a[n].p[e] - m->g.l[e])/m->g.w[e]; + mi = (int)floor(t); /* Grid coordinate */ + if (mi < 0) /* Limit to valid cube base index range */ + mi = 0; + else if (mi >= gres_1[e]) /* Make sure outer point can't be base */ + mi = gres_1[e]-1; + ix += mi * m->g.ci[e]; /* Add Index offset for grid cube base in dimen */ + we[e] = t - (double)mi; /* 1.0 - weight */ + } + m->d[n].b = ix; + + /* Compute corner weights needed for interpolation */ + m->d[n].w[0] = 1.0; + for (e = 0, g = 1; e < di; g *= 2, e++) { + for (i = 0; i < g; i++) { + m->d[n].w[g+i] = m->d[n].w[i] * we[e]; + m->d[n].w[i] *= (1.0 - we[e]); + } + } + +#ifdef DEBUG + printf("Data point %d weighting factors = \n",n); + for (e = 0; e < (1 << di); e++) { + printf("%d: %f\n",e,m->d[n].w[e]); + } +#endif /* DEBUG */ + } + + /* Set the solution matricies to unalocated */ + m->q.ccv = NULL; + m->q.A = NULL; + m->q.ixcol = NULL; + m->q.b = NULL; + m->q.x = NULL; + + return m; +} + +/* Completely free an mgtmp */ +static void free_mgtmp(mgtmp *m) { + int e, di = m->s->di, gno = m->g.no; + + for (e = 0; e < m->s->di; e++) { + if (m->g.ipos[e] != NULL) + free(m->g.ipos[e]); + } + if (m->q.ccv != NULL) + free_dmatrix(m->q.ccv,0,gno-1,0,di-1); + free_dvector(m->q.x,0,gno-1); + free_dvector(m->q.b,0,gno-1); + free((void *)m->q.ixcol); + free_dmatrix(m->q.A,0,gno-1,0,m->q.acols-1); + free((void *)m->d); + free((void *)m); +} + +/* Initialise the A[][] and b[] matricies ready to solve, given f */ +/* (Can be used to re-initialize an mgtmp for changing curve/extra fit factors) */ +/* We are setting up the matrix equation Ax = b to solve, where the aim is */ +/* to solve the energy minimization problem by setting up a series of interconnected */ +/* equations for each grid node value (x) in which the partial derivative */ +/* of the equation to be minimized is zero. The A matrix holds the dependence on */ +/* the grid points with regard to smoothness and interpolation */ +/* fit to the scattered data points, while b holds constant values (e.g. the data */ +/* point determined boundary conditions). A[][] stores the packed sparse triangular matrix. */ + +/* + + The overall equation to be minimized is: + + sum(curvature errors at each grid point) ^ 2 + + sum(data interpolation errors) ^ 2 + + The way this is solved is to take the partial derivative of + the above with respect to each grid point value, and simultaineously + solve all the partial derivative equations for zero. + + Each row of A[][] and b[] represents the cooeficients of one of + the partial derivative equations (it does NOT correspond to one + grid points curvature etc.). Because the partial derivative + of any sum term that does not have the grid point in question in it + will have a partial derivative of zero, each row equation consists + of just those terms that have that grid points value in it, + with the vast majoroty of the sum terms omitted. + + */ + +static void setup_solve( + mgtmp *m, /* initialized grid temp structure */ + int final /* nz if final resolution (activate EXTRA_SURFACE_SMOOTHING) */ +) { + rspl *s = m->s; + int di = s->di; + int gno = m->g.no, dno = s->d.no; + int *gres = m->g.res, *gci = m->g.ci; + int f = m->f; /* Output dimensions being worked on */ + + double **ccv = m->q.ccv; /* ccv vector for adjusting simultabeous equation */ + double **A = m->q.A; /* A matrix of interpoint weights */ + int acols = m->q.acols; /* A matrix packed columns needed */ + int *xcol = m->q.xcol; /* A array column translation from packed to sparse index */ + int *ixcol = m->q.ixcol; /* A array column translation from sparse to packed index */ + double *b = m->q.b; /* b vector for RHS of simultabeous equation */ + double *x = m->q.x; /* x vector for LHS of simultabeous equation */ + int e, n,i,k; + double oawt; /* Overall adjustment weight */ + double nbsum; /* normb sum */ + +//printf("~1 setup_solve got ccv = 0x%x\n",ccv); + + /* Allocate and init the A array column sparse packing lookup and inverse. */ + /* Note that this only works for a minumum grid resolution of 4. */ + /* The sparse di dimension region allowed for is a +/-1 cube around the point */ + /* question, plus +/-2 offsets in axis direction only, */ + /* plus +/-3 offset in axis directions if 2nd order smoothing is defined. */ + if (A == NULL) { /* Not been allocated previously */ + DCOUNT(gc, MXDIDO, di, -3, -3, 4); /* Step through +/- 3 cube offset */ + int ix; /* Grid point offset in grid points */ + acols = 0; + + DC_INIT(gc); + + while (!DC_DONE(gc)) { + int n3 = 0, n2 = 0, nz = 0; + + /* Detect +/-3 +/-2 and 0 elements */ + for (k = 0; k < di; k++) { + if (gc[k] == 3 || gc[k] == -3) + n3++; + if (gc[k] == 2 || gc[k] == -2) + n2++; + if (gc[k] == 0) + nz++; + } + + /* Accept only if doesn't have a +/-2, */ + /* or if it has exactly one +/-2 and otherwise 0 */ + if ((n3 == 0 && n2 == 0) + || (n2 == 1 && nz == (di-1)) +#ifdef SMOOTH2 + || (n3 == 1 && nz == (di-1)) +#endif /* SMOOTH2*/ + ) { + for (ix = 0, k = 0; k < di; k++) + ix += gc[k] * gci[k]; /* Multi-dimension grid offset */ + if (ix >= 0) { + xcol[acols++] = ix; /* We only store half, due to symetry */ + } + } + DC_INC(gc); + } + + ix = xcol[acols-1] + 1; /* Number of expanded rows */ + + /* Create inverse lookup */ + if (ixcol == NULL) { + if ((ixcol = (int *) malloc(ix * sizeof(int))) == NULL) + error("rspl malloc failed - ixcol"); + } + + for (k = 0; k < ix; k++) + ixcol[k] = -0x7fffffff; /* Mark rows that aren't allowed for */ + + for (k = 0; k < acols; k++) + ixcol[xcol[k]] = k; /* Set inverse lookup */ + +#ifdef DEBUG + printf("Sparse array expansion = \n"); + for (k = 0; k < acols; k++) { + printf("%d: %d\n",k,xcol[k]); + } + printf("\nSparse array encoding = \n"); + for (k = 0; k < ix; k++) { + printf("%d: %d\n",k,ixcol[k]); + } +#endif /* DEBUG */ + + /* We store the packed diagonals of the sparse A matrix */ + /* If re-initializing, zero matrices, else allocate zero'd matricies */ + if ((A = dmatrixz(0,gno-1,0,acols-1)) == NULL) { + error("Malloc of A[][] failed with [%d][%d]",gno,acols); + } + if ((b = dvectorz(0,gno)) == NULL) { + free_dmatrix(A,0,gno-1,0,acols-1); + error("Malloc of b[] failed"); + } + if ((x = dvector(0,gno-1)) == NULL) { + free_dmatrix(A,0,gno-1,0,acols-1); + free_dvector(b,0,gno-1); + error("Malloc of x[] failed"); + } + + /* Stash in the mgtmp */ + m->q.ccv = ccv; + m->q.A = A; + m->q.b = b; + m->q.x = x; + m->q.acols = acols; + m->q.ixcol = ixcol; + + } else { /* re-initializing, zero matrices */ + for (i = 0; i < gno; i++) + for (k = 0; k < acols; k++) { + A[i][k] = 0.0; + } + for (i = 0; i < gno; i++) + b[i] = 0.0; + } + +#ifdef NEVER + /* Production version, without extra edge weight */ + + /* Accumulate curvature dependent factors to the triangular A matrix. */ + /* Because it's triangular, we compute and add in all the weighting */ + /* factors at and to the right of each cell. */ + + /* The ipos[] factor is to allow for the possibility that the */ + /* grid spacing may be non-uniform in the colorspace where the */ + /* function being modelled is smooth. Our curvature computation */ + /* needs to make allowsance for this fact in computing the */ + /* node value differences that equate to zero curvature. */ + /* + The old curvature fixed grid spacing equation was: + ki * (u[i-1] - 2 * u[i] + u[i+1])^2 + with derivatives wrt each node: + ki-1 * 1 * 2 * u[i-1] + ki * -2 * 2 * u[i] + ki+1 * 1 * 2 * u[i+1] + + Allowing for scaling of each grid difference by w[i-1] and w[i], + where w[i-1] corresponds to the width of cell i-1 to i, + and w[i] corresponds to the width of cell i to i+1: + ki * (w[i-1] * (u[i-1] - u[i]) + w[i] * (u[i+1] - u[i[))^2 + = ki * (w[i-1] * u[i-1] - (w[i-1] + w[i]) * u[i]) + w[i] * u[i+1])^2 + with derivatives wrt each node: + ki-1 * w[i-1] * w[i-1] * u[i-1] + ki * -(w[i-1] + w[i]) * -(w[i-1] + w[i]) * u[i]) + ki+1 * w[i] * w[i] * u[i+1] + */ + + { /* Setting this up from scratch */ + ECOUNT(gc, MXDIDO, di, 0, gres, 0); + EC_INIT(gc); + + for (oawt = 0.0, i = 1; i < 21; i++) + oawt += wvals[i]; + oawt *= wvals[0]; + + for (i = 0; i < gno; i++) { + + for (e = 0; e < di; e++) { /* For each curvature direction */ + double w0, w1, tt; + double cw = 2.0 * m->sf.cw[e]; /* Overall curvature weight */ + cw *= s->d.vw[f]; /* Scale curvature weight for data range */ + + /* If at least two above lower edge in this dimension */ + /* Add influence on Curvature of cell below */ + if ((gc[e]-2) >= 0) { + /* double kw = cw * gp[UO_C(e,1)].k; */ /* Cell bellow k value */ + double kw = cw; + w0 = w1 = 1.0; + if (m->g.ipos[e] != NULL) { + w0 = fabs(m->g.ipos[e][gc[e]-1] - m->g.ipos[e][gc[e]-2]); + w1 = fabs(m->g.ipos[e][gc[e]-0] - m->g.ipos[e][gc[e]-1]); + tt = sqrt(w0 * w1); /* Normalise overall width weighting effect */ + w1 = tt/w1; + } + A[i][ixcol[0]] += w1 * w1 * kw; + if (ccv != NULL) + b[i] += kw * (w1) * ccv[i - gci[e]][e]; /* Curvature compensation value */ + } + /* If not one from upper or lower edge in this dimension */ + /* Add influence on Curvature of this cell */ + if ((gc[e]-1) >= 0 && (gc[e]+1) < gres[e]) { + /* double kw = cw * gp->k; */ /* This cells k value */ + double kw = cw; + w0 = w1 = 1.0; + if (m->g.ipos[e] != NULL) { + w0 = fabs(m->g.ipos[e][gc[e]-0] - m->g.ipos[e][gc[e]-1]); + w1 = fabs(m->g.ipos[e][gc[e]+1] - m->g.ipos[e][gc[e]-0]); + tt = sqrt(w0 * w1); + w0 = tt/w0; + w1 = tt/w1; + } + A[i][ixcol[0]] += -(w0 + w1) * -(w0 + w1) * kw; + A[i][ixcol[gci[e]]] += -(w0 + w1) * w1 * kw * oawt; + if (ccv != NULL) + b[i] += kw * -(w0 + w1) * ccv[i][e]; /* Curvature compensation value */ + } + /* If at least two below the upper edge in this dimension */ + /* Add influence on Curvature of cell above */ + if ((gc[e]+2) < gres[e]) { + /* double kw = cw * gp[UO_C(e,2)].k; */ /* Cell above k value */ + double kw = cw; + w0 = w1 = 1.0; + if (m->g.ipos[e] != NULL) { + w0 = fabs(m->g.ipos[e][gc[e]+1] - m->g.ipos[e][gc[e]+0]); + w1 = fabs(m->g.ipos[e][gc[e]+2] - m->g.ipos[e][gc[e]+1]); + tt = sqrt(w0 * w1); + w0 = tt/w0; + w1 = tt/w1; + } + A[i][ixcol[0]] += w0 * w0 * kw; + A[i][ixcol[gci[e]]] += w0 * -(w0 + w1) * kw; + A[i][ixcol[2 * gci[e]]] += w0 * w1 * kw; + if (ccv != NULL) + b[i] += kw * -(w0 + w1) * ccv[i][e]; /* Curvature compensation value */ + } + } + EC_INC(gc); + } + } +#endif /* NEVER */ + +#ifdef ALWAYS + /* Production version that allows for extra weight on grid edges */ + + /* Accumulate curvature dependent factors to the triangular A matrix. */ + /* Because it's triangular, we compute and add in all the weighting */ + /* factors at and to the right of each cell. */ + + /* The ipos[] factor is to allow for the possibility that the */ + /* grid spacing may be non-uniform in the colorspace where the */ + /* function being modelled is smooth. Our curvature computation */ + /* needs to make allowsance for this fact in computing the */ + /* node value differences that equate to zero curvature. */ + /* + The old curvature fixed grid spacing equation was: + ki * (u[i-1] - 2 * u[i] + u[i+1])^2 + with derivatives wrt each node: + ki-1 * 1 * 2 * u[i-1] + ki * -2 * 2 * u[i] + ki+1 * 1 * 2 * u[i+1] + + Allowing for scaling of each grid difference by w[i-1] and w[i], + where w[i-1] corresponds to the width of cell i-1 to i, + and w[i] corresponds to the width of cell i to i+1: + ki * (w[i-1] * (u[i-1] - u[i]) + w[i] * (u[i+1] - u[i[))^2 + = ki * (w[i-1] * u[i-1] - (w[i-1] + w[i]) * u[i]) + w[i] * u[i+1])^2 + with derivatives wrt each node: + ki-1 * w[i-1] * w[i-1] * u[i-1] + ki * -(w[i-1] + w[i]) * -(w[i-1] + w[i]) * u[i]) + ki+1 * w[i] * w[i] * u[i+1] + */ + { /* Setting this up from scratch */ + ECOUNT(gc, MXDIDO, di, 0, gres, 0); +#ifdef EXTRA_SURFACE_SMOOTHING +// double k0w = 4.0, k1w = 1.3333; /* Extra stiffness */ +// double k0w = 3.0, k1w = 1.26; /* Some extra stiffness */ + double k0w = 2.0, k1w = 1.15; /* A little extra stiffness */ +#else + double k0w = 1.0, k1w = 1.0; /* No extra weights */ +#endif + + EC_INIT(gc); + for (oawt = 0.0, i = 1; i < 21; i++) + oawt += wvals[i]; + oawt *= wvals[0]; + + if (final == 0) + k0w = k1w = 1.0; /* Activate extra edge smoothing on final grid ? */ + + for (i = 0; i < gno; i++) { + int k; + + /* We're creating the equation cooeficients for solving the */ + /* partial derivative equation w.r.t. node point i. */ + /* Due to symetry in the smoothness interactions, only */ + /* the triangle cooeficients of neighbour nodes is needed. */ + for (e = 0; e < di; e++) { /* For each curvature direction */ + double kw, w0, w1, tt; + double cw = 2.0 * m->sf.cw[e]; /* Overall curvature weight */ + double xx = 1.0; /* Extra edge weighing */ + cw *= s->d.vw[f]; /* Scale curvature weight for data range */ + + /* weight factor for outer or 2nd outer in other dimensions */ + for (k = 0; k < di; k++) { + if (k == e) + continue; + if (gc[k] == 0 || gc[k] == (gres[k]-1)) + xx *= k0w; + else if (gc[k] == 1 || gc[k] == (gres[k]-2)) + xx *= k1w; + } + + /* If at least two above lower edge in this dimension */ + /* Add influence on Curvature of cell below */ + if ((gc[e]-2) >= 0) { + /* double kw = cw * gp[-gc[e]].k; */ /* Cell bellow k value */ + kw = cw * xx; + w0 = w1 = 1.0; + if (m->g.ipos[e] != NULL) { + w0 = fabs(m->g.ipos[e][gc[e]-1] - m->g.ipos[e][gc[e]-2]); + w1 = fabs(m->g.ipos[e][gc[e]-0] - m->g.ipos[e][gc[e]-1]); + tt = sqrt(w0 * w1); /* Normalise overall width weighting effect */ + w1 = tt/w1; + } + if ((gc[e]-2) == 0 || (gc[e]+0) == (gres[e]-1)) + kw *= k0w; + else if ((gc[e]-2) == 1 || (gc[e]-0) == (gres[e]-2)) + kw *= k1w; + A[i][ixcol[0]] += kw * (w1) * w1; + if (ccv != NULL) { +//printf("~1 tweak b[%d] by %e\n",i,kw * (w1) * ccv[i - gci[e]][e]); + b[i] += kw * (w1) * ccv[i - gci[e]][e]; /* Curvature compensation value */ + } + } + /* If not one from upper or lower edge in this dimension */ + /* Add influence on Curvature of this cell */ + if ((gc[e]-1) >= 0 && (gc[e]+1) < gres[e]) { + /* double kw = cw * gp->k; */ /* This cells k value */ + kw = cw * xx; + w0 = w1 = 1.0; + if (m->g.ipos[e] != NULL) { + w0 = fabs(m->g.ipos[e][gc[e]-0] - m->g.ipos[e][gc[e]-1]); + w1 = fabs(m->g.ipos[e][gc[e]+1] - m->g.ipos[e][gc[e]-0]); + tt = sqrt(w0 * w1); + w0 = tt/w0; + w1 = tt/w1; + } + if ((gc[e]-1) == 0 || (gc[e]+1) == (gres[e]-1)) + kw *= k0w; + else if ((gc[e]-1) == 1 || (gc[e]+1) == (gres[e]-2)) + kw *= k1w; + A[i][ixcol[0]] += kw * -(w0 + w1) * -(w0 + w1); + A[i][ixcol[gci[e]]] += kw * -(w0 + w1) * w1 * oawt; + if (ccv != NULL) { +//printf("~1 tweak b[%d] by %e\n",i, kw * -(w0 + w1) * ccv[i][e]); + b[i] += kw * -(w0 + w1) * ccv[i][e]; /* Curvature compensation value */ + } + } + /* If at least two below the upper edge in this dimension */ + /* Add influence on Curvature of cell above */ + if ((gc[e]+2) < gres[e]) { + /* double kw = cw * gp[gc[e]].k; */ /* Cell above k value */ + kw = cw * xx; + w0 = w1 = 1.0; + if (m->g.ipos[e] != NULL) { + w0 = fabs(m->g.ipos[e][gc[e]+1] - m->g.ipos[e][gc[e]+0]); + w1 = fabs(m->g.ipos[e][gc[e]+2] - m->g.ipos[e][gc[e]+1]); + tt = sqrt(w0 * w1); + w0 = tt/w0; + w1 = tt/w1; + } + if ((gc[e]+0) == 0 || (gc[e]+2) == (gres[e]-1)) + kw *= k0w; + else if ((gc[e]+0) == 1 || (gc[e]+2) == (gres[e]-2)) + kw *= k1w; + A[i][ixcol[0]] += kw * (w0) * w0; + A[i][ixcol[gci[e]]] += kw * (w0) * -(w0 + w1); + A[i][ixcol[2 * gci[e]]] += kw * (w0) * w1; + if (ccv != NULL) { +//printf("~1 tweak b[%d] by %e\n",i, kw * (w0) * ccv[i + gci[e]][e]); + b[i] += kw * (w0) * ccv[i + gci[e]][e]; /* Curvature compensation value */ + } + } + } + EC_INC(gc); + } + } +#endif /* ALWAYS */ + +#ifdef DEBUG + printf("After adding curvature equations:\n"); + for (i = 0; i < gno; i++) { + printf("b[%d] = %f\n",i,b[i]); + for (k = 0; k < acols; k++) { + printf("A[%d][%d] = %f\n",i,k,A[i][k]); + } + printf("\n"); + } +#endif /* DEBUG */ + + nbsum = 0.0; /* Zero sum of b[] squared */ + +#ifdef ALWAYS + /* Accumulate weak default function factors. These are effectively a */ + /* weak "data point" exactly at each grid point. */ + /* (Note we're not currently doing this in a cache friendly order, */ + /* and we're calling the function once for each output component..) */ + if (s->dfunc != NULL) { /* Setting this up from scratch */ + double iv[MXDI], ov[MXDO]; + ECOUNT(gc, MXDIDO, di, 0, gres, 0); + EC_INIT(gc); + for (i = 0; i < gno; i++) { + double d, tt; + + /* Get weak default function value for this grid point */ + for (e = 0; e < s->di; e++) + iv[e] = m->g.l[e] + gc[e] * m->g.w[e]; /* Input sample values */ + s->dfunc(s->dfctx, ov, iv); + + /* Compute values added to matrix */ + d = 2.0 * m->wdfw; + tt = d * ov[f]; /* Change in data component */ + nbsum += (2.0 * b[i] + tt) * tt; /* += (b[i] + tt)^2 - b[i]^2 */ + b[i] += tt; /* New data component value */ + A[i][0] += d; /* dui component to itself */ + + EC_INC(gc); + } + +#ifdef DEBUG + printf("After adding weak default equations:\n"); + for (i = 0; i < gno; i++) { + printf("b[%d] = %f\n",i,b[i]); + for (k = 0; k < acols; k++) { + printf("A[%d][%d] = %f\n",i,k,A[i][k]); + } + printf("\n"); + } +#endif /* DEBUG */ + + } +#endif /* ALWAYS */ + +#ifdef ALWAYS + /* Accumulate data point dependent factors */ + for (n = 0; n < dno; n++) { /* Go through all the data points */ + int j,k; + int bp = m->d[n].b; /* index to base grid point in grid points */ + + /* For each point in the cube as the base grid point, */ + /* add in the appropriate weighting for its weighted neighbors. */ + for (j = 0; j < (1 << di); j++) { /* Binary sequence */ + double d, w, tt; + int ai; + + ai = bp + m->g.hi[j]; /* A matrix index */ + + w = m->d[n].w[j]; /* Base point grid weight */ + d = 2.0 * s->d.a[n].k[f] * w; /* (2.0, w are derivative factors, k data pnt wgt) */ + tt = d * s->d.a[n].cv[f]; /* Change in (corrected) data component */ + + nbsum += (2.0 * b[ai] + tt) * tt; /* += (b[ai] + tt)^2 - b[ai]^2 */ + b[ai] += tt; /* New data component value */ + A[ai][0] += d * w; /* dui component to itself */ + + /* For all the other simplex points ahead of this one, */ + /* add in linear interpolation derivative weightings */ + for (k = j+1; k < (1 << di); k++) { /* Binary sequence */ + int ii; + ii = ixcol[m->g.hi[k] - m->g.hi[j]]; /* A matrix column index */ + A[ai][ii] += d * m->d[n].w[k]; /* dui component due to ui+1 */ + } + } + } + + /* Compute norm of b[] from sum of squares */ + nbsum = sqrt(nbsum); + if (nbsum < 1e-4) + nbsum = 1e-4; + m->q.normb = nbsum; + +#endif /* ALWAYS */ + +#ifdef DEBUG + printf("After adding data point equations:\n"); + for (i = 0; i < gno; i++) { + printf("b[%d] = %f\n",i,b[i]); + for (k = 0; k < acols; k++) { + printf("A[%d][%d] = %f\n",i,k,A[i][k]); + } + printf("\n"); + } +#endif /* DEBUG */ + +// exit(0); +} + + +/* Given that we've done a complete fit at the current resolution, */ +/* allocate and compute the curvature error of each grid point and put it in */ +/* s->g.ccv[gno][di] */ +static void comp_ccv( + mgtmp *m /* Solution to use */ +) { + rspl *s = m->s; + int gno = m->g.no, *gres = m->g.res, *gci = m->g.ci; + int di = s->di; + double *x = m->q.x; /* Grid solution values */ + int f = m->f; /* Output dimensions being worked on */ + int e, i; + + ECOUNT(gc, MXDIDO, di, 0, gres, 0); + EC_INIT(gc); + + if (s->g.ccv == NULL) { + if ((s->g.ccv = dmatrixz(0, gno-1, 0, di-1)) == NULL) { + error("Malloc of ccv[] failed with [%d][%d]",di,gno); + } + } + + for (i = 0; i < gno; i++) { + for (e = 0; e < di; e++) { /* For each curvature direction */ + double w0, w1, tt; + + s->g.ccv[i][e] = 0.0; /* Default value */ + + /* If not one from upper or lower edge in this dimension */ + if ((gc[e]-1) >= 0 && (gc[e]+1) < gres[e]) { + /* double kw = cw * gp->k; */ /* This cells k value */ + w0 = w1 = 1.0; + if (m->g.ipos[e] != NULL) { + w0 = fabs(m->g.ipos[e][gc[e]-0] - m->g.ipos[e][gc[e]-1]); + w1 = fabs(m->g.ipos[e][gc[e]+1] - m->g.ipos[e][gc[e]-0]); + tt = sqrt(w0 * w1); + w0 = tt/w0; + w1 = tt/w1; + } + s->g.ccv[i][e] += w0 * x[i - gci[e]]; + s->g.ccv[i][e] += -(w0 + w1) * x[i]; + s->g.ccv[i][e] += w1 * x[i + gci[e]]; + } +//printf("~1 computing ccv for node %d is %f\n",i,s->g.ccv[i][0]); + } + EC_INC(gc); + } +} + +/* Down sample the curvature compensation values in s->g.ccv to */ +/* a given solution. Allocate the m->q.ccv if necessary. */ +static void init_ccv( + mgtmp *m /* Destination */ +) { + rspl *s = m->s; + int f = m->f; /* Output dimensions being worked on */ + int di = s->di; + int gno = m->g.no; + int gres1_1[MXDI]; /* Destination */ + int gres2_1[MXDI]; /* Source */ + double scale[MXDI]; /* ccv scale factor */ + int e, n; + ECOUNT(gc, MXDIDO, di, 0, m->g.res, 0); /* Counter for output points */ + + for (e = 0; e < di; e++) { + gres1_1[e] = m->g.res[e]-1; + gres2_1[e] = s->g.res[e]-1; + } + + if (m->q.ccv == NULL) { + if ((m->q.ccv = dmatrixz(0, gno-1, 0, di-1)) == NULL) { + error("Malloc of ccv[] failed with [%d][%d]",di,gno); + } + } + + /* Compute the scale factor to compensate for the grid resolution */ + /* effect on the grid difference values. */ + for (e = 0; e < di; e++) { + double rsm_s, rsm_d; + + if (s->symdom) { /* Relative final grid size */ + rsm_s = s->g.res[e]; + rsm_d = m->g.res[e]; + } else { /* Relative mean final grid size */ + rsm_s = s->g.mres; + rsm_d = m->g.mres; + } + + rsm_s = pow((rsm_s-1.0), 2.0); /* Geometric resolution factor for smooth surfaces */ + rsm_d = pow((rsm_d-1.0), 2.0); /* (It's ^2 rather than ^4 as it's is before squaring) */ + + scale[e] = rsm_s/rsm_d; + } + + /* Point sampling is probably not the ideal way of down sampling, */ + /* but it's easy, and won't be too bad if the s->g.ccv has been */ + /* low pass filtered. */ + + /* For all grid ccv's */ + EC_INIT(gc); + for (n = 0; n < gno; n++) { + double we[MXRI]; /* 1.0 - Weight in each dimension */ + double gw[POW2MXRI]; /* weight for each grid cube corner */ + int ix; /* Index of source ccv grid cube base */ + + /* Figure out which grid cell the point falls into */ + { + double t; + int mi; + ix = 0; + for (e = 0; e < di; e++) { + t = ((double)gc[e]/(double)gres1_1[e]) * (double)gres2_1[e]; + mi = (int)floor(t); /* Grid coordinate */ + if (mi < 0) /* Limit to valid cube base index range */ + mi = 0; + else if (mi >= gres2_1[e]) + mi = gres2_1[e]-1; + ix += mi * s->g.ci[e]; /* Add Index offset for grid cube base in dimen */ + we[e] = t - (double)mi; /* 1.0 - weight */ + } + } + + /* Compute corner weights needed for interpolation */ + { + int i, g; + gw[0] = 1.0; + for (e = 0, g = 1; e < di; g *= 2, e++) { + for (i = 0; i < g; i++) { + gw[g+i] = gw[i] * we[e]; + gw[i] *= (1.0 - we[e]); + } + } + } + + /* Compute the output values */ + { + int i; + for (e = 0; e < di; e++) + m->q.ccv[n][e] = 0.0; /* Zero output value */ + for (i = 0; i < (1 << di); i++) { /* For all corners of cube */ + int oix = ix + s->g.hi[i]; + for (e = 0; e < di; e++) + m->q.ccv[n][e] += gw[i] * s->g.ccv[oix][e]; + } + /* Rescale curvature for grid spacing */ + for (e = 0; e < di; e++) + m->q.ccv[n][e] *= scale[e]; +//printf("~1 downsampling ccv for node %d is %f\n",n,m->q.ccv[n][0]); + } + EC_INC(gc); + } +} + +/* Apply a gaussian filter to the curvature compensation values */ +/* s->g.ccv[gno][di], to apply smoothing. */ +static void filter_ccv( + rspl *s, + double stdev /* Standard deviation diameter of filter (in input) */ + /* 1.0 = grid width */ +) { + int k, e, ee, i, j, di = s->di; + int gno = s->g.no, *gres = s->g.res, *gci = s->g.ci; + double *_fkern[MXDI], *fkern[MXDI]; /* Filter kernels */ + int kmin[MXDI], kmax[MXDI]; /* Kernel index range (inclusive) */ + double *_row, *row; /* Extended copy of each row processed */ + +//printf("Doing filter stdev %f\n",stdev); + +//printf("~1 bres = %d, index %d to %d\n",s->g.bres,-s->g.bres+1,s->g.bres+s->g.bres-1); + if ((_row = (double *) malloc(sizeof(double) * (s->g.bres * 3 - 2))) == NULL) + error("rspl malloc failed - ccv row copy"); + row = _row + s->g.bres-1; /* Allow +/- gres-1 */ + + /* Compute the kernel weightings for the given stdev */ + for (ee = 0; ee < di; ee++) { /* For each dimension direction */ + int cres; /* Current res */ + double k1, k2, tot; + +//printf("Filter along dim %d:\n",ee); + if ((_fkern[ee] = (double *) malloc(sizeof(double) * (gres[ee] * 2 - 1))) == NULL) + error("rspl malloc failed - ccv filter kernel"); + fkern[ee] = _fkern[ee] + gres[ee]-1; /* node of interest at center */ + + /* Take gaussian constants out of the loop */ + k2 = 1.0 / (2.0 * pow(fabs(stdev), TWOPASSORDER)); + k1 = k2 / 3.1415926; + + /* Comute the range needed */ + if (s->symdom) { + cres = gres[ee]; + } else { + cres = s->g.mres; + } + kmin[ee] = (int)floor(-5.0 * stdev * (cres-1.0)); + kmax[ee] = (int)ceil(5.0 * stdev * (cres-1.0)); + + if (kmin[ee] < (-gres[ee]+1)) + kmin[ee] = -gres[ee]+1; + else if (kmin[ee] > -1) + kmin[ee] = -1; + if (kmax[ee] > (gres[ee]-1)) + kmax[ee] = gres[ee]-1; + else if (kmax[ee] < 1) + kmax[ee] = 1; +//printf("kmin = %d, kmax = %d\n",kmin[ee], kmax[ee]); + + for (tot = 0.0, i = kmin[ee]; i <= kmax[ee]; i++) { + double fi = (double)i; + + /* Do a discrete integration of the gassian function */ + /* to compute discrete weightings */ + fkern[ee][i] = 0.0; + for (k = -4; k < 5; k++) { + double oset = (fi + k/9.0)/(cres-1.0); + double val; + + val = k1 * exp(-k2 * pow(fabs(oset), TWOPASSORDER)); + fkern[ee][i] += val; + tot += val; + } + } + /* Normalize the sum */ + for (tot = 1.0/tot, i = kmin[ee]; i <= kmax[ee]; i++) + fkern[ee][i] *= tot; +//printf("Filter cooefs:\n"); +//for (i = kmin[ee]; i <= kmax[ee]; i++) +//printf("%d: %e\n",i,fkern[ee][i]); + } + + for (k = 0; k < di; k++) { /* For each curvature direction */ + for (ee = 0; ee < di; ee++) { /* For each dimension direction */ + int tgres[MXDI-1]; + +//printf("~1 Filtering curv dir %d, dim dir %d\n",k,ee); + /* Setup counters for scanning through all other dimensions */ + for (j = e = 0; e < di; e++) { + if (e == ee) + continue; + tgres[j++] = gres[e]; + } + /* For each row of this dimension */ + { + ECOUNT(gc, MXDIDO-1, di-1, 0, tgres, 0); /* Count other dimensions */ + + EC_INIT(gc); + for (; di <= 1 || !EC_DONE(gc);) { + int ix; + + /* Compute index of start of row */ + for (ix = j = e = 0; e < di; e++) { + if (e == ee) + continue; + ix += gc[j++] * gci[e]; + } + + /* Copy row to temporary array, and expand */ + /* edge values by mirroring them. */ + for (i = 0; i < gres[ee]; i++) + row[i] = s->g.ccv[ix + i * gci[ee]][k]; + for (i = kmin[ee]; i < 0; i++) + row[i] = 2.0 * row[0] - row[-i]; /* Mirror the value */ + for (i = gres[ee]-1 + kmax[ee]; i > (gres[ee]-1); i--) + row[i] = 2.0 * row[gres[ee]-1] - row[gres[ee]-1-i]; /* Mirror the value */ +//printf("~1 Row = \n"); +//for (i = kmin[ee]; i <= (gres[ee]-1 + kmax[ee]); i++) +//printf("%d: %f\n",i,row[i]); + + /* Apply the 1D convolution to the temporary array */ + /* to produce the filtered values. */ + for (i = 0; i < gres[ee]; i++) { + double fv; + + for (fv = 0.0, j = kmin[ee]; j <= kmax[ee]; j++) + fv += fkern[ee][j] * row[i + j]; + s->g.ccv[ix + i * gci[ee]][k] = fv; + } + if (di <= 1) + break; + EC_INC(gc); + } + } + } + } + + for (ee = 0; ee < di; ee++) + free(_fkern[ee] ); + free(_row); +} + +/* Given that we've done a complete fit at the current resolution, */ +/* compute the error of each data point, and then compute */ +/* a correction factor .cv[] for each point from this. */ +static void comp_extrafit_corr( + mgtmp *m /* Current resolution mgtmp */ +) { + rspl *s = m->s; + int n; + int dno = s->d.no; + int di = s->di; + double *x = m->q.x; /* Grid solution values */ + int f = m->f; /* Output dimensions being worked on */ + + /* Compute error for each data point */ + for (n = 0; n < dno; n++) { + int j; + int bp = m->d[n].b; /* index to base grid point in grid points */ + double val; /* Current interpolated value */ + double err; + double gain = 1.0; + + /* Compute the interpolated grid value for this data point */ + for (val = 0.0, j = 0; j < (1 << di); j++) { /* Binary sequence */ + val += m->d[n].w[j] * x[bp + m->g.hi[j]]; + } + + err = s->d.a[n].v[f] - val; + +#ifdef NEVER + /* Compute gain from previous move */ + if (fabs(s->d.a[n].pe[f]) > 0.001) { + gain = (val - s->d.a[n].pv[f])/s->d.a[n].pe[f]; + if (gain < 0.2) + gain = 0.2; + else if (gain > 5.0) + gain = 5.0; + gain = pow(gain, 0.6); + } else { + gain = 1.0; + } +#endif + /* Correct the target data point value by the error */ + s->d.a[n].cv[f] += err / gain; + +//printf("~1 Data point %d, v = %f, cv = %f, change = %f\n",n,s->d.a[n].v[f],s->d.a[n].cv[f],-val); +//printf("~1 Data point %d, pe = %f, change = %f, gain = %f\n",n,s->d.a[n].pe[f],val - s->d.a[n].pv[f],gain); +//printf("~1 Data point %d err = %f, target %f, was %f, now %f\n",n,err,s->d.a[n].v[f],val,s->d.a[n].cv[f]); +// s->d.a[n].pe[f] = err / gain; +// s->d.a[n].pv[f] = val; + } +} + +/* Transfer a solution from one mgtmp to another */ +/* (We assume that they are for the same problem) */ +static void init_soln( + mgtmp *m1, /* Destination */ + mgtmp *m2 /* Source */ +) { + rspl *s = m1->s; + int di = s->di; + int gno = m1->g.no; + int gres1_1[MXDI]; + int gres2_1[MXDI]; + int e, n; + ECOUNT(gc, MXDIDO, di, 0, m1->g.res, 0); /* Counter for output points */ + + for (e = 0; e < di; e++) { + gres1_1[e] = m1->g.res[e]-1; + gres2_1[e] = m2->g.res[e]-1; + } + + /* For all output grid points */ + EC_INIT(gc); + for (n = 0; n < gno; n++) { + double we[MXRI]; /* 1.0 - Weight in each dimension */ + double gw[POW2MXRI]; /* weight for each grid cube corner */ + double *gp; /* Pointer to x2[] grid cube base */ + + /* Figure out which grid cell the point falls into */ + { + double t; + int mi; + gp = m2->q.x; /* Base of solution array */ + for (e = 0; e < di; e++) { + t = ((double)gc[e]/(double)gres1_1[e]) * (double)gres2_1[e]; + mi = (int)floor(t); /* Grid coordinate */ + if (mi < 0) /* Limit to valid cube base index range */ + mi = 0; + else if (mi >= gres2_1[e]) + mi = gres2_1[e]-1; + gp += mi * m2->g.ci[e]; /* Add Index offset for grid cube base in dimen */ + we[e] = t - (double)mi; /* 1.0 - weight */ + } + } + + /* Compute corner weights needed for interpolation */ + { + int i, g; + gw[0] = 1.0; + for (e = 0, g = 1; e < di; g *= 2, e++) { + for (i = 0; i < g; i++) { + gw[g+i] = gw[i] * we[e]; + gw[i] *= (1.0 - we[e]); + } + } + } + + /* Compute the output values */ + { + int i; + m1->q.x[n] = 0.0; /* Zero output value */ + for (i = 0; i < (1 << di); i++) { /* For all corners of cube */ + m1->q.x[n] += gw[i] * gp[m2->g.hi[i]]; + } + } + EC_INC(gc); + } +} + +/* - - - - - - - - - - - - - - - - - - - -*/ + +static double one_itter1(cj_arrays *ta, double **A, double *x, double *b, double normb, + int gno, int acols, int *xcol, int di, int *gres, int *gci, + int max_it, double tol); +static void one_itter2(double **A, double *x, double *b, int gno, int acols, int *xcol, + int di, int *gres, int *gci, double ovsh); +static double soln_err(double **A, double *x, double *b, double normb, int gno, int acols, int *xcol); +static double cj_line(cj_arrays *ta, double **A, double *x, double *b, int gno, int acols, + int *xcol, int sof, int nid, int inc, int max_it, double tol); + +/* Solve scattered data to grid point fit */ +static void +solve_gres(mgtmp *m, cj_arrays *ta, double tol, int final) +{ + rspl *s = m->s; + int di = s->di; + int gno = m->g.no, *gres = m->g.res, *gci = m->g.ci; + int i; + double **A = m->q.A; /* A matrix of interpoint weights */ + int acols = m->q.acols; /* A matrix columns needed */ + int *xcol = m->q.xcol; /* A array column translation from packed to sparse index */ + double *b = m->q.b; /* b vector for RHS of simultabeous equation */ + double *x = m->q.x; /* x vector for result */ + + /* + * The regular spline fitting problem to be solved here strongly + * resembles those involved in solving partial differential equation + * problems. The scattered data points equate to boundary conditions, + * while the smoothness criteria equate to partial differential equations. + */ + + /* + * There are many approaches that can be used to solve the + * symetric positive-definite system Ax = b, where A is a + * sparse diagonal matrix with fringes. A direct method + * would be Cholesky decomposition, and this works well for + * the 1D case (no fringes), but for more than 1D, it generates + * fill-ins between the fringes. Given that the widest spaced + * fringes are at 2 * gres ^ (dim-1) spacing, this leads + * to an unacceptable storage requirement for A, at the resolutions + * and dimensions needed in color correction. + * + * The approaches that minimise A storage are itterative schemes, + * such as Gauss-Seidel relaxation, or conjugate-gradient methods. + * + * There are two methods allowed for below, depending on the + * value of JITTERS. + * If JITTERS is non-zero, then there will be JITTERS passes of + * a combination of multi-grid, Gauss-Seidel relaxation, + * and conjugate gradient. + * + * The outermost loop will use a series of grid resolutions that + * approach the final resolution. Each solution gives us a close + * starting point for the next higher resolution. + * + * The middle loop, uses Gauss-Seidel relaxation to approach + * the desired solution at a given grid resolution. + * + * The inner loop can use the conjugate-gradient method to solve + * a line of values simultaniously in a particular dimension. + * All the lines in each dimension are processed in red/black order + * to optimise convergence rate. + * + * (conjugate gradient seems to be slower than pure relaxation, so + * it is not currently used.) + * + * If JITTERS is zero, then a pure Gauss-Seidel relaxation approach + * is used, with the solution elements being updated in RED-BLACK + * order. Experimentation seems to prove that this is the overall + * fastest approach. + * + * The equation Ax = b solves the fitting for the derivative of + * the fit error == 0. The error metric used is the norm(b - A * x)/norm(b). + * I'm not sure if that is the best metric for the problem at hand though. + * b[] is only non-zero where there are scattered data points (or a weak + * default function), so the error metric is being normalised to number + * of scattered data points. Perhaps normb should always be == 1.0 ? + * + * The norm(b - A * x) is effectively the RMS error of the derivative + * fit, so it balances average error and peak error, but another + * approach might be to work on peak error, and apply Gauss-Seidel relaxation + * to grid points in peak error order (ie. relax the top 10% of grid + * points each itteration round) ?? + * + */ + + /* Note that we process the A[][] sparse columns in compact form */ + +#ifdef DEBUG_PROGRESS + printf("Target tol = %f\n",tol); +#endif + /* If the number of point is small, or it is just one */ + /* dimensional, solve it more directly. */ + if (m->g.bres <= 4) { /* Don't want to multigrid below this */ + /* Solve using just conjugate-gradient */ + cj_line(ta, A, x, b, gno, acols, xcol, 0, gno, 1, 10 * gno, tol); +#ifdef DEBUG_PROGRESS + printf("Solved at res %d using conjugate-gradient\n",gres[0]); +#endif + } else { /* Try relax till done */ + double lerr = 1.0, err = tol * 10.0, derr, ovsh = 1.0; + int jitters = JITTERS; + + /* Compute an initial error */ + err = soln_err(A, x, b, m->q.normb, gno, acols, xcol); +#ifdef DEBUG_PROGRESS + printf("Initial error res %d is %f\n",gres[0],err); +#endif + + for (i = 0; i < 500; i++) { + if (i < jitters) { /* conjugate-gradient and relaxation */ + lerr = err; + err = one_itter1(ta, A, x, b, m->q.normb, gno, acols, xcol, di, gres, gci, (int)m->g.mres, tol * CONJ_TOL); + + derr = err/lerr; + if (derr > 0.8) /* We're not improving using itter1() fast enough */ + jitters = i-1; /* Move to just relaxation */ +#ifdef DEBUG_PROGRESS + printf("one_itter1 at res %d has err %f, derr %f\n",gres[0],err,derr); +#endif + } else { /* Use just relaxation */ + int j, ni = 0; /* Number of itters */ + if (i == jitters) { /* Never done a relaxation itter before */ + ni = 1; /* Just do one, to get estimate */ + } else { + ni = (int)(((log(tol) - log(err)) * (double)ni)/(log(err) - log(lerr))); + if (ni < 1) + ni = 1; /* Minimum of 1 at a time */ + else if (ni > MAXNI) + ni = MAXNI; /* Maximum of MAXNI at a time */ + } + for (j = 0; j < ni; j++) /* Do them in groups for efficiency */ + one_itter2(A, x, b, gno, acols, xcol, di, gres, gci, ovsh); + lerr = err; + err = soln_err(A, x, b, m->q.normb, gno, acols, xcol); + derr = pow(err/lerr, 1.0/ni); +#ifdef DEBUG_PROGRESS + printf("%d * one_itter2 at res %d has err %f, derr %f\n",ni,gres[0],err,derr); +#endif + if (s->verbose) { + printf("*"); fflush(stdout); + } + } +#ifdef OVERRLX + if (derr > 0.7 && derr < 1.0) { + ovsh = 1.0 * derr/0.7; + } +#endif /* OVERRLX */ + if (err < tol || (derr <= 1.0 && derr > TOL_IMP)) /* within tol or < tol_improvement */ + break; + } + } +} + +/* - - - - - - - - - - - - - - - - - - - - - - - -*/ +/* Do one relaxation itteration of applying */ +/* cj_line to solve each line of x[] values, in */ +/* each line of each dimension. Return the */ +/* current solution error. */ +static double +one_itter1( + cj_arrays *ta, /* cj_line temporary arrays */ + double **A, /* Sparse A[][] matrix */ + double *x, /* x[] matrix */ + double *b, /* b[] matrix */ + double normb, /* Norm of b[] */ + int gno, /* Total number of unknowns */ + int acols, /* Use colums in A[][] */ + int *xcol, /* sparse expansion lookup array */ + int di, /* number of dimensions */ + int *gres, /* Grid resolution */ + int *gci, /* Array increment for each dimension */ + int max_it, /* maximum number of itterations to use (min gres) */ + double tol /* Tollerance to solve line */ +) { + int e,d; + + /* For each dimension */ + for (d = 0; d < di; d++) { + int ld = d == 0 ? 1 : 0; /* lowest dim */ + int sof, gc[MXRI]; + +//printf("~1 doing one_itter1 for dim %d\n",d); + for (e = 0; e < di; e++) + gc[e] = 0; /* init coords */ + + /* Until we've done all lines in direction d, */ + /* processed in red/black order */ + for (sof = 0, e = 0; e < di;) { + + /* Solve a line */ +//printf("~~solve line start %d, inc %d, len %d\n",sof,gci[d],gres[d]); + cj_line(ta, A, x, b, gno, acols, xcol, sof, gres[d], gci[d], max_it, tol); + + /* Increment index */ + for (e = 0; e < di; e++) { + if (e == d) /* Don't go in direction d */ + continue; + if (e == ld) { + gc[e] += 2; /* Inc coordinate */ + sof += 2 * gci[e]; /* Track start point */ + } else { + gc[e] += 1; /* Inc coordinate */ + sof += 1 * gci[e]; /* Track start point */ + } + if (gc[e] < gres[e]) + break; /* No carry */ + gc[e] -= gres[e]; /* Reset coord */ + sof -= gres[e] * gci[e]; /* Track start point */ + + if ((gres[e] & 1) == 0) { /* Compensate for odd grid */ + if ((gc[ld] & 1) == 1) { + gc[ld] -= 1; /* XOR lsb */ + sof -= gci[ld]; + } else { + gc[ld] += 1; + sof += gci[ld]; + } + } + } + /* Stop on reaching 0 */ + for(e = 0; e < di; e++) + if (gc[e] != 0) + break; + } + } + + return soln_err(A, x, b, normb, gno, acols, xcol); +} + +/* - - - - - - - - - - - - - - - - - - - - - - - -*/ +/* Do one relaxation itteration of applying */ +/* direct relaxation to x[] values, in */ +/* red/black order */ +static void +one_itter2( + double **A, /* Sparse A[][] matrix */ + double *x, /* x[] matrix */ + double *b, /* b[] matrix */ + int gno, /* Total number of unknowns */ + int acols, /* Use colums in A[][] */ + int *xcol, /* sparse expansion lookup array */ + int di, /* number of dimensions */ + int *gres, /* Grid resolution */ + int *gci, /* Array increment for each dimension */ + double ovsh /* Overshoot to use, 1.0 for none */ +) { + int e,i,k; + int gc[MXRI]; + + for (i = e = 0; e < di; e++) + gc[e] = 0; /* init coords */ + + for (e = 0; e < di;) { + int k0,k1,k2,k3; + double sm = 0.0; + + /* Right of diagonal in 4's */ + for (k = 1, k3 = i+xcol[k+3]; (k+3) < acols && k3 < gno; k += 4, k3 = i+xcol[k+3]) { + k0 = i + xcol[k+0]; + k1 = i + xcol[k+1]; + k2 = i + xcol[k+2]; + sm += A[i][k+0] * x[k0]; + sm += A[i][k+1] * x[k1]; + sm += A[i][k+2] * x[k2]; + sm += A[i][k+3] * x[k3]; + } + /* Finish any remaining */ + for (k3 = i + xcol[k]; k < acols && k3 < gno; k++, k3 = i + xcol[k]) + sm += A[i][k] * x[k3]; + + /* Left of diagonal in 4's */ + /* (We take advantage of the symetry: what would be in the row */ + /* to the left is repeated in the column above.) */ + for (k = 1, k3 = i-xcol[k+3]; (k+3) < acols && k3 >= 0; k += 4, k3 = i-xcol[k+3]) { + k0 = i-xcol[k+0]; + k1 = i-xcol[k+1]; + k2 = i-xcol[k+2]; + sm += A[k0][k+0] * x[k0]; + sm += A[k1][k+1] * x[k1]; + sm += A[k2][k+2] * x[k2]; + sm += A[k3][k+3] * x[k3]; + } + /* Finish any remaining */ + for (k3 = i-xcol[k]; k < acols && k3 >= 0; k++, k3 = i-xcol[k]) + sm += A[k3][k] * x[k3]; + +// x[i] = (b[i] - sm)/A[i][0]; + x[i] += ovsh * ((b[i] - sm)/A[i][0] - x[i]); + +#ifdef RED_BLACK + /* Increment index */ + for (e = 0; e < di; e++) { + if (e == 0) { + gc[0] += 2; /* Inc coordinate by 2 */ + i += 2; /* Track start point */ + } else { + gc[e] += 1; /* Inc coordinate */ + i += gci[e]; /* Track start point */ + } + if (gc[e] < gres[e]) + break; /* No carry */ + gc[e] -= gres[e]; /* Reset coord */ + i -= gres[e] * gci[e]; /* Track start point */ + + if ((gres[e] & 1) == 0) { /* Compensate for odd grid */ + gc[0] ^= 1; /* XOR lsb */ + i ^= 1; + } + } + /* Stop on reaching 0 */ + for(e = 0; e < di; e++) + if (gc[e] != 0) + break; +#else + if (++i >= gno) + break; +#endif + } +} + +/* - - - - - - - - - - - - - - - - - - - - - - - -*/ +/* This function returns the current solution error. */ +static double +soln_err( + double **A, /* Sparse A[][] matrix */ + double *x, /* x[] matrix */ + double *b, /* b[] matrix */ + double normb, /* Norm of b[] */ + int gno, /* Total number of unknowns */ + int acols, /* Use colums in A[][] */ + int *xcol /* sparse expansion lookup array */ +) { + int i, k; + double resid; + + /* Compute norm of b - A * x */ + resid = 0.0; + for (i = 0; i < gno; i++) { + int k0,k1,k2,k3; + double sm = 0.0; + + /* Diagonal and to right in 4's */ + for (k = 0, k3 = i+xcol[k+3]; (k+3) < acols && k3 < gno; k += 4, k3 = i+xcol[k+3]) { + k0 = i + xcol[k+0]; + k1 = i + xcol[k+1]; + k2 = i + xcol[k+2]; + sm += A[i][k+0] * x[k0]; + sm += A[i][k+1] * x[k1]; + sm += A[i][k+2] * x[k2]; + sm += A[i][k+3] * x[k3]; + } + /* Finish any remaining */ + for (k3 = i + xcol[k]; k < acols && k3 < gno; k++, k3 = i + xcol[k]) + sm += A[i][k] * x[k3]; + + /* Left of diagonal in 4's */ + /* (We take advantage of the symetry: what would be in the row */ + /* to the left is repeated in the column above.) */ + for (k = 1, k3 = i-xcol[k+3]; (k+3) < acols && k3 >= 0; k += 4, k3 = i-xcol[k+3]) { + k0 = i-xcol[k+0]; + k1 = i-xcol[k+1]; + k2 = i-xcol[k+2]; + sm += A[k0][k+0] * x[k0]; + sm += A[k1][k+1] * x[k1]; + sm += A[k2][k+2] * x[k2]; + sm += A[k3][k+3] * x[k3]; + } + /* Finish any remaining */ + for (k3 = i-xcol[k]; k < acols && k3 >= 0; k++, k3 = i-xcol[k]) + sm += A[k3][k] * x[k3]; + + sm = b[i] - sm; + resid += sm * sm; + } + resid = sqrt(resid); + + return resid/normb; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - -*/ + +/* Init temporary vectors */ +static void init_cj_arrays(cj_arrays *ta) { + memset((void *)ta, 0, sizeof(cj_arrays)); +} + +/* Alloc, or re-alloc temporary vectors */ +static void realloc_cj_arrays(cj_arrays *ta, int nid) { + + if (nid > ta->l_nid) { + if (ta->l_nid > 0) { + free_dvector(ta->z,0,ta->l_nid); + free_dvector(ta->r,0,ta->l_nid); + free_dvector(ta->q,0,ta->l_nid); + free_dvector(ta->xx,0,ta->l_nid); + free_dvector(ta->n,0,ta->l_nid); + } + if ((ta->n = dvector(0,nid)) == NULL) + error("Malloc of n[] failed"); + if ((ta->z = dvector(0,nid)) == NULL) + error("Malloc of z[] failed"); + if ((ta->xx = dvector(0,nid)) == NULL) + error("Malloc of xx[] failed"); + if ((ta->q = dvector(0,nid)) == NULL) + error("Malloc of q[] failed"); + if ((ta->r = dvector(0,nid)) == NULL) + error("Malloc of r[] failed"); + ta->l_nid = nid; + } +} + +/* De-alloc temporary vectors */ +static void free_cj_arrays(cj_arrays *ta) { + + if (ta->l_nid > 0) { + free_dvector(ta->z,0,ta->l_nid); + free_dvector(ta->r,0,ta->l_nid); + free_dvector(ta->q,0,ta->l_nid); + free_dvector(ta->xx,0,ta->l_nid); + free_dvector(ta->n,0,ta->l_nid); + } +} + + +/* This function applies the conjugate gradient */ +/* algorithm to completely solve a line of values */ +/* in one of the dimensions of the grid. */ +/* Return the normalised tollerance achieved. */ +/* This is used by an outer relaxation algorithm */ +static double +cj_line( + cj_arrays *ta, /* Temporary array data */ + double **A, /* Sparse A[][] matrix */ + double *x, /* x[] matrix */ + double *b, /* b[] matrix */ + int gno, /* Total number of unknowns */ + int acols, /* Use colums in A[][] */ + int *xcol, /* sparse expansion lookup array */ + int sof, /* start offset of x[] to be found */ + int nid, /* Number in dimension */ + int inc, /* Increment to move in lines dimension */ + int max_it, /* maximum number of itterations to use (min nid) */ + double tol /* Normalised tollerance to stop on */ +) { + int i, ii, k, it; + double sm; + double resid; + double alpha, rho = 0.0, rho_1 = 0.0; + double normb; + int eof = sof + nid * inc; /* End offset */ + + /* Alloc, or re-alloc temporary vectors */ + realloc_cj_arrays(ta, nid); + + /* Compute initial norm of b[] */ + for (sm = 0.0, ii = sof; ii < eof; ii += inc) + sm += b[ii] * b[ii]; + normb = sqrt(sm); + if (normb == 0.0) + normb = 1.0; + + /* Compute r = b - A * x */ + for (i = 0, ii = sof; i < nid; i++, ii += inc) { + int k0,k1,k2,k3; + sm = 0.0; + + /* Diagonal and to right in 4's */ + for (k = 0, k3 = ii+xcol[k+3]; (k+3) < acols && k3 < gno; k += 4, k3 = ii+xcol[k+3]) { + k0 = ii + xcol[k+0]; + k1 = ii + xcol[k+1]; + k2 = ii + xcol[k+2]; + sm += A[ii][k+0] * x[k0]; + sm += A[ii][k+1] * x[k1]; + sm += A[ii][k+2] * x[k2]; + sm += A[ii][k+3] * x[k3]; + } + /* Finish any remaining */ + for (k3 = ii + xcol[k]; k < acols && k3 < gno; k++, k3 = ii + xcol[k]) + sm += A[ii][k] * x[k3]; + + /* Left of diagonal in 4's */ + /* (We take advantage of the symetry: what would be in the row */ + /* to the left is repeated in the column above.) */ + for (k = 1, k3 = ii-xcol[k+3]; (k+3) < acols && k3 >= 0; k += 4, k3 = ii-xcol[k+3]) { + k0 = ii-xcol[k+0]; + k1 = ii-xcol[k+1]; + k2 = ii-xcol[k+2]; + sm += A[k0][k+0] * x[k0]; + sm += A[k1][k+1] * x[k1]; + sm += A[k2][k+2] * x[k2]; + sm += A[k3][k+3] * x[k3]; + } + /* Finish any remaining */ + for (k3 = ii-xcol[k]; k < acols && k3 >= 0; k++, k3 = ii-xcol[k]) + sm += A[k3][k] * x[k3]; + + ta->r[i] = b[ii] - sm; + } + + /* Transfer the x[] values we are trying to solve into */ + /* temporary xx[]. The values of interest in x[] will be */ + /* used to hold the p[] values, so that q = A * p can be */ + /* computed in the context of the x[] values we are not */ + /* trying to solve. */ + /* We also zero out p[] (== x[] in range), to compute n[]. */ + /* n[] is used to normalize the q = A * p calculation. If we */ + /* were solving all x[], then q = A * p will be 0 for p = 0. */ + /* Since we are only solving some x[], this will not be true. */ + /* We compensate for this by computing q = A * p - n */ + /* (Note that n[] could probably be combined with b[]) */ + + for (i = 0, ii = sof; i < nid; i++, ii += inc) { + ta->xx[i] = x[ii]; + x[ii] = 0.0; + } + /* Compute n = A * 0 */ + for (i = 0, ii = sof; i < nid; i++, ii += inc) { + sm = 0.0; + for (k = 0; k < acols && (ii+xcol[k]) < gno; k++) + sm += A[ii][k] * x[ii+xcol[k]]; /* Diagonal and to right */ + for (k = 1; k < acols && (ii-xcol[k]) >= 0; k++) + sm += A[ii-xcol[k]][k] * x[ii-xcol[k]]; /* Left of diagonal */ + ta->n[i] = sm; + } + + /* Compute initial error = norm of r[] */ + for (sm = 0.0, i = 0; i < nid; i++) + sm += ta->r[i] * ta->r[i]; + resid = sqrt(sm)/normb; + + /* Initial conditions don't need improvement */ + if (resid <= tol) { + tol = resid; + max_it = 0; + } + + for (it = 1; it <= max_it; it++) { + + /* Aproximately solve for z[] given r[], */ + /* and also compute rho = r.z */ + for (rho = 0.0, i = 0, ii = sof; i < nid; i++, ii += inc) { + sm = A[ii][0]; + ta->z[i] = sm != 0.0 ? ta->r[i] / sm : ta->r[i]; /* Simple aprox soln. */ + rho += ta->r[i] * ta->z[i]; + } + + if (it == 1) { + for (i = 0, ii = sof; i < nid; i++, ii += inc) + x[ii] = ta->z[i]; + } else { + sm = rho / rho_1; + for (i = 0, ii = sof; i < nid; i++, ii += inc) + x[ii] = ta->z[i] + sm * x[ii]; + } + /* Compute q = A * p - n, */ + /* and also alpha = p.q */ + for (alpha = 0.0, i = 0, ii = sof; i < nid; i++, ii += inc) { + sm = A[ii][0] * x[ii]; + for (k = 1; k < acols; k++) { + int pxk = xcol[k]; + int nxk = ii-pxk; + pxk += ii; + if (pxk < gno) + sm += A[ii][k] * x[pxk]; + if (nxk >= 0) + sm += A[nxk][k] * x[nxk]; + } + ta->q[i] = sm - ta->n[i]; + alpha += ta->q[i] * x[ii]; + } + + if (alpha != 0.0) + alpha = rho / alpha; + else + alpha = 0.5; /* ?????? */ + + /* Adjust soln and residual vectors, */ + /* and also norm of r[] */ + for (resid = 0.0, i = 0, ii = sof; i < nid; i++, ii += inc) { + ta->xx[i] += alpha * x[ii]; + ta->r[i] -= alpha * ta->q[i]; + resid += ta->r[i] * ta->r[i]; + } + resid = sqrt(resid)/normb; + + /* If we're done as far as we want */ + if (resid <= tol) { + tol = resid; + max_it = it; + break; + } + rho_1 = rho; + } + /* Substitute solution xx[] back into x[] */ + for (i = 0, ii = sof; i < nid; i++, ii += inc) + x[ii] = ta->xx[i]; + +// printf("~~ CJ Itters = %d, tol = %f\n",max_it,tol); + return tol; +} + +/* ============================================ */ + + + + + + + + + + + + + + + diff --git a/rspl/scat2.c b/rspl/scat2.c new file mode 100644 index 0000000..7579366 --- /dev/null +++ b/rspl/scat2.c @@ -0,0 +1,233 @@ + +/* + * Argyll Color Correction System + * Multi-dimensional multilevel spline data fitter + * mlbs base version. + * + * Author: Graeme W. Gill + * Date: 2000/11/10 + * + * Copyright 1996 - 2000 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 file contains the scattered data solution specific code */ + +/* TTBD: + * + * mlbs code doesn't work. Results are rubbish. + * + * Fix bugs ? + * merge stest.c into this file. + * + * Get rid of error() calls - return status instead + */ + +#include +#include +#include +#include +#include +#if defined(__IBMC__) && defined(_M_IX86) +#include +#endif + +#include "rspl_imp.h" +#include "numlib.h" +#include "mlbs.h" + +extern void error(char *fmt, ...), warning(char *fmt, ...); + +#undef DEBUG + +#undef NEVER +#define ALWAYS + +/* Implemented in rspl.c: */ +extern void alloc_grid(rspl *s); + +extern int is_mono(rspl *s); + +/* ============================================ */ +void set_from_mlbs(void *cbctx, double *out, double *in) { + mlbs *p = (mlbs *)cbctx; + co tp; + int i; + + for (i = 0; i < p->di; i++) + tp.p[i] = in[i]; + + if (p->lookup(p, &tp)) + error("Internal, set_from_mlbs failed!"); + + for (i = 0; i < p->di; i++) + out[i] = tp.v[i]; +} + +/* Initialise the regular spline from scattered data */ +/* Return non-zero if non-monotonic */ +static int +fit_rspl_imp( + rspl *s, /* this */ + int flags, /* Combination of flags */ + void *d, /* Array holding position and function values of data points */ + int dtp, /* Flag indicating data type, 0 = (co *), 1 = (cow *) */ + int dno, /* Number of data points */ + datai glow, /* Grid low scale - will be expanded to enclose data, NULL = default 0.0 */ + datai ghigh, /* Grid high scale - will be expanded to enclose data, NULL = default 1.0 */ + int gres, /* Spline grid resolution */ + datao vlow, /* Data value low normalize, NULL = default 0.0 */ + datao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth /* Smoothing factor, nominal = 1.0 */ +) { + int di = s->di, fdi = s->fdi; + int i, n, e, f; + int rv; + int nigc; + int bres; + mlbs *p; + +#if defined(__IBMC__) && defined(_M_IX86) + _control87(EM_UNDERFLOW, EM_UNDERFLOW); +#endif + /* set debug level */ + s->debug = (flags >> 24); + + /* Init other flags */ + if (flags & RSPL_NONMON) /* Enable elimination of non-monoticities */ + s->nm = 1; + else + s->nm = 0; + + if (flags & RSPL_VERBOSE) /* Turn on progress messages to stdout */ + s->verbose = 1; + else + s->verbose = 0; + + /* Save smoothing factor */ + s->smooth = smooth; + + /* Stash the data points away */ + s->d.no = dno; /* Number of data points */ + + /* Allocate the scattered data space */ + if ((s->d.a = (dpnts *) malloc(sizeof(dpnts) * s->d.no)) == NULL) + error("rspl malloc failed - data points"); + + if (dtp == 0) { /* Default weight */ + co *dp = (co *)d; + + /* Copy the list into data points */ + for (n = 0; n < s->d.no; n++) { + for (e = 0; e < s->di; e++) + s->d.a[n].p[e] = dp[n].p[e]; + for (f = 0; f < s->fdi; f++) + s->d.a[n].v[f] = dp[n].v[f]; + s->d.a[n].k = 1.0; /* Assume all data points have same weight */ + } + } else { /* Per data point weight */ + cow *dp = (cow *)d; + + /* Copy the list into data points */ + for (n = 0; n < s->d.no; n++) { + for (e = 0; e < s->di; e++) + s->d.a[n].p[e] = dp[n].p[e]; + for (f = 0; f < s->fdi; f++) + s->d.a[n].v[f] = dp[n].v[f]; + s->d.a[n].k = dp[n].w; /* Weight specified */ + } + } + + /* Compute target B-Spline resolution */ + /* Make it worst case half the target rspl resolution */ + for (bres = 2; (2 * bres) < gres; bres = 2 * bres -1) + ; + + /* Create multilevel B-Spline fit */ + p = new_mlbs(di, fdi, bres, s->d.a, s->d.no, glow, ghigh, smooth); + if (p == NULL) + error("new_mlbs() failed"); + + /* Create rspl grid points by looking up the B-Spline values */ + rv = s->set_rspl(s, 0, (void *)p, set_from_mlbs, glow, ghigh, gres, vlow, vhigh); + + /* Don't need B-Spline any more */ + p->del(p); + + return rv; +} + +/* Initialise the regular spline from scattered data */ +/* Return non-zero if non-monotonic */ +int +fit_rspl( + rspl *s, /* this */ + int flags, /* Combination of flags */ + co *d, /* Array holding position and function values of data points */ + int dno, /* Number of data points */ + datai glow, /* Grid low scale - will be expanded to enclose data, NULL = default 0.0 */ + datai ghigh, /* Grid high scale - will be expanded to enclose data, NULL = default 1.0 */ + int gres, /* Spline grid resolution */ + datao vlow, /* Data value low normalize, NULL = default 0.0 */ + datao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth /* Smoothing factor, nominal = 1.0 */ +) { + /* Call implementation with (co *) data */ + return fit_rspl_imp(s, flags, (void *)d, 0, dno, glow, ghigh, gres, vlow, vhigh, smooth); +} + +/* Initialise the regular spline from scattered data with weights */ +/* Return non-zero if non-monotonic */ +int +fit_rspl_w( + rspl *s, /* this */ + int flags, /* Combination of flags */ + cow *d, /* Array holding position, function and weight values of data points */ + int dno, /* Number of data points */ + datai glow, /* Grid low scale - will be expanded to enclose data, NULL = default 0.0 */ + datai ghigh, /* Grid high scale - will be expanded to enclose data, NULL = default 1.0 */ + int gres, /* Spline grid resolution */ + datao vlow, /* Data value low normalize, NULL = default 0.0 */ + datao vhigh, /* Data value high normalize - NULL = default 1.0 */ + double smooth /* Smoothing factor, nominal = 1.0 */ +) { + /* Call implementation with (cow *) data */ + return fit_rspl_imp(s, flags, (void *)d, 1, dno, glow, ghigh, gres, vlow, vhigh, smooth); +} + +/* Init scattered data elements in rspl */ +void +init_data(rspl *s) { + s->d.no = 0; + s->d.a = NULL; + s->fit_rspl = fit_rspl; + s->fit_rspl_w = fit_rspl_w; +} + +/* Free the scattered data allocation */ +void +free_data(rspl *s) { + if (s->d.a != NULL) { + free((void *)s->d.a); + s->d.a = NULL; + } +} + +/* ============================================ */ + + + + + + + + + + + + + + + diff --git a/rspl/sm1.c b/rspl/sm1.c new file mode 100644 index 0000000..5bcf26e --- /dev/null +++ b/rspl/sm1.c @@ -0,0 +1,88 @@ + +/* Test smoothness scaling behaviour for 1D */ + +#include +#include + +double trans(double *v, int luord, double vv); + +double f(double x) { + double fp[5] = { +1.0, 0.7, -0.3, 0.0, 0.0 }; + double y; + + y = trans(fp, 5, x); + + return y; +} + +int main() { + double min = 0.0; + double max = 1.0; + int i, res = 4; + int di = 1; + +#define LOC(xx) (min + (max-min) * (xx)/(res-1.0)) + + /* For each resolution */ + for (i = 0; i < 10; i++, res *= 2) { + double tse = 0.0; /* Total squared error */ + int j; + + /* For each grid point with neigbors */ + for (j = 1; j < (res-1); j++) { + double err; + double y1, y2, y3; + + y1 = f(LOC(j-1)); + y2 = f(LOC(j)); + y3 = f(LOC(j+1)); + err = 0.5 * (y3 + y1) - y2; + tse += err * err; +// tse += fabs(err); + } + /* Apply adjustments and corrections to error squared */ + tse *= pow((res-1.0), 4.0); /* Aprox. geometric resolution factor */ + tse /= pow((res-2.0),(double)di); /* Average squared non-smoothness */ + +// tse /= (di * pow((res-2.0),(double)di)); /* Average squared non-smoothness */ + printf("Res %d, tse = %f\n",res,tse); + } + + + return 0; +} + +/* Transfer function */ +double trans( +double *v, /* Pointer to first parameter */ +int luord, /* Number of parameters */ +double vv /* Source of value */ +) { + double g; + int ord; + + for (ord = 0; ord < luord; ord++) { + int nsec; /* Number of sections */ + double sec; /* Section */ + + g = v[ord]; /* Parameter */ + + nsec = ord + 1; /* Increase sections for each order */ + + vv *= (double)nsec; + + sec = floor(vv); + if (((int)sec) & 1) + g = -g; /* Alternate action in each section */ + vv -= sec; + if (g >= 0.0) { + vv = vv/(g - g * vv + 1.0); + } else { + vv = (vv - g * vv)/(1.0 - g * vv); + } + vv += sec; + vv /= (double)nsec; + } + + return vv; +} diff --git a/rspl/sm2.c b/rspl/sm2.c new file mode 100644 index 0000000..84bc8cf --- /dev/null +++ b/rspl/sm2.c @@ -0,0 +1,110 @@ + +/* Test smoothness scaling behaviour for 2D */ + +#include +#include + +double trans(double *v, int luord, double vv); + +double f(double x, double y) { + double fp[2][5] = { + { 1.0, 0.7, -0.3, 0.0, 0.0 }, + { 1.0, 0.7, -0.3, 0.0, 0.0 } + }; + double v; + +#ifdef NEVER + /* 1D function */ + v = trans(fp[0], 5, x); +#else + /* 1D on angle */ + v = trans(fp[0], 5, 0.5 * (x+y)); + v *= 2.0 * sqrt(2.0); // ????? +#endif + +// v = trans(fp[0], 5, x) +// + trans(fp[0], 5, y); + + return v; +} + +int main() { + double min = 0.0; + double max = 1.0; + int di = 2; + int i, res = 4; + +#define LOC(xx) (min + (max-min) * (xx)/(res-1.0)) + + /* For each resolution */ + for (i = 0; i < 10; i++, res *= 2) { + double tse = 0.0; /* Total squared error */ + int j, k; + + /* For each grid point with neigbors */ + for (j = 1; j < (res-1); j++) { + for (k = 1; k < (res-1); k++) { + double err; + double y1, y2, y3; + + y1 = f(LOC(j-1), LOC(k)); + y2 = f(LOC(j+0), LOC(k)); + y3 = f(LOC(j+1), LOC(k)); + err = 0.5 * (y3 + y1) - y2; + tse += err * err; +// tse += fabs(err); + + y1 = f(LOC(j), LOC(k-1)); + y2 = f(LOC(j), LOC(k+0)); + y3 = f(LOC(j), LOC(k+1)); + err = 0.5 * (y3 + y1) - y2; + tse += err * err; +// tse += fabs(err); + } + } + /* Apply adjustments and corrections */ + tse *= pow((res-1.0), 4.0); /* Aprox. geometric resolution factor */ + tse /= pow((res-2.0),(double)di); /* Average squared non-smoothness */ + +// tse /= (di * pow((res-2.0),(double)di)); /* Average squared non-smoothness */ + printf("Res %d, tse = %f\n",res,tse); + } + + + return 0; +} + +/* Transfer function */ +double trans( +double *v, /* Pointer to first parameter */ +int luord, /* Number of parameters */ +double vv /* Source of value */ +) { + double g; + int ord; + + for (ord = 0; ord < luord; ord++) { + int nsec; /* Number of sections */ + double sec; /* Section */ + + g = v[ord]; /* Parameter */ + + nsec = ord + 1; /* Increase sections for each order */ + + vv *= (double)nsec; + + sec = floor(vv); + if (((int)sec) & 1) + g = -g; /* Alternate action in each section */ + vv -= sec; + if (g >= 0.0) { + vv = vv/(g - g * vv + 1.0); + } else { + vv = (vv - g * vv)/(1.0 - g * vv); + } + vv += sec; + vv /= (double)nsec; + } + + return vv; +} diff --git a/rspl/sm3.c b/rspl/sm3.c new file mode 100644 index 0000000..9307787 --- /dev/null +++ b/rspl/sm3.c @@ -0,0 +1,110 @@ + +/* Test smoothness scaling behaviour for 1D */ + +#include +#include + +double trans(double *v, int luord, double vv); + +double f(double x, double y, double z) { + double fp[5] = { 1.0, 0.7, -0.3, 0.0, 0.0 }; + double v; + +#ifdef NEVER + /* 1D function */ + v = trans(fp, 5, x); +#else + /* 1D on angle */ + v = trans(fp, 5, (x+y+z)/3.0); + v *= 3.0 * sqrt(3.0); // ????? +#endif + + return v; +} + +int main() { + double min = 0.0; + double max = 1.0; + int di = 3; + int i, res = 4; + +#define LOC(xx) (min + (max-min) * (xx)/(res-1.0)) + + /* For each resolution */ + for (i = 0; i < 10; i++, res *= 2) { + double tse = 0.0; /* Total squared error */ + int j, k, m; + + /* For each grid point with neigbors */ + for (j = 1; j < (res-1); j++) { + for (k = 1; k < (res-1); k++) { + for (m = 1; m < (res-1); m++) { + double err; + double y1, y2, y3; + + y1 = f(LOC(j-1), LOC(k), LOC(m)); + y2 = f(LOC(j+0), LOC(k), LOC(m)); + y3 = f(LOC(j+1), LOC(k), LOC(m)); + err = 0.5 * (y3 + y1) - y2; + tse += err * err; + + y1 = f(LOC(j), LOC(k-1), LOC(m)); + y2 = f(LOC(j), LOC(k+0), LOC(m)); + y3 = f(LOC(j), LOC(k+1), LOC(m)); + err = 0.5 * (y3 + y1) - y2; + tse += err * err; + + y1 = f(LOC(j), LOC(k), LOC(m-1)); + y2 = f(LOC(j), LOC(k), LOC(m+0)); + y3 = f(LOC(j), LOC(k), LOC(m+1)); + err = 0.5 * (y3 + y1) - y2; + tse += err * err; + } + } + } + /* Apply adjustments and corrections */ + tse *= pow((res-1.0), 4.0); /* Aprox. geometric resolution factor */ + tse /= pow((res-2.0),(double)di); /* Average squared non-smoothness */ + +// tse /= (di * pow((res-2.0),(double)di)); /* Average squared non-smoothness */ + printf("Res %d, tse = %f\n",res,tse); + } + + + return 0; +} + +/* Transfer function */ +double trans( +double *v, /* Pointer to first parameter */ +int luord, /* Number of parameters */ +double vv /* Source of value */ +) { + double g; + int ord; + + for (ord = 0; ord < luord; ord++) { + int nsec; /* Number of sections */ + double sec; /* Section */ + + g = v[ord]; /* Parameter */ + + nsec = ord + 1; /* Increase sections for each order */ + + vv *= (double)nsec; + + sec = floor(vv); + if (((int)sec) & 1) + g = -g; /* Alternate action in each section */ + vv -= sec; + if (g >= 0.0) { + vv = vv/(g - g * vv + 1.0); + } else { + vv = (vv - g * vv)/(1.0 - g * vv); + } + vv += sec; + vv /= (double)nsec; + } + + return vv; +} diff --git a/rspl/smtmpp.c b/rspl/smtmpp.c new file mode 100644 index 0000000..ed7bac7 --- /dev/null +++ b/rspl/smtmpp.c @@ -0,0 +1,1203 @@ + +/*****************************************************************/ +/* Smoothness factor tuning of RSPL in N Dimensions, using MPP's */ +/*****************************************************************/ + +/* Author: Graeme Gill + * Date: 28/11/2005 + * Derived from cmatch.c + * Copyright 1995 - 2005 Graeme W. Gill + * + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + * Test set for tuning smoothness factor for optimal interpolation + * with respect to dimension, number of sample points, and uncertainty + * of the sample points. + * + * The reference is an RGB or CMYK .mpp profile. For 1 and 2 dimensions, + * combinations of 1 or 2 input channels are used. + */ + + +#undef DEBUG + +#include +#include +#include +#include "rspl.h" +#include "numlib.h" +#include "xicc.h" +#include "plot.h" +#include "rspl_imp.h" +#include "counters.h" /* Counter macros */ + +#ifdef DEBUG +#define DBG(xxxx) printf xxxx +#else +#define DBG(xxxx) +#endif + +#define MXCHPARAMS 8 + +#define PLOTRES 256 + +/* Reference convertion object */ +struct _refconv { + char *fname; /* File name */ + mpp *mppo; /* Underlying MPP */ + inkmask imask; /* Device Ink mask */ + int pdi; /* mpp input dim */ + int di; /* effective input dim */ + int ix; /* channel to use/not use */ + double dmedia[MXDI]; /* Media color */ + + /* Do reference lookup */ + void (*lookup)( + struct _refconv *s, /* this */ + double *out, + double *in); + +}; typedef struct _refconv refconv; + + +/* Do a straight conversion */ +static void refconv_default( +refconv *rco, +double *out, +double *in) { + rco->mppo->lookup(rco->mppo, out, in); +} + +/* Do a 1d emulation */ +static void refconv_1d( +refconv *s, +double *out, +double *in +) { + double dval[MXDI]; + int e; + + for (e = 0; e < s->pdi; e++) { + if (e == s->ix) { + if (s->imask & ICX_INVERTED) + dval[e] = 1.0 - in[0]; + else + dval[e] = in[0]; + } else { + dval[e] = s->dmedia[e]; + } + } + s->mppo->lookup(s->mppo, out, dval); +//printf("~1 1D %f == %f %f %f -> %f %f %f\n", in[0], dval[0], dval[1], dval[2], out[0], out[1], out[2]); +} + +/* Do a 2d emulation */ +static void refconv_2d( +refconv *s, +double *out, +double *in +) { + double dval[MXDI]; + int e, j; + + for (j = e = 0; e < s->pdi; e++) { + if (e < 3 && e != s->ix) { + if (s->imask & ICX_INVERTED) + dval[e] = 1.0 - in[j++]; + else + dval[e] = in[j++]; + } else { + dval[e] = s->dmedia[e]; + } + } + + s->mppo->lookup(s->mppo, out, dval); +//printf("~1 2D %f %f == %f %f %f -> %f %f %f\n", in[0], in[1], dval[0], dval[1], dval[2], out[0], out[1], out[2]); +} + + +/* Setup the reference convertion object to imitate the given dimentionality */ +/* return nz if the given idex is out of range */ +static int set_refconv( + refconv *s, + int ix, /* Index of convertion, typicall 0-3 */ + int di /* Chosen dimentionalty */ +) { + int e; + + s->di = di; + s->ix = ix; + + if (di == s->pdi) { + if (ix == 0) { + s->lookup = refconv_default; + return 0; + } else { + return 1; + } + } + if (di == 3 || di == 4) + return 1; + + /* Have to emulate a lower dimension. */ + + /* Decide what the media color is */ + if (s->imask & ICX_INVERTED) { + for (e = 0; e < s->pdi; e++) + s->dmedia[e] = 1.0; + } else { + for (e = 0; e < s->pdi; e++) + s->dmedia[e] = 0.0; + } + + /* See where we're up to */ + if (di == 1) { + if (ix < 0 || ix >= s->pdi) /* RGB or CMYK channels */ + return 1; + + s->lookup = refconv_1d; + return 0; + + } else if (di == 2) { + if (ix < 0 || ix >= 3) /* Just RGB or CMY */ + return 1; + s->di = di; + s->lookup = refconv_2d; + return 0; + } + + return 0; +} + +/* ---------------------------------------------------------------------- */ + +/* Do one set of tests and return the results */ +static void do_test( + refconv *rco, + double *trmse, /* RETURN total RMS error */ + double *tmaxe, /* RETURN total maximum error */ + double *tavge, /* RETURN total average error */ + int verb, /* Verbosity */ + int plot, /* Plot graphs */ + int di, /* Dimensions */ + int res, /* RSPL grid resolution */ + int ntps, /* Number of sample points */ + double noise, /* Sample point noise volume */ + int unif, /* nz for uniform noise, else normal */ + double smooth, /* Smoothness to test */ + int twopass, /* Two pass flag */ + int extra /* Extra fit flag */ +); + +/* Compute smoothness of function */ +static double do_stest( + refconv *rco, + int verb, /* Verbosity */ + int di, /* Dimensions */ + int its, /* Number of function tests */ + int res /* RSPL grid resolution */ + +); + + + +/* ---------------------------------------------------------------------- */ +/* Locate minimum of smoothness series result */ + +#define MXMSS 50 /* Maximum smoothness series */ + +/* Return the optimal smoothness value, based on the */ +/* minimum RMS value. */ +static double best(int n, double *rmse, double *smv) { + int i, bi; + rspl *curve; + co *tps = NULL; + int ns = 500; /* Number of steps to search */ + datai low,high; + int gres[1]; + datai dlow,dhigh; + double avgdev[1]; + double brmse; /* best solution value */ + double blsmv = 0.0; /* best solution location */ + double rv; /* Return value */ + + /* Create interpolated curve */ + if ((curve = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL) + error ("New rspl failed"); + + /* Create the list of sampling points */ + if ((tps = (co *)malloc(n * sizeof(co))) == NULL) + error ("malloc failed"); + + for (i = 0; i < n; i++) { + tps[i].p[0] = log10(smv[i]); + tps[i].v[0] = rmse[i]; + } + + gres[0] = 100; + low[0] = log10(smv[0]); + high[0] = log10(smv[n-1]); + dlow[0] = 0.0; + dhigh[0] = 1.0; + avgdev[0] = 0.0; + + curve->fit_rspl(curve, + 0, /* Non-mon and clip flags */ + tps, /* Test points */ + n, /* Number of test points */ + NULL, NULL, gres, /* Low, high, resolution of grid */ + NULL, NULL, /* Default data scale */ + -0.000005, /* Underlying smoothing */ + avgdev, /* Average deviation */ + NULL); /* iwidth */ + +#ifdef NEVER + /* Check the fit */ + for (i = 0; i < n; i++) { + co tp; + + tp.p[0] = log10(smv[i]); + curve->interp(curve, &tp); + + printf("Point %d at %f, should be %f is %f\n",i,log10(smv[i]),rmse[i],tp.v[0]); + } + +#define TPRES 100 + /* Plot the result */ + { + double xx[TPRES], yy[TPRES]; + + for (i = 0; i < TPRES; i++) { + co tp; + double vi = i/(TPRES-1.0); + + tp.p[0] = log10(smv[0]) + (log10(smv[n-1]) - log10(smv[0])) * vi; + curve->interp(curve, &tp); + xx[i] = tp.p[0]; + yy[i] = tp.v[0]; + } + do_plot(xx,yy,NULL,NULL,TPRES); + } +#endif + + /* Choose a solution */ + + /* First find the very lowest error */ + brmse = 1e38; + for (i = 0; i < ns ; i++) { + co tp; + double vi; + + vi = i/(ns-1.0); + tp.p[0] = log10(smv[0]) + (log10(smv[n-1]) - log10(smv[0])) * vi; + curve->interp(curve, &tp); + + if (tp.v[0] < brmse) { + blsmv = tp.p[0]; + brmse = tp.v[0]; + bi = i; + } + } + +//printf("~1 located minimum at %f err %f\n",pow(10.0, blsmv), brmse); + + /* Then locate the larger smoothness value that */ + /* gives a slightly higher error. */ + if ((brmse * 1.1) < (brmse + 1.0)) + brmse *= 1.1; /* + 10% */ + else + brmse += 1.0; /* or 1 delta E */ + for (i = bi; i < ns ; i++) { + co tp; + double vi; + + vi = i/(ns-1.0); + tp.p[0] = log10(smv[0]) + (log10(smv[n-1]) - log10(smv[0])) * vi; + curve->interp(curve, &tp); + + if (tp.v[0] >= brmse) { + blsmv = tp.p[0]; + brmse = tp.v[0]; + break; + } + } +//printf("~1 located minimum + 20%% at %f err %f\n",pow(10.0, blsmv), brmse); + + rv = pow(10.0, blsmv); + return rv; +} + +/* ---------------------------------------------------------------------- */ +/* Explore ideal smoothness change with test point number and noise volume */ +static void do_series_1(refconv* rco, int tdi, int unif, int tntps, int tnlev, int twopass, int extra) { + int verb = 0; + int plot = 0; + int sdi = 1, edi = 4, di; + int res = 0; + int ntps = 0; + double noise = 0.0; + double smooth = 0.0; + double trmse, tavge, tmaxe; + int i, j, k; + + /* Resolution of grid for each dimension */ + int reses[4][4] = { + { 257, 129, 65, 33 }, + { 128, 65, 33, 17 }, + { 65, 33, 17, 9 }, + { 33, 17, 9, 5 } + }; + + /* Set of smoothnesses to explore */ + double smset[20] = { + -0.0000001, + -0.0000010, + -0.0000050, + -0.0000100, + -0.0000500, + -0.0001000, + -0.0005000, + -0.0010000, + -0.0050000, + -0.0100000, + -0.0500000, + -0.1000000, + -0.5000000, + -1.0000000, + 0 + }; + + /* For 2 pass smoothing */ + double smset2[20] = { + -0.0200000, + -0.0500000, + -0.0800000, + -0.1000000, + -0.1500000, + -0.2000000, + -0.3000000, + -0.4000000, + -0.5000000, + -0.6000000, + -0.7000000, + -0.8000000, + -1.0000000, + 0 + }; + + + /* Set of sample points to explore */ + int nset[4][20] = { + { + 5, 10, 20, 50, 100, 200, 0 /* di = 1 */ + }, + { + 25, 100, 400, 2500, 10000, 40000, 0, /* di = 2 */ + }, + { + 25, 50, 75, 125, 250, 500, 1000, 2000, 8000, 125000, 0, /* di = 3 */ + }, + { + 50, 100, 200, 450, 625, 900, 1800, 3600, 10000, 160000, 1000000, 0, /* di = 4 */ + } + }; + + /* Set of total noise levels to explore */ + /* Set of noise levels to explore (average deviation * 4) */ + double noiseset[4][20] = { + { + 0.0, /* Perfect data */ + 0.01, /* 1.0 % */ + 0.02, /* 2.0 % */ + 0.05, /* 5.0 % */ + 0.10, /* 10.0 % */ + 0.20, /* 20.0 % */ + -1.0, + }, + { + 0.0, /* Perfect data */ + 0.01, /* 1.0 % */ + 0.02, /* 2.0 % */ + 0.05, /* 5.0 % */ + 0.10, /* 10.0 % */ + 0.20, /* 20.0 % */ + -1.0, + }, + { + 0.0, /* Perfect data */ + 0.01, /* 1.0 % */ + 0.02, /* 2.0 % */ + 0.05, /* 5.0 % */ + 0.10, /* 10.0 % */ + 0.20, /* 20.0 % */ + -1.0, + }, + { + 0.0, /* Perfect data */ + 0.01, /* 1.0 % */ + 0.02, /* 2.0 % */ + 0.03, /* 3.0 % */ + 0.05, /* 5.0 % */ + 0.10, /* 10.0 % */ + 0.20, /* 20.0 % */ + -1.0, + }, + }; + + + printf("Testing underlying smoothness\n"); + + printf("Profile is '%s'\n",rco->fname); + + if (twopass) + printf("Two Pass smoothing\n"); + if (extra) + printf("Extra fitting\n"); + + /* For dimensions */ + if (tdi != 0) + sdi = edi = tdi; + DBG(("sdi = %d, edi = %d\n",sdi,edi)); + for (di = sdi; di <= edi; di++) { // dimensions + + res = reses[di-1][1]; /* Just 2nd highest res */ + + printf("Dimensions %d\n",di); + printf("RSPL resolution %d\n",res); + + /* For number of sample points */ + for (i = 0; i < 20; i++) { + ntps = nset[di-1][i]; + + if (ntps == 0) { + DBG(("nset[%d][%d] = %d, ntps == 0\n",di-1,i,nset[di-1][i])); + break; + } + + if (tntps != 0 && ntps != tntps) { /* Skip any not requested */ + DBG(("tntps %d != 0 && ntps %d != tntps\n",tntps,ntps)); + continue; + } + + printf("No. Sample points %d\n",ntps); + + /* For noise levels */ + for (j = tnlev; j < 20; j++) { + double smv[20]; + double rmse[20]; + double maxe[20]; + double bfit; + + int ix; + double avgbest = 0.0; /* Average best smoothness */ + + if (tnlev != 0 && j != tnlev) { + DBG(("tnlev != 0 && j != tnlev\n")); + break; + } + + noise = noiseset[di-1][j]; + if (noise < 0.0) + break; + printf("Noise volume %f%%\n",noise * 100.0); + + /* For each channel combination within profile */ + for (ix = 0; ; ix++) { + + if (set_refconv(rco, ix, di)) { + DBG(("set_refconv returned nz with ix %f\n",ix)); + break; + } + + if (di == 1 || di == 2) + printf("Channel %d\n",ix); + + /* For smooth factors */ + for (k = 0; k < 20; k++) { + if (twopass) + smooth = smset2[k]; + else + smooth = smset[k]; + if (smooth == 0.0) { + DBG(("smooth == 0\n")); + break; + } + + printf("Smooth %9.7f, ",-smooth); fflush(stdout); + + do_test(rco, &trmse, &tmaxe, &tavge, verb, plot, di, res, ntps, noise, unif, smooth, twopass, extra); + smv[k] = -smooth; + rmse[k] = trmse; + maxe[k] = tmaxe; + + printf("maxerr %f, avgerr %f, rmserr %f\n", tmaxe, tavge, trmse); + } +// bfit = best(k, rmse, smv); /* Best or RMS */ + bfit = best(k, maxe, smv); /* Best of max error */ + printf("Best smoothness = %9.7f, log10 = %4.1f\n",bfit,log10(bfit)); + avgbest += log10(bfit); + } + if (ix > 0) { + avgbest /= (double)ix; + printf("Average best smoothness of %d = %9.7f, log10 = %4.1f\n",ix,pow(10.0,avgbest),avgbest); + } + } + + } + printf("\n"); + } +} + +/* Verify the current behaviour with test point number and noise volume */ +static void do_series_2(refconv *rco, int di, int unif, int twopass, int extra) { + int verb = 0; + int plot = 0; + int res = 0; + int ntps = 0; + double noise = 0.0; + double smooth = 0.0; + double trmse, tavge, tmaxe; + int i, j, k; + + /* Number of trials to do for each dimension */ + int trials[4] = { + 8, + 8, + 8, + 5 + }; + + /* Resolution of grid for each dimension */ + int reses[4] = { + 129, + 65, + 33, + 17 + }; + + /* Set of smoothnesses to explore */ + double smset[5] = { + 00.01, + 00.10, + 01.00, + 10.00, + 100.0 + }; + +#ifdef NEVER + /* Set of sample points to explore */ + int nset[4][20] = { + { + 5, 10, 20, 50, 100, 200, 0 /* di = 1 */ + }, + { + 25, 100, 400, 2500, 10000, 40000, 0, /* di = 2 */ + }, + { + 25, 50, 75, 125, 250, 500, 1000, 2000, 8000, 125000, 0, /* di = 3 */ + }, + { + 50, 100, 200, 450, 625, 900, 1800, 3600, 10000, 160000, 1000000, 0, /* di = 4 */ + } + }; + +#else + /* Set of sample points to explore */ + int nset[4][20] = { + { + 5, 10, 20, 50, 0 + }, + { + 25, 100, 400, 2500 , 0 + }, + { + 250, 500, 1000, 2000, 4000, 8000, 0 + }, + { + 450, 900, 1800, 3600, 0 + } + }; +#endif /* NEVER */ + + /* Set of noise levels to explore */ + double noiseset[6] = { + 0.0, /* Perfect data */ + 0.01, /* 1.0 % */ + 0.02, /* 2.0 % */ + 0.05, /* 5.0 % */ + 0.10, /* 10.0 % */ + 0.20, /* 20.0 % */ + }; + + res = reses[di-1]; + + printf("Verification\n"); + printf("Dimensions %d\n",di); + printf("RSPL resolution %d\n",res); + + /* For number of sample points */ + for (i = 0; i < 20; i++) { + ntps = nset[di-1][i]; + + if (ntps == 0) + break; + + printf("No. Sample points %d\n",ntps); + + /* For noise levels */ + for (j = 0; j < 6; j++) { + noise = noiseset[j]; + + printf("Noise volume %f%%\n",noise * 100.0); + + /* For smooth factors */ + for (k = 0; k < 5; k++) { + smooth = smset[k]; + + printf("Smooth %9.7f, ",smooth); fflush(stdout); + + do_test(rco, &trmse, &tmaxe, &tavge, verb, plot, di, res, ntps, noise, unif, smooth, twopass, extra); + + printf("maxerr %f, avgerr %f, rmserr %f\n", tmaxe, tavge, trmse); + } + } + } + printf("\n"); +} + +/* ---------------------------------------------------------------------- */ +void usage(void) { + fprintf(stderr,"Test smoothness factor tuning of RSPL in N Dimensions with MPP\n"); + fprintf(stderr,"Author: Graeme W. Gill\n"); + fprintf(stderr,"usage: smtmpp [options] profile.mpp\n"); + fprintf(stderr," -v Verbose\n"); + fprintf(stderr," -p Plot graphs\n"); + fprintf(stderr," -z n Do test series ""n""\n"); + fprintf(stderr," 1 = underlying smoothness\n"); + fprintf(stderr," 2 = verification of optimal smoothness\n"); + fprintf(stderr," -S Compute smoothness factor instead\n"); + fprintf(stderr," -u Use uniformly distributed noise\n"); + fprintf(stderr," -d n Test ""d"" dimension, 1-4 (default 1)\n"); + fprintf(stderr," -r res Rspl resolution (defaults 129, 65, 33, 17)\n"); + fprintf(stderr," -n no Test ""no"" sample points (default 20, 40, 80, 100)\n"); + fprintf(stderr," -a amnt Add total level amnt randomness (default 0.0)\n"); + fprintf(stderr," -A n Just do the n'th noise level of series\n"); + fprintf(stderr," -2 Use two pass smoothing\n"); + fprintf(stderr," -x Use extra fitting\n"); + fprintf(stderr," -s smooth RSPL extra smoothness factor to test (default 1.0)\n"); + fprintf(stderr," -g smooth RSPL underlying smoothness factor to test\n"); + fprintf(stderr," profile.mpp MPP profile to use\n"); + exit(1); +} + +int main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + char prof_name[500]; + refconv rco; + char *ident = NULL; /* Device colorspec description */ + int verb = 0; + int plot = 0; + int series = 0; + int unif = 0; + int di = 0; /* Test input dimensions */ + int its = 3; /* Smooth test itterations */ + int res = -1; + int ntps = 0; + double noise = 0.0; + int nlev = 0; + double smooth = 1.0; + double gsmooth = 0.0; + int twopass = 0; + int extra = 0; + int smfunc = 0; + double trmse, tavge, tmaxe; + + int rv; + + error_program = "smtmpp"; + + /* 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(); + + } else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + + } else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') { + plot = 1; + + } else if (argv[fa][1] == 'u' || argv[fa][1] == 'U') { + unif = 1; + + /* Test series */ + } else if (argv[fa][1] == 'z' || argv[fa][1] == 'Z') { + fa = nfa; + if (na == NULL) usage(); + series = atoi(na); + if (series <= 0) usage(); + + /* Compute smoothness factor */ + } else if (argv[fa][1] == 'S') { + smfunc = 1; + + /* Dimension */ + } else if (argv[fa][1] == 'd' || argv[fa][1] == 'D') { + fa = nfa; + if (na == NULL) usage(); + di = atoi(na); + if (di <= 0 || di > 4) usage(); + + /* Resolution */ + } else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') { + fa = nfa; + if (na == NULL) usage(); + res = atoi(na); + if (res <= 0) usage(); + + /* Number of sample points */ + } else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') { + fa = nfa; + if (na == NULL) usage(); + ntps = atoi(na); + if (ntps <= 0) usage(); + + /* Randomness */ + } else if (argv[fa][1] == 'a') { + fa = nfa; + if (na == NULL) usage(); + noise = atof(na); + if (noise < 0.0) usage(); + + /* Series Noise Level */ + } else if (argv[fa][1] == 'A') { + fa = nfa; + if (na == NULL) usage(); + nlev = atoi(na); + if (noise < 0) usage(); + + } else if (argv[fa][1] == '2') { + twopass = 1; + + } else if (argv[fa][1] == 'x' || argv[fa][1] == 'X') { + extra = 1; + + /* Extra smooth factor */ + } else if (argv[fa][1] == 's') { + fa = nfa; + if (na == NULL) usage(); + smooth = atof(na); + if (smooth < 0.0) usage(); + + /* Underlying smoothnes factor */ + } else if (argv[fa][1] == 'g') { + fa = nfa; + if (na == NULL) usage(); + smooth = atof(na); + if (gsmooth < 0.0) usage(); + + + } else + usage(); + } else + break; + } + + if (fa >= argc || argv[fa][0] == '-') usage(); + strcpy(prof_name,argv[fa]); + + rco.fname = prof_name; + + if ((rco.mppo = new_mpp()) == NULL) + error ("Creation of MPP object failed"); + + if ((rv = rco.mppo->read_mpp(rco.mppo,prof_name)) != 0) + error ("%d, %s",rv,rco.mppo->err); + + rco.mppo->get_info(rco.mppo, &rco.imask, &rco.pdi, NULL, NULL, NULL, NULL, NULL, NULL); + ident = icx_inkmask2char(rco.imask, 1); + + if (rco.pdi != 3 && rco.pdi != 4) + error("Expect RGB or CMYK .mpp"); + + if (verb) { + printf("MPP profile with %d colorants, type %s\n",rco.pdi,ident); + } + + /* Select Lab return value details */ + if ((rv = rco.mppo->set_ilob(rco.mppo, icxIT_default, NULL, icxOT_default, NULL, icSigLabData, 0)) != 0) { + if (rv == 1) + error("Spectral profile needed for custom illuminant, observer or FWA"); + error("Error setting illuminant, observer, or FWA"); + } + + if (series > 0) { + if (series == 1) + do_series_1(&rco, di, unif, ntps, nlev, twopass, extra); + else if (series == 2) + do_series_2(&rco, di, unif, twopass, extra); + else + error("Unknown series %d\n",series); + return 0; + } + + if (res < 0) { + if (di == 1) + res = 129; + else if (di == 2) + res = 65; + else if (di == 3) + res = 33; + else + res = 17; + } + + if (ntps < 0) { + if (di == 1) + ntps = 20; + else if (di == 2) + ntps = 40; + else if (di == 3) + ntps = 60; + else + ntps = 80; + } + + if (smfunc) { + double sm; + + if (verb) { + printf("Dimensions %d\n",di); + printf("Tests %d\n",its); + printf("Grid resolution %d\n",res); + } + + sm = do_stest(&rco, verb, di, its, res); + + printf("Results: smoothness factor = %f\n",sm); + + } else { + + if (verb) { + printf("Dimensions %d\n",di); + printf("RSPL resolution %d\n",res); + printf("No. Sample points %d (norm %f)\n",ntps, pow((double)ntps, 1.0/di)); + printf("Noise volume total %f, == avg. dev. %f\n",noise, 0.25 * noise); + if (gsmooth > 0.0) + printf("Underlying smooth %f\n",gsmooth); + else + printf("Extra smooth %f\n",smooth); + } + + if (gsmooth > 0.0) + do_test(&rco, &trmse, &tmaxe, &tavge, verb, plot, di, res, ntps, noise, unif, -gsmooth, twopass, extra); + else + do_test(&rco, &trmse, &tmaxe, &tavge, verb, plot, di, res, ntps, noise, unif, smooth, twopass, extra); + + printf("Results: maxerr %f, avgerr %f, rmserr %f\n", + tmaxe, tavge, trmse); + } + + rco.mppo->del(rco.mppo); + + free(ident); + + return 0; +} + +/* ----------------------------------------------- */ + +/* Do one set of tests and return the results */ +static void do_test( + refconv *rco, + double *trmse, /* RETURN total RMS error */ + double *tmaxe, /* RETURN total maximum error */ + double *tavge, /* RETURN total average error */ + int verb, /* Verbosity */ + int plot, /* Plot graphs */ + int di, /* Dimensions in */ + int res, /* RSPL grid resolution */ + int ntps, /* Number of sample points */ + double noise, /* Sample point noise volume */ + int unif, /* nz for uniform noise, else normal */ + double smooth, /* Smoothness to test, +ve for extra, -ve for underlying */ + int twopass, /* Two pass flag */ + int extra /* Extra fit flag */ +) { + sobol *so; /* Sobol sequence generator */ + co *tps = NULL; + rspl *rss; /* Multi-resolution regularized spline structure */ + datai low,high; + int gres[MXDI]; + double avgdev[MXDO]; + int i, j, it; + int flags = RSPL_NOFLAGS; + + *trmse = 0.0; + *tmaxe = 0.0; + *tavge = 0.0; + + for (j = 0; j < di; j++) { + low[j] = 0.0; + high[j] = 1.0; + gres[j] = res; + } + + if (twopass) + flags |= RSPL_2PASSSMTH; + + if (extra) + flags |= RSPL_EXTRAFIT2; + + /* Make repeatable by setting random seed before a test set. */ + rand32(0x12345678); + + if ((so = new_sobol(di)) == NULL) + error("Creating sobol sequence generator failed"); + + { + double rmse, avge, maxe; + + /* Create the object */ + rss = new_rspl(RSPL_NOFLAGS,di, 3); + + /* Create the list of sampling points */ + tps = (co *)malloc(ntps * sizeof(co)); + + so->reset(so); + + if (verb) printf("Generating the sample points\n"); + + /* Random sobol test set */ + for (i = 0; i < ntps; i++) { + double out[3]; + int f; + + so->next(so, tps[i].p); + rco->lookup(rco, out, tps[i].p); + /* Add randomness to the PCS values */ + /* 0.25 * converts total volume to average deviation */ + for (f = 0; f < 3; f++) { + if (unif) { + tps[i].v[f] = out[f] + 100.0 * d_rand(-0.5 * noise, 0.5 * noise); + } else { + tps[i].v[f] = out[f] + 100.0 * noise * 0.25 * 1.2533 * norm_rand(); + } + } +//printf("~1 data %d: %f %f %f -> %f %f %f, inc noise %f %f %f\n", i, tps[i].p[0], tps[i].p[1], tps[i].p[2], out[0], out[1], out[2], tps[i].v[0], tps[i].v[1], tps[i].v[2]); + } + + /* Average deviation of ouput % */ + avgdev[0] = 0.25 * noise; + avgdev[1] = 0.25 * noise; + avgdev[2] = 0.25 * noise; + + /* Fit to scattered data */ + if (verb) printf("Fitting the scattered data\n"); + rss->fit_rspl(rss, + flags, /* Non-mon and clip flags */ + tps, /* Test points */ + ntps, /* Number of test points */ + low, high, gres, /* Low, high, resolution of grid */ + NULL, NULL, /* Default data scale */ + smooth, /* Smoothing */ + avgdev, /* Average deviation */ + NULL); /* iwidth */ + + /* Plot out function values */ + if (plot) { + int slice; + printf("Black is target, Red is rspl\n"); + for (slice = 0; slice < (di+1); slice++) { + co tp; /* Test point */ + double x[PLOTRES]; + double ya[PLOTRES]; + double yb[PLOTRES]; + double yc[PLOTRES]; + double pp[MXDI], p1[MXDI], p2[MXDI], ss[MXDI]; + int n = PLOTRES; + + /* setup slices on each axis at 0.5 and diagonal */ + if (slice < di) { + for (j = 0; j < di; j++) + p1[j] = p2[j] = 0.5; + p1[slice] = 0.0; + p2[slice] = 1.0; + printf("Slice along axis %d\n",slice); + } else { + for (j = 0; j < di; j++) { + p1[j] = 0.0; + p2[j] = 1.0; + } + printf("Slice along diagonal\n"); + } + + for (j = 0; j < di; j++) { + ss[j] = (p2[j] - p1[j])/n; + pp[j] = p1[j]; + } + + for (i = 0; i < n; i++) { + double out[3]; + double vv = i/(n-1.0); + x[i] = vv; + + /* Reference */ + rco->lookup(rco, out, pp); + ya[i] = 0.01 * out[0]; + + /* RSPL aproximation */ + for (j = 0; j < di; j++) + tp.p[j] = pp[j]; + + if (rss->interp(rss, &tp)) + tp.v[0] = -0.1; + yb[i] = tp.v[0]; + + /* Crude way of setting the scale: */ + yc[i] = 0.0; + if (i == (n-1)) + yc[0] = 1.0; + + for (j = 0; j < di; j++) + pp[j] += ss[j]; + } + + /* Plot the result */ + do_plot(x,ya,yb,yc,n); + } + } + + /* Compute statistics */ + rmse = 0.0; + avge = 0.0; + maxe = 0.0; +// so->reset(so); + + + /* Fit to scattered data */ + if (verb) printf("Fitting the scattered data\n"); + for (i = 0; i < 100000; i++) { +// for (i = 0; i < 100; i++) { + double out[3]; + co tp; /* Test point */ + double err; + + if (so->next(so, tp.p)) + error("Ran out of pseudo radom points"); + + /* Reference */ + rco->lookup(rco, out, tp.p); + + /* RSPL aproximation */ + rss->interp(rss, &tp); + +//printf("~1 point %f %f %f -> ref %f %f %f, test %f %f %f\n", tp.p[0], tp.p[1], tp.p[2], out[0], out[1], out[2], tp.v[0], tp.v[1], tp.v[2]); + + err = icmLabDE(out, tp.v); + avge += err; + rmse += err * err; + if (err > maxe) + maxe = err; + } + avge /= (double)i; + rmse /= (double)i; + + if (verb) + printf("Dim %d, res %d, noise %f, points %d, maxerr %f, rmserr %f, avgerr %f\n", + di, res, noise, ntps, maxe, sqrt(rmse), avge); + + *trmse += rmse; + if (maxe > *tmaxe) + *tmaxe = maxe; + *tavge += avge; + + rss->del(rss); + free(tps); + } + so->del(so); + + *trmse = sqrt(*trmse); +} + +/* Do smoothness scaling check & return results */ +static double do_stest( + refconv *rco, + int verb, /* Verbosity */ + int di, /* Dimensions */ + int its, /* Number of function tests */ + int res /* RSPL grid resolution */ +) { + DCOUNT(gc, MXDIDO, di, 1, 1, res-1); + int it; + double atse = 0.0; + + /* Make repeatable by setting random seed before a test set. */ + rand32(0x12345678); + + for (it = 0; it < its; it++) { + double tse; + int fdi = it % 3; /* Rotate amongsth L, a, b */ + + DC_INIT(gc) + tse = 0.0; + for (; !DC_DONE(gc);) { + double out[3]; + double g[MXDI]; + int e, k; + double y1, y2, y3; + double del; + + for (e = 0; e < di; e++) + g[e] = gc[e]/(res-1.0); + rco->lookup(rco, out, g); + y2 = 0.01 * out[fdi]; + + del = 1.0/(res-1.0); + + for (k = 0 ; k < di; k++) { + double err; + + g[k] -= del; + rco->lookup(rco, out, g); + y1 = 0.01 * out[fdi]; + g[k] += 2.0 * del; + rco->lookup(rco, out, g); + y3 = 0.01 * out[fdi]; + g[k] -= del; + + err = 0.5 * (y3 + y1) - y2; + tse += err * err; + } + + DC_INC(gc); + } + /* Apply adjustments and corrections */ + tse *= pow((res-1.0), 4.0); /* Aprox. geometric resolution factor */ + tse /= pow((res-2.0),(double)di); /* Average squared non-smoothness */ + + if (verb) + printf("smf for it %d = %f\n",it,tse); + atse += tse; + } + + return atse/(double)its; +} + + diff --git a/rspl/smtnd.c b/rspl/smtnd.c new file mode 100644 index 0000000..cdef0a2 --- /dev/null +++ b/rspl/smtnd.c @@ -0,0 +1,1171 @@ + +/*****************************************************/ +/* Smoothness factor tuning of RSPL in N Dimensions. */ +/*****************************************************/ + +/* Author: Graeme Gill + * Date: 28/11/2005 + * Derived from cmatch.c + * Copyright 1995 - 2005 Graeme W. Gill + * + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + * Test set for tuning smoothness factor for optimal interpolation + * with respect to dimension, number of sample points, and uncertainty + * of the sample points. + */ + + +#undef DEBUG +#undef DETAILED + +#include +#include +#include +#include "rspl.h" +#include "numlib.h" +#include "xicc.h" /* For mpp support */ +#include "plot.h" +#include "rspl_imp.h" +#include "counters.h" /* Counter macros */ + +/* rspl flags */ +#define MXCHPARAMS 8 + +#define PLOTRES 256 + +/* Function being modeled by rspl */ +/* Similar to MPP model */ +typedef struct { + int di; /* Number of dimensions */ + double ip[MXDI][MXCHPARAMS]; /* Input channel parameters */ + double shape[MXDI][1 << MXDI]; /* Channel interaction shape parameters */ + double op[1 << MXDI]; /* Output channel combination parameters */ +} funcp; + + +/* Setup a random function in the given dimensions */ +static void setup_func(funcp *p, int di) { + double mn,mx; + int i, j; + + p->di = di; + + /* Setup random input parameters */ + /* (This is the one that effects smoothness of function the most) */ + for (j = 0; j < di; j++) { + for (mx = 3.0, i = 0; i < MXCHPARAMS; i++, mx *= 0.6) { + p->ip[j][i] = d_rand(-mx, mx); + } + } + + /* Setup random shape parameters */ + for (j = 0; j < di; j++) { + for (i = 0; i < (1 << di); i++) { /* Initially random */ + p->shape[j][i] = d_rand(-1.0, 1.0); + } + } + + /* Setup the random output parameters */ + mn = 2.0; + mx = -1.0; + for (i = 0; i < (1 << di); i++) { /* Initially random */ + p->op[i] = d_rand(0.0, 1.0); + if (p->op[i] < mn) + mn = p->op[i]; + if (p->op[i] > mx) + mx = p->op[i]; + } + for (i = 0; i < (1 << di); i++) { /* Then scale to between 0.0 and 1.0 */ + p->op[i] = (p->op[i] - mn)/(mx - mn); + } +} + +/* Lookup the function value */ +static double lookup_func(funcp *p, double *v) { + int m, k; + int di = p->di; + double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */ + double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */ + double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */ + double ov; /* Output value */ + + /* Input curve lookup */ + for (m = 0; m < di; m++) { + tcnv[m] = icxTransFunc(p->ip[m],MXCHPARAMS,v[m]); + tcnv1[m] = 1.0 - tcnv[m]; + } + + for (m = 0; m < di; m++) + ww[m] = 0.0; + + /* Lookup the shape values */ + for (k = 0; k < (1 << di); k++) { /* For each interp vertex */ + double vv; + for (vv = 1.0, m = 0; m < di; m++) { /* Compute weighting */ + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + for (m = 0; m < di; m++) { + ww[m] += p->shape[m][k & ~(1<= 0.0) { + vv = vv/(gg - gg * vv + 1.0); + } else { + vv = (vv - gg * vv)/(1.0 - gg * vv); + } + tcnv[m] = vv; + tcnv1[m] = 1.0 - vv; + } + + /* Compute the primary combination values */ + for (ov = 0.0, k = 0; k < (1 << di); k++) { + double vv = p->op[k]; + for (m = 0; m < di; m++) { + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + ov += vv; + } + + return ov; +} + +/* Do one set of tests and return the results */ +static void do_test( + double *trmse, /* RETURN total RMS error */ + double *tmaxe, /* RETURN total maximum error */ + double *tavge, /* RETURN total average error */ + int verb, /* Verbosity */ + int plot, /* Plot graphs */ + int di, /* Dimensions */ + int its, /* Number of function tests */ + int res, /* RSPL grid resolution */ + int ntps, /* Number of sample points */ + double noise, /* Sample point noise volume */ + int unif, /* NZ if uniform rather than standard deistribution noise */ + double smooth, /* Smoothness to test */ + int twopass, /* Two pass flag */ + int extra /* Extra fit flag */ +); + +/* Compute smoothness of function */ +static double do_stest( + int verb, /* Verbosity */ + int di, /* Dimensions */ + int its, /* Number of function tests */ + int res /* RSPL grid resolution */ +); + +/* ---------------------------------------------------------------------- */ +/* Locate minimum of smoothness series result */ + +#define MXMSS 50 /* Maximum smoothness series */ + +/* Return the optimal smoothness value, based on the */ +/* minimum RMS value. */ +static double best(int n, double *rmse, double *smv) { + int i; + rspl *curve; + co *tps = NULL; + int ns = 500; /* Number of samples */ + datai low,high; + int gres[1]; + datai dlow,dhigh; + double avgdev[1]; + double brmse; /* best solution value */ + double blsmv = 0.0; /* best solution location */ + double rv; /* Return value */ + + /* Create interpolated curve */ + if ((curve = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL) + error ("New rspl failed"); + + /* Create the list of sampling points */ + if ((tps = (co *)malloc(n * sizeof(co))) == NULL) + error ("malloc failed"); + + for (i = 0; i < n; i++) { + tps[i].p[0] = log10(smv[i]); + tps[i].v[0] = rmse[i]; + } + + gres[0] = 100; + low[0] = log10(smv[0]); + high[0] = log10(smv[n-1]); + dlow[0] = 0.0; + dhigh[0] = 1.0; + avgdev[0] = 0.0; + + curve->fit_rspl(curve, + 0, /* Non-mon and clip flags */ + tps, /* Test points */ + n, /* Number of test points */ + NULL, NULL, gres, /* Low, high, resolution of grid */ + NULL, NULL, /* Default data scale */ + -0.00001, /* Underlying smoothing */ + avgdev, /* Average deviation */ + NULL); /* iwidth */ + +#ifdef NEVER + /* Check the fit */ + for (i = 0; i < n; i++) { + co tp; + + tp.p[0] = log10(smv[i]); + curve->interp(curve, &tp); + + printf("Point %d at %f, should be %f is %f\n",i,log10(smv[i]),rmse[i],tp.v[0]); + } + +#define TPRES 100 + /* Plot the result */ + { + double xx[TPRES], yy[TPRES]; + + for (i = 0; i < TPRES; i++) { + co tp; + double vi = i/(TPRES-1.0); + + tp.p[0] = log10(smv[0]) + (log10(smv[n-1]) - log10(smv[0])) * vi; + curve->interp(curve, &tp); + xx[i] = tp.p[0]; + yy[i] = tp.v[0]; + } + do_plot(xx,yy,NULL,NULL,TPRES); + } +#endif + + /* Choose a solution */ + brmse = 1e38; + for (i = 0; i < ns ; i++) { + co tp; + double vi; + + vi = i/(ns-1.0); + tp.p[0] = log10(smv[0]) + (log10(smv[n-1]) - log10(smv[0])) * vi; + curve->interp(curve, &tp); + + if (tp.v[0] < brmse) { + blsmv = tp.p[0]; + brmse = tp.v[0]; + } + } + + rv = pow(10.0, blsmv); + return rv; +} + +/* ---------------------------------------------------------------------- */ +/* Test series */ + +/* Explore ideal smoothness change with test point number and noise volume */ +/* If tdi != 0, just do the given dimension */ +/* If tntps != 0, just do the given number of points */ +/* If tnlev != 0, just do the given noise level */ +static void do_series_1(int unif, int tdi, int tntps, int tnlev, int twopass, int extra) { + int verb = 0; + int plot = 0; + int sdi = 1, edi = 4, di; + int its; + int res = 0; + int ntps = 0; + double noise = 0.0; + double smooth = 0.0; + double trmse, tavge, tmaxe; + int m, i, j, k; + + /* Number of trials to do for each dimension */ + int trials[4] = { + 8, + 6, + 4, + 3 + }; + + /* Resolution of grid for each dimension */ + int reses[4][4] = { + { 257, 129, 65, 33 }, + { 128, 65, 33, 17 }, + { 65, 33, 17, 9 }, + { 33, 17, 9, 5 } + }; + + /* Set of sample points to explore */ + int nset[4][20] = { + { + 5, 10, 20, 50, 100, 200, /* di = 1 */ + }, + { + 25, 100, 400, 2500, 10000, 40000, /* di = 2 */ + }, + { + 25, 50, 75, 125, 250, 500, 1000, 2000, 8000, 125000, /* di = 3 */ + }, + { + 50, 100, 200, 450, 625, 900, 1800, 3600, 10000, 160000, 1000000, /* di = 4 */ + } + }; + + /* Set of smoothnesses to explore */ + double smset[4][20] = { + { + -0.0000001, + -0.0000010, + -0.0000100, + -0.0001000, + -0.0010000, + -0.0100000, + -0.1000000, + -1.0000000, + 0.0 + }, + { + -0.0000001, + -0.0000010, + -0.0000100, + -0.0001000, + -0.0010000, + -0.0100000, + -0.1000000, + -1.0000000, + 0.0 + }, + { + -0.0000010, + -0.0000100, + -0.0001000, + -0.0010000, + -0.0100000, + -0.1000000, + -1.0000000, + 0.0 + }, + { + -0.0000100, + -0.0001000, + -0.0010000, + -0.0100000, + -0.1000000, + -1.0000000, + -10.000000, + 0.0 + } + }; + + /* Set of smoothnesses for twopass smoothing */ + double smset2[4][20] = { + { + -0.0000001, + -0.0000010, + -0.0000100, + -0.0001000, + -0.0010000, + -0.0100000, + -0.1000000, + -1.0000000, + 0.0 + }, + { + -0.0000001, + -0.0000010, + -0.0000100, + -0.0001000, + -0.0010000, + -0.0100000, + -0.1000000, + -1.0000000, + 0.0 + }, + { + -0.0000010, + -0.0000100, + -0.0001000, + -0.0010000, + -0.0100000, + -0.1000000, + -1.0000000, + 0.0 + }, + { + -0.0000100, + -0.0001000, + -0.0010000, + -0.0100000, + -0.1000000, + -1.0000000, + -10.000000, + 0.0 + } + }; + + /* Set of noise levels to explore (average deviation * 4) */ + double noiseset[4][20] = { + { + 0.0, /* Perfect data */ + 0.01, /* 1.0 % */ + 0.02, /* 2.0 % */ + 0.05, /* 5.0 % */ + 0.10, /* 10.0 % */ + 0.20, /* 20.0 % */ + -1.0, + }, + { + 0.0, /* Perfect data */ + 0.01, /* 1.0 % */ + 0.02, /* 2.0 % */ + 0.05, /* 5.0 % */ + 0.10, /* 10.0 % */ + 0.20, /* 20.0 % */ + -1.0, + }, + { + 0.0, /* Perfect data */ + 0.01, /* 1.0 % */ + 0.02, /* 2.0 % */ + 0.05, /* 5.0 % */ + 0.10, /* 10.0 % */ + 0.20, /* 20.0 % */ + -1.0, + }, + { + 0.0, /* Perfect data */ + 0.01, /* 1.0 % */ + 0.02, /* 2.0 % */ + 0.03, /* 3.0 % */ + 0.05, /* 5.0 % */ + 0.10, /* 10.0 % */ + 0.20, /* 20.0 % */ + -1.0, + }, + }; + + + printf("Testing effect of underlying smoothness factors\n"); + if (twopass) + printf("Two Pass smoothing\n"); + if (extra) + printf("Extra fitting\n"); + + /* For dimensions */ + if (tdi != 0) + sdi = edi = tdi; + for (di = sdi; di <= edi; di++) { // dimensions + + its = trials[di-1]; + + for (m = 1; m < 2; m++) { // Just 2nd-highest resolution + res = reses[di-1][m]; + + printf("Tests %d\n",its); + printf("Dimensions %d\n",di); + printf("RSPL resolution %d\n",res); + + /* For noise levels */ + for (j = tnlev; j < 20; j++) { // All noise levels + double smv[20]; + double rmse[20]; + double bfit; + + if (tnlev != 0 && j != tnlev) + break; + + noise = noiseset[di-1][j]; + if (noise < 0.0) + break; + printf("\nNoise volume %f%%, average deviation %f%%\n",noise * 100.0, noise * 25.0); + + /* For number of sample points */ + for (i = 0; i < 20; i++) { // All test points + int rpts; + ntps = nset[di-1][i]; + + if (ntps == 0) + break; + + if (tntps != 0 && ntps != tntps) /* Skip any not requested */ + continue; + + /* Make sure at least 100 points are tested */ + rpts = 1 + 100/ntps; + if (rpts > 5) + rpts = 5; + + printf("\nNo. Sample points %d, norm %8.2f, total its %d\n",ntps, pow((double)ntps, 1.0/di),its * rpts); + + /* For smooth factors */ + for (k = 0; k < 20; k++) { // All smoothing levels + if (twopass) + smooth = smset2[di-1][k]; + else + smooth = smset[di-1][k]; + if (smooth == 0.0) + break; + + printf("Underlying smooth %9.7f, ",-smooth); fflush(stdout); + + do_test(&trmse, &tmaxe, &tavge, verb, plot, di, its * rpts, res, ntps, noise, unif,smooth, twopass, extra); + smv[k] = -smooth; + rmse[k] = trmse; + printf("maxerr %f%%, avgerr %f%%, rmserr %f%%\n", + tmaxe * 100.0, tavge * 100.0, trmse * 100.0); + } + + bfit = best(k, rmse, smv); + printf("Best smoothness = %9.7f, log10 = %4.1f\n",bfit,log10(bfit)); + } + } + } + printf("\n"); + } +} + +/* Explore performance of "optimised" smoothness over test point number and noise volume */ +static void do_series_2(int unif, int twopass, int extra) { + int verb = 0; + int plot = 0; + int di = 0; + int its; + int res = 0; + int ntps = 0; + double noise = 0.0; + double smooth = 0.0; + double trmse, tavge, tmaxe; + int i, j, k; + + /* Number of trials to do for each dimension */ + int trials[4] = { + 16, + 12, + 8, + 5 + }; + + + /* Resolution of grid for each dimension */ + int reses[4] = { + 129, + 65, + 33, + 17 + }; + +#ifdef NEVER + /* Set of sample points to explore */ + int nset[4][20] = { + { + 5, 10, 20, 50, 0 + }, + { + 25, 100, 400, 2500, 0 + }, + { + 125, 1000, 8000, 125000, 0 + }, + { + 625, 10000, 160000, 1000000, 0 + } + }; +#else + /* Set of sample points to explore */ + int nset[4][20] = { + { + 5, 10, 20, 50, 0 + }, + { + 25, 100, 400, 2500, 0 + }, + { + 250, 500, 1000, 2000, 0 + }, + { + 450, 900, 1800, 3600, 0 + } + }; +#endif /* NEVER */ + + + /* Set of smoothnesses to explore */ + double smset[5] = { + 00.01, + 00.10, + 01.00, + 10.00, + 100.0 + }; + + /* Set of noise levels to explore (average deviation * 4) */ + double noiseset[6] = { + 0.0, /* Perfect data */ + 0.01, /* 1.0 % */ + 0.02, /* 2.0 % */ + 0.05, /* 5.0 % */ + 0.10, /* 10.0 % */ + 0.20, /* 20.0 % */ + }; + + + printf("Verifying optimised smoothness factors\n"); + + /* For dimensions */ + for (di = 1; di <= 4; di++) { + + its = trials[di-1]; + res = reses[di-1]; + + printf("Tests %d\n",its); + printf("Dimensions %d\n",di); + printf("RSPL resolution %d\n",res); + + /* For number of sample points */ + for (i = 0; i < 20; i++) { + ntps = nset[di-1][i]; + + if (ntps == 0) + break; + + printf("\nNo. Sample points %d, norm %8.2f\n",ntps, pow((double)ntps, 1.0/di)); + + /* For noise levels */ + for (j = 0; j < 6; j++) { + double rmse[20]; + double bfit; + + noise = noiseset[j]; + printf("Noise volume %f%%, average deviation %f%%\n",noise * 100.0, noise * 25.0); + + /* For smooth factors */ + for (k = 0; k < 5; k++) { + smooth = smset[k]; + + printf("Extra smooth %f, ",smooth); fflush(stdout); + + do_test(&trmse, &tmaxe, &tavge, verb, plot, di, its, res, ntps, noise, unif, smooth, twopass, extra); + + rmse[k] = trmse; + printf("maxerr %f%%, avgerr %f%%, rmserr %f%%\n", + tmaxe * 100.0, tavge * 100.0, trmse * 100.0); + } + bfit = best(5, rmse, smset); + printf("Best smoothness = %9.7f, log10 = %4.1f\n",bfit,log10(bfit)); + } + } + printf("\n"); + } +} + +/* ---------------------------------------------------------------------- */ +void usage(void) { + fprintf(stderr,"Test smoothness factor tuning of RSPL in N Dimensions\n"); + fprintf(stderr,"Author: Graeme W. Gill\n"); + fprintf(stderr,"usage: smtnd [options]\n"); + fprintf(stderr," -v Verbose\n"); + fprintf(stderr," -p Plot graphs\n"); + fprintf(stderr," -z n Do test series ""n"" else single test\n"); + fprintf(stderr," 1 = Underlying smoothness\n"); + fprintf(stderr," 2 = Verify optimised smoothness\n"); + fprintf(stderr," -S Compute smoothness factor instead\n"); + fprintf(stderr," -u Use uniformly distributed noise\n"); + fprintf(stderr," -d n Test ""d"" dimension, 1-4 (default 1)\n"); + fprintf(stderr," -t n Test ""n"" random functions (default 1)\n"); + fprintf(stderr," -r res Rspl resolution (defaults 129, 65, 33, 17)\n"); + fprintf(stderr," -n no Test ""no"" sample points (default 20, 40, 80, 100)\n"); + fprintf(stderr," -a amnt Add total randomness to function value (default 0.0)\n"); + fprintf(stderr," -A n Just do the n'th noise level of series\n"); + fprintf(stderr," -2 Use two pass smoothing\n"); + fprintf(stderr," -x Use extra fitting\n"); + fprintf(stderr," -s smooth RSPL extra smoothness factor to test (default 1.0)\n"); + fprintf(stderr," -g smooth RSPL underlying smoothness factor to test\n"); + exit(1); +} + +int main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + int verb = 0; + int plot = 0; + int series = 0; + int unif = 0; + int di = 0; + int its = 1; + int res = -1; + int ntps = 0; + double noise = 0.0; + int nlev = 0; + double smooth = 1.0; + double gsmooth = 0.0; + int twopass = 0; + int extra = 0; + int smfunc = 0; + double trmse, tavge, tmaxe; + + error_program = "smtnd"; + +#ifdef NEVER + { + double rmse[10], smv[10], rv; + + smv[0] = 0.0000100, rmse[0] = 2.566116; + smv[1] = 0.0001000, rmse[1] = 2.528666; + smv[2] = 0.0010000, rmse[2] = 2.489116; + smv[3] = 0.0100000, rmse[3] = 3.409045; + smv[4] = 0.1000000, rmse[4] = 5.727079; + smv[5] = 1.0000000, rmse[5] = 6.653747; + + rv = best(6,rmse, smv); + printf("~1 best = %f\n",rv); + exit(0); + } +#endif + /* 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(); + + } else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + + } else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') { + plot = 1; + + } else if (argv[fa][1] == 'u' || argv[fa][1] == 'U') { + unif = 1; + + /* Test series */ + } else if (argv[fa][1] == 'z' || argv[fa][1] == 'Z') { + fa = nfa; + if (na == NULL) usage(); + series = atoi(na); + if (series <= 0) usage(); + + /* Compute smoothness factor */ + } else if (argv[fa][1] == 'S') { + smfunc = 1; + + /* Dimension */ + } else if (argv[fa][1] == 'd' || argv[fa][1] == 'D') { + fa = nfa; + if (na == NULL) usage(); + di = atoi(na); + if (di <= 0 || di > 4) usage(); +printf("~1 Got -d %s = %d\n",na,di); + + /* Number of tests */ + } else if (argv[fa][1] == 't' || argv[fa][1] == 'T') { + fa = nfa; + if (na == NULL) usage(); + its = atoi(na); + if (its <= 0) usage(); + + /* Resolution */ + } else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') { + fa = nfa; + if (na == NULL) usage(); + res = atoi(na); + if (res <= 0) usage(); + + /* Number of sample points */ + } else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') { + fa = nfa; + if (na == NULL) usage(); + ntps = atoi(na); + if (ntps <= 0) usage(); + + /* Randomness */ + } else if (argv[fa][1] == 'a') { + fa = nfa; + if (na == NULL) usage(); + noise = atof(na); + if (noise < 0.0) usage(); + + /* Series Noise Level */ + } else if (argv[fa][1] == 'A') { + fa = nfa; + if (na == NULL) usage(); + nlev = atoi(na); + if (noise < 0) usage(); + + } else if (argv[fa][1] == '2') { + twopass = 1; + + } else if (argv[fa][1] == 'x' || argv[fa][1] == 'X') { + extra = 1; + + /* Extra smooth factor */ + } else if (argv[fa][1] == 's') { + fa = nfa; + if (na == NULL) usage(); + smooth = atof(na); + if (smooth < 0.0) usage(); + + /* Underlying smoothnes factor */ + } else if (argv[fa][1] == 'g') { + fa = nfa; + if (na == NULL) usage(); + smooth = atof(na); + if (gsmooth < 0.0) usage(); + + } else + usage(); + } else + break; + } + + if (series > 0) { + if (series == 1) + do_series_1(unif, di, ntps, nlev, twopass, extra); + else if (series == 2) + do_series_2(unif, twopass, extra); + else + error("Unknown series %d\n",series); + return 0; + } + + if (di == 0) + di = 1; + + if (res < 0) { + if (di == 1) + res = 129; + else if (di == 2) + res = 65; + else if (di == 3) + res = 33; + else + res = 17; + } + + if (ntps <= 0) { + if (di == 1) + ntps = 20; + else if (di == 2) + ntps = 40; + else if (di == 3) + ntps = 60; + else + ntps = 80; + } + + if (smfunc) { + double sm; + + if (verb) { + printf("Dimensions %d\n",di); + printf("Tests %d\n",its); + printf("Grid resolution %d\n",res); + } + + sm = do_stest(verb, di, its, res); + + printf("Results: smoothness factor = %f\n",sm); + + } else { + + if (verb) { + printf("Dimensions %d\n",di); + printf("Tests %d\n",its); + printf("RSPL resolution %d\n",res); + printf("No. Sample points %d (norm %f)\n",ntps, pow((double)ntps, 1.0/di)); + printf("Noise volume %f\n",noise); + if (gsmooth > 0.0) + printf("Underlying smooth %f\n",gsmooth); + else + printf("Extra smooth %f\n",smooth); + } + + if (gsmooth > 0.0) + do_test(&trmse, &tmaxe, &tavge, verb, plot, di, its, res, ntps, noise, unif, -gsmooth, twopass, extra); + else + do_test(&trmse, &tmaxe, &tavge, verb, plot, di, its, res, ntps, noise, unif, smooth, twopass, extra); + + printf("Results: maxerr %f%%, avgerr %f%%, rmserr %f%%\n", + tmaxe * 100.0, tavge * 100.0, trmse * 100.0); + } + + return 0; +} + +/* Do one set of tests and return the results */ +static void do_test( + double *trmse, /* RETURN total RMS error */ + double *tmaxe, /* RETURN total maximum error */ + double *tavge, /* RETURN total average error */ + int verb, /* Verbosity */ + int plot, /* Plot graphs */ + int di, /* Dimensions */ + int its, /* Number of function tests */ + int res, /* RSPL grid resolution */ + int ntps, /* Number of sample points */ + double noise, /* Sample point noise volume (total = 4 x average deviation) */ + int unif, /* NZ if uniform rather than standard deistribution noise */ + double smooth, /* Smoothness to test, +ve for extra, -ve for underlying */ + int twopass, /* Two pass flag */ + int extra /* Extra fit flag */ +) { + funcp fp; /* Function parameters */ + sobol *so; /* Sobol sequence generator */ + co *tps = NULL; + rspl *rss; /* Multi-resolution regularized spline structure */ + datai low,high; + double avgdev[MXDO]; + int gres[MXDI]; + int i, j, it; + int flags = RSPL_NOFLAGS; + + *trmse = 0.0; + *tmaxe = 0.0; + *tavge = 0.0; + + for (j = 0; j < di; j++) { + low[j] = 0.0; + high[j] = 1.0; + gres[j] = res; + } + + if (twopass) + flags |= RSPL_2PASSSMTH; + + if (extra) + flags |= RSPL_EXTRAFIT2; + + if ((so = new_sobol(di)) == NULL) + error("Creating sobol sequence generator failed"); + + for (it = 0; it < its; it++) { + double rmse, avge, maxe; + + /* Make repeatable by setting random seed before a test set. */ + rand32(0x12345678 + 0x1000 * it); + + /* New function */ + setup_func(&fp, di); + + /* Create the object */ + rss = new_rspl(RSPL_NOFLAGS,di, 1); + + /* Create the list of sampling points */ + if ((tps = (co *)malloc(ntps * sizeof(co))) == NULL) + error ("malloc failed"); + + so->reset(so); + + if (verb) printf("Generating the sample points\n"); + + for (i = 0; i < ntps; i++) { + so->next(so, tps[i].p); + tps[i].v[0] = lookup_func(&fp, tps[i].p); + if (unif) + tps[i].v[0] += d_rand(-0.5 * noise, 0.5 * noise); + else + tps[i].v[0] += noise * 0.25 * 1.2533 * norm_rand(); + } + + /* Fit to scattered data */ + if (verb) printf("Fitting the scattered data\n"); + avgdev[0] = 0.25 * noise; + rss->fit_rspl(rss, + flags, /* Non-mon and clip flags */ + tps, /* Test points */ + ntps, /* Number of test points */ + low, high, gres, /* Low, high, resolution of grid */ + low, high, /* Default data scale */ + smooth, /* Smoothing to test */ + avgdev, /* Average deviation */ + NULL); /* iwidth */ + + /* Plot out function values */ + if (plot) { + int slice; + printf("Black is target, Red is rspl\n"); + for (slice = 0; slice < (di+1); slice++) { + co tp; /* Test point */ + double x[PLOTRES]; + double ya[PLOTRES]; + double yb[PLOTRES]; + double yc[PLOTRES]; + double pp[MXDI], p1[MXDI], p2[MXDI], ss[MXDI]; + int n = PLOTRES; + + /* setup slices on each axis at 0.5 and diagonal */ + if (slice < di) { + for (j = 0; j < di; j++) + p1[j] = p2[j] = 0.5; + p1[slice] = 0.0; + p2[slice] = 1.0; + printf("Slice along axis %d\n",slice); + } else { + for (j = 0; j < di; j++) { + p1[j] = 0.0; + p2[j] = 1.0; + } + printf("Slice along diagonal\n"); + } + + for (j = 0; j < di; j++) { + ss[j] = (p2[j] - p1[j])/n; + pp[j] = p1[j]; + } + + for (i = 0; i < n; i++) { + double vv = i/(n-1.0); + x[i] = vv; + + /* Reference */ + ya[i] = lookup_func(&fp, pp); + + /* RSPL aproximation */ + for (j = 0; j < di; j++) + tp.p[j] = pp[j]; + + if (rss->interp(rss, &tp)) + tp.v[0] = -0.1; + yb[i] = tp.v[0]; + + /* Crude way of setting the scale: */ + yc[i] = 0.0; + if (i == (n-1)) + yc[0] = 1.0; + + for (j = 0; j < di; j++) + pp[j] += ss[j]; + } + + /* Plot the result */ + do_plot(x,ya,yb,yc,n); + } + } + + /* Compute statistics */ + rmse = 0.0; + avge = 0.0; + maxe = 0.0; +// so->reset(so); + + /* Fit to scattered data */ + if (verb) printf("Fitting the scattered data\n"); + for (i = 0; i <100000; i++) { + co tp; /* Test point */ + double aa, bb, err; + + so->next(so, tp.p); + + /* Reference */ + aa = lookup_func(&fp, tp.p); + + /* RSPL aproximation */ + rss->interp(rss, &tp); + bb = tp.v[0]; + + err = fabs(aa - bb); + avge += err; + rmse += err * err; + if (err > maxe) + maxe = err; + } + avge /= (double)i; + rmse /= (double)i; + + if (verb) + printf("Dim %d, res %d, noise %f, points %d, maxerr %f%%, rmserr %f%%, avgerr %f%%\n", + di, res, noise, ntps, maxe * 100.0, sqrt(rmse) * 100.0, avge * 100.0); + + *trmse += rmse; + *tmaxe += maxe; + *tavge += avge; + + rss->del(rss); + free(tps); + } + so->del(so); + + *trmse = sqrt(*trmse/(double)its); + *tmaxe /= (double)its; + *tavge /= (double)its; +} + +/* Do smoothness scaling check & return results */ +static double do_stest( + int verb, /* Verbosity */ + int di, /* Dimensions */ + int its, /* Number of function tests */ + int res /* RSPL grid resolution */ +) { + funcp fp; /* Function parameters */ + DCOUNT(gc, MXDIDO, di, 1, 1, res-1); + int it; + double atse = 0.0; + + /* Make repeatable by setting random seed before a test set. */ + rand32(0x12345678); + + for (it = 0; it < its; it++) { + double tse; + setup_func(&fp, di); /* New function */ + + DC_INIT(gc) + tse = 0.0; + for (; !DC_DONE(gc);) { + double g[MXDI]; + int e, k; + double y1, y2, y3; + double del; + + for (e = 0; e < di; e++) + g[e] = gc[e]/(res-1.0); + y2 = lookup_func(&fp, g); + + del = 1.0/(res-1.0); + + for (k = 0 ; k < di; k++) { + double err; + + g[k] -= del; + y1 = lookup_func(&fp, g); + g[k] += 2.0 * del; + y3 = lookup_func(&fp, g); + g[k] -= del; + + err = 0.5 * (y3 + y1) - y2; + tse += err * err; + } + + DC_INC(gc); + } + /* Apply adjustments and corrections */ + tse *= pow((res-1.0), 4.0); /* Aprox. geometric resolution factor */ + tse /= pow((res-2.0),(double)di); /* Average squared non-smoothness */ + + if (verb) + printf("smf for it %d = %f\n",it,tse); + atse += tse; + } + + return atse/(double)its; +} + + diff --git a/rspl/spline.c b/rspl/spline.c new file mode 100644 index 0000000..dabeb0f --- /dev/null +++ b/rspl/spline.c @@ -0,0 +1,352 @@ + +/* + * Argyll Color Correction System + * Multi-dimensional regularized spline data structure + * + * Spline forward interpolation support. + * + * Author: Graeme W. Gill + * Date: 12/10/98 + * + * Copyright 1998, 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: + Get rid of error() calls - return status instead + */ + +#include +#include +#include +#include +#include +//#if defined(__IBMC__) && defined(_M_IX86) +//#include +//#endif + +#include "rspl_imp.h" +#include "numlib.h" + +int spline_interp_rspl(rspl *ss, co *cp); + +#undef DEBUG + +#undef NEVER +#define ALWAYS + +/* Convention is to use: + i to index grid points u.a + n to index data points d.a + e to index position dimension di + f to index output function dimension fdi + j misc and cube corners + k misc + */ + +/* ====================================================== */ + +/* Init spline elements in rspl */ +void +init_spline(rspl *s) { + s->spline.nm = 0; + s->spline.spline = 0; + s->spline.magic = NULL; + + s->spline_interp = spline_interp_rspl; +} + +/* Free up the spline interpolation info */ +void free_spline( +rspl *s /* Pointer to rspl grid */ +) { + if (s->spline.magic != NULL) { + free(s->spline.magic); + } + s->spline.nm = 0; + s->spline.spline = 0; +} + +/* ====================================================== */ +/* Setup functions first: */ + +/* Hermite spline, magic matrix */ +/* Indexes are: param powers 0, 1, 2, 3; Offset from base vertex 0,1; Dimension mask 0,1 */ +static double hmagic[4][2][2] = { + { { 1.0, 0.0}, { 0.0, 0.0} }, + { { 0.0, 1.0}, { 0.0, 0.0} }, + { {-3.0, -2.0}, { 3.0, -1.0} }, + { { 2.0, 1.0}, {-2.0, 1.0} } +}; + +/* Allocate and initialize tangency information for each grid point */ +static void make_tang( +rspl *s /* Pointer to rspl grid */ +) { + int i,p,j; + int di = s->di; + int fdi = s->fdi; + int nig = s->g.no; + float *tp; /* Pointer to tangent values */ + int nim, mix; /* Number in magic, magic index */ + float *tang_alloc, *tang; /* Tangency info */ + float *gt; /* Working grid point */ + +//printf("~~make_tang called\n"); + /* Organized as: tang[[grid]][di combs.][fdi] */ + /* Allocate space for tangency info */ + if ((tang_alloc = (float *) malloc(sizeof(float) * nig * (((1 << di) * fdi)+G_XTRA))) == NULL) + error("rspl malloc failed - tangecy points"); + tang = tang_alloc + G_XTRA; /* Offset for flags and non-mono error */ + + /* For all grid points */ + for (tp = tang, gt = s->g.a, i = 0; i < nig; i++, gt += s->g.pss, tp += G_XTRA) { + int ee; +/* printf("\n~~ grid point %d\n",i); */ + + /* Look at surrounding grid points in combinations of +- 1 all dimensions */ + for (ee = 0; ee < (1 << di); ee++) { + double av[MXRO]; /* average */ + int nia = 0; /* Number in average */ + int f, ec; + +/* printf("Dim combo %d\n",ee); */ + /* special case - base value */ + if (ee == 0) { + *((int *)(tp-2)) = *((int *)(gt-2)); /* Copy flags */ + tp[-1] = gt[-1]; /* Copy ink limit function value */ + for (f = 0; f < fdi; f++) { + *tp++ = gt[f]; +/* printf("Tang value out %d = %f\n",f,tp[-1]); */ + } + continue; + } + for (f = 0; f < fdi; f++) + av[f] = 0.0; /* Init average */ + /* For all surroundin grid points in this combination */ + for (ec = 0; ec < (1 << di); ec++) { + int xo, io, sgn, e, ex; +/* printf("~~checking out surrounding combo %d\n",ec); */ + if (ec & ~ee) { +/* printf("~~being skipped\n"); */ + continue; /* Skip invalid combo */ + } + xo = io = 0; /* Grid float offset */ + sgn = 1; /* Sign */ + ex = 0; /* Flag - No extrapolation */ + for (e = 0; e < di; e++) { /* For each dimension */ +/* printf("~~checking dimension %d\n",e); */ + if (!(ee & (1 << e))) { +/* printf("~~dimension not active\n"); */ + continue; /* Dimension is not active */ + } + if (ec & (1 << e)) { + /* If + dimension is valid */ + if (((G_FL(gt,e) & 3) > 0) || (G_FL(gt,e) & 0x4)) { + int to = s->g.fci[e]; /* +1 in dimension */ + io += to; /* real/pivot point */ + xo += to; /* reflected point */ + } else { + ex = 1; /* Use extrapolation */ + xo -= s->g.fci[e]; /* -1 in dimension */ + } + } else { + sgn = -sgn; /* Reverse sign */ + /* If - dimension is valid */ + if (((G_FL(gt,e) & 3) > 0) || !(G_FL(gt,e) & 0x4)) { + int to = -s->g.fci[e]; /* -1 in dimension */ + io += to; /* real/pivot point */ + xo += to; /* reflected point */ + } else { + ex = 1; /* Use extrapolation */ + xo += s->g.fci[e]; /* +1 in dimension */ + } + } + } + /* Add surrounding grid points value into the average */ + if (!ex) { + for (f = 0; f < fdi; f++) + av[f] += (double)sgn * gt[io + f]; + } else { /* Extrapolate point beyond edge */ + /* Use an extrapolation that tries to maintain curvature */ + for (f = 0; f < fdi; f++) { + double v0,v1,v2; + v0 = gt[io + f]; /* Pivot point */ + v1 = gt[xo + f]; /* Reflection of target in pivot */ + v2 = gt[2 * xo - io + f]; /* Reflection +2 */ + av[f] += (double)sgn * (3.0 * (v0 - v1) + v2); + } + } + nia++; + } + for (f = 0; f < fdi; f++) { + *tp++ = (float)(av[f]/(double)nia); +/* printf("Tang value out %d = %f, average of %d\n",f,tp[-1],nia); */ + } + } /* Next dimension combination */ + } /* Next grid point */ + + /* Create a full sized hermite magic matrix */ + /* Organized as: magic[4^di][2^di][2^di] */ + /* = [param power combos][cube vertex index][di combos], */ + /* but then only store non-zero weight values. */ + for (i = 0, nim = 1; i < di; nim *= 10, i++); /* Number of entries needed */ + if (s->spline.magic == NULL) { /* Allocate space for magic matrix info */ + if ((s->spline.magic = (magic_data *) malloc(sizeof(magic_data) * nim)) == NULL) + error("rspl malloc failed - hermite magic matrix data"); + } + + mix = 0; + for (p = 0; p < (1 << (2 * di)); p++) { /* For all combinations of parameter powers */ + for (i = 0; i < (1 << di); i++) { /* For all corners of cube */ + for (j = 0; j < (1 << di); j++) { /* For all dimension combinations */ + int ii; + double wgt = 1.0; + for (ii = 0; ii < di; ii++) { + wgt *= hmagic[3&(p>>(2*ii))][1&(i>>ii)][1&(j>>ii)]; + } + if (wgt != 0.0) { /* record non-zero weight value */ + s->spline.magic[mix].p = p; + s->spline.magic[mix].i = i; + s->spline.magic[mix].j = fdi * j; /* Pre-scale */ + s->spline.magic[mix].wgt = (float)wgt; + mix++; + } + } + } + } + /* mix should == nim! */ + s->spline.nm = nim; + + /* Free basic grid info, and substitute tangency enhanced version */ + /* ~~~~!! need to free any other structures in rspl that depend on */ + /* ~~~~!! g.pss size, ie. rev stuff ??? */ + if (s->g.alloc != NULL) + free((void *)s->g.alloc); + + s->g.alloc = tang_alloc; + s->g.a = tang; + + /* Adjust index tables */ + s->g.pss = (1 << di) * fdi + G_XTRA; + for (i = 0; i < di; i++) + s->g.fci[i] = s->g.ci[i] * s->g.pss; /* In floats */ + for (i = 0; i < (1 << di); i++) + s->g.fhi[i] = s->g.hi[i] * s->g.pss; /* In floats */ + + s->spline.spline = 1; + +//printf("~~make_tang finished\n"); +} + +/* Do a Hermite spline smooth interpolation based on the finest grid */ +/* (To do this more accurately, the data point interpolation within */ +/* the grid itteration should be of the same order. This increases */ +/* itteration complexity quite a bit, so we won't bother for the moment.) */ +/* This code is not optimised for speed. */ +/* Return 0 if OK, 1 if input was clipped to grid */ +int spline_interp_rspl( +rspl *s, +co *cp /* Input value and returned function value */ +) { + int e,f,p,i; + int di = s->di; + int fdi = s->fdi; + double ppw[MXRI][4]; /* Parameter powers of 0, 1, 2, 3 */ + float *ga[POW2MXRI]; /* Pointers to grid cubes data in tang[] */ + magic_data *tp; /* Pointer to items in magic matrix */ + int rv = 0; + +/* printf("~~smooth interp called\n"); */ + + /* This is a restricted size function */ + if (di > MXRI) + error("rspl: spline can't handle di = %d",di); + if (fdi > MXRO) + error("rspl: spline can't handle fdi = %d",fdi); + + if (s->spline.spline == 0) /* Compute tangent info if it doesn't exist */ + make_tang(s); + + /* Locate grid base point, and position with base cube */ + ga[0] = s->g.a; /* Base pointer of cube */ + for (e = 0; e < di; e++) { + double t, pe; + int mi, gres_1 = s->g.res[e]-1; + + pe = cp->p[e]; + if (pe < s->g.l[e]) { /* Clip to grid */ + pe = s->g.l[e]; + rv = 1; + } + if (pe > s->g.h[e]) { + pe = s->g.h[e]; + rv = 1; + } + t = (pe - s->g.l[e])/s->g.w[e]; + 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; + ga[0] += s->g.fci[e] * mi; /* Add offset in dimen */ + t = t - (double)mi;; /* sub-cube offset = parameter in dimension e */ + ppw[e][0] = 1.0; /* Powers of parameter */ + ppw[e][1] = t; + ppw[e][2] = t * t; + ppw[e][3] = t * t * t; + } + + /* Compute indexes into cube corners in tangent array */ + for (i = 1; i < (1 << di); i++) + ga[i] = ga[0] + s->g.fhi[i]; + + /* Now compute the output values */ + for (f = 0; f < fdi; f++) /* Zero output value sums */ + cp->v[f] = 0.0; + + /* For all non-zero combinations of parameter powers */ + { + double ppc = -1000.0; /* Parameter power combination */ + for (tp = s->spline.magic, p = -1; tp < &s->spline.magic[s->spline.nm]; tp++) { + double wgt; /* Magic matrix weight */ + float *gp; /* Pointer to vertex data */ + + if (p != tp->p) { /* Param power needs re-calculating */ + int pp; + p = tp->p; + for (ppc = 1.0, pp = 0; pp < di; pp++) + ppc *= ppw[pp][3&(p>>(2*pp))]; /* comb. of param powers value */ + } + + wgt = tp->wgt * ppc; /* matrix times parameter */ + gp = ga[tp->i] + tp->j; /* Point to base of vertex data */ + for (f = 0; f < fdi; f++) /* For all output values */ + cp->v[f] += wgt * gp[f]; + } + } +/* printf("~~smooth interp finished\n"); */ + return rv; +} + + + + + + + + + + + + + + + + + + + diff --git a/rspl/stest.c b/rspl/stest.c new file mode 100644 index 0000000..d1cf2da --- /dev/null +++ b/rspl/stest.c @@ -0,0 +1,654 @@ + +/* + * Scattered Data Interpolation + * with multilevel B-splines + * research. + * + * This is from the paper + * "Scattered Data Interpolation with Multilevel B-Splines" + * by Seungyong Lee, George Wolberg and Sung Yong Shin, + */ + +/* + * Author: Graeme W. Gill + * Date: 2001/1/1 + * + * Copyright 2000 - 2001 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 +#include +#include +#include +#include +#include +#include "../numeric/numlib.h" + + +#ifndef NUMSUP_H +void error(char *fmt, ...), warning(char *fmt, ...); +#endif + +#define MXDI 4 +#define MXDO 4 + +/* A control point */ +typedef struct { + double p[MXDI]; + double v[MXDO]; +} co; + +/* Neighborhood latice cache data */ +typedef struct { + int c[MXDI]; /* Coordinate */ + int xo; /* Offset into srbs latice */ + double w; /* B-spline basis weight */ +} neigh; + +/* Structure that represents a resolution level of B-splines */ +struct _srbs { + struct _mrbs *p; /* Parent structure */ + int res; /* Basic resolution */ + int coi[MXDI]; /* Double increment for each input dimension into latice */ + double *lat; /* Control latice, extending from +/- 1 from 0..res-1 */ + double *_lat; /* Allocation base of lat */ + int lsize, loff; /* Number of doubles in _lat, offset of lat from _lat */ + double w[MXDI]; /* Input data cell width */ + neigh *n; /* Neighborhood latice cache */ + int nsize; /* Number of n entries */ +}; typedef struct _srbs srbs; + +/* Structure that represents the whole scattered interpolation state */ +struct _mrbs { + int di; /* Input dimensions */ + int fdi; /* Output dimensions */ + int tres; /* Target resolution */ + double smf; /* Smoothing factor */ + int npts; /* Number of data points */ + co *pts; /* Coordinate points */ + double l[MXDI], h[MXDI]; /* Input data range, cell width */ + srbs *s; /* Current B-spline latice */ +}; typedef struct _mrbs mrbs; + +static void delete_srbs(srbs *s); + +/* Create a new empty mrbs */ +static mrbs *new_mrbs( +int di, /* Input dimensionality */ +int fdi, /* Output dimesionality */ +int res, /* Target resolution */ +double smf /* Smoothing factor */ +) { + mrbs *p; + if ((p = (mrbs *)malloc(sizeof(mrbs))) == NULL) + error("Malloc mrbs failed"); + + p->di = di; + p->fdi = fdi; + p->tres = res; + p->smf = smf; + p->s = NULL; + + return p; +} + +static void delete_mrbs(mrbs *p) { + + if (p != NULL) { + delete_srbs(p->s); + free(p); + } +} + +/* Create a new empty srbs */ +static srbs *new_srbs( +mrbs *p, /* Parent mrbs */ +int res /* Resolution of this srbs */ +) { + srbs *s; + int e, f; + double *_lat, *lat; /* Latice base address */ + int ix, oe, oo[MXDI]; /* Neighborhood offset index, counter */ + + if ((s = (srbs *)malloc(sizeof(srbs))) == NULL) + error("Malloc srbs failed"); + + s->p = p; + s->res = res; + + for (s->lsize = p->fdi, s->nsize = 1, e = 0; e < p->di; e++) { + s->coi[e] = s->lsize; /* (double) increment in this input dimension */ + s->lsize *= (res + 2); /* Latice in 1D +/- 1 */ + s->nsize *= 4; /* Neighborhood of 4 */ + } + + if ((s->_lat = (double *)malloc(s->lsize * sizeof(double))) == NULL) + error("Malloc srbs latice failed"); + + /* Compute the base address */ + for (s->loff = 0, e = 0; e < p->di; e++) { + s->loff += s->coi[e]; /* Offset by 1 in each input dimension */ + } + s->lat = s->_lat + s->loff; + + /* Figure the cell width */ + for (e = 0; e < p->di; e++) + s->w[e] = (p->h[e] - p->l[e])/(res-1.0); + + /* Setup neighborhood cache info */ + if ((s->n = (neigh *)malloc(s->nsize * sizeof(neigh))) == NULL) + error("Malloc srbs neighborhood failed"); + + for (oe = 0; oe < p->di; oe++) + oo[oe] = 0; + + for(ix = oe = 0; oe < p->di; ix++) { + int xo; + for (xo = e = 0; e < p->di; e++) { + s->n[ix].c[e] = oo[e]; + xo += s->coi[e] * oo[e]; /* Accumulate latice offset */ + } + s->n[ix].xo = xo; + s->n[ix].w = 0.0; + + /* Increment destination offset counter */ + for (oe = 0; oe < p->di; oe++) { + if (++oo[oe] <= 3) /* Counting from 0 ... 3 */ + break; + oo[oe] = 0; + } + } + + return s; +} + +/* Destroy a srbs */ +static void delete_srbs(srbs *s) { + if (s != NULL) { + free(s->_lat); + free(s->n); + free(s); + } +} + +/* Dump the 2D -> 1D contents of an srbs */ +static void dump_srbs(srbs *s) { + int e, f; + int ce, co[MXDI]; /* latice counter */ + mrbs *p = s->p; /* Parent object */ + + /* Init the counter */ + for (ce = 0; ce < p->di; ce++) + co[ce] = -1; + ce = 0; + + f = 0; + while(ce < p->di) { + double v; + int off = 0; /* Latice offset */ + for (e = 0; e < p->di; e++) { + off += co[e] * s->coi[e]; /* Accumulate latice offset */ + } + v = s->lat[off + f]; /* Value of this latice point */ + + printf("Latice at [%d][%d] = %f\n",co[1],co[0],v); + + /* Increment the latice counter */ + for (ce = 0; ce < p->di; ce++) { + if (++co[ce] <= s->res) /* Counting from -1 ... s->res */ + break; + co[ce] = -1; + } + } +} + +/* Initialise an srbs with a linear approximation to the scattered data */ +static void linear_srbs( +srbs *s +) { + int i, e, f; + mrbs *p = s->p; /* Parent object */ + double **A; /* A matrix holding scattered data points */ + double *B; /* B matrix holding RHS & solution */ + + /* Allocate the matricies */ + B = dvector(0, p->npts-1); + A = dmatrix(0, p->npts-1, 0, p->di); + + /* For each output dimension, solve the linear equation coeficients */ + for (f = 0; f < p->fdi; f++) { + int ce, co[MXDI]; /* latice counter */ + + /* Init A[][] with the scattered data points positions */ + /* Also init B[] with the value for this output dimension */ + for (i = 0; i < p->npts; i++) { + for (e = 0; e < p->di; e++) + A[i][e] = p->pts[i].p[e]; + A[i][e] = 1.0; + B[i] = p->pts[i].v[f]; + } + + /* Solve the equation A.x = b using SVD */ + /* (The w[] values are thresholded for best accuracy) */ + /* Return non-zero if no solution found */ + if (svdsolve(A, B, p->npts, p->di+1) != 0) + error("SVD least squares failed"); + /* A[][] will have been changed, and B[] holds the p->di+1 coefficients */ + + /* Use the coefficients to initialise the srbs values */ + for (ce = 0; ce < p->di; ce++) + co[ce] = -1; + ce = 0; + + while(ce < p->di) { + double v = B[p->di]; /* Constant */ + int off = 0; /* Latice offset */ + for (e = 0; e < p->di; e++) { + double lv; + lv = p->l[e] + s->w[e] * co[e]; /* Input value for this latice location */ + v += B[e] * lv; + off += co[e] * s->coi[e]; /* Accumulate latice offset */ + } + s->lat[off + f] = v; /* Value of this latice point */ + + /* Increment the latice counter */ + for (ce = 0; ce < p->di; ce++) { + if (++co[ce] <= s->res) /* Counting from -1 ... s->res */ + break; + co[ce] = -1; + } + } + } + free_dmatrix(A, 0, p->npts-1, 0, p->di); + free_dvector(B, 0, p->npts-1); +} + +/* Do a latice refinement - upsample the current */ +/* source latice to the destination latice. */ +static void refine_srbs( +srbs *ds, /* Destination srbs */ +srbs *ss /* Source srbs */ +) { + mrbs *p = ss->p; /* Parent object */ + int ce, co[MXDI]; /* Source coordinate counter */ + int six; /* Source index */ + int dix; /* destination index */ + static double _wt[5] = { 1.0/8.0, 4.0/8.0, 6.0/8.0, 4.0/8.0, 1.0/8.0 }; + static double *wt = &_wt[2]; /* 1D Distribution weighting */ + + /* Zero the destination latice before accumulating values */ + for (dix = 0; dix < ds->lsize; dix++) + ds->_lat[dix] = 0.0; + + /* Now for each source latice entry, add weighted portions */ + /* to the associated destination points */ + + /* Init the source coordinate counter */ + for (ce = 0; ce < p->di; ce++) + co[ce] = -1; + ce = 0; + six = -ss->loff; + + while(ce < p->di) { + int oe, oo[MXDI]; /* Destination offset counter */ + +//printf("Source coord %d %d, offset %d, value %f\n",co[0], co[1], six, ss->lat[six]); + /* calc destination index, and init offest counter */ + for (dix = oe = 0; oe < p->di; oe++) { + oo[oe] = -2; + dix += co[oe] * 2 * ds->coi[oe]; /* Accumulate dest offset */ + } + oe = 0; + +//printf("Dest coord %d %d\n",co[0] * 2, co[1] * 2); + /* For all the offsets from the destination point */ + while(oe < p->di) { + int e, f, dixo; /* Destination index offset */ + double w = 1.0; /* Weighting */ + +//printf("dest offset %d %d\n",oo[0], oo[1]); + /* Compute dest index offset, and check that we are not outside the destination */ + for (dixo = e = 0; e < p->di; e++) { + int x = co[e] * 2 + oo[e]; /* dest coord */ + dixo += oo[e] * ds->coi[e]; /* Accumulate dest offset */ +//printf("x[%d] = %d\n",e, x); + w *= wt[oo[e]]; /* Compute distribution weighting */ + if (x < -1 || x > ds->res) + break; /* No good */ + } + if (e >= p->di) { /* We are within the destination latice */ +//if ((co[0] * 2 + oo[0]) == 0 && (co[1] * 2 + oo[1]) == 0) { +//printf("Source coord %d %d, offset %d, value %f\n",co[0], co[1], six, ss->lat[six]); +//printf("Dest coord %d %d ix %d, weight %f\n",co[0] * 2 + oo[0], co[1] * 2 + oo[1], dix+dixo, w); +//} + + for (f = 0; f < p->fdi; f++) { /* Distribute weighted values */ + double v = ss->lat[six + f]; +//if ((co[0] * 2 + oo[0]) == 0 && (co[1] * 2 + oo[1]) == 0) +//printf("Value being dist %f, weighted value %f\n", v, v * w); + ds->lat[dix + dixo + f] += v * w; + } + } + + /* Increment destination offset counter */ + for (oe = 0; oe < p->di; oe++) { + if (++oo[oe] <= 2) /* Counting from -2 ... +2 */ + break; + oo[oe] = -2; + } + } + + /* Increment the source index and coordinat counter */ + six += p->fdi; + for (ce = 0; ce < p->di; ce++) { + if (++co[ce] <= ss->res) /* Counting from -1 ... ss->res */ + break; + co[ce] = -1; + } + } +} + +/* Compute the Cubic B-spline weightings for a given t */ +void basis(double b[4], double t) { + double _t3, _t2, _t1, _3t3, _3t2, _3t1, _6t2; + + _t1 = t/6.0; + _t2 = _t1 * _t1; + _t3 = _t2 * _t1; + _3t1 = 3.0 * _t1; + _3t2 = 3.0 * _t2; + _3t3 = 3.0 * _t3; + _6t2 = 6.0 * _t2; + + b[0] = - _t3 + _3t2 - _3t1 + 1.0/6.0; + b[1] = _3t3 - _6t2 + 4.0/6.0; + b[2] = -_3t3 + _3t2 + _3t1 + 1.0/6.0; + b[3] = _t3; +} + + +/* Improve an srbs to make it closer to the scattered data */ +static void improve_srbs( +srbs *s +) { + int i, e, f; + mrbs *p = s->p; /* Parent object */ + double *delta; /* Delta accumulation */ + double *omega; /* Omega accumulation */ + + /* Allocate temporary accumulation arrays */ + if ((delta = (double *)calloc(sizeof(double), s->lsize)) == NULL) + error("Malloc srbs temp latice failed"); + delta += s->loff; + if ((omega = (double *)calloc(sizeof(double), s->lsize)) == NULL) + error("Malloc srbs temp latice failed"); + omega += s->loff; + + /* For each scattered data point */ + for (i = 0; i < p->npts; i++) { + int ix; /* Latice index of base of neighborhood */ + double b[MXDI][4]; /* B-spline basis factors for each dimension */ + double sws; /* Sum of all the basis factors squared */ + double ve[MXDO]; /* Current output value error */ + int nn; /* Neighbor counter */ + + /* Figure out our neighborhood */ + for (ix = e = 0; e < p->di; e++) { + int x; + double t, sp, fp; + sp = (p->pts[i].p[e] - p->l[e])/s->w[e]; /* Scaled position */ + fp = floor(sp); + x = (int)(fp - 1.0); /* Grid coordinate */ + ix += s->coi[e] * x; /* Accume latice offset */ + t = sp - fp; /* Spline parameter */ + basis(b[e], t); /* Compute basis function values */ + } + + /* Compute the grid basis weight functions, */ + /* the sum of the weights squared, and the current */ + /* output value estimate. */ + for (f = 0; f < p->fdi; f++) + ve[f] = p->pts[i].v[f]; /* Target output value */ + for (sws = 0.0, nn = 0; nn < s->nsize; nn++) { + double w; + for (w = 1.0, e = 0; e < p->di; e++) + w *= b[e][s->n[nn].c[e]]; + s->n[nn].w = w; /* cache weighting */ + sws += w * w; + for (f = 0; f < p->fdi; f++) + ve[f] -= w * s->lat[ix + s->n[nn].xo + f]; /* Subtract current aprox value */ + } +printf("Error at point %d = %f\n",i,ve[0]); + + /* Accumulate the delta and omega factors */ + /* for this resolutions improvement. */ + for (nn = 0; nn < s->nsize; nn++) { + double ws, ww, w = s->n[nn].w; + int xo = ix + s->n[nn].xo; /* Latice offset */ + ww = w * w; + ws = ww * w/sws; /* Scale factor for delta */ + omega[xo] += ww; /* Accumulate omega */ + for (f = 0; f < p->fdi; f++) + delta[xo + f] += ws * ve[f]; /* Accumulate delta */ +printf("Distributing omega %f to %d %d\n",ww,s->n[nn].c[0],s->n[nn].c[1]); +printf("Distributing delta %f to %d %d\n",ws * ve[0],s->n[nn].c[0],s->n[nn].c[1]); + } + } + + omega -= s->loff; /* Base them back to -1 corner */ + delta -= s->loff; + + /* Go through the delta and omega arrays, */ + /* compute and add the refinements to the current */ + /* B-spline control latice. */ + for (i = 0; i < s->lsize; i++) { + double om = omega[i]; + if (om != 0.0) { + for (f = 0; f < p->fdi; f++) + s->_lat[i] += delta[i + f]/om; +printf("Adjusting latice index %d by %f to give %f\n",i, delta[i]/om, s->_lat[i]); + } + } + + /* Done with temporary arrays */ + free(omega); + free(delta); +} + +/* Return the interpolated value for a given point */ +/* Return NZ if input point is out of range */ +static int lookup_srbs( +mrbs *p, +co *c /* Point to interpolate */ +) { + srbs *s = p->s; + int e, f; + int ix; /* Latice index of base of neighborhood */ + double b[MXDI][4]; /* B-spline basis factors for each dimension */ + int nn; /* Neighbor counter */ + + /* Figure out our neighborhood */ + for (ix = e = 0; e < p->di; e++) { + int x; + double t, sp, fp; + sp = c->p[e]; + if (sp < p->l[e] || sp > p->h[e]) + return 1; + sp = (sp - p->l[e])/s->w[e]; /* Scaled position */ + fp = floor(sp); + x = (int)(fp - 1.0); /* Grid coordinate */ + ix += s->coi[e] * x; /* Accume latice offset */ + t = sp - fp; /* Spline parameter */ + basis(b[e], t); /* Compute basis function values */ + } + + /* Compute the the current output value. */ + for (f = 0; f < p->fdi; f++) + c->v[f] = 0.0; + for (nn = 0; nn < s->nsize; nn++) { + double w; + for (w = 1.0, e = 0; e < p->di; e++) + w *= b[e][s->n[nn].c[e]]; + for (f = 0; f < p->fdi; f++) + c->v[f] += w * s->lat[ix + s->n[nn].xo + f]; /* Accume spline value */ + } + + return 0; +} + +/* Take a list of scattered data points, */ +/* and setup the mrbs. */ +void set_mrbs( +mrbs *p, /* mrbs to set up */ +co *pts, /* scattered data points (taken) */ +int npts, /* number of scattered data points */ +double *l, /* Input data range, low (May be NULL) */ +double *h /* Input data range, high (May be NULL) */ +) { + int res; + int i, e, f; + srbs *s0 = NULL, *s1; + + /* Establish the input data range */ + for (e = 0; e < p->di; e++) { + if (l == NULL) + p->l[e] = 1e60; + else + p->l[e] = l[e]; + if (h == NULL) + p->h[e] = -1e60; + else + p->h[e] = h[e]; + } + for (i = 0; i < npts; i++) { + for (e = 0; e < p->di; e++) { + if (pts[i].p[e] < p->l[e]) + p->l[e] = pts[i].p[e]; + if (pts[i].p[e] > p->h[e]) + p->h[e] = pts[i].p[e]; + } + } + + /* Take ownership the data */ + p->pts = pts; + p->npts = npts; + + /* Create an initial srbs */ + res = 2; + if ((s1 = new_srbs(p, 2)) == NULL) + error("new_srbs failed"); + + /* Set it up with a linear first approximation */ + linear_srbs(s1); +dump_srbs(s1); + + /* Build up the resolution */ + for (res = 2 * res -1; res < p->tres; res = 2 * res -1) { + +printf("~1 doing resolution %d\n",res); + delete_srbs(s0); + s0 = s1; + if ((s1 = new_srbs(p, res)) == NULL) + error("new_srbs failed"); + + refine_srbs(s1, s0); +dump_srbs(s1); + improve_srbs(s1); + } + + delete_srbs(s0); + p->s = s1; /* Final resolution */ +} + + +/* - - - - - - - - - - - - - - - - */ + +/* Some test points */ +#define NP 5 +co tpts[NP] = { + { { 1, 5 }, { 3 } }, + { { 2, 9 }, { 6 } }, + { { 8, 0 }, { 1 } }, + { { 5, 4 }, { 10 } }, + { { 3, 9 }, { 4 } } +}; + +#define FRES 33 + +int +main(void) { + double ll[MXDI], hh[MXDI]; + mrbs *p; + int i; + +#ifdef NEVER /* Make points be on a plane */ +{ +for (i = 0; i < NP; i++) { + tpts[i].v[0] = 0.5 * tpts[i].p[0] + 0.7 * tpts[i].p[1] + 3.0; +printf("%f, %f -> %f\n", tpts[i].p[0], tpts[i].p[1], tpts[i].v[0]); +} +} +#endif + error_program = "stest"; + + printf("Starting test\n"); + p = new_mrbs(2, 1, FRES, 1.0); + + ll[0] = 0.0; + ll[1] = 0.0; + hh[0] = 10.0; + hh[1] = 10.0; + set_mrbs(p, tpts, NP, ll, hh); + + printf("Interpolation setup\n"); + + /* Check out the accuracy */ + for (i = 0; i < NP; i++) { + co tp; + tp.p[0] = tpts[i].p[0]; + tp.p[1] = tpts[i].p[1]; + if (lookup_srbs(p, &tp)) + printf("Point %d, %f %f outside domain\n",i,tpts[i].p[0],tpts[i].p[1]); + else { + printf("Point %d has value %f, should be %f\n",i,tp.v[0], tpts[i].v[0]); + } + } + + printf("Test done\n"); + return 0; +} + +#ifndef NUMSUP_H +/* Basic printf type error() and warning() routines */ + +void +error(char *fmt, ...) +{ + va_list args; + + fprintf(stderr,"stest: Error - "); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); + exit (-1); +} + +void +warning(char *fmt, ...) +{ + va_list args; + + fprintf(stderr,"stest: Warning - "); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); +} + +#endif /* NUMSUP_H */ + diff --git a/rspl/t2d.c b/rspl/t2d.c new file mode 100644 index 0000000..b1167ef --- /dev/null +++ b/rspl/t2d.c @@ -0,0 +1,1016 @@ +/************************************************/ +/* Test RSPL in 2D */ +/************************************************/ + +/* Author: Graeme Gill + * Date: 22/4/96 + * Derived from cmatch.c + * Copyright 1995 Graeme W. Gill + * + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + + +#define DEBUG +#define DETAILED + +#include +#include +#include +#include +#include "rspl.h" +#include "tiffio.h" +#include "plot.h" + + +#ifdef NEVER +FILE *verbose_out = stdout; +int verbose_level = 6; /* Current verbosity level */ + /* 0 = none */ + /* !0 = diagnostics */ +#endif /* NEVER */ + +#define PLOTRES 256 +#define WIDTH 400 /* Raster size */ +#define HEIGHT 400 + +#define MAX_ITS 500 +#define IT_TOL 0.0005 +#define GRES0 25 /* Default resolutions */ +#define GRES1 25 +#undef NEVER +#define ALWAYS + +//double t1xa[PNTS] = { 0.2, 0.25, 0.30, 0.35, 0.40, 0.44, 0.48, 0.51, 0.64, 0.75 }; +//double t1ya[PNTS] = { 0.3, 0.35, 0.4, 0.41, 0.42, 0.46, 0.5, 0.575, 0.48, 0.75 }; + +/* 1D test function repeated 3 times along x = 0.5 */ +co test_points1[] = { + {{ 0.4,0.20 },{ 0.30 }}, /* 0 */ + {{ 0.4,0.25 },{ 0.35 }}, /* 1 */ + {{ 0.4,0.30 },{ 0.40 }}, /* 2 */ + {{ 0.4,0.35 },{ 0.41 }}, /* 3 */ + {{ 0.4,0.40 },{ 0.42 }}, /* 4 */ + {{ 0.4,0.44 },{ 0.46 }}, /* 5 */ + {{ 0.4,0.48 },{ 0.50 }}, /* 6 */ + {{ 0.4,0.51 },{ 0.575 }}, /* 7 */ + {{ 0.4,0.64 },{ 0.48 }}, /* 8 */ + {{ 0.4,0.75 },{ 0.75 }}, /* 9 */ + + {{ 0.5,0.20 },{ 0.30 }}, /* 0 */ + {{ 0.5,0.25 },{ 0.35 }}, /* 1 */ + {{ 0.5,0.30 },{ 0.40 }}, /* 2 */ + {{ 0.5,0.35 },{ 0.41 }}, /* 3 */ + {{ 0.5,0.40 },{ 0.42 }}, /* 4 */ + {{ 0.5,0.44 },{ 0.46 }}, /* 5 */ + {{ 0.5,0.48 },{ 0.50 }}, /* 6 */ + {{ 0.5,0.51 },{ 0.575 }}, /* 7 */ + {{ 0.5,0.64 },{ 0.48 }}, /* 8 */ + {{ 0.5,0.75 },{ 0.75 }}, /* 9 */ + + {{ 0.6,0.20 },{ 0.30 }}, /* 0 */ + {{ 0.6,0.25 },{ 0.35 }}, /* 1 */ + {{ 0.6,0.30 },{ 0.40 }}, /* 2 */ + {{ 0.6,0.35 },{ 0.41 }}, /* 3 */ + {{ 0.6,0.40 },{ 0.42 }}, /* 4 */ + {{ 0.6,0.44 },{ 0.46 }}, /* 5 */ + {{ 0.6,0.48 },{ 0.50 }}, /* 6 */ + {{ 0.6,0.51 },{ 0.575 }}, /* 7 */ + {{ 0.6,0.64 },{ 0.48 }}, /* 8 */ + {{ 0.6,0.75 },{ 0.75 }} /* 9 */ +}; + +/* x + y^2 function with one non-monotonic point */ +co test_points2[] = { + {{ 0.1,0.1,0.5,0.0 },{ 0.11 }}, /* 0 */ + {{ 0.2,0.7,0.1,0.3 },{ 0.69 }}, /* 1 */ + {{ 0.8,0.8,0.8,0.2 },{ 1.44 }}, /* 2 */ + {{ 0.5,0.6,0.4,0.9 },{ 0.86 }}, /* 3 */ + {{ 0.2,0.5,0.2,0.7 },{ 0.45 }}, /* 4 */ +// {{ 0.3,0.7,0.2,0.8 },{ 0.35 }}, /* nm 5 */ + {{ 0.5,0.5,0.2,0.8 },{ 0.30 }}, /* nm 5 */ + {{ 0.5,0.4,0.9,0.3 },{ 0.66 }}, /* 6 */ + {{ 0.1,0.9,0.7,0.4 },{ 0.91 }}, /* 7 */ + {{ 0.7,0.2,0.1,0.3 },{ 0.74 }}, /* 8 */ + {{ 0.8,0.4,0.3,0.7 },{ 0.96 }}, /* 9 */ + {{ 0.3,0.3,0.4,0.1 },{ 0.39 }} /* 10 */ + }; + +/* x + y^2 function */ +co test_points3[] = { + {{ 0.1,0.1,0.5,0.0 },{ 0.11 }}, /* 0 */ + {{ 0.2,0.7,0.1,0.3 },{ 0.69 }}, /* 1 */ + {{ 0.8,0.8,0.8,0.2 },{ 1.44 }}, /* 2 */ + {{ 0.5,0.6,0.4,0.9 },{ 0.86 }}, /* 3 */ + {{ 0.2,0.5,0.2,0.7 },{ 0.45 }}, /* 4 */ + {{ 0.3,0.7,0.2,0.8 },{ 0.79 }}, /* 5 */ + {{ 0.5,0.4,0.9,0.3 },{ 0.66 }}, /* 6 */ + {{ 0.1,0.9,0.7,0.4 },{ 0.91 }}, /* 7 */ + {{ 0.7,0.2,0.1,0.3 },{ 0.74 }}, /* 8 */ + {{ 0.8,0.4,0.3,0.7 },{ 0.96 }}, /* 9 */ + {{ 0.3,0.3,0.4,0.1 },{ 0.39 }} /* 10 */ + }; + +/* Arbitrary values */ +co test_points4[] = { + {{ 0.1,0.1,0.5,0.0 },{ 0.0 }}, /* 0 */ + {{ 0.2,0.7,0.1,0.3 },{ 0.1 }}, /* 1 */ + {{ 0.8,0.8,0.8,0.2 },{ 0.2 }}, /* 2 */ + {{ 0.5,0.6,0.4,0.9 },{ 0.3 }}, /* 3 */ + {{ 0.2,0.5,0.2,0.7 },{ 0.4 }}, /* 4 */ + {{ 0.3,0.7,0.2,0.8 },{ 0.5 }}, /* 5 */ + {{ 0.5,0.4,0.9,0.3 },{ 0.6 }}, /* 6 */ + {{ 0.1,0.9,0.7,0.4 },{ 0.7 }}, /* 7 */ + {{ 0.7,0.2,0.1,0.3 },{ 0.8 }}, /* 8 */ + {{ 0.8,0.4,0.3,0.7 },{ 0.9 }}, /* 9 */ + {{ 0.3,0.3,0.4,0.1 },{ 1.0 }} /* 10 */ + }; + +/* single dimension line */ +co test_points5[] = { + {{ 0.4,0.2 },{ 0.10 }}, /* 0 */ + {{ 0.5,0.4 },{ 0.50 }}, /* 1 */ + {{ 0.6,0.6 },{ 0.90 }} /* 2 */ + }; + +/* Single value, many points */ +co test_points6[] = { + {{ 0.5,0.6 },{ 0.4 }}, /* 0 */ + {{ 0.4,0.4 },{ 0.4 }}, /* 1 */ + {{ 0.6,0.4 },{ 0.4 }}, /* 2 */ + {{ 0.5,0.8 },{ 0.4 }}, /* 3 */ + {{ 0.8,0.6 },{ 0.4 }}, /* 4 */ + {{ 0.8,0.3 },{ 0.4 }}, /* 5 */ + {{ 0.5,0.15 },{ 0.4 }}, /* 6 */ + {{ 0.2,0.3 },{ 0.4 }}, /* 7 */ + {{ 0.2,0.6 },{ 0.4 }} /* 8 */ + }; + +/* single value triangle */ +co test_points7[] = { + {{ 0.2,0.5 },{ 0.4 }}, /* 0 */ + {{ 0.6,0.2 },{ 0.4 }}, /* 1 */ + {{ 0.6,0.8 },{ 0.4 }} /* 2 */ + }; + +/* 3dap C+M L* values */ +co test_points8[] = { + {{ 0.0, 0.0 }, { 0.96433 }}, + {{ 1.0, 0.0 }, { 0.54532 }}, + {{ 0.0, 1.0 }, { 0.13844 }}, + {{ 1.0, 1.0 }, { 0.10533 }}, + {{ 0.52015, 0.51226 }, { 0.44379 }}, + {{ 0.016882, 0.4899 }, { 0.55744 }}, + {{ 0.48229, 0.014288 }, { 0.71457 }}, + {{ 0.32501, 0.99594 }, { 0.14023 }}, + {{ 0.9429, 0.39117 }, { 0.40529 }}, + {{ 0.25639, 0.25624 }, { 0.65594 }}, + {{ 0.80601, 0.62945 }, { 0.32338 }}, + {{ 0.74113, 0.20699 }, { 0.528 }}, + {{ 0.21766, 0.72345 }, { 0.37442 }}, + {{ 0.64679, 0.94379 }, { 0.16175 }}, + {{ 0.24217, 0.012757 }, { 0.81892 }}, + {{ 0.013911, 0.25268 }, { 0.72574 }}, + {{ 0.54723, 0.74872 }, { 0.30302 }}, + {{ 0.016488, 0.77535 }, { 0.33234 }}, + {{ 0.82135, 0.044133 }, { 0.57191 }}, + {{ 0.52377, 0.2403 }, { 0.58315 }}, + {{ 0.27798, 0.51935 }, { 0.49399 }}, + {{ 0.1186, 0.93321 }, { 0.19912 }}, + {{ 0.95827, 0.18481 }, { 0.48037 }}, + {{ 0.7714, 0.48664 }, { 0.39652 }}, + {{ 0.94209, 0.7863 }, { 0.22555 }}, + {{ 0.9429, 0.58967 }, { 0.32235 }}, + {{ 0.12251, 0.12897 }, { 0.78743 }}, + {{ 0.14563, 0.38536 }, { 0.60355 }}, + {{ 0.41616, 0.65055 }, { 0.3876 }}, + {{ 0.38091, 0.14221 }, { 0.68198 }}, + {{ 0.388, 0.38098 }, { 0.54479 }}, + {{ 0.74149, 0.77517 }, { 0.25597 }}, + {{ 0.11267, 0.62485 }, { 0.45976 }}, + {{ 0.62755, 0.37228 }, { 0.48943 }}, + {{ 0.6502, 0.59695 }, { 0.37031 }}, + {{ 0.32033, 0.81669 }, { 0.28831 }}, + {{ 0.664, 0.096944 }, { 0.60575 }}, + {{ 0.82772, 0.33242 }, { 0.45496 }}, + {{ 0.49121, 0.89468 }, { 0.20858 }}, + {{ 0.84729, 0.94866 }, { 0.1453 }} +}; + +/* 3dap C+M a* values */ +co test_points9[] = { + {{ 0.0, 0.0 }, { 0.504266 }}, + {{ 1.0, 0.0 }, { 0.14962 }}, + {{ 0.0, 1.0 }, { 0.538792 }}, + {{ 1.0, 1.0 }, { 0.481662 }}, + {{ 0.52015, 0.51226 }, { 0.39446 }}, + {{ 0.016882, 0.4899 }, { 0.5038104 }}, + {{ 0.48229, 0.014288 }, { 0.32387 }}, + {{ 0.32501, 0.99594 }, { 0.5028343 }}, + {{ 0.9429, 0.39117 }, { 0.24127 }}, + {{ 0.25639, 0.25624 }, { 0.43018 }}, + {{ 0.80601, 0.62945 }, { 0.34684 }}, + {{ 0.74113, 0.20699 }, { 0.274 }}, + {{ 0.21766, 0.72345 }, { 0.480491 }}, + {{ 0.64679, 0.94379 }, { 0.472241 }}, + {{ 0.24217, 0.012757 }, { 0.39938 }}, + {{ 0.013911, 0.25268 }, { 0.5035042 }}, + {{ 0.54723, 0.74872 }, { 0.433005 }}, + {{ 0.016488, 0.77535 }, { 0.513262 }}, + {{ 0.82135, 0.044133 }, { 0.21553 }}, + {{ 0.52377, 0.2403 }, { 0.35082 }}, + {{ 0.27798, 0.51935 }, { 0.451019 }}, + {{ 0.1186, 0.93321 }, { 0.513378 }}, + {{ 0.95827, 0.18481 }, { 0.19309 }}, + {{ 0.7714, 0.48664 }, { 0.32019 }}, + {{ 0.94209, 0.7863 }, { 0.37573 }}, + {{ 0.9429, 0.58967 }, { 0.29975 }}, + {{ 0.12251, 0.12897 }, { 0.460094 }}, + {{ 0.14563, 0.38536 }, { 0.471575 }}, + {{ 0.41616, 0.65055 }, { 0.439078 }}, + {{ 0.38091, 0.14221 }, { 0.37965 }}, + {{ 0.388, 0.38098 }, { 0.409296 }}, + {{ 0.74149, 0.77517 }, { 0.403494 }}, + {{ 0.11267, 0.62485 }, { 0.4905027 }}, + {{ 0.62755, 0.37228 }, { 0.34157 }}, + {{ 0.6502, 0.59695 }, { 0.37896 }}, + {{ 0.32033, 0.81669 }, { 0.477444 }}, + {{ 0.664, 0.096944 }, { 0.28 }}, + {{ 0.82772, 0.33242 }, {0.26821 }}, + {{ 0.49121, 0.89468 }, { 0.47241 }}, + {{ 0.84729, 0.94866 }, { 0.459256 }} +}; + +/* 3dap C+M b* values */ +co test_points10[] = { + {{ 0.0, 0.0 }, { 0.4918362 }}, + {{ 1.0, 0.0 }, { 0.0 }}, + {{ 0.0, 1.0 }, { 0.457067 }}, + {{ 1.0, 1.0 }, { 0.407021 }}, + {{ 0.52015, 0.51226 }, { 0.34029 }}, + {{ 0.016882, 0.4899 }, { 0.48672 }}, + {{ 0.48229, 0.014288 }, { 0.19406 }}, + {{ 0.32501, 0.99594 }, { 0.453116 }}, + {{ 0.9429, 0.39117 }, { 0.1766 }}, + {{ 0.25639, 0.25624 }, { 0.37533 }}, + {{ 0.80601, 0.62945 }, { 0.29851 }}, + {{ 0.74113, 0.20699 }, { 0.16808 }}, + {{ 0.21766, 0.72345 }, { 0.448783 }}, + {{ 0.64679, 0.94379 }, { 0.415526 }}, + {{ 0.24217, 0.012757 }, { 0.32194 }}, + {{ 0.013911, 0.25268 }, { 0.486404 }}, + {{ 0.54723, 0.74872 }, { 0.38695 }}, + {{ 0.016488, 0.77535 }, { 0.484031 }}, + {{ 0.82135, 0.044133 }, { 0.07184 }}, + {{ 0.52377, 0.2403 }, { 0.26192 }}, + {{ 0.27798, 0.51935 }, { 0.412787 }}, + {{ 0.1186, 0.93321 }, { 0.463822 }}, + {{ 0.95827, 0.18481 }, { 0.09177 }}, + {{ 0.7714, 0.48664 }, { 0.26049 }}, + {{ 0.94209, 0.7863 }, { 0.3249 }}, + {{ 0.9429, 0.58967 }, { 0.25115 }}, + {{ 0.12251, 0.12897 }, { 0.419966 }}, + {{ 0.14563, 0.38536 }, { 0.436771 }}, + {{ 0.41616, 0.65055 }, { 0.39638 }}, + {{ 0.38091, 0.14221 }, { 0.29133 }}, + {{ 0.388, 0.38098 }, { 0.35442 }}, + {{ 0.74149, 0.77517 }, { 0.35501 }}, + {{ 0.11267, 0.62485 }, { 0.466548 }}, + {{ 0.62755, 0.37228 }, { 0.2675 }}, + {{ 0.6502, 0.59695 }, { 0.32833 }}, + {{ 0.32033, 0.81669 }, { 0.438066 }}, + {{ 0.664, 0.096944 }, { 0.15598 }}, + {{ 0.82772, 0.33242 }, { 0.18834 }}, + {{ 0.49121, 0.89468 }, { 0.423113 }}, + {{ 0.84729, 0.94866 }, { 0.3979 }} +}; + +/* Values at edges test */ +double test_f11(double x, double y) { /* Function that computes values */ + double val = 0.0; + + val = (x * x - 0.5) * 0.6 + + (y * y - 0.5) * 0.4 + + 0.5; + + return val; +} +co test_points11[] = { + {{ 0.0, 0.0 }}, + {{ 0.0, 0.25 }}, + {{ 0.0, 0.5 }}, + {{ 0.0, 0.75 }}, + {{ 0.0, 1.0 }}, + + {{ 0.25, 0.0 }}, + {{ 0.25, 0.25 }}, + {{ 0.25, 0.5 }}, + {{ 0.25, 0.75 }}, + {{ 0.25, 1.0 }}, + + {{ 0.5, 0.0 }}, + {{ 0.5, 0.25 }}, + {{ 0.5, 0.5 }}, + {{ 0.5, 0.75 }}, + {{ 0.5, 1.0 }}, + + {{ 0.75, 0.0 }}, + {{ 0.75, 0.25 }}, + {{ 0.75, 0.5 }}, + {{ 0.75, 0.75 }}, + {{ 0.75, 1.0 }}, + + {{ 1.0, 0.0 }}, + {{ 1.0, 0.25 }}, + {{ 1.0, 0.5 }}, + {{ 1.0, 0.75 }}, + {{ 1.0, 1.0 }}, + + + {{ 0.2, 0.7 }}, + {{ 0.2, 0.8 }}, + {{ 0.2, 0.9 }}, + {{ 0.2, 1.0 }}, + + {{ 0.1, 0.7 }}, + {{ 0.1, 0.8 }}, + {{ 0.1, 0.9 }}, + {{ 0.1, 1.0 }}, + + {{ 0.0, 0.7 }}, + {{ 0.0, 0.8 }}, + {{ 0.0, 0.9 }} +}; + +/* Points consistent with a matrix interpolation */ +co test_points12[] = { + {{ 0.1, 0.1 }, { 0.1 }}, + {{ 0.5, 0.1 }, { 0.5 }}, + {{ 0.9, 0.1 }, { 0.9 }}, + {{ 0.1, 0.5 }, { 0.1 }}, + {{ 0.5, 0.5 }, { 0.35 }}, + {{ 0.9, 0.5 }, { 0.6 }}, + {{ 0.1, 0.9 }, { 0.1 }}, + {{ 0.5, 0.9 }, { 0.2 }}, + {{ 0.9, 0.9 }, { 0.3 }} +}; + +/* Points down the "neutral axis" extrapolation test */ +co test_points13[] = { + {{ 0.0069, 0.0071 }, { 0.0726 }}, + {{ 0.0068, 0.0071 }, { 0.0704 }}, + {{ 0.0069, 0.0072 }, { 0.0720 }}, + {{ 0.0069, 0.0072 }, { 0.0734 }}, + {{ 0.0069, 0.0072 }, { 0.0750 }}, + {{ 0.0070, 0.0072 }, { 0.0779 }}, + {{ 0.0070, 0.0072 }, { 0.0741 }}, + {{ 0.0069, 0.0072 }, { 0.0745 }}, + {{ 0.0069, 0.0072 }, { 0.0747 }}, + {{ 0.0071, 0.0073 }, { 0.0760 }}, + {{ 0.0070, 0.0073 }, { 0.0751 }}, + {{ 0.0070, 0.0073 }, { 0.0759 }}, + {{ 0.0071, 0.0074 }, { 0.0693 }}, + {{ 0.0071, 0.0074 }, { 0.0740 }}, + {{ 0.0072, 0.0075 }, { 0.0741 }}, + {{ 0.0199, 0.0209 }, { 0.1019 }}, + {{ 0.0296, 0.0306 }, { 0.1213 }}, + {{ 0.0627, 0.0651 }, { 0.1779 }}, + {{ 0.0831, 0.0863 }, { 0.2095 }}, + {{ 0.1091, 0.1134 }, { 0.2487 }}, + {{ 0.1442, 0.1497 }, { 0.2949 }}, + {{ 0.1745, 0.1814 }, { 0.3360 }}, + {{ 0.1747, 0.1816 }, { 0.3367 }}, + {{ 0.1747, 0.1816 }, { 0.3364 }}, + {{ 0.1748, 0.1816 }, { 0.3355 }}, + {{ 0.1749, 0.1817 }, { 0.3344 }}, + {{ 0.1748, 0.1817 }, { 0.3356 }}, + {{ 0.1748, 0.1817 }, { 0.3354 }}, + {{ 0.1749, 0.1817 }, { 0.3361 }}, + {{ 0.1749, 0.1818 }, { 0.3368 }}, + {{ 0.1749, 0.1818 }, { 0.3335 }}, + {{ 0.1750, 0.1818 }, { 0.3367 }}, + {{ 0.1750, 0.1819 }, { 0.3362 }}, + {{ 0.1750, 0.1819 }, { 0.3359 }}, + {{ 0.1751, 0.1820 }, { 0.3354 }}, + {{ 0.1752, 0.1821 }, { 0.3355 }}, + {{ 0.1754, 0.1823 }, { 0.3369 }}, + {{ 0.1756, 0.1824 }, { 0.3360 }}, + {{ 0.2743, 0.2842 }, { 0.4381 }}, + {{ 0.3289, 0.3411 }, { 0.4922 }}, + {{ 0.4036, 0.4184 }, { 0.5617 }}, + {{ 0.4689, 0.4854 }, { 0.6147 }}, + {{ 0.5379, 0.5567 }, { 0.6709 }}, + {{ 0.7137, 0.7420 }, { 0.8045 }}, + {{ 0.8730, 0.9105 }, { 0.9150 }}, + {{ 0.8738, 0.9113 }, { 0.9141 }}, + {{ 0.8741, 0.9116 }, { 0.9120 }}, + {{ 0.8744, 0.9118 }, { 0.9173 }}, + {{ 0.8748, 0.9123 }, { 0.9219 }}, + {{ 0.8748, 0.9123 }, { 0.9133 }}, + {{ 0.8748, 0.9124 }, { 0.9210 }}, + {{ 0.8751, 0.9127 }, { 0.9207 }}, + {{ 0.8751, 0.9127 }, { 0.9225 }}, + {{ 0.8754, 0.9130 }, { 0.9137 }}, + {{ 0.8757, 0.9133 }, { 0.9219 }}, + {{ 0.8759, 0.9135 }, { 0.9166 }}, + {{ 0.8761, 0.9137 }, { 0.9162 }}, + {{ 0.8759, 0.9137 }, { 0.9151 }}, + {{ 0.8765, 0.9141 }, { 0.9167 }} +}; + +#ifdef NEVER +#ifdef __STDC__ +#include +void error(char *fmt, ...), warning(char *fmt, ...), verbose(int level, char *fmt, ...); +#else +#include +void error(), warning(), verbose(); +#endif +#endif /* NEVER */ + +void write_rgb_tiff(char *name, int width, int height, unsigned char *data); + +void usage(void) { + fprintf(stderr,"Test 2D rspl interpolation\n"); + fprintf(stderr,"Author: Graeme W. Gill\n"); + fprintf(stderr,"usage: t2d [options]\n"); + fprintf(stderr," -t n Test set:\n"); + fprintf(stderr," * 1 = 1D curve along x = 0.5\n"); + fprintf(stderr," 2 = x + y^2 with nonmon point\n"); + fprintf(stderr," 3 = x + y^2\n"); + fprintf(stderr," 4 = arbitrary11\n"); + fprintf(stderr," 5 = 1D line of 3 points\n"); + fprintf(stderr," 6 = same value 11 points\n"); + fprintf(stderr," 7 = same value 3 points\n"); + fprintf(stderr," 8 = C + M printer L* values\n"); + fprintf(stderr," 9 = C + M printer a* values\n"); + fprintf(stderr," 10 = C + M printer b* values\n"); + fprintf(stderr," 11 = Points up to edge test\n"); + fprintf(stderr," 12 = Four points with high smoothing\n"); + fprintf(stderr," 13 = Neutral axis extrapolation\n"); + fprintf(stderr," -r resx,resy Set grid resolutions (def %d %d)\n",GRES0,GRES1); + fprintf(stderr," -h Test half scale resolution too\n"); + fprintf(stderr," -q Test quarter scale resolution too\n"); + fprintf(stderr," -2 Use two pass smoothing\n"); + fprintf(stderr," -x Use extra fitting\n"); + fprintf(stderr," -s Test symetric smoothness (set asymetric -r !)\n"); + fprintf(stderr," -S Test spline interpolation\n"); + fprintf(stderr," -p plot 3 slices, x = 0.5, y = 0.5, x = y\n"); + fprintf(stderr," -P x1:y1:x2:y2 plot a slice from x1,y1 to x2,y2\n"); + fprintf(stderr," -m No red point markers in TIFF\n"); + exit(1); +} + +int main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + rspl *rss; /* Regularized spline structure */ + rspl *rss2 = NULL; /* Regularized spline structure at half resolution */ + datai low,high; + int gres[MXDI]; + int gres2[MXDI]; + double avgdev[MXDO]; + co *test_points = test_points1; + int npoints = sizeof(test_points1)/sizeof(co); + int dospline = 0; + int twopass = 0; + int extra = 0; + int dosym = 0; + int doplot = 0; + double plotpts[2][2]; /* doplot == 2 start/end points */ + int doh = 0; /* half scale */ + int doq = 0; + int rsv; + int flags = RSPL_NOFLAGS; + int markers = 1; + double smooth = 1.0; + + low[0] = 0.0; + low[1] = 0.0; + high[0] = 1.0; + high[1] = 1.0; + gres[0] = GRES0; + gres[1] = GRES1; + avgdev[0] = 0.0; + avgdev[1] = 0.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(); + + /* test set */ + else if (argv[fa][1] == 't' || argv[fa][1] == 'T') { + int ix; + fa = nfa; + if (na == NULL) usage(); + ix = atoi(na); + switch (ix) { + case 1: + test_points = test_points1; + npoints = sizeof(test_points1)/sizeof(co); + break; + case 2: + test_points = test_points2; + npoints = sizeof(test_points2)/sizeof(co); + break; + case 3: + test_points = test_points3; + npoints = sizeof(test_points3)/sizeof(co); + break; + case 4: + test_points = test_points4; + npoints = sizeof(test_points4)/sizeof(co); + break; + case 5: + test_points = test_points5; + npoints = sizeof(test_points5)/sizeof(co); + break; + case 6: + test_points = test_points6; + npoints = sizeof(test_points6)/sizeof(co); + break; + case 7: + test_points = test_points7; + npoints = sizeof(test_points7)/sizeof(co); + break; + case 8: + test_points = test_points8; + npoints = sizeof(test_points8)/sizeof(co); + break; + case 9: + test_points = test_points9; + npoints = sizeof(test_points9)/sizeof(co); + break; + case 10: + test_points = test_points10; + npoints = sizeof(test_points10)/sizeof(co); + break; + case 11: { + int i; + test_points = test_points11; + npoints = sizeof(test_points11)/sizeof(co); + for (i = 0; i < npoints; i++) { + test_points[i].v[0] = test_f11(test_points[i].p[0],test_points[i].p[1]); + } + break; + case 12: + test_points = test_points12; + npoints = sizeof(test_points12)/sizeof(co); +// smooth = -100000.0; +// smooth = 1e-4; + avgdev[0] = 0.1; + avgdev[1] = 0.1; + break; + case 13: + test_points = test_points13; + npoints = sizeof(test_points13)/sizeof(co); + break; + } + default: + usage(); + } + + } else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') { + fa = nfa; + if (na == NULL) usage(); + if (sscanf(na, " %d,%d ", &gres[0], &gres[1]) != 2) + usage(); + + } else if (argv[fa][1] == 'h' || argv[fa][1] == 'H') { + doh = 1; + + } else if (argv[fa][1] == 'q' || argv[fa][1] == 'Q') { + doh = 1; + doq = 1; + + } else if (argv[fa][1] == 'p') { + doplot = 1; + + } else if (argv[fa][1] == 'P') { + doplot = 2; + fa = nfa; + if (na == NULL) usage(); + if (sscanf(na,"%lf:%lf:%lf:%lf",&plotpts[0][0],&plotpts[0][1],&plotpts[1][0],&plotpts[1][1]) != 4) { + usage(); + } + + } else if (argv[fa][1] == 'S') { + dospline = 1; + + } else if (argv[fa][1] == '2') { + twopass = 1; + + } else if (argv[fa][1] == 'x' || argv[fa][1] == 'X') { + extra = 1; + + } else if (argv[fa][1] == 's') { + dosym = 1; + + } else if (argv[fa][1] == 'm') { + markers = 0; + + } else + usage(); + } else + break; + } + + + if (twopass) + flags |= RSPL_2PASSSMTH; + + if (extra) + flags |= RSPL_EXTRAFIT2; + + if (dosym) + flags |= RSPL_SYMDOMAIN; + + /* Create the object */ + rss = new_rspl(RSPL_NOFLAGS, 2, 1); + + /* Fit to scattered data */ + rss->fit_rspl(rss, + flags, /* Non-mon and clip flags */ + test_points, /* Test points */ + npoints, /* Number of test points */ + low, high, gres, /* Low, high, resolution of grid */ + NULL, NULL, /* Default data scale */ + smooth, /* Smoothing */ + avgdev, /* Average deviation */ + NULL); /* iwidth */ + if (doh) { + + if (doq) { + gres2[0] = gres[0]/4; + gres2[1] = gres[1]/4; + } else { + gres2[0] = gres[0]/2; + gres2[1] = gres[1]/2; + } + + rss2 = new_rspl(RSPL_NOFLAGS, 2, 1); + + /* Fit to scattered data */ + rss2->fit_rspl(rss2, + flags, /* Non-mon and clip flags */ + test_points, /* Test points */ + npoints, /* Number of test points */ + low, high, gres2, /* Low, high, resolution of grid */ + NULL, NULL, /* Default data scale */ + 1.0, /* Smoothing */ + avgdev, /* Average deviation */ + NULL); /* iwidth */ + } + + /* Plot the interpolation in 2D */ + for (rsv = 0; rsv <= doh; rsv++) { + double x1 = -0.2; /* Plot range */ + double x2 = 1.2; + double y1 = -0.2; + double y2 = 1.2; + double min = -0.0; + double max = 1.0; + rspl *rs; + unsigned char pa[HEIGHT][WIDTH][3]; + co tco; /* Test point */ + double sx,sy; + int i,j,k; + + if (rsv == 0) + rs = rss; + else + rs = rss2; + + sx = (x2 - x1)/(double)WIDTH; + sy = (y2 - y1)/(double)HEIGHT; + + for (j=0; j < HEIGHT; j++) { + tco.p[1] = (double)((HEIGHT-1) - j) * sy + y1; + for (i=0; i < WIDTH; i++) { + tco.p[0] = (double)i * sx + x1; + if ((dospline && rs->spline_interp(rs, &tco)) + || (!dospline && rs->interp(rs, &tco))) { + pa[j][i][0] = 0; /* Out of bounds in green */ + pa[j][i][1] = 100; + pa[j][i][2] = 0; + } else { + int m; + /* printf("%d %d, %f %f returned %f\n",i,j,tco.p[0],tco.p[1],tco.v[0]); */ + m = (int)((255.0 * (tco.v[0] - min)/(max - min)) + 0.5); + if (m < 0) { + pa[j][i][0] = 20; /* Dark blue */ + pa[j][i][1] = 20; + pa[j][i][2] = 50; + } else if (m > 255) { + pa[j][i][0] = 230; /* Light blue */ + pa[j][i][1] = 230; + pa[j][i][2] = 255; + } else { + pa[j][i][0] = m; /* Level in grey */ + pa[j][i][1] = m; + pa[j][i][2] = m; + } + } + } + } + + if (markers) { + /* Mark verticies in red */ + for(k = 0; k < npoints; k++) { + j = (int)((HEIGHT * (y2 - test_points[k].p[1])/(y2 - y1)) + 0.5); + i = (int)((WIDTH * (test_points[k].p[0] - x1)/(x2 - x1)) + 0.5); + pa[j][i][0] = 255; + pa[j][i][1] = 0; + pa[j][i][2] = 0; + } + } + write_rgb_tiff(rsv == 0 ? "t2d.tif" : "t2dh.tif" ,WIDTH,HEIGHT,(unsigned char *)pa); + } + + /* Plot out 3 slices */ + if (doplot == 1) { + int slice; + + for (slice = 0; slice < 3; slice++) { + co tp; /* Test point */ + double x[PLOTRES]; + double ya[PLOTRES]; + double yb[PLOTRES]; + double xx,yy; + double x1,x2,y1,y2; + double sx,sy; + int i,n; + + /* Set up slice to plot */ + if (slice == 0) { + printf("Slice along x = 0.5\n"); + x1 = 0.5; y1 = 0.0; + x2 = 0.5; y2 = 1.0; + n = PLOTRES; + } else if (slice == 1) { + printf("Slice along y = 0.5\n"); + x1 = 0.0; y1 = 0.5; + x2 = 1.0; y2 = 0.5; + n = PLOTRES; + } else { + printf("Slice along x = y\n"); + x1 = 0.0; y1 = 0.0; + x2 = 1.0; y2 = 1.0; + n = PLOTRES; + } + + sx = (x2 - x1)/n; + sy = (y2 - y1)/n; + + xx = x1; + yy = y1; + for (i = 0; i < n; i++) { + double vv = i/(n-1.0); + x[i] = vv; + tp.p[0] = xx; + tp.p[1] = yy; + + if ((dospline && rss->spline_interp(rss, &tp)) + || (!dospline && rss->interp(rss, &tp))) + tp.v[0] = -0.1; + ya[i] = tp.v[0]; + + if (doh) { + if ((dospline && rss2->spline_interp(rss2, &tp)) + || (!dospline && rss2->interp(rss2, &tp))) + tp.v[0] = -0.1; + yb[i] = tp.v[0]; + } + + xx += sx; + yy += sy; + } + + /* Plot the result */ + if (doh) + do_plot(x,ya,yb,NULL,n); + else + do_plot(x,ya,NULL,NULL,n); + } + } else if (doplot == 2) { /* Plot a given slice */ + co tp; /* Test point */ + double x[PLOTRES]; + double ya[PLOTRES]; + double yb[PLOTRES]; + double xx,yy; + double x1,x2,y1,y2; + double sx,sy; + int i,n; + + x1 = plotpts[0][0]; + y1 = plotpts[0][1]; + x2 = plotpts[1][0]; + y2 = plotpts[1][1]; + + printf("Slice from %f,%f to %f,%f\n",x1,y1,x2,y2); + n = PLOTRES; + + sx = (x2 - x1)/n; + sy = (y2 - y1)/n; + + xx = x1; + yy = y1; + for (i = 0; i < n; i++) { + double vv = i/(n-1.0); + x[i] = vv; + tp.p[0] = xx; + tp.p[1] = yy; + + if ((dospline && rss->spline_interp(rss, &tp)) + || (!dospline && rss->interp(rss, &tp))) + tp.v[0] = -0.1; + ya[i] = tp.v[0]; + + if (doh) { + if ((dospline && rss2->spline_interp(rss2, &tp)) + || (!dospline && rss2->interp(rss2, &tp))) + tp.v[0] = -0.1; + yb[i] = tp.v[0]; + } + + xx += sx; + yy += sy; + } + + /* Plot the result */ + if (doh) + do_plot(x,ya,yb,NULL,n); + else + do_plot(x,ya,NULL,NULL,n); + } + + /* Report the fit */ + { + co tco; /* Test point */ + int k; + double avg = 0; + double max = 0.0; + + for(k = 0; k < npoints; k++) { + double err; + tco.p[0] = test_points[k].p[0]; + tco.p[1] = test_points[k].p[1]; + if (dospline) + rss->spline_interp(rss, &tco); + else + rss->interp(rss, &tco); + + err = tco.v[0] - test_points[k].v[0]; + err = fabs(err); + + avg += err; + if (err > max) + max = err; + } + avg /= (double)npoints; + printf("Max error %f%%, average %f%%\n",100.0 * max, 100.0 * avg); + } + return 0; +} + +/* ---------------------- */ +/* Tiff diagnostic output */ + +void +write_rgb_tiff( +char *name, +int width, +int height, +unsigned char *data +) { + int y; + unsigned char *dp; + TIFF *tif; + + if ((tif = TIFFOpen(name, "w")) == NULL) { + fprintf(stderr,"Failed to open output TIFF file '%s'\n",name); + exit (-1); + } + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + + for (dp = data, y = 0; y < height; y++, dp += 3 * width) { + if (TIFFWriteScanline(tif, (tdata_t)dp, y, 0) < 0) { + fprintf(stderr,"WriteScanline Failed at line %d\n",y); + exit (-1); + } + } + (void) TIFFClose(tif); +} + +#ifdef NEVER +/******************************************************************/ +/* Error/debug output routines */ +/******************************************************************/ + +/* Basic printf type error() and warning() routines */ + +#ifdef __STDC__ +void +error(char *fmt, ...) +#else +void +error(va_alist) +va_dcl +#endif +{ + va_list args; +#ifndef __STDC__ + char *fmt; +#endif + + fprintf(stderr,"cmatch: Error - "); +#ifdef __STDC__ + va_start(args, fmt); +#else + va_start(args); + fmt = va_arg(args, char *); +#endif + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); + fflush(stdout); + exit (-1); +} + +#ifdef __STDC__ +void +warning(char *fmt, ...) +#else +void +warning(va_alist) +va_dcl +#endif +{ + va_list args; +#ifndef __STDC__ + char *fmt; +#endif + + fprintf(stderr,"cmatch: Warning - "); +#ifdef __STDC__ + va_start(args, fmt); +#else + va_start(args); + fmt = va_arg(args, char *); +#endif + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); +} + +#ifdef __STDC__ +void +verbose(int level, char *fmt, ...) +{ + va_list args; + va_start(args, fmt); +#else +verbose(va_alist) +va_dcl +{ + va_list args; + int level; + char *fmt; + va_start(args); + level = va_arg(args, int); + fmt = va_arg(args, char *); +#endif + if (verbose_level >= level) + { + fprintf(verbose_out,"cmatch: "); + vfprintf(verbose_out, fmt, args); + fprintf(verbose_out, "\n"); + fflush(verbose_out); + } + va_end(args); +} +#endif /* NEVER */ diff --git a/rspl/t2ddf.c b/rspl/t2ddf.c new file mode 100644 index 0000000..92e7e3f --- /dev/null +++ b/rspl/t2ddf.c @@ -0,0 +1,517 @@ +/************************************************/ +/* Test RSPL in 2D with a weak default function */ +/************************************************/ + +/* Author: Graeme Gill + * Date: 20/11/2005 + * Derived from cmatch.c + * Copyright 1995, 2005 Graeme W. Gill + * + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + + +#define DEBUG +#define DETAILED + +#include +#include +#include +#include +#include "rspl.h" +#include "tiffio.h" +#include "plot.h" + +#ifdef NEVER +#define INTERP spline_interp +#else +#define INTERP interp +#endif + +#ifdef NEVER +FILE *verbose_out = stdout; +int verbose_level = 6; /* Current verbosity level */ + /* 0 = none */ + /* !0 = diagnostics */ +#endif /* NEVER */ + +/* rspl flags */ +#define FLAGS (0 /* | RSPL_EXTRAFIT */) + +#define PLOTRES 256 +#define WIDTH 400 /* Raster size */ +#define HEIGHT 400 + +#define MAX_ITS 500 +#define IT_TOL 0.0005 +#define GRES0 25 /* Default resolutions */ +#define GRES1 25 +#undef NEVER +#define ALWAYS + +/* two correction points along x = 0.5 */ +co test_points1[] = { +// {{ 0.5,0.325 },{ 0.4 }}, /* 0 */ +// {{ 0.5,0.625 },{ 0.70 }} /* 1 */ + {{ 0.4,0.325 },{ 0.5 }}, /* 0 */ + {{ 0.4,0.625 },{ 0.8 }}, /* 1 */ + {{ 0.5,0.325 },{ 0.5 }}, /* 0 */ + {{ 0.5,0.625 },{ 0.8 }}, /* 1 */ + {{ 0.6,0.325 },{ 0.5 }}, /* 0 */ + {{ 0.6,0.625 },{ 0.8 }} /* 1 */ +}; + +#ifdef NEVER +#ifdef __STDC__ +#include +void error(char *fmt, ...), warning(char *fmt, ...), verbose(int level, char *fmt, ...); +#else +#include +void error(), warning(), verbose(); +#endif +#endif /* NEVER */ + +void write_rgb_tiff(char *name, int width, int height, unsigned char *data); + +/* Weak default function */ +/* Linear along y, independent of x */ +static void wfunc(void *cbntx, double *out, double *in) { + out[0] = in[1]; +} + +void usage(void) { + fprintf(stderr,"Test 2D rspl interpolation with weak default function\n"); + fprintf(stderr,"Author: Graeme W. Gill\n"); + fprintf(stderr,"usage: t2d [options]\n"); + fprintf(stderr," -t n Test set:\n"); + fprintf(stderr," * 1 = two points along x\n"); + fprintf(stderr," -w wweight Set weak default function weight (default 1.0)\n"); + fprintf(stderr," -r resx,resy Set grid resolutions (def %d %d)\n",GRES0,GRES1); + fprintf(stderr," -h Test half scale resolution too\n"); + fprintf(stderr," -q Test quarter scale resolution too\n"); + fprintf(stderr," -s Test symetric smoothness\n"); + fprintf(stderr," -p plot 3 slices, x = 0.5, y = 0.5, x = y\n"); + exit(1); +} + +int main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + rspl *rss; /* Regularized spline structure */ + rspl *rss2 = NULL; /* Regularized spline structure at half resolution */ + datai low,high; + int gres[MXDI]; + int gres2[MXDI]; + double avgdev[MXDO]; + co *test_points = test_points1; + int npoints = sizeof(test_points1)/sizeof(co); + double wweight = 1.0; + int dosym = 0; + int doplot = 0; + int doh = 0; + int doq = 0; + int rsv; + int flags = FLAGS; + + low[0] = 0.0; + low[1] = 0.0; + high[0] = 1.0; + high[1] = 1.0; + gres[0] = GRES0; + gres[1] = GRES1; + avgdev[0] = 0.0; + avgdev[1] = 0.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(); + + /* test set */ + else if (argv[fa][1] == 't' || argv[fa][1] == 'T') { + int ix; + fa = nfa; + if (na == NULL) usage(); + ix = atoi(na); + switch (ix) { + case 1: + test_points = test_points1; + npoints = sizeof(test_points1)/sizeof(co); + break; + default: + usage(); + } + + } else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') { + fa = nfa; + if (na == NULL) usage(); + wweight = atof(na); + + } else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') { + fa = nfa; + if (na == NULL) usage(); + if (sscanf(na, " %d,%d ", &gres[0], &gres[1]) != 2) + usage(); + + } else if (argv[fa][1] == 'h' || argv[fa][1] == 'H') { + doh = 1; + + } else if (argv[fa][1] == 'q' || argv[fa][1] == 'Q') { + doh = 1; + doq = 1; + + } else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') { + doplot = 1; + + } else if (argv[fa][1] == 's' || argv[fa][1] == 'S') { + dosym = 1; + + } else + usage(); + } else + break; + } + + + if (dosym) + flags |= RSPL_SYMDOMAIN; + + /* Create the object */ + rss = new_rspl(RSPL_NOFLAGS, 2, 1); + + /* Fit to scattered data */ + rss->fit_rspl_df(rss, + flags, /* Non-mon and clip flags */ + test_points, /* Test points */ + npoints, /* Number of test points */ + low, high, gres, /* Low, high, resolution of grid */ + NULL, NULL, /* Default data scale */ + 1.0, /* Smoothing */ + avgdev, /* Average Deviation */ + NULL, /* iwidth */ + wweight, /* weak function weight */ + NULL, /* No context */ + wfunc /* Weak function */ + ); + + if (doh) { + + if (doq) { + gres2[0] = gres[0]/4; + gres2[1] = gres[1]/4; + } else { + gres2[0] = gres[0]/2; + gres2[1] = gres[1]/2; + } + + rss2 = new_rspl(RSPL_NOFLAGS, 2, 1); + + /* Fit to scattered data */ + rss2->fit_rspl_df(rss2, + flags, /* Non-mon and clip flags */ + test_points, /* Test points */ + npoints, /* Number of test points */ + low, high, gres2, /* Low, high, resolution of grid */ + NULL, NULL, /* Default data scale */ + 1.0, /* Smoothing */ + avgdev, /* Average Deviation */ + NULL, /* iwidth */ + wweight, /* weak function weight */ + NULL, /* No context */ + wfunc /* Weak function */ + ); + } + + /* Test the interpolation in 2D */ + for (rsv = 0; rsv <= doh; rsv++) { + double x1 = -0.2; + double x2 = 1.2; + double y1 = -0.2; + double y2 = 1.2; + double min = -0.0; + double max = 1.0; + rspl *rs; + unsigned char pa[HEIGHT][WIDTH][3]; + co tco; /* Test point */ + double sx,sy; + int i,j,k; + + if (rsv == 0) + rs = rss; + else + rs = rss2; + + sx = (x2 - x1)/(double)WIDTH; + sy = (y2 - y1)/(double)HEIGHT; + + for (j=0; j < HEIGHT; j++) { + tco.p[1] = (double)((HEIGHT-1) - j) * sy + y1; + for (i=0; i < WIDTH; i++) { + tco.p[0] = (double)i * sx + x1; + if (rs->INTERP(rs, &tco)) { + pa[j][i][0] = 0; /* Out of bounds in green */ + pa[j][i][1] = 100; + pa[j][i][2] = 0; + } else { + int m; + /* printf("%d %d, %f %f returned %f\n",i,j,tco.p[0],tco.p[1],tco.v[0]); */ + m = (int)((255.0 * (tco.v[0] - min)/(max - min)) + 0.5); + if (m < 0) { + pa[j][i][0] = 20; /* Dark blue */ + pa[j][i][1] = 20; + pa[j][i][2] = 50; + } else if (m > 255) { + pa[j][i][0] = 230; /* Light blue */ + pa[j][i][1] = 230; + pa[j][i][2] = 255; + } else { + pa[j][i][0] = m; /* Level in grey */ + pa[j][i][1] = m; + pa[j][i][2] = m; + } + } + } + } + + /* Mark verticies in red */ + for(k = 0; k < npoints; k++) { + j = (int)((HEIGHT * (y2 - test_points[k].p[1])/(y2 - y1)) + 0.5); + i = (int)((WIDTH * (test_points[k].p[0] - x1)/(x2 - x1)) + 0.5); + pa[j][i][0] = 255; + pa[j][i][1] = 0; + pa[j][i][2] = 0; + } + write_rgb_tiff(rsv == 0 ? "t2d.tif" : "t2dh.tif" ,WIDTH,HEIGHT,(unsigned char *)pa); + } + + /* Plot out 3 slices */ + if (doplot) { + int slice; + + for (slice = 0; slice < 3; slice++) { + co tp; /* Test point */ + double x[PLOTRES]; + double ya[PLOTRES]; + double yb[PLOTRES]; + double xx,yy; + double x1,x2,y1,y2; + double sx,sy; + int i,n; + + /* Set up slice to plot */ + if (slice == 0) { + x1 = 0.5; y1 = 0.0; + x2 = 0.5; y2 = 1.0; + n = PLOTRES; + printf("Plot along y at x = 0.5\n"); + } else if (slice == 1) { + x1 = 0.0; y1 = 0.5; + x2 = 1.0; y2 = 0.5; + n = PLOTRES; + printf("Plot along x at y = 0.5\n"); + } else { + x1 = 0.0; y1 = 0.0; + x2 = 1.0; y2 = 1.0; + n = PLOTRES; + printf("Plot along x = y\n"); + } + + sx = (x2 - x1)/n; + sy = (y2 - y1)/n; + + xx = x1; + yy = y1; + for (i = 0; i < n; i++) { + double vv = i/(n-1.0); + x[i] = vv; + tp.p[0] = xx; + tp.p[1] = yy; + + if (rss->INTERP(rss, &tp)) + tp.v[0] = -0.1; + ya[i] = tp.v[0]; + + if (doh) { + if (rss2->INTERP(rss2, &tp)) + tp.v[0] = -0.1; + yb[i] = tp.v[0]; + } + + xx += sx; + yy += sy; + } + + /* Plot the result */ + if (doh) + do_plot(x,ya,yb,NULL,n); + else + do_plot(x,ya,NULL,NULL,n); + } + } + + /* Report the fit */ + { + co tco; /* Test point */ + int k; + double avg = 0; + double max = 0.0; + + for(k = 0; k < npoints; k++) { + double err; + tco.p[0] = test_points[k].p[0]; + tco.p[1] = test_points[k].p[1]; + rss->INTERP(rss, &tco); + + err = tco.v[0] - test_points[k].v[0]; + err = fabs(err); + + avg += err; + if (err > max) + max = err; + } + avg /= (double)npoints; + printf("Max error %f%%, average %f%%\n",100.0 * max, 100.0 * avg); + } + return 0; +} + +/* ---------------------- */ +/* Tiff diagnostic output */ + +void +write_rgb_tiff( +char *name, +int width, +int height, +unsigned char *data +) { + int y; + unsigned char *dp; + TIFF *tif; + + if ((tif = TIFFOpen(name, "w")) == NULL) { + fprintf(stderr,"Failed to open output TIFF file '%s'\n",name); + exit (-1); + } + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + + for (dp = data, y = 0; y < height; y++, dp += 3 * width) { + if (TIFFWriteScanline(tif, (tdata_t)dp, y, 0) < 0) { + fprintf(stderr,"WriteScanline Failed at line %d\n",y); + exit (-1); + } + } + (void) TIFFClose(tif); +} + +#ifdef NEVER +/******************************************************************/ +/* Error/debug output routines */ +/******************************************************************/ + +/* Basic printf type error() and warning() routines */ + +#ifdef __STDC__ +void +error(char *fmt, ...) +#else +void +error(va_alist) +va_dcl +#endif +{ + va_list args; +#ifndef __STDC__ + char *fmt; +#endif + + fprintf(stderr,"cmatch: Error - "); +#ifdef __STDC__ + va_start(args, fmt); +#else + va_start(args); + fmt = va_arg(args, char *); +#endif + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); + fflush(stdout); + exit (-1); +} + +#ifdef __STDC__ +void +warning(char *fmt, ...) +#else +void +warning(va_alist) +va_dcl +#endif +{ + va_list args; +#ifndef __STDC__ + char *fmt; +#endif + + fprintf(stderr,"cmatch: Warning - "); +#ifdef __STDC__ + va_start(args, fmt); +#else + va_start(args); + fmt = va_arg(args, char *); +#endif + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); +} + +#ifdef __STDC__ +void +verbose(int level, char *fmt, ...) +{ + va_list args; + va_start(args, fmt); +#else +verbose(va_alist) +va_dcl +{ + va_list args; + int level; + char *fmt; + va_start(args); + level = va_arg(args, int); + fmt = va_arg(args, char *); +#endif + if (verbose_level >= level) + { + fprintf(verbose_out,"cmatch: "); + vfprintf(verbose_out, fmt, args); + fprintf(verbose_out, "\n"); + fflush(verbose_out); + } + va_end(args); +} +#endif /* NEVER */ diff --git a/rspl/t3d.c b/rspl/t3d.c new file mode 100644 index 0000000..1935545 --- /dev/null +++ b/rspl/t3d.c @@ -0,0 +1,905 @@ +/************************************************/ +/* Test RSPL in 3D */ +/************************************************/ + +/* Author: Graeme Gill + * Date: 22/4/96 + * Derived from cmatch.c + * Copyright 1995 Graeme W. Gill + * + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + + +#define DEBUG +#define DETAILED + +#include +#include +#include +#include +#include "rspl.h" +#include "tiffio.h" +#include "plot.h" + +#ifdef NEVER +#define INTERP spline_interp +#else +#define INTERP interp +#endif + +#ifdef NEVER +FILE *verbose_out = stdout; +int verbose_level = 6; /* Current verbosity level */ + /* 0 = none */ + /* !0 = diagnostics */ +#endif /* NEVER */ + +#define PLOTRES 256 +#define WIDTH 400 /* Raster size */ +#define HEIGHT 400 + +#define MAX_ITS 500 +#define IT_TOL 0.0005 +#define GRES0 33 /* Default rspl resolutions */ +#define GRES1 33 +#define GRES2 33 +#undef NEVER +#define ALWAYS + +//double t1xa[PNTS] = { 0.2, 0.25, 0.30, 0.35, 0.40, 0.44, 0.48, 0.51, 0.64, 0.75 }; +//double t1ya[PNTS] = { 0.3, 0.35, 0.4, 0.41, 0.42, 0.46, 0.5, 0.575, 0.48, 0.75 }; + +/* 1D test function repeated 3 times along x = y = 0.5 */ +co test_points1[] = { + {{ 0.4,0.4,0.20 },{ 0.30 }}, /* 0 */ + {{ 0.4,0.4,0.25 },{ 0.35 }}, /* 1 */ + {{ 0.4,0.4,0.30 },{ 0.40 }}, /* 2 */ + {{ 0.4,0.4,0.35 },{ 0.41 }}, /* 3 */ + {{ 0.4,0.4,0.40 },{ 0.42 }}, /* 4 */ + {{ 0.4,0.4,0.44 },{ 0.46 }}, /* 5 */ + {{ 0.4,0.4,0.48 },{ 0.50 }}, /* 6 */ + {{ 0.4,0.4,0.51 },{ 0.575 }}, /* 7 */ + {{ 0.4,0.4,0.64 },{ 0.48 }}, /* 8 */ + {{ 0.4,0.4,0.75 },{ 0.75 }}, /* 9 */ + + {{ 0.5,0.4,0.20 },{ 0.30 }}, /* 0 */ + {{ 0.5,0.4,0.25 },{ 0.35 }}, /* 1 */ + {{ 0.5,0.4,0.30 },{ 0.40 }}, /* 2 */ + {{ 0.5,0.4,0.35 },{ 0.41 }}, /* 3 */ + {{ 0.5,0.4,0.40 },{ 0.42 }}, /* 4 */ + {{ 0.5,0.4,0.44 },{ 0.46 }}, /* 5 */ + {{ 0.5,0.4,0.48 },{ 0.50 }}, /* 6 */ + {{ 0.5,0.4,0.51 },{ 0.575 }}, /* 7 */ + {{ 0.5,0.4,0.64 },{ 0.48 }}, /* 8 */ + {{ 0.5,0.4,0.75 },{ 0.75 }}, /* 9 */ + + {{ 0.6,0.4,0.20 },{ 0.30 }}, /* 0 */ + {{ 0.6,0.4,0.25 },{ 0.35 }}, /* 1 */ + {{ 0.6,0.4,0.30 },{ 0.40 }}, /* 2 */ + {{ 0.6,0.4,0.35 },{ 0.41 }}, /* 3 */ + {{ 0.6,0.4,0.40 },{ 0.42 }}, /* 4 */ + {{ 0.6,0.4,0.44 },{ 0.46 }}, /* 5 */ + {{ 0.6,0.4,0.48 },{ 0.50 }}, /* 6 */ + {{ 0.6,0.4,0.51 },{ 0.575 }}, /* 7 */ + {{ 0.6,0.4,0.64 },{ 0.48 }}, /* 8 */ + {{ 0.6,0.4,0.75 },{ 0.75 }}, /* 9 */ + + + {{ 0.4,0.5,0.20 },{ 0.30 }}, /* 0 */ + {{ 0.4,0.5,0.25 },{ 0.35 }}, /* 1 */ + {{ 0.4,0.5,0.30 },{ 0.40 }}, /* 2 */ + {{ 0.4,0.5,0.35 },{ 0.41 }}, /* 3 */ + {{ 0.4,0.5,0.40 },{ 0.42 }}, /* 4 */ + {{ 0.4,0.5,0.44 },{ 0.46 }}, /* 5 */ + {{ 0.4,0.5,0.48 },{ 0.50 }}, /* 6 */ + {{ 0.4,0.5,0.51 },{ 0.575 }}, /* 7 */ + {{ 0.4,0.5,0.64 },{ 0.48 }}, /* 8 */ + {{ 0.4,0.5,0.75 },{ 0.75 }}, /* 9 */ + + {{ 0.5,0.5,0.20 },{ 0.30 }}, /* 0 */ + {{ 0.5,0.5,0.25 },{ 0.35 }}, /* 1 */ + {{ 0.5,0.5,0.30 },{ 0.40 }}, /* 2 */ + {{ 0.5,0.5,0.35 },{ 0.41 }}, /* 3 */ + {{ 0.5,0.5,0.40 },{ 0.42 }}, /* 4 */ + {{ 0.5,0.5,0.44 },{ 0.46 }}, /* 5 */ + {{ 0.5,0.5,0.48 },{ 0.50 }}, /* 6 */ + {{ 0.5,0.5,0.51 },{ 0.575 }}, /* 7 */ + {{ 0.5,0.5,0.64 },{ 0.48 }}, /* 8 */ + {{ 0.5,0.5,0.75 },{ 0.75 }}, /* 9 */ + + {{ 0.6,0.5,0.20 },{ 0.30 }}, /* 0 */ + {{ 0.6,0.5,0.25 },{ 0.35 }}, /* 1 */ + {{ 0.6,0.5,0.30 },{ 0.40 }}, /* 2 */ + {{ 0.6,0.5,0.35 },{ 0.41 }}, /* 3 */ + {{ 0.6,0.5,0.40 },{ 0.42 }}, /* 4 */ + {{ 0.6,0.5,0.44 },{ 0.46 }}, /* 5 */ + {{ 0.6,0.5,0.48 },{ 0.50 }}, /* 6 */ + {{ 0.6,0.5,0.51 },{ 0.575 }}, /* 7 */ + {{ 0.6,0.5,0.64 },{ 0.48 }}, /* 8 */ + {{ 0.6,0.5,0.75 },{ 0.75 }}, /* 9 */ + + + {{ 0.4,0.6,0.20 },{ 0.30 }}, /* 0 */ + {{ 0.4,0.6,0.25 },{ 0.35 }}, /* 1 */ + {{ 0.4,0.6,0.30 },{ 0.40 }}, /* 2 */ + {{ 0.4,0.6,0.35 },{ 0.41 }}, /* 3 */ + {{ 0.4,0.6,0.40 },{ 0.42 }}, /* 4 */ + {{ 0.4,0.6,0.44 },{ 0.46 }}, /* 5 */ + {{ 0.4,0.6,0.48 },{ 0.50 }}, /* 6 */ + {{ 0.4,0.6,0.51 },{ 0.575 }}, /* 7 */ + {{ 0.4,0.6,0.64 },{ 0.48 }}, /* 8 */ + {{ 0.4,0.6,0.75 },{ 0.75 }}, /* 9 */ + + {{ 0.5,0.6,0.20 },{ 0.30 }}, /* 0 */ + {{ 0.5,0.6,0.25 },{ 0.35 }}, /* 1 */ + {{ 0.5,0.6,0.30 },{ 0.40 }}, /* 2 */ + {{ 0.5,0.6,0.35 },{ 0.41 }}, /* 3 */ + {{ 0.5,0.6,0.40 },{ 0.42 }}, /* 4 */ + {{ 0.5,0.6,0.44 },{ 0.46 }}, /* 5 */ + {{ 0.5,0.6,0.48 },{ 0.50 }}, /* 6 */ + {{ 0.5,0.6,0.51 },{ 0.575 }}, /* 7 */ + {{ 0.5,0.6,0.64 },{ 0.48 }}, /* 8 */ + {{ 0.5,0.6,0.75 },{ 0.75 }}, /* 9 */ + + {{ 0.6,0.6,0.20 },{ 0.30 }}, /* 0 */ + {{ 0.6,0.6,0.25 },{ 0.35 }}, /* 1 */ + {{ 0.6,0.6,0.30 },{ 0.40 }}, /* 2 */ + {{ 0.6,0.6,0.35 },{ 0.41 }}, /* 3 */ + {{ 0.6,0.6,0.40 },{ 0.42 }}, /* 4 */ + {{ 0.6,0.6,0.44 },{ 0.46 }}, /* 5 */ + {{ 0.6,0.6,0.48 },{ 0.50 }}, /* 6 */ + {{ 0.6,0.6,0.51 },{ 0.575 }}, /* 7 */ + {{ 0.6,0.6,0.64 },{ 0.48 }}, /* 8 */ + {{ 0.6,0.6,0.75 },{ 0.75 }} /* 9 */ +}; + +/* function */ +co test_points2[] = { + {{ 0.50915, 0.50936, 0.57048 },{ 0.36209169635 }}, + {{ 0.85943, 0.84331, 0.81487 },{ 0.91571493013 }}, + {{ 0.11381, 0.80378, 0.82951 },{ 0.16052707023 }}, + {{ 0.79087, 0.11157, 0.83913 },{ 0.30641388193 }}, + {{ 0.16297, 0.23090, 0.96417 },{ 0.15005479047 }}, + {{ 0.78181, 0.80097, 0.00192 },{ 0.32179402798 }}, + {{ 0.16141, 0.84321, 0.16561 },{ 0.14446082013 }}, + {{ 0.79859, 0.11111, 0.15547 },{ -0.1308162293 }}, + {{ 0.12959, 0.16184, 0.21825 },{ 0.03520247555 }}, + {{ 1.00000, 0.46395, 0.84399 },{ 0.63914200000 }}, + {{ 0.48724, 0.76024, 1.00000 },{ 0.64150992880 }}, + {{ 0.40744, 0.00000, 0.65533 },{ 0.13060244736 }}, + {{ 0.10931, 0.43216, 0.25195 },{ 0.06329759515 }}, + {{ 0.69401, 0.99412, 0.55335 },{ 0.75632862795 }}, + {{ 0.51759, 0.30372, 0.13622 },{ 0.07965761859 }}, + {{ 0.98628, 0.40857, 0.47688 },{ 0.29286006552 }}, + {{ 0.44474, 0.26024, 1.00000 },{ 0.37263430380 }}, + {{ 0.15229, 0.52694, 0.75101 },{ 0.16014862087 }}, + {{ 0.50968, 0.74522, 0.14058 },{ 0.30725752992 }}, + {{ 0.82291, 0.10614, 0.49956 },{ 0.07762756903 }}, + {{ 0.15674, 0.78167, 0.50766 },{ 0.17389174472 }}, + {{ 0.12961, 0.17234, 0.65990 },{ 0.08236132255 }}, + {{ 1.00000, 0.83202, 0.35206 },{ 0.61366800000 }}, + {{ 0.36633, 0.89555, 0.76014 },{ 0.48373766601 }}, + {{ 0.85641, 0.39194, 0.18691 },{ 0.09699956583 }}, + {{ 0.50918, 0.23923, 0.42132 },{ 0.16380116928 }}, + {{ 0.65513, 0.40585, 0.83832 },{ 0.49065371733 }}, + {{ 0.66726, 0.75472, 0.25419 },{ 0.41666516892 }}, + {{ 0.22193, 0.58555, 0.13920 },{ 0.13003877385 }}, + {{ 0.41507, 0.89581, 0.25539 },{ 0.37048608609 }}, + {{ 0.43231, 0.12362, 0.17297 },{ 0.01981752271 }}, + {{ 0.78191, 0.63297, 0.72639 },{ 0.64361123257 }} +}; + +co test_points3[] = { + {{ 0.50915, 0.50936, 0.57048 },{ -0.00010 }}, + {{ 0.85943, 0.84331, 0.81487 },{ 0.46573 }}, + {{ 0.11381, 0.80378, 0.82951 },{ 0.56085 }}, + {{ 0.79087, 0.11157, 0.83913 },{ 0.33378 }}, + {{ 0.16297, 0.23090, 0.96417 },{ 0.38872 }}, + {{ 0.78181, 0.80097, 0.00192 },{ 0.28654 }}, + {{ 0.16141, 0.84321, 0.16561 },{ 0.43410 }}, + {{ 0.79859, 0.11111, 0.15547 },{ 0.35140 }}, + {{ 0.12959, 0.16184, 0.21825 },{ 0.46711 }}, + {{ 1.00000, 0.46395, 0.84399 },{ 0.94213 }}, + {{ 0.48724, 0.76024, 1.00000 },{ 0.00058 }}, + {{ 0.40744, 0.00000, 0.65533 },{ 0.00859 }}, + {{ 0.10931, 0.43216, 0.25195 },{ 0.54679 }}, + {{ 0.69401, 0.99412, 0.55335 },{ 0.12494 }}, + {{ 0.51759, 0.30372, 0.13622 },{ -0.00126 }}, + {{ 0.98628, 0.40857, 0.47688 },{ 0.89573 }}, + {{ 0.44474, 0.26024, 1.00000 },{ 0.00280 }}, + {{ 0.15229, 0.52694, 0.75101 },{ 0.43807 }}, + {{ 0.50968, 0.74522, 0.14058 },{ 0.00004 }}, + {{ 0.82291, 0.10614, 0.49956 },{ 0.40990 }}, + {{ 0.15674, 0.78167, 0.50766 },{ 0.44271 }}, + {{ 0.12961, 0.17234, 0.65990 },{ 0.46804 }}, + {{ 1.00000, 0.83202, 0.35206 },{ 0.90938 }}, + {{ 0.36633, 0.89555, 0.76014 },{ 0.07020 }}, + {{ 0.85641, 0.39194, 0.18691 },{ 0.48559 }}, + {{ 0.50918, 0.23923, 0.42132 },{ -0.00391 }}, + {{ 0.65513, 0.40585, 0.83832 },{ 0.09449 }}, + {{ 0.66726, 0.75472, 0.25419 },{ 0.10065 }}, + {{ 0.22193, 0.58555, 0.13920 },{ 0.28186 }}, + {{ 0.41507, 0.89581, 0.25539 },{ 0.02877 }}, + {{ 0.43231, 0.12362, 0.17297 },{ 0.00237 }}, + {{ 0.78191, 0.63297, 0.72639 },{ 0.29573 }} +}; + + +/* x + y^2 + z^1/3 function with one non-monotonic point */ +co test_points4[] = { + {{ 0.1,0.1,0.5 },{ 0.11 }}, /* 0 */ + {{ 0.2,0.7,0.1 },{ 0.69 }}, /* 1 */ + {{ 0.8,0.8,0.8 },{ 1.44 }}, /* 2 */ + {{ 0.5,0.6,0.4 },{ 0.86 }}, /* 3 */ + {{ 0.2,0.5,0.2 },{ 0.45 }}, /* 4 */ + {{ 0.3,0.7,0.2 },{ 0.35 }}, /* nm 5 */ + {{ 0.5,0.4,0.9 },{ 0.66 }}, /* 6 */ + {{ 0.1,0.9,0.7 },{ 0.91 }}, /* 7 */ + {{ 0.7,0.2,0.1 },{ 0.74 }}, /* 8 */ + {{ 0.8,0.4,0.3 },{ 0.96 }}, /* 9 */ + {{ 0.3,0.3,0.4 },{ 0.39 }} /* 10 */ + }; + +/* doubled up x + y^2 + z^1/3 function with one non-monotonic point */ +co test_points5[] = { + {{ 0.1,0.1,0.5 },{ 0.11 }}, /* 0 */ + {{ 0.101,0.101,0.501 },{ 0.11 }}, /* 0d */ + {{ 0.2,0.7,0.1 },{ 0.69 }}, /* 1 */ + {{ 0.201,0.701,0.101 },{ 0.69 }}, /* 1d */ + {{ 0.8,0.8,0.8 },{ 1.44 }}, /* 2 */ + {{ 0.801,0.801,0.801 },{ 1.44 }}, /* 2d */ + {{ 0.5,0.6,0.4 },{ 0.86 }}, /* 3 */ + {{ 0.501,0.601,0.401 },{ 0.86 }}, /* 3d */ + {{ 0.2,0.5,0.2 },{ 0.45 }}, /* 4 */ + {{ 0.201,0.501,0.201 },{ 0.45 }}, /* 4d */ + {{ 0.3,0.7,0.2 },{ 0.35 }}, /* nm 5 */ + {{ 0.301,0.701,0.201 },{ 0.35 }}, /* nm 5d */ + {{ 0.5,0.4,0.9 },{ 0.66 }}, /* 6 */ + {{ 0.501,0.401,0.901 },{ 0.66 }}, /* 6d */ + {{ 0.1,0.9,0.7 },{ 0.91 }}, /* 7 */ + {{ 0.101,0.901,0.701 },{ 0.91 }}, /* 7d */ + {{ 0.7,0.2,0.1 },{ 0.74 }}, /* 8 */ + {{ 0.701,0.201,0.101 },{ 0.74 }}, /* 8d */ + {{ 0.8,0.4,0.3 },{ 0.96 }}, /* 9 */ + {{ 0.801,0.401,0.301 },{ 0.96 }}, /* 9d */ + {{ 0.3,0.3,0.4 },{ 0.39 }}, /* 10 */ + {{ 0.301,0.301,0.401 },{ 0.39 }} /* 10d */ + }; + +co test_points6[] = { + {{ 0.0069, 0.0071, 0.0061 },{ 0.0726 }}, + {{ 0.0068, 0.0071, 0.0060 },{ 0.0704 }}, + {{ 0.0069, 0.0072, 0.0062 },{ 0.0720 }}, + {{ 0.0069, 0.0072, 0.0061 },{ 0.0734 }}, + {{ 0.0069, 0.0072, 0.0063 },{ 0.0750 }}, + {{ 0.0070, 0.0072, 0.0062 },{ 0.0779 }}, + {{ 0.0070, 0.0072, 0.0063 },{ 0.0741 }}, + {{ 0.0069, 0.0072, 0.0061 },{ 0.0745 }}, + {{ 0.0069, 0.0072, 0.0061 },{ 0.0747 }}, + {{ 0.0071, 0.0073, 0.0063 },{ 0.0760 }}, + {{ 0.0070, 0.0073, 0.0063 },{ 0.0751 }}, + {{ 0.0070, 0.0073, 0.0062 },{ 0.0759 }}, + {{ 0.0071, 0.0074, 0.0062 },{ 0.0693 }}, + {{ 0.0071, 0.0074, 0.0064 },{ 0.0740 }}, + {{ 0.0072, 0.0075, 0.0064 },{ 0.0741 }}, + {{ 0.0199, 0.0209, 0.0184 },{ 0.1019 }}, + {{ 0.0296, 0.0306, 0.0257 },{ 0.1213 }}, + {{ 0.0627, 0.0651, 0.0548 },{ 0.1779 }}, + {{ 0.0831, 0.0863, 0.0718 },{ 0.2095 }}, + {{ 0.1091, 0.1134, 0.0946 },{ 0.2487 }}, + {{ 0.1442, 0.1497, 0.1227 },{ 0.2949 }}, + {{ 0.1745, 0.1814, 0.1495 },{ 0.3360 }}, + {{ 0.1747, 0.1816, 0.1498 },{ 0.3367 }}, + {{ 0.1747, 0.1816, 0.1496 },{ 0.3364 }}, + {{ 0.1748, 0.1816, 0.1497 },{ 0.3355 }}, + {{ 0.1749, 0.1817, 0.1497 },{ 0.3344 }}, + {{ 0.1748, 0.1817, 0.1498 },{ 0.3356 }}, + {{ 0.1748, 0.1817, 0.1498 },{ 0.3354 }}, + {{ 0.1749, 0.1817, 0.1496 },{ 0.3361 }}, + {{ 0.1749, 0.1818, 0.1498 },{ 0.3368 }}, + {{ 0.1749, 0.1818, 0.1498 },{ 0.3335 }}, + {{ 0.1750, 0.1818, 0.1499 },{ 0.3367 }}, + {{ 0.1750, 0.1819, 0.1500 },{ 0.3362 }}, + {{ 0.1750, 0.1819, 0.1498 },{ 0.3359 }}, + {{ 0.1751, 0.1820, 0.1500 },{ 0.3354 }}, + {{ 0.1752, 0.1821, 0.1501 },{ 0.3355 }}, + {{ 0.1754, 0.1823, 0.1502 },{ 0.3369 }}, + {{ 0.1756, 0.1824, 0.1504 },{ 0.3360 }}, + {{ 0.2743, 0.2842, 0.2367 },{ 0.4381 }}, + {{ 0.3289, 0.3411, 0.2834 },{ 0.4922 }}, + {{ 0.4036, 0.4184, 0.3475 },{ 0.5617 }}, + {{ 0.4689, 0.4854, 0.4020 },{ 0.6147 }}, + {{ 0.5379, 0.5567, 0.4606 },{ 0.6709 }}, + {{ 0.7137, 0.7420, 0.6169 },{ 0.8045 }}, + {{ 0.8730, 0.9105, 0.7433 },{ 0.9150 }}, + {{ 0.8738, 0.9113, 0.7435 },{ 0.9141 }}, + {{ 0.8741, 0.9116, 0.7445 },{ 0.9120 }}, + {{ 0.8744, 0.9118, 0.7443 },{ 0.9173 }}, + {{ 0.8748, 0.9123, 0.7457 },{ 0.9219 }}, + {{ 0.8748, 0.9123, 0.7450 },{ 0.9133 }}, + {{ 0.8748, 0.9124, 0.7445 },{ 0.9210 }}, + {{ 0.8751, 0.9127, 0.7462 },{ 0.9207 }}, + {{ 0.8751, 0.9127, 0.7457 },{ 0.9225 }}, + {{ 0.8754, 0.9130, 0.7454 },{ 0.9137 }}, + {{ 0.8757, 0.9133, 0.7456 },{ 0.9219 }}, + {{ 0.8759, 0.9135, 0.7470 },{ 0.9166 }}, + {{ 0.8761, 0.9137, 0.7469 },{ 0.9162 }}, + {{ 0.8759, 0.9137, 0.7469 },{ 0.9151 }}, + {{ 0.8765, 0.9141, 0.7470 },{ 0.9167 }}, +}; + + +#ifdef NEVER +#ifdef __STDC__ +#include +void error(char *fmt, ...), warning(char *fmt, ...), verbose(int level, char *fmt, ...); +#else +#include +void error(), warning(), verbose(); +#endif +#endif /* NEVER */ + +void write_rgb_tiff(char *name, int width, int height, unsigned char *data); + +void usage(void) { + fprintf(stderr,"Test 3D rspl interpolation\n"); + fprintf(stderr,"Author: Graeme W. Gill\n"); + fprintf(stderr,"usage: t2d [options]\n"); + fprintf(stderr," -t n Test set:\n"); + fprintf(stderr," * 1 = 1D test set along x = y = 0.5\n"); + fprintf(stderr," 2 = Test set 1\n"); + fprintf(stderr," 3 = Test set 2\n"); + fprintf(stderr," 4 = x + y^2 + z^1/3 with nonmon point\n"); + fprintf(stderr," 5 = doubled up x + y^2 + z^1/3 with nonmon point\n"); + fprintf(stderr," 6 = neutral axis extrapolation\n"); + fprintf(stderr," -r resx,resy,resz Set grid resolutions (def %d %d %d)\n",GRES0,GRES1,GRES2); + fprintf(stderr," -h Test half scale resolution too\n"); + fprintf(stderr," -q Test quarter scale resolution too\n"); + fprintf(stderr," -2 Use two pass smoothing\n"); + fprintf(stderr," -x Use extra fitting\n"); + fprintf(stderr," -s Test symetric smoothness\n"); + fprintf(stderr," -p plot 4 slices, xy = 0.5, yz = 0.5, xz = 0.5, x=y=z\n"); + fprintf(stderr," -P x1:y1:z1:x2:y2:z2 plot slice from x1,y1,z1,x2,y2,z2\n"); + fprintf(stderr," -S factor smoothing factor (default 1.0)\n"); + exit(1); +} + +int main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + rspl *rss; /* Regularized spline structure */ + rspl *rss2 = NULL; /* Regularized spline structure at half/quarter resolution */ + datai low,high; + int gres[MXDI]; + int gres2[MXDI]; + double avgdev[MXDO]; + co *test_points = test_points1; + int npoints = sizeof(test_points1)/sizeof(co); + int dosym = 0; + int twopass = 0; + int extra = 0; + int doplot = 0; + double plotpts[2][3]; /* doplot == 2 start/end points */ + int doh = 0; /* half scale */ + int doq = 0; + int rsv; + double smoothf = 1.0; + int flags = RSPL_NOFLAGS; + + low[0] = 0.0; + low[1] = 0.0; + low[2] = 0.0; + high[0] = 1.0; + high[1] = 1.0; + high[2] = 1.0; + gres[0] = GRES0; + gres[1] = GRES1; + gres[2] = GRES2; + avgdev[0] = 0.0; + avgdev[1] = 0.0; + avgdev[2] = 0.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(); + + /* test set */ + else if (argv[fa][1] == 't' || argv[fa][1] == 'T') { + int ix; + fa = nfa; + if (na == NULL) usage(); + ix = atoi(na); + switch (ix) { + case 1: + test_points = test_points1; + npoints = sizeof(test_points1)/sizeof(co); + break; + case 2: + test_points = test_points2; + npoints = sizeof(test_points2)/sizeof(co); + break; + case 3: + test_points = test_points3; + npoints = sizeof(test_points3)/sizeof(co); + break; + case 4: + test_points = test_points4; + npoints = sizeof(test_points4)/sizeof(co); + break; + case 5: + test_points = test_points5; + npoints = sizeof(test_points5)/sizeof(co); + break; + case 6: + test_points = test_points6; + npoints = sizeof(test_points6)/sizeof(co); + break; + default: + usage(); + } + + } else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') { + fa = nfa; + if (na == NULL) usage(); + if (sscanf(na, " %d,%d,%d ", &gres[0], &gres[1], &gres[2]) != 3) + usage(); + + } else if (argv[fa][1] == 'h' || argv[fa][1] == 'H') { + doh = 1; + + } else if (argv[fa][1] == 'q' || argv[fa][1] == 'Q') { + doh = 1; + doq = 1; + + } else if (argv[fa][1] == 'p') { + doplot = 1; + + } else if (argv[fa][1] == 'P') { + doplot = 2; + fa = nfa; + if (na == NULL) usage(); + if (sscanf(na,"%lf:%lf:%lf:%lf:%lf:%lf",&plotpts[0][0],&plotpts[0][1],&plotpts[0][2],&plotpts[1][0],&plotpts[1][1],&plotpts[1][2]) != 6) { + usage(); + } + + } else if (argv[fa][1] == 's') { + dosym = 1; + + } else if (argv[fa][1] == '2') { + twopass = 1; + + } else if (argv[fa][1] == 'x' || argv[fa][1] == 'X') { + extra = 1; + + /* smoothing factor */ + } else if (argv[fa][1] == 'S') { + int ix; + fa = nfa; + if (na == NULL) usage(); + smoothf = atof(na); + + } else + usage(); + } else + break; + } + + + if (twopass) + flags |= RSPL_2PASSSMTH; + + if (extra) + flags |= RSPL_EXTRAFIT2; + + if (dosym) + flags |= RSPL_SYMDOMAIN; + + + /* Create the object */ + rss = new_rspl(RSPL_NOFLAGS, 3, 1); + + /* Fit to scattered data */ + rss->fit_rspl(rss, + flags, /* Non-mon and clip flags */ + test_points, /* Test points */ + npoints, /* Number of test points */ + low, high, gres, /* Low, high, resolution of grid */ + NULL, NULL, /* Default data scale */ + smoothf, /* Smoothing */ + avgdev, /* Average deviation */ + NULL); /* iwidth */ + if (doh) { + + if (doq) { + gres2[0] = gres[0]/4; + gres2[1] = gres[1]/4; + gres2[2] = gres[2]/4; + } else { + gres2[0] = gres[0]/2; + gres2[1] = gres[1]/2; + gres2[2] = gres[2]/2; + } + + rss2 = new_rspl(RSPL_NOFLAGS, 3, 1); + + /* Fit to scattered data */ + rss2->fit_rspl(rss2, + flags, /* Non-mon and clip flags */ + test_points, /* Test points */ + npoints, /* Number of test points */ + low, high, gres2, /* Low, high, resolution of grid */ + NULL, NULL, /* Default data scale */ + smoothf, /* Smoothing */ + avgdev, /* Average deviation */ + NULL); /* iwidth */ + } + + /* Test the interpolation with a slice in 2D */ + for (rsv = 0; rsv <= doh; rsv++) { + double z[2][2] = { { 0.1, 0.5 } , { 0.5, 0.9 } }; + double x1 = -0.2; + double x2 = 1.2; + double y1 = -0.2; + double y2 = 1.2; + double min = -0.0; + double max = 1.0; + rspl *rs; + unsigned char pa[HEIGHT][WIDTH][3]; + co tco; /* Test point */ + double sx,sy; + int i,j,k; + + if (rsv == 0) + rs = rss; + else + rs = rss2; + + sx = (x2 - x1)/(double)WIDTH; + sy = (y2 - y1)/(double)HEIGHT; + + for (j=0; j < HEIGHT; j++) { + double jj = j/(HEIGHT-1.0); + tco.p[1] = (double)((HEIGHT-1) - j) * sy + y1; + for (i = 0; i < WIDTH; i++) { + double ii = j/(HEIGHT-1.0); + tco.p[0] = (double)i * sx + x1; + + tco.p[2] = (1.0-ii) * (1.0-jj) * z[0][0] + + (1.0-ii) * jj * z[0][1] + + ii * (1.0-jj) * z[1][0] + + ii * jj * z[1][1]; + + if (rs->INTERP(rs, &tco)) { + pa[j][i][0] = 0; /* Out of bounds in green */ + pa[j][i][1] = 100; + pa[j][i][2] = 0; + } else { + int m; + /* printf("%d %d, %f %f returned %f\n",i,j,tco.p[0],tco.p[1],tco.v[0]); */ + m = (int)((255.0 * (tco.v[0] - min)/(max - min)) + 0.5); + if (m < 0) { + pa[j][i][0] = 20; /* Dark blue */ + pa[j][i][1] = 20; + pa[j][i][2] = 50; + } else if (m > 255) { + pa[j][i][0] = 230; /* Light blue */ + pa[j][i][1] = 230; + pa[j][i][2] = 255; + } else { + pa[j][i][0] = m; /* Level in grey */ + pa[j][i][1] = m; + pa[j][i][2] = m; + } + } + } + } + + /* Mark verticies in red */ + for(k = 0; k < npoints; k++) { + j = (int)((HEIGHT * (y2 - test_points[k].p[1])/(y2 - y1)) + 0.5); + i = (int)((WIDTH * (test_points[k].p[0] - x1)/(x2 - x1)) + 0.5); + pa[j][i][0] = 255; + pa[j][i][1] = 0; + pa[j][i][2] = 0; + } + write_rgb_tiff(rsv == 0 ? "t3d.tif" : "t3dh.tif" ,WIDTH,HEIGHT,(unsigned char *)pa); + } + + /* Plot out 4 slices */ + if (doplot == 1) { + int slice; + + for (slice = 0; slice < 4; slice++) { + co tp; /* Test point */ + double x[PLOTRES]; + double ya[PLOTRES]; + double yb[PLOTRES]; + double xx,yy,zz; + double x1,x2,y1,y2,z1,z2; + double sx,sy,sz; + int i,n; + + /* Set up slice to plot */ + if (slice == 0) { + x1 = 0.5; y1 = 0.5, z1 = 0.0; + x2 = 0.5; y2 = 0.5, z2 = 1.0; + printf("Plot along z at x = y = 0.5\n"); + n = PLOTRES; + } else if (slice == 1) { + x1 = 0.0; y1 = 0.5, z1 = 0.5; + x2 = 1.0; y2 = 0.5, z2 = 0.5; + printf("Plot along x at y = z = 0.5\n"); + n = PLOTRES; + } else if (slice == 2) { + x1 = 0.5; y1 = 0.0, z1 = 0.5; + x2 = 0.5; y2 = 1.0, z2 = 0.5; + printf("Plot along y at x = z = 0.5\n"); + n = PLOTRES; + } else { + x1 = 0.0; y1 = 0.0, z1 = 0.0; + x2 = 1.0; y2 = 1.0, z2 = 1.0; + printf("Plot along x = y = z\n"); + n = PLOTRES; + } + + sx = (x2 - x1)/n; + sy = (y2 - y1)/n; + sz = (z2 - z1)/n; + + xx = x1; + yy = y1; + zz = z1; + for (i = 0; i < n; i++) { + double vv = i/(n-1.0); + x[i] = vv; + tp.p[0] = xx; + tp.p[1] = yy; + tp.p[2] = zz; + + if (rss->INTERP(rss, &tp)) + tp.v[0] = -0.1; + ya[i] = tp.v[0]; + + if (doh) { + if (rss2->INTERP(rss2, &tp)) + tp.v[0] = -0.1; + yb[i] = tp.v[0]; + } + + xx += sx; + yy += sy; + zz += sz; + } + + /* Plot the result */ + if (doh) + do_plot(x,ya,yb,NULL,n); + else + do_plot(x,ya,NULL,NULL,n); + } + } else if (doplot == 2) { + co tp; /* Test point */ + double x[PLOTRES]; + double ya[PLOTRES]; + double yb[PLOTRES]; + double xx,yy,zz; + double x1,x2,y1,y2,z1,z2; + double sx,sy,sz; + int i,n; + + + x1 = plotpts[0][0]; + y1 = plotpts[0][1]; + z1 = plotpts[0][2]; + x2 = plotpts[1][0]; + y2 = plotpts[1][1]; + z2 = plotpts[1][2]; + + printf("Plot along z at x = y = 0.5\n"); + n = PLOTRES; + + sx = (x2 - x1)/n; + sy = (y2 - y1)/n; + sz = (z2 - z1)/n; + + xx = x1; + yy = y1; + zz = z1; + for (i = 0; i < n; i++) { + double vv = i/(n-1.0); + x[i] = vv; + tp.p[0] = xx; + tp.p[1] = yy; + tp.p[2] = zz; + + if (rss->INTERP(rss, &tp)) + tp.v[0] = -0.1; + ya[i] = tp.v[0]; + + if (doh) { + if (rss2->INTERP(rss2, &tp)) + tp.v[0] = -0.1; + yb[i] = tp.v[0]; + } + + xx += sx; + yy += sy; + zz += sz; + } + + /* Plot the result */ + if (doh) + do_plot(x,ya,yb,NULL,n); + else + do_plot(x,ya,NULL,NULL,n); + } + + /* Report the fit */ + { + co tco; /* Test point */ + int k; + double avg = 0; + double max = 0.0; + + for(k = 0; k < npoints; k++) { + double err; + tco.p[0] = test_points[k].p[0]; + tco.p[1] = test_points[k].p[1]; + tco.p[2] = test_points[k].p[2]; + rss->INTERP(rss, &tco); + + err = tco.v[0] - test_points[k].v[0]; + err = fabs(err); + + avg += err; + if (err > max) + max = err; + } + avg /= (double)npoints; + printf("Max error %f%%, average %f%%\n",100.0 * max, 100.0 * avg); + } + return 0; +} + +/* ---------------------- */ +/* Tiff diagnostic output */ + +void +write_rgb_tiff( +char *name, +int width, +int height, +unsigned char *data +) { + int y; + unsigned char *dp; + TIFF *tif; + + if ((tif = TIFFOpen(name, "w")) == NULL) { + fprintf(stderr,"Failed to open output TIFF file '%s'\n",name); + exit (-1); + } + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + + for (dp = data, y = 0; y < height; y++, dp += 3 * width) { + if (TIFFWriteScanline(tif, (tdata_t)dp, y, 0) < 0) { + fprintf(stderr,"WriteScanline Failed at line %d\n",y); + exit (-1); + } + } + (void) TIFFClose(tif); +} + +#ifdef NEVER +/******************************************************************/ +/* Error/debug output routines */ +/******************************************************************/ + +/* Basic printf type error() and warning() routines */ + +#ifdef __STDC__ +void +error(char *fmt, ...) +#else +void +error(va_alist) +va_dcl +#endif +{ + va_list args; +#ifndef __STDC__ + char *fmt; +#endif + + fprintf(stderr,"cmatch: Error - "); +#ifdef __STDC__ + va_start(args, fmt); +#else + va_start(args); + fmt = va_arg(args, char *); +#endif + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); + fflush(stdout); + exit (-1); +} + +#ifdef __STDC__ +void +warning(char *fmt, ...) +#else +void +warning(va_alist) +va_dcl +#endif +{ + va_list args; +#ifndef __STDC__ + char *fmt; +#endif + + fprintf(stderr,"cmatch: Warning - "); +#ifdef __STDC__ + va_start(args, fmt); +#else + va_start(args); + fmt = va_arg(args, char *); +#endif + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); +} + +#ifdef __STDC__ +void +verbose(int level, char *fmt, ...) +{ + va_list args; + va_start(args, fmt); +#else +verbose(va_alist) +va_dcl +{ + va_list args; + int level; + char *fmt; + va_start(args); + level = va_arg(args, int); + fmt = va_arg(args, char *); +#endif + if (verbose_level >= level) + { + fprintf(verbose_out,"cmatch: "); + vfprintf(verbose_out, fmt, args); + fprintf(verbose_out, "\n"); + fflush(verbose_out); + } + va_end(args); +} +#endif /* NEVER */ diff --git a/rspl/t3ddf.c b/rspl/t3ddf.c new file mode 100644 index 0000000..b0e7d18 --- /dev/null +++ b/rspl/t3ddf.c @@ -0,0 +1,570 @@ +/************************************************/ +/* Test RSPL in 3D with weak default function */ +/************************************************/ + +/* Author: Graeme Gill + * Date: 20/11/2005 + * Derived from cmatch.c + * Copyright 1995, 2005 Graeme W. Gill + * + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + + +#define DEBUG +#define DETAILED + +#include +#include +#include +#include +#include "rspl.h" +#include "tiffio.h" +#include "plot.h" + +#ifdef NEVER +#define INTERP spline_interp +#else +#define INTERP interp +#endif + +#ifdef NEVER +FILE *verbose_out = stdout; +int verbose_level = 6; /* Current verbosity level */ + /* 0 = none */ + /* !0 = diagnostics */ +#endif /* NEVER */ + +#define PLOTRES 256 +#define WIDTH 400 /* Raster size */ +#define HEIGHT 400 + +#define MAX_ITS 500 +#define IT_TOL 0.0005 +#define GRES0 33 /* Default rspl resolutions */ +#define GRES1 33 +#define GRES2 33 +#undef NEVER +#define ALWAYS + +/* two correction points along x = y = 0.5 */ +co test_points1[] = { +// {{ 0.5, 0.5, 0.325 },{ 0.4 }}, /* 0 */ +// {{ 0.5, 0.5, 0.625 },{ 0.70 }} /* 1 */ + + {{ 0.4, 0.4, 0.325 },{ 0.5 }}, /* 0 */ + {{ 0.4, 0.4, 0.625 },{ 0.8 }}, /* 1 */ + {{ 0.5, 0.4, 0.325 },{ 0.5 }}, /* 0 */ + {{ 0.5, 0.4, 0.625 },{ 0.8 }}, /* 1 */ + {{ 0.6, 0.4, 0.325 },{ 0.5 }}, /* 0 */ + {{ 0.6, 0.4, 0.625 },{ 0.8 }}, /* 1 */ + + {{ 0.4, 0.5, 0.325 },{ 0.5 }}, /* 0 */ + {{ 0.4, 0.5, 0.625 },{ 0.8 }}, /* 1 */ + {{ 0.5, 0.5, 0.325 },{ 0.5 }}, /* 0 */ + {{ 0.5, 0.5, 0.625 },{ 0.8 }}, /* 1 */ + {{ 0.6, 0.5, 0.325 },{ 0.5 }}, /* 0 */ + {{ 0.6, 0.5, 0.625 },{ 0.8 }}, /* 1 */ + + {{ 0.4, 0.6, 0.325 },{ 0.5 }}, /* 0 */ + {{ 0.4, 0.6, 0.625 },{ 0.8 }}, /* 1 */ + {{ 0.5, 0.6, 0.325 },{ 0.5 }}, /* 0 */ + {{ 0.5, 0.6, 0.625 },{ 0.8 }}, /* 1 */ + {{ 0.6, 0.6, 0.325 },{ 0.5 }}, /* 0 */ + {{ 0.6, 0.6, 0.625 },{ 0.8 }} /* 1 */ +}; + +#ifdef NEVER +#ifdef __STDC__ +#include +void error(char *fmt, ...), warning(char *fmt, ...), verbose(int level, char *fmt, ...); +#else +#include +void error(), warning(), verbose(); +#endif +#endif /* NEVER */ + +void write_rgb_tiff(char *name, int width, int height, unsigned char *data); + +/* Weak default function */ +/* Linear along z, independent of x & y */ +static void wfunc(void *cbntx, double *out, double *in) { + out[0] = in[2]; +} + +void usage(void) { + fprintf(stderr,"Test 3D rspl interpolation with weak default function\n"); + fprintf(stderr,"Author: Graeme W. Gill\n"); + fprintf(stderr,"usage: t2d [options]\n"); + fprintf(stderr," -t n Test set:\n"); + fprintf(stderr," * 1 = two points along x & y\n"); + fprintf(stderr," -w wweight Set weak default function weight (default 1.0)\n"); + fprintf(stderr," -r resx,resy,resz Set grid resolutions (def %d %d %d)\n",GRES0,GRES1,GRES2); + fprintf(stderr," -h Test half scale resolution too\n"); + fprintf(stderr," -q Test quarter scale resolution too\n"); + fprintf(stderr," -x Use extra fitting\n"); + fprintf(stderr," -s Test symetric smoothness (set asymetric -r !)\n"); + fprintf(stderr," -s Test symetric smoothness\n"); + fprintf(stderr," -p plot 4 slices, xy = 0.5, yz = 0.5, xz = 0.5, x=y=z\n"); + exit(1); +} + +int main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + rspl *rss; /* Regularized spline structure */ + rspl *rss2 = NULL; /* Regularized spline structure at half/quarter resolution */ + datai low,high; + int gres[MXDI]; + int gres2[MXDI]; + double avgdev[MXDO]; + co *test_points = test_points1; + int npoints = sizeof(test_points1)/sizeof(co); + double wweight = 1.0; + int twopass = 0; + int extra = 0; + int dosym = 0; + int doplot = 0; + int doh = 0; + int doq = 0; + int rsv; + int flags = RSPL_NOFLAGS; + + low[0] = 0.0; + low[1] = 0.0; + low[2] = 0.0; + high[0] = 1.0; + high[1] = 1.0; + high[2] = 1.0; + gres[0] = GRES0; + gres[1] = GRES1; + gres[2] = GRES2; + avgdev[0] = 0.0; + avgdev[1] = 0.0; + avgdev[2] = 0.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(); + + /* test set */ + else if (argv[fa][1] == 't' || argv[fa][1] == 'T') { + int ix; + fa = nfa; + if (na == NULL) usage(); + ix = atoi(na); + switch (ix) { + case 1: + test_points = test_points1; + npoints = sizeof(test_points1)/sizeof(co); + break; + default: + usage(); + } + + } else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') { + fa = nfa; + if (na == NULL) usage(); + wweight = atof(na); + + } else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') { + fa = nfa; + if (na == NULL) usage(); + if (sscanf(na, " %d,%d,%d ", &gres[0], &gres[1], &gres[2]) != 2) + usage(); + + } else if (argv[fa][1] == 'h' || argv[fa][1] == 'H') { + doh = 1; + + } else if (argv[fa][1] == 'q' || argv[fa][1] == 'Q') { + doh = 1; + doq = 1; + + } else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') { + doplot = 1; + + } else if (argv[fa][1] == 's' || argv[fa][1] == 'S') { + dosym = 1; + + } else if (argv[fa][1] == 'x' || argv[fa][1] == 'X') { + extra = 1; + + } else if (argv[fa][1] == 's') { + dosym = 1; + + } else + usage(); + } else + break; + } + + + if (twopass) + flags |= RSPL_2PASSSMTH; + + if (extra) + flags |= RSPL_EXTRAFIT2; + + if (dosym) + flags |= RSPL_SYMDOMAIN; + + /* Create the object */ + rss = new_rspl(RSPL_NOFLAGS, 3, 1); + + /* Fit to scattered data */ + rss->fit_rspl_df(rss, + flags, /* Non-mon and clip flags */ + test_points, /* Test points */ + npoints, /* Number of test points */ + low, high, gres, /* Low, high, resolution of grid */ + low, high, /* Default data scale */ + 1.0, /* Smoothing */ + avgdev, /* Average Deviation */ + NULL, /* iwidth */ + wweight, /* weak function weight */ + NULL, /* No context */ + wfunc /* Weak function */ + ); + if (doh) { + + if (doq) { + gres2[0] = gres[0]/4; + gres2[1] = gres[1]/4; + gres2[2] = gres[2]/4; + } else { + gres2[0] = gres[0]/2; + gres2[1] = gres[1]/2; + gres2[2] = gres[2]/2; + } + + rss2 = new_rspl(RSPL_NOFLAGS, 3, 1); + + /* Fit to scattered data */ + rss2->fit_rspl_df(rss2, + flags, /* Non-mon and clip flags */ + test_points, /* Test points */ + npoints, /* Number of test points */ + low, high, gres2, /* Low, high, resolution of grid */ + low, high, /* Default data scale */ + 1.0, /* Smoothing */ + avgdev, /* Average Deviation */ + NULL, /* iwidth */ + wweight, /* weak function weight */ + NULL, /* No context */ + wfunc /* Weak function */ + ); + } + + /* Test the interpolation with a slice in 2D */ + for (rsv = 0; rsv <= doh; rsv++) { + double z[2][2] = { { 0.1, 0.5 }, { 0.5, 0.9 } }; + double x1 = -0.2; + double x2 = 1.2; + double y1 = -0.2; + double y2 = 1.2; + double min = -0.0; + double max = 1.0; + rspl *rs; + unsigned char pa[HEIGHT][WIDTH][3]; + co tco; /* Test point */ + double sx,sy; + int i,j,k; + + if (rsv == 0) + rs = rss; + else + rs = rss2; + + sx = (x2 - x1)/(double)WIDTH; + sy = (y2 - y1)/(double)HEIGHT; + + for (j=0; j < HEIGHT; j++) { + double jj = j/(HEIGHT-1.0); + tco.p[1] = (double)((HEIGHT-1) - j) * sy + y1; + for (i = 0; i < WIDTH; i++) { + double ii = j/(HEIGHT-1.0); + tco.p[0] = (double)i * sx + x1; + + tco.p[2] = (1.0-ii) * (1.0-jj) * z[0][0] + + (1.0-ii) * jj * z[0][1] + + ii * (1.0-jj) * z[1][0] + + ii * jj * z[1][1]; + + if (rs->INTERP(rs, &tco)) { + pa[j][i][0] = 0; /* Out of bounds in green */ + pa[j][i][1] = 100; + pa[j][i][2] = 0; + } else { + int m; + /* printf("%d %d, %f %f returned %f\n",i,j,tco.p[0],tco.p[1],tco.v[0]); */ + m = (int)((255.0 * (tco.v[0] - min)/(max - min)) + 0.5); + if (m < 0) { + pa[j][i][0] = 20; /* Dark blue */ + pa[j][i][1] = 20; + pa[j][i][2] = 50; + } else if (m > 255) { + pa[j][i][0] = 230; /* Light blue */ + pa[j][i][1] = 230; + pa[j][i][2] = 255; + } else { + pa[j][i][0] = m; /* Level in grey */ + pa[j][i][1] = m; + pa[j][i][2] = m; + } + } + } + } + + /* Mark verticies in red */ + for(k = 0; k < npoints; k++) { + j = (int)((HEIGHT * (y2 - test_points[k].p[1])/(y2 - y1)) + 0.5); + i = (int)((WIDTH * (test_points[k].p[0] - x1)/(x2 - x1)) + 0.5); + pa[j][i][0] = 255; + pa[j][i][1] = 0; + pa[j][i][2] = 0; + } + write_rgb_tiff(rsv == 0 ? "t3d.tif" : "t3dh.tif" ,WIDTH,HEIGHT,(unsigned char *)pa); + } + + /* Plot out 4 slices */ + if (doplot) { + int slice; + + for (slice = 0; slice < 4; slice++) { + co tp; /* Test point */ + double x[PLOTRES]; + double ya[PLOTRES]; + double yb[PLOTRES]; + double xx,yy,zz; + double x1,x2,y1,y2,z1,z2; + double sx,sy,sz; + int i,n; + + /* Set up slice to plot */ + if (slice == 0) { + x1 = 0.5; y1 = 0.5, z1 = 0.0; + x2 = 0.5; y2 = 0.5, z2 = 1.0; + printf("Plot along z at x = y = 0.5\n"); + n = PLOTRES; + } else if (slice == 1) { + x1 = 0.0; y1 = 0.5, z1 = 0.5; + x2 = 1.0; y2 = 0.5, z2 = 0.5; + printf("Plot along x at y = z = 0.5\n"); + n = PLOTRES; + } else if (slice == 2) { + x1 = 0.5; y1 = 0.0, z1 = 0.5; + x2 = 0.5; y2 = 1.0, z2 = 0.5; + printf("Plot along y at x = z = 0.5\n"); + n = PLOTRES; + } else { + x1 = 0.0; y1 = 0.0, z1 = 0.0; + x2 = 1.0; y2 = 1.0, z2 = 1.0; + printf("Plot along x = y = z\n"); + n = PLOTRES; + } + + sx = (x2 - x1)/n; + sy = (y2 - y1)/n; + sz = (z2 - z1)/n; + + xx = x1; + yy = y1; + zz = z1; + for (i = 0; i < n; i++) { + double vv = i/(n-1.0); + x[i] = vv; + tp.p[0] = xx; + tp.p[1] = yy; + tp.p[2] = zz; + + if (rss->INTERP(rss, &tp)) + tp.v[0] = -0.1; + ya[i] = tp.v[0]; + + if (doh) { + if (rss2->INTERP(rss2, &tp)) + tp.v[0] = -0.1; + yb[i] = tp.v[0]; + } + + xx += sx; + yy += sy; + zz += sz; + } + + /* Plot the result */ + if (doh) + do_plot(x,ya,yb,NULL,n); + else + do_plot(x,ya,NULL,NULL,n); + } + } + + /* Report the fit */ + { + co tco; /* Test point */ + int k; + double avg = 0; + double max = 0.0; + + for(k = 0; k < npoints; k++) { + double err; + tco.p[0] = test_points[k].p[0]; + tco.p[1] = test_points[k].p[1]; + tco.p[2] = test_points[k].p[2]; + rss->INTERP(rss, &tco); + + err = tco.v[0] - test_points[k].v[0]; + err = fabs(err); + + avg += err; + if (err > max) + max = err; + } + avg /= (double)npoints; + printf("Max error %f%%, average %f%%\n",100.0 * max, 100.0 * avg); + } + return 0; +} + +/* ---------------------- */ +/* Tiff diagnostic output */ + +void +write_rgb_tiff( +char *name, +int width, +int height, +unsigned char *data +) { + int y; + unsigned char *dp; + TIFF *tif; + + if ((tif = TIFFOpen(name, "w")) == NULL) { + fprintf(stderr,"Failed to open output TIFF file '%s'\n",name); + exit (-1); + } + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + + for (dp = data, y = 0; y < height; y++, dp += 3 * width) { + if (TIFFWriteScanline(tif, (tdata_t)dp, y, 0) < 0) { + fprintf(stderr,"WriteScanline Failed at line %d\n",y); + exit (-1); + } + } + (void) TIFFClose(tif); +} + +#ifdef NEVER +/******************************************************************/ +/* Error/debug output routines */ +/******************************************************************/ + +/* Basic printf type error() and warning() routines */ + +#ifdef __STDC__ +void +error(char *fmt, ...) +#else +void +error(va_alist) +va_dcl +#endif +{ + va_list args; +#ifndef __STDC__ + char *fmt; +#endif + + fprintf(stderr,"cmatch: Error - "); +#ifdef __STDC__ + va_start(args, fmt); +#else + va_start(args); + fmt = va_arg(args, char *); +#endif + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); + fflush(stdout); + exit (-1); +} + +#ifdef __STDC__ +void +warning(char *fmt, ...) +#else +void +warning(va_alist) +va_dcl +#endif +{ + va_list args; +#ifndef __STDC__ + char *fmt; +#endif + + fprintf(stderr,"cmatch: Warning - "); +#ifdef __STDC__ + va_start(args, fmt); +#else + va_start(args); + fmt = va_arg(args, char *); +#endif + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); +} + +#ifdef __STDC__ +void +verbose(int level, char *fmt, ...) +{ + va_list args; + va_start(args, fmt); +#else +verbose(va_alist) +va_dcl +{ + va_list args; + int level; + char *fmt; + va_start(args); + level = va_arg(args, int); + fmt = va_arg(args, char *); +#endif + if (verbose_level >= level) + { + fprintf(verbose_out,"cmatch: "); + vfprintf(verbose_out, fmt, args); + fprintf(verbose_out, "\n"); + fflush(verbose_out); + } + va_end(args); +} +#endif /* NEVER */ diff --git a/rspl/tnd.c b/rspl/tnd.c new file mode 100644 index 0000000..95e4d16 --- /dev/null +++ b/rspl/tnd.c @@ -0,0 +1,489 @@ + +/************************************************/ +/* Test RSPL in 3/4D */ +/************************************************/ + +/* Author: Graeme Gill + * Date: 22/4/96 + * Derived from cmatch.c + * Copyright 1995 - 2000 Graeme W. Gill + * + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + + +#undef DEBUG +#undef DETAILED + +#include +#include +#include +#include "rspl.h" +#include "numlib.h" +#include "tiffio.h" + +#ifdef NEVER +FILE *verbose_out = stdout; +int verbose_level = 6; /* Current verbosity level */ + /* 0 = none */ + /* !0 = diagnostics */ +#endif /* NEVER */ + +#define spline_interp interp + +/* rspl flags */ +#define FLAGS (0 /* */) + +#define TEST_FWD_2D +#define TEST_REV_LOOKUP +#undef TEST_SLICE +#undef TEST_RANDOM_POINTS + +#define MAX_ITS 500 +#define IT_TOL 0.0005 +#define GRES 17 /* Grid resolution */ +#define DI 4 /* Dimensions in */ +#define FDI 4 /* Function (out) Dimensions */ +#undef NEVER +#define ALWAYS + +/* Arbitrary values */ +static co test_points[] = { + {{ 0.1,0.1,0.5,0.0 },{ 0.6, 0.2, 0.3, 0.99 }}, /* 0 */ + {{ 0.2,0.7,0.1,0.3 },{ 0.3, 0.1, 0.1, 0.45 }}, /* 1 */ + {{ 0.8,0.8,0.8,0.2 },{ 0.1, 0.7, 0.7, 0.7 }}, /* 2 */ + {{ 0.5,0.6,0.4,0.9 },{ 0.7, 0.6, 0.5, 0.4 }}, /* 3 */ + {{ 0.2,0.5,0.2,0.7 },{ 0.2, 0.3, 0.2, 0.2 }}, /* 4 */ + {{ 0.3,0.7,0.2,0.8 },{ 0.8, 0.9, 0.3, 0.5 }}, /* 5 */ + {{ 0.5,0.4,0.9,0.3 },{ 0.6, 0.4, 0.2, 0.01 }}, /* 6 */ + {{ 0.1,0.9,0.7,0.4 },{ 1.0, 0.9, 0.3, 0.6 }}, /* 7 */ + {{ 0.7,0.2,0.1,0.3 },{ 0.2, 0.3, 0.7, 0.3 }}, /* 8 */ + {{ 0.8,0.4,0.3,0.7 },{ 0.4, 0.5, 0.6, 0.2 }}, /* 9 */ + {{ 0.3,0.3,0.4,0.1 },{ 0.8, 0.6, 0.8, 0.1 }} /* 10 */ + }; + +#ifdef NEVER +/* Inverting table */ +static co test_points[] = { + {{ 0.1,0.1,0.5,0.0 },{ 0.9, 0.9, 0.5, 1.0 }}, /* 0 */ + {{ 0.2,0.7,0.1,0.3 },{ 0.8, 0.3, 0.9, 0.7 }}, /* 1 */ + {{ 0.8,0.8,0.8,0.2 },{ 0.2, 0.2, 0.2, 0.8 }}, /* 2 */ + {{ 0.5,0.6,0.4,0.9 },{ 0.5, 0.4, 0.6, 0.1 }}, /* 3 */ + {{ 0.2,0.5,0.2,0.7 },{ 0.8, 0.5, 0.8, 0.3 }}, /* 4 */ + {{ 0.3,0.7,0.2,0.8 },{ 0.7, 0.3, 0.8, 0.2 }}, /* 5 */ + {{ 0.5,0.4,0.9,0.3 },{ 0.5, 0.6, 0.1, 0.7 }}, /* 6 */ + {{ 0.1,0.9,0.7,0.4 },{ 0.9, 0.1, 0.3, 0.6 }}, /* 7 */ + {{ 0.7,0.2,0.1,0.3 },{ 0.3, 0.8, 0.9, 0.7 }}, /* 8 */ + {{ 0.8,0.4,0.3,0.7 },{ 0.2, 0.6, 0.7, 0.3 }}, /* 9 */ + {{ 0.3,0.3,0.4,0.1 },{ 0.7, 0.7, 0.6, 0.9 }} /* 10 */ + }; +#endif /* NEVER */ + +#ifdef NEVER +#ifdef __STDC__ +#include +void error(char *fmt, ...), warning(char *fmt, ...), verbose(int level, char *fmt, ...); +#else +#include +void error(), warning(), verbose(); +#endif +#endif /* NEVER */ + +void write_rgb_tiff(char *name, int width, int height, unsigned char *data); + +int main(int argc, char *argv[]) { + co *tps = NULL; + int ntps = 0; + rspl *rss; /* Multi-resolution regularized spline structure */ + datai low,high; + int gres[MXDI]; + double avgdev[MXDO]; + low[0] = 0.0; + low[1] = 0.0; + low[2] = 0.0; + low[3] = 0.0; + high[0] = 1.0; + high[1] = 1.0; + high[2] = 1.0; + high[3] = 1.0; + gres[0] = GRES; + gres[1] = GRES; + gres[2] = GRES; + gres[3] = GRES; + avgdev[0] = 0.0; + avgdev[1] = 0.0; + avgdev[2] = 0.0; + avgdev[3] = 0.0; + + /* Create the object */ + rss = new_rspl(RSPL_NOFLAGS, DI, /* di */ + FDI); /* fdi */ + +#ifdef TEST_RANDOM_POINTS + { + int i; + ntps = i_rand(30,150); + tps = (co *)malloc(ntps * sizeof(co)); + for (i = 0; i < ntps; i++) { + tps[i].p[0] = d_rand(0.0,1.0); + tps[i].p[1] = d_rand(0.0,1.0); + tps[i].p[2] = d_rand(0.0,1.0); + tps[i].p[3] = d_rand(0.0,1.0); + tps[i].v[0] = d_rand(0.0,1.0); + tps[i].v[1] = d_rand(0.0,1.0); + tps[i].v[2] = d_rand(0.0,1.0); + tps[i].v[3] = d_rand(0.0,1.0); + } + } +#else + tps = test_points; + ntps = sizeof(test_points)/sizeof(co); +#endif + + /* Fit to scattered data */ + rss->fit_rspl(rss, + FLAGS, /* Non-mon and clip flags */ + tps, /* Test points */ + ntps, /* Number of test points */ + low, high, gres, /* Low, high, resolution of grid */ + NULL, NULL, /* Default data scale */ + 1.0, /* Smoothing */ + avgdev, /* Average deviation */ + NULL); /* iwidth */ + /* IT_TOL, MAX_ITS); */ + +/* verbose(1,"Regular spline fit error = %f\n",rss->efactor(rss,0)); */ + + /* Do a quick check */ + { + co tco; /* Test point */ + int i,j; + double df,sm; + for (i = 0; i < ntps; i++) { + for (j = 0; j < DI; j++) + tco.p[j] = tps[i].p[j]; + + rss->spline_interp(rss, &tco); + sm = 0.0; + for (j = 0; j < DI; j++) { + df = tco.v[j] - tps[i].v[j]; + sm += df * df; + } + printf("Error at data point %d = %f\n",i,sqrt(sm)); + } + } + +#ifdef TEST_REV_LOOKUP + { +#define NIP 10 + int i, r; + double v[MXDO]; /* Target output value */ + co tp[NIP], chp; /* Test point, check point */ + double cvec[4]; /* Text clip vector */ + int auxm[4]; /* Auxiliary target value valid flag */ + + tp[0].v[0] = v[0] = 0.5; + tp[0].v[1] = v[1] = 0.5; + tp[0].v[2] = v[2] = 0.5; + tp[0].v[3] = v[3] = 0.5; + + /* Set auxiliary target */ + auxm[0] = 0; + auxm[1] = 0; + auxm[2] = 1; + auxm[3] = 0; + tp[0].p[0] = -1.0; + tp[0].p[1] = -1.0; + tp[0].p[2] = 0.5; + tp[0].p[3] = -1.0; + + for (i = 1; i < NIP; i++) { /* Make sure we can see changes */ + tp[i].p[0] = -1.0; + tp[i].p[1] = -1.0; + tp[i].p[2] = -1.0; + tp[i].p[3] = -1.0; + } + + /* Clip center */ + cvec[0] = 0.0 - tp[0].v[0]; + cvec[1] = 0.0 - tp[0].v[1]; + cvec[2] = 0.0 - tp[0].v[2]; + cvec[3] = 0.0 - tp[0].v[3]; + + /* Do reverse interpolation ~~~1 */ + if ((r = rss->rev_interp(rss, 0, NIP, auxm, NULL /*cvec*/, tp)) > 0) { + printf("Total of %d Results\n",r); + for (i = 0; i < r; i++) + printf("Result %d = %f, %f, %f, %f\n",i, tp[i].p[0],tp[i].p[1],tp[i].p[2],tp[i].p[3]); + + /* Check test result */ + for (i = 0; i < r; i++) { + chp.p[0] = tp[i].p[0]; + chp.p[1] = tp[i].p[1]; + chp.p[2] = tp[i].p[2]; + chp.p[3] = tp[i].p[3]; + chp.v[0] = -1.0; + chp.v[1] = -1.0; + chp.v[2] = -1.0; + chp.v[3] = -1.0; + if (rss->interp(rss, &chp)) + printf("Fwd check %d failed!\n",i); + else { + int p; + double er = 0.0; + for (p = 0; p < FDI; p++) + er += (v[p] - chp.v[p]) * (v[p] - chp.v[p]); + printf("Fwd check error %d = %f\n",i,er); + } + } + } else + printf("Rev lookup result returned none\n"); + } +#endif /* TEST_REV_LOOKUP */ + +#ifdef TEST_SLICE + /* Test the interpolation */ + { + co tp; /* Test point */ + double x[50000]; + double y[50000]; + double ya[50000]; + double xx,yy; + double x1,x2,y1,y2; + double sx,sy; + int i,j,n; + + /* Set up slice to plot */ + x1 = 0.1; y1 = 0.5; /* ~4 */ + x2 = 0.9; y2 = 0.5; + n = 100; + + sx = (x2 - x1)/n; + sy = (y2 - y1)/n; + + xx = x1; + yy = y1; + for (j = i = 0; i < n; i++) + { + tp.p[0] = xx; + tp.p[1] = yy; + if (rss->spline_interp(rss, &tp)) + { + tp.v[0] = -0.1; + } + x[j] = xx; + y[j] = tp.v[0]; + j++; + xx += sx; + yy += sy; + } + + /* Plot the result */ + do_plot(x,y,NULL,NULL,j); + } +#endif /* TEST_SLICE */ + +#ifdef TEST_FWD_2D + /* Test the interpolation in 2D */ + { +#define WIDTH 200 +#define HEIGHT 200 + double x1 = -0.2; + double x2 = 1.2; + double y1 = -0.2; + double y2 = 1.2; + double min = -0.0; + double max = 1.0; + + unsigned char pa[HEIGHT][WIDTH][3]; + co tco; /* Test point */ + double sx,sy; + int i,j,k; + + sx = (x2 - x1)/(double)WIDTH; + sy = (y2 - y1)/(double)HEIGHT; + + tco.p[2] = 0.5; /* Set slice */ + tco.p[3] = 0.5; + for (j=0; j < HEIGHT; j++) + { + tco.p[1] = (double)((HEIGHT-1) - j) * sy + y1; + for (i=0; i < WIDTH; i++) + { + tco.p[0] = (double)i * sx + x1; + if (rss->spline_interp(rss, &tco)) + { + pa[j][i][0] = 0; /* Out of bounds in green */ + pa[j][i][1] = 100; + pa[j][i][2] = 0; + } + else + { + int m; +/* printf("%d %d, %f %f returned %f\n",i,j,tco.p[0],tco.p[1],tco.v[0]); */ + m = (int)((255.0 * (tco.v[0] - min)/(max - min)) + 0.5); + if (m < 0) + { + pa[j][i][0] = 0; /* Dark blue */ + pa[j][i][1] = 0; + pa[j][i][2] = 40; + } + else if (m > 255) + { + pa[j][i][0] = 220; /* Light blue */ + pa[j][i][1] = 220; + pa[j][i][2] = 255; + } + else + { + pa[j][i][0] = m; /* Level in grey */ + pa[j][i][1] = m; + pa[j][i][2] = m; + } + } + } + } + + /* Mark verticies in red */ + for(k = 0; k < ntps; k++) + { + j = (int)((HEIGHT * (y2 - tps[k].p[1])/(y2 - y1)) + 0.5); + i = (int)((WIDTH * (tps[k].p[0] - x1)/(x2 - x1)) + 0.5); + pa[j][i][0] = 255; + pa[j][i][1] = 0; + pa[j][i][2] = 0; + } + + write_rgb_tiff("tnd.tif",WIDTH,HEIGHT,(unsigned char *)pa); + } +#endif /* TEST_FWD_2D */ + return 0; + } + +/* ---------------------- */ +/* Tiff diagnostic output */ + +void +write_rgb_tiff( +char *name, +int width, +int height, +unsigned char *data +) { + int y; + unsigned char *dp; + TIFF *tif; + + if ((tif = TIFFOpen(name, "w")) == NULL) { + fprintf(stderr,"Failed to open output TIFF file '%s'\n",name); + exit (-1); + } + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + + for (dp = data, y = 0; y < height; y++, dp += 3 * width) { + if (TIFFWriteScanline(tif, (tdata_t)dp, y, 0) < 0) { + fprintf(stderr,"WriteScanline Failed at line %d\n",y); + exit (-1); + } + } + (void) TIFFClose(tif); +} + +#ifdef NEVER + +/******************************************************************/ +/* Error/debug output routines */ +/******************************************************************/ + +/* Basic printf type error() and warning() routines */ + +#ifdef __STDC__ +void +error(char *fmt, ...) +#else +void +error(va_alist) +va_dcl +#endif +{ + va_list args; +#ifndef __STDC__ + char *fmt; +#endif + + fprintf(stderr,"cmatch: Error - "); +#ifdef __STDC__ + va_start(args, fmt); +#else + va_start(args); + fmt = va_arg(args, char *); +#endif + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); + fflush(stdout); + exit (-1); +} + +#ifdef __STDC__ +void +warning(char *fmt, ...) +#else +void +warning(va_alist) +va_dcl +#endif +{ + va_list args; +#ifndef __STDC__ + char *fmt; +#endif + + fprintf(stderr,"cmatch: Warning - "); +#ifdef __STDC__ + va_start(args, fmt); +#else + va_start(args); + fmt = va_arg(args, char *); +#endif + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); +} + +#ifdef __STDC__ +void +verbose(int level, char *fmt, ...) +{ + va_list args; + va_start(args, fmt); +#else +verbose(va_alist) +va_dcl +{ + va_list args; + int level; + char *fmt; + va_start(args); + level = va_arg(args, int); + fmt = va_arg(args, char *); +#endif + if (verbose_level >= level) + { + fprintf(verbose_out,"cmatch: "); + vfprintf(verbose_out, fmt, args); + fprintf(verbose_out, "\n"); + fflush(verbose_out); + } + va_end(args); +} +#endif /* NEVER */ diff --git a/rspl/trnd.c b/rspl/trnd.c new file mode 100644 index 0000000..2676182 --- /dev/null +++ b/rspl/trnd.c @@ -0,0 +1,275 @@ + +/************************************************/ +/* Test RSPL reverse lookup in 3/4D */ +/************************************************/ + +/* Author: Graeme Gill + * Date: 31/10/96 + * Derived from tnd.c + * Copyright 1999 - 2000 Graeme W. Gill + * + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + + +#undef DEBUG +#undef DETAILED + +#include +#include +#include +#include "rspl.h" +#include "numlib.h" + +#ifdef NEVER +FILE *verbose_out = stdout; +int verbose_level = 6; /* Current verbosity level */ + /* 0 = none */ + /* !0 = diagnostics */ +#endif /* NEVER */ + +#define GRES 10 /* Grid resolution */ +#define DI 4 /* Dimensions in */ +#define FDI 3 /* Function (out) Dimensions */ +#define DOLIMIT +#define LIMITV 1.50 + +/* Fwd function approximated by rspl */ +void func( +void *cbctx, +double *out, +double *in) { + double tt[4]; + +#ifdef NEVER +printf(" Got input %f %f\n",in[0],in[1]); + if (in[1] < 0.5 && in[0] < 0.5) { /* 0,0 */ + out[0] = 0.1; + out[1] = 0.5; + } + if (in[1] < 0.5 && in[0] > 0.5) { /* 0,1 */ + out[0] = 0.5; + out[1] = 0.0; + } + if (in[1] > 0.5 && in[0] < 0.5) { /* 0,1 */ + out[0] = 0.9; + out[1] = 0.8; + } + if (in[1] > 0.5 && in[0] > 0.5) { /* 0,1 */ + out[0] = 0.9; + out[1] = 0.2; + } +#endif /* NEVER */ + +#if DI >= 3 + tt[0] = 0.7 * in[0] + 0.2 * in[1] + 0.1 * in[2]; + tt[0] = tt[0] * tt[0]; + tt[1] = 0.2 * in[0] + 0.8 * in[1] - 0.1 * in[2]; + tt[1] = tt[1] * tt[1]; + tt[2] = 0.3 * in[0] - 0.2 * in[1] + 0.9 * in[2]; + tt[2] = tt[2] * tt[2]; +#endif + +#if DI == 4 + tt[0] *= in[3]; + tt[1] *= in[3]; + tt[2] *= in[3]; +#endif + + tt[3] = 0.3 * in[0] + 0.4 * in[1] + 0.3 * in[2]; + +#if FDI > 0 + out[0] = tt[0]; +#endif +#if FDI > 1 + out[1] = tt[1]; +#endif +#if FDI > 2 + out[2] = tt[2]; +#endif +#if FDI > 3 + out[3] = tt[3]; +#endif +} + +/* Simplex ink limit function */ +double limitf( +void *lcntx, +double *in +) { + int i; + double ov; + + for (ov = 0.0, i = 0; i < DI; i++) { + ov += in[i]; + } + return ov; +} + +int +main( +int argc, +char *argv[] +) { + int e; + rspl *rss; /* Multi-resolution regularized spline structure */ + int gres[MXDI]; + + for (e = 0; e < DI; e++) + gres[e] = GRES; + + printf("Started test\n"); + /* Create the object */ + rss = new_rspl(RSPL_NOFLAGS, DI, FDI); + + printf("Rspl allocated\n"); + rss->set_rspl(rss, 0, (void *)NULL, func, NULL, NULL, gres, NULL, NULL); +// rss->set_rspl(rss, RSPL_SET_APXLS, (void *)NULL, func, NULL, NULL, gres, NULL, NULL); + + printf("Rspl set\n"); + + { +#define NIP 10 /* Number of solutions allowed */ + int i, r, cl; + double v[MXDO]; /* Target output value */ + co tp[NIP], chp; /* Test point, check point */ + double cvec[4]; /* Text clip vector */ + int auxm[4]; /* Auxiliary target value valid flag */ + double lmin[4], lmax[4]; /* Locus min/max values */ + + /* Output value being looked for */ +/* + tp[0].v[0] = v[0] = 1.5; + tp[0].v[1] = v[1] = 0.9; + tp[0].v[2] = v[2] = 1.2; + tp[0].v[3] = v[3] = 0.0; +*/ + tp[0].v[0] = v[0] = 0.3; + tp[0].v[1] = v[1] = 0.4; + tp[0].v[2] = v[2] = 0.26; + tp[0].v[3] = v[3] = 0.0; + + /* Set auxiliary target */ + auxm[0] = 0; + auxm[1] = 0; + auxm[2] = 0; + auxm[3] = 1; + tp[0].p[0] = 0; + tp[0].p[1] = 0; + tp[0].p[2] = 0.0; + tp[0].p[3] = 0.87; + + for (i = 1; i < NIP; i++) { /* Make sure we can see changes */ + tp[i].p[0] = -1.0; + tp[i].p[1] = -1.0; + tp[i].p[2] = -1.0; + tp[i].p[3] = -1.0; + } + + /* Clip center */ + cvec[0] = 0.1 - tp[0].v[0]; + cvec[1] = 0.1 - tp[0].v[1]; + cvec[2] = 0.1 - tp[0].v[2]; + cvec[3] = 0.1 - tp[0].v[3]; + +#ifdef DOLIMIT + /* Setup limit */ + rss->rev_set_limit(rss, + limitf, /* limit function */ + NULL, /* Function context */ + LIMITV /* limit maximum value */ + ); +#endif /* DOLIMIT */ + +#if DI > FDI + /* Check out the locus size */ + if ((r = rss->rev_locus(rss, + auxm, /* auxm Auxiliary mask flags */ + tp, /* Input and auxiliary values */ + lmin, /* Locus min/max return values */ + lmax + )) > 0) { + + printf("Total of %d Results\n",r); + for (i = 0; i < FDI; i++) { + if (auxm[i] == 0) + continue; + printf("Auxiliary %d min = %f, max = %f\n",i, lmin[i], lmax[i]); + } + } else { + printf("Failed to find gamut range\n"); + } + printf("\n\n"); +#endif + + /* Do reverse interpolation ~~~1 */ + if ((r = rss->rev_interp(rss, + 0 /* RSPL_EXACTAUX */, /* Hint flags */ + NIP, /* Number of solutions allowed */ + auxm, /* auxm Auxiliary mask flags */ + cvec, /* cvec Clip vector direction & length */ + tp) /* Input and output values */ + ) > 0) { + + printf("Total of %d Results\n", r &= RSPL_NOSOLNS); + + printf("Target output %f, %f, %f, %f\n", v[0], v[1], v[2], v[3]); + + cl = r & RSPL_DIDCLIP; /* clipped flag */ + r &= RSPL_NOSOLNS; /* Get number of solutions */ + + /* Check test result */ + for (i = 0; i < r; i++) { + chp.p[0] = tp[i].p[0]; + chp.p[1] = tp[i].p[1]; + chp.p[2] = tp[i].p[2]; + chp.p[3] = tp[i].p[3]; + chp.v[0] = -1.0; + chp.v[1] = -1.0; + chp.v[2] = -1.0; + chp.v[3] = -1.0; + printf("Result %d = inp: %f, %f, %f, %f\n",i, tp[i].p[0],tp[i].p[1],tp[i].p[2],tp[i].p[3]); + printf(" out: %f, %f, %f, %f\n",tp[i].v[0],tp[i].v[1],tp[i].v[2],tp[i].v[3]); + + if (rss->interp(rss, &chp)) + printf("Fwd check %d failed!\n",i); + else { + int p; + double er = 0.0; + for (p = 0; p < FDI; p++) + er += (v[p] - chp.v[p]) * (v[p] - chp.v[p]); + er = sqrt(er); + printf("Fwd check error %d = %f\n",i,er); + + if (cl != 0) { + /* Check if clipped result us reasonable */ + /* ~~~ */ + } + } + { + printf("Output limit = %f\n",limitf(NULL, tp[i].p)); + } + } + } else + printf("Rev lookup result returned none\n"); + } + rss->del(rss); + return 0; +} + + +#ifdef NEVER +/* Standard interface for powell function */ +/* return err on sucess, -1.0 on failure */ +/* Result will be in cp */ +double powell( +int di, /* Dimensionality */ +double cp[], /* Initial starting point */ +double s[], /* Size of initial search area */ +double ftol, /* Tollerance of error change to stop on */ +int maxit, /* Maximum iterations allowed */ +double (*func)(void *fdata, double tp[]), /* Error function to evaluate */ +void *fdata /* Opaque data needed by function */ +) { +#endif -- cgit v1.2.3