From 1c6b3f273ca48e59a6eef099fa1942851f3111e0 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Thu, 9 Mar 2023 11:13:09 +0800 Subject: [PATCH 01/40] Add apply_vector_overlay and clear_vector_overlay --- carta/image.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/carta/image.py b/carta/image.py index 72f62ed..c782b83 100644 --- a/carta/image.py +++ b/carta/image.py @@ -593,6 +593,16 @@ def hide_contours(self): """Hide the contours.""" self.set_contours_visible(False) + # VECTOR OVERLAY + + def apply_vector_overlay(self): + """Apply the vector overlay configuration.""" + self.call_action("applyVectorOverlay") + + def clear_vector_overlay(self): + """Clear the vector overlay configuration.""" + self.call_action("clearVectorOverlay", True) + # HISTOGRAM @validate(Boolean()) From c1c0ebd68093b20048ee7fc6a1e81eba0ee5af72 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Sat, 11 Mar 2023 17:33:45 +0800 Subject: [PATCH 02/40] Add set_vector_overlay_color and set_vector_overlay_colormap --- carta/image.py | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/carta/image.py b/carta/image.py index c782b83..e822624 100644 --- a/carta/image.py +++ b/carta/image.py @@ -595,13 +595,49 @@ def hide_contours(self): # VECTOR OVERLAY + @validate(Color()) + def set_vector_overlay_color(self, color): + """Set the vector overlay color. + + This automatically disables use of the vector overlay colormap. + + Parameters + ---------- + color : {0} + The color. + """ + self.call_action("vectorOverlayConfig.setColor", color) + self.call_action("vectorOverlayConfig.setColormapEnabled", False) + + @validate(Constant(Colormap), NoneOr(Number()), NoneOr(Number())) + def set_vector_overlay_colormap(self, colormap, bias=None, contrast=None): + """Set the contour colormap. + + This automatically enables use of the vector overlay colormap. + + Parameters + ---------- + colormap : {0} + The colormap. + bias : {1} + The colormap bias. + contrast : {2} + The colormap contrast. + """ + self.call_action("vectorOverlayConfig.setColormap", colormap) + self.call_action("vectorOverlayConfig.setColormapEnabled", True) + if bias is not None: + self.call_action("vectorOverlayConfig.setColormapBias", bias) + if contrast is not None: + self.call_action("vectorOverlayConfig.setColormapContrast", contrast) + def apply_vector_overlay(self): """Apply the vector overlay configuration.""" self.call_action("applyVectorOverlay") def clear_vector_overlay(self): """Clear the vector overlay configuration.""" - self.call_action("clearVectorOverlay", True) + self.call_action("clearVectorOverlay", True) # HISTOGRAM From 43c8dd0dd3b3290faf5604e2941681cbb9063e45 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Sat, 11 Mar 2023 17:35:33 +0800 Subject: [PATCH 03/40] Minor modification --- carta/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/carta/image.py b/carta/image.py index e822624..36eda68 100644 --- a/carta/image.py +++ b/carta/image.py @@ -611,7 +611,7 @@ def set_vector_overlay_color(self, color): @validate(Constant(Colormap), NoneOr(Number()), NoneOr(Number())) def set_vector_overlay_colormap(self, colormap, bias=None, contrast=None): - """Set the contour colormap. + """Set the vector overlay colormap. This automatically enables use of the vector overlay colormap. From 6e924e862183819ad87cee1e3a5b5b5341fda114 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Mon, 13 Mar 2023 16:04:36 +0800 Subject: [PATCH 04/40] Add configure_vector_overlay --- carta/constants.py | 2 ++ carta/image.py | 31 ++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/carta/constants.py b/carta/constants.py index 0eb1bf8..001cf88 100644 --- a/carta/constants.py +++ b/carta/constants.py @@ -112,6 +112,8 @@ def __init__(self, value): SmoothingMode = Enum('SmoothingMode', ('NO_SMOOTHING', 'BLOCK_AVERAGE', 'GAUSSIAN_BLUR'), type=int, start=0) SmoothingMode.__doc__ = """Contour smoothing modes.""" +VectorOverlaySource = Enum('VectorOverlaySource', ('NONE', 'CURRENT', 'COMPUTED'), type=int, start=-1) +VectorOverlaySource.__doc__ = """Vector overlay source.""" class ContourDashMode(str, Enum): """Contour dash modes.""" diff --git a/carta/image.py b/carta/image.py index 36eda68..d3db40c 100644 --- a/carta/image.py +++ b/carta/image.py @@ -4,7 +4,7 @@ """ import posixpath -from .constants import Colormap, Scaling, SmoothingMode, ContourDashMode, Polarization +from .constants import Colormap, Scaling, SmoothingMode, ContourDashMode, Polarization, VectorOverlaySource from .util import Macro, cached from .validation import validate, Number, Color, Constant, Boolean, NoneOr, IterableOf, Evaluate, Attr, Attrs, OneOf @@ -595,6 +595,35 @@ def hide_contours(self): # VECTOR OVERLAY + @validate(Constant(VectorOverlaySource), Constant(VectorOverlaySource), Boolean(), Number(), Boolean(), Boolean(), NoneOr(Number()), Boolean(), NoneOr(Number()), NoneOr(Number())) + def configure_vector_overlay(self, angular_source=VectorOverlaySource.CURRENT, intensity_source=VectorOverlaySource.CURRENT, pixel_averaging_enabled=True, pixel_averaging=4, fractional_intensity=False, threshold_enabled=False, threshold=0, debiasing=False, qError=None, uError=None): + """Configure contours. + + Parameters + ---------- + angular_source : {0} + The angular source. + intensity_source : {1} + The intensity source. + pixel_averaging_enabled : {2} + To enable or disable pixel averaging. + pixel_averaging : {3} + The pixel averaging width. + fractional_intensity : {4} + Set polarization intensity to absolute or fractional. + threshold_enabled : {5} + To enable or disable threshold. + threshold : {6} + The threshold. + debiasing : {7} + To enable. + qError : {8} + The stoke Q Error. + uError : {9} + The stoke U Error. + """ + self.call_action("contourConfig.setVectorOverlayConfiguration", angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, qError, uError) + @validate(Color()) def set_vector_overlay_color(self, color): """Set the vector overlay color. From ce98bd763b75b89b3faacc3d166f622c829443b1 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Mon, 13 Mar 2023 16:37:06 +0800 Subject: [PATCH 05/40] Minor modification for comments --- carta/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/carta/image.py b/carta/image.py index d3db40c..9d29350 100644 --- a/carta/image.py +++ b/carta/image.py @@ -597,7 +597,7 @@ def hide_contours(self): @validate(Constant(VectorOverlaySource), Constant(VectorOverlaySource), Boolean(), Number(), Boolean(), Boolean(), NoneOr(Number()), Boolean(), NoneOr(Number()), NoneOr(Number())) def configure_vector_overlay(self, angular_source=VectorOverlaySource.CURRENT, intensity_source=VectorOverlaySource.CURRENT, pixel_averaging_enabled=True, pixel_averaging=4, fractional_intensity=False, threshold_enabled=False, threshold=0, debiasing=False, qError=None, uError=None): - """Configure contours. + """Set the "Configuration" panel for the vector overlay except for Color and Colormap. Parameters ---------- @@ -616,7 +616,7 @@ def configure_vector_overlay(self, angular_source=VectorOverlaySource.CURRENT, i threshold : {6} The threshold. debiasing : {7} - To enable. + To enable or disable debiasing. qError : {8} The stoke Q Error. uError : {9} From 32aae923ac3a5c85a4d0221e7ebde70de710c50f Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Mon, 13 Mar 2023 23:25:42 +0800 Subject: [PATCH 06/40] Deduce pixel_averaging_enabled and threshold_enabled --- carta/image.py | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/carta/image.py b/carta/image.py index 9d29350..7c12046 100644 --- a/carta/image.py +++ b/carta/image.py @@ -595,9 +595,9 @@ def hide_contours(self): # VECTOR OVERLAY - @validate(Constant(VectorOverlaySource), Constant(VectorOverlaySource), Boolean(), Number(), Boolean(), Boolean(), NoneOr(Number()), Boolean(), NoneOr(Number()), NoneOr(Number())) - def configure_vector_overlay(self, angular_source=VectorOverlaySource.CURRENT, intensity_source=VectorOverlaySource.CURRENT, pixel_averaging_enabled=True, pixel_averaging=4, fractional_intensity=False, threshold_enabled=False, threshold=0, debiasing=False, qError=None, uError=None): - """Set the "Configuration" panel for the vector overlay except for Color and Colormap. + @validate(Constant(VectorOverlaySource), Constant(VectorOverlaySource), NoneOr(Number()), Boolean(), NoneOr(Number()), Boolean(), NoneOr(Number()), NoneOr(Number())) + def configure_vector_overlay(self, angular_source=VectorOverlaySource.CURRENT, intensity_source=VectorOverlaySource.CURRENT, pixel_averaging=4, fractional_intensity=False, threshold=None, debiasing=False, qError=None, uError=None): + """Configure vector overlay. Parameters ---------- @@ -605,23 +605,29 @@ def configure_vector_overlay(self, angular_source=VectorOverlaySource.CURRENT, i The angular source. intensity_source : {1} The intensity source. - pixel_averaging_enabled : {2} - To enable or disable pixel averaging. - pixel_averaging : {3} - The pixel averaging width. - fractional_intensity : {4} + pixel_averaging : {2} + The pixel averaging width. Set it to None to disable the usage pixel averaging width. + fractional_intensity : {3} Set polarization intensity to absolute or fractional. - threshold_enabled : {5} - To enable or disable threshold. - threshold : {6} - The threshold. - debiasing : {7} + threshold : {4} + The threshold. Set it to None to disable the usage of threshold. + debiasing : {5} To enable or disable debiasing. - qError : {8} + qError : {6} The stoke Q Error. - uError : {9} + uError : {7} The stoke U Error. """ + if pixel_averaging is not None: + pixel_averaging_enabled = True + else: + pixel_averaging_enabled = False + + if threshold is not None: + threshold_enabled = True + else: + threshold_enabled = False + self.call_action("contourConfig.setVectorOverlayConfiguration", angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, qError, uError) @validate(Color()) From cb56287f6e13c9e9bf056f4160dfaf5348d59d82 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Tue, 14 Mar 2023 18:15:55 +0800 Subject: [PATCH 07/40] Deduce debiasing and modify description --- carta/image.py | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/carta/image.py b/carta/image.py index 7c12046..1aa3fe7 100644 --- a/carta/image.py +++ b/carta/image.py @@ -595,40 +595,36 @@ def hide_contours(self): # VECTOR OVERLAY - @validate(Constant(VectorOverlaySource), Constant(VectorOverlaySource), NoneOr(Number()), Boolean(), NoneOr(Number()), Boolean(), NoneOr(Number()), NoneOr(Number())) - def configure_vector_overlay(self, angular_source=VectorOverlaySource.CURRENT, intensity_source=VectorOverlaySource.CURRENT, pixel_averaging=4, fractional_intensity=False, threshold=None, debiasing=False, qError=None, uError=None): + @validate(Constant(VectorOverlaySource), Constant(VectorOverlaySource), NoneOr(Number()), Boolean(), NoneOr(Number()), NoneOr(Number()), NoneOr(Number())) + def configure_vector_overlay(self, angular_source=VectorOverlaySource.CURRENT, intensity_source=VectorOverlaySource.CURRENT, pixel_averaging=4, fractional_intensity=False, threshold=None, q_error=None, u_error=None): """Configure vector overlay. Parameters ---------- angular_source : {0} - The angular source. + The angular source. By default the current image is used. intensity_source : {1} - The intensity source. + The intensity source. By default the current image is used. pixel_averaging : {2} - The pixel averaging width. Set it to None to disable the usage pixel averaging width. + The pixel averaging width in pixel. Set to ``None`` to disable pixel averaging. fractional_intensity : {3} - Set polarization intensity to absolute or fractional. + Enable fractional polarization intensity. By default the absolute polarization intensity is used. threshold : {4} - The threshold. Set it to None to disable the usage of threshold. - debiasing : {5} - To enable or disable debiasing. - qError : {6} - The stoke Q Error. - uError : {7} - The stoke U Error. - """ - if pixel_averaging is not None: - pixel_averaging_enabled = True + The threshold in Jy/pixel. By default the threshold is disabled. + q_error : {5} + The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. The debiasing is disabled by default. + u_error : {6} + The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. The debiasing is disabled by default. + """ + pixel_averaging_enabled = pixel_averaging is not None + threshold_enabled = threshold is not None + if q_error is not None and u_error is not None: + debiasing = True + elif q_error is None and u_error is None: + debiasing = False else: - pixel_averaging_enabled = False - - if threshold is not None: - threshold_enabled = True - else: - threshold_enabled = False - - self.call_action("contourConfig.setVectorOverlayConfiguration", angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, qError, uError) + print("q_error and u_error must be both set to enable debiasing.") + self.call_action("contourConfig.setVectorOverlayConfiguration", angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) @validate(Color()) def set_vector_overlay_color(self, color): From cbdce93000f91ce202ced9e46de90d7d2df3bfdd Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Tue, 14 Mar 2023 18:25:39 +0800 Subject: [PATCH 08/40] Minor modification --- carta/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/carta/image.py b/carta/image.py index 1aa3fe7..76d5e57 100644 --- a/carta/image.py +++ b/carta/image.py @@ -623,7 +623,7 @@ def configure_vector_overlay(self, angular_source=VectorOverlaySource.CURRENT, i elif q_error is None and u_error is None: debiasing = False else: - print("q_error and u_error must be both set to enable debiasing.") + print("q_error and u_error must be both set to enable debiasing.") self.call_action("contourConfig.setVectorOverlayConfiguration", angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) @validate(Color()) From 401652ed0dd74930afb42635cd0f8b4ae790f215 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Wed, 15 Mar 2023 18:40:28 +0800 Subject: [PATCH 09/40] Add set_style plot set_visible show hide for vector overlay --- carta/image.py | 129 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 114 insertions(+), 15 deletions(-) diff --git a/carta/image.py b/carta/image.py index 76d5e57..f29f59b 100644 --- a/carta/image.py +++ b/carta/image.py @@ -5,7 +5,7 @@ import posixpath from .constants import Colormap, Scaling, SmoothingMode, ContourDashMode, Polarization, VectorOverlaySource -from .util import Macro, cached +from .util import logger, Macro, cached from .validation import validate, Number, Color, Constant, Boolean, NoneOr, IterableOf, Evaluate, Attr, Attrs, OneOf @@ -595,36 +595,58 @@ def hide_contours(self): # VECTOR OVERLAY - @validate(Constant(VectorOverlaySource), Constant(VectorOverlaySource), NoneOr(Number()), Boolean(), NoneOr(Number()), NoneOr(Number()), NoneOr(Number())) - def configure_vector_overlay(self, angular_source=VectorOverlaySource.CURRENT, intensity_source=VectorOverlaySource.CURRENT, pixel_averaging=4, fractional_intensity=False, threshold=None, q_error=None, u_error=None): + @validate(NoneOr(Constant(VectorOverlaySource)), NoneOr(Constant(VectorOverlaySource)), NoneOr(Number()), Boolean(), NoneOr(Number()), NoneOr(Number()), NoneOr(Number())) + def configure_vector_overlay(self, angular_source=None, intensity_source=None, pixel_averaging=4, fractional_intensity=False, threshold=None, q_error=None, u_error=None): """Configure vector overlay. Parameters ---------- angular_source : {0} - The angular source. By default the current image is used. + The angular source. If the image is with Stoke information, ``Computed PA`` is set by default; If the image is without Stoke information, ``Current image`` is set. intensity_source : {1} - The intensity source. By default the current image is used. + The intensity source. If the image is with Stoke information, ``Computed PI`` is set by default; If the image is without Stoke information, ``Current image`` is set. pixel_averaging : {2} The pixel averaging width in pixel. Set to ``None`` to disable pixel averaging. fractional_intensity : {3} Enable fractional polarization intensity. By default the absolute polarization intensity is used. threshold : {4} - The threshold in Jy/pixel. By default the threshold is disabled. + The threshold in Jy/pixels. By default is 4. q_error : {5} - The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. The debiasing is disabled by default. + The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. Debiasing is disabled by default. u_error : {6} - The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. The debiasing is disabled by default. + The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. Debiasing is disabled by default. """ pixel_averaging_enabled = pixel_averaging is not None threshold_enabled = threshold is not None - if q_error is not None and u_error is not None: - debiasing = True - elif q_error is None and u_error is None: - debiasing = False - else: - print("q_error and u_error must be both set to enable debiasing.") - self.call_action("contourConfig.setVectorOverlayConfiguration", angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) + debiasing = q_error is not None and u_error is not None + if not debiasing and (q_error is not None or u_error is not None): + logger.warning("The Stokes Q and the Stokes Q must both be set to enable debiasing.") + print(debiasing) + self.call_action("vectorOverlayConfig.setVectorOverlayConfiguration", angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) + + @validate(Number(), NoneOr(Number()), NoneOr(Number()), Number(), Number(), Number()) + def set_vector_overlay_style(self, thickness=1, intensity_min=None, intensity_max=None, length_min=0, length_max=20, rotation_offset=0): + """Set the styling (line thickness, intensity range, line length range, rotation offset) of vector overlay. + + Parameters + ---------- + thickness : {0} + The line thickness in pixels. By default is 1. + intensity_min : {1} + The minimum value of intensity in Jy/pixel. + intensity_min : {2} + The maximum value of intensity in Jy/pixel. + length_min : {3} + The minimum value of line length in pixels. By default is 0. + length_min : {4} + The maximum value of line length in pixels. By default is 20. + rotation_offset : {5} + The rotation offset in degrees. By default is 0. + """ + self.call_action("vectorOverlayConfig.setThickness", thickness) + self.call_action("vectorOverlayConfig.setIntensityRange", [intensity_min, intensity_max]) + self.call_action("vectorOverlayConfig.setLengthRange", [length_min, length_max]) + self.call_action("vectorOverlayConfig.setRotationOffset", rotation_offset) @validate(Color()) def set_vector_overlay_color(self, color): @@ -666,10 +688,87 @@ def apply_vector_overlay(self): """Apply the vector overlay configuration.""" self.call_action("applyVectorOverlay") + @validate(NoneOr(Constant(VectorOverlaySource)), NoneOr(Constant(VectorOverlaySource)), NoneOr(Number()), Boolean(), NoneOr(Number()), NoneOr(Number()), NoneOr(Number()), Number(), NoneOr(Number()), NoneOr(Number()), Number(), Number(), Number(), NoneOr(Color()), NoneOr(Constant(Colormap)), NoneOr(Number()), NoneOr(Number())) + def plot_vector_overlay(self, angular_source=None, intensity_source=None, pixel_averaging=4, fractional_intensity=False, threshold=None, q_error=None, u_error=None, thickness=1, intensity_min=None, intensity_max=None, length_min=0, length_max=20, rotation_offset=0, color=None, colormap=None, bias=None, contrast=None): + """Set the vector overlay configuration, styling and colour or colourmap; and apply vector overlay; in a single step. + + If both a colour and a colourmap are provided, the colourmap will be visible. + + Parameters + ---------- + angular_source : {0} + The angular source. If the image is with Stoke information, ``Computed PA`` is set by default; If the image is without Stoke information, ``Current image`` is set. + intensity_source : {1} + The intensity source. If the image is with Stoke information, ``Computed PI`` is set by default; If the image is without Stoke information, ``Current image`` is set. + pixel_averaging : {2} + The pixel averaging width in pixel. Set to ``None`` to disable pixel averaging. + fractional_intensity : {3} + Enable fractional polarization intensity. By default the absolute polarization intensity is used. + threshold : {4} + The threshold in Jy/pixels. By default is 4. + q_error : {5} + The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. Debiasing is disabled by default. + u_error : {6} + The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. Debiasing is disabled by default. + thickness : {0} + The line thickness in pixels. By default is 1. + intensity_min : {1} + The minimum value of intensity in Jy/pixel. + intensity_min : {2} + The maximum value of intensity in Jy/pixel. + length_min : {3} + The minimum value of line length in pixels. By default is 0. + length_min : {4} + The maximum value of line length in pixels. By default is 20. + rotation_offset : {5} + The rotation offset in degrees. By default is 0. + color : {0} + The color. + colormap : {0} + The colormap. + bias : {1} + The colormap bias. + contrast : {2} + The colormap contrast. + """ + self.configure_vector_overlay(angular_source, intensity_source, pixel_averaging, fractional_intensity, threshold, q_error, u_error) + self.set_vector_overlay_style(thickness, intensity_min, intensity_max, length_min, length_max, rotation_offset) + if color is not None: + self.call_action("vectorOverlayConfig.setColor", color) + if colormap is not None: + self.call_action("vectorOverlayConfig.setColormap", colormap) + self.call_action("vectorOverlayConfig.setColormapEnabled", True) + if bias is not None: + self.call_action("vectorOverlayConfig.setColormapBias", bias) + if contrast is not None: + self.call_action("vectorOverlayConfig.setColormapContrast", contrast) + else: + self.call_action("vectorOverlayConfig.setColormapEnabled", False) + self.apply_vector_overlay() + def clear_vector_overlay(self): """Clear the vector overlay configuration.""" self.call_action("clearVectorOverlay", True) + @validate(Boolean()) + def set_vector_overlay_visible(self, state): + """Set the vector overlay visibility. + + Parameters + ---------- + state : {0} + The desired visibility state. + """ + self.call_action("vectorOverlayConfig.setVisible", state) + + def show_vector_overlay(self): + """Show the contours.""" + self.set_vector_overlay_visible(True) + + def hide_vector_overlay(self): + """Hide the contours.""" + self.set_vector_overlay_visible(False) + # HISTOGRAM @validate(Boolean()) From 4ab22861b1b19669116a194b7cf2d6983d772c82 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Wed, 15 Mar 2023 18:56:29 +0800 Subject: [PATCH 10/40] Minor modification --- carta/image.py | 1 - 1 file changed, 1 deletion(-) diff --git a/carta/image.py b/carta/image.py index f29f59b..c969594 100644 --- a/carta/image.py +++ b/carta/image.py @@ -621,7 +621,6 @@ def configure_vector_overlay(self, angular_source=None, intensity_source=None, p debiasing = q_error is not None and u_error is not None if not debiasing and (q_error is not None or u_error is not None): logger.warning("The Stokes Q and the Stokes Q must both be set to enable debiasing.") - print(debiasing) self.call_action("vectorOverlayConfig.setVectorOverlayConfiguration", angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) @validate(Number(), NoneOr(Number()), NoneOr(Number()), Number(), Number(), Number()) From 26aac1fc1b265d95da583bb060ebd373a773ceb8 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Wed, 15 Mar 2023 21:31:54 +0800 Subject: [PATCH 11/40] Modify plot_vector_overlay --- carta/image.py | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/carta/image.py b/carta/image.py index c969594..ca327f6 100644 --- a/carta/image.py +++ b/carta/image.py @@ -709,40 +709,33 @@ def plot_vector_overlay(self, angular_source=None, intensity_source=None, pixel_ The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. Debiasing is disabled by default. u_error : {6} The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. Debiasing is disabled by default. - thickness : {0} + thickness : {7} The line thickness in pixels. By default is 1. - intensity_min : {1} + intensity_min : {8} The minimum value of intensity in Jy/pixel. - intensity_min : {2} + intensity_min : {9} The maximum value of intensity in Jy/pixel. - length_min : {3} + length_min : {10} The minimum value of line length in pixels. By default is 0. - length_min : {4} + length_min : {11} The maximum value of line length in pixels. By default is 20. - rotation_offset : {5} + rotation_offset : {12} The rotation offset in degrees. By default is 0. - color : {0} + color : {13} The color. - colormap : {0} + colormap : {14} The colormap. - bias : {1} + bias : {15} The colormap bias. - contrast : {2} + contrast : {16} The colormap contrast. """ self.configure_vector_overlay(angular_source, intensity_source, pixel_averaging, fractional_intensity, threshold, q_error, u_error) self.set_vector_overlay_style(thickness, intensity_min, intensity_max, length_min, length_max, rotation_offset) if color is not None: - self.call_action("vectorOverlayConfig.setColor", color) + self.set_vector_overlay_color(color) if colormap is not None: - self.call_action("vectorOverlayConfig.setColormap", colormap) - self.call_action("vectorOverlayConfig.setColormapEnabled", True) - if bias is not None: - self.call_action("vectorOverlayConfig.setColormapBias", bias) - if contrast is not None: - self.call_action("vectorOverlayConfig.setColormapContrast", contrast) - else: - self.call_action("vectorOverlayConfig.setColormapEnabled", False) + self.set_vector_overlay_colormap(colormap, bias, contrast) self.apply_vector_overlay() def clear_vector_overlay(self): From 406ef91bf39fc169df7c550eed2e20473ac0e77f Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Wed, 15 Mar 2023 21:56:04 +0800 Subject: [PATCH 12/40] Minor modification --- carta/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/carta/image.py b/carta/image.py index ca327f6..5fe049e 100644 --- a/carta/image.py +++ b/carta/image.py @@ -620,7 +620,7 @@ def configure_vector_overlay(self, angular_source=None, intensity_source=None, p threshold_enabled = threshold is not None debiasing = q_error is not None and u_error is not None if not debiasing and (q_error is not None or u_error is not None): - logger.warning("The Stokes Q and the Stokes Q must both be set to enable debiasing.") + logger.warning("The Stokes Q and the Stokes U must both be set to enable debiasing.") self.call_action("vectorOverlayConfig.setVectorOverlayConfiguration", angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) @validate(Number(), NoneOr(Number()), NoneOr(Number()), Number(), Number(), Number()) From a330edcbb2b84a3731ac18e2b7b43a8055487f8a Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Fri, 17 Mar 2023 15:23:27 +0800 Subject: [PATCH 13/40] Modify setIntensityRange and setLengthRange --- carta/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/carta/image.py b/carta/image.py index 5fe049e..331b2be 100644 --- a/carta/image.py +++ b/carta/image.py @@ -643,8 +643,8 @@ def set_vector_overlay_style(self, thickness=1, intensity_min=None, intensity_ma The rotation offset in degrees. By default is 0. """ self.call_action("vectorOverlayConfig.setThickness", thickness) - self.call_action("vectorOverlayConfig.setIntensityRange", [intensity_min, intensity_max]) - self.call_action("vectorOverlayConfig.setLengthRange", [length_min, length_max]) + self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, intensity_max) + self.call_action("vectorOverlayConfig.setLengthRange", length_min, length_max) self.call_action("vectorOverlayConfig.setRotationOffset", rotation_offset) @validate(Color()) From e947e968e874294a225e5840295ea349f79486b6 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Tue, 21 Mar 2023 14:33:00 +0800 Subject: [PATCH 14/40] Modification of setting intensity range in set_vector_overlay_style --- carta/image.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/carta/image.py b/carta/image.py index 331b2be..68bceb4 100644 --- a/carta/image.py +++ b/carta/image.py @@ -596,7 +596,7 @@ def hide_contours(self): # VECTOR OVERLAY @validate(NoneOr(Constant(VectorOverlaySource)), NoneOr(Constant(VectorOverlaySource)), NoneOr(Number()), Boolean(), NoneOr(Number()), NoneOr(Number()), NoneOr(Number())) - def configure_vector_overlay(self, angular_source=None, intensity_source=None, pixel_averaging=4, fractional_intensity=False, threshold=None, q_error=None, u_error=None): + def configure_vector_overlay(self, angular_source=VectorOverlaySource.NONE, intensity_source=VectorOverlaySource.NONE, pixel_averaging=4, fractional_intensity=False, threshold=None, q_error=None, u_error=None): """Configure vector overlay. Parameters @@ -643,7 +643,8 @@ def set_vector_overlay_style(self, thickness=1, intensity_min=None, intensity_ma The rotation offset in degrees. By default is 0. """ self.call_action("vectorOverlayConfig.setThickness", thickness) - self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, intensity_max) + if intensity_min is not None and intensity_max is not None: + self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, intensity_max) self.call_action("vectorOverlayConfig.setLengthRange", length_min, length_max) self.call_action("vectorOverlayConfig.setRotationOffset", rotation_offset) From a77a30c98effa32b156589fa2b518fbbd866b6d8 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Tue, 21 Mar 2023 14:38:01 +0800 Subject: [PATCH 15/40] Monor modification --- carta/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/carta/image.py b/carta/image.py index 68bceb4..d9ad69b 100644 --- a/carta/image.py +++ b/carta/image.py @@ -596,7 +596,7 @@ def hide_contours(self): # VECTOR OVERLAY @validate(NoneOr(Constant(VectorOverlaySource)), NoneOr(Constant(VectorOverlaySource)), NoneOr(Number()), Boolean(), NoneOr(Number()), NoneOr(Number()), NoneOr(Number())) - def configure_vector_overlay(self, angular_source=VectorOverlaySource.NONE, intensity_source=VectorOverlaySource.NONE, pixel_averaging=4, fractional_intensity=False, threshold=None, q_error=None, u_error=None): + def configure_vector_overlay(self, angular_source=None, intensity_source=None, pixel_averaging=4, fractional_intensity=False, threshold=None, q_error=None, u_error=None): """Configure vector overlay. Parameters From 7169d886e0b4d429d4e7c7bd1da055b7d62f1559 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Tue, 28 Mar 2023 13:04:13 +0800 Subject: [PATCH 16/40] Replace None with non-empty string --- carta/image.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/carta/image.py b/carta/image.py index 111cda8..bc6ca20 100644 --- a/carta/image.py +++ b/carta/image.py @@ -633,6 +633,16 @@ def configure_vector_overlay(self, angular_source=None, intensity_source=None, p debiasing = q_error is not None and u_error is not None if not debiasing and (q_error is not None or u_error is not None): logger.warning("The Stokes Q and the Stokes U must both be set to enable debiasing.") + if angular_source is None: + angular_source = "None" + if intensity_source is None: + intensity_source = "None" + if threshold is None: + threshold = "None" + if q_error is None: + q_error = "None" + if u_error is None: + u_error = "None" self.call_action("vectorOverlayConfig.setVectorOverlayConfiguration", angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) @validate(Number(), NoneOr(Number()), NoneOr(Number()), Number(), Number(), Number()) @@ -657,6 +667,10 @@ def set_vector_overlay_style(self, thickness=1, intensity_min=None, intensity_ma self.call_action("vectorOverlayConfig.setThickness", thickness) if intensity_min is not None and intensity_max is not None: self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, intensity_max) + elif intensity_min is None and intensity_max is not None: + self.call_action("vectorOverlayConfig.setIntensityRange", "None", intensity_max) + elif intensity_min is not None and intensity_max is None: + self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, "None") self.call_action("vectorOverlayConfig.setLengthRange", length_min, length_max) self.call_action("vectorOverlayConfig.setRotationOffset", rotation_offset) From 3ccac4f9f16be7b404b628f4990ddf5327d93150 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Mon, 3 Apr 2023 19:11:59 +0800 Subject: [PATCH 17/40] Adopt macro in configure_vector_overlay --- carta/image.py | 54 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/carta/image.py b/carta/image.py index bc6ca20..a137925 100644 --- a/carta/image.py +++ b/carta/image.py @@ -607,8 +607,8 @@ def hide_contours(self): # VECTOR OVERLAY - @validate(NoneOr(Constant(VectorOverlaySource)), NoneOr(Constant(VectorOverlaySource)), NoneOr(Number()), Boolean(), NoneOr(Number()), NoneOr(Number()), NoneOr(Number())) - def configure_vector_overlay(self, angular_source=None, intensity_source=None, pixel_averaging=4, fractional_intensity=False, threshold=None, q_error=None, u_error=None): + @validate(NoneOr(Constant(VectorOverlaySource)), NoneOr(Constant(VectorOverlaySource)), NoneOr(Boolean()), NoneOr(Number()), Boolean(), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Number())) + def configure_vector_overlay(self, angular_source=None, intensity_source=None, pixel_averaging_enabled=None, pixel_averaging=None, fractional_intensity=None, threshold_enabled=None, threshold=None, debiasing=None, q_error=None, u_error=None): """Configure vector overlay. Parameters @@ -617,32 +617,52 @@ def configure_vector_overlay(self, angular_source=None, intensity_source=None, p The angular source. If the image is with Stoke information, ``Computed PA`` is set by default; If the image is without Stoke information, ``Current image`` is set. intensity_source : {1} The intensity source. If the image is with Stoke information, ``Computed PI`` is set by default; If the image is without Stoke information, ``Current image`` is set. - pixel_averaging : {2} + pixel_averaging_enabled : {2} + To enable pixel averaging. + pixel_averaging : {3} The pixel averaging width in pixel. Set to ``None`` to disable pixel averaging. - fractional_intensity : {3} + fractional_intensity : {4} Enable fractional polarization intensity. By default the absolute polarization intensity is used. - threshold : {4} + threshold_enabled : {5} + To enable threshold. + threshold : {6} The threshold in Jy/pixels. By default is 4. - q_error : {5} + debiasing : {7} + To enable debiasing. + q_error : {8} The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. Debiasing is disabled by default. - u_error : {6} + u_error : {9} The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. Debiasing is disabled by default. """ - pixel_averaging_enabled = pixel_averaging is not None - threshold_enabled = threshold is not None - debiasing = q_error is not None and u_error is not None - if not debiasing and (q_error is not None or u_error is not None): - logger.warning("The Stokes Q and the Stokes U must both be set to enable debiasing.") + if pixel_averaging is not None and pixel_averaging_enabled is None: + pixel_averaging_enabled = True + if threshold is not None and threshold_enabled is None: + threshold_enabled = True + if q_error is not None and u_error is not None and debiasing is None: + debiasing = True + if (q_error is not None and u_error is None) or (q_error is None and u_error is not None): + debiasing = False + logger.warning("The Stokes Q error and Stokes U error must both be set to enable debiasing.") if angular_source is None: - angular_source = "None" + angular_source = Macro("vectorOverlayConfig", "angularSource") if intensity_source is None: - intensity_source = "None" + intensity_source = Macro("vectorOverlayConfig", "intensitySource") + if pixel_averaging_enabled is None: + pixel_averaging_enabled = Macro("vectorOverlayConfig", "pixelAveragingEnabled") + if pixel_averaging is None: + pixel_averaging = Macro("vectorOverlayConfig", "pixelAveraging") + if fractional_intensity is None: + fractional_intensity = Macro("vectorOverlayConfig", "fractionalIntensity") + if threshold_enabled is None: + threshold_enabled = Macro("vectorOverlayConfig", "thresholdEnabled") if threshold is None: - threshold = "None" + threshold = Macro("vectorOverlayConfig", "threshold") + if debiasing is None: + debiasing = Macro("vectorOverlayConfig", "debiasing") if q_error is None: - q_error = "None" + q_error = Macro("vectorOverlayConfig", "qError") if u_error is None: - u_error = "None" + u_error = Macro("vectorOverlayConfig", "uError") self.call_action("vectorOverlayConfig.setVectorOverlayConfiguration", angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) @validate(Number(), NoneOr(Number()), NoneOr(Number()), Number(), Number(), Number()) From d8554c4cb236974f24d2bfbaba1a85e2a8023e28 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Mon, 3 Apr 2023 21:19:54 +0800 Subject: [PATCH 18/40] Modification to set_vector_overlay_style --- carta/constants.py | 4 ++++ carta/image.py | 24 ++++++++++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/carta/constants.py b/carta/constants.py index 001cf88..02bd4db 100644 --- a/carta/constants.py +++ b/carta/constants.py @@ -115,6 +115,10 @@ def __init__(self, value): VectorOverlaySource = Enum('VectorOverlaySource', ('NONE', 'CURRENT', 'COMPUTED'), type=int, start=-1) VectorOverlaySource.__doc__ = """Vector overlay source.""" +class Auto(str, Enum): + """Special value for parameters to be calculated automatically.""" + AUTO = "Auto" + class ContourDashMode(str, Enum): """Contour dash modes.""" NONE = "None" diff --git a/carta/image.py b/carta/image.py index a137925..d5d705f 100644 --- a/carta/image.py +++ b/carta/image.py @@ -4,7 +4,7 @@ """ import posixpath -from .constants import Colormap, Scaling, SmoothingMode, ContourDashMode, Polarization, VectorOverlaySource +from .constants import Colormap, Scaling, SmoothingMode, ContourDashMode, Polarization, VectorOverlaySource, Auto from .util import logger, Macro, cached from .validation import validate, Number, Color, Constant, Boolean, NoneOr, IterableOf, Evaluate, Attr, Attrs, OneOf @@ -607,7 +607,7 @@ def hide_contours(self): # VECTOR OVERLAY - @validate(NoneOr(Constant(VectorOverlaySource)), NoneOr(Constant(VectorOverlaySource)), NoneOr(Boolean()), NoneOr(Number()), Boolean(), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Number())) + @validate(NoneOr(Constant(VectorOverlaySource)), NoneOr(Constant(VectorOverlaySource)), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Boolean()), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Number())) def configure_vector_overlay(self, angular_source=None, intensity_source=None, pixel_averaging_enabled=None, pixel_averaging=None, fractional_intensity=None, threshold_enabled=None, threshold=None, debiasing=None, q_error=None, u_error=None): """Configure vector overlay. @@ -665,8 +665,8 @@ def configure_vector_overlay(self, angular_source=None, intensity_source=None, p u_error = Macro("vectorOverlayConfig", "uError") self.call_action("vectorOverlayConfig.setVectorOverlayConfiguration", angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) - @validate(Number(), NoneOr(Number()), NoneOr(Number()), Number(), Number(), Number()) - def set_vector_overlay_style(self, thickness=1, intensity_min=None, intensity_max=None, length_min=0, length_max=20, rotation_offset=0): + @validate(NoneOr(Number()), NoneOr(OneOf(Number(), Constant(Auto))), NoneOr(OneOf(Number(), Constant(Auto))), NoneOr(Number()), NoneOr(Number()), NoneOr(Number())) + def set_vector_overlay_style(self, thickness=None, intensity_min=None, intensity_max=None, length_min=None, length_max=None, rotation_offset=None): """Set the styling (line thickness, intensity range, line length range, rotation offset) of vector overlay. Parameters @@ -674,9 +674,9 @@ def set_vector_overlay_style(self, thickness=1, intensity_min=None, intensity_ma thickness : {0} The line thickness in pixels. By default is 1. intensity_min : {1} - The minimum value of intensity in Jy/pixel. + The minimum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. intensity_min : {2} - The maximum value of intensity in Jy/pixel. + The maximum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. length_min : {3} The minimum value of line length in pixels. By default is 0. length_min : {4} @@ -684,14 +684,22 @@ def set_vector_overlay_style(self, thickness=1, intensity_min=None, intensity_ma rotation_offset : {5} The rotation offset in degrees. By default is 0. """ + if thickness is None: + thickness = Macro("vectorOverlayConfig", "thickness") self.call_action("vectorOverlayConfig.setThickness", thickness) if intensity_min is not None and intensity_max is not None: self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, intensity_max) elif intensity_min is None and intensity_max is not None: - self.call_action("vectorOverlayConfig.setIntensityRange", "None", intensity_max) + self.call_action("vectorOverlayConfig.setIntensityRange", Macro("vectorOverlayConfig", "intensityMin"), intensity_max) elif intensity_min is not None and intensity_max is None: - self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, "None") + self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, Macro("vectorOverlayConfig", "intensityMax")) + if length_min is None: + length_min = Macro("vectorOverlayConfig", "lengthMin") + if length_max is None: + length_max = Macro("vectorOverlayConfig", "lengthMax") self.call_action("vectorOverlayConfig.setLengthRange", length_min, length_max) + if rotation_offset is None: + rotation_offset = Macro("vectorOverlayConfig", "rotationOffset") self.call_action("vectorOverlayConfig.setRotationOffset", rotation_offset) @validate(Color()) From e47a216f879536bc92227862b4b95b10f99f2619 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Tue, 4 Apr 2023 11:24:59 +0800 Subject: [PATCH 19/40] Modify plot_vector_overlay --- carta/image.py | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/carta/image.py b/carta/image.py index d5d705f..cf89bc6 100644 --- a/carta/image.py +++ b/carta/image.py @@ -693,6 +693,8 @@ def set_vector_overlay_style(self, thickness=None, intensity_min=None, intensity self.call_action("vectorOverlayConfig.setIntensityRange", Macro("vectorOverlayConfig", "intensityMin"), intensity_max) elif intensity_min is not None and intensity_max is None: self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, Macro("vectorOverlayConfig", "intensityMax")) + else: + self.call_action("vectorOverlayConfig.setIntensityRange", Macro("vectorOverlayConfig", "intensityMin"), Macro("vectorOverlayConfig", "intensityMax")) if length_min is None: length_min = Macro("vectorOverlayConfig", "lengthMin") if length_max is None: @@ -742,8 +744,8 @@ def apply_vector_overlay(self): """Apply the vector overlay configuration.""" self.call_action("applyVectorOverlay") - @validate(NoneOr(Constant(VectorOverlaySource)), NoneOr(Constant(VectorOverlaySource)), NoneOr(Number()), Boolean(), NoneOr(Number()), NoneOr(Number()), NoneOr(Number()), Number(), NoneOr(Number()), NoneOr(Number()), Number(), Number(), Number(), NoneOr(Color()), NoneOr(Constant(Colormap)), NoneOr(Number()), NoneOr(Number())) - def plot_vector_overlay(self, angular_source=None, intensity_source=None, pixel_averaging=4, fractional_intensity=False, threshold=None, q_error=None, u_error=None, thickness=1, intensity_min=None, intensity_max=None, length_min=0, length_max=20, rotation_offset=0, color=None, colormap=None, bias=None, contrast=None): + @validate(NoneOr(Constant(VectorOverlaySource)), NoneOr(Constant(VectorOverlaySource)), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Boolean()), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Number()),NoneOr(Number()), NoneOr(OneOf(Number(), Constant(Auto))), NoneOr(OneOf(Number(), Constant(Auto))), NoneOr(Number()), NoneOr(Number()), NoneOr(Number()), NoneOr(Color()), NoneOr(Constant(Colormap)), NoneOr(Number()), NoneOr(Number())) + def plot_vector_overlay(self, angular_source=None, intensity_source=None, pixel_averaging_enabled=None, pixel_averaging=None, fractional_intensity=None, threshold_enabled=None, threshold=None, debiasing=None, q_error=None, u_error=None, thickness=None, intensity_min=None, intensity_max=None, length_min=None, length_max=None, rotation_offset=None, color=None, colormap=None, bias=None, contrast=None): """Set the vector overlay configuration, styling and colour or colourmap; and apply vector overlay; in a single step. If both a colour and a colourmap are provided, the colourmap will be visible. @@ -754,38 +756,44 @@ def plot_vector_overlay(self, angular_source=None, intensity_source=None, pixel_ The angular source. If the image is with Stoke information, ``Computed PA`` is set by default; If the image is without Stoke information, ``Current image`` is set. intensity_source : {1} The intensity source. If the image is with Stoke information, ``Computed PI`` is set by default; If the image is without Stoke information, ``Current image`` is set. - pixel_averaging : {2} + pixel_averaging_enabled: {2} + To enable pixel averaging. Default is ``True`` + pixel_averaging : {3} The pixel averaging width in pixel. Set to ``None`` to disable pixel averaging. - fractional_intensity : {3} + fractional_intensity : {4} Enable fractional polarization intensity. By default the absolute polarization intensity is used. - threshold : {4} + threshold_enabled : {5} + To enable pixel threshold. Default is ``False`` + threshold : {6} The threshold in Jy/pixels. By default is 4. - q_error : {5} + debiasing : {7} + To enable debiasing. Default is ``False``. + q_error : {8} The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. Debiasing is disabled by default. - u_error : {6} + u_error : {9} The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. Debiasing is disabled by default. - thickness : {7} + thickness : {10} The line thickness in pixels. By default is 1. - intensity_min : {8} + intensity_min : {11} The minimum value of intensity in Jy/pixel. - intensity_min : {9} + intensity_min : {12} The maximum value of intensity in Jy/pixel. - length_min : {10} + length_min : {13} The minimum value of line length in pixels. By default is 0. - length_min : {11} + length_min : {14} The maximum value of line length in pixels. By default is 20. - rotation_offset : {12} + rotation_offset : {15} The rotation offset in degrees. By default is 0. - color : {13} + color : {16} The color. - colormap : {14} + colormap : {17} The colormap. - bias : {15} + bias : {18} The colormap bias. - contrast : {16} + contrast : {19} The colormap contrast. """ - self.configure_vector_overlay(angular_source, intensity_source, pixel_averaging, fractional_intensity, threshold, q_error, u_error) + self.configure_vector_overlay(angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) self.set_vector_overlay_style(thickness, intensity_min, intensity_max, length_min, length_max, rotation_offset) if color is not None: self.set_vector_overlay_color(color) From a250939038b569d062094b0398f2b195c9060a53 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Tue, 4 Apr 2023 12:09:11 +0800 Subject: [PATCH 20/40] Logic modification to set_vector_overlay_style --- carta/image.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/carta/image.py b/carta/image.py index cf89bc6..ff9281f 100644 --- a/carta/image.py +++ b/carta/image.py @@ -684,25 +684,18 @@ def set_vector_overlay_style(self, thickness=None, intensity_min=None, intensity rotation_offset : {5} The rotation offset in degrees. By default is 0. """ - if thickness is None: - thickness = Macro("vectorOverlayConfig", "thickness") - self.call_action("vectorOverlayConfig.setThickness", thickness) + if thickness is not None: + self.call_action("vectorOverlayConfig.setThickness", thickness) if intensity_min is not None and intensity_max is not None: self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, intensity_max) elif intensity_min is None and intensity_max is not None: self.call_action("vectorOverlayConfig.setIntensityRange", Macro("vectorOverlayConfig", "intensityMin"), intensity_max) elif intensity_min is not None and intensity_max is None: self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, Macro("vectorOverlayConfig", "intensityMax")) - else: - self.call_action("vectorOverlayConfig.setIntensityRange", Macro("vectorOverlayConfig", "intensityMin"), Macro("vectorOverlayConfig", "intensityMax")) - if length_min is None: - length_min = Macro("vectorOverlayConfig", "lengthMin") - if length_max is None: - length_max = Macro("vectorOverlayConfig", "lengthMax") - self.call_action("vectorOverlayConfig.setLengthRange", length_min, length_max) - if rotation_offset is None: - rotation_offset = Macro("vectorOverlayConfig", "rotationOffset") - self.call_action("vectorOverlayConfig.setRotationOffset", rotation_offset) + if length_min is not None and length_max is None: + self.call_action("vectorOverlayConfig.setLengthRange", length_min, length_max) + if rotation_offset is not None: + self.call_action("vectorOverlayConfig.setRotationOffset", rotation_offset) @validate(Color()) def set_vector_overlay_color(self, color): From 892014fa1637434237ff22d021f62566234b1273 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Tue, 4 Apr 2023 12:50:30 +0800 Subject: [PATCH 21/40] Minor modification to description --- carta/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/carta/image.py b/carta/image.py index ff9281f..bd83853 100644 --- a/carta/image.py +++ b/carta/image.py @@ -675,11 +675,11 @@ def set_vector_overlay_style(self, thickness=None, intensity_min=None, intensity The line thickness in pixels. By default is 1. intensity_min : {1} The minimum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. - intensity_min : {2} + intensity_max : {2} The maximum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. length_min : {3} The minimum value of line length in pixels. By default is 0. - length_min : {4} + length_max : {4} The maximum value of line length in pixels. By default is 20. rotation_offset : {5} The rotation offset in degrees. By default is 0. From 91eac819674c7b5e14cc8c0987737c41572b0d55 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Tue, 4 Apr 2023 22:03:31 +0800 Subject: [PATCH 22/40] Modify validate check for intensity_max and intensity_min --- carta/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/carta/image.py b/carta/image.py index 9ebf7e8..a73d740 100644 --- a/carta/image.py +++ b/carta/image.py @@ -725,7 +725,7 @@ def configure_vector_overlay(self, angular_source=None, intensity_source=None, p u_error = Macro("vectorOverlayConfig", "uError") self.call_action("vectorOverlayConfig.setVectorOverlayConfiguration", angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) - @validate(NoneOr(Number()), NoneOr(OneOf(Number(), Constant(Auto))), NoneOr(OneOf(Number(), Constant(Auto))), NoneOr(Number()), NoneOr(Number()), NoneOr(Number())) + @validate(NoneOr(Number()), NoneOr(Number(), Constant(Auto)), NoneOr(Number(), Constant(Auto)), NoneOr(Number()), NoneOr(Number()), NoneOr(Number())) def set_vector_overlay_style(self, thickness=None, intensity_min=None, intensity_max=None, length_min=None, length_max=None, rotation_offset=None): """Set the styling (line thickness, intensity range, line length range, rotation offset) of vector overlay. From bc80d47da6eed54b24d926a3b6a42e7503e06899 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Fri, 26 May 2023 15:50:08 +0800 Subject: [PATCH 23/40] Update outdated syntax --- carta/image.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/carta/image.py b/carta/image.py index 3333187..ad61256 100644 --- a/carta/image.py +++ b/carta/image.py @@ -711,25 +711,25 @@ def configure_vector_overlay(self, angular_source=None, intensity_source=None, p debiasing = False logger.warning("The Stokes Q error and Stokes U error must both be set to enable debiasing.") if angular_source is None: - angular_source = Macro("vectorOverlayConfig", "angularSource") + angular_source = self.macro("vectorOverlayConfig", "angularSource") if intensity_source is None: - intensity_source = Macro("vectorOverlayConfig", "intensitySource") + intensity_source = self.macro("vectorOverlayConfig", "intensitySource") if pixel_averaging_enabled is None: - pixel_averaging_enabled = Macro("vectorOverlayConfig", "pixelAveragingEnabled") + pixel_averaging_enabled = self.macro("vectorOverlayConfig", "pixelAveragingEnabled") if pixel_averaging is None: - pixel_averaging = Macro("vectorOverlayConfig", "pixelAveraging") + pixel_averaging = self.macro("vectorOverlayConfig", "pixelAveraging") if fractional_intensity is None: - fractional_intensity = Macro("vectorOverlayConfig", "fractionalIntensity") + fractional_intensity = self.macro("vectorOverlayConfig", "fractionalIntensity") if threshold_enabled is None: - threshold_enabled = Macro("vectorOverlayConfig", "thresholdEnabled") + threshold_enabled = self.macro("vectorOverlayConfig", "thresholdEnabled") if threshold is None: - threshold = Macro("vectorOverlayConfig", "threshold") + threshold = self.macro("vectorOverlayConfig", "threshold") if debiasing is None: - debiasing = Macro("vectorOverlayConfig", "debiasing") + debiasing = self.macro("vectorOverlayConfig", "debiasing") if q_error is None: - q_error = Macro("vectorOverlayConfig", "qError") + q_error = self.macro("vectorOverlayConfig", "qError") if u_error is None: - u_error = Macro("vectorOverlayConfig", "uError") + u_error = self.macro("vectorOverlayConfig", "uError") self.call_action("vectorOverlayConfig.setVectorOverlayConfiguration", angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) @validate(NoneOr(Number()), NoneOr(Number(), Constant(Auto)), NoneOr(Number(), Constant(Auto)), NoneOr(Number()), NoneOr(Number()), NoneOr(Number())) @@ -756,10 +756,10 @@ def set_vector_overlay_style(self, thickness=None, intensity_min=None, intensity if intensity_min is not None and intensity_max is not None: self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, intensity_max) elif intensity_min is None and intensity_max is not None: - self.call_action("vectorOverlayConfig.setIntensityRange", Macro("vectorOverlayConfig", "intensityMin"), intensity_max) + self.call_action("vectorOverlayConfig.setIntensityRange", self.macro("vectorOverlayConfig", "intensityMin"), intensity_max) elif intensity_min is not None and intensity_max is None: - self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, Macro("vectorOverlayConfig", "intensityMax")) - if length_min is not None and length_max is None: + self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, self.macro("vectorOverlayConfig", "intensityMax")) + if length_min is not None and length_max is not None: self.call_action("vectorOverlayConfig.setLengthRange", length_min, length_max) if rotation_offset is not None: self.call_action("vectorOverlayConfig.setRotationOffset", rotation_offset) From 285433b903ac372ea4c1dc81535e72b6e904e68e Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Tue, 30 May 2023 15:50:57 +0800 Subject: [PATCH 24/40] Add Undefined class in util --- carta/image.py | 8 +++++++- carta/util.py | 7 +++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/carta/image.py b/carta/image.py index ad61256..6a0b584 100644 --- a/carta/image.py +++ b/carta/image.py @@ -5,7 +5,7 @@ import posixpath from .constants import Colormap, Scaling, SmoothingMode, ContourDashMode, Polarization, VectorOverlaySource, Auto -from .util import logger, Macro, cached +from .util import logger, Macro, cached, Undefined from .validation import validate, Number, Color, Constant, Boolean, NoneOr, IterableOf, Evaluate, Attr, Attrs, OneOf @@ -759,6 +759,12 @@ def set_vector_overlay_style(self, thickness=None, intensity_min=None, intensity self.call_action("vectorOverlayConfig.setIntensityRange", self.macro("vectorOverlayConfig", "intensityMin"), intensity_max) elif intensity_min is not None and intensity_max is None: self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, self.macro("vectorOverlayConfig", "intensityMax")) + elif intensity_min is Auto.AUTO and intensity_max is Auto.AUTO: + self.call_action("vectorOverlayConfig.setIntensityRange", Undefined(), Undefined()) + elif intensity_min is not Auto.AUTO and intensity_max is Auto.AUTO: + self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, Undefined()) + elif intensity_min is Auto.AUTO and intensity_max is not Auto.AUTO: + self.call_action("vectorOverlayConfig.setIntensityRange", Undefined(), intensity_max) if length_min is not None and length_max is not None: self.call_action("vectorOverlayConfig.setLengthRange", length_min, length_max) if rotation_offset is not None: diff --git a/carta/util.py b/carta/util.py index 7d31bd2..7fbde63 100644 --- a/carta/util.py +++ b/carta/util.py @@ -88,7 +88,14 @@ def __repr__(self): def json(self): """The JSON serialization of this object.""" return {"macroTarget": self.target, "macroVariable": self.variable} + +class Undefined(Macro): + """ + A subclass of Macro to construct a placeholder for `"undefined"`. + """ + def __init__(self, target="", variable="undefined"): + super().__init__(target, variable) class CartaEncoder(json.JSONEncoder): """A custom encoder to JSON which correctly serialises custom objects with a ``json`` method, and numpy arrays.""" From 556eb32462ca8e650046ede7fbd4ea2da8ed5e03 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Tue, 30 May 2023 17:18:35 +0800 Subject: [PATCH 25/40] Simplify intensity_min and intensity_max definition --- carta/image.py | 25 +++++++++++++------------ carta/util.py | 4 ++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/carta/image.py b/carta/image.py index 6a0b584..8dada11 100644 --- a/carta/image.py +++ b/carta/image.py @@ -753,18 +753,19 @@ def set_vector_overlay_style(self, thickness=None, intensity_min=None, intensity """ if thickness is not None: self.call_action("vectorOverlayConfig.setThickness", thickness) - if intensity_min is not None and intensity_max is not None: - self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, intensity_max) - elif intensity_min is None and intensity_max is not None: - self.call_action("vectorOverlayConfig.setIntensityRange", self.macro("vectorOverlayConfig", "intensityMin"), intensity_max) - elif intensity_min is not None and intensity_max is None: - self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, self.macro("vectorOverlayConfig", "intensityMax")) - elif intensity_min is Auto.AUTO and intensity_max is Auto.AUTO: - self.call_action("vectorOverlayConfig.setIntensityRange", Undefined(), Undefined()) - elif intensity_min is not Auto.AUTO and intensity_max is Auto.AUTO: - self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, Undefined()) - elif intensity_min is Auto.AUTO and intensity_max is not Auto.AUTO: - self.call_action("vectorOverlayConfig.setIntensityRange", Undefined(), intensity_max) + if intensity_min is None: + intensity_min = self.macro("vectorOverlayConfig", "intensityMin") + elif intensity_min is Auto.AUTO: + intensity_min = Undefined() + else: + intensity_min = intensity_min + if intensity_max is None: + intensity_max = self.macro("vectorOverlayConfig", "intensityMax") + elif intensity_max is Auto.AUTO: + intensity_max = Undefined() + else: + intensity_max = intensity_max + self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, intensity_max) if length_min is not None and length_max is not None: self.call_action("vectorOverlayConfig.setLengthRange", length_min, length_max) if rotation_offset is not None: diff --git a/carta/util.py b/carta/util.py index 7fbde63..be7199c 100644 --- a/carta/util.py +++ b/carta/util.py @@ -94,8 +94,8 @@ class Undefined(Macro): A subclass of Macro to construct a placeholder for `"undefined"`. """ - def __init__(self, target="", variable="undefined"): - super().__init__(target, variable) + def __init__(self): + super().__init__(target="", variable="undefined") class CartaEncoder(json.JSONEncoder): """A custom encoder to JSON which correctly serialises custom objects with a ``json`` method, and numpy arrays.""" From 142c3b74a1777ced96f45a813fc07af79f4f20c3 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Tue, 30 May 2023 17:32:29 +0800 Subject: [PATCH 26/40] Modification to description and setIntensityRange logic --- carta/image.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/carta/image.py b/carta/image.py index 8dada11..183be60 100644 --- a/carta/image.py +++ b/carta/image.py @@ -681,21 +681,21 @@ def configure_vector_overlay(self, angular_source=None, intensity_source=None, p Parameters ---------- angular_source : {0} - The angular source. If the image is with Stoke information, ``Computed PA`` is set by default; If the image is without Stoke information, ``Current image`` is set. + The angular source. This is initially set to computed PA if the image contains Stokes information, otherwise to the current image. intensity_source : {1} The intensity source. If the image is with Stoke information, ``Computed PI`` is set by default; If the image is without Stoke information, ``Current image`` is set. pixel_averaging_enabled : {2} - To enable pixel averaging. + Enable pixel averaging. pixel_averaging : {3} - The pixel averaging width in pixel. Set to ``None`` to disable pixel averaging. + The pixel averaging width in pixel. fractional_intensity : {4} Enable fractional polarization intensity. By default the absolute polarization intensity is used. threshold_enabled : {5} - To enable threshold. + Enable threshold. threshold : {6} - The threshold in Jy/pixels. By default is 4. + The threshold in Jy/pixels. debiasing : {7} - To enable debiasing. + Enable debiasing. q_error : {8} The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. Debiasing is disabled by default. u_error : {9} @@ -757,14 +757,10 @@ def set_vector_overlay_style(self, thickness=None, intensity_min=None, intensity intensity_min = self.macro("vectorOverlayConfig", "intensityMin") elif intensity_min is Auto.AUTO: intensity_min = Undefined() - else: - intensity_min = intensity_min if intensity_max is None: intensity_max = self.macro("vectorOverlayConfig", "intensityMax") elif intensity_max is Auto.AUTO: intensity_max = Undefined() - else: - intensity_max = intensity_max self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, intensity_max) if length_min is not None and length_max is not None: self.call_action("vectorOverlayConfig.setLengthRange", length_min, length_max) @@ -820,7 +816,7 @@ def plot_vector_overlay(self, angular_source=None, intensity_source=None, pixel_ Parameters ---------- angular_source : {0} - The angular source. If the image is with Stoke information, ``Computed PA`` is set by default; If the image is without Stoke information, ``Current image`` is set. + The angular source. This is initially set to computed PA if the image contains Stokes information, otherwise to the current image. intensity_source : {1} The intensity source. If the image is with Stoke information, ``Computed PI`` is set by default; If the image is without Stoke information, ``Current image`` is set. pixel_averaging_enabled: {2} From 5b9103afad00a7c8c35e9bdd7ceca29713ceb643 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Fri, 2 Jun 2023 16:34:11 +0800 Subject: [PATCH 27/40] Description modified --- carta/image.py | 70 ++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/carta/image.py b/carta/image.py index 183be60..b277ae1 100644 --- a/carta/image.py +++ b/carta/image.py @@ -678,28 +678,32 @@ def hide_contours(self): def configure_vector_overlay(self, angular_source=None, intensity_source=None, pixel_averaging_enabled=None, pixel_averaging=None, fractional_intensity=None, threshold_enabled=None, threshold=None, debiasing=None, q_error=None, u_error=None): """Configure vector overlay. + All parameters are optional. For each option that is not provided, the value currently set in the frontend will be preserved. Initial frontend settings are noted below. + + We deduce some boolean options. For example, providing an explicit pixel averaging width with the **pixel_averaging** parameter will automatically enable pixel averaging unless **pixel_averaging_enabled** is also explicitly set to ``False``. To disable pixel averaging, explicitly set **pixel_averaging_enabled** to ``False``. + Parameters ---------- angular_source : {0} The angular source. This is initially set to computed PA if the image contains Stokes information, otherwise to the current image. intensity_source : {1} - The intensity source. If the image is with Stoke information, ``Computed PI`` is set by default; If the image is without Stoke information, ``Current image`` is set. + The intensity source. This is initially set to computed PI if the image contains Stokes information, otherwise to the current image. pixel_averaging_enabled : {2} - Enable pixel averaging. + Enable pixel averaging. This is initially enabled if the pixel averaging width is positive. pixel_averaging : {3} - The pixel averaging width in pixel. + The pixel averaging width in pixels. The initial value can be configured in the frontend preferences (the default is ``4``). fractional_intensity : {4} - Enable fractional polarization intensity. By default the absolute polarization intensity is used. + Enable fractional polarization intensity. The initial value can be configured in the frontend preferences. By default this is disabled and the absolute polarization intensity is used. threshold_enabled : {5} - Enable threshold. + Enable threshold. Initially the threshold is disabled. threshold : {6} - The threshold in Jy/pixels. + The threshold in Jy/pixels. The initial value is zero. debiasing : {7} - Enable debiasing. + Enable debiasing. This is initially disabled. q_error : {8} - The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. Debiasing is disabled by default. + The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. Initially set to zero. u_error : {9} - The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. Debiasing is disabled by default. + The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. Initially set to zero. """ if pixel_averaging is not None and pixel_averaging_enabled is None: pixel_averaging_enabled = True @@ -739,17 +743,17 @@ def set_vector_overlay_style(self, thickness=None, intensity_min=None, intensity Parameters ---------- thickness : {0} - The line thickness in pixels. By default is 1. + The line thickness in pixels. The default is ``1``. intensity_min : {1} The minimum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. intensity_max : {2} The maximum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. length_min : {3} - The minimum value of line length in pixels. By default is 0. + The minimum value of line length in pixels. The defauly is ``0``. length_max : {4} - The maximum value of line length in pixels. By default is 20. + The maximum value of line length in pixels. The default is ``20``. rotation_offset : {5} - The rotation offset in degrees. By default is 0. + The rotation offset in degrees. The default is ``0``. """ if thickness is not None: self.call_action("vectorOverlayConfig.setThickness", thickness) @@ -809,44 +813,44 @@ def apply_vector_overlay(self): @validate(NoneOr(Constant(VectorOverlaySource)), NoneOr(Constant(VectorOverlaySource)), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Boolean()), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Number()),NoneOr(Number()), NoneOr(OneOf(Number(), Constant(Auto))), NoneOr(OneOf(Number(), Constant(Auto))), NoneOr(Number()), NoneOr(Number()), NoneOr(Number()), NoneOr(Color()), NoneOr(Constant(Colormap)), NoneOr(Number()), NoneOr(Number())) def plot_vector_overlay(self, angular_source=None, intensity_source=None, pixel_averaging_enabled=None, pixel_averaging=None, fractional_intensity=None, threshold_enabled=None, threshold=None, debiasing=None, q_error=None, u_error=None, thickness=None, intensity_min=None, intensity_max=None, length_min=None, length_max=None, rotation_offset=None, color=None, colormap=None, bias=None, contrast=None): - """Set the vector overlay configuration, styling and colour or colourmap; and apply vector overlay; in a single step. + """Set the vector overlay configuration, styling and color or colormap; and apply vector overlay; in a single step. - If both a colour and a colourmap are provided, the colourmap will be visible. + If both a color and a colormap are provided, the colormap will be visible. Parameters ---------- angular_source : {0} The angular source. This is initially set to computed PA if the image contains Stokes information, otherwise to the current image. intensity_source : {1} - The intensity source. If the image is with Stoke information, ``Computed PI`` is set by default; If the image is without Stoke information, ``Current image`` is set. - pixel_averaging_enabled: {2} - To enable pixel averaging. Default is ``True`` + The intensity source. This is initially set to computed PI if the image contains Stokes information, otherwise to the current image. + pixel_averaging_enabled : {2} + Enable pixel averaging. This is initially enabled if the pixel averaging width is positive. pixel_averaging : {3} - The pixel averaging width in pixel. Set to ``None`` to disable pixel averaging. + The pixel averaging width in pixels. The initial value can be configured in the frontend preferences (the default is ``4``). fractional_intensity : {4} - Enable fractional polarization intensity. By default the absolute polarization intensity is used. + Enable fractional polarization intensity. The initial value can be configured in the frontend preferences. By default this is disabled and the absolute polarization intensity is used. threshold_enabled : {5} - To enable pixel threshold. Default is ``False`` + Enable threshold. Initially the threshold is disabled. threshold : {6} - The threshold in Jy/pixels. By default is 4. + The threshold in Jy/pixels. The initial value is zero. debiasing : {7} - To enable debiasing. Default is ``False``. + Enable debiasing. This is initially disabled. q_error : {8} - The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. Debiasing is disabled by default. + The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. Initially set to zero. u_error : {9} - The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. Debiasing is disabled by default. + The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. Initially set to zero. thickness : {10} - The line thickness in pixels. By default is 1. + The line thickness in pixels. The default is ``1``. intensity_min : {11} - The minimum value of intensity in Jy/pixel. - intensity_min : {12} - The maximum value of intensity in Jy/pixel. + The minimum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. + intensity_max : {12} + The maximum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. length_min : {13} - The minimum value of line length in pixels. By default is 0. - length_min : {14} - The maximum value of line length in pixels. By default is 20. + The minimum value of line length in pixels. Defauly is ``0``. + length_max : {14} + The maximum value of line length in pixels. The default is ``20``. rotation_offset : {15} - The rotation offset in degrees. By default is 0. + The rotation offset in degrees. The default is ``0``. color : {16} The color. colormap : {17} From 910c2a5c45d536f9f83eccf65d3a8ce555a11257 Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Fri, 2 Jun 2023 17:45:08 +0800 Subject: [PATCH 28/40] Add colormap default description --- carta/image.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/carta/image.py b/carta/image.py index b277ae1..b8fba16 100644 --- a/carta/image.py +++ b/carta/image.py @@ -597,7 +597,7 @@ def set_contour_colormap(self, colormap, bias=None, contrast=None): Parameters ---------- colormap : {0} - The colormap. + The colormap. The default is :obj:`carta.constants.Colormap.VIRIDIS`. bias : {1} The colormap bias. contrast : {2} @@ -635,7 +635,7 @@ def plot_contours(self, levels=None, smoothing_mode=None, smoothing_factor=None, color : {5} The color. colormap : {6} - The colormap. + The colormap. The default is :obj:`carta.constants.Colormap.VIRIDIS`. bias : {7} The colormap bias. contrast : {8} @@ -794,11 +794,11 @@ def set_vector_overlay_colormap(self, colormap, bias=None, contrast=None): Parameters ---------- colormap : {0} - The colormap. + The colormap. The default is :obj:`carta.constants.Colormap.VIRIDIS`. bias : {1} - The colormap bias. + The colormap bias. Set to ``0`` by default. contrast : {2} - The colormap contrast. + The colormap contrast. Set to ``1`` by default """ self.call_action("vectorOverlayConfig.setColormap", colormap) self.call_action("vectorOverlayConfig.setColormapEnabled", True) @@ -854,11 +854,11 @@ def plot_vector_overlay(self, angular_source=None, intensity_source=None, pixel_ color : {16} The color. colormap : {17} - The colormap. + The colormap. The default is :obj:`carta.constants.Colormap.VIRIDIS`. bias : {18} - The colormap bias. + The colormap bias. Set to ``0`` by default. contrast : {19} - The colormap contrast. + The colormap contrast. Set to ``1`` by default """ self.configure_vector_overlay(angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) self.set_vector_overlay_style(thickness, intensity_min, intensity_max, length_min, length_max, rotation_offset) From 2a4df7fad54566f17ffbd250efaf3a9d287cb17a Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Fri, 2 Jun 2023 22:31:53 +0800 Subject: [PATCH 29/40] Add default value for color --- carta/image.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/carta/image.py b/carta/image.py index b8fba16..898c138 100644 --- a/carta/image.py +++ b/carta/image.py @@ -583,7 +583,7 @@ def set_contour_color(self, color): Parameters ---------- color : {0} - The color. + The color. The default value is ``#238551`` (a shade of green). """ self.call_action("contourConfig.setColor", color) self.call_action("contourConfig.setColormapEnabled", False) @@ -633,7 +633,7 @@ def plot_contours(self, levels=None, smoothing_mode=None, smoothing_factor=None, thickness : {4} The dash thickness. color : {5} - The color. + The color. The default value is ``#238551`` (a shade of green). colormap : {6} The colormap. The default is :obj:`carta.constants.Colormap.VIRIDIS`. bias : {7} @@ -780,7 +780,7 @@ def set_vector_overlay_color(self, color): Parameters ---------- color : {0} - The color. + The color. The default value is ``#238551`` (a shade of green). """ self.call_action("vectorOverlayConfig.setColor", color) self.call_action("vectorOverlayConfig.setColormapEnabled", False) @@ -852,7 +852,7 @@ def plot_vector_overlay(self, angular_source=None, intensity_source=None, pixel_ rotation_offset : {15} The rotation offset in degrees. The default is ``0``. color : {16} - The color. + The color. The default value is ``#238551`` (a shade of green). colormap : {17} The colormap. The default is :obj:`carta.constants.Colormap.VIRIDIS`. bias : {18} From 1e7d13e23e4c7390ed43b70a455ced5de614b1dc Mon Sep 17 00:00:00 2001 From: I-Chenn Date: Tue, 4 Jul 2023 09:51:40 +0800 Subject: [PATCH 30/40] Format fix --- carta/constants.py | 2 ++ carta/image.py | 2 +- carta/util.py | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/carta/constants.py b/carta/constants.py index ba9c988..9fc8406 100644 --- a/carta/constants.py +++ b/carta/constants.py @@ -135,10 +135,12 @@ def __init__(self, value): VectorOverlaySource = Enum('VectorOverlaySource', ('NONE', 'CURRENT', 'COMPUTED'), type=int, start=-1) VectorOverlaySource.__doc__ = """Vector overlay source.""" + class Auto(str, Enum): """Special value for parameters to be calculated automatically.""" AUTO = "Auto" + class ContourDashMode(str, Enum): """Contour dash modes.""" NONE = "None" diff --git a/carta/image.py b/carta/image.py index 1e08c7a..066b2eb 100644 --- a/carta/image.py +++ b/carta/image.py @@ -879,7 +879,7 @@ def apply_vector_overlay(self): """Apply the vector overlay configuration.""" self.call_action("applyVectorOverlay") - @validate(NoneOr(Constant(VectorOverlaySource)), NoneOr(Constant(VectorOverlaySource)), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Boolean()), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Number()),NoneOr(Number()), NoneOr(OneOf(Number(), Constant(Auto))), NoneOr(OneOf(Number(), Constant(Auto))), NoneOr(Number()), NoneOr(Number()), NoneOr(Number()), NoneOr(Color()), NoneOr(Constant(Colormap)), NoneOr(Number()), NoneOr(Number())) + @validate(NoneOr(Constant(VectorOverlaySource)), NoneOr(Constant(VectorOverlaySource)), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Boolean()), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Number()), NoneOr(Number()), NoneOr(OneOf(Number(), Constant(Auto))), NoneOr(OneOf(Number(), Constant(Auto))), NoneOr(Number()), NoneOr(Number()), NoneOr(Number()), NoneOr(Color()), NoneOr(Constant(Colormap)), NoneOr(Number()), NoneOr(Number())) def plot_vector_overlay(self, angular_source=None, intensity_source=None, pixel_averaging_enabled=None, pixel_averaging=None, fractional_intensity=None, threshold_enabled=None, threshold=None, debiasing=None, q_error=None, u_error=None, thickness=None, intensity_min=None, intensity_max=None, length_min=None, length_max=None, rotation_offset=None, color=None, colormap=None, bias=None, contrast=None): """Set the vector overlay configuration, styling and color or colormap; and apply vector overlay; in a single step. diff --git a/carta/util.py b/carta/util.py index 85c2915..abdbc5c 100644 --- a/carta/util.py +++ b/carta/util.py @@ -94,7 +94,8 @@ def __eq__(self, other): def json(self): """The JSON serialization of this object.""" return {"macroTarget": self.target, "macroVariable": self.variable} - + + class Undefined(Macro): """ A subclass of Macro to construct a placeholder for `"undefined"`. @@ -103,6 +104,7 @@ class Undefined(Macro): def __init__(self): super().__init__(target="", variable="undefined") + class CartaEncoder(json.JSONEncoder): """A custom encoder to JSON which correctly serialises custom objects with a ``json`` method, and numpy arrays.""" From d9d336c2744d66c84b4155adc9fde31d2b4990e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrianna=20Pi=C5=84ska?= Date: Mon, 7 Aug 2023 16:46:48 +0200 Subject: [PATCH 31/40] formatting pass and flake8 fix --- carta/constants.py | 1 + tests/test_image.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/carta/constants.py b/carta/constants.py index 41ab155..a8a2996 100644 --- a/carta/constants.py +++ b/carta/constants.py @@ -140,6 +140,7 @@ class SmoothingMode(IntEnum): BLOCK_AVERAGE = 1 GAUSSIAN_BLUR = 2 + VectorOverlaySource = Enum('VectorOverlaySource', ('NONE', 'CURRENT', 'COMPUTED'), type=int, start=-1) VectorOverlaySource.__doc__ = """Vector overlay source.""" diff --git a/tests/test_image.py b/tests/test_image.py index f89631b..15283e3 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -127,7 +127,7 @@ def test_new(session, mock_session_call_action, mock_session_method, args, kwarg mock_session_call_action.assert_called_with(*expected_params, return_path='frameInfo.fileId') - assert type(image_object) == Image + assert type(image_object) is Image assert image_object.session == session assert image_object.image_id == 123 assert image_object.file_name == expected_params[2] From 08a61fecd9176e9a09f79971f5394778db24c258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrianna=20Pi=C5=84ska?= Date: Mon, 7 Aug 2023 18:04:20 +0200 Subject: [PATCH 32/40] Added some magic for specifying optional and repeated validation parameters more cleanly. Did some refactoring for legibility. Still to be tested. --- carta/image.py | 64 +++++++++++++++++++++++++-------------------- carta/validation.py | 26 +++++++++++++++--- 2 files changed, 58 insertions(+), 32 deletions(-) diff --git a/carta/image.py b/carta/image.py index 649bc10..5405dcd 100644 --- a/carta/image.py +++ b/carta/image.py @@ -5,7 +5,7 @@ from .constants import Colormap, Scaling, SmoothingMode, ContourDashMode, Polarization, CoordinateSystem, SpatialAxis, VectorOverlaySource, Auto from .util import logger, Macro, cached, PixelValue, AngularSize, WorldCoordinate, Undefined -from .validation import validate, Number, Color, Constant, Boolean, NoneOr, IterableOf, Evaluate, Attr, Attrs, OneOf, Size, Coordinate +from .validation import validate, Number, Color, Constant, Boolean, NoneOr, IterableOf, Evaluate, Attr, Attrs, OneOf, Size, Coordinate, all_optional, Union class Image: @@ -608,7 +608,7 @@ def hide_raster(self): # CONTOURS - @validate(NoneOr(IterableOf(Number())), NoneOr(Constant(SmoothingMode)), NoneOr(Number())) + @validate(*all_optional(IterableOf(Number()), Constant(SmoothingMode), Number())) def configure_contours(self, levels=None, smoothing_mode=None, smoothing_factor=None): """Configure contours. @@ -629,7 +629,7 @@ def configure_contours(self, levels=None, smoothing_mode=None, smoothing_factor= smoothing_factor = self.macro("contourConfig", "smoothingFactor") self.call_action("contourConfig.setContourConfiguration", levels, smoothing_mode, smoothing_factor) - @validate(NoneOr(Constant(ContourDashMode)), NoneOr(Number())) + @validate(*all_optional(Constant(ContourDashMode), Number())) def set_contour_dash(self, dash_mode=None, thickness=None): """Set the contour dash style. @@ -685,7 +685,7 @@ def apply_contours(self): """Apply the contour configuration.""" self.call_action("applyContours") - @validate(NoneOr(IterableOf(Number())), NoneOr(Constant(SmoothingMode)), NoneOr(Number()), NoneOr(Constant(ContourDashMode)), NoneOr(Number()), NoneOr(Color()), NoneOr(Constant(Colormap)), NoneOr(Number()), NoneOr(Number())) + @validate(*all_optional(*configure_contours.VARGS, *set_contour_dash.VARGS, *set_contour_color.VARGS, *set_contour_colormap.VARGS)) def plot_contours(self, levels=None, smoothing_mode=None, smoothing_factor=None, dash_mode=None, thickness=None, color=None, colormap=None, bias=None, contrast=None): """Configure contour levels, scaling, dash, and colour or colourmap; and apply contours; in a single step. @@ -745,7 +745,7 @@ def hide_contours(self): # VECTOR OVERLAY - @validate(NoneOr(Constant(VectorOverlaySource)), NoneOr(Constant(VectorOverlaySource)), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Boolean()), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Number())) + @validate(*all_optional(Constant(VectorOverlaySource), Constant(VectorOverlaySource), Boolean(), Number(), Boolean(), Boolean(), Number(), Boolean(), Number(), Number())) def configure_vector_overlay(self, angular_source=None, intensity_source=None, pixel_averaging_enabled=None, pixel_averaging=None, fractional_intensity=None, threshold_enabled=None, threshold=None, debiasing=None, q_error=None, u_error=None): """Configure vector overlay. @@ -782,32 +782,33 @@ def configure_vector_overlay(self, angular_source=None, intensity_source=None, p threshold_enabled = True if q_error is not None and u_error is not None and debiasing is None: debiasing = True + if (q_error is not None and u_error is None) or (q_error is None and u_error is not None): debiasing = False logger.warning("The Stokes Q error and Stokes U error must both be set to enable debiasing.") - if angular_source is None: - angular_source = self.macro("vectorOverlayConfig", "angularSource") - if intensity_source is None: - intensity_source = self.macro("vectorOverlayConfig", "intensitySource") - if pixel_averaging_enabled is None: - pixel_averaging_enabled = self.macro("vectorOverlayConfig", "pixelAveragingEnabled") - if pixel_averaging is None: - pixel_averaging = self.macro("vectorOverlayConfig", "pixelAveraging") - if fractional_intensity is None: - fractional_intensity = self.macro("vectorOverlayConfig", "fractionalIntensity") - if threshold_enabled is None: - threshold_enabled = self.macro("vectorOverlayConfig", "thresholdEnabled") - if threshold is None: - threshold = self.macro("vectorOverlayConfig", "threshold") - if debiasing is None: - debiasing = self.macro("vectorOverlayConfig", "debiasing") - if q_error is None: - q_error = self.macro("vectorOverlayConfig", "qError") - if u_error is None: - u_error = self.macro("vectorOverlayConfig", "uError") - self.call_action("vectorOverlayConfig.setVectorOverlayConfiguration", angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) - - @validate(NoneOr(Number()), NoneOr(Number(), Constant(Auto)), NoneOr(Number(), Constant(Auto)), NoneOr(Number()), NoneOr(Number()), NoneOr(Number())) + + args = [] + + for value, attr_name in ( + (angular_source, "angularSource"), + (intensity_source, "intensitySource"), + (pixel_averaging_enabled, "pixelAveragingEnabled"), + (pixel_averaging, "pixelAveraging"), + (fractional_intensity, "fractionalIntensity"), + (threshold_enabled, "thresholdEnabled"), + (threshold, "threshold"), + (debiasing, "debiasing"), + (q_error, "qError"), + (u_error, "uError"), + ): + if value is None: + args.append(self.macro("vectorOverlayConfig", attr_name)) + else: + args.append(value) + + self.call_action("vectorOverlayConfig.setVectorOverlayConfiguration", *args) + + @validate(*all_optional(Number(), Union(Number(), Constant(Auto)), Union(Number(), Constant(Auto)), Number(), Number(), Number())) def set_vector_overlay_style(self, thickness=None, intensity_min=None, intensity_max=None, length_min=None, length_max=None, rotation_offset=None): """Set the styling (line thickness, intensity range, line length range, rotation offset) of vector overlay. @@ -828,17 +829,22 @@ def set_vector_overlay_style(self, thickness=None, intensity_min=None, intensity """ if thickness is not None: self.call_action("vectorOverlayConfig.setThickness", thickness) + if intensity_min is None: intensity_min = self.macro("vectorOverlayConfig", "intensityMin") elif intensity_min is Auto.AUTO: intensity_min = Undefined() + if intensity_max is None: intensity_max = self.macro("vectorOverlayConfig", "intensityMax") elif intensity_max is Auto.AUTO: intensity_max = Undefined() + self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, intensity_max) + if length_min is not None and length_max is not None: self.call_action("vectorOverlayConfig.setLengthRange", length_min, length_max) + if rotation_offset is not None: self.call_action("vectorOverlayConfig.setRotationOffset", rotation_offset) @@ -882,7 +888,7 @@ def apply_vector_overlay(self): """Apply the vector overlay configuration.""" self.call_action("applyVectorOverlay") - @validate(NoneOr(Constant(VectorOverlaySource)), NoneOr(Constant(VectorOverlaySource)), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Boolean()), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Boolean()), NoneOr(Number()), NoneOr(Number()), NoneOr(Number()), NoneOr(OneOf(Number(), Constant(Auto))), NoneOr(OneOf(Number(), Constant(Auto))), NoneOr(Number()), NoneOr(Number()), NoneOr(Number()), NoneOr(Color()), NoneOr(Constant(Colormap)), NoneOr(Number()), NoneOr(Number())) + @validate(*all_optional(*configure_vector_overlay.VARGS, *set_vector_overlay_style.VARGS, *set_vector_overlay_color.VARGS, *set_vector_overlay_colormap.VARGS)) def plot_vector_overlay(self, angular_source=None, intensity_source=None, pixel_averaging_enabled=None, pixel_averaging=None, fractional_intensity=None, threshold_enabled=None, threshold=None, debiasing=None, q_error=None, u_error=None, thickness=None, intensity_min=None, intensity_max=None, length_min=None, length_max=None, rotation_offset=None, color=None, colormap=None, bias=None, contrast=None): """Set the vector overlay configuration, styling and color or colormap; and apply vector overlay; in a single step. diff --git a/carta/validation.py b/carta/validation.py index c6464d9..c472456 100644 --- a/carta/validation.py +++ b/carta/validation.py @@ -444,7 +444,7 @@ def description(self): class NoneOr(Union): """A union of other parameter descriptors as well as ``None``. - In the most common use case, this is used with a single other parameter type for optional parameters which are ``None`` by default. In more complex cases this can be used as shorthand in place of a :obj:`carta.validation.Union` with an explicit :obj:`carta.validation.NoneParameter` option. + In the most common use case, this is used with a single other parameter type for optional parameters which are ``None`` by default. In more complex cases this can be used as shorthand in place of a :obj:`carta.validation.Union` with an explicit :obj:`carta.validation.NoneParameter` option. Also see :obj:`carta.validation.all_optional` for a less verbose way to specify multiple sequential optional parameters. Parameters ---------- @@ -799,13 +799,13 @@ def newfunc(self, *args, **kwargs): param = kwvargs[key] param.validate(value, self) except KeyError: - raise CartaValidationFailed(f"Unexpected keyword parameter: {key}") + raise CartaValidationFailed(f"Unexpected keyword parameter passed to {func.__name__}: {key}") except (TypeError, ValueError, AttributeError) as e: # Strip out any documentation formatting from the descriptions msg = str(e) msg = STRIP_OBJ.sub(r"\1", msg) msg = STRIP_CODE.sub(r"\1", msg) - raise CartaValidationFailed(f"Invalid function parameter: {msg}") + raise CartaValidationFailed(f"Invalid function parameter passed to {func.__name__}: {msg}") return func(self, *args, **kwargs) # If descriptions contain formatting they are not formatted correctly by Sphinx @@ -822,5 +822,25 @@ def fix_description(s): if newfunc.__doc__ is not None: newfunc.__doc__ = newfunc.__doc__.format(*(fix_description(p.description) for p in vargs)) + # Add a handle to the validation parameters to allow functions which call other functions to reuse parameters + newfunc.VARGS = vargs + return newfunc return decorator + + +def all_optional(*vargs): + """Wrapper to make all parameters in an iterable optional. + + For improved legibility in functions with many sequential optional parameters. Can also enable reuse of validation parameters in functions which call other functions. + + Parameters + ---------- + *vargs : iterable of :obj:`carta.validation.Parameter` objects + + Returns + ------- + iterable of :obj:`carta.validation.Parameter` objects + The same parameters in the same order, but with all non-optional parameters made optional (that is, wrapped in a obj:`carta.validation.NoneOr` parameter). + """ + return tuple(NoneOr(param) if not isinstance(param, NoneOr) else param for param in vargs) From 390de89aa1eaaef4f86d2c33774825ee7f640267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrianna=20Pi=C5=84ska?= Date: Wed, 9 Aug 2023 01:52:51 +0200 Subject: [PATCH 33/40] Some tests; minor fixes and refactoring --- carta/image.py | 88 +++++++++++++++++++++++---------------------- carta/util.py | 9 ++--- tests/test_image.py | 76 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 122 insertions(+), 51 deletions(-) diff --git a/carta/image.py b/carta/image.py index 5405dcd..d7fbbd3 100644 --- a/carta/image.py +++ b/carta/image.py @@ -4,7 +4,7 @@ """ from .constants import Colormap, Scaling, SmoothingMode, ContourDashMode, Polarization, CoordinateSystem, SpatialAxis, VectorOverlaySource, Auto -from .util import logger, Macro, cached, PixelValue, AngularSize, WorldCoordinate, Undefined +from .util import logger, Macro, cached, PixelValue, AngularSize, WorldCoordinate from .validation import validate, Number, Color, Constant, Boolean, NoneOr, IterableOf, Evaluate, Attr, Attrs, OneOf, Size, Coordinate, all_optional, Union @@ -745,7 +745,7 @@ def hide_contours(self): # VECTOR OVERLAY - @validate(*all_optional(Constant(VectorOverlaySource), Constant(VectorOverlaySource), Boolean(), Number(), Boolean(), Boolean(), Number(), Boolean(), Number(), Number())) + @validate(*all_optional(Constant(VectorOverlaySource), Constant(VectorOverlaySource), Boolean(), Number(), Number(), Boolean(), Number(), Boolean(), Number(), Number())) def configure_vector_overlay(self, angular_source=None, intensity_source=None, pixel_averaging_enabled=None, pixel_averaging=None, fractional_intensity=None, threshold_enabled=None, threshold=None, debiasing=None, q_error=None, u_error=None): """Configure vector overlay. @@ -776,37 +776,40 @@ def configure_vector_overlay(self, angular_source=None, intensity_source=None, p u_error : {9} The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. Initially set to zero. """ - if pixel_averaging is not None and pixel_averaging_enabled is None: - pixel_averaging_enabled = True - if threshold is not None and threshold_enabled is None: - threshold_enabled = True - if q_error is not None and u_error is not None and debiasing is None: - debiasing = True - - if (q_error is not None and u_error is None) or (q_error is None and u_error is not None): - debiasing = False - logger.warning("The Stokes Q error and Stokes U error must both be set to enable debiasing.") - - args = [] - - for value, attr_name in ( - (angular_source, "angularSource"), - (intensity_source, "intensitySource"), - (pixel_averaging_enabled, "pixelAveragingEnabled"), - (pixel_averaging, "pixelAveraging"), - (fractional_intensity, "fractionalIntensity"), - (threshold_enabled, "thresholdEnabled"), - (threshold, "threshold"), - (debiasing, "debiasing"), - (q_error, "qError"), - (u_error, "uError"), - ): - if value is None: - args.append(self.macro("vectorOverlayConfig", attr_name)) - else: - args.append(value) - - self.call_action("vectorOverlayConfig.setVectorOverlayConfiguration", *args) + + # Avoid doing a lot of needless work for a no-op + if any(name != "self" and arg is not None for name, arg in locals().items()): + if pixel_averaging is not None and pixel_averaging_enabled is None: + pixel_averaging_enabled = True + if threshold is not None and threshold_enabled is None: + threshold_enabled = True + if q_error is not None and u_error is not None and debiasing is None: + debiasing = True + + if (q_error is not None and u_error is None) or (q_error is None and u_error is not None): + debiasing = False + logger.warning("The Stokes Q error and Stokes U error must both be set to enable debiasing.") + + args = [] + + for value, attr_name in ( + (angular_source, "angularSource"), + (intensity_source, "intensitySource"), + (pixel_averaging_enabled, "pixelAveragingEnabled"), + (pixel_averaging, "pixelAveraging"), + (fractional_intensity, "fractionalIntensity"), + (threshold_enabled, "thresholdEnabled"), + (threshold, "threshold"), + (debiasing, "debiasing"), + (q_error, "qError"), + (u_error, "uError"), + ): + if value is None: + args.append(self.macro("vectorOverlayConfig", attr_name)) + else: + args.append(value) + + self.call_action("vectorOverlayConfig.setVectorOverlayConfiguration", *args) @validate(*all_optional(Number(), Union(Number(), Constant(Auto)), Union(Number(), Constant(Auto)), Number(), Number(), Number())) def set_vector_overlay_style(self, thickness=None, intensity_min=None, intensity_max=None, length_min=None, length_max=None, rotation_offset=None): @@ -830,17 +833,18 @@ def set_vector_overlay_style(self, thickness=None, intensity_min=None, intensity if thickness is not None: self.call_action("vectorOverlayConfig.setThickness", thickness) - if intensity_min is None: - intensity_min = self.macro("vectorOverlayConfig", "intensityMin") - elif intensity_min is Auto.AUTO: - intensity_min = Undefined() + if intensity_min is not None or intensity_max is not None: + if intensity_min is None: + intensity_min = self.macro("vectorOverlayConfig", "intensityMin") + elif intensity_min is Auto.AUTO: + intensity_min = Macro.UNDEFINED - if intensity_max is None: - intensity_max = self.macro("vectorOverlayConfig", "intensityMax") - elif intensity_max is Auto.AUTO: - intensity_max = Undefined() + if intensity_max is None: + intensity_max = self.macro("vectorOverlayConfig", "intensityMax") + elif intensity_max is Auto.AUTO: + intensity_max = Macro.UNDEFINED - self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, intensity_max) + self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, intensity_max) if length_min is not None and length_max is not None: self.call_action("vectorOverlayConfig.setLengthRange", length_min, length_max) diff --git a/carta/util.py b/carta/util.py index abdbc5c..dcf7410 100644 --- a/carta/util.py +++ b/carta/util.py @@ -96,13 +96,8 @@ def json(self): return {"macroTarget": self.target, "macroVariable": self.variable} -class Undefined(Macro): - """ - A subclass of Macro to construct a placeholder for `"undefined"`. - """ - - def __init__(self): - super().__init__(target="", variable="undefined") +Macro.UNDEFINED = Macro("", "undefined") +Macro.UNDEFINED.__doc__ = """A :obj:`carta.util.Macro` instance which is deserialized as ``undefined`` by the frontend.""" class CartaEncoder(json.JSONEncoder): diff --git a/tests/test_image.py b/tests/test_image.py index 15283e3..0c6a4de 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -3,8 +3,8 @@ from carta.session import Session from carta.image import Image -from carta.util import CartaValidationFailed -from carta.constants import NumberFormat as NF, CoordinateSystem, SpatialAxis as SA +from carta.util import CartaValidationFailed, Macro +from carta.constants import NumberFormat as NF, CoordinateSystem, SpatialAxis as SA, VectorOverlaySource as VOS, Auto # FIXTURES @@ -256,3 +256,75 @@ def test_zoom_to_size_invalid(image, mock_property, axis, val, wcs, error_contai with pytest.raises(Exception) as e: image.zoom_to_size(val, axis) assert error_contains in str(e.value) + +# VECTOR OVERLAY + + +@pytest.mark.parametrize("args,kwargs,expected_args", [ + # Nothing + ((), {}, None), + # Everything + ((VOS.CURRENT, VOS.CURRENT, True, 1, 2, True, 3, True, 4, 5), {}, (VOS.CURRENT, VOS.CURRENT, True, 1, 2, True, 3, True, 4, 5)), + # Deduce pixel averaging flag + ((), {"pixel_averaging": 1}, + ("M(angularSource)", "M(intensitySource)", True, 1, "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", "M(debiasing)", "M(qError)", "M(uError)")), + # Don't deduce pixel averaging flag + ((), {"pixel_averaging": 1, "pixel_averaging_enabled": False}, + ("M(angularSource)", "M(intensitySource)", False, 1, "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", "M(debiasing)", "M(qError)", "M(uError)")), + # Deduce threshold flag + ((), {"threshold": 2}, + ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", True, 2, "M(debiasing)", "M(qError)", "M(uError)")), + # Don't deduce threshold flag + ((), {"threshold": 2, "threshold_enabled": False}, + ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", False, 2, "M(debiasing)", "M(qError)", "M(uError)")), + # Deduce debiasing flag + ((), {"q_error": 3, "u_error": 4}, + ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", True, 3, 4)), + # Don'teduce debiasing flag + ((), {"q_error": 3, "u_error": 4, "debiasing": False}, + ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", False, 3, 4)), + # Disable debiasing (no q_error) + ((), {"u_error": 4, "debiasing": True}, + ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", False, "M(qError)", 4)), + # Disable debiasing (no u_error) + ((), {"q_error": 3, "debiasing": True}, + ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", False, 3, "M(uError)")), +]) +def test_configure_vector_overlay(image, mock_call_action, mock_method, args, kwargs, expected_args): + mock_method("macro", lambda _, v: f"M({v})") + image.configure_vector_overlay(*args, **kwargs) + if expected_args is None: + mock_call_action.assert_not_called() + else: + mock_call_action.assert_called_with("vectorOverlayConfig.setVectorOverlayConfiguration", *expected_args) + + +@pytest.mark.parametrize("args,kwargs,expected_calls", [ + # Nothing + ((), {}, ()), + # Everything + ((1, 2, 3, 4, 5, 6), {}, ( + ("vectorOverlayConfig.setThickness", 1), + ("vectorOverlayConfig.setIntensityRange", 2, 3), + ("vectorOverlayConfig.setLengthRange", 4, 5), + ("vectorOverlayConfig.setRotationOffset", 6), + )), + # No intensity min; auto intensity max + ((), {"intensity_max": Auto.AUTO}, (("vectorOverlayConfig.setIntensityRange", "M(intensityMin)", Macro.UNDEFINED),)), + # Auto intensity min; no intensity max + ((), {"intensity_min": Auto.AUTO}, (("vectorOverlayConfig.setIntensityRange", Macro.UNDEFINED, "M(intensityMax)"),)), +]) +def test_set_vector_overlay_style(mocker, image, mock_call_action, mock_method, args, kwargs, expected_calls): + mock_method("macro", lambda _, v: f"M({v})") + image.set_vector_overlay_style(*args, **kwargs) + mock_call_action.assert_has_calls([mocker.call(*call) for call in expected_calls]) + + +# TODO test_set_vector_overlay_color +# TODO test_set_vector_overlay_colormap +# TODO test_apply_vector_overlay +# TODO test_plot_vector_overlay +# TODO test_clear_vector_overlay +# TODO test_set_vector_overlay_visible +# TODO test_show_vector_overlay +# TODO test_hide_vector_overlay From 295d2f5ad04256fd0ae6b1c69a838181648a4d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrianna=20Pi=C5=84ska?= Date: Tue, 24 Oct 2023 16:42:11 +0200 Subject: [PATCH 34/40] Moved vector overlay functions to sub-object. --- VERSION.txt | 2 +- carta/image.py | 239 +------------------------------- carta/vector_overlay.py | 254 +++++++++++++++++++++++++++++++++++ tests/test_image.py | 76 +---------- tests/test_vector_overlay.py | 134 ++++++++++++++++++ 5 files changed, 398 insertions(+), 307 deletions(-) create mode 100644 carta/vector_overlay.py create mode 100644 tests/test_vector_overlay.py diff --git a/VERSION.txt b/VERSION.txt index 9ee1f78..26aaba0 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.1.11 +1.2.0 diff --git a/carta/image.py b/carta/image.py index a87a1be..89b49ea 100644 --- a/carta/image.py +++ b/carta/image.py @@ -3,11 +3,12 @@ Image objects should not be instantiated directly, and should only be created through methods on the :obj:`carta.session.Session` object. """ -from .constants import Colormap, Scaling, SmoothingMode, ContourDashMode, Polarization, SpatialAxis, VectorOverlaySource, Auto -from .util import logger, Macro, cached, BasePathMixin +from .constants import Colormap, Scaling, SmoothingMode, ContourDashMode, Polarization, SpatialAxis +from .util import Macro, cached, BasePathMixin from .units import AngularSize, WorldCoordinate -from .validation import validate, Number, Color, Constant, Boolean, NoneOr, IterableOf, Evaluate, Attr, Attrs, OneOf, Size, Coordinate, all_optional, Union +from .validation import validate, Number, Color, Constant, Boolean, NoneOr, IterableOf, Evaluate, Attr, Attrs, OneOf, Size, Coordinate, all_optional from .metadata import parse_header +from .vector_overlay import VectorOverlay class Image(BasePathMixin): @@ -37,6 +38,9 @@ def __init__(self, session, image_id): self._base_path = f"frameMap[{image_id}]" self._frame = Macro("", self._base_path) + # Sub-objects grouping related functions + self.vectors = VectorOverlay(self) + @classmethod def new(cls, session, directory, file_name, hdu, append, image_arithmetic, make_active=True, update_directory=False): """Open or append a new image in the session and return an image object associated with it. @@ -628,235 +632,6 @@ def hide_contours(self): """Hide the contours.""" self.set_contours_visible(False) - # VECTOR OVERLAY - - @validate(*all_optional(Constant(VectorOverlaySource), Constant(VectorOverlaySource), Boolean(), Number(), Number(), Boolean(), Number(), Boolean(), Number(), Number())) - def configure_vector_overlay(self, angular_source=None, intensity_source=None, pixel_averaging_enabled=None, pixel_averaging=None, fractional_intensity=None, threshold_enabled=None, threshold=None, debiasing=None, q_error=None, u_error=None): - """Configure vector overlay. - - All parameters are optional. For each option that is not provided, the value currently set in the frontend will be preserved. Initial frontend settings are noted below. - - We deduce some boolean options. For example, providing an explicit pixel averaging width with the **pixel_averaging** parameter will automatically enable pixel averaging unless **pixel_averaging_enabled** is also explicitly set to ``False``. To disable pixel averaging, explicitly set **pixel_averaging_enabled** to ``False``. - - Parameters - ---------- - angular_source : {0} - The angular source. This is initially set to computed PA if the image contains Stokes information, otherwise to the current image. - intensity_source : {1} - The intensity source. This is initially set to computed PI if the image contains Stokes information, otherwise to the current image. - pixel_averaging_enabled : {2} - Enable pixel averaging. This is initially enabled if the pixel averaging width is positive. - pixel_averaging : {3} - The pixel averaging width in pixels. The initial value can be configured in the frontend preferences (the default is ``4``). - fractional_intensity : {4} - Enable fractional polarization intensity. The initial value can be configured in the frontend preferences. By default this is disabled and the absolute polarization intensity is used. - threshold_enabled : {5} - Enable threshold. Initially the threshold is disabled. - threshold : {6} - The threshold in Jy/pixels. The initial value is zero. - debiasing : {7} - Enable debiasing. This is initially disabled. - q_error : {8} - The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. Initially set to zero. - u_error : {9} - The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. Initially set to zero. - """ - - # Avoid doing a lot of needless work for a no-op - if any(name != "self" and arg is not None for name, arg in locals().items()): - if pixel_averaging is not None and pixel_averaging_enabled is None: - pixel_averaging_enabled = True - if threshold is not None and threshold_enabled is None: - threshold_enabled = True - if q_error is not None and u_error is not None and debiasing is None: - debiasing = True - - if (q_error is not None and u_error is None) or (q_error is None and u_error is not None): - debiasing = False - logger.warning("The Stokes Q error and Stokes U error must both be set to enable debiasing.") - - args = [] - - for value, attr_name in ( - (angular_source, "angularSource"), - (intensity_source, "intensitySource"), - (pixel_averaging_enabled, "pixelAveragingEnabled"), - (pixel_averaging, "pixelAveraging"), - (fractional_intensity, "fractionalIntensity"), - (threshold_enabled, "thresholdEnabled"), - (threshold, "threshold"), - (debiasing, "debiasing"), - (q_error, "qError"), - (u_error, "uError"), - ): - if value is None: - args.append(self.macro("vectorOverlayConfig", attr_name)) - else: - args.append(value) - - self.call_action("vectorOverlayConfig.setVectorOverlayConfiguration", *args) - - @validate(*all_optional(Number(), Union(Number(), Constant(Auto)), Union(Number(), Constant(Auto)), Number(), Number(), Number())) - def set_vector_overlay_style(self, thickness=None, intensity_min=None, intensity_max=None, length_min=None, length_max=None, rotation_offset=None): - """Set the styling (line thickness, intensity range, line length range, rotation offset) of vector overlay. - - Parameters - ---------- - thickness : {0} - The line thickness in pixels. The default is ``1``. - intensity_min : {1} - The minimum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. - intensity_max : {2} - The maximum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. - length_min : {3} - The minimum value of line length in pixels. The defauly is ``0``. - length_max : {4} - The maximum value of line length in pixels. The default is ``20``. - rotation_offset : {5} - The rotation offset in degrees. The default is ``0``. - """ - if thickness is not None: - self.call_action("vectorOverlayConfig.setThickness", thickness) - - if intensity_min is not None or intensity_max is not None: - if intensity_min is None: - intensity_min = self.macro("vectorOverlayConfig", "intensityMin") - elif intensity_min is Auto.AUTO: - intensity_min = Macro.UNDEFINED - - if intensity_max is None: - intensity_max = self.macro("vectorOverlayConfig", "intensityMax") - elif intensity_max is Auto.AUTO: - intensity_max = Macro.UNDEFINED - - self.call_action("vectorOverlayConfig.setIntensityRange", intensity_min, intensity_max) - - if length_min is not None and length_max is not None: - self.call_action("vectorOverlayConfig.setLengthRange", length_min, length_max) - - if rotation_offset is not None: - self.call_action("vectorOverlayConfig.setRotationOffset", rotation_offset) - - @validate(Color()) - def set_vector_overlay_color(self, color): - """Set the vector overlay color. - - This automatically disables use of the vector overlay colormap. - - Parameters - ---------- - color : {0} - The color. The default value is ``#238551`` (a shade of green). - """ - self.call_action("vectorOverlayConfig.setColor", color) - self.call_action("vectorOverlayConfig.setColormapEnabled", False) - - @validate(Constant(Colormap), NoneOr(Number()), NoneOr(Number())) - def set_vector_overlay_colormap(self, colormap, bias=None, contrast=None): - """Set the vector overlay colormap. - - This automatically enables use of the vector overlay colormap. - - Parameters - ---------- - colormap : {0} - The colormap. The default is :obj:`carta.constants.Colormap.VIRIDIS`. - bias : {1} - The colormap bias. Set to ``0`` by default. - contrast : {2} - The colormap contrast. Set to ``1`` by default - """ - self.call_action("vectorOverlayConfig.setColormap", colormap) - self.call_action("vectorOverlayConfig.setColormapEnabled", True) - if bias is not None: - self.call_action("vectorOverlayConfig.setColormapBias", bias) - if contrast is not None: - self.call_action("vectorOverlayConfig.setColormapContrast", contrast) - - def apply_vector_overlay(self): - """Apply the vector overlay configuration.""" - self.call_action("applyVectorOverlay") - - @validate(*all_optional(*configure_vector_overlay.VARGS, *set_vector_overlay_style.VARGS, *set_vector_overlay_color.VARGS, *set_vector_overlay_colormap.VARGS)) - def plot_vector_overlay(self, angular_source=None, intensity_source=None, pixel_averaging_enabled=None, pixel_averaging=None, fractional_intensity=None, threshold_enabled=None, threshold=None, debiasing=None, q_error=None, u_error=None, thickness=None, intensity_min=None, intensity_max=None, length_min=None, length_max=None, rotation_offset=None, color=None, colormap=None, bias=None, contrast=None): - """Set the vector overlay configuration, styling and color or colormap; and apply vector overlay; in a single step. - - If both a color and a colormap are provided, the colormap will be visible. - - Parameters - ---------- - angular_source : {0} - The angular source. This is initially set to computed PA if the image contains Stokes information, otherwise to the current image. - intensity_source : {1} - The intensity source. This is initially set to computed PI if the image contains Stokes information, otherwise to the current image. - pixel_averaging_enabled : {2} - Enable pixel averaging. This is initially enabled if the pixel averaging width is positive. - pixel_averaging : {3} - The pixel averaging width in pixels. The initial value can be configured in the frontend preferences (the default is ``4``). - fractional_intensity : {4} - Enable fractional polarization intensity. The initial value can be configured in the frontend preferences. By default this is disabled and the absolute polarization intensity is used. - threshold_enabled : {5} - Enable threshold. Initially the threshold is disabled. - threshold : {6} - The threshold in Jy/pixels. The initial value is zero. - debiasing : {7} - Enable debiasing. This is initially disabled. - q_error : {8} - The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. Initially set to zero. - u_error : {9} - The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. Initially set to zero. - thickness : {10} - The line thickness in pixels. The default is ``1``. - intensity_min : {11} - The minimum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. - intensity_max : {12} - The maximum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. - length_min : {13} - The minimum value of line length in pixels. Defauly is ``0``. - length_max : {14} - The maximum value of line length in pixels. The default is ``20``. - rotation_offset : {15} - The rotation offset in degrees. The default is ``0``. - color : {16} - The color. The default value is ``#238551`` (a shade of green). - colormap : {17} - The colormap. The default is :obj:`carta.constants.Colormap.VIRIDIS`. - bias : {18} - The colormap bias. Set to ``0`` by default. - contrast : {19} - The colormap contrast. Set to ``1`` by default - """ - self.configure_vector_overlay(angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) - self.set_vector_overlay_style(thickness, intensity_min, intensity_max, length_min, length_max, rotation_offset) - if color is not None: - self.set_vector_overlay_color(color) - if colormap is not None: - self.set_vector_overlay_colormap(colormap, bias, contrast) - self.apply_vector_overlay() - - def clear_vector_overlay(self): - """Clear the vector overlay configuration.""" - self.call_action("clearVectorOverlay", True) - - @validate(Boolean()) - def set_vector_overlay_visible(self, state): - """Set the vector overlay visibility. - - Parameters - ---------- - state : {0} - The desired visibility state. - """ - self.call_action("vectorOverlayConfig.setVisible", state) - - def show_vector_overlay(self): - """Show the contours.""" - self.set_vector_overlay_visible(True) - - def hide_vector_overlay(self): - """Hide the contours.""" - self.set_vector_overlay_visible(False) - # HISTOGRAM @validate(Boolean()) diff --git a/carta/vector_overlay.py b/carta/vector_overlay.py new file mode 100644 index 0000000..becd38e --- /dev/null +++ b/carta/vector_overlay.py @@ -0,0 +1,254 @@ +"""This module contains functionality for interacting with the vector overlay of an image. The class in this module should not be instantiated directly. When an image object is created, a vector overlay object is automatically created as a property.""" + +from .util import logger, Macro, BasePathMixin +from .constants import Colormap, VectorOverlaySource, Auto +from .validation import validate, Number, Color, Constant, Boolean, NoneOr, all_optional, Union + + +class VectorOverlay(BasePathMixin): + """Utility object for collecting image functions related to the vector overlay. + + Parameters + ---------- + image : :obj:`carta.image.Image` object + The image associated with this vector overlay. + + Attributes + ---------- + image : :obj:`carta.image.Image` object + The image associated with this vector overlay. + session : :obj:`carta.session.Session` object + The session object associated with this vector overlay. + """ + + def __init__(self, image): + self.image = image + self.session = image.session + self._base_path = f"{image._base_path}.vectorOverlayConfig" + + @validate(*all_optional(Constant(VectorOverlaySource), Constant(VectorOverlaySource), Boolean(), Number(), Number(), Boolean(), Number(), Boolean(), Number(), Number())) + def configure(self, angular_source=None, intensity_source=None, pixel_averaging_enabled=None, pixel_averaging=None, fractional_intensity=None, threshold_enabled=None, threshold=None, debiasing=None, q_error=None, u_error=None): + """Configure vector overlay. + + All parameters are optional. For each option that is not provided, the value currently set in the frontend will be preserved. Initial frontend settings are noted below. + + We deduce some boolean options. For example, providing an explicit pixel averaging width with the **pixel_averaging** parameter will automatically enable pixel averaging unless **pixel_averaging_enabled** is also explicitly set to ``False``. To disable pixel averaging, explicitly set **pixel_averaging_enabled** to ``False``. + + Parameters + ---------- + angular_source : {0} + The angular source. This is initially set to computed PA if the image contains Stokes information, otherwise to the current image. + intensity_source : {1} + The intensity source. This is initially set to computed PI if the image contains Stokes information, otherwise to the current image. + pixel_averaging_enabled : {2} + Enable pixel averaging. This is initially enabled if the pixel averaging width is positive. + pixel_averaging : {3} + The pixel averaging width in pixels. The initial value can be configured in the frontend preferences (the default is ``4``). + fractional_intensity : {4} + Enable fractional polarization intensity. The initial value can be configured in the frontend preferences. By default this is disabled and the absolute polarization intensity is used. + threshold_enabled : {5} + Enable threshold. Initially the threshold is disabled. + threshold : {6} + The threshold in Jy/pixels. The initial value is zero. + debiasing : {7} + Enable debiasing. This is initially disabled. + q_error : {8} + The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. Initially set to zero. + u_error : {9} + The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. Initially set to zero. + """ + + # Avoid doing a lot of needless work for a no-op + if any(name != "self" and arg is not None for name, arg in locals().items()): + if pixel_averaging is not None and pixel_averaging_enabled is None: + pixel_averaging_enabled = True + if threshold is not None and threshold_enabled is None: + threshold_enabled = True + if q_error is not None and u_error is not None and debiasing is None: + debiasing = True + + if (q_error is not None and u_error is None) or (q_error is None and u_error is not None): + debiasing = False + logger.warning("The Stokes Q error and Stokes U error must both be set to enable debiasing.") + + args = [] + + for value, attr_name in ( + (angular_source, "angularSource"), + (intensity_source, "intensitySource"), + (pixel_averaging_enabled, "pixelAveragingEnabled"), + (pixel_averaging, "pixelAveraging"), + (fractional_intensity, "fractionalIntensity"), + (threshold_enabled, "thresholdEnabled"), + (threshold, "threshold"), + (debiasing, "debiasing"), + (q_error, "qError"), + (u_error, "uError"), + ): + if value is None: + args.append(self.macro("", attr_name)) + else: + args.append(value) + + self.call_action("setVectorOverlayConfiguration", *args) + + @validate(*all_optional(Number(), Union(Number(), Constant(Auto)), Union(Number(), Constant(Auto)), Number(), Number(), Number())) + def set_style(self, thickness=None, intensity_min=None, intensity_max=None, length_min=None, length_max=None, rotation_offset=None): + """Set the styling (line thickness, intensity range, line length range, rotation offset) of vector overlay. + + Parameters + ---------- + thickness : {0} + The line thickness in pixels. The default is ``1``. + intensity_min : {1} + The minimum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. + intensity_max : {2} + The maximum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. + length_min : {3} + The minimum value of line length in pixels. The defauly is ``0``. + length_max : {4} + The maximum value of line length in pixels. The default is ``20``. + rotation_offset : {5} + The rotation offset in degrees. The default is ``0``. + """ + if thickness is not None: + self.call_action("setThickness", thickness) + + if intensity_min is not None or intensity_max is not None: + if intensity_min is None: + intensity_min = self.macro("", "intensityMin") + elif intensity_min is Auto.AUTO: + intensity_min = Macro.UNDEFINED + + if intensity_max is None: + intensity_max = self.macro("", "intensityMax") + elif intensity_max is Auto.AUTO: + intensity_max = Macro.UNDEFINED + + self.call_action("setIntensityRange", intensity_min, intensity_max) + + if length_min is not None and length_max is not None: + self.call_action("setLengthRange", length_min, length_max) + + if rotation_offset is not None: + self.call_action("setRotationOffset", rotation_offset) + + @validate(Color()) + def set_color(self, color): + """Set the vector overlay color. + + This automatically disables use of the vector overlay colormap. + + Parameters + ---------- + color : {0} + The color. The default value is ``#238551`` (a shade of green). + """ + self.call_action("setColor", color) + self.call_action("setColormapEnabled", False) + + @validate(Constant(Colormap), NoneOr(Number()), NoneOr(Number())) + def set_colormap(self, colormap, bias=None, contrast=None): + """Set the vector overlay colormap. + + This automatically enables use of the vector overlay colormap. + + Parameters + ---------- + colormap : {0} + The colormap. The default is :obj:`carta.constants.Colormap.VIRIDIS`. + bias : {1} + The colormap bias. Set to ``0`` by default. + contrast : {2} + The colormap contrast. Set to ``1`` by default + """ + self.call_action("setColormap", colormap) + self.call_action("setColormapEnabled", True) + if bias is not None: + self.call_action("setColormapBias", bias) + if contrast is not None: + self.call_action("setColormapContrast", contrast) + + def apply(self): + """Apply the vector overlay configuration.""" + self.image.call_action("applyVectorOverlay") + + @validate(*all_optional(*configure.VARGS, *set_style.VARGS, *set_color.VARGS, *set_colormap.VARGS)) + def plot(self, angular_source=None, intensity_source=None, pixel_averaging_enabled=None, pixel_averaging=None, fractional_intensity=None, threshold_enabled=None, threshold=None, debiasing=None, q_error=None, u_error=None, thickness=None, intensity_min=None, intensity_max=None, length_min=None, length_max=None, rotation_offset=None, color=None, colormap=None, bias=None, contrast=None): + """Set the vector overlay configuration, styling and color or colormap; and apply vector overlay; in a single step. + + If both a color and a colormap are provided, the colormap will be visible. + + Parameters + ---------- + angular_source : {0} + The angular source. This is initially set to computed PA if the image contains Stokes information, otherwise to the current image. + intensity_source : {1} + The intensity source. This is initially set to computed PI if the image contains Stokes information, otherwise to the current image. + pixel_averaging_enabled : {2} + Enable pixel averaging. This is initially enabled if the pixel averaging width is positive. + pixel_averaging : {3} + The pixel averaging width in pixels. The initial value can be configured in the frontend preferences (the default is ``4``). + fractional_intensity : {4} + Enable fractional polarization intensity. The initial value can be configured in the frontend preferences. By default this is disabled and the absolute polarization intensity is used. + threshold_enabled : {5} + Enable threshold. Initially the threshold is disabled. + threshold : {6} + The threshold in Jy/pixels. The initial value is zero. + debiasing : {7} + Enable debiasing. This is initially disabled. + q_error : {8} + The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. Initially set to zero. + u_error : {9} + The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. Initially set to zero. + thickness : {10} + The line thickness in pixels. The default is ``1``. + intensity_min : {11} + The minimum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. + intensity_max : {12} + The maximum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. + length_min : {13} + The minimum value of line length in pixels. Defauly is ``0``. + length_max : {14} + The maximum value of line length in pixels. The default is ``20``. + rotation_offset : {15} + The rotation offset in degrees. The default is ``0``. + color : {16} + The color. The default value is ``#238551`` (a shade of green). + colormap : {17} + The colormap. The default is :obj:`carta.constants.Colormap.VIRIDIS`. + bias : {18} + The colormap bias. Set to ``0`` by default. + contrast : {19} + The colormap contrast. Set to ``1`` by default + """ + self.configure(angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) + self.set_style(thickness, intensity_min, intensity_max, length_min, length_max, rotation_offset) + if color is not None: + self.set_color(color) + if colormap is not None: + self.set_colormap(colormap, bias, contrast) + self.apply() + + def clear(self): + """Clear the vector overlay configuration.""" + self.image.call_action("clearVectorOverlay", True) + + @validate(Boolean()) + def set_visible(self, state): + """Set the vector overlay visibility. + + Parameters + ---------- + state : {0} + The desired visibility state. + """ + self.call_action("setVisible", state) + + def show(self): + """Show the contours.""" + self.set_visible(True) + + def hide(self): + """Hide the contours.""" + self.set_visible(False) diff --git a/tests/test_image.py b/tests/test_image.py index 79e4392..141cf6d 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -3,8 +3,8 @@ from carta.session import Session from carta.image import Image -from carta.util import CartaValidationFailed, Macro -from carta.constants import NumberFormat as NF, SpatialAxis as SA, VectorOverlaySource as VOS, Auto +from carta.util import CartaValidationFailed +from carta.constants import NumberFormat as NF, SpatialAxis as SA # FIXTURES @@ -246,75 +246,3 @@ def test_zoom_to_size_invalid(image, mock_property, axis, val, wcs, error_contai with pytest.raises(Exception) as e: image.zoom_to_size(val, axis) assert error_contains in str(e.value) - -# VECTOR OVERLAY - - -@pytest.mark.parametrize("args,kwargs,expected_args", [ - # Nothing - ((), {}, None), - # Everything - ((VOS.CURRENT, VOS.CURRENT, True, 1, 2, True, 3, True, 4, 5), {}, (VOS.CURRENT, VOS.CURRENT, True, 1, 2, True, 3, True, 4, 5)), - # Deduce pixel averaging flag - ((), {"pixel_averaging": 1}, - ("M(angularSource)", "M(intensitySource)", True, 1, "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", "M(debiasing)", "M(qError)", "M(uError)")), - # Don't deduce pixel averaging flag - ((), {"pixel_averaging": 1, "pixel_averaging_enabled": False}, - ("M(angularSource)", "M(intensitySource)", False, 1, "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", "M(debiasing)", "M(qError)", "M(uError)")), - # Deduce threshold flag - ((), {"threshold": 2}, - ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", True, 2, "M(debiasing)", "M(qError)", "M(uError)")), - # Don't deduce threshold flag - ((), {"threshold": 2, "threshold_enabled": False}, - ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", False, 2, "M(debiasing)", "M(qError)", "M(uError)")), - # Deduce debiasing flag - ((), {"q_error": 3, "u_error": 4}, - ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", True, 3, 4)), - # Don'teduce debiasing flag - ((), {"q_error": 3, "u_error": 4, "debiasing": False}, - ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", False, 3, 4)), - # Disable debiasing (no q_error) - ((), {"u_error": 4, "debiasing": True}, - ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", False, "M(qError)", 4)), - # Disable debiasing (no u_error) - ((), {"q_error": 3, "debiasing": True}, - ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", False, 3, "M(uError)")), -]) -def test_configure_vector_overlay(image, mock_call_action, mock_method, args, kwargs, expected_args): - mock_method("macro", lambda _, v: f"M({v})") - image.configure_vector_overlay(*args, **kwargs) - if expected_args is None: - mock_call_action.assert_not_called() - else: - mock_call_action.assert_called_with("vectorOverlayConfig.setVectorOverlayConfiguration", *expected_args) - - -@pytest.mark.parametrize("args,kwargs,expected_calls", [ - # Nothing - ((), {}, ()), - # Everything - ((1, 2, 3, 4, 5, 6), {}, ( - ("vectorOverlayConfig.setThickness", 1), - ("vectorOverlayConfig.setIntensityRange", 2, 3), - ("vectorOverlayConfig.setLengthRange", 4, 5), - ("vectorOverlayConfig.setRotationOffset", 6), - )), - # No intensity min; auto intensity max - ((), {"intensity_max": Auto.AUTO}, (("vectorOverlayConfig.setIntensityRange", "M(intensityMin)", Macro.UNDEFINED),)), - # Auto intensity min; no intensity max - ((), {"intensity_min": Auto.AUTO}, (("vectorOverlayConfig.setIntensityRange", Macro.UNDEFINED, "M(intensityMax)"),)), -]) -def test_set_vector_overlay_style(mocker, image, mock_call_action, mock_method, args, kwargs, expected_calls): - mock_method("macro", lambda _, v: f"M({v})") - image.set_vector_overlay_style(*args, **kwargs) - mock_call_action.assert_has_calls([mocker.call(*call) for call in expected_calls]) - - -# TODO test_set_vector_overlay_color -# TODO test_set_vector_overlay_colormap -# TODO test_apply_vector_overlay -# TODO test_plot_vector_overlay -# TODO test_clear_vector_overlay -# TODO test_set_vector_overlay_visible -# TODO test_show_vector_overlay -# TODO test_hide_vector_overlay diff --git a/tests/test_vector_overlay.py b/tests/test_vector_overlay.py new file mode 100644 index 0000000..2074736 --- /dev/null +++ b/tests/test_vector_overlay.py @@ -0,0 +1,134 @@ +import pytest + +from carta.session import Session +from carta.image import Image +from carta.vector_overlay import VectorOverlay +from carta.util import Macro +from carta.constants import VectorOverlaySource as VOS, Auto + + +@pytest.fixture +def session(): + """Return a session object. + + The session's protocol is set to None, so any tests that use this must also mock the session's call_action and/or higher-level functions which call it. + """ + return Session(0, None) + + +@pytest.fixture +def image(session): + """Return an image object which uses the session fixture. + """ + return Image(session, 0) + + +@pytest.fixture +def vector_overlay(image): + """Return a vector overlay object which uses the image fixture. + """ + return VectorOverlay(image) + + +@pytest.fixture +def mock_get_value(vector_overlay, mocker): + """Return a mock for vector overlay's get_value.""" + return mocker.patch.object(vector_overlay, "get_value") + + +@pytest.fixture +def mock_call_action(vector_overlay, mocker): + """Return a mock for vector overlay's call_action.""" + return mocker.patch.object(vector_overlay, "call_action") + + +@pytest.fixture +def mock_image_call_action(image, mocker): + """Return a mock for image's call_action.""" + return mocker.patch.object(image, "call_action") + + +@pytest.fixture +def mock_property(mocker): + """Return a helper function to mock the value of a decorated vector overlay property using a simple syntax.""" + def func(property_name, mock_value): + return mocker.patch(f"carta.vector_overlay.VectorOverlay.{property_name}", new_callable=mocker.PropertyMock, return_value=mock_value) + return func + + +@pytest.fixture +def mock_method(vector_overlay, mocker): + """Return a helper function to mock the return value(s) of an vector overlay method using a simple syntax.""" + def func(method_name, return_values): + return mocker.patch.object(vector_overlay, method_name, side_effect=return_values) + return func + + +@pytest.mark.parametrize("args,kwargs,expected_args", [ + # Nothing + ((), {}, None), + # Everything + ((VOS.CURRENT, VOS.CURRENT, True, 1, 2, True, 3, True, 4, 5), {}, (VOS.CURRENT, VOS.CURRENT, True, 1, 2, True, 3, True, 4, 5)), + # Deduce pixel averaging flag + ((), {"pixel_averaging": 1}, + ("M(angularSource)", "M(intensitySource)", True, 1, "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", "M(debiasing)", "M(qError)", "M(uError)")), + # Don't deduce pixel averaging flag + ((), {"pixel_averaging": 1, "pixel_averaging_enabled": False}, + ("M(angularSource)", "M(intensitySource)", False, 1, "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", "M(debiasing)", "M(qError)", "M(uError)")), + # Deduce threshold flag + ((), {"threshold": 2}, + ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", True, 2, "M(debiasing)", "M(qError)", "M(uError)")), + # Don't deduce threshold flag + ((), {"threshold": 2, "threshold_enabled": False}, + ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", False, 2, "M(debiasing)", "M(qError)", "M(uError)")), + # Deduce debiasing flag + ((), {"q_error": 3, "u_error": 4}, + ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", True, 3, 4)), + # Don'teduce debiasing flag + ((), {"q_error": 3, "u_error": 4, "debiasing": False}, + ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", False, 3, 4)), + # Disable debiasing (no q_error) + ((), {"u_error": 4, "debiasing": True}, + ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", False, "M(qError)", 4)), + # Disable debiasing (no u_error) + ((), {"q_error": 3, "debiasing": True}, + ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", False, 3, "M(uError)")), +]) +def test_configure(vector_overlay, mock_call_action, mock_method, args, kwargs, expected_args): + mock_method("macro", lambda _, v: f"M({v})") + vector_overlay.configure(*args, **kwargs) + if expected_args is None: + mock_call_action.assert_not_called() + else: + mock_call_action.assert_called_with("setVectorOverlayConfiguration", *expected_args) + + +@pytest.mark.parametrize("args,kwargs,expected_calls", [ + # Nothing + ((), {}, ()), + # Everything + ((1, 2, 3, 4, 5, 6), {}, ( + ("setThickness", 1), + ("setIntensityRange", 2, 3), + ("setLengthRange", 4, 5), + ("setRotationOffset", 6), + )), + # No intensity min; auto intensity max + ((), {"intensity_max": Auto.AUTO}, (("setIntensityRange", "M(intensityMin)", Macro.UNDEFINED),)), + # Auto intensity min; no intensity max + ((), {"intensity_min": Auto.AUTO}, (("setIntensityRange", Macro.UNDEFINED, "M(intensityMax)"),)), +]) +def test_set_style(mocker, vector_overlay, mock_call_action, mock_method, args, kwargs, expected_calls): + mock_method("macro", lambda _, v: f"M({v})") + vector_overlay.set_style(*args, **kwargs) + mock_call_action.assert_has_calls([mocker.call(*call) for call in expected_calls]) + + +# TODO test_set_color +# TODO test_set_colormap +# TODO test_apply +# TODO test_plot +# TODO test_clear +# TODO test_set_visible +# TODO test_show +# TODO test_hide From bb7223c83790880d5b753e478dc337f153a5bb6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrianna=20Pi=C5=84ska?= Date: Wed, 25 Oct 2023 21:28:25 +0200 Subject: [PATCH 35/40] Refactored plot function to avoid unnecessary calls. Completed vector overlay tests. --- carta/vector_overlay.py | 76 +++++++++++++++++++++--------------- tests/test_vector_overlay.py | 75 ++++++++++++++++++++++++++++++----- 2 files changed, 110 insertions(+), 41 deletions(-) diff --git a/carta/vector_overlay.py b/carta/vector_overlay.py index becd38e..dc30064 100644 --- a/carta/vector_overlay.py +++ b/carta/vector_overlay.py @@ -2,7 +2,7 @@ from .util import logger, Macro, BasePathMixin from .constants import Colormap, VectorOverlaySource, Auto -from .validation import validate, Number, Color, Constant, Boolean, NoneOr, all_optional, Union +from .validation import validate, Number, Color, Constant, Boolean, all_optional, Union class VectorOverlay(BasePathMixin): @@ -99,17 +99,17 @@ def set_style(self, thickness=None, intensity_min=None, intensity_max=None, leng Parameters ---------- thickness : {0} - The line thickness in pixels. The default is ``1``. + The line thickness in pixels. The initial value is ``1``. intensity_min : {1} The minimum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. intensity_max : {2} The maximum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. length_min : {3} - The minimum value of line length in pixels. The defauly is ``0``. + The minimum value of line length in pixels. The initial value is ``0``. length_max : {4} - The maximum value of line length in pixels. The default is ``20``. + The maximum value of line length in pixels. The initial value is ``20``. rotation_offset : {5} - The rotation offset in degrees. The default is ``0``. + The rotation offset in degrees. The initial value is ``0``. """ if thickness is not None: self.call_action("setThickness", thickness) @@ -142,28 +142,27 @@ def set_color(self, color): Parameters ---------- color : {0} - The color. The default value is ``#238551`` (a shade of green). + The color. The initial value is ``#238551`` (a shade of green). """ self.call_action("setColor", color) self.call_action("setColormapEnabled", False) - @validate(Constant(Colormap), NoneOr(Number()), NoneOr(Number())) - def set_colormap(self, colormap, bias=None, contrast=None): - """Set the vector overlay colormap. - - This automatically enables use of the vector overlay colormap. + @validate(*all_optional(Constant(Colormap), Number(-1, 1), Number(0, 2))) + def set_colormap(self, colormap=None, bias=None, contrast=None): + """Set the vector overlay colormap and/or the colormap options. Parameters ---------- colormap : {0} - The colormap. The default is :obj:`carta.constants.Colormap.VIRIDIS`. + The colormap. The initial value is :obj:`carta.constants.Colormap.VIRIDIS`. If this parameter is set, the overlay colormap is automatically enabled. bias : {1} - The colormap bias. Set to ``0`` by default. + The colormap bias. The initial value is ``0``. contrast : {2} - The colormap contrast. Set to ``1`` by default + The colormap contrast. The initial value is ``1``. """ - self.call_action("setColormap", colormap) - self.call_action("setColormapEnabled", True) + if colormap is not None: + self.call_action("setColormap", colormap) + self.call_action("setColormapEnabled", True) if bias is not None: self.call_action("setColormapBias", bias) if contrast is not None: @@ -177,7 +176,7 @@ def apply(self): def plot(self, angular_source=None, intensity_source=None, pixel_averaging_enabled=None, pixel_averaging=None, fractional_intensity=None, threshold_enabled=None, threshold=None, debiasing=None, q_error=None, u_error=None, thickness=None, intensity_min=None, intensity_max=None, length_min=None, length_max=None, rotation_offset=None, color=None, colormap=None, bias=None, contrast=None): """Set the vector overlay configuration, styling and color or colormap; and apply vector overlay; in a single step. - If both a color and a colormap are provided, the colormap will be visible. + If both a color and a colormap are provided, the colormap will be enabled. Parameters ---------- @@ -202,33 +201,48 @@ def plot(self, angular_source=None, intensity_source=None, pixel_averaging_enabl u_error : {9} The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. Initially set to zero. thickness : {10} - The line thickness in pixels. The default is ``1``. + The line thickness in pixels. The initial value is ``1``. intensity_min : {11} The minimum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. intensity_max : {12} The maximum value of intensity in Jy/pixel. Use :obj:`carta.constants.Auto.AUTO` to clear the custom value and calculate it automatically. length_min : {13} - The minimum value of line length in pixels. Defauly is ``0``. + The minimum value of line length in pixels. The initial value is ``0``. length_max : {14} - The maximum value of line length in pixels. The default is ``20``. + The maximum value of line length in pixels. The initial value is ``20``. rotation_offset : {15} - The rotation offset in degrees. The default is ``0``. + The rotation offset in degrees. The initial value is ``0``. color : {16} - The color. The default value is ``#238551`` (a shade of green). + The color. The initial value value is ``#238551`` (a shade of green). colormap : {17} - The colormap. The default is :obj:`carta.constants.Colormap.VIRIDIS`. + The colormap. The initial value is :obj:`carta.constants.Colormap.VIRIDIS`. bias : {18} - The colormap bias. Set to ``0`` by default. + The colormap bias. The initial value is ``0``. contrast : {19} - The colormap contrast. Set to ``1`` by default + The colormap contrast. The initial value is ``1``. """ - self.configure(angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) - self.set_style(thickness, intensity_min, intensity_max, length_min, length_max, rotation_offset) + changes_made = False + + configure_args = (angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error) + if any(v is not None for v in configure_args): + self.configure(*configure_args) + changes_made = True + + set_style_args = (thickness, intensity_min, intensity_max, length_min, length_max, rotation_offset) + if any(v is not None for v in set_style_args): + self.set_style(*set_style_args) + changes_made = True + if color is not None: self.set_color(color) - if colormap is not None: + changes_made = True + + if colormap is not None or bias is not None or contrast is not None: self.set_colormap(colormap, bias, contrast) - self.apply() + changes_made = True + + if changes_made: + self.apply() def clear(self): """Clear the vector overlay configuration.""" @@ -246,9 +260,9 @@ def set_visible(self, state): self.call_action("setVisible", state) def show(self): - """Show the contours.""" + """Show the vector overlay.""" self.set_visible(True) def hide(self): - """Hide the contours.""" + """Hide the vector overlay.""" self.set_visible(False) diff --git a/tests/test_vector_overlay.py b/tests/test_vector_overlay.py index 2074736..8683b84 100644 --- a/tests/test_vector_overlay.py +++ b/tests/test_vector_overlay.py @@ -4,7 +4,7 @@ from carta.image import Image from carta.vector_overlay import VectorOverlay from carta.util import Macro -from carta.constants import VectorOverlaySource as VOS, Auto +from carta.constants import VectorOverlaySource as VOS, Auto, Colormap as CM @pytest.fixture @@ -84,7 +84,7 @@ def func(method_name, return_values): # Deduce debiasing flag ((), {"q_error": 3, "u_error": 4}, ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", True, 3, 4)), - # Don'teduce debiasing flag + # Don't deduce debiasing flag ((), {"q_error": 3, "u_error": 4, "debiasing": False}, ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", False, 3, 4)), # Disable debiasing (no q_error) @@ -124,11 +124,66 @@ def test_set_style(mocker, vector_overlay, mock_call_action, mock_method, args, mock_call_action.assert_has_calls([mocker.call(*call) for call in expected_calls]) -# TODO test_set_color -# TODO test_set_colormap -# TODO test_apply -# TODO test_plot -# TODO test_clear -# TODO test_set_visible -# TODO test_show -# TODO test_hide +def test_set_color(mocker, vector_overlay, mock_call_action): + vector_overlay.set_color("blue") + mock_call_action.assert_has_calls([ + mocker.call("setColor", "blue"), + mocker.call("setColormapEnabled", False), + ]) + + +@pytest.mark.parametrize("args,kwargs,expected_calls", [ + ([], {}, []), + ([CM.VIRIDIS, 0.5, 1.5], {}, [("setColormap", CM.VIRIDIS), ("setColormapEnabled", True), ("setColormapBias", 0.5), ("setColormapContrast", 1.5)]), + ([CM.VIRIDIS], {}, [("setColormap", CM.VIRIDIS), ("setColormapEnabled", True)]), + ([], {"bias": 0.5}, [("setColormapBias", 0.5)]), + ([], {"contrast": 1.5}, [("setColormapContrast", 1.5)]), +]) +def test_set_colormap(mocker, vector_overlay, mock_call_action, args, kwargs, expected_calls): + vector_overlay.set_colormap(*args, **kwargs) + mock_call_action.assert_has_calls([mocker.call(*call) for call in expected_calls]) + + +def test_apply(vector_overlay, mock_image_call_action): + vector_overlay.apply() + mock_image_call_action.assert_called_with("applyVectorOverlay") + + +def test_clear(vector_overlay, mock_image_call_action): + vector_overlay.clear() + mock_image_call_action.assert_called_with("clearVectorOverlay", True) + + +@pytest.mark.parametrize("args,kwargs,expected_calls", [ + ([], {}, []), + ([VOS.CURRENT, VOS.CURRENT, True, 1, 2, True, 3, True, 4, 5, 1, 2, 3, 4, 5, 6, "blue", CM.VIRIDIS, 0.5, 1.5], {}, [("configure", VOS.CURRENT, VOS.CURRENT, True, 1, 2, True, 3, True, 4, 5), ("set_style", 1, 2, 3, 4, 5, 6), ("set_color", "blue"), ("set_colormap", CM.VIRIDIS, 0.5, 1.5), ("apply",)]), + ([], {"pixel_averaging": 1, "thickness": 2, "color": "blue", "bias": 0.5}, [("configure", None, None, None, 1, None, None, None, None, None, None), ("set_style", 2, None, None, None, None, None), ("set_color", "blue"), ("set_colormap", None, 0.5, None), ("apply",)]), + ([], {"thickness": 2}, [("set_style", 2, None, None, None, None, None), ("apply",)]), +]) +def test_plot(vector_overlay, mock_method, args, kwargs, expected_calls): + mocks = {} + for method_name in ("configure", "set_style", "set_color", "set_colormap", "apply"): + mocks[method_name] = mock_method(method_name, None) + + vector_overlay.plot(*args, **kwargs) + + for method_name, *expected_args in expected_calls: + mocks[method_name].assert_called_with(*expected_args) + + +@pytest.mark.parametrize("state", [True, False]) +def test_set_visible(vector_overlay, mock_call_action, state): + vector_overlay.set_visible(state) + mock_call_action.assert_called_with("setVisible", state) + + +def test_show(vector_overlay, mock_method): + mock_set_visible = mock_method("set_visible", None) + vector_overlay.show() + mock_set_visible.assert_called_with(True) + + +def test_hide(vector_overlay, mock_method): + mock_set_visible = mock_method("set_visible", None) + vector_overlay.hide() + mock_set_visible.assert_called_with(False) From 6f1d8e06be977c5756bd29c349ae9a0f140eafe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrianna=20Pi=C5=84ska?= Date: Wed, 25 Oct 2023 21:35:43 +0200 Subject: [PATCH 36/40] Added check for subobjects on image --- tests/test_image.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_image.py b/tests/test_image.py index 141cf6d..223df06 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -133,6 +133,16 @@ def test_new(session, mock_session_call_action, mock_session_method, args, kwarg assert image_object.image_id == 123 +# SUBOBJECTS + + +@pytest.mark.parametrize("name,classname", [ + ("vectors", "VectorOverlay"), +]) +def test_subobjects(image, name, classname): + assert getattr(image, name).__class__.__name__ == classname + + # SIMPLE PROPERTIES TODO to be completed. @pytest.mark.parametrize("property_name,expected_path", [ From fc91eeeaae234574c705d43236f0c68e71e6cc54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrianna=20Pi=C5=84ska?= Date: Wed, 25 Oct 2023 21:39:44 +0200 Subject: [PATCH 37/40] Added missing module to docs. --- docs/source/carta.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/source/carta.rst b/docs/source/carta.rst index bda3ba5..6310a0c 100644 --- a/docs/source/carta.rst +++ b/docs/source/carta.rst @@ -88,3 +88,12 @@ carta.validation module :members: :undoc-members: :show-inheritance: + +carta.vector_overlay module +--------------------------- + +.. automodule:: carta.vector_overlay + :members: + :undoc-members: + :show-inheritance: + From 6515caba3b722e7fa879e5962e4d7dcb6df3e262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrianna=20Pi=C5=84ska?= Date: Fri, 27 Oct 2023 15:17:47 +0200 Subject: [PATCH 38/40] Renamed fixtures to make merge easier --- tests/test_image.py | 74 +++++++++++++------------- tests/test_session.py | 100 +++++++++++++++++------------------ tests/test_vector_overlay.py | 56 ++++++++++---------- 3 files changed, 115 insertions(+), 115 deletions(-) diff --git a/tests/test_image.py b/tests/test_image.py index 223df06..72a152e 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -27,25 +27,25 @@ def image(session): @pytest.fixture -def mock_get_value(image, mocker): +def get_value(image, mocker): """Return a mock for image's get_value.""" return mocker.patch.object(image, "get_value") @pytest.fixture -def mock_call_action(image, mocker): +def call_action(image, mocker): """Return a mock for image's call_action.""" return mocker.patch.object(image, "call_action") @pytest.fixture -def mock_session_call_action(session, mocker): +def session_call_action(session, mocker): """Return a mock for session's call_action.""" return mocker.patch.object(session, "call_action") @pytest.fixture -def mock_property(mocker): +def property_(mocker): """Return a helper function to mock the value of a decorated image property using a simple syntax.""" def func(property_name, mock_value): return mocker.patch(f"carta.image.Image.{property_name}", new_callable=mocker.PropertyMock, return_value=mock_value) @@ -53,7 +53,7 @@ def func(property_name, mock_value): @pytest.fixture -def mock_method(image, mocker): +def method(image, mocker): """Return a helper function to mock the return value(s) of an image method using a simple syntax.""" def func(method_name, return_values): return mocker.patch.object(image, method_name, side_effect=return_values) @@ -61,7 +61,7 @@ def func(method_name, return_values): @pytest.fixture -def mock_session_method(session, mocker): +def session_method(session, mocker): """Return a helper function to mock the return value(s) of a session method using a simple syntax.""" def func(method_name, return_values): return mocker.patch.object(session, method_name, side_effect=return_values) @@ -120,13 +120,13 @@ def test_image_properties_have_docstrings(member): (["subdir", "image.fits", "", True, False], {"make_active": False}, ["appendFile", "/my_data/subdir", "image.fits", "", False, False, False]), ]) -def test_new(session, mock_session_call_action, mock_session_method, args, kwargs, expected_params): - mock_session_method("pwd", ["/my_data"]) - mock_session_call_action.side_effect = [123] +def test_new(session, session_call_action, session_method, args, kwargs, expected_params): + session_method("pwd", ["/my_data"]) + session_call_action.side_effect = [123] image_object = Image.new(session, *args, **kwargs) - mock_session_call_action.assert_called_with(*expected_params, return_path='frameInfo.fileId') + session_call_action.assert_called_with(*expected_params, return_path='frameInfo.fileId') assert type(image_object) is Image assert image_object.session == session @@ -150,24 +150,24 @@ def test_subobjects(image, name, classname): ("directory", "frameInfo.directory"), ("width", "frameInfo.fileInfoExtended.width"), ]) -def test_simple_properties(image, property_name, expected_path, mock_get_value): +def test_simple_properties(image, property_name, expected_path, get_value): getattr(image, property_name) - mock_get_value.assert_called_with(expected_path) + get_value.assert_called_with(expected_path) # TODO tests for all existing functions to be filled in -def test_make_active(image, mock_session_call_action): +def test_make_active(image, session_call_action): image.make_active() - mock_session_call_action.assert_called_with("setActiveFrameById", 0) + session_call_action.assert_called_with("setActiveFrameById", 0) @pytest.mark.parametrize("channel", [0, 10, 19]) -def test_set_channel_valid(image, channel, mock_call_action, mock_property): - mock_property("depth", 20) +def test_set_channel_valid(image, channel, call_action, property_): + property_("depth", 20) image.set_channel(channel) - mock_call_action.assert_called_with("setChannels", channel, image.macro("", "requiredStokes"), True) + call_action.assert_called_with("setChannels", channel, image.macro("", "requiredStokes"), True) @pytest.mark.parametrize("channel,error_contains", [ @@ -175,8 +175,8 @@ def test_set_channel_valid(image, channel, mock_call_action, mock_property): (1.5, "not an increment of 1"), (-3, "must be greater or equal"), ]) -def test_set_channel_invalid(image, channel, error_contains, mock_property): - mock_property("depth", 20) +def test_set_channel_invalid(image, channel, error_contains, property_): + property_("depth", 20) with pytest.raises(CartaValidationFailed) as e: image.set_channel(channel) @@ -185,13 +185,13 @@ def test_set_channel_invalid(image, channel, error_contains, mock_property): @pytest.mark.parametrize("x", [-30, 0, 10, 12.3, 30]) @pytest.mark.parametrize("y", [-30, 0, 10, 12.3, 30]) -def test_set_center_valid_pixels(image, mock_property, mock_call_action, x, y): +def test_set_center_valid_pixels(image, property_, call_action, x, y): # Currently we have no range validation, for consistency with WCS coordinates. - mock_property("width", 20) - mock_property("height", 20) + property_("width", 20) + property_("height", 20) image.set_center(x, y) - mock_call_action.assert_called_with("setCenter", x, y) + call_action.assert_called_with("setCenter", x, y) @pytest.mark.parametrize("x,y,x_fmt,y_fmt,x_norm,y_norm", [ @@ -202,12 +202,12 @@ def test_set_center_valid_pixels(image, mock_property, mock_call_action, x, y): ("12h34m56.789s", "5h34m56.789s", NF.HMS, NF.HMS, "12:34:56.789", "5:34:56.789"), ("12d34m56.789s", "12d34m56.789s", NF.DMS, NF.DMS, "12:34:56.789", "12:34:56.789"), ]) -def test_set_center_valid_wcs(image, mock_property, mock_session_method, mock_call_action, x, y, x_fmt, y_fmt, x_norm, y_norm): - mock_property("valid_wcs", True) - mock_session_method("number_format", [(x_fmt, y_fmt, None)]) +def test_set_center_valid_wcs(image, property_, session_method, call_action, x, y, x_fmt, y_fmt, x_norm, y_norm): + property_("valid_wcs", True) + session_method("number_format", [(x_fmt, y_fmt, None)]) image.set_center(x, y) - mock_call_action.assert_called_with("setCenterWcs", x_norm, y_norm) + call_action.assert_called_with("setCenterWcs", x_norm, y_norm) @pytest.mark.parametrize("x,y,wcs,x_fmt,y_fmt,error_contains", [ @@ -218,11 +218,11 @@ def test_set_center_valid_wcs(image, mock_property, mock_session_method, mock_ca (123, "123", True, NF.DEGREES, NF.DEGREES, "Cannot mix image and world coordinates"), ("123", 123, True, NF.DEGREES, NF.DEGREES, "Cannot mix image and world coordinates"), ]) -def test_set_center_invalid(image, mock_property, mock_session_method, mock_call_action, x, y, wcs, x_fmt, y_fmt, error_contains): - mock_property("width", 200) - mock_property("height", 200) - mock_property("valid_wcs", wcs) - mock_session_method("number_format", [(x_fmt, y_fmt, None)]) +def test_set_center_invalid(image, property_, session_method, call_action, x, y, wcs, x_fmt, y_fmt, error_contains): + property_("width", 200) + property_("height", 200) + property_("valid_wcs", wcs) + session_method("number_format", [(x_fmt, y_fmt, None)]) with pytest.raises(Exception) as e: image.set_center(x, y) @@ -239,10 +239,10 @@ def test_set_center_invalid(image, mock_property, mock_session_method, mock_call ("123deg", "zoomToSize{0}Wcs", "123deg"), ("123 deg", "zoomToSize{0}Wcs", "123deg"), ]) -def test_zoom_to_size(image, mock_property, mock_call_action, axis, val, action, norm): - mock_property("valid_wcs", True) +def test_zoom_to_size(image, property_, call_action, axis, val, action, norm): + property_("valid_wcs", True) image.zoom_to_size(val, axis) - mock_call_action.assert_called_with(action.format(axis.upper()), norm) + call_action.assert_called_with(action.format(axis.upper()), norm) @pytest.mark.parametrize("axis", [SA.X, SA.Y]) @@ -251,8 +251,8 @@ def test_zoom_to_size(image, mock_property, mock_call_action, axis, val, action, ("abc", True, "Invalid function parameter"), ("123arcsec", False, "does not contain valid WCS information"), ]) -def test_zoom_to_size_invalid(image, mock_property, axis, val, wcs, error_contains): - mock_property("valid_wcs", wcs) +def test_zoom_to_size_invalid(image, property_, axis, val, wcs, error_contains): + property_("valid_wcs", wcs) with pytest.raises(Exception) as e: image.zoom_to_size(val, axis) assert error_contains in str(e.value) diff --git a/tests/test_session.py b/tests/test_session.py index 824c434..8e7adcc 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -19,19 +19,19 @@ def session(): @pytest.fixture -def mock_get_value(session, mocker): +def get_value(session, mocker): """Return a mock for session's get_value.""" return mocker.patch.object(session, "get_value") @pytest.fixture -def mock_call_action(session, mocker): +def call_action(session, mocker): """Return a mock for session's call_action.""" return mocker.patch.object(session, "call_action") @pytest.fixture -def mock_property(mocker): +def property_(mocker): """Return a helper function to mock the value of a decorated session property using a simple syntax.""" def func(property_name, mock_value): return mocker.patch(f"carta.session.Session.{property_name}", new_callable=mocker.PropertyMock, return_value=mock_value) @@ -39,7 +39,7 @@ def func(property_name, mock_value): @pytest.fixture -def mock_method(session, mocker): +def method(session, mocker): """Return a helper function to mock the return value(s) of an session method using a simple syntax.""" def func(method_name, return_values): return mocker.patch.object(session, method_name, side_effect=return_values) @@ -82,32 +82,32 @@ def test_session_classmethods_have_docstrings(member): ("foo/..", "/current/dir"), ("foo/../bar", "/current/dir/bar"), ]) -def test_resolve_file_path(session, mock_method, path, expected_path): - mock_method("pwd", ["/current/dir"]) +def test_resolve_file_path(session, method, path, expected_path): + method("pwd", ["/current/dir"]) assert session.resolve_file_path(path) == expected_path -def test_pwd(session, mock_call_action, mock_get_value): - mock_get_value.side_effect = ["current/dir/"] +def test_pwd(session, call_action, get_value): + get_value.side_effect = ["current/dir/"] pwd = session.pwd() - mock_call_action.assert_called_with("fileBrowserStore.getFileList", Macro('fileBrowserStore', 'startingDirectory')) - mock_get_value.assert_called_with("fileBrowserStore.fileList.directory") + call_action.assert_called_with("fileBrowserStore.getFileList", Macro('fileBrowserStore', 'startingDirectory')) + get_value.assert_called_with("fileBrowserStore.fileList.directory") assert pwd == "/current/dir" -def test_ls(session, mock_method, mock_call_action, mock_get_value): - mock_method("pwd", ["/current/dir"]) - mock_get_value.side_effect = [{"files": [{"name": "foo.fits"}, {"name": "bar.fits"}], "subdirectories": [{"name": "baz"}]}] +def test_ls(session, method, call_action, get_value): + method("pwd", ["/current/dir"]) + get_value.side_effect = [{"files": [{"name": "foo.fits"}, {"name": "bar.fits"}], "subdirectories": [{"name": "baz"}]}] ls = session.ls() - mock_call_action.assert_called_with("fileBrowserStore.getFileList", "/current/dir") - mock_get_value.assert_called_with("fileBrowserStore.fileList") + call_action.assert_called_with("fileBrowserStore.getFileList", "/current/dir") + get_value.assert_called_with("fileBrowserStore.fileList") assert ls == ["bar.fits", "baz/", "foo.fits"] -def test_cd(session, mock_method, mock_call_action): - mock_method("resolve_file_path", ["/resolved/file/path"]) +def test_cd(session, method, call_action): + method("resolve_file_path", ["/resolved/file/path"]) session.cd("original/path") - mock_call_action.assert_called_with("fileBrowserStore.saveStartingDirectory", "/resolved/file/path") + call_action.assert_called_with("fileBrowserStore.saveStartingDirectory", "/resolved/file/path") # OPENING IMAGES @@ -179,8 +179,8 @@ def test_open_LEL_image(mocker, session, args, kwargs, expected_args, expected_k @pytest.mark.parametrize("append", [True, False]) -def test_open_images(mocker, session, mock_method, append): - mock_open_image = mock_method("open_image", ["1", "2", "3"]) +def test_open_images(mocker, session, method, append): + mock_open_image = method("open_image", ["1", "2", "3"]) images = session.open_images(["foo.fits", "bar.fits", "baz.fits"], append) mock_open_image.assert_has_calls([ mocker.call("foo.fits", append=append), @@ -202,14 +202,14 @@ def test_open_images(mocker, session, mock_method, append): (True, "appendConcatFile"), (False, "openConcatFile"), ]) -def test_open_hypercube_guess_polarization(mocker, session, mock_call_action, mock_method, paths, expected_args, append, expected_command): - mock_method("pwd", ["/current/dir"]) - mock_method("resolve_file_path", ["/resolved/path"] * 3) - mock_call_action.side_effect = [*expected_args[0], 123] +def test_open_hypercube_guess_polarization(mocker, session, call_action, method, paths, expected_args, append, expected_command): + method("pwd", ["/current/dir"]) + method("resolve_file_path", ["/resolved/path"] * 3) + call_action.side_effect = [*expected_args[0], 123] hypercube = session.open_hypercube(paths, append) - mock_call_action.assert_has_calls([ + call_action.assert_has_calls([ mocker.call("fileBrowserStore.getStokesFile", "/resolved/path", "foo.fits", ""), mocker.call("fileBrowserStore.getStokesFile", "/resolved/path", "bar.fits", ""), mocker.call("fileBrowserStore.getStokesFile", "/resolved/path", "baz.fits", ""), @@ -235,16 +235,16 @@ def test_open_hypercube_guess_polarization(mocker, session, mock_call_action, mo {"directory": "/resolved/path", "file": "bar.fits", "hdu": "", "polarizationType": 1}, ], "Duplicate polarizations deduced"), ]) -def test_open_hypercube_guess_polarization_bad(mocker, session, mock_call_action, mock_method, paths, expected_calls, mocked_side_effect, expected_error): - mock_method("pwd", ["/current/dir"]) - mock_method("resolve_file_path", ["/resolved/path"] * 3) - mock_call_action.side_effect = mocked_side_effect +def test_open_hypercube_guess_polarization_bad(mocker, session, call_action, method, paths, expected_calls, mocked_side_effect, expected_error): + method("pwd", ["/current/dir"]) + method("resolve_file_path", ["/resolved/path"] * 3) + call_action.side_effect = mocked_side_effect with pytest.raises(ValueError) as e: session.open_hypercube(paths) assert expected_error in str(e.value) - mock_call_action.assert_has_calls([mocker.call(*args) for args in expected_calls]) + call_action.assert_has_calls([mocker.call(*args) for args in expected_calls]) @pytest.mark.parametrize("paths,expected_args", [ @@ -259,14 +259,14 @@ def test_open_hypercube_guess_polarization_bad(mocker, session, mock_call_action (True, "appendConcatFile"), (False, "openConcatFile"), ]) -def test_open_hypercube_explicit_polarization(mocker, session, mock_call_action, mock_method, paths, expected_args, append, expected_command): - mock_method("pwd", ["/current/dir"]) - mock_method("resolve_file_path", ["/resolved/path"] * 3) - mock_call_action.side_effect = [123] +def test_open_hypercube_explicit_polarization(mocker, session, call_action, method, paths, expected_args, append, expected_command): + method("pwd", ["/current/dir"]) + method("resolve_file_path", ["/resolved/path"] * 3) + call_action.side_effect = [123] hypercube = session.open_hypercube(paths, append) - mock_call_action.assert_has_calls([ + call_action.assert_has_calls([ mocker.call(expected_command, *expected_args), ]) @@ -280,9 +280,9 @@ def test_open_hypercube_explicit_polarization(mocker, session, mock_call_action, (["foo.fits"], "at least 2"), ]) @pytest.mark.parametrize("append", [True, False]) -def test_open_hypercube_bad(mocker, session, mock_call_action, mock_method, paths, expected_error, append): - mock_method("pwd", ["/current/dir"]) - mock_method("resolve_file_path", ["/resolved/path"] * 3) +def test_open_hypercube_bad(mocker, session, call_action, method, paths, expected_error, append): + method("pwd", ["/current/dir"]) + method("resolve_file_path", ["/resolved/path"] * 3) with pytest.raises(Exception) as e: session.open_hypercube(paths, append) @@ -293,9 +293,9 @@ def test_open_hypercube_bad(mocker, session, mock_call_action, mock_method, path @pytest.mark.parametrize("system", CoordinateSystem) -def test_set_coordinate_system(session, mock_call_action, system): +def test_set_coordinate_system(session, call_action, system): session.set_coordinate_system(system) - mock_call_action.assert_called_with("overlayStore.global.setSystem", system) + call_action.assert_called_with("overlayStore.global.setSystem", system) def test_set_coordinate_system_invalid(session): @@ -304,18 +304,18 @@ def test_set_coordinate_system_invalid(session): assert "Invalid function parameter" in str(e.value) -def test_coordinate_system(session, mock_get_value): - mock_get_value.return_value = "AUTO" +def test_coordinate_system(session, get_value): + get_value.return_value = "AUTO" system = session.coordinate_system() - mock_get_value.assert_called_with("overlayStore.global.system") + get_value.assert_called_with("overlayStore.global.system") assert isinstance(system, CoordinateSystem) @pytest.mark.parametrize("x", NF) @pytest.mark.parametrize("y", NF) -def test_set_custom_number_format(mocker, session, mock_call_action, x, y): +def test_set_custom_number_format(mocker, session, call_action, x, y): session.set_custom_number_format(x, y) - mock_call_action.assert_has_calls([ + call_action.assert_has_calls([ mocker.call("overlayStore.numbers.setFormatX", x), mocker.call("overlayStore.numbers.setFormatY", y), mocker.call("overlayStore.numbers.setCustomFormat", True), @@ -333,15 +333,15 @@ def test_set_custom_number_format_invalid(session, x, y): assert "Invalid function parameter" in str(e.value) -def test_clear_custom_number_format(session, mock_call_action): +def test_clear_custom_number_format(session, call_action): session.clear_custom_number_format() - mock_call_action.assert_called_with("overlayStore.numbers.setCustomFormat", False) + call_action.assert_called_with("overlayStore.numbers.setCustomFormat", False) -def test_number_format(session, mock_get_value, mocker): - mock_get_value.side_effect = [NF.DEGREES, NF.DEGREES, False] +def test_number_format(session, get_value, mocker): + get_value.side_effect = [NF.DEGREES, NF.DEGREES, False] x, y, _ = session.number_format() - mock_get_value.assert_has_calls([ + get_value.assert_has_calls([ mocker.call("overlayStore.numbers.formatTypeX"), mocker.call("overlayStore.numbers.formatTypeY"), mocker.call("overlayStore.numbers.customFormat"), diff --git a/tests/test_vector_overlay.py b/tests/test_vector_overlay.py index 8683b84..24a8883 100644 --- a/tests/test_vector_overlay.py +++ b/tests/test_vector_overlay.py @@ -31,25 +31,25 @@ def vector_overlay(image): @pytest.fixture -def mock_get_value(vector_overlay, mocker): +def get_value(vector_overlay, mocker): """Return a mock for vector overlay's get_value.""" return mocker.patch.object(vector_overlay, "get_value") @pytest.fixture -def mock_call_action(vector_overlay, mocker): +def call_action(vector_overlay, mocker): """Return a mock for vector overlay's call_action.""" return mocker.patch.object(vector_overlay, "call_action") @pytest.fixture -def mock_image_call_action(image, mocker): +def image_call_action(image, mocker): """Return a mock for image's call_action.""" return mocker.patch.object(image, "call_action") @pytest.fixture -def mock_property(mocker): +def property_(mocker): """Return a helper function to mock the value of a decorated vector overlay property using a simple syntax.""" def func(property_name, mock_value): return mocker.patch(f"carta.vector_overlay.VectorOverlay.{property_name}", new_callable=mocker.PropertyMock, return_value=mock_value) @@ -57,7 +57,7 @@ def func(property_name, mock_value): @pytest.fixture -def mock_method(vector_overlay, mocker): +def method(vector_overlay, mocker): """Return a helper function to mock the return value(s) of an vector overlay method using a simple syntax.""" def func(method_name, return_values): return mocker.patch.object(vector_overlay, method_name, side_effect=return_values) @@ -94,13 +94,13 @@ def func(method_name, return_values): ((), {"q_error": 3, "debiasing": True}, ("M(angularSource)", "M(intensitySource)", "M(pixelAveragingEnabled)", "M(pixelAveraging)", "M(fractionalIntensity)", "M(thresholdEnabled)", "M(threshold)", False, 3, "M(uError)")), ]) -def test_configure(vector_overlay, mock_call_action, mock_method, args, kwargs, expected_args): - mock_method("macro", lambda _, v: f"M({v})") +def test_configure(vector_overlay, call_action, method, args, kwargs, expected_args): + method("macro", lambda _, v: f"M({v})") vector_overlay.configure(*args, **kwargs) if expected_args is None: - mock_call_action.assert_not_called() + call_action.assert_not_called() else: - mock_call_action.assert_called_with("setVectorOverlayConfiguration", *expected_args) + call_action.assert_called_with("setVectorOverlayConfiguration", *expected_args) @pytest.mark.parametrize("args,kwargs,expected_calls", [ @@ -118,15 +118,15 @@ def test_configure(vector_overlay, mock_call_action, mock_method, args, kwargs, # Auto intensity min; no intensity max ((), {"intensity_min": Auto.AUTO}, (("setIntensityRange", Macro.UNDEFINED, "M(intensityMax)"),)), ]) -def test_set_style(mocker, vector_overlay, mock_call_action, mock_method, args, kwargs, expected_calls): - mock_method("macro", lambda _, v: f"M({v})") +def test_set_style(mocker, vector_overlay, call_action, method, args, kwargs, expected_calls): + method("macro", lambda _, v: f"M({v})") vector_overlay.set_style(*args, **kwargs) - mock_call_action.assert_has_calls([mocker.call(*call) for call in expected_calls]) + call_action.assert_has_calls([mocker.call(*call) for call in expected_calls]) -def test_set_color(mocker, vector_overlay, mock_call_action): +def test_set_color(mocker, vector_overlay, call_action): vector_overlay.set_color("blue") - mock_call_action.assert_has_calls([ + call_action.assert_has_calls([ mocker.call("setColor", "blue"), mocker.call("setColormapEnabled", False), ]) @@ -139,19 +139,19 @@ def test_set_color(mocker, vector_overlay, mock_call_action): ([], {"bias": 0.5}, [("setColormapBias", 0.5)]), ([], {"contrast": 1.5}, [("setColormapContrast", 1.5)]), ]) -def test_set_colormap(mocker, vector_overlay, mock_call_action, args, kwargs, expected_calls): +def test_set_colormap(mocker, vector_overlay, call_action, args, kwargs, expected_calls): vector_overlay.set_colormap(*args, **kwargs) - mock_call_action.assert_has_calls([mocker.call(*call) for call in expected_calls]) + call_action.assert_has_calls([mocker.call(*call) for call in expected_calls]) -def test_apply(vector_overlay, mock_image_call_action): +def test_apply(vector_overlay, image_call_action): vector_overlay.apply() - mock_image_call_action.assert_called_with("applyVectorOverlay") + image_call_action.assert_called_with("applyVectorOverlay") -def test_clear(vector_overlay, mock_image_call_action): +def test_clear(vector_overlay, image_call_action): vector_overlay.clear() - mock_image_call_action.assert_called_with("clearVectorOverlay", True) + image_call_action.assert_called_with("clearVectorOverlay", True) @pytest.mark.parametrize("args,kwargs,expected_calls", [ @@ -160,10 +160,10 @@ def test_clear(vector_overlay, mock_image_call_action): ([], {"pixel_averaging": 1, "thickness": 2, "color": "blue", "bias": 0.5}, [("configure", None, None, None, 1, None, None, None, None, None, None), ("set_style", 2, None, None, None, None, None), ("set_color", "blue"), ("set_colormap", None, 0.5, None), ("apply",)]), ([], {"thickness": 2}, [("set_style", 2, None, None, None, None, None), ("apply",)]), ]) -def test_plot(vector_overlay, mock_method, args, kwargs, expected_calls): +def test_plot(vector_overlay, method, args, kwargs, expected_calls): mocks = {} for method_name in ("configure", "set_style", "set_color", "set_colormap", "apply"): - mocks[method_name] = mock_method(method_name, None) + mocks[method_name] = method(method_name, None) vector_overlay.plot(*args, **kwargs) @@ -172,18 +172,18 @@ def test_plot(vector_overlay, mock_method, args, kwargs, expected_calls): @pytest.mark.parametrize("state", [True, False]) -def test_set_visible(vector_overlay, mock_call_action, state): +def test_set_visible(vector_overlay, call_action, state): vector_overlay.set_visible(state) - mock_call_action.assert_called_with("setVisible", state) + call_action.assert_called_with("setVisible", state) -def test_show(vector_overlay, mock_method): - mock_set_visible = mock_method("set_visible", None) +def test_show(vector_overlay, method): + mock_set_visible = method("set_visible", None) vector_overlay.show() mock_set_visible.assert_called_with(True) -def test_hide(vector_overlay, mock_method): - mock_set_visible = mock_method("set_visible", None) +def test_hide(vector_overlay, method): + mock_set_visible = method("set_visible", None) vector_overlay.hide() mock_set_visible.assert_called_with(False) From 1099738d9a23c5d89a86774a1c3a5fea8f034de1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrianna=20Pi=C5=84ska?= Date: Fri, 27 Oct 2023 15:29:25 +0200 Subject: [PATCH 39/40] Remove shared fixtures from vector overlay test file --- tests/test_vector_overlay.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/tests/test_vector_overlay.py b/tests/test_vector_overlay.py index 24a8883..b42c324 100644 --- a/tests/test_vector_overlay.py +++ b/tests/test_vector_overlay.py @@ -1,28 +1,10 @@ import pytest -from carta.session import Session -from carta.image import Image from carta.vector_overlay import VectorOverlay from carta.util import Macro from carta.constants import VectorOverlaySource as VOS, Auto, Colormap as CM -@pytest.fixture -def session(): - """Return a session object. - - The session's protocol is set to None, so any tests that use this must also mock the session's call_action and/or higher-level functions which call it. - """ - return Session(0, None) - - -@pytest.fixture -def image(session): - """Return an image object which uses the session fixture. - """ - return Image(session, 0) - - @pytest.fixture def vector_overlay(image): """Return a vector overlay object which uses the image fixture. From ce1d341137bc3ec3d75fad870cd148265ba0146a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrianna=20Pi=C5=84ska?= Date: Fri, 27 Oct 2023 16:17:17 +0200 Subject: [PATCH 40/40] refactored fixtures --- tests/test_vector_overlay.py | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/tests/test_vector_overlay.py b/tests/test_vector_overlay.py index b42c324..5fa4a86 100644 --- a/tests/test_vector_overlay.py +++ b/tests/test_vector_overlay.py @@ -7,43 +7,32 @@ @pytest.fixture def vector_overlay(image): - """Return a vector overlay object which uses the image fixture. - """ return VectorOverlay(image) @pytest.fixture -def get_value(vector_overlay, mocker): - """Return a mock for vector overlay's get_value.""" - return mocker.patch.object(vector_overlay, "get_value") +def get_value(vector_overlay, mock_get_value): + return mock_get_value(vector_overlay) @pytest.fixture -def call_action(vector_overlay, mocker): - """Return a mock for vector overlay's call_action.""" - return mocker.patch.object(vector_overlay, "call_action") +def call_action(vector_overlay, mock_call_action): + return mock_call_action(vector_overlay) @pytest.fixture -def image_call_action(image, mocker): - """Return a mock for image's call_action.""" - return mocker.patch.object(image, "call_action") +def image_call_action(image, mock_call_action): + return mock_call_action(image) @pytest.fixture -def property_(mocker): - """Return a helper function to mock the value of a decorated vector overlay property using a simple syntax.""" - def func(property_name, mock_value): - return mocker.patch(f"carta.vector_overlay.VectorOverlay.{property_name}", new_callable=mocker.PropertyMock, return_value=mock_value) - return func +def property_(mock_property): + return mock_property("carta.vector_overlay.VectorOverlay") @pytest.fixture -def method(vector_overlay, mocker): - """Return a helper function to mock the return value(s) of an vector overlay method using a simple syntax.""" - def func(method_name, return_values): - return mocker.patch.object(vector_overlay, method_name, side_effect=return_values) - return func +def method(vector_overlay, mock_method): + return mock_method(vector_overlay) @pytest.mark.parametrize("args,kwargs,expected_args", [