Linux x86_64 Release Build / build (push) Failing after 1m5s
Windows x86_64 Release Build / build (push) Has been cancelled
macOS Release Build / build (arm64, macOS_arm64_bin, arm64-apple-darwin, macos-latest) (push) Has been cancelled
macOS Release Build / build (x86_64, osx10.6_x86_64_bin, x86_64-apple-darwin, macos-13) (push) Has been cancelled
920 lines
24 KiB
C
920 lines
24 KiB
C
/*
|
|
* Argyll Color Management System
|
|
* Average one or more .ti3 (or other CGATS like) file values together.
|
|
* If just one file is supplied, all patches within it with
|
|
* the same device value are averaged together.
|
|
*
|
|
* Author: Graeme W. Gill
|
|
* Date: 18/1/2011
|
|
*
|
|
* Copyright 2005, 2010, 2011, 2025 Graeme W. Gill
|
|
* All rights reserved.
|
|
*
|
|
* This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
|
|
* see the License.txt file for licencing details.
|
|
*
|
|
* (based on splitti3.c)
|
|
*/
|
|
|
|
/*
|
|
* TTBD:
|
|
|
|
Should probably re-index SAMPLE_ID field
|
|
|
|
Geometric mean is limited to just 3 channels.
|
|
*/
|
|
|
|
|
|
#undef DEBUG
|
|
|
|
#define verbo stdout
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
#include "copyright.h"
|
|
#include "aconfig.h"
|
|
#include "numlib.h"
|
|
#include "sort.h"
|
|
#include "cgats.h"
|
|
#include "conv.h"
|
|
#include "xicc.h"
|
|
#include "insttypes.h"
|
|
|
|
static double average(double *vals, int nvals);
|
|
static double median(double *vals, int nvals);
|
|
static void geommed(double res[3], double vals[][3], int nvals);
|
|
|
|
void usage(char *diag, ...) {
|
|
int i;
|
|
fprintf(stderr,"Average or merge values in .ti3 like files, Version %s\n",ARGYLL_VERSION_STR);
|
|
if (diag != NULL) {
|
|
va_list args;
|
|
fprintf(stderr," Diagnostic: ");
|
|
va_start(args, diag);
|
|
vfprintf(stderr, diag, args);
|
|
va_end(args);
|
|
fprintf(stderr,"\n");
|
|
}
|
|
fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
|
|
fprintf(stderr,"usage: average [-options] input1.ti3 input2.ti3 ... output.ti3\n");
|
|
fprintf(stderr," -v Verbose\n");
|
|
fprintf(stderr," -e Median rather than average\n");
|
|
fprintf(stderr," -g Geometric Median of PCS in encoded space\n");
|
|
fprintf(stderr," -L Geometric Median of PCS in L*a*b* space\n");
|
|
fprintf(stderr," -X Geometric Median of PCS in XYZ space\n");
|
|
fprintf(stderr," -m Merge rather than average\n");
|
|
fprintf(stderr," input1.ti3 First input file\n");
|
|
fprintf(stderr," input2.ti3 Second input file\n");
|
|
fprintf(stderr," ... etc.\n");
|
|
fprintf(stderr," output.ti3 Resulting averaged or merged output file\n");
|
|
exit(1);
|
|
}
|
|
|
|
/* Information about each file */
|
|
struct _inpinfo {
|
|
char name[MAXNAMEL+1];
|
|
cgats *c;
|
|
}; typedef struct _inpinfo inpinfo;
|
|
|
|
#include "ui.h"
|
|
|
|
int main(int argc, char *argv[]) {
|
|
int fa,nfa; /* current argument we're looking at */
|
|
int verb = 0;
|
|
int domedian = 0; /* Median rather than average */
|
|
int dogeom = 0; /* Do geometric median of PCS or Dev, 2 = Lab, 3 = PCS */
|
|
int domerge = 0; /* Merge rather than average */
|
|
|
|
int ninps = 0; /* Number of input files */
|
|
inpinfo *inps; /* Input file info. inp[ninp] == output file */
|
|
cgats *ocg; /* Copy of output cgats * */
|
|
|
|
cgats_set_elem *setel; /* Array of set value elements */
|
|
int *flags; /* Point to destination of set */
|
|
|
|
int inp = -1; /* 0 for output, 1 for input device files */
|
|
|
|
int nchan = 0; /* Number of device channels */
|
|
int chix[ICX_MXINKS]; /* Device chanel indexes */
|
|
|
|
int haspcs[2] = { 0 }; /* Has [Lab, XYZ] */
|
|
int pcsix[2][3]; /* Lab, XYZ chanel indexes */
|
|
|
|
int dti;
|
|
int i, j, n;
|
|
|
|
|
|
error_program = "average";
|
|
|
|
if (argc < 2)
|
|
usage("Too few arguments (%d, minimum is 2)",argc-1);
|
|
|
|
/* Process the arguments */
|
|
for (fa = 1;fa < argc;fa++) {
|
|
nfa = fa; /* skip to nfa if next argument is used */
|
|
if (argv[fa][0] == '-') { /* Look for any flags */
|
|
char *na = NULL; /* next argument after flag, null if none */
|
|
|
|
if (argv[fa][2] != '\000')
|
|
na = &argv[fa][2]; /* next is directly after flag */
|
|
else {
|
|
if ((fa+1) < argc) {
|
|
if (argv[fa+1][0] != '-') {
|
|
nfa = fa + 1;
|
|
na = argv[nfa]; /* next is seperate non-flag argument */
|
|
}
|
|
}
|
|
}
|
|
|
|
if (argv[fa][1] == '?')
|
|
usage("Usage requested");
|
|
|
|
/* Median */
|
|
else if (argv[fa][1] == 'e') {
|
|
domedian = 1;
|
|
}
|
|
|
|
/* Geometric Median of PCS or Dev */
|
|
else if (argv[fa][1] == 'g') {
|
|
dogeom = 1;
|
|
}
|
|
|
|
/* Geometric Median of PCS in L*a*b* */
|
|
else if (argv[fa][1] == 'L') {
|
|
dogeom = 2;
|
|
}
|
|
|
|
/* Geometric Median of PCS in XYZ */
|
|
else if (argv[fa][1] == 'X') {
|
|
dogeom = 3;
|
|
}
|
|
|
|
/* Merge */
|
|
else if (argv[fa][1] == 'm') {
|
|
domerge = 1;
|
|
}
|
|
|
|
/* Verbosity */
|
|
else if (argv[fa][1] == 'v') {
|
|
verb = 1;
|
|
}
|
|
|
|
else {
|
|
usage("Unknown flag '%c'",argv[fa][1]);
|
|
}
|
|
|
|
} else if (argv[fa][0] != '\000') {
|
|
/* Get the next filename */
|
|
|
|
if (ninps == 0)
|
|
inps = (inpinfo *)malloc(sizeof(inpinfo));
|
|
else
|
|
inps = (inpinfo *)realloc(inps, (ninps+1) * sizeof(inpinfo));
|
|
if (inps == NULL)
|
|
error("Malloc failed in allocating space for file info.");
|
|
|
|
memset((void *)&inps[ninps], 0, sizeof(inpinfo));
|
|
strncpy(inps[ninps].name,argv[fa],MAXNAMEL);
|
|
inps[ninps].name[MAXNAMEL] = '\000';
|
|
|
|
ninps++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ninps < 2)
|
|
error("Must be at least one input and one output file specified");
|
|
|
|
ninps--; /* Number of inputs */
|
|
|
|
|
|
/* Open and read each input file, and create output file */
|
|
for (n = 0; n <= ninps; n++) {
|
|
|
|
if ((inps[n].c = new_cgats()) == NULL)
|
|
error("Failed to create cgats object for file '%s'",inps[n].name);
|
|
|
|
if (n < ninps) { /* If input file, read it */
|
|
inps[n].c->add_other(inps[n].c, ""); /* Allow any signature file */
|
|
|
|
if (inps[n].c->read_name(inps[n].c, inps[n].name))
|
|
error("CGATS file '%s' read error : %s",inps[n].name,inps[n].c->e.m);
|
|
|
|
if (inps[n].c->ntables < 1)
|
|
error ("Input file '%s' doesn't contain at least one table",inps[n].name);
|
|
}
|
|
}
|
|
ocg = inps[ninps].c; /* Alias for output file */
|
|
|
|
/* Check the device/profile type tag to see if input type */
|
|
for (n = 0; n < inps[0].c->ntables; n++) {
|
|
if ((dti = inps[n].c->find_kword(inps[n].c, 0, "DEVICE_CLASS")) < 0) {
|
|
if (n == 0)
|
|
inp = 0; /* Default to output */
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(inps[n].c->t[0].kdata[dti],"INPUT") == 0
|
|
|| strcmp(inps[n].c->t[0].kdata[dti],"EMISINPUT") == 0) {
|
|
if (n == 0)
|
|
inp = 1;
|
|
else if (inp != 1)
|
|
error ("Input file '%s' has conflicting DEVICE_CLASS",inps[n].name);
|
|
} else {
|
|
if (n == 0)
|
|
inp = 0;
|
|
else if (inp != 0)
|
|
error ("Input file '%s' has conflicting DEVICE_CLASS",inps[n].name);
|
|
}
|
|
}
|
|
|
|
/* Duplicate everything from the first input file into the output file. */
|
|
for (n = 0; n < inps[0].c->ntables; n++) {
|
|
|
|
if (inps[0].c->t[n].tt == cgats_X) {
|
|
ocg->add_other(ocg, inps[0].c->cgats_type);
|
|
ocg->add_table(ocg, tt_other, 0);
|
|
} else if (inps[0].c->t[n].tt == tt_other) {
|
|
int oi;
|
|
oi = ocg->add_other(ocg, inps[0].c->others[inps[0].c->t[n].oi]);
|
|
ocg->add_table(ocg, tt_other, oi);
|
|
} else {
|
|
ocg->add_table(ocg, inps[0].c->t[n].tt, 0);
|
|
}
|
|
|
|
/* Duplicate all the keywords */
|
|
for (i = 0; i < inps[0].c->t[n].nkwords; i++) {
|
|
//printf("~1 table %d, adding keyword '%s'\n",n,inps[0].c->t[n].ksym[i]);
|
|
ocg->add_kword(ocg, n, inps[0].c->t[n].ksym[i], inps[0].c->t[n].kdata[i], NULL);
|
|
}
|
|
|
|
/* Duplicate all of the fields */
|
|
for (i = 0; i < inps[0].c->t[n].nfields; i++) {
|
|
ocg->add_field(ocg, n, inps[0].c->t[n].fsym[i], inps[0].c->t[n].ftype[i]);
|
|
}
|
|
|
|
/* If more than one file, must be merging or averaging between files */
|
|
if (ninps > 1) {
|
|
/* Duplicate all of the data or first file to output file */
|
|
if ((setel = (cgats_set_elem *)malloc(
|
|
sizeof(cgats_set_elem) * inps[0].c->t[n].nfields)) == NULL)
|
|
error("Malloc failed!");
|
|
|
|
for (i = 0; i < inps[0].c->t[n].nsets; i++) {
|
|
inps[0].c->get_setarr(inps[0].c, n, i, setel);
|
|
ocg->add_setarr(ocg, n, setel);
|
|
}
|
|
free(setel);
|
|
}
|
|
}
|
|
|
|
/* Figure out the indexes of the device channels */
|
|
if (inps[0].c->find_kword(inps[0].c, 0, "COLOR_REP") < 0) {
|
|
warning("Input file '%s' doesn't contain keyword COLOR_REP", inps[0].name);
|
|
} else {
|
|
int ti;
|
|
char *buf;
|
|
char *outc;
|
|
int nmask;
|
|
char *bident;
|
|
|
|
if ((ti = inps[0].c->find_kword(inps[0].c, 0, "COLOR_REP")) < 0)
|
|
error("Input file '%s' doesn't contain keyword COLOR_REP", inps[0].name);
|
|
|
|
if ((buf = strdup(inps[0].c->t[0].kdata[ti])) == NULL)
|
|
error("Malloc failed");
|
|
|
|
/* Split COLOR_REP into device and PCS space */
|
|
if ((outc = strchr(buf, '_')) == NULL)
|
|
error("COLOR_REP '%s' invalid", inps[0].c->t[0].kdata[ti]);
|
|
*outc++ = '\000';
|
|
|
|
if ((nmask = icx_char2inkmask(buf)) == 0) {
|
|
|
|
/* Hmm. This might be an input reference. */
|
|
if (strcmp(buf, "XYZ") == 0 || strcmp(buf, "LAB") == 0) {
|
|
if ((nmask = icx_char2inkmask(outc)) == 0) {
|
|
error ("File '%s' keyword COLOR_REP has unknown device value '%s'",inps[0].name,outc);
|
|
}
|
|
} else {
|
|
error ("File '%s' keyword COLOR_REP has unknown device value '%s'",inps[0].name,buf);
|
|
}
|
|
}
|
|
|
|
nchan = icx_noofinks(nmask);
|
|
bident = icx_inkmask2char(nmask, 0);
|
|
|
|
/* Find device fields */
|
|
for (j = 0; j < nchan; j++) {
|
|
int ii, imask;
|
|
char fname[100];
|
|
|
|
imask = icx_index2ink(nmask, j);
|
|
sprintf(fname,"%s_%s",nmask == ICX_W || nmask == ICX_K ? "GRAY" : bident,
|
|
icx_ink2char(imask));
|
|
|
|
if ((ii = inps[0].c->find_field(inps[0].c, 0, fname)) < 0)
|
|
error ("Input file doesn't contain field %s",fname);
|
|
if (inps[0].c->t[0].ftype[ii] != r_t)
|
|
error ("Field %s is wrong type",fname);
|
|
chix[j] = ii;
|
|
}
|
|
}
|
|
|
|
/* Figure out the indexes of the PCS channels, if any */
|
|
{
|
|
int npcs;
|
|
char *fname[2][3] = { { "LAB_L", "LAB_A", "LAB_B" },
|
|
{ "XYZ_X", "XYZ_Y", "XYZ_Z" } };
|
|
|
|
/* For Lab and XYZ */
|
|
for (j = 0; j < 2; j++) {
|
|
for (npcs = 0; npcs < 3; npcs++) {
|
|
int ii;
|
|
|
|
if ((ii = inps[0].c->find_field(inps[0].c, 0, fname[j][npcs])) < 0)
|
|
break; /* Try next or give up */
|
|
|
|
if (inps[0].c->t[0].ftype[ii] != r_t)
|
|
error ("Field %s is wrong type",fname[j][npcs]);
|
|
pcsix[j][npcs] = ii;
|
|
}
|
|
if (npcs == 3)
|
|
haspcs[j] = 1;
|
|
}
|
|
}
|
|
|
|
if (!haspcs[0] && !haspcs[1]) {
|
|
if (inp)
|
|
error("Need a PCS to average for input device chart");
|
|
else
|
|
warning("No PCS fields found - hope that's OK!");
|
|
}
|
|
|
|
if (inp && dogeom != 0) {
|
|
|
|
if (nchan != 3)
|
|
error ("Can't do geometric mean for %d device channels",nchan);
|
|
|
|
if (dogeom > 1) {
|
|
warning("Geometric mean of XYZ or Lab ignored for input type since device values are being averaged");
|
|
dogeom = 1;
|
|
}
|
|
}
|
|
|
|
if (!domerge && verb) {
|
|
printf("Averaging the following fields:");
|
|
for (j = 0; j < inps[0].c->t[0].nfields; j++) {
|
|
int jj;
|
|
|
|
/* Only real types */
|
|
if (inps[0].c->t[0].ftype[j] != r_t) {
|
|
continue;
|
|
}
|
|
|
|
if (inp) {
|
|
/* Not PCS channels */
|
|
int k;
|
|
for (k = 0; k < 2; k++) {
|
|
if (haspcs[k] == 0)
|
|
continue;
|
|
for (jj = 0; jj < 3; jj++) {
|
|
if (pcsix[k][jj] == j)
|
|
break;
|
|
}
|
|
if (jj < 3)
|
|
break;
|
|
}
|
|
if (k < 2)
|
|
continue;
|
|
} else {
|
|
/* Not device channels */
|
|
for (jj = 0; jj < nchan; jj++) {
|
|
if (chix[jj] == j)
|
|
break;
|
|
}
|
|
if (jj < nchan)
|
|
continue;
|
|
}
|
|
|
|
printf(" %s",inps[0].c->t[0].fsym[j]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
/* Get ready to add more values to output, */
|
|
/* for merging or averaging within one file. */
|
|
if ((setel = (cgats_set_elem *)malloc(
|
|
sizeof(cgats_set_elem) * inps[0].c->t[0].nfields)) == NULL)
|
|
error("Malloc failed!");
|
|
|
|
|
|
/* If averaging values within the one file */
|
|
if (ninps == 1) {
|
|
int *valdone;
|
|
int npat;
|
|
double *vlist;
|
|
double (*v3list)[3] = NULL;
|
|
int k, e;
|
|
n = 0; /* Output set index */
|
|
|
|
if ((valdone = (int *)calloc(inps[0].c->t[0].nsets, sizeof(int))) == NULL)
|
|
error("Malloc failed!");
|
|
|
|
if ((vlist = (double *)calloc(inps[0].c->t[0].nsets, sizeof(double))) == NULL)
|
|
error("Malloc failed!");
|
|
|
|
if (dogeom && (haspcs[0] || haspcs[1])) {
|
|
if ((v3list = (double (*)[3])calloc(inps[0].c->t[0].nsets, 3 * sizeof(double))) == NULL)
|
|
error("Malloc failed!");
|
|
}
|
|
|
|
/* For each patch */
|
|
for (i = 0; i < inps[0].c->t[0].nsets; i++) {
|
|
|
|
if (valdone[i])
|
|
continue;
|
|
|
|
inps[0].c->get_setarr(inps[0].c, 0, i, setel);
|
|
ocg->add_setarr(ocg, 0, setel);
|
|
|
|
/* For each non-device real field values */
|
|
for (j = 0; j < inps[0].c->t[0].nfields; j++) {
|
|
int jj;
|
|
|
|
/* Only real types */
|
|
if (inps[0].c->t[0].ftype[j] != r_t)
|
|
continue;
|
|
|
|
if (inp) {
|
|
/* Not PCS channels */
|
|
int k;
|
|
for (k = 0; k < 2; k++) {
|
|
if (haspcs[k] == 0)
|
|
continue;
|
|
for (jj = 0; jj < 3; jj++) {
|
|
if (pcsix[k][jj] == j)
|
|
break;
|
|
}
|
|
if (jj < 3)
|
|
break;
|
|
}
|
|
if (k < 2)
|
|
continue;
|
|
|
|
/* Locate any patches (including starting patch) with matching PCS values */
|
|
npat = 0;
|
|
for (k = i; k < inps[0].c->t[0].nsets; k++) {
|
|
int mm;
|
|
|
|
/* Check if the PCS values match */
|
|
for (mm = 0; mm < 2; mm++) {
|
|
if (haspcs[mm] == 0)
|
|
continue;
|
|
for (e = 0; e < nchan; e++) {
|
|
double diff;
|
|
|
|
diff = *((double *)inps[0].c->t[0].fdata[i][pcsix[mm][e]])
|
|
- *((double *)inps[0].c->t[0].fdata[k][pcsix[mm][e]]);
|
|
|
|
if (fabs(diff) > 0.001) {
|
|
break;
|
|
}
|
|
}
|
|
if (e < nchan)
|
|
break;
|
|
}
|
|
if (mm < 2)
|
|
continue; /* They don't match */
|
|
|
|
vlist[npat++] = *((double *)inps[0].c->t[0].fdata[k][j]);
|
|
valdone[k] = 1;
|
|
}
|
|
} else {
|
|
/* Not device channels */
|
|
for (jj = 0; jj < nchan; jj++) {
|
|
if (chix[jj] == j)
|
|
break;
|
|
}
|
|
if (jj < nchan)
|
|
continue;
|
|
|
|
/* Locate any patches (including starting patch) with matching device values */
|
|
npat = 0;
|
|
for (k = i; k < inps[0].c->t[0].nsets; k++) {
|
|
|
|
/* Check if the device values match */
|
|
for (e = 0; e < nchan; e++) {
|
|
double diff;
|
|
|
|
diff = *((double *)inps[0].c->t[0].fdata[i][chix[e]])
|
|
- *((double *)inps[0].c->t[0].fdata[k][chix[e]]);
|
|
|
|
if (fabs(diff) > 0.001) {
|
|
break;
|
|
}
|
|
}
|
|
if (e < nchan) {
|
|
continue;
|
|
}
|
|
|
|
vlist[npat++] = *((double *)inps[0].c->t[0].fdata[k][j]);
|
|
valdone[k] = 1;
|
|
}
|
|
}
|
|
if (domedian)
|
|
*((double *)ocg->t[0].fdata[n][j]) = median(vlist, npat);
|
|
else
|
|
*((double *)ocg->t[0].fdata[n][j]) = average(vlist, npat);
|
|
}
|
|
|
|
/* Override per-component average/median if Device Geometric Median */
|
|
if (inp && dogeom && (haspcs[0] || haspcs[1])) {
|
|
double res[3];
|
|
|
|
/* Locate any patches (including starting patch) with matching PCS values */
|
|
npat = 0;
|
|
for (k = i; k < inps[0].c->t[0].nsets; k++) {
|
|
int mm;
|
|
|
|
/* Check if the PCS values match */
|
|
for (mm = 0; mm < 2; mm++) {
|
|
if (haspcs[mm] == 0)
|
|
continue;
|
|
for (e = 0; e < nchan; e++) {
|
|
double diff;
|
|
|
|
diff = *((double *)inps[0].c->t[0].fdata[i][pcsix[mm][e]])
|
|
- *((double *)inps[0].c->t[0].fdata[k][pcsix[mm][e]]);
|
|
|
|
if (fabs(diff) > 0.001) {
|
|
break;
|
|
}
|
|
}
|
|
if (e < nchan)
|
|
break;
|
|
}
|
|
if (mm < 2)
|
|
continue; /* They don't match */
|
|
|
|
/* (nchan == 3 when inp and dogeom) */
|
|
v3list[npat][0] = *((double *)inps[0].c->t[0].fdata[k][chix[0]]);
|
|
v3list[npat][1] = *((double *)inps[0].c->t[0].fdata[k][chix[1]]);
|
|
v3list[npat][2] = *((double *)inps[0].c->t[0].fdata[k][chix[2]]);
|
|
|
|
npat++;
|
|
}
|
|
geommed(res, v3list, npat);
|
|
|
|
*((double *)ocg->t[0].fdata[n][chix[0]]) = res[0];
|
|
*((double *)ocg->t[0].fdata[n][chix[1]]) = res[1];
|
|
*((double *)ocg->t[0].fdata[n][chix[2]]) = res[2];
|
|
|
|
/* Override per-component average/median if PCS Geometric Median */
|
|
} else if (!inp && dogeom && (haspcs[0] || haspcs[1])) {
|
|
double res[3];
|
|
|
|
/* For Lab and XYZ */
|
|
for (j = 0; j < 2; j++) {
|
|
|
|
if (haspcs[j] == 0)
|
|
continue;
|
|
|
|
/* Locate any patches (including starting patch) with matching device values */
|
|
npat = 0;
|
|
for (k = i; k < inps[0].c->t[0].nsets; k++) {
|
|
|
|
/* Check if the device values match */
|
|
for (e = 0; e < nchan; e++) {
|
|
double diff;
|
|
|
|
diff = *((double *)inps[0].c->t[0].fdata[i][chix[e]])
|
|
- *((double *)inps[0].c->t[0].fdata[k][chix[e]]);
|
|
|
|
if (fabs(diff) > 0.001) {
|
|
break;
|
|
}
|
|
}
|
|
if (e < nchan) {
|
|
continue;
|
|
}
|
|
|
|
v3list[npat][0] = *((double *)inps[0].c->t[0].fdata[k][pcsix[j][0]]);
|
|
v3list[npat][1] = *((double *)inps[0].c->t[0].fdata[k][pcsix[j][1]]);
|
|
v3list[npat][2] = *((double *)inps[0].c->t[0].fdata[k][pcsix[j][2]]);
|
|
|
|
if (j == 0 && dogeom == 3) /* Lab and want XYZ */
|
|
icmLab2XYZ(&icmD50_100, v3list[npat], v3list[npat]);
|
|
else if (j == 1 && dogeom == 2) /* XYZ and want Lab */
|
|
icmXYZ2Lab(&icmD50_100, v3list[npat], v3list[npat]);
|
|
|
|
npat++;
|
|
}
|
|
geommed(res, v3list, npat);
|
|
|
|
if (j == 0 && dogeom == 3)
|
|
icmXYZ2Lab(&icmD50_100, res, res);
|
|
else if (j == 1 && dogeom == 2)
|
|
icmLab2XYZ(&icmD50_100, res, res);
|
|
|
|
*((double *)ocg->t[0].fdata[n][pcsix[j][0]]) = res[0];
|
|
*((double *)ocg->t[0].fdata[n][pcsix[j][1]]) = res[1];
|
|
*((double *)ocg->t[0].fdata[n][pcsix[j][2]]) = res[2];
|
|
}
|
|
}
|
|
n++; /* One more output set */
|
|
}
|
|
|
|
if (v3list != NULL)
|
|
free(v3list);
|
|
free(vlist);
|
|
free(valdone);
|
|
|
|
/* Averaging patches between identical files, */
|
|
/* or concatenating (merging) several files */
|
|
} else {
|
|
|
|
/* Check/process all the other input files */
|
|
for (n = 1; n < ninps; n++) {
|
|
|
|
/* Check all the fields match the first file */
|
|
if (inps[0].c->t[0].nfields != inps[n].c->t[0].nfields)
|
|
error ("File '%s' has %d fields, file '%s has %d",
|
|
inps[n].name, inps[n].c->t[0].nfields, inps[0].name, inps[0].c->t[0].nfields);
|
|
for (j = 0; j < inps[0].c->t[0].nfields; j++) {
|
|
if (inps[0].c->t[0].ftype[j] != inps[n].c->t[0].ftype[j])
|
|
error ("File '%s' field no. %d named '%s' doesn't match file '%s' field '%s'",
|
|
inps[n].name, j, inps[n].c->t[0].fsym[j], inps[0].name, inps[0].c->t[0].fsym[j]);
|
|
}
|
|
|
|
/* If merging, append all the values */
|
|
if (domerge) {
|
|
for (i = 0; i < inps[n].c->t[0].nsets; i++) {
|
|
inps[n].c->get_setarr(inps[n].c, 0, i, setel);
|
|
ocg->add_setarr(ocg, 0, setel);
|
|
}
|
|
|
|
} else { /* Averaging */
|
|
|
|
/* Check the number of patches matches the first file */
|
|
if (inps[0].c->t[0].nsets != inps[n].c->t[0].nsets)
|
|
error ("File '%s' has %d sets, file '%s has %d",
|
|
inps[n].name, inps[n].c->t[0].nsets, inps[0].name, inps[0].c->t[0].nsets);
|
|
|
|
/* For all the patches: */
|
|
for (i = 0; i < inps[n].c->t[0].nsets; i++) {
|
|
if (inp) {
|
|
int k;
|
|
|
|
/* Check that the PCS values match the first file */
|
|
for (k = 0; k < 2; k++) {
|
|
if (haspcs[k] == 0)
|
|
continue;
|
|
for (j = 0; j < 3; j++) {
|
|
double diff;
|
|
diff = *((double *)inps[0].c->t[0].fdata[i][pcsix[k][j]])
|
|
- *((double *)inps[n].c->t[0].fdata[i][pcsix[k][j]]);
|
|
|
|
if (fabs(diff) > 0.001)
|
|
error ("File '%s' set %d has field '%s' value that differs from '%s'",
|
|
inps[n].name, i+1, inps[n].c->t[0].fsym[pcsix[k][j]], inps[0].name);
|
|
}
|
|
}
|
|
} else {
|
|
/* Check that the device values match the first file */
|
|
for (j = 0; j < nchan; j++) {
|
|
double diff;
|
|
diff = *((double *)inps[0].c->t[0].fdata[i][chix[j]])
|
|
- *((double *)inps[n].c->t[0].fdata[i][chix[j]]);
|
|
|
|
if (fabs(diff) > 0.001)
|
|
error ("File '%s' set %d has field '%s' value that differs from '%s'",
|
|
inps[n].name, i+1, inps[n].c->t[0].fsym[chix[j]], inps[0].name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If averaging */
|
|
if (!domerge) {
|
|
int npat;
|
|
double *vlist;
|
|
double (*v3list)[3] = NULL;
|
|
|
|
if ((vlist = (double *)calloc(ninps, sizeof(double))) == NULL)
|
|
error("Malloc failed!");
|
|
|
|
if (dogeom && (haspcs[0] || haspcs[1])) {
|
|
if ((v3list = (double (*)[3])calloc(inps[0].c->t[0].nsets, 3 * sizeof(double))) == NULL)
|
|
error("Malloc failed!");
|
|
}
|
|
|
|
/* For all the non-device real field values */
|
|
for (j = 0; j < inps[0].c->t[0].nfields; j++) {
|
|
int jj;
|
|
|
|
/* Only real types */
|
|
if (inps[0].c->t[0].ftype[j] != r_t)
|
|
continue;
|
|
|
|
if (inp) {
|
|
/* Not PCS channels */
|
|
int k;
|
|
for (k = 0; k < 2; k++) {
|
|
if (haspcs[k] == 0)
|
|
continue;
|
|
for (jj = 0; jj < 3; jj++) {
|
|
if (pcsix[k][jj] == j)
|
|
break;
|
|
}
|
|
if (jj < 3)
|
|
break;
|
|
}
|
|
if (k < 2)
|
|
continue;
|
|
} else {
|
|
/* Not device channels */
|
|
for (jj = 0; jj < nchan; jj++) {
|
|
if (chix[jj] == j)
|
|
break;
|
|
}
|
|
if (jj < nchan)
|
|
continue;
|
|
}
|
|
|
|
/* For each patch */
|
|
for (i = 0; i < inps[0].c->t[0].nsets; i++) {
|
|
|
|
/* For all input files */
|
|
npat = 0;
|
|
for (n = 0; n < ninps; n++) {
|
|
vlist[npat++] = *((double *)inps[n].c->t[0].fdata[i][j]);
|
|
}
|
|
|
|
if (domedian)
|
|
*((double *)ocg->t[0].fdata[i][j]) = median(vlist, npat);
|
|
else
|
|
*((double *)ocg->t[0].fdata[i][j]) = average(vlist, npat);
|
|
}
|
|
}
|
|
|
|
/* Override per-component average/median if PCS Geometric Median */
|
|
if (inp && dogeom && (haspcs[0] || haspcs[1])) {
|
|
double res[3];
|
|
|
|
/* For each patch */
|
|
for (i = 0; i < inps[0].c->t[0].nsets; i++) {
|
|
|
|
/* For all input files */
|
|
npat = 0;
|
|
for (n = 0; n < nchan; n++) {
|
|
v3list[npat][0] = *((double *)inps[n].c->t[0].fdata[i][chix[0]]);
|
|
v3list[npat][1] = *((double *)inps[n].c->t[0].fdata[i][chix[1]]);
|
|
v3list[npat][2] = *((double *)inps[n].c->t[0].fdata[i][chix[2]]);
|
|
npat++;
|
|
}
|
|
geommed(res, v3list, npat);
|
|
|
|
*((double *)ocg->t[0].fdata[i][chix[0]]) = res[0];
|
|
*((double *)ocg->t[0].fdata[i][chix[1]]) = res[1];
|
|
*((double *)ocg->t[0].fdata[i][chix[2]]) = res[2];
|
|
}
|
|
|
|
/* Override per-component average/median if PCS Geometric Median */
|
|
} else if (!inp && dogeom && (haspcs[0] || haspcs[1])) {
|
|
double res[3];
|
|
|
|
/* For each patch */
|
|
for (i = 0; i < inps[0].c->t[0].nsets; i++) {
|
|
|
|
/* For Lab and XYZ */
|
|
for (j = 0; j < 2; j++) {
|
|
|
|
if (haspcs[j] == 0)
|
|
continue;
|
|
|
|
/* For all input files */
|
|
npat = 0;
|
|
for (n = 0; n < ninps; n++) {
|
|
v3list[npat][0] = *((double *)inps[n].c->t[0].fdata[i][pcsix[j][0]]);
|
|
v3list[npat][1] = *((double *)inps[n].c->t[0].fdata[i][pcsix[j][1]]);
|
|
v3list[npat][2] = *((double *)inps[n].c->t[0].fdata[i][pcsix[j][2]]);
|
|
|
|
if (j == 0 && dogeom == 3) /* Lab and want XYZ */
|
|
icmLab2XYZ(&icmD50_100, v3list[npat], v3list[npat]);
|
|
else if (j == 1 && dogeom == 2) /* XYZ and want Lab */
|
|
icmXYZ2Lab(&icmD50_100, v3list[npat], v3list[npat]);
|
|
|
|
npat++;
|
|
}
|
|
geommed(res, v3list, npat);
|
|
|
|
if (j == 0 && dogeom == 3)
|
|
icmXYZ2Lab(&icmD50_100, res, res);
|
|
else if (j == 1 && dogeom == 2)
|
|
icmLab2XYZ(&icmD50_100, res, res);
|
|
|
|
*((double *)ocg->t[0].fdata[i][pcsix[j][0]]) = res[0];
|
|
*((double *)ocg->t[0].fdata[i][pcsix[j][1]]) = res[1];
|
|
*((double *)ocg->t[0].fdata[i][pcsix[j][2]]) = res[2];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (v3list != NULL)
|
|
free(v3list);
|
|
free(vlist);
|
|
}
|
|
}
|
|
|
|
/* Write out the output and free the cgats * */
|
|
for (n = 0; n <= ninps; n++) {
|
|
|
|
if (n >= ninps) { /* If output file, write it */
|
|
if (inps[n].c->write_name(inps[n].c, inps[ninps].name))
|
|
error("CGATS file '%s' write error : %s",inps[n].name,inps[n].c->e.m);
|
|
}
|
|
inps[n].c->del(inps[n].c);
|
|
}
|
|
|
|
free(setel);
|
|
free(inps);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static double average(double *vals, int nvals) {
|
|
double rv;
|
|
int i;
|
|
|
|
for (rv = 0.0, i = 0; i < nvals; i++)
|
|
rv += vals[i];
|
|
|
|
if (nvals > 0)
|
|
rv /= (double)nvals;
|
|
|
|
return rv;
|
|
}
|
|
|
|
static double median(double *vals, int nvals) {
|
|
if (nvals < 3)
|
|
return average(vals, nvals);
|
|
|
|
#define HEAP_COMPARE(A,B) (A < B)
|
|
HEAPSORT(double,vals,nvals);
|
|
|
|
if ((nvals & 1) != 0)
|
|
return vals[nvals/2];
|
|
else
|
|
return 0.5 * (vals[nvals/2] + vals[nvals/2-1]);
|
|
}
|
|
|
|
/* Compute Geometric Median of PCS or Dev3 values */
|
|
/* using Weiszfeld's algorithm. */
|
|
static void geommed(double res[3], double vals[][3], int nvals) {
|
|
int i, j;
|
|
|
|
/* Start with mean value */
|
|
icmSet3(res, 0.0);
|
|
for (i = 0; i < nvals; i++)
|
|
icmAdd3(res, res, vals[i]);
|
|
icmScale3(res, res, 1.0/(double)nvals);
|
|
|
|
//printf("\n~1 average = %f %f %f\n", res[0], res[1], res[2]);
|
|
|
|
/* Itterate to approach Geometric Mean */
|
|
for (j = 0; j < 20; j++) {
|
|
double tv[3], tt;
|
|
int k;
|
|
|
|
icmSet3(tv, 0.0);
|
|
tt = 0.0;
|
|
for (k = i = 0; i < nvals; i++) {
|
|
double norm = icmNorm33(vals[i], res);
|
|
if (norm < 1e-6)
|
|
continue;
|
|
tv[0] += vals[i][0]/norm;
|
|
tv[1] += vals[i][1]/norm;
|
|
tv[2] += vals[i][2]/norm;
|
|
tt += 1.0/norm;
|
|
k++;
|
|
//printf("Norm = %f, tv = %f %f %f, tt = %f\n",norm, tv[0], tv[1], tv[2], tt);
|
|
}
|
|
if (k > 0)
|
|
icmScale3(res, tv, 1.0/tt);
|
|
//printf("~1 res = %f %f %f\n", res[0], res[1], res[2]);
|
|
}
|
|
|
|
//printf("~1 geomm = %f %f %f\n", res[0], res[1], res[2]);
|
|
}
|
|
|
|
|
|
|
|
|