sentinel1decoder 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: sentinel1decoder
3
+ Version: 1.0.0
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: Implementation :: CPython
6
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
7
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Dist: numpy
10
+ Requires-Dist: pandas
11
+ Requires-Dist: maturin>=1.4.0 ; extra == 'dev'
12
+ Requires-Dist: pytest ; extra == 'dev'
13
+ Requires-Dist: pytest-cov ; extra == 'dev'
14
+ Requires-Dist: pre-commit ; extra == 'dev'
15
+ Requires-Dist: black==24.2 ; extra == 'dev'
16
+ Requires-Dist: flake8==7.0 ; extra == 'dev'
17
+ Requires-Dist: autoflake ; extra == 'dev'
18
+ Requires-Dist: isort==5.13.2 ; extra == 'dev'
19
+ Requires-Dist: mypy==1.8.0 ; extra == 'dev'
20
+ Requires-Dist: jupyter ; extra == 'dev'
21
+ Requires-Dist: jupyterlab ; extra == 'dev'
22
+ Requires-Dist: ipywidgets ; extra == 'dev'
23
+ Requires-Dist: scipy ; extra == 'dev'
24
+ Requires-Dist: matplotlib ; extra == 'dev'
25
+ Requires-Dist: pandas-stubs ; extra == 'dev'
26
+ Provides-Extra: dev
27
+ License-File: LICENSE
28
+ Summary: A python decoder for ESA Sentinel-1 Level0 files
29
+ License: GPL-3.0
30
+ Requires-Python: >=3.8
31
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
32
+ Project-URL: Homepage, https://github.com/Rich-Hall/sentinel1decoder
33
+
34
+ # sentinel1decoder
35
+ Python decoder for Sentinel-1 level0 files. The level0 format consists of the raw space packets downlinked from the Sentinel-1 spacecraft. This package decodes these and produces the raw I/Q sensor output from the SAR instrument, which can then be further processed to focus a SAR image. An example Jupyter notebook which runs through the process of decoding Level 0 data and forming an image is available on github [here](https://github.com/Rich-Hall/sentinel1Level0DecodingDemo) or nbviewer.org [here](https://nbviewer.org/github/Rich-Hall/sentinel1Level0DecodingDemo/blob/main/sentinel1Level0DecodingDemo.ipynb).
36
+
37
+ This code is heavily based on an implementation in C by jmfriedt which can be found [here](https://github.com/jmfriedt/sentinel1_level0).
38
+
39
+ ## Installation
40
+
41
+ In a terminal window:
42
+ ```
43
+ pip install sentinel1decoder
44
+ ```
45
+
46
+ This package requires python 3.8 or higher. [Numpy](https://numpy.org/) and [Pandas](https://pandas.pydata.org/) are also required.
47
+
48
+ ## Usage
49
+
50
+ Import the package:
51
+ ```
52
+ import sentinel1decoder
53
+ ```
54
+
55
+ The Level0File class wraps most of the below functionality, while also breaking the file into bursts of constant swath number/number of quads, to allow for easy handling.
56
+
57
+ Initialize a Level0File object:
58
+ ```
59
+ l0file = sentinel1decoder.Level0File( filename )
60
+ ```
61
+
62
+ This class contains: a dataframe containing the packet metadata:
63
+ ```
64
+ l0file.packet_metadata
65
+ ```
66
+
67
+ A dataframe containing the ephemeris:
68
+ ```
69
+ l0file.ephemeris
70
+ ```
71
+
72
+ The metadata is indexed by burst as well as packet number. Metadata on individual bursts can be accessed via:
73
+ ```
74
+ l0file.get_burst_metadata( burst )
75
+ ```
76
+
77
+ The I/Q array for each burst can be generated via:
78
+ ```
79
+ l0file.get_burst_data( burst )
80
+ ```
81
+
82
+ Importantly, this data can now be cached in an `.npy` file using:
83
+ ```
84
+ l0file.save_burst_data( burst )
85
+ ```
86
+
87
+ --------------------------------------------
88
+
89
+ The individual decoding functions can still be used:
90
+
91
+ Initialize a Level0Decoder object:
92
+ ```
93
+ decoder = sentinel1decoder.Level0Decoder( filename )
94
+ ```
95
+
96
+ Generate a Pandas dataframe containing the header information associated with the Sentinel-1 downlink packets contained in the file:
97
+ ```
98
+ df = decoder.decode_metadata()
99
+ ```
100
+
101
+ Further decode the satellite ephemeris data from the information in the packet headers:
102
+ ```
103
+ ephemeris = sentinel1decoder.utilities.read_subcommed_data(df)
104
+ ```
105
+
106
+ Extract the data payload from the data packets in the file. Takes a Pandas dataframe as an input, and only decodes packets whose header is present in the input dataframe. The intended usage of this is to allow the user to select which packets to decode, rather than having to always decode the full file. For example, to decode the first 100 packets only:
107
+ ```
108
+ selection = df.iloc[0:100]
109
+ iq_array = decoder.decode_packets(selection)
110
+ ```
111
+
@@ -0,0 +1,77 @@
1
+ # sentinel1decoder
2
+ Python decoder for Sentinel-1 level0 files. The level0 format consists of the raw space packets downlinked from the Sentinel-1 spacecraft. This package decodes these and produces the raw I/Q sensor output from the SAR instrument, which can then be further processed to focus a SAR image. An example Jupyter notebook which runs through the process of decoding Level 0 data and forming an image is available on github [here](https://github.com/Rich-Hall/sentinel1Level0DecodingDemo) or nbviewer.org [here](https://nbviewer.org/github/Rich-Hall/sentinel1Level0DecodingDemo/blob/main/sentinel1Level0DecodingDemo.ipynb).
3
+
4
+ This code is heavily based on an implementation in C by jmfriedt which can be found [here](https://github.com/jmfriedt/sentinel1_level0).
5
+
6
+ ## Installation
7
+
8
+ In a terminal window:
9
+ ```
10
+ pip install sentinel1decoder
11
+ ```
12
+
13
+ This package requires python 3.8 or higher. [Numpy](https://numpy.org/) and [Pandas](https://pandas.pydata.org/) are also required.
14
+
15
+ ## Usage
16
+
17
+ Import the package:
18
+ ```
19
+ import sentinel1decoder
20
+ ```
21
+
22
+ The Level0File class wraps most of the below functionality, while also breaking the file into bursts of constant swath number/number of quads, to allow for easy handling.
23
+
24
+ Initialize a Level0File object:
25
+ ```
26
+ l0file = sentinel1decoder.Level0File( filename )
27
+ ```
28
+
29
+ This class contains: a dataframe containing the packet metadata:
30
+ ```
31
+ l0file.packet_metadata
32
+ ```
33
+
34
+ A dataframe containing the ephemeris:
35
+ ```
36
+ l0file.ephemeris
37
+ ```
38
+
39
+ The metadata is indexed by burst as well as packet number. Metadata on individual bursts can be accessed via:
40
+ ```
41
+ l0file.get_burst_metadata( burst )
42
+ ```
43
+
44
+ The I/Q array for each burst can be generated via:
45
+ ```
46
+ l0file.get_burst_data( burst )
47
+ ```
48
+
49
+ Importantly, this data can now be cached in an `.npy` file using:
50
+ ```
51
+ l0file.save_burst_data( burst )
52
+ ```
53
+
54
+ --------------------------------------------
55
+
56
+ The individual decoding functions can still be used:
57
+
58
+ Initialize a Level0Decoder object:
59
+ ```
60
+ decoder = sentinel1decoder.Level0Decoder( filename )
61
+ ```
62
+
63
+ Generate a Pandas dataframe containing the header information associated with the Sentinel-1 downlink packets contained in the file:
64
+ ```
65
+ df = decoder.decode_metadata()
66
+ ```
67
+
68
+ Further decode the satellite ephemeris data from the information in the packet headers:
69
+ ```
70
+ ephemeris = sentinel1decoder.utilities.read_subcommed_data(df)
71
+ ```
72
+
73
+ Extract the data payload from the data packets in the file. Takes a Pandas dataframe as an input, and only decodes packets whose header is present in the input dataframe. The intended usage of this is to allow the user to select which packets to decode, rather than having to always decode the full file. For example, to decode the first 100 packets only:
74
+ ```
75
+ selection = df.iloc[0:100]
76
+ iq_array = decoder.decode_packets(selection)
77
+ ```
@@ -0,0 +1,74 @@
1
+ [build-system]
2
+ requires = ["maturin>=1,<2"]
3
+ build-backend = "maturin"
4
+
5
+ [project]
6
+ name = "sentinel1decoder"
7
+ version = "1.0.0"
8
+ description = "A python decoder for ESA Sentinel-1 Level0 files"
9
+ readme = "README.md"
10
+ license = {text = "GPL-3.0"}
11
+ classifiers = [
12
+ "Programming Language :: Rust",
13
+ "Programming Language :: Python :: Implementation :: CPython",
14
+ "Programming Language :: Python :: Implementation :: PyPy",
15
+ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
16
+ "Operating System :: OS Independent",
17
+ ]
18
+ requires-python = ">=3.8"
19
+ dependencies = [
20
+ "numpy",
21
+ "pandas",
22
+ ]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/Rich-Hall/sentinel1decoder"
26
+
27
+ [project.optional-dependencies]
28
+ dev = [
29
+ # Build system
30
+ "maturin>=1.4.0",
31
+ # Testing
32
+ "pytest",
33
+ "pytest-cov",
34
+ "pre-commit",
35
+ "black==24.2",
36
+ "flake8==7.0",
37
+ "autoflake",
38
+ "isort==5.13.2",
39
+ "mypy==1.8.0",
40
+ # Jupyter
41
+ "jupyter",
42
+ "jupyterlab",
43
+ "ipywidgets",
44
+ # Related packages useful in jupyter notebooks
45
+ "scipy",
46
+ "matplotlib",
47
+ # Type checking
48
+ "pandas-stubs",
49
+ ]
50
+
51
+ [tool.maturin]
52
+ python-source = "src"
53
+ module-name = "sentinel1decoder"
54
+ manifest-path = "rust/Cargo.toml"
55
+ manylinux = "2_34"
56
+ interpreter = ["python3.8", "python3.9", "python3.10", "python3.11"]
57
+
58
+ [tool.black]
59
+ line-length = 120
60
+
61
+ [tool.isort]
62
+ profile = "black"
63
+ multi_line_output = 3
64
+
65
+ [tool.mypy]
66
+ disallow_untyped_defs = true
67
+ disallow_incomplete_defs = true
68
+ check_untyped_defs = true
69
+ disallow_untyped_decorators = true
70
+ no_implicit_optional = true
71
+ warn_redundant_casts = true
72
+ warn_unused_ignores = true
73
+ warn_return_any = true
74
+ warn_unreachable = true
@@ -0,0 +1,164 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "autocfg"
7
+ version = "1.5.0"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
10
+
11
+ [[package]]
12
+ name = "heck"
13
+ version = "0.5.0"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
16
+
17
+ [[package]]
18
+ name = "indoc"
19
+ version = "2.0.6"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
22
+
23
+ [[package]]
24
+ name = "libc"
25
+ version = "0.2.174"
26
+ source = "registry+https://github.com/rust-lang/crates.io-index"
27
+ checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
28
+
29
+ [[package]]
30
+ name = "memoffset"
31
+ version = "0.9.1"
32
+ source = "registry+https://github.com/rust-lang/crates.io-index"
33
+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
34
+ dependencies = [
35
+ "autocfg",
36
+ ]
37
+
38
+ [[package]]
39
+ name = "once_cell"
40
+ version = "1.21.3"
41
+ source = "registry+https://github.com/rust-lang/crates.io-index"
42
+ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
43
+
44
+ [[package]]
45
+ name = "portable-atomic"
46
+ version = "1.11.1"
47
+ source = "registry+https://github.com/rust-lang/crates.io-index"
48
+ checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
49
+
50
+ [[package]]
51
+ name = "proc-macro2"
52
+ version = "1.0.95"
53
+ source = "registry+https://github.com/rust-lang/crates.io-index"
54
+ checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
55
+ dependencies = [
56
+ "unicode-ident",
57
+ ]
58
+
59
+ [[package]]
60
+ name = "pyo3"
61
+ version = "0.25.1"
62
+ source = "registry+https://github.com/rust-lang/crates.io-index"
63
+ checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a"
64
+ dependencies = [
65
+ "indoc",
66
+ "libc",
67
+ "memoffset",
68
+ "once_cell",
69
+ "portable-atomic",
70
+ "pyo3-build-config",
71
+ "pyo3-ffi",
72
+ "pyo3-macros",
73
+ "unindent",
74
+ ]
75
+
76
+ [[package]]
77
+ name = "pyo3-build-config"
78
+ version = "0.25.1"
79
+ source = "registry+https://github.com/rust-lang/crates.io-index"
80
+ checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598"
81
+ dependencies = [
82
+ "once_cell",
83
+ "target-lexicon",
84
+ ]
85
+
86
+ [[package]]
87
+ name = "pyo3-ffi"
88
+ version = "0.25.1"
89
+ source = "registry+https://github.com/rust-lang/crates.io-index"
90
+ checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c"
91
+ dependencies = [
92
+ "libc",
93
+ "pyo3-build-config",
94
+ ]
95
+
96
+ [[package]]
97
+ name = "pyo3-macros"
98
+ version = "0.25.1"
99
+ source = "registry+https://github.com/rust-lang/crates.io-index"
100
+ checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50"
101
+ dependencies = [
102
+ "proc-macro2",
103
+ "pyo3-macros-backend",
104
+ "quote",
105
+ "syn",
106
+ ]
107
+
108
+ [[package]]
109
+ name = "pyo3-macros-backend"
110
+ version = "0.25.1"
111
+ source = "registry+https://github.com/rust-lang/crates.io-index"
112
+ checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc"
113
+ dependencies = [
114
+ "heck",
115
+ "proc-macro2",
116
+ "pyo3-build-config",
117
+ "quote",
118
+ "syn",
119
+ ]
120
+
121
+ [[package]]
122
+ name = "quote"
123
+ version = "1.0.40"
124
+ source = "registry+https://github.com/rust-lang/crates.io-index"
125
+ checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
126
+ dependencies = [
127
+ "proc-macro2",
128
+ ]
129
+
130
+ [[package]]
131
+ name = "sentinel1decoder"
132
+ version = "1.0.0"
133
+ dependencies = [
134
+ "pyo3",
135
+ ]
136
+
137
+ [[package]]
138
+ name = "syn"
139
+ version = "2.0.104"
140
+ source = "registry+https://github.com/rust-lang/crates.io-index"
141
+ checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
142
+ dependencies = [
143
+ "proc-macro2",
144
+ "quote",
145
+ "unicode-ident",
146
+ ]
147
+
148
+ [[package]]
149
+ name = "target-lexicon"
150
+ version = "0.13.2"
151
+ source = "registry+https://github.com/rust-lang/crates.io-index"
152
+ checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a"
153
+
154
+ [[package]]
155
+ name = "unicode-ident"
156
+ version = "1.0.18"
157
+ source = "registry+https://github.com/rust-lang/crates.io-index"
158
+ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
159
+
160
+ [[package]]
161
+ name = "unindent"
162
+ version = "0.2.4"
163
+ source = "registry+https://github.com/rust-lang/crates.io-index"
164
+ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
@@ -0,0 +1,15 @@
1
+ [package]
2
+ name = "sentinel1decoder"
3
+ version = "1.0.0"
4
+ edition = "2021"
5
+
6
+ [lib]
7
+ name = "sentinel1decoder"
8
+ crate-type = ["cdylib"]
9
+
10
+ [dependencies]
11
+ pyo3 = { version = "0.25.1", features = ["extension-module"] }
12
+
13
+ [package.metadata.maturin]
14
+ # Optional: set the Python package name if different from the Rust crate name
15
+ # name = "sentinel1decoder"
@@ -0,0 +1,14 @@
1
+ use pyo3::prelude::*;
2
+
3
+ /// Formats the sum of two numbers as string.
4
+ #[pyfunction]
5
+ fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
6
+ Ok((a + b).to_string())
7
+ }
8
+
9
+ /// A Python module implemented in Rust.
10
+ #[pymodule]
11
+ fn sentinel1decoder(m: &Bound<'_, PyModule>) -> PyResult<()> {
12
+ m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
13
+ Ok(())
14
+ }
@@ -0,0 +1,14 @@
1
+ """Sentinel-1 decoder package."""
2
+
3
+ from . import constants, utilities
4
+ from .l0decoder import Level0Decoder
5
+ from .l0file import Level0File
6
+
7
+ __version__ = "0.1"
8
+
9
+ __all__ = [
10
+ "Level0Decoder",
11
+ "Level0File",
12
+ "utilities",
13
+ "constants",
14
+ ]
@@ -0,0 +1,100 @@
1
+ import math
2
+
3
+ import numpy as np
4
+
5
+
6
+ def _ten_bit_unsigned_to_signed_int(ten_bit: int) -> int:
7
+ """
8
+ Convert a ten-bit unsigned int to a standard signed int.
9
+
10
+ Args:
11
+ ten_bit: Raw ten-bit int extracted from packet.
12
+
13
+ Returns:
14
+ A standard signed integer
15
+ """
16
+ # First bit is the sign, remaining 9 encode the number
17
+ sign = int((-1) ** ((ten_bit >> 9) & 0x1))
18
+ return sign * (ten_bit & 0x1FF)
19
+
20
+
21
+ class BypassDecoder:
22
+ """Decode user data format type A and B (“Bypass” or “Decimation Only”)."""
23
+
24
+ def __init__(self, data: bytes, num_quads: int) -> None:
25
+ self._data = data
26
+ self._num_quads = num_quads
27
+
28
+ _num_words = math.ceil((10 / 16) * num_quads) # No. of 16-bit words per channel
29
+ self._num_bytes = 2 * _num_words # No. of 8-bit bytes per channel
30
+
31
+ self._i_evens = self._process_channel(0)
32
+ self._i_odds = self._process_channel(self._num_bytes)
33
+ self._q_evens = self._process_channel(2 * self._num_bytes)
34
+ self._q_odds = self._process_channel(3 * self._num_bytes)
35
+
36
+ @property
37
+ def i_evens(self) -> np.ndarray:
38
+ return self._i_evens
39
+
40
+ @property
41
+ def i_odds(self) -> np.ndarray:
42
+ return self._i_odds
43
+
44
+ @property
45
+ def q_evens(self) -> np.ndarray:
46
+ return self._q_evens
47
+
48
+ @property
49
+ def q_odds(self) -> np.ndarray:
50
+ return self._q_odds
51
+
52
+ def _process_channel(self, start_8bit_index: int) -> np.ndarray:
53
+ """Process a single channel's data.
54
+
55
+ Python doesn't have an easy way of extracting 10-bit integers.
56
+ We're going to read in sets of five normal 8-bit bytes, and extract four
57
+ 10-bit words per set. We'll need to track the indexing separately and
58
+ check for the end of the file each time.
59
+
60
+ Args:
61
+ channel_name: The name of the channel to process.
62
+ output_array: The array to store the processed data.
63
+ start_8bit_index: The starting index of the 8-bit bytes to process.
64
+ """
65
+ index_8bit = start_8bit_index
66
+ index_10bit = 0
67
+ output_array = np.zeros(self._num_quads, dtype=int)
68
+
69
+ while index_10bit < self._num_quads:
70
+ if index_10bit < self._num_quads:
71
+ s_code = (self._data[index_8bit] << 2 | self._data[index_8bit + 1] >> 6) & 1023
72
+ output_array[index_10bit] = _ten_bit_unsigned_to_signed_int(s_code)
73
+ index_10bit += 1
74
+ else:
75
+ break
76
+
77
+ if index_10bit < self._num_quads:
78
+ s_code = (self._data[index_8bit + 1] << 4 | self._data[index_8bit + 2] >> 4) & 1023
79
+ output_array[index_10bit] = _ten_bit_unsigned_to_signed_int(s_code)
80
+ index_10bit += 1
81
+ else:
82
+ break
83
+
84
+ if index_10bit < self._num_quads:
85
+ s_code = (self._data[index_8bit + 2] << 6 | self._data[index_8bit + 3] >> 2) & 1023
86
+ output_array[index_10bit] = _ten_bit_unsigned_to_signed_int(s_code)
87
+ index_10bit += 1
88
+ else:
89
+ break
90
+
91
+ if index_10bit < self._num_quads:
92
+ s_code = (self._data[index_8bit + 3] << 8 | self._data[index_8bit + 4] >> 0) & 1023
93
+ output_array[index_10bit] = _ten_bit_unsigned_to_signed_int(s_code)
94
+ index_10bit += 1
95
+ else:
96
+ break
97
+
98
+ index_8bit += 5
99
+
100
+ return output_array