hsi-preprocessing-toolkit 1.1.3__tar.gz → 1.1.5.dev4__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hsi-preprocessing-toolkit
3
- Version: 1.1.3
3
+ Version: 1.1.5.dev4
4
4
  Summary: HSI Preprocessing Toolkit
5
5
  Author: songyz2023
6
6
  Author-email: songyz2023 <songyz2023dlut@outlook.com>
@@ -39,6 +39,7 @@ A Hyperspectral Preprocessing Toolkit that
39
39
  3. Preview HSI, and convert it to RGB `.png` file
40
40
  4. Crop and rotate the HSI and preview in realtime
41
41
  5. Select spectrals of interest visually and save them into a `.mat` file
42
+ 6. Mix multiple HSI image with layers. (coming in v2.0.0, now availabe in git)
42
43
 
43
44
 
44
45
  ## Usage
@@ -16,6 +16,7 @@ A Hyperspectral Preprocessing Toolkit that
16
16
  3. Preview HSI, and convert it to RGB `.png` file
17
17
  4. Crop and rotate the HSI and preview in realtime
18
18
  5. Select spectrals of interest visually and save them into a `.mat` file
19
+ 6. Mix multiple HSI image with layers. (coming in v2.0.0, now availabe in git)
19
20
 
20
21
 
21
22
  ## Usage
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hsi-preprocessing-toolkit"
3
- version = "1.1.3"
3
+ version = "1.1.5.dev4"
4
4
  description = "HSI Preprocessing Toolkit"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -33,7 +33,7 @@ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"
33
33
  default = true
34
34
 
35
35
  [build-system]
36
- requires = ["uv_build >= 0.8.0, <0.9.0"]
36
+ requires = ["uv_build >= 0.8.0, <0.10.0"]
37
37
  build-backend = "uv_build"
38
38
 
39
39
  [tool.hatch.build.targets.wheel]
@@ -8,91 +8,19 @@ from rasterio import open as rasterio_open
8
8
  from scipy.io import savemat, loadmat
9
9
  import gradio as gr
10
10
  import gradio.utils
11
- from scipy.ndimage import rotate
12
11
  from rs_fusion_datasets.util.hsi2rgb import _hsi2rgb, hsi2rgb
13
12
  from jaxtyping import Float
14
13
  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
- })
14
+ from .page.scanner_calc import scanner_calc_tab
15
+ import logging
16
+ from .i18n import i18n
17
+ from .algorithm import composite_img
18
+
19
+
20
+ logging.basicConfig(level=logging.INFO)
21
+ logger = logging.getLogger(__name__)
22
+ logger.info("started")
23
+
96
24
 
97
25
  class AppState(Enum):
98
26
  NOT_LOADED = 0
@@ -151,19 +79,7 @@ def load_data(
151
79
  return data, data_path
152
80
 
153
81
 
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
82
 
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
83
 
168
84
  # def update_ui(state_app_state: AppState):
169
85
  # gr.Info(f"App state changed to {state_app_state.name}")
@@ -179,11 +95,15 @@ def process_img(img, crop_top, crop_left, crop_bottom, crop_right, rotate_deg):
179
95
  # )
180
96
 
181
97
  def gr_load(
98
+ state_current_layer_index,
99
+ state_transforms :list[dict], state_original_rgb, state_original_data, state_original_data_path,
182
100
  dat_files :List[gradio.utils.NamedString] | None,
183
101
  input_format, input_mat_key: str | None,
184
102
  manual_normalize: bool, normalize_min: float, normalize_max: float,
185
103
  wavelength_from: int, wavelength_to: int,
186
104
  ):
105
+ logger.info(f"gr_load {state_current_layer_index=} {len(state_original_rgb)=}")
106
+
187
107
  data, data_path = load_data(dat_files, input_format, input_mat_key)
188
108
  if data is None:
189
109
  raise gr.Error("No data file provided or data loading failed.")
@@ -201,26 +121,40 @@ def gr_load(
201
121
  "original_reflection_mean": float(data.mean()),
202
122
  "original_reflection_std": float(data.std()),
203
123
  })
