Skip to content

Commit

Permalink
Merge branch 'pytroll:main' into add_support_mersi1
Browse files Browse the repository at this point in the history
  • Loading branch information
yukaribbba committed May 15, 2024
2 parents e9aa4ad + 36f49f3 commit 9f36465
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 22 deletions.
36 changes: 18 additions & 18 deletions satpy/etc/readers/msi_safe.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
reader:
name: msi_safe
short_name: MSI SAFE
long_name: Sentinel-2 A and B MSI data in SAFE format
long_name: Sentinel-2 A and B MSI data in SAFE format, supporting L1C format only.
description: SAFE Reader for MSI data (Sentinel-2)
status: Nominal
supports_fsspec: false
Expand All @@ -10,16 +10,16 @@ reader:
reader: !!python/name:satpy.readers.yaml_reader.FileYAMLReader

file_types:
safe_granule:
safe_granule_l1c:
file_reader: !!python/name:satpy.readers.msi_safe.SAFEMSIL1C
file_patterns: ['{fmission_id:3s}_MSIL1C_{observation_time:%Y%m%dT%H%M%S}_N{fprocessing_baseline_number:4d}_R{relative_orbit_number:3d}_T{dtile_number:5s}_{dproduct_discriminator:%Y%m%dT%H%M%S}.SAFE/GRANULE/L1C_T{gtile_number:5s}_A{absolute_orbit_number:6d}_{gfile_discriminator:%Y%m%dT%H%M%S}/IMG_DATA/T{tile_number:5s}_{file_discriminator:%Y%m%dT%H%M%S}_{band_name:3s}.jp2']
file_patterns: ['{fmission_id:3s}_MSI{proclevel:3s}_{observation_time:%Y%m%dT%H%M%S}_N{fprocessing_baseline_number:4d}_R{relative_orbit_number:3d}_T{dtile_number:5s}_{dproduct_discriminator:%Y%m%dT%H%M%S}.SAFE/GRANULE/L1C_T{gtile_number:5s}_A{absolute_orbit_number:6d}_{gfile_discriminator:%Y%m%dT%H%M%S}/IMG_DATA/T{tile_number:5s}_{file_discriminator:%Y%m%dT%H%M%S}_{band_name:3s}.jp2']
requires: [safe_metadata, safe_tile_metadata]
safe_tile_metadata:
file_reader: !!python/name:satpy.readers.msi_safe.SAFEMSITileMDXML
file_patterns: ['{fmission_id:3s}_MSIL1C_{observation_time:%Y%m%dT%H%M%S}_N{fprocessing_baseline_number:4d}_R{relative_orbit_number:3d}_T{dtile_number:5s}_{dproduct_discriminator:%Y%m%dT%H%M%S}.SAFE/GRANULE/L1C_T{gtile_number:5s}_A{absolute_orbit_number:6d}_{gfile_discriminator:%Y%m%dT%H%M%S}/MTD_TL.xml']
file_patterns: ['{fmission_id:3s}_MSI{proclevel:3s}_{observation_time:%Y%m%dT%H%M%S}_N{fprocessing_baseline_number:4d}_R{relative_orbit_number:3d}_T{dtile_number:5s}_{dproduct_discriminator:%Y%m%dT%H%M%S}.SAFE/GRANULE/L1C_T{gtile_number:5s}_A{absolute_orbit_number:6d}_{gfile_discriminator:%Y%m%dT%H%M%S}/MTD_TL.xml']
safe_metadata:
file_reader: !!python/name:satpy.readers.msi_safe.SAFEMSIMDXML
file_patterns: ['{fmission_id:3s}_MSIL1C_{observation_time:%Y%m%dT%H%M%S}_N{fprocessing_baseline_number:4d}_R{relative_orbit_number:3d}_T{dtile_number:5s}_{dproduct_discriminator:%Y%m%dT%H%M%S}.SAFE/MTD_MSIL1C.xml']
file_patterns: ['{fmission_id:3s}_MSI{proclevel:3s}_{observation_time:%Y%m%dT%H%M%S}_N{fprocessing_baseline_number:4d}_R{relative_orbit_number:3d}_T{dtile_number:5s}_{dproduct_discriminator:%Y%m%dT%H%M%S}.SAFE/MTD_MSIL1C.xml']


datasets:
Expand All @@ -39,7 +39,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: safe_granule
file_type: safe_granule_l1c

B02:
name: B02
Expand All @@ -56,7 +56,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: safe_granule
file_type: safe_granule_l1c

B03:
name: B03
Expand All @@ -73,7 +73,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: safe_granule
file_type: safe_granule_l1c

B04:
name: B04
Expand All @@ -90,7 +90,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: safe_granule
file_type: safe_granule_l1c

B05:
name: B05
Expand All @@ -107,7 +107,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: safe_granule
file_type: safe_granule_l1c

B06:
name: B06
Expand All @@ -124,7 +124,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: safe_granule
file_type: safe_granule_l1c

B07:
name: B07
Expand All @@ -141,7 +141,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: safe_granule
file_type: safe_granule_l1c

B08:
name: B08
Expand All @@ -158,7 +158,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: safe_granule
file_type: safe_granule_l1c

B8A:
name: B8A
Expand All @@ -175,7 +175,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: safe_granule
file_type: safe_granule_l1c

B09:
name: B09
Expand All @@ -192,7 +192,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: safe_granule
file_type: safe_granule_l1c

B10:
name: B10
Expand All @@ -209,7 +209,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: safe_granule
file_type: safe_granule_l1c

B11:
name: B11
Expand All @@ -226,7 +226,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: safe_granule
file_type: safe_granule_l1c

