autoforge 1.9.3__tar.gz → 1.9.4__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.
Files changed (59) hide show
  1. {autoforge-1.9.3 → autoforge-1.9.4}/PKG-INFO +1 -1
  2. {autoforge-1.9.3 → autoforge-1.9.4}/pyproject.toml +1 -1
  3. {autoforge-1.9.3 → autoforge-1.9.4}/src/AutoForge.egg-info/PKG-INFO +1 -1
  4. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Helper/PruningHelper.py +145 -83
  5. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Helper/filamentcolors_library.py +2 -1
  6. {autoforge-1.9.3 → autoforge-1.9.4}/README.md +0 -0
  7. {autoforge-1.9.3 → autoforge-1.9.4}/setup.cfg +0 -0
  8. {autoforge-1.9.3 → autoforge-1.9.4}/src/AutoForge.egg-info/SOURCES.txt +0 -0
  9. {autoforge-1.9.3 → autoforge-1.9.4}/src/AutoForge.egg-info/dependency_links.txt +0 -0
  10. {autoforge-1.9.3 → autoforge-1.9.4}/src/AutoForge.egg-info/entry_points.txt +0 -0
  11. {autoforge-1.9.3 → autoforge-1.9.4}/src/AutoForge.egg-info/requires.txt +0 -0
  12. {autoforge-1.9.3 → autoforge-1.9.4}/src/AutoForge.egg-info/top_level.txt +0 -0
  13. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Helper/AmpUtils.py +0 -0
  14. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Helper/CAdamW.py +0 -0
  15. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Helper/FilamentHelper.py +0 -0
  16. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Helper/Heightmaps/ChristofidesHeightMap.py +0 -0
  17. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Helper/Heightmaps/DepthEstimateHeightMap.py +0 -0
  18. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Helper/Heightmaps/__init__.py +0 -0
  19. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Helper/ImageHelper.py +0 -0
  20. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Helper/OptimizerHelper.py +0 -0
  21. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Helper/OtherHelper.py +0 -0
  22. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Helper/OutputHelper.py +0 -0
  23. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Helper/__init__.py +0 -0
  24. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Init_comparer/__init__.py +0 -0
  25. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Init_comparer/auto_forge_init_check.py +0 -0
  26. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Init_comparer/plot_results.py +0 -0
  27. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Loss/LossFunctions.py +0 -0
  28. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Loss/PerceptionLoss.py +0 -0
  29. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Loss/__init__.py +0 -0
  30. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Misc/__init__.py +0 -0
  31. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Misc/color_fit_all.py +0 -0
  32. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Modules/Optimizer.py +0 -0
  33. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/Modules/__init__.py +0 -0
  34. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/__init__.py +0 -0
  35. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/__main__.py +0 -0
  36. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/auto_forge.py +0 -0
  37. {autoforge-1.9.3 → autoforge-1.9.4}/src/autoforge/priority_mask_editor.py +0 -0
  38. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_amp_autocast_probe.py +0 -0
  39. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_cadamw.py +0 -0
  40. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_christofides_height_map.py +0 -0
  41. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_christofides_heightmap.py +0 -0
  42. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_depth_adjusted_height_map.py +0 -0
  43. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_depth_estimate_height_map.py +0 -0
  44. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_end_to_end_optimizer.py +0 -0
  45. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_filament_and_output.py +0 -0
  46. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_filament_helper.py +0 -0
  47. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_flatforge.py +0 -0
  48. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_heightmap_init.py +0 -0
  49. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_image_helper.py +0 -0
  50. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_loss_functions.py +0 -0
  51. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_optimizer_helper.py +0 -0
  52. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_optimizer_module.py +0 -0
  53. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_optimizer_smoke.py +0 -0
  54. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_output_helper.py +0 -0
  55. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_output_helper_extended.py +0 -0
  56. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_perception_and_pruning.py +0 -0
  57. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_property_based.py +0 -0
  58. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_pruning_helper.py +0 -0
  59. {autoforge-1.9.3 → autoforge-1.9.4}/tests/test_remove_height_spikes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AutoForge
