1 /*****************************************************************************
2 Major portions of this software are copyrighted by the Medical College
3 of Wisconsin, 1994-2000, and are released under the Gnu General Public
4 License, Version 2. See the file README.Copyright for details.
5 ******************************************************************************/
6
7 #ifndef __PLUGIN_REORDER_PARSEMAP_C__
8 #define __PLUGIN_REORDER_PARSEMAP_C__
9
10 #ifndef MAIN_PLUGIN_REORDER
11 #define MAIN_PLUGIN_REORDER_PARSEMAP /* compile parseMap for command line testing */
12 #define DEBUG_PLUGIN_REORDER_PARSEMAP
13 #endif
14
15 /*
16 Function: REORDER_parseMap
17
18 Author: Jay Brian Kummer/Medical College Of WI/Neitz & DeYoe Labs
19
20 Date: April 21, 1997
21
22 Purpose:
23
24 AFNI 'reorder' plugin routine that parses the epoch map file
25 for a requested shuffling of a 3D+Time dataSet. This function
26 will return an array of indices representing the new (reorderd)
27 position of time-course values. The caller will then apply this
28 order to all voxels in the dataSet.
29
30 This version of the parseMap function, 'parseMap', 'collates'
31 duplicated classes in the map file in the order that they appear
32 (e.g., given a sequence of classes [ D C A B A ], the reorderd
33 order returned will be indices for [ A1 A2 B C D ]).
34
35 The "epoch map" is a series of entries (given in a text file)
36 which indicates the classification and, implicitly, the target
37 order of epochs of a time-course. These maps are companions to
38 dataSets arising from specific sequences of stimulus presentation
39 and, therefore, should have a one-to-one correspondence to those
40 dataSets (i.e., they have the same time length).
41
42 The expected format of the epoch map file is as follows:
43
44 # Comment lines begin with a '#' and persist to the end of the line.
45 [ <EpochClass><PointNumberInClass> | - ]
46 ...one entry for each TR in the stimulus presentation...
47
48 Each entry is either an 'EpochClass' or a '-'. The latter excludes the
49 point from the resulting reordering. 'EpochClass' is a single letter,
50 [a-zA-Z], which classifies the current epoch; the contatenated number
51 is an increasing value from 1 to the epoch length and is used mainly
52 to delimit contiguous instances of the same class.
53
54 For example:
55
56 # This map is a companion to a dataSet acquired during visual
57 # presentation of sequence of different size rings:
58 #
59 # RingSize |__A__
60 # | | __D__
61 # | |__B__ |
62 # | | |
63 # | |__C__
64 # |_____________________
65 # 1 20 (TR)
66 #
67 # 5 scans per presentation, total of 20 scans, 4 epochs.
68 #
69 # User expects response amplitudes to be proportional
70 # to ring sizes; this will be much easier to analyze if
71 # epochs are ordered corresponding to an increasing
72 # trend in ring size, so reorder the epochs:
73 C1
74 C2
75 C3
76 C4
77 C5
78 B1
79 B2
80 B3
81 B4
82 B5
83 D1
84 D2
85 D3
86 D4
87 D5
88 A1
89 A2
90 A3
91 A4
92 A5
93 # Resulting order:
94 # RingSize | __A__
95 # | __D__|
96 # | __B__|
97 # | |
98 # |__C_|
99 # |_____________________
100 # 1 20 (TR)
101 #
102
103 The TR number of each entry is, implicitly, the line number of the
104 entry; therefore, the number of lines in the map file (except for
105 comment lines) should equal the number of TRs in the companion
106 dataSet.
107
108 Usage:
109
110 char *myFile = "QQ_epoch.map"; / epoch map for a given experiment /
111 int length = npts; / number of time-course points in /
112 / the dataSet /
113 ClassInfo *classKRH; / Place to store the sequence of classes /
114 int classCount; / Number of classes in 'class' array /
115 if(NULL == REORDER_parseMap(myFile, &length, &classKRH, &classCount)) {
116 printf("!! error !!\n");
117 FreeWorkspace();
118 return NULL;
119 }
120
121 Input Parameters:
122
123 myFile, char *, pointer to a NULL-terminated string, filename for an
124 epoch map
125 length, int *, pointer to a value storing the number of time-course points
126 in the dataSet
127 Output Parameters:
128
129 int *, pointer to an array of length 'length' containing indices (on
130 the interval [0, (length-1)] for the new ordering of time-course
131 data points. NULL is returned on any error.
132 By parameter:
133 class, ClassInfo **, address of a pointer to an array of structures
134 that will contain class and class length info.
135 classCount, integer, address of an integer to receive the length of 'class'
136
137 Side Effects:
138
139 The contents of 'length' will hold the length of the 3D+time dataSet
140 to be processed by this plugin; on return, this will contain the length of
141 the array of indices returned by this function, which in turn will also be
142 the time-length of the reorderd 3D+time dataset. The return length can only
143 be altered (decreased) by excluding TR points from the reorderd dataSet (by
144 specifying '-' in the epoch map file). 'class' will be allocated to the
145 length 'classCount' if parsing is successful.
146
147 Pseudo Code:
148
149 Check input parameters.
150 Count the number of entries in the file.
151 ...Pseudo code boring...losing consciousness...
152
153 Code: */
154
155 #include <stdio.h>
156 #include <stdlib.h>
157 #include <ctype.h>
158 #include <string.h>
159
160 #ifndef MAIN_PLUGIN_REORDER
161 typedef struct {
162 char classKRH;
163 int length;
164 } ClassInfo;
165 #endif
166
REORDER_parseMap(char * mapFile,int * length,ClassInfo ** classKRH,int * classCount)167 int *REORDER_parseMap(char *mapFile
168 , int *length
169 , ClassInfo **classKRH
170 , int *classCount)
171 {
172 FILE *inf = NULL;
173 char c; int icc ;
174 char *sptr;
175 char *classList=NULL;
176 char cBuf[256] = {0};
177 int i;
178 int j;
179 int k=0;
180 int line;
181 int excluded;
182 int rawLength;
183 int *index = NULL;
184 int *classNum=NULL;
185 int classStart;
186 char currentClass;
187 int currentClassPos;
188
189 /* Check input parameters */
190 if(NULL == mapFile) {
191 printf("!! [AFNI/reorder] NULL file name !!\n");
192 *length = *classCount = 0;
193 return NULL;
194 }
195
196 if(0 == mapFile[0]) {
197 printf("!! [AFNI/reorder] Empty file name !!\n");
198 *length = *classCount = 0;
199 return NULL;
200 }
201
202 if(NULL == (inf = fopen(mapFile, "r"))) {
203 printf("!! [AFNI/reorder] Trouble opening '%s' for reading !!\n", mapFile);
204 *length = *classCount = 0;
205 return NULL;
206 }
207
208 /* Count the number of noncomment entries in the file */
209 for(i = 0, line = 1, excluded = 0;; line++) {
210 if(i > *length) {
211 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
212 printf("[parseMap] Entry count exceeds expected\n");
213 #endif
214 printf("!! [AFNI/reorder] Number of entries in 'mapFile' exceeds expected of %d !!\n"
215 , *length);
216 *length = *classCount = 0;
217 return NULL;
218 }
219
220 /* Test for EOF */
221 c = icc = fgetc(inf);
222 if(EOF == icc || feof(inf)) {
223 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
224 printf("[parseMap] EOF detected: ");
225 #endif
226 if(i != *length) {
227 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
228 printf("Abnormal\n");
229 #endif
230 printf("!! [AFNI/reorder] Unexpected EOF at line %d !!\n", line);
231 *length = *classCount = 0;
232 return NULL;
233 }
234 else {
235 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
236 printf("Normal\n");
237 #endif
238 break;
239 }
240 }
241 ungetc(c, inf);
242
243 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
244 printf("[parseMap] Processing line %d...\n", line);
245 #endif
246
247 /* Get the next line */
248 fgets(cBuf, sizeof(cBuf), inf);
249
250 /* Delete newline character */
251 sptr = strchr(cBuf, '\n');
252 if(sptr) {
253 *sptr = 0;
254 }
255
256 /* Eat leading whitespace */
257 sptr = cBuf;
258 while(isspace(*sptr)) ++sptr;
259
260 /* Skip comments and empty lines */
261 if('#' == *sptr || 0 == *sptr) {
262 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
263 printf("[parseMap] Skipping comment or empty line.\n");
264 #endif
265 continue;
266 }
267
268 if('-' == *sptr) { /* excluded value */
269 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
270 printf("[parseMap] Excluded point detected.\n");
271 #endif
272 ++i;
273 ++excluded;
274 continue;
275 }
276
277 /* Check for valid class name */
278 if(!isalpha(*sptr)) {
279 printf("!! [AFNI/reorder] Bad map entry: '%s' at line %d !!\n"
280 , sptr, line);
281 *length = *classCount = 0;
282 return NULL;
283 }
284
285 if(!isdigit(*(sptr+1))) {
286 printf("!! [AFNI/reorder] Illformed entry '%s' at line %d !!\n"
287 , sptr, line);
288 *length = *classCount = 0;
289 return NULL;
290 }
291
292 ++i;
293 }
294
295 /* Rewind the input file for reuse */
296 rewind(inf);
297
298 /* Allocate workspace */
299 index = (int *)calloc(sizeof(int), i-excluded);
300 if(NULL == index) {
301 printf("!! [AFNI/reorder] Allocation error(1) !!\n");
302 *length = *classCount = 0;
303 return NULL;
304 }
305
306 classList = (char *)calloc(sizeof(char), i);
307 if(NULL == classList) {
308 printf("!! [AFNI/reorder] Allocation error(2) !!\n");
309 free(index);
310 *length = *classCount = 0;
311 return NULL;
312 }
313
314 classNum = (int *)calloc(sizeof(int), i);
315 if(NULL == classNum) {
316 printf("!! [AFNI/reorder] Allocation error(3) !!\n");
317 free(index);
318 free(classList);
319 *length = *classCount = 0;
320 return NULL;
321 }
322
323 /* Arrays to be reallocated (initialize) */
324 (*classKRH) = (ClassInfo *)calloc(sizeof(ClassInfo), 1);
325 if(NULL == (*classKRH)) {
326 printf("!! [AFNI/reorder] Allocation error(4) !!\n");
327 free(index);
328 free(classList);
329 free(classNum);
330 *length = *classCount = 0;
331 return NULL;
332 }
333
334 /* Set return length */
335 rawLength = i;
336 *length = i - excluded;
337 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
338 printf("[parseMap] Return length of array is %d elements...\n", *length);
339 #endif
340
341 /* Collect mapping information from file */
342 currentClass = 0;
343 *classCount = 0;
344 for(i = 0, line = 1; i < rawLength; line++) {
345 /* Get the next line */
346 fgets(cBuf, sizeof(cBuf), inf);
347
348 /* Delete newline character */
349 sptr = strchr(cBuf, '\n');
350 if(sptr) {
351 *sptr = 0;
352 }
353
354 /* Eat leading whitespace */
355 sptr = cBuf;
356 while(isspace(*sptr)) ++sptr;
357
358 /* Skip comments and empty lines */
359 if('#' == *sptr || 0 == *sptr) {
360 continue;
361 }
362
363 if('-' == *sptr) { /* excluded value */
364 classList[i] = classNum[i] = 0;
365 i++;
366 continue;
367 }
368
369 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
370 printf("[parseMap] Processing line %d [%s]...\n", line, sptr);
371 #endif
372
373 classList[i] = cBuf[0];
374 classNum[i] = atoi(&cBuf[1]);
375
376 /* Count classes and make sure they are numbered properly */
377 if(0 == currentClass && 0 == *classCount) { /* first class */
378 currentClass = cBuf[0];
379 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
380 printf("[parseMap] First class: %c\n", currentClass);
381 #endif
382 k = classNum[i];
383 if(k != 1) {
384 printf("!! [AFNI/reorder] Invalid class numbering at line %d [Should start at 1] {1} !!\n"
385 , line);
386 free(classList);
387 free(classNum);
388 free(index);
389 free((*classKRH));
390 (*classKRH) = NULL;
391 *length = *classCount = 0;
392 return NULL;
393 }
394
395 (*classCount)++;
396
397 /* reallocate space */
398 (*classKRH) = (ClassInfo *)realloc((void *)(*classKRH), (*classCount)*sizeof(ClassInfo));
399 if(NULL == (*classKRH)) {
400 printf("!! [AFNI/reorder] Allocation error(4) !!\n");
401 free(index);
402 free(classList);
403 free(classNum);
404 *length = *classCount = 0;
405 return NULL;
406 }
407 (*classKRH)[(*classCount)-1].classKRH = currentClass;
408 (*classKRH)[(*classCount)-1].length = 1;
409 }
410 else if(currentClass != cBuf[0]) { /* new class */
411 currentClass = cBuf[0];
412 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
413 printf("[parseMap] Next class: %c\n", currentClass);
414 #endif
415 k = classNum[i];
416 if(k != 1) {
417 printf("!! [AFNI/reorder] Invalid class numbering at line %d [Should start at 1] {2} !!\n"
418 , line);
419 free(classList);
420 free(classNum);
421 free(index);
422 free((*classKRH));
423 (*classKRH) = NULL;
424 *length = *classCount = 0;
425 return NULL;
426 }
427
428 (*classCount)++;
429
430 /* reallocate space */
431 (*classKRH) = (ClassInfo *)realloc((void *)(*classKRH), (*classCount)*sizeof(ClassInfo));
432 if(NULL == (*classKRH)) {
433 printf("!! [AFNI/reorder] Allocation error(4) !!\n");
434 free(index);
435 free(classList);
436 free(classNum);
437 *length = *classCount = 0;
438 return NULL;
439 }
440 (*classKRH)[(*classCount)-1].classKRH = currentClass;
441 (*classKRH)[(*classCount)-1].length = 1;
442 }
443 else {
444 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
445 printf("[parseMap] Next entry, checking class numbering...\n");
446 #endif
447 ++k;
448 if(1 == classNum[i]) { /* contiguous instance of current class */
449 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
450 printf("[parseMap] Contiguous class: %c\n", currentClass);
451 #endif
452
453 k = 1;
454 (*classCount)++;
455
456 /* reallocate space */
457 (*classKRH) = (ClassInfo *)realloc((void *)(*classKRH), (*classCount)*sizeof(ClassInfo));
458 if(NULL == (*classKRH)) {
459 printf("!! [AFNI/reorder] Allocation error(4) !!\n");
460 free(index);
461 free(classList);
462 free(classNum);
463 *length = *classCount = 0;
464 return NULL;
465 }
466 (*classKRH)[(*classCount)-1].classKRH = currentClass;
467 (*classKRH)[(*classCount)-1].length = 0;
468 }
469 else if(classNum[i] != k) {
470 printf("!! [AFNI/reorder] Invalid class numbering at line %d {3} !!\n"
471 , line);
472 free(index);
473 free(classList);
474 free(classNum);
475 free((*classKRH));
476 (*classKRH) = NULL;
477 *length = *classCount = 0;
478 return NULL;
479 }
480 ++(*classKRH)[(*classCount)-1].length;
481 }
482 ++i;
483 }
484 fclose(inf);
485
486 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
487 printf("\n[parseMap] Epoch map has %d distinct classes.\n\n", *classCount);
488 printf("[parseMap] Parsed the following:\n");
489 for(i = 0; i < rawLength; i++) {
490 if(classList[i]) {
491 printf(" [%d] Class %c %d\n", i, classList[i], classNum[i]);
492 }
493 else {
494 printf(" [%d] Excluded.\n", i);
495 }
496 }
497 printf("\n[parseMap] Meta-sequence of classes is:\n");
498 for(i = 0; i < *classCount; i++) {
499 printf(" [%d] Class %c [Width %d TRs]\n", i, (*classKRH)[i].classKRH, (*classKRH)[i].length);
500 }
501 #endif
502
503 /* Build array of indices for reordering the time-course */
504 /* Find the first occurrence of the lowest value class [currentClass].
505 Store the index positions of that class sequence in the current
506 positions available in the 'index' array.
507 */
508
509 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
510 printf("\n[parseMap] Building mapping array...\n");
511 #endif
512 for(i = 0, currentClass = 0; i < *length; /* quit when all indices are remapped */) {
513 /* Get next class to remap */
514 if(!currentClass) {
515 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
516 printf("\n Determining next class to map...\n");
517 #endif
518 j = 0;
519 while(j < rawLength) { /* look for next non-null class */
520 if(classList[j]) {
521 break;
522 }
523 ++j;
524 }
525 if(j == rawLength) {
526 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
527 printf(" << No more classes to map >>\n");
528 #endif
529 break; /* done if there is none */
530 }
531
532 currentClass = classList[j]; /* current epoch class to remap */
533 classStart = classNum[j]; /* starting number of current class */
534 currentClassPos = j; /* index of current class */
535
536 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
537 printf(" Next valid class is '%c', checking to see if there is a better one...\n"
538 , currentClass);
539 #endif
540
541 /* See if there's a better one */
542 for(j = 1; j < rawLength; j++) {
543 if(classList[j]) {
544 if(classList[j] < currentClass) {
545 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
546 printf(" Class '%c' should be done before '%c'...\n"
547 , classList[j], currentClass);
548 #endif
549 currentClass = classList[j]; /* new epoch class to remap */
550 classStart = classNum[j]; /* starting number of new class */
551 currentClassPos = j; /* index of new class */
552 }
553 }
554 }
555
556 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
557 printf(" Remapping class '%c' [starting index is %d]...\n"
558 , currentClass, currentClassPos);
559 #endif
560 }
561
562 /* currenClassPos is the index of the start position for the current
563 class to be remapped; remap until class changes or class number
564 returns to 1 */
565 /* for(k = currentClassPos; i < rawLength, k < rawLength; ) { */
566 /* for(k = currentClassPos; i < rawLength && k < rawLength; ) { */
567 for(k = currentClassPos; k < rawLength; ) {
568 if(classList[k]) {
569 #ifdef DEBUG_PLUGIN_REORDER_PARSEMAP
570 printf(" Index[%d] <-- %d [Old index]\n", i, k);
571 #endif
572 index[i] = k; /* store the position of the TR value from the old time-course */
573 classList[k] = 0; /* mark as 'done' */
574 ++i;
575 ++k;
576 }
577 else { /* stop when a 'exclusion' point is found */
578 break;
579 }
580
581 if(classList[k] != currentClass || 1 == classNum[k]) {
582 break;
583 }
584 }
585 currentClass = 0;
586 }
587
588 /* Free workspace */
589 free(classNum);
590
591 return(index);
592 }
593
594 #ifdef MAIN_PLUGIN_REORDER_PARSEMAP
main(int argc,char * argv[])595 main(int argc, char *argv[])
596 {
597 int *index = NULL;
598 ClassInfo *classKRH = NULL;
599 int classCount = 0;
600 int length;
601 int i;
602
603 if(3 != argc) {
604 printf("usage: parseMap <MapFile> <TargetCount>\n");
605 exit(1);
606 }
607
608 length = atoi(argv[2]);
609 if(length < 1) {
610 printf("!! [Main] Invalid target count: %d !!\n", length);
611 exit(1);
612 }
613
614 if(NULL == (index = REORDER_parseMap(argv[1], &length, &classKRH, &classCount))) {
615 printf("!! [Main] Trouble parsing epoch map file !!\n");
616 exit(1);
617 }
618
619 printf("\n[Main] Indices for epoch remapping:\n");
620 for(i = 0; i < length; i++) {
621 printf(" %d\n", index[i]);
622 }
623
624 printf("\n[Main] Meta-sequence of classes is:\n");
625 for(i = 0; i < classCount; i++) {
626 printf(" [%d] Class %c [Width %d TRs]\n", i, classKRH[i].classKRH, classKRH[i].length);
627 }
628
629 free(index);
630 free(classKRH);
631 }
632 #endif
633
634 #endif
635
636