descriptron-gui 2.0.2__tar.gz → 2.0.3__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.5
2
2
  Name: descriptron-gui
3
- Version: 2.0.2
3
+ Version: 2.0.3
4
4
  Summary: The Descriptron annotation GUI: SAM2-assisted segmentation, keypoints and COCO export
5
5
  Project-URL: Homepage, https://github.com/alexrvandam/Descriptron
6
6
  Author: Alex R. Van Dam
@@ -14,8 +14,8 @@ Classifier: Operating System :: OS Independent
14
14
  Classifier: Programming Language :: Python :: 3
15
15
  Classifier: Topic :: Scientific/Engineering :: Image Processing
16
16
  Requires-Python: >=3.9
17
- Requires-Dist: descriptron-core==2.0.2
18
- Requires-Dist: descriptron-vision==2.0.2
17
+ Requires-Dist: descriptron-core==2.0.3
18
+ Requires-Dist: descriptron-vision==2.0.3
19
19
  Provides-Extra: dev
20
20
  Requires-Dist: build>=1; extra == 'dev'
21
21
  Requires-Dist: pytest>=7; extra == 'dev'
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "descriptron-gui"
7
+ version = "2.0.3"
8
+ description = "The Descriptron annotation GUI: SAM2-assisted segmentation, keypoints and COCO export"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "Apache-2.0" }
12
+ authors = [{ name = "Alex R. Van Dam" }]
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Environment :: X11 Applications",
16
+ "Topic :: Scientific/Engineering :: Image Processing",
17
+ "Intended Audience :: Science/Research",
18
+ "License :: OSI Approved :: Apache Software License",
19
+ "Operating System :: OS Independent",
20
+ ]
21
+
22
+ # tkinter is part of the standard library and so is not listed here, but it is
23
+ # not always packaged with Python on Linux: `apt install python3-tk` if the GUI
24
+ # fails to start. The GUI drives the analysis scripts directly, so it needs core
25
+ # as well as vision — it is not a thin front end.
26
+ dependencies = [
27
+ "descriptron-core==2.0.3",
28
+ "descriptron-vision==2.0.3",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ dev = ["pytest>=7", "build>=1", "twine>=5"]
33
+
34
+ [project.scripts]
35
+ descriptron-gui = "descriptron_gui.cli:main"
36
+
37
+ [project.gui-scripts]
38
+ # on Windows this launches without a console window behind it
39
+ descriptron-gui-app = "descriptron_gui.cli:main"
40
+
41
+ [project.urls]
42
+ Homepage = "https://github.com/alexrvandam/Descriptron"
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["src/descriptron_gui"]
@@ -13200,6 +13200,9 @@ def open_pal_popup():
13200
13200
  chunk_size_var = tk.StringVar(value="0")
13201
13201
  iou_threshold_var = tk.StringVar(value="0.5")
13202
13202
  load_checkpoint_var = tk.StringVar(value="")
13203
+ # v21 opt-in robustness options (both OFF by default; image in the template's orientation when possible)
13204
+ orientation_search_var = tk.BooleanVar(value=False)
13205
+ flip_augment_var = tk.BooleanVar(value=False)
13203
13206
 
13204
13207
  # Create scrollable frame for the popup
13205
13208
  main_canvas = tk.Canvas(popup)
@@ -13226,12 +13229,12 @@ def open_pal_popup():
13226
13229
 
13227
13230
  # Version and status info
13228
13231
  status_parts = [f"Script: {pal_script_name}"]
13229
- if version_str in ["v17", "v18"]:
13232
+ if version_str in ["v17", "v18", "v19", "v20", "v21"]:
13230
13233
  status_parts.append("Multi-mask: [OK]")
13231
- if version_str == "v18":
13234
+ if version_str in ["v18", "v19", "v20", "v21"]:
13232
13235
  status_parts.append("OOM-fix: [OK]")
13233
13236
  status_parts.append("Cycle-consistency: [OK]")
13234
- if version_str in ["v16", "v17"]:
13237
+ if version_str in ["v16", "v17", "v18", "v19", "v20", "v21"]:
13235
13238
  status_parts.append(f"LoRA: {'[OK] Available' if lora_available else 'Install peft'}")
13236
13239
  status_text = " | ".join(status_parts)
13237
13240
  tk.Label(scrollable_popup, text=status_text, fg="blue", font=("Arial", 9)).grid(row=row, column=0, columnspan=3, pady=(0, 5))
@@ -13286,7 +13289,7 @@ This forces the model to learn REAL tracking, not just memorization."""
13286
13289
  tk.Button(json_frame, text="Browse", command=lambda: browse_file_pal(template_json_var, "Select Template COCO JSON", [("JSON Files", "*.json")])).grid(row=0, column=2, padx=5, pady=3)
13287
13290
 
13288
13291
  # Info about multi-mask
13289
- if version_str == "v17":
13292
+ if version_str in ["v17", "v18", "v19", "v20", "v21"]:
13290
13293
  tk.Label(json_frame, text="v17: Multiple masks (e.g., scape, antenna, eye) will ALL be trained and predicted",
13291
13294
  fg="green", font=("Arial", 8)).grid(row=1, column=0, columnspan=3, sticky='w', padx=5)
13292
13295
  else:
@@ -13370,7 +13373,7 @@ This forces the model to learn REAL tracking, not just memorization."""
13370
13373
  row += 1
13371
13374
 
13372
13375
  # === LoRA Section (v16/v17 only) ===
13373
- if version_str in ["v16", "v17"]:
13376
+ if version_str in ["v16", "v17", "v18", "v19", "v20", "v21"]:
13374
13377
  lora_frame = tk.LabelFrame(scrollable_popup, text="LoRA Fine-tuning (v16+ - Optional)", padx=5, pady=5)
13375
13378
  lora_frame.grid(row=row, column=0, columnspan=3, padx=10, pady=5, sticky='ew')
13376
13379
 
@@ -13404,7 +13407,7 @@ This forces the model to learn REAL tracking, not just memorization."""
13404
13407
  tk.Entry(output_frame, textvariable=num_points_var, width=5).grid(row=2, column=2, sticky='w', padx=2)
13405
13408
 
13406
13409
  # V18 inference strategy options
13407
- if version_str in ["v18"]:
13410
+ if version_str in ["v18", "v19", "v20", "v21"]:
13408
13411
  v18_frame = tk.LabelFrame(output_frame, text="v18 Inference Strategy", padx=3, pady=3)
13409
13412
  v18_frame.grid(row=3, column=0, columnspan=3, sticky='ew', padx=5, pady=5)
13410
13413
 
@@ -13435,6 +13438,15 @@ This forces the model to learn REAL tracking, not just memorization."""
13435
13438
  tk.Button(v18_frame, text="Browse", command=_browse_load_ckpt, width=8).grid(row=3, column=2, padx=2)
13436
13439
  tk.Label(v18_frame, text="(skip training, use existing .pt for inference only)",
13437
13440
  fg="gray", font=("Arial", 8)).grid(row=4, column=0, columnspan=3, sticky='w', padx=5)
13441
+ if version_str == "v21":
13442
+ tk.Checkbutton(v18_frame, text="Orientation search (specimens may be rotated; 4x slower)",
13443
+ variable=orientation_search_var).grid(row=5, column=0, columnspan=3, sticky='w')
13444
+ tk.Label(v18_frame, text="(tries 0/90/180/270 deg per image, keeps the most confident; cannot fix mirrored "
13445
+ "imaging - best practice is to image in the template's orientation)",
13446
+ fg="gray", font=("Arial", 8), wraplength=520, justify='left').grid(row=6, column=0, columnspan=3,
13447
+ sticky='w', padx=5)
13448
+ tk.Checkbutton(v18_frame, text="Flip augmentation during fine-tuning (original + h + v + hv copies)",
13449
+ variable=flip_augment_var).grid(row=7, column=0, columnspan=3, sticky='w')
13438
13450
 
13439
13451
  row += 1
13440
13452
 
@@ -13498,7 +13510,7 @@ This forces the model to learn REAL tracking, not just memorization."""
13498
13510
  do_finetune = enable_finetune_var.get()
13499
13511
  if do_finetune:
13500
13512
  # === FIX: Allow COCO JSON mode for fine-tuning in v17 ===
13501
- if mode == "json" and version_str not in ["v17", "v18"]:
13513
+ if mode == "json" and version_str not in ["v17", "v18", "v19", "v20", "v21"]:
13502
13514
  messagebox.showerror("Error", "PAL fine-tuning with COCO JSON requires v17.\nPlease use Binary Mask mode or upgrade to sam2_pal_batch_v17.py")
13503
13515
  return
13504
13516
  # For mask mode, still require a mask file
@@ -13569,7 +13581,7 @@ This forces the model to learn REAL tracking, not just memorization."""
13569
13581
  command.extend(['--training_images_dir', training_images_dir_var.get()])
13570
13582
 
13571
13583
  # LoRA options (v16/v17 only)
13572
- if version_str in ["v16", "v17", "v18"] and use_lora_var.get() and lora_available:
13584
+ if version_str in ["v16", "v17", "v18", "v19", "v20", "v21"] and use_lora_var.get() and lora_available:
13573
13585
  command.append('--use_lora')
13574
13586
  command.extend(['--lora_rank', lora_rank_var.get()])
13575
13587
 
@@ -13584,7 +13596,7 @@ This forces the model to learn REAL tracking, not just memorization."""
13584
13596
  command.append('--interleave_template')
13585
13597
 
13586
13598
  # V18 inference strategy options
13587
- if version_str in ["v18"]:
13599
+ if version_str in ["v18", "v19", "v20", "v21"]:
13588
13600
  if load_checkpoint_var.get() and os.path.exists(load_checkpoint_var.get()):
13589
13601
  command.extend(['--load_checkpoint', load_checkpoint_var.get()])
13590
13602
  if cycle_consistency_var.get():
@@ -13595,6 +13607,11 @@ This forces the model to learn REAL tracking, not just memorization."""
13595
13607
  iou_thr = float(iou_threshold_var.get() or "0.5")
13596
13608
  if iou_thr != 0.5:
13597
13609
  command.extend(['--iou_threshold', str(iou_thr)])
13610
+ if version_str == "v21":
13611
+ if orientation_search_var.get():
13612
+ command.extend(['--orientation_search', 'rot4'])
13613
+ if do_finetune and flip_augment_var.get():
13614
+ command.extend(['--flip_augment', 'all'])
13598
13615
 
13599
13616
  # === FIX: Save settings to YAML file ===
13600
13617
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
@@ -13636,6 +13653,8 @@ This forces the model to learn REAL tracking, not just memorization."""
13636
13653
  'chunk_size': chunk_size_var.get(),
13637
13654
  'iou_threshold': iou_threshold_var.get(),
13638
13655
  'load_checkpoint': load_checkpoint_var.get(),
13656
+ 'orientation_search': orientation_search_var.get(),
13657
+ 'flip_augment': flip_augment_var.get(),
13639
13658
  },
13640
13659
  'command': ' '.join(command),
13641
13660
  }
@@ -15491,6 +15510,10 @@ def _dinoland_build_cmd(v, script_path, env=None):
15491
15510
  cmd += ["--batch_n", str(int(v["batch_n"]))]
15492
15511
  if v.get("emit_refs"):
15493
15512
  cmd.append("--emit_refs")
15513
+ if v.get("mirror_refs"):
15514
+ cmd.append("--mirror_refs")
15515
+ if v.get("rotate_refs"):
15516
+ cmd += ["--orientation_search", "rot4"] # v52+ (same flag name as SAM2-PAL)
15494
15517
  else: # pair
15495
15518
  cmd += ["--imgB", v["imgB"]]
15496
15519
  if v.get("maskB"):
@@ -15542,6 +15565,8 @@ def run_dinoland_script():
15542
15565
  facet = tk.StringVar(value="token"); align = tk.StringVar(value="mask")
15543
15566
  preview = tk.StringVar(value="original"); fig_per_page = tk.StringVar(value="12")
15544
15567
  emit_refs = tk.BooleanVar(value=True); extra = tk.StringVar()
15568
+ mirror_refs = tk.BooleanVar(value=False)
15569
+ rotate_refs = tk.BooleanVar(value=False)
15545
15570
 
15546
15571
  IMG_TYPES = [("Images", "*.jpg *.jpeg *.png *.tif *.tiff"), ("All Files", "*.*")]
15547
15572
  JSON_TYPES = [("COCO keypoints JSON", "*.json"), ("All Files", "*.*")]
@@ -15607,6 +15632,20 @@ def run_dinoland_script():
15607
15632
  tk.OptionMenu(of, align, "mask", "feature").pack(side=tk.LEFT)
15608
15633
  tk.OptionMenu(of, preview, "original", "letterbox").pack(side=tk.LEFT)
15609
15634
  tk.Checkbutton(of, text="emit per-image COCO + refs_confirmed.txt", variable=emit_refs).pack(side=tk.LEFT, padx=8)
15635
+ row += 1
15636
+ tk.Checkbutton(input_window, variable=mirror_refs,
15637
+ text="Batch: specimens may be mirror images (wings, legs, left/right parts) - "
15638
+ "also use a flipped copy of every reference").grid(row=row, column=1, sticky='w', padx=5, pady=3)
15639
+ row += 1
15640
+ if (Path(__file__).parent / 'dinov3_landmark_transfer_v52.py').is_file():
15641
+ tk.Checkbutton(input_window, variable=rotate_refs,
15642
+ text="Batch: specimens may be turned or upside-down - orientation search over "
15643
+ "0/90/180/270 deg (not sure how specimens were imaged? tick both boxes)"
15644
+ ).grid(row=row, column=1, sticky='w', padx=5, pady=3)
15645
+ row += 1
15646
+ tk.Label(input_window, text="Tip: 5 or more references from different species transfer best "
15647
+ "(Diaphorina wings: 61% -> 70% of landmarks within 5% of wing length).",
15648
+ fg="gray30").grid(row=row, column=1, sticky='w', padx=5)
15610
15649
  add_row("Extra arguments:", extra)
15611
15650
 
15612
15651
  def submit():
@@ -15617,7 +15656,8 @@ def run_dinoland_script():
15617
15656
  mask_dir=mask_dir.get().strip(), outdir=outdir.get().strip() or "lm_out",
15618
15657
  model=model_id.get(), layers=layers.get(), n_lm=n_lm.get(), radius=radius.get(),
15619
15658
  dim=dim.get(), facet=facet.get(), align=align.get(), preview=preview.get(),
15620
- fig_per_page=fig_per_page.get(), emit_refs=emit_refs.get(), extra=extra.get())
15659
+ fig_per_page=fig_per_page.get(), emit_refs=emit_refs.get(), extra=extra.get(),
15660
+ mirror_refs=mirror_refs.get(), rotate_refs=rotate_refs.get())
15621
15661
  if not v["imgA"]:
15622
15662
  messagebox.showerror("Input Error", "Please select the reference image A.")
15623
15663
  return
@@ -15629,7 +15669,9 @@ def run_dinoland_script():
15629
15669
  return
15630
15670
 
15631
15671
  script_dir = Path(__file__).parent
15632
- dinoland_script = script_dir / 'dinov3_landmark_transfer_v51.py'
15672
+ dinoland_script = script_dir / 'dinov3_landmark_transfer_v52.py' # v52 = v51 + --rotate_refs
15673
+ if not dinoland_script.is_file():
15674
+ dinoland_script = script_dir / 'dinov3_landmark_transfer_v51.py'
15633
15675
  if not dinoland_script.is_file():
15634
15676
  messagebox.showerror("Script Error", f"DINOLand script not found at: {dinoland_script}")
15635
15677
  return
File without changes
File without changes