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 osisaf SST reader #1410

Merged
merged 21 commits into from Apr 14, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions satpy/composites/__init__.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2015-2020 Satpy developers
# Copyright (c) 2015-2020, 2022 Satpy developers
#
# This file is part of satpy.
#
Expand Down Expand Up @@ -495,7 +495,7 @@ def build_colormap(palette, dtype, info):
Colormaps come in different forms, but they are all supposed to have
color values between 0 and 255. The following cases are considered:

- Palettes comprised of only a list on colors. If *dtype* is uint8,
- Palettes comprised of only a list of colors. If *dtype* is uint8,
the values of the colormap are the enumeration of the colors.
Otherwise, the colormap values will be spread evenly from the min
to the max of the valid_range provided in `info`.
Expand Down
36 changes: 36 additions & 0 deletions satpy/etc/enhancements/generic.yaml
Expand Up @@ -930,3 +930,39 @@ enhancements:
factor: 21.0
min_stretch: 0.0
max_stretch: 20.0

osisaf_sst_smhi_colormap:
adybbroe marked this conversation as resolved.
Show resolved Hide resolved
standard_name: sea_surface_subskin_temperature
operations:
- name: osisaf_sst
method: !!python/name:satpy.enhancements.colorize
kwargs:
palettes:
- colors: [
[255, 0, 255],
[195, 0, 129],
[129, 0, 47],
[195, 0, 0],
[255, 0, 0],
[236, 43, 0],
[217, 86, 0],
[200, 128, 0],
[211, 154, 13],
[222, 180, 26],
[233, 206, 39],
[244, 232, 52],
[255.99609375, 255.99609375, 63.22265625],
[203.125, 255.99609375, 52.734375],
[136.71875, 255.99609375, 27.34375],
[0, 255.99609375, 0],
[0, 207.47265625, 0],
[0, 158.94921875, 0],
[0, 110.42578125, 0],
[0, 82.8203125, 63.99609375],
[0, 55.21484375, 127.9921875],
[0, 27.609375, 191.98828125],
[0, 0, 255.99609375],
[100.390625, 100.390625, 255.99609375],
[150.5859375, 150.5859375, 255.99609375]]
min_value: 296.55
max_value: 273.55
97 changes: 97 additions & 0 deletions satpy/etc/readers/ghrsst_l2.yaml
@@ -0,0 +1,97 @@
reader:
description: NC Reader for GHRSST Level 2 data
name: ghrsst_l2
adybbroe marked this conversation as resolved.
Show resolved Hide resolved
sensors: ['slstr', 'avhrr/3', 'viirs']
default_channels: []
reader: !!python/name:satpy.readers.yaml_reader.FileYAMLReader

file_types:
GHRSST_OSISAF:
file_reader: !!python/name:satpy.readers.ghrsst_l2.GHRSSTL2FileHandler
# S-OSI_-FRA_-NPP_-NARSST_FIELD-202010141300Z.nc
file_patterns: ['S-OSI_-{generating_centre:4s}-{satid:s}-{field_type:s}_FIELD-{valid_time:%Y%m%d%H%M}Z.nc']

SLSTR:
file_reader: !!python/name:satpy.readers.ghrsst_l2.GHRSSTL2FileHandler
file_patterns: ['{dt1:%Y%m%d%H%M%S}-{generating_centre:3s}-{type_id:3s}_GHRSST-SSTskin-SLSTR{something:1s}-{dt2:%Y%m%d%H%M%S}-{version}.nc',
'{mission_id:3s}_SL_{processing_level:1s}_WST____{start_time:%Y%m%dT%H%M%S}_{end_time:%Y%m%dT%H%M%S}_{creation_time:%Y%m%dT%H%M%S}_{duration:4d}_{cycle:3d}_{relative_orbit:3d}_{frame:4s}_{centre:3s}_{mode:1s}_{timeliness:2s}_{collection:3s}.SEN3.tar']

datasets:
# SLSTR SST and Sea Ice products
longitude_slstr:
name: longitude_slstr
resolution: 1000
view: nadir
file_type: SLSTR
standard_name: lon
units: degree

latitude_slstr:
name: latitude_slstr
resolution: 1000
view: nadir
file_type: SLSTR
standard_name: lat
units: degree

sea_surface_temperature_slstr:
name: sea_surface_temperature
sensor: slstr
coordinates: [longitude_slstr, latitude_slstr]
file_type: SLSTR
resolution: 1000
view: nadir
units: kelvin
standard_name: sea_surface_temperature

