from models.training import Training from models.TrainingProjectDetails import TrainingProjectDetails from models.TrainingSize import TrainingSize from database.database import db def push_yolox_exp_to_db(settings): """Save YOLOX settings to database""" normalized = dict(settings) # Map common frontend aliases to DB column names alias_map = { 'act': 'activation', 'nmsthre': 'nms_thre', 'select_model': 'selected_model' } for a, b in alias_map.items(): if a in normalized and b not in normalized: normalized[b] = normalized.pop(a) # Convert 'on'/'off' or 'true'/'false' strings to boolean for known boolean fields for bool_field in ['save_history_ckpt', 'ema', 'enable_mixup']: if bool_field in normalized: val = normalized[bool_field] if isinstance(val, str): normalized[bool_field] = val.lower() in ('1', 'true', 'on') else: normalized[bool_field] = bool(val) # Extract size arrays for separate TrainingSize table (3NF) size_arrays = {} for key in ['input_size', 'test_size', 'mosaic_scale', 'mixup_scale']: if key in normalized: if isinstance(normalized[key], str): parts = [p.strip() for p in normalized[key].split(',') if p.strip()] try: arr = [float(p) for p in parts] except Exception: arr = parts size_arrays[key] = arr[0] if len(arr) == 1 else (arr if isinstance(arr, list) else [arr]) elif isinstance(normalized[key], list): size_arrays[key] = normalized[key] elif normalized[key] is not None: size_arrays[key] = [float(normalized[key])] # Remove from normalized dict since it won't be stored in training table del normalized[key] # Ensure we have a TrainingProjectDetails row for project_id project_id = normalized.get('project_id') if not project_id: raise Exception('Missing project_id in settings') details = TrainingProjectDetails.query.filter_by(project_id=project_id).first() if not details: raise Exception(f'TrainingProjectDetails not found for project_id {project_id}') normalized['project_details_id'] = details.id # Filter normalized to only columns that exist on the Training model valid_cols = {c.name: c for c in Training.__table__.columns} filtered = {} for k, v in normalized.items(): if k in valid_cols: col_type = valid_cols[k].type.__class__.__name__ # Try to coerce types for numeric/boolean columns try: if 'Integer' in col_type: if v is None or v == '': filtered[k] = None else: filtered[k] = int(float(v)) elif 'Float' in col_type: if v is None or v == '': filtered[k] = None else: filtered[k] = float(v) elif 'Boolean' in col_type: if isinstance(v, str): filtered[k] = v.lower() in ('1', 'true', 'on') else: filtered[k] = bool(v) elif 'LargeBinary' in col_type: # If a file path was passed, store its bytes; otherwise store raw bytes if isinstance(v, str): try: filtered[k] = v.encode('utf-8') except Exception: filtered[k] = None else: filtered[k] = v else: filtered[k] = v except Exception: # If conversion fails, just assign raw value filtered[k] = v # Create DB row training = Training(**filtered) db.session.add(training) db.session.flush() # Get training.id # Save size arrays to TrainingSize table (3NF) for size_type, values in size_arrays.items(): if values and isinstance(values, list): for order, value in enumerate(values): size_record = TrainingSize( training_id=training.id, size_type=size_type, value_order=order, value=float(value) ) db.session.add(size_record) db.session.commit() return training