summaryrefslogtreecommitdiff
path: root/src/silx/math/test
diff options
context:
space:
mode:
Diffstat (limited to 'src/silx/math/test')
-rw-r--r--src/silx/math/test/__init__.py23
-rw-r--r--src/silx/math/test/benchmark_combo.py192
-rw-r--r--src/silx/math/test/histo_benchmarks.py269
-rw-r--r--src/silx/math/test/test_HistogramndLut_nominal.py571
-rw-r--r--src/silx/math/test/test_calibration.py145
-rw-r--r--src/silx/math/test/test_colormap.py269
-rw-r--r--src/silx/math/test/test_combo.py207
-rw-r--r--src/silx/math/test/test_histogramnd_error.py519
-rw-r--r--src/silx/math/test/test_histogramnd_nominal.py937
-rw-r--r--src/silx/math/test/test_histogramnd_vs_np.py826
-rw-r--r--src/silx/math/test/test_interpolate.py125
-rw-r--r--src/silx/math/test/test_marchingcubes.py174
12 files changed, 4257 insertions, 0 deletions
diff --git a/src/silx/math/test/__init__.py b/src/silx/math/test/__init__.py
new file mode 100644
index 0000000..ad9836c
--- /dev/null
+++ b/src/silx/math/test/__init__.py
@@ -0,0 +1,23 @@
+# coding: utf-8
+# /*##########################################################################
+# Copyright (C) 2016-2019 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ############################################################################*/
diff --git a/src/silx/math/test/benchmark_combo.py b/src/silx/math/test/benchmark_combo.py
new file mode 100644
index 0000000..c12f590
--- /dev/null
+++ b/src/silx/math/test/benchmark_combo.py
@@ -0,0 +1,192 @@
+# coding: utf-8
+# /*##########################################################################
+# Copyright (C) 2016-2017 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ############################################################################*/
+"""Benchmarks of the combo module"""
+
+from __future__ import division
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "17/01/2018"
+
+
+import logging
+import os.path
+import time
+import unittest
+
+import numpy
+
+from silx.test.utils import temp_dir
+from silx.utils.testutils import ParametricTestCase
+
+from silx.math import combo
+
+_logger = logging.getLogger(__name__)
+_logger.setLevel(logging.DEBUG)
+
+
+class TestBenchmarkMinMax(ParametricTestCase):
+ """Benchmark of min max combo"""
+
+ DTYPES = ('float32', 'float64',
+ 'int8', 'int16', 'int32', 'int64',
+ 'uint8', 'uint16', 'uint32', 'uint64')
+
+ ARANGE = 'ascent', 'descent', 'random'
+
+ EXPONENT = 3, 4, 5, 6, 7
+
+ def test_benchmark_min_max(self):
+ """Benchmark min_max without min positive.
+
+ Compares with:
+
+ - numpy.nanmin, numpy.nanmax and
+ - numpy.argmin, numpy.argmax
+
+ It runs bench for different types, different data size and 3
+ data sets: increasing , decreasing and random data.
+ """
+ durations = {'min/max': [], 'argmin/max': [], 'combo': []}
+
+ _logger.info('Benchmark against argmin/argmax and nanmin/nanmax')
+
+ for dtype in self.DTYPES:
+ for arange in self.ARANGE:
+ for exponent in self.EXPONENT:
+ size = 10**exponent
+ with self.subTest(dtype=dtype, size=size, arange=arange):
+ if arange == 'ascent':
+ data = numpy.arange(0, size, 1, dtype=dtype)
+ elif arange == 'descent':
+ data = numpy.arange(size, 0, -1, dtype=dtype)
+ else:
+ if dtype in ('float32', 'float64'):
+ data = numpy.random.random(size)
+ else:
+ data = numpy.random.randint(10**6, size=size)
+ data = numpy.array(data, dtype=dtype)
+
+ start = time.time()
+ ref_min = numpy.nanmin(data)
+ ref_max = numpy.nanmax(data)
+ durations['min/max'].append(time.time() - start)
+
+ start = time.time()
+ ref_argmin = numpy.argmin(data)
+ ref_argmax = numpy.argmax(data)
+ durations['argmin/max'].append(time.time() - start)
+
+ start = time.time()
+ result = combo.min_max(data, min_positive=False)
+ durations['combo'].append(time.time() - start)
+
+ _logger.info(
+ '%s-%s-10**%d\tx%.2f argmin/max x%.2f min/max',
+ dtype, arange, exponent,
+ durations['argmin/max'][-1] / durations['combo'][-1],
+ durations['min/max'][-1] / durations['combo'][-1])
+
+ self.assertEqual(result.minimum, ref_min)
+ self.assertEqual(result.maximum, ref_max)
+ self.assertEqual(result.argmin, ref_argmin)
+ self.assertEqual(result.argmax, ref_argmax)
+
+ self.show_results('min/max', durations, 'combo')
+
+ def test_benchmark_min_pos(self):
+ """Benchmark min_max wit min positive.
+
+ Compares with:
+
+ - numpy.nanmin(data[data > 0]); numpy.nanmin(pos); numpy.nanmax(pos)
+
+ It runs bench for different types, different data size and 3
+ data sets: increasing , decreasing and random data.
+ """
+ durations = {'min/max': [], 'combo': []}
+
+ _logger.info('Benchmark against min, max, positive min')
+
+ for dtype in self.DTYPES:
+ for arange in self.ARANGE:
+ for exponent in self.EXPONENT:
+ size = 10**exponent
+ with self.subTest(dtype=dtype, size=size, arange=arange):
+ if arange == 'ascent':
+ data = numpy.arange(0, size, 1, dtype=dtype)
+ elif arange == 'descent':
+ data = numpy.arange(size, 0, -1, dtype=dtype)
+ else:
+ if dtype in ('float32', 'float64'):
+ data = numpy.random.random(size)
+ else:
+ data = numpy.random.randint(10**6, size=size)
+ data = numpy.array(data, dtype=dtype)
+
+ start = time.time()
+ ref_min_positive = numpy.nanmin(data[data > 0])
+ ref_min = numpy.nanmin(data)
+ ref_max = numpy.nanmax(data)
+ durations['min/max'].append(time.time() - start)
+
+ start = time.time()
+ result = combo.min_max(data, min_positive=True)
+ durations['combo'].append(time.time() - start)
+
+ _logger.info(
+ '%s-%s-10**%d\tx%.2f min/minpos/max',
+ dtype, arange, exponent,
+ durations['min/max'][-1] / durations['combo'][-1])
+
+ self.assertEqual(result.min_positive, ref_min_positive)
+ self.assertEqual(result.minimum, ref_min)
+ self.assertEqual(result.maximum, ref_max)
+
+ self.show_results('min/max/min positive', durations, 'combo')
+
+ def show_results(self, title, durations, ref_key):
+ try:
+ from matplotlib import pyplot
+ except ImportError:
+ _logger.warning('matplotlib not available')
+ return
+
+ pyplot.title(title)
+ pyplot.xlabel('-'.join(self.DTYPES))
+ pyplot.ylabel('duration (sec)')
+ for label, values in durations.items():
+ pyplot.semilogy(values, label=label)
+ pyplot.legend()
+ pyplot.show()
+
+ pyplot.title(title)
+ pyplot.xlabel('-'.join(self.DTYPES))
+ pyplot.ylabel('Duration ratio')
+ ref = numpy.array(durations[ref_key])
+ for label, values in durations.items():
+ values = numpy.array(values)
+ pyplot.plot(values/ref, label=label + ' / ' + ref_key)
+ pyplot.legend()
+ pyplot.show()
diff --git a/src/silx/math/test/histo_benchmarks.py b/src/silx/math/test/histo_benchmarks.py
new file mode 100644
index 0000000..7d3216d
--- /dev/null
+++ b/src/silx/math/test/histo_benchmarks.py
@@ -0,0 +1,269 @@
+# coding: utf-8
+# /*##########################################################################
+# Copyright (C) 2016 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ############################################################################*/
+"""
+histogramnd benchmarks, vs numpy.histogramdd (bin counts and weights).
+"""
+
+import numpy as np
+
+import time
+
+from silx.math import histogramnd
+
+
+def print_times(t0s, t1s, t2s, t3s):
+ c_times = t1s - t0s
+ np_times = t2s - t1s
+ np_w_times = t3s - t2s
+
+ time_txt = 'min : {0: <7.3f}; max : {1: <7.3f}; avg : {2: <7.3f}'
+
+ print('\tTimes :')
+ print('\tC : ' + time_txt.format(c_times.min(),
+ c_times.max(),
+ c_times.mean()))
+ print('\tNP : ' + time_txt.format(np_times.min(),
+ np_times.max(),
+ np_times.mean()))
+ print('\tNP(W) : ' + time_txt.format(np_w_times.min(),
+ np_w_times.max(),
+ np_w_times.mean()))
+
+
+def commpare_results(txt,
+ times,
+ result_c,
+ result_np,
+ result_np_w,
+ sample,
+ weights,
+ raise_ex=False):
+
+ if result_np:
+ hits_cmp = np.array_equal(result_c[0], result_np[0])
+ else:
+ hits_cmp = None
+
+ if result_np_w and result_c[1] is not None:
+ weights_cmp = np.array_equal(result_c[1], result_np_w[0])
+ else:
+ weights_cmp = None
+
+ if((hits_cmp is not None and not hits_cmp) or
+ (weights_cmp is not None and not weights_cmp)):
+ err_txt = (txt + ' : results arent the same : '
+ 'hits : {0}, '
+ 'weights : {1}.'
+ ''.format('OK' if hits_cmp else 'NOK',
+ 'OK' if weights_cmp else 'NOK'))
+ print('\t' + err_txt)
+ if raise_ex:
+ raise ValueError(err_txt)
+ return False
+
+ result_txt = ' : results OK. c : {0: <7.3f};'.format(times[0])
+ if result_np or result_np_w:
+ result_txt += (' np : {0: <7.3f}; '
+ 'np (weights) {1: <7.3f}.'
+ ''.format(times[1], times[2]))
+ print('\t' + txt + result_txt)
+ return True
+
+
+def benchmark(n_loops,
+ sample_shape,
+ sample_rng,
+ weights_rng,
+ histo_range,
+ n_bins,
+ weight_min,
+ weight_max,
+ last_bin_closed,
+ dtype=np.double,
+ do_weights=True,
+ do_numpy=True):
+
+ int_min = 0
+ int_max = 100000
+
+ sample = np.random.randint(int_min,
+ high=int_max,
+ size=sample_shape).astype(np.double)
+ sample = (sample_rng[0] +
+ (sample - int_min) *
+ (sample_rng[1] - sample_rng[0]) /
+ (int_max - int_min))
+ sample = sample.astype(dtype)
+
+ if do_weights:
+ weights = np.random.randint(int_min,
+ high=int_max,
+ size=(ssetup.pyample_shape[0],))
+ weights = weights.astype(np.double)
+ weights = (weights_rng[0] +
+ (weights - int_min) *
+ (weights_rng[1] - weights_rng[0]) /
+ (int_max - int_min))
+ else:
+ weights = None
+
+ t0s = []
+ t1s = []
+ t2s = []
+ t3s = []
+
+ for i in range(n_loops):
+ t0s.append(time.time())
+ result_c = histogramnd(sample,
+ histo_range,
+ n_bins,
+ weights=weights,
+ weight_min=weight_min,
+ weight_max=weight_max,
+ last_bin_closed=last_bin_closed)
+ t1s.append(time.time())
+ if do_numpy:
+ result_np = np.histogramdd(sample,
+ bins=n_bins,
+ range=histo_range)
+ t2s.append(time.time())
+ result_np_w = np.histogramdd(sample,
+ bins=n_bins,
+ range=histo_range,
+ weights=weights)
+ t3s.append(time.time())
+ else:
+ result_np = None
+ result_np_w = None
+ t2s.append(0)
+ t3s.append(0)
+
+ commpare_results('Run {0}'.format(i),
+ [t1s[-1] - t0s[-1], t2s[-1] - t1s[-1], t3s[-1] - t2s[-1]],
+ result_c,
+ result_np,
+ result_np_w,
+ sample,
+ weights)
+
+ print_times(np.array(t0s), np.array(t1s), np.array(t2s), np.array(t3s))
+
+
+def run_benchmark(dtype=np.double,
+ do_weights=True,
+ do_numpy=True):
+ n_loops = 5
+
+ weights_rng = [0., 100.]
+ sample_rng = [0., 100.]
+
+ weight_min = None
+ weight_max = None
+ last_bin_closed = True
+
+ # ====================================================
+ # ====================================================
+ # 1D
+ # ====================================================
+ # ====================================================
+
+ print('==========================')
+ print(' 1D [{0}]'.format(dtype))
+ print('==========================')
+ sample_shape = (10**7,)
+ histo_range = [[0., 100.]]
+ n_bins = 30
+
+ benchmark(n_loops,
+ sample_shape,
+ sample_rng,
+ weights_rng,
+ histo_range,
+ n_bins,
+ weight_min,
+ weight_max,
+ last_bin_closed,
+ dtype=dtype,
+ do_weights=True,
+ do_numpy=do_numpy)
+
+ # ====================================================
+ # ====================================================
+ # 2D
+ # ====================================================
+ # ====================================================
+
+ print('==========================')
+ print(' 2D [{0}]'.format(dtype))
+ print('==========================')
+ sample_shape = (10**7, 2)
+ histo_range = [[0., 100.], [0., 100.]]
+ n_bins = 30
+
+ benchmark(n_loops,
+ sample_shape,
+ sample_rng,
+ weights_rng,
+ histo_range,
+ n_bins,
+ weight_min,
+ weight_max,
+ last_bin_closed,
+ dtype=dtype,
+ do_weights=True,
+ do_numpy=do_numpy)
+
+ # ====================================================
+ # ====================================================
+ # 3D
+ # ====================================================
+ # ====================================================
+
+ print('==========================')
+ print(' 3D [{0}]'.format(dtype))
+ print('==========================')
+ sample_shape = (10**7, 3)
+ histo_range = np.array([[0., 100.], [0., 100.], [0., 100.]])
+ n_bins = 30
+
+ benchmark(n_loops,
+ sample_shape,
+ sample_rng,
+ weights_rng,
+ histo_range,
+ n_bins,
+ weight_min,
+ weight_max,
+ last_bin_closed,
+ dtype=dtype,
+ do_weights=True,
+ do_numpy=do_numpy)
+
+if __name__ == '__main__':
+ types = (np.double, np.int32, np.float32,)
+
+ for t in types:
+ run_benchmark(t,
+ do_weights=True,
+ do_numpy=True)
diff --git a/src/silx/math/test/test_HistogramndLut_nominal.py b/src/silx/math/test/test_HistogramndLut_nominal.py
new file mode 100644
index 0000000..52e003c
--- /dev/null
+++ b/src/silx/math/test/test_HistogramndLut_nominal.py
@@ -0,0 +1,571 @@
+# coding: utf-8
+# /*##########################################################################
+# Copyright (C) 2016-2019 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ############################################################################*/
+"""
+Nominal tests of the HistogramndLut function.
+"""
+
+import unittest
+
+import numpy as np
+
+from silx.math import HistogramndLut
+
+
+def _get_bin_edges(histo_range, n_bins, n_dims):
+ edges = []
+ for i_dim in range(n_dims):
+ edges.append(histo_range[i_dim, 0] +
+ np.arange(n_bins[i_dim] + 1) *
+ (histo_range[i_dim, 1] - histo_range[i_dim, 0]) /
+ n_bins[i_dim])
+ return tuple(edges)
+
+
+# ==============================================================
+# ==============================================================
+# ==============================================================
+
+
+class _TestHistogramndLut_nominal(unittest.TestCase):
+ """
+ Unit tests of the HistogramndLut class.
+ """
+ __test__ = False # ignore abstract class
+
+ ndims = None
+
+ def setUp(self):
+ ndims = self.ndims
+ if ndims is None:
+ self.skipTest("Abstract class")
+ self.tested_dim = ndims-1
+
+ if ndims is None:
+ raise ValueError('ndims class member not set.')
+
+ sample = np.array([5.5, -3.3,
+ 0., -0.5,
+ 3.3, 8.8,
+ -7.7, 6.0,
+ -4.0])
+
+ weights = np.array([500.5, -300.3,
+ 0.01, -0.5,
+ 300.3, 800.8,
+ -700.7, 600.6,
+ -400.4])
+
+ n_elems = len(sample)
+
+ if ndims == 1:
+ shape = (n_elems,)
+ else:
+ shape = (n_elems, ndims)
+
+ self.sample = np.zeros(shape=shape, dtype=sample.dtype)
+ if ndims == 1:
+ self.sample = sample
+ else:
+ self.sample[..., ndims-1] = sample
+
+ self.weights = weights
+
+ # the tests are performed along one dimension,
+ # all the other bins indices along the other dimensions
+ # are expected to be 2
+ # (e.g : when testing a 2D sample : [0, x] will go into
+ # bin [2, y] because of the bin ranges [-2, 2] and n_bins = 4
+ # for the first dimension)
+ self.other_axes_index = 2
+ self.histo_range = np.repeat([[-2., 2.]], ndims, axis=0)
+ self.histo_range[ndims-1] = [-4., 6.]
+
+ self.n_bins = np.array([4]*ndims)
+ self.n_bins[ndims-1] = 5
+
+ if ndims == 1:
+ def fill_histo(h, v, dim, op=None):
+ if op:
+ h[:] = op(h[:], v)
+ else:
+ h[:] = v
+ self.fill_histo = fill_histo
+ else:
+ def fill_histo(h, v, dim, op=None):
+ idx = [self.other_axes_index]*len(h.shape)
+ idx[dim] = slice(0, None)
+ idx = tuple(idx)
+ if op:
+ h[idx] = op(h[idx], v)
+ else:
+ h[idx] = v
+ self.fill_histo = fill_histo
+
+ def test_nominal_bin_edges(self):
+
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ self.n_bins)
+
+ bin_edges = instance.bins_edges
+
+ expected_edges = _get_bin_edges(self.histo_range,
+ self.n_bins,
+ self.ndims)
+
+ for i_edges, edges in enumerate(expected_edges):
+ self.assertTrue(np.array_equal(bin_edges[i_edges],
+ expected_edges[i_edges]),
+ msg='Testing bin_edges for dim {0}'
+ ''.format(i_edges+1))
+
+ def test_nominal_histo_range(self):
+
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ self.n_bins)
+
+ histo_range = instance.histo_range
+
+ self.assertTrue(np.array_equal(histo_range, self.histo_range))
+
+ def test_nominal_last_bin_closed(self):
+
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ self.n_bins)
+
+ last_bin_closed = instance.last_bin_closed
+
+ self.assertEqual(last_bin_closed, False)
+
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ self.n_bins,
+ last_bin_closed=True)
+
+ last_bin_closed = instance.last_bin_closed
+
+ self.assertEqual(last_bin_closed, True)
+
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ self.n_bins,
+ last_bin_closed=False)
+
+ last_bin_closed = instance.last_bin_closed
+
+ self.assertEqual(last_bin_closed, False)
+
+ def test_nominal_n_bins_array(self):
+
+ test_n_bins = np.arange(self.ndims) + 10
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ test_n_bins)
+
+ n_bins = instance.n_bins
+
+ self.assertTrue(np.array_equal(test_n_bins, n_bins))
+
+ def test_nominal_n_bins_scalar(self):
+
+ test_n_bins = 10
+ expected_n_bins = np.array([test_n_bins] * self.ndims)
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ test_n_bins)
+
+ n_bins = instance.n_bins
+
+ self.assertTrue(np.array_equal(expected_n_bins, n_bins))
+
+ def test_nominal_histo_ref(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ self.n_bins)
+
+ instance.accumulate(self.weights)
+
+ histo = instance.histo()
+ w_histo = instance.weighted_histo()
+ histo_ref = instance.histo(copy=False)
+ w_histo_ref = instance.weighted_histo(copy=False)
+
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(w_histo, expected_c))
+ self.assertTrue(np.array_equal(histo_ref, expected_h))
+ self.assertTrue(np.array_equal(w_histo_ref, expected_c))
+
+ histo_ref[0, ...] = histo_ref[0, ...] + 10
+ w_histo_ref[0, ...] = w_histo_ref[0, ...] + 20
+
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(w_histo, expected_c))
+ self.assertFalse(np.array_equal(histo_ref, expected_h))
+ self.assertFalse(np.array_equal(w_histo_ref, expected_c))
+
+ histo_2 = instance.histo()
+ w_histo_2 = instance.weighted_histo()
+
+ self.assertFalse(np.array_equal(histo_2, expected_h))
+ self.assertFalse(np.array_equal(w_histo_2, expected_c))
+ self.assertTrue(np.array_equal(histo_2, histo_ref))
+ self.assertTrue(np.array_equal(w_histo_2, w_histo_ref))
+
+ def test_nominal_accumulate_once(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ self.n_bins)
+
+ instance.accumulate(self.weights)
+
+ histo = instance.histo()
+ w_histo = instance.weighted_histo()
+
+ self.assertEqual(w_histo.dtype, np.float64)
+ self.assertEqual(histo.dtype, np.uint32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(w_histo, expected_c))
+ self.assertTrue(np.array_equal(instance.histo(), expected_h))
+ self.assertTrue(np.array_equal(instance.weighted_histo(),
+ expected_c))
+
+ def test_nominal_accumulate_twice(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ # calling accumulate twice
+ expected_h *= 2
+ expected_c *= 2
+
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ self.n_bins)
+
+ instance.accumulate(self.weights)
+
+ instance.accumulate(self.weights)
+
+ histo = instance.histo()
+ w_histo = instance.weighted_histo()
+
+ self.assertEqual(w_histo.dtype, np.float64)
+ self.assertEqual(histo.dtype, np.uint32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(w_histo, expected_c))
+ self.assertTrue(np.array_equal(instance.histo(), expected_h))
+ self.assertTrue(np.array_equal(instance.weighted_histo(),
+ expected_c))
+
+ def test_nominal_apply_lut_once(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ self.n_bins)
+
+ histo, w_histo = instance.apply_lut(self.weights)
+
+ self.assertEqual(w_histo.dtype, np.float64)
+ self.assertEqual(histo.dtype, np.uint32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(w_histo, expected_c))
+ self.assertEqual(instance.histo(), None)
+ self.assertEqual(instance.weighted_histo(), None)
+
+ def test_nominal_apply_lut_twice(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ # calling apply_lut twice
+ expected_h *= 2
+ expected_c *= 2
+
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ self.n_bins)
+
+ histo, w_histo = instance.apply_lut(self.weights)
+ histo_2, w_histo_2 = instance.apply_lut(self.weights,
+ histo=histo,
+ weighted_histo=w_histo)
+
+ self.assertEqual(id(histo), id(histo_2))
+ self.assertEqual(id(w_histo), id(w_histo_2))
+ self.assertEqual(w_histo.dtype, np.float64)
+ self.assertEqual(histo.dtype, np.uint32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(w_histo, expected_c))
+ self.assertEqual(instance.histo(), None)
+ self.assertEqual(instance.weighted_histo(), None)
+
+ def test_nominal_accumulate_last_bin_closed(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 2])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 1101.1])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ self.n_bins,
+ last_bin_closed=True)
+
+ instance.accumulate(self.weights)
+
+ histo = instance.histo()
+ w_histo = instance.weighted_histo()
+
+ self.assertEqual(w_histo.dtype, np.float64)
+ self.assertEqual(histo.dtype, np.uint32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(w_histo, expected_c))
+
+ def test_nominal_accumulate_weight_min_max(self):
+ """
+ """
+ weight_min = -299.9
+ weight_max = 499.9
+
+ expected_h_tpl = np.array([0, 1, 1, 1, 0])
+ expected_c_tpl = np.array([0., -0.5, 0.01, 300.3, 0.])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ self.n_bins)
+
+ instance.accumulate(self.weights,
+ weight_min=weight_min,
+ weight_max=weight_max)
+
+ histo = instance.histo()
+ w_histo = instance.weighted_histo()
+
+ self.assertEqual(w_histo.dtype, np.float64)
+ self.assertEqual(histo.dtype, np.uint32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(w_histo, expected_c))
+
+ def test_nominal_accumulate_forced_int32(self):
+ """
+ double weights, int32 weighted_histogram
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700, 0, 0, 300, 500])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ self.n_bins,
+ dtype=np.int32)
+
+ instance.accumulate(self.weights)
+
+ histo = instance.histo()
+ w_histo = instance.weighted_histo()
+
+ self.assertEqual(w_histo.dtype, np.int32)
+ self.assertEqual(histo.dtype, np.uint32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(w_histo, expected_c))
+
+ def test_nominal_accumulate_forced_float32(self):
+ """
+ int32 weights, float32 weighted_histogram
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700., 0., 0., 300., 500.])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.float32)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ self.n_bins,
+ dtype=np.float32)
+
+ instance.accumulate(self.weights.astype(np.int32))
+
+ histo = instance.histo()
+ w_histo = instance.weighted_histo()
+
+ self.assertEqual(w_histo.dtype, np.float32)
+ self.assertEqual(histo.dtype, np.uint32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(w_histo, expected_c))
+
+ def test_nominal_accumulate_int32(self):
+ """
+ int32 weights
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700, 0, 0, 300, 500])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.int32)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ self.n_bins)
+
+ instance.accumulate(self.weights.astype(np.int32))
+
+ histo = instance.histo()
+ w_histo = instance.weighted_histo()
+
+ self.assertEqual(w_histo.dtype, np.int32)
+ self.assertEqual(histo.dtype, np.uint32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(w_histo, expected_c))
+
+ def test_nominal_accumulate_int32_double(self):
+ """
+ int32 weights
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700, 0, 0, 300, 500])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.int32)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ instance = HistogramndLut(self.sample,
+ self.histo_range,
+ self.n_bins)
+
+ instance.accumulate(self.weights.astype(np.int32))
+ instance.accumulate(self.weights)
+
+ histo = instance.histo()
+ w_histo = instance.weighted_histo()
+
+ expected_h *= 2
+ expected_c *= 2
+
+ self.assertEqual(w_histo.dtype, np.int32)
+ self.assertEqual(histo.dtype, np.uint32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(w_histo, expected_c))
+
+ def testNoneNativeTypes(self):
+ type = self.sample.dtype.newbyteorder("B")
+ sampleB = self.sample.astype(type)
+
+ type = self.sample.dtype.newbyteorder("L")
+ sampleL = self.sample.astype(type)
+
+ histo_inst = HistogramndLut(sampleB,
+ self.histo_range,
+ self.n_bins)
+
+ histo_inst = HistogramndLut(sampleL,
+ self.histo_range,
+ self.n_bins)
+
+
+class TestHistogramndLut_nominal_1d(_TestHistogramndLut_nominal):
+ __test__ = True # because _TestHistogramndLut_nominal is ignored
+ ndims = 1
+
+
+class TestHistogramndLut_nominal_2d(_TestHistogramndLut_nominal):
+ __test__ = True # because _TestHistogramndLut_nominal is ignored
+ ndims = 2
+
+
+class TestHistogramndLut_nominal_3d(_TestHistogramndLut_nominal):
+ __test__ = True # because _TestHistogramndLut_nominal is ignored
+ ndims = 3
diff --git a/src/silx/math/test/test_calibration.py b/src/silx/math/test/test_calibration.py
new file mode 100644
index 0000000..7158293
--- /dev/null
+++ b/src/silx/math/test/test_calibration.py
@@ -0,0 +1,145 @@
+# coding: utf-8
+# /*##########################################################################
+# Copyright (C) 2018 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ############################################################################*/
+"""Tests of the calibration module"""
+
+from __future__ import division
+
+__authors__ = ["P. Knobel"]
+__license__ = "MIT"
+__date__ = "14/05/2018"
+
+
+import unittest
+
+import numpy
+
+from silx.math.calibration import NoCalibration, LinearCalibration, \
+ ArrayCalibration, FunctionCalibration
+
+
+X = numpy.array([3.14, 2.73, 1337])
+
+
+class TestNoCalibration(unittest.TestCase):
+ def setUp(self):
+ self.calib = NoCalibration()
+
+ def testIsAffine(self):
+ self.assertTrue(self.calib.is_affine())
+
+ def testSlope(self):
+ self.assertEqual(self.calib.get_slope(), 1.)
+
+ def testYIntercept(self):
+ self.assertEqual(self.calib(0.),
+ 0.)
+
+ def testCall(self):
+ self.assertTrue(numpy.array_equal(self.calib(X), X))
+
+
+class TestLinearCalibration(unittest.TestCase):
+ def setUp(self):
+ self.y_intercept = 1.5
+ self.slope = 2.5
+ self.calib = LinearCalibration(y_intercept=self.y_intercept,
+ slope=self.slope)
+
+ def testIsAffine(self):
+ self.assertTrue(self.calib.is_affine())
+
+ def testSlope(self):
+ self.assertEqual(self.calib.get_slope(), self.slope)
+
+ def testYIntercept(self):
+ self.assertEqual(self.calib(0.),
+ self.y_intercept)
+
+ def testCall(self):
+ self.assertTrue(numpy.array_equal(self.calib(X),
+ self.y_intercept + self.slope * X))
+
+
+class TestArrayCalibration(unittest.TestCase):
+ def setUp(self):
+ self.arr = numpy.array([45.2, 25.3, 666., -8.])
+ self.calib = ArrayCalibration(self.arr)
+ self.affine_calib = ArrayCalibration([0.1, 0.2, 0.3])
+
+ def testIsAffine(self):
+ self.assertFalse(self.calib.is_affine())
+ self.assertTrue(self.affine_calib.is_affine())
+
+ def testSlope(self):
+ with self.assertRaises(AttributeError):
+ self.calib.get_slope()
+ self.assertEqual(self.affine_calib.get_slope(),
+ 0.1)
+
+ def testYIntercept(self):
+ self.assertEqual(self.calib(0),
+ self.arr[0])
+
+ def testCall(self):
+ with self.assertRaises(ValueError):
+ # X is an array with a different shape
+ self.calib(X)
+
+ with self.assertRaises(ValueError):
+ # floats are not valid indices
+ self.calib(3.14)
+
+ self.assertTrue(
+ numpy.array_equal(self.calib([1, 2, 3, 4]),
+ self.arr))
+
+ for idx, value in enumerate(self.arr):
+ self.assertEqual(self.calib(idx), value)
+
+
+class TestFunctionCalibration(unittest.TestCase):
+ def setUp(self):
+ self.non_affine_fun = numpy.sin
+ self.non_affine_calib = FunctionCalibration(self.non_affine_fun)
+
+ self.affine_fun = lambda x: 52. * x + 0.01
+ self.affine_calib = FunctionCalibration(self.affine_fun,
+ is_affine=True)
+
+ def testIsAffine(self):
+ self.assertFalse(self.non_affine_calib.is_affine())
+ self.assertTrue(self.affine_calib.is_affine())
+
+ def testSlope(self):
+ with self.assertRaises(AttributeError):
+ self.non_affine_calib.get_slope()
+ self.assertAlmostEqual(self.affine_calib.get_slope(),
+ 52.)
+
+ def testCall(self):
+ for x in X:
+ self.assertAlmostEqual(self.non_affine_calib(x),
+ self.non_affine_fun(x))
+ self.assertAlmostEqual(self.affine_calib(x),
+ self.affine_fun(x))
diff --git a/src/silx/math/test/test_colormap.py b/src/silx/math/test/test_colormap.py
new file mode 100644
index 0000000..0b0ec59
--- /dev/null
+++ b/src/silx/math/test/test_colormap.py
@@ -0,0 +1,269 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2018-2021 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ############################################################################*/
+"""Test for colormap mapping implementation"""
+
+from __future__ import division
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "16/05/2018"
+
+
+import logging
+import sys
+
+import numpy
+
+from silx.utils.testutils import ParametricTestCase
+from silx.math import colormap
+
+
+_logger = logging.getLogger(__name__)
+
+
+class TestNormalization(ParametricTestCase):
+ """Test silx.math.colormap.Normalization sub classes"""
+
+ def _testCodec(self, normalization, rtol=1e-5):
+ """Test apply/revert for normalizations"""
+ test_data = (numpy.arange(1, 10, dtype=numpy.int32),
+ numpy.linspace(1., 100., 1000, dtype=numpy.float32),
+ numpy.linspace(-1., 1., 100, dtype=numpy.float32),
+ 1.,
+ 1)
+
+ for index in range(len(test_data)):
+ with self.subTest(normalization=normalization, data_index=index):
+ data = test_data[index]
+ normalized = normalization.apply(data, 1., 100.)
+ result = normalization.revert(normalized, 1., 100.)
+
+ self.assertTrue(numpy.array_equal(
+ numpy.isnan(normalized), numpy.isnan(result)))
+
+ if isinstance(data, numpy.ndarray):
+ notNaN = numpy.logical_not(numpy.isnan(result))
+ data = data[notNaN]
+ result = result[notNaN]
+ self.assertTrue(numpy.allclose(data, result, rtol=rtol))
+
+ def testLinearNormalization(self):
+ """Test for LinearNormalization"""
+ normalization = colormap.LinearNormalization()
+ self._testCodec(normalization)
+
+ def testLogarithmicNormalization(self):
+ """Test for LogarithmicNormalization"""
+ normalization = colormap.LogarithmicNormalization()
+ # relative tolerance is higher because of the log approximation
+ self._testCodec(normalization, rtol=1e-3)
+
+ # Specific extra tests
+ self.assertTrue(numpy.isnan(normalization.apply(-1., 1., 100.)))
+ self.assertTrue(numpy.isnan(normalization.apply(numpy.nan, 1., 100.)))
+ self.assertEqual(normalization.apply(numpy.inf, 1., 100.), numpy.inf)
+ self.assertEqual(normalization.apply(0, 1., 100.), - numpy.inf)
+
+ def testArcsinhNormalization(self):
+ """Test for ArcsinhNormalization"""
+ self._testCodec(colormap.ArcsinhNormalization())
+
+ def testSqrtNormalization(self):
+ """Test for SqrtNormalization"""
+ normalization = colormap.SqrtNormalization()
+ self._testCodec(normalization)
+
+ # Specific extra tests
+ self.assertTrue(numpy.isnan(normalization.apply(-1., 0., 100.)))
+ self.assertTrue(numpy.isnan(normalization.apply(numpy.nan, 0., 100.)))
+ self.assertEqual(normalization.apply(numpy.inf, 0., 100.), numpy.inf)
+ self.assertEqual(normalization.apply(0, 0., 100.), 0.)
+
+
+class TestColormap(ParametricTestCase):
+ """Test silx.math.colormap.cmap"""
+
+ NORMALIZATIONS = (
+ 'linear',
+ 'log',
+ 'arcsinh',
+ 'sqrt',
+ colormap.LinearNormalization(),
+ colormap.LogarithmicNormalization(),
+ colormap.GammaNormalization(2.),
+ colormap.GammaNormalization(0.5))
+
+ @staticmethod
+ def ref_colormap(data, colors, vmin, vmax, normalization, nan_color):
+ """Reference implementation of colormap
+
+ :param numpy.ndarray data: Data to convert
+ :param numpy.ndarray colors: Color look-up-table
+ :param float vmin: Lower bound of the colormap range
+ :param float vmax: Upper bound of the colormap range
+ :param str normalization: Normalization to use
+ :param Union[numpy.ndarray, None] nan_color: Color to use for NaN
+ """
+ norm_functions = {'linear': lambda v: v,
+ 'log': numpy.log10,
+ 'arcsinh': numpy.arcsinh,
+ 'sqrt': numpy.sqrt}
+
+ if isinstance(normalization, str):
+ norm_function = norm_functions[normalization]
+ else:
+ def norm_function(value):
+ return normalization.apply(value, vmin, vmax)
+
+ with numpy.errstate(divide='ignore', invalid='ignore'):
+ # Ignore divide by zero and invalid value encountered in log10, sqrt
+ norm_data, vmin, vmax = map(norm_function, (data, vmin, vmax))
+
+ if normalization == 'arcsinh' and sys.platform == 'win32':
+ # There is a difference of behavior of numpy.arcsinh
+ # between Windows and other OS for results of infinite values
+ # This makes Windows behaves as Linux and MacOS
+ norm_data[data == numpy.inf] = numpy.inf
+ norm_data[data == -numpy.inf] = -numpy.inf
+
+ nb_colors = len(colors)
+ scale = nb_colors / (vmax - vmin)
+
+ # Substraction must be done in float to avoid overflow with uint
+ indices = numpy.clip(scale * (norm_data - float(vmin)),
+ 0, nb_colors - 1)
+ indices[numpy.isnan(indices)] = nb_colors # Use an extra index for NaN
+ indices = indices.astype('uint')
+
+ # Add NaN color to array
+ if nan_color is None:
+ nan_color = (0,) * colors.shape[-1]
+ colors = numpy.append(colors, numpy.atleast_2d(nan_color), axis=0)
+
+ return colors[indices]
+
+ def _test(self, data, colors, vmin, vmax, normalization, nan_color):
+ """Run test of colormap against alternative implementation
+
+ :param numpy.ndarray data: Data to convert
+ :param numpy.ndarray colors: Color look-up-table
+ :param float vmin: Lower bound of the colormap range
+ :param float vmax: Upper bound of the colormap range
+ :param str normalization: Normalization to use
+ :param Union[numpy.ndarray, None] nan_color: Color to use for NaN
+ """
+ image = colormap.cmap(
+ data, colors, vmin, vmax, normalization, nan_color)
+
+ ref_image = self.ref_colormap(
+ data, colors, vmin, vmax, normalization, nan_color)
+
+ self.assertTrue(numpy.allclose(ref_image, image))
+ self.assertEqual(image.dtype, colors.dtype)
+ self.assertEqual(image.shape, data.shape + (colors.shape[-1],))
+
+ def test(self):
+ """Test all dtypes with finite data
+
+ Test all supported types and endianness
+ """
+ colors = numpy.zeros((256, 4), dtype=numpy.uint8)
+ colors[:, 0] = numpy.arange(len(colors))
+ colors[:, 3] = 255
+
+ # Generates (u)int and floats types
+ dtypes = [e + k + i for e in '<>' for k in 'uif' for i in '1248'
+ if k != 'f' or i != '1']
+ dtypes.append(numpy.dtype(numpy.longdouble).name) # Add long double
+
+ for normalization in self.NORMALIZATIONS:
+ for dtype in dtypes:
+ with self.subTest(dtype=dtype, normalization=normalization):
+ _logger.info('normalization: %s, dtype: %s',
+ normalization, dtype)
+ data = numpy.arange(-5, 15, dtype=dtype).reshape(4, 5)
+
+ self._test(data, colors, 1, 10, normalization, None)
+
+ def test_not_finite(self):
+ """Test float data with not finite values"""
+ colors = numpy.zeros((256, 4), dtype=numpy.uint8)
+ colors[:, 0] = numpy.arange(len(colors))
+ colors[:, 3] = 255
+
+ test_data = { # message: data
+ 'no finite values': (float('inf'), float('-inf'), float('nan')),
+ 'only NaN': (float('nan'), float('nan'), float('nan')),
+ 'mix finite/not finite': (float('inf'), float('-inf'), 1., float('nan')),
+ }
+
+ for normalization in self.NORMALIZATIONS:
+ for msg, data in test_data.items():
+ with self.subTest(msg, normalization=normalization):
+ _logger.info('normalization: %s, %s', normalization, msg)
+ data = numpy.array(data, dtype=numpy.float64)
+ self._test(data, colors, 1, 10, normalization, (0, 0, 0, 0))
+
+ def test_errors(self):
+ """Test raising exception for bad vmin, vmax, normalization parameters
+ """
+ colors = numpy.zeros((256, 4), dtype=numpy.uint8)
+ colors[:, 0] = numpy.arange(len(colors))
+ colors[:, 3] = 255
+
+ data = numpy.arange(10, dtype=numpy.float64)
+
+ test_params = [ # (vmin, vmax, normalization)
+ (-1., 2., 'log'),
+ (0., 1., 'log'),
+ (1., 0., 'log'),
+ (-1., 1., 'sqrt'),
+ (1., -1., 'sqrt'),
+ ]
+
+ for vmin, vmax, normalization in test_params:
+ with self.subTest(
+ vmin=vmin, vmax=vmax, normalization=normalization):
+ _logger.info('normalization: %s, range: [%f, %f]',
+ normalization, vmin, vmax)
+ with self.assertRaises(ValueError):
+ self._test(data, colors, vmin, vmax, normalization, None)
+
+
+def test_apply_colormap():
+ """Basic test of silx.math.colormap.apply_colormap"""
+ data = numpy.arange(256)
+ expected_colors = numpy.empty((256, 4), dtype=numpy.uint8)
+ expected_colors[:, :3] = numpy.arange(256, dtype=numpy.uint8).reshape(256, 1)
+ expected_colors[:, 3] = 255
+ colors = colormap.apply_colormap(
+ data,
+ colormap="gray",
+ norm="linear",
+ autoscale="minmax",
+ vmin=None,
+ vmax=None,
+ gamma=1.0)
+ assert numpy.array_equal(colors, expected_colors)
diff --git a/src/silx/math/test/test_combo.py b/src/silx/math/test/test_combo.py
new file mode 100644
index 0000000..9a96923
--- /dev/null
+++ b/src/silx/math/test/test_combo.py
@@ -0,0 +1,207 @@
+# coding: utf-8
+# /*##########################################################################
+# Copyright (C) 2016-2020 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ############################################################################*/
+"""Tests of the combo module"""
+
+from __future__ import division
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "17/01/2018"
+
+
+import unittest
+
+import numpy
+
+from silx.utils.testutils import ParametricTestCase
+
+from silx.math.combo import min_max
+
+
+class TestMinMax(ParametricTestCase):
+ """Tests of min max combo"""
+
+ FLOATING_DTYPES = 'float32', 'float64'
+ if hasattr(numpy, "float128"):
+ FLOATING_DTYPES += ('float128',)
+ SIGNED_INT_DTYPES = 'int8', 'int16', 'int32', 'int64'
+ UNSIGNED_INT_DTYPES = 'uint8', 'uint16', 'uint32', 'uint64'
+ DTYPES = FLOATING_DTYPES + SIGNED_INT_DTYPES + UNSIGNED_INT_DTYPES
+
+ def _numpy_min_max(self, data, min_positive=False, finite=False):
+ """Reference numpy implementation of min_max
+
+ :param numpy.ndarray data: Data set to use for test
+ :param bool min_positive: True to test with positive min
+ :param bool finite: True to only test finite values
+ """
+ data = numpy.array(data, copy=False)
+ if data.size == 0:
+ raise ValueError('Zero-sized array')
+
+ minimum = None
+ argmin = None
+ maximum = None
+ argmax = None
+ min_pos = None
+ argmin_pos = None
+
+ if finite:
+ filtered_data = data[numpy.isfinite(data)]
+ else:
+ filtered_data = data
+
+ if filtered_data.size > 0:
+ if numpy.all(numpy.isnan(filtered_data)):
+ minimum = numpy.nan
+ argmin = 0
+ maximum = numpy.nan
+ argmax = 0
+ else:
+ minimum = numpy.nanmin(filtered_data)
+ # nanargmin equivalent
+ argmin = numpy.where(data == minimum)[0][0]
+ maximum = numpy.nanmax(filtered_data)
+ # nanargmax equivalent
+ argmax = numpy.where(data == maximum)[0][0]
+
+ if min_positive:
+ with numpy.errstate(invalid='ignore'):
+ # Ignore invalid value encountered in greater
+ pos_data = filtered_data[filtered_data > 0]
+ if pos_data.size > 0:
+ min_pos = numpy.min(pos_data)
+ argmin_pos = numpy.where(data == min_pos)[0][0]
+
+ return minimum, min_pos, maximum, argmin, argmin_pos, argmax
+
+ def _test_min_max(self, data, min_positive, finite=False):
+ """Compare min_max with numpy for the given dataset
+
+ :param numpy.ndarray data: Data set to use for test
+ :param bool min_positive: True to test with positive min
+ :param bool finite: True to only test finite values
+ """
+ minimum, min_pos, maximum, argmin, argmin_pos, argmax = \
+ self._numpy_min_max(data, min_positive, finite)
+
+ result = min_max(data, min_positive, finite)
+
+ self.assertSimilar(minimum, result.minimum)
+ self.assertSimilar(min_pos, result.min_positive)
+ self.assertSimilar(maximum, result.maximum)
+ self.assertSimilar(argmin, result.argmin)
+ self.assertSimilar(argmin_pos, result.argmin_positive)
+ self.assertSimilar(argmax, result.argmax)
+
+ def assertSimilar(self, a, b):
+ """Assert that a and b are both None or NaN or that a == b."""
+ self.assertTrue((a is None and b is None) or
+ (numpy.isnan(a) and numpy.isnan(b)) or
+ a == b)
+
+ def test_different_datasets(self):
+ """Test min_max with different numpy.arange datasets."""
+ size = 1000
+
+ for dtype in self.DTYPES:
+
+ tests = {
+ '0 to N': (0, 1),
+ 'N-1 to 0': (size - 1, -1)}
+ if dtype not in self.UNSIGNED_INT_DTYPES:
+ tests['N/2 to -N/2'] = size // 2, -1
+ tests['0 to -N'] = 0, -1
+
+ for name, (start, step) in tests.items():
+ for min_positive in (True, False):
+ with self.subTest(dtype=dtype,
+ min_positive=min_positive,
+ data=name):
+ data = numpy.arange(
+ start, start + step * size, step, dtype=dtype)
+
+ self._test_min_max(data, min_positive)
+
+ def test_nodata(self):
+ """Test min_max with None and empty array"""
+ for dtype in self.DTYPES:
+ with self.subTest(dtype=dtype):
+ with self.assertRaises(TypeError):
+ min_max(None)
+
+ data = numpy.array((), dtype=dtype)
+ with self.assertRaises(ValueError):
+ min_max(data)
+
+ NAN_TEST_DATA = [
+ (float('nan'), float('nan')), # All NaNs
+ (float('nan'), 1.0), # NaN first and positive
+ (float('nan'), -1.0), # NaN first and negative
+ (1.0, 2.0, float('nan')), # NaN last and positive
+ (-1.0, -2.0, float('nan')), # NaN last and negative
+ (1.0, float('nan'), -1.0), # Some NaN
+ ]
+
+ def test_nandata(self):
+ """Test min_max with NaN in data"""
+ for dtype in self.FLOATING_DTYPES:
+ for data in self.NAN_TEST_DATA:
+ with self.subTest(dtype=dtype, data=data):
+ data = numpy.array(data, dtype=dtype)
+ self._test_min_max(data, min_positive=True)
+
+ INF_TEST_DATA = [
+ [float('inf')] * 3, # All +inf
+ [float('-inf')] * 3, # All -inf
+ (float('inf'), float('-inf')), # + and - inf
+ (float('inf'), float('-inf'), float('nan')), # +/-inf, nan last
+ (float('nan'), float('-inf'), float('inf')), # +/-inf, nan first
+ (float('inf'), float('nan'), float('-inf')), # +/-inf, nan center
+ ]
+
+ def test_infdata(self):
+ """Test min_max with inf."""
+ for dtype in self.FLOATING_DTYPES:
+ for data in self.INF_TEST_DATA:
+ with self.subTest(dtype=dtype, data=data):
+ data = numpy.array(data, dtype=dtype)
+ self._test_min_max(data, min_positive=True)
+
+ def test_finite(self):
+ """Test min_max with finite=True"""
+ tests = [
+ (-1., 2., 0.), # Basic test
+ (float('nan'), float('inf'), float('-inf')), # NaN + Inf
+ (float('nan'), float('inf'), -2, float('-inf')), # NaN + Inf + 1 value
+ (float('inf'), -3, -2), # values + inf
+ ]
+ tests += self.INF_TEST_DATA
+ tests += self.NAN_TEST_DATA
+
+ for dtype in self.FLOATING_DTYPES:
+ for data in tests:
+ with self.subTest(dtype=dtype, data=data):
+ data = numpy.array(data, dtype=dtype)
+ self._test_min_max(data, min_positive=True, finite=True)
diff --git a/src/silx/math/test/test_histogramnd_error.py b/src/silx/math/test/test_histogramnd_error.py
new file mode 100644
index 0000000..22304cb
--- /dev/null
+++ b/src/silx/math/test/test_histogramnd_error.py
@@ -0,0 +1,519 @@
+# coding: utf-8
+# /*##########################################################################
+# Copyright (C) 2016 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ############################################################################*/
+
+__authors__ = ["D. Naudet"]
+__license__ = "MIT"
+__date__ = "01/02/2016"
+
+"""
+Tests of the histogramnd function, error cases.
+"""
+import sys
+import platform
+import unittest
+
+import numpy as np
+
+from silx.math.chistogramnd import chistogramnd as histogramnd
+from silx.math import Histogramnd
+
+
+# ==============================================================
+# ==============================================================
+# ==============================================================
+
+
+class _Test_chistogramnd_errors(unittest.TestCase):
+ """
+ Unit tests of the chistogramnd error cases.
+ """
+ __test__ = False # ignore abstract class
+
+ def setUp(self):
+ self.skipTest("Abstract class")
+
+ def test_weights_shape(self):
+ """
+ """
+
+ for err_w_shape in self.err_weights_shapes:
+ test_msg = ('Testing invalid weights shape : {0}'
+ ''.format(err_w_shape))
+
+ err_weights = np.random.randint(0,
+ high=10,
+ size=err_w_shape)
+ err_weights = err_weights.astype(np.double)
+
+ ex_str = None
+ try:
+ histo, cumul = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=err_weights)[0:2]
+ except ValueError as ex:
+ ex_str = str(ex)
+
+ self.assertIsNotNone(ex_str, msg=test_msg)
+ self.assertEqual(ex_str,
+ '<weights> must be an array whose length '
+ 'is equal to the number of samples.')
+
+ def test_histo_range_shape(self):
+ """
+ """
+ n_dims = 1 if len(self.s_shape) == 1 else self.s_shape[1]
+ expected_txt_tpl = ('<histo_range> error : expected {n_dims} sets '
+ 'of lower and upper bin edges, '
+ 'got the following instead : {histo_range}. '
+ '(provided <sample> contains '
+ '{n_dims}D values)')
+
+ for err_histo_range in self.err_histo_range_shapes:
+ test_msg = ('Testing invalid histo_range shape : {0}'
+ ''.format(err_histo_range))
+
+ expected_txt = expected_txt_tpl.format(histo_range=err_histo_range,
+ n_dims=n_dims)
+
+ ex_str = None
+ try:
+ histo, cumul = histogramnd(self.sample,
+ err_histo_range,
+ self.n_bins,
+ weights=self.weights)[0:2]
+ except ValueError as ex:
+ ex_str = str(ex)
+
+ self.assertIsNotNone(ex_str, msg=test_msg)
+ self.assertEqual(ex_str, expected_txt, msg=test_msg)
+
+ def test_nbins_shape(self):
+ """
+ """
+
+ expected_txt = ('n_bins must be either a scalar (same number '
+ 'of bins for all dimensions) or '
+ 'an array (number of bins for each '
+ 'dimension).')
+
+ for err_n_bins in self.err_n_bins_shapes:
+ test_msg = ('Testing invalid n_bins shape : {0}'
+ ''.format(err_n_bins))
+
+ ex_str = None
+ try:
+ histo, cumul = histogramnd(self.sample,
+ self.histo_range,
+ err_n_bins,
+ weights=self.weights)[0:2]
+ except ValueError as ex:
+ ex_str = str(ex)
+
+ self.assertIsNotNone(ex_str, msg=test_msg)
+ self.assertEqual(ex_str, expected_txt, msg=test_msg)
+
+ def test_nbins_values(self):
+ """
+ """
+ expected_txt = ('<n_bins> : only positive values allowed.')
+
+ for err_n_bins in self.err_n_bins_values:
+ test_msg = ('Testing invalid n_bins value : {0}'
+ ''.format(err_n_bins))
+
+ ex_str = None
+ try:
+ histo, cumul = histogramnd(self.sample,
+ self.histo_range,
+ err_n_bins,
+ weights=self.weights)[0:2]
+ except ValueError as ex:
+ ex_str = str(ex)
+
+ self.assertIsNotNone(ex_str, msg=test_msg)
+ self.assertEqual(ex_str, expected_txt, msg=test_msg)
+
+ def test_histo_shape(self):
+ """
+ """
+ for err_h_shape in self.err_histo_shapes:
+
+ # windows & python 2.7 : numpy shapes are long values
+ if platform.system() == 'Windows':
+ version = (sys.version_info.major, sys.version_info.minor)
+ if version <= (2, 7):
+ err_h_shape = tuple([long(val) for val in err_h_shape])
+
+ test_msg = ('Testing invalid histo shape : {0}'
+ ''.format(err_h_shape))
+
+ expected_txt = ('Provided <histo> array doesn\'t have '
+ 'a shape compatible with <n_bins> '
+ ': should be {0} instead of {1}.'
+ ''.format(self.h_shape, err_h_shape))
+
+ histo = np.zeros(shape=err_h_shape, dtype=np.uint32)
+
+ ex_str = None
+ try:
+ histo, cumul = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ histo=histo)[0:2]
+ except ValueError as ex:
+ ex_str = str(ex)
+
+ self.assertIsNotNone(ex_str, msg=test_msg)
+ self.assertEqual(ex_str, expected_txt, msg=test_msg)
+
+ def test_histo_dtype(self):
+ """
+ """
+ for err_h_dtype in self.err_histo_dtypes:
+ test_msg = ('Testing invalid histo dtype : {0}'
+ ''.format(err_h_dtype))
+
+ histo = np.zeros(shape=self.h_shape, dtype=err_h_dtype)
+
+ expected_txt = ('Provided <histo> array doesn\'t have '
+ 'the expected type '
+ ': should be {0} instead of {1}.'
+ ''.format(np.uint32, histo.dtype))
+
+ ex_str = None
+ try:
+ histo, cumul = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ histo=histo)[0:2]
+ except ValueError as ex:
+ ex_str = str(ex)
+
+ self.assertIsNotNone(ex_str, msg=test_msg)
+ self.assertEqual(ex_str, expected_txt, msg=test_msg)
+
+ def test_weighted_histo_shape(self):
+ """
+ """
+ # using the same values as histo
+ for err_h_shape in self.err_histo_shapes:
+
+ # windows & python 2.7 : numpy shapes are long values
+ if platform.system() == 'Windows':
+ version = (sys.version_info.major, sys.version_info.minor)
+ if version <= (2, 7):
+ err_h_shape = tuple([long(val) for val in err_h_shape])
+
+ test_msg = ('Testing invalid weighted_histo shape : {0}'
+ ''.format(err_h_shape))
+
+ expected_txt = ('Provided <weighted_histo> array doesn\'t have '
+ 'a shape compatible with <n_bins> '
+ ': should be {0} instead of {1}.'
+ ''.format(self.h_shape, err_h_shape))
+
+ cumul = np.zeros(shape=err_h_shape, dtype=np.double)
+
+ ex_str = None
+ try:
+ histo, cumul = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ weighted_histo=cumul)[0:2]
+ except ValueError as ex:
+ ex_str = str(ex)
+
+ self.assertIsNotNone(ex_str, msg=test_msg)
+ self.assertEqual(ex_str, expected_txt, msg=test_msg)
+
+ def test_cumul_dtype(self):
+ """
+ """
+ # using the same values as histo
+ for err_h_dtype in self.err_histo_dtypes:
+ test_msg = ('Testing invalid weighted_histo dtype : {0}'
+ ''.format(err_h_dtype))
+
+ cumul = np.zeros(shape=self.h_shape, dtype=err_h_dtype)
+
+ expected_txt = ('Provided <weighted_histo> array doesn\'t have '
+ 'the expected type '
+ ': should be {0} or {1} instead of {2}.'
+ ''.format(np.float64, np.float32, cumul.dtype))
+
+ ex_str = None
+ try:
+ histo, cumul = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ weighted_histo=cumul)[0:2]
+ except ValueError as ex:
+ ex_str = str(ex)
+
+ self.assertIsNotNone(ex_str, msg=test_msg)
+ self.assertEqual(ex_str, expected_txt, msg=test_msg)
+
+ def test_wh_histo_dtype(self):
+ """
+ """
+ # using the same values as histo
+ for err_h_dtype in self.err_histo_dtypes:
+ test_msg = ('Testing invalid wh_dtype dtype : {0}'
+ ''.format(err_h_dtype))
+
+ expected_txt = ('<wh_dtype> type not supported : {0}.'
+ ''.format(err_h_dtype))
+
+ ex_str = None
+ try:
+ histo, cumul = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ wh_dtype=err_h_dtype)[0:2]
+ except ValueError as ex:
+ ex_str = str(ex)
+
+ self.assertIsNotNone(ex_str, msg=test_msg)
+ self.assertEqual(ex_str, expected_txt, msg=test_msg)
+
+ def test_unmanaged_dtypes(self):
+ """
+ """
+ for err_unmanaged_dtype in self.err_unmanaged_dtypes:
+ test_msg = ('Testing unmanaged dtypes : {0}'
+ ''.format(err_unmanaged_dtype))
+
+ sample = self.sample.astype(err_unmanaged_dtype[0])
+ weights = self.weights.astype(err_unmanaged_dtype[1])
+
+ expected_txt = ('Case not supported - sample:{0} '
+ 'and weights:{1}.'
+ ''.format(sample.dtype,
+ weights.dtype))
+
+ ex_str = None
+ try:
+ histogramnd(sample,
+ self.histo_range,
+ self.n_bins,
+ weights=weights)
+ except TypeError as ex:
+ ex_str = str(ex)
+
+ self.assertIsNotNone(ex_str, msg=test_msg)
+ self.assertEqual(ex_str, expected_txt, msg=test_msg)
+
+ def test_uncontiguous_histo(self):
+ """
+ """
+ # non contiguous array
+ shape = np.array(self.n_bins, ndmin=1)
+ shape[0] *= 2
+ histo_tmp = np.zeros(shape)
+ histo = histo_tmp[::2, ...]
+
+ expected_txt = ('<histo> must be a C_CONTIGUOUS numpy array.')
+
+ ex_str = None
+ try:
+ histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ histo=histo)
+ except ValueError as ex:
+ ex_str = str(ex)
+
+ self.assertIsNotNone(ex_str)
+ self.assertEqual(ex_str, expected_txt)
+
+ def test_uncontiguous_weighted_histo(self):
+ """
+ """
+ # non contiguous array
+ shape = np.array(self.n_bins, ndmin=1)
+ shape[0] *= 2
+ cumul_tmp = np.zeros(shape)
+ cumul = cumul_tmp[::2, ...]
+
+ expected_txt = ('<weighted_histo> must be a C_CONTIGUOUS numpy array.')
+
+ ex_str = None
+ try:
+ histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ weighted_histo=cumul)
+ except ValueError as ex:
+ ex_str = str(ex)
+
+ self.assertIsNotNone(ex_str)
+ self.assertEqual(ex_str, expected_txt)
+
+
+class Test_chistogramnd_1D_errors(_Test_chistogramnd_errors):
+ """
+ Unit tests of the 1D histogramnd error cases.
+ """
+ __test__ = True # because _Test_chistogramnd_errors is ignored
+
+ def setUp(self):
+ # nominal values
+ self.n_elements = 1000
+ self.s_shape = (self.n_elements,)
+ self.w_shape = (self.n_elements,)
+
+ self.histo_range = [0., 100.]
+ self.n_bins = 10
+
+ self.h_shape = (self.n_bins,)
+
+ self.sample = np.random.randint(0,
+ high=10,
+ size=self.s_shape)
+ self.sample = self.sample.astype(np.double)
+
+ self.weights = np.random.randint(0,
+ high=10,
+ size=self.w_shape)
+ self.weights = self.weights.astype(np.double)
+
+ self.err_weights_shapes = ((self.n_elements+1,),
+ (self.n_elements-1,),
+ (self.n_elements-1, 3))
+ self.err_histo_range_shapes = ([0.],
+ [0., 1., 2.],
+ [[0.], [1.]])
+ self.err_n_bins_shapes = ([10, 2],
+ [[10], [2]])
+ self.err_n_bins_values = (0,
+ [-10],
+ None)
+ self.err_histo_shapes = ((self.n_bins+1,),
+ (self.n_bins-1,),
+ (self.n_bins, self.n_bins))
+ # these are used for testing the histo parameter as well
+ # as the weighted_histo parameter.
+ self.err_histo_dtypes = (np.uint16,
+ np.float16)
+
+ self.err_unmanaged_dtypes = ((np.double, np.uint16),
+ (np.uint16, np.double),
+ (np.uint16, np.uint16))
+
+class Test_chistogramnd_ND_range(unittest.TestCase):
+ """
+
+ """
+
+ def test_invalid_histo_range(self):
+ data = np.random.random((60, 60))
+ nbins = 10
+
+ with self.assertRaises(ValueError):
+ histo_range = data.min(), np.inf
+
+ Histogramnd(sample=data.ravel(),
+ histo_range=histo_range,
+ n_bins=nbins)
+
+ histo_range = data.min(), np.nan
+
+ Histogramnd(sample=data.ravel(),
+ histo_range=histo_range,
+ n_bins=nbins)
+
+
+class Test_chistogramnd_ND_errors(_Test_chistogramnd_errors):
+ """
+ Unit tests of the 3D histogramnd error cases.
+ """
+ __test__ = True # because _Test_chistogramnd_errors is ignored
+
+ def setUp(self):
+ # nominal values
+ self.n_elements = 1000
+ self.s_shape = (self.n_elements, 3)
+ self.w_shape = (self.n_elements,)
+
+ self.histo_range = [[0., 100.], [0., 100.], [0., 100.]]
+ self.n_bins = (10, 20, 30)
+
+ self.h_shape = self.n_bins
+
+ self.sample = np.random.randint(0,
+ high=10,
+ size=self.s_shape)
+ self.sample = self.sample.astype(np.double)
+
+ self.weights = np.random.randint(0,
+ high=10,
+ size=self.w_shape)
+ self.weights = self.weights.astype(np.double)
+
+ self.err_weights_shapes = ((self.n_elements+1,),
+ (self.n_elements-1,),
+ (self.n_elements-1, 3))
+ self.err_histo_range_shapes = ([0.],
+ [0., 1.],
+ [[0., 10.], [0., 10.]],
+ [0., 10., 0, 10., 0, 10.])
+ self.err_n_bins_shapes = ([10, 2],
+ [[10], [20], [30]])
+ self.err_n_bins_values = (0,
+ [-10],
+ [10, 20, -4],
+ None,
+ [10, None, 30])
+ self.err_histo_shapes = ((self.n_bins[0]+1,
+ self.n_bins[1],
+ self.n_bins[2]),
+ (self.n_bins[0],
+ self.n_bins[1],
+ self.n_bins[2]-1),
+ (self.n_bins[0],
+ self.n_bins[1]),
+ (self.n_bins[1],
+ self.n_bins[0],
+ self.n_bins[2]),
+ (self.n_bins[0],
+ self.n_bins[1],
+ self.n_bins[2],
+ 10)
+ )
+ # these are used for testing the histo parameter as well
+ # as the weighted_histo parameter.
+ self.err_histo_dtypes = (np.uint16,
+ np.float16)
+
+ self.err_unmanaged_dtypes = ((np.double, np.uint16),
+ (np.uint16, np.double),
+ (np.uint16, np.uint16))
diff --git a/src/silx/math/test/test_histogramnd_nominal.py b/src/silx/math/test/test_histogramnd_nominal.py
new file mode 100644
index 0000000..031a772
--- /dev/null
+++ b/src/silx/math/test/test_histogramnd_nominal.py
@@ -0,0 +1,937 @@
+# coding: utf-8
+# /*##########################################################################
+# Copyright (C) 2016-2021 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ############################################################################*/
+"""
+Nominal tests of the histogramnd function.
+"""
+
+import unittest
+import pytest
+
+import numpy as np
+
+from silx.math.chistogramnd import chistogramnd as histogramnd
+from silx.math import Histogramnd
+
+
+def _get_bin_edges(histo_range, n_bins, n_dims):
+ edges = []
+ for i_dim in range(n_dims):
+ edges.append(histo_range[i_dim, 0] +
+ np.arange(n_bins[i_dim] + 1) *
+ (histo_range[i_dim, 1] - histo_range[i_dim, 0]) /
+ n_bins[i_dim])
+ return tuple(edges)
+
+
+# ==============================================================
+# ==============================================================
+# ==============================================================
+
+
+class _Test_chistogramnd_nominal(unittest.TestCase):
+ """
+ Unit tests of the histogramnd function.
+ """
+ __test__ = False # ignore abstract classe
+
+ ndims = None
+
+ def setUp(self):
+ if type(self).__name__.startswith("_"):
+ self.skipTest("Abstract class")
+ ndims = self.ndims
+ self.tested_dim = ndims-1
+
+ if ndims is None:
+ raise ValueError('ndims class member not set.')
+
+ sample = np.array([5.5, -3.3,
+ 0., -0.5,
+ 3.3, 8.8,
+ -7.7, 6.0,
+ -4.0])
+
+ weights = np.array([500.5, -300.3,
+ 0.01, -0.5,
+ 300.3, 800.8,
+ -700.7, 600.6,
+ -400.4])
+
+ n_elems = len(sample)
+
+ if ndims == 1:
+ shape = (n_elems,)
+ else:
+ shape = (n_elems, ndims)
+
+ self.sample = np.zeros(shape=shape, dtype=sample.dtype)
+ if ndims == 1:
+ self.sample = sample
+ else:
+ self.sample[..., ndims-1] = sample
+
+ self.weights = weights
+
+ # the tests are performed along one dimension,
+ # all the other bins indices along the other dimensions
+ # are expected to be 2
+ # (e.g : when testing a 2D sample : [0, x] will go into
+ # bin [2, y] because of the bin ranges [-2, 2] and n_bins = 4
+ # for the first dimension)
+ self.other_axes_index = 2
+ self.histo_range = np.repeat([[-2., 2.]], ndims, axis=0)
+ self.histo_range[ndims-1] = [-4., 6.]
+
+ self.n_bins = np.array([4]*ndims)
+ self.n_bins[ndims-1] = 5
+
+ if ndims == 1:
+ def fill_histo(h, v, dim, op=None):
+ if op:
+ h[:] = op(h[:], v)
+ else:
+ h[:] = v
+ self.fill_histo = fill_histo
+ else:
+ def fill_histo(h, v, dim, op=None):
+ idx = [self.other_axes_index]*len(h.shape)
+ idx[dim] = slice(0, None)
+ idx = tuple(idx)
+ if op:
+ h[idx] = op(h[idx], v)
+ else:
+ h[idx] = v
+ self.fill_histo = fill_histo
+
+ def test_nominal(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ histo, cumul, bin_edges = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights)
+
+ expected_edges = _get_bin_edges(self.histo_range,
+ self.n_bins,
+ self.ndims)
+
+ self.assertEqual(cumul.dtype, np.float64)
+ self.assertEqual(histo.dtype, np.uint32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(cumul, expected_c))
+
+ for i_edges, edges in enumerate(expected_edges):
+ self.assertTrue(np.array_equal(bin_edges[i_edges],
+ expected_edges[i_edges]),
+ msg='Testing bin_edges for dim {0}'
+ ''.format(i_edges+1))
+
+ def test_nominal_wh_dtype(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.float32)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ histo, cumul, bin_edges = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ wh_dtype=np.float32)
+
+ self.assertEqual(cumul.dtype, np.float32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.allclose(cumul, expected_c))
+
+ def test_nominal_uncontiguous_sample(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ shape = list(self.sample.shape)
+ shape[0] *= 2
+ sample = np.zeros(shape, dtype=self.sample.dtype)
+ uncontig_sample = sample[::2, ...]
+ uncontig_sample[:] = self.sample
+
+ self.assertFalse(uncontig_sample.flags['C_CONTIGUOUS'],
+ msg='Making sure the array is not contiguous.')
+
+ histo, cumul, bin_edges = histogramnd(uncontig_sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights)
+
+ self.assertEqual(cumul.dtype, np.float64)
+ self.assertEqual(histo.dtype, np.uint32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(cumul, expected_c))
+
+ def test_nominal_uncontiguous_weights(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ shape = list(self.weights.shape)
+ shape[0] *= 2
+ weights = np.zeros(shape, dtype=self.weights.dtype)
+ uncontig_weights = weights[::2, ...]
+ uncontig_weights[:] = self.weights
+
+ self.assertFalse(uncontig_weights.flags['C_CONTIGUOUS'],
+ msg='Making sure the array is not contiguous.')
+
+ histo, cumul, bin_edges = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=uncontig_weights)
+
+ self.assertEqual(cumul.dtype, np.float64)
+ self.assertEqual(histo.dtype, np.uint32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(cumul, expected_c))
+
+ def test_nominal_wo_weights(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+
+ histo, cumul = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=None)[0:2]
+
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(cumul is None)
+
+ def test_nominal_wo_weights_w_cumul(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ # creating an array of ones just to make sure that
+ # it is not cleared by histogramnd
+ cumul_in = np.ones(self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+
+ histo, cumul = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=None,
+ weighted_histo=cumul_in)[0:2]
+
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(cumul is None)
+ self.assertTrue(np.array_equal(cumul_in,
+ np.ones(shape=self.n_bins,
+ dtype=np.double)))
+
+ def test_nominal_wo_weights_w_histo(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ # creating an array of ones just to make sure that
+ # it is not cleared by histogramnd
+ histo_in = np.ones(self.n_bins, dtype=np.uint32)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+
+ histo, cumul = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=None,
+ histo=histo_in)[0:2]
+
+ self.assertTrue(np.array_equal(histo, expected_h + 1))
+ self.assertTrue(cumul is None)
+ self.assertEqual(id(histo), id(histo_in))
+
+ def test_nominal_last_bin_closed(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 2])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 1101.1])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ histo, cumul = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ last_bin_closed=True)[0:2]
+
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(cumul, expected_c))
+
+ def test_int32_weights_double_weights_range(self):
+ """
+ """
+ weight_min = -299.9 # ===> will be cast to -299
+ weight_max = 499.9 # ===> will be cast to 499
+
+ expected_h_tpl = np.array([0, 1, 1, 1, 0])
+ expected_c_tpl = np.array([0., 0., 0., 300., 0.])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ histo, cumul = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights.astype(np.int32),
+ weight_min=weight_min,
+ weight_max=weight_max)[0:2]
+
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(cumul, expected_c))
+
+ def test_reuse_histo(self):
+ """
+ """
+
+ expected_h_tpl = np.array([2, 3, 2, 2, 2])
+ expected_c_tpl = np.array([0.0, -7007, -5.0, 0.1, 3003.0])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ histo, cumul = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights)[0:2]
+
+ sample_2 = self.sample[:]
+ if len(sample_2.shape) == 1:
+ idx = (slice(0, None),)
+ else:
+ idx = slice(0, None), self.tested_dim
+
+ sample_2[idx] += 2
+
+ histo_2, cumul = histogramnd(sample_2, # <==== !!
+ self.histo_range,
+ self.n_bins,
+ weights=10 * self.weights, # <==== !!
+ histo=histo)[0:2]
+
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(cumul, expected_c))
+ self.assertEqual(id(histo), id(histo_2))
+
+ def test_reuse_cumul(self):
+ """
+ """
+
+ expected_h_tpl = np.array([0, 2, 1, 1, 1])
+ expected_c_tpl = np.array([-700.7, -7007.5, -4.99, 300.4, 3503.5])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ histo, cumul = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights)[0:2]
+
+ sample_2 = self.sample[:]
+ if len(sample_2.shape) == 1:
+ idx = (slice(0, None),)
+ else:
+ idx = slice(0, None), self.tested_dim
+
+ sample_2[idx] += 2
+
+ histo, cumul_2 = histogramnd(sample_2, # <==== !!
+ self.histo_range,
+ self.n_bins,
+ weights=10 * self.weights, # <==== !!
+ weighted_histo=cumul)[0:2]
+
+ self.assertEqual(cumul.dtype, np.float64)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.allclose(cumul, expected_c, rtol=10e-15))
+ self.assertEqual(id(cumul), id(cumul_2))
+
+ def test_reuse_cumul_float(self):
+ """
+ """
+
+ expected_h_tpl = np.array([0, 2, 1, 1, 1])
+ expected_c_tpl = np.array([-700.7, -7007.5, -4.99, 300.4, 3503.5],
+ dtype=np.float32)
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ histo, cumul = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights)[0:2]
+
+ # converting the cumul array to float
+ cumul = cumul.astype(np.float32)
+
+ sample_2 = self.sample[:]
+ if len(sample_2.shape) == 1:
+ idx = (slice(0, None),)
+ else:
+ idx = slice(0, None), self.tested_dim
+
+ sample_2[idx] += 2
+
+ histo, cumul_2 = histogramnd(sample_2, # <==== !!
+ self.histo_range,
+ self.n_bins,
+ weights=10 * self.weights, # <==== !!
+ weighted_histo=cumul)[0:2]
+
+ self.assertEqual(cumul.dtype, np.float32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertEqual(id(cumul), id(cumul_2))
+ self.assertTrue(np.allclose(cumul, expected_c, rtol=10e-15))
+
+class _Test_Histogramnd_nominal(unittest.TestCase):
+ """
+ Unit tests of the Histogramnd class.
+ """
+ __test__ = False # ignore abstract class
+
+ ndims = None
+
+ def setUp(self):
+ ndims = self.ndims
+ if ndims is None:
+ self.skipTest("Abstract class")
+ self.tested_dim = ndims-1
+
+ if ndims is None:
+ raise ValueError('ndims class member not set.')
+
+ sample = np.array([5.5, -3.3,
+ 0., -0.5,
+ 3.3, 8.8,
+ -7.7, 6.0,
+ -4.0])
+
+ weights = np.array([500.5, -300.3,
+ 0.01, -0.5,
+ 300.3, 800.8,
+ -700.7, 600.6,
+ -400.4])
+
+ n_elems = len(sample)
+
+ if ndims == 1:
+ shape = (n_elems,)
+ else:
+ shape = (n_elems, ndims)
+
+ self.sample = np.zeros(shape=shape, dtype=sample.dtype)
+ if ndims == 1:
+ self.sample = sample
+ else:
+ self.sample[..., ndims-1] = sample
+
+ self.weights = weights
+
+ # the tests are performed along one dimension,
+ # all the other bins indices along the other dimensions
+ # are expected to be 2
+ # (e.g : when testing a 2D sample : [0, x] will go into
+ # bin [2, y] because of the bin ranges [-2, 2] and n_bins = 4
+ # for the first dimension)
+ self.other_axes_index = 2
+ self.histo_range = np.repeat([[-2., 2.]], ndims, axis=0)
+ self.histo_range[ndims-1] = [-4., 6.]
+
+ self.n_bins = np.array([4]*ndims)
+ self.n_bins[ndims-1] = 5
+
+ if ndims == 1:
+ def fill_histo(h, v, dim, op=None):
+ if op:
+ h[:] = op(h[:], v)
+ else:
+ h[:] = v
+ self.fill_histo = fill_histo
+ else:
+ def fill_histo(h, v, dim, op=None):
+ idx = [self.other_axes_index]*len(h.shape)
+ idx[dim] = slice(0, None)
+ idx = tuple(idx)
+ if op:
+ h[idx] = op(h[idx], v)
+ else:
+ h[idx] = v
+ self.fill_histo = fill_histo
+
+ def test_nominal(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ histo = Histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights)
+
+ histo, cumul, bin_edges = histo
+
+ expected_edges = _get_bin_edges(self.histo_range,
+ self.n_bins,
+ self.ndims)
+
+ self.assertEqual(cumul.dtype, np.float64)
+ self.assertEqual(histo.dtype, np.uint32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(cumul, expected_c))
+
+ for i_edges, edges in enumerate(expected_edges):
+ self.assertTrue(np.array_equal(bin_edges[i_edges],
+ expected_edges[i_edges]),
+ msg='Testing bin_edges for dim {0}'
+ ''.format(i_edges+1))
+
+ def test_nominal_wh_dtype(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.float32)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ histo, cumul, bin_edges = Histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ wh_dtype=np.float32)
+
+ self.assertEqual(cumul.dtype, np.float32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.allclose(cumul, expected_c))
+
+ def test_nominal_uncontiguous_sample(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ shape = list(self.sample.shape)
+ shape[0] *= 2
+ sample = np.zeros(shape, dtype=self.sample.dtype)
+ uncontig_sample = sample[::2, ...]
+ uncontig_sample[:] = self.sample
+
+ self.assertFalse(uncontig_sample.flags['C_CONTIGUOUS'],
+ msg='Making sure the array is not contiguous.')
+
+ histo, cumul, bin_edges = Histogramnd(uncontig_sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights)
+
+ self.assertEqual(cumul.dtype, np.float64)
+ self.assertEqual(histo.dtype, np.uint32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(cumul, expected_c))
+
+ def test_nominal_uncontiguous_weights(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ shape = list(self.weights.shape)
+ shape[0] *= 2
+ weights = np.zeros(shape, dtype=self.weights.dtype)
+ uncontig_weights = weights[::2, ...]
+ uncontig_weights[:] = self.weights
+
+ self.assertFalse(uncontig_weights.flags['C_CONTIGUOUS'],
+ msg='Making sure the array is not contiguous.')
+
+ histo, cumul, bin_edges = Histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=uncontig_weights)
+
+ self.assertEqual(cumul.dtype, np.float64)
+ self.assertEqual(histo.dtype, np.uint32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(cumul, expected_c))
+
+ def test_nominal_wo_weights(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+
+ histo, cumul = Histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=None)[0:2]
+
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(cumul is None)
+
+ def test_nominal_last_bin_closed(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 2])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 1101.1])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ histo, cumul = Histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ last_bin_closed=True)[0:2]
+
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(cumul, expected_c))
+
+ def test_int32_weights_double_weights_range(self):
+ """
+ """
+ weight_min = -299.9 # ===> will be cast to -299
+ weight_max = 499.9 # ===> will be cast to 499
+
+ expected_h_tpl = np.array([0, 1, 1, 1, 0])
+ expected_c_tpl = np.array([0., 0., 0., 300., 0.])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ histo, cumul = Histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights.astype(np.int32),
+ weight_min=weight_min,
+ weight_max=weight_max)[0:2]
+
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(cumul, expected_c))
+
+ def test_nominal_no_sample(self):
+ """
+ """
+
+ histo_inst = Histogramnd(None,
+ self.histo_range,
+ self.n_bins)
+
+ histo, weighted_histo, edges = histo_inst
+
+ self.assertIsNone(histo)
+ self.assertIsNone(weighted_histo)
+ self.assertIsNone(edges)
+ self.assertIsNone(histo_inst.histo)
+ self.assertIsNone(histo_inst.weighted_histo)
+ self.assertIsNone(histo_inst.edges)
+
+ def test_empty_init_accumulate(self):
+ """
+ """
+ expected_h_tpl = np.array([2, 1, 1, 1, 1])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ histo_inst = Histogramnd(None,
+ self.histo_range,
+ self.n_bins)
+
+ histo_inst.accumulate(self.sample,
+ weights=self.weights)
+
+ histo = histo_inst.histo
+ cumul = histo_inst.weighted_histo
+ bin_edges = histo_inst.edges
+
+ expected_edges = _get_bin_edges(self.histo_range,
+ self.n_bins,
+ self.ndims)
+
+ self.assertEqual(cumul.dtype, np.float64)
+ self.assertEqual(histo.dtype, np.uint32)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(cumul, expected_c))
+
+ for i_edges, edges in enumerate(expected_edges):
+ self.assertTrue(np.array_equal(bin_edges[i_edges],
+ expected_edges[i_edges]),
+ msg='Testing bin_edges for dim {0}'
+ ''.format(i_edges+1))
+
+ def test_accumulate(self):
+ """
+ """
+
+ expected_h_tpl = np.array([2, 3, 2, 2, 2])
+ expected_c_tpl = np.array([-700.7, -7007.5, -4.99, 300.4, 3503.5])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ histo_inst = Histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights)
+
+ sample_2 = self.sample[:]
+ if len(sample_2.shape) == 1:
+ idx = (slice(0, None),)
+ else:
+ idx = slice(0, None), self.tested_dim
+
+ sample_2[idx] += 2
+
+ histo_inst.accumulate(sample_2, # <==== !!
+ weights=10 * self.weights) # <==== !!
+
+ histo = histo_inst.histo
+ cumul = histo_inst.weighted_histo
+ bin_edges = histo_inst.edges
+
+ self.assertEqual(cumul.dtype, np.float64)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.allclose(cumul, expected_c, rtol=10e-15))
+
+ def test_accumulate_no_weights(self):
+ """
+ """
+
+ expected_h_tpl = np.array([2, 3, 2, 2, 2])
+ expected_c_tpl = np.array([-700.7, -0.5, 0.01, 300.3, 500.5])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ histo_inst = Histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights)
+
+ sample_2 = self.sample[:]
+ if len(sample_2.shape) == 1:
+ idx = (slice(0, None),)
+ else:
+ idx = slice(0, None), self.tested_dim
+
+ sample_2[idx] += 2
+
+ histo_inst.accumulate(sample_2) # <==== !!
+
+ histo = histo_inst.histo
+ cumul = histo_inst.weighted_histo
+ bin_edges = histo_inst.edges
+
+ self.assertEqual(cumul.dtype, np.float64)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.allclose(cumul, expected_c, rtol=10e-15))
+
+ def test_accumulate_no_weights_at_init(self):
+ """
+ """
+
+ expected_h_tpl = np.array([2, 3, 2, 2, 2])
+ expected_c_tpl = np.array([0.0, -700.7, -0.5, 0.01, 300.3])
+
+ expected_h = np.zeros(shape=self.n_bins, dtype=np.double)
+ expected_c = np.zeros(shape=self.n_bins, dtype=np.double)
+
+ self.fill_histo(expected_h, expected_h_tpl, self.ndims-1)
+ self.fill_histo(expected_c, expected_c_tpl, self.ndims-1)
+
+ histo_inst = Histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=None) # <==== !!
+
+ cumul = histo_inst.weighted_histo
+ self.assertIsNone(cumul)
+
+ sample_2 = self.sample[:]
+ if len(sample_2.shape) == 1:
+ idx = (slice(0, None),)
+ else:
+ idx = slice(0, None), self.tested_dim
+
+ sample_2[idx] += 2
+
+ histo_inst.accumulate(sample_2,
+ weights=self.weights) # <==== !!
+
+ histo = histo_inst.histo
+ cumul = histo_inst.weighted_histo
+ bin_edges = histo_inst.edges
+
+ self.assertEqual(cumul.dtype, np.float64)
+ self.assertTrue(np.array_equal(histo, expected_h))
+ self.assertTrue(np.array_equal(cumul, expected_c))
+
+ def testNoneNativeTypes(self):
+ type = self.sample.dtype.newbyteorder("B")
+ sampleB = self.sample.astype(type)
+
+ type = self.sample.dtype.newbyteorder("L")
+ sampleL = self.sample.astype(type)
+
+ histo_inst = Histogramnd(sampleB,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights)
+
+ histo_inst = Histogramnd(sampleL,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights)
+
+
+class Test_chistogram_nominal_1d(_Test_chistogramnd_nominal):
+ __test__ = True # because _Test_chistogramnd_nominal is ignored
+ ndims = 1
+
+
+class Test_chistogram_nominal_2d(_Test_chistogramnd_nominal):
+ __test__ = True # because _Test_chistogramnd_nominal is ignored
+ ndims = 2
+
+
+class Test_chistogram_nominal_3d(_Test_chistogramnd_nominal):
+ __test__ = True # because _Test_chistogramnd_nominal is ignored
+ ndims = 3
+
+
+class Test_Histogramnd_nominal_1d(_Test_Histogramnd_nominal):
+ __test__ = True # because _Test_chistogramnd_nominal is ignored
+ ndims = 1
+
+
+class Test_Histogramnd_nominal_2d(_Test_Histogramnd_nominal):
+ __test__ = True # because _Test_chistogramnd_nominal is ignored
+ ndims = 2
+
+
+class Test_Histogramnd_nominal_3d(_Test_Histogramnd_nominal):
+ __test__ = True # because _Test_chistogramnd_nominal is ignored
+ ndims = 3
diff --git a/src/silx/math/test/test_histogramnd_vs_np.py b/src/silx/math/test/test_histogramnd_vs_np.py
new file mode 100644
index 0000000..d6a8d19
--- /dev/null
+++ b/src/silx/math/test/test_histogramnd_vs_np.py
@@ -0,0 +1,826 @@
+# coding: utf-8
+# /*##########################################################################
+# Copyright (C) 2016-2021 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ############################################################################*/
+"""
+Tests for the histogramnd function.
+Results are compared to numpy's histogramdd.
+"""
+
+import unittest
+import operator
+
+import numpy as np
+
+from silx.math.chistogramnd import chistogramnd as histogramnd
+
+# ==============================================================
+# ==============================================================
+# ==============================================================
+
+_RTOL_DICT = {np.float64: 10**-13,
+ np.float32: 10**-5}
+
+# ==============================================================
+# ==============================================================
+# ==============================================================
+
+
+def _add_values_to_array_if_missing(array, values, n_values):
+ max_in_col = np.any(array[:, ...] == values, axis=0)
+
+ if len(array.shape) == 1:
+ if not max_in_col:
+ rnd_idx = np.random.randint(0,
+ high=len(array)-1,
+ size=(n_values,))
+ array[rnd_idx] = values
+ else:
+ for i in range(len(max_in_col)):
+ if not max_in_col[i]:
+ rnd_idx = np.random.randint(0,
+ high=len(array)-1,
+ size=(n_values,))
+ array[rnd_idx, i] = values[i]
+
+
+def _get_values_index(array, values, op=operator.lt):
+ idx = op(array[:, ...], values)
+ if array.ndim > 1:
+ idx = np.all(idx, axis=1)
+ return np.where(idx)[0]
+
+
+def _get_in_range_indices(array,
+ minvalues,
+ maxvalues,
+ minop=operator.ge,
+ maxop=operator.lt):
+ idx = np.logical_and(minop(array, minvalues),
+ maxop(array, maxvalues))
+ if array.ndim > 1:
+ idx = np.all(idx, axis=1)
+ return np.where(idx)[0]
+
+
+class _TestHistogramnd(unittest.TestCase):
+ """
+ Unit tests of the histogramnd function.
+ """
+ __test__ = False # ignore abstract class
+
+ sample_rng = None
+ weights_rng = None
+ n_dims = None
+
+ filter_min = None
+ filter_max = None
+
+ histo_range = None
+ n_bins = None
+
+ dtype_sample = None
+ dtype_weights = None
+
+ def generate_data(self):
+
+ self.longMessage = True
+
+ int_min = 0
+ int_max = 100000
+ n_elements = 10**5
+
+ if self.n_dims == 1:
+ shape = (n_elements,)
+ else:
+ shape = (n_elements, self.n_dims,)
+
+ self.rng_state = np.random.get_state()
+
+ self.state_msg = ('Current RNG state :\n'
+ '{0}'.format(self.rng_state))
+
+ sample = np.random.randint(int_min,
+ high=int_max,
+ size=shape)
+
+ sample = sample.astype(self.dtype_sample)
+ sample = (self.sample_rng[0] +
+ (sample-int_min) *
+ (self.sample_rng[1]-self.sample_rng[0]) /
+ (int_max-int_min)).astype(self.dtype_sample)
+
+ weights = np.random.randint(int_min,
+ high=int_max,
+ size=(n_elements,))
+ weights = weights.astype(self.dtype_weights)
+ weights = (self.weights_rng[0] +
+ (weights-int_min) *
+ (self.weights_rng[1]-self.weights_rng[0]) /
+ (int_max-int_min)).astype(self.dtype_weights)
+
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ # the bins range are cast to the same type as the sample
+ # in order to get the same results as numpy
+ # (which doesnt cast the range)
+ self.histo_range = np.array(self.histo_range).astype(self.dtype_sample)
+
+ # adding some values that are equal to the max
+ # in order to test the opened/closed last bin
+ bins_max = [b[1] for b in self.histo_range]
+ _add_values_to_array_if_missing(sample,
+ bins_max,
+ 100)
+
+ # adding some values that are equal to the min weight value
+ # in order to test the filters
+ _add_values_to_array_if_missing(weights,
+ self.weights_rng[0],
+ 100)
+
+ # adding some values that are equal to the max weight value
+ # in order to test the filters
+ _add_values_to_array_if_missing(weights,
+ self.weights_rng[1],
+ 100)
+
+ return sample, weights
+
+ def setUp(self):
+ if type(self).__name__.startswith("_"):
+ self.skipTest("Abstract class")
+ self.sample, self.weights = self.generate_data()
+ self.rtol = _RTOL_DICT.get(self.dtype_weights, None)
+
+ def array_compare(self, ar_a, ar_b):
+ if self.rtol is None:
+ return np.array_equal(ar_a, ar_b)
+ return np.allclose(ar_a, ar_b, self.rtol)
+
+ def test_bin_ranges(self):
+ """
+
+ """
+ result_c = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ last_bin_closed=True)
+
+ result_np = np.histogramdd(self.sample,
+ bins=self.n_bins,
+ range=self.histo_range)
+
+ for i_edges, edges in enumerate(result_c[2]):
+ # allclose for now until I can try with the latest version (TBD)
+ # of numpy
+ self.assertTrue(np.allclose(edges,
+ result_np[1][i_edges]),
+ msg='{0}. Testing bin_edges for dim {1}.'
+ ''.format(self.state_msg, i_edges+1))
+
+ def test_last_bin_closed(self):
+ """
+
+ """
+ result_c = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ last_bin_closed=True)
+
+ result_np = np.histogramdd(self.sample,
+ bins=self.n_bins,
+ range=self.histo_range)
+
+ result_np_w = np.histogramdd(self.sample,
+ bins=self.n_bins,
+ range=self.histo_range,
+ weights=self.weights)
+
+ # comparing "hits"
+ hits_cmp = np.array_equal(result_c[0],
+ result_np[0])
+ # comparing weights
+ weights_cmp = np.array_equal(result_c[1],
+ result_np_w[0])
+
+ self.assertTrue(hits_cmp, msg=self.state_msg)
+ self.assertTrue(weights_cmp, msg=self.state_msg)
+
+ bins_min = [rng[0] for rng in self.histo_range]
+ bins_max = [rng[1] for rng in self.histo_range]
+ inrange_idx = _get_in_range_indices(self.sample,
+ bins_min,
+ bins_max,
+ minop=operator.ge,
+ maxop=operator.le)
+
+ self.assertEqual(result_c[0].sum(), inrange_idx.shape[0],
+ msg=self.state_msg)
+
+ # we have to sum the weights using the same precision as the
+ # histogramnd function
+ weights_sum = self.weights[inrange_idx].astype(result_c[1].dtype).sum()
+ self.assertTrue(self.array_compare(result_c[1].sum(), weights_sum),
+ msg=self.state_msg)
+
+ def test_last_bin_open(self):
+ """
+
+ """
+ result_c = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ last_bin_closed=False)
+
+ bins_max = [rng[1] for rng in self.histo_range]
+ filtered_idx = _get_values_index(self.sample, bins_max)
+
+ result_np = np.histogramdd(self.sample[filtered_idx],
+ bins=self.n_bins,
+ range=self.histo_range)
+
+ result_np_w = np.histogramdd(self.sample[filtered_idx],
+ bins=self.n_bins,
+ range=self.histo_range,
+ weights=self.weights[filtered_idx])
+
+ # comparing "hits"
+ hits_cmp = np.array_equal(result_c[0], result_np[0])
+ # comparing weights
+ weights_cmp = np.array_equal(result_c[1],
+ result_np_w[0])
+
+ self.assertTrue(hits_cmp, msg=self.state_msg)
+ self.assertTrue(weights_cmp, msg=self.state_msg)
+
+ bins_min = [rng[0] for rng in self.histo_range]
+ bins_max = [rng[1] for rng in self.histo_range]
+ inrange_idx = _get_in_range_indices(self.sample,
+ bins_min,
+ bins_max,
+ minop=operator.ge,
+ maxop=operator.lt)
+
+ self.assertEqual(result_c[0].sum(), len(inrange_idx),
+ msg=self.state_msg)
+ # we have to sum the weights using the same precision as the
+ # histogramnd function
+ weights_sum = self.weights[inrange_idx].astype(result_c[1].dtype).sum()
+ self.assertTrue(self.array_compare(result_c[1].sum(), weights_sum),
+ msg=self.state_msg)
+
+ def test_filter_min(self):
+ """
+
+ """
+ result_c = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ last_bin_closed=True,
+ weight_min=self.filter_min)
+
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ filter_min = self.dtype_weights(self.filter_min)
+
+ weight_idx = _get_values_index(self.weights,
+ filter_min, # <------ !!!
+ operator.ge)
+
+ result_np = np.histogramdd(self.sample[weight_idx],
+ bins=self.n_bins,
+ range=self.histo_range)
+
+ result_np_w = np.histogramdd(self.sample[weight_idx],
+ bins=self.n_bins,
+ range=self.histo_range,
+ weights=self.weights[weight_idx])
+
+ # comparing "hits"
+ hits_cmp = np.array_equal(result_c[0],
+ result_np[0])
+ # comparing weights
+ weights_cmp = np.array_equal(result_c[1], result_np_w[0])
+
+ self.assertTrue(hits_cmp, msg=self.state_msg)
+ self.assertTrue(weights_cmp, msg=self.state_msg)
+
+ bins_min = [rng[0] for rng in self.histo_range]
+ bins_max = [rng[1] for rng in self.histo_range]
+ inrange_idx = _get_in_range_indices(self.sample[weight_idx],
+ bins_min,
+ bins_max,
+ minop=operator.ge,
+ maxop=operator.le)
+
+ inrange_idx = weight_idx[inrange_idx]
+
+ self.assertEqual(result_c[0].sum(), len(inrange_idx),
+ msg=self.state_msg)
+
+ # we have to sum the weights using the same precision as the
+ # histogramnd function
+ weights_sum = self.weights[inrange_idx].astype(result_c[1].dtype).sum()
+ self.assertTrue(self.array_compare(result_c[1].sum(), weights_sum),
+ msg=self.state_msg)
+
+ def test_filter_max(self):
+ """
+
+ """
+ result_c = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ last_bin_closed=True,
+ weight_max=self.filter_max)
+
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ filter_max = self.dtype_weights(self.filter_max)
+
+ weight_idx = _get_values_index(self.weights,
+ filter_max, # <------ !!!
+ operator.le)
+
+ result_np = np.histogramdd(self.sample[weight_idx],
+ bins=self.n_bins,
+ range=self.histo_range)
+
+ result_np_w = np.histogramdd(self.sample[weight_idx],
+ bins=self.n_bins,
+ range=self.histo_range,
+ weights=self.weights[weight_idx])
+
+ # comparing "hits"
+ hits_cmp = np.array_equal(result_c[0],
+ result_np[0])
+ # comparing weights
+ weights_cmp = np.array_equal(result_c[1], result_np_w[0])
+
+ self.assertTrue(hits_cmp, msg=self.state_msg)
+ self.assertTrue(weights_cmp, msg=self.state_msg)
+
+ bins_min = [rng[0] for rng in self.histo_range]
+ bins_max = [rng[1] for rng in self.histo_range]
+ inrange_idx = _get_in_range_indices(self.sample[weight_idx],
+ bins_min,
+ bins_max,
+ minop=operator.ge,
+ maxop=operator.le)
+
+ inrange_idx = weight_idx[inrange_idx]
+
+ self.assertEqual(result_c[0].sum(), len(inrange_idx),
+ msg=self.state_msg)
+
+ # we have to sum the weights using the same precision as the
+ # histogramnd function
+ weights_sum = self.weights[inrange_idx].astype(result_c[1].dtype).sum()
+ self.assertTrue(self.array_compare(result_c[1].sum(), weights_sum),
+ msg=self.state_msg)
+
+ def test_filter_minmax(self):
+ """
+
+ """
+ result_c = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ last_bin_closed=True,
+ weight_min=self.filter_min,
+ weight_max=self.filter_max)
+
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ filter_min = self.dtype_weights(self.filter_min)
+ filter_max = self.dtype_weights(self.filter_max)
+
+ weight_idx = _get_in_range_indices(self.weights,
+ filter_min, # <------ !!!
+ filter_max, # <------ !!!
+ minop=operator.ge,
+ maxop=operator.le)
+
+ result_np = np.histogramdd(self.sample[weight_idx],
+ bins=self.n_bins,
+ range=self.histo_range)
+
+ result_np_w = np.histogramdd(self.sample[weight_idx],
+ bins=self.n_bins,
+ range=self.histo_range,
+ weights=self.weights[weight_idx])
+
+ # comparing "hits"
+ hits_cmp = np.array_equal(result_c[0],
+ result_np[0])
+ # comparing weights
+ weights_cmp = np.array_equal(result_c[1], result_np_w[0])
+
+ self.assertTrue(hits_cmp)
+ self.assertTrue(weights_cmp)
+
+ bins_min = [rng[0] for rng in self.histo_range]
+ bins_max = [rng[1] for rng in self.histo_range]
+ inrange_idx = _get_in_range_indices(self.sample[weight_idx],
+ bins_min,
+ bins_max,
+ minop=operator.ge,
+ maxop=operator.le)
+
+ inrange_idx = weight_idx[inrange_idx]
+
+ self.assertEqual(result_c[0].sum(), len(inrange_idx),
+ msg=self.state_msg)
+
+ # we have to sum the weights using the same precision as the
+ # histogramnd function
+ weights_sum = self.weights[inrange_idx].astype(result_c[1].dtype).sum()
+ self.assertTrue(self.array_compare(result_c[1].sum(), weights_sum),
+ msg=self.state_msg)
+
+ def test_reuse_histo(self):
+ """
+
+ """
+ result_c_1 = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ last_bin_closed=True)
+
+ result_np_1 = np.histogramdd(self.sample,
+ bins=self.n_bins,
+ range=self.histo_range)
+
+ np.histogramdd(self.sample,
+ bins=self.n_bins,
+ range=self.histo_range,
+ weights=self.weights)
+
+ sample_2, weights_2 = self.generate_data()
+
+ result_c_2 = histogramnd(sample_2,
+ self.histo_range,
+ self.n_bins,
+ weights=weights_2,
+ last_bin_closed=True,
+ histo=result_c_1[0])
+
+ result_np_2 = np.histogramdd(sample_2,
+ bins=self.n_bins,
+ range=self.histo_range)
+
+ result_np_w_2 = np.histogramdd(sample_2,
+ bins=self.n_bins,
+ range=self.histo_range,
+ weights=weights_2)
+
+ # comparing "hits"
+ hits_cmp = np.array_equal(result_c_2[0],
+ result_np_1[0] +
+ result_np_2[0])
+ # comparing weights
+ weights_cmp = np.array_equal(result_c_2[1],
+ result_np_w_2[0])
+
+ self.assertTrue(hits_cmp, msg=self.state_msg)
+ self.assertTrue(weights_cmp, msg=self.state_msg)
+
+ def test_reuse_cumul(self):
+ """
+
+ """
+ result_c = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ last_bin_closed=True)
+
+ np.histogramdd(self.sample,
+ bins=self.n_bins,
+ range=self.histo_range)
+
+ result_np_w = np.histogramdd(self.sample,
+ bins=self.n_bins,
+ range=self.histo_range,
+ weights=self.weights)
+
+ sample_2, weights_2 = self.generate_data()
+
+ result_c_2 = histogramnd(sample_2,
+ self.histo_range,
+ self.n_bins,
+ weights=weights_2,
+ last_bin_closed=True,
+ weighted_histo=result_c[1])
+
+ result_np_2 = np.histogramdd(sample_2,
+ bins=self.n_bins,
+ range=self.histo_range)
+
+ result_np_w_2 = np.histogramdd(sample_2,
+ bins=self.n_bins,
+ range=self.histo_range,
+ weights=weights_2)
+
+ # comparing "hits"
+ hits_cmp = np.array_equal(result_c_2[0],
+ result_np_2[0])
+ # comparing weights
+
+ self.assertTrue(hits_cmp, msg=self.state_msg)
+ self.assertTrue(self.array_compare(result_c_2[1],
+ result_np_w[0] + result_np_w_2[0]),
+ msg=self.state_msg)
+
+ def test_reuse_cumul_float(self):
+ """
+
+ """
+ n_bins = np.array(self.n_bins, ndmin=1)
+ if len(self.sample.shape) == 2:
+ if len(n_bins) == self.sample.shape[1]:
+ shp = tuple([x for x in n_bins])
+ else:
+ shp = (self.n_bins,) * self.sample.shape[1]
+ cumul = np.zeros(shp, dtype=np.float32)
+ else:
+ shp = (self.n_bins,)
+ cumul = np.zeros(shp, dtype=np.float32)
+
+ result_c_1 = histogramnd(self.sample,
+ self.histo_range,
+ self.n_bins,
+ weights=self.weights,
+ last_bin_closed=True,
+ weighted_histo=cumul)
+
+ result_np_1 = np.histogramdd(self.sample,
+ bins=self.n_bins,
+ range=self.histo_range)
+
+ result_np_w_1 = np.histogramdd(self.sample,
+ bins=self.n_bins,
+ range=self.histo_range,
+ weights=self.weights)
+
+ # comparing "hits"
+ hits_cmp = np.array_equal(result_c_1[0],
+ result_np_1[0])
+
+ self.assertTrue(hits_cmp, msg=self.state_msg)
+ self.assertEqual(result_c_1[1].dtype, np.float32, msg=self.state_msg)
+
+ bins_min = [rng[0] for rng in self.histo_range]
+ bins_max = [rng[1] for rng in self.histo_range]
+ inrange_idx = _get_in_range_indices(self.sample,
+ bins_min,
+ bins_max,
+ minop=operator.ge,
+ maxop=operator.le)
+ weights_sum = \
+ self.weights[inrange_idx].astype(np.float32).sum(dtype=np.float64)
+ self.assertTrue(np.allclose(result_c_1[1].sum(dtype=np.float64),
+ weights_sum), msg=self.state_msg)
+ self.assertTrue(np.allclose(result_c_1[1].sum(dtype=np.float64),
+ result_np_w_1[0].sum(dtype=np.float64)),
+ msg=self.state_msg)
+
+
+class _TestHistogramnd_1d(_TestHistogramnd):
+ """
+ Unit tests of the 1D histogramnd function.
+ """
+ sample_rng = [-55., 100.]
+ weights_rng = [-70., 150.]
+ n_dims = 1
+ filter_min = -15.6
+ filter_max = 85.7
+
+ histo_range = [[-30.2, 90.3]]
+ n_bins = 30
+
+ dtype = None
+
+
+class _TestHistogramnd_2d(_TestHistogramnd):
+ """
+ Unit tests of the 1D histogramnd function.
+ """
+ sample_rng = [-50.2, 100.99]
+ weights_rng = [70., 150.]
+ n_dims = 2
+ filter_min = 81.7
+ filter_max = 135.3
+
+ histo_range = [[10., 90.], [20., 70.]]
+ n_bins = 30
+
+ dtype = None
+
+
+class _TestHistogramnd_3d(_TestHistogramnd):
+ """
+ Unit tests of the 1D histogramnd function.
+ """
+ sample_rng = [10.2, 200.9]
+ weights_rng = [0., 100.]
+ n_dims = 3
+ filter_min = 31.5
+ filter_max = 83.7
+
+ histo_range = [[30.8, 150.2], [20.1, 90.9], [10.1, 195.]]
+ n_bins = 30
+
+ dtype = None
+
+
+# ################################################################
+# ################################################################
+# ################################################################
+# ################################################################
+
+
+class TestHistogramnd_1d_double_double(_TestHistogramnd_1d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.double
+ dtype_weights = np.double
+
+
+class TestHistogramnd_1d_double_float(_TestHistogramnd_1d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.double
+ dtype_weights = np.float32
+
+
+class TestHistogramnd_1d_double_int32(_TestHistogramnd_1d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.double
+ dtype_weights = np.int32
+
+
+class TestHistogramnd_1d_float_double(_TestHistogramnd_1d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.float32
+ dtype_weights = np.double
+
+
+class TestHistogramnd_1d_float_float(_TestHistogramnd_1d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.float32
+ dtype_weights = np.float32
+
+
+class TestHistogramnd_1d_float_int32(_TestHistogramnd_1d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.float32
+ dtype_weights = np.int32
+
+
+class TestHistogramnd_1d_int32_double(_TestHistogramnd_1d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.int32
+ dtype_weights = np.double
+
+
+class TestHistogramnd_1d_int32_float(_TestHistogramnd_1d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.int32
+ dtype_weights = np.float32
+
+
+class TestHistogramnd_1d_int32_int32(_TestHistogramnd_1d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.int32
+ dtype_weights = np.int32
+
+
+class TestHistogramnd_2d_double_double(_TestHistogramnd_2d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.double
+ dtype_weights = np.double
+
+
+class TestHistogramnd_2d_double_float(_TestHistogramnd_2d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.double
+ dtype_weights = np.float32
+
+
+class TestHistogramnd_2d_double_int32(_TestHistogramnd_2d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.double
+ dtype_weights = np.int32
+
+
+class TestHistogramnd_2d_float_double(_TestHistogramnd_2d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.float32
+ dtype_weights = np.double
+
+
+class TestHistogramnd_2d_float_float(_TestHistogramnd_2d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.float32
+ dtype_weights = np.float32
+
+
+class TestHistogramnd_2d_float_int32(_TestHistogramnd_2d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.float32
+ dtype_weights = np.int32
+
+
+class TestHistogramnd_2d_int32_double(_TestHistogramnd_2d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.int32
+ dtype_weights = np.double
+
+
+class TestHistogramnd_2d_int32_float(_TestHistogramnd_2d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.int32
+ dtype_weights = np.float32
+
+
+class TestHistogramnd_2d_int32_int32(_TestHistogramnd_2d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.int32
+ dtype_weights = np.int32
+
+
+class TestHistogramnd_3d_double_double(_TestHistogramnd_3d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.double
+ dtype_weights = np.double
+
+
+class TestHistogramnd_3d_double_float(_TestHistogramnd_3d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.double
+ dtype_weights = np.float32
+
+
+class TestHistogramnd_3d_double_int32(_TestHistogramnd_3d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.double
+ dtype_weights = np.int32
+
+
+class TestHistogramnd_3d_float_double(_TestHistogramnd_3d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.float32
+ dtype_weights = np.double
+
+
+class TestHistogramnd_3d_float_float(_TestHistogramnd_3d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.float32
+ dtype_weights = np.float32
+
+
+class TestHistogramnd_3d_float_int32(_TestHistogramnd_3d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.float32
+ dtype_weights = np.int32
+
+
+class TestHistogramnd_3d_int32_double(_TestHistogramnd_3d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.int32
+ dtype_weights = np.double
+
+
+class TestHistogramnd_3d_int32_float(_TestHistogramnd_3d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.int32
+ dtype_weights = np.float32
+
+
+class TestHistogramnd_3d_int32_int32(_TestHistogramnd_3d):
+ __test__ = True # because _TestHistogramnd is ignored
+ dtype_sample = np.int32
+ dtype_weights = np.int32
diff --git a/src/silx/math/test/test_interpolate.py b/src/silx/math/test/test_interpolate.py
new file mode 100644
index 0000000..146449d
--- /dev/null
+++ b/src/silx/math/test/test_interpolate.py
@@ -0,0 +1,125 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2019 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ############################################################################*/
+"""Test for interpolate module"""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "11/07/2019"
+
+
+import unittest
+
+import numpy
+try:
+ from scipy.interpolate import interpn
+except ImportError:
+ interpn = None
+
+from silx.utils.testutils import ParametricTestCase
+from silx.math import interpolate
+
+
+@unittest.skipUnless(interpn is not None, "scipy missing")
+class TestInterp3d(ParametricTestCase):
+ """Test silx.math.interpolate.interp3d"""
+
+ @staticmethod
+ def ref_interp3d(data, points):
+ """Reference implementation of interp3d based on scipy
+
+ :param numpy.ndarray data: 3D floating dataset
+ :param numpy.ndarray points: Array of points of shape (N, 3)
+ """
+ return interpn(
+ [numpy.arange(dim, dtype=data.dtype) for dim in data.shape],
+ data,
+ points,
+ method='linear')
+
+ def test_random_data(self):
+ """Test interp3d with random data"""
+ size = 32
+ npoints = 10
+
+ ref_data = numpy.random.random((size, size, size))
+ ref_points = numpy.random.random(npoints*3).reshape(npoints, 3) * (size -1)
+
+ for dtype in (numpy.float32, numpy.float64):
+ data = ref_data.astype(dtype)
+ points = ref_points.astype(dtype)
+ ref_result = self.ref_interp3d(data, points)
+
+ for method in (u'linear', u'linear_omp'):
+ with self.subTest(method=method):
+ result = interpolate.interp3d(data, points, method=method)
+ self.assertTrue(numpy.allclose(ref_result, result))
+
+ def test_notfinite_data(self):
+ """Test interp3d with NaN and inf"""
+ data = numpy.ones((3, 3, 3), dtype=numpy.float64)
+ data[0, 0, 0] = numpy.nan
+ data[2, 2, 2] = numpy.inf
+ points = numpy.array([(0.5, 0.5, 0.5),
+ (1.5, 1.5, 1.5)])
+
+ for method in (u'linear', u'linear_omp'):
+ with self.subTest(method=method):
+ result = interpolate.interp3d(
+ data, points, method=method)
+ self.assertTrue(numpy.isnan(result[0]))
+ self.assertTrue(result[1] == numpy.inf)
+
+ def test_points_outside(self):
+ """Test interp3d with points outside the volume"""
+ data = numpy.ones((4, 4, 4), dtype=numpy.float64)
+ points = numpy.array([(-0.1, -0.1, -0.1),
+ (3.1, 3.1, 3.1),
+ (-0.1, 1., 1.),
+ (1., 1., 3.1)])
+
+ for method in (u'linear', u'linear_omp'):
+ for fill_value in (numpy.nan, 0., -1.):
+ with self.subTest(method=method):
+ result = interpolate.interp3d(
+ data, points, method=method, fill_value=fill_value)
+ if numpy.isnan(fill_value):
+ self.assertTrue(numpy.all(numpy.isnan(result)))
+ else:
+ self.assertTrue(numpy.all(numpy.equal(result, fill_value)))
+
+ def test_integer_points(self):
+ """Test interp3d with integer points coord"""
+ data = numpy.arange(4**3, dtype=numpy.float64).reshape(4, 4, 4)
+ points = numpy.array([(0., 0., 0.),
+ (0., 0., 1.),
+ (2., 3., 0.),
+ (3., 3., 3.)])
+
+ ref_result = data[tuple(points.T.astype(numpy.int32))]
+
+ for method in (u'linear', u'linear_omp'):
+ with self.subTest(method=method):
+ result = interpolate.interp3d(data, points, method=method)
+ self.assertTrue(numpy.allclose(ref_result, result))
diff --git a/src/silx/math/test/test_marchingcubes.py b/src/silx/math/test/test_marchingcubes.py
new file mode 100644
index 0000000..5e2b193
--- /dev/null
+++ b/src/silx/math/test/test_marchingcubes.py
@@ -0,0 +1,174 @@
+# coding: utf-8
+# /*##########################################################################
+# Copyright (C) 2016 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ############################################################################*/
+"""Tests of the marchingcubes module"""
+
+from __future__ import division
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "17/01/2018"
+
+import unittest
+
+import numpy
+
+from silx.utils.testutils import ParametricTestCase
+
+from silx.math import marchingcubes
+
+
+class TestMarchingCubes(ParametricTestCase):
+ """Tests of marching cubes"""
+
+ def assertAllClose(self, array1, array2, msg=None,
+ rtol=1e-05, atol=1e-08):
+ """Assert that the 2 numpy.ndarrays are almost equal.
+
+ :param str msg: Message to provide when assert fails
+ :param float rtol: Relative tolerance, see :func:`numpy.allclose`
+ :param float atol: Absolute tolerance, see :func:`numpy.allclose`
+ """
+ if not numpy.allclose(array1, array2, rtol, atol):
+ raise self.failureException(msg)
+
+ def test_cube(self):
+ """Unit tests with a single cube"""
+
+ # No isosurface
+ cube_zero = numpy.zeros((2, 2, 2), dtype=numpy.float32)
+
+ result = marchingcubes.MarchingCubes(cube_zero, 1.)
+ self.assertEqual(result.shape, cube_zero.shape)
+ self.assertEqual(result.isolevel, 1.)
+ self.assertEqual(result.invert_normals, True)
+
+ vertices, normals, indices = result
+ self.assertEqual(len(vertices), 0)
+ self.assertEqual(len(normals), 0)
+ self.assertEqual(len(indices), 0)
+
+ # Cube array dimensions: shape = (dim 0, dim 1, dim2)
+ #
+ # dim 0 (Z)
+ # ^
+ # |
+ # 4 +------+ 5
+ # /| /|
+ # / | / |
+ # 6 +------+ 7|
+ # | | | |
+ # |0 +---|--+ 1 -> dim 2 (X)
+ # | / | /
+ # |/ |/
+ # 2 +------+ 3
+ # /
+ # dim 1 (Y)
+
+ # isosurface perpendicular to dim 0 (Z)
+ cube = numpy.array(
+ (((0., 0.), (0., 0.)),
+ ((1., 1.), (1., 1.))), dtype=numpy.float32)
+ level = 0.5
+ vertices, normals, indices = marchingcubes.MarchingCubes(
+ cube, level, invert_normals=False)
+ self.assertAllClose(vertices[:, 0], level)
+ self.assertAllClose(normals, (1., 0., 0.))
+ self.assertEqual(len(indices), 2)
+
+ # isosurface perpendicular to dim 1 (Y)
+ cube = numpy.array(
+ (((0., 0.), (1., 1.)),
+ ((0., 0.), (1., 1.))), dtype=numpy.float32)
+ level = 0.2
+ vertices, normals, indices = marchingcubes.MarchingCubes(cube, level)
+ self.assertAllClose(vertices[:, 1], level)
+ self.assertAllClose(normals, (0., -1., 0.))
+ self.assertEqual(len(indices), 2)
+
+ # isosurface perpendicular to dim 2 (X)
+ cube = numpy.array(
+ (((0., 1.), (0., 1.)),
+ ((0., 1.), (0., 1.))), dtype=numpy.float32)
+ level = 0.9
+ vertices, normals, indices = marchingcubes.MarchingCubes(
+ cube, level, invert_normals=False)
+ self.assertAllClose(vertices[:, 2], level)
+ self.assertAllClose(normals, (0., 0., 1.))
+ self.assertEqual(len(indices), 2)
+
+ # isosurface normal in dim1, dim 0 (Y, Z) plane
+ cube = numpy.array(
+ (((0., 0.), (0., 0.)),
+ ((0., 0.), (1., 1.))), dtype=numpy.float32)
+ level = 0.5
+ vertices, normals, indices = marchingcubes.MarchingCubes(cube, level)
+ self.assertAllClose(normals[:, 2], 0.)
+ self.assertEqual(len(indices), 2)
+
+ def test_sampling(self):
+ """Test different sampling, comparing to reference without sampling"""
+ isolevel = 0.5
+ size = 9
+ chessboard = numpy.zeros((size, size, size), dtype=numpy.float32)
+ chessboard.reshape(-1)[::2] = 1 # OK as long as dimensions are odd
+
+ ref_result = marchingcubes.MarchingCubes(chessboard, isolevel)
+
+ samplings = [
+ (2, 1, 1),
+ (1, 2, 1),
+ (1, 1, 2),
+ (2, 2, 2),
+ (3, 3, 3),
+ (1, 3, 1),
+ (1, 1, 3),
+ ]
+
+ for sampling in samplings:
+ with self.subTest(sampling=sampling):
+ sampling = numpy.array(sampling)
+
+ data = 1e6 * numpy.ones(
+ sampling * size, dtype=numpy.float32)
+ # Copy ref chessboard in data according to sampling
+ data[::sampling[0], ::sampling[1], ::sampling[2]] = chessboard
+
+ result = marchingcubes.MarchingCubes(data, isolevel,
+ sampling=sampling)
+ # Compare vertices normalized with shape
+ self.assertAllClose(
+ ref_result.get_vertices() / ref_result.shape,
+ result.get_vertices() / result.shape,
+ atol=0., rtol=0.)
+
+ # Compare normals
+ # This comparison only works for normals aligned with axes
+ # otherwise non uniform sampling would make different normals
+ self.assertAllClose(ref_result.get_normals(),
+ result.get_normals(),
+ atol=0., rtol=0.)
+
+ self.assertAllClose(ref_result.get_indices(),
+ result.get_indices(),
+ atol=0., rtol=0.)