hsi-preprocessing-toolkit 1.1.2.dev7__py3-none-any.whl
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.
- hsi_preprocessing_toolkit/__init__.py +0 -0
- hsi_preprocessing_toolkit/__main__.py +580 -0
- hsi_preprocessing_toolkit/scanner_calc.py +38 -0
- hsi_preprocessing_toolkit-1.1.2.dev7.dist-info/METADATA +56 -0
- hsi_preprocessing_toolkit-1.1.2.dev7.dist-info/RECORD +7 -0
- hsi_preprocessing_toolkit-1.1.2.dev7.dist-info/WHEEL +4 -0
- hsi_preprocessing_toolkit-1.1.2.dev7.dist-info/entry_points.txt +3 -0
|
File without changes
|
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import shutil
|
|
3
|
+
from typing import Tuple, List
|
|
4
|
+
import einops
|
|
5
|
+
import numpy as np
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
from rasterio import open as rasterio_open
|
|
8
|
+
from scipy.io import savemat, loadmat
|
|
9
|
+
import gradio as gr
|
|
10
|
+
import gradio.utils
|
|
11
|
+
from scipy.ndimage import rotate
|
|
12
|
+
from rs_fusion_datasets.util.hsi2rgb import _hsi2rgb, hsi2rgb
|
|
13
|
+
from jaxtyping import Float
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from scanner_calc import scanner_calc_tab
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
plt.rcParams['font.family'] = 'SimHei'
|
|
19
|
+
|
|
20
|
+
i18n = gr.I18n(**{
|
|
21
|
+
'en': {
|
|
22
|
+
"title": "hsi-preprocessing-toolkit",
|
|
23
|
+
"load": "Load",
|
|
24
|
+
"upload_instructions": "**Upload one of the following formats:**\n1. One .hdr file + one raw data file without extension\n2. One .mat file",
|
|
25
|
+
"input_format": "Input Image Shape Format",
|
|
26
|
+
"data_files": "Data Files",
|
|
27
|
+
"manual_normalize": "Manual Normalize (affects preview only)",
|
|
28
|
+
"normalize_min": "Normalize Min",
|
|
29
|
+
"normalize_max": "Normalize Max",
|
|
30
|
+
"wavelength_start": "Wavelength Range Start",
|
|
31
|
+
"wavelength_end": "Wavelength Range End",
|
|
32
|
+
"processing": "Processing",
|
|
33
|
+
"crop": "Crop",
|
|
34
|
+
"top": "Top",
|
|
35
|
+
"bottom": "Bottom",
|
|
36
|
+
"left": "Left",
|
|
37
|
+
"right": "Right",
|
|
38
|
+
"rotate": "Rotate",
|
|
39
|
+
"rotate_degree": "Rotate Degree",
|
|
40
|
+
"preview": "Preview",
|
|
41
|
+
"apply_processing": "Apply Processing Effects",
|
|
42
|
+
"mat_data_type": "MAT Data Type",
|
|
43
|
+
"mat_format": "MAT Image Shape Format",
|
|
44
|
+
"mat_key": "Key of MAT file",
|
|
45
|
+
"compress_mat": "Produce Compressed MAT File",
|
|
46
|
+
"spectral_selection": "Spectral Selection",
|
|
47
|
+
"spectral_selection_help": "Click on the image to select pixels for spectral data extraction. The selected pixels will be plotted in the spectral plot below.",
|
|
48
|
+
"spectral_plot": "Spectral Plot",
|
|
49
|
+
"style": "Style",
|
|
50
|
+
"clear": "Clear",
|
|
51
|
+
"download": "Download",
|
|
52
|
+
"output_results": "Output Results",
|
|
53
|
+
"mat_file": "MAT File",
|
|
54
|
+
"info": "Info",
|
|
55
|
+
"same_as_input": "Same",
|
|
56
|
+
"auto_detect": "Auto Detect",
|
|
57
|
+
},
|
|
58
|
+
'zh-CN':{
|
|
59
|
+
"title": "高光谱图像预处理工具箱",
|
|
60
|
+
"load": "加载",
|
|
61
|
+
"upload_instructions": "**应上传以下两种格式中的一种**\n1. 同时上传一个.hdr文件 + 一个无后缀的数据文件\n2. 一个.mat文件",
|
|
62
|
+
"input_format": "输入数据形状",
|
|
63
|
+
"data_files": "数据文件",
|
|
64
|
+
"manual_normalize": "手动归一化(仅影响预览结果)",
|
|
65
|
+
"normalize_min": "归一化最小值",
|
|
66
|
+
"normalize_max": "归一化最大值",
|
|
67
|
+
"wavelength_start": "波长范围起始",
|
|
68
|
+
"wavelength_end": "波长范围结束",
|
|
69
|
+
"processing": "处理",
|
|
70
|
+
"crop": "裁切",
|
|
71
|
+
"top": "上",
|
|
72
|
+
"bottom": "下",
|
|
73
|
+
"left": "左",
|
|
74
|
+
"right": "右",
|
|
75
|
+
"rotate": "旋转",
|
|
76
|
+
"rotate_degree": "旋转角度",
|
|
77
|
+
"preview": "预览",
|
|
78
|
+
"apply_processing": "应用处理效果",
|
|
79
|
+
"mat_data_type": "mat文件数据类型",
|
|
80
|
+
"mat_format": "mat文件格式",
|
|
81
|
+
"mat_key": "mat文件的key",
|
|
82
|
+
"compress_mat": "启用mat文件压缩",
|
|
83
|
+
"spectral_selection": "光谱选择",
|
|
84
|
+
"spectral_selection_help": "点击预览图像图像中的像素进行光谱数据提取。选中的像素将在下方的光谱图中绘制。",
|
|
85
|
+
"spectral_plot": "光谱图",
|
|
86
|
+
"style": "样式",
|
|
87
|
+
"clear": "清空",
|
|
88
|
+
"download": "下载",
|
|
89
|
+
"output_results": "输出结果",
|
|
90
|
+
"mat_file": "MAT文件",
|
|
91
|
+
"info": "信息",
|
|
92
|
+
"same_as_input": "与输入相同",
|
|
93
|
+
"auto_detect": "自动检测",
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
class AppState(Enum):
|
|
98
|
+
NOT_LOADED = 0
|
|
99
|
+
LOADED = 2
|
|
100
|
+
PREVIEWED = 3
|
|
101
|
+
COVERTED = 4
|
|
102
|
+
|
|
103
|
+
# Void -> HWC
|
|
104
|
+
def load_data(
|
|
105
|
+
dat_files :List[gr.utils.NamedString] | None,
|
|
106
|
+
input_format, mat_key: str | None = None
|
|
107
|
+
) -> Tuple[np.ndarray | None, Path | None]:
|
|
108
|
+
if dat_files is None or len(dat_files) == 0:
|
|
109
|
+
raise gr.Error("No data file provided. 请上传数据文件. Please upload a data file.")
|
|
110
|
+
|
|
111
|
+
dat_paths = [Path(f.name) for f in dat_files]
|
|
112
|
+
mat_paths = [p for p in dat_paths if p.suffix == '.mat']
|
|
113
|
+
hdr_paths = [p for p in dat_paths if p.suffix == '.hdr']
|
|
114
|
+
raw_paths = [p for p in dat_paths if p.suffix == '']
|
|
115
|
+
|
|
116
|
+
if len(mat_paths) > 0:
|
|
117
|
+
data_path = mat_paths[0]
|
|
118
|
+
mat_dat = loadmat(mat_paths[0], squeeze_me=True, mat_dtype=True, struct_as_record=False)
|
|
119
|
+
mat_keys = [x for x in mat_dat.keys() if not x.startswith('__') and not x.endswith('__')]
|
|
120
|
+
if mat_key is None or mat_key == '':
|
|
121
|
+
mat_key = mat_keys[0]
|
|
122
|
+
if len(mat_keys) >= 2:
|
|
123
|
+
gr.Warning(f"Multiple keys found: {mat_keys}. Using: {mat_key}")
|
|
124
|
+
else:
|
|
125
|
+
if mat_key not in mat_dat:
|
|
126
|
+
raise gr.Error(f"Key '{mat_key}' not found in the MAT file. Available keys: {mat_keys}")
|
|
127
|
+
data = mat_dat[mat_key]
|
|
128
|
+
|
|
129
|
+
elif len(hdr_paths) == 0 or len(raw_paths) == 0:
|
|
130
|
+
raise gr.Error("Both .hdr and raw data files are required. Only one is provided.")
|
|
131
|
+
elif len(hdr_paths) > 0 and len(raw_paths) > 0:
|
|
132
|
+
data_path = raw_paths[0]
|
|
133
|
+
hdr_path = hdr_paths[0]
|
|
134
|
+
if hdr_path.parent != data_path.parent:
|
|
135
|
+
shutil.copy(hdr_path, data_path.with_suffix('.hdr'))
|
|
136
|
+
print(f"Loading {data_path}")
|
|
137
|
+
with rasterio_open(data_path, 'r') as src:
|
|
138
|
+
data = src.read()
|
|
139
|
+
print(f"Loaded {data_path}")
|
|
140
|
+
else:
|
|
141
|
+
raise gr.Error("Unknown file format")
|
|
142
|
+
|
|
143
|
+
if data is None:
|
|
144
|
+
raise gr.Error("Data loading failed.")
|
|
145
|
+
elif len(data.shape) != 3:
|
|
146
|
+
raise gr.Error(f"Data shape {data.shape} is not valid. Expected 3D array.")
|
|
147
|
+
|
|
148
|
+
if input_format == 'CHW':
|
|
149
|
+
data = einops.rearrange(data, 'c h w -> h w c')
|
|
150
|
+
|
|
151
|
+
return data, data_path
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# HWC -> HWC
|
|
155
|
+
def process_img(img, crop_top, crop_left, crop_bottom, crop_right, rotate_deg):
|
|
156
|
+
# Rotate
|
|
157
|
+
if rotate_deg % 360 != 0:
|
|
158
|
+
img = rotate(img, angle=rotate_deg, axes=(0, 1), reshape=True)
|
|
159
|
+
|
|
160
|
+
# Crop
|
|
161
|
+
if crop_top > 0 or crop_left > 0 or crop_bottom > 0 or crop_right > 0:
|
|
162
|
+
crop_bottom = None if crop_bottom == 0 else -crop_bottom
|
|
163
|
+
crop_right = None if crop_right == 0 else -crop_right
|
|
164
|
+
img = img[crop_top:crop_bottom, crop_left:crop_right, :]
|
|
165
|
+
|
|
166
|
+
return img
|
|
167
|
+
|
|
168
|
+
# def update_ui(state_app_state: AppState):
|
|
169
|
+
# gr.Info(f"App state changed to {state_app_state.name}")
|
|
170
|
+
|
|
171
|
+
# preview_visible = state_app_state in [AppState.LOADED, AppState.PREVIEWED, AppState.COVERTED]
|
|
172
|
+
# convert_visible = state_app_state in [AppState.PREVIEWED, AppState.COVERTED]
|
|
173
|
+
# plot_visible = state_app_state in [AppState.COVERTED]
|
|
174
|
+
|
|
175
|
+
# return (
|
|
176
|
+
# gr.update(visible=preview_visible),
|
|
177
|
+
# gr.update(visible=convert_visible),
|
|
178
|
+
# gr.update(visible=plot_visible)
|
|
179
|
+
# )
|
|
180
|
+
|
|
181
|
+
def gr_load(
|
|
182
|
+
dat_files :List[gradio.utils.NamedString] | None,
|
|
183
|
+
input_format, input_mat_key: str | None,
|
|
184
|
+
manual_normalize: bool, normalize_min: float, normalize_max: float,
|
|
185
|
+
wavelength_from: int, wavelength_to: int,
|
|
186
|
+
):
|
|
187
|
+
data, data_path = load_data(dat_files, input_format, input_mat_key)
|
|
188
|
+
if data is None:
|
|
189
|
+
raise gr.Error("No data file provided or data loading failed.")
|
|
190
|
+
gr.Info("Loading data...")
|
|
191
|
+
if not manual_normalize:
|
|
192
|
+
rgb = hsi2rgb(data, wavelength_range=(wavelength_from, wavelength_to), input_format='HWC', output_format='HWC', to_u8np=True)
|
|
193
|
+
else:
|
|
194
|
+
rgb = _hsi2rgb( (data-normalize_min)/(normalize_max-normalize_min), wavelength=np.linspace(wavelength_from, wavelength_to, data.shape[-1]))
|
|
195
|
+
rgb = (rgb*255.0).astype(np.uint8)
|
|
196
|
+
gr.Success(f"Data loaded")
|
|
197
|
+
logging_text = str({
|
|
198
|
+
"original_shape": str(data.shape),
|
|
199
|
+
"original_data_type": str(data.dtype),
|
|
200
|
+
"original_reflection_range": [float(data.min()), float(data.max())],
|
|
201
|
+
"original_reflection_mean": float(data.mean()),
|
|
202
|
+
"original_reflection_std": float(data.std()),
|
|
203
|
+
})
|
|
204
|
+
return rgb, data, data_path, AppState.LOADED, logging_text
|
|
205
|
+
|
|
206
|
+
def gr_preview(
|
|
207
|
+
state_rgb :Float[np.ndarray, 'h w c'] | None,
|
|
208
|
+
crop_top: int, crop_left: int, crop_bottom: int, crop_right: int,
|
|
209
|
+
rotate_deg: int
|
|
210
|
+
):
|
|
211
|
+
if state_rgb is None:
|
|
212
|
+
raise gr.Error("No data provided.")
|
|
213
|
+
return process_img(
|
|
214
|
+
state_rgb, crop_top, crop_left, crop_bottom, crop_right, rotate_deg
|
|
215
|
+
), AppState.PREVIEWED
|
|
216
|
+
|
|
217
|
+
def gr_download_selected_spectral(data, state_select_location, data_path):
|
|
218
|
+
if not state_select_location or len(state_select_location) == 0:
|
|
219
|
+
raise gr.Error("No spectral data selected. Please select at least one pixel to download")
|
|
220
|
+
|
|
221
|
+
gr.Info("Converting ...")
|
|
222
|
+
|
|
223
|
+
dat_path = Path(data_path.name)
|
|
224
|
+
result = []
|
|
225
|
+
result_location = []
|
|
226
|
+
for (row, col) in state_select_location:
|
|
227
|
+
result.append(data[row, col,:])
|
|
228
|
+
result_location.append([row, col])
|
|
229
|
+
|
|
230
|
+
result = np.array(result)
|
|
231
|
+
result_location = np.array(result_location)
|
|
232
|
+
|
|
233
|
+
mat_path = dat_path.with_stem(dat_path.stem + '_selected_spectral').with_suffix('.mat')
|
|
234
|
+
savemat(mat_path, {'data': result, 'location': result_location}, do_compression=True, format='5')
|
|
235
|
+
|
|
236
|
+
gr.Success("Done ...")
|
|
237
|
+
|
|
238
|
+
return str(mat_path)
|
|
239
|
+
|
|
240
|
+
def gr_convert(
|
|
241
|
+
data, data_path,
|
|
242
|
+
crop_top: int, crop_left: int, crop_bottom: int, crop_right: int,
|
|
243
|
+
rotate_deg: int,
|
|
244
|
+
mat_dtype, output_format, mat_key, compress_mat: bool,
|
|
245
|
+
logging_text: str
|
|
246
|
+
):
|
|
247
|
+
if output_format == 'same':
|
|
248
|
+
output_format = input_format
|
|
249
|
+
|
|
250
|
+
data = process_img(
|
|
251
|
+
data, crop_top, crop_left, crop_bottom, crop_right, rotate_deg
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# Convert to mat
|
|
255
|
+
mat_file = data_path.with_stem(data_path.stem + '_converted').with_suffix('.mat')
|
|
256
|
+
if mat_dtype == "default":
|
|
257
|
+
data_processed = data
|
|
258
|
+
else:
|
|
259
|
+
data_processed = data.astype(mat_dtype)
|
|
260
|
+
|
|
261
|
+
if output_format == 'CHW':
|
|
262
|
+
mat_dat_sav = einops.rearrange(data_processed, 'h w c -> c h w')
|
|
263
|
+
else:
|
|
264
|
+
mat_dat_sav = data_processed
|
|
265
|
+
savemat(mat_file, {mat_key: mat_dat_sav}, do_compression=compress_mat, format='5')
|
|
266
|
+
|
|
267
|
+
info = {
|
|
268
|
+
'original_shape': str(data.shape),
|
|
269
|
+
'original_data_type': str(data.dtype),
|
|
270
|
+
'original_reflection_range': [float(data.min()), float(data.max())],
|
|
271
|
+
'original_reflection_mean': float(data.mean()),
|
|
272
|
+
'original_reflection_std': float(data.std()),
|
|
273
|
+
'output_shape': str(mat_dat_sav.shape),
|
|
274
|
+
'output_data_type': str(mat_dat_sav.dtype),
|
|
275
|
+
'output_reflection_range': [float(mat_dat_sav.min()), float(mat_dat_sav.max())],
|
|
276
|
+
'output_reflection_mean': float(mat_dat_sav.mean()),
|
|
277
|
+
'output_reflection_std': float(mat_dat_sav.std()),
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
logging_text += "\n".join([f"{k}: {v}" for k, v in info.items()]) + "\n"
|
|
281
|
+
|
|
282
|
+
# Gradio will handle the visibility of mat_file_output when a file path is returned
|
|
283
|
+
return data_processed, str(mat_file), logging_text, AppState.COVERTED
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def gr_on_img_clicked(evt: gr.SelectData, state_figure :tuple, data, state_select_location, wavelength_from: int, wavelength_to :int, plot_hint: str):
|
|
287
|
+
"""绘制选中像素的光谱曲线"""
|
|
288
|
+
if data is None:
|
|
289
|
+
raise gr.Error("No converted data provided")
|
|
290
|
+
if state_select_location is None:
|
|
291
|
+
state_select_location = []
|
|
292
|
+
col, row = evt.index
|
|
293
|
+
state_select_location.append((row, col)) # WHY?
|
|
294
|
+
|
|
295
|
+
wavelengths = np.linspace(wavelength_from, wavelength_to, data.shape[-1])
|
|
296
|
+
# spectral_plot = pd.DataFrame(
|
|
297
|
+
# {
|
|
298
|
+
# "Wavelength": wavelengths,
|
|
299
|
+
# "Reflectance": data[row, col, :]
|
|
300
|
+
# }
|
|
301
|
+
# ) # typecheck: ignore
|
|
302
|
+
|
|
303
|
+
# fig, ax = plt.subplots(figsize=(10, 6))
|
|
304
|
+
if state_figure is None:
|
|
305
|
+
state_figure = plt.subplots(figsize=(10, 6))
|
|
306
|
+
fig, ax = state_figure
|
|
307
|
+
# for (row, col) in state_select_location:
|
|
308
|
+
# ax.plot(wavelengths, data[row, col, :], plot_hint ,linewidth=2)
|
|
309
|
+
ax.plot(wavelengths, data[row, col, :], plot_hint ,linewidth=2)
|
|
310
|
+
ax.set_xlabel('Wavelength')
|
|
311
|
+
ax.set_ylabel('Reflectance')
|
|
312
|
+
plt.tight_layout()
|
|
313
|
+
state_figure = (fig, ax)
|
|
314
|
+
return fig, state_figure, state_select_location
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
if __name__ == "__main__":
|
|
318
|
+
theme = gr.themes.Default(primary_hue='cyan').set(
|
|
319
|
+
button_primary_background_fill='#39c5bb',
|
|
320
|
+
button_primary_background_fill_hover="#30A8A0",
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
with gr.Blocks(title="hsi-preprocessing-toolkit", theme=theme) as demo:
|
|
324
|
+
state_app_state = gr.State(value=AppState.NOT_LOADED)
|
|
325
|
+
state_original_data = gr.State(value=None)
|
|
326
|
+
state_processed_data = gr.State(value=None)
|
|
327
|
+
state_data_path = gr.State(value=None,)
|
|
328
|
+
state_selected_location = gr.State(value=[])
|
|
329
|
+
state_original_rgb = gr.State(value=None)
|
|
330
|
+
state_spectral_figure = gr.State(value=None)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
with gr.Tab(i18n("title"), id="hsi_preprocessing_toolkit"):
|
|
334
|
+
with gr.Row():
|
|
335
|
+
with gr.Column():
|
|
336
|
+
with gr.Accordion(i18n("load")) as load_panel:
|
|
337
|
+
gr.Markdown(i18n("upload_instructions"))
|
|
338
|
+
with gr.Row():
|
|
339
|
+
input_format = gr.Radio(
|
|
340
|
+
label=i18n("input_format"),
|
|
341
|
+
choices=['HWC', 'CHW'],
|
|
342
|
+
value='CHW'
|
|
343
|
+
)
|
|
344
|
+
input_mat_key = gr.Textbox(
|
|
345
|
+
label=i18n("mat_key"),
|
|
346
|
+
value=None,
|
|
347
|
+
placeholder=i18n("auto_detect"),
|
|
348
|
+
visible=True,
|
|
349
|
+
)
|
|
350
|
+
dat_files = gr.File(
|
|
351
|
+
label=i18n("data_files"),
|
|
352
|
+
file_count="multiple",
|
|
353
|
+
type="filepath",
|
|
354
|
+
)
|
|
355
|
+
manual_normalize = gr.Checkbox(
|
|
356
|
+
label=i18n("manual_normalize"),
|
|
357
|
+
value=False,
|
|
358
|
+
)
|
|
359
|
+
with gr.Row():
|
|
360
|
+
normalize_min = gr.Number(
|
|
361
|
+
label=i18n("normalize_min"),
|
|
362
|
+
value=0,
|
|
363
|
+
precision=1,
|
|
364
|
+
visible=False,
|
|
365
|
+
)
|
|
366
|
+
normalize_max = gr.Number(
|
|
367
|
+
label=i18n("normalize_max"),
|
|
368
|
+
value=2**16-1,
|
|
369
|
+
precision=1,
|
|
370
|
+
visible=False,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
def toggle_normalize_fields(manual_normalize):
|
|
374
|
+
return (
|
|
375
|
+
gr.update(visible=manual_normalize),
|
|
376
|
+
gr.update(visible=manual_normalize),
|
|
377
|
+
)
|
|
378
|
+
manual_normalize.change(
|
|
379
|
+
fn=toggle_normalize_fields,
|
|
380
|
+
inputs=[manual_normalize],
|
|
381
|
+
outputs=[normalize_min, normalize_max],
|
|
382
|
+
)
|
|
383
|
+
with gr.Row():
|
|
384
|
+
wavelength_from = gr.Number(
|
|
385
|
+
label=i18n("wavelength_start"),
|
|
386
|
+
value=400,
|
|
387
|
+
precision=1,
|
|
388
|
+
)
|
|
389
|
+
wavelength_to = gr.Number(
|
|
390
|
+
label=i18n("wavelength_end"),
|
|
391
|
+
value=1000,
|
|
392
|
+
precision=1,
|
|
393
|
+
)
|
|
394
|
+
reload_btn = gr.Button(i18n("load"), variant="primary")
|
|
395
|
+
|
|
396
|
+
with gr.Accordion(i18n("processing"), visible=False) as preview_panel:
|
|
397
|
+
with gr.Column():
|
|
398
|
+
gr.Markdown(f"### {i18n('crop')}")
|
|
399
|
+
with gr.Row():
|
|
400
|
+
crop_top = gr.Slider(
|
|
401
|
+
label=i18n("top"),
|
|
402
|
+
minimum=0,
|
|
403
|
+
maximum=0,
|
|
404
|
+
step=1,
|
|
405
|
+
)
|
|
406
|
+
crop_bottom = gr.Slider(
|
|
407
|
+
label=i18n("bottom"),
|
|
408
|
+
minimum=0,
|
|
409
|
+
maximum=0,
|
|
410
|
+
step=1,
|
|
411
|
+
)
|
|
412
|
+
with gr.Row():
|
|
413
|
+
crop_left = gr.Slider(
|
|
414
|
+
label=i18n("left"),
|
|
415
|
+
minimum=0,
|
|
416
|
+
maximum=0,
|
|
417
|
+
step=1,
|
|
418
|
+
)
|
|
419
|
+
crop_right = gr.Slider(
|
|
420
|
+
label=i18n("right"),
|
|
421
|
+
minimum=0,
|
|
422
|
+
maximum=0,
|
|
423
|
+
step=1,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
with gr.Column():
|
|
427
|
+
gr.Markdown(f"### {i18n('rotate')}")
|
|
428
|
+
rotate_deg = gr.Slider(
|
|
429
|
+
label=i18n("rotate_degree"),
|
|
430
|
+
minimum=-360,
|
|
431
|
+
maximum=360,
|
|
432
|
+
step=1,
|
|
433
|
+
value=0
|
|
434
|
+
)
|
|
435
|
+
# 这个按钮因为实时预览实际上不需要了,但是暂且隐藏备用
|
|
436
|
+
preview_btn = gr.Button(i18n("preview"), variant="primary", visible=False)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
with gr.Accordion(i18n("apply_processing"), visible=False) as convert_panel:
|
|
440
|
+
mat_dtype = gr.Radio(
|
|
441
|
+
label=i18n("mat_data_type"),
|
|
442
|
+
choices=["same", "uint8", "uint16", "float32"],
|
|
443
|
+
value="float32"
|
|
444
|
+
)
|
|
445
|
+
output_format = gr.Radio(
|
|
446
|
+
label=i18n("mat_format"),
|
|
447
|
+
choices=[
|
|
448
|
+
'same',
|
|
449
|
+
'HWC',
|
|
450
|
+
'CHW'
|
|
451
|
+
],
|
|
452
|
+
value='same'
|
|
453
|
+
)
|
|
454
|
+
mat_key = gr.Text(
|
|
455
|
+
label=i18n("mat_key"),
|
|
456
|
+
value='data',
|
|
457
|
+
)
|
|
458
|
+
compress_mat = gr.Checkbox(
|
|
459
|
+
label=i18n("compress_mat"),
|
|
460
|
+
value=True
|
|
461
|
+
)
|
|
462
|
+
convert_btn = gr.Button(i18n("apply_processing"), variant="primary")
|
|
463
|
+
|
|
464
|
+
with gr.Accordion(i18n("spectral_selection"),visible=False) as plot_panel:
|
|
465
|
+
gr.Markdown(i18n('spectral_selection_help'))
|
|
466
|
+
spectral_plot = gr.Plot(
|
|
467
|
+
label=i18n("spectral_plot"),
|
|
468
|
+
visible=True,
|
|
469
|
+
# x="Wavelength", y="Reflectance",
|
|
470
|
+
# height=400, width=600,
|
|
471
|
+
)
|
|
472
|
+
plot_hint = gr.Textbox(
|
|
473
|
+
label=i18n("style"),
|
|
474
|
+
value='b-',
|
|
475
|
+
)
|
|
476
|
+
with gr.Row():
|
|
477
|
+
clear_plot_btn = gr.Button(
|
|
478
|
+
i18n("clear"),
|
|
479
|
+
variant="secondary",
|
|
480
|
+
)
|
|
481
|
+
download_select_spectral = gr.DownloadButton(
|
|
482
|
+
"Download",
|
|
483
|
+
variant="primary",
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
with gr.Column():
|
|
487
|
+
with gr.Column(variant="panel"):
|
|
488
|
+
gr.Markdown(f"## {i18n('output_results')}")
|
|
489
|
+
preview_img = gr.Image(label=i18n("preview"), format="png", height="auto", width="auto", interactive=False)
|
|
490
|
+
mat_file_output = gr.File(
|
|
491
|
+
label=i18n("mat_file"),
|
|
492
|
+
type="filepath",
|
|
493
|
+
interactive=False
|
|
494
|
+
)
|
|
495
|
+
logging_text = gr.Textbox(
|
|
496
|
+
label=i18n("info"),
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
# dat_files.upload(
|
|
501
|
+
# fn=gr_on_file_upload,
|
|
502
|
+
# inputs=[dat_files, input_format, manual_normalize, normalize_min, normalize_max, wavelength_from, wavelength_to],
|
|
503
|
+
# outputs=[state_original_rgb, state_original_data, state_data_path, state_app_state]
|
|
504
|
+
# )
|
|
505
|
+
reload_btn.click(
|
|
506
|
+
fn=gr_load,
|
|
507
|
+
inputs=[dat_files, input_format, input_mat_key, manual_normalize, normalize_min, normalize_max, wavelength_from, wavelength_to],
|
|
508
|
+
outputs=[state_original_rgb, state_original_data, state_data_path, state_app_state, logging_text]
|
|
509
|
+
)
|
|
510
|
+
convert_btn.click(
|
|
511
|
+
fn=gr_convert,
|
|
512
|
+
inputs=[
|
|
513
|
+
state_original_data, state_data_path,
|
|
514
|
+
crop_top, crop_left, crop_bottom, crop_right,
|
|
515
|
+
rotate_deg,
|
|
516
|
+
mat_dtype, output_format, mat_key, compress_mat,
|
|
517
|
+
logging_text
|
|
518
|
+
],
|
|
519
|
+
outputs=[state_processed_data ,mat_file_output, logging_text, state_app_state]
|
|
520
|
+
)
|
|
521
|
+
for component in [crop_top, crop_left, crop_bottom, crop_right, rotate_deg]:
|
|
522
|
+
component.change(
|
|
523
|
+
fn=gr_preview,
|
|
524
|
+
inputs=[state_original_rgb, crop_top, crop_left, crop_bottom, crop_right, rotate_deg],
|
|
525
|
+
outputs=[preview_img, state_app_state]
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
preview_btn.click(
|
|
529
|
+
fn=gr_preview,
|
|
530
|
+
inputs=[
|
|
531
|
+
state_original_rgb,
|
|
532
|
+
crop_top, crop_left, crop_bottom, crop_right,
|
|
533
|
+
rotate_deg,
|
|
534
|
+
],
|
|
535
|
+
outputs=[preview_img, state_app_state]
|
|
536
|
+
)
|
|
537
|
+
preview_img.select(
|
|
538
|
+
fn=gr_on_img_clicked,
|
|
539
|
+
inputs=[state_spectral_figure, state_processed_data, state_selected_location, wavelength_from, wavelength_to, plot_hint],
|
|
540
|
+
outputs=[spectral_plot, state_spectral_figure, state_selected_location]
|
|
541
|
+
)
|
|
542
|
+
clear_plot_btn.click(
|
|
543
|
+
fn=lambda: (None, None, []),
|
|
544
|
+
outputs=[spectral_plot, state_spectral_figure, state_selected_location]
|
|
545
|
+
)
|
|
546
|
+
download_select_spectral.click(
|
|
547
|
+
fn=gr_download_selected_spectral,
|
|
548
|
+
inputs=[state_processed_data, state_selected_location, state_data_path],
|
|
549
|
+
outputs=[download_select_spectral]
|
|
550
|
+
)
|
|
551
|
+
state_original_data.change(
|
|
552
|
+
fn=lambda x: (
|
|
553
|
+
gr.update(maximum=x.shape[1] if x is not None else 0),
|
|
554
|
+
gr.update(maximum=x.shape[1] if x is not None else 0),
|
|
555
|
+
gr.update(maximum=x.shape[0] if x is not None else 0),
|
|
556
|
+
gr.update(maximum=x.shape[0] if x is not None else 0),
|
|
557
|
+
),
|
|
558
|
+
inputs=[state_original_data],
|
|
559
|
+
outputs=[crop_left, crop_right, crop_top, crop_bottom]
|
|
560
|
+
)
|
|
561
|
+
state_original_rgb.change(
|
|
562
|
+
fn=lambda x:x,
|
|
563
|
+
inputs=[state_original_rgb],
|
|
564
|
+
outputs=[preview_img]
|
|
565
|
+
)
|
|
566
|
+
state_app_state.change(
|
|
567
|
+
fn=lambda x: (
|
|
568
|
+
gr.update(visible=True, open=(x==AppState.NOT_LOADED)),
|
|
569
|
+
gr.update(visible=x!=AppState.NOT_LOADED, open=(x in [AppState.LOADED, AppState.PREVIEWED])),
|
|
570
|
+
gr.update(visible=x!=AppState.NOT_LOADED, open=(x in [AppState.LOADED, AppState.PREVIEWED])),
|
|
571
|
+
gr.update(visible=x==AppState.COVERTED, open=(x==AppState.COVERTED))
|
|
572
|
+
),
|
|
573
|
+
inputs=[state_app_state],
|
|
574
|
+
outputs=[load_panel, preview_panel, convert_panel, plot_panel]
|
|
575
|
+
)
|
|
576
|
+
with gr.Tab("推扫参数计算"):
|
|
577
|
+
scanner_calc_tab()
|
|
578
|
+
|
|
579
|
+
demo.launch(share=False, inbrowser=True, i18n=i18n)
|
|
580
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import gradio as gr
|
|
2
|
+
|
|
3
|
+
def _calculate_scanner_parameters(meta_pixel_size, n_wpixel, focal, h, delta_t):
|
|
4
|
+
"""All Unit should be in SI unit."""
|
|
5
|
+
v = (meta_pixel_size * h) / (focal * delta_t)
|
|
6
|
+
resolution = (meta_pixel_size * h) / focal
|
|
7
|
+
return v, resolution
|
|
8
|
+
|
|
9
|
+
def calculate_scanner_parameters(meta_pixel_size, n_wpixel, focal, h, delta_t):
|
|
10
|
+
""" Use the provide unit"""
|
|
11
|
+
meta_pixel_size /= 1e6 # Convert um to m
|
|
12
|
+
focal /= 1000 # Convert mm to m
|
|
13
|
+
delta_t /= 1000 # Convert ms to s
|
|
14
|
+
v, resolution = _calculate_scanner_parameters(meta_pixel_size, n_wpixel, focal, h, delta_t) # Convert ms to s
|
|
15
|
+
v *= 100 # Convert m/s to cm/s
|
|
16
|
+
resolution *= 100 # Convert m to cm
|
|
17
|
+
return v, resolution
|
|
18
|
+
|
|
19
|
+
def scanner_calc_tab():
|
|
20
|
+
with gr.Row():
|
|
21
|
+
with gr.Column():
|
|
22
|
+
meta_pixel_size = gr.Number(label="像元尺寸 (um)", value=7.40, precision=2)
|
|
23
|
+
n_wpixel = gr.Number(label="横向空间像素数量", value=640)
|
|
24
|
+
focal = gr.Number(label="焦距 (mm)", value=8.0, precision=1)
|
|
25
|
+
h = gr.Number(label="物距(高度) (m)", value=0.432, precision=3)
|
|
26
|
+
delta_t = gr.Number(label="帧间隔 (ms)", value=100)
|
|
27
|
+
btn = gr.Button("计算", variant="primary")
|
|
28
|
+
with gr.Column():
|
|
29
|
+
v = gr.Number(label="推扫速度 (cm/s)", interactive=False, precision=2)
|
|
30
|
+
resolution = gr.Number(label="分辨率 (cm/pixel)", interactive=False, precision=3)
|
|
31
|
+
|
|
32
|
+
btn.click(
|
|
33
|
+
calculate_scanner_parameters,
|
|
34
|
+
inputs=[meta_pixel_size, n_wpixel, focal, h, delta_t],
|
|
35
|
+
outputs=[v, resolution]
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
__all__ = ["scanner_calc_tab"]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: hsi-preprocessing-toolkit
|
|
3
|
+
Version: 1.1.2.dev7
|
|
4
|
+
Summary: Hsi Preprocessing Toolkit
|
|
5
|
+
Requires-Dist: einops>=0.8.1
|
|
6
|
+
Requires-Dist: gradio>=5.35.0
|
|
7
|
+
Requires-Dist: matplotlib>=3.10.3
|
|
8
|
+
Requires-Dist: rasterio>=1.4.3
|
|
9
|
+
Requires-Dist: rs-fusion-datasets>=0.16.0
|
|
10
|
+
Requires-Dist: scipy>=1.15.3
|
|
11
|
+
Requires-Python: >=3.13
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# HSI Preprocessing Toolkit
|
|
15
|
+
|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
A Hyperspectral Preprocessing Toolkit that
|
|
19
|
+
1. Read the raw data from the HSI camera, and convert it into `.mat` file
|
|
20
|
+
2. Read the `.mat` file
|
|
21
|
+
3. Preview HSI, and convert it to RGB `.png` file
|
|
22
|
+
4. Crop and rotate the HSI and preview in realtime
|
|
23
|
+
5. Select spectrals of interest visually and save them into a `.mat` file
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
1. Download from GitHub release
|
|
29
|
+
2. Double click the `start.cmd` file
|
|
30
|
+
|
|
31
|
+
## Credit
|
|
32
|
+
1. [uv](https://docs.astral.sh/uv/) for providing a new reliable solution for Python application distribution.
|
|
33
|
+
2. `gradio` for modern Python Data Science UI
|
|
34
|
+
3. `rasterio` for remote sensing data reading
|
|
35
|
+
4. `scipy`, `numpy`, `matplotlib` and `einops`
|
|
36
|
+
5. For more projects, see `pyproject.toml`
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
Copyright (C) 2025 songyz2019
|
|
43
|
+
|
|
44
|
+
This program is free software: you can redistribute it and/or modify
|
|
45
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
46
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
47
|
+
(at your option) any later version.
|
|
48
|
+
|
|
49
|
+
This program is distributed in the hope that it will be useful,
|
|
50
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
51
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
52
|
+
GNU Affero General Public License for more details.
|
|
53
|
+
|
|
54
|
+
You should have received a copy of the GNU Affero General Public License
|
|
55
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
56
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
hsi_preprocessing_toolkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
hsi_preprocessing_toolkit/__main__.py,sha256=sU9r5td62sGq-bOlyvtGPWjerRcKsld9aDiqR6XMJzs,24762
|
|
3
|
+
hsi_preprocessing_toolkit/scanner_calc.py,sha256=rubXX08xl9TIYTSuIbdgEosauCnK3r6kCRN3N8iSo20,1652
|
|
4
|
+
hsi_preprocessing_toolkit-1.1.2.dev7.dist-info/WHEEL,sha256=Pi5uDq5Fdo_Rr-HD5h9BiPn9Et29Y9Sh8NhcJNnFU1c,79
|
|
5
|
+
hsi_preprocessing_toolkit-1.1.2.dev7.dist-info/entry_points.txt,sha256=cjesXDaqD1AH25-EdLHmucugtq0KlhhpcF62X26Gcv4,87
|
|
6
|
+
hsi_preprocessing_toolkit-1.1.2.dev7.dist-info/METADATA,sha256=Aaas-5v_0FXE0faTP87q6mYuhM49OS4pEKnx_TydTB4,1820
|
|
7
|
+
hsi_preprocessing_toolkit-1.1.2.dev7.dist-info/RECORD,,
|