diff --git a/.github/workflows/docker_build_test_publish.yml b/.github/workflows/docker_build_test_publish.yml index a0a9ebfb..5a240dc6 100644 --- a/.github/workflows/docker_build_test_publish.yml +++ b/.github/workflows/docker_build_test_publish.yml @@ -33,5 +33,5 @@ jobs: - name: Run docker container with tests shell: bash -l {0} run: | - docker run --rm --entrypoint /bin/bash -v /home/runner/work/CILViewer/CILViewer:/root/source_code cil-viewer -c "source ./mambaforge/etc/profile.d/conda.sh && conda activate cilviewer_webapp && conda install cil-data pytest -c ccpi && python -m pytest /root/source_code/Wrappers/Python -k 'not test_version and not test_cli_resample'" + docker run --rm --entrypoint /bin/bash -v /home/runner/work/CILViewer/CILViewer:/root/source_code cil-viewer -c "source ./mambaforge/etc/profile.d/conda.sh && conda activate cilviewer_webapp && conda install cil-data pytest -c ccpi && python -m pytest /root/source_code/Wrappers/Python -k 'not test_version and not test_cli_resample and not test_CILViewerBase and not test_CILViewer3D'" # TODO: publish to come later diff --git a/CHANGELOG.md b/CHANGELOG.md index b6ade731..456d4ba9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ do this for some hdf5 formats. - Fix bug with returning HDF5 attributes from HDF5Reader - Move code for resetting camera out of TrameViewer3D and into CILViewer +- Adds the following methods to CILViewer (and corresponding unit tests): + - getGradientOpacityPercentiles + - getScalarOpacityPercentiles + - getVolumeColorPercentiles +- Adds to CILViewerBase (and corresponding unit tests): + - getSliceColorPercentiles - setup.py: - Always normalise the version from git describe to pep440 diff --git a/Wrappers/Python/ccpi/viewer/CILViewer.py b/Wrappers/Python/ccpi/viewer/CILViewer.py index 280845a6..0af914dd 100644 --- a/Wrappers/Python/ccpi/viewer/CILViewer.py +++ b/Wrappers/Python/ccpi/viewer/CILViewer.py @@ -749,6 +749,21 @@ def setGradientOpacityPercentiles(self, min, max, update_pipeline=True): go_min, go_max = self.getImageMapRange((min, max), 'gradient') self.setGradientOpacityRange(go_min, go_max, update_pipeline) + def getGradientOpacityPercentiles(self): + ''' + Returns + ----------- + min, max: float, default: (80., 99.) + the percentiles on the image gradient values that the + opacity will be mapped to if setVolumeRenderOpacityMethod + has been set to 'gradient'. + ''' + go_min, go_max = self.getGradientOpacityRange() + value_min, value_max = self.getImageMapWholeRange('gradient') + min_percentage = (go_min - value_min) / (value_max - value_min) * 100 + max_percentage = (go_max - value_min) / (value_max - value_min) * 100 + return min_percentage, max_percentage + def setScalarOpacityPercentiles(self, min, max, update_pipeline=True): ''' min, max: float, default: (80., 99.) @@ -759,14 +774,44 @@ def setScalarOpacityPercentiles(self, min, max, update_pipeline=True): so_min, so_max = self.getImageMapRange((min, max), 'scalar') self.setScalarOpacityRange(so_min, so_max, update_pipeline) + def getScalarOpacityPercentiles(self): + ''' + Returns + ----------- + min, max: float, default: (80., 99.) + the percentiles on the image values that the + opacity will be mapped to if setVolumeRenderOpacityMethod + has been set to 'scalar'. + ''' + so_min, so_max = self.getScalarOpacityRange() + value_min, value_max = self.getImageMapWholeRange('scalar') + min_percentage = (so_min - value_min) / (value_max - value_min) * 100 + max_percentage = (so_max - value_min) / (value_max - value_min) * 100 + return min_percentage, max_percentage + def setVolumeColorPercentiles(self, min, max, update_pipeline=True): ''' + Parameters + ----------- min, max: int, default: (85., 95.) the percentiles on the image values upon which the colours will be mapped to ''' cmin, cmax = self.getImageMapRange((min, max), 'scalar') self.setVolumeColorRange(cmin, cmax, update_pipeline) + def getVolumeColorPercentiles(self): + ''' + Returns + ----------- + min, max: int, default: (85., 95.) + the percentiles on the image values upon which the colours will be mapped to + ''' + cmin, cmax = self.getVolumeColorRange() + value_min, value_max = self.getImageMapWholeRange('scalar') + min_percentage = (cmin - value_min) / (value_max - value_min) * 100 + max_percentage = (cmax - value_min) / (value_max - value_min) * 100 + return min_percentage, max_percentage + def setGradientOpacityRange(self, min, max, update_pipeline=True): ''' Parameters diff --git a/Wrappers/Python/ccpi/viewer/CILViewerBase.py b/Wrappers/Python/ccpi/viewer/CILViewerBase.py index df9302d7..c159eb00 100644 --- a/Wrappers/Python/ccpi/viewer/CILViewerBase.py +++ b/Wrappers/Python/ccpi/viewer/CILViewerBase.py @@ -266,6 +266,13 @@ def setSliceColorPercentiles(self, min_percentage, max_percentage): min_val, max_val = self.getSliceMapRange((min_percentage, max_percentage), 'scalar') self.setSliceMapRange(min_val, max_val) + def getSliceColorPercentiles(self): + min_val, max_val = self.getSliceMapWholeRange('scalar') + min_color, max_color = self.getSliceMapRange() + min_percentage = (min_color - min_val) / (max_val - min_val) * 100 + max_percentage = (max_color - min_val) / (max_val - min_val) * 100 + return min_percentage, max_percentage + def setSliceColorWindow(self, window): ''' Set the window for the 2D slice of the 3D image. diff --git a/Wrappers/Python/test/test_CILViewer3D.py b/Wrappers/Python/test/test_CILViewer3D.py new file mode 100644 index 00000000..1a6aa496 --- /dev/null +++ b/Wrappers/Python/test/test_CILViewer3D.py @@ -0,0 +1,82 @@ +# Copyright 2023 STFC, United Kingdom Research and Innovation +# +# Author 2023 Laura Murgatroyd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import os +import unittest +from unittest import mock + +from ccpi.viewer.CILViewer import CILViewer + +# skip the tests on GitHub actions +if os.environ.get('CONDA_BUILD', '0') == '1': + skip_test = True +else: + skip_test = False + +print("skip_test is set to ", skip_test) + + +@unittest.skipIf(skip_test, "Skipping tests on GitHub Actions") +class CILViewer3DTest(unittest.TestCase): + + def setUp(self): + self.cil_viewer = CILViewer() + + def test_getGradientOpacityPercentiles_returns_correct_percentiles_when_image_values_start_at_zero(self): + self.cil_viewer.gradient_opacity_limits = [40, 50] + self.cil_viewer.getImageMapWholeRange = mock.MagicMock(return_value=[0, 50]) + expected_percentages = (80.0, 100.0) + actual_percentages = self.cil_viewer.getGradientOpacityPercentiles() + self.assertEqual(expected_percentages, actual_percentages) + + def test_getGradientOpacityPercentiles_returns_correct_percentiles_when_image_values_start_at_non_zero(self): + self.cil_viewer.gradient_opacity_limits = [40, 50] + self.cil_viewer.getImageMapWholeRange = mock.MagicMock(return_value=[10, 50]) + expected_percentages = (75.0, 100.0) + actual_percentages = self.cil_viewer.getGradientOpacityPercentiles() + self.assertEqual(expected_percentages, actual_percentages) + + def test_getScalarOpacityPercentiles_returns_correct_percentiles_when_image_values_start_at_zero(self): + self.cil_viewer.scalar_opacity_limits = [40, 50] + self.cil_viewer.getImageMapWholeRange = mock.MagicMock(return_value=[0, 50]) + expected_percentages = (80.0, 100.0) + actual_percentages = self.cil_viewer.getScalarOpacityPercentiles() + self.assertEqual(expected_percentages, actual_percentages) + + def test_getScalarOpacityPercentiles_returns_correct_percentiles_when_image_values_start_at_non_zero(self): + self.cil_viewer.scalar_opacity_limits = [40, 50] + self.cil_viewer.getImageMapWholeRange = mock.MagicMock(return_value=[10, 50]) + expected_percentages = (75.0, 100.0) + actual_percentages = self.cil_viewer.getScalarOpacityPercentiles() + self.assertEqual(expected_percentages, actual_percentages) + + def test_getVolumeColorPercentiles_returns_correct_percentiles_when_image_values_start_at_zero(self): + self.cil_viewer.volume_colormap_limits = [40, 50] + self.cil_viewer.getImageMapWholeRange = mock.MagicMock(return_value=[0, 50]) + expected_percentages = (80.0, 100.0) + actual_percentages = self.cil_viewer.getVolumeColorPercentiles() + self.assertEqual(expected_percentages, actual_percentages) + + def test_getVolumeColorPercentiles_returns_correct_percentiles_when_image_values_start_at_non_zero(self): + self.cil_viewer.volume_colormap_limits = [40, 50] + self.cil_viewer.getImageMapWholeRange = mock.MagicMock(return_value=[10, 50]) + expected_percentages = (75.0, 100.0) + actual_percentages = self.cil_viewer.getVolumeColorPercentiles() + self.assertEqual(expected_percentages, actual_percentages) + + +if __name__ == '__main__': + unittest.main() diff --git a/Wrappers/Python/test/test_CILViewerBase.py b/Wrappers/Python/test/test_CILViewerBase.py new file mode 100644 index 00000000..1d49ef42 --- /dev/null +++ b/Wrappers/Python/test/test_CILViewerBase.py @@ -0,0 +1,54 @@ +# Copyright 2023 STFC, United Kingdom Research and Innovation +# +# Author 2023 Laura Murgatroyd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import unittest +from unittest import mock +import os + +from ccpi.viewer.CILViewer import CILViewerBase + +# skip the tests on GitHub actions +if os.environ.get('CONDA_BUILD', '0') == '1': + skip_test = True +else: + skip_test = False + +print("skip_test is set to ", skip_test) + + +@unittest.skipIf(skip_test, "Skipping tests on GitHub Actions") +class CILViewer3DTest(unittest.TestCase): + + def setUp(self): + self.cil_viewer = CILViewerBase() + + def test_getSliceColorPercentiles_returns_correct_percentiles_when_slice_values_start_at_zero(self): + self.cil_viewer.getSliceMapRange = mock.MagicMock(return_value=[40, 50]) + self.cil_viewer.getSliceMapWholeRange = mock.MagicMock(return_value=[0, 50]) + expected_percentages = (80.0, 100.0) + actual_percentages = self.cil_viewer.getSliceColorPercentiles() + self.assertEqual(expected_percentages, actual_percentages) + + def test_getSliceColorPercentiles_returns_correct_percentiles_when_slice_values_start_at_non_zero(self): + self.cil_viewer.getSliceMapRange = mock.MagicMock(return_value=[40, 50]) + self.cil_viewer.getSliceMapWholeRange = mock.MagicMock(return_value=[10, 50]) + expected_percentages = (75.0, 100.0) + actual_percentages = self.cil_viewer.getSliceColorPercentiles() + self.assertEqual(expected_percentages, actual_percentages) + + +if __name__ == '__main__': + unittest.main()