3
- Version: 1.9.3
3
+ Version: 1.9.4
4
4
  Summary: AutoForge is a Python tool for generating 3D printed layered models from an input image.
5
5
  Author-email: Hendric Voss <hvoss@techfak.uni-bielefeld.de>
6
6
  License: CC BY-NC-SA 4.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "AutoForge"
7
- version="1.9.3"
7
+ version="1.9.4"
8
8
  description = "AutoForge is a Python tool for generating 3D printed layered models from an input image."
9
9
  authors = [{ name = "Hendric Voss", email = "hvoss@techfak.uni-bielefeld.de" }]
10
10
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AutoForge
3
- Version: 1.9.3
3
+ Version: 1.9.4
4
4
  Summary: AutoForge is a Python tool for generating 3D printed layered models from an input image.
5
5
  Author-email: Hendric Voss <hvoss@techfak.uni-bielefeld.de>
6
6
  License: CC BY-NC-SA 4.0
@@ -10,6 +10,7 @@ import numpy as np
10
10
  from autoforge.Helper.OptimizerHelper import composite_image_disc
11
11
  from autoforge.Loss.LossFunctions import compute_loss
12
12
  from autoforge.Modules.Optimizer import FilamentOptimizer
13
+ from collections import deque
13
14
 
14
15
  # One global lock that serialises every call that needs GPU / VRAM
15
16
  _gpu_lock = threading.Lock()
@@ -46,22 +47,15 @@ def _chunked(iterable, chunk_size):
46
47
  def prune_num_colors(
47
48
  optimizer: FilamentOptimizer,
48
49
  max_colors_allowed: int,
49
- tau_for_comp: float, # kept for API compatibility
50
- perception_loss_module: torch.nn.Module, # kept for API compatibility
51
- n_jobs: int | None = -1, # how many workers joblib should start
50
+ tau_for_comp: float,
51
+ perception_loss_module: torch.nn.Module,
52
+ n_jobs: int | None = -1,
52
53
  *,
53
- fast: bool = True, # enable incremental search
54
- chunking_percent=0.05, # percentage of layers to process at once
55
- allowed_loss_increase_percent=0.00, # percentage of loss increase allowed over best loss
54
+ fast: bool = True,
55
+ chunking_percent=0.05,
56
+ allowed_loss_increase_percent=0.00,
56
57
  ) -> torch.Tensor:
57
- """Iteratively merge materials until the number of distinct colors is
58
- <= max_colors_allowed or until merging would worsen the loss.
59
58
 
60
- When *fast* is True we only look at 10 percent of the candidate merges at a
61
- time. If none of those improve the objective we look at the next 10 percent
62
- and so on. In the worst case we still examine every merge, in the best case
63
- we stop after the first improving chunk, saving work.
64
- """
65
59
  num_materials = optimizer.material_colors.shape[0]
66
60
  disc_global, _ = optimizer.get_discretized_solution(best=True)
67
61
 
@@ -70,28 +64,41 @@ def prune_num_colors(
70
64
  ) -> tuple[float, torch.Tensor]:
71
65
  dg_test = merge_color(dg_base, c_from, c_to)
72
66
  logits_for_disc = disc_to_logits(dg_test, num_materials, big_pos=1e5)
67
+
73
68
  with _gpu_lock, torch.no_grad():
74
69
  out_im = optimizer.get_best_discretized_image(
75
70
  custom_global_logits=logits_for_disc
76
71
  )
77
72
  loss = compute_loss(comp=out_im, target=optimizer.target)
73
+
78
74
  return loss, dg_test
79
75
 
80
76
  def get_image_loss(dg_test: torch.Tensor) -> float:
81
77
  logits_for_disc = disc_to_logits(dg_test, num_materials, big_pos=1e5)