204
- return rgb, data, data_path, AppState.LOADED, logging_text
205
124
 
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
125
+ # FIXME: Do not use dirty fix
126
+ while state_current_layer_index >= len(state_original_rgb):
127
+ state_original_rgb.append(None)
128
+ state_original_data.append(None)
129
+ state_original_data_path.append(None)
130
+ state_transforms.append(DEFAULT_TRANSFORM)
131
+
132
+ state_original_rgb[state_current_layer_index] = rgb
133
+ state_original_data[state_current_layer_index] = data
134
+ state_original_data_path[state_current_layer_index] = data_path
135
+ state_transforms[state_current_layer_index] = DEFAULT_TRANSFORM
136
+
137
+ logger.info(f"gr_loaded {state_current_layer_index=} {len(state_original_rgb)=}")
138
+
139
+ return state_transforms, state_original_rgb, state_original_data, state_original_data_path, AppState.LOADED, logging_text
140
+
141
+ def gr_composite(
142
+ state_original_rgb :Float[np.ndarray, 'h w c'] | None,
143
+ state_transforms,
210
144
  ):
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
145
+ logger.info("gr_composite")
146
+ img = composite_img(state_original_rgb, state_transforms)
147
+ logger.info(f"gr_composited {img.shape=} {type(img)=}")
148
+ return img, AppState.PREVIEWED
216
149
 
217
- def gr_download_selected_spectral(data, state_select_location, data_path):
150
+
151
+ def gr_download_selected_spectral(state_current_layer_index, data, state_select_location, data_path):
218
152
  if not state_select_location or len(state_select_location) == 0:
219
153
  raise gr.Error("No spectral data selected. Please select at least one pixel to download")
220
154
 
221
155
  gr.Info("Converting ...")
222
156
 
223
- dat_path = Path(data_path.name)
157
+ dat_path = Path(data_path[state_current_layer_index].name)
224
158
  result = []
225
159
  result_location = []
226
160
  for (row, col) in state_select_location:
@@ -238,17 +172,17 @@ def gr_download_selected_spectral(data, state_select_location, data_path):
238
172
  return str(mat_path)
239
173
 
240
174
  def gr_convert(
175
+ state_current_layer_index,
176
+ state_transforms,
241
177
  data, data_path,
242
- crop_top: int, crop_left: int, crop_bottom: int, crop_right: int,
243
- rotate_deg: int,
244
178
  mat_dtype, output_format, mat_key, compress_mat: bool,
245
179
  logging_text: str
246
180
  ):
247
- if output_format == 'same':
248
- output_format = input_format
181
+ logger.info("gr_convert")
249
182
 
