Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix track number/burst id calculation #77

Merged
merged 36 commits into from
Nov 23, 2022

Conversation

scottstanie
Copy link
Contributor

@scottstanie scottstanie commented Oct 28, 2022

This PR implements several fixes to our burst id naming scheme:

  1. For ascending note crossings that happen mid-track, the track number should update (even if there is only one absolute orbit given)
  2. The calculation we've been doing is actually different than ESAs, and leads to a different burst number on many frames (I believe ones closer to the crossing node are more often different since we're missing the "preamble")

For (1), We can double check our work with the manifest.safe file. In a crossing, theres two relativeOrbitNumbers:

(mapping) [staniewi@aurora s1-reader]$ grep -r  relativeOrbitNumber tests/
tests/data/S1A_IW_SLC__1SDV_20221024T184148_20221024T184218_045587_05735F_D6E2.SAFE/manifest.safe:            <safe:relativeOrbitNumber type="start">15</safe:relativeOrbitNumber>
tests/data/S1A_IW_SLC__1SDV_20221024T184148_20221024T184218_045587_05735F_D6E2.SAFE/manifest.safe:            <safe:relativeOrbitNumber type="stop">16</safe:relativeOrbitNumber>

For (2), i'm using the Sentinel-1 Level 1 Detailed Algorithm Definition section 9.25

I've also added 2 test data cases, where I included some scripts to shrink down the real data.

One note: the current test was one where the burst ids we were making were incorrect.
before: expected_burst_id = f't071_{151200 + i}_iw3'

I have added a small sample of the ESA burst database with their geometries. I added a test that compares the overlap to make sure our id matches theirs.

scripts helped make very small version, orbits manually shrunk
@scottstanie scottstanie marked this pull request as draft October 28, 2022 19:40
@scottstanie scottstanie marked this pull request as ready for review October 29, 2022 00:41
@scottstanie scottstanie marked this pull request as draft November 1, 2022 16:38
@scottstanie scottstanie marked this pull request as ready for review November 1, 2022 20:52
Copy link
Contributor

@vbrancat vbrancat left a comment

Choose a reason for hiding this comment

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

@scottstanie thanks a lot for this PR. This is so crucial to make the CSLC-S1 workflow to properly work with the TrackFrame database.

My main concern with this PR is related to the unit test. It seems that you are trying to upload a bunch of small file in the repository to make the unit test to run. If I am not mistaken, we decided to avoid going down this route. Our best option would be to store the data on zenodo, download them on the CI, run the unit tests. @LiangJYu and @rtburns-jpl might help you along the way.

If the above is correct, I recommend:

  1. Submit the unit test in a separate PR. You might need some time to set up the repository on zenodo and having the test running on the CI
  2. Include a README file together with the data downloaded for the unit test. This should describe the source of the data, the region covered, what files they contain and why they have been selected for the unit test

@@ -652,8 +652,9 @@ def eap_compensation_lut(self):
f' IPF version = {self.ipf_version}')