78
+
82
79
  with _gpu_lock, torch.no_grad():
83
80
  out_im = optimizer.get_best_discretized_image(
84
81
  custom_global_logits=logits_for_disc
85
82
  )
86
83
  loss = compute_loss(comp=out_im, target=optimizer.target)
84
+
87
85
  return loss
88
86
 
89
87
  best_dg = disc_global.clone()
90
88
  best_loss = get_image_loss(best_dg)
91
89
 
90
+ distinct_mats = torch.unique(best_dg)
91
+
92
+ print(f"PRUNING: Color - initial loss={best_loss:.4f}, initial colors={len(distinct_mats)}")
93
+
92
94
  tbar = tqdm(total=100, leave=False)
95
+
93
96
  while True:
94
97
  distinct_mats = torch.unique(best_dg)
98
+
99
+ if len(distinct_mats) <= 1:
100
+ break
101
+
95
102
  merge_pairs = [
96
103
  (c_from.item(), c_to.item())
97
104
  for c_from in distinct_mats
@@ -110,69 +117,80 @@ def prune_num_colors(
110
117
  if fast:
111
118
  random.shuffle(merge_pairs)
112
119
  chunk_size = max(1, math.ceil(len(merge_pairs) * chunking_percent))
120
+
113
121
  improved = False
114
- c_cand = None
115
- c_loss = 1000
122
+ best_candidate = None
123
+ best_candidate_loss = float("inf")
124
+
116
125
  for chunk in _chunked(merge_pairs, chunk_size):
117
126
  cand_results = Parallel(
118
127
  n_jobs=n_jobs, backend="threading", prefer="threads"
119
128
  )(delayed(score_color)(best_dg, *pair) for pair in chunk)
129
+
120
130
  merge_loss, merge_dg = min(cand_results, key=lambda x: x[0])
131
+
121
132
  if merge_loss < best_loss * (1 + allowed_loss_increase_percent):
122
- best_dg, best_loss = merge_dg, merge_loss
133
+ best_dg = merge_dg
134
+ best_loss = merge_loss
123
135
  improved = True
124
- break # move on to next outer iteration
125
- else:
126
- if len(distinct_mats) > max_colors_allowed:
127
- if merge_loss < c_loss:
128
- c_cand = (merge_dg, merge_loss)
129
- c_loss = merge_loss
130
- if c_cand is not None:
131
- best_dg, best_loss = c_cand
132
- distinct_mats = torch.unique(best_dg)
133
- optimizer.best_params["global_logits"] = disc_to_logits(
134
- best_dg, num_materials=num_materials, big_pos=1e5
135
- )
136
- tbar.set_description(
137
- f"Colors {len(distinct_mats)} | Loss {best_loss:.4f} | Merge pairs {len(merge_pairs)}"
138
- )
139
- improved = True
136
+ break
137
+
138
+ if merge_loss < best_candidate_loss:
139
+ best_candidate = merge_dg
140
+ best_candidate_loss = merge_loss
140
141
 
141
142
  if not improved:
142
- break # no chunk provided improvement and we are within budget
143
+ if len(distinct_mats) > max_colors_allowed and best_candidate is not None:
144
+ best_dg = best_candidate
145
+ best_loss = best_candidate_loss
146
+ else:
147
+ break
148
+
143
149
  else:
144
150
  cand_results = Parallel(
145
151
  n_jobs=n_jobs, backend="threading", prefer="threads"
146
152
  )(delayed(score_color)(best_dg, *pair) for pair in merge_pairs)
153
+
147
154
  merge_loss, merge_dg = min(cand_results, key=lambda x: x[0])
155
+
148
156
  if merge_loss < best_loss or len(distinct_mats) > max_colors_allowed:
149
- best_dg, best_loss = merge_dg, merge_loss
157
+ best_dg = merge_dg
158
+ best_loss = merge_loss
150
159
  else:
151
160
  break
152
161
 
153
162
  tbar.close()
163
+
154
164
  optimizer.best_params["global_logits"] = disc_to_logits(
155
165
  best_dg, num_materials=num_materials, big_pos=1e5
156
166
  )
167
+
168
+ final_colors = torch.unique(best_dg)
169
+
170
+ assert len(final_colors) <= max_colors_allowed, (
171
+ f"Color pruning failed: {len(final_colors)} > {max_colors_allowed}"
172
+ )
173
+
174
+ print(f"PRUNING: Color - final loss={best_loss:.4f}, final colors={len(final_colors)}")
175
+
157
176
  return best_dg
158
177
 
159
178
 
160
179
  def prune_num_swaps(
161
180
  optimizer: FilamentOptimizer,
162
181
  max_swaps_allowed: int,
163
- tau_for_comp: float, # kept for API compatibility
164
- perception_loss_module: torch.nn.Module, # kept for API compatibility
165
- n_jobs: int | None = -1, # how many workers joblib should start
182
+ tau_for_comp: float,
183
+ perception_loss_module: torch.nn.Module,
184
+ n_jobs: int | None = -1,
166
185
  *,
167
- fast: bool = True, # enable incremental search
168
- chunking_percent=0.05, # percentage of layers to process at once
169
- allowed_loss_increase_percent=0.000, # percentage of loss increase allowed over best loss
186
+ fast: bool = True,
187
+ chunking_percent=0.05,
188
+ allowed_loss_increase_percent=0.000,
170
189
  ) -> torch.Tensor:
171
- """Reduce the number of color boundaries until it is below or equal to
172
- *max_swaps_allowed*, stopping earlier if any further merge would worsen the
173
- loss. The *fast* parameter activates the same 10 percent chunk search logic
174
- used in *prune_num_colors*.
190
+ """Reduce the number of color boundaries until it is <= max_swaps_allowed.
191
+ If necessary, merges are forced even when they worsen the loss.
175
192
  """
193
+
176
194
  num_materials = optimizer.material_colors.shape[0]
177
195
  disc_global, _ = optimizer.get_discretized_solution(best=True)
178
196
 
@@ -198,10 +216,17 @@ def prune_num_swaps(
198
216
  best_dg = disc_global.clone()
199
217
  best_loss = get_image_loss(best_dg)
200
218
 
219
+ bands = find_color_bands(best_dg)
220
+ num_swaps = len(bands) - 1
221
+
222
+ print(f"PRUNING: Swap - initial loss={best_loss:.4f}, initial swaps={num_swaps:d}")
223
+
201
224
  tbar = tqdm(total=100, leave=False)
225
+
202
226
  while True:
203
227
  bands = find_color_bands(best_dg)
204
228
  num_swaps = len(bands) - 1
229
+
205
230
  if num_swaps == 0:
206
231
  break
207
232
 
@@ -223,38 +248,45 @@ def prune_num_swaps(
223
248
  if fast:
224
249
  random.shuffle(merge_specs)
225
250
  chunk_size = max(1, math.ceil(len(merge_specs) * chunking_percent))
251
+
226
252
  improved = False
227
- c_cand = None
228
- c_loss = 10000
253
+ best_candidate = None
254
+ best_candidate_loss = float("inf")
255
+
229
256
  for chunk in _chunked(merge_specs, chunk_size):
257
+
230
258
  cand_results = Parallel(
231
259
  n_jobs=n_jobs, backend="threading", prefer="threads"
232
260
  )(
233
261
  delayed(score_swap)(best_dg, band_a, band_b, direction)
234
262
  for band_a, band_b, direction in chunk
235
263
  )
264
+
236
265
  merge_loss, merge_dg = min(cand_results, key=lambda x: x[0])
266
+
267
+ # Accept merge if loss increase is allowed
237
268
  if merge_loss < best_loss * (1 + allowed_loss_increase_percent):
238
269
  best_dg, best_loss = merge_dg, merge_loss
239
270
  improved = True
240
271
  break
241
- else:
242
- if num_swaps > max_swaps_allowed and merge_loss < c_loss:
243
- c_cand = (merge_dg, merge_loss)
244
- c_loss = merge_loss
245
- if c_cand is not None:
246
- best_dg, best_loss = c_cand
247
- num_swaps = len(find_color_bands(best_dg)) - 1
248
- optimizer.best_params["global_logits"] = disc_to_logits(
249
- best_dg, num_materials=num_materials, big_pos=1e5
250
- )
251
- tbar.set_description(
252
- f"Swaps {num_swaps} | Loss {best_loss:.4f} | Merge swaps {len(merge_specs)}"
253
- )
254
- improved = True
255
272
 
273
+ # Track best candidate for forced merge
274
+ if merge_loss < best_candidate_loss:
275
+ best_candidate = (merge_dg, merge_loss)
276
+ best_candidate_loss = merge_loss
277
+
278
+ bands = find_color_bands(best_dg)
279
+ num_swaps = len(bands) - 1
280
+
281
+ # If no acceptable merge but we still exceed swap budget,
282
+ # force the best candidate.
256
283
  if not improved:
257
- break
284
+ if num_swaps > max_swaps_allowed and best_candidate is not None:
285
+ best_dg, best_loss = best_candidate
286
+ improved = True
287
+ else:
288
+ break
289
+
258
290
  else:
259
291
  cand_results = Parallel(
260
292
  n_jobs=n_jobs, backend="threading", prefer="threads"
@@ -262,16 +294,28 @@ def prune_num_swaps(
262
294
  delayed(score_swap)(best_dg, band_a, band_b, direction)
263
295
  for band_a, band_b, direction in merge_specs
264
296
  )
297
+
265
298
  merge_loss, merge_dg = min(cand_results, key=lambda x: x[0])
299
+
266
300
  if merge_loss < best_loss or num_swaps > max_swaps_allowed:
267
301
  best_dg, best_loss = merge_dg, merge_loss
268
302
  else:
269
303
  break
270
304
 
305
+ print(f"PRUNING: Swap - final loss={best_loss:.4f}, final swaps={len(find_color_bands(best_dg)) - 1:d}")
306
+
271
307
  tbar.close()
308
+
272
309
  optimizer.best_params["global_logits"] = disc_to_logits(
273
310
  best_dg, num_materials=num_materials, big_pos=1e5
274
311
  )
312
+
313
+ # safety check
314
+ final_swaps = len(find_color_bands(best_dg)) - 1
315
+ assert final_swaps <= max_swaps_allowed, (
316
+ f"Swap pruning failed: {final_swaps} swaps > {max_swaps_allowed}"
317
+ )
318
+
275
319
  return best_dg
276
320
 
277
321
 
@@ -417,6 +461,8 @@ def prune_redundant_layers(
417
461
  # Baseline loss with current best parameters
418
462
  best_loss = get_initial_loss(current_max_layers, optimizer)
419
463
 
464
+ print(f"PRUNING: Layer - initial loss={best_loss:.4f}, initial layer={current_max_layers:d}")
465
+
420
466
  tbar = tqdm(
421
467
  desc=f"Layer pruning | Loss {best_loss:.4f}",
422
468
  total=max(current_max_layers - 1, 0),
@@ -550,6 +596,8 @@ def prune_redundant_layers(
550
596
  else:
551
597
  break
552
598
 
599
+ print(f"PRUNING: Layers - final loss={best_loss:.4f}, final layers={current_max_layers:d}")
600
+
553
601
  tbar.close()
554
602
  return optimizer.best_params, best_loss, current_max_layers
555
603
 
@@ -807,6 +855,7 @@ def optimise_swap_positions(
807
855
 
808
856
  best_dg, _ = optimizer.get_discretized_solution(best=True)
809
857
  best_loss = disc_loss(best_dg)
858
+ print(f"PRUNING: Swap position - initial loss={best_loss:.4f}")
810
859
 
811
860
  outer_tbar = tqdm(desc="Optimising swap positions", total=100, leave=False)
812
861
  improved = True
@@ -857,13 +906,14 @@ def optimise_swap_positions(
857
906
  outer_tbar.set_description(
858
907
  f"Swap {swap_idx} moved {old_pos}->{new_pos} | Loss {best_loss:.4f}"
859
908
  )
860
- print(
861
- f"Swap {swap_idx} moved {old_pos}->{new_pos} | Loss {best_loss:.4f}"
862
- )
909
+ # print(
910
+ # f"Swap {swap_idx} moved {old_pos}->{new_pos} | Loss {best_loss:.4f}"
911
+ # )
863
912
  improved = True
864
913
  break
865
914
 
866
915
  outer_tbar.close()
916
+ print(f"PRUNING: Swap position - final loss={best_loss:.4f}")
867
917
  return best_dg
868
918
 
869
919
 
@@ -937,26 +987,38 @@ def remove_height_spikes(
937
987
 
938
988
  out = dh_np.copy()
939
989
  spikes = 0
940
- tbar = tqdm(range(1, H - 1), desc="Removing height spikes", leave=False)
941
- for y in tbar:
942
- for x in range(1, W - 1):
943
- window = dh_np[y - 1 : y + 2, x - 1 : x + 2]
944
- med_all = np.median(window)
945
- mask = window - med_all >= threshold_layers
946
- spike_count = int(mask.sum())
947
- if not mask[1, 1] or spike_count == 0 or spike_count > max_outliers:
948
- continue
949
-
950
- non_outliers = window[~mask]
951
- if non_outliers.size == 0:
952
- continue
953
- med_non_outliers = float(np.median(non_outliers))
954
- center_val = float(window[1, 1])
955
- if center_val < med_non_outliers + threshold_layers:
990
+ queue = deque((y, x) for y in range(1, H - 1) for x in range(1, W - 1))
991
+ total_num = len(queue)
992
+ max_iters = (H - 2) * (W - 2) * 10 # safety cap
993
+ iters = 0
994
+ tbar = tqdm(total=total_num, desc="Removing height spikes", leave=False)
995
+ while queue and iters < max_iters:
996
+ tbar.update(1)
997
+ y, x = queue.popleft()
998
+ iters += 1
999
+ window = out[y - 1: y + 2, x - 1: x + 2]
1000
+ replacement, is_spike, _ = _median_without_outliers(
1001
+ window, threshold_layers, max_outliers
1002
+ )
1003
+ center_val = out[y, x]
1004
+ if (
1005
+ not is_spike
1006
+ or center_val < replacement + threshold_layers
1007
+ or replacement == center_val
1008
+ ):
1009
+ continue
1010
+ out[y, x] = replacement
1011
+ spikes += 1
1012
+ for ny in range(y - 1, y + 2):
1013
+ if ny <= 0 or ny >= H - 1:
956
1014
  continue
957
-
958
- out[y, x] = med_non_outliers # only replace the center spike
959
- spikes += 1
1015
+ for nx in range(x - 1, x + 2):
1016
+ if nx <= 0 or nx >= W - 1:
1017
+ continue
1018
+ queue.append((ny, nx))
1019
+ total_num += 1
1020
+ tbar.total = total_num
1021
+ tbar.close()
960
1022
 
961
1023
  if spikes == 0:
962
1024
  return disc_height, 0
@@ -2,7 +2,7 @@ import json
2
2
  import os
3
3
  import time
4
4
  import traceback
5
-
5
+ import time
6
6
  import requests
7
7
  from tqdm import tqdm
8
8
 
@@ -89,6 +89,7 @@ def download_all_pages(url):
89
89
  all_results.extend(data.get("results", []))
90
90
  url = data.get("next")
91
91
  tbar.update(1)
92
+ time.sleep(5)
92
93
  return all_results
93
94
 
94
95
 
File without changes
File without changes