ibsocr1 commited on
Commit
24bf943
·
verified ·
1 Parent(s): df02181

Upload 5 files

Browse files
Files changed (2) hide show
  1. app.py +28 -17
  2. training/train.py +2 -2
app.py CHANGED
@@ -4,7 +4,6 @@ import os
4
  import shutil
5
  import subprocess
6
  import sys
7
- import threading
8
  from collections import Counter
9
  from pathlib import Path
10
 
@@ -329,14 +328,18 @@ def build_coco():
329
  if not classes:
330
  raise RuntimeError("No classes configured.")
331
  items = [x for x in data["images"] if x.get("annotations")]
332
- if len(items) < 2:
333
- raise RuntimeError("Annotate at least 2 images before training.")
334
-
335
- # Deterministic split; upload a reasonably shuffled dataset.
336
- split = max(1, int(len(items) * 0.8))
337
- if split == len(items):
338
- split -= 1
339
- train_items, val_items = items[:split], items[split:]
 
 
 
 
340
  category_id = {name: i + 1 for i, name in enumerate(classes)}
341
 
342
  def make_coco(selected):
@@ -397,7 +400,8 @@ def run_training(epochs, batch_size, learning_rate):
397
  ]
398
  result = subprocess.run(cmd, cwd=ROOT, capture_output=True, text=True)
399
  if result.returncode != 0:
400
- raise RuntimeError((result.stderr or result.stdout)[-8000:])
 
401
  _model = None
402
  _training = {"running": False, "message": "training complete", "error": None}
403
  except Exception as e:
@@ -405,14 +409,21 @@ def run_training(epochs, batch_size, learning_rate):
405
 
406
 
407
  def start_training(epochs, batch_size, learning_rate):
 
 
 
 
 
 
408
  if _training["running"]:
409
- return "Training is already running."
410
- threading.Thread(
411
- target=run_training,
412
- args=(epochs, batch_size, learning_rate),
413
- daemon=True,
414
- ).start()
415
- return "Training started in the background. Use Refresh Training Status."
 
416
 
417
 
418
  def training_status():
 
4
  import shutil
5
  import subprocess
6
  import sys
 
7
  from collections import Counter
8
  from pathlib import Path
9
 
 
328
  if not classes:
329
  raise RuntimeError("No classes configured.")
330
  items = [x for x in data["images"] if x.get("annotations")]
331
+ if not items:
332
+ raise RuntimeError("Annotate at least 1 image before training.")
333
+
334
+ # With only one annotated image, use it for both training and validation so
335
+ # the first training run is possible. With 2+ images, use an 80/20 split.
336
+ if len(items) == 1:
337
+ train_items, val_items = items, items
338
+ else:
339
+ split = max(1, int(len(items) * 0.8))
340
+ if split >= len(items):
341
+ split = len(items) - 1
342
+ train_items, val_items = items[:split], items[split:]
343
  category_id = {name: i + 1 for i, name in enumerate(classes)}
344
 
345
  def make_coco(selected):
 
400
  ]
401
  result = subprocess.run(cmd, cwd=ROOT, capture_output=True, text=True)
402
  if result.returncode != 0:
403
+ details = result.stderr.strip() or result.stdout.strip() or f"training process exited with code {result.returncode}"
404
+ raise RuntimeError(details[-12000:])
405
  _model = None
406
  _training = {"running": False, "message": "training complete", "error": None}
407
  except Exception as e:
 
409
 
410
 
411
  def start_training(epochs, batch_size, learning_rate):
412
+ """Start training from the Gradio event itself.
413
+
414
+ Calling the @spaces.GPU function directly is important on Hugging Face
415
+ ZeroGPU: starting it from a normal Python background thread can bypass the
416
+ GPU allocation context, making the button appear to do nothing.
417
+ """
418
  if _training["running"]:
419
+ return json.dumps(_training, indent=2)
420
+ # Basic validation before requesting GPU time.
421
+ annotated = sum(bool(x.get("annotations")) for x in load_dataset()["images"])
422
+ if annotated < 1:
423
+ _training = {"running": False, "message": "training failed", "error": "Annotate at least 1 image before training."}
424
+ return json.dumps(_training, indent=2)
425
+ run_training(epochs, batch_size, learning_rate)
426
+ return json.dumps(_training, indent=2)
427
 
428
 
429
  def training_status():
training/train.py CHANGED
@@ -93,9 +93,9 @@ def main():
93
  val=COCODetectionDataset(Path(a.val_dir)/"images",Path(a.val_dir)/"annotations.json",proc)
94
 
95
  if len(train)==0 or len(val)==0:
96
- raise ValueError("Both train and validation splits must contain annotated images.")
97
  if len(train.category_id_to_label)!=len(classes) or len(val.category_id_to_label)!=len(classes):
98
- raise ValueError("COCO categories do not match classes.txt.")
99
 
100
  model=RTDetrForObjectDetection.from_pretrained(
101
  BASE_MODEL,num_labels=len(classes),id2label=id2label,label2id=label2id,
 
93
  val=COCODetectionDataset(Path(a.val_dir)/"images",Path(a.val_dir)/"annotations.json",proc)
94
 
95
  if len(train)==0 or len(val)==0:
96
+ raise ValueError("Training and validation datasets must contain at least one image.")
97
  if len(train.category_id_to_label)!=len(classes) or len(val.category_id_to_label)!=len(classes):
98
+ raise ValueError("COCO categories do not match classes.txt. Rebuild the dataset after saving the classes.")
99
 
100
  model=RTDetrForObjectDetection.from_pretrained(
101
  BASE_MODEL,num_labels=len(classes),id2label=id2label,label2id=label2id,