ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/util/rmtxop.c
Revision: 2.26
Committed: Fri Dec 1 02:05:00 2023 UTC (4 months, 3 weeks ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.25: +10 -11 lines
Log Message:
refactor(rmtxop): Changed rmatrix library interface for by row matrix output

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: rmtxop.c,v 2.25 2023/11/29 18:57:10 greg Exp $";
3 #endif
4 /*
5 * General component matrix operations.
6 */
7
8 #include <errno.h>
9 #include <ctype.h>
10 #include "rtio.h"
11 #include "resolu.h"
12 #include "rmatrix.h"
13 #include "platform.h"
14
15 #define MAXCOMP MAXCSAMP /* #components we support */
16
17 /* Unary matrix operation(s) */
18 typedef struct {
19 double sca[MAXCOMP]; /* scalar coefficients */
20 double cmat[MAXCOMP*MAXCOMP]; /* component transformation */
21 short nsf; /* number of scalars */
22 short clen; /* number of coefficients */
23 char csym[11]; /* symbolic coefficients */
24 char transpose; /* do transpose? */
25 } RUNARYOP;
26
27 /* Matrix input source and requested operation(s) */
28 typedef struct {
29 const char *inspec; /* input specification */
30 RMPref rmp; /* matrix preference */
31 RUNARYOP preop; /* unary operation(s) */
32 RMATRIX *mtx; /* original matrix if loaded */
33 int binop; /* binary op with next (or 0) */
34 } ROPMAT;
35
36 int verbose = 0; /* verbose reporting? */
37
38 /* Load matrix */
39 static int
40 loadmatrix(ROPMAT *rop)
41 {
42 if (rop->mtx != NULL) /* already loaded? */
43 return(0);
44
45 rop->mtx = rmx_load(rop->inspec, rop->rmp);
46
47 return(!rop->mtx ? -1 : 1);
48 }
49
50 /* Compute conversion row from spectrum to one channel of RGB */
51 static void
52 rgbrow(ROPMAT *rop, int r, int p)
53 {
54 const int nc = rop->mtx->ncomp;
55 const float * wlp = rop->mtx->wlpart;
56 int i;
57
58 for (i = nc; i--; ) {
59 int nmEnd = wlp[0] + (wlp[3] - wlp[0])*i/nc;
60 int nmStart = wlp[0] + (wlp[3] - wlp[0])*(i+1)/nc;
61 COLOR crgb;
62 spec_rgb(crgb, nmStart, nmEnd);
63 rop->preop.cmat[r*nc+i] = crgb[p];
64 }
65 }
66
67 /* Compute conversion row from spectrum to one channel of XYZ */
68 static void
69 xyzrow(ROPMAT *rop, int r, int p)
70 {
71 const int nc = rop->mtx->ncomp;
72 const float * wlp = rop->mtx->wlpart;
73 int i;
74
75 for (i = nc; i--; ) {
76 int nmEnd = wlp[0] + (wlp[3] - wlp[0])*i/nc;
77 int nmStart = wlp[0] + (wlp[3] - wlp[0])*(i+1)/nc;
78 COLOR cxyz;
79 spec_cie(cxyz, nmStart, nmEnd);
80 rop->preop.cmat[r*nc+i] = cxyz[p];
81 }
82 }
83
84 /* Use the spectral sensitivity function to compute matrix coefficients */
85 static void
86 sensrow(ROPMAT *rop, int r, double (*sf)(SCOLOR sc, int ncs, const float wlpt[4]))
87 {
88 const int nc = rop->mtx->ncomp;
89 int i;
90
91 for (i = nc; i--; ) {
92 SCOLOR sclr;
93 scolorblack(sclr);
94 sclr[i] = 1.f;
95 rop->preop.cmat[r*nc+i] = (*sf)(sclr, nc, rop->mtx->wlpart);
96 }
97 }
98
99 /* Check/set symbolic transform */
100 static int
101 checksymbolic(ROPMAT *rop)
102 {
103 const int nc = rop->mtx->ncomp;
104 const int dt = rop->mtx->dtype;
105 int i, j;
106
107 if (nc < 3) {
108 fprintf(stderr, "%s: -c '%s' requires at least 3 components\n",
109 rop->inspec, rop->preop.csym);
110 return(-1);
111 }
112 rop->preop.clen = strlen(rop->preop.csym) * nc;
113 if (rop->preop.clen > MAXCOMP*MAXCOMP) {
114 fprintf(stderr, "%s: -c '%s' results in too many components\n",
115 rop->inspec, rop->preop.csym);
116 return(-1);
117 }
118 for (j = 0; rop->preop.csym[j]; j++) {
119 int comp = 0;
120 switch (rop->preop.csym[j]) {
121 case 'B':
122 ++comp;
123 /* fall through */
124 case 'G':
125 ++comp;
126 /* fall through */
127 case 'R':
128 if (dt == DTxyze) {
129 for (i = 3; i--; )
130 rop->preop.cmat[j*nc+i] = 1./WHTEFFICACY *
131 xyz2rgbmat[comp][i];
132 } else if (nc == 3)
133 rop->preop.cmat[j*nc+comp] = 1.;
134 else
135 rgbrow(rop, j, comp);
136 break;
137 case 'Z':
138 ++comp;
139 /* fall through */
140 case 'Y':
141 ++comp;
142 /* fall through */
143 case 'X':
144 if (dt == DTxyze) {
145 rop->preop.cmat[j*nc+comp] = 1.;
146 } else if (nc == 3) {
147 for (i = 3; i--; )
148 rop->preop.cmat[j*nc+i] =
149 rgb2xyzmat[comp][i];
150 } else if (comp == CIEY)
151 sensrow(rop, j, scolor2photopic);
152 else
153 xyzrow(rop, j, comp);
154
155 for (i = nc*(dt != DTxyze); i--; )
156 rop->preop.cmat[j*nc+i] *= WHTEFFICACY;
157 break;
158 case 'S': /* scotopic (il)luminance */
159 sensrow(rop, j, scolor2scotopic);
160 for (i = nc; i--; )
161 rop->preop.cmat[j*nc+i] *= WHTSCOTOPIC;
162 break;
163 case 'M': /* melanopic (il)luminance */
164 sensrow(rop, j, scolor2melanopic);
165 for (i = nc; i--; )
166 rop->preop.cmat[j*nc+i] *= WHTMELANOPIC;
167 break;
168 case 'A': /* average component */
169 for (i = nc; i--; )
170 rop->preop.cmat[j*nc+i] = 1./(double)nc;
171 break;
172 default:
173 fprintf(stderr, "%s: -c '%c' unsupported\n",
174 rop->inspec, rop->preop.csym[j]);
175 return(-1);
176 }
177 }
178 /* return recommended output type */
179 if (!strcmp(rop->preop.csym, "XYZ")) {
180 if (dt <= DTspec)
181 return(DTxyze);
182 } else if (!strcmp(rop->preop.csym, "RGB")) {
183 if (dt <= DTspec)
184 return(DTrgbe);
185 }
186 if ((nc > 3) & (dt <= DTspec))
187 return(DTfloat); /* probably not actual spectrum */
188 return(0);
189 }
190
191 /* Get matrix and perform unary operations */
192 static RMATRIX *
193 loadop(ROPMAT *rop)
194 {
195 int outtype = 0;
196 RMATRIX *mres;
197 int i, j;
198
199 if (loadmatrix(rop) < 0) /* make sure we're loaded */
200 return(NULL);
201
202 if (rop->preop.csym[0] && /* symbolic transform? */
203 (outtype = checksymbolic(rop)) < 0)
204 goto failure;
205 if (rop->preop.clen > 0) { /* apply component transform? */
206 if (rop->preop.clen % rop->mtx->ncomp) {
207 fprintf(stderr, "%s: -c must have N x %d coefficients\n",
208 rop->inspec, rop->mtx->ncomp);
209 goto failure;
210 }
211 if (rop->preop.nsf > 0) { /* scale transform, first */
212 if (rop->preop.nsf == 1) {
213 for (i = rop->preop.clen; i--; )
214 rop->preop.cmat[i] *= rop->preop.sca[0];
215 } else if (rop->preop.nsf != rop->mtx->ncomp) {
216 fprintf(stderr, "%s: -s must have one or %d factors\n",
217 rop->inspec, rop->mtx->ncomp);
218 goto failure;
219 } else {
220 for (j = rop->preop.clen/rop->preop.nsf; j--; )
221 for (i = rop->preop.nsf; i--; )
222 rop->preop.cmat[j*rop->preop.nsf+i] *=
223 rop->preop.sca[i];
224 }
225 }
226 mres = rmx_transform(rop->mtx, rop->preop.clen/rop->mtx->ncomp,
227 rop->preop.cmat);
228 if (mres == NULL) {
229 fprintf(stderr, "%s: matrix transform failed\n",
230 rop->inspec);
231 goto failure;
232 }
233 if (verbose)
234 fprintf(stderr, "%s: applied %d x %d transform%s\n",
235 rop->inspec, mres->ncomp,
236 rop->mtx->ncomp,
237 rop->preop.nsf ? " (* scalar)" : "");
238 rop->preop.nsf = 0; /* now folded in */
239 if ((mres->ncomp > 3) & (mres->dtype <= DTspec))
240 outtype = DTfloat; /* probably not actual spectrum */
241 rmx_free(rop->mtx);
242 rop->mtx = mres;
243 }
244 if (rop->preop.nsf > 0) { /* apply scalar(s)? */
245 if (rop->preop.nsf == 1) {
246 for (i = rop->mtx->ncomp; --i; )
247 rop->preop.sca[i] = rop->preop.sca[0];
248 } else if (rop->preop.nsf != rop->mtx->ncomp) {
249 fprintf(stderr, "%s: -s must have one or %d factors\n",
250 rop->inspec, rop->mtx->ncomp);
251 goto failure;
252 }
253 if (!rmx_scale(rop->mtx, rop->preop.sca)) {
254 fputs(rop->inspec, stderr);
255 fputs(": scalar operation failed\n", stderr);
256 goto failure;
257 }
258 if (verbose) {
259 fputs(rop->inspec, stderr);
260 fputs(": applied scalar (", stderr);
261 for (i = 0; i < rop->preop.nsf; i++)
262 fprintf(stderr, " %f", rop->preop.sca[i]);
263 fputs(" )\n", stderr);
264 }
265 }
266 if (rop->preop.transpose) { /* transpose matrix? */
267 mres = rmx_transpose(rop->mtx);
268 if (mres == NULL) {
269 fputs(rop->inspec, stderr);
270 fputs(": transpose failed\n", stderr);
271 goto failure;
272 }
273 if (verbose) {
274 fputs(rop->inspec, stderr);
275 fputs(": transposed rows and columns\n", stderr);
276 }
277 rmx_free(rop->mtx);
278 rop->mtx = mres;
279 }
280 mres = rop->mtx;
281 rop->mtx = NULL;
282 if (outtype)
283 mres->dtype = outtype;
284 return(mres);
285 failure:
286 rmx_free(rop->mtx);
287 return(rop->mtx = NULL);
288 }
289
290 /* Execute binary operation, free matrix arguments and return new result */
291 static RMATRIX *
292 binaryop(const char *inspec, RMATRIX *mleft, int op, RMATRIX *mright)
293 {
294 RMATRIX *mres = NULL;
295 int i;
296
297 if ((mleft == NULL) | (mright == NULL))
298 return(NULL);
299 switch (op) {
300 case '.': /* concatenate */
301 if (mleft->ncomp != mright->ncomp) {
302 fputs(inspec, stderr);
303 fputs(": # components do not match\n", stderr);
304 } else if (mleft->ncols != mright->nrows) {
305 fputs(inspec, stderr);
306 fputs(": mismatched dimensions\n",
307 stderr);
308 } else
309 mres = rmx_multiply(mleft, mright);
310 rmx_free(mleft);
311 rmx_free(mright);
312 if (mres == NULL) {
313 fputs(inspec, stderr);
314 fputs(": concatenation failed\n", stderr);
315 return(NULL);
316 }
317 if (verbose) {
318 fputs(inspec, stderr);
319 fputs(": concatenated matrix\n", stderr);
320 }
321 break;
322 case '+':
323 if (!rmx_sum(mleft, mright, NULL)) {
324 fputs(inspec, stderr);
325 fputs(": matrix sum failed\n", stderr);
326 rmx_free(mleft);
327 rmx_free(mright);
328 return(NULL);
329 }
330 if (verbose) {
331 fputs(inspec, stderr);
332 fputs(": added in matrix\n", stderr);
333 }
334 rmx_free(mright);
335 mres = mleft;
336 break;
337 case '*':
338 case '/': {
339 const char * tnam = (op == '/') ?
340 "division" : "multiplication";
341 errno = 0;
342 if (!rmx_elemult(mleft, mright, (op == '/'))) {
343 fprintf(stderr, "%s: element-wise %s failed\n",
344 inspec, tnam);
345 rmx_free(mleft);
346 rmx_free(mright);
347 return(NULL);
348 }
349 if (errno)
350 fprintf(stderr,
351 "%s: warning - error during element-wise %s\n",
352 inspec, tnam);
353 else if (verbose)
354 fprintf(stderr, "%s: element-wise %s\n", inspec, tnam);
355 rmx_free(mright);
356 mres = mleft;
357 } break;
358 default:
359 fprintf(stderr, "%s: unknown operation '%c'\n", inspec, op);
360 rmx_free(mleft);
361 rmx_free(mright);
362 return(NULL);
363 }
364 return(mres);
365 }
366
367 /* Perform matrix operations from left to right */
368 static RMATRIX *
369 op_left2right(ROPMAT *mop)
370 {
371 RMATRIX *mleft = loadop(mop);
372
373 while (mop->binop) {
374 if (mleft == NULL)
375 break;
376 mleft = binaryop(mop[1].inspec,
377 mleft, mop->binop, loadop(mop+1));
378 mop++;
379 }
380 return(mleft);
381 }
382
383 /* Perform matrix operations from right to left */
384 static RMATRIX *
385 op_right2left(ROPMAT *mop)
386 {
387 RMATRIX *mright;
388 int rpos = 0;
389 /* find end of list */
390 while (mop[rpos].binop)
391 if (mop[rpos++].binop != '.') {
392 fputs(
393 "Right-to-left evaluation only for matrix multiplication!\n",
394 stderr);
395 return(NULL);
396 }
397 mright = loadop(mop+rpos);
398 while (rpos-- > 0) {
399 if (mright == NULL)
400 break;
401 mright = binaryop(mop[rpos+1].inspec,
402 loadop(mop+rpos), mop[rpos].binop, mright);
403 }
404 return(mright);
405 }
406
407 #define t_nrows(mop) ((mop)->preop.transpose ? (mop)->mtx->ncols \
408 : (mop)->mtx->nrows)
409 #define t_ncols(mop) ((mop)->preop.transpose ? (mop)->mtx->nrows \
410 : (mop)->mtx->ncols)
411
412 /* Should we prefer concatenating from rightmost matrix towards left? */
413 static int
414 prefer_right2left(ROPMAT *mop)
415 {
416 int mri = 0;
417
418 while (mop[mri].binop) /* find rightmost matrix */
419 if (mop[mri++].binop != '.')
420 return(0); /* pre-empt reversal for other ops */
421
422 if (mri <= 1)
423 return(0); /* won't matter */
424
425 if (loadmatrix(mop+mri) < 0) /* load rightmost cat */
426 return(1); /* fail will bail in a moment */
427
428 if (t_ncols(mop+mri) == 1)
429 return(1); /* definitely better R->L */
430
431 if (t_ncols(mop+mri) >= t_nrows(mop+mri))
432 return(0); /* ...probably worse */
433
434 if (loadmatrix(mop) < 0) /* load leftmost */
435 return(0); /* fail will bail in a moment */
436
437 return(t_ncols(mop+mri) < t_nrows(mop));
438 }
439
440 static int
441 get_factors(double da[], int n, char *av[])
442 {
443 int ac;
444
445 for (ac = 0; ac < n && isflt(av[ac]); ac++)
446 da[ac] = atof(av[ac]);
447 return(ac);
448 }
449
450 static ROPMAT *
451 resize_moparr(ROPMAT *mop, int n2alloc)
452 {
453 int nmats = 0;
454 int i;
455
456 while (mop[nmats++].binop)
457 ;
458 for (i = nmats; i > n2alloc; i--)
459 rmx_free(mop[i].mtx);
460 mop = (ROPMAT *)realloc(mop, n2alloc*sizeof(ROPMAT));
461 if (mop == NULL) {
462 fputs("Out of memory in resize_moparr()\n", stderr);
463 exit(1);
464 }
465 if (n2alloc > nmats)
466 memset(mop+nmats, 0, (n2alloc-nmats)*sizeof(ROPMAT));
467 return(mop);
468 }
469
470 /* Load one or more matrices and operate on them, sending results to stdout */
471 int
472 main(int argc, char *argv[])
473 {
474 int outfmt = DTfromHeader;
475 int nall = 2;
476 ROPMAT *mop = (ROPMAT *)calloc(nall, sizeof(ROPMAT));
477 int nmats = 0;
478 RMATRIX *mres = NULL;
479 int stdin_used = 0;
480 int i;
481 /* get options and arguments */
482 for (i = 1; i < argc; i++) {
483 if (argv[i][0] && !argv[i][1] &&
484 strchr(".+*/", argv[i][0]) != NULL) {
485 if (!nmats || mop[nmats-1].binop) {
486 fprintf(stderr,
487 "%s: missing matrix argument before '%c' operation\n",
488 argv[0], argv[i][0]);
489 return(1);
490 }
491 mop[nmats-1].binop = argv[i][0];
492 } else if (argv[i][0] != '-' || !argv[i][1]) {
493 if (argv[i][0] == '-') {
494 if (stdin_used++) {
495 fprintf(stderr,
496 "%s: standard input used for more than one matrix\n",
497 argv[0]);
498 return(1);
499 }
500 mop[nmats].inspec = stdin_name;
501 } else
502 mop[nmats].inspec = argv[i];
503 if (nmats > 0 && !mop[nmats-1].binop)
504 mop[nmats-1].binop = '.';
505 nmats++;
506 } else {
507 int n = argc-1 - i;
508 switch (argv[i][1]) { /* get option */
509 case 'v':
510 verbose++;
511 break;
512 case 'f':
513 switch (argv[i][2]) {
514 case 'd':
515 outfmt = DTdouble;
516 break;
517 case 'f':
518 outfmt = DTfloat;
519 break;
520 case 'a':
521 outfmt = DTascii;
522 break;
523 case 'c':
524 outfmt = DTrgbe;
525 break;
526 default:
527 goto userr;
528 }
529 break;
530 case 't':
531 mop[nmats].preop.transpose = 1;
532 break;
533 case 's':
534 if (n > MAXCOMP) n = MAXCOMP;
535 i += mop[nmats].preop.nsf =
536 get_factors(mop[nmats].preop.sca,
537 n, argv+i+1);
538 if (mop[nmats].preop.nsf <= 0) {
539 fprintf(stderr, "%s: -s missing arguments\n",
540 argv[0]);
541 goto userr;
542 }
543 break;
544 case 'c':
545 if (n && isupper(argv[i+1][0])) {
546 strlcpy(mop[nmats].preop.csym,
547 argv[++i],
548 sizeof(mop[0].preop.csym));
549 mop[nmats].preop.clen = 0;
550 break;
551 }
552 if (n > MAXCOMP*MAXCOMP) n = MAXCOMP*MAXCOMP;
553 i += mop[nmats].preop.clen =
554 get_factors(mop[nmats].preop.cmat,
555 n, argv+i+1);
556 if (mop[nmats].preop.clen <= 0) {
557 fprintf(stderr, "%s: -c missing arguments\n",
558 argv[0]);
559 goto userr;
560 }
561 mop[nmats].preop.csym[0] = '\0';
562 break;
563 case 'r':
564 if (argv[i][2] == 'f')
565 mop[nmats].rmp = RMPreflF;
566 else if (argv[i][2] == 'b')
567 mop[nmats].rmp = RMPreflB;
568 else
569 goto userr;
570 break;
571 default:
572 fprintf(stderr, "%s: unknown operation '%s'\n",
573 argv[0], argv[i]);
574 goto userr;
575 }
576 }
577 if (nmats >= nall)
578 mop = resize_moparr(mop, nall += 2);
579 }
580 if (mop[0].inspec == NULL) /* nothing to do? */
581 goto userr;
582 if (mop[nmats-1].binop) {
583 fprintf(stderr,
584 "%s: missing matrix argument after '%c' operation\n",
585 argv[0], mop[nmats-1].binop);
586 return(1);
587 }
588 /* favor quicker concatenation */
589 mop[nmats].mtx = prefer_right2left(mop) ? op_right2left(mop)
590 : op_left2right(mop);
591 if (mop[nmats].mtx == NULL)
592 return(1);
593 /* apply trailing unary operations */
594 mop[nmats].inspec = "trailing_ops";
595 mres = loadop(mop+nmats);
596 if (mres == NULL)
597 return(1);
598 if (outfmt == DTfromHeader) /* check data type */
599 outfmt = mres->dtype;
600 if (outfmt == DTrgbe) {
601 if (mres->ncomp > 3)
602 outfmt = DTspec;
603 else if (mres->dtype == DTxyze)
604 outfmt = DTxyze;
605 }
606 newheader("RADIANCE", stdout); /* write result to stdout */
607 printargs(argc, argv, stdout);
608 return(rmx_write(mres, outfmt, stdout) ? 0 : 1);
609 userr:
610 fprintf(stderr,
611 "Usage: %s [-v][-f{adfc}][-t][-s sf .. | -c ce ..][-rf|-rb] m1 [.+*/] .. > mres\n",
612 argv[0]);
613 return(1);
614 }