6
6
# copyright and license terms.
7
7
#
8
8
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
9
- """ Read ECAT format images """
9
+ """ Read ECAT format images
10
+
11
+ An ECAT format image consists of:
12
+
13
+ * a *main header*;
14
+ * at least one *matrix list* (mlist);
15
+
16
+ ECAT thinks of memory locations in terms of *records*. One record is 512
17
+ bytes. Thus record 1 is at 0 bytes, record 2 at 512 bytes, and so on.
18
+
19
+ The matrix list is an array with one row per frame in the data.
20
+
21
+ Columns in the matrix list are:
22
+
23
+ * 0 - Matrix identifier (frame number)
24
+ * 1 - matrix data start record number (subheader stored here)
25
+ * 2 - Last record number of matrix data block.
26
+ * 3 - Matrix status:
27
+ * 1 - exists - rw
28
+ * 2 - exists - ro
29
+ * 3 - matrix deleted
30
+
31
+ There is one sub-header for each image frame (or matrix in the terminology
32
+ above).
33
+
34
+ A sub-header can also be called an *image header*.
35
+
36
+ There is very little documentation of this format, and many of the comments in
37
+ this code come from a combination of trial and error and wild speculation.
38
+
39
+ XMedcon can read and write ECAT 6 format, and read ECAT 7 format: see
40
+ http://xmedcon.sourceforge.net and the ECAT files in the source of XMedCon,
41
+ currently ``libs/tpc/*ecat*`` and ``source/m-ecat*``. Unfortunately XMedCon is
42
+ GPL and some of the header files are adapted from CTI files (called CTI code
43
+ below). It's not clear what the licenses are for these files.
44
+ """
10
45
11
46
import warnings
12
47
from numbers import Integral
20
55
from .wrapstruct import WrapStruct
21
56
from .fileslice import canonical_slicers , predict_shape , slice2outax
22
57
58
+ RECORD_BYTES = 512
23
59
24
60
MAINHDRSZ = 502
25
61
main_header_dtd = [
@@ -307,39 +343,43 @@ def read_mlist(fileobj, endianness):
307
343
308
344
Notes
309
345
-----
310
- A 'record' appears to be a block of 512 bytes.
346
+ A 'record' or ' block' is 512 bytes.
311
347
312
- ``record_no`` in the code below is 1-based. Record 1 may be the main
313
- header, and the mlist records start at 2.
348
+ ``record_no`` in the code below is 1-based. Record 1 is the main header,
349
+ and the mlist records start at record number 2.
314
350
315
- The 512 bytes in a record represents 32 rows of the int32 (nframes, 4)
316
- mlist matrix.
351
+ The 512 bytes in an mlist record represents 32 rows of the int32 (nframes,
352
+ 4) mlist matrix.
317
353
318
354
The first row of these 32 looks like a special row. The 4 values appear
319
355
to be (respectively):
320
356
321
357
* not sure - maybe negative number of mlist rows (out of 31) that are
322
- blank and not used in this record.
323
- * record_no - of next set of mlist entries or 0 if no more entries
324
- * <no idea>
325
- * n_rows - number of mlist rows in this record (between ?0 and 31)
358
+ blank and not used in this record. Called `nfree` but unused in CTI
359
+ code;
360
+ * record_no - of next set of mlist entries or 2 if no more entries. We also
361
+ allow 1 or 0 to signal no more entries;
362
+ * <no idea>. Called `prvblk` in CTI code, so maybe previous record no;
363
+ * n_rows - number of mlist rows in this record (between ?0 and 31) (called
364
+ `nused` in CTI code).
326
365
"""
327
366
dt = np .dtype (np .int32 ) # should this be uint32 given mlist dtype?
328
367
if not endianness is native_code :
329
368
dt = dt .newbyteorder (endianness )
330
369
mlists = []
331
370
mlist_index = 0
332
- mlist_record_no = 2 # 1-based indexing
371
+ mlist_record_no = 2 # 1-based indexing, record with first mlist
333
372
while True :
334
373
# Read record containing mlist entries
335
- fileobj .seek ((mlist_record_no - 1 ) * 512 ) # fix 1-based indexing
374
+ fileobj .seek ((mlist_record_no - 1 ) * RECORD_BYTES ) # fix 1-based indexing
336
375
dat = fileobj .read (128 * 32 ) # isn't this too long? Should be 512?
337
376
rows = np .ndarray (shape = (32 , 4 ), dtype = dt , buffer = dat )
338
- # First row special
339
- v0 , mlist_record_no , v2 , n_rows = rows [0 ]
340
- if not (v0 + n_rows ) == 31 : # Some error condition here?
377
+ # First row special, points to next mlist entries if present
378
+ n_unused , mlist_record_no , _ , n_rows = rows [0 ]
379
+ if not (n_unused + n_rows ) == 31 : # Some error condition here?
341
380
mlist = []
342
381
return mlist
382
+ # Use all but first housekeeping row
343
383
mlists .append (rows [1 :n_rows + 1 ])
344
384
mlist_index += n_rows
345
385
if mlist_record_no <= 2 : # should record_no in (1, 2) be an error?
@@ -432,7 +472,7 @@ def get_series_framenumbers(mlist):
432
472
433
473
434
474
def read_subheaders (fileobj , mlist , endianness ):
435
- """retreive all subheaders and return list of subheader recarrays
475
+ """ Retrieve all subheaders and return list of subheader recarrays
436
476
437
477
Parameters
438
478
----------
@@ -446,6 +486,11 @@ def read_subheaders(fileobj, mlist, endianness):
446
486
* 3 - Matrix status:
447
487
endianness : {'<', '>'}
448
488
little / big endian code
489
+
490
+ Returns
491
+ -------
492
+ subheaders : list
493
+ List of subheader structured arrays
449
494
"""
450
495
subheaders = []
451
496
dt = subhdr_dtype
@@ -470,7 +515,7 @@ def __init__(self,fileobj, hdr):
470
515
471
516
Container for Ecat mlist
472
517
473
- Data for mlist is numpy array shaem (frames, 4)
518
+ Data for mlist is numpy array shape (frames, 4)
474
519
475
520
Columns are:
476
521
0 commit comments