hsi-preprocessing-toolkit 1.1.2.dev7__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,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
+ ![](asset/screenshot.jpg)
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,43 @@
1
+ # HSI Preprocessing Toolkit
2
+
3
+ ![](asset/screenshot.jpg)
4
+
5
+ A Hyperspectral Preprocessing Toolkit that
6
+ 1. Read the raw data from the HSI camera, and convert it into `.mat` file
7
+ 2. Read the `.mat` file
8
+ 3. Preview HSI, and convert it to RGB `.png` file
9
+ 4. Crop and rotate the HSI and preview in realtime
10
+ 5. Select spectrals of interest visually and save them into a `.mat` file
11
+
12
+
13
+
14
+ ## Usage
15
+ 1. Download from GitHub release
16
+ 2. Double click the `start.cmd` file
17
+
18
+ ## Credit
19
+ 1. [uv](https://docs.astral.sh/uv/) for providing a new reliable solution for Python application distribution.
20
+ 2. `gradio` for modern Python Data Science UI
21
+ 3. `rasterio` for remote sensing data reading
22
+ 4. `scipy`, `numpy`, `matplotlib` and `einops`
23
+ 5. For more projects, see `pyproject.toml`
24
+
25
+
26
+ ## License
27
+
28
+ ```text
29
+ Copyright (C) 2025 songyz2019
30
+
31
+ This program is free software: you can redistribute it and/or modify
32
+ it under the terms of the GNU Affero General Public License as published by
33
+ the Free Software Foundation, either version 3 of the License, or
34
+ (at your option) any later version.
35
+
36
+ This program is distributed in the hope that it will be useful,
37
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
38
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39
+ GNU Affero General Public License for more details.
40
+
41
+ You should have received a copy of the GNU Affero General Public License
42
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
43
+ ```
@@ -0,0 +1,29 @@
1
+ [project]
2
+ name = "hsi-preprocessing-toolkit"
3
+ version = "1.1.2.dev7"
4
+ description = "Hsi Preprocessing Toolkit"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = [
8
+ "einops>=0.8.1",
9
+ "gradio>=5.35.0",
10
+ "matplotlib>=3.10.3",
11
+ "rasterio>=1.4.3",
12
+ "rs-fusion-datasets>=0.16.0",
13
+ "scipy>=1.15.3",
14
+ ]
15
+
16
+ [[tool.uv.index]]
17
+ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"
18
+ default = true
19
+
20
+ [build-system]
21
+ requires = ["uv_build >= 0.8.0, <0.9.0"]
22
+ build-backend = "uv_build"
23
+
24
+ [tool.hatch.build.targets.wheel]
25
+ packages = ["hsi-preprocessing-toolkit/__main__.py"]
26
+
27
+
28
+ [project.scripts]
29
+ hsi_preprocessing_toolkit = "hsi_preprocessing_toolkit.__main__:main"
@@ -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"]