250
- data = process_img(
251
- data, crop_top, crop_left, crop_bottom, crop_right, rotate_deg
183
+ data_path = data_path[state_current_layer_index]
184
+ data = composite_img(
185
+ data, state_transforms
252
186
  )
253
187
 
254
188
  # Convert to mat
@@ -313,26 +247,80 @@ def gr_on_img_clicked(evt: gr.SelectData, state_figure :tuple, data, state_selec
313
247
  state_figure = (fig, ax)
314
248
  return fig, state_figure, state_select_location
315
249
 
250
+ def gr_update_transforms(state_transforms, state_current_layer_index, crop_top, crop_left, crop_bottom, crop_right, rotate_deg, offset_x, offset_y):
251
+ state_transforms[state_current_layer_index] = {
252
+ 'rotation': rotate_deg,
253
+ 'crop': [crop_top, crop_left, crop_bottom, crop_right],
254
+ 'location': [offset_x, offset_y]
255
+ }
256
+ return state_transforms
316
257
 
317
- if __name__ == "__main__":
258
+ def gr_on_state_current_layer_index_changed(state_current_layer_index, state_transforms, state_original_data):
259
+ if state_current_layer_index >= len(state_transforms):
260
+ trans = DEFAULT_TRANSFORM
261
+ shape = [0,0]
262
+ else:
263
+ trans = state_transforms[state_current_layer_index]
264
+ shape = state_original_data[state_current_layer_index].shape
265
+
266
+ crop_top, crop_left, crop_bottom, crop_right = trans['crop']
267
+ rotate_deg = trans['rotation']
268
+ offset_x, offset_y = trans['location']
269
+
270
+ max_h, max_w = shape[:2]
271
+ logger.info(f"gr_on_state_current_layer_index_changed {state_current_layer_index=} {max_h=}, {max_w=}")
272
+ update_crop_top = gr.update(value=crop_top, maximum=max_h, minimum=0)
273
+ update_crop_bottom = gr.update(value=crop_bottom, maximum=max_h, minimum=0)
274
+ update_crop_left = gr.update(value=crop_left, maximum=max_w, minimum=0)
275
+ update_crop_right = gr.update(value=crop_bottom, maximum=max_w, minimum=0)
276
+
277
+ return update_crop_top, update_crop_left, update_crop_bottom, update_crop_right, rotate_deg, offset_x, offset_y
278
+
279
+
280
+ DEFAULT_TRANSFORM = {
281
+ 'rotation': 0,
282
+ 'crop': [0,0,0,0],
283
+ 'location': [0,0]
284
+ }
285
+
286
+ def main():
318
287
  theme = gr.themes.Default(primary_hue='cyan').set(
319
288
  button_primary_background_fill='#39c5bb',
320
289
  button_primary_background_fill_hover="#30A8A0",
321
290
  )
322
291
 
323
292
  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)
293
+ # 应用整体状态
294
+ state_app_state = gr.State(value=AppState.NOT_LOADED) # 操作阶段
295
+ # 输入的状态,支持多输出
296
+ state_current_layer_index = gr.State(value=0) # 已选中的图层的数组index
297
+ state_max_layer_index = gr.State(value=0) # 已选中的图层的数组index
298
+ state_original_data = gr.State(value=[]) # 原数据
299
+ state_data_path = gr.State(value=[]) # 原数据文件路径
300
+ state_original_rgb = gr.State(value=[]) # 原数据的RGB代理
301
+ state_transforms = gr.State(value=[]) # 对数据的变换
302
+ # 缓存状态
303
+ # state_original_alpha = gr.State(value=[]) # 用于合成的Alpha通道
304
+ # state_original_offset = gr.State(value=[]) # 尽管用alpha更好,但先用offset吧
305
+ # 中间状态,多个
306
+ # state_local_rgb = gr.State(value=[])
307
+ # 输出内容状态,保持单个
308
+ state_processed_data = gr.State(value=None) # 处理后的数据
309
+ state_selected_location = gr.State(value=[]) # 已选择的光谱XY坐标点
310
+ state_spectral_figure = gr.State(value=None) # 已选择的光谱的绘图
331
311
 
332
312
 
333
313
  with gr.Tab(i18n("title"), id="hsi_preprocessing_toolkit"):
334
314
  with gr.Row():
335
315
  with gr.Column():
316
+ with gr.Column():
317
+ current_layer_index_slider = gr.Slider(
318
+ label="当前图层",
319
+ minimum=0,
320
+ maximum=3,
321
+ step=1,
322
+ value=0
323
+ )
336
324
  with gr.Accordion(i18n("load")) as load_panel:
337
325
  gr.Markdown(i18n("upload_instructions"))
338
326
  with gr.Row():
@@ -391,8 +379,10 @@ if __name__ == "__main__":
391
379
  value=1000,
392
380
  precision=1,
393
381
  )
394
- reload_btn = gr.Button(i18n("load"), variant="primary")
395
-
382
+ with gr.Row():
383
+ reload_btn = gr.Button(i18n("load"), variant="primary")
384
+ reset_loaded_btn = gr.Button(i18n("clear"), variant="secondary")
385
+ # TODO: set maxium to be shape[i]
396
386
  with gr.Accordion(i18n("processing"), visible=False) as preview_panel:
397
387
  with gr.Column():
398
388
  gr.Markdown(f"### {i18n('crop')}")
@@ -400,26 +390,26 @@ if __name__ == "__main__":
400
390
  crop_top = gr.Slider(
401
391
  label=i18n("top"),
402
392
  minimum=0,
403
- maximum=0,
393
+ maximum=9999,
404
394
  step=1,
405
395
  )
406
396
  crop_bottom = gr.Slider(
407
397
  label=i18n("bottom"),
408
398
  minimum=0,
409
- maximum=0,
399
+ maximum=9999,
410
400
  step=1,
411
401
  )
412
402
  with gr.Row():
413
403
  crop_left = gr.Slider(
414
404
  label=i18n("left"),
415
405
  minimum=0,
416
- maximum=0,
406
+ maximum=9999,
417
407
  step=1,
418
408
  )
419
409
  crop_right = gr.Slider(
420
410
  label=i18n("right"),
421
411
  minimum=0,
422
- maximum=0,
412
+ maximum=9999,
423
413
  step=1,
424
414
  )
425
415
 
@@ -432,24 +422,36 @@ if __name__ == "__main__":
432
422
  step=1,
433
423
  value=0
434
424
  )
435
- # 这个按钮因为实时预览实际上不需要了,但是暂且隐藏备用
436
- preview_btn = gr.Button(i18n("preview"), variant="primary", visible=False)
437
425
 
