1 /* ----------------------------------------------------------------------------
2 @COPYRIGHT  :
3               Copyright 1993,1994,1995 David MacDonald,
4               McConnell Brain Imaging Centre,
5               Montreal Neurological Institute, McGill University.
6               Permission to use, copy, modify, and distribute this
7               software and its documentation for any purpose and without
8               fee is hereby granted, provided that the above copyright
9               notice appear in all copies.  The author and McGill University
10               make no representations about the suitability of this
11               software for any purpose.  It is provided "as is" without
12               express or implied warranty.
13 ---------------------------------------------------------------------------- */
14 
15 #include  <internal_volume_io.h>
16 #include  <limits.h>
17 #include  <float.h>
18 
19 /* ----------------------------- MNI Header -----------------------------------
20 @NAME       : create_empty_multidim_array
21 @INPUT      : n_dimensions
22               data_type
23 @OUTPUT     : array
24 @RETURNS    :
25 @DESCRIPTION: Creates a multidimensional array, without allocating its data.
26 @METHOD     :
27 @GLOBALS    :
28 @CALLS      :
29 @CREATED    : Sep. 1, 1995    David MacDonald
30 @MODIFIED   :
31 ---------------------------------------------------------------------------- */
32 
create_empty_multidim_array(multidim_array * array,int n_dimensions,Data_types data_type)33 VIOAPI   void   create_empty_multidim_array(
34     multidim_array  *array,
35     int             n_dimensions,
36     Data_types      data_type )
37 {
38     if( n_dimensions < 1 || n_dimensions > MAX_DIMENSIONS )
39     {
40         print_error(
41      "create_empty_multidim_array(): n_dimensions (%d) not in range 1 to %d.\n",
42                n_dimensions, MAX_DIMENSIONS );
43     }
44 
45     array->n_dimensions = n_dimensions;
46     array->data_type = data_type;
47     array->data = (void *) NULL;
48 }
49 
50 /* ----------------------------- MNI Header -----------------------------------
51 @NAME       : get_multidim_data_type
52 @INPUT      : array
53 @OUTPUT     :
54 @RETURNS    : data type
55 @DESCRIPTION: Returns the data type of the multidimensional array.
56 @METHOD     :
57 @GLOBALS    :
58 @CALLS      :
59 @CREATED    : Sep. 1, 1995    David MacDonald
60 @MODIFIED   :
61 ---------------------------------------------------------------------------- */
62 
get_multidim_data_type(multidim_array * array)63 VIOAPI  Data_types  get_multidim_data_type(
64     multidim_array       *array )
65 {
66     return( array->data_type );
67 }
68 
69 /* ----------------------------- MNI Header -----------------------------------
70 @NAME       : set_multidim_data_type
71 @INPUT      : array
72               data_type
73 @OUTPUT     :
74 @RETURNS    :
75 @DESCRIPTION: Sets the data type of the array.
76 @METHOD     :
77 @GLOBALS    :
78 @CALLS      :
79 @CREATED    : Sep. 1, 1995    David MacDonald
80 @MODIFIED   :
81 ---------------------------------------------------------------------------- */
82 
set_multidim_data_type(multidim_array * array,Data_types data_type)83 VIOAPI  void  set_multidim_data_type(
84     multidim_array       *array,
85     Data_types           data_type )
86 {
87     array->data_type = data_type;
88 }
89 
90 /* ----------------------------------------------------------------------------
91 @NAME       : get_type_size
92 @INPUT      : type
93 @OUTPUT     :
94 @RETURNS    : size of the type
95 @DESCRIPTION: Returns the size of the given type.
96 @METHOD     :
97 @GLOBALS    :
98 @CALLS      :
99 @CREATED    : June, 1993           David MacDonald
100 @MODIFIED   :
101 ---------------------------------------------------------------------------- */
102 
get_type_size(Data_types type)103 VIOAPI  int  get_type_size(
104     Data_types   type )
105 {
106     int   size;
107 
108     switch( type )
109     {
110     case  UNSIGNED_BYTE:    size = sizeof( unsigned char );   break;
111     case  SIGNED_BYTE:      size = sizeof( signed   char );   break;
112     case  UNSIGNED_SHORT:   size = sizeof( unsigned short );  break;
113     case  SIGNED_SHORT:     size = sizeof( signed   short );  break;
114     case  UNSIGNED_INT:     size = sizeof( unsigned int );    break;
115     case  SIGNED_INT:       size = sizeof( signed   int );    break;
116     case  FLOAT:            size = sizeof( float );           break;
117     case  DOUBLE:           size = sizeof( double );          break;
118     }
119 
120     return( size );
121 }
122 
get_type_range(Data_types type,Real * min_value,Real * max_value)123 VIOAPI  void  get_type_range(
124     Data_types   type,
125     Real         *min_value,
126     Real         *max_value )
127 {
128     switch( type )
129     {
130     case UNSIGNED_BYTE:
131         *min_value = 0.0;
132         *max_value = (Real) UCHAR_MAX;     break;
133     case SIGNED_BYTE:
134         *min_value = (Real) SCHAR_MIN;
135         *max_value = (Real) SCHAR_MAX;     break;
136     case UNSIGNED_SHORT:
137         *min_value = 0.0;
138         *max_value = (Real) USHRT_MAX;     break;
139     case SIGNED_SHORT:
140         *min_value = (Real) SHRT_MIN;
141         *max_value = (Real) SHRT_MAX;      break;
142     case UNSIGNED_INT:
143         *min_value = 0.0;
144         *max_value = (Real) UINT_MAX;     break;
145     case SIGNED_INT:
146         *min_value = (Real) INT_MIN;
147         *max_value = (Real) INT_MAX;      break;
148     case FLOAT:
149         *min_value = (Real) -FLT_MAX;
150         *max_value = (Real) FLT_MAX;       break;
151     case DOUBLE:
152         *min_value = (Real) -DBL_MAX;
153         *max_value = (Real) DBL_MAX;       break;
154     }
155 }
156 
157 /* ----------------------------- MNI Header -----------------------------------
158 @NAME       : set_multidim_sizes
159 @INPUT      : array
160               sizes
161 @OUTPUT     :
162 @RETURNS    :
163 @DESCRIPTION: Sets the sizes of the array.
164 @METHOD     :
165 @GLOBALS    :
166 @CALLS      :
167 @CREATED    : Sep. 1, 1995    David MacDonald
168 @MODIFIED   :
169 ---------------------------------------------------------------------------- */
170 
set_multidim_sizes(multidim_array * array,int sizes[])171 VIOAPI  void  set_multidim_sizes(
172     multidim_array   *array,
173     int              sizes[] )
174 {
175     int    dim;
176 
177     for_less( dim, 0, array->n_dimensions )
178         array->sizes[dim] = sizes[dim];
179 }
180 
181 /* ----------------------------- MNI Header -----------------------------------
182 @NAME       : get_multidim_sizes
183 @INPUT      : array
184 @OUTPUT     : sizes
185 @RETURNS    :
186 @DESCRIPTION: Passes back the sizes of the multidimensional array.
187 @METHOD     :
188 @GLOBALS    :
189 @CALLS      :
190 @CREATED    : Sep. 1, 1995    David MacDonald
191 @MODIFIED   :
192 ---------------------------------------------------------------------------- */
193 
get_multidim_sizes(multidim_array * array,int sizes[])194 VIOAPI  void  get_multidim_sizes(
195     multidim_array   *array,
196     int              sizes[] )
197 {
198     int   i;
199 
200     for_less( i, 0, array->n_dimensions )
201         sizes[i] = array->sizes[i];
202 }
203 
204 /* ----------------------------- MNI Header -----------------------------------
205 @NAME       : multidim_array_is_alloced
206 @INPUT      : array
207 @OUTPUT     :
208 @RETURNS    : TRUE if array is allocated
209 @DESCRIPTION:
210 @METHOD     :
211 @GLOBALS    :
212 @CALLS      :
213 @CREATED    : Sep. 1, 1995    David MacDonald
214 @MODIFIED   :
215 ---------------------------------------------------------------------------- */
216 
multidim_array_is_alloced(multidim_array * array)217 VIOAPI  BOOLEAN  multidim_array_is_alloced(
218     multidim_array   *array ) {
219 
220     BOOLEAN status = FALSE;
221     void    **p1, ***p2, ****p3, *****p4, ******p5;
222 
223     if( array == NULL ) return( FALSE );
224 
225     switch( array->n_dimensions ) {
226       case  1: p1 = (void **)&array->data;
227                status = ( *p1 != NULL );
228                break;
229       case  2: p2 = (void ***)&array->data;
230                if( *p2 == NULL ) break;
231                if( **p2 == NULL ) break;
232                status = TRUE;
233                break;
234       case  3: p3 = (void ****)&array->data;
235                if( *p3 == NULL ) break;
236                if( **p3 == NULL ) break;
237                if( ***p3 == NULL ) break;
238                status = TRUE;
239                break;
240       case  4: p4 = (void *****)&array->data;
241                if( *p4 == NULL ) break;
242                if( **p4 == NULL ) break;
243                if( ***p4 == NULL ) break;
244                if( ****p4 == NULL ) break;
245                status = TRUE;
246                break;
247       case  5: p5 = (void ******)&array->data;
248                if( *p5 == NULL ) break;
249                if( **p5 == NULL ) break;
250                if( ***p5 == NULL ) break;
251                if( ****p5 == NULL ) break;
252                if( *****p5 == NULL ) break;
253                status = TRUE;
254                break;
255     }
256 
257     return( status );
258 }
259 
260 /* ----------------------------- MNI Header -----------------------------------
261 @NAME       : alloc_multidim_array
262 @INPUT      : array
263 @OUTPUT     :
264 @RETURNS    :
265 @DESCRIPTION: Allocates the data for the multidimensional array.
266 @METHOD     :
267 @GLOBALS    :
268 @CALLS      :
269 @CREATED    : Sep. 1, 1995    David MacDonald
270 @MODIFIED   :
271 ---------------------------------------------------------------------------- */
272 
alloc_multidim_array(multidim_array * array)273 VIOAPI  void  alloc_multidim_array(
274     multidim_array   *array )
275 {
276     int     dim;
277     size_t  type_size, sizes[5];
278     void    *p1, **p2, ***p3, ****p4, *****p5;
279 
280     if( multidim_array_is_alloced( array ) )
281         delete_multidim_array( array );
282 
283     if( array->data_type == NO_DATA_TYPE )
284     {
285         print_error(
286            "Error: cannot allocate array data until size specified.\n" );
287         return;
288     }
289 
290     for_less( dim, 0, array->n_dimensions )
291         sizes[dim] = (size_t) array->sizes[dim];
292 
293     type_size = (size_t) get_type_size( array->data_type );
294 
295     switch( array->n_dimensions )
296     {
297     case  1:
298         ASSIGN_PTR(p1) = alloc_memory_1d( sizes[0], type_size
299                                           _ALLOC_SOURCE_LINE );
300         array->data = (void *) p1;
301         break;
302     case  2:
303         ASSIGN_PTR(p2) = alloc_memory_2d( sizes[0], sizes[1], type_size
304                                           _ALLOC_SOURCE_LINE);
305         array->data = (void *) p2;
306         break;
307     case  3:
308         ASSIGN_PTR(p3) = alloc_memory_3d( sizes[0], sizes[1], sizes[2],
309                                           type_size _ALLOC_SOURCE_LINE );
310         array->data = (void *) p3;
311         break;
312     case  4:
313         ASSIGN_PTR(p4) = alloc_memory_4d( sizes[0], sizes[1],
314                              sizes[2], sizes[3], type_size _ALLOC_SOURCE_LINE );
315         array->data = (void *) p4;
316         break;
317     case  5:
318         ASSIGN_PTR(p5) = alloc_memory_5d( sizes[0], sizes[1],
319                               sizes[2], sizes[3], sizes[4], type_size
320                               _ALLOC_SOURCE_LINE );
321         array->data = (void *) p5;
322         break;
323     }
324 }
325 
326 /* ----------------------------- MNI Header -----------------------------------
327 @NAME       : create_multidim_array
328 @INPUT      : array
329               n_dimensions
330               sizes
331               data_type
332 @OUTPUT     :
333 @RETURNS    :
334 @DESCRIPTION: Creates a multidimensional array.
335 @METHOD     :
336 @GLOBALS    :
337 @CALLS      :
338 @CREATED    : Sep. 1, 1995    David MacDonald
339 @MODIFIED   :
340 ---------------------------------------------------------------------------- */
341 
create_multidim_array(multidim_array * array,int n_dimensions,int sizes[],Data_types data_type)342 VIOAPI   void   create_multidim_array(
343     multidim_array  *array,
344     int             n_dimensions,
345     int             sizes[],
346     Data_types      data_type )
347 {
348     create_empty_multidim_array( array, n_dimensions, data_type );
349     set_multidim_sizes( array, sizes );
350     alloc_multidim_array( array );
351 }
352 
353 /* ----------------------------- MNI Header -----------------------------------
354 @NAME       : delete_multidim_array
355 @INPUT      : array
356 @OUTPUT     :
357 @RETURNS    :
358 @DESCRIPTION: Deletes the multidimensional array.
359 @METHOD     :
360 @GLOBALS    :
361 @CALLS      :
362 @CREATED    : Sep. 1, 1995    David MacDonald
363 @MODIFIED   :
364 ---------------------------------------------------------------------------- */
365 
delete_multidim_array(multidim_array * array)366 VIOAPI  void  delete_multidim_array(
367     multidim_array   *array )
368 {
369     if( array->data == NULL )
370     {
371         print_error( "Warning: cannot free NULL multidim data.\n" );
372         return;
373     }
374 
375     switch( array->n_dimensions )
376     {
377     case  1:  free_memory_1d( (void **) &array->data _ALLOC_SOURCE_LINE );
378               break;
379     case  2:  free_memory_2d( (void ***) &array->data _ALLOC_SOURCE_LINE );
380               break;
381     case  3:  free_memory_3d( (void ****) &array->data _ALLOC_SOURCE_LINE );
382               break;
383     case  4:  free_memory_4d( (void *****) &array->data _ALLOC_SOURCE_LINE );
384               break;
385     case  5:  free_memory_5d( (void ******) &array->data _ALLOC_SOURCE_LINE );
386               break;
387     }
388 
389     array->data = NULL;
390 }
391 
392 /* ----------------------------- MNI Header -----------------------------------
393 @NAME       : get_multidim_n_dimensions
394 @INPUT      : array
395 @OUTPUT     :
396 @RETURNS    : number of dimensions
397 @DESCRIPTION: Returns the number of dimensions of the array
398 @METHOD     :
399 @GLOBALS    :
400 @CALLS      :
401 @CREATED    : June, 1993           David MacDonald
402 @MODIFIED   :
403 ---------------------------------------------------------------------------- */
404 
get_multidim_n_dimensions(multidim_array * array)405 VIOAPI  int  get_multidim_n_dimensions(
406     multidim_array   *array )
407 {
408     return( array->n_dimensions );
409 }
410 
411 /* ----------------------------- MNI Header -----------------------------------
412 @NAME       : copy_multidim_data_reordered
413 @INPUT      : type_size
414               void_dest_ptr
415               n_dest_dims
416               dest_sizes
417               void_src_ptr
418               n_src_dims
419               src_sizes
420               counts
421               to_dest_index
422               use_src_order   - whether to step through arrays in the
423                                 reverse order of src or dest
424 @OUTPUT     :
425 @RETURNS    :
426 @DESCRIPTION: Copies any type of multidimensional data from the src array
427               to the destination array.  to_dest_index is a lookup that
428               converts src indices to destination indices, to allow arbitrary
429               reordering of array data.
430 @METHOD     :
431 @GLOBALS    :
432 @CALLS      :
433 @CREATED    : Sep. 1, 1995    David MacDonald
434 @MODIFIED   : Feb. 27, 1996   D. MacDonald  - made more efficient
435 ---------------------------------------------------------------------------- */
436 
copy_multidim_data_reordered(int type_size,void * void_dest_ptr,int n_dest_dims,int dest_sizes[],void * void_src_ptr,int n_src_dims,int src_sizes[],int counts[],int to_dest_index[],BOOLEAN use_src_order)437 VIOAPI  void  copy_multidim_data_reordered(
438     int                 type_size,
439     void                *void_dest_ptr,
440     int                 n_dest_dims,
441     int                 dest_sizes[],
442     void                *void_src_ptr,
443     int                 n_src_dims,
444     int                 src_sizes[],
445     int                 counts[],
446     int                 to_dest_index[],
447     BOOLEAN             use_src_order )
448 {
449     char      *src_ptr, *dest_ptr;
450     int       d;
451     int       dest_offsets[MAX_DIMENSIONS], src_offsets[MAX_DIMENSIONS];
452     int       dest_offset0, dest_offset1, dest_offset2, dest_offset3;
453     int       dest_offset4;
454     int       src_offset0, src_offset1, src_offset2, src_offset3;
455     int       src_offset4;
456     int       dest_steps[MAX_DIMENSIONS], src_steps[MAX_DIMENSIONS];
457     int       dest_index;
458     int       n_transfer_dims;
459     int       src_axis[MAX_DIMENSIONS], dest_axis[MAX_DIMENSIONS];
460     int       transfer_counts[MAX_DIMENSIONS];
461     int       v0, v1, v2, v3, v4;
462     int       size0, size1, size2, size3, size4;
463     BOOLEAN   full_count_used;
464 
465     /*--- initialize dest */
466 
467     dest_ptr = (char *) void_dest_ptr;
468     dest_steps[n_dest_dims-1] = type_size;
469     for_down( d, n_dest_dims-2, 0 )
470         dest_steps[d] = dest_steps[d+1] * dest_sizes[d+1];
471 
472     /*--- initialize src */
473 
474     src_ptr = (char *) void_src_ptr;
475     src_steps[n_src_dims-1] = type_size;
476     for_down( d, n_src_dims-2, 0 )
477         src_steps[d] = src_steps[d+1] * src_sizes[d+1];
478 
479     n_transfer_dims = 0;
480 
481     if( getenv( "VOLUME_IO_SRC_ORDER" ) )
482         use_src_order = TRUE;
483     else if( getenv( "VOLUME_IO_DEST_ORDER" ) )
484         use_src_order = FALSE;
485 
486     if( use_src_order )
487     {
488         for_less( d, 0, n_src_dims )
489         {
490             dest_index = to_dest_index[d];
491             if( dest_index >= 0 )
492             {
493                 src_axis[n_transfer_dims] = d;
494                 dest_axis[n_transfer_dims] = dest_index;
495                 src_offsets[n_transfer_dims] = src_steps[d];
496                 dest_offsets[n_transfer_dims] = dest_steps[dest_index];
497                 transfer_counts[n_transfer_dims] = counts[d];
498                 ++n_transfer_dims;
499             }
500         }
501     }
502     else
503     {
504         for_less( dest_index, 0, n_dest_dims )
505         {
506             for_less( d, 0, n_src_dims )
507                 if( to_dest_index[d] == dest_index )
508                     break;
509 
510             if( d < n_src_dims )
511             {
512                 src_axis[n_transfer_dims] = d;
513                 dest_axis[n_transfer_dims] = dest_index;
514                 src_offsets[n_transfer_dims] = src_steps[d];
515                 dest_offsets[n_transfer_dims] = dest_steps[dest_index];
516                 transfer_counts[n_transfer_dims] = counts[d];
517                 ++n_transfer_dims;
518             }
519         }
520     }
521 
522     /*--- check if we can transfer more than one at once */
523 
524     full_count_used = TRUE;
525 
526     while( n_transfer_dims > 0 &&
527            src_axis[n_transfer_dims-1] == n_src_dims-1 &&
528            dest_axis[n_transfer_dims-1] == n_dest_dims-1 && full_count_used )
529     {
530         if( transfer_counts[n_transfer_dims-1] != src_sizes[n_src_dims-1] ||
531             transfer_counts[n_transfer_dims-1] != dest_sizes[n_dest_dims-1] )
532         {
533             full_count_used = FALSE;
534         }
535 
536         type_size *= transfer_counts[n_transfer_dims-1];
537         --n_src_dims;
538         --n_dest_dims;
539         --n_transfer_dims;
540     }
541 
542     for_less( d, 0, n_transfer_dims-1 )
543     {
544         src_offsets[d] -= src_offsets[d+1] * transfer_counts[d+1];
545         dest_offsets[d] -= dest_offsets[d+1] * transfer_counts[d+1];
546     }
547 
548     /*--- slide the transfer dims to the last of the 5 dimensions */
549 
550     for_down( d, n_transfer_dims-1, 0 )
551     {
552         src_offsets[d+MAX_DIMENSIONS-n_transfer_dims] = src_offsets[d];
553         dest_offsets[d+MAX_DIMENSIONS-n_transfer_dims] = dest_offsets[d];
554         transfer_counts[d+MAX_DIMENSIONS-n_transfer_dims] = transfer_counts[d];
555     }
556 
557     for_less( d, 0, MAX_DIMENSIONS-n_transfer_dims )
558     {
559         transfer_counts[d] = 1;
560         src_offsets[d] = 0;
561         dest_offsets[d] = 0;
562     }
563 
564     size0 = transfer_counts[0];
565     size1 = transfer_counts[1];
566     size2 = transfer_counts[2];
567     size3 = transfer_counts[3];
568     size4 = transfer_counts[4];
569 
570     src_offset0 = src_offsets[0];
571     src_offset1 = src_offsets[1];
572     src_offset2 = src_offsets[2];
573     src_offset3 = src_offsets[3];
574     src_offset4 = src_offsets[4];
575 
576     dest_offset0 = dest_offsets[0];
577     dest_offset1 = dest_offsets[1];
578     dest_offset2 = dest_offsets[2];
579     dest_offset3 = dest_offsets[3];
580     dest_offset4 = dest_offsets[4];
581 
582     for_less( v0, 0, size0 )
583     {
584         for_less( v1, 0, size1 )
585         {
586             for_less( v2, 0, size2 )
587             {
588                 for_less( v3, 0, size3 )
589                 {
590                     for_less( v4, 0, size4 )
591                     {
592                         (void) memcpy( dest_ptr, src_ptr, (size_t) type_size );
593                         src_ptr += src_offset4;
594                         dest_ptr += dest_offset4;
595                     }
596                     src_ptr += src_offset3;
597                     dest_ptr += dest_offset3;
598                 }
599                 src_ptr += src_offset2;
600                 dest_ptr += dest_offset2;
601             }
602             src_ptr += src_offset1;
603             dest_ptr += dest_offset1;
604         }
605         src_ptr += src_offset0;
606         dest_ptr += dest_offset0;
607     }
608 }
609 
610 /* ----------------------------- MNI Header -----------------------------------
611 @NAME       : copy_multidim_reordered
612 @INPUT      : dest
613               dest_ind
614               src
615               src_ind
616               counts
617               to_dest_index
618 @OUTPUT     :
619 @RETURNS    :
620 @DESCRIPTION: Copies data from src array to dest array, with dimension
621               translation given by to_dest_index[].
622 @METHOD     :
623 @GLOBALS    :
624 @CALLS      :
625 @CREATED    : Sep. 1, 1995    David MacDonald
626 @MODIFIED   :
627 ---------------------------------------------------------------------------- */
628 
copy_multidim_reordered(multidim_array * dest,int dest_ind[],multidim_array * src,int src_ind[],int counts[],int to_dest_index[])629 VIOAPI  void  copy_multidim_reordered(
630     multidim_array      *dest,
631     int                 dest_ind[],
632     multidim_array      *src,
633     int                 src_ind[],
634     int                 counts[],
635     int                 to_dest_index[] )
636 {
637     int       n_src_dims, n_dest_dims, type_size;
638     int       src_sizes[MAX_DIMENSIONS], dest_sizes[MAX_DIMENSIONS];
639     char      *dest_ptr, *src_ptr;
640     void      *void_ptr;
641 
642     type_size = get_type_size( get_multidim_data_type(dest) );
643 
644     /*--- initialize dest */
645 
646     n_dest_dims = get_multidim_n_dimensions( dest );
647     get_multidim_sizes( dest, dest_sizes );
648     GET_MULTIDIM_PTR( void_ptr, *dest, dest_ind[0], dest_ind[1], dest_ind[2],
649                       dest_ind[3], dest_ind[4] );
650     ASSIGN_PTR( dest_ptr ) = void_ptr;
651 
652     /*--- initialize src */
653 
654     n_src_dims = get_multidim_n_dimensions( src );
655     get_multidim_sizes( src, src_sizes );
656     GET_MULTIDIM_PTR( void_ptr, *src, src_ind[0], src_ind[1], src_ind[2],
657                       src_ind[3], src_ind[4] );
658     ASSIGN_PTR( src_ptr ) = void_ptr;
659 
660     copy_multidim_data_reordered( type_size,
661                                   dest_ptr, n_dest_dims, dest_sizes,
662                                   src_ptr, n_src_dims, src_sizes,
663                                   counts, to_dest_index, TRUE );
664 }
665