From 7d1e59abb6966d7bf9fc02b8a7b8199a69a322c0 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Thu, 16 Oct 2025 10:57:52 +0200 Subject: [PATCH 1/8] Refactor TargettedPixelGroup to TargetedPixelGroup and add deprecation warnings --- cherab/tools/observers/__init__.py | 2 +- cherab/tools/observers/group/__init__.py | 5 +- cherab/tools/observers/group/targetedpixel.py | 123 ++++++++++++++++++ .../tools/observers/group/targettedpixel.py | 100 +++----------- cherab/tools/tests/test_observer_groups.py | 29 ++++- 5 files changed, 167 insertions(+), 92 deletions(-) create mode 100644 cherab/tools/observers/group/targetedpixel.py diff --git a/cherab/tools/observers/__init__.py b/cherab/tools/observers/__init__.py index d134ef63..99cf5ab7 100644 --- a/cherab/tools/observers/__init__.py +++ b/cherab/tools/observers/__init__.py @@ -21,4 +21,4 @@ from .calcam import load_calcam_calibration from .intersections import find_wall_intersection from .spectroscopy import SpectroscopicSightLine, SpectroscopicFibreOptic -from .group import PixelGroup, TargettedPixelGroup, SightLineGroup, FibreOpticGroup, SpectroscopicFibreOpticGroup, SpectroscopicSightLineGroup +from .group import PixelGroup, TargetedPixelGroup, TargettedPixelGroup, SightLineGroup, FibreOpticGroup, SpectroscopicFibreOpticGroup, SpectroscopicSightLineGroup diff --git a/cherab/tools/observers/group/__init__.py b/cherab/tools/observers/group/__init__.py index eca93585..bbcf8633 100644 --- a/cherab/tools/observers/group/__init__.py +++ b/cherab/tools/observers/group/__init__.py @@ -17,7 +17,8 @@ # under the Licence. from .fibreoptic import FibreOpticGroup -from .sightline import SightLineGroup -from .targettedpixel import TargettedPixelGroup from .pixel import PixelGroup +from .sightline import SightLineGroup from .spectroscopic import SpectroscopicFibreOpticGroup, SpectroscopicSightLineGroup +from .targetedpixel import TargetedPixelGroup +from .targettedpixel import TargettedPixelGroup diff --git a/cherab/tools/observers/group/targetedpixel.py b/cherab/tools/observers/group/targetedpixel.py new file mode 100644 index 00000000..165921b5 --- /dev/null +++ b/cherab/tools/observers/group/targetedpixel.py @@ -0,0 +1,123 @@ +# Copyright 2016-2021 Euratom +# Copyright 2016-2021 United Kingdom Atomic Energy Authority +# Copyright 2016-2021 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas +# +# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the +# European Commission - subsequent versions of the EUPL (the "Licence"); +# You may not use this work except in compliance with the Licence. +# You may obtain a copy of the Licence at: +# +# https://joinup.ec.europa.eu/software/page/eupl5 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. +# +# See the Licence for the specific language governing permissions and limitations +# under the Licence. + +from numpy import ndarray +from raysect.optical.observer import TargetedPixel + +from .base import Observer0DGroup + + +class TargetedPixelGroup(Observer0DGroup): + """ + A group of targeted pixel under a single scene-graph node. + + A scene-graph object regrouping a series of 'TargetedPixel' + observers as a scene-graph parent. Allows combined observation and display + control simultaneously. + + :ivar list x_width: Width of pixel along local x axis + :ivar list y_width: Width of pixel along local y axis + :ivar list targets: Targets for preferential sampling + :ivar list targeted_path_prob: Probability of ray being casted at the target + """ + + _OBSERVER_TYPE = TargetedPixel + + @property + def x_width(self): + return [pixel.x_width for pixel in self._observers] + + @x_width.setter + def x_width(self, value): + if isinstance(value, (list, tuple, ndarray)): + if len(value) == len(self._observers): + for pixel, v in zip(self._observers, value): + pixel.x_width = v + else: + raise ValueError( + "The length of 'x_width' ({}) mismatches the number of pixels ({}).".format(len(value), len(self._observers)) + ) + else: + for pixel in self._observers: + pixel.x_width = value + + @property + def y_width(self): + return [pixel.y_width for pixel in self._observers] + + @y_width.setter + def y_width(self, value): + if isinstance(value, (list, tuple, ndarray)): + if len(value) == len(self._observers): + for pixel, v in zip(self._observers, value): + pixel.y_width = v + else: + raise ValueError( + "The length of 'y_width' ({}) mismatches the number of pixels ({}).".format(len(value), len(self._observers)) + ) + else: + for pixel in self._observers: + pixel.y_width = value + + @property + def targets(self): + """ + List of target lists used by pixels for preferential sampling + + :param list value: List of primitives to be set to each pixel or + list of lists containing targets specific for each pixel + in this case the number of lists must match number of pixels + + :rtype: list + """ + return [pixel.targets for pixel in self._observers] + + @targets.setter + def targets(self, value): + if all(isinstance(v, (list, tuple)) for v in value): + if len(value) == len(self._observers): + for pixel, v in zip(self._observers, value): + pixel.targets = v + else: + raise ValueError( + "The number of provided target lists' ({}) mismatches the number of pixels ({}).".format( + len(value), len(self._observers) + ) + ) + else: + # assuming a list of primitives, the pixel's setter will throw an error if not + for pixel in self._observers: + pixel.targets = value + + @property + def targeted_path_prob(self): + return [pixel.targeted_path_prob for pixel in self._observers] + + @targeted_path_prob.setter + def targeted_path_prob(self, value): + if isinstance(value, (list, tuple)): + if len(value) == len(self._observers): + for pixel, v in zip(self._observers, value): + pixel.targeted_path_prob = v + else: + raise ValueError( + "The length of 'value' ({}) mismatches the number of pixels ({}).".format(len(value), len(self._observers)) + ) + else: + for pixel in self._observers: + pixel.targeted_path_prob = value diff --git a/cherab/tools/observers/group/targettedpixel.py b/cherab/tools/observers/group/targettedpixel.py index 07d13e7f..3d612b2f 100644 --- a/cherab/tools/observers/group/targettedpixel.py +++ b/cherab/tools/observers/group/targettedpixel.py @@ -16,17 +16,20 @@ # See the Licence for the specific language governing permissions and limitations # under the Licence. -from numpy import ndarray -from raysect.optical.observer import TargettedPixel +import warnings -from .base import Observer0DGroup +from .targetedpixel import TargetedPixelGroup as _TargetedPixelGroup -class TargettedPixelGroup(Observer0DGroup): +class TargettedPixelGroup(_TargetedPixelGroup): """ - A group of targetted pixel under a single scene-graph node. + A group of targeted pixel under a single scene-graph node. - A scene-graph object regrouping a series of 'TargettedPixel' + .. deprecated:: + TargettedPixelGroup is deprecated and will be removed in a future version. + Use TargetedPixelGroup instead. + + A scene-graph object regrouping a series of 'TargetedPixel' observers as a scene-graph parent. Allows combined observation and display control simultaneously. @@ -35,82 +38,11 @@ class TargettedPixelGroup(Observer0DGroup): :ivar list targets: Targets for preferential sampling :ivar list targetted_path_prob: Probability of ray being casted at the target """ - _OBSERVER_TYPE = TargettedPixel - - @property - def x_width(self): - return [pixel.x_width for pixel in self._observers] - - @x_width.setter - def x_width(self, value): - if isinstance(value, (list, tuple, ndarray)): - if len(value) == len(self._observers): - for pixel, v in zip(self._observers, value): - pixel.x_width = v - else: - raise ValueError("The length of 'x_width' ({}) " - "mismatches the number of pixels ({}).".format(len(value), len(self._observers))) - else: - for pixel in self._observers: - pixel.x_width = value - - @property - def y_width(self): - return [pixel.y_width for pixel in self._observers] - - @y_width.setter - def y_width(self, value): - if isinstance(value, (list, tuple, ndarray)): - if len(value) == len(self._observers): - for pixel, v in zip(self._observers, value): - pixel.y_width = v - else: - raise ValueError("The length of 'y_width' ({}) " - "mismatches the number of pixels ({}).".format(len(value), len(self._observers))) - else: - for pixel in self._observers: - pixel.y_width = value - - @property - def targets(self): - """ - List of target lists used by pixels for preferential sampling - - :param list value: List of primitives to be set to each pixel or - list of lists containing targets specific for each pixel - in this case the number of lists must match number of pixels - - :rtype: list - """ - return [pixel.targets for pixel in self._observers] - - @targets.setter - def targets(self, value): - if all(isinstance(v, (list, tuple)) for v in value): - if len(value) == len(self._observers): - for pixel, v in zip(self._observers, value): - pixel.targets = v - else: - raise ValueError("The number of provided target lists' ({}) " - "mismatches the number of pixels ({}).".format(len(value), len(self._observers))) - else: - # assuming a list of primitives, the pixel's setter will throw an error if not - for pixel in self._observers: - pixel.targets = value - @property - def targetted_path_prob(self): - return [pixel.targetted_path_prob for pixel in self._observers] - - @targetted_path_prob.setter - def targetted_path_prob(self, value): - if isinstance(value, (list, tuple)): - if len(value) == len(self._observers): - for pixel, v in zip(self._observers, value): - pixel.targetted_path_prob = v - else: - raise ValueError("The length of 'value' ({}) " - "mismatches the number of pixels ({}).".format(len(value), len(self._observers))) - else: - for pixel in self._observers: - pixel.targetted_path_prob = value + def __init__(self, *args, **kwargs): + warnings.warn( + "TargettedPixelGroup is deprecated and will be removed in a future version. Use TargetedPixelGroup instead.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) diff --git a/cherab/tools/tests/test_observer_groups.py b/cherab/tools/tests/test_observer_groups.py index 1f5b7cb0..649e5a96 100644 --- a/cherab/tools/tests/test_observer_groups.py +++ b/cherab/tools/tests/test_observer_groups.py @@ -1,11 +1,12 @@ import unittest +import warnings from raysect.core.workflow import RenderEngine from raysect.optical.observer import Observer0D, SightLine, FibreOptic, Pixel, TargettedPixel, PowerPipeline0D, SpectralPowerPipeline0D from raysect.primitive import Sphere +from cherab.tools.observers.group import FibreOpticGroup, PixelGroup, SightLineGroup, TargetedPixelGroup, TargettedPixelGroup from cherab.tools.observers.group.base import Observer0DGroup -from cherab.tools.observers.group import SightLineGroup, FibreOpticGroup, PixelGroup, TargettedPixelGroup from cherab.tools.raytransfer import pipelines @@ -348,8 +349,8 @@ def test_widths(self): group.y_width = [1e-1] * (len(group) + 1) -class TargettedPixelGroupTestCase(PixelGroupTestCase): - _GROUP_CLASS = TargettedPixelGroup +class TargetedPixelGroupTestCase(PixelGroupTestCase): + _GROUP_CLASS = TargetedPixelGroup def setUp(self): self.observers = [TargettedPixel(targets=[Sphere()], pipelines=[PowerPipeline0D()]) for _ in range(self._NUM)] @@ -374,7 +375,7 @@ def test_targets(self): with self.assertRaises(ValueError): group.targets = targets - # targetted path prob + # targeted path prob prob = [0.9, 0.95, 1] group.targetted_path_prob = prob self.assertListEqual(group.targetted_path_prob, prob) @@ -385,4 +386,22 @@ def test_targets(self): self.assertEqual(group_targetted_path_prob, prob) with self.assertRaises(ValueError): - group.targetted_path_prob = [0.7] * (len(group) + 1) + group.targeted_path_prob = [0.7] * (len(group) + 1) + + +class TargettedPixelGroupTestCase(TargetedPixelGroupTestCase): + """Test case for deprecated TargettedPixelGroup class.""" + + _GROUP_CLASS = TargettedPixelGroup + + def test_deprecation_warning(self): + """Test that using TargettedPixelGroup raises a deprecation warning.""" + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + group = TargettedPixelGroup(observers=self.observers) + + # Check that a warning was issued + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[0].category, DeprecationWarning)) + self.assertIn("TargettedPixelGroup is deprecated", str(w[0].message)) + self.assertIn("Use TargetedPixelGroup instead", str(w[0].message)) From 24779d59290ec0d41afd85c0280fda885eb46e4e Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Thu, 16 Oct 2025 10:58:19 +0200 Subject: [PATCH 2/8] Fix formatting issues by removing unnecessary blank lines --- cherab/tools/tests/test_observer_groups.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cherab/tools/tests/test_observer_groups.py b/cherab/tools/tests/test_observer_groups.py index 649e5a96..09527634 100644 --- a/cherab/tools/tests/test_observer_groups.py +++ b/cherab/tools/tests/test_observer_groups.py @@ -27,7 +27,7 @@ def test_get_item(self): idx = slice(1, 3, 1) for observer, input_observer in zip(group[idx], self.observers[idx]): self.assertIs(observer, input_observer) - + for i, name in enumerate(names): self.assertIs(group[name], self.observers[i]) @@ -84,7 +84,7 @@ def test_assignments(self): with self.assertRaises(ValueError): group.pipelines = [ppln_0] - # render_engine + # render_engine engine = RenderEngine() group.render_engine = engine for group_engine in group.render_engine: @@ -103,7 +103,7 @@ def test_assignments(self): with self.assertRaises(ValueError): group.render_engine = [RenderEngine() for _ in range(len(group) - 1)] - # wavelengths + # wavelengths wvl = 500 group.min_wavelength = wvl - 100 group.max_wavelength = wvl + 100 @@ -140,7 +140,7 @@ def test_assignments(self): with self.assertRaises(ValueError): group.spectral_bins = [1000] * (len(group) + 1) - # quiet + # quiet quiet = [True] * len(group) group.quiet = quiet self.assertListEqual(group.quiet, quiet) @@ -153,7 +153,7 @@ def test_assignments(self): with self.assertRaises(ValueError): group.quiet = [False] * (len(group) + 1) - # rays + # rays probs = [0.2 + i*0.1 for i in range(len(group))] max_depths = [5 + i for i in range(len(group))] min_depths = [2 + i for i in range(len(group))] @@ -197,7 +197,7 @@ def test_assignments(self): group.ray_importance_sampling = [False] * (len(group) + 1) with self.assertRaises(ValueError): group.ray_important_path_weight = [0.7] * (len(group) + 1) - + # samples pixel_samples = [2000 + i*500 for i in range(len(group))] per_task = [5000 + i*100 for i in range(len(group))] From 1a1aaafcf8f79239137b8221bdd2a28ba5aa2a7d Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Thu, 16 Oct 2025 11:07:46 +0200 Subject: [PATCH 3/8] Fix typo in TargetedPixelGroup class docstring --- cherab/tools/observers/group/targetedpixel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cherab/tools/observers/group/targetedpixel.py b/cherab/tools/observers/group/targetedpixel.py index 165921b5..9f4fb814 100644 --- a/cherab/tools/observers/group/targetedpixel.py +++ b/cherab/tools/observers/group/targetedpixel.py @@ -24,7 +24,7 @@ class TargetedPixelGroup(Observer0DGroup): """ - A group of targeted pixel under a single scene-graph node. + A group of targeted pixels under a single scene-graph node. A scene-graph object regrouping a series of 'TargetedPixel' observers as a scene-graph parent. Allows combined observation and display From 866740b9a51729ab71de1556f0c2a87939c71120 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Thu, 16 Oct 2025 11:21:21 +0200 Subject: [PATCH 4/8] Correct imported class `TargetedPixel` not using `TargettedPixel` --- cherab/tools/tests/test_observer_groups.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cherab/tools/tests/test_observer_groups.py b/cherab/tools/tests/test_observer_groups.py index 09527634..a602531e 100644 --- a/cherab/tools/tests/test_observer_groups.py +++ b/cherab/tools/tests/test_observer_groups.py @@ -2,7 +2,7 @@ import warnings from raysect.core.workflow import RenderEngine -from raysect.optical.observer import Observer0D, SightLine, FibreOptic, Pixel, TargettedPixel, PowerPipeline0D, SpectralPowerPipeline0D +from raysect.optical.observer import Observer0D, SightLine, FibreOptic, Pixel, TargetedPixel, PowerPipeline0D, SpectralPowerPipeline0D from raysect.primitive import Sphere from cherab.tools.observers.group import FibreOpticGroup, PixelGroup, SightLineGroup, TargetedPixelGroup, TargettedPixelGroup @@ -353,7 +353,7 @@ class TargetedPixelGroupTestCase(PixelGroupTestCase): _GROUP_CLASS = TargetedPixelGroup def setUp(self): - self.observers = [TargettedPixel(targets=[Sphere()], pipelines=[PowerPipeline0D()]) for _ in range(self._NUM)] + self.observers = [TargetedPixel(targets=[Sphere()], pipelines=[PowerPipeline0D()]) for _ in range(self._NUM)] def test_targets(self): group = self._GROUP_CLASS(observers=self.observers) @@ -377,13 +377,13 @@ def test_targets(self): # targeted path prob prob = [0.9, 0.95, 1] - group.targetted_path_prob = prob - self.assertListEqual(group.targetted_path_prob, prob) + group.targeted_path_prob = prob + self.assertListEqual(group.targeted_path_prob, prob) prob = 0.8 - group.targetted_path_prob = prob - for group_targetted_path_prob in group.targetted_path_prob: - self.assertEqual(group_targetted_path_prob, prob) + group.targeted_path_prob = prob + for group_targeted_path_prob in group.targeted_path_prob: + self.assertEqual(group_targeted_path_prob, prob) with self.assertRaises(ValueError): group.targeted_path_prob = [0.7] * (len(group) + 1) From 1a640cf69d9193fc4366d9552e46bfba758dc833 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Thu, 16 Oct 2025 11:24:10 +0200 Subject: [PATCH 5/8] Revert previous property to keep it in deprecated class --- cherab/tools/observers/group/targettedpixel.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cherab/tools/observers/group/targettedpixel.py b/cherab/tools/observers/group/targettedpixel.py index 3d612b2f..b08ea996 100644 --- a/cherab/tools/observers/group/targettedpixel.py +++ b/cherab/tools/observers/group/targettedpixel.py @@ -46,3 +46,11 @@ def __init__(self, *args, **kwargs): stacklevel=2, ) super().__init__(*args, **kwargs) + + @property + def targetted_path_prob(self): + return self.targeted_path_prob + + @targetted_path_prob.setter + def targetted_path_prob(self, value): + self.targeted_path_prob = value From fd044a64ecd7c8d84622e255a1548b2c2e82abb0 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Thu, 16 Oct 2025 11:37:33 +0200 Subject: [PATCH 6/8] Update CHANGELOG.md --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f22a5d4..1c132f58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Project Changelog Release 1.6.0 (TBD) ------------------- +API changes: +* Rename `TargettedPixelGroup` to `TargetedPixelGroup` for correct spelling. Still keep `TargettedPixelGroup` as an alias for backwards compatibility. (#487) + New: * Add Function6D framework. (#478) * Add e_field attribute to Plasma object for electric field vector. (#465) @@ -134,7 +137,7 @@ API changes: New: * Merged cherab-openadas package into the core cherab package to simplify installation. -* Beam object uses a cone primitive instead of a cylinder for the bounding volume of divergent beams. +* Beam object uses a cone primitive instead of a cylinder for the bounding volume of divergent beams. * Added Clamp functions. * Added ThermalCXRate. * Added optimised ray transfer grid calculation tools. @@ -162,7 +165,7 @@ New: Bug fixes: * Improved handling on non c-order arrays in various methods. -* Numerous minor bug fixes (see commit history) +* Numerous minor bug fixes (see commit history) Release 1.0.1 (1 Oct 2018) From 088a369a6a648234f1f924798cdfee49f83caa6a Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Fri, 17 Oct 2025 10:35:53 +0200 Subject: [PATCH 7/8] Update deprecation notice for TargettedPixelGroup to specify removal in version 2.0 --- CHANGELOG.md | 2 +- cherab/tools/observers/group/targettedpixel.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c132f58..0737af44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ Release 1.6.0 (TBD) ------------------- API changes: -* Rename `TargettedPixelGroup` to `TargetedPixelGroup` for correct spelling. Still keep `TargettedPixelGroup` as an alias for backwards compatibility. (#487) +* Rename `TargettedPixelGroup` to `TargetedPixelGroup` for correct spelling. Still keep `TargettedPixelGroup` as an alias for backwards compatibility until the next major release. (#487) New: * Add Function6D framework. (#478) diff --git a/cherab/tools/observers/group/targettedpixel.py b/cherab/tools/observers/group/targettedpixel.py index b08ea996..5d1ac69f 100644 --- a/cherab/tools/observers/group/targettedpixel.py +++ b/cherab/tools/observers/group/targettedpixel.py @@ -26,10 +26,10 @@ class TargettedPixelGroup(_TargetedPixelGroup): A group of targeted pixel under a single scene-graph node. .. deprecated:: - TargettedPixelGroup is deprecated and will be removed in a future version. - Use TargetedPixelGroup instead. + `TargettedPixelGroup` is deprecated and will be removed in version 2.0. + Use `TargetedPixelGroup` instead. - A scene-graph object regrouping a series of 'TargetedPixel' + A scene-graph object regrouping a series of `TargetedPixel` observers as a scene-graph parent. Allows combined observation and display control simultaneously. @@ -41,7 +41,8 @@ class TargettedPixelGroup(_TargetedPixelGroup): def __init__(self, *args, **kwargs): warnings.warn( - "TargettedPixelGroup is deprecated and will be removed in a future version. Use TargetedPixelGroup instead.", + "TargettedPixelGroup is deprecated and will be removed in version 2.0. " + + "Use TargetedPixelGroup instead.", DeprecationWarning, stacklevel=2, ) From f0a38db645a442250d13b8ed23d12544a7134dc9 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Thu, 27 Nov 2025 10:40:52 +0100 Subject: [PATCH 8/8] Add TargetedPixelGroup class to observers documentation --- docs/source/tools/observers.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/tools/observers.rst b/docs/source/tools/observers.rst index e93e2057..b3d5e612 100644 --- a/docs/source/tools/observers.rst +++ b/docs/source/tools/observers.rst @@ -127,6 +127,9 @@ in the group. .. autoclass:: cherab.tools.observers.group.PixelGroup :members: +.. autoclass:: cherab.tools.observers.group.TargetedPixelGroup + :members: + .. autoclass:: cherab.tools.observers.group.TargettedPixelGroup :members: