Index: /branches/eam_branches/ipp-20211108/ippTests/ippCheck/smfcheck.pro
===================================================================
--- /branches/eam_branches/ipp-20211108/ippTests/ippCheck/smfcheck.pro	(revision 41983)
+++ /branches/eam_branches/ipp-20211108/ippTests/ippCheck/smfcheck.pro	(revision 41983)
@@ -0,0 +1,139 @@
+# compare smf-to-smf
+
+input tap.sh
+
+macro check.extensions
+  if ($0 != 3)
+    echo "USAGE: check.extensions (smf1) (smf2)"
+    break
+  end
+
+  plan 5
+
+  # 
+  list ext0 -x "ftable -list $1"
+  list ext1 -x "ftable -list $2"
+
+  ok ($ext0:n == $ext1:n) "same number of extensions"
+
+  # XXX how to do the next steps if the numbers do not match?
+  # NOTE: ext0:0 is the title line
+  for i 1 $ext0:n
+    list w0 -split $ext0:$i
+    list w1 -split $ext1:$i
+    ok ($w0:0 == $w1:0) "matching ext names"
+
+    # if extname is XYnn.hdr, compare the headers
+    strstr -q $w0:0 .hdr -start s1
+    if ($s1 == 4)
+      $Next = $i - 1
+      echo $w0:0 == $Next
+      echo ippCheckKeys $1 $Next $2 $Next
+      exec ippCheckKeys $1 $Next $2 $Next
+      break
+    end
+  end
+end
+
+macro check.keywords
+  if ($0 != 5)
+    echo "USAGE: check.keywords (smf1) (ext1) (smf2) (ext2)"
+    break
+  end
+
+  exec fhead -x $2 $1 > t1.hdr
+  exec fhead -x $4 $3 > t2.hdr
+  
+  
+  # this comparison compares FITS Table fields
+  exec grep "^TTYPE" t1.hdr | prcol 3 > f1.hdr
+  exec grep "^TTYPE" t2.hdr | prcol 3 > f2.hdr
+
+  break -auto off
+  echo "fields added to $3"
+  exec diff f1.hdr f2.hdr | grep '>'
+
+  echo "fields removed from $1"
+  exec diff f1.hdr f2.hdr | grep '<'
+  break -auto on
+end
+
+macro match.keywords
+  if ($0 != 5)
+    echo "USAGE test.keywords (smf1) (ext1) (smf2) (ext2)"
+    break
+  end
+
+  # smf1/ext1 holds the referece list
+  list keys -x "smfkeys.sh $1 $2"
+
+  rd h1 $1 -x $2
+  rd h2 $3 -x $4
+  
+  for i 0 $keys:n
+    if ("$keys:$i" == "XTENSION") continue; # opihi transforms XTENSION to SIMPLE
+
+    $mykey = $keys:$i
+    keyword h1 $mykey val1
+    keyword h2 $mykey val2
+
+    # XXX I do not have a good way to test the
+    # type of the header value. I can just check
+
+    # empty string needs to be specially handled
+    if (("$val1" == "") || ("$val2" == ""))
+      if ("$val1" == "$val2") 
+        # echo "matched values $keys:$i : $val1 vs $val2"
+      else
+        echo "mismatched values $keys:$i : $val1 vs $val2"
+      end
+      continue
+    end
+
+    # multi-word strings need to be specially handled
+    getchr "$val1" " " s1
+    getchr "$val2" " " s2
+    if (($s1 > -1) || ($s2 > -1))
+      # echo "multiword: $val1 : $s1"
+      # echo "multiword: $val2 : $s2"
+      if ("$val1" == "$val2") 
+        # echo "matched multivalues $keys:$i : $val1 vs $val2"
+      else
+        echo "mismatched multivalues $keys:$i : $val1 vs $val2"
+      end
+      continue
+    end
+
+    # strings with +,-,*,/,^ need to be specially handled
+    $hasmath1 = 0
+    $hasmath2 = 0
+    foreach mychar + - * / ^ : ,
+      getchr "$val1" "$mychar" s1
+      if ($s1 > -1)
+        $hasmath1 = 1
+      end
+      getchr "$val2" "$mychar" s2
+      if ($s2 > -1)
+        $hasmath2 = 1
+      end
+    end
+
+    if ($hasmath1 || $hasmath2)
+      #echo "math: $val1 : $s1"
+      #echo "math: $val2 : $s2"
+      if ("$val1" == "$val2") 
+        # echo "matched multivalues $keys:$i : $val1 vs $val2"
+      else
+        echo "mismatched multivalues $keys:$i : $val1 vs $val2"
+      end
+      continue
+    end
+
+    if (isword($val1)) 
+      if ("$val1" == "$val2") continue
+    else
+      if ($val1 == $val2) continue
+    end
+    echo "mismatched values $keys:$i : $val1 vs $val2"
+  end
+end
Index: /branches/eam_branches/ipp-20211108/ippTests/ippCheck/src/Makefile
===================================================================
--- /branches/eam_branches/ipp-20211108/ippTests/ippCheck/src/Makefile	(revision 41983)
+++ /branches/eam_branches/ipp-20211108/ippTests/ippCheck/src/Makefile	(revision 41983)
@@ -0,0 +1,7 @@
+
+CC       = gcc -g
+CFLAGS   = -lpslib -L /home/real/eugene/src/psconfig/ipp-dev-20211108.lin64/lib -I /home/real/eugene/src/psconfig/ipp-dev-20211108.lin64/include/pslib
+
+ippCheckKeys: ippCheckKeys.c
+	$(CC) -o $@ $^ $(CFLAGS)
+
Index: /branches/eam_branches/ipp-20211108/ippTests/ippCheck/src/ippCheckKeys.c
===================================================================
--- /branches/eam_branches/ipp-20211108/ippTests/ippCheck/src/ippCheckKeys.c	(revision 41983)
+++ /branches/eam_branches/ipp-20211108/ippTests/ippCheck/src/ippCheckKeys.c	(revision 41983)
@@ -0,0 +1,215 @@
+// # include <ippCheckKeys.h>
+# include <pslib.h>
+
+int main(int argc, char **argv) {
+
+    psLibInit(NULL);
+
+    if (argc != 5) {
+	fprintf (stderr, "USAGE: ippCheckKeys file1 ext1 file2 ext2\n");
+	exit (2);
+    }
+
+    // this is the reference file
+    psFits *file_1 = psFitsOpen (argv[1], "r");
+
+    int ext_1 = atoi (argv[2]);
+    psFitsMoveExtNum(file_1, ext_1, false);
+
+    psMetadata *head_1 = psFitsReadHeader (NULL, file_1);
+
+    // this is the test file
+    psFits *file_2 = psFitsOpen (argv[3], "r");
+
+    int ext_2 = atoi (argv[4]);
+    psFitsMoveExtNum(file_2, ext_2, false);
+
+    psMetadata *head_2 = psFitsReadHeader (NULL, file_2);
+
+    // I want to save a few lists of issues:
+    // 1) missing keywords
+    // 2) mismatch types
+    // 3) mismatch booleans
+    // 4) values differ by 100%
+    // 5) values differ by 10%
+    // 6) values differ by 1%
+
+    psMetadata *missing = psMetadataAlloc ();
+    psMetadata *badtype = psMetadataAlloc ();
+    psMetadata *badbool = psMetadataAlloc ();
+    psMetadata *offstr  = psMetadataAlloc ();
+    psMetadata *off100  = psMetadataAlloc ();
+    psMetadata *off10   = psMetadataAlloc ();
+    psMetadata *off1    = psMetadataAlloc ();
+    psMetadata *offlow  = psMetadataAlloc ();
+
+    // compare keywords
+    psMetadataIterator *iter = psMetadataIteratorAlloc (head_1, PS_LIST_HEAD, NULL);
+    psMetadataItem *item_1 = NULL;
+    while ((item_1 = psMetadataGetAndIncrement (iter)) != NULL) {
+
+	// skip standard duplicate entries:
+	if (!strcmp(item_1->name, "HISTORY")) continue;
+	if (!strcmp(item_1->name, "COMMENT")) continue;
+
+        psMetadataItem *item_2 = psMetadataLookup(head_2, item_1->name);
+	if (!item_2) {
+	    // fprintf (stdout, "key %s in 1 not in 2\n", item_1->name);
+	    psMetadataItem *newItem = psMetadataItemCopy(item_2); // Copied item
+	    psMetadataAddItem (missing, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+	    continue;
+	}
+
+	char *type_1 = NULL; PS_TYPE_NAME (type_1, item_1->type);
+	char *type_2 = NULL; PS_TYPE_NAME (type_2, item_2->type);
+
+	if (item_1->type != item_2->type) {
+	    // fprintf (stdout, "key %s type mismatch: %s vs %s\n", item_1->name, type_1, type_2);
+	    psMetadataItem *newItem = psMetadataItemCopy(item_2); // Copied item
+	    psMetadataAddItem (badtype, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+	    continue;
+	}
+
+	switch (item_1->type) {
+	  case PS_DATA_BOOL:
+	    if (item_1->data.B != item_2->data.B) {
+		// fprintf (stdout, "key %s value mismatch: %c vs %c\n", item_1->name, (item_1->data.B ? 'T' : 'F'), (item_2->data.B ? 'T' : 'F'));
+		psMetadataItem *newItem = psMetadataItemCopy(item_2); // Copied item
+		psMetadataAddItem (badbool, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+	    }
+	    break;
+
+	  case PS_DATA_S32:
+	    if (item_1->data.S32 != item_2->data.S32) {
+		// fprintf (stdout, "key %s value mismatch: %d vs %d\n", item_1->name, item_1->data.S32, item_2->data.S32);
+		int delta = (item_1->data.S32 - item_2->data.S32);
+		float fdelta = delta / (float) item_1->data.S32;
+		if (fabs(fdelta) > 1.0)    {
+		    psMetadataItem *newItem = psMetadataItemCopy(item_2); // Copied item
+		    psMetadataAddItem (off100, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+		    continue;
+		}
+		if (fabs(fdelta) > 0.1)    {
+		    psMetadataItem *newItem = psMetadataItemCopy(item_2); // Copied item
+		    psMetadataAddItem (off10, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+		    continue;
+		}
+		if (fabs(fdelta) > 0.01)    {
+		    psMetadataItem *newItem = psMetadataItemCopy(item_2); // Copied item
+		    psMetadataAddItem (off1, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+		    continue;
+		}
+		if (fabs(fdelta) <= 0.01)    {
+		    psMetadataItem *newItem = psMetadataItemCopy(item_2); // Copied item
+		    psMetadataAddItem (offlow, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+		    continue;
+		}
+	    }
+	    break;
+
+	  case PS_DATA_F64:
+	    if (item_1->data.F64 != item_2->data.F64) {
+		double delta = (item_1->data.F64 - item_2->data.F64);
+		double fdelta = delta / (float) item_1->data.F64;
+		if (fabs(fdelta) > 1.0)    {
+		    psMetadataItem *newItem = psMetadataItemCopy(item_2); // Copied item
+		    psMetadataAddItem (off100, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+		    continue;
+		}
+		if (fabs(fdelta) > 0.1)    {
+		    psMetadataItem *newItem = psMetadataItemCopy(item_2); // Copied item
+		    psMetadataAddItem (off10, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+		    continue;
+		}
+		if (fabs(fdelta) > 0.01)    {
+		    psMetadataItem *newItem = psMetadataItemCopy(item_2); // Copied item
+		    psMetadataAddItem (off1, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+		    continue;
+		}
+		if (fabs(fdelta) <= 0.01)    {
+		    psMetadataItem *newItem = psMetadataItemCopy(item_2); // Copied item
+		    psMetadataAddItem (offlow, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+		    continue;
+		}
+	    }
+	    break;
+		
+	  case PS_DATA_STRING:
+	    // allow mis-matched case?
+	    if (strcmp (item_1->data.str, item_2->data.str)) {
+		// fprintf (stdout, "key %s value mismatch: %s vs %s\n", item_1->name, item_1->data.str, item_2->data.str);
+		psMetadataItem *newItem = psMetadataItemCopy(item_2); // Copied item
+		psMetadataAddItem (offstr, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+	    }
+	    break;
+
+	  default:
+	    fprintf (stdout, "key %s unexpected type: %d\n", item_1->name, item_1->type);
+	    break;
+	}
+	// fprintf (stdout, "name: %s, type: %s\n", item->name, myType);
+    }
+
+    fprintf (stdout, "Keys missing from file 2:\n");
+    psMetadataPrint (0, missing, 1);
+
+    fprintf (stdout, "Keys with inconsistent types:\n");
+    psMetadataPrint (0, badtype, 1);
+
+    fprintf (stdout, "Keys with mismatched strings:\n");
+    psMetadataPrint (0, offstr, 1);
+
+    fprintf (stdout, "Keys with mismatched boolean values:\n");
+    psMetadataPrint (0, badbool, 1);
+
+    fprintf (stdout, "Keys with inconsistent by 100%% or more:\n");
+    psMetadataPrint (0, off100, 1);
+
+    fprintf (stdout, "Keys with inconsistent by 10 - 100%% or more:\n");
+    psMetadataPrint (0, off10, 1);
+
+    fprintf (stdout, "Keys with inconsistent by 1 - 10%% or more:\n");
+    psMetadataPrint (0, off1, 1);
+
+    fprintf (stdout, "Keys with inconsistent by < 1%%:\n");
+    psMetadataPrint (0, offlow, 1);
+
+    exit (0);
+}
+
+# if (0)
+case PS_DATA_BOOL:	fprintf (stdout, "name %s, type: %s, value: %c\n", item_1->name, myType, (item_1->data.B ? 'T' : 'F')
+	  case PS_DATA_S32:    fprintf (stdout, "name: %s, type: %s, value: %d\n", item->name, myType, item->data.S32); break;
+	  case PS_DATA_F64:    fprintf (stdout, "name: %s, type: %s, value: %f\n", item->name, myType, item->data.F64); break;
+	  case PS_DATA_STRING: fprintf (stdout, "name: %s, type: STRING, value: %s\n", item->name, item->data.str); break;
+
+    // Parse the configuration and arguments
+    pmConfig *config = dvoApplyCorrArguments (argc, argv);
+    if (config == NULL) {
+        psErrorStackPrint(stderr, "Unable to parse command-line arguments.");
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    // define recipe options
+    // define the active I/O files
+    dvoApplyCorrOptions *options = dvoApplyCorrParseCamera(config);
+    if (options == NULL) {
+        psErrorStackPrint(stderr, "Unable to parse camera.");
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    // Image Loop
+    if (!dvoApplyCorrLoop(config, options)) {
+        psErrorStackPrint(stderr, "Unable to loop over input");
+        exit(PS_EXIT_SYS_ERROR);
+    }
+
+    psLogMsg ("ppImage", 3, "Complete ppImage run: %f sec\n", psTimerMark(TIMERNAME));
+
+    // Cleaning up
+    dvoApplyCorrCleanup(config, options);
+
+    return PS_EXIT_SUCCESS;
+}
+
+# endif
Index: /branches/eam_branches/ipp-20211108/ippTests/ippCheck/tap.sh
===================================================================
--- /branches/eam_branches/ipp-20211108/ippTests/ippCheck/tap.sh	(revision 41983)
+++ /branches/eam_branches/ipp-20211108/ippTests/ippCheck/tap.sh	(revision 41983)
@@ -0,0 +1,64 @@
+
+# TAP_VERBOSE levels: 0 : only errors, 1 : major diagnostics, 2 : details
+if (not($?TAP_VERBOSE)) set TAP_VERBOSE = 1
+
+macro plan
+ if ($0 != 2)
+  echo "USAGE: plan (Ntest)"
+  break
+ end
+
+ $tap_Nplan = $1
+ $tap_Npass = 0
+ $tap_Nfail = 0
+ $tap_Ntest = 0
+end
+
+macro done
+ if (not($?tap_Nplan))
+  echo "ERROR: tap tests not planned"
+  break
+ end
+
+ echo "planned $tap_Nplan tests, ran $tap_Ntest"
+ echo "passed $tap_Npass tests, failed $tap_Nfail tests"
+
+ if (($tap_Nfail == 0) && ($tap_Ntest == $tap_Nplan) && ($tap_Npass == $tap_Ntest))
+   echo "PASS: all tests run and passed"
+   return 1
+ end
+
+ if ($tap_Nfail > 0)
+   echo "FAIL: $tap_Nfail tests failed"
+ end
+
+ if ($tap_Ntest != $tap_Nplan)
+   echo "FAIL: not all tests run"
+ end
+
+ if ($tap_Ntest != $tap_Npass + $tap_Nfail)
+   echo "FAIL: inconsistent count: $tap_Ntest tests != $tap_Npass pass + $tap_Nfail fail"
+ end
+end
+
+macro ok
+ if ($0 != 3)
+  echo "USAGE (condition) (label)"
+  break
+ end
+ 
+ if (not($?tap_Nplan))
+  echo "ERROR: tap tests not planned"
+  break
+ end
+ 
+ if ($1)
+  if ($TAP_VERBOSE > 0) echo "OK: $2"
+  $tap_Npass ++
+ else     
+  echo "not OK: $2"
+  $tap_Nfail ++
+ end
+ $tap_Ntest ++
+end
+
