1# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
2# vi: set ft=python sts=4 ts=4 sw=4 et:
3### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
4#
5#   See COPYING file distributed along with the NiBabel package for the
6#   copyright and license terms.
7#
8### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
9
10import os
11import warnings
12
13import numpy as np
14
15from ..openers import Opener
16from ..ecat import (EcatHeader, EcatSubHeader, EcatImage, read_mlist,
17                    get_frame_order, get_series_framenumbers)
18
19from unittest import TestCase
20import pytest
21
22from numpy.testing import assert_array_equal, assert_array_almost_equal
23
24from ..testing import data_path, suppress_warnings
25from ..tmpdirs import InTemporaryDirectory
26
27from .test_wrapstruct import _TestWrapStructBase
28from .test_fileslice import slicer_samples
29
30ecat_file = os.path.join(data_path, 'tinypet.v')
31
32
33class TestEcatHeader(_TestWrapStructBase):
34    header_class = EcatHeader
35    example_file = ecat_file
36
37    def test_header_size(self):
38        assert self.header_class.template_dtype.itemsize == 512
39
40    def test_empty(self):
41        hdr = self.header_class()
42        assert len(hdr.binaryblock) == 512
43        assert hdr['magic_number'] == b'MATRIX72'
44        assert hdr['sw_version'] == 74
45        assert hdr['num_frames'] == 0
46        assert hdr['file_type'] == 0
47        assert hdr['ecat_calibration_factor'] == 1.0
48
49    def _set_something_into_hdr(self, hdr):
50        # Called from test_bytes test method.  Specific to the header data type
51        hdr['scan_start_time'] = 42
52
53    def test_dtype(self):
54        # dtype not specified in header, only in subheaders
55        hdr = self.header_class()
56        with pytest.raises(NotImplementedError):
57            hdr.get_data_dtype()
58
59    def test_header_codes(self):
60        fid = open(ecat_file, 'rb')
61        hdr = self.header_class()
62        newhdr = hdr.from_fileobj(fid)
63        fid.close()
64        assert newhdr.get_filetype() == 'ECAT7_VOLUME16'
65        assert (newhdr.get_patient_orient() ==
66                     'ECAT7_Unknown_Orientation')
67
68    def test_update(self):
69        hdr = self.header_class()
70        assert hdr['num_frames'] == 0
71        hdr['num_frames'] = 2
72        assert hdr['num_frames'] == 2
73
74    def test_from_eg_file(self):
75        # Example header is big-endian
76        with Opener(self.example_file) as fileobj:
77            hdr = self.header_class.from_fileobj(fileobj, check=False)
78        assert hdr.endianness == '>'
79
80
81class TestEcatMlist(TestCase):
82    header_class = EcatHeader
83    example_file = ecat_file
84
85    def test_mlist(self):
86        fid = open(self.example_file, 'rb')
87        hdr = self.header_class.from_fileobj(fid)
88        mlist = read_mlist(fid, hdr.endianness)
89        fid.seek(0)
90        fid.seek(512)
91        dat = fid.read(128 * 32)
92        dt = np.dtype([('matlist', np.int32)])
93        dt = dt.newbyteorder('>')
94        mats = np.recarray(shape=(32, 4), dtype=dt, buf=dat)
95        fid.close()
96        # tests
97        assert mats['matlist'][0, 0] + mats['matlist'][0, 3] == 31
98        assert get_frame_order(mlist)[0][0] == 0
99        assert get_frame_order(mlist)[0][1] == 16842758.0
100        # test badly ordered mlist
101        badordermlist = np.array([[1.68427540e+07, 3.00000000e+00,
102                                   1.20350000e+04, 1.00000000e+00],
103                                  [1.68427530e+07, 1.20360000e+04,
104                                   2.40680000e+04, 1.00000000e+00],
105                                  [1.68427550e+07, 2.40690000e+04,
106                                   3.61010000e+04, 1.00000000e+00],
107                                  [1.68427560e+07, 3.61020000e+04,
108                                   4.81340000e+04, 1.00000000e+00],
109                                  [1.68427570e+07, 4.81350000e+04,
110                                   6.01670000e+04, 1.00000000e+00],
111                                  [1.68427580e+07, 6.01680000e+04,
112                                   7.22000000e+04, 1.00000000e+00]])
113        with suppress_warnings():  # STORED order
114            assert get_frame_order(badordermlist)[0][0] == 1
115
116    def test_mlist_errors(self):
117        fid = open(self.example_file, 'rb')
118        hdr = self.header_class.from_fileobj(fid)
119        hdr['num_frames'] = 6
120        mlist = read_mlist(fid, hdr.endianness)
121        mlist = np.array([[1.68427540e+07, 3.00000000e+00,
122                           1.20350000e+04, 1.00000000e+00],
123                          [1.68427530e+07, 1.20360000e+04,
124                           2.40680000e+04, 1.00000000e+00],
125                          [1.68427550e+07, 2.40690000e+04,
126                           3.61010000e+04, 1.00000000e+00],
127                          [1.68427560e+07, 3.61020000e+04,
128                           4.81340000e+04, 1.00000000e+00],
129                          [1.68427570e+07, 4.81350000e+04,
130                           6.01670000e+04, 1.00000000e+00],
131                          [1.68427580e+07, 6.01680000e+04,
132                           7.22000000e+04, 1.00000000e+00]])
133        with suppress_warnings():  # STORED order
134            series_framenumbers = get_series_framenumbers(mlist)
135        # first frame stored was actually 2nd frame acquired
136        assert series_framenumbers[0] == 2
137        order = [series_framenumbers[x] for x in sorted(series_framenumbers)]
138        # true series order is [2,1,3,4,5,6], note counting starts at 1
139        assert order == [2, 1, 3, 4, 5, 6]
140        mlist[0, 0] = 0
141        with suppress_warnings():
142            frames_order = get_frame_order(mlist)
143        neworder = [frames_order[x][0] for x in sorted(frames_order)]
144        assert neworder == [1, 2, 3, 4, 5]
145        with suppress_warnings():
146            with pytest.raises(IOError):
147                get_series_framenumbers(mlist)
148
149
150class TestEcatSubHeader(TestCase):
151    header_class = EcatHeader
152    subhdr_class = EcatSubHeader
153    example_file = ecat_file
154    fid = open(example_file, 'rb')
155    hdr = header_class.from_fileobj(fid)
156    mlist = read_mlist(fid, hdr.endianness)
157    subhdr = subhdr_class(hdr, mlist, fid)
158
159    def test_subheader_size(self):
160        assert self.subhdr_class._subhdrdtype.itemsize == 510
161
162    def test_subheader(self):
163        assert self.subhdr.get_shape() == (10, 10, 3)
164        assert self.subhdr.get_nframes() == 1
165        assert (self.subhdr.get_nframes() ==
166                     len(self.subhdr.subheaders))
167        assert self.subhdr._check_affines() is True
168        assert_array_almost_equal(np.diag(self.subhdr.get_frame_affine()),
169                                  np.array([2.20241979, 2.20241979, 3.125, 1.]))
170        assert self.subhdr.get_zooms()[0] == 2.20241978764534
171        assert self.subhdr.get_zooms()[2] == 3.125
172        assert self.subhdr._get_data_dtype(0) == np.int16
173        #assert_equal(self.subhdr._get_frame_offset(), 1024)
174        assert self.subhdr._get_frame_offset() == 1536
175        dat = self.subhdr.raw_data_from_fileobj()
176        assert dat.shape == self.subhdr.get_shape()
177        assert self.subhdr.subheaders[0]['scale_factor'].item() == 1.0
178        ecat_calib_factor = self.hdr['ecat_calibration_factor']
179        assert ecat_calib_factor == 25007614.0
180
181
182class TestEcatImage(TestCase):
183    image_class = EcatImage
184    example_file = ecat_file
185    img = image_class.load(example_file)
186
187    def test_file(self):
188        assert (self.img.file_map['header'].filename ==
189                     self.example_file)
190        assert (self.img.file_map['image'].filename ==
191                     self.example_file)
192
193    def test_save(self):
194        tmp_file = 'tinypet_tmp.v'
195        with InTemporaryDirectory():
196            self.img.to_filename(tmp_file)
197            other = self.image_class.load(tmp_file)
198            assert_array_equal(self.img.get_fdata(), other.get_fdata())
199            # Delete object holding reference to temporary file to make Windows
200            # happier.
201            del other
202
203    def test_data(self):
204        dat = self.img.get_fdata()
205        assert dat.shape == self.img.shape
206        frame = self.img.get_frame(0)
207        assert_array_equal(frame, dat[:, :, :, 0])
208
209    def test_array_proxy(self):
210        # Get the cached data copy
211        dat = self.img.get_fdata()
212        # Make a new one to test arrayproxy
213        img = self.image_class.load(self.example_file)
214        data_prox = img.dataobj
215        data2 = np.array(data_prox)
216        assert_array_equal(data2, dat)
217        # Check it rereads
218        data3 = np.array(data_prox)
219        assert_array_equal(data3, dat)
220
221    def test_array_proxy_slicing(self):
222        # Test slicing of array proxy
223        arr = self.img.get_fdata()
224        prox = self.img.dataobj
225        assert prox.is_proxy
226        for sliceobj in slicer_samples(self.img.shape):
227            assert_array_equal(arr[sliceobj], prox[sliceobj])
228
229    def test_isolation(self):
230        # Test image isolated from external changes to affine
231        img_klass = self.image_class
232        arr, aff, hdr, sub_hdr, mlist = (self.img.get_fdata(),
233                                         self.img.affine,
234                                         self.img.header,
235                                         self.img.get_subheaders(),
236                                         self.img.get_mlist())
237        img = img_klass(arr, aff, hdr, sub_hdr, mlist)
238        assert_array_equal(img.affine, aff)
239        aff[0, 0] = 99
240        assert not np.all(img.affine == aff)
241
242    def test_get_affine_deprecated(self):
243        with pytest.deprecated_call(match="from version: 2.1"):
244            aff = self.img.get_affine()
245        assert np.array_equal(aff, self.img.affine)
246
247    def test_float_affine(self):
248        # Check affines get converted to float
249        img_klass = self.image_class
250        arr, aff, hdr, sub_hdr, mlist = (self.img.get_fdata(),
251                                         self.img.affine,
252                                         self.img.header,
253                                         self.img.get_subheaders(),
254                                         self.img.get_mlist())
255        img = img_klass(arr, aff.astype(np.float32), hdr, sub_hdr, mlist)
256        assert img.affine.dtype == np.dtype(np.float64)
257        img = img_klass(arr, aff.astype(np.int16), hdr, sub_hdr, mlist)
258        assert img.affine.dtype == np.dtype(np.float64)
259
260    def test_data_regression(self):
261        # Test whether data read has changed since 1.3.0
262        # These values came from reading the example image using nibabel 1.3.0
263        vals = dict(max=248750736458.0,
264                    min=1125342630.0,
265                    mean=117907565661.46666)
266        data = self.img.get_fdata()
267        assert data.max() == vals['max']
268        assert data.min() == vals['min']
269        assert_array_almost_equal(data.mean(), vals['mean'])
270
271    def test_mlist_regression(self):
272        # Test mlist is as same as for nibabel 1.3.0
273        assert_array_equal(self.img.get_mlist(),
274                           [[16842758, 3, 3011, 1]])
275
276
277def test_from_filespec_deprecation():
278    # Check from_filespec raises Deprecation
279    with pytest.deprecated_call() as w:
280        # No warning for standard load
281        img_loaded = EcatImage.load(ecat_file)
282        assert len(w) == 0
283        # Warning for from_filespec
284        img_speced = EcatImage.from_filespec(ecat_file)
285        assert len(w) == 1
286        assert_array_equal(img_loaded.get_fdata(), img_speced.get_fdata())
287