|
19 | 19 | """The HRIT msg reader tests package.""" |
20 | 20 |
|
21 | 21 | import datetime as dt |
| 22 | +import os |
22 | 23 | import unittest |
| 24 | +import warnings |
| 25 | +import zipfile |
23 | 26 | from unittest import mock |
24 | 27 |
|
| 28 | +import fsspec |
25 | 29 | import numpy as np |
26 | 30 | import pytest |
27 | 31 | import xarray as xr |
28 | 32 | from numpy import testing as npt |
29 | 33 | from pyproj import CRS |
30 | 34 |
|
31 | 35 | import satpy.tests.reader_tests.test_seviri_l1b_hrit_setup as setup |
| 36 | +from satpy.readers import FSFile |
32 | 37 | from satpy.readers.seviri_l1b_hrit import HRITMSGEpilogueFileHandler, HRITMSGFileHandler, HRITMSGPrologueFileHandler |
33 | 38 | from satpy.tests.reader_tests.test_seviri_base import ORBIT_POLYNOMIALS_INVALID |
34 | 39 | from satpy.tests.reader_tests.test_seviri_l1b_calibration import TestFileHandlerCalibrationBase |
@@ -502,3 +507,173 @@ def test_mask_bad_quality(self, file_handler): |
502 | 507 | new_data[:, :] = np.nan |
503 | 508 | expected = expected.copy(data=new_data) |
504 | 509 | xr.testing.assert_equal(res, expected) |
| 510 | + |
| 511 | + |
| 512 | +@pytest.fixture(scope="session") |
| 513 | +def prologue_file(session_tmp_path, prologue_header_contents): |
| 514 | + """Create a dummy prologue file.""" |
| 515 | + from satpy.readers.seviri_l1b_native_hdr import hrit_prologue |
| 516 | + header = prologue_header_contents |
| 517 | + contents = np.void(1, dtype=hrit_prologue) |
| 518 | + contents["SatelliteStatus"]["SatelliteDefinition"]["SatelliteId"] = 324 |
| 519 | + return create_file(session_tmp_path / "prologue", header + [contents]) |
| 520 | + |
| 521 | + |
| 522 | +@pytest.fixture(scope="session") |
| 523 | +def prologue_header_contents(): |
| 524 | + """Get the contents of the header.""" |
| 525 | + return [ |
| 526 | + # prime header |
| 527 | + np.void((0, 16), dtype=[("hdr_id", "u1"), ("record_length", ">u2")]), |
| 528 | + np.void((128, 90, 3403688), |
| 529 | + dtype=[("file_type", "u1"), ("total_header_length", ">u4"), ("data_field_length", ">u8")]), |
| 530 | + # second header |
| 531 | + np.void((4, 64), dtype=[("hdr_id", "u1"), ("record_length", ">u2")]), |
| 532 | + np.array(b"H-000-MSG4__-MSG4________-_________-PRO______-201802281500-__", dtype="|S61"), |
| 533 | + # timestamp record |
| 534 | + np.void((5, 10), dtype=[("hdr_id", "u1"), ("record_length", ">u2")]), |
| 535 | + np.void((64, (21973, 54911033)), |
| 536 | + dtype=[("cds_p_field", "u1"), ("timestamp", [("Days", ">u2"), ("Milliseconds", ">u4")])]) |
| 537 | + ] |
| 538 | + |
| 539 | + |
| 540 | +@pytest.fixture(scope="session") |
| 541 | +def epilogue_file(session_tmp_path, epilogue_header_contents): |
| 542 | + """Create a dummy epilogue file.""" |
| 543 | + from satpy.readers.seviri_l1b_native_hdr import hrit_epilogue |
| 544 | + header = epilogue_header_contents |
| 545 | + contents = np.void(1, dtype=hrit_epilogue) |
| 546 | + return create_file(session_tmp_path / "epilogue", header + [contents]) |
| 547 | + |
| 548 | + |
| 549 | +@pytest.fixture(scope="session") |
| 550 | +def epilogue_header_contents(): |
| 551 | + """Get the contents of the header.""" |
| 552 | + return [ |
| 553 | + np.void((0, 16), dtype=[("hdr_id", "u1"), ("record_length", ">u2")]), |
| 554 | + np.void((129, 90, 3042600), |
| 555 | + dtype=[("file_type", "u1"), ("total_header_length", ">u4"), ("data_field_length", ">u8")]), |
| 556 | + np.void((4, 64), dtype=[("hdr_id", "u1"), ("record_length", ">u2")]), |
| 557 | + np.array(b"H-000-MSG4__-MSG4________-_________-EPI______-201802281500-__", dtype="|S61"), |
| 558 | + np.void((5, 10), dtype=[("hdr_id", "u1"), ("record_length", ">u2")]), |
| 559 | + np.void((64, (21973, 54911033)), |
| 560 | + dtype=[("cds_p_field", "u1"), ("timestamp", [("Days", ">u2"), ("Milliseconds", ">u4")])]), |
| 561 | + ] |
| 562 | + |
| 563 | + |
| 564 | +def create_file(filename, file_contents): |
| 565 | + """Create an hrit file.""" |
| 566 | + with open(filename, "wb") as fh: |
| 567 | + for array in file_contents: |
| 568 | + array.tofile(fh) |
| 569 | + return filename |
| 570 | + |
| 571 | + |
| 572 | +@pytest.fixture(scope="session") |
| 573 | +def segment_file(session_tmp_path): |
| 574 | + """Create a segment_file.""" |
| 575 | + cols = 3712 |
| 576 | + lines = 464 |
| 577 | + bpp = 10 |
| 578 | + header = [ |
| 579 | + np.void((0, 16), dtype=[("hdr_id", "u1"), ("record_length", ">u2")]), |
| 580 | + np.void((0, 6198, 17223680), dtype=[("file_type", "u1"), ("total_header_length", ">u4"), |
| 581 | + ("data_field_length", ">u8")]), |
| 582 | + np.void((1, 9), dtype=[("hdr_id", "u1"), ("record_length", ">u2")]), |
| 583 | + np.void((bpp, cols, lines, 0), dtype=[("number_of_bits_per_pixel", "u1"), ("number_of_columns", ">u2"), |
| 584 | + ("number_of_lines", ">u2"), ("compression_flag_for_data", "u1")]), |
| 585 | + np.void((2, 51), dtype=[("hdr_id", "u1"), ("record_length", ">u2")]), |
| 586 | + np.void((b"GEOS(+000.0) ", -13642337, -13642337, 1856, 1856), |
| 587 | + dtype=[("projection_name", "S32"), |
| 588 | + ("cfac", ">i4"), ("lfac", ">i4"), |
| 589 | + ("coff", ">i4"), ("loff", ">i4")]), |
| 590 | + np.void((4, 64), dtype=[("hdr_id", "u1"), ("record_length", ">u2")]), |
| 591 | + np.array(b"H-000-MSG4__-MSG4________-VIS008___-000001___-201802281500-__", dtype="|S61"), |
| 592 | + np.void((5, 10), dtype=[("hdr_id", "u1"), ("record_length", ">u2")]), |
| 593 | + np.void((64, (21973, 54911033)), dtype=[("cds_p_field", "u1"), ("timestamp", [("Days", ">u2"), |
| 594 | + ("Milliseconds", ">u4")])]), |
| 595 | + np.void((128, 13), dtype=[("hdr_id", "u1"), ("record_length", ">u2")]), |
| 596 | + np.void((324, 2, 1, 1, 8, 0), dtype=[("GP_SC_ID", ">i2"), ("spectral_channel_id", "i1"), |
| 597 | + ("segment_sequence_number", ">u2"), |
| 598 | + ("planned_start_segment_number", ">u2"), |
| 599 | + ("planned_end_segment_number", ">u2"), |
| 600 | + ("data_field_representation", "i1")]), |
| 601 | + np.void((129, 6035), dtype=[("hdr_id", "u1"), ("record_length", ">u2")]), |
| 602 | + np.zeros((464, ), dtype=[("line_number_in_grid", ">i4"), |
| 603 | + ("line_mean_acquisition", [("days", ">u2"), ("milliseconds", ">u4")]), |
| 604 | + ("line_validity", "u1"), ("line_radiometric_quality", "u1"), |
| 605 | + ("line_geometric_quality", "u1")]), |
| 606 | + ] |
| 607 | + contents = np.empty(cols * lines * bpp // 8, dtype="u1") |
| 608 | + |
| 609 | + return create_file(session_tmp_path / "segment", header + [contents]) |
| 610 | + |
| 611 | + |
| 612 | +def test_read_real_segment(prologue_file, epilogue_file, segment_file): |
| 613 | + """Test reading an hrit segment.""" |
| 614 | + info = dict(start_time=dt.datetime(2018, 2, 28, 15, 0), service="") |
| 615 | + prologue_fh = HRITMSGPrologueFileHandler(prologue_file, info, dict()) |
| 616 | + epilogue_fh = HRITMSGEpilogueFileHandler(epilogue_file, info, dict()) |
| 617 | + with warnings.catch_warnings(): |
| 618 | + warnings.filterwarnings("ignore", category=UserWarning, message="No orbit polynomial valid") |
| 619 | + filehandler = HRITMSGFileHandler(segment_file, info, dict(), prologue_fh, epilogue_fh) |
| 620 | + res = filehandler.get_dataset(dict(name="VIS008", calibration="counts"), |
| 621 | + dict(units="", wavelength=0.8, standard_name="counts")) |
| 622 | + res.compute() |
| 623 | + |
| 624 | + |
| 625 | +@pytest.fixture(scope="session") |
| 626 | +def compressed_seviri_hrit_files(session_tmp_path, prologue_file, epilogue_file, segment_file): |
| 627 | + """Return the fsspec paths to the given seviri hrit files inside a zip file.""" |
| 628 | + zip_full_path = session_tmp_path / "test_seviri_hrit.zip" |
| 629 | + with zipfile.ZipFile(zip_full_path, mode="w") as archive: |
| 630 | + for filename in (prologue_file, epilogue_file, segment_file): |
| 631 | + archive.write(filename, os.path.basename(filename)) |
| 632 | + return {hrit_file: f"zip://{hrit_file}::file://{zip_full_path.as_posix()}" |
| 633 | + for hrit_file in ("prologue", "epilogue", "segment")} |
| 634 | + |
| 635 | +def test_read_real_segment_zipped(compressed_seviri_hrit_files): |
| 636 | + """Test reading a remote hrit segment passed as FSFile.""" |
| 637 | + info = dict(start_time=dt.datetime(2018, 2, 28, 15, 0), service="") |
| 638 | + prologue = FSFile(fsspec.open(compressed_seviri_hrit_files["prologue"])) |
| 639 | + prologue_fh = HRITMSGPrologueFileHandler(prologue, info, dict()) |
| 640 | + epilogue = FSFile(fsspec.open(compressed_seviri_hrit_files["epilogue"])) |
| 641 | + epilogue_fh = HRITMSGEpilogueFileHandler(epilogue, info, dict()) |
| 642 | + segment = FSFile(fsspec.open(compressed_seviri_hrit_files["segment"])) |
| 643 | + with warnings.catch_warnings(): |
| 644 | + warnings.filterwarnings("ignore", category=UserWarning, message="No orbit polynomial valid") |
| 645 | + filehandler = HRITMSGFileHandler(segment, info, dict(), prologue_fh, epilogue_fh) |
| 646 | + res = filehandler.get_dataset(dict(name="VIS008", calibration="counts"), |
| 647 | + dict(units="", wavelength=0.8, standard_name="counts")) |
| 648 | + res.compute() |
| 649 | + |
| 650 | + |
| 651 | +def to_upath(fsfile): |
| 652 | + """Convert FSFile instance to UPath.""" |
| 653 | + from upath import UPath |
| 654 | + fsfile_fs = fsfile.fs.to_dict() |
| 655 | + fsfile_fs.pop("cls") |
| 656 | + path = UPath(os.fspath(fsfile), **fsfile_fs) |
| 657 | + return path |
| 658 | + |
| 659 | + |
| 660 | +def test_read_real_segment_zipped_with_upath(compressed_seviri_hrit_files): |
| 661 | + """Test reading a remote hrit segment passed as UPath.""" |
| 662 | + info = dict(start_time=dt.datetime(2018, 2, 28, 15, 0), service="") |
| 663 | + |
| 664 | + prologue = FSFile(fsspec.open(compressed_seviri_hrit_files["prologue"])) |
| 665 | + prologue = to_upath(prologue) |
| 666 | + prologue_fh = HRITMSGPrologueFileHandler(prologue, info, dict()) |
| 667 | + |
| 668 | + epilogue = FSFile(fsspec.open(compressed_seviri_hrit_files["epilogue"])) |
| 669 | + epilogue = to_upath(epilogue) |
| 670 | + epilogue_fh = HRITMSGEpilogueFileHandler(epilogue, info, dict()) |
| 671 | + |
| 672 | + segment = FSFile(fsspec.open(compressed_seviri_hrit_files["segment"])) |
| 673 | + segment = to_upath(segment) |
| 674 | + with warnings.catch_warnings(): |
| 675 | + warnings.filterwarnings("ignore", category=UserWarning, message="No orbit polynomial valid") |
| 676 | + filehandler = HRITMSGFileHandler(segment, info, dict(), prologue_fh, epilogue_fh) |
| 677 | + res = filehandler.get_dataset(dict(name="VIS008", calibration="counts"), |
| 678 | + dict(units="", wavelength=0.8, standard_name="counts")) |
| 679 | + res.compute() |
0 commit comments