implemented locking in training setup
This commit is contained in:
217
TRANSFER_LEARNING_FEATURE.md
Normal file
217
TRANSFER_LEARNING_FEATURE.md
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
# Transfer Learning Base Configuration Feature
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This feature implements automatic loading of base configurations when "Train on COCO" transfer learning is selected. Base parameters are loaded from `backend/data/` based on the selected YOLOX model, and these protected fields are displayed as greyed out and non-editable in the frontend.
|
||||||
|
|
||||||
|
## Components Modified/Created
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
#### 1. Base Configuration Files (`backend/data/`)
|
||||||
|
- **`yolox_s.py`** - Base config for YOLOX-Small (depth=0.33, width=0.50)
|
||||||
|
- **`yolox_m.py`** - Base config for YOLOX-Medium (depth=0.67, width=0.75)
|
||||||
|
- **`yolox_l.py`** - Base config for YOLOX-Large (depth=1.0, width=1.0)
|
||||||
|
- **`yolox_x.py`** - Base config for YOLOX-XLarge (depth=1.33, width=1.25)
|
||||||
|
|
||||||
|
Each file contains a `BaseExp` class with protected parameters:
|
||||||
|
- Model architecture (depth, width, activation)
|
||||||
|
- Training hyperparameters (max_epoch, warmup_epochs, scheduler, etc.)
|
||||||
|
- Optimizer settings (momentum, weight_decay)
|
||||||
|
- Augmentation probabilities (mosaic_prob, mixup_prob, etc.)
|
||||||
|
- Input/output sizes
|
||||||
|
|
||||||
|
#### 2. Services (`backend/services/generate_yolox_exp.py`)
|
||||||
|
**New functions:**
|
||||||
|
- `load_base_config(selected_model)` - Dynamically loads base config using importlib
|
||||||
|
- Modified `generate_yolox_inference_exp()` to support `use_base_config` parameter
|
||||||
|
- Base config merging logic: base → user overrides → defaults
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- `transfer_learning='coco'` → loads base config + applies user overrides
|
||||||
|
- `transfer_learning='sketch'` → uses only user-defined values
|
||||||
|
- Protected parameters from base config are preserved unless explicitly overridden
|
||||||
|
|
||||||
|
#### 3. API Routes (`backend/routes/api.py`)
|
||||||
|
**New endpoint:**
|
||||||
|
```python
|
||||||
|
@api_bp.route('/base-config/<model_name>', methods=['GET'])
|
||||||
|
def get_base_config(model_name):
|
||||||
|
```
|
||||||
|
Returns the base configuration JSON for a specific YOLOX model.
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
#### 1. HTML (`edit-training.html`)
|
||||||
|
**Added:**
|
||||||
|
- Info banner to indicate when base config is active
|
||||||
|
- CSS styles for disabled input fields (grey background, not-allowed cursor)
|
||||||
|
- Visual feedback showing which model's base config is loaded
|
||||||
|
|
||||||
|
**Banner HTML:**
|
||||||
|
```html
|
||||||
|
<div id="base-config-info" style="display:none; ...">
|
||||||
|
🔒 Base Configuration Active
|
||||||
|
Protected parameters are loaded from [model] base config
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**CSS for disabled fields:**
|
||||||
|
```css
|
||||||
|
.setting-row input[type="number"]:disabled,
|
||||||
|
.setting-row input[type="text"]:disabled,
|
||||||
|
.setting-row input[type="checkbox"]:disabled {
|
||||||
|
background: #d3d3d3 !important;
|
||||||
|
color: #666 !important;
|
||||||
|
cursor: not-allowed !important;
|
||||||
|
border: 1px solid #999 !important;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. JavaScript (`js/start-training.js`)
|
||||||
|
**New functionality:**
|
||||||
|
|
||||||
|
1. **Base Config Loading:**
|
||||||
|
```javascript
|
||||||
|
function loadBaseConfig(modelName)
|
||||||
|
```
|
||||||
|
Fetches base config from `/api/base-config/<model>`
|
||||||
|
|
||||||
|
2. **Apply Base Config:**
|
||||||
|
```javascript
|
||||||
|
function applyBaseConfig(config, isCocoMode)
|
||||||
|
```
|
||||||
|
- Applies config values to form fields
|
||||||
|
- Disables and greys out protected fields
|
||||||
|
- Shows/hides info banner
|
||||||
|
- Adds tooltips to disabled fields
|
||||||
|
|
||||||
|
3. **Update Transfer Learning Mode:**
|
||||||
|
```javascript
|
||||||
|
function updateTransferLearningMode()
|
||||||
|
```
|
||||||
|
- Monitors changes to "Transfer Learning" dropdown
|
||||||
|
- Monitors changes to "Select Model" dropdown
|
||||||
|
- Loads appropriate base config when COCO mode is selected
|
||||||
|
- Clears base config when sketch mode is selected
|
||||||
|
|
||||||
|
4. **Form Submission Enhancement:**
|
||||||
|
- Temporarily enables disabled fields before submission
|
||||||
|
- Ensures protected parameters are included in form data
|
||||||
|
- Re-disables fields after collection
|
||||||
|
|
||||||
|
**Protected Fields List:**
|
||||||
|
```javascript
|
||||||
|
const protectedFields = [
|
||||||
|
'depth', 'width', 'act', 'max_epoch', 'warmup_epochs', 'warmup_lr',
|
||||||
|
'scheduler', 'no_aug_epochs', 'min_lr_ratio', 'ema', 'weight_decay',
|
||||||
|
'momentum', 'input_size', 'mosaic_scale', 'test_size', 'enable_mixup',
|
||||||
|
'mosaic_prob', 'mixup_prob', 'hsv_prob', 'flip_prob', 'degrees',
|
||||||
|
'translate', 'shear', 'mixup_scale', 'print_interval', 'eval_interval'
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## User Flow
|
||||||
|
|
||||||
|
### 1. Normal Custom Training (Train from sketch)
|
||||||
|
- User selects model: e.g., "YOLOX-s"
|
||||||
|
- User selects "Train from sketch"
|
||||||
|
- All fields are editable (white background)
|
||||||
|
- User can customize all parameters
|
||||||
|
- Submission uses user-defined values only
|
||||||
|
|
||||||
|
### 2. COCO Transfer Learning (Train on COCO)
|
||||||
|
- User selects model: e.g., "YOLOX-s"
|
||||||
|
- User selects "Train on coco"
|
||||||
|
- **Automatic actions:**
|
||||||
|
1. Frontend calls `/api/base-config/YOLOX-s`
|
||||||
|
2. Base config is loaded and applied
|
||||||
|
3. Protected fields become greyed out and disabled
|
||||||
|
4. Green info banner appears: "🔒 Base Configuration Active"
|
||||||
|
5. Tooltip on hover: "Protected by base config for YOLOX-s. Switch to 'Train from sketch' to customize."
|
||||||
|
- User can still edit non-protected fields
|
||||||
|
- On submit: both base config values AND user overrides are sent to backend
|
||||||
|
- Backend generates exp.py with merged settings
|
||||||
|
|
||||||
|
### 3. Switching Models
|
||||||
|
- User changes from "YOLOX-s" to "YOLOX-l" (while in COCO mode)
|
||||||
|
- Frontend automatically:
|
||||||
|
1. Fetches new base config for YOLOX-l
|
||||||
|
2. Updates field values (depth=1.0, width=1.0, etc.)
|
||||||
|
3. Updates banner text to show "YOLOX-l"
|
||||||
|
- Protected parameters update to match new model's architecture
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Manual Test Steps:
|
||||||
|
|
||||||
|
1. **Test Base Config Loading:**
|
||||||
|
```bash
|
||||||
|
cd backend/data
|
||||||
|
python test_base_configs.py
|
||||||
|
```
|
||||||
|
Should display all parameters for yolox-s, yolox-m, yolox-l, yolox-x
|
||||||
|
|
||||||
|
2. **Test API Endpoint:**
|
||||||
|
```bash
|
||||||
|
# Start Flask server
|
||||||
|
cd backend
|
||||||
|
python app.py
|
||||||
|
|
||||||
|
# In another terminal:
|
||||||
|
curl http://localhost:3000/api/base-config/YOLOX-s
|
||||||
|
```
|
||||||
|
Should return JSON with depth, width, activation, etc.
|
||||||
|
|
||||||
|
3. **Test Frontend:**
|
||||||
|
- Open `edit-training.html?id=<project_id>` in browser
|
||||||
|
- Select "YOLOX-s" model
|
||||||
|
- Select "Train on coco" → fields should grey out
|
||||||
|
- Select "Train from sketch" → fields should become editable
|
||||||
|
- Switch to "YOLOX-l" (in COCO mode) → values should update
|
||||||
|
- Open browser console and check for: `Applied base config. Protected fields: depth, width, ...`
|
||||||
|
|
||||||
|
4. **Test Form Submission:**
|
||||||
|
- With COCO mode active (fields greyed out)
|
||||||
|
- Click "Save Parameters"
|
||||||
|
- Check browser Network tab → POST to `/api/yolox-settings`
|
||||||
|
- Verify payload includes protected parameters (depth, width, etc.)
|
||||||
|
- Check Flask logs for successful save
|
||||||
|
|
||||||
|
### Expected Behaviors:
|
||||||
|
|
||||||
|
✅ **COCO mode + YOLOX-s:**
|
||||||
|
- depth: 0.33 (greyed out)
|
||||||
|
- width: 0.50 (greyed out)
|
||||||
|
- activation: silu (greyed out)
|
||||||
|
- Info banner visible
|
||||||
|
|
||||||
|
✅ **COCO mode + YOLOX-l:**
|
||||||
|
- depth: 1.0 (greyed out)
|
||||||
|
- width: 1.0 (greyed out)
|
||||||
|
- activation: silu (greyed out)
|
||||||
|
|
||||||
|
✅ **Sketch mode:**
|
||||||
|
- All fields white/editable
|
||||||
|
- No info banner
|
||||||
|
- User can set any values
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- **`backend/data/README.md`** - Complete guide on base config system
|
||||||
|
- **`backend/data/test_base_configs.py`** - Test script for base configs
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
1. **Proven defaults:** Users start with battle-tested COCO pretraining settings
|
||||||
|
2. **Prevents mistakes:** Can't accidentally break model architecture by changing depth/width
|
||||||
|
3. **Easy customization:** Can still override specific parameters if needed
|
||||||
|
4. **Visual feedback:** Clear indication of which fields are protected
|
||||||
|
5. **Model-specific:** Each model (s/m/l/x) has appropriate architecture defaults
|
||||||
|
6. **Flexible:** Can easily add new models by creating new base config files
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
- Add "Override" button next to protected fields to unlock individual parameters
|
||||||
|
- Show diff comparison between base config and user overrides
|
||||||
|
- Add validation warnings if user tries values far from base config ranges
|
||||||
|
- Export final merged config as preview before training
|
||||||
28
backend/0815/27/exp_infer.py
Normal file
28
backend/0815/27/exp_infer.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
# Copyright (c) Megvii, Inc. and its affiliates.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from yolox.exp import Exp as MyExp
|
||||||
|
|
||||||
|
|
||||||
|
class Exp(MyExp):
|
||||||
|
def __init__(self):
|
||||||
|
super(Exp, self).__init__()
|
||||||
|
self.data_dir = "/home/kitraining/To_Annotate/"
|
||||||
|
self.train_ann = "coco_project_27_train.json"
|
||||||
|
self.val_ann = "coco_project_27_valid.json"
|
||||||
|
self.test_ann = "coco_project_27_test.json"
|
||||||
|
self.num_classes = 80
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-s.pth'
|
||||||
|
|
||||||
|
|
||||||
|
self.depth = 1.0
|
||||||
|
self.width = 1.0
|
||||||
|
self.input_size = (640.0, 640.0)
|
||||||
|
self.mosaic_scale = (0.1, 2.0)
|
||||||
|
self.random_size = (10, 20)
|
||||||
|
self.test_size = (640.0, 640.0)
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
|
self.enable_mixup = False
|
||||||
25
backend/asdf/5/exp_infer.py
Normal file
25
backend/asdf/5/exp_infer.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
# Copyright (c) Megvii, Inc. and its affiliates.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from yolox.exp import Exp as MyExp
|
||||||
|
|
||||||
|
|
||||||
|
class Exp(MyExp):
|
||||||
|
def __init__(self):
|
||||||
|
super(Exp, self).__init__()
|
||||||
|
self.data_dir = "/home/kitraining/To_Annotate/"
|
||||||
|
self.train_ann = "coco_project_5_train.json"
|
||||||
|
self.val_ann = "coco_project_5_valid.json"
|
||||||
|
self.test_ann = "coco_project_5_test.json"
|
||||||
|
self.num_classes = 4
|
||||||
|
self.depth = 1.0
|
||||||
|
self.width = 1.0
|
||||||
|
self.input_size = (640, 640)
|
||||||
|
self.mosaic_scale = (0.1, 2)
|
||||||
|
self.random_size = (10, 20)
|
||||||
|
self.test_size = (640, 640)
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
|
self.enable_mixup = False
|
||||||
140
backend/data/README.md
Normal file
140
backend/data/README.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# YOLOX Base Configuration System
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This directory contains base experiment configurations for YOLOX models. These configurations define "protected" parameters that are preserved during transfer learning from COCO-pretrained models.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### Transfer Learning Flow
|
||||||
|
|
||||||
|
1. **COCO Transfer Learning** (`transfer_learning = 'coco'`):
|
||||||
|
- Loads base configuration from `data/yolox_*.py` based on `selected_model`
|
||||||
|
- Base parameters are **protected** and used as defaults
|
||||||
|
- User settings from the form only override what's explicitly set
|
||||||
|
- Result: Best of both worlds - proven COCO settings + your customizations
|
||||||
|
|
||||||
|
2. **Sketch/Custom Training** (`transfer_learning = 'sketch'`):
|
||||||
|
- No base configuration loaded
|
||||||
|
- Uses only user-defined parameters from the training form
|
||||||
|
- Full control over all settings
|
||||||
|
|
||||||
|
### Base Configuration Files
|
||||||
|
|
||||||
|
- `yolox_s.py` - YOLOX-Small (depth=0.33, width=0.50)
|
||||||
|
- `yolox_m.py` - YOLOX-Medium (depth=0.67, width=0.75)
|
||||||
|
- `yolox_l.py` - YOLOX-Large (depth=1.0, width=1.0)
|
||||||
|
- `yolox_x.py` - YOLOX-XLarge (depth=1.33, width=1.25)
|
||||||
|
|
||||||
|
### Protected Parameters
|
||||||
|
|
||||||
|
These parameters are defined in base configs and **preserved** unless explicitly overridden:
|
||||||
|
|
||||||
|
**Model Architecture:**
|
||||||
|
- `depth` - Model depth multiplier
|
||||||
|
- `width` - Model width multiplier
|
||||||
|
- `activation` - Activation function (silu)
|
||||||
|
|
||||||
|
**Training Hyperparameters:**
|
||||||
|
- `basic_lr_per_img` - Learning rate per image
|
||||||
|
- `scheduler` - LR scheduler (yoloxwarmcos)
|
||||||
|
- `warmup_epochs` - Warmup epochs
|
||||||
|
- `max_epoch` - Maximum training epochs
|
||||||
|
- `no_aug_epochs` - No augmentation epochs
|
||||||
|
- `min_lr_ratio` - Minimum LR ratio
|
||||||
|
|
||||||
|
**Optimizer:**
|
||||||
|
- `momentum` - SGD momentum
|
||||||
|
- `weight_decay` - Weight decay
|
||||||
|
|
||||||
|
**Augmentation:**
|
||||||
|
- `mosaic_prob` - Mosaic probability
|
||||||
|
- `mixup_prob` - Mixup probability
|
||||||
|
- `hsv_prob` - HSV augmentation probability
|
||||||
|
- `flip_prob` - Flip probability
|
||||||
|
- `degrees` - Rotation degrees
|
||||||
|
- `translate` - Translation
|
||||||
|
- `shear` - Shear
|
||||||
|
- `mosaic_scale` - Mosaic scale range
|
||||||
|
- `mixup_scale` - Mixup scale range
|
||||||
|
- `enable_mixup` - Enable mixup
|
||||||
|
|
||||||
|
**Input/Output:**
|
||||||
|
- `input_size` - Training input size
|
||||||
|
- `test_size` - Testing size
|
||||||
|
- `random_size` - Random size range
|
||||||
|
|
||||||
|
**Evaluation:**
|
||||||
|
- `eval_interval` - Evaluation interval
|
||||||
|
- `print_interval` - Print interval
|
||||||
|
|
||||||
|
## Customizing Base Configurations
|
||||||
|
|
||||||
|
### Adding a New Model
|
||||||
|
|
||||||
|
Create a new file `data/yolox_MODELNAME.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
# Base configuration for YOLOX-MODELNAME
|
||||||
|
|
||||||
|
class BaseExp:
|
||||||
|
"""Base experiment configuration for YOLOX-MODELNAME"""
|
||||||
|
|
||||||
|
# Define protected parameters
|
||||||
|
depth = 1.0
|
||||||
|
width = 1.0
|
||||||
|
# ... other parameters
|
||||||
|
```
|
||||||
|
|
||||||
|
### Modifying Parameters
|
||||||
|
|
||||||
|
Edit the corresponding `yolox_*.py` file and update the `BaseExp` class attributes.
|
||||||
|
|
||||||
|
**Example:** To change YOLOX-S max epochs:
|
||||||
|
```python
|
||||||
|
# In data/yolox_s.py
|
||||||
|
class BaseExp:
|
||||||
|
max_epoch = 500 # Changed from 300
|
||||||
|
# ... other parameters
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameter Priority
|
||||||
|
|
||||||
|
The merge logic follows this priority (highest to lowest):
|
||||||
|
|
||||||
|
1. **User form values** (if explicitly set, not None)
|
||||||
|
2. **Base config values** (if transfer_learning='coco')
|
||||||
|
3. **Default fallbacks** (hardcoded minimums)
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
### COCO Transfer Learning
|
||||||
|
```
|
||||||
|
User sets in form: max_epoch=100, depth=0.5
|
||||||
|
Base config (yolox_s.py) has: depth=0.33, width=0.50, max_epoch=300
|
||||||
|
|
||||||
|
Result: depth=0.5 (user override), width=0.50 (base), max_epoch=100 (user override)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sketch Training
|
||||||
|
```
|
||||||
|
User sets in form: max_epoch=100, depth=0.5
|
||||||
|
No base config loaded
|
||||||
|
|
||||||
|
Result: depth=0.5 (user), max_epoch=100 (user), width=1.0 (default fallback)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
To see which base config was loaded, check Flask logs:
|
||||||
|
```
|
||||||
|
Loaded base config for yolox-s: ['depth', 'width', 'activation', ...]
|
||||||
|
```
|
||||||
|
|
||||||
|
If base config fails to load:
|
||||||
|
```
|
||||||
|
Warning: Could not load base config for yolox-s: [error message]
|
||||||
|
Falling back to custom settings only
|
||||||
|
```
|
||||||
1
backend/data/__init__.py
Normal file
1
backend/data/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Base experiment configurations for YOLOX models
|
||||||
79
backend/data/test_base_configs.py
Normal file
79
backend/data/test_base_configs.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test script to demonstrate base configuration loading for YOLOX models
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
|
||||||
|
from services.generate_yolox_exp import load_base_config
|
||||||
|
|
||||||
|
def test_base_configs():
|
||||||
|
"""Test loading all base configurations"""
|
||||||
|
models = ['yolox-s', 'yolox-m', 'yolox-l', 'yolox-x']
|
||||||
|
|
||||||
|
print("=" * 80)
|
||||||
|
print("YOLOX Base Configuration Test")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
for model in models:
|
||||||
|
print(f"\n{'='*80}")
|
||||||
|
print(f"Model: {model.upper()}")
|
||||||
|
print(f"{'='*80}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
config = load_base_config(model)
|
||||||
|
|
||||||
|
# Group parameters by category
|
||||||
|
arch_params = ['depth', 'width', 'activation']
|
||||||
|
training_params = ['max_epoch', 'warmup_epochs', 'basic_lr_per_img', 'scheduler',
|
||||||
|
'no_aug_epochs', 'min_lr_ratio']
|
||||||
|
optimizer_params = ['momentum', 'weight_decay']
|
||||||
|
augmentation_params = ['mosaic_prob', 'mixup_prob', 'hsv_prob', 'flip_prob',
|
||||||
|
'degrees', 'translate', 'shear', 'mosaic_scale',
|
||||||
|
'mixup_scale', 'enable_mixup']
|
||||||
|
input_params = ['input_size', 'test_size', 'random_size']
|
||||||
|
eval_params = ['eval_interval', 'print_interval']
|
||||||
|
|
||||||
|
print("\n[Architecture]")
|
||||||
|
for param in arch_params:
|
||||||
|
if param in config:
|
||||||
|
print(f" {param:25s} = {config[param]}")
|
||||||
|
|
||||||
|
print("\n[Training Hyperparameters]")
|
||||||
|
for param in training_params:
|
||||||
|
if param in config:
|
||||||
|
print(f" {param:25s} = {config[param]}")
|
||||||
|
|
||||||
|
print("\n[Optimizer]")
|
||||||
|
for param in optimizer_params:
|
||||||
|
if param in config:
|
||||||
|
print(f" {param:25s} = {config[param]}")
|
||||||
|
|
||||||
|
print("\n[Data Augmentation]")
|
||||||
|
for param in augmentation_params:
|
||||||
|
if param in config:
|
||||||
|
print(f" {param:25s} = {config[param]}")
|
||||||
|
|
||||||
|
print("\n[Input/Output]")
|
||||||
|
for param in input_params:
|
||||||
|
if param in config:
|
||||||
|
print(f" {param:25s} = {config[param]}")
|
||||||
|
|
||||||
|
print("\n[Evaluation]")
|
||||||
|
for param in eval_params:
|
||||||
|
if param in config:
|
||||||
|
print(f" {param:25s} = {config[param]}")
|
||||||
|
|
||||||
|
print(f"\n✓ Successfully loaded {len(config)} parameters")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Error loading config: {e}")
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("Test Complete")
|
||||||
|
print("="*80)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test_base_configs()
|
||||||
15
backend/data/yolox_l.py
Normal file
15
backend/data/yolox_l.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
# Base configuration for YOLOX-L model
|
||||||
|
# These parameters are preserved during transfer learning from COCO
|
||||||
|
|
||||||
|
class BaseExp:
|
||||||
|
"""Base experiment configuration for YOLOX-L"""
|
||||||
|
|
||||||
|
# Model architecture (protected - always use these for yolox-l)
|
||||||
|
depth = 1.0
|
||||||
|
width = 1.0
|
||||||
|
|
||||||
|
scheduler = "yoloxwarmcos"
|
||||||
|
|
||||||
|
activation = "silu"
|
||||||
15
backend/data/yolox_m.py
Normal file
15
backend/data/yolox_m.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
# Base configuration for YOLOX-M model
|
||||||
|
# These parameters are preserved during transfer learning from COCO
|
||||||
|
|
||||||
|
class BaseExp:
|
||||||
|
"""Base experiment configuration for YOLOX-M"""
|
||||||
|
|
||||||
|
# Model architecture (protected - always use these for yolox-m)
|
||||||
|
depth = 0.67
|
||||||
|
width = 0.75
|
||||||
|
|
||||||
|
scheduler = "yoloxwarmcos"
|
||||||
|
|
||||||
|
activation = "silu"
|
||||||
17
backend/data/yolox_s.py
Normal file
17
backend/data/yolox_s.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
# Base configuration for YOLOX-S model
|
||||||
|
# These parameters are preserved during transfer learning from COCO
|
||||||
|
|
||||||
|
class BaseExp:
|
||||||
|
"""Base experiment configuration for YOLOX-S"""
|
||||||
|
|
||||||
|
# Model architecture (protected - always use these for yolox-s)
|
||||||
|
depth = 0.33
|
||||||
|
width = 0.50
|
||||||
|
|
||||||
|
scheduler = "yoloxwarmcos"
|
||||||
|
|
||||||
|
activation = "silu"
|
||||||
|
|
||||||
|
|
||||||
15
backend/data/yolox_x.py
Normal file
15
backend/data/yolox_x.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
# Base configuration for YOLOX-X model
|
||||||
|
# These parameters are preserved during transfer learning from COCO
|
||||||
|
|
||||||
|
class BaseExp:
|
||||||
|
"""Base experiment configuration for YOLOX-X"""
|
||||||
|
|
||||||
|
# Model architecture (protected - always use these for yolox-x)
|
||||||
|
depth = 1.33
|
||||||
|
width = 1.25
|
||||||
|
|
||||||
|
scheduler = "yoloxwarmcos"
|
||||||
|
|
||||||
|
activation = "silu"
|
||||||
@@ -529,3 +529,13 @@ def delete_training_project(id):
|
|||||||
except Exception as error:
|
except Exception as error:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
return jsonify({'message': 'Failed to delete training project', 'error': str(error)}), 500
|
return jsonify({'message': 'Failed to delete training project', 'error': str(error)}), 500
|
||||||
|
|
||||||
|
@api_bp.route('/base-config/<model_name>', methods=['GET'])
|
||||||
|
def get_base_config(model_name):
|
||||||
|
"""Get base configuration for a specific YOLOX model"""
|
||||||
|
try:
|
||||||
|
from services.generate_yolox_exp import load_base_config
|
||||||
|
config = load_base_config(model_name)
|
||||||
|
return jsonify(config)
|
||||||
|
except Exception as error:
|
||||||
|
return jsonify({'message': f'Failed to load base config for {model_name}', 'error': str(error)}), 404
|
||||||
|
|||||||
@@ -1,8 +1,31 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import importlib.util
|
||||||
from models.training import Training
|
from models.training import Training
|
||||||
from models.TrainingProject import TrainingProject
|
from models.TrainingProject import TrainingProject
|
||||||
|
|
||||||
|
def load_base_config(selected_model):
|
||||||
|
"""Load base configuration for a specific YOLOX model"""
|
||||||
|
model_name = selected_model.lower().replace('-', '_').replace('.pth', '')
|
||||||
|
base_config_path = os.path.join(os.path.dirname(__file__), '..', 'data', f'{model_name}.py')
|
||||||
|
|
||||||
|
if not os.path.exists(base_config_path):
|
||||||
|
raise Exception(f'Base configuration not found for model: {model_name} at {base_config_path}')
|
||||||
|
|
||||||
|
# Load the module dynamically
|
||||||
|
spec = importlib.util.spec_from_file_location(f"base_config_{model_name}", base_config_path)
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
|
||||||
|
# Extract all attributes from BaseExp class
|
||||||
|
base_exp = module.BaseExp()
|
||||||
|
base_config = {}
|
||||||
|
for attr in dir(base_exp):
|
||||||
|
if not attr.startswith('_'):
|
||||||
|
base_config[attr] = getattr(base_exp, attr)
|
||||||
|
|
||||||
|
return base_config
|
||||||
|
|
||||||
def generate_yolox_exp(training_id):
|
def generate_yolox_exp(training_id):
|
||||||
"""Generate YOLOX exp.py file"""
|
"""Generate YOLOX exp.py file"""
|
||||||
# Fetch training row from DB
|
# Fetch training row from DB
|
||||||
@@ -13,26 +36,14 @@ def generate_yolox_exp(training_id):
|
|||||||
if not training:
|
if not training:
|
||||||
raise Exception(f'Training not found for trainingId or project_details_id: {training_id}')
|
raise Exception(f'Training not found for trainingId or project_details_id: {training_id}')
|
||||||
|
|
||||||
# If transfer_learning is 'coco', copy default exp.py
|
# If transfer_learning is 'coco', generate exp using base config + custom settings
|
||||||
if training.transfer_learning == 'coco':
|
if training.transfer_learning == 'coco':
|
||||||
selected_model = training.selected_model.lower().replace('-', '_')
|
exp_content = generate_yolox_inference_exp(training_id, use_base_config=True)
|
||||||
exp_source_path = f'/home/kitraining/Yolox/YOLOX-main/exps/default/{selected_model}.py'
|
return {'type': 'custom', 'expContent': exp_content}
|
||||||
|
|
||||||
if not os.path.exists(exp_source_path):
|
|
||||||
raise Exception(f'Default exp.py not found for model: {selected_model} at {exp_source_path}')
|
|
||||||
|
|
||||||
# Copy to project folder
|
|
||||||
project_details_id = training.project_details_id
|
|
||||||
project_folder = os.path.join(os.path.dirname(__file__), '..', f'project_23/{project_details_id}')
|
|
||||||
os.makedirs(project_folder, exist_ok=True)
|
|
||||||
|
|
||||||
exp_dest_path = os.path.join(project_folder, 'exp.py')
|
|
||||||
shutil.copyfile(exp_source_path, exp_dest_path)
|
|
||||||
return {'type': 'default', 'expPath': exp_dest_path}
|
|
||||||
|
|
||||||
# If transfer_learning is 'sketch', generate custom exp.py
|
# If transfer_learning is 'sketch', generate custom exp.py
|
||||||
if training.transfer_learning == 'sketch':
|
if training.transfer_learning == 'sketch':
|
||||||
exp_content = generate_yolox_inference_exp(training_id)
|
exp_content = generate_yolox_inference_exp(training_id, use_base_config=False)
|
||||||
return {'type': 'custom', 'expContent': exp_content}
|
return {'type': 'custom', 'expContent': exp_content}
|
||||||
|
|
||||||
raise Exception(f'Unknown transfer_learning type: {training.transfer_learning}')
|
raise Exception(f'Unknown transfer_learning type: {training.transfer_learning}')
|
||||||
@@ -53,8 +64,14 @@ def save_yolox_exp(training_id, out_path):
|
|||||||
else:
|
else:
|
||||||
raise Exception('Unknown expResult type or missing content')
|
raise Exception('Unknown expResult type or missing content')
|
||||||
|
|
||||||
def generate_yolox_inference_exp(training_id, options=None):
|
def generate_yolox_inference_exp(training_id, options=None, use_base_config=False):
|
||||||
"""Generate inference exp.py using DB values"""
|
"""Generate inference exp.py using DB values
|
||||||
|
|
||||||
|
Args:
|
||||||
|
training_id: The training/project_details ID
|
||||||
|
options: Optional overrides for data paths
|
||||||
|
use_base_config: If True, load base config and only override with user-defined values
|
||||||
|
"""
|
||||||
if options is None:
|
if options is None:
|
||||||
options = {}
|
options = {}
|
||||||
|
|
||||||
@@ -90,14 +107,69 @@ def generate_yolox_inference_exp(training_id, options=None):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'Could not determine num_classes from TrainingProject.classes: {e}')
|
print(f'Could not determine num_classes from TrainingProject.classes: {e}')
|
||||||
|
|
||||||
depth = options.get('depth', training.depth or 1.00)
|
# Initialize config dictionary
|
||||||
width = options.get('width', training.width or 1.00)
|
config = {}
|
||||||
input_size = options.get('input_size', training.input_size or [640, 640])
|
|
||||||
mosaic_scale = options.get('mosaic_scale', training.mosaic_scale or [0.1, 2])
|
# If using base config (transfer learning from COCO), load protected parameters first
|
||||||
random_size = options.get('random_size', [10, 20])
|
if use_base_config and training.selected_model:
|
||||||
test_size = options.get('test_size', training.test_size or [640, 640])
|
try:
|
||||||
exp_name = options.get('exp_name', 'inference_exp')
|
base_config = load_base_config(training.selected_model)
|
||||||
enable_mixup = options.get('enable_mixup', False)
|
config.update(base_config)
|
||||||
|
print(f'Loaded base config for {training.selected_model}: {list(base_config.keys())}')
|
||||||
|
except Exception as e:
|
||||||
|
print(f'Warning: Could not load base config for {training.selected_model}: {e}')
|
||||||
|
print('Falling back to custom settings only')
|
||||||
|
|
||||||
|
# Override with user-defined values from training table (only if they exist and are not None)
|
||||||
|
user_overrides = {
|
||||||
|
'depth': training.depth,
|
||||||
|
'width': training.width,
|
||||||
|
'input_size': training.input_size,
|
||||||
|
'mosaic_scale': training.mosaic_scale,
|
||||||
|
'test_size': training.test_size,
|
||||||
|
'enable_mixup': training.enable_mixup,
|
||||||
|
'max_epoch': training.max_epoch,
|
||||||
|
'warmup_epochs': training.warmup_epochs,
|
||||||
|
'warmup_lr': training.warmup_lr,
|
||||||
|
'basic_lr_per_img': training.basic_lr_per_img,
|
||||||
|
'scheduler': training.scheduler,
|
||||||
|
'no_aug_epochs': training.no_aug_epochs,
|
||||||
|
'min_lr_ratio': training.min_lr_ratio,
|
||||||
|
'ema': training.ema,
|
||||||
|
'weight_decay': training.weight_decay,
|
||||||
|
'momentum': training.momentum,
|
||||||
|
'print_interval': training.print_interval,
|
||||||
|
'eval_interval': training.eval_interval,
|
||||||
|
'test_conf': training.test_conf,
|
||||||
|
'nms_thre': training.nms_thre,
|
||||||
|
'mosaic_prob': training.mosaic_prob,
|
||||||
|
'mixup_prob': training.mixup_prob,
|
||||||
|
'hsv_prob': training.hsv_prob,
|
||||||
|
'flip_prob': training.flip_prob,
|
||||||
|
'degrees': training.degrees,
|
||||||
|
'translate': training.translate,
|
||||||
|
'shear': training.shear,
|
||||||
|
'mixup_scale': training.mixup_scale,
|
||||||
|
'activation': training.activation,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Only override if value is explicitly set (not None)
|
||||||
|
for key, value in user_overrides.items():
|
||||||
|
if value is not None:
|
||||||
|
config[key] = value
|
||||||
|
|
||||||
|
# Apply any additional options overrides
|
||||||
|
config.update(options)
|
||||||
|
|
||||||
|
# Set defaults for any missing required parameters
|
||||||
|
config.setdefault('depth', 1.00)
|
||||||
|
config.setdefault('width', 1.00)
|
||||||
|
config.setdefault('input_size', [640, 640])
|
||||||
|
config.setdefault('mosaic_scale', [0.1, 2])
|
||||||
|
config.setdefault('random_size', [10, 20])
|
||||||
|
config.setdefault('test_size', [640, 640])
|
||||||
|
config.setdefault('enable_mixup', False)
|
||||||
|
config.setdefault('exp_name', 'inference_exp')
|
||||||
|
|
||||||
# Build exp content
|
# Build exp content
|
||||||
exp_content = f'''#!/usr/bin/env python3
|
exp_content = f'''#!/usr/bin/env python3
|
||||||
@@ -115,7 +187,7 @@ class Exp(MyExp):
|
|||||||
self.data_dir = "{data_dir}"
|
self.data_dir = "{data_dir}"
|
||||||
self.train_ann = "{train_ann}"
|
self.train_ann = "{train_ann}"
|
||||||
self.val_ann = "{val_ann}"
|
self.val_ann = "{val_ann}"
|
||||||
self.test_ann = "coco_project_{training_id}_test.json"
|
self.test_ann = "{test_ann}"
|
||||||
self.num_classes = {num_classes}
|
self.num_classes = {num_classes}
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -127,26 +199,30 @@ class Exp(MyExp):
|
|||||||
exp_content += f" self.pretrained_ckpt = r'{yolox_base_dir}/pretrained/{selected_model}.pth'\n"
|
exp_content += f" self.pretrained_ckpt = r'{yolox_base_dir}/pretrained/{selected_model}.pth'\n"
|
||||||
|
|
||||||
# Format arrays
|
# Format arrays
|
||||||
input_size_str = ', '.join(map(str, input_size)) if isinstance(input_size, list) else str(input_size)
|
def format_value(val):
|
||||||
mosaic_scale_str = ', '.join(map(str, mosaic_scale)) if isinstance(mosaic_scale, list) else str(mosaic_scale)
|
if isinstance(val, (list, tuple)):
|
||||||
random_size_str = ', '.join(map(str, random_size)) if isinstance(random_size, list) else str(random_size)
|
return '(' + ', '.join(map(str, val)) + ')'
|
||||||
test_size_str = ', '.join(map(str, test_size)) if isinstance(test_size, list) else str(test_size)
|
elif isinstance(val, bool):
|
||||||
|
return str(val)
|
||||||
|
elif isinstance(val, str):
|
||||||
|
return f'"{val}"'
|
||||||
|
else:
|
||||||
|
return str(val)
|
||||||
|
|
||||||
exp_content += f''' self.depth = {depth}
|
# Add all config parameters to exp
|
||||||
self.width = {width}
|
for key, value in config.items():
|
||||||
self.input_size = ({input_size_str})
|
if key not in ['exp_name']: # exp_name is handled separately
|
||||||
self.mosaic_scale = ({mosaic_scale_str})
|
exp_content += f" self.{key} = {format_value(value)}\n"
|
||||||
self.random_size = ({random_size_str})
|
|
||||||
self.test_size = ({test_size_str})
|
# Add exp_name at the end (uses dynamic path)
|
||||||
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
exp_content += f''' self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
self.enable_mixup = {str(enable_mixup)}
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
return exp_content
|
return exp_content
|
||||||
|
|
||||||
def save_yolox_inference_exp(training_id, out_path, options=None):
|
def save_yolox_inference_exp(training_id, out_path, options=None):
|
||||||
"""Save inference exp.py to custom path"""
|
"""Save inference exp.py to custom path"""
|
||||||
exp_content = generate_yolox_inference_exp(training_id, options)
|
exp_content = generate_yolox_inference_exp(training_id, options, use_base_config=False)
|
||||||
with open(out_path, 'w') as f:
|
with open(out_path, 'w') as f:
|
||||||
f.write(exp_content)
|
f.write(exp_content)
|
||||||
return out_path
|
return out_path
|
||||||
|
|||||||
@@ -5,32 +5,88 @@ from database.database import db
|
|||||||
def push_yolox_exp_to_db(settings):
|
def push_yolox_exp_to_db(settings):
|
||||||
"""Save YOLOX settings to database"""
|
"""Save YOLOX settings to database"""
|
||||||
normalized = dict(settings)
|
normalized = dict(settings)
|
||||||
|
|
||||||
# Map 'act' from frontend to 'activation' for DB
|
# Map common frontend aliases to DB column names
|
||||||
if 'act' in normalized:
|
alias_map = {
|
||||||
normalized['activation'] = normalized['act']
|
'act': 'activation',
|
||||||
del normalized['act']
|
'nmsthre': 'nms_thre',
|
||||||
|
'select_model': 'selected_model'
|
||||||
# Convert 'on'/'off' to boolean for save_history_ckpt
|
}
|
||||||
if isinstance(normalized.get('save_history_ckpt'), str):
|
for a, b in alias_map.items():
|
||||||
normalized['save_history_ckpt'] = normalized['save_history_ckpt'] == 'on'
|
if a in normalized and b not in normalized:
|
||||||
|
normalized[b] = normalized.pop(a)
|
||||||
# Convert comma-separated strings to arrays
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# Convert comma-separated strings to arrays for JSON fields
|
||||||
for key in ['input_size', 'test_size', 'mosaic_scale', 'mixup_scale']:
|
for key in ['input_size', 'test_size', 'mosaic_scale', 'mixup_scale']:
|
||||||
if isinstance(normalized.get(key), str):
|
if key in normalized and isinstance(normalized[key], str):
|
||||||
arr = [float(v.strip()) for v in normalized[key].split(',')]
|
parts = [p.strip() for p in normalized[key].split(',') if p.strip()]
|
||||||
|
try:
|
||||||
|
arr = [float(p) for p in parts]
|
||||||
|
except Exception:
|
||||||
|
arr = parts
|
||||||
normalized[key] = arr[0] if len(arr) == 1 else arr
|
normalized[key] = arr[0] if len(arr) == 1 else arr
|
||||||
|
|
||||||
# Find TrainingProjectDetails for this project
|
# Ensure we have a TrainingProjectDetails row for project_id
|
||||||
details = TrainingProjectDetails.query.filter_by(project_id=normalized['project_id']).first()
|
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:
|
if not details:
|
||||||
raise Exception(f'TrainingProjectDetails not found for project_id {normalized["project_id"]}')
|
raise Exception(f'TrainingProjectDetails not found for project_id {project_id}')
|
||||||
|
|
||||||
normalized['project_details_id'] = details.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 'JSON' in col_type:
|
||||||
|
filtered[k] = 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
|
# Create DB row
|
||||||
training = Training(**normalized)
|
training = Training(**filtered)
|
||||||
db.session.add(training)
|
db.session.add(training)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return training
|
return training
|
||||||
|
|||||||
@@ -65,6 +65,19 @@
|
|||||||
border: 1px solid #009eac;
|
border: 1px solid #009eac;
|
||||||
background: #f8f8f8;
|
background: #f8f8f8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.setting-row input[type="number"]:disabled,
|
||||||
|
.setting-row input[type="text"]:disabled,
|
||||||
|
.setting-row input[type="checkbox"]:disabled {
|
||||||
|
background: #d3d3d3 !important;
|
||||||
|
color: #666 !important;
|
||||||
|
cursor: not-allowed !important;
|
||||||
|
border: 1px solid #999 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row input[disabled]::placeholder {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
.setting-row input[type="checkbox"] {
|
.setting-row input[type="checkbox"] {
|
||||||
margin-right: 18px;
|
margin-right: 18px;
|
||||||
@@ -267,6 +280,9 @@
|
|||||||
<div style="display: flex; flex-wrap: wrap; gap: 32px; justify-content: center; align-items: flex-start; margin-top:32px;">
|
<div style="display: flex; flex-wrap: wrap; gap: 32px; justify-content: center; align-items: flex-start; margin-top:32px;">
|
||||||
<div id="exp-setup" style="flex: 1 1 420px; min-width: 340px; max-width: 600px; margin:0;">
|
<div id="exp-setup" style="flex: 1 1 420px; min-width: 340px; max-width: 600px; margin:0;">
|
||||||
<h2 style="margin-bottom:18px;color:#009eac;">YOLOX Training Settings</h2>
|
<h2 style="margin-bottom:18px;color:#009eac;">YOLOX Training Settings</h2>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<form id="settings-form">
|
<form id="settings-form">
|
||||||
<!-- Autogenerated/Keep Variables -->
|
<!-- Autogenerated/Keep Variables -->
|
||||||
<!-- <div class="config-section">
|
<!-- <div class="config-section">
|
||||||
@@ -361,11 +377,19 @@
|
|||||||
<label class="setting-label" for="test-slider">Test</label>
|
<label class="setting-label" for="test-slider">Test</label>
|
||||||
<input type="range" id="test-slider" name="test" min="0" max="100" value="10" step="1" style="width:120px;">
|
<input type="range" id="test-slider" name="test" min="0" max="100" value="10" step="1" style="width:120px;">
|
||||||
<span id="test-value">10%</span>s
|
<span id="test-value">10%</span>s
|
||||||
</div></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="config-section-foldable" style="margin-top:24px;">
|
<div class="config-section-foldable" style="margin-top:24px;">
|
||||||
<h3>Model Settings</h3>
|
<h3>Model Settings</h3>
|
||||||
<div class="setting-row"><div class="setting-row-inner">
|
<div class="setting-row">
|
||||||
|
<div class="setting-row-inner">
|
||||||
|
<label class="setting-label" for="transfer-learning">Transfer Learning</label>
|
||||||
|
<select id="transfer-learning" name="transfer_learning" onchange="toggleCkptUpload()">
|
||||||
|
<option value="sketch">Train from sketch</option>
|
||||||
|
<option value="coco">Train on coco</option>
|
||||||
|
<option value="custom">Train on custom</option>
|
||||||
|
</select>
|
||||||
|
</div></div>
|
||||||
|
<div class="setting-row" id="select-model-row"><div class="setting-row-inner">
|
||||||
<label class="setting-label" for="select-model">Select Model</label>
|
<label class="setting-label" for="select-model">Select Model</label>
|
||||||
<select id="select-model" name="select_model">
|
<select id="select-model" name="select_model">
|
||||||
<option value="YOLOX-s">YOLOX-s</option>
|
<option value="YOLOX-s">YOLOX-s</option>
|
||||||
@@ -377,15 +401,6 @@
|
|||||||
<option value="YOLOX-Tiny">YOLOX-Tiny</option>
|
<option value="YOLOX-Tiny">YOLOX-Tiny</option>
|
||||||
</select>
|
</select>
|
||||||
</div></div>
|
</div></div>
|
||||||
<div class="setting-row">
|
|
||||||
<div class="setting-row-inner">
|
|
||||||
<label class="setting-label" for="transfer-learning">Transfer Learning</label>
|
|
||||||
<select id="transfer-learning" name="transfer_learning" onchange="toggleCkptUpload()">
|
|
||||||
<option value="sketch">Train from sketch</option>
|
|
||||||
<option value="coco">Train on coco</option>
|
|
||||||
<option value="custom">Train on custom</option>
|
|
||||||
</select>
|
|
||||||
</div></div>
|
|
||||||
<div class="setting-row" id="ckpt-upload-row" style="display:none;">
|
<div class="setting-row" id="ckpt-upload-row" style="display:none;">
|
||||||
<div class="setting-row-inner">
|
<div class="setting-row-inner">
|
||||||
<label class="setting-label" for="ckpt-upload">Upload .pth file</label>
|
<label class="setting-label" for="ckpt-upload">Upload .pth file</label>
|
||||||
@@ -483,6 +498,14 @@ window.addEventListener('DOMContentLoaded', function() { syncSplitSliders('train
|
|||||||
<script>
|
<script>
|
||||||
document.getElementById('parameters-form').addEventListener('submit', async function(e) {
|
document.getElementById('parameters-form').addEventListener('submit', async function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Temporarily enable all disabled fields so they get included
|
||||||
|
const disabledInputs = [];
|
||||||
|
document.querySelectorAll('#settings-form input[disabled], #settings-form select[disabled]').forEach(input => {
|
||||||
|
input.disabled = false;
|
||||||
|
disabledInputs.push(input);
|
||||||
|
});
|
||||||
|
|
||||||
// Collect YOLOX settings from #settings-form
|
// Collect YOLOX settings from #settings-form
|
||||||
const settingsForm = document.getElementById('settings-form');
|
const settingsForm = document.getElementById('settings-form');
|
||||||
const settingsData = {};
|
const settingsData = {};
|
||||||
@@ -495,6 +518,12 @@ document.getElementById('parameters-form').addEventListener('submit', async func
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Re-disable the inputs
|
||||||
|
disabledInputs.forEach(input => {
|
||||||
|
input.disabled = true;
|
||||||
|
});
|
||||||
|
|
||||||
// Collect parameters from #parameters-form
|
// Collect parameters from #parameters-form
|
||||||
const paramsForm = document.getElementById('parameters-form');
|
const paramsForm = document.getElementById('parameters-form');
|
||||||
Array.from(paramsForm.elements).forEach(el => {
|
Array.from(paramsForm.elements).forEach(el => {
|
||||||
|
|||||||
@@ -3,6 +3,156 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
// Get the form element at the top
|
// Get the form element at the top
|
||||||
const form = document.getElementById('settings-form');
|
const form = document.getElementById('settings-form');
|
||||||
|
|
||||||
|
// Base config state
|
||||||
|
let currentBaseConfig = null;
|
||||||
|
let baseConfigFields = [];
|
||||||
|
// Define which fields are protected by base config
|
||||||
|
const protectedFields = [
|
||||||
|
'depth', 'width', 'act', 'max_epoch', 'warmup_epochs', 'warmup_lr',
|
||||||
|
'scheduler', 'no_aug_epochs', 'min_lr_ratio', 'ema', 'weight_decay',
|
||||||
|
'momentum', 'input_size', 'mosaic_scale', 'test_size', 'enable_mixup',
|
||||||
|
'mosaic_prob', 'mixup_prob', 'hsv_prob', 'flip_prob', 'degrees',
|
||||||
|
'translate', 'shear', 'mixup_scale', 'print_interval', 'eval_interval'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Map backend field names to frontend field names
|
||||||
|
const fieldNameMap = {
|
||||||
|
'activation': 'act', // Backend uses 'activation', frontend uses 'act'
|
||||||
|
'nms_thre': 'nmsthre'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to load base config for selected model
|
||||||
|
function loadBaseConfig(modelName) {
|
||||||
|
if (!modelName) return Promise.resolve(null);
|
||||||
|
|
||||||
|
return fetch(`/api/base-config/${modelName}`)
|
||||||
|
.then(res => {
|
||||||
|
if (!res.ok) throw new Error('Base config not found');
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.warn(`Could not load base config for ${modelName}:`, err);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to apply base config to form fields
|
||||||
|
function applyBaseConfig(config, isCocoMode) {
|
||||||
|
const infoBanner = document.getElementById('base-config-info');
|
||||||
|
const modelNameSpan = document.getElementById('base-config-model');
|
||||||
|
|
||||||
|
if (!config || !isCocoMode) {
|
||||||
|
// Hide info banner
|
||||||
|
if (infoBanner) infoBanner.style.display = 'none';
|
||||||
|
|
||||||
|
// Remove grey styling and enable all fields
|
||||||
|
protectedFields.forEach(fieldName => {
|
||||||
|
const input = form.querySelector(`[name="${fieldName}"]`);
|
||||||
|
if (input) {
|
||||||
|
input.disabled = false;
|
||||||
|
input.style.backgroundColor = '#f8f8f8';
|
||||||
|
input.style.color = '#333';
|
||||||
|
input.style.cursor = 'text';
|
||||||
|
input.title = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
baseConfigFields = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show info banner
|
||||||
|
if (infoBanner) {
|
||||||
|
infoBanner.style.display = 'block';
|
||||||
|
const modelName = form.querySelector('[name="select_model"]')?.value || 'selected model';
|
||||||
|
if (modelNameSpan) modelNameSpan.textContent = modelName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply base config values and grey out fields
|
||||||
|
baseConfigFields = [];
|
||||||
|
Object.entries(config).forEach(([key, value]) => {
|
||||||
|
// Map backend field name to frontend field name if needed
|
||||||
|
const frontendFieldName = fieldNameMap[key] || key;
|
||||||
|
|
||||||
|
if (protectedFields.includes(frontendFieldName)) {
|
||||||
|
const input = form.querySelector(`[name="${frontendFieldName}"]`);
|
||||||
|
if (input) {
|
||||||
|
baseConfigFields.push(frontendFieldName);
|
||||||
|
|
||||||
|
// Set value based on type
|
||||||
|
if (input.type === 'checkbox') {
|
||||||
|
input.checked = Boolean(value);
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
input.value = value.join(',');
|
||||||
|
} else {
|
||||||
|
input.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grey out and disable
|
||||||
|
input.disabled = true;
|
||||||
|
input.style.backgroundColor = '#d3d3d3';
|
||||||
|
input.style.color = '#666';
|
||||||
|
input.style.cursor = 'not-allowed';
|
||||||
|
|
||||||
|
// Add title tooltip
|
||||||
|
const modelName = form.querySelector('[name="select_model"]')?.value || 'selected model';
|
||||||
|
input.title = `Protected by base config for ${modelName}. Switch to "Train from sketch" to customize.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Applied base config. Protected fields: ${baseConfigFields.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to update form based on transfer learning mode
|
||||||
|
function updateTransferLearningMode() {
|
||||||
|
const transferLearning = document.getElementById('transfer-learning');
|
||||||
|
const selectModel = document.getElementById('select-model');
|
||||||
|
const selectModelRow = document.getElementById('select-model-row');
|
||||||
|
|
||||||
|
if (!transferLearning || !selectModel) return;
|
||||||
|
|
||||||
|
const isCocoMode = transferLearning.value === 'coco';
|
||||||
|
const isCustomMode = transferLearning.value === 'custom';
|
||||||
|
const isSketchMode = transferLearning.value === 'sketch';
|
||||||
|
const modelName = selectModel.value;
|
||||||
|
|
||||||
|
// Show/hide select model based on transfer learning mode
|
||||||
|
if (selectModelRow) {
|
||||||
|
if (isSketchMode) {
|
||||||
|
selectModelRow.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
selectModelRow.style.display = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCocoMode && modelName) {
|
||||||
|
// Load and apply base config
|
||||||
|
loadBaseConfig(modelName).then(config => {
|
||||||
|
currentBaseConfig = config;
|
||||||
|
applyBaseConfig(config, true);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Clear base config
|
||||||
|
currentBaseConfig = null;
|
||||||
|
applyBaseConfig(null, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for changes to transfer learning dropdown
|
||||||
|
const transferLearningSelect = document.getElementById('transfer-learning');
|
||||||
|
if (transferLearningSelect) {
|
||||||
|
transferLearningSelect.addEventListener('change', updateTransferLearningMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for changes to model selection
|
||||||
|
const modelSelect = document.getElementById('select-model');
|
||||||
|
if (modelSelect) {
|
||||||
|
modelSelect.addEventListener('change', updateTransferLearningMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial update on page load
|
||||||
|
setTimeout(updateTransferLearningMode, 100);
|
||||||
|
|
||||||
// Auto-set num_classes from training_project classes array
|
// Auto-set num_classes from training_project classes array
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
@@ -43,17 +193,26 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
form.addEventListener('submit', function(e) {
|
form.addEventListener('submit', function(e) {
|
||||||
console.log("Form submitted");
|
console.log("Form submitted");
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Temporarily enable disabled fields so they get included in FormData
|
||||||
|
const disabledInputs = [];
|
||||||
|
form.querySelectorAll('input[disabled], select[disabled]').forEach(input => {
|
||||||
|
input.disabled = false;
|
||||||
|
disabledInputs.push(input);
|
||||||
|
});
|
||||||
|
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
const settings = {};
|
const settings = {};
|
||||||
let fileToUpload = null;
|
let fileToUpload = null;
|
||||||
|
|
||||||
for (const [key, value] of formData.entries()) {
|
for (const [key, value] of formData.entries()) {
|
||||||
if (key === 'model_upload' && form.elements[key].files.length > 0) {
|
if (key === 'model_upload' && form.elements[key].files.length > 0) {
|
||||||
fileToUpload = form.elements[key].files[0];
|
fileToUpload = form.elements[key].files[0];
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (key === 'ema' || key === 'enable_mixup') {
|
if (key === 'ema' || key === 'enable_mixup' || key === 'save_history_ckpt') {
|
||||||
settings[key] = form.elements[key].checked;
|
settings[key] = form.elements[key].checked;
|
||||||
} else if (key === 'scale' || key === 'mosaic_scale') {
|
} else if (key === 'scale' || key === 'mosaic_scale' || key === 'mixup_scale' || key === 'input_size' || key === 'test_size') {
|
||||||
settings[key] = value.split(',').map(v => parseFloat(v.trim()));
|
settings[key] = value.split(',').map(v => parseFloat(v.trim()));
|
||||||
} else if (!isNaN(value) && value !== '') {
|
} else if (!isNaN(value) && value !== '') {
|
||||||
settings[key] = parseFloat(value);
|
settings[key] = parseFloat(value);
|
||||||
@@ -61,6 +220,12 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
settings[key] = value;
|
settings[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-disable the inputs
|
||||||
|
disabledInputs.forEach(input => {
|
||||||
|
input.disabled = true;
|
||||||
|
});
|
||||||
|
|
||||||
// Attach project id from URL
|
// Attach project id from URL
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const projectId = urlParams.get('id');
|
const projectId = urlParams.get('id');
|
||||||
|
|||||||
Reference in New Issue
Block a user