autoforge 1.5.3__tar.gz → 1.5.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 (39) hide show
  1. {autoforge-1.5.3 → autoforge-1.5.4}/PKG-INFO +3 -2
  2. {autoforge-1.5.3 → autoforge-1.5.4}/README.md +1 -1
  3. {autoforge-1.5.3 → autoforge-1.5.4}/pyproject.toml +3 -2
  4. {autoforge-1.5.3 → autoforge-1.5.4}/src/AutoForge.egg-info/PKG-INFO +3 -2
  5. {autoforge-1.5.3 → autoforge-1.5.4}/src/AutoForge.egg-info/requires.txt +1 -0
  6. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Helper/Heightmaps/ChristofidesHeightMap.py +47 -45
  7. autoforge-1.5.4/src/autoforge/Helper/OptimizerHelper.py +475 -0
  8. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Modules/Optimizer.py +1 -1
  9. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/auto_forge.py +8 -4
  10. autoforge-1.5.3/src/autoforge/Helper/OptimizerHelper.py +0 -985
  11. {autoforge-1.5.3 → autoforge-1.5.4}/setup.cfg +0 -0
  12. {autoforge-1.5.3 → autoforge-1.5.4}/src/AutoForge.egg-info/SOURCES.txt +0 -0
  13. {autoforge-1.5.3 → autoforge-1.5.4}/src/AutoForge.egg-info/dependency_links.txt +0 -0
  14. {autoforge-1.5.3 → autoforge-1.5.4}/src/AutoForge.egg-info/entry_points.txt +0 -0
  15. {autoforge-1.5.3 → autoforge-1.5.4}/src/AutoForge.egg-info/top_level.txt +0 -0
  16. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Gui/Views/__init__.py +0 -0
  17. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Gui/Views/backend_database.py +0 -0
  18. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Gui/Views/filament_editor.py +0 -0
  19. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Gui/__init__.py +0 -0
  20. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Gui/app.py +0 -0
  21. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Gui/main.py +0 -0
  22. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Helper/CAdamW.py +0 -0
  23. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Helper/FilamentHelper.py +0 -0
  24. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Helper/Heightmaps/DepthEstimateHeightMap.py +0 -0
  25. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Helper/Heightmaps/OldHeightmap.py +0 -0
  26. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Helper/Heightmaps/__init__.py +0 -0
  27. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Helper/ImageHelper.py +0 -0
  28. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Helper/OutputHelper.py +0 -0
  29. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Helper/PruningHelper.py +0 -0
  30. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Helper/__init__.py +0 -0
  31. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Helper/filamentcolors_library.py +0 -0
  32. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Loss/LossFunctions.py +0 -0
  33. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Loss/PerceptionLoss.py +0 -0
  34. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Loss/__init__.py +0 -0
  35. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Misc/__init__.py +0 -0
  36. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Misc/color_fit_all.py +0 -0
  37. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/Modules/__init__.py +0 -0
  38. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/__init__.py +0 -0
  39. {autoforge-1.5.3 → autoforge-1.5.4}/src/autoforge/__main__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: AutoForge
3
- Version: 1.5.3
3
+ Version: 1.5.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
@@ -18,6 +18,7 @@ Requires-Dist: pandas>=1.1.0
18
18
  Requires-Dist: scikit-image
19
19
  Requires-Dist: scikit-learn
20
20
  Requires-Dist: tensorboard
21
+ Requires-Dist: pillow>=11.1.0
21
22
 
22
23
  # AutoForge
23
24
 
@@ -104,7 +105,7 @@ autoforge --input_image path/to/input_image.jpg --csv_file path/to/materials.csv
104
105
  - `--background_color`: Background color in hexadecimal format (default: `#000000` aka Black). \
105
106
  **Note:** The solver currently assumes that you have a solid color in the background, which means a color with a TD value of 4 or less (if you have a background height of 0.4)
106
107
  - `--output_size`: Maximum dimension for target image (default: 1024).
