66# copyright and license terms.
77#
88### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
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+ """
1045
1146import warnings
1247from numbers import Integral
2055from .wrapstruct import WrapStruct
2156from .fileslice import canonical_slicers , predict_shape , slice2outax
2257
58+ RECORD_BYTES = 512
2359
2460MAINHDRSZ = 502
2561main_header_dtd = [
@@ -307,39 +343,43 @@ def read_mlist(fileobj, endianness):
307343
308344 Notes
309345 -----
310- A 'record' appears to be a block of 512 bytes.
346+ A 'record' or ' block' is 512 bytes.
311347
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.
314350
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.
317353
318354 The first row of these 32 looks like a special row. The 4 values appear
319355 to be (respectively):
320356
321357 * 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).
326365 """
327366 dt = np .dtype (np .int32 ) # should this be uint32 given mlist dtype?
328367 if not endianness is native_code :
329368 dt = dt .newbyteorder (endianness )
330369 mlists = []
331370 mlist_index = 0
332- mlist_record_no = 2 # 1-based indexing
371+ mlist_record_no = 2 # 1-based indexing, record with first mlist
333372 while True :
334373 # 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
336375 dat = fileobj .read (128 * 32 ) # isn't this too long? Should be 512?
337376 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?
341380 mlist = []
342381 return mlist
382+ # Use all but first housekeeping row
343383 mlists .append (rows [1 :n_rows + 1 ])
344384 mlist_index += n_rows
345385 if mlist_record_no <= 2 : # should record_no in (1, 2) be an error?
@@ -432,7 +472,7 @@ def get_series_framenumbers(mlist):
432472
433473
434474def 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
436476
437477 Parameters
438478 ----------
@@ -446,6 +486,11 @@ def read_subheaders(fileobj, mlist, endianness):
446486 * 3 - Matrix status:
447487 endianness : {'<', '>'}
448488 little / big endian code
489+
490+ Returns
491+ -------
492+ subheaders : list
493+ List of subheader structured arrays
449494 """
450495 subheaders = []
451496 dt = subhdr_dtype
@@ -470,7 +515,7 @@ def __init__(self,fileobj, hdr):
470515
471516 Container for Ecat mlist
472517
473- Data for mlist is numpy array shaem (frames, 4)
518+ Data for mlist is numpy array shape (frames, 4)
474519
475520 Columns are:
476521
0 commit comments