Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .github/workflows/frontend-api-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Frontend API compatibility

on:
push:
branches: [ dev ]
pull_request:
branches: [ dev ]
workflow_dispatch:

permissions:
contents: read

jobs:
check:
runs-on: ubuntu-latest

steps:
- name: Checkout carta-python
uses: actions/checkout@v6

- name: Checkout carta-frontend API checker
uses: actions/checkout@v6
with:
repository: CARTAvis/carta-frontend
ref: zhenkai/api_check
path: carta-frontend
submodules: true

- name: Install uv and set Python version
uses: astral-sh/setup-uv@v7
with:
python-version: "3.10"
enable-cache: true

- name: Sync Python environment
run: uv sync --locked --no-group dev

- name: Generate API manifest
run: uv run --no-sync scripts/extract_frontend_api.py --write-manifest

- name: Install Node dependencies
uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
cache-dependency-path: carta-frontend/package-lock.json

- name: Install frontend dependencies
working-directory: carta-frontend
run: npm ci --ignore-scripts

- name: Build frontend protobuf types
working-directory: carta-frontend
run: npm run build-protobuf

- name: Check frontend APIs
working-directory: carta-frontend
run: npm run check-python-api -- --manifest "$GITHUB_WORKSPACE/frontend_api.json"
2 changes: 1 addition & 1 deletion carta/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ def valid_wcs(self):
boolean
Whether the image has WCS information.
"""
return self.get_value("validWcs")
return self.get_value("isValidWcs")

@validate(Coordinate(), Coordinate())
def set_center(self, x, y):
Expand Down
4 changes: 2 additions & 2 deletions carta/region.py
Original file line number Diff line number Diff line change
Expand Up @@ -1988,7 +1988,7 @@ def arrowheads_visible(self):
boolean
Whether the east arrowhead is visible.
"""
return self.get_value("northArrowhead"), self.get_value("eastArrowhead")
return self.get_value("hasNorthArrowhead"), self.get_value("hasEastArrowhead")

# SET PROPERTIES

Expand Down Expand Up @@ -2109,7 +2109,7 @@ def auxiliary_lines_visible(self):
boolean
Whether the auxiliary lines are visible.
"""
return self.get_value("auxiliaryLineVisible")
return self.get_value("isAuxiliaryLineVisible")

@property
def auxiliary_lines_dash_length(self):
Expand Down
6 changes: 2 additions & 4 deletions carta/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -1011,10 +1011,8 @@ def rendered_view_url(self, background_color=None):

"""
self.call_action("waitForImageData")
args = ["getImageDataUrl"]
if background_color:
args.append(background_color)
return self.call_action(*args, response_expected=True)
args = [background_color] if background_color else []
return self.call_action("getImageDataUrl", *args, response_expected=True)

@validate(NoneOr(Color()))
def rendered_view_data(self, background_color=None):
Expand Down
73 changes: 32 additions & 41 deletions carta/vector_overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,43 +26,37 @@ def __init__(self, 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):
@validate(*all_optional(Constant(VectorOverlaySource), Constant(VectorOverlaySource), Number(), Boolean(), Boolean(), Number(), Boolean(), Number(), Number()))
def configure(self, angular_source=None, intensity_source=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}
pixel_averaging : {2}
The pixel averaging width in pixels. The initial value can be configured in the frontend preferences (the default is ``4``).
fractional_intensity : {4}
fractional_intensity : {3}
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}
threshold_enabled : {4}
Enable threshold. Initially the threshold is disabled.
threshold : {6}
threshold : {5}
The threshold in Jy/pixels. The initial value is zero.
debiasing : {7}
debiasing : {6}
Enable debiasing. This is initially disabled.
q_error : {8}
q_error : {7}
The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. Initially set to zero.
u_error : {9}
u_error : {8}
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
args = (angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error)
args = (angular_source, intensity_source, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error)
if any(a is not None for a in args):
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:
Expand All @@ -77,12 +71,11 @@ def configure(self, angular_source=None, intensity_source=None, pixel_averaging_
for value, attr_name in (
(angular_source, "angularSource"),
(intensity_source, "intensitySource"),
(pixel_averaging_enabled, "pixelAveragingEnabled"),
(pixel_averaging, "pixelAveraging"),
(fractional_intensity, "fractionalIntensity"),
(threshold_enabled, "thresholdEnabled"),
(fractional_intensity, "isFractionalIntensity"),
(threshold_enabled, "isThresholdEnabled"),
(threshold, "threshold"),
(debiasing, "debiasing"),
(debiasing, "isDebiasing"),
(q_error, "qError"),
(u_error, "uError"),
):
Expand Down Expand Up @@ -201,7 +194,7 @@ def apply(self):
self.image.call_action("applyVectorOverlay")

@validate(*all_optional(*vargs(configure, set_thickness, set_intensity_range, set_length_range, set_rotation_offset, set_color, set_colormap, set_bias_and_contrast)))
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):
def plot(self, angular_source=None, intensity_source=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):
"""Configure, style, and apply the vector overlay in a single step.

If both a color and a colormap are provided, the colormap will be enabled.
Expand All @@ -212,47 +205,45 @@ def plot(self, angular_source=None, intensity_source=None, pixel_averaging_enabl
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}
pixel_averaging : {2}
The pixel averaging width in pixels. The initial value can be configured in the frontend preferences (the default is ``4``).
fractional_intensity : {4}
fractional_intensity : {3}
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}
threshold_enabled : {4}
Enable threshold. Initially the threshold is disabled.
threshold : {6}
threshold : {5}
The threshold in Jy/pixels. The initial value is zero.
debiasing : {7}
debiasing : {6}
Enable debiasing. This is initially disabled.
q_error : {8}
q_error : {7}
The Stokes Q error in Jy/beam. Set both this and ``u_error`` to enable debiasing. Initially set to zero.
u_error : {9}
u_error : {8}
The Stokes U error in Jy/beam. Set both this and ``q_error`` to enable debiasing. Initially set to zero.
thickness : {10}
thickness : {9}
The line thickness in pixels. The initial value is ``1``.
intensity_min : {11}
intensity_min : {10}
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}
intensity_max : {11}
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}
length_min : {12}
The minimum value of line length in pixels. The initial value is ``0``.
length_max : {14}
length_max : {13}
The maximum value of line length in pixels. The initial value is ``20``.
rotation_offset : {15}
rotation_offset : {14}
The rotation offset in degrees. The initial value is ``0``.
color : {16}
color : {15}
The color. The initial value value is ``#238551`` (a shade of green).
colormap : {17}
colormap : {16}
The colormap. The initial value is :obj:`carta.constants.Colormap.VIRIDIS`.
bias : {18}
bias : {17}
The colormap bias. The initial value is ``0``.
contrast : {19}
contrast : {18}
The colormap contrast. The initial value is ``1``.
"""
changes_made = False