107
- - `--solver_size`: Maximum dimension for solver (fast) image (default: 128). \
108
+ - `--solver_size`: Maximum dimension for solver (fast) image (default: 256). \
108
109
  **Note:** We solve on a smaller size as this is many times faster, but also a bit less accurate. Increase if you need more accuracy.
109
110
  - `--init_tau`: Initial tau value for Gumbel-Softmax (default: 1.0).
110
111
  - `--final_tau`: Final tau value for the Gumbel-Softmax formulation (default: 0.01).
@@ -83,7 +83,7 @@ autoforge --input_image path/to/input_image.jpg --csv_file path/to/materials.csv
83
83
  - `--background_color`: Background color in hexadecimal format (default: `#000000` aka Black). \
84
84
  **Note:** The solver currently assumes that you have a solid color in the background, which means a color with a TD value of 4 or less (if you have a background height of 0.4)
85
85
  - `--output_size`: Maximum dimension for target image (default: 1024).
86
- - `--solver_size`: Maximum dimension for solver (fast) image (default: 128). \
86
+ - `--solver_size`: Maximum dimension for solver (fast) image (default: 256). \
87
87
  **Note:** We solve on a smaller size as this is many times faster, but also a bit less accurate. Increase if you need more accuracy.
88
88
  - `--init_tau`: Initial tau value for Gumbel-Softmax (default: 1.0).
89
89
  - `--final_tau`: Final tau value for the Gumbel-Softmax formulation (default: 0.01).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "AutoForge"
7
- version="1.5.3"
7
+ version="1.5.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"
@@ -22,7 +22,8 @@ dependencies = [
22
22
  "pandas>=1.1.0",
23
23
  "scikit-image",
24
24
  "scikit-learn",
25
- "tensorboard"
25
+ "tensorboard",
26
+ "pillow>=11.1.0"
26
27
  ]
27
28
 
28
29
  [tool.setuptools]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: AutoForge
3
- Version: 1.5.3
3
+ Version: 1.5.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
@@ -18,6 +18,7 @@ Requires-Dist: pandas>=1.1.0
18
18
  Requires-Dist: scikit-image
19
19
  Requires-Dist: scikit-learn
20
20
  Requires-Dist: tensorboard
21
+ Requires-Dist: pillow>=11.1.0
21
22
 
22
23
  # AutoForge
23
24
 
@@ -104,7 +105,7 @@ autoforge --input_image path/to/input_image.jpg --csv_file path/to/materials.csv
104
105
  - `--background_color`: Background color in hexadecimal format (default: `#000000` aka Black). \
105
106
  **Note:** The solver currently assumes that you have a solid color in the background, which means a color with a TD value of 4 or less (if you have a background height of 0.4)
106
107
  - `--output_size`: Maximum dimension for target image (default: 1024).
107
- - `--solver_size`: Maximum dimension for solver (fast) image (default: 128). \
108
+ - `--solver_size`: Maximum dimension for solver (fast) image (default: 256). \
108
109
  **Note:** We solve on a smaller size as this is many times faster, but also a bit less accurate. Increase if you need more accuracy.
109
110
  - `--init_tau`: Initial tau value for Gumbel-Softmax (default: 1.0).
110
111
  - `--final_tau`: Final tau value for the Gumbel-Softmax formulation (default: 0.01).
@@ -10,3 +10,4 @@ pandas>=1.1.0
10
10
  scikit-image
11
11
  scikit-learn
12
12
  tensorboard
13
+ pillow>=11.1.0
@@ -3,9 +3,9 @@ import random
3
3
  from concurrent.futures import ThreadPoolExecutor
4
4
 
5
5
  import numpy as np
6
+ from PIL import Image
6
7
  from scipy.spatial.distance import cdist
7
8
  from skimage.color import rgb2lab
8
- from sklearn.cluster import MiniBatchKMeans
9
9
  from tqdm import tqdm
10
10
 
11
11
 
@@ -244,7 +244,6 @@ def tsp_order_christofides_path(nodes, labs, bg, fg):
244
244
  Returns an ordering (list of cluster indices) that contains all nodes,