426
+ with gr.Column():
427
+ gr.Markdown(f"### 平移")
428
+ with gr.Row():
429
+ offset_x = gr.Slider(
430
+ label="X轴",
431
+ minimum=0,
432
+ maximum=+9999,
433
+ step=1,
434
+ )
435
+ offset_y = gr.Slider(
436
+ label="Y轴",
437
+ minimum=0,
438
+ maximum=+9999,
439
+ step=1,
440
+ )
438
441
 
439
442
  with gr.Accordion(i18n("apply_processing"), visible=False) as convert_panel:
440
443
  mat_dtype = gr.Radio(
441
444
  label=i18n("mat_data_type"),
442
- choices=["same", "uint8", "uint16", "float32"],
445
+ choices=["auto", "uint8", "uint16", "float32"],
443
446
  value="float32"
444
447
  )
445
448
  output_format = gr.Radio(
446
449
  label=i18n("mat_format"),
447
450
  choices=[
448
- 'same',
449
451
  'HWC',
450
452
  'CHW'
451
453
  ],
452
- value='same'
454
+ value='CHW'
453
455
  )
454
456
  mat_key = gr.Text(
455
457
  label=i18n("mat_key"),
@@ -496,44 +498,74 @@ if __name__ == "__main__":
496
498
  label=i18n("info"),
497
499
  )
498
500
 
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
501
  reload_btn.click(
506
502
  fn=gr_load,
507
- inputs=[dat_files, input_format, input_mat_key, manual_normalize, normalize_min, normalize_max, wavelength_from, wavelength_to],
503
+ inputs=[state_current_layer_index, state_transforms, state_original_rgb, state_original_data, state_data_path, dat_files, input_format, input_mat_key, manual_normalize, normalize_min, normalize_max, wavelength_from, wavelength_to],
504
+ outputs=[state_transforms, state_original_rgb, state_original_data, state_data_path, state_app_state, logging_text]
505
+ )
506
+ reset_loaded_btn.click(
507
+ fn=lambda logging_text: ([],[],[],AppState.NOT_LOADED, "已清空"),
508
+ inputs=[logging_text],
508
509
  outputs=[state_original_rgb, state_original_data, state_data_path, state_app_state, logging_text]
509
510
  )
510
511
  convert_btn.click(
511
512
  fn=gr_convert,
512
513
  inputs=[
514
+ state_current_layer_index,
515
+ state_transforms,
513
516
  state_original_data, state_data_path,
514
- crop_top, crop_left, crop_bottom, crop_right,
515
- rotate_deg,
516
517
  mat_dtype, output_format, mat_key, compress_mat,
517
518
  logging_text
518
519
  ],
519
520
  outputs=[state_processed_data ,mat_file_output, logging_text, state_app_state]
520
521
  )
521
- for component in [crop_top, crop_left, crop_bottom, crop_right, rotate_deg]:
522
+ for component in [crop_top, crop_left, crop_bottom, crop_right, rotate_deg, offset_x, offset_y]:
522
523
  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]
524
+ fn = gr_update_transforms,
525
+ inputs=[state_transforms, state_current_layer_index, crop_top, crop_left, crop_bottom, crop_right, rotate_deg, offset_x, offset_y],
526
+ outputs=[state_transforms]
526
527
  )
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
- ],
528
+
529
+ state_transforms.change(
530
+ fn=gr_composite,
531
+ inputs=[state_original_rgb, state_transforms],
535
532
  outputs=[preview_img, state_app_state]
536
533
  )
534
+ # Legacy Code:
535
+ # preview_btn.click(
536
+ # fn=gr_composite,
537
+ # inputs=[
538
+ # state_original_rgb,
539
+ # crop_top, crop_left, crop_bottom, crop_right,
540
+ # rotate_deg,
541
+ # ],
542
+ # outputs=[preview_img, state_app_state]
543
+ # )
544
+ current_layer_index_slider.change(
545
+ fn = lambda x: x,
546
+ inputs=[current_layer_index_slider],
547
+ outputs=[state_current_layer_index]
548
+
549
+ )
550
+
551
+ # ------------
552
+ state_current_layer_index.change(
553
+ fn=gr_on_state_current_layer_index_changed,
554
+ inputs=[state_current_layer_index, state_transforms, state_original_data],
555
+ outputs=[
556
+ crop_top, crop_left, crop_bottom, crop_right, rotate_deg, offset_x, offset_y
557
+ ]
558
+ )
559
+ state_original_data.change(
560
+ fn=gr_on_state_current_layer_index_changed,
561
+ inputs=[state_current_layer_index, state_transforms, state_original_data],
562
+ outputs=[
563
+ crop_top, crop_left, crop_bottom, crop_right, rotate_deg, offset_x, offset_y
564
+ ]
565
+ )
566
+ # ------------
567
+
568
+
537
569
  preview_img.select(
538
570
  fn=gr_on_img_clicked,
539
571
  inputs=[state_spectral_figure, state_processed_data, state_selected_location, wavelength_from, wavelength_to, plot_hint],
@@ -545,24 +577,26 @@ if __name__ == "__main__":
545
577
  )