for method, args in [
(self.configure, (angular_source, intensity_source, pixel_averaging_enabled, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error)),
(self.configure, (angular_source, intensity_source, pixel_averaging, fractional_intensity, threshold_enabled, threshold, debiasing, q_error, u_error)),
(self.set_thickness, (thickness,)),
(self.set_intensity_range, (intensity_min, intensity_max)),
(self.set_length_range, (length_min, length_max)),
Expand Down
29 changes: 19 additions & 10 deletions carta/wcs_overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def palette_to_rgb(self, color):
The RGB value of the palette colour in the session's current theme, as a 6-digit hexadecimal with a leading ``#``.
"""
color = PaletteColor(color)
if self.session.get_value("darkTheme"):
if self.session.get_value("isDarkTheme"):
return color.rgb_dark
return color.rgb_light

Expand Down Expand Up @@ -168,7 +168,7 @@ def custom_color(self):
boolean
Whether a custom color is applied.
"""
return self.get_value("customColor")
return self.get_value("hasCustomColor")

@validate(Constant(PaletteColor))
def set_color(self, color):
Expand Down Expand Up @@ -208,7 +208,7 @@ def custom_text(self):
boolean
Whether custom text is applied.
"""
return self.get_value("customText")
return self.get_value("hasCustomText")

@validate(Boolean())
def set_custom_text(self, state):
Expand Down Expand Up @@ -305,7 +305,7 @@ def visible(self):
boolean
Whether this component is visible.
"""
return self.get_value("visible")
return self.get_value("isVisible")

@validate(Boolean())
def set_visible(self, state):
Expand Down Expand Up @@ -402,7 +402,7 @@ def custom_precision(self):
boolean
Whether a custom precision is applied.
"""
return self.get_value("customPrecision")
return self.get_value("hasCustomPrecision")

@validate(Number(min=0))
def set_precision(self, precision):
Expand Down Expand Up @@ -598,7 +598,7 @@ def custom_gap(self):
boolean
Whether a custom gap is applied.
"""
return self.get_value("customGap")
return self.get_value("hasCustomGap")

@validate(*all_optional(Number.POSITIVE, Number.POSITIVE))
def set_gap(self, gap_x, gap_y):
Expand Down Expand Up @@ -692,7 +692,7 @@ def custom_format(self):
boolean
Whether a custom format is applied.
"""
return self.get_value("customFormat")
return self.get_value("hasCustomFormat")

@validate(*all_optional(Constant(NumberFormat), Constant(NumberFormat)))
def set_format(self, format_x=None, format_y=None):
Expand Down Expand Up @@ -804,7 +804,7 @@ def custom_density(self):
boolean
Whether a custom density is applied.
"""
return self.get_value("customDensity")
return self.get_value("hasCustomDensity")

@property
def draw_on_all_edges(self):
Expand All @@ -815,7 +815,7 @@ def draw_on_all_edges(self):
boolean
Whether the ticks are drawn on all edges.
"""
return self.get_value("drawAll")
return self.get_value("shouldDrawAll")

@property
def minor_length(self):
Expand Down Expand Up @@ -951,6 +951,15 @@ def get_value(self, path, return_path=None):
object
The unmodified return value of the colorbar method.
"""
property_rewrites = {
"isVisible": f"is{self.PREFIX.title()}Visible",
"hasCustomColor": f"has{self.PREFIX.title()}CustomColor",
"hasCustomText": f"has{self.PREFIX.title()}CustomText",
"hasCustomPrecision": f"has{self.PREFIX.title()}CustomPrecision",
}
if path in property_rewrites:
return self.colorbar.get_value(property_rewrites[path], return_path=return_path)

def rewrite(m):
before, first, rest = m.groups()
return f"{before}{self.PREFIX}{first.upper()}{rest}"
Expand Down Expand Up @@ -1129,7 +1138,7 @@ def interactive(self):
boolean
Whether the colorbar is interactive.
"""
return self.get_value("interactive")
return self.get_value("isInteractive")

@property
def offset(self):
Expand Down
Loading