sea_ice_fraction_slstr:
name: sea_ice_fraction
sensor: slstr
coordinates: [longitude_slstr, latitude_slstr]
file_type: SLSTR
resolution: 1000
view: nadir
units: "%"
standard_name: sea_ice_fraction

# Quality estimation 0-5: no data, cloud, worst, low, acceptable, best
quality_level_slstr:
name: quality_level
sensor: slstr
coordinates: [longitude_slstr, latitude_slstr]
file_type: SLSTR
resolution: 1000
view: nadir
standard_name: quality_level


# OSISAF SST:
longitude_osisaf:
name: longitude_osisaf
resolution: 2000
file_type: GHRSST_OSISAF
standard_name: lon
units: degree

latitude_osisaf:
name: latitude_osisaf
resolution: 2000
file_type: GHRSST_OSISAF
standard_name: lat
units: degree

sea_surface_temperature_osisaf:
name: sea_surface_temperature
coordinates: [longitude_osisaf, latitude_osisaf]
file_type: GHRSST_OSISAF
resolution: 2000
units: kelvin
standard_name: sea_surface_temperature

sea_ice_fraction_osisaf:
name: sea_ice_fraction
coordinates: [longitude_osisaf, latitude_osisaf]
file_type: GHRSST_OSISAF
resolution: 2000
units: "%"
standard_name: sea_ice_fraction
17 changes: 0 additions & 17 deletions satpy/etc/readers/ghrsst_l3c_sst.yaml

This file was deleted.

27 changes: 16 additions & 11 deletions satpy/readers/slstr_l2.py → satpy/readers/ghrsst_l2.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Satpy developers
# Copyright (c) 2017 - 2020, 2022 Satpy developers
#
# This file is part of satpy.
# This file is part of Satpy.
#
# satpy is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
Expand All @@ -14,8 +14,11 @@
#
# You should have received a copy of the GNU General Public License along with
# satpy. If not, see <http://www.gnu.org/licenses/>.
"""Reader for Sentinel-3 SLSTR SST data."""
"""Reader for the GHRSST level-2 formatted data."""

import os
import tarfile
import tempfile
from datetime import datetime

import xarray as xr
Expand All @@ -24,21 +27,18 @@
from satpy.readers.file_handlers import BaseFileHandler


class SLSTRL2FileHandler(BaseFileHandler):
"""File handler for Sentinel-3 SSL L2 netCDF files."""
class GHRSSTL2FileHandler(BaseFileHandler):
"""File handler for GHRSST L2 netCDF files."""

def __init__(self, filename, filename_info, filetype_info, engine=None):
"""Initialize the file handler for Sentinel-3 SSL L2 netCDF data."""
super(SLSTRL2FileHandler, self).__init__(filename, filename_info, filetype_info)
"""Initialize the file handler for GHRSST L2 netCDF data."""
super(GHRSSTL2FileHandler, self).__init__(filename, filename_info, filetype_info)
adybbroe marked this conversation as resolved.
Show resolved Hide resolved

if filename.endswith('tar'):
import os
import tarfile
import tempfile
with tempfile.TemporaryDirectory() as tempdir:
with tarfile.open(name=filename, mode='r') as tf:
sst_filename = next((name for name in tf.getnames()
if name.endswith('nc') and 'GHRSST-SSTskin' in name))
if name.endswith('nc') and 'GHRSST-SSTskin' in name))
Copy link
Member

Choose a reason for hiding this comment

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

You could move the if condition to a separate method, _is_sst_file for instance

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 suggestion, I will fix