546
578
  download_select_spectral.click(
547
579
  fn=gr_download_selected_spectral,
548
- inputs=[state_processed_data, state_selected_location, state_data_path],
580
+ inputs=[state_current_layer_index, state_processed_data, state_selected_location, state_data_path],
549
581
  outputs=[download_select_spectral]
550
582
  )
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
- )
583
+ # TODO: Bring it back
584
+ # state_original_data.change(
585
+ # fn=lambda x,i: (
586
+ # gr.update(maximum=x[i].shape[1]),
587
+ # gr.update(maximum=x[i].shape[1]),
588
+ # gr.update(maximum=x[i].shape[0]),
589
+ # gr.update(maximum=x[i].shape[0]),
590
+ # ),
591
+ # inputs=[ state_original_data, state_current_layer_index ],
592
+ # outputs=[crop_left, crop_right, crop_top, crop_bottom]
593
+ # )
594
+ # TODO: shoud we bring it back?
595
+ # state_original_rgb.change(
596
+ # fn=lambda x:x,
597
+ # inputs=[state_original_rgb],
598
+ # outputs=[preview_img]
599
+ # )
566
600
  state_app_state.change(
567
601
  fn=lambda x: (
568
602
  gr.update(visible=True, open=(x==AppState.NOT_LOADED)),
@@ -573,8 +607,18 @@ if __name__ == "__main__":
573
607
  inputs=[state_app_state],
574
608
  outputs=[load_panel, preview_panel, convert_panel, plot_panel]
575
609
  )
610
+ # FIXME:
611
+ # state_max_layer_index.change(
612
+ # fn=lambda :gr.update(maximum=state_max_layer_index),
613
+ # inputs=[],
614
+ # outputs=[current_layer_index_slider]
615
+ # )
576
616
  with gr.Tab("推扫参数计算"):
577
617
  scanner_calc_tab()
578
618
 
579
619
  demo.launch(share=False, inbrowser=True, i18n=i18n)
580
620
 
621
+
622
+ if __name__ == "__main__":
623
+ plt.rcParams['font.family'] = 'SimHei'
624
+ main()
@@ -0,0 +1,42 @@
1
+ from scipy.ndimage import rotate
2
+ import numpy as np
3
+ import logging
4
+ from copy import deepcopy
5
+
6
+ logging.basicConfig(level=logging.INFO)
7
+ logger = logging.getLogger(__name__)
8
+ logger.info("started")
9
+
10
+ # Return A HWC Image, the C should work with both RGB and HSI
11
+ def composite_img(imgs :list[np.ndarray], transforms:list[dict]):
12
+ imgs = deepcopy(imgs)
13
+ # Transform
14
+ logger.info(f"{transforms=}")
15
+ for i, (img,trans) in enumerate(zip(imgs, transforms)):
16
+ rotate_deg = trans['rotation']
17
+ if rotate_deg % 360 != 0:
18
+ imgs[i] = rotate(img, angle=rotate_deg, axes=(0, 1), reshape=True)
19
+
20
+ # Crop
21
+ crop_top, crop_left, crop_bottom, crop_right = trans['crop']
22
+ if crop_top > 0 or crop_left > 0 or crop_bottom > 0 or crop_right > 0:
23
+ crop_bottom = None if crop_bottom == 0 else -crop_bottom
24
+ crop_right = None if crop_right == 0 else -crop_right
25
+ imgs[i] = img[crop_top:crop_bottom, crop_left:crop_right, :]
26
+
27
+ # Caclate canvas size
28
+ shapes = [x.shape for x in imgs]
29
+ offsets = [ x['location'] for x in transforms ]
30
+ canvas_c = imgs[0].shape[-1]
31
+ sizes = [ ((h+abs(x)),w+abs(y)) for (h,w,_),(x,y) in zip(shapes, offsets)]
32
+ canvas_h, canvas_w = ( max([x[0] for x in sizes]), max([x[1] for x in sizes]) )
33
+ canvas_shape = (canvas_h, canvas_w, canvas_c)
34
+ logger.info(f"{sizes=} {canvas_shape=} {shapes=}")
35
+ canvas = np.zeros(shape=canvas_shape, dtype=imgs[0].dtype)
36
+
37
+ # 合成 Compositing
38
+ for img,trans in zip(imgs, transforms):
39
+ x,y = trans['location']
40
+ h,w,_ = img.shape
41
+ canvas[x:x+h, y:y+w] = img
42
+ return canvas
@@ -0,0 +1,78 @@
1
+ import gradio as gr
2
+
3
+ i18n = gr.I18n(**{
4
+ 'en': {
5
+ "title": "hsi-preprocessing-toolkit",
6
+ "load": "Load",
7
+ "upload_instructions": "**Upload one of the following formats:**\n1. One .hdr file + one raw data file without extension\n2. One .mat file",
8
+ "input_format": "Input Image Shape Format",
9
+ "data_files": "Data Files",
10
+ "manual_normalize": "Manual Normalize (affects preview only)",
11
+ "normalize_min": "Normalize Min",
12
+ "normalize_max": "Normalize Max",
13
+ "wavelength_start": "Wavelength Range Start",
14
+ "wavelength_end": "Wavelength Range End",
15
+ "processing": "Processing",
16
+ "crop": "Crop",
17
+ "top": "Top",
18
+ "bottom": "Bottom",
19
+ "left": "Left",
20
+ "right": "Right",
21
+ "rotate": "Rotate",
22
+ "rotate_degree": "Rotate Degree",
23
+ "preview": "Preview",
24
+ "apply_processing": "Apply Processing Effects",
25
+ "mat_data_type": "MAT Data Type",
26
+ "mat_format": "MAT Image Shape Format",
27
+ "mat_key": "Key of MAT file",
28
+ "compress_mat": "Produce Compressed MAT File",
29
+ "spectral_selection": "Spectral Selection",
30
+ "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.",
31
+ "spectral_plot": "Spectral Plot",
32
+ "style": "Style",
33
+ "clear": "Clear",
34
+ "download": "Download",
35
+ "output_results": "Output Results",
36
+ "mat_file": "MAT File",
37
+ "info": "Info",
38
+ "same_as_input": "Same",
39
+ "auto_detect": "Auto Detect",
40
+ },
41
+ 'zh-CN':{
42
+ "title": "高光谱图像预处理工具箱",
43
+ "load": "加载",
44
+ "upload_instructions": "**应上传以下两种格式中的一种**\n1. 同时上传一个.hdr文件 + 一个无后缀的数据文件\n2. 一个.mat文件",
45
+ "input_format": "输入数据形状",
46
+ "data_files": "数据文件",
47
+ "manual_normalize": "手动归一化(仅影响预览结果)",
48
+ "normalize_min": "归一化最小值",
49
+ "normalize_max": "归一化最大值",
50
+ "wavelength_start": "波长范围起始",
51
+ "wavelength_end": "波长范围结束",
52
+ "processing": "处理",
53
+ "crop": "裁切",
54
+ "top": "上",
55
+ "bottom": "下",
56
+ "left": "左",
57
+ "right": "右",
58
+ "rotate": "旋转",
59
+ "rotate_degree": "旋转角度",
60
+ "preview": "预览",
61
+ "apply_processing": "应用处理效果",
62
+ "mat_data_type": "mat文件数据类型",
63
+ "mat_format": "mat文件格式",
64
+ "mat_key": "mat文件的key",
65
+ "compress_mat": "启用mat文件压缩",
66
+ "spectral_selection": "光谱选择",
67
+ "spectral_selection_help": "点击预览图像图像中的像素进行光谱数据提取。选中的像素将在下方的光谱图中绘制。",
68
+ "spectral_plot": "光谱图",
69
+ "style": "样式",
70
+ "clear": "清空",
71
+ "download": "下载",
72
+ "output_results": "输出结果",
73
+ "mat_file": "MAT文件",
74
+ "info": "信息",
75
+ "same_as_input": "与输入相同",
76
+ "auto_detect": "自动检测",
77
+ }
78
+ })