Skip to content

Commit 27fb8de

Browse files
committed
DOC: fill out ECAT comments / docstrings
Trying to get on top of ECAT7 format by filling out docs.
1 parent b0a8006 commit 27fb8de

File tree

1 file changed

+62
-17
lines changed

1 file changed

+62
-17
lines changed

nibabel/ecat.py

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,42 @@
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

1146
import warnings
1247
from numbers import Integral
@@ -20,6 +55,7 @@
2055
from .wrapstruct import WrapStruct
2156
from .fileslice import canonical_slicers, predict_shape, slice2outax
2257

58+
RECORD_BYTES = 512
2359

2460
MAINHDRSZ = 502
2561
main_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

434474
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
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

Comments
 (0)