Skip to content

Add use of WCS units to center and zoom functions#116

Merged
confluence merged 38 commits intodevfrom
confluence/zoom_centre_units
Jul 3, 2023
Merged

Add use of WCS units to center and zoom functions#116
confluence merged 38 commits intodevfrom
confluence/zoom_centre_units

Conversation

@confluence
Copy link
Copy Markdown
Collaborator

@confluence confluence commented May 17, 2023

This PR implements #56, #57, and #109.

  • Image.set_center now accepts either world coordinates or image coordinates.
    • The validation permits a wider range of formats than the GUI pan and zoom dialog, including those requested in the example snippet in allowing world coordinates as input in Image.set_center() #56. These formats are normalized to the forms accepted by the frontend. Numbers or numeric strings without units are assumed to be in degrees, for consistency with the GUI.
    • A limitation is that the parameter formats must match the currently set overlay number formats (this is required by the frontend). We can detect a mismatch, but we can't automatically correct the settings because 1) we can't tell if a HMS string is supposed to be HMS or DMS, 2) we can't deduce a coordinate system from a format, and 3) we don't know if the user intends to change the coordinate system or set a custom number format. So it has to be the user's responsibility to ensure this.
    • set_center does have an optional parameter to set the system (for consistency with the dropdown in the GUI dialog), and I have added high-level functions to Session to make setting or clearing a custom number format more user-friendly.
    • Another limitation is that image and world coordinates can't be combined. We detect this and fail explicitly.
    • We also fail if world coordinates are provided and the image has no WCS info.
  • The old Image.set_zoom has been renamed to Image.set_zoom_level.
  • Image.zoom_to_size_x and Image.zoom_to_size_y are used to fit the specified dimension to the size provided, and accept both pixel sizes and angular sizes.
    • We accept a wider range of unit names than the frontend, and normalize them (to " for arcsec, ' for arcmin, and deg for degrees). A number or numeric string with no units is assumed to be in arcsec (for consistency with the frontend).
    • We fail if an angular size is provided and the image has no WCS info.
  • This PR also contains unit tests for the high-level functionality added in this PR, as well as a basic framework for writing unit tests with pytest. Once this is merged, tests for existing high-level functions can be filled in.
    • The unit test directory has been added to the format scripts.
  • CoordinateSystem enum values have been corrected to match the frontend.
  • The String validation class now has an integer parameter for arbitrary regex flags instead of a boolean parameter for ignoring the case. Existing code was using the parameter like this already (it worked by accident!).
  • Macro objects can now be compared (needed for unit tests).
  • Utility and validation classes for parsing and validating sizes and coordinates have been added and can be reused in other functions.

I made some decisions about the allowed / disallowed input formats which are up for discussion -- these can be changed relatively easily. For example:

  • the full list of allowed units and formats
  • what numbers without units default to in each function
  • spaces not being allowed before ' and ", but being allowed before multi-character unit words
  • h/d, m and s are all optional in hms / dms format. All three colons must be present if the colon separator format is used (this is consistent with the frontend). If letters are used, both number and letter must be included or excluded together. This means that "" is a valid coordinate which is evaluated as "::".
  • More generally, you can see what inputs are allowed by looking at the validation tests, and how they are transformed before being sent to the frontend by looking at the util tests.

To be completed:

  • Sample validation class tests
  • Session function for retrieving number format
  • Tests for this function and the function for retrieving the coordinate system
  • Basic e2e sanity check
  • Check the rendering of the documentation

@confluence confluence marked this pull request as draft May 17, 2023 19:33
@confluence confluence marked this pull request as ready for review May 18, 2023 10:36
@confluence confluence self-assigned this May 18, 2023
Copy link
Copy Markdown
Contributor

@I-Chenn I-Chenn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new added functions work without any problem in my attempt of test.

@confluence
Copy link
Copy Markdown
Collaborator Author

I wasn't entirely happy with the way that the parsing of the different formats was arranged internally, so I did a bit of refactoring to clean up the design and make the separation and code reuse more logical. The user-facing API is unchanged. While updating the tests I found some with duplicate names which I think never ran, and fixed them.

Copy link
Copy Markdown
Contributor

@kswang1029 kswang1029 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@confluence I just had some initial code comments. Will proceed user testing.

Comment thread carta/session.py
"""
self.call_action("overlayStore.global.setSystem", system)

def coordinate_system(self):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall we make it clear between native coordinate system (as defined in header) and "rendered" coordinate system?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mostly unambiguous because it's a function on the session, not the image (and will probably soon move to a separate image viewer class). But we can explain this in more detail in the docstring.

Comment thread carta/util.py Outdated
class AngularSizeString:
"""Parses angular sizes."""

NORMALIZED_UNIT = {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest we also support

  1. milliarcsec (10^-3 arcsec for VLA/ALMA etc)
  2. microarcsec (10^-6 arcsec for VLBI, EHT etc)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is doable; I'll need to do a bit of refactoring to normalize these to one of the units accepted by the frontend. Are there any other aliases of these unit names that you would like to support?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added these (together with the full words, and mas and µas). I also added uas, since I know that u has sometimes been used as an ASCII approximation for µ. It does turn up in search results, but possibly most of these results are just badly OCRed academic papers. If nobody actually uses this, I can take it out.

Comment thread carta/util.py Outdated
"""Parses angular sizes."""

NORMALIZED_UNIT = {
"arcmin": "'",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can consider to also support arcsecond and arcminute. Sometimes, we also use "asec" for arcsec and "amin" for arcmin.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are all very easy to add.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added these, together with "arcseconds" and "arcminutes" ("degrees" is also supported, but only these full words can be plural, not any of the abbreviations).

Comment thread carta/util.py
}

@classmethod
def valid(cls, value):
Copy link
Copy Markdown
Contributor

@kswang1029 kswang1029 May 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wonder if we need to validate value range? For example, for "hms" convention, 0 <= h < 24, 0 <= m < 60, and 0 <= s < 60. For "dms", -90 <= d <= 90,0 <= m < 60, and 0 <= s < 60. For "d" as "longitude", 0 <= d < 360 and as "latitude" -90 <= d <= 90

Copy link
Copy Markdown
Collaborator Author

@confluence confluence May 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also doable. We would need to be aware of both the coordinate system and which coordinate is which, so this probably needs to be done inside the set_center function.

As far as I can tell, the frontend currently doesn't do any validation beyond the format -- it attempts to transform whatever values are provided and applies the result if it's valid.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, for HMS and DMS we can do a more specific check in the util class. We'll just need the system and the axes for the degree ranges.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kswang1029:

  1. Should the latitude range for degrees not be -90 <= d <= 90?
  2. For HMS and DMS, I assume that e.g. H can only be 24 if M and S are zero (i.e. 24:12:34.5 = not valid). Is that correct?
  3. What if the user sets DMS as the custom number format for a longitude axis? I understand that this is not the convention, but nothing is preventing it. Should we then validate that 0 <= d < 360, as for decimal degrees?
  4. Am I correct in assuming that longitude = right ascension = x axis and latitude = declination = y axis in all coordinate systems except for ecliptic in which case latitude = x axis and right ascension = y axis? And does it still make sense for the user to provide the coordinates in (x, y) order in the latter case?

Copy link
Copy Markdown
Contributor

@kswang1029 kswang1029 Jun 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kswang1029:

  1. Should the latitude range for degrees not be -90 <= d <= 90?

When the coordinate system is galactic or ecliptic, we express the axis as Galactic/Ecliptic longitude and Galactic/Ecliptic latitude. Mostly, we use decimal degrees as the expression of longitude and latitude. For longitude, the range is 0 deg <= longitude < 360 deg, and for latitude, the range is -90 deg <= latitude <= 90 deg.

  1. For HMS and DMS, I assume that e.g. H can only be 24 if M and S are zero (i.e. 24:12:34.5 = not valid). Is that correct?

Correct, the HMS (hour-minute-second) range is like 0h:0m:0s, 0h:0m:1s, ..., 23h59m59.999999....s. The DMS (degree-arcmin-arcsec) range is -90d:0m:0s, ... -89d0m0s, ..., +90d0m0s. These are all sexagesimal.

  1. What if the user sets DMS as the custom number format for a longitude axis? I understand that this is not the convention, but nothing is preventing it. Should we then validate that 0 <= d < 360, as for decimal degrees?

Usually for ICRS, FK5 and FK4 (ie equatorial system), we use the HMS-DMS convention (sexagesimal). But it is totally fine to convert sexagesimal format to decimal degree format. For GALACTIC and ECLIPTIC, we use decimal degrees convention. Rarely we use sexagesimal format for these two systems (but it is valid to do such conversion).

  1. Am I correct in assuming that longitude = right ascension = x axis and latitude = declination = y axis in all coordinate systems except for ecliptic in which case latitude = x axis and right ascension = y axis? And does it still make sense for the user to provide the coordinates in (x, y) order in the latter case?

AST has its logic on where to map the longitude/latitude or RA/DEC axes to the canvas xy axes if the field has extra rotation. Assuming there is no extra field rotation, for ICRS, FK5 and FK4 systems, North is up and East is left. Then x is RA and y is DEC. For GALACTIC and ECLIPTIC systems without extra field rotation, North is up and East is left too. So x is longitude and y is latitude. The tricky part is, if we select GALACTIC grid to render from a native FK5 system (or vice versa), we will see the grid is rotated (hence we may see unexpected longitude/latitude to x/y mapping due to AST).

Copy link
Copy Markdown
Collaborator Author

@confluence confluence Jun 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following up on our previous conversation, I also checked what happens in the GUI if you select HMS as the format for latitude. This appears to be valid, and negative values are converted to the expected negative HMS values, which suggests that in this case the range should be -6:00:00 to 6:00:00.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm I would suggest we fix this behavior of the frontend. 🤔 showing HMS for latitude is not right.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That may be tricky. The preferences are for the X and Y axis, and as we discussed, which axis is which depends on how AST decides to display that image.

Copy link
Copy Markdown
Collaborator Author

@confluence confluence Jun 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fortunately I was overthinking this (I misread this part of your reply). I see now that the orientation of X and Y also changes, so X is always longitude and Y is always latitude. That should make it easier to implement the ranges (and also to disable specific custom number options in the frontend).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also misread the HMS range -- I see that 24 is excluded from the hour range.

Comment thread tests/test_image.py
mock_call_action.assert_called_with("setCenter", float(x), float(y))


@pytest.mark.parametrize("x,y,x_fmt,y_fmt,x_norm,y_norm", [
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be good to add test cases like (30d, -30d) where the latitude coordinate is a negative number. Need unit tests for this case too.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would probably be in the unit test for set_center.

@kswang1029
Copy link
Copy Markdown
Contributor

kswang1029 commented May 31, 2023

@confluence here is one issue I spotted. If we have two images loaded (not matched) and they both have equatorial coordinate system defined as the native one, then if we try to set_center using GALACTIC coordinate to the non active image, the result will be incorrect. Example script is shown below (let me know if you need the two test images I used).

from carta.session import Session
from carta.constants import CoordinateSystem
import time

url_with_token = 'http://localhost:3002'
session_id = 3055419803

image_file_3 = '/Users/kswang/carta_image_pool/set_herschel_pacs_spire/spire500_ext.fits'
image_file_1 = '/Users/kswang/carta_image_pool/set_HD163296/HD163296_C18O_2-1.fits'


##############################################################

session = Session.interact(url_with_token, session_id, debug_no_auth=True)
img0 = session.open_image(image_file_3)
img1 = session.append_image(image_file_1)


print(img0.valid_wcs)
img0.set_center("7:09:00", "-11:12:37")
img0.set_zoom_level(2.0)

time.sleep(5)

img0.make_active() # try to comment out this to see the issue

img0.set_center(224.791, -1.733, system=CoordinateSystem.GALACTIC)
img0.set_zoom_level(4.0)


time.sleep(5)

session.save_rendered_view('test.png')
session.close()
Screen.Recording.2023-05-31.at.12.34.18.mov

If we make image active first before set_center, it works. However, the axis labels are not rendered correctly.

Screen.Recording.2023-05-31.at.12.36.01.mov

@I-Chenn I-Chenn self-requested a review June 2, 2023 03:12
@confluence confluence marked this pull request as draft June 5, 2023 10:33
@confluence
Copy link
Copy Markdown
Collaborator Author

I'm temporarily moving this back to draft so that I can push some incomplete changes while I'm refactoring. I will move it back out once it's ready for review again.

@confluence confluence marked this pull request as ready for review June 5, 2023 20:03
Comment thread carta/image.py Outdated
# Image coordinates
x_value = PixelValue.as_float(str(x))
y_value = PixelValue.as_float(str(y))
if 0 <= x_value < self.width and 0 <= y_value < self.height:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is minor but I just realized, in CARTA we refer the center of the pixel at the bottom-left corner as (0, 0). This means, for example in x direction we have 100 pixels, the valid range should be -0.5 <= x < 99.5 (or -0.5 <= x <= 99.5?).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to make the range symmetrical -- if the user can center the bottom-left corner, they should also be able to center the other corners.

Does it make sense to apply a range limit at all? The user can specify WCS coordinates which are outside the image area, right? Should they not be able to do the same here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, world coordinate can be outside the image. So guess we can remove the range check for consistency and potentially enable some use cases.

Comment thread carta/image.py Outdated
self.call_action("zoomToSizeX", PixelValue.as_float(size))
else:
if not self.valid_wcs:
raise ValueError("Cannot parse angular size. This image does not contain valid WCS information.")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, if the image is not have a valid WCS, we render the xy axes as "linear". So maybe we can add a hint to use image coordinate (ie pixel) as the size input.

Comment thread carta/image.py Outdated
self.call_action("zoomToSizeXWcs", str(AngularSize.from_string(size)))

@validate(Size())
def zoom_to_size_y(self, size):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder if we can combine zoom_to_size_x and zoom_to_size_y as zoom_to_size and have an argument as "direction" (or "dimension" or "axis"?) for x or y? This would simplify the code repeatition here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. I already added a SpatialAxis enum for the set_center range validation, so this is a simple change.

@confluence
Copy link
Copy Markdown
Collaborator Author

As discussed, I will merge this as-is so that we can start using the testing framework, and create a new issue for the inactive image problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants