From b215a9ae287e6d40cc5e79b7db2b42c3cb9c6f06 Mon Sep 17 00:00:00 2001 From: Dan Bloomberg Date: Wed, 9 Jun 2021 16:21:07 -0700 Subject: [PATCH] Fix hashmap with dna key that was simply cast from the float64 value. * this might fix a problem with failures in 1.81.0 on 20-23 of hash_reg on architectures like armvhl, aarch64, ppc64le, s390x. * fix bug in hash_reg.c; add test for identical content between aset and hashmap operations on dna. * also add test of writing/reading in jp2kio_reg with J2K codec. --- prog/hash_reg.c | 97 +++++++++++++++++++++------------------------- prog/jp2kio_reg.c | 43 ++++++++++++++------ src/allheaders.h | 1 + src/dnafunc1.c | 8 ++-- src/jp2kheader.c | 10 ++--- src/jp2kio.c | 2 + src/utils1.c | 30 ++++++++++++++ version-notes.html | 1 + 8 files changed, 121 insertions(+), 71 deletions(-) diff --git a/prog/hash_reg.c b/prog/hash_reg.c index 2b4293df8..8b408d6d7 100644 --- a/prog/hash_reg.c +++ b/prog/hash_reg.c @@ -59,11 +59,11 @@ const l_int32 da_intersection = 16001; l_int32 main(int argc, char **argv) { -l_uint8 *data1, *data2; -l_int32 i, n, c1, c2, s1; -size_t size1, size2; +l_uint8 *data1; +l_int32 i, n, c1, c2, c3, c4, c5, s1; +size_t size1; L_ASET *set; -L_DNA *da0, *da1, *da2, *da3; +L_DNA *da0, *da1, *da2, *da3, *da4, *da5; L_HASHMAP *hmap; PTA *pta0, *pta1, *pta2, *pta3; SARRAY *sa0, *sa1, *sa2, *sa3; @@ -96,20 +96,17 @@ L_REGPARAMS *rp; c1 = sarrayGetCount(sa3); sarrayDestroy(&sa3); regTestCompareValues(rp, string_set, c1, 0); /* 1 */ - if (rp->display) - lept_stderr(" aset: size without dups = %d\n", c1); + if (rp->display) lept_stderr(" aset: size without dups = %d\n", c1); sarrayIntersectionByAset(sa1, sa2, &sa3); c1 = sarrayGetCount(sa3); sarrayDestroy(&sa3); regTestCompareValues(rp, string_intersection, c1, 0); /* 2 */ - if (rp->display) - lept_stderr(" aset: intersection size = %d\n", c1); + if (rp->display) lept_stderr(" aset: intersection size = %d\n", c1); sarrayUnionByAset(sa1, sa2, &sa3); c1 = sarrayGetCount(sa3); sarrayDestroy(&sa3); regTestCompareValues(rp, string_union, c1, 0); /* 3 */ - if (rp->display) - lept_stderr(" aset: union size = %d\n", c1); + if (rp->display) lept_stderr(" aset: union size = %d\n", c1); /* Test string hashing with hashmap */ hmap = l_hmapCreateFromSarray(sa1); @@ -122,20 +119,17 @@ L_REGPARAMS *rp; c1 = sarrayGetCount(sa3); sarrayDestroy(&sa3); regTestCompareValues(rp, string_set, c1, 0); /* 5 */ - if (rp->display) - lept_stderr(" hmap: size without dups = %d\n", c1); + if (rp->display) lept_stderr(" hmap: size without dups = %d\n", c1); sarrayIntersectionByHmap(sa1, sa2, &sa3); c1 = sarrayGetCount(sa3); sarrayDestroy(&sa3); regTestCompareValues(rp, string_intersection, c1, 0); /* 6 */ - if (rp->display) - lept_stderr(" hmap: intersection size = %d\n", c1); + if (rp->display) lept_stderr(" hmap: intersection size = %d\n", c1); sarrayUnionByHmap(sa1, sa2, &sa3); c1 = sarrayGetCount(sa3); sarrayDestroy(&sa3); regTestCompareValues(rp, string_union, c1, 0); /* 7 */ - if (rp->display) - lept_stderr(" hmap: union size = %d\n", c1); + if (rp->display) lept_stderr(" hmap: union size = %d\n", c1); sarrayDestroy(&sa3); sarrayDestroy(&sa0); sarrayDestroy(&sa1); @@ -162,20 +156,17 @@ L_REGPARAMS *rp; c1 = ptaGetCount(pta3); ptaDestroy(&pta3); regTestCompareValues(rp, pta_set, c1, 0); /* 9 */ - if (rp->display) - lept_stderr(" aset: size without dups = %d\n", c1); + if (rp->display) lept_stderr(" aset: size without dups = %d\n", c1); ptaIntersectionByAset(pta1, pta2, &pta3); c1 = ptaGetCount(pta3); ptaDestroy(&pta3); regTestCompareValues(rp, pta_intersection, c1, 0); /* 10 */ - if (rp->display) - lept_stderr(" aset: intersection size = %d\n", c1); + if (rp->display) lept_stderr(" aset: intersection size = %d\n", c1); ptaUnionByAset(pta1, pta2, &pta3); c1 = ptaGetCount(pta3); ptaDestroy(&pta3); regTestCompareValues(rp, pta_union, c1, 0); /* 11 */ - if (rp->display) - lept_stderr(" aset: union size = %d\n", c1); + if (rp->display) lept_stderr(" aset: union size = %d\n", c1); /* Test point hashing with hashmap */ hmap = l_hmapCreateFromPta(pta2); @@ -187,20 +178,17 @@ L_REGPARAMS *rp; c1 = ptaGetCount(pta3); ptaDestroy(&pta3); regTestCompareValues(rp, pta_set, c1, 0); /* 13 */ - if (rp->display) - lept_stderr(" hmap: size without dups = %d\n", c1); + if (rp->display) lept_stderr(" hmap: size without dups = %d\n", c1); ptaIntersectionByHmap(pta1, pta2, &pta3); c1 = ptaGetCount(pta3); ptaDestroy(&pta3); regTestCompareValues(rp, pta_intersection, c1, 0); /* 14 */ - if (rp->display) - lept_stderr(" hmap: intersection size = %d\n", c1); + if (rp->display) lept_stderr(" hmap: intersection size = %d\n", c1); ptaUnionByHmap(pta1, pta2, &pta3); c1 = ptaGetCount(pta3); ptaDestroy(&pta3); regTestCompareValues(rp, pta_union, c1, 0); /* 15 */ - if (rp->display) - lept_stderr(" hmap: union size = %d\n", c1); + if (rp->display) lept_stderr(" hmap: union size = %d\n", c1); ptaDestroy(&pta0); ptaDestroy(&pta1); ptaDestroy(&pta2); @@ -228,20 +216,17 @@ L_REGPARAMS *rp; c1 = l_dnaGetCount(da3); l_dnaDestroy(&da3); regTestCompareValues(rp, da_set, c1, 0); /* 17 */ - if (rp->display) - lept_stderr(" aset: size without dups = %d\n", c1); + if (rp->display) lept_stderr(" aset: size without dups = %d\n", c1); l_dnaIntersectionByAset(da1, da2, &da3); c1 = l_dnaGetCount(da3); l_dnaDestroy(&da3); regTestCompareValues(rp, da_intersection, c1, 0); /* 18 */ - if (rp->display) - lept_stderr(" aset: intersection size = %d\n", c1); + if (rp->display) lept_stderr(" aset: intersection size = %d\n", c1); l_dnaUnionByAset(da1, da2, &da3); c1 = l_dnaGetCount(da3); l_dnaDestroy(&da3); regTestCompareValues(rp, da_union, c1, 0); /* 19 */ - if (rp->display) - lept_stderr(" aset: union size = %d\n", c1); + if (rp->display) lept_stderr(" aset: union size = %d\n", c1); /* Test dna hashing with hashmap */ hmap = l_hmapCreateFromDna(da2); @@ -253,26 +238,23 @@ L_REGPARAMS *rp; c1 = l_dnaGetCount(da3); l_dnaDestroy(&da3); regTestCompareValues(rp, da_set, c1, 0); /* 21 */ - if (rp->display) - lept_stderr(" hmap: size without dups = %d\n", c1); + if (rp->display) lept_stderr(" hmap: size without dups = %d\n", c1); l_dnaIntersectionByHmap(da1, da2, &da3); c1 = l_dnaGetCount(da3); l_dnaDestroy(&da3); regTestCompareValues(rp, da_intersection, c1, 0); /* 22 */ - if (rp->display) - lept_stderr(" hmap: intersection size = %d\n", c1); + if (rp->display) lept_stderr(" hmap: intersection size = %d\n", c1); l_dnaUnionByHmap(da1, da2, &da3); c1 = l_dnaGetCount(da3); l_dnaDestroy(&da3); regTestCompareValues(rp, da_union, c1, 0); /* 23 */ - if (rp->display) - lept_stderr(" hmap: union size = %d\n", c1); + if (rp->display) lept_stderr(" hmap: union size = %d\n", c1); l_dnaDestroy(&da0); l_dnaDestroy(&da1); l_dnaDestroy(&da2); - /* Test dna hashing, comparing ordered and unordered, and - * comparing the results. */ + /* Another test of dna hashing, showing equivalence + * of results between ordered and unordered sets. */ da0 = l_dnaMakeSequence(0, 1, 20); n = l_dnaGetCount(da0); for (i = 0; i < n; i++) @@ -284,25 +266,36 @@ L_REGPARAMS *rp; c1 = l_dnaGetCount(da1); c2 = l_dnaGetCount(da2); l_dnaRemoveDupsByAset(da2, &da3); - l_dnaWriteMem(&data1, &size1, da3); - l_dnaDestroy(&da3); - l_dnaRemoveDupsByHmap(da2, &da3, NULL); - l_dnaWriteMem(&data2, &size2, da3); - regTestCompareStrings(rp, data1, size1, data2, size2); /* 24 */ - if (rp->display) lept_stderr("%s", data1); - lept_free(data1); - lept_free(data2); + l_dnaRemoveDupsByHmap(da2, &da4, NULL); + /* Show the two sets da3 and da4 are identical in content, + * because they have the same size and their intersection also + * has the same size. */ + c3 = l_dnaGetCount(da3); + c4 = l_dnaGetCount(da4); + regTestCompareValues(rp, c3, c4, 0); /* 24 */ + l_dnaIntersectionByHmap(da3, da4, &da5); + c5 = l_dnaGetCount(da5); + regTestCompareValues(rp, c4, c5, 0); /* 25 */ + if (rp->display) { + lept_stderr("\nc1 = %d, c2 = %d\n", c1, c2); + lept_stderr("c3 = %d, c4 = %d, c5 = %d\n", c3, c4, c5); + l_dnaWriteMem(&data1, &size1, da4); + lept_stderr("%s", data1); + lept_free(data1); + } l_dnaDestroy(&da0); l_dnaDestroy(&da1); l_dnaDestroy(&da2); l_dnaDestroy(&da3); + l_dnaDestroy(&da4); + l_dnaDestroy(&da5); /* Test pixel counting operations with hashmap and ordered map */ pix1 = pixRead("wet-day.jpg"); pixCountRGBColorsByHash(pix1, &c1); pixCountRGBColors(pix1, 1, &c2); - regTestCompareValues(rp, 42427, c1, 0); /* 24 */ - regTestCompareValues(rp, 42427, c2, 0); /* 25 */ + regTestCompareValues(rp, 42427, c1, 0); /* 26 */ + regTestCompareValues(rp, 42427, c2, 0); /* 27 */ if (rp->display) { lept_stderr("Color count using hashmap: %d\n", c1); lept_stderr("Color count using aset: %d\n", c2); diff --git a/prog/jp2kio_reg.c b/prog/jp2kio_reg.c index 678063ab9..f3b9b97af 100644 --- a/prog/jp2kio_reg.c +++ b/prog/jp2kio_reg.c @@ -56,6 +56,7 @@ void DoJp2kTest1(L_REGPARAMS *rp, const char *fname); void DoJp2kTest2(L_REGPARAMS *rp, const char *fname); +void DoJp2kTest3(L_REGPARAMS *rp, const char *fname); int main(int argc, @@ -84,6 +85,7 @@ L_REGPARAMS *rp; DoJp2kTest1(rp, "test24.jpg"); /* DoJp2kTest2(rp, "karen8.jpg"); */ /* encode fails on smallest image */ DoJp2kTest2(rp, "test24.jpg"); + DoJp2kTest3(rp, "wyom.jpg"); return regTestCleanup(rp); } @@ -101,10 +103,10 @@ PIX *pix0, *pix1, *pix2, *pix3; pix0 = pixRead(fname); pix1 = pixScale(pix0, 0.5, 0.5); pixGetDimensions(pix1, &w, &h, NULL); - regTestWritePixAndCheck(rp, pix1, IFF_JP2); + regTestWritePixAndCheck(rp, pix1, IFF_JP2); /* 0, 5 */ name = regTestGenLocalFilename(rp, -1, IFF_JP2); pix2 = pixRead(name); - regTestWritePixAndCheck(rp, pix2, IFF_JP2); + regTestWritePixAndCheck(rp, pix2, IFF_JP2); /* 1, 6 */ pixDisplayWithTitle(pix2, 0, 100, "1", rp->display); pixDestroy(&pix1); pixDestroy(&pix2); @@ -115,12 +117,12 @@ PIX *pix0, *pix1, *pix2, *pix3; snprintf(buf, sizeof(buf), "/tmp/lept/regout/jp2kio.%02d.jp2", rp->index + 1); pixWriteJp2k(buf, pix1, 38, 0, 0, 0); /* write cropped to the box */ - regTestCheckFile(rp, buf); + regTestCheckFile(rp, buf); /* 2, 7 */ pix2 = pixRead(buf); /* read the cropped image */ - regTestWritePixAndCheck(rp, pix2, IFF_JP2); + regTestWritePixAndCheck(rp, pix2, IFF_JP2); /* 3, 8 */ pixDisplayWithTitle(pix2, 500, 100, "2", rp->display); pix3 = pixReadJp2k(buf, 2, NULL, 0, 0); /* read cropped image at 2x red */ - regTestWritePixAndCheck(rp, pix3, IFF_JP2); + regTestWritePixAndCheck(rp, pix3, IFF_JP2); /* 4, 9 */ pixDisplayWithTitle(pix3, 1000, 100, "3", rp->display); pixDestroy(&pix0); pixDestroy(&pix1); @@ -146,13 +148,13 @@ PIX *pix0, *pix1, *pix2, *pix3; pix0 = pixRead(fname); pix1 = pixScale(pix0, 0.5, 0.5); pixGetDimensions(pix1, &w, &h, NULL); - regTestWritePixAndCheck(rp, pix1, IFF_JP2); + regTestWritePixAndCheck(rp, pix1, IFF_JP2); /* 10 */ name = regTestGenLocalFilename(rp, -1, IFF_JP2); pix2 = pixRead(name); - regTestWritePixAndCheck(rp, pix2, IFF_JP2); + regTestWritePixAndCheck(rp, pix2, IFF_JP2); /* 11 */ data = l_binaryRead(name, &nbytes); pix3 = pixReadMemJp2k(data, nbytes, 1, NULL, 0, 0); - regTestWritePixAndCheck(rp, pix3, IFF_JP2); + regTestWritePixAndCheck(rp, pix3, IFF_JP2); /* 12 */ pixDisplayWithTitle(pix3, 0, 100, "1", rp->display); pixDestroy(&pix1); pixDestroy(&pix2); @@ -165,13 +167,13 @@ PIX *pix0, *pix1, *pix2, *pix3; snprintf(buf, sizeof(buf), "/tmp/lept/regout/jp2kio.%02d.jp2", rp->index + 1); pixWriteJp2k(buf, pix1, 38, 0, 0, 0); /* write cropped to the box */ - regTestCheckFile(rp, buf); + regTestCheckFile(rp, buf); /* 13 */ data = l_binaryRead(buf, &nbytes); pix2 = pixReadMemJp2k(data, nbytes, 1, NULL, 0, 0); /* read it again */ - regTestWritePixAndCheck(rp, pix2, IFF_JP2); + regTestWritePixAndCheck(rp, pix2, IFF_JP2); /* 14 */ pixDisplayWithTitle(pix2, 500, 100, "2", rp->display); pix3 = pixReadMemJp2k(data, nbytes, 2, NULL, 0, 0); /* read at 2x red */ - regTestWritePixAndCheck(rp, pix3, IFF_JP2); + regTestWritePixAndCheck(rp, pix3, IFF_JP2); /* 15 */ pixDisplayWithTitle(pix3, 1000, 100, "3", rp->display); boxDestroy(&box); pixDestroy(&pix0); @@ -182,3 +184,22 @@ PIX *pix0, *pix1, *pix2, *pix3; lept_free(name); return; } + +void DoJp2kTest3(L_REGPARAMS *rp, + const char *fname) +{ +FILE *fp; +PIX *pix0, *pix1; + + /* Test write and read using J2K codec */ + lept_mkdir("lept/jp2k"); + pix0 = pixRead(fname); + fp = fopenWriteStream("/tmp/lept/jp2k/wyom.j2k", "wb+"); + pixWriteStreamJp2k(fp, pix0, 34, 4, L_J2K_CODEC, 0, 0); + fclose(fp); + pix1 = pixRead("/tmp/lept/jp2k/wyom.j2k"); + regTestCompareSimilarPix(rp, pix0, pix1, 20, 0.01, 0); /* 16 */ + pixDisplayWithTitle(pix1, 500, 500, NULL, rp->display); + pixDestroy(&pix0); + pixDestroy(&pix1); +} diff --git a/src/allheaders.h b/src/allheaders.h index 43d9ae4a3..7a5341caf 100644 --- a/src/allheaders.h +++ b/src/allheaders.h @@ -2658,6 +2658,7 @@ LEPT_DLL extern l_int32 lept_roundftoi ( l_float32 fval ); LEPT_DLL extern l_ok l_hashStringToUint64 ( const char *str, l_uint64 *phash ); LEPT_DLL extern l_ok l_hashStringToUint64Fast ( const char *str, l_uint64 *phash ); LEPT_DLL extern l_ok l_hashPtToUint64 ( l_int32 x, l_int32 y, l_uint64 *phash ); +LEPT_DLL extern l_ok l_hashFloat64ToUint64 ( l_float64 val, l_uint64 *phash ); LEPT_DLL extern l_ok findNextLargerPrime ( l_int32 start, l_uint32 *pprime ); LEPT_DLL extern l_ok lept_isPrime ( l_uint64 n, l_int32 *pis_prime, l_uint32 *pfactor ); LEPT_DLL extern l_uint32 convertIntToGrayCode ( l_uint32 val ); diff --git a/src/dnafunc1.c b/src/dnafunc1.c index db43dd283..35c49224d 100644 --- a/src/dnafunc1.c +++ b/src/dnafunc1.c @@ -517,7 +517,7 @@ L_DNA *da_small, *da_big, *dad; * *
  *  Notes:
- *       (1) Use the values in %da as the hash keys.
+ *       (1) Derive the hash keys from the values in %da.
  *       (2) The indices into %da are stored in the val field of the hashitems.
  *           This is necessary so that %hmap and %da can be used together.
  * 
@@ -540,7 +540,8 @@ L_HASHMAP *hmap; hmap = l_hmapCreate(0, 0); for (i = 0; i < n; i++) { l_dnaGetDValue(da, i, &dval); - hitem = l_hmapLookup(hmap, (l_uint64)dval, i, L_HMAP_CREATE); + l_hashFloat64ToUint64(dval, &key); + hitem = l_hmapLookup(hmap, key, i, L_HMAP_CREATE); } return hmap; } @@ -699,7 +700,8 @@ L_HASHMAP *hmap; n = l_dnaGetCount(da_small); for (i = 0; i < n; i++) { l_dnaGetDValue(da_small, i, &dval); - hitem = l_hmapLookup(hmap, (l_uint64)dval, i, L_HMAP_CHECK); + l_hashFloat64ToUint64(dval, &key); + hitem = l_hmapLookup(hmap, key, i, L_HMAP_CHECK); if (!hitem || hitem->count == 0) continue; l_dnaAddNumber(dad, dval); diff --git a/src/jp2kheader.c b/src/jp2kheader.c index 803146180..15f64c130 100644 --- a/src/jp2kheader.c +++ b/src/jp2kheader.c @@ -73,7 +73,7 @@ static const l_int32 MAX_JP2K_HEIGHT = 100000; * \param[out] ph [optional] * \param[out] pbps [optional] bits/sample * \param[out] pspp [optional] samples/pixel - * \param[out] pcodec [optional] JP2_CODEC or J2K_CODEC + * \param[out] pcodec [optional] L_JP2_CODEC or L_J2K_CODEC * \return 0 if OK, 1 on error */ l_ok @@ -108,7 +108,7 @@ FILE *fp; * \param[out] ph [optional] * \param[out] pbps [optional] bits/sample * \param[out] pspp [optional] samples/pixel - * \param[out] pcodec [optional] JP2_CODEC or J2K_CODEC + * \param[out] pcodec [optional] L_JP2_CODEC or L_J2K_CODEC * \return 0 if OK, 1 on error */ l_ok @@ -147,7 +147,7 @@ l_int32 nread, ret; * \param[out] ph [optional] * \param[out] pbps [optional] bits/sample * \param[out] pspp [optional] samples/pixel - * \param[out] pcodec [optional] JP2_CODEC or J2K_CODEC + * \param[out] pcodec [optional] L_JP2_CODEC or L_J2K_CODEC * \return 0 if OK, 1 on error * *
@@ -155,7 +155,7 @@ l_int32  nread, ret;
  *      (1) The ISO/IEC reference for jpeg2000 is
  *               http://www.jpeg.org/public/15444-1annexi.pdf
  *          and the file format syntax begins at page 127.
- *      (2) With a image file codec (JP2_CODEC), the Image Header Box
+ *      (2) With a image file codec (L_JP2_CODEC), the Image Header Box
  *          begins with 'ihdr' = 0x69686472 in big-endian order.  This
  *          typically, but not always, starts on byte 44, with the
  *          big-endian data fields beginning at byte 48:
@@ -163,7 +163,7 @@ l_int32  nread, ret;
  *               w:    4 bytes
  *               spp:  2 bytes
  *               bps:  1 byte   (contains bps - 1)
- *      (3) With a codestream codec (J2K_CODEC), the first 4 bytes are
+ *      (3) With a codestream codec (L_J2K_CODEC), the first 4 bytes are
  *          0xff4fff51.  The fields for w and h appear to start on byte 8,
  *          and the fields for spp and bps appear to start on byte 40.
  * 
diff --git a/src/jp2kio.c b/src/jp2kio.c index d9ac3553b..b2dd98ff6 100644 --- a/src/jp2kio.c +++ b/src/jp2kio.c @@ -649,6 +649,8 @@ opj_image_t *image = NULL; return ERROR_INT("failed to set up the encoder\n", procName, 1); } + /* Set the resolution (TBD) */ + /* Open a compression stream for writing. In 2.0 we could use this: * opj_stream_create_default_file_stream(fp, 0) * but the file stream interface was removed in 2.1. */ diff --git a/src/utils1.c b/src/utils1.c index ea9a067d4..a85b117da 100644 --- a/src/utils1.c +++ b/src/utils1.c @@ -76,6 +76,7 @@ * l_int32 l_hashStringToUint64() * l_int32 l_hashStringToUint64Fast() * l_int32 l_hashPtToUint64() + * l_int32 l_hashFloat64ToUint64() * * Prime finders * l_int32 findNextLargerPrime() @@ -817,6 +818,35 @@ l_hashPtToUint64(l_int32 x, } +/*! + * \brief l_hashFloat64ToUint64() + * + * \param[in] val + * \param[out] phash hash key + * \return 0 if OK, 1 on error + * + *
+ * Notes:
+ *      (1) This is a simple hash for using hashmaps with 64-bit float data.
+ *      (2) The resulting hash is called a "key" in a lookup operation.
+ *          The bucket for %val in a hashmap is then found by taking the mod
+ *          of the hash key with the number of buckets (which is prime).
+ * 
+ */ +l_ok +l_hashFloat64ToUint64(l_float64 val, + l_uint64 *phash) +{ + PROCNAME("l_hashFloatToUint64"); + + if (!phash) + return ERROR_INT("&hash not defined", procName, 1); + val = (val >= 0.0) ? 847019.66701 * val : -217324.91613 * val; + *phash = (l_uint64)val; + return 0; +} + + /*---------------------------------------------------------------------* * Prime finders * *---------------------------------------------------------------------*/ diff --git a/version-notes.html b/version-notes.html index 9f1ebcec5..f7d5201af 100644 --- a/version-notes.html +++ b/version-notes.html @@ -91,6 +91,7 @@

1.81.1 June 10, 2021 * Added choice of codec (JP2 or J2K) when writing jp2k files. + * Fix use of hashmap with key based on dna. 1.81.0 June 6, 2021 * Fixed problems with tiff pdf wrapping photometry.