cleanup add training bell
This commit is contained in:
0
.gitignore
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
@@ -1,217 +0,0 @@
|
|||||||
# 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
|
|
||||||
124
add-project.html
Normal file → Executable file
124
add-project.html
Normal file → Executable file
@@ -29,6 +29,14 @@
|
|||||||
<icon class="header-icon" onclick="window.location.href='/index.html'" , onmouseover=""
|
<icon class="header-icon" onclick="window.location.href='/index.html'" , onmouseover=""
|
||||||
style="cursor: pointer;" src="./media/logo.png" alt="Logo"></icon>
|
style="cursor: pointer;" src="./media/logo.png" alt="Logo"></icon>
|
||||||
<div class="button-row">
|
<div class="button-row">
|
||||||
|
<!-- Training Notification Bell -->
|
||||||
|
<button id="training-bell" onclick="toggleTrainingModal()" class="button" title="Training Status"
|
||||||
|
style="padding: 8px 16px; margin-right: 10px; position: relative; background: #999;">
|
||||||
|
🔔
|
||||||
|
<span id="bell-badge" style="display: none; position: absolute; top: -5px; right: -5px; background: #ff4d4f;
|
||||||
|
color: white; border-radius: 50%; width: 20px; height: 20px; font-size: 12px; line-height: 20px;
|
||||||
|
text-align: center; font-weight: bold;">0</span>
|
||||||
|
</button>
|
||||||
<button id="Add Training Project" class="button-red">Add Training Project</button>
|
<button id="Add Training Project" class="button-red">Add Training Project</button>
|
||||||
<button id="Add Dataset" class="button">Add Dataset</button>
|
<button id="Add Dataset" class="button">Add Dataset</button>
|
||||||
<button id="Import Dataset" class="button">Refresh Label-Studio</button>
|
<button id="Import Dataset" class="button">Refresh Label-Studio</button>
|
||||||
@@ -234,7 +242,123 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Training Status Modal -->
|
||||||
|
<div id="training-status-modal" class="modal" style="display: none;">
|
||||||
|
<div class="modal-content" style="max-width: 700px; max-height: 90vh; overflow-y: auto;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Training Status</h2>
|
||||||
|
<button class="close-btn" onclick="toggleTrainingModal()">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="settings-section" id="current-training-section" style="display: none;">
|
||||||
|
<h3 style="color: #009eac;">Current Training</h3>
|
||||||
|
<div id="current-training-info" style="background: #eaf7fa; padding: 16px; border-radius: 8px; margin-bottom: 16px;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-section" id="queue-section" style="display: none;">
|
||||||
|
<h3 style="color: #666;">Queue (<span id="queue-count">0</span>)</h3>
|
||||||
|
<div id="queue-list" style="display: flex; flex-direction: column; gap: 12px;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="no-trainings-msg" style="text-align: center; padding: 32px; color: #666;">
|
||||||
|
No trainings running or queued.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="js/settings.js"></script>
|
<script src="js/settings.js"></script>
|
||||||
|
<script>
|
||||||
|
let trainingStatusPoller = null;
|
||||||
|
|
||||||
|
function toggleTrainingModal() {
|
||||||
|
const modal = document.getElementById('training-status-modal');
|
||||||
|
if (modal.style.display === 'none') {
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
updateTrainingStatus();
|
||||||
|
} else {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTrainingStatus() {
|
||||||
|
fetch('/api/training-status')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
const bell = document.getElementById('training-bell');
|
||||||
|
const badge = document.getElementById('bell-badge');
|
||||||
|
const currentSection = document.getElementById('current-training-section');
|
||||||
|
const queueSection = document.getElementById('queue-section');
|
||||||
|
const noTrainingsMsg = document.getElementById('no-trainings-msg');
|
||||||
|
|
||||||
|
const totalCount = (data.current ? 1 : 0) + data.queue.length;
|
||||||
|
|
||||||
|
if (totalCount > 0) {
|
||||||
|
bell.style.background = '#009eac';
|
||||||
|
badge.style.display = 'block';
|
||||||
|
badge.textContent = totalCount;
|
||||||
|
} else {
|
||||||
|
bell.style.background = '#999';
|
||||||
|
badge.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.current) {
|
||||||
|
currentSection.style.display = 'block';
|
||||||
|
noTrainingsMsg.style.display = 'none';
|
||||||
|
|
||||||
|
const percentage = Math.round((data.current.iteration / data.current.max_epoch) * 100);
|
||||||
|
document.getElementById('current-training-info').innerHTML = `
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||||
|
<strong>${data.current.name || 'Training'}</strong>
|
||||||
|
<span style="font-weight: bold; color: #009eac;">${percentage}%</span>
|
||||||
|
</div>
|
||||||
|
<div style="background: #ddd; border-radius: 4px; height: 24px; overflow: hidden; margin-bottom: 8px;">
|
||||||
|
<div style="background: #009eac; height: 100%; width: ${percentage}%; transition: width 0.3s;"></div>
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 14px; color: #666;">
|
||||||
|
Epoch ${data.current.iteration} / ${data.current.max_epoch}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
currentSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.queue.length > 0) {
|
||||||
|
queueSection.style.display = 'block';
|
||||||
|
noTrainingsMsg.style.display = 'none';
|
||||||
|
document.getElementById('queue-count').textContent = data.queue.length;
|
||||||
|
|
||||||
|
document.getElementById('queue-list').innerHTML = data.queue.map((t, idx) => `
|
||||||
|
<div style="background: #f5f5f5; padding: 12px; border-radius: 8px; border-left: 4px solid #009eac;">
|
||||||
|
<strong>#${idx + 1}: ${t.name || 'Training'}</strong>
|
||||||
|
<div style="font-size: 13px; color: #666; margin-top: 4px;">
|
||||||
|
${t.max_epoch} epochs • Waiting...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
} else {
|
||||||
|
queueSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalCount === 0) {
|
||||||
|
noTrainingsMsg.style.display = 'block';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => console.error('Failed to fetch training status:', err));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', function() {
|
||||||
|
updateTrainingStatus();
|
||||||
|
trainingStatusPoller = setInterval(updateTrainingStatus, 5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', function() {
|
||||||
|
if (trainingStatusPoller) clearInterval(trainingStatusPoller);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
0
backend/.gitignore
vendored
Normal file → Executable file
0
backend/.gitignore
vendored
Normal file → Executable file
@@ -1,28 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#!/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_2_train.json"
|
|
||||||
self.val_ann = "coco_project_2_valid.json"
|
|
||||||
self.test_ann = "coco_project_2_test.json"
|
|
||||||
self.num_classes = 2
|
|
||||||
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-s.pth'
|
|
||||||
self.activation = "silu"
|
|
||||||
self.depth = 0.33
|
|
||||||
self.scheduler = "yoloxwarmcos"
|
|
||||||
self.width = 0.5
|
|
||||||
self.input_size = (640.0, 640.0)
|
|
||||||
self.mosaic_scale = (0.1, 2.0)
|
|
||||||
self.test_size = (640.0, 640.0)
|
|
||||||
self.enable_mixup = True
|
|
||||||
self.max_epoch = 300
|
|
||||||
self.warmup_epochs = 5
|
|
||||||
self.warmup_lr = 0.0
|
|
||||||
self.no_aug_epochs = 15
|
|
||||||
self.min_lr_ratio = 0.05
|
|
||||||
self.ema = True
|
|
||||||
self.weight_decay = 0.0005
|
|
||||||
self.momentum = 0.9
|
|
||||||
self.print_interval = 10
|
|
||||||
self.eval_interval = 10
|
|
||||||
self.test_conf = 0.01
|
|
||||||
self.nms_thre = 0.65
|
|
||||||
self.mosaic_prob = 1.0
|
|
||||||
self.mixup_prob = 1.0
|
|
||||||
self.hsv_prob = 1.0
|
|
||||||
self.flip_prob = 0.5
|
|
||||||
self.degrees = 10.0
|
|
||||||
self.translate = 0.1
|
|
||||||
self.shear = 2.0
|
|
||||||
self.mixup_scale = (0.5, 1.5)
|
|
||||||
self.random_size = (10, 20)
|
|
||||||
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#!/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_6_train.json"
|
|
||||||
self.val_ann = "coco_project_6_valid.json"
|
|
||||||
self.test_ann = "coco_project_6_test.json"
|
|
||||||
self.num_classes = 2
|
|
||||||
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-s.pth'
|
|
||||||
self.depth = 0.33
|
|
||||||
self.width = 0.5
|
|
||||||
self.input_size = (640.0, 640.0)
|
|
||||||
self.mosaic_scale = (0.1, 2.0)
|
|
||||||
self.test_size = (640.0, 640.0)
|
|
||||||
self.enable_mixup = True
|
|
||||||
self.max_epoch = 300
|
|
||||||
self.warmup_epochs = 5
|
|
||||||
self.warmup_lr = 0.0
|
|
||||||
self.scheduler = "yoloxwarmcos"
|
|
||||||
self.no_aug_epochs = 15
|
|
||||||
self.min_lr_ratio = 0.05
|
|
||||||
self.ema = True
|
|
||||||
self.weight_decay = 0.0005
|
|
||||||
self.momentum = 0.9
|
|
||||||
self.print_interval = 10
|
|
||||||
self.eval_interval = 10
|
|
||||||
self.test_conf = 0.01
|
|
||||||
self.nms_thre = 0.65
|
|
||||||
self.mosaic_prob = 1.0
|
|
||||||
self.mixup_prob = 1.0
|
|
||||||
self.hsv_prob = 1.0
|
|
||||||
self.flip_prob = 0.5
|
|
||||||
self.degrees = 10.0
|
|
||||||
self.translate = 0.1
|
|
||||||
self.shear = 2.0
|
|
||||||
self.mixup_scale = (0.5, 1.5)
|
|
||||||
self.activation = "silu"
|
|
||||||
self.random_size = (10, 20)
|
|
||||||
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
# Quick Start Guide - Python Backend
|
|
||||||
|
|
||||||
## Step-by-Step Setup
|
|
||||||
|
|
||||||
### 1. Install Python
|
|
||||||
Make sure you have Python 3.8 or higher installed:
|
|
||||||
```bash
|
|
||||||
python --version
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Create Virtual Environment
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
python -m venv venv
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Activate Virtual Environment
|
|
||||||
|
|
||||||
**Windows:**
|
|
||||||
```powershell
|
|
||||||
.\venv\Scripts\Activate.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
**Linux/Mac:**
|
|
||||||
```bash
|
|
||||||
source venv/bin/activate
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Install Dependencies
|
|
||||||
```bash
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Verify Database Connection
|
|
||||||
Make sure MySQL is running and the database `myapp` exists:
|
|
||||||
```sql
|
|
||||||
CREATE DATABASE IF NOT EXISTS myapp;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. Run the Server
|
|
||||||
```bash
|
|
||||||
python start.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Or:
|
|
||||||
```bash
|
|
||||||
python app.py
|
|
||||||
```
|
|
||||||
|
|
||||||
The server should now be running at `http://0.0.0.0:3000`
|
|
||||||
|
|
||||||
## Testing the API
|
|
||||||
|
|
||||||
Test if the server is working:
|
|
||||||
```bash
|
|
||||||
curl http://localhost:3000/api/training-projects
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Issues
|
|
||||||
|
|
||||||
### ModuleNotFoundError
|
|
||||||
If you get import errors, make sure you've activated the virtual environment and installed all dependencies.
|
|
||||||
|
|
||||||
### Database Connection Error
|
|
||||||
Check that:
|
|
||||||
- MySQL is running
|
|
||||||
- Database credentials in `app.py` are correct
|
|
||||||
- Database `myapp` exists
|
|
||||||
|
|
||||||
### Port Already in Use
|
|
||||||
If port 3000 is already in use, modify the port in `app.py`:
|
|
||||||
```python
|
|
||||||
app.run(host='0.0.0.0', port=3001, debug=True)
|
|
||||||
```
|
|
||||||
|
|
||||||
## What Changed from Node.js
|
|
||||||
|
|
||||||
1. **Server Framework**: Express.js → Flask
|
|
||||||
2. **ORM**: Sequelize → SQLAlchemy
|
|
||||||
3. **HTTP Client**: node-fetch → requests
|
|
||||||
4. **Package Manager**: npm → pip
|
|
||||||
5. **Dependencies**: package.json → requirements.txt
|
|
||||||
6. **Startup**: `node server.js` → `python app.py`
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. Test all API endpoints
|
|
||||||
2. Update frontend to point to the new Python backend (if needed)
|
|
||||||
3. Migrate any remaining Node.js-specific logic
|
|
||||||
4. Test file uploads and downloads
|
|
||||||
5. Test YOLOX training functionality
|
|
||||||
|
|
||||||
## File Structure Comparison
|
|
||||||
|
|
||||||
**Before (Node.js):**
|
|
||||||
```
|
|
||||||
backend/
|
|
||||||
├── server.js
|
|
||||||
├── package.json
|
|
||||||
├── routes/api.js
|
|
||||||
├── models/*.js
|
|
||||||
└── services/*.js
|
|
||||||
```
|
|
||||||
|
|
||||||
**After (Python):**
|
|
||||||
```
|
|
||||||
backend/
|
|
||||||
├── app.py
|
|
||||||
├── requirements.txt
|
|
||||||
├── routes/api.py
|
|
||||||
├── models/*.py
|
|
||||||
└── services/*.py
|
|
||||||
```
|
|
||||||
0
backend/README.md
Normal file → Executable file
0
backend/README.md
Normal file → Executable file
0
backend/app.py
Normal file → Executable file
0
backend/app.py
Normal file → Executable file
@@ -1,25 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
#!/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" # Where images are located
|
|
||||||
self.annotations_dir = "./backend\\1\\custom_exp_1" # Where annotation JSONs are located
|
|
||||||
self.train_ann = "coco_project_1_train.json"
|
|
||||||
self.val_ann = "coco_project_1_valid.json"
|
|
||||||
self.test_ann = "coco_project_1_test.json"
|
|
||||||
self.num_classes = 2
|
|
||||||
# Disable train2017 subdirectory - our images are directly in data_dir
|
|
||||||
self.name = ""
|
|
||||||
# Set data workers for training
|
|
||||||
self.data_num_workers = 8
|
|
||||||
self.depth = 1.0
|
|
||||||
self.width = 1.0
|
|
||||||
self.input_size = (640.0, 640.0)
|
|
||||||
self.mosaic_scale = (0.1, 2.0)
|
|
||||||
self.test_size = (640.0, 640.0)
|
|
||||||
self.enable_mixup = True
|
|
||||||
self.max_epoch = 300
|
|
||||||
self.warmup_epochs = 5
|
|
||||||
self.warmup_lr = 0.0
|
|
||||||
self.scheduler = "yoloxwarmcos"
|
|
||||||
self.no_aug_epochs = 15
|
|
||||||
self.min_lr_ratio = 0.05
|
|
||||||
self.ema = True
|
|
||||||
self.weight_decay = 0.0005
|
|
||||||
self.momentum = 0.9
|
|
||||||
self.print_interval = 10
|
|
||||||
self.eval_interval = 10
|
|
||||||
self.test_conf = 0.01
|
|
||||||
self.nms_thre = 0.65
|
|
||||||
self.mosaic_prob = 1.0
|
|
||||||
self.mixup_prob = 1.0
|
|
||||||
self.hsv_prob = 1.0
|
|
||||||
self.flip_prob = 0.5
|
|
||||||
self.degrees = 10.0
|
|
||||||
self.translate = 0.1
|
|
||||||
self.shear = 2.0
|
|
||||||
self.mixup_scale = (0.5, 1.5)
|
|
||||||
self.activation = "silu"
|
|
||||||
self.random_size = (10, 20)
|
|
||||||
|
|
||||||
def get_dataset(self, cache=False, cache_type="ram"):
|
|
||||||
"""Override to use name parameter for images directory"""
|
|
||||||
from yolox.data import COCODataset
|
|
||||||
|
|
||||||
# COCODataset constructs image paths as: os.path.join(data_dir, name, file_name)
|
|
||||||
# YOLOX adds "annotations/" to data_dir automatically, so we pass annotations_dir directly
|
|
||||||
# Use empty string for name since we have absolute paths in JSON
|
|
||||||
return COCODataset(
|
|
||||||
data_dir=self.annotations_dir,
|
|
||||||
json_file=self.train_ann,
|
|
||||||
name="",
|
|
||||||
img_size=self.input_size,
|
|
||||||
preproc=self.preproc if hasattr(self, 'preproc') else None,
|
|
||||||
cache=cache,
|
|
||||||
cache_type=cache_type,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_eval_dataset(self, **kwargs):
|
|
||||||
"""Override eval dataset using name parameter"""
|
|
||||||
from yolox.data import COCODataset
|
|
||||||
|
|
||||||
testdev = kwargs.get("testdev", False)
|
|
||||||
legacy = kwargs.get("legacy", False)
|
|
||||||
|
|
||||||
return COCODataset(
|
|
||||||
data_dir=self.annotations_dir,
|
|
||||||
json_file=self.val_ann if not testdev else self.test_ann,
|
|
||||||
name="",
|
|
||||||
img_size=self.test_size,
|
|
||||||
preproc=None, # No preprocessing for evaluation
|
|
||||||
)
|
|
||||||
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,84 +0,0 @@
|
|||||||
#!/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" # Where images are located
|
|
||||||
self.annotations_dir = "./backend/1/custom_exp_1" # Where annotation JSONs are located
|
|
||||||
self.train_ann = "coco_project_1_train.json"
|
|
||||||
self.val_ann = "coco_project_1_valid.json"
|
|
||||||
self.test_ann = "coco_project_1_test.json"
|
|
||||||
self.num_classes = 2
|
|
||||||
# Disable train2017 subdirectory - our images are directly in data_dir
|
|
||||||
self.name = ""
|
|
||||||
# Set data workers for training
|
|
||||||
self.data_num_workers = 8
|
|
||||||
self.depth = 1.0
|
|
||||||
self.width = 1.0
|
|
||||||
self.input_size = (640.0, 640.0)
|
|
||||||
self.mosaic_scale = (0.1, 2.0)
|
|
||||||
self.test_size = (640.0, 640.0)
|
|
||||||
self.enable_mixup = True
|
|
||||||
self.max_epoch = 300
|
|
||||||
self.warmup_epochs = 5
|
|
||||||
self.warmup_lr = 0.0
|
|
||||||
self.scheduler = "yoloxwarmcos"
|
|
||||||
self.no_aug_epochs = 15
|
|
||||||
self.min_lr_ratio = 0.05
|
|
||||||
self.ema = True
|
|
||||||
self.weight_decay = 0.0005
|
|
||||||
self.momentum = 0.9
|
|
||||||
self.print_interval = 10
|
|
||||||
self.eval_interval = 10
|
|
||||||
self.test_conf = 0.01
|
|
||||||
self.nms_thre = 0.65
|
|
||||||
self.mosaic_prob = 1.0
|
|
||||||
self.mixup_prob = 1.0
|
|
||||||
self.hsv_prob = 1.0
|
|
||||||
self.flip_prob = 0.5
|
|
||||||
self.degrees = 10.0
|
|
||||||
self.translate = 0.1
|
|
||||||
self.shear = 2.0
|
|
||||||
self.mixup_scale = (0.5, 1.5)
|
|
||||||
self.activation = "silu"
|
|
||||||
self.random_size = (10, 20)
|
|
||||||
|
|
||||||
def get_dataset(self, cache=False, cache_type="ram"):
|
|
||||||
"""Override to use name parameter for images directory"""
|
|
||||||
from yolox.data import COCODataset
|
|
||||||
|
|
||||||
# COCODataset constructs image paths as: os.path.join(data_dir, name, file_name)
|
|
||||||
# YOLOX adds "annotations/" to data_dir automatically, so we pass annotations_dir directly
|
|
||||||
# Use empty string for name since we have absolute paths in JSON
|
|
||||||
return COCODataset(
|
|
||||||
data_dir=self.annotations_dir,
|
|
||||||
json_file=self.train_ann,
|
|
||||||
name="",
|
|
||||||
img_size=self.input_size,
|
|
||||||
preproc=self.preproc if hasattr(self, 'preproc') else None,
|
|
||||||
cache=cache,
|
|
||||||
cache_type=cache_type,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_eval_dataset(self, **kwargs):
|
|
||||||
"""Override eval dataset using name parameter"""
|
|
||||||
from yolox.data import COCODataset
|
|
||||||
|
|
||||||
testdev = kwargs.get("testdev", False)
|
|
||||||
legacy = kwargs.get("legacy", False)
|
|
||||||
|
|
||||||
return COCODataset(
|
|
||||||
data_dir=self.annotations_dir,
|
|
||||||
json_file=self.val_ann if not testdev else self.test_ann,
|
|
||||||
name="",
|
|
||||||
img_size=self.test_size,
|
|
||||||
preproc=None, # No preprocessing for evaluation
|
|
||||||
)
|
|
||||||
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
#!/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" # Where images are located
|
|
||||||
self.annotations_dir = "./backend\\1\\custom_exp_2" # Where annotation JSONs are located
|
|
||||||
self.train_ann = "coco_project_2_train.json"
|
|
||||||
self.val_ann = "coco_project_2_valid.json"
|
|
||||||
self.test_ann = "coco_project_2_test.json"
|
|
||||||
self.num_classes = 2
|
|
||||||
# Disable train2017 subdirectory - our images are directly in data_dir
|
|
||||||
self.name = ""
|
|
||||||
# Set data workers for training
|
|
||||||
self.data_num_workers = 8
|
|
||||||
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-s.pth'
|
|
||||||
self.activation = "silu"
|
|
||||||
self.depth = 0.33
|
|
||||||
self.scheduler = "yoloxwarmcos"
|
|
||||||
self.width = 0.5
|
|
||||||
self.input_size = (640.0, 640.0)
|
|
||||||
self.mosaic_scale = (0.1, 2.0)
|
|
||||||
self.test_size = (640.0, 640.0)
|
|
||||||
self.enable_mixup = True
|
|
||||||
self.max_epoch = 300
|
|
||||||
self.warmup_epochs = 5
|
|
||||||
self.warmup_lr = 0.0
|
|
||||||
self.no_aug_epochs = 15
|
|
||||||
self.min_lr_ratio = 0.05
|
|
||||||
self.ema = True
|
|
||||||
self.weight_decay = 0.0005
|
|
||||||
self.momentum = 0.9
|
|
||||||
self.print_interval = 10
|
|
||||||
self.eval_interval = 10
|
|
||||||
self.test_conf = 0.01
|
|
||||||
self.nms_thre = 0.65
|
|
||||||
self.mosaic_prob = 1.0
|
|
||||||
self.mixup_prob = 1.0
|
|
||||||
self.hsv_prob = 1.0
|
|
||||||
self.flip_prob = 0.5
|
|
||||||
self.degrees = 10.0
|
|
||||||
self.translate = 0.1
|
|
||||||
self.shear = 2.0
|
|
||||||
self.mixup_scale = (0.5, 1.5)
|
|
||||||
self.random_size = (10, 20)
|
|
||||||
|
|
||||||
def get_dataset(self, cache=False, cache_type="ram"):
|
|
||||||
"""Override to use name parameter for images directory"""
|
|
||||||
from yolox.data import COCODataset
|
|
||||||
|
|
||||||
# COCODataset constructs image paths as: os.path.join(data_dir, name, file_name)
|
|
||||||
# YOLOX adds "annotations/" to data_dir automatically, so we pass annotations_dir directly
|
|
||||||
# Use empty string for name since we have absolute paths in JSON
|
|
||||||
return COCODataset(
|
|
||||||
data_dir=self.annotations_dir,
|
|
||||||
json_file=self.train_ann,
|
|
||||||
name="",
|
|
||||||
img_size=self.input_size,
|
|
||||||
preproc=self.preproc if hasattr(self, 'preproc') else None,
|
|
||||||
cache=cache,
|
|
||||||
cache_type=cache_type,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_eval_dataset(self, **kwargs):
|
|
||||||
"""Override eval dataset using name parameter"""
|
|
||||||
from yolox.data import COCODataset
|
|
||||||
|
|
||||||
testdev = kwargs.get("testdev", False)
|
|
||||||
legacy = kwargs.get("legacy", False)
|
|
||||||
|
|
||||||
return COCODataset(
|
|
||||||
data_dir=self.annotations_dir,
|
|
||||||
json_file=self.val_ann if not testdev else self.test_ann,
|
|
||||||
name="",
|
|
||||||
img_size=self.test_size,
|
|
||||||
preproc=None, # No preprocessing for evaluation
|
|
||||||
)
|
|
||||||
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
|
||||||
0
backend/check_db.py
Normal file → Executable file
0
backend/check_db.py
Normal file → Executable file
0
backend/data/README.md
Normal file → Executable file
0
backend/data/README.md
Normal file → Executable file
0
backend/data/__init__.py
Normal file → Executable file
0
backend/data/__init__.py
Normal file → Executable file
0
backend/data/test_base_configs.py
Normal file → Executable file
0
backend/data/test_base_configs.py
Normal file → Executable file
0
backend/data/yolox_l.py
Normal file → Executable file
0
backend/data/yolox_l.py
Normal file → Executable file
0
backend/data/yolox_m.py
Normal file → Executable file
0
backend/data/yolox_m.py
Normal file → Executable file
0
backend/data/yolox_s.py
Normal file → Executable file
0
backend/data/yolox_s.py
Normal file → Executable file
0
backend/data/yolox_x.py
Normal file → Executable file
0
backend/data/yolox_x.py
Normal file → Executable file
0
backend/database/__init__.py
Normal file → Executable file
0
backend/database/__init__.py
Normal file → Executable file
0
backend/database/database.py
Normal file → Executable file
0
backend/database/database.py
Normal file → Executable file
0
backend/models/Annotation.py
Normal file → Executable file
0
backend/models/Annotation.py
Normal file → Executable file
0
backend/models/AnnotationProjectMapping.py
Normal file → Executable file
0
backend/models/AnnotationProjectMapping.py
Normal file → Executable file
0
backend/models/ClassMapping.py
Normal file → Executable file
0
backend/models/ClassMapping.py
Normal file → Executable file
0
backend/models/Images.py
Normal file → Executable file
0
backend/models/Images.py
Normal file → Executable file
0
backend/models/LabelStudioProject.py
Normal file → Executable file
0
backend/models/LabelStudioProject.py
Normal file → Executable file
0
backend/models/ProjectClass.py
Normal file → Executable file
0
backend/models/ProjectClass.py
Normal file → Executable file
0
backend/models/Settings.py
Normal file → Executable file
0
backend/models/Settings.py
Normal file → Executable file
0
backend/models/TrainingProject.py
Normal file → Executable file
0
backend/models/TrainingProject.py
Normal file → Executable file
0
backend/models/TrainingProjectDetails.py
Normal file → Executable file
0
backend/models/TrainingProjectDetails.py
Normal file → Executable file
0
backend/models/TrainingSize.py
Normal file → Executable file
0
backend/models/TrainingSize.py
Normal file → Executable file
0
backend/models/__init__.py
Normal file → Executable file
0
backend/models/__init__.py
Normal file → Executable file
0
backend/models/training.py
Normal file → Executable file
0
backend/models/training.py
Normal file → Executable file
0
backend/node
Normal file → Executable file
0
backend/node
Normal file → Executable file
0
backend/package-lock.json
generated
Normal file → Executable file
0
backend/package-lock.json
generated
Normal file → Executable file
0
backend/package.json
Normal file → Executable file
0
backend/package.json
Normal file → Executable file
0
backend/requirements.txt
Normal file → Executable file
0
backend/requirements.txt
Normal file → Executable file
0
backend/routes/__init__.py
Normal file → Executable file
0
backend/routes/__init__.py
Normal file → Executable file
36
backend/routes/api.py
Normal file → Executable file
36
backend/routes/api.py
Normal file → Executable file
@@ -90,9 +90,11 @@ def start_yolox_training():
|
|||||||
details_id = training.project_details_id
|
details_id = training.project_details_id
|
||||||
|
|
||||||
# Step 1: Generate COCO JSON files
|
# Step 1: Generate COCO JSON files
|
||||||
|
# IMPORTANT: Pass training_id (not details_id) so JSON files are generated
|
||||||
|
# in the same location where exp.py expects them
|
||||||
from services.generate_json_yolox import generate_training_json
|
from services.generate_json_yolox import generate_training_json
|
||||||
print(f'Generating COCO JSON for training {training_id}...')
|
print(f'Generating COCO JSON for training {training_id}...')
|
||||||
generate_training_json(details_id)
|
generate_training_json(training_id)
|
||||||
|
|
||||||
# Step 2: Generate exp.py
|
# Step 2: Generate exp.py
|
||||||
from services.generate_yolox_exp import save_yolox_exp
|
from services.generate_yolox_exp import save_yolox_exp
|
||||||
@@ -157,8 +159,9 @@ def start_yolox_training():
|
|||||||
venv_activate = yolox_venv
|
venv_activate = yolox_venv
|
||||||
cmd = f'cmd /c ""{venv_activate}" && python tools\\train.py {train_args}"'
|
cmd = f'cmd /c ""{venv_activate}" && python tools\\train.py {train_args}"'
|
||||||
else:
|
else:
|
||||||
# Linux: Use bash with source
|
# Linux: Use bash with source to activate the venv
|
||||||
cmd = f'bash -c "source {yolox_venv} && python tools/train.py {train_args}"'
|
venv_activate = os.path.join(yolox_venv, 'bin', 'activate')
|
||||||
|
cmd = f'bash -c "source {venv_activate} && python tools/train.py {train_args}"'
|
||||||
|
|
||||||
print(f'Training command: {cmd}')
|
print(f'Training command: {cmd}')
|
||||||
|
|
||||||
@@ -496,17 +499,26 @@ def yolox_settings():
|
|||||||
settings['activation'] = settings['act']
|
settings['activation'] = settings['act']
|
||||||
del settings['act']
|
del settings['act']
|
||||||
|
|
||||||
# Type conversions
|
# Type conversions - Integer fields
|
||||||
numeric_fields = [
|
integer_fields = [
|
||||||
'max_epoch', 'depth', 'width', 'warmup_epochs', 'warmup_lr',
|
'max_epoch', 'warmup_epochs', 'no_aug_epochs', 'print_interval',
|
||||||
'no_aug_epochs', 'min_lr_ratio', 'weight_decay', 'momentum',
|
'eval_interval', 'multiscale_range', 'data_num_workers', 'num_classes'
|
||||||
'print_interval', 'eval_interval', 'test_conf', 'nmsthre',
|
|
||||||
'multiscale_range', 'degrees', 'translate', 'shear',
|
|
||||||
'train', 'valid', 'test'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
for field in numeric_fields:
|
for field in integer_fields:
|
||||||
if field in settings:
|
if field in settings and settings[field] not in [None, '']:
|
||||||
|
settings[field] = int(float(settings[field])) # Convert via float to handle "10.0" strings
|
||||||
|
|
||||||
|
# Type conversions - Float fields
|
||||||
|
float_fields = [
|
||||||
|
'depth', 'width', 'warmup_lr', 'min_lr_ratio', 'weight_decay',
|
||||||
|
'momentum', 'test_conf', 'nmsthre', 'degrees', 'translate',
|
||||||
|
'shear', 'mosaic_prob', 'mixup_prob', 'hsv_prob', 'flip_prob',
|
||||||
|
'basic_lr_per_img', 'train', 'valid', 'test'
|
||||||
|
]
|
||||||
|
|
||||||
|
for field in float_fields:
|
||||||
|
if field in settings and settings[field] not in [None, '']:
|
||||||
settings[field] = float(settings[field])
|
settings[field] = float(settings[field])
|
||||||
|
|
||||||
# Boolean conversions
|
# Boolean conversions
|
||||||
|
|||||||
0
backend/server.js
Normal file → Executable file
0
backend/server.js
Normal file → Executable file
0
backend/services/__init__.py
Normal file → Executable file
0
backend/services/__init__.py
Normal file → Executable file
0
backend/services/fetch_labelstudio.py
Normal file → Executable file
0
backend/services/fetch_labelstudio.py
Normal file → Executable file
85
backend/services/generate_json_yolox.py
Normal file → Executable file
85
backend/services/generate_json_yolox.py
Normal file → Executable file
@@ -7,12 +7,30 @@ from models.Images import Image
|
|||||||
from models.Annotation import Annotation
|
from models.Annotation import Annotation
|
||||||
|
|
||||||
def generate_training_json(training_id):
|
def generate_training_json(training_id):
|
||||||
"""Generate COCO JSON for training, validation, and test sets"""
|
"""Generate COCO JSON for training, validation, and test sets
|
||||||
# training_id is now project_details_id
|
|
||||||
training_project_details = TrainingProjectDetails.query.get(training_id)
|
Args:
|
||||||
|
training_id: Can be either a Training.id or TrainingProjectDetails.id
|
||||||
|
Function will automatically detect which one and find the correct details_id
|
||||||
|
"""
|
||||||
|
from models.training import Training
|
||||||
|
|
||||||
|
# First, try to get as a Training record
|
||||||
|
training_record = Training.query.get(training_id)
|
||||||
|
|
||||||
|
if training_record:
|
||||||
|
# It's a Training.id - use its project_details_id
|
||||||
|
details_id = training_record.project_details_id
|
||||||
|
print(f'[generate_training_json] Using training_id={training_id}, mapped to project_details_id={details_id}')
|
||||||
|
else:
|
||||||
|
# Try as TrainingProjectDetails.id directly
|
||||||
|
details_id = training_id
|
||||||
|
print(f'[generate_training_json] Using training_id={training_id} as project_details_id directly')
|
||||||
|
|
||||||
|
training_project_details = TrainingProjectDetails.query.get(details_id)
|
||||||
|
|
||||||
if not training_project_details:
|
if not training_project_details:
|
||||||
raise Exception(f'No TrainingProjectDetails found for project_details_id {training_id}')
|
raise Exception(f'No TrainingProjectDetails found for id {training_id} (details_id: {details_id})')
|
||||||
|
|
||||||
details_obj = training_project_details.to_dict()
|
details_obj = training_project_details.to_dict()
|
||||||
|
|
||||||
@@ -110,22 +128,35 @@ def generate_training_json(training_id):
|
|||||||
break
|
break
|
||||||
|
|
||||||
# Construct ABSOLUTE path using data_dir
|
# Construct ABSOLUTE path using data_dir
|
||||||
# Normalize data_dir - ensure it uses backslashes for Windows
|
# Detect platform for proper path handling
|
||||||
normalized_data_dir = data_dir.rstrip('/\\').replace('/', '\\')
|
import platform
|
||||||
|
is_windows = platform.system() == 'Windows'
|
||||||
|
|
||||||
|
# Normalize data_dir and file_name based on platform
|
||||||
|
if is_windows:
|
||||||
|
# Windows: use backslashes
|
||||||
|
normalized_data_dir = data_dir.rstrip('/\\').replace('/', '\\')
|
||||||
|
file_name = file_name.replace('/', '\\')
|
||||||
|
else:
|
||||||
|
# Linux/Mac: use forward slashes
|
||||||
|
normalized_data_dir = data_dir.rstrip('/\\').replace('\\', '/')
|
||||||
|
file_name = file_name.replace('\\', '/')
|
||||||
|
|
||||||
# Check if already absolute path
|
# Check if already absolute path
|
||||||
if not (file_name.startswith('\\\\') or (len(file_name) > 1 and file_name[1] == ':')):
|
is_absolute = False
|
||||||
# It's a relative path, combine with data_dir
|
if is_windows:
|
||||||
# For UNC paths, we need to manually concatenate to preserve \\
|
is_absolute = file_name.startswith('\\\\') or (len(file_name) > 1 and file_name[1] == ':')
|
||||||
if normalized_data_dir.startswith('\\\\'):
|
|
||||||
# UNC path
|
|
||||||
file_name = normalized_data_dir + '\\' + file_name.replace('/', '\\')
|
|
||||||
else:
|
|
||||||
# Regular path
|
|
||||||
file_name = os.path.join(normalized_data_dir, file_name.replace('/', '\\'))
|
|
||||||
else:
|
else:
|
||||||
# Already absolute, just normalize separators
|
is_absolute = file_name.startswith('/')
|
||||||
file_name = file_name.replace('/', '\\')
|
|
||||||
|
if not is_absolute:
|
||||||
|
# It's a relative path, combine with data_dir
|
||||||
|
if is_windows and normalized_data_dir.startswith('\\\\'):
|
||||||
|
# Windows UNC path
|
||||||
|
file_name = normalized_data_dir + '\\' + file_name
|
||||||
|
else:
|
||||||
|
# Regular path (Windows or Linux)
|
||||||
|
file_name = os.path.join(normalized_data_dir, file_name)
|
||||||
|
|
||||||
# Get annotations for this image
|
# Get annotations for this image
|
||||||
annotations = Annotation.query.filter_by(image_id=image.image_id).all()
|
annotations = Annotation.query.filter_by(image_id=image.image_id).all()
|
||||||
@@ -218,13 +249,19 @@ def generate_training_json(training_id):
|
|||||||
|
|
||||||
project_name = training_project.title.replace(' ', '_') if training_project and training_project.title else f'project_{details_obj["project_id"]}'
|
project_name = training_project.title.replace(' ', '_') if training_project and training_project.title else f'project_{details_obj["project_id"]}'
|
||||||
|
|
||||||
# Get training record to use its name for folder
|
# Get training record to use its name and ID for folder and file names
|
||||||
training_record = Training.query.filter_by(project_details_id=training_id).first()
|
# Use the same training_id that was passed in (if it was a Training.id)
|
||||||
training_folder_name = f"{training_record.exp_name or training_record.training_name or 'training'}_{training_record.id}" if training_record else str(training_id)
|
# or find the first training for this details_id
|
||||||
training_folder_name = training_folder_name.replace(' ', '_')
|
if not training_record:
|
||||||
|
training_record = Training.query.filter_by(project_details_id=details_id).first()
|
||||||
|
|
||||||
# Use training_record.id for file names to match what generate_yolox_exp expects
|
if training_record:
|
||||||
training_file_id = training_record.id if training_record else training_id
|
training_folder_name = f"{training_record.exp_name or training_record.training_name or 'training'}_{training_record.id}"
|
||||||
|
training_folder_name = training_folder_name.replace(' ', '_')
|
||||||
|
training_file_id = training_record.id
|
||||||
|
else:
|
||||||
|
training_folder_name = str(details_id)
|
||||||
|
training_file_id = details_id
|
||||||
|
|
||||||
# Save annotations to the configured output folder
|
# Save annotations to the configured output folder
|
||||||
annotations_dir = os.path.join(output_base_path, project_name, training_folder_name, 'annotations')
|
annotations_dir = os.path.join(output_base_path, project_name, training_folder_name, 'annotations')
|
||||||
@@ -242,7 +279,7 @@ def generate_training_json(training_id):
|
|||||||
with open(test_path, 'w') as f:
|
with open(test_path, 'w') as f:
|
||||||
json.dump(test_json, f, indent=2)
|
json.dump(test_json, f, indent=2)
|
||||||
|
|
||||||
print(f'COCO JSON splits written to {annotations_dir} for trainingId {training_id}')
|
print(f'COCO JSON splits written to {annotations_dir} for training_id={training_file_id} (details_id={details_id})')
|
||||||
|
|
||||||
# Also generate inference exp.py
|
# Also generate inference exp.py
|
||||||
from services.generate_yolox_exp import generate_yolox_inference_exp
|
from services.generate_yolox_exp import generate_yolox_inference_exp
|
||||||
|
|||||||
68
backend/services/generate_yolox_exp.py
Normal file → Executable file
68
backend/services/generate_yolox_exp.py
Normal file → Executable file
@@ -220,6 +220,10 @@ def generate_yolox_inference_exp(training_id, options=None, use_base_config=Fals
|
|||||||
annotations_parent_dir = os.path.join(output_base_path, project_name, training_folder_name)
|
annotations_parent_dir = os.path.join(output_base_path, project_name, training_folder_name)
|
||||||
annotations_parent_escaped = annotations_parent_dir.replace('\\', '\\\\')
|
annotations_parent_escaped = annotations_parent_dir.replace('\\', '\\\\')
|
||||||
|
|
||||||
|
# Set output directory for checkpoints - models subdirectory
|
||||||
|
models_dir = os.path.join(annotations_parent_dir, 'models')
|
||||||
|
models_dir_escaped = models_dir.replace('\\', '\\\\')
|
||||||
|
|
||||||
# Build exp content
|
# Build exp content
|
||||||
exp_content = f'''#!/usr/bin/env python3
|
exp_content = f'''#!/usr/bin/env python3
|
||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
@@ -235,6 +239,7 @@ class Exp(MyExp):
|
|||||||
super(Exp, self).__init__()
|
super(Exp, self).__init__()
|
||||||
self.data_dir = "{data_dir_escaped}" # Where images are located
|
self.data_dir = "{data_dir_escaped}" # Where images are located
|
||||||
self.annotations_dir = "{annotations_parent_escaped}" # Where annotation JSONs are located
|
self.annotations_dir = "{annotations_parent_escaped}" # Where annotation JSONs are located
|
||||||
|
self.output_dir = "{models_dir_escaped}" # Where checkpoints will be saved
|
||||||
self.train_ann = "{train_ann}"
|
self.train_ann = "{train_ann}"
|
||||||
self.val_ann = "{val_ann}"
|
self.val_ann = "{val_ann}"
|
||||||
self.test_ann = "{test_ann}"
|
self.test_ann = "{test_ann}"
|
||||||
@@ -252,21 +257,46 @@ class Exp(MyExp):
|
|||||||
if selected_model:
|
if selected_model:
|
||||||
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 and values for Python code generation
|
||||||
def format_value(val):
|
# Integer-only parameters (sizes, epochs, intervals)
|
||||||
|
integer_params = {
|
||||||
|
'input_size', 'test_size', 'random_size', 'max_epoch', 'warmup_epochs',
|
||||||
|
'no_aug_epochs', 'print_interval', 'eval_interval', 'multiscale_range',
|
||||||
|
'data_num_workers', 'num_classes'
|
||||||
|
}
|
||||||
|
|
||||||
|
def format_value(val, param_name=''):
|
||||||
if isinstance(val, (list, tuple)):
|
if isinstance(val, (list, tuple)):
|
||||||
return '(' + ', '.join(map(str, val)) + ')'
|
# Check if this parameter should have integer values
|
||||||
|
if param_name in integer_params:
|
||||||
|
# Convert all values to integers
|
||||||
|
formatted_items = [str(int(float(item))) if isinstance(item, (int, float)) else str(item) for item in val]
|
||||||
|
else:
|
||||||
|
# Keep as floats or original type
|
||||||
|
formatted_items = []
|
||||||
|
for item in val:
|
||||||
|
if isinstance(item, float):
|
||||||
|
formatted_items.append(str(item))
|
||||||
|
elif isinstance(item, int):
|
||||||
|
formatted_items.append(str(item))
|
||||||
|
else:
|
||||||
|
formatted_items.append(str(item))
|
||||||
|
return '(' + ', '.join(formatted_items) + ')'
|
||||||
elif isinstance(val, bool):
|
elif isinstance(val, bool):
|
||||||
return str(val)
|
return str(val)
|
||||||
elif isinstance(val, str):
|
elif isinstance(val, str):
|
||||||
return f'"{val}"'
|
return f'"{val}"'
|
||||||
|
elif isinstance(val, int):
|
||||||
|
return str(val)
|
||||||
|
elif isinstance(val, float):
|
||||||
|
return str(val)
|
||||||
else:
|
else:
|
||||||
return str(val)
|
return str(val)
|
||||||
|
|
||||||
# Add all config parameters to exp
|
# Add all config parameters to exp
|
||||||
for key, value in config.items():
|
for key, value in config.items():
|
||||||
if key not in ['exp_name']: # exp_name is handled separately
|
if key not in ['exp_name']: # exp_name is handled separately
|
||||||
exp_content += f" self.{key} = {format_value(value)}\n"
|
exp_content += f" self.{key} = {format_value(value, key)}\n"
|
||||||
|
|
||||||
# Add get_dataset override using name parameter for image directory
|
# Add get_dataset override using name parameter for image directory
|
||||||
exp_content += '''
|
exp_content += '''
|
||||||
@@ -289,7 +319,7 @@ class Exp(MyExp):
|
|||||||
|
|
||||||
def get_eval_dataset(self, **kwargs):
|
def get_eval_dataset(self, **kwargs):
|
||||||
"""Override eval dataset using name parameter"""
|
"""Override eval dataset using name parameter"""
|
||||||
from yolox.data import COCODataset
|
from yolox.data import COCODataset, ValTransform
|
||||||
|
|
||||||
testdev = kwargs.get("testdev", False)
|
testdev = kwargs.get("testdev", False)
|
||||||
legacy = kwargs.get("legacy", False)
|
legacy = kwargs.get("legacy", False)
|
||||||
@@ -299,8 +329,34 @@ class Exp(MyExp):
|
|||||||
json_file=self.val_ann if not testdev else self.test_ann,
|
json_file=self.val_ann if not testdev else self.test_ann,
|
||||||
name="",
|
name="",
|
||||||
img_size=self.test_size,
|
img_size=self.test_size,
|
||||||
preproc=None, # No preprocessing for evaluation
|
preproc=ValTransform(legacy=legacy), # Use proper validation transform
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_eval_loader(self, batch_size, is_distributed, **kwargs):
|
||||||
|
"""Standard YOLOX eval loader - matches official implementation"""
|
||||||
|
import torch
|
||||||
|
import torch.distributed as dist
|
||||||
|
from torch.utils.data import DataLoader
|
||||||
|
|
||||||
|
valdataset = self.get_eval_dataset(**kwargs)
|
||||||
|
|
||||||
|
if is_distributed:
|
||||||
|
batch_size = batch_size // dist.get_world_size()
|
||||||
|
sampler = torch.utils.data.distributed.DistributedSampler(
|
||||||
|
valdataset, shuffle=False
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
sampler = torch.utils.data.SequentialSampler(valdataset)
|
||||||
|
|
||||||
|
dataloader_kwargs = {
|
||||||
|
"num_workers": self.data_num_workers,
|
||||||
|
"pin_memory": True,
|
||||||
|
"sampler": sampler,
|
||||||
|
}
|
||||||
|
dataloader_kwargs["batch_size"] = batch_size
|
||||||
|
val_loader = DataLoader(valdataset, **dataloader_kwargs)
|
||||||
|
|
||||||
|
return val_loader
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# Add exp_name at the end (uses dynamic path)
|
# Add exp_name at the end (uses dynamic path)
|
||||||
|
|||||||
0
backend/services/push_yolox_exp.py
Normal file → Executable file
0
backend/services/push_yolox_exp.py
Normal file → Executable file
0
backend/services/seed_label_studio.py
Normal file → Executable file
0
backend/services/seed_label_studio.py
Normal file → Executable file
0
backend/services/settings_service.py
Normal file → Executable file
0
backend/services/settings_service.py
Normal file → Executable file
46
backend/services/training_queue.py
Normal file → Executable file
46
backend/services/training_queue.py
Normal file → Executable file
@@ -112,16 +112,35 @@ class TrainingQueueManager:
|
|||||||
if line:
|
if line:
|
||||||
print(line.strip())
|
print(line.strip())
|
||||||
|
|
||||||
# Parse iteration from YOLOX output
|
# Parse epoch and iteration from YOLOX output
|
||||||
# Example: "2025-12-02 07:30:15 | INFO | yolox.core.trainer:78 - Epoch: [5/300]"
|
# Example: "epoch: 3/300, iter: 90/101"
|
||||||
match = re.search(r'Epoch:\s*\[(\d+)/(\d+)\]', line)
|
epoch_match = re.search(r'epoch:\s*(\d+)/(\d+)', line, re.IGNORECASE)
|
||||||
if match:
|
iter_match = re.search(r'iter:\s*(\d+)/(\d+)', line, re.IGNORECASE)
|
||||||
current_epoch = int(match.group(1))
|
|
||||||
total_epochs = int(match.group(2))
|
if epoch_match:
|
||||||
|
current_epoch = int(epoch_match.group(1))
|
||||||
|
total_epochs = int(epoch_match.group(2))
|
||||||
if self.current_training:
|
if self.current_training:
|
||||||
self.current_training['iteration'] = current_epoch
|
self.current_training['current_epoch'] = current_epoch
|
||||||
self.current_training['max_epoch'] = total_epochs
|
self.current_training['max_epoch'] = total_epochs
|
||||||
print(f'Progress: {current_epoch}/{total_epochs}')
|
# Debug log
|
||||||
|
print(f'[PROGRESS] Parsed epoch: {current_epoch}/{total_epochs}')
|
||||||
|
|
||||||
|
if iter_match:
|
||||||
|
current_iter = int(iter_match.group(1))
|
||||||
|
total_iters = int(iter_match.group(2))
|
||||||
|
if self.current_training:
|
||||||
|
self.current_training['current_iter'] = current_iter
|
||||||
|
self.current_training['total_iters'] = total_iters
|
||||||
|
|
||||||
|
# Calculate overall progress percentage
|
||||||
|
if 'current_epoch' in self.current_training and 'max_epoch' in self.current_training:
|
||||||
|
epoch_progress = (self.current_training['current_epoch'] - 1) / self.current_training['max_epoch']
|
||||||
|
iter_progress = current_iter / total_iters / self.current_training['max_epoch']
|
||||||
|
total_progress = (epoch_progress + iter_progress) * 100
|
||||||
|
self.current_training['progress'] = round(total_progress, 2)
|
||||||
|
# Debug log
|
||||||
|
print(f'[PROGRESS] Epoch {self.current_training["current_epoch"]}/{self.current_training["max_epoch"]}, Iter {current_iter}/{total_iters}, Progress: {self.current_training["progress"]}%')
|
||||||
|
|
||||||
# Wait for completion
|
# Wait for completion
|
||||||
self.current_process.wait()
|
self.current_process.wait()
|
||||||
@@ -158,11 +177,18 @@ class TrainingQueueManager:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.current_training:
|
if self.current_training:
|
||||||
|
current_epoch = self.current_training.get('current_epoch', 0)
|
||||||
|
max_epoch = self.current_training.get('max_epoch', 300)
|
||||||
result['current'] = {
|
result['current'] = {
|
||||||
'training_id': self.current_training['training_id'],
|
'training_id': self.current_training['training_id'],
|
||||||
'name': self.current_training.get('name', f'Training {self.current_training["training_id"]}'),
|
'name': self.current_training.get('name', f'Training {self.current_training["training_id"]}'),
|
||||||
'iteration': self.current_training.get('iteration', 0),
|
'epoch': current_epoch, # For backward compatibility
|
||||||
'max_epoch': self.current_training.get('max_epoch', 300)
|
'current_epoch': current_epoch,
|
||||||
|
'max_epoch': max_epoch,
|
||||||
|
'current_iter': self.current_training.get('current_iter', 0),
|
||||||
|
'total_iters': self.current_training.get('total_iters', 0),
|
||||||
|
'progress': self.current_training.get('progress', 0.0),
|
||||||
|
'iteration': current_epoch # For backward compatibility
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
244
backend/services/validate_dataset.py
Executable file
244
backend/services/validate_dataset.py
Executable file
@@ -0,0 +1,244 @@
|
|||||||
|
"""
|
||||||
|
Validate dataset for training - check for problematic images and annotations
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from PIL import Image
|
||||||
|
import cv2
|
||||||
|
|
||||||
|
def validate_coco_json(json_path, data_dir):
|
||||||
|
"""
|
||||||
|
Validate a COCO JSON file and check all images
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_path: Path to COCO JSON file
|
||||||
|
data_dir: Directory where images are located
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict with validation results
|
||||||
|
"""
|
||||||
|
print(f"\n{'='*60}")
|
||||||
|
print(f"Validating: {json_path}")
|
||||||
|
print(f"{'='*60}\n")
|
||||||
|
|
||||||
|
issues = {
|
||||||
|
'missing_images': [],
|
||||||
|
'corrupted_images': [],
|
||||||
|
'zero_dimension_images': [],
|
||||||
|
'invalid_annotations': [],
|
||||||
|
'zero_area_boxes': []
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(json_path, 'r') as f:
|
||||||
|
coco_data = json.load(f)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Failed to load JSON: {e}")
|
||||||
|
return issues
|
||||||
|
|
||||||
|
images = coco_data.get('images', [])
|
||||||
|
annotations = coco_data.get('annotations', [])
|
||||||
|
|
||||||
|
print(f"📊 Dataset Stats:")
|
||||||
|
print(f" Images: {len(images)}")
|
||||||
|
print(f" Annotations: {len(annotations)}")
|
||||||
|
print(f" Categories: {len(coco_data.get('categories', []))}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Validate images
|
||||||
|
print("🔍 Validating images...")
|
||||||
|
for idx, img_info in enumerate(images):
|
||||||
|
img_id = img_info.get('id')
|
||||||
|
file_name = img_info.get('file_name', '')
|
||||||
|
width = img_info.get('width', 0)
|
||||||
|
height = img_info.get('height', 0)
|
||||||
|
|
||||||
|
# Check if image file exists
|
||||||
|
# Try to construct the full path
|
||||||
|
if os.path.isabs(file_name):
|
||||||
|
img_path = file_name
|
||||||
|
else:
|
||||||
|
img_path = os.path.join(data_dir, file_name)
|
||||||
|
|
||||||
|
if not os.path.exists(img_path):
|
||||||
|
issues['missing_images'].append({
|
||||||
|
'id': img_id,
|
||||||
|
'file_name': file_name,
|
||||||
|
'expected_path': img_path
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if image can be loaded
|
||||||
|
try:
|
||||||
|
# Try with PIL
|
||||||
|
with Image.open(img_path) as pil_img:
|
||||||
|
pil_width, pil_height = pil_img.size
|
||||||
|
|
||||||
|
# Check if dimensions match JSON
|
||||||
|
if pil_width != width or pil_height != height:
|
||||||
|
print(f"⚠️ Image {img_id}: Dimension mismatch - JSON: {width}x{height}, Actual: {pil_width}x{pil_height}")
|
||||||
|
|
||||||
|
# Check for zero dimensions
|
||||||
|
if pil_width == 0 or pil_height == 0:
|
||||||
|
issues['zero_dimension_images'].append({
|
||||||
|
'id': img_id,
|
||||||
|
'file_name': file_name,
|
||||||
|
'dimensions': f"{pil_width}x{pil_height}"
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
issues['corrupted_images'].append({
|
||||||
|
'id': img_id,
|
||||||
|
'file_name': file_name,
|
||||||
|
'error': str(e)
|
||||||
|
})
|
||||||
|
|
||||||
|
# Progress indicator
|
||||||
|
if (idx + 1) % 100 == 0:
|
||||||
|
print(f" Checked {idx + 1}/{len(images)} images...")
|
||||||
|
|
||||||
|
print(f"✅ Image validation complete\n")
|
||||||
|
|
||||||
|
# Validate annotations
|
||||||
|
print("🔍 Validating annotations...")
|
||||||
|
for idx, ann in enumerate(annotations):
|
||||||
|
ann_id = ann.get('id')
|
||||||
|
img_id = ann.get('image_id')
|
||||||
|
bbox = ann.get('bbox', [])
|
||||||
|
|
||||||
|
if len(bbox) != 4:
|
||||||
|
issues['invalid_annotations'].append({
|
||||||
|
'id': ann_id,
|
||||||
|
'image_id': img_id,
|
||||||
|
'reason': f'Invalid bbox length: {len(bbox)}'
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
x, y, w, h = bbox
|
||||||
|
|
||||||
|
# Check for zero or negative dimensions
|
||||||
|
if w <= 0 or h <= 0:
|
||||||
|
issues['zero_area_boxes'].append({
|
||||||
|
'id': ann_id,
|
||||||
|
'image_id': img_id,
|
||||||
|
'bbox': bbox,
|
||||||
|
'reason': f'Zero or negative dimensions: w={w}, h={h}'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Check for extremely small boxes (potential issue with mixup)
|
||||||
|
if w < 1 or h < 1:
|
||||||
|
issues['zero_area_boxes'].append({
|
||||||
|
'id': ann_id,
|
||||||
|
'image_id': img_id,
|
||||||
|
'bbox': bbox,
|
||||||
|
'reason': f'Extremely small box: w={w}, h={h}'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Progress indicator
|
||||||
|
if (idx + 1) % 1000 == 0:
|
||||||
|
print(f" Checked {idx + 1}/{len(annotations)} annotations...")
|
||||||
|
|
||||||
|
print(f"✅ Annotation validation complete\n")
|
||||||
|
|
||||||
|
# Print summary
|
||||||
|
print(f"\n{'='*60}")
|
||||||
|
print("VALIDATION SUMMARY")
|
||||||
|
print(f"{'='*60}\n")
|
||||||
|
|
||||||
|
total_issues = sum(len(v) for v in issues.values())
|
||||||
|
|
||||||
|
if total_issues == 0:
|
||||||
|
print("✅ No issues found! Dataset is ready for training.")
|
||||||
|
else:
|
||||||
|
print(f"⚠️ Found {total_issues} total issues:\n")
|
||||||
|
|
||||||
|
if issues['missing_images']:
|
||||||
|
print(f" ❌ Missing images: {len(issues['missing_images'])}")
|
||||||
|
for item in issues['missing_images'][:5]: # Show first 5
|
||||||
|
print(f" - {item['file_name']}")
|
||||||
|
if len(issues['missing_images']) > 5:
|
||||||
|
print(f" ... and {len(issues['missing_images']) - 5} more")
|
||||||
|
|
||||||
|
if issues['corrupted_images']:
|
||||||
|
print(f" ❌ Corrupted images: {len(issues['corrupted_images'])}")
|
||||||
|
for item in issues['corrupted_images'][:5]:
|
||||||
|
print(f" - {item['file_name']}: {item['error']}")
|
||||||
|
if len(issues['corrupted_images']) > 5:
|
||||||
|
print(f" ... and {len(issues['corrupted_images']) - 5} more")
|
||||||
|
|
||||||
|
if issues['zero_dimension_images']:
|
||||||
|
print(f" ❌ Zero dimension images: {len(issues['zero_dimension_images'])}")
|
||||||
|
for item in issues['zero_dimension_images'][:5]:
|
||||||
|
print(f" - {item['file_name']}: {item['dimensions']}")
|
||||||
|
if len(issues['zero_dimension_images']) > 5:
|
||||||
|
print(f" ... and {len(issues['zero_dimension_images']) - 5} more")
|
||||||
|
|
||||||
|
if issues['invalid_annotations']:
|
||||||
|
print(f" ❌ Invalid annotations: {len(issues['invalid_annotations'])}")
|
||||||
|
for item in issues['invalid_annotations'][:5]:
|
||||||
|
print(f" - Ann ID {item['id']}: {item['reason']}")
|
||||||
|
if len(issues['invalid_annotations']) > 5:
|
||||||
|
print(f" ... and {len(issues['invalid_annotations']) - 5} more")
|
||||||
|
|
||||||
|
if issues['zero_area_boxes']:
|
||||||
|
print(f" ⚠️ Zero/tiny area boxes: {len(issues['zero_area_boxes'])}")
|
||||||
|
print(f" These may cause issues with mixup augmentation!")
|
||||||
|
for item in issues['zero_area_boxes'][:5]:
|
||||||
|
print(f" - Ann ID {item['id']}, bbox: {item['bbox']}")
|
||||||
|
if len(issues['zero_area_boxes']) > 5:
|
||||||
|
print(f" ... and {len(issues['zero_area_boxes']) - 5} more")
|
||||||
|
|
||||||
|
print()
|
||||||
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
def validate_training_dataset(training_id):
|
||||||
|
"""
|
||||||
|
Validate all COCO JSON files for a training
|
||||||
|
|
||||||
|
Args:
|
||||||
|
training_id: The training ID to validate
|
||||||
|
"""
|
||||||
|
from models.training import Training
|
||||||
|
from models.TrainingProject import TrainingProject
|
||||||
|
from services.settings_service import get_setting
|
||||||
|
|
||||||
|
training = Training.query.get(training_id)
|
||||||
|
if not training:
|
||||||
|
print(f"❌ Training {training_id} not found")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get paths
|
||||||
|
from models.TrainingProjectDetails import TrainingProjectDetails
|
||||||
|
details = TrainingProjectDetails.query.get(training.project_details_id)
|
||||||
|
training_project = TrainingProject.query.get(details.project_id)
|
||||||
|
project_name = training_project.title.replace(' ', '_') if training_project else f'project_{details.project_id}'
|
||||||
|
|
||||||
|
training_folder_name = f"{training.exp_name or training.training_name or 'training'}_{training_id}"
|
||||||
|
training_folder_name = training_folder_name.replace(' ', '_')
|
||||||
|
|
||||||
|
output_base_path = get_setting('yolox_output_path', './backend')
|
||||||
|
data_dir = get_setting('yolox_data_dir', '/home/kitraining/To_Annotate/')
|
||||||
|
|
||||||
|
annotations_dir = os.path.join(output_base_path, project_name, training_folder_name, 'annotations')
|
||||||
|
|
||||||
|
# Validate each split
|
||||||
|
splits = ['train', 'valid', 'test']
|
||||||
|
all_issues = {}
|
||||||
|
|
||||||
|
for split in splits:
|
||||||
|
json_file = os.path.join(annotations_dir, f'coco_project_{training_id}_{split}.json')
|
||||||
|
if os.path.exists(json_file):
|
||||||
|
all_issues[split] = validate_coco_json(json_file, data_dir)
|
||||||
|
else:
|
||||||
|
print(f"⚠️ JSON file not found: {json_file}")
|
||||||
|
|
||||||
|
return all_issues
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
training_id = int(sys.argv[1])
|
||||||
|
validate_training_dataset(training_id)
|
||||||
|
else:
|
||||||
|
print("Usage: python validate_dataset.py <training_id>")
|
||||||
0
backend/start.py
Normal file → Executable file
0
backend/start.py
Normal file → Executable file
0
backend/test/7/exp.py
Normal file → Executable file
0
backend/test/7/exp.py
Normal file → Executable file
0
documentation/Projektdoku.pdf
Normal file → Executable file
0
documentation/Projektdoku.pdf
Normal file → Executable file
0
documentation/Projektdokumentation.md
Normal file → Executable file
0
documentation/Projektdokumentation.md
Normal file → Executable file
125
edit-training.html
Normal file → Executable file
125
edit-training.html
Normal file → Executable file
@@ -202,6 +202,14 @@
|
|||||||
<label id="project-title-label"
|
<label id="project-title-label"
|
||||||
style="display: block; text-align: left; font-weight: bold; font-size: x-large;">title</label>
|
style="display: block; text-align: left; font-weight: bold; font-size: x-large;">title</label>
|
||||||
<div class="button-row">
|
<div class="button-row">
|
||||||
|
<!-- Training Notification Bell -->
|
||||||
|
<button id="training-bell" onclick="toggleTrainingModal()" class="button" title="Training Status"
|
||||||
|
style="padding: 8px 16px; margin-right: 10px; position: relative; background: #999;">
|
||||||
|
🔔
|
||||||
|
<span id="bell-badge" style="display: none; position: absolute; top: -5px; right: -5px; background: #ff4d4f;
|
||||||
|
color: white; border-radius: 50%; width: 20px; height: 20px; font-size: 12px; line-height: 20px;
|
||||||
|
text-align: center; font-weight: bold;">0</span>
|
||||||
|
</button>
|
||||||
<button id="Add Training Project" onclick="window.location.href='/add-project.html'"
|
<button id="Add Training Project" onclick="window.location.href='/add-project.html'"
|
||||||
class="button-red">Add Training Project</button>
|
class="button-red">Add Training Project</button>
|
||||||
<button id="seed-db-btn" class="button">
|
<button id="seed-db-btn" class="button">
|
||||||
@@ -705,4 +713,121 @@ window.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Training Status Modal -->
|
||||||
|
<div id="training-status-modal" class="modal" style="display: none;">
|
||||||
|
<div class="modal-content" style="max-width: 700px; max-height: 90vh; overflow-y: auto;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Training Status</h2>
|
||||||
|
<button class="close-btn" onclick="toggleTrainingModal()">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="settings-section" id="current-training-section" style="display: none;">
|
||||||
|
<h3 style="color: #009eac;">Current Training</h3>
|
||||||
|
<div id="current-training-info" style="background: #eaf7fa; padding: 16px; border-radius: 8px; margin-bottom: 16px;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-section" id="queue-section" style="display: none;">
|
||||||
|
<h3 style="color: #666;">Queue (<span id="queue-count">0</span>)</h3>
|
||||||
|
<div id="queue-list" style="display: flex; flex-direction: column; gap: 12px;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="no-trainings-msg" style="text-align: center; padding: 32px; color: #666;">
|
||||||
|
No trainings running or queued.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let trainingStatusPoller = null;
|
||||||
|
|
||||||
|
function toggleTrainingModal() {
|
||||||
|
const modal = document.getElementById('training-status-modal');
|
||||||
|
if (modal.style.display === 'none') {
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
updateTrainingStatus();
|
||||||
|
} else {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTrainingStatus() {
|
||||||
|
fetch('/api/training-status')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
const bell = document.getElementById('training-bell');
|
||||||
|
const badge = document.getElementById('bell-badge');
|
||||||
|
const currentSection = document.getElementById('current-training-section');
|
||||||
|
const queueSection = document.getElementById('queue-section');
|
||||||
|
const noTrainingsMsg = document.getElementById('no-trainings-msg');
|
||||||
|
|
||||||
|
const totalCount = (data.current ? 1 : 0) + data.queue.length;
|
||||||
|
|
||||||
|
if (totalCount > 0) {
|
||||||
|
bell.style.background = '#009eac';
|
||||||
|
badge.style.display = 'block';
|
||||||
|
badge.textContent = totalCount;
|
||||||
|
} else {
|
||||||
|
bell.style.background = '#999';
|
||||||
|
badge.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.current) {
|
||||||
|
currentSection.style.display = 'block';
|
||||||
|
noTrainingsMsg.style.display = 'none';
|
||||||
|
|
||||||
|
const percentage = Math.round((data.current.iteration / data.current.max_epoch) * 100);
|
||||||
|
document.getElementById('current-training-info').innerHTML = `
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||||
|
<strong>${data.current.name || 'Training'}</strong>
|
||||||
|
<span style="font-weight: bold; color: #009eac;">${percentage}%</span>
|
||||||
|
</div>
|
||||||
|
<div style="background: #ddd; border-radius: 4px; height: 24px; overflow: hidden; margin-bottom: 8px;">
|
||||||
|
<div style="background: #009eac; height: 100%; width: ${percentage}%; transition: width 0.3s;"></div>
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 14px; color: #666;">
|
||||||
|
Epoch ${data.current.iteration} / ${data.current.max_epoch}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
currentSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.queue.length > 0) {
|
||||||
|
queueSection.style.display = 'block';
|
||||||
|
noTrainingsMsg.style.display = 'none';
|
||||||
|
document.getElementById('queue-count').textContent = data.queue.length;
|
||||||
|
|
||||||
|
document.getElementById('queue-list').innerHTML = data.queue.map((t, idx) => `
|
||||||
|
<div style="background: #f5f5f5; padding: 12px; border-radius: 8px; border-left: 4px solid #009eac;">
|
||||||
|
<strong>#${idx + 1}: ${t.name || 'Training'}</strong>
|
||||||
|
<div style="font-size: 13px; color: #666; margin-top: 4px;">
|
||||||
|
${t.max_epoch} epochs • Waiting...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
} else {
|
||||||
|
queueSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalCount === 0) {
|
||||||
|
noTrainingsMsg.style.display = 'block';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => console.error('Failed to fetch training status:', err));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', function() {
|
||||||
|
updateTrainingStatus();
|
||||||
|
trainingStatusPoller = setInterval(updateTrainingStatus, 5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', function() {
|
||||||
|
if (trainingStatusPoller) clearInterval(trainingStatusPoller);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body></html>
|
</body></html>
|
||||||
0
globals.css
Normal file → Executable file
0
globals.css
Normal file → Executable file
134
index.html
Normal file → Executable file
134
index.html
Normal file → Executable file
@@ -28,6 +28,14 @@
|
|||||||
<icon class="header-icon" onclick="window.location.href='/index.html'" , onmouseover="" style="cursor: pointer;"
|
<icon class="header-icon" onclick="window.location.href='/index.html'" , onmouseover="" style="cursor: pointer;"
|
||||||
src="./media/logo.png" alt="Logo"></icon>
|
src="./media/logo.png" alt="Logo"></icon>
|
||||||
<div class="button-row">
|
<div class="button-row">
|
||||||
|
<!-- Training Notification Bell -->
|
||||||
|
<button id="training-bell" onclick="toggleTrainingModal()" class="button" title="Training Status"
|
||||||
|
style="padding: 8px 16px; margin-right: 10px; position: relative; background: #999;">
|
||||||
|
🔔
|
||||||
|
<span id="bell-badge" style="display: none; position: absolute; top: -5px; right: -5px; background: #ff4d4f;
|
||||||
|
color: white; border-radius: 50%; width: 20px; height: 20px; font-size: 12px; line-height: 20px;
|
||||||
|
text-align: center; font-weight: bold;">0</span>
|
||||||
|
</button>
|
||||||
<button id="Add Training Project" onclick="window.location.href='/add-project.html'" class="button-red">Add
|
<button id="Add Training Project" onclick="window.location.href='/add-project.html'" class="button-red">Add
|
||||||
Training Project</button>
|
Training Project</button>
|
||||||
<button id="seed-db-btn" class="button">
|
<button id="seed-db-btn" class="button">
|
||||||
@@ -159,7 +167,133 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Training Status Modal -->
|
||||||
|
<div id="training-status-modal" class="modal" style="display: none;">
|
||||||
|
<div class="modal-content" style="max-width: 700px; max-height: 90vh; overflow-y: auto;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Training Status</h2>
|
||||||
|
<button class="close-btn" onclick="toggleTrainingModal()">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- Current Training -->
|
||||||
|
<div class="settings-section" id="current-training-section" style="display: none;">
|
||||||
|
<h3 style="color: #009eac;">Current Training</h3>
|
||||||
|
<div id="current-training-info" style="background: #eaf7fa; padding: 16px; border-radius: 8px; margin-bottom: 16px;">
|
||||||
|
<!-- Populated by JS -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Queued Trainings -->
|
||||||
|
<div class="settings-section" id="queue-section" style="display: none;">
|
||||||
|
<h3 style="color: #666;">Queue (<span id="queue-count">0</span>)</h3>
|
||||||
|
<div id="queue-list" style="display: flex; flex-direction: column; gap: 12px;">
|
||||||
|
<!-- Populated by JS -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- No trainings message -->
|
||||||
|
<div id="no-trainings-msg" style="text-align: center; padding: 32px; color: #666;">
|
||||||
|
No trainings running or queued.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="js/settings.js"></script>
|
<script src="js/settings.js"></script>
|
||||||
|
<script>
|
||||||
|
// Training status polling
|
||||||
|
let trainingStatusPoller = null;
|
||||||
|
|
||||||
|
function toggleTrainingModal() {
|
||||||
|
const modal = document.getElementById('training-status-modal');
|
||||||
|
if (modal.style.display === 'none') {
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
updateTrainingStatus(); // Immediate update
|
||||||
|
} else {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTrainingStatus() {
|
||||||
|
fetch('/api/training-status')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
const bell = document.getElementById('training-bell');
|
||||||
|
const badge = document.getElementById('bell-badge');
|
||||||
|
const currentSection = document.getElementById('current-training-section');
|
||||||
|
const queueSection = document.getElementById('queue-section');
|
||||||
|
const noTrainingsMsg = document.getElementById('no-trainings-msg');
|
||||||
|
|
||||||
|
const totalCount = (data.current ? 1 : 0) + data.queue.length;
|
||||||
|
|
||||||
|
// Update bell appearance
|
||||||
|
if (totalCount > 0) {
|
||||||
|
bell.style.background = '#009eac';
|
||||||
|
badge.style.display = 'block';
|
||||||
|
badge.textContent = totalCount;
|
||||||
|
} else {
|
||||||
|
bell.style.background = '#999';
|
||||||
|
badge.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update modal content
|
||||||
|
if (data.current) {
|
||||||
|
currentSection.style.display = 'block';
|
||||||
|
noTrainingsMsg.style.display = 'none';
|
||||||
|
|
||||||
|
const percentage = Math.round((data.current.iteration / data.current.max_epoch) * 100);
|
||||||
|
document.getElementById('current-training-info').innerHTML = `
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||||
|
<strong>${data.current.name || 'Training'}</strong>
|
||||||
|
<span style="font-weight: bold; color: #009eac;">${percentage}%</span>
|
||||||
|
</div>
|
||||||
|
<div style="background: #ddd; border-radius: 4px; height: 24px; overflow: hidden; margin-bottom: 8px;">
|
||||||
|
<div style="background: #009eac; height: 100%; width: ${percentage}%; transition: width 0.3s;"></div>
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 14px; color: #666;">
|
||||||
|
Epoch ${data.current.iteration} / ${data.current.max_epoch}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
currentSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.queue.length > 0) {
|
||||||
|
queueSection.style.display = 'block';
|
||||||
|
noTrainingsMsg.style.display = 'none';
|
||||||
|
document.getElementById('queue-count').textContent = data.queue.length;
|
||||||
|
|
||||||
|
document.getElementById('queue-list').innerHTML = data.queue.map((t, idx) => `
|
||||||
|
<div style="background: #f5f5f5; padding: 12px; border-radius: 8px; border-left: 4px solid #009eac;">
|
||||||
|
<strong>#${idx + 1}: ${t.name || 'Training'}</strong>
|
||||||
|
<div style="font-size: 13px; color: #666; margin-top: 4px;">
|
||||||
|
${t.max_epoch} epochs • Waiting...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
} else {
|
||||||
|
queueSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalCount === 0) {
|
||||||
|
noTrainingsMsg.style.display = 'block';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => console.error('Failed to fetch training status:', err));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll every 5 seconds
|
||||||
|
window.addEventListener('DOMContentLoaded', function() {
|
||||||
|
updateTrainingStatus();
|
||||||
|
trainingStatusPoller = setInterval(updateTrainingStatus, 5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stop polling when page unloads
|
||||||
|
window.addEventListener('beforeunload', function() {
|
||||||
|
if (trainingStatusPoller) clearInterval(trainingStatusPoller);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
0
js/add-class.js
Normal file → Executable file
0
js/add-class.js
Normal file → Executable file
0
js/add-image.js
Normal file → Executable file
0
js/add-image.js
Normal file → Executable file
0
js/dashboard-label-studio.js
Normal file → Executable file
0
js/dashboard-label-studio.js
Normal file → Executable file
0
js/dashboard.js
Normal file → Executable file
0
js/dashboard.js
Normal file → Executable file
0
js/overview-training.js
Normal file → Executable file
0
js/overview-training.js
Normal file → Executable file
0
js/settings.js
Normal file → Executable file
0
js/settings.js
Normal file → Executable file
0
js/setup-training-project.js
Normal file → Executable file
0
js/setup-training-project.js
Normal file → Executable file
0
js/start-training.js
Normal file → Executable file
0
js/start-training.js
Normal file → Executable file
0
js/storage.js
Normal file → Executable file
0
js/storage.js
Normal file → Executable file
0
media/logo.png
Normal file → Executable file
0
media/logo.png
Normal file → Executable file
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
0
overview-training.html
Normal file → Executable file
0
overview-training.html
Normal file → Executable file
124
project-details.html
Normal file → Executable file
124
project-details.html
Normal file → Executable file
@@ -32,6 +32,14 @@
|
|||||||
style="cursor: pointer;" src="./media/logo.png" alt="Logo"></icon>
|
style="cursor: pointer;" src="./media/logo.png" alt="Logo"></icon>
|
||||||
<label id="project-title-label" style="display: block; text-align: left; font-weight: bold; font-size: x-large;">title</label>
|
<label id="project-title-label" style="display: block; text-align: left; font-weight: bold; font-size: x-large;">title</label>
|
||||||
<div class="button-row">
|
<div class="button-row">
|
||||||
|
<!-- Training Notification Bell -->
|
||||||
|
<button id="training-bell" onclick="toggleTrainingModal()" class="button" title="Training Status"
|
||||||
|
style="padding: 8px 16px; margin-right: 10px; position: relative; background: #999;">
|
||||||
|
🔔
|
||||||
|
<span id="bell-badge" style="display: none; position: absolute; top: -5px; right: -5px; background: #ff4d4f;
|
||||||
|
color: white; border-radius: 50%; width: 20px; height: 20px; font-size: 12px; line-height: 20px;
|
||||||
|
text-align: center; font-weight: bold;">0</span>
|
||||||
|
</button>
|
||||||
<button id="Add Training Project" onclick="window.location.href='/add-project.html'"
|
<button id="Add Training Project" onclick="window.location.href='/add-project.html'"
|
||||||
class="button-red">Add Training Project</button>
|
class="button-red">Add Training Project</button>
|
||||||
<button id="seed-db-btn" class="button">
|
<button id="seed-db-btn" class="button">
|
||||||
@@ -177,7 +185,123 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Training Status Modal -->
|
||||||
|
<div id="training-status-modal" class="modal" style="display: none;">
|
||||||
|
<div class="modal-content" style="max-width: 700px; max-height: 90vh; overflow-y: auto;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Training Status</h2>
|
||||||
|
<button class="close-btn" onclick="toggleTrainingModal()">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="settings-section" id="current-training-section" style="display: none;">
|
||||||
|
<h3 style="color: #009eac;">Current Training</h3>
|
||||||
|
<div id="current-training-info" style="background: #eaf7fa; padding: 16px; border-radius: 8px; margin-bottom: 16px;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-section" id="queue-section" style="display: none;">
|
||||||
|
<h3 style="color: #666;">Queue (<span id="queue-count">0</span>)</h3>
|
||||||
|
<div id="queue-list" style="display: flex; flex-direction: column; gap: 12px;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="no-trainings-msg" style="text-align: center; padding: 32px; color: #666;">
|
||||||
|
No trainings running or queued.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="js/settings.js"></script>
|
<script src="js/settings.js"></script>
|
||||||
|
<script>
|
||||||
|
let trainingStatusPoller = null;
|
||||||
|
|
||||||
|
function toggleTrainingModal() {
|
||||||
|
const modal = document.getElementById('training-status-modal');
|
||||||
|
if (modal.style.display === 'none') {
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
updateTrainingStatus();
|
||||||
|
} else {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTrainingStatus() {
|
||||||
|
fetch('/api/training-status')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
const bell = document.getElementById('training-bell');
|
||||||
|
const badge = document.getElementById('bell-badge');
|
||||||
|
const currentSection = document.getElementById('current-training-section');
|
||||||
|
const queueSection = document.getElementById('queue-section');
|
||||||
|
const noTrainingsMsg = document.getElementById('no-trainings-msg');
|
||||||
|
|
||||||
|
const totalCount = (data.current ? 1 : 0) + data.queue.length;
|
||||||
|
|
||||||
|
if (totalCount > 0) {
|
||||||
|
bell.style.background = '#009eac';
|
||||||
|
badge.style.display = 'block';
|
||||||
|
badge.textContent = totalCount;
|
||||||
|
} else {
|
||||||
|
bell.style.background = '#999';
|
||||||
|
badge.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.current) {
|
||||||
|
currentSection.style.display = 'block';
|
||||||
|
noTrainingsMsg.style.display = 'none';
|
||||||
|
|
||||||
|
const percentage = Math.round((data.current.iteration / data.current.max_epoch) * 100);
|
||||||
|
document.getElementById('current-training-info').innerHTML = `
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||||
|
<strong>${data.current.name || 'Training'}</strong>
|
||||||
|
<span style="font-weight: bold; color: #009eac;">${percentage}%</span>
|
||||||
|
</div>
|
||||||
|
<div style="background: #ddd; border-radius: 4px; height: 24px; overflow: hidden; margin-bottom: 8px;">
|
||||||
|
<div style="background: #009eac; height: 100%; width: ${percentage}%; transition: width 0.3s;"></div>
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 14px; color: #666;">
|
||||||
|
Epoch ${data.current.iteration} / ${data.current.max_epoch}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
currentSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.queue.length > 0) {
|
||||||
|
queueSection.style.display = 'block';
|
||||||
|
noTrainingsMsg.style.display = 'none';
|
||||||
|
document.getElementById('queue-count').textContent = data.queue.length;
|
||||||
|
|
||||||
|
document.getElementById('queue-list').innerHTML = data.queue.map((t, idx) => `
|
||||||
|
<div style="background: #f5f5f5; padding: 12px; border-radius: 8px; border-left: 4px solid #009eac;">
|
||||||
|
<strong>#${idx + 1}: ${t.name || 'Training'}</strong>
|
||||||
|
<div style="font-size: 13px; color: #666; margin-top: 4px;">
|
||||||
|
${t.max_epoch} epochs • Waiting...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
} else {
|
||||||
|
queueSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalCount === 0) {
|
||||||
|
noTrainingsMsg.style.display = 'block';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => console.error('Failed to fetch training status:', err));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', function() {
|
||||||
|
updateTrainingStatus();
|
||||||
|
trainingStatusPoller = setInterval(updateTrainingStatus, 5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', function() {
|
||||||
|
if (trainingStatusPoller) clearInterval(trainingStatusPoller);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
0
settings.html
Normal file → Executable file
0
settings.html
Normal file → Executable file
130
setup-training-project.html
Normal file → Executable file
130
setup-training-project.html
Normal file → Executable file
@@ -29,6 +29,14 @@
|
|||||||
src="./media/logo.png" alt="Logo"></icon>
|
src="./media/logo.png" alt="Logo"></icon>
|
||||||
<label id="project-title-label" style="display: block; text-align: left; font-weight: bold; font-size: x-large;">title</label>
|
<label id="project-title-label" style="display: block; text-align: left; font-weight: bold; font-size: x-large;">title</label>
|
||||||
<div class="button-row">
|
<div class="button-row">
|
||||||
|
<!-- Training Notification Bell -->
|
||||||
|
<button id="training-bell" onclick="toggleTrainingModal()" class="button" title="Training Status"
|
||||||
|
style="padding: 8px 16px; margin-right: 10px; position: relative; background: #999;">
|
||||||
|
🔔
|
||||||
|
<span id="bell-badge" style="display: none; position: absolute; top: -5px; right: -5px; background: #ff4d4f;
|
||||||
|
color: white; border-radius: 50%; width: 20px; height: 20px; font-size: 12px; line-height: 20px;
|
||||||
|
text-align: center; font-weight: bold;">0</span>
|
||||||
|
</button>
|
||||||
<button id="Add Training Project" onclick="window.location.href='/add-project.html'" class="button-red">Add
|
<button id="Add Training Project" onclick="window.location.href='/add-project.html'" class="button-red">Add
|
||||||
Training Project</button>
|
Training Project</button>
|
||||||
<button id="seed-db-btn" class="button">
|
<button id="seed-db-btn" class="button">
|
||||||
@@ -170,7 +178,129 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Training Status Modal -->
|
||||||
|
<div id="training-status-modal" class="modal" style="display: none;">
|
||||||
|
<div class="modal-content" style="max-width: 700px; max-height: 90vh; overflow-y: auto;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Training Status</h2>
|
||||||
|
<button class="close-btn" onclick="toggleTrainingModal()">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- Current Training -->
|
||||||
|
<div class="settings-section" id="current-training-section" style="display: none;">
|
||||||
|
<h3 style="color: #009eac;">Current Training</h3>
|
||||||
|
<div id="current-training-info" style="background: #eaf7fa; padding: 16px; border-radius: 8px; margin-bottom: 16px;">
|
||||||
|
<!-- Populated by JS -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Queued Trainings -->
|
||||||
|
<div class="settings-section" id="queue-section" style="display: none;">
|
||||||
|
<h3 style="color: #666;">Queue (<span id="queue-count">0</span>)</h3>
|
||||||
|
<div id="queue-list" style="display: flex; flex-direction: column; gap: 12px;">
|
||||||
|
<!-- Populated by JS -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- No trainings message -->
|
||||||
|
<div id="no-trainings-msg" style="text-align: center; padding: 32px; color: #666;">
|
||||||
|
No trainings running or queued.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="js/settings.js"></script>
|
<script src="js/settings.js"></script>
|
||||||
|
<script>
|
||||||
|
// Training status polling
|
||||||
|
let trainingStatusPoller = null;
|
||||||
|
|
||||||
|
function toggleTrainingModal() {
|
||||||
|
const modal = document.getElementById('training-status-modal');
|
||||||
|
if (modal.style.display === 'none') {
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
updateTrainingStatus();
|
||||||
|
} else {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTrainingStatus() {
|
||||||
|
fetch('/api/training-status')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
const bell = document.getElementById('training-bell');
|
||||||
|
const badge = document.getElementById('bell-badge');
|
||||||
|
const currentSection = document.getElementById('current-training-section');
|
||||||
|
const queueSection = document.getElementById('queue-section');
|
||||||
|
const noTrainingsMsg = document.getElementById('no-trainings-msg');
|
||||||
|
|
||||||
|
const totalCount = (data.current ? 1 : 0) + data.queue.length;
|
||||||
|
|
||||||
|
if (totalCount > 0) {
|
||||||
|
bell.style.background = '#009eac';
|
||||||
|
badge.style.display = 'block';
|
||||||
|
badge.textContent = totalCount;
|
||||||
|
} else {
|
||||||
|
bell.style.background = '#999';
|
||||||
|
badge.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.current) {
|
||||||
|
currentSection.style.display = 'block';
|
||||||
|
noTrainingsMsg.style.display = 'none';
|
||||||
|
|
||||||
|
const percentage = Math.round((data.current.iteration / data.current.max_epoch) * 100);
|
||||||
|
document.getElementById('current-training-info').innerHTML = `
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||||
|
<strong>${data.current.name || 'Training'}</strong>
|
||||||
|
<span style="font-weight: bold; color: #009eac;">${percentage}%</span>
|
||||||
|
</div>
|
||||||
|
<div style="background: #ddd; border-radius: 4px; height: 24px; overflow: hidden; margin-bottom: 8px;">
|
||||||
|
<div style="background: #009eac; height: 100%; width: ${percentage}%; transition: width 0.3s;"></div>
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 14px; color: #666;">
|
||||||
|
Epoch ${data.current.iteration} / ${data.current.max_epoch}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
currentSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.queue.length > 0) {
|
||||||
|
queueSection.style.display = 'block';
|
||||||
|
noTrainingsMsg.style.display = 'none';
|
||||||
|
document.getElementById('queue-count').textContent = data.queue.length;
|
||||||
|
|
||||||
|
document.getElementById('queue-list').innerHTML = data.queue.map((t, idx) => `
|
||||||
|
<div style="background: #f5f5f5; padding: 12px; border-radius: 8px; border-left: 4px solid #009eac;">
|
||||||
|
<strong>#${idx + 1}: ${t.name || 'Training'}</strong>
|
||||||
|
<div style="font-size: 13px; color: #666; margin-top: 4px;">
|
||||||
|
${t.max_epoch} epochs • Waiting...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
} else {
|
||||||
|
queueSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalCount === 0) {
|
||||||
|
noTrainingsMsg.style.display = 'block';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => console.error('Failed to fetch training status:', err));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', function() {
|
||||||
|
updateTrainingStatus();
|
||||||
|
trainingStatusPoller = setInterval(updateTrainingStatus, 5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', function() {
|
||||||
|
if (trainingStatusPoller) clearInterval(trainingStatusPoller);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
0
start-training.html
Normal file → Executable file
0
start-training.html
Normal file → Executable file
0
styleguide.css
Normal file → Executable file
0
styleguide.css
Normal file → Executable file
247
venv/bin/Activate.ps1
Executable file
247
venv/bin/Activate.ps1
Executable file
@@ -0,0 +1,247 @@
|
|||||||
|
<#
|
||||||
|
.Synopsis
|
||||||
|
Activate a Python virtual environment for the current PowerShell session.
|
||||||
|
|
||||||
|
.Description
|
||||||
|
Pushes the python executable for a virtual environment to the front of the
|
||||||
|
$Env:PATH environment variable and sets the prompt to signify that you are
|
||||||
|
in a Python virtual environment. Makes use of the command line switches as
|
||||||
|
well as the `pyvenv.cfg` file values present in the virtual environment.
|
||||||
|
|
||||||
|
.Parameter VenvDir
|
||||||
|
Path to the directory that contains the virtual environment to activate. The
|
||||||
|
default value for this is the parent of the directory that the Activate.ps1
|
||||||
|
script is located within.
|
||||||
|
|
||||||
|
.Parameter Prompt
|
||||||
|
The prompt prefix to display when this virtual environment is activated. By
|
||||||
|
default, this prompt is the name of the virtual environment folder (VenvDir)
|
||||||
|
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
||||||
|
|
||||||
|
.Example
|
||||||
|
Activate.ps1
|
||||||
|
Activates the Python virtual environment that contains the Activate.ps1 script.
|
||||||
|
|
||||||
|
.Example
|
||||||
|
Activate.ps1 -Verbose
|
||||||
|
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||||
|
and shows extra information about the activation as it executes.
|
||||||
|
|
||||||
|
.Example
|
||||||
|
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
||||||
|
Activates the Python virtual environment located in the specified location.
|
||||||
|
|
||||||
|
.Example
|
||||||
|
Activate.ps1 -Prompt "MyPython"
|
||||||
|
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||||
|
and prefixes the current prompt with the specified string (surrounded in
|
||||||
|
parentheses) while the virtual environment is active.
|
||||||
|
|
||||||
|
.Notes
|
||||||
|
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
||||||
|
execution policy for the user. You can do this by issuing the following PowerShell
|
||||||
|
command:
|
||||||
|
|
||||||
|
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||||
|
|
||||||
|
For more information on Execution Policies:
|
||||||
|
https://go.microsoft.com/fwlink/?LinkID=135170
|
||||||
|
|
||||||
|
#>
|
||||||
|
Param(
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[String]
|
||||||
|
$VenvDir,
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[String]
|
||||||
|
$Prompt
|
||||||
|
)
|
||||||
|
|
||||||
|
<# Function declarations --------------------------------------------------- #>
|
||||||
|
|
||||||
|
<#
|
||||||
|
.Synopsis
|
||||||
|
Remove all shell session elements added by the Activate script, including the
|
||||||
|
addition of the virtual environment's Python executable from the beginning of
|
||||||
|
the PATH variable.
|
||||||
|
|
||||||
|
.Parameter NonDestructive
|
||||||
|
If present, do not remove this function from the global namespace for the
|
||||||
|
session.
|
||||||
|
|
||||||
|
#>
|
||||||
|
function global:deactivate ([switch]$NonDestructive) {
|
||||||
|
# Revert to original values
|
||||||
|
|
||||||
|
# The prior prompt:
|
||||||
|
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
|
||||||
|
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
|
||||||
|
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
|
||||||
|
}
|
||||||
|
|
||||||
|
# The prior PYTHONHOME:
|
||||||
|
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
|
||||||
|
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
|
||||||
|
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
|
||||||
|
}
|
||||||
|
|
||||||
|
# The prior PATH:
|
||||||
|
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
|
||||||
|
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
|
||||||
|
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
# Just remove the VIRTUAL_ENV altogether:
|
||||||
|
if (Test-Path -Path Env:VIRTUAL_ENV) {
|
||||||
|
Remove-Item -Path env:VIRTUAL_ENV
|
||||||
|
}
|
||||||
|
|
||||||
|
# Just remove VIRTUAL_ENV_PROMPT altogether.
|
||||||
|
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
|
||||||
|
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
|
||||||
|
}
|
||||||
|
|
||||||
|
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
|
||||||
|
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
||||||
|
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
# Leave deactivate function in the global namespace if requested:
|
||||||
|
if (-not $NonDestructive) {
|
||||||
|
Remove-Item -Path function:deactivate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.Description
|
||||||
|
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
|
||||||
|
given folder, and returns them in a map.
|
||||||
|
|
||||||
|
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
|
||||||
|
two strings separated by `=` (with any amount of whitespace surrounding the =)
|
||||||
|
then it is considered a `key = value` line. The left hand string is the key,
|
||||||
|
the right hand is the value.
|
||||||
|
|
||||||
|
If the value starts with a `'` or a `"` then the first and last character is
|
||||||
|
stripped from the value before being captured.
|
||||||
|
|
||||||
|
.Parameter ConfigDir
|
||||||
|
Path to the directory that contains the `pyvenv.cfg` file.
|
||||||
|
#>
|
||||||
|
function Get-PyVenvConfig(
|
||||||
|
[String]
|
||||||
|
$ConfigDir
|
||||||
|
) {
|
||||||
|
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
||||||
|
|
||||||
|
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
|
||||||
|
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
||||||
|
|
||||||
|
# An empty map will be returned if no config file is found.
|
||||||
|
$pyvenvConfig = @{ }
|
||||||
|
|
||||||
|
if ($pyvenvConfigPath) {
|
||||||
|
|
||||||
|
Write-Verbose "File exists, parse `key = value` lines"
|
||||||
|
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
||||||
|
|
||||||
|
$pyvenvConfigContent | ForEach-Object {
|
||||||
|
$keyval = $PSItem -split "\s*=\s*", 2
|
||||||
|
if ($keyval[0] -and $keyval[1]) {
|
||||||
|
$val = $keyval[1]
|
||||||
|
|
||||||
|
# Remove extraneous quotations around a string value.
|
||||||
|
if ("'""".Contains($val.Substring(0, 1))) {
|
||||||
|
$val = $val.Substring(1, $val.Length - 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
$pyvenvConfig[$keyval[0]] = $val
|
||||||
|
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $pyvenvConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
<# Begin Activate script --------------------------------------------------- #>
|
||||||
|
|
||||||
|
# Determine the containing directory of this script
|
||||||
|
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
$VenvExecDir = Get-Item -Path $VenvExecPath
|
||||||
|
|
||||||
|
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
||||||
|
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
|
||||||
|
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
||||||
|
|
||||||
|
# Set values required in priority: CmdLine, ConfigFile, Default
|
||||||
|
# First, get the location of the virtual environment, it might not be
|
||||||
|
# VenvExecDir if specified on the command line.
|
||||||
|
if ($VenvDir) {
|
||||||
|
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
||||||
|
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
||||||
|
Write-Verbose "VenvDir=$VenvDir"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Next, read the `pyvenv.cfg` file to determine any required value such
|
||||||
|
# as `prompt`.
|
||||||
|
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
||||||
|
|
||||||
|
# Next, set the prompt from the command line, or the config file, or
|
||||||
|
# just use the name of the virtual environment folder.
|
||||||
|
if ($Prompt) {
|
||||||
|
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
||||||
|
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
||||||
|
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
||||||
|
$Prompt = $pyvenvCfg['prompt'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
|
||||||
|
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
|
||||||
|
$Prompt = Split-Path -Path $venvDir -Leaf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Verbose "Prompt = '$Prompt'"
|
||||||
|
Write-Verbose "VenvDir='$VenvDir'"
|
||||||
|
|
||||||
|
# Deactivate any currently active virtual environment, but leave the
|
||||||
|
# deactivate function in place.
|
||||||
|
deactivate -nondestructive
|
||||||
|
|
||||||
|
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
|
||||||
|
# that there is an activated venv.
|
||||||
|
$env:VIRTUAL_ENV = $VenvDir
|
||||||
|
|
||||||
|
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
||||||
|
|
||||||
|
Write-Verbose "Setting prompt to '$Prompt'"
|
||||||
|
|
||||||
|
# Set the prompt to include the env name
|
||||||
|
# Make sure _OLD_VIRTUAL_PROMPT is global
|
||||||
|
function global:_OLD_VIRTUAL_PROMPT { "" }
|
||||||
|
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
|
||||||
|
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
||||||
|
|
||||||
|
function global:prompt {
|
||||||
|
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
|
||||||
|
_OLD_VIRTUAL_PROMPT
|
||||||
|
}
|
||||||
|
$env:VIRTUAL_ENV_PROMPT = $Prompt
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clear PYTHONHOME
|
||||||
|
if (Test-Path -Path Env:PYTHONHOME) {
|
||||||
|
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
|
||||||
|
Remove-Item -Path Env:PYTHONHOME
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add the venv to the PATH
|
||||||
|
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
|
||||||
|
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
|
||||||
69
venv/bin/activate
Executable file
69
venv/bin/activate
Executable file
@@ -0,0 +1,69 @@
|
|||||||
|
# This file must be used with "source bin/activate" *from bash*
|
||||||
|
# you cannot run it directly
|
||||||
|
|
||||||
|
deactivate () {
|
||||||
|
# reset old environment variables
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||||
|
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||||
|
export PATH
|
||||||
|
unset _OLD_VIRTUAL_PATH
|
||||||
|
fi
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||||
|
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||||
|
export PYTHONHOME
|
||||||
|
unset _OLD_VIRTUAL_PYTHONHOME
|
||||||
|
fi
|
||||||
|
|
||||||
|
# This should detect bash and zsh, which have a hash command that must
|
||||||
|
# be called to get it to forget past commands. Without forgetting
|
||||||
|
# past commands the $PATH changes we made may not be respected
|
||||||
|
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||||
|
hash -r 2> /dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||||
|
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||||
|
export PS1
|
||||||
|
unset _OLD_VIRTUAL_PS1
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset VIRTUAL_ENV
|
||||||
|
unset VIRTUAL_ENV_PROMPT
|
||||||
|
if [ ! "${1:-}" = "nondestructive" ] ; then
|
||||||
|
# Self destruct!
|
||||||
|
unset -f deactivate
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# unset irrelevant variables
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
VIRTUAL_ENV=/home/kitraining/coco-tool/Abschluss-Projekt/venv
|
||||||
|
export VIRTUAL_ENV
|
||||||
|
|
||||||
|
_OLD_VIRTUAL_PATH="$PATH"
|
||||||
|
PATH="$VIRTUAL_ENV/"bin":$PATH"
|
||||||
|
export PATH
|
||||||
|
|
||||||
|
# unset PYTHONHOME if set
|
||||||
|
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||||
|
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||||
|
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||||
|
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||||
|
unset PYTHONHOME
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||||
|
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||||
|
PS1='(venv) '"${PS1:-}"
|
||||||
|
export PS1
|
||||||
|
VIRTUAL_ENV_PROMPT='(venv) '
|
||||||
|
export VIRTUAL_ENV_PROMPT
|
||||||
|
fi
|
||||||
|
|
||||||
|
# This should detect bash and zsh, which have a hash command that must
|
||||||
|
# be called to get it to forget past commands. Without forgetting
|
||||||
|
# past commands the $PATH changes we made may not be respected
|
||||||
|
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||||
|
hash -r 2> /dev/null
|
||||||
|
fi
|
||||||
26
venv/bin/activate.csh
Executable file
26
venv/bin/activate.csh
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||||
|
# You cannot run it directly.
|
||||||
|
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||||
|
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
||||||
|
|
||||||
|
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||||
|
|
||||||
|
# Unset irrelevant variables.
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
setenv VIRTUAL_ENV /home/kitraining/coco-tool/Abschluss-Projekt/venv
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PATH="$PATH"
|
||||||
|
setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
|
||||||
|
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||||
|
|
||||||
|
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||||
|
set prompt = '(venv) '"$prompt"
|
||||||
|
setenv VIRTUAL_ENV_PROMPT '(venv) '
|
||||||
|
endif
|
||||||
|
|
||||||
|
alias pydoc python -m pydoc
|
||||||
|
|
||||||
|
rehash
|
||||||
69
venv/bin/activate.fish
Executable file
69
venv/bin/activate.fish
Executable file
@@ -0,0 +1,69 @@
|
|||||||
|
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
|
||||||
|
# (https://fishshell.com/); you cannot run it directly.
|
||||||
|
|
||||||
|
function deactivate -d "Exit virtual environment and return to normal shell environment"
|
||||||
|
# reset old environment variables
|
||||||
|
if test -n "$_OLD_VIRTUAL_PATH"
|
||||||
|
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||||
|
set -e _OLD_VIRTUAL_PATH
|
||||||
|
end
|
||||||
|
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
||||||
|
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
||||||
|
set -e _OLD_VIRTUAL_PYTHONHOME
|
||||||
|
end
|
||||||
|
|
||||||
|
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||||
|
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||||
|
# prevents error when using nested fish instances (Issue #93858)
|
||||||
|
if functions -q _old_fish_prompt
|
||||||
|
functions -e fish_prompt
|
||||||
|
functions -c _old_fish_prompt fish_prompt
|
||||||
|
functions -e _old_fish_prompt
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
set -e VIRTUAL_ENV
|
||||||
|
set -e VIRTUAL_ENV_PROMPT
|
||||||
|
if test "$argv[1]" != "nondestructive"
|
||||||
|
# Self-destruct!
|
||||||
|
functions -e deactivate
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Unset irrelevant variables.
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
set -gx VIRTUAL_ENV /home/kitraining/coco-tool/Abschluss-Projekt/venv
|
||||||
|
|
||||||
|
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||||
|
set -gx PATH "$VIRTUAL_ENV/"bin $PATH
|
||||||
|
|
||||||
|
# Unset PYTHONHOME if set.
|
||||||
|
if set -q PYTHONHOME
|
||||||
|
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||||
|
set -e PYTHONHOME
|
||||||
|
end
|
||||||
|
|
||||||
|
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||||
|
# fish uses a function instead of an env var to generate the prompt.
|
||||||
|
|
||||||
|
# Save the current fish_prompt function as the function _old_fish_prompt.
|
||||||
|
functions -c fish_prompt _old_fish_prompt
|
||||||
|
|
||||||
|
# With the original prompt function renamed, we can override with our own.
|
||||||
|
function fish_prompt
|
||||||
|
# Save the return status of the last command.
|
||||||
|
set -l old_status $status
|
||||||
|
|
||||||
|
# Output the venv prompt; color taken from the blue of the Python logo.
|
||||||
|
printf "%s%s%s" (set_color 4B8BBE) '(venv) ' (set_color normal)
|
||||||
|
|
||||||
|
# Restore the return status of the previous command.
|
||||||
|
echo "exit $old_status" | .
|
||||||
|
# Output the original/"old" prompt.
|
||||||
|
_old_fish_prompt
|
||||||
|
end
|
||||||
|
|
||||||
|
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||||
|
set -gx VIRTUAL_ENV_PROMPT '(venv) '
|
||||||
|
end
|
||||||
8
venv/bin/dotenv
Executable file
8
venv/bin/dotenv
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/kitraining/coco-tool/Abschluss-Projekt/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from dotenv.__main__ import cli
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(cli())
|
||||||
8
venv/bin/flask
Executable file
8
venv/bin/flask
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/kitraining/coco-tool/Abschluss-Projekt/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from flask.cli import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
venv/bin/normalizer
Executable file
8
venv/bin/normalizer
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/kitraining/coco-tool/Abschluss-Projekt/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from charset_normalizer.cli import cli_detect
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(cli_detect())
|
||||||
8
venv/bin/pip
Executable file
8
venv/bin/pip
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/kitraining/coco-tool/Abschluss-Projekt/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pip._internal.cli.main import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
venv/bin/pip3
Executable file
8
venv/bin/pip3
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/kitraining/coco-tool/Abschluss-Projekt/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pip._internal.cli.main import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
venv/bin/pip3.11
Executable file
8
venv/bin/pip3.11
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/kitraining/coco-tool/Abschluss-Projekt/venv/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pip._internal.cli.main import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
1
venv/bin/python
Symbolic link
1
venv/bin/python
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
python3
|
||||||
1
venv/bin/python3
Symbolic link
1
venv/bin/python3
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
/usr/bin/python3
|
||||||
1
venv/bin/python3.11
Symbolic link
1
venv/bin/python3.11
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
python3
|
||||||
164
venv/include/site/python3.11/greenlet/greenlet.h
Executable file
164
venv/include/site/python3.11/greenlet/greenlet.h
Executable file
@@ -0,0 +1,164 @@
|
|||||||
|
/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
|
||||||
|
|
||||||
|
/* Greenlet object interface */
|
||||||
|
|
||||||
|
#ifndef Py_GREENLETOBJECT_H
|
||||||
|
#define Py_GREENLETOBJECT_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <Python.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* This is deprecated and undocumented. It does not change. */
|
||||||
|
#define GREENLET_VERSION "1.0.0"
|
||||||
|
|
||||||
|
#ifndef GREENLET_MODULE
|
||||||
|
#define implementation_ptr_t void*
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct _greenlet {
|
||||||
|
PyObject_HEAD
|
||||||
|
PyObject* weakreflist;
|
||||||
|
PyObject* dict;
|
||||||
|
implementation_ptr_t pimpl;
|
||||||
|
} PyGreenlet;
|
||||||
|
|
||||||
|
#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type))
|
||||||
|
|
||||||
|
|
||||||
|
/* C API functions */
|
||||||
|
|
||||||
|
/* Total number of symbols that are exported */
|
||||||
|
#define PyGreenlet_API_pointers 12
|
||||||
|
|
||||||
|
#define PyGreenlet_Type_NUM 0
|
||||||
|
#define PyExc_GreenletError_NUM 1
|
||||||
|
#define PyExc_GreenletExit_NUM 2
|
||||||
|
|
||||||
|
#define PyGreenlet_New_NUM 3
|
||||||
|
#define PyGreenlet_GetCurrent_NUM 4
|
||||||
|
#define PyGreenlet_Throw_NUM 5
|
||||||
|
#define PyGreenlet_Switch_NUM 6
|
||||||
|
#define PyGreenlet_SetParent_NUM 7
|
||||||
|
|
||||||
|
#define PyGreenlet_MAIN_NUM 8
|
||||||
|
#define PyGreenlet_STARTED_NUM 9
|
||||||
|
#define PyGreenlet_ACTIVE_NUM 10
|
||||||
|
#define PyGreenlet_GET_PARENT_NUM 11
|
||||||
|
|
||||||
|
#ifndef GREENLET_MODULE
|
||||||
|
/* This section is used by modules that uses the greenlet C API */
|
||||||
|
static void** _PyGreenlet_API = NULL;
|
||||||
|
|
||||||
|
# define PyGreenlet_Type \
|
||||||
|
(*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM])
|
||||||
|
|
||||||
|
# define PyExc_GreenletError \
|
||||||
|
((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM])
|
||||||
|
|
||||||
|
# define PyExc_GreenletExit \
|
||||||
|
((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PyGreenlet_New(PyObject *args)
|
||||||
|
*
|
||||||
|
* greenlet.greenlet(run, parent=None)
|
||||||
|
*/
|
||||||
|
# define PyGreenlet_New \
|
||||||
|
(*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \
|
||||||
|
_PyGreenlet_API[PyGreenlet_New_NUM])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PyGreenlet_GetCurrent(void)
|
||||||
|
*
|
||||||
|
* greenlet.getcurrent()
|
||||||
|
*/
|
||||||
|
# define PyGreenlet_GetCurrent \
|
||||||
|
(*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PyGreenlet_Throw(
|
||||||
|
* PyGreenlet *greenlet,
|
||||||
|
* PyObject *typ,
|
||||||
|
* PyObject *val,
|
||||||
|
* PyObject *tb)
|
||||||
|
*
|
||||||
|
* g.throw(...)
|
||||||
|
*/
|
||||||
|
# define PyGreenlet_Throw \
|
||||||
|
(*(PyObject * (*)(PyGreenlet * self, \
|
||||||
|
PyObject * typ, \
|
||||||
|
PyObject * val, \
|
||||||
|
PyObject * tb)) \
|
||||||
|
_PyGreenlet_API[PyGreenlet_Throw_NUM])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args)
|
||||||
|
*
|
||||||
|
* g.switch(*args, **kwargs)
|
||||||
|
*/
|
||||||
|
# define PyGreenlet_Switch \
|
||||||
|
(*(PyObject * \
|
||||||
|
(*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \
|
||||||
|
_PyGreenlet_API[PyGreenlet_Switch_NUM])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent)
|
||||||
|
*
|
||||||
|
* g.parent = new_parent
|
||||||
|
*/
|
||||||
|
# define PyGreenlet_SetParent \
|
||||||
|
(*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \
|
||||||
|
_PyGreenlet_API[PyGreenlet_SetParent_NUM])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PyGreenlet_GetParent(PyObject* greenlet)
|
||||||
|
*
|
||||||
|
* return greenlet.parent;
|
||||||
|
*
|
||||||
|
* This could return NULL even if there is no exception active.
|
||||||
|
* If it does not return NULL, you are responsible for decrementing the
|
||||||
|
* reference count.
|
||||||
|
*/
|
||||||
|
# define PyGreenlet_GetParent \
|
||||||
|
(*(PyGreenlet* (*)(PyGreenlet*)) \
|
||||||
|
_PyGreenlet_API[PyGreenlet_GET_PARENT_NUM])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* deprecated, undocumented alias.
|
||||||
|
*/
|
||||||
|
# define PyGreenlet_GET_PARENT PyGreenlet_GetParent
|
||||||
|
|
||||||
|
# define PyGreenlet_MAIN \
|
||||||
|
(*(int (*)(PyGreenlet*)) \
|
||||||
|
_PyGreenlet_API[PyGreenlet_MAIN_NUM])
|
||||||
|
|
||||||
|
# define PyGreenlet_STARTED \
|
||||||
|
(*(int (*)(PyGreenlet*)) \
|
||||||
|
_PyGreenlet_API[PyGreenlet_STARTED_NUM])
|
||||||
|
|
||||||
|
# define PyGreenlet_ACTIVE \
|
||||||
|
(*(int (*)(PyGreenlet*)) \
|
||||||
|
_PyGreenlet_API[PyGreenlet_ACTIVE_NUM])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Macro that imports greenlet and initializes C API */
|
||||||
|
/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we
|
||||||
|
keep the older definition to be sure older code that might have a copy of
|
||||||
|
the header still works. */
|
||||||
|
# define PyGreenlet_Import() \
|
||||||
|
{ \
|
||||||
|
_PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* GREENLET_MODULE */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif /* !Py_GREENLETOBJECT_H */
|
||||||
1
venv/lib/python3.11/site-packages/Flask_Cors-4.0.0.dist-info/INSTALLER
Executable file
1
venv/lib/python3.11/site-packages/Flask_Cors-4.0.0.dist-info/INSTALLER
Executable file
@@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
7
venv/lib/python3.11/site-packages/Flask_Cors-4.0.0.dist-info/LICENSE
Executable file
7
venv/lib/python3.11/site-packages/Flask_Cors-4.0.0.dist-info/LICENSE
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
Copyright (C) 2016 Cory Dolphin, Olin College
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
147
venv/lib/python3.11/site-packages/Flask_Cors-4.0.0.dist-info/METADATA
Executable file
147
venv/lib/python3.11/site-packages/Flask_Cors-4.0.0.dist-info/METADATA
Executable file
@@ -0,0 +1,147 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: Flask-Cors
|
||||||
|
Version: 4.0.0
|
||||||
|
Summary: A Flask extension adding a decorator for CORS support
|
||||||
|
Home-page: https://github.com/corydolphin/flask-cors
|
||||||
|
Author: Cory Dolphin
|
||||||
|
Author-email: corydolphin@gmail.com
|
||||||
|
License: MIT
|
||||||
|
Platform: any
|
||||||
|
Classifier: Environment :: Web Environment
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
|
Classifier: Operating System :: OS Independent
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Programming Language :: Python :: 3.8
|
||||||
|
Classifier: Programming Language :: Python :: 3.9
|
||||||
|
Classifier: Programming Language :: Python :: 3.10
|
||||||
|
Classifier: Programming Language :: Python :: 3.11
|
||||||
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||||
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||||
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||||
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||||
|
License-File: LICENSE
|
||||||
|
Requires-Dist: Flask (>=0.9)
|
||||||
|
|
||||||
|
Flask-CORS
|
||||||
|
==========
|
||||||
|
|
||||||
|
|Build Status| |Latest Version| |Supported Python versions|
|
||||||
|
|License|
|
||||||
|
|
||||||
|
A Flask extension for handling Cross Origin Resource Sharing (CORS), making cross-origin AJAX possible.
|
||||||
|
|
||||||
|
This package has a simple philosophy: when you want to enable CORS, you wish to enable it for all use cases on a domain.
|
||||||
|
This means no mucking around with different allowed headers, methods, etc.
|
||||||
|
|
||||||
|
By default, submission of cookies across domains is disabled due to the security implications.
|
||||||
|
Please see the documentation for how to enable credential'ed requests, and please make sure you add some sort of `CSRF <http://en.wikipedia.org/wiki/Cross-site_request_forgery>`__ protection before doing so!
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
Install the extension with using pip, or easy\_install.
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
$ pip install -U flask-cors
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
This package exposes a Flask extension which by default enables CORS support on all routes, for all origins and methods.
|
||||||
|
It allows parameterization of all CORS headers on a per-resource level.
|
||||||
|
The package also contains a decorator, for those who prefer this approach.
|
||||||
|
|
||||||
|
Simple Usage
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
In the simplest case, initialize the Flask-Cors extension with default arguments in order to allow CORS for all domains on all routes.
|
||||||
|
See the full list of options in the `documentation <https://flask-cors.corydolphin.com/en/latest/api.html#extension>`__.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
from flask_cors import CORS
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
CORS(app)
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def helloWorld():
|
||||||
|
return "Hello, cross-origin-world!"
|
||||||
|
|
||||||
|
Resource specific CORS
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Alternatively, you can specify CORS options on a resource and origin level of granularity by passing a dictionary as the `resources` option, mapping paths to a set of options.
|
||||||
|
See the full list of options in the `documentation <https://flask-cors.corydolphin.com/en/latest/api.html#extension>`__.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
|
||||||
|
|
||||||
|
@app.route("/api/v1/users")
|
||||||
|
def list_users():
|
||||||
|
return "user example"
|
||||||
|
|
||||||
|
Route specific CORS via decorator
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
This extension also exposes a simple decorator to decorate flask routes with.
|
||||||
|
Simply add ``@cross_origin()`` below a call to Flask's ``@app.route(..)`` to allow CORS on a given route.
|
||||||
|
See the full list of options in the `decorator documentation <https://flask-cors.corydolphin.com/en/latest/api.html#decorator>`__.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
@cross_origin()
|
||||||
|
def helloWorld():
|
||||||
|
return "Hello, cross-origin-world!"
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
For a full list of options, please see the full `documentation <https://flask-cors.corydolphin.com/en/latest/api.html>`__
|
||||||
|
|
||||||
|
Troubleshooting
|
||||||
|
---------------
|
||||||
|
|
||||||
|
If things aren't working as you expect, enable logging to help understand what is going on under the hood, and why.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
logging.getLogger('flask_cors').level = logging.DEBUG
|
||||||
|
|
||||||
|
|
||||||
|
Tests
|
||||||
|
-----
|
||||||
|
|
||||||
|
A simple set of tests is included in ``test/``.
|
||||||
|
To run, install nose, and simply invoke ``nosetests`` or ``python setup.py test`` to exercise the tests.
|
||||||
|
|
||||||
|
If nosetests does not work for you, due to it no longer working with newer python versions.
|
||||||
|
You can use pytest to run the tests instead.
|
||||||
|
|
||||||
|
Contributing
|
||||||
|
------------
|
||||||
|
|
||||||
|
Questions, comments or improvements?
|
||||||
|
Please create an issue on `Github <https://github.com/corydolphin/flask-cors>`__, tweet at `@corydolphin <https://twitter.com/corydolphin>`__ or send me an email.
|
||||||
|
I do my best to include every contribution proposed in any way that I can.
|
||||||
|
|
||||||
|
Credits
|
||||||
|
-------
|
||||||
|
|
||||||
|
This Flask extension is based upon the `Decorator for the HTTP Access Control <https://web.archive.org/web/20190128010149/http://flask.pocoo.org/snippets/56/>`__ written by Armin Ronacher.
|
||||||
|
|
||||||
|
.. |Build Status| image:: https://api.travis-ci.org/corydolphin/flask-cors.svg?branch=master
|
||||||
|
:target: https://travis-ci.org/corydolphin/flask-cors
|
||||||
|
.. |Latest Version| image:: https://img.shields.io/pypi/v/Flask-Cors.svg
|
||||||
|
:target: https://pypi.python.org/pypi/Flask-Cors/
|
||||||
|
.. |Supported Python versions| image:: https://img.shields.io/pypi/pyversions/Flask-Cors.svg
|
||||||
|
:target: https://img.shields.io/pypi/pyversions/Flask-Cors.svg
|
||||||
|
.. |License| image:: http://img.shields.io/:license-mit-blue.svg
|
||||||
|
:target: https://pypi.python.org/pypi/Flask-Cors/
|
||||||
17
venv/lib/python3.11/site-packages/Flask_Cors-4.0.0.dist-info/RECORD
Executable file
17
venv/lib/python3.11/site-packages/Flask_Cors-4.0.0.dist-info/RECORD
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
Flask_Cors-4.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
Flask_Cors-4.0.0.dist-info/LICENSE,sha256=bhob3FSDTB4HQMvOXV9vLK4chG_Sp_SCsRZJWU-vvV0,1069
|
||||||
|
Flask_Cors-4.0.0.dist-info/METADATA,sha256=iien2vLs6EIqceJgaNEJ6FPinwfjzFWSNl7XOkuyc10,5419
|
||||||
|
Flask_Cors-4.0.0.dist-info/RECORD,,
|
||||||
|
Flask_Cors-4.0.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
Flask_Cors-4.0.0.dist-info/WHEEL,sha256=a-zpFRIJzOq5QfuhBzbhiA1eHTzNCJn8OdRvhdNX0Rk,110
|
||||||
|
Flask_Cors-4.0.0.dist-info/top_level.txt,sha256=aWye_0QNZPp_QtPF4ZluLHqnyVLT9CPJsfiGhwqkWuo,11
|
||||||
|
flask_cors/__init__.py,sha256=wZDCvPTHspA2g1VV7KyKN7R-uCdBnirTlsCzgPDcQtI,792
|
||||||
|
flask_cors/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
flask_cors/__pycache__/core.cpython-311.pyc,,
|
||||||
|
flask_cors/__pycache__/decorator.cpython-311.pyc,,
|
||||||
|
flask_cors/__pycache__/extension.cpython-311.pyc,,
|
||||||
|
flask_cors/__pycache__/version.cpython-311.pyc,,
|
||||||
|
flask_cors/core.py,sha256=e1u_o5SOcS_gMWGjcQrkyk91uPICnzZ3AXZvy5jQ_FE,14063
|
||||||
|
flask_cors/decorator.py,sha256=BeJsyX1wYhVKWN04FAhb6z8YqffiRr7wKqwzHPap4bw,5009
|
||||||
|
flask_cors/extension.py,sha256=nP4Zq_BhgDVWwPdIl_f-uucNxD38pXUo-dkL-voXc58,7832
|
||||||
|
flask_cors/version.py,sha256=61rJjfThnbRdElpSP2tm31hPmFnHJmcwoPhtqA0Bi_Q,22
|
||||||
6
venv/lib/python3.11/site-packages/Flask_Cors-4.0.0.dist-info/WHEEL
Executable file
6
venv/lib/python3.11/site-packages/Flask_Cors-4.0.0.dist-info/WHEEL
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: bdist_wheel (0.40.0)
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py2-none-any
|
||||||
|
Tag: py3-none-any
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user