return self.burst_eap.compute_eap_compensation_lut(self.width)
def bbox(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

Any particular reason why we are removing this function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed this once I realized why the border was a list of Polygon's, when it seemed like it was always a list of one single Polygon: opera-adt/burst_db#1

the anti-meridian crossing bursts (international date line) have two polygons, since it's conventional to split the latlon that way, so this function is giving an incorrect bbox for those ones. I can leave it in and submit an issue to correct it in the future if you'd like.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixing it might be as simple as

    return shapely.geometry.MultiPolygon(b.border).bounds

as long as we don't care that it returns longitudes greater than 180:

In [5]: b = s1reader.load_bursts('S1A_IW_SLC__1SDV_20220110T181913_20220110T181938_041402_04EC40_E2D9.zip', None, 1)[0]
In [6]: b.border
Out[6]: [<shapely.geometry.polygon.Polygon object at 0x7fadef947670>, <shapely.geometry.polygon.Polygon object at 0x7fadef944d00>]

In [8]: b.border[0].bounds
Out[8]: (180.0, 52.19675210021637, 180.6997402722514, 52.44622930532407)

In [11]: import shapely.geometry
In [12]: shapely.geometry.MultiPolygon(b.border).bounds
Out[12]: (179.3998234765543, 52.19675210021637, 180.6997402722514, 52.50961732430616)

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's submit an issue separately. We do not want to have longitudes greater than 180 (if this is the only fix)

src/s1reader/s1_reader.py Show resolved Hide resolved
src/s1reader/s1_reader.py Show resolved Hide resolved
src/s1reader/s1_reader.py Show resolved Hide resolved
@@ -860,9 +850,106 @@ def _burst_from_safe_dir(safe_dir_path: str, id_str: str, orbit_path: str, flag_
else:
msg = f'measurement directory NOT found in {safe_dir_path}'
msg += ', continue with metadata only.'
print(msg)
# print(msg)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it intentional to have a comment here? Don't you want to just print the message for debugging purposes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah sorry, I had turned this off while downloading the cycle of metadata so it didn't print the message 3 * 10 * (number of frames in a cycle) times. I'll turn it back on, but it would be nice to have it be a .debug logging message once we switch in python logging or do #25

Relative orbit number at the start of the acquisition, from 1-175.
end_track : int
Relative orbit number at the end of the acquisition.
subswath_name : str, {'IW1', 'IW2', 'IW3'}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this case-sensitive?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

nope, and I'll add a comment saying so

ESA Sentinel-1 Level 1 Detailed Algorithm Definition
https://sentinels.copernicus.eu/documents/247904/1877131/S1-TN-MDA-52-7445_Sentinel-1+Level+1+Detailed+Algorithm+Definition_v2-4.pdf/83624863-6429-cfb8-2371-5c5ca82907b8
"""
# Constants in Table 9-7
Copy link
Contributor

Choose a reason for hiding this comment

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

Wondering again whether we should have a separate file in the repository where to insert all the constants

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same comment here- I can move these to a constants file if you'd like, but i'm unsure if the constants would be easier to interpret if they're moved away from function that uses them. If we're planning on making a big constants.py then I can start with these

Copy link
Contributor

@LiangJYu LiangJYu Nov 18, 2022

Choose a reason for hiding this comment

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

I don't think there's enough for a dedicated constants file. From a quick glance, I didn't see anything beyond 3 constants below in the repo.

What do you about moving get_burst_id into its own file? A s1_burst_id.py that's something like the following (some inspiration from):

from typing import ClassVar
from dataclasses import dataclass

@dataclass
class S1BurstID:
    T_beam: ClassVar[float] = 2.758273  # interval of one burst [s]
    T_pre: ClassVar[float] = 2.299849  # Preamble time interval [s]
    T_orb: ClassVar[float] = 12 * 24 * 3600 / 175  # Nominal orbit period [s]
    track_number: int
    esa_burst_id: int
    subswath_name: str

    @classmethod
    def params_to_id(cls, 
        sensing_time: datetime.datetime, 
        ascending_node_dt: datetime.datetime,
        start_track: int,
        end_track: int,
        subswath_name: str):
        # do same computations
        return cls(track_numer, esa_burst_id, subswath)

    @property
    def as_str(self):
        return f"t{self.track_number:03d}_{self.esa_burst_id:06d}_{self.subswath_name.lower()}"

We can then change this from str to S1BurstID

Benefits:

  • By importing this module, all the constants as accessible a class properties.
  • Clearer way to import and generate JPL S1 burst IDs without having to dig through s1_reader.py.

@@ -0,0 +1,33 @@
- `make_empty_safe.sf` converts a full SDV SAFE folder into a 2.1 Mb folder of 1 vv-polarization annotation files.
- `make_empty_img.py` Converts a measurement/ .tiff file into an all-zeros file < 50kb using SPARSE_OK=TRUE.
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure if it is intentional, but I do see that this PR is trying to upload a lot of files into the repository. @LiangJYu can correct me if I am wrong, but I thought we decided to upload all the test data on zenodo.org in order to avoid increasing the size of the s1-reader repo. If this is the case, I would recommend to submit the unit test in a separate PR as this would require some more work (i.e. data uploading, set the CI) and we desperately need this PR for our CSLC-S1 Beta delivery

@@ -1 +1 @@
include src/s1reader/data/sentinel1_track_burst_id.txt
recursive-include src/s1reader/data/ *
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As a note: this will fix #85 by including all the files in the data folder

@scottstanie
Copy link
Contributor Author

alright @vbrancat now this PR is much smaller haha. i've separated out the unit tests/new test file generation into #84 and i'll talk with @LiangJYu about his usage of Zenodo-pulled files.

@@ -0,0 +1,47 @@
import pandas as pd
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure if pandas is included in the dependency for s1-reader?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok i'll keep that in mind for the other PR that I moved these test scripts to 👍

Comment on lines 956 to 958
has_anx_crossing = (end_track == start_track + 1) or (
end_track == 1 and start_track == 175
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
has_anx_crossing = (end_track == start_track + 1) or (
end_track == 1 and start_track == 175
)
has_anx_crossing = (end_track == (start_track + 1) % 175)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

haha I think i waffled back and forth between whether the modulo was clearer or the other one. i'll change to the modulo version

Copy link
Contributor Author

Choose a reason for hiding this comment

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

-------
str
Search path to extract from the ET of the manifest.safe XML.

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice improvement of the function!
Please add description for the returning namespace nsmap

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good idea, i added a comment and a link to the lxml document where it came from

src/s1reader/s1_reader.py Outdated Show resolved Hide resolved
src/s1reader/s1_reader.py Outdated Show resolved Hide resolved
src/s1reader/s1_reader.py Outdated Show resolved Hide resolved
ESA Sentinel-1 Level 1 Detailed Algorithm Definition
https://sentinels.copernicus.eu/documents/247904/1877131/S1-TN-MDA-52-7445_Sentinel-1+Level+1+Detailed+Algorithm+Definition_v2-4.pdf/83624863-6429-cfb8-2371-5c5ca82907b8
"""
# Constants in Table 9-7
Copy link
Contributor

@LiangJYu LiangJYu Nov 18, 2022

Choose a reason for hiding this comment

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

I don't think there's enough for a dedicated constants file. From a quick glance, I didn't see anything beyond 3 constants below in the repo.

What do you about moving get_burst_id into its own file? A s1_burst_id.py that's something like the following (some inspiration from):

from typing import ClassVar
from dataclasses import dataclass

@dataclass
class S1BurstID:
    T_beam: ClassVar[float] = 2.758273  # interval of one burst [s]
    T_pre: ClassVar[float] = 2.299849  # Preamble time interval [s]
    T_orb: ClassVar[float] = 12 * 24 * 3600 / 175  # Nominal orbit period [s]
    track_number: int
    esa_burst_id: int
    subswath_name: str

    @classmethod
    def params_to_id(cls, 
        sensing_time: datetime.datetime, 
        ascending_node_dt: datetime.datetime,
        start_track: int,
        end_track: int,
        subswath_name: str):
        # do same computations
        return cls(track_numer, esa_burst_id, subswath)

    @property
    def as_str(self):
        return f"t{self.track_number:03d}_{self.esa_burst_id:06d}_{self.subswath_name.lower()}"

We can then change this from str to S1BurstID

Benefits:

  • By importing this module, all the constants as accessible a class properties.
  • Clearer way to import and generate JPL S1 burst IDs without having to dig through s1_reader.py.

@scottstanie
Copy link
Contributor Author

@LiangJYu the new module is up, where you make the S1BurstId with the .from_burst_params classmethod

we'll have to change this bit in COMPASS

https://github.com/opera-adt/COMPASS/blob/bb2630675878d8a227e1c0eeb0ea1dd0266c378e/src/compass/utils/runconfig.py#L395-L400

to '_'.join(str(b.burst_id)) to join the burst id to other strings

Copy link
Contributor

@LiangJYu LiangJYu left a comment

Choose a reason for hiding this comment

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

LGTM. Really nice changes/additions to s1-reader!

src/s1reader/s1_reader.py Outdated Show resolved Hide resolved
src/s1reader/s1_reader.py Show resolved Hide resolved
src/s1reader/s1_reader.py Show resolved Hide resolved
@scottstanie
Copy link
Contributor Author

also, perhaps I should open an issue in RTC for @gshiroma for the same small fix once this is merged in: https://github.com/opera-adt/RTC/blob/93c3118ea4cb91058c4afc0a9e410444b038a57f/src/rtc/runconfig.py#L428 which will just have to coerce to str() to combine using join()

@scottstanie
Copy link
Contributor Author

although I actually see one other change to the RTC repo: https://github.com/opera-adt/RTC/blob/cf04dc7ff68f559d3fa56605321c312a144b3e7b/src/rtc/h5_prep.py#L144

@LiangJYu do you think

  1. we should add a method to S1BurstId that matches the string.split method
def split(sep=None, maxsplit=-1):
    return str(self).split(sep, maxsplit)
  1. we should have people explicitly do str(burst_id_obj) to use the string methods on the string repr of S1BurstId?

@LiangJYu
Copy link
Contributor

although I actually see one other change to the RTC repo: https://github.com/opera-adt/RTC/blob/cf04dc7ff68f559d3fa56605321c312a144b3e7b/src/rtc/h5_prep.py#L144

@LiangJYu do you think

1. we should add a method to `S1BurstId` that matches the string.split method
def split(sep=None, maxsplit=-1):
    return str(self).split(sep, maxsplit)
2. we should have people explicitly do `str(burst_id_obj)` to use the string methods on the string repr of S1BurstId?

With regards to the RTC code, I think it would be clearer if it track_number was retrieved via burst_in.burst_id.track_number instead of string.split(). This train of thought sort of leads to me thinking we should not add a split method.

What other string methods were you think about besides split? Comparisons for individual attributes?

@scottstanie
Copy link
Contributor Author

i didn't have any others; I was just checking thoughts on trying to not break other code now that are expected it to be a string, vs. having everyone switch to use the new object

@gshiroma
Copy link
Contributor

i didn't have any others; I was just checking thoughts on trying to not break other code now that are expected it to be a string, vs. having everyone switch to use the new object

Thank you, @scottstanie and @LiangJYu ! No problem if you guys decide to go ahead and make burst_id an object (other than str). We'll update our code accordingly. The fact that you @scottstanie identified the points of change and @LiangJYu suggested a solution makes it even easier to fix. Thanks again!

Copy link
Contributor

@vbrancat vbrancat left a comment

Choose a reason for hiding this comment

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

@scottstanie Thanks for having addressed my comments. I have no comment left at this stage :). Nice work.

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.

None yet

5 participants