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

1# MIT License 

2 

3# Copyright (c) 2024 Jeremy Fix 

4 

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: 

11 

12# The above copyright notice and this permission notice shall be included in 

13# all copies or substantial portions of the Software. 

14 

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. 

22 

23# Standard imports 

24import struct 

25 

26# External imports 

27import numpy as np 

28 

29# Local imports 

30from . import parse_utils 

31 

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 

37 

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 

56 

57 

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 

88 

89 

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 

98 

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") 

103 

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) 

118 

119 number_pixels = record_info["count_data_pixels"] 

120 

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) 

125 

126 cplx_datas = [real + 1j * imag for (real, imag) in zip(datas[::2], datas[1::2])] 

127 cplx_datas = np.array(cplx_datas) 

128 

129 lines.append(cplx_datas) 

130 

131 # Shift the base_offset to the next record header beginning 

132 base_offset += 8 * number_pixels 

133 

134 datas = np.array(lines) 

135 

136 return datas 

137 

138 

139class SARImage: 

140 r""" 

141 Processing of the SAR Image 

142 """ 

143 

144 def __init__(self, filepath): 

145 self.descriptor_records = {} 

146 self.data_records = {} 

147 self.filepath = filepath 

148 

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 ) 

159 

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 ) 

169 

170 @property 

171 def num_rows(self): 

172 return self.descriptor_records["number_data_records"] 

173 

174 @property 

175 def num_cols(self): 

176 return self.data_records["count_data_pixels"] 

177 

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 ) 

190 

191 # Move the file descriptor to the beginning of the record 

192 fh.seek(offset) 

193 

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 ) 

204 

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) 

215 

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} 

222 

223Data: 

224{data_txt} 

225 """