tf.extract(sst_filename, tempdir)
adybbroe marked this conversation as resolved.
Show resolved Hide resolved
fullpath = os.path.join(tempdir, sst_filename)
self.nc = xr.open_dataset(fullpath,
Expand Down Expand Up @@ -75,3 +75,8 @@ def start_time(self):
def end_time(self):
"""Get end time."""
return self.filename_info['end_time']

@property
def sensor(self):
"""Get the sensor name."""
return self.nc.attrs['sensor'].lower()
121 changes: 121 additions & 0 deletions satpy/tests/reader_tests/test_ghrsst_l2.py
@@ -0,0 +1,121 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2018, 2022 Satpy developers
#
# This file is part of satpy.
#
# satpy is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# satpy is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# satpy. If not, see <http://www.gnu.org/licenses/>.
"""Module for testing the satpy.readers.ghrsst_l2 module."""

import unittest
from datetime import datetime
from unittest import mock
from unittest.mock import MagicMock, patch

import numpy as np
import xarray as xr

from satpy.readers.ghrsst_l2 import GHRSSTL2FileHandler


class TestGHRSSTL2Reader(unittest.TestCase):
"""Test Sentinel-3 SST L2 reader."""

@mock.patch('satpy.readers.ghrsst_l2.xr')
def setUp(self, xr_):
"""Create a fake osisaf ghrsst dataset."""
self.base_data = np.array(([-32768, 1135, 1125], [1138, 1128, 1080]))
self.sst = xr.DataArray(
self.base_data,
dims=('nj', 'ni'),
attrs={'scale_factor': 0.01, 'add_offset': 273.15,
'_FillValue': -32768, 'units': 'kelvin',
}
)
self.fake_dataset = xr.Dataset(
data_vars={
'sea_surface_temperature': self.sst,
},
attrs={
"start_time": "20220321T112640Z",
"stop_time": "20220321T145711Z",
"platform": 'NOAA20',
"sensor": "VIIRS",
},
)

@mock.patch('xarray.open_dataset')
def test_instantiate(self, mocked_dataset):
"""Test initialization of file handlers."""
filename_info = {}
tmp = MagicMock(start_time='20191120T125002Z', stop_time='20191120T125002Z')
tmp.rename.return_value = tmp
xr.open_dataset.return_value = tmp
adybbroe marked this conversation as resolved.
Show resolved Hide resolved
GHRSSTL2FileHandler('somedir/somefile.nc', filename_info, None)
mocked_dataset.assert_called()
mocked_dataset.reset_mock()

with patch('tarfile.open') as tf:
Copy link
Member

Choose a reason for hiding this comment

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

Instead of mocking, would it be possible to write such a tarfile during test setup?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I think this is a good idea. I will come back with a proposal

tf.return_value.__enter__.return_value = MagicMock(getnames=lambda *a: ["GHRSST-SSTskin.nc"])
GHRSSTL2FileHandler('somedir/somefile.tar', filename_info, None)
mocked_dataset.assert_called()
mocked_dataset.reset_mock()

@mock.patch('xarray.open_dataset')
def test_get_dataset(self, mocked_dataset):
"""Test retrieval of datasets."""
filename_info = {}
tmp = MagicMock(start_time='20191120T125002Z', stop_time='20191120T125002Z')
tmp.rename.return_value = tmp
xr.open_dataset.return_value = tmp
adybbroe marked this conversation as resolved.
Show resolved Hide resolved
test = GHRSSTL2FileHandler('somedir/somefile.nc', filename_info, None)
test.nc = {'longitude': xr.Dataset(),
'latitude': xr.Dataset(),
'sea_surface_temperature': xr.Dataset(),
'sea_ice_fraction': xr.Dataset(),
}
test.get_dataset('longitude', {'standard_name': 'longitude'})
test.get_dataset('latitude', {'standard_name': 'latitude'})
test.get_dataset('sea_surface_temperature', {'standard_name': 'sea_surface_temperature'})
test.get_dataset('sea_ice_fraction', {'standard_name': 'sea_ice_fraction'})
with self.assertRaises(KeyError):
test.get_dataset('erroneous dataset', {'standard_name': 'erroneous dataset'})
mocked_dataset.assert_called()
mocked_dataset.reset_mock()

@mock.patch('xarray.open_dataset')
def test_get_sensor(self, mocked_dataset):
"""Test retrieval of the sensor name from the netCDF file."""
mocked_dataset.return_value = self.fake_dataset
dt_valid = datetime(2022, 3, 21, 11, 26, 40) # 202203211200Z
filename_info = {'field_type': 'NARSST', 'generating_centre': 'FRA_',
'satid': 'NOAA20_', 'valid_time': dt_valid}

test = GHRSSTL2FileHandler('somedir/somefile.nc', filename_info, None)
assert test.sensor == 'viirs'

@mock.patch('xarray.open_dataset')
def test_get_start_and_end_times(self, mocked_dataset):
"""Test retrieval of the sensor name from the netCDF file."""
mocked_dataset.return_value = self.fake_dataset
dt_valid = datetime(2022, 3, 21, 11, 26, 40) # 202203211200Z
good_start_time = datetime(2022, 3, 21, 11, 26, 40) # 20220321T112640Z
good_stop_time = datetime(2022, 3, 21, 14, 57, 11) # 20220321T145711Z

filename_info = {'field_type': 'NARSST', 'generating_centre': 'FRA_',
'satid': 'NOAA20_', 'valid_time': dt_valid}

test = GHRSSTL2FileHandler('somedir/somefile.nc', filename_info, None)

assert test.start_time == good_start_time
assert test.end_time == good_stop_time