Index: trunk/ippTools/src/dettool.c
===================================================================
--- trunk/ippTools/src/dettool.c	(revision 8354)
+++ trunk/ippTools/src/dettool.c	(revision 8364)
@@ -41,4 +41,5 @@
 
 static bool mapPositionToDetRun(psArray *mds);
+static detNormalizedStatImfileRow *detStackedToDetNormalizedStatImfile(pxConfig *config, detStackedImfileRow *stackedImfile);
 static psArray *validDetInputClassIds(pxConfig *config, const char *det_id);
 static psArray *searchInputImfiles(pxConfig *config, const char *det_id);
@@ -1720,5 +1721,5 @@
         "     AND detInputExp.iteration = detStackedImfile.iteration"
         " GROUP BY"
-        "     rawDetrendExp.imfiles"
+        "     rawDetrendExp.exp_id"
         " HAVING MAX(rawDetrendExp.imfiles) = COUNT(detStackedImfile.class_id)"
         ); 
@@ -1771,9 +1772,115 @@
 static bool addnormstatMode(pxConfig *config)
 {
-    return true;
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    // select * from detStackedImfile
+    // by det_id, iteration, class_id
+    // where det_id, iteration, class_id is not in detNormalizedStatImfile
+    psString query = psStringCopy(
+        "SELECT DISTINCT"
+        "   detStackedImfile.*"
+        " FROM detStackedImfile"
+        " LEFT JOIN detNormalizedStatImfile"
+        "   USING(det_id, iteration, class_id)"
+        " WHERE"
+        "  detNormalizedStatImfile.det_id IS NULL" 
+        "  AND detNormalizedStatImfile.iteration IS NULL" 
+        "  AND detNormalizedStatImfile.class_id IS NULL" 
+        );
+
+#if 0
+    if (config->where) {
+        psString whereClaus = psDBGenerateWhereConditionSQL(config->where);
+        psStringAppend(&query, " AND %s", whereClaus);
+        psFree(whereClaus);
+    }
+#endif
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    } 
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        // XXX check psError here
+        psError(PS_ERR_UNKNOWN, false, "no pending rawDetrendExp rows found");
+        return false;
+    }
+
+    // start a transaction so it's all rows or nothing
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *row = output->data[i];
+        // convert metadata into a detStackedImfile object
+        detStackedImfileRow *stackedImfile = detStackedImfileObjectFromMetadata(row);
+        // convert detStackedImfile object into a detNormalizedStat object
+        detNormalizedStatImfileRow *stat = detStackedToDetNormalizedStatImfile(config, stackedImfile);
+        psFree(stackedImfile);
+        if (!stat) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to convert detStackedImfile to detNormalizedStatImfile");
+            psFree(output);
+            return false;
+        }
+        // insert detNormlized Stat object into the database
+        if (!detNormalizedStatImfileInsertObject(config->dbh, stat)) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(stat);
+            psFree(output);
+        }
+        psFree(stat);
+    }
+
+    psFree(output);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static detNormalizedStatImfileRow *detStackedToDetNormalizedStatImfile(pxConfig *config, detStackedImfileRow *stackedImfile) 
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(stackedImfile, NULL);
+
+    bool status = false;
+    psF32 norm = psMetadataLookupF32(&status, config->args, "-norm");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -norm");
+        return false;
+    }
+    if (isnan(norm)) {
+        psError(PS_ERR_UNKNOWN, true, "-norm is required");
+        return false;
+    }
+
+    return detNormalizedStatImfileRowAlloc(
+        stackedImfile->det_id,
+        stackedImfile->iteration,
+        stackedImfile->class_id,
+        norm
+    );
 }
 
 static bool normstatMode(pxConfig *config)
 {
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
     return true;
 }