B12:
name: B12
Expand All @@ -243,7 +243,7 @@ datasets:
counts:
standard_name: counts
units: "1"
file_type: safe_granule
file_type: safe_granule_l1c


solar_zenith_angle:
Expand Down
13 changes: 10 additions & 3 deletions satpy/readers/msi_safe.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@
reader_kwargs={'mask_saturated': False})
scene.load(['B01'])
L1B format description for the files read here:
L1C format description for the files read here:
https://sentinels.copernicus.eu/documents/247904/0/Sentinel-2-product-specifications-document-V14-9.pdf/
"""

import logging
from datetime import datetime

import dask.array as da
import defusedxml.ElementTree as ET
Expand Down Expand Up @@ -63,13 +64,14 @@ def __init__(self, filename, filename_info, filetype_info, mda, tile_mda, mask_s
super(SAFEMSIL1C, self).__init__(filename, filename_info,
filetype_info)
del mask_saturated
self._start_time = filename_info["observation_time"]
self._end_time = filename_info["observation_time"]
self._channel = filename_info["band_name"]
self._tile_mda = tile_mda
self._mda = mda
self.platform_name = PLATFORMS[filename_info["fmission_id"]]

self._start_time = self._tile_mda.start_time()
self._end_time = filename_info["observation_time"]

def get_dataset(self, key, info):
"""Load a dataset."""
if self._channel != key["name"]:
Expand Down Expand Up @@ -269,6 +271,11 @@ def _shape(self, resolution):
cols = int(self.geocoding.find('Size[@resolution="' + str(resolution) + '"]/NCOLS').text)
return cols, rows

def start_time(self):
"""Get the observation time from the tile metadata."""
timestr = self.root.find(".//SENSING_TIME").text
return datetime.strptime(timestr, "%Y-%m-%dT%H:%M:%S.%fZ")

@staticmethod
def _do_interp(minterp, xcoord, ycoord):
interp_points2 = np.vstack((ycoord.ravel(), xcoord.ravel()))
Expand Down
22 changes: 21 additions & 1 deletion satpy/tests/reader_tests/test_msi_safe.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# satpy. If not, see <http://www.gnu.org/licenses/>.
"""Module for testing the satpy.readers.msi_safe module."""
import unittest.mock as mock
from datetime import datetime
from io import BytesIO, StringIO

import numpy as np
Expand All @@ -25,6 +26,10 @@

from satpy.tests.utils import make_dataid

# Datetimes used for checking start time is correctly set.
fname_dt = datetime(2020, 10, 1, 18, 35, 41)
tilemd_dt = datetime(2020, 10, 1, 16, 34, 23, 153611)

mtd_tile_xml = b"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<n1:Level-1C_Tile_ID xmlns:n1="https://psd-14.sentinel2.eo.esa.int/PSD/S2_PDI_Level-1C_Tile_Metadata.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://psd-14.sentinel2.eo.esa.int/PSD/S2_PDI_Level-1C_Tile_Metadata.xsd /gpfs/dpc/app/s2ipf/FORMAT_METADATA_TILE_L1C/02.14.00/scripts/../../../schemas/02.17.00/PSD/S2_PDI_Level-1C_Tile_Metadata.xsd">
Expand Down Expand Up @@ -873,6 +878,10 @@ def setup_method(self):
self.old_xml_fh = SAFEMSIMDXML(StringIO(mtd_l1c_old_xml), filename_info, mock.MagicMock())
self.xml_fh = SAFEMSIMDXML(StringIO(mtd_l1c_xml), filename_info, mock.MagicMock(), mask_saturated=True)

def test_start_time(self):
"""Ensure start time is read correctly from XML."""
assert self.xml_tile_fh.start_time() == tilemd_dt

def test_satellite_zenith_array(self):
"""Test reading the satellite zenith array."""
info = dict(xml_tag="Viewing_Incidence_Angles_Grids", xml_item="Zenith")
Expand Down Expand Up @@ -980,10 +989,11 @@ class TestSAFEMSIL1C:
def setup_method(self):
"""Set up the test."""
from satpy.readers.msi_safe import SAFEMSITileMDXML
self.filename_info = dict(observation_time=None, fmission_id="S2A", band_name="B01", dtile_number=None)
self.filename_info = dict(observation_time=fname_dt, fmission_id="S2A", band_name="B01", dtile_number=None)
self.fake_data = xr.Dataset({"band_data": xr.DataArray([[[0, 1], [65534, 65535]]], dims=["band", "x", "y"])})
self.tile_mda = mock.create_autospec(SAFEMSITileMDXML)(BytesIO(mtd_tile_xml),
self.filename_info, mock.MagicMock())
self.tile_mda.start_time.return_value = tilemd_dt

@pytest.mark.parametrize(("mask_saturated", "calibration", "expected"),
[(True, "reflectance", [[np.nan, 0.01 - 10], [645.34, np.inf]]),
Expand All @@ -1001,3 +1011,13 @@ def test_calibration_and_masking(self, mask_saturated, calibration, expected):
with mock.patch("xarray.open_dataset", return_value=self.fake_data):
res = self.jp2_fh.get_dataset(make_dataid(name="B01", calibration=calibration), info=dict())
np.testing.assert_allclose(res, expected)


def test_start_time(self):
"""Test that the correct start time is returned."""
from satpy.readers.msi_safe import SAFEMSIL1C, SAFEMSIMDXML

mda = SAFEMSIMDXML(StringIO(mtd_l1c_xml), self.filename_info, mock.MagicMock())
self.jp2_fh = SAFEMSIL1C("somefile", self.filename_info, mock.MagicMock(),
mda, self.tile_mda)
assert tilemd_dt == self.jp2_fh.start_time

0 comments on commit 9f36465

Please sign in to comment.