Coverage for /home/runner/work/torchcvnn/torchcvnn/src/torchcvnn/datasets/alos2/sar_image.py: 0%
59 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-13 08:53 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-13 08:53 +0000
1# MIT License
3# Copyright (c) 2024 Jeremy Fix
5# Permission is hereby granted, free of charge, to any person obtaining a copy
6# of this software and associated documentation files (the "Software"), to deal
7# in the Software without restriction, including without limitation the rights
8# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9# copies of the Software, and to permit persons to whom the Software is
10# furnished to do so, subject to the following conditions:
12# The above copyright notice and this permission notice shall be included in
13# all copies or substantial portions of the Software.
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21# SOFTWARE.
23# Standard imports
24import struct
26# External imports
27import numpy as np
29# Local imports
30from . import parse_utils
32# Format described p84 of
33# https://www.eorc.jaxa.jp/ALOS/en/alos-2/pdf/product_format_description/PALSAR-2_xx_Format_CEOS_E_g.pdf
34# A SAR Image file contains
35# - A File descriptor with 720 bytes
36# - For L1.1 : Signal data
38descriptor_format = [
39 ("format_control_document_id", 16, 12, "A", "CEOS-SAR "),
40 ("file_id", 48, 16, "A", None),
41 ("number_data_records", 180, 6, "I", None),
42 ("sar_data_record_length", 186, 6, "I", None),
43 ("bit_length_per_sample", 216, 4, "I", None),
44 ("num_samples_per_data_group", 220, 4, "I", None),
45 ("num_bytes_per_data_group", 224, 4, "I", None),
46 ("number_bytes_prefix_data", 276, 4, "I", None), # 544 for L1.1, 192 for L1.5/3.1
47 ("number_bytes_data_record", 280, 8, "I", None),
48 ("number_bytes_suffix_data", 288, 4, "I", None), # 0
49 ("sample_data_number_locator", 296, 8, "A", None),
50 ("sar_data_format_type", 428, 4, "A", None), # e.g L1.1 'C*8b'
51 ("scanscar_num_burst", 448, 4, "I", None),
52 ("scanscar_num_lines_per_burst", 452, 4, "I", None),
53 ("scanscar_burst_information", 456, 4, "I", None),
54]
55descriptor_record_length = 720
58data_records_format = [
59 ("record_sequence_number", 0, 4, "B", None),
60 ("record_type_code", 5, 1, "B", 10), # To check we are aligned with the file format
61 ("record_length", 8, 4, "B", None),
62 ("sar_image_data_line_number", 12, 4, "B", None),
63 ("num_left_fill_pixels", 20, 4, "B", 0), # No left fill in 1.1, hence 0 is expected
64 ("count_data_pixels", 24, 4, "B", None),
65 (
66 "num_right_fill_pixels",
67 28,
68 4,
69 "B",
70 0,
71 ), # No right fill in 1.1, hence 0 is expected
72 ("transmitted_pulse_polarization", 52, 2, "B", None), # either H(0) or V(1)
73 ("received_pulse_polarization", 54, 2, "B", None), # either H(0) or V(1)
74 ("chirp_length", 68, 4, "B", None),
75 ("chirp_constant_coefficient", 72, 4, "B", None),
76 ("chirp_linear_coefficient", 76, 4, "B", None),
77 ("chirp_quadratic_coefficient", 80, 4, "B", None),
78 ("receiver_gain", 92, 4, "B", None),
79 ("slant_range,_first_data", 116, 4, "B", None),
80 ("latitude_first", 192, 4, "B", None), # 1/1,000,000 deg
81 ("latitude_last", 200, 4, "B", None), # 1/1,000,000 deg
82 ("longitude_first", 204, 4, "B", None), # 1/1,000,000 deg
83 ("longitude_last", 212, 4, "B", None), # 1/1,000,000 deg
84 ("ALOS2_frame_number", 284, 4, "B", 0),
85 ("auxiliary_data", 288, 256, "B", 0),
86]
87data_record_header_length = 544
90def parse_image_data(fh, base_offset, number_records):
91 # For some reasons, the following which I expect to be faster
92 # is taking much more memory than the line by line approach below
93 # print("Parsing image data")
94 # data_bytes = fh.read(number_records * number_pixels * 8)
95 # datas = struct.unpack(">" + ("i" * number_records * number_pixels * 2), data_bytes)
96 # cplx_datas = [real + 1j * imag for (real, imag) in zip(datas[0::2], datas[1::2])]
97 # del datas
99 # print("Cplx data")
100 # datas = np.array(cplx_datas).reshape(number_records, number_pixels) / 2**16
101 # del cplx_datas
102 # print("As numpy")
104 lines = []
105 record_info = {}
106 for i in range(number_records):
107 # print(base_offset)
108 # Read the header and shift the file pointer
109 base_offset = parse_utils.parse_from_format(
110 fh,
111 record_info,
112 data_records_format,
113 1,
114 data_record_header_length,
115 base_offset,
116 )
117 assert i == (record_info["sar_image_data_line_number"] - 1)
119 number_pixels = record_info["count_data_pixels"]
121 fh.seek(base_offset)
122 data_bytes = fh.read(number_pixels * 8)
123 # TODO: is that the correct unpacking format ?!
124 datas = struct.unpack(">" + ("f" * (number_pixels * 2)), data_bytes)
126 cplx_datas = [real + 1j * imag for (real, imag) in zip(datas[::2], datas[1::2])]
127 cplx_datas = np.array(cplx_datas)
129 lines.append(cplx_datas)
131 # Shift the base_offset to the next record header beginning
132 base_offset += 8 * number_pixels
134 datas = np.array(lines)
136 return datas
139class SARImage:
140 r"""
141 Processing of the SAR Image
142 """
144 def __init__(self, filepath):
145 self.descriptor_records = {}
146 self.data_records = {}
147 self.filepath = filepath
149 with open(filepath, "rb") as fh:
150 fh_offset = 0
151 fh_offset = parse_utils.parse_from_format(
152 fh,
153 self.descriptor_records,
154 descriptor_format,
155 1,
156 descriptor_record_length,
157 fh_offset,
158 )
160 # Read the header of the first record
161 fh_offset = parse_utils.parse_from_format(
162 fh,
163 self.data_records,
164 data_records_format,
165 1,
166 data_record_header_length,
167 fh_offset,
168 )
170 @property
171 def num_rows(self):
172 return self.descriptor_records["number_data_records"]
174 @property
175 def num_cols(self):
176 return self.data_records["count_data_pixels"]
178 def read_patch(self, start_line, num_lines, start_col, num_cols):
179 number_of_pixels_per_line = self.data_records["count_data_pixels"]
180 base_offset = (
181 descriptor_record_length
182 + (data_record_header_length + 8 * number_of_pixels_per_line) * start_line
183 )
184 lines = []
185 with open(self.filepath, "rb") as fh:
186 for i in range(num_lines):
187 offset = base_offset + i * (
188 data_record_header_length + 8 * number_of_pixels_per_line
189 )
191 # Move the file descriptor to the beginning of the record
192 fh.seek(offset)
194 # And read/decode the record header
195 record = {}
196 parse_utils.parse_from_format(
197 fh,
198 record,
199 data_records_format,
200 1,
201 data_record_header_length,
202 offset,
203 )
205 # Move the fh up to the columns to read
206 fh.seek(8 * start_col, 1) # whence=1 for relative shift
207 data_bytes = fh.read(num_cols * 8)
208 datas = struct.unpack(">" + ("f" * (num_cols * 2)), data_bytes)
209 cplx_datas = [
210 real + 1j * imag for (real, imag) in zip(datas[::2], datas[1::2])
211 ]
212 cplx_datas = np.array(cplx_datas)
213 lines.append(cplx_datas)
214 return np.array(lines)
216 def __repr__(self):
217 descriptor_txt = parse_utils.format_dictionary(self.descriptor_records, 1)
218 data_txt = parse_utils.format_dictionary(self.data_records, 1)
219 return f"""
220Descriptor :
221{descriptor_txt}
223Data:
224{data_txt}
225 """