245
245
  starts with bg and ends with fg.
246
246
  """
247
- # Use an artificial node to force a Hamiltonian path.
248
247
  artificial = -1
249
248
  augmented_nodes = nodes + [artificial]
250
249
  LARGE = 1e6
@@ -255,9 +254,8 @@ def tsp_order_christofides_path(nodes, labs, bg, fg):
255
254
 
256
255
  # Build an augmented matrix of size (n+1)x(n+1).
257
256
  aug_mat = np.zeros((n + 1, n + 1))
258
- # Fill the top-left block with D.
259
257
  aug_mat[:n, :n] = D
260
- # For the artificial node (last row/col), set costs.
258
+ # For the artificial node, set cost = 0 for bg/fg, LARGE otherwise.
261
259
  for i, u in enumerate(nodes):
262
260
  if u in {bg, fg}:
263
261
  aug_mat[i, n] = 0.0
@@ -265,7 +263,6 @@ def tsp_order_christofides_path(nodes, labs, bg, fg):
265
263
  else:
266
264
  aug_mat[i, n] = LARGE
267
265
  aug_mat[n, i] = LARGE
268
- # Set artificial-to-artificial cost to 0.
269
266
  aug_mat[n, n] = 0.0
270
267
 
271
268
  # Convert the augmented matrix into a graph dictionary.
@@ -291,6 +288,15 @@ def tsp_order_christofides_path(nodes, labs, bg, fg):
291
288
  cycle.remove(fg)
292
289
  cycle.append(fg)
293
290
 
291
+ # --- NEW: Check if reversing the internal order yields a lower ordering metric ---
292
+ if len(cycle) > 2:
293
+ # Reverse the ordering of the nodes between bg and fg.
294
+ reversed_cycle = [cycle[0]] + cycle[1:-1][::-1] + [cycle[-1]]
295
+ if compute_ordering_metric(reversed_cycle, labs) < compute_ordering_metric(
296
+ cycle, labs
297
+ ):
298
+ cycle = reversed_cycle
299
+
294
300
  return cycle
295
301
 
296
302
 
@@ -338,55 +344,55 @@ def init_height_map(
338
344
  random.seed(random_seed)
339
345
 
340
346
  H, W, _ = target.shape
341
- target_np = np.asarray(target).reshape(H, W, 3).astype(np.float32) / 255.0
342
-
343
- # Convert the image to Lab space and apply weights.
344
- target_lab = rgb2lab(target_np)
345
- # Apply weights: scale L channel by lab_weights[0], a by lab_weights[1], b by lab_weights[2]
346
- target_lab[..., 0] *= lab_weights[0]
347
- target_lab[..., 1] *= lab_weights[1]
348
- target_lab[..., 2] *= lab_weights[2]
349
347
 
350
- # Reshape for clustering.
351
- target_lab_reshaped = target_lab.reshape(-1, 3)
352
-
353
- # Cluster pixels using MiniBatchKMeans in the weighted Lab space.
354
- kmeans = MiniBatchKMeans(
355
- n_clusters=max_layers * 2, random_state=random_seed, max_iter=300
356
- )
357
- kmeans.fit(target_lab_reshaped)
358
- centers = kmeans.cluster_centers_
359
- labels = kmeans.predict(target_lab_reshaped).reshape(H, W)
360
-
361
- # For subsequent ordering, we use the weighted Lab centers.
362
- labs = centers # labs now holds weighted Lab values
363
-
364
- # Convert the background color to Lab and apply the same weighting.
348
+ # Convert target (assumed in [0,255]) to a PIL Image.
349
+ pil_im = Image.fromarray(target.astype(np.uint8))
350
+ # Quantize image into max_layers colors.
351
+ quantized_im = pil_im.quantize(colors=max_layers)
352
+ # Retrieve per-pixel labels (indices into the palette).
353
+ labels = np.array(quantized_im)
354
+
355
+ # Get the full palette and reshape it into (-1, 3)
356
+ full_palette = quantized_im.getpalette()
357
+ palette_arr = np.array(full_palette, dtype=np.uint8).reshape(-1, 3)
358
+
359
+ # Get only the palette indices actually used in the quantized image.
360
+ unique_palette_indices = sorted(np.unique(labels))
361
+ # Create the labs array from the used palette colors.
362
+ labs_rgb = palette_arr[unique_palette_indices].astype(np.float32)
363
+
364
+ # Convert RGB values (0-255) to Lab space.
365
+ labs = rgb2lab(labs_rgb / 255.0)
366
+ # Apply the weighting to each Lab channel.
367
+ labs[:, 0] *= lab_weights[0]
368
+ labs[:, 1] *= lab_weights[1]
369
+ labs[:, 2] *= lab_weights[2]
370
+
371
+ # Remap the labels in the image to a compact index range.
372
+ palette_map = {old: new for new, old in enumerate(unique_palette_indices)}
373
+ labels = np.vectorize(lambda x: palette_map[x])(labels)
374
+
375
+ # Convert the background color to Lab (with same weighting).
365
376
  bg_rgb = np.array(background_tuple).astype(np.float32) / 255.0
366
377
  bg_lab = rgb2lab(np.array([[bg_rgb]]))[0, 0, :]
367
378
  bg_lab[0] *= lab_weights[0]
368
379
  bg_lab[1] *= lab_weights[1]
369
380
  bg_lab[2] *= lab_weights[2]
370
381
 
371
- # Identify the cluster closest to the background and the farthest.
382
+ # Identify the best background and foreground clusters.
372
383
  distances = np.linalg.norm(labs - bg_lab, axis=1)
373
384
  bg_cluster = int(np.argmin(distances))
374
385
  fg_cluster = int(np.argmax(distances))
375
386
 
376
- # Get the unique clusters (should be 0...max_layers-1 ideally).
377
- unique_clusters = sorted(np.unique(labels))
387
+ # Use all the new compact cluster indices.
388
+ unique_clusters = list(range(len(unique_palette_indices)))
378
389
  nodes = unique_clusters
379
390
 
380
- # Get the ordering from the TSP ordering function.
391
+ # Get the ordering via TSP ordering function.
381
392
  final_ordering = tsp_order_christofides_path(nodes, labs, bg_cluster, fg_cluster)
382
- # Optionally, prune out outlier clusters.
393
+ # Optionally prune out outliers.
383
394
  final_ordering = prune_ordering(
384
- final_ordering,
385
- labs,
386
- bg_cluster,
387
- fg_cluster,
388
- min_length=3,
389
- improvement_factor=3,
395
+ final_ordering, labs, bg_cluster, fg_cluster, min_length=3, improvement_factor=3
390
396
  )
391
397
 
392
398
  # Create a mapping that covers all clusters.
@@ -395,11 +401,6 @@ def init_height_map(
395
401
  pixel_height_logits = np.log((new_labels + eps) / (1 - new_labels + eps))
396
402
 
397
403
  ordering_metric = compute_ordering_metric(final_ordering, labs)
398
- # print(
399
- # "Ordering metric (total weighted Lab-space distance):",
400
- # ordering_metric / max_layers,
401
- # )
402
-
403
404
  return pixel_height_logits, ordering_metric
404
405
 
405
406
 
@@ -410,7 +411,7 @@ def run_init_threads(
410
411
  background_tuple,
411
412
  eps=1e-6,
412
413
  random_seed=None,
413
- num_threads=16,
414
+ num_threads=64,
414
415
  ):
415
416
  """
416
417
  Initializes pixel height logits using multiple threads.
@@ -447,6 +448,7 @@ def run_init_threads(
447
448
  total=num_threads,
448
449
  )
449
450
  ]
451
+ # print(f"metric values: {[r[1] for r in results]}")
450
452
  # get lowest ordering metric
451
453
  best_result = min(results, key=lambda x: x[1])
452
454