initial push
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Ignore YOLOX model weights
|
||||||
|
backend/uploads/*.pth
|
||||||
|
*.pth
|
||||||
|
backend/node_modules/
|
||||||
175
add-project.html
Normal file
175
add-project.html
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="globals.css" />
|
||||||
|
<link rel="stylesheet" href="styleguide.css" />
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
<style>
|
||||||
|
#projects-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<div id="header">
|
||||||
|
<icon class="header-icon" onclick="window.location.href='/index.html'" , onmouseover=""
|
||||||
|
style="cursor: pointer;" src="./media/logo.png" alt="Logo"></icon>
|
||||||
|
<div class="button-row">
|
||||||
|
<button id="Add Training Project" class="button-red">Add Training Project</button>
|
||||||
|
<button id="Add Dataset" class="button">Add Dataset</button>
|
||||||
|
<button id="Import Dataset" class="button">Refresh Label-Studio</button>
|
||||||
|
<button id="seed-db-btn" class="button">Seed Database</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="popup">
|
||||||
|
<div class="upload-button" onclick="uploadButtonHandler()">
|
||||||
|
<span class="upload-button-text">Upload</span>
|
||||||
|
</div>
|
||||||
|
<div class="image"></div>
|
||||||
|
|
||||||
|
<div class="div">
|
||||||
|
|
||||||
|
<div class="add-category">
|
||||||
|
|
||||||
|
<input class="div-wrapper" placeholder="Class name"></input>
|
||||||
|
<button class="upload-button-text-wrapper">
|
||||||
|
<span class="button-text-upload">Add Class</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<input class="project-name" placeholder="Project Name" id="project_name_input"></input>
|
||||||
|
<textarea class="add-description" placeholder="Description" id="project_description_input"></textarea>
|
||||||
|
<div class="add-class-wrapper">
|
||||||
|
<script type="module">
|
||||||
|
import { addClass } from './js/add-class.js';
|
||||||
|
addClass();
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="confirm-button-datasetcreation">
|
||||||
|
<span class="button-text-upload">Confirm</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript" src="./js/add-image.js"></script>
|
||||||
|
<script type="module">
|
||||||
|
import { addClass } from './js/add-class.js';
|
||||||
|
// Grab the inputs and the button
|
||||||
|
const projectNameInput = document.querySelector('.project-name');
|
||||||
|
const descriptionInput = document.querySelector('.add-description');
|
||||||
|
const confirmButton = document.querySelector('.confirm-button-datasetcreation');
|
||||||
|
const classWrapper = document.querySelector('.add-class-wrapper');
|
||||||
|
|
||||||
|
|
||||||
|
const addClassButton = document.querySelector('.upload-button-text-wrapper');
|
||||||
|
const addProjectButton = document.querySelector('.confirm-button-datasetcreation')
|
||||||
|
|
||||||
|
addClassButton.addEventListener('click', () => {
|
||||||
|
const event = new Event('classListUpdated');
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Function to update button state
|
||||||
|
function updateButtonState() {
|
||||||
|
const projectName = projectNameInput.value.trim();
|
||||||
|
const description = descriptionInput.value.trim();
|
||||||
|
const existingClasses = classWrapper.querySelectorAll('.overlap-group');
|
||||||
|
|
||||||
|
// Disable button if either field is empty
|
||||||
|
if (projectName === '' || description === '' || existingClasses.length === 0) {
|
||||||
|
confirmButton.disabled = true;
|
||||||
|
confirmButton.style.cursor = 'not-allowed';
|
||||||
|
} else {
|
||||||
|
confirmButton.disabled = false;
|
||||||
|
confirmButton.style.cursor = 'pointer';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial check on page load
|
||||||
|
updateButtonState();
|
||||||
|
|
||||||
|
|
||||||
|
projectNameInput.addEventListener('input', updateButtonState);
|
||||||
|
descriptionInput.addEventListener('input', updateButtonState);
|
||||||
|
document.addEventListener('classListUpdated', updateButtonState);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('seed-db-btn').addEventListener('click', function () {
|
||||||
|
fetch('/api/seed')
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
document.getElementById('seed-db-btn').addEventListener('click', function () {
|
||||||
|
const elLoader = document.getElementById("loader")
|
||||||
|
elLoader.style.display = "inherit"
|
||||||
|
|
||||||
|
fetch('/api/seed')
|
||||||
|
.finally(() => {
|
||||||
|
// Instead of hiding loader immediately, poll /api/update-status until done
|
||||||
|
function pollStatus() {
|
||||||
|
fetch('/api/update-status')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(status => {
|
||||||
|
if (status && status.running) {
|
||||||
|
// Still running, poll again after short delay
|
||||||
|
setTimeout(pollStatus, 5000);
|
||||||
|
} else {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pollStatus();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show loader if backend is still processing on page load
|
||||||
|
|
||||||
|
function pollStatus() {
|
||||||
|
const elLoader = document.getElementById("loader");
|
||||||
|
fetch('/api/update-status')
|
||||||
|
.then(res => res.json())
|
||||||
|
|
||||||
|
.then(status => {
|
||||||
|
if (status && status.running) {
|
||||||
|
elLoader.style.display = "inherit";
|
||||||
|
setTimeout(pollStatus, 5000);
|
||||||
|
} else {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
10
backend/database/database.js
Normal file
10
backend/database/database.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// database.js
|
||||||
|
const { Sequelize } = require('sequelize');
|
||||||
|
|
||||||
|
const sequelize = new Sequelize('myapp', 'root', 'root', {
|
||||||
|
host: 'localhost',
|
||||||
|
dialect: 'mysql',
|
||||||
|
logging: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = sequelize;
|
||||||
250
backend/database/myapp.sql
Normal file
250
backend/database/myapp.sql
Normal file
File diff suppressed because one or more lines are too long
40
backend/models/Annotation.js
Normal file
40
backend/models/Annotation.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
const { DataTypes } = require('sequelize');
|
||||||
|
const sequelize = require('../database/database.js');
|
||||||
|
|
||||||
|
|
||||||
|
const Annotation = sequelize.define('Annotation', {
|
||||||
|
annotation_id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true,
|
||||||
|
},
|
||||||
|
image_id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
type: DataTypes.FLOAT,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
type: DataTypes.FLOAT,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: DataTypes.FLOAT,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: DataTypes.FLOAT,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
Label: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
tableName: 'annotation',
|
||||||
|
timestamps: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Annotation;
|
||||||
35
backend/models/Images.js
Normal file
35
backend/models/Images.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
const { DataTypes } = require('sequelize');
|
||||||
|
const sequelize = require('../database/database.js');
|
||||||
|
|
||||||
|
const Image = sequelize.define('Image', {
|
||||||
|
image_id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true,
|
||||||
|
},
|
||||||
|
image_path: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
project_id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: DataTypes.FLOAT,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: DataTypes.FLOAT,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
}, {
|
||||||
|
tableName: 'image',
|
||||||
|
timestamps: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Image;
|
||||||
|
|
||||||
|
|
||||||
24
backend/models/LabelStudioProject.js
Normal file
24
backend/models/LabelStudioProject.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
const { DataTypes } = require('sequelize');
|
||||||
|
const sequelize = require('../database/database.js');
|
||||||
|
|
||||||
|
const Label_studio_project = sequelize.define('LabelStudioProject', {
|
||||||
|
project_id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
unique: true,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
title:{
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
}, {
|
||||||
|
tableName: 'label_studio_project',
|
||||||
|
timestamps: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Label_studio_project;
|
||||||
|
|
||||||
|
|
||||||
38
backend/models/TrainingProject.js
Normal file
38
backend/models/TrainingProject.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
const { DataTypes } = require('sequelize');
|
||||||
|
const sequelize = require('../database/database.js');
|
||||||
|
|
||||||
|
const Training_Project = sequelize.define('LabelStudioProject', {
|
||||||
|
project_id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
unique: true,
|
||||||
|
allowNull: false,
|
||||||
|
autoIncrement: true,
|
||||||
|
},
|
||||||
|
title:{
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
},
|
||||||
|
classes: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
project_image: {
|
||||||
|
type: DataTypes.BLOB,
|
||||||
|
},
|
||||||
|
project_image_type: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
}, {
|
||||||
|
tableName: 'training_project',
|
||||||
|
timestamps: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Training_Project;
|
||||||
|
|
||||||
|
|
||||||
33
backend/models/TrainingProjectDetails.js
Normal file
33
backend/models/TrainingProjectDetails.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
const { DataTypes } = require('sequelize');
|
||||||
|
const sequelize = require('../database/database.js');
|
||||||
|
|
||||||
|
const TrainingProjectDetails = sequelize.define('TrainingProjectDetails', {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true,
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
|
project_id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
|
annotation_projects: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
class_map: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
allowNull: true,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
tableName: 'training_project_details',
|
||||||
|
timestamps: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = TrainingProjectDetails;
|
||||||
30
backend/models/index.js
Normal file
30
backend/models/index.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
const LabelStudioProject = require('./LabelStudioProject.js');
|
||||||
|
const Annotation = require('./Annotation.js');
|
||||||
|
const Image = require('./Images.js');
|
||||||
|
const sequelize = require('../database/database.js');
|
||||||
|
const TrainingProjectDetails = require('./TrainingProjectDetails.js');
|
||||||
|
const TrainingProject = require('./TrainingProject.js');
|
||||||
|
const Training = require('./training.js');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const Project = LabelStudioProject;
|
||||||
|
const Img = Image;
|
||||||
|
const Ann = Annotation;
|
||||||
|
|
||||||
|
// Associations
|
||||||
|
Project.hasMany(Img, { foreignKey: 'project_id' });
|
||||||
|
Img.belongsTo(Project, { foreignKey: 'project_id' });
|
||||||
|
|
||||||
|
Img.hasMany(Ann, { foreignKey: 'image_id' });
|
||||||
|
Ann.belongsTo(Img, { foreignKey: 'image_id' });
|
||||||
|
|
||||||
|
// TrainingProjectDetails <-> TrainingProject
|
||||||
|
TrainingProjectDetails.belongsTo(TrainingProject, { foreignKey: 'project_id' });
|
||||||
|
TrainingProject.hasOne(TrainingProjectDetails, { foreignKey: 'project_id' });
|
||||||
|
|
||||||
|
// Training <-> TrainingProjectDetails
|
||||||
|
Training.belongsTo(TrainingProjectDetails, { foreignKey: 'project_details_id' });
|
||||||
|
TrainingProjectDetails.hasMany(Training, { foreignKey: 'project_details_id' });
|
||||||
|
|
||||||
|
module.exports = { Project, Img, Ann, TrainingProjectDetails, TrainingProject, Training };
|
||||||
140
backend/models/training.js
Normal file
140
backend/models/training.js
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
const { DataTypes } = require('sequelize');
|
||||||
|
const sequelize = require('../database/database.js');
|
||||||
|
|
||||||
|
const Training = sequelize.define('training', {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
unique: true,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
exp_name: {
|
||||||
|
type: DataTypes.STRING(255)
|
||||||
|
},
|
||||||
|
max_epoch: {
|
||||||
|
type: DataTypes.INTEGER
|
||||||
|
},
|
||||||
|
depth: {
|
||||||
|
type: DataTypes.FLOAT
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: DataTypes.FLOAT
|
||||||
|
},
|
||||||
|
activation: {
|
||||||
|
type: DataTypes.STRING(255)
|
||||||
|
},
|
||||||
|
warmup_epochs: {
|
||||||
|
type: DataTypes.INTEGER
|
||||||
|
},
|
||||||
|
warmup_lr: {
|
||||||
|
type: DataTypes.FLOAT
|
||||||
|
},
|
||||||
|
basic_lr_per_img: {
|
||||||
|
type: DataTypes.FLOAT
|
||||||
|
},
|
||||||
|
scheduler: {
|
||||||
|
type: DataTypes.STRING(255)
|
||||||
|
},
|
||||||
|
no_aug_epochs: {
|
||||||
|
type: DataTypes.INTEGER
|
||||||
|
},
|
||||||
|
min_lr_ratio: {
|
||||||
|
type: DataTypes.FLOAT
|
||||||
|
},
|
||||||
|
ema: {
|
||||||
|
type: DataTypes.BOOLEAN
|
||||||
|
},
|
||||||
|
weight_decay: {
|
||||||
|
type: DataTypes.FLOAT
|
||||||
|
},
|
||||||
|
momentum: {
|
||||||
|
type: DataTypes.FLOAT
|
||||||
|
},
|
||||||
|
input_size: {
|
||||||
|
type: DataTypes.JSON
|
||||||
|
},
|
||||||
|
print_interval: {
|
||||||
|
type: DataTypes.INTEGER
|
||||||
|
},
|
||||||
|
eval_interval: {
|
||||||
|
type: DataTypes.INTEGER
|
||||||
|
},
|
||||||
|
save_history_ckpt: {
|
||||||
|
type: DataTypes.BOOLEAN
|
||||||
|
},
|
||||||
|
test_size: {
|
||||||
|
type: DataTypes.JSON
|
||||||
|
},
|
||||||
|
test_conf: {
|
||||||
|
type: DataTypes.FLOAT
|
||||||
|
},
|
||||||
|
nms_thre: {
|
||||||
|
type: DataTypes.FLOAT
|
||||||
|
},
|
||||||
|
multiscale_range: {
|
||||||
|
type: DataTypes.INTEGER
|
||||||
|
},
|
||||||
|
enable_mixup: {
|
||||||
|
type: DataTypes.BOOLEAN
|
||||||
|
},
|
||||||
|
mosaic_prob: {
|
||||||
|
type: DataTypes.FLOAT
|
||||||
|
},
|
||||||
|
mixup_prob: {
|
||||||
|
type: DataTypes.FLOAT
|
||||||
|
},
|
||||||
|
hsv_prob: {
|
||||||
|
type: DataTypes.FLOAT
|
||||||
|
},
|
||||||
|
flip_prob: {
|
||||||
|
type: DataTypes.FLOAT
|
||||||
|
},
|
||||||
|
degrees: {
|
||||||
|
type: DataTypes.FLOAT
|
||||||
|
},
|
||||||
|
mosaic_scale: {
|
||||||
|
type: DataTypes.JSON
|
||||||
|
},
|
||||||
|
mixup_scale: {
|
||||||
|
type: DataTypes.JSON
|
||||||
|
},
|
||||||
|
translate: {
|
||||||
|
type: DataTypes.FLOAT
|
||||||
|
},
|
||||||
|
shear: {
|
||||||
|
type: DataTypes.FLOAT
|
||||||
|
},
|
||||||
|
training_name: {
|
||||||
|
type: DataTypes.STRING(255)
|
||||||
|
},
|
||||||
|
project_details_id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
seed: {
|
||||||
|
type: DataTypes.INTEGER
|
||||||
|
},
|
||||||
|
train: {
|
||||||
|
type: DataTypes.INTEGER
|
||||||
|
},
|
||||||
|
valid: {
|
||||||
|
type: DataTypes.INTEGER
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
type: DataTypes.INTEGER
|
||||||
|
},
|
||||||
|
selected_model: {
|
||||||
|
type: DataTypes.STRING(255)
|
||||||
|
},
|
||||||
|
transfer_learning: {
|
||||||
|
type: DataTypes.STRING(255)
|
||||||
|
},
|
||||||
|
model_upload: {
|
||||||
|
type: DataTypes.BLOB
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
tableName: 'training',
|
||||||
|
timestamps: false
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Training;
|
||||||
0
backend/node
Normal file
0
backend/node
Normal file
1300
backend/package-lock.json
generated
Normal file
1300
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
backend/package.json
Normal file
20
backend/package.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "backend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"multer": "^2.0.1",
|
||||||
|
"mysql": "^2.18.1",
|
||||||
|
"mysql2": "^3.14.1",
|
||||||
|
"sequelize": "^6.37.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
0
backend/project_23/25/exp.py
Normal file
0
backend/project_23/25/exp.py
Normal file
15
backend/project_23/40/exp.py
Normal file
15
backend/project_23/40/exp.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/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.depth = 1.33
|
||||||
|
self.width = 1.25
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
15
backend/project_23/41/exp.py
Normal file
15
backend/project_23/41/exp.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/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.depth = 1.33
|
||||||
|
self.width = 1.25
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
20
backend/project_23/42/exp.py
Normal file
20
backend/project_23/42/exp.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/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.depth = 0.33
|
||||||
|
self.width = 0.375
|
||||||
|
self.input_size = (416, 416)
|
||||||
|
self.mosaic_scale = (0.5, 1.5)
|
||||||
|
self.random_size = (10, 20)
|
||||||
|
self.test_size = (416, 416)
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
|
self.enable_mixup = False
|
||||||
15
backend/project_23/43/exp.py
Normal file
15
backend/project_23/43/exp.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/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.depth = 1.33
|
||||||
|
self.width = 1.25
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
20
backend/project_23/46/exp.py
Normal file
20
backend/project_23/46/exp.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/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.depth = 0.33
|
||||||
|
self.width = 0.375
|
||||||
|
self.input_size = (416, 416)
|
||||||
|
self.mosaic_scale = (0.5, 1.5)
|
||||||
|
self.random_size = (10, 20)
|
||||||
|
self.test_size = (416, 416)
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
|
self.enable_mixup = False
|
||||||
20
backend/project_23/47/exp.py
Normal file
20
backend/project_23/47/exp.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/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.depth = 0.33
|
||||||
|
self.width = 0.375
|
||||||
|
self.input_size = (416, 416)
|
||||||
|
self.mosaic_scale = (0.5, 1.5)
|
||||||
|
self.random_size = (10, 20)
|
||||||
|
self.test_size = (416, 416)
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
|
self.enable_mixup = False
|
||||||
15
backend/project_23/48/exp.py
Normal file
15
backend/project_23/48/exp.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/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.depth = 0.33
|
||||||
|
self.width = 0.50
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
15
backend/project_23/49/exp.py
Normal file
15
backend/project_23/49/exp.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/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.depth = 0.33
|
||||||
|
self.width = 0.50
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
15
backend/project_23/50/exp.py
Normal file
15
backend/project_23/50/exp.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/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.depth = 0.33
|
||||||
|
self.width = 0.50
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
15
backend/project_23/54/exp.py
Normal file
15
backend/project_23/54/exp.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/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.depth = 0.33
|
||||||
|
self.width = 0.50
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
BIN
backend/project_35/37/__pycache__/exp_infer.cpython-310.pyc
Normal file
BIN
backend/project_35/37/__pycache__/exp_infer.cpython-310.pyc
Normal file
Binary file not shown.
22
backend/project_35/37/exp_infer.py
Normal file
22
backend/project_35/37/exp_infer.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/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_37_train.json"
|
||||||
|
self.val_ann = "coco_project_37_valid.json"
|
||||||
|
self.test_ann = "coco_project_37_test.json"
|
||||||
|
self.num_classes = 1
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-Tiny.pth'
|
||||||
|
self.depth = 1.0
|
||||||
|
self.width = 1.0
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
|
self.enable_mixup = False
|
||||||
292
backend/project_35/37/testy.py
Normal file
292
backend/project_35/37/testy.py
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
import os
|
||||||
|
import random
|
||||||
|
|
||||||
|
import torch
|
||||||
|
import torch.distributed as dist
|
||||||
|
import torch.nn as nn
|
||||||
|
|
||||||
|
# Dynamically import BaseExp from fixed path
|
||||||
|
import importlib.util
|
||||||
|
import sys
|
||||||
|
base_exp_path = '/home/kitraining/Yolox/YOLOX-main/yolox/exp/base_exp.py'
|
||||||
|
spec = importlib.util.spec_from_file_location('base_exp', base_exp_path)
|
||||||
|
base_exp = importlib.util.module_from_spec(spec)
|
||||||
|
sys.modules['base_exp'] = base_exp
|
||||||
|
spec.loader.exec_module(base_exp)
|
||||||
|
BaseExp = base_exp.BaseExp
|
||||||
|
|
||||||
|
__all__ = ["Exp", "check_exp_value"]
|
||||||
|
|
||||||
|
class Exp(BaseExp):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.seed = None
|
||||||
|
self.data_dir = r'/home/kitraining/To_Annotate/'
|
||||||
|
self.train_ann = 'coco_project_37_train.json'
|
||||||
|
self.val_ann = 'coco_project_37_valid.json'
|
||||||
|
self.test_ann = 'coco_project_37_test.json'
|
||||||
|
self.num_classes = 80
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-l.pth'
|
||||||
|
self.depth = 1.00
|
||||||
|
self.width = 1.00
|
||||||
|
self.act = 'silu'
|
||||||
|
self.data_num_workers = 4
|
||||||
|
self.input_size = (640, 640)
|
||||||
|
self.multiscale_range = 5
|
||||||
|
self.mosaic_prob = 1.0
|
||||||
|
self.mixup_prob = 1.0
|
||||||
|
self.hsv_prob = 1.0
|
||||||
|
self.flip_prob = 0.5
|
||||||
|
self.degrees = (10.0, 10.0)
|
||||||
|
self.translate = (0.1, 0.1)
|
||||||
|
self.mosaic_scale = (0.1, 2)
|
||||||
|
self.enable_mixup = True
|
||||||
|
self.mixup_scale = (0.5, 1.5)
|
||||||
|
self.shear = (2.0, 2.0)
|
||||||
|
self.warmup_epochs = 5
|
||||||
|
self.max_epoch = 300
|
||||||
|
self.warmup_lr = 0
|
||||||
|
self.min_lr_ratio = 0.05
|
||||||
|
self.basic_lr_per_img = 0.01 / 64.0
|
||||||
|
self.scheduler = 'yoloxwarmcos'
|
||||||
|
self.no_aug_epochs = 15
|
||||||
|
self.ema = True
|
||||||
|
self.weight_decay = 5e-4
|
||||||
|
self.momentum = 0.9
|
||||||
|
self.print_interval = 10
|
||||||
|
self.eval_interval = 10
|
||||||
|
self.save_history_ckpt = True
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split('.')[0]
|
||||||
|
self.test_size = (640, 640)
|
||||||
|
self.test_conf = 0.01
|
||||||
|
self.nmsthre = 0.65
|
||||||
|
self.exp_name = 'custom_exp123'
|
||||||
|
self.max_epoch = 300
|
||||||
|
self.depth = 1
|
||||||
|
self.width = 1
|
||||||
|
self.activation = 'silu'
|
||||||
|
self.warmup_epochs = 5
|
||||||
|
self.warmup_lr = 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.input_size = (640, 640)
|
||||||
|
self.print_interval = 10
|
||||||
|
self.eval_interval = 10
|
||||||
|
self.save_history_ckpt = True
|
||||||
|
self.test_size = (640, 640)
|
||||||
|
self.test_conf = 0.01
|
||||||
|
self.multiscale_range = 5
|
||||||
|
self.enable_mixup = True
|
||||||
|
self.mosaic_prob = 1
|
||||||
|
self.mixup_prob = 1
|
||||||
|
self.hsv_prob = 1
|
||||||
|
self.flip_prob = 0.5
|
||||||
|
self.degrees = (10, 10)
|
||||||
|
self.mosaic_scale = (0.1, 2)
|
||||||
|
self.mixup_scale = (0.5, 1.5)
|
||||||
|
self.translate = (0.1, 0.1)
|
||||||
|
self.shear = (2, 2)
|
||||||
|
self.project_details_id = 37
|
||||||
|
self.selected_model = 'YOLOX-l'
|
||||||
|
self.transfer_learning = 'coco'
|
||||||
|
|
||||||
|
def get_model(self):
|
||||||
|
from yolox.models import YOLOX, YOLOPAFPN, YOLOXHead
|
||||||
|
def init_yolo(M):
|
||||||
|
for m in M.modules():
|
||||||
|
if isinstance(m, nn.BatchNorm2d):
|
||||||
|
m.eps = 1e-3
|
||||||
|
m.momentum = 0.03
|
||||||
|
if getattr(self, 'model', None) is None:
|
||||||
|
in_channels = [256, 512, 1024]
|
||||||
|
backbone = YOLOPAFPN(self.depth, self.width, in_channels=in_channels, act=self.act)
|
||||||
|
head = YOLOXHead(self.num_classes, self.width, in_channels=in_channels, act=self.act)
|
||||||
|
self.model = YOLOX(backbone, head)
|
||||||
|
self.model.apply(init_yolo)
|
||||||
|
self.model.head.initialize_biases(1e-2)
|
||||||
|
self.model.train()
|
||||||
|
return self.model
|
||||||
|
|
||||||
|
def get_dataset(self, cache=False, cache_type='ram'):
|
||||||
|
from yolox.data import COCODataset, TrainTransform
|
||||||
|
return COCODataset(
|
||||||
|
data_dir=self.data_dir,
|
||||||
|
json_file=self.train_ann,
|
||||||
|
img_size=self.input_size,
|
||||||
|
preproc=TrainTransform(
|
||||||
|
max_labels=50,
|
||||||
|
flip_prob=self.flip_prob,
|
||||||
|
hsv_prob=self.hsv_prob
|
||||||
|
),
|
||||||
|
cache=cache,
|
||||||
|
cache_type=cache_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_data_loader(self, batch_size, is_distributed, no_aug=False, cache_img=None):
|
||||||
|
from yolox.data import (
|
||||||
|
TrainTransform,
|
||||||
|
YoloBatchSampler,
|
||||||
|
DataLoader,
|
||||||
|
InfiniteSampler,
|
||||||
|
MosaicDetection,
|
||||||
|
worker_init_reset_seed,
|
||||||
|
)
|
||||||
|
from yolox.utils import wait_for_the_master
|
||||||
|
if self.dataset is None:
|
||||||
|
with wait_for_the_master():
|
||||||
|
assert cache_img is None, 'cache_img must be None if you did not create self.dataset before launch'
|
||||||
|
self.dataset = self.get_dataset(cache=False, cache_type=cache_img)
|
||||||
|
self.dataset = MosaicDetection(
|
||||||
|
dataset=self.dataset,
|
||||||
|
mosaic=not no_aug,
|
||||||
|
img_size=self.input_size,
|
||||||
|
preproc=TrainTransform(
|
||||||
|
max_labels=120,
|
||||||
|
flip_prob=self.flip_prob,
|
||||||
|
hsv_prob=self.hsv_prob),
|
||||||
|
degrees=self.degrees,
|
||||||
|
translate=self.translate,
|
||||||
|
mosaic_scale=self.mosaic_scale,
|
||||||
|
mixup_scale=self.mixup_scale,
|
||||||
|
shear=self.shear,
|
||||||
|
enable_mixup=self.enable_mixup,
|
||||||
|
mosaic_prob=self.mosaic_prob,
|
||||||
|
mixup_prob=self.mixup_prob,
|
||||||
|
)
|
||||||
|
if is_distributed:
|
||||||
|
batch_size = batch_size // dist.get_world_size()
|
||||||
|
sampler = InfiniteSampler(len(self.dataset), seed=self.seed if self.seed else 0)
|
||||||
|
batch_sampler = YoloBatchSampler(
|
||||||
|
sampler=sampler,
|
||||||
|
batch_size=batch_size,
|
||||||
|
drop_last=False,
|
||||||
|
mosaic=not no_aug,
|
||||||
|
)
|
||||||
|
dataloader_kwargs = {'num_workers': self.data_num_workers, 'pin_memory': True}
|
||||||
|
dataloader_kwargs['batch_sampler'] = batch_sampler
|
||||||
|
dataloader_kwargs['worker_init_fn'] = worker_init_reset_seed
|
||||||
|
train_loader = DataLoader(self.dataset, **dataloader_kwargs)
|
||||||
|
return train_loader
|
||||||
|
|
||||||
|
def random_resize(self, data_loader, epoch, rank, is_distributed):
|
||||||
|
tensor = torch.LongTensor(2).cuda()
|
||||||
|
if rank == 0:
|
||||||
|
size_factor = self.input_size[1] * 1.0 / self.input_size[0]
|
||||||
|
if not hasattr(self, 'random_size'):
|
||||||
|
min_size = int(self.input_size[0] / 32) - self.multiscale_range
|
||||||
|
max_size = int(self.input_size[0] / 32) + self.multiscale_range
|
||||||
|
self.random_size = (min_size, max_size)
|
||||||
|
size = random.randint(*self.random_size)
|
||||||
|
size = (int(32 * size), 32 * int(size * size_factor))
|
||||||
|
tensor[0] = size[0]
|
||||||
|
tensor[1] = size[1]
|
||||||
|
if is_distributed:
|
||||||
|
dist.barrier()
|
||||||
|
dist.broadcast(tensor, 0)
|
||||||
|
input_size = (tensor[0].item(), tensor[1].item())
|
||||||
|
return input_size
|
||||||
|
|
||||||
|
def preprocess(self, inputs, targets, tsize):
|
||||||
|
scale_y = tsize[0] / self.input_size[0]
|
||||||
|
scale_x = tsize[1] / self.input_size[1]
|
||||||
|
if scale_x != 1 or scale_y != 1:
|
||||||
|
inputs = nn.functional.interpolate(
|
||||||
|
inputs, size=tsize, mode='bilinear', align_corners=False
|
||||||
|
)
|
||||||
|
targets[..., 1::2] = targets[..., 1::2] * scale_x
|
||||||
|
targets[..., 2::2] = targets[..., 2::2] * scale_y
|
||||||
|
return inputs, targets
|
||||||
|
|
||||||
|
def get_optimizer(self, batch_size):
|
||||||
|
if 'optimizer' not in self.__dict__:
|
||||||
|
if self.warmup_epochs > 0:
|
||||||
|
lr = self.warmup_lr
|
||||||
|
else:
|
||||||
|
lr = self.basic_lr_per_img * batch_size
|
||||||
|
pg0, pg1, pg2 = [], [], []
|
||||||
|
for k, v in self.model.named_modules():
|
||||||
|
if hasattr(v, 'bias') and isinstance(v.bias, nn.Parameter):
|
||||||
|
pg2.append(v.bias)
|
||||||
|
if isinstance(v, nn.BatchNorm2d) or 'bn' in k:
|
||||||
|
pg0.append(v.weight)
|
||||||
|
elif hasattr(v, 'weight') and isinstance(v.weight, nn.Parameter):
|
||||||
|
pg1.append(v.weight)
|
||||||
|
optimizer = torch.optim.SGD(
|
||||||
|
pg0, lr=lr, momentum=self.momentum, nesterov=True
|
||||||
|
)
|
||||||
|
optimizer.add_param_group({'params': pg1, 'weight_decay': self.weight_decay})
|
||||||
|
optimizer.add_param_group({'params': pg2})
|
||||||
|
self.optimizer = optimizer
|
||||||
|
return self.optimizer
|
||||||
|
|
||||||
|
def get_lr_scheduler(self, lr, iters_per_epoch):
|
||||||
|
from yolox.utils import LRScheduler
|
||||||
|
scheduler = LRScheduler(
|
||||||
|
self.scheduler,
|
||||||
|
lr,
|
||||||
|
iters_per_epoch,
|
||||||
|
self.max_epoch,
|
||||||
|
warmup_epochs=self.warmup_epochs,
|
||||||
|
warmup_lr_start=self.warmup_lr,
|
||||||
|
no_aug_epochs=self.no_aug_epochs,
|
||||||
|
min_lr_ratio=self.min_lr_ratio,
|
||||||
|
)
|
||||||
|
return scheduler
|
||||||
|
|
||||||
|
def get_eval_dataset(self, **kwargs):
|
||||||
|
from yolox.data import COCODataset, ValTransform
|
||||||
|
testdev = kwargs.get('testdev', False)
|
||||||
|
legacy = kwargs.get('legacy', False)
|
||||||
|
return COCODataset(
|
||||||
|
data_dir=self.data_dir,
|
||||||
|
json_file=self.val_ann if not testdev else self.test_ann,
|
||||||
|
name='' if not testdev else 'test2017',
|
||||||
|
img_size=self.test_size,
|
||||||
|
preproc=ValTransform(legacy=legacy),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_eval_loader(self, batch_size, is_distributed, **kwargs):
|
||||||
|
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 = torch.utils.data.DataLoader(valdataset, **dataloader_kwargs)
|
||||||
|
return val_loader
|
||||||
|
|
||||||
|
def get_evaluator(self, batch_size, is_distributed, testdev=False, legacy=False):
|
||||||
|
from yolox.evaluators import COCOEvaluator
|
||||||
|
return COCOEvaluator(
|
||||||
|
dataloader=self.get_eval_loader(batch_size, is_distributed,
|
||||||
|
testdev=testdev, legacy=legacy),
|
||||||
|
img_size=self.test_size,
|
||||||
|
confthre=self.test_conf,
|
||||||
|
nmsthre=self.nmsthre,
|
||||||
|
num_classes=self.num_classes,
|
||||||
|
testdev=testdev,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_trainer(self, args):
|
||||||
|
from yolox.core import Trainer
|
||||||
|
trainer = Trainer(self, args)
|
||||||
|
return trainer
|
||||||
|
|
||||||
|
def eval(self, model, evaluator, is_distributed, half=False, return_outputs=False):
|
||||||
|
return evaluator.evaluate(model, is_distributed, half, return_outputs=return_outputs)
|
||||||
|
|
||||||
|
def check_exp_value(exp):
|
||||||
|
h, w = exp.input_size
|
||||||
|
assert h % 32 == 0 and w % 32 == 0, 'input size must be multiples of 32'
|
||||||
BIN
backend/project_36/38/__pycache__/exp_infer.cpython-310.pyc
Normal file
BIN
backend/project_36/38/__pycache__/exp_infer.cpython-310.pyc
Normal file
Binary file not shown.
20
backend/project_36/38/exp_infer.py
Normal file
20
backend/project_36/38/exp_infer.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/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_38_train.json"
|
||||||
|
self.val_ann = "coco_project_38_valid.json"
|
||||||
|
self.test_ann = "coco_project_38_test.json"
|
||||||
|
self.depth = 0.33
|
||||||
|
self.width = 0.50
|
||||||
|
self.num_classes = 1
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
292
backend/project_36/38/testy.py
Normal file
292
backend/project_36/38/testy.py
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
import os
|
||||||
|
import random
|
||||||
|
|
||||||
|
import torch
|
||||||
|
import torch.distributed as dist
|
||||||
|
import torch.nn as nn
|
||||||
|
|
||||||
|
# Dynamically import BaseExp from fixed path
|
||||||
|
import importlib.util
|
||||||
|
import sys
|
||||||
|
base_exp_path = '/home/kitraining/Yolox/YOLOX-main/yolox/exp/base_exp.py'
|
||||||
|
spec = importlib.util.spec_from_file_location('base_exp', base_exp_path)
|
||||||
|
base_exp = importlib.util.module_from_spec(spec)
|
||||||
|
sys.modules['base_exp'] = base_exp
|
||||||
|
spec.loader.exec_module(base_exp)
|
||||||
|
BaseExp = base_exp.BaseExp
|
||||||
|
|
||||||
|
__all__ = ["Exp", "check_exp_value"]
|
||||||
|
|
||||||
|
class Exp(BaseExp):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.seed = None
|
||||||
|
self.data_dir = r'/home/kitraining/To_Annotate/'
|
||||||
|
self.train_ann = 'coco_project_38_train.json'
|
||||||
|
self.val_ann = 'coco_project_38_valid.json'
|
||||||
|
self.test_ann = 'coco_project_38_test.json'
|
||||||
|
self.num_classes = 80
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-s.pth'
|
||||||
|
self.depth = 1.00
|
||||||
|
self.width = 1.00
|
||||||
|
self.act = 'silu'
|
||||||
|
self.data_num_workers = 4
|
||||||
|
self.input_size = (640, 640)
|
||||||
|
self.multiscale_range = 5
|
||||||
|
self.mosaic_prob = 1.0
|
||||||
|
self.mixup_prob = 1.0
|
||||||
|
self.hsv_prob = 1.0
|
||||||
|
self.flip_prob = 0.5
|
||||||
|
self.degrees = (10.0, 10.0)
|
||||||
|
self.translate = (0.1, 0.1)
|
||||||
|
self.mosaic_scale = (0.1, 2)
|
||||||
|
self.enable_mixup = True
|
||||||
|
self.mixup_scale = (0.5, 1.5)
|
||||||
|
self.shear = (2.0, 2.0)
|
||||||
|
self.warmup_epochs = 5
|
||||||
|
self.max_epoch = 300
|
||||||
|
self.warmup_lr = 0
|
||||||
|
self.min_lr_ratio = 0.05
|
||||||
|
self.basic_lr_per_img = 0.01 / 64.0
|
||||||
|
self.scheduler = 'yoloxwarmcos'
|
||||||
|
self.no_aug_epochs = 15
|
||||||
|
self.ema = True
|
||||||
|
self.weight_decay = 5e-4
|
||||||
|
self.momentum = 0.9
|
||||||
|
self.print_interval = 10
|
||||||
|
self.eval_interval = 10
|
||||||
|
self.save_history_ckpt = True
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split('.')[0]
|
||||||
|
self.test_size = (640, 640)
|
||||||
|
self.test_conf = 0.01
|
||||||
|
self.nmsthre = 0.65
|
||||||
|
self.exp_name = 'lalalalal'
|
||||||
|
self.max_epoch = 300
|
||||||
|
self.depth = 1
|
||||||
|
self.width = 1
|
||||||
|
self.activation = 'silu'
|
||||||
|
self.warmup_epochs = 5
|
||||||
|
self.warmup_lr = 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.input_size = (640, 640)
|
||||||
|
self.print_interval = 10
|
||||||
|
self.eval_interval = 10
|
||||||
|
self.save_history_ckpt = True
|
||||||
|
self.test_size = (640, 640)
|
||||||
|
self.test_conf = 0.01
|
||||||
|
self.multiscale_range = 5
|
||||||
|
self.enable_mixup = True
|
||||||
|
self.mosaic_prob = 1
|
||||||
|
self.mixup_prob = 1
|
||||||
|
self.hsv_prob = 1
|
||||||
|
self.flip_prob = 0.5
|
||||||
|
self.degrees = (10, 10)
|
||||||
|
self.mosaic_scale = (0.1, 2)
|
||||||
|
self.mixup_scale = (0.5, 1.5)
|
||||||
|
self.translate = (0.1, 0.1)
|
||||||
|
self.shear = (2, 2)
|
||||||
|
self.project_details_id = 38
|
||||||
|
self.selected_model = 'YOLOX-s'
|
||||||
|
self.transfer_learning = 'coco'
|
||||||
|
|
||||||
|
def get_model(self):
|
||||||
|
from yolox.models import YOLOX, YOLOPAFPN, YOLOXHead
|
||||||
|
def init_yolo(M):
|
||||||
|
for m in M.modules():
|
||||||
|
if isinstance(m, nn.BatchNorm2d):
|
||||||
|
m.eps = 1e-3
|
||||||
|
m.momentum = 0.03
|
||||||
|
if getattr(self, 'model', None) is None:
|
||||||
|
in_channels = [256, 512, 1024]
|
||||||
|
backbone = YOLOPAFPN(self.depth, self.width, in_channels=in_channels, act=self.act)
|
||||||
|
head = YOLOXHead(self.num_classes, self.width, in_channels=in_channels, act=self.act)
|
||||||
|
self.model = YOLOX(backbone, head)
|
||||||
|
self.model.apply(init_yolo)
|
||||||
|
self.model.head.initialize_biases(1e-2)
|
||||||
|
self.model.train()
|
||||||
|
return self.model
|
||||||
|
|
||||||
|
def get_dataset(self, cache=False, cache_type='ram'):
|
||||||
|
from yolox.data import COCODataset, TrainTransform
|
||||||
|
return COCODataset(
|
||||||
|
data_dir=self.data_dir,
|
||||||
|
json_file=self.train_ann,
|
||||||
|
img_size=self.input_size,
|
||||||
|
preproc=TrainTransform(
|
||||||
|
max_labels=50,
|
||||||
|
flip_prob=self.flip_prob,
|
||||||
|
hsv_prob=self.hsv_prob
|
||||||
|
),
|
||||||
|
cache=cache,
|
||||||
|
cache_type=cache_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_data_loader(self, batch_size, is_distributed, no_aug=False, cache_img=None):
|
||||||
|
from yolox.data import (
|
||||||
|
TrainTransform,
|
||||||
|
YoloBatchSampler,
|
||||||
|
DataLoader,
|
||||||
|
InfiniteSampler,
|
||||||
|
MosaicDetection,
|
||||||
|
worker_init_reset_seed,
|
||||||
|
)
|
||||||
|
from yolox.utils import wait_for_the_master
|
||||||
|
if self.dataset is None:
|
||||||
|
with wait_for_the_master():
|
||||||
|
assert cache_img is None, 'cache_img must be None if you did not create self.dataset before launch'
|
||||||
|
self.dataset = self.get_dataset(cache=False, cache_type=cache_img)
|
||||||
|
self.dataset = MosaicDetection(
|
||||||
|
dataset=self.dataset,
|
||||||
|
mosaic=not no_aug,
|
||||||
|
img_size=self.input_size,
|
||||||
|
preproc=TrainTransform(
|
||||||
|
max_labels=120,
|
||||||
|
flip_prob=self.flip_prob,
|
||||||
|
hsv_prob=self.hsv_prob),
|
||||||
|
degrees=self.degrees,
|
||||||
|
translate=self.translate,
|
||||||
|
mosaic_scale=self.mosaic_scale,
|
||||||
|
mixup_scale=self.mixup_scale,
|
||||||
|
shear=self.shear,
|
||||||
|
enable_mixup=self.enable_mixup,
|
||||||
|
mosaic_prob=self.mosaic_prob,
|
||||||
|
mixup_prob=self.mixup_prob,
|
||||||
|
)
|
||||||
|
if is_distributed:
|
||||||
|
batch_size = batch_size // dist.get_world_size()
|
||||||
|
sampler = InfiniteSampler(len(self.dataset), seed=self.seed if self.seed else 0)
|
||||||
|
batch_sampler = YoloBatchSampler(
|
||||||
|
sampler=sampler,
|
||||||
|
batch_size=batch_size,
|
||||||
|
drop_last=False,
|
||||||
|
mosaic=not no_aug,
|
||||||
|
)
|
||||||
|
dataloader_kwargs = {'num_workers': self.data_num_workers, 'pin_memory': True}
|
||||||
|
dataloader_kwargs['batch_sampler'] = batch_sampler
|
||||||
|
dataloader_kwargs['worker_init_fn'] = worker_init_reset_seed
|
||||||
|
train_loader = DataLoader(self.dataset, **dataloader_kwargs)
|
||||||
|
return train_loader
|
||||||
|
|
||||||
|
def random_resize(self, data_loader, epoch, rank, is_distributed):
|
||||||
|
tensor = torch.LongTensor(2).cuda()
|
||||||
|
if rank == 0:
|
||||||
|
size_factor = self.input_size[1] * 1.0 / self.input_size[0]
|
||||||
|
if not hasattr(self, 'random_size'):
|
||||||
|
min_size = int(self.input_size[0] / 32) - self.multiscale_range
|
||||||
|
max_size = int(self.input_size[0] / 32) + self.multiscale_range
|
||||||
|
self.random_size = (min_size, max_size)
|
||||||
|
size = random.randint(*self.random_size)
|
||||||
|
size = (int(32 * size), 32 * int(size * size_factor))
|
||||||
|
tensor[0] = size[0]
|
||||||
|
tensor[1] = size[1]
|
||||||
|
if is_distributed:
|
||||||
|
dist.barrier()
|
||||||
|
dist.broadcast(tensor, 0)
|
||||||
|
input_size = (tensor[0].item(), tensor[1].item())
|
||||||
|
return input_size
|
||||||
|
|
||||||
|
def preprocess(self, inputs, targets, tsize):
|
||||||
|
scale_y = tsize[0] / self.input_size[0]
|
||||||
|
scale_x = tsize[1] / self.input_size[1]
|
||||||
|
if scale_x != 1 or scale_y != 1:
|
||||||
|
inputs = nn.functional.interpolate(
|
||||||
|
inputs, size=tsize, mode='bilinear', align_corners=False
|
||||||
|
)
|
||||||
|
targets[..., 1::2] = targets[..., 1::2] * scale_x
|
||||||
|
targets[..., 2::2] = targets[..., 2::2] * scale_y
|
||||||
|
return inputs, targets
|
||||||
|
|
||||||
|
def get_optimizer(self, batch_size):
|
||||||
|
if 'optimizer' not in self.__dict__:
|
||||||
|
if self.warmup_epochs > 0:
|
||||||
|
lr = self.warmup_lr
|
||||||
|
else:
|
||||||
|
lr = self.basic_lr_per_img * batch_size
|
||||||
|
pg0, pg1, pg2 = [], [], []
|
||||||
|
for k, v in self.model.named_modules():
|
||||||
|
if hasattr(v, 'bias') and isinstance(v.bias, nn.Parameter):
|
||||||
|
pg2.append(v.bias)
|
||||||
|
if isinstance(v, nn.BatchNorm2d) or 'bn' in k:
|
||||||
|
pg0.append(v.weight)
|
||||||
|
elif hasattr(v, 'weight') and isinstance(v.weight, nn.Parameter):
|
||||||
|
pg1.append(v.weight)
|
||||||
|
optimizer = torch.optim.SGD(
|
||||||
|
pg0, lr=lr, momentum=self.momentum, nesterov=True
|
||||||
|
)
|
||||||
|
optimizer.add_param_group({'params': pg1, 'weight_decay': self.weight_decay})
|
||||||
|
optimizer.add_param_group({'params': pg2})
|
||||||
|
self.optimizer = optimizer
|
||||||
|
return self.optimizer
|
||||||
|
|
||||||
|
def get_lr_scheduler(self, lr, iters_per_epoch):
|
||||||
|
from yolox.utils import LRScheduler
|
||||||
|
scheduler = LRScheduler(
|
||||||
|
self.scheduler,
|
||||||
|
lr,
|
||||||
|
iters_per_epoch,
|
||||||
|
self.max_epoch,
|
||||||
|
warmup_epochs=self.warmup_epochs,
|
||||||
|
warmup_lr_start=self.warmup_lr,
|
||||||
|
no_aug_epochs=self.no_aug_epochs,
|
||||||
|
min_lr_ratio=self.min_lr_ratio,
|
||||||
|
)
|
||||||
|
return scheduler
|
||||||
|
|
||||||
|
def get_eval_dataset(self, **kwargs):
|
||||||
|
from yolox.data import COCODataset, ValTransform
|
||||||
|
testdev = kwargs.get('testdev', False)
|
||||||
|
legacy = kwargs.get('legacy', False)
|
||||||
|
return COCODataset(
|
||||||
|
data_dir=self.data_dir,
|
||||||
|
json_file=self.val_ann if not testdev else self.test_ann,
|
||||||
|
name='' if not testdev else 'test2017',
|
||||||
|
img_size=self.test_size,
|
||||||
|
preproc=ValTransform(legacy=legacy),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_eval_loader(self, batch_size, is_distributed, **kwargs):
|
||||||
|
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 = torch.utils.data.DataLoader(valdataset, **dataloader_kwargs)
|
||||||
|
return val_loader
|
||||||
|
|
||||||
|
def get_evaluator(self, batch_size, is_distributed, testdev=False, legacy=False):
|
||||||
|
from yolox.evaluators import COCOEvaluator
|
||||||
|
return COCOEvaluator(
|
||||||
|
dataloader=self.get_eval_loader(batch_size, is_distributed,
|
||||||
|
testdev=testdev, legacy=legacy),
|
||||||
|
img_size=self.test_size,
|
||||||
|
confthre=self.test_conf,
|
||||||
|
nmsthre=self.nmsthre,
|
||||||
|
num_classes=self.num_classes,
|
||||||
|
testdev=testdev,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_trainer(self, args):
|
||||||
|
from yolox.core import Trainer
|
||||||
|
trainer = Trainer(self, args)
|
||||||
|
return trainer
|
||||||
|
|
||||||
|
def eval(self, model, evaluator, is_distributed, half=False, return_outputs=False):
|
||||||
|
return evaluator.evaluate(model, is_distributed, half, return_outputs=return_outputs)
|
||||||
|
|
||||||
|
def check_exp_value(exp):
|
||||||
|
h, w = exp.input_size
|
||||||
|
assert h % 32 == 0 and w % 32 == 0, 'input size must be multiples of 32'
|
||||||
BIN
backend/project_37/39/__pycache__/exp_infer.cpython-310.pyc
Normal file
BIN
backend/project_37/39/__pycache__/exp_infer.cpython-310.pyc
Normal file
Binary file not shown.
27
backend/project_37/39/exp_infer.py
Normal file
27
backend/project_37/39/exp_infer.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/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_39_train.json"
|
||||||
|
self.val_ann = "coco_project_39_valid.json"
|
||||||
|
self.test_ann = "coco_project_39_test.json"
|
||||||
|
self.num_classes = 80
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-Tiny.pth'
|
||||||
|
self.depth = 0.33
|
||||||
|
self.width = 0.375
|
||||||
|
self.input_size = (416, 416)
|
||||||
|
self.mosaic_scale = (0.5, 1.5)
|
||||||
|
self.random_size = (10, 20)
|
||||||
|
self.test_size = (416, 416)
|
||||||
|
self.enable_mixup = False
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
|
|
||||||
BIN
backend/project_38/40/__pycache__/exp_infer.cpython-310.pyc
Normal file
BIN
backend/project_38/40/__pycache__/exp_infer.cpython-310.pyc
Normal file
Binary file not shown.
15
backend/project_38/40/exp.py
Normal file
15
backend/project_38/40/exp.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/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.depth = 1.33
|
||||||
|
self.width = 1.25
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
26
backend/project_38/40/exp_infer.py
Normal file
26
backend/project_38/40/exp_infer.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/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_40_train.json"
|
||||||
|
self.val_ann = "coco_project_40_valid.json"
|
||||||
|
self.test_ann = "coco_project_40_test.json"
|
||||||
|
self.num_classes = 80
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-x.pth'
|
||||||
|
self.depth = 1.33
|
||||||
|
self.width = 1.25
|
||||||
|
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
|
||||||
15
backend/project_38/40/testy.py
Normal file
15
backend/project_38/40/testy.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/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.depth = 1.33
|
||||||
|
self.width = 1.25
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
15
backend/project_40/41/exp.py
Normal file
15
backend/project_40/41/exp.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/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.depth = 1.33
|
||||||
|
self.width = 1.25
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
26
backend/project_40/41/exp_infer.py
Normal file
26
backend/project_40/41/exp_infer.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/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_41_train.json"
|
||||||
|
self.val_ann = "coco_project_41_valid.json"
|
||||||
|
self.test_ann = "coco_project_41_test.json"
|
||||||
|
self.num_classes = 1
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-l.pth'
|
||||||
|
self.depth = 1
|
||||||
|
self.width = 1
|
||||||
|
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
|
||||||
BIN
backend/project_41/42/__pycache__/exp.cpython-310.pyc
Normal file
BIN
backend/project_41/42/__pycache__/exp.cpython-310.pyc
Normal file
Binary file not shown.
BIN
backend/project_41/42/__pycache__/exp_infer.cpython-310.pyc
Normal file
BIN
backend/project_41/42/__pycache__/exp_infer.cpython-310.pyc
Normal file
Binary file not shown.
25
backend/project_41/42/exp.py
Normal file
25
backend/project_41/42/exp.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
# Copyright (c) Megvii, Inc. and its affiliates.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from yolox.exp import Exp as MyExp
|
||||||
|
|
||||||
|
|
||||||
|
class Exp(MyExp):
|
||||||
|
def __init__(self):
|
||||||
|
super(Exp, self).__init__()
|
||||||
|
|
||||||
|
self.data_dir = "/home/kitraining/To_Annotate/"
|
||||||
|
self.train_ann = "coco_project_42_train.json"
|
||||||
|
self.val_ann = "coco_project_42_valid.json"
|
||||||
|
self.test_ann = "coco_project_42_test.json"
|
||||||
|
self.depth = 0.33
|
||||||
|
self.width = 0.375
|
||||||
|
self.input_size = (416, 416)
|
||||||
|
self.mosaic_scale = (0.5, 1.5)
|
||||||
|
self.random_size = (10, 20)
|
||||||
|
self.test_size = (416, 416)
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
|
self.enable_mixup = False
|
||||||
26
backend/project_41/42/exp_infer.py
Normal file
26
backend/project_41/42/exp_infer.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/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_42_train.json"
|
||||||
|
self.val_ann = "coco_project_42_valid.json"
|
||||||
|
self.test_ann = "coco_project_42_test.json"
|
||||||
|
self.num_classes = 4
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-s.pth'
|
||||||
|
self.depth = 1
|
||||||
|
self.width = 1
|
||||||
|
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
|
||||||
BIN
backend/project_43/43/__pycache__/exp.cpython-310.pyc
Normal file
BIN
backend/project_43/43/__pycache__/exp.cpython-310.pyc
Normal file
Binary file not shown.
19
backend/project_43/43/exp.py
Normal file
19
backend/project_43/43/exp.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/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_43_train.json"
|
||||||
|
self.val_ann = "coco_project_43_valid.json"
|
||||||
|
self.test_ann = "coco_project_43_test.json"
|
||||||
|
self.depth = 1.33
|
||||||
|
self.width = 1.25
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
26
backend/project_43/43/exp_infer.py
Normal file
26
backend/project_43/43/exp_infer.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/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_43_train.json"
|
||||||
|
self.val_ann = "coco_project_43_valid.json"
|
||||||
|
self.test_ann = "coco_project_43_test.json"
|
||||||
|
self.num_classes = 1
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-Tiny.pth'
|
||||||
|
self.depth = 1
|
||||||
|
self.width = 1
|
||||||
|
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
|
||||||
BIN
backend/project_44/44/__pycache__/exp.cpython-310.pyc
Normal file
BIN
backend/project_44/44/__pycache__/exp.cpython-310.pyc
Normal file
Binary file not shown.
25
backend/project_44/44/exp.py
Normal file
25
backend/project_44/44/exp.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
# Copyright (c) Megvii, Inc. and its affiliates.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from yolox.exp import Exp as MyExp
|
||||||
|
|
||||||
|
|
||||||
|
class Exp(MyExp):
|
||||||
|
def __init__(self):
|
||||||
|
super(Exp, self).__init__()
|
||||||
|
self.data_dir = "/home/kitraining/To_Annotate/"
|
||||||
|
self.train_ann = "coco_project_48_train.json"
|
||||||
|
self.val_ann = "coco_project_48_valid.json"
|
||||||
|
self.test_ann = "coco_project_48_test.json"
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/tiny_persondetector/best_ckpt.pth'
|
||||||
|
self.num_classes = 4
|
||||||
|
self.depth = 0.33
|
||||||
|
self.width = 0.375
|
||||||
|
self.ingput_size = (416, 416)
|
||||||
|
self.mosaic_scale = (0.5, 1.5)
|
||||||
|
self.random_size = (10, 20)
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
|
self.enable_mixup = False
|
||||||
26
backend/project_44/44/exp_infer.py
Normal file
26
backend/project_44/44/exp_infer.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/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_44_train.json"
|
||||||
|
self.val_ann = "coco_project_44_valid.json"
|
||||||
|
self.test_ann = "coco_project_44_test.json"
|
||||||
|
self.num_classes = 2
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-x.pth'
|
||||||
|
self.depth = 1.33
|
||||||
|
self.width = 1.25
|
||||||
|
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
|
||||||
BIN
backend/project_46/46/__pycache__/exp.cpython-310.pyc
Normal file
BIN
backend/project_46/46/__pycache__/exp.cpython-310.pyc
Normal file
Binary file not shown.
26
backend/project_46/46/exp.py
Normal file
26
backend/project_46/46/exp.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/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_46_train.json"
|
||||||
|
self.val_ann = "coco_project_46_valid.json"
|
||||||
|
self.test_ann = "coco_project_46_test.json"
|
||||||
|
self.num_classes = 4
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-Tiny.pth'
|
||||||
|
self.depth = 0.33
|
||||||
|
self.width = 0.375
|
||||||
|
self.input_size = (416, 416)
|
||||||
|
self.mosaic_scale = (0.5, 1.5)
|
||||||
|
self.random_size = (10, 20)
|
||||||
|
self.test_size = (416, 416)
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
|
self.enable_mixup = True
|
||||||
26
backend/project_46/46/exp_infer.py
Normal file
26
backend/project_46/46/exp_infer.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/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_46_train.json"
|
||||||
|
self.val_ann = "coco_project_46_valid.json"
|
||||||
|
self.test_ann = "coco_project_46_test.json"
|
||||||
|
self.num_classes = 80
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-Tiny.pth'
|
||||||
|
self.depth = 1
|
||||||
|
self.width = 1
|
||||||
|
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
|
||||||
BIN
backend/project_47/47/__pycache__/exp.cpython-310.pyc
Normal file
BIN
backend/project_47/47/__pycache__/exp.cpython-310.pyc
Normal file
Binary file not shown.
26
backend/project_47/47/exp.py
Normal file
26
backend/project_47/47/exp.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/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_47_train.json"
|
||||||
|
self.val_ann = "coco_project_47_valid.json"
|
||||||
|
self.test_ann = "coco_project_47_test.json"
|
||||||
|
self.num_classes = 2
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-Tiny.pth'
|
||||||
|
self.depth = 0.33
|
||||||
|
self.width = 0.375
|
||||||
|
self.input_size = (416, 416)
|
||||||
|
self.mosaic_scale = (0.5, 1.5)
|
||||||
|
self.random_size = (10, 20)
|
||||||
|
self.test_size = (416, 416)
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
|
self.enable_mixup = True
|
||||||
26
backend/project_47/47/exp_infer.py
Normal file
26
backend/project_47/47/exp_infer.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/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_47_train.json"
|
||||||
|
self.val_ann = "coco_project_47_valid.json"
|
||||||
|
self.test_ann = "coco_project_47_test.json"
|
||||||
|
self.num_classes = 4
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-x.pth'
|
||||||
|
self.depth = 1
|
||||||
|
self.width = 1
|
||||||
|
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
|
||||||
BIN
backend/project_48/48/__pycache__/exp.cpython-310.pyc
Normal file
BIN
backend/project_48/48/__pycache__/exp.cpython-310.pyc
Normal file
Binary file not shown.
22
backend/project_48/48/exp.py
Normal file
22
backend/project_48/48/exp.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/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_48_train.json"
|
||||||
|
self.val_ann = "coco_project_48_valid.json"
|
||||||
|
self.test_ann = "coco_project_48_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.50
|
||||||
|
self.act = "relu"
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
25
backend/project_48/48/exp_infer.py
Normal file
25
backend/project_48/48/exp_infer.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
# Copyright (c) Megvii, Inc. and its affiliates.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from yolox.exp import Exp as MyExp
|
||||||
|
|
||||||
|
|
||||||
|
class Exp(MyExp):
|
||||||
|
def __init__(self):
|
||||||
|
super(Exp, self).__init__()
|
||||||
|
self.data_dir = "/home/kitraining/To_Annotate/"
|
||||||
|
self.train_ann = "coco_project_48_train.json"
|
||||||
|
self.val_ann = "coco_project_48_valid.json"
|
||||||
|
self.test_ann = "coco_project_48_test.json"
|
||||||
|
self.num_classes = 4
|
||||||
|
self.depth = 1
|
||||||
|
self.width = 1
|
||||||
|
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
|
||||||
BIN
backend/project_49/49/__pycache__/exp.cpython-310.pyc
Normal file
BIN
backend/project_49/49/__pycache__/exp.cpython-310.pyc
Normal file
Binary file not shown.
28
backend/project_49/49/exp.py
Normal file
28
backend/project_49/49/exp.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
# Copyright (c) Megvii, Inc. and its affiliates.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from yolox.exp import Exp as MyExp
|
||||||
|
|
||||||
|
|
||||||
|
class Exp(MyExp):
|
||||||
|
def __init__(self):
|
||||||
|
super(Exp, self).__init__()
|
||||||
|
self.data_dir = "/home/kitraining/To_Annotate/"
|
||||||
|
self.train_ann = "coco_project_49_train.json"
|
||||||
|
self.val_ann = "coco_project_49_valid.json"
|
||||||
|
self.test_ann = "coco_project_49_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.50
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
|
self.enable_mixup = True
|
||||||
|
|
||||||
|
|
||||||
|
# -------------- training config --------------------- #
|
||||||
|
self.warmup_epochs = 5
|
||||||
|
self.max_epoch = 100
|
||||||
|
self.act = "silu"
|
||||||
26
backend/project_49/49/exp_infer.py
Normal file
26
backend/project_49/49/exp_infer.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/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_49_train.json"
|
||||||
|
self.val_ann = "coco_project_49_valid.json"
|
||||||
|
self.test_ann = "coco_project_49_test.json"
|
||||||
|
self.num_classes = 4
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-Tiny.pth'
|
||||||
|
self.depth = 1
|
||||||
|
self.width = 1
|
||||||
|
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
|
||||||
BIN
backend/project_50/50/__pycache__/exp.cpython-310.pyc
Normal file
BIN
backend/project_50/50/__pycache__/exp.cpython-310.pyc
Normal file
Binary file not shown.
1
backend/project_50/50/camera_inference.py
Normal file
1
backend/project_50/50/camera_inference.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python tools/demo.py video -f /home/kitraining/coco_tool/backend/project_50/50/exp.py -c ./YOLOX_outputs/exp/best_ckpt.pth --path /home/kitraining/Videos/test_1.mkv --conf 0.25 --nms 0.45 --tsize 640 --save_result --device gpu
|
||||||
57
backend/project_50/50/exp.py
Normal file
57
backend/project_50/50/exp.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#!/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_50_train.json"
|
||||||
|
self.val_ann = "coco_project_50_valid.json"
|
||||||
|
self.test_ann = "coco_project_50_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.50
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
|
|
||||||
|
# -------------- training config --------------------- #
|
||||||
|
self.warmup_epochs = 15 # More warmup
|
||||||
|
self.max_epoch = 250 # more epochs
|
||||||
|
self.act = "silu" #Activation function
|
||||||
|
|
||||||
|
# Thresholds
|
||||||
|
self.test_conf = 0.01 # Low to catch more the second class
|
||||||
|
self.nmsthre = 0.7
|
||||||
|
|
||||||
|
# Data Augmentation intens to improve generalization
|
||||||
|
self.enable_mixup = True
|
||||||
|
self.mixup_prob = 0.9 # mixup
|
||||||
|
self.mosaic_prob = 0.9 # mosaico
|
||||||
|
self.degrees = 30.0 # Rotation
|
||||||
|
self.translate = 0.4 # Translation
|
||||||
|
self.scale = (0.2, 2.0) # Scaling
|
||||||
|
self.shear = 10.0 # Shear
|
||||||
|
self.flip_prob = 0.8
|
||||||
|
self.hsv_prob = 1.0
|
||||||
|
|
||||||
|
# Learning rate
|
||||||
|
self.basic_lr_per_img = 0.001 / 64.0 # Lower LR to avoid divergence
|
||||||
|
self.scheduler = "yoloxwarmcos"
|
||||||
|
|
||||||
|
# Loss weights
|
||||||
|
self.cls_loss_weight = 8.0 # More weight to the classification loss
|
||||||
|
self.obj_loss_weight = 1.0
|
||||||
|
self.reg_loss_weight = 0.5
|
||||||
|
|
||||||
|
# Input size bigger for better detection of small objects like babys
|
||||||
|
self.input_size = (832, 832)
|
||||||
|
self.test_size = (832, 832)
|
||||||
|
|
||||||
|
# Batch size
|
||||||
|
self.batch_size = 5 # Reduce if you have memory issues
|
||||||
26
backend/project_50/50/exp_infer.py
Normal file
26
backend/project_50/50/exp_infer.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/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_50_train.json"
|
||||||
|
self.val_ann = "coco_project_50_valid.json"
|
||||||
|
self.test_ann = "coco_project_50_test.json"
|
||||||
|
self.num_classes = 2
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-s.pth'
|
||||||
|
self.depth = 1
|
||||||
|
self.width = 1
|
||||||
|
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
|
||||||
BIN
backend/project_53/53/__pycache__/exp.cpython-310.pyc
Normal file
BIN
backend/project_53/53/__pycache__/exp.cpython-310.pyc
Normal file
Binary file not shown.
58
backend/project_53/53/exp.py
Normal file
58
backend/project_53/53/exp.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#!/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_53_train.json"
|
||||||
|
self.val_ann = "coco_project_53_valid.json"
|
||||||
|
self.test_ann = "coco_project_53_test.json"
|
||||||
|
self.num_classes = 3
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/YOLOX_outputs/exp_Topview_4/best_ckpt.pth'
|
||||||
|
self.depth = 0.33
|
||||||
|
self.width = 0.50
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
|
|
||||||
|
|
||||||
|
# -------------- training config --------------------- #
|
||||||
|
self.warmup_epochs = 15 # More warmup
|
||||||
|
self.max_epoch = 250 # more epochs
|
||||||
|
self.act = "silu" #Activation function
|
||||||
|
|
||||||
|
# Thresholds
|
||||||
|
self.test_conf = 0.01 # Low to catch more the second class
|
||||||
|
self.nmsthre = 0.7
|
||||||
|
|
||||||
|
# Data Augmentation intens to improve generalization
|
||||||
|
self.enable_mixup = True
|
||||||
|
self.mixup_prob = 0.9 # mixup
|
||||||
|
self.mosaic_prob = 0.9 # mosaico
|
||||||
|
self.degrees = 30.0 # Rotation
|
||||||
|
self.translate = 0.4 # Translation
|
||||||
|
self.scale = (0.2, 2.0) # Scaling
|
||||||
|
self.shear = 10.0 # Shear
|
||||||
|
self.flip_prob = 0.8
|
||||||
|
self.hsv_prob = 1.0
|
||||||
|
|
||||||
|
# Learning rate
|
||||||
|
self.basic_lr_per_img = 0.001 / 64.0 # Lower LR to avoid divergence
|
||||||
|
self.scheduler = "yoloxwarmcos"
|
||||||
|
|
||||||
|
# Loss weights
|
||||||
|
self.cls_loss_weight = 8.0 # More weight to the classification loss
|
||||||
|
self.obj_loss_weight = 1.0
|
||||||
|
self.reg_loss_weight = 0.5
|
||||||
|
|
||||||
|
# Input size bigger for better detection of small objects like babys
|
||||||
|
self.input_size = (832, 832)
|
||||||
|
self.test_size = (832, 832)
|
||||||
|
|
||||||
|
# Batch size
|
||||||
|
self.batch_size = 5 # Reduce if you have memory issues
|
||||||
25
backend/project_53/53/exp_infer.py
Normal file
25
backend/project_53/53/exp_infer.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
# Copyright (c) Megvii, Inc. and its affiliates.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from yolox.exp import Exp as MyExp
|
||||||
|
|
||||||
|
|
||||||
|
class Exp(MyExp):
|
||||||
|
def __init__(self):
|
||||||
|
super(Exp, self).__init__()
|
||||||
|
self.data_dir = "/home/kitraining/To_Annotate/"
|
||||||
|
self.train_ann = "coco_project_53_train.json"
|
||||||
|
self.val_ann = "coco_project_53_valid.json"
|
||||||
|
self.test_ann = "coco_project_53_test.json"
|
||||||
|
self.num_classes = 3
|
||||||
|
self.depth = 1
|
||||||
|
self.width = 1
|
||||||
|
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
|
||||||
BIN
backend/project_54/54/__pycache__/exp.cpython-310.pyc
Normal file
BIN
backend/project_54/54/__pycache__/exp.cpython-310.pyc
Normal file
Binary file not shown.
67
backend/project_54/54/exp.py
Normal file
67
backend/project_54/54/exp.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#!/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_54_train.json"
|
||||||
|
self.val_ann = "coco_project_54_valid.json"
|
||||||
|
self.test_ann = "coco_project_54_test.json"
|
||||||
|
self.num_classes = 3
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX_s.pth'
|
||||||
|
self.depth = 0.33
|
||||||
|
self.width = 0.50
|
||||||
|
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
||||||
|
|
||||||
|
|
||||||
|
# -------------- training config --------------------- #
|
||||||
|
|
||||||
|
self.use_focal_loss = True # Focal Loss for better class imbalance handling
|
||||||
|
self.focal_loss_alpha = 0.25
|
||||||
|
self.focal_loss_gamma = 1.5
|
||||||
|
|
||||||
|
self.warmup_epochs = 20 # More warmup
|
||||||
|
self.max_epoch = 150 # More epochs for better convergence
|
||||||
|
self.act = "silu" # Activation function
|
||||||
|
self.no_aug_epochs = 30 # No augmentation for last epochs to stabilize training
|
||||||
|
|
||||||
|
self.class_weights = [1.0, 1.0, 1.0] # Weights for each class to handle imbalance
|
||||||
|
|
||||||
|
# Thresholds
|
||||||
|
self.test_conf = 0.15 # Low to catch more the second class
|
||||||
|
self.nmsthre = 0.5 # IoU threshold for NMS
|
||||||
|
|
||||||
|
# Data Augmentation intens to improve generalization
|
||||||
|
self.enable_mixup = True
|
||||||
|
self.mixup_prob = 0.7 # mixup
|
||||||
|
self.mosaic_prob = 0.8 # mosaico
|
||||||
|
self.degrees = 20.0 # Rotation
|
||||||
|
self.translate = 0.2 # Translation
|
||||||
|
self.scale = (0.5, 1.5) # Scaling
|
||||||
|
self.shear = 5.0 # Shear
|
||||||
|
self.flip_prob = 0.8
|
||||||
|
self.hsv_prob = 1.0
|
||||||
|
|
||||||
|
# Learning rate
|
||||||
|
self.basic_lr_per_img = 0.001 / 64.0 # Lower LR to avoid divergence
|
||||||
|
self.scheduler = "yoloxwarmcos"
|
||||||
|
self.min_lr_ratio = 0.01
|
||||||
|
|
||||||
|
# Loss weights
|
||||||
|
self.cls_loss_weight = 8.0 # More weight to the classification loss
|
||||||
|
self.obj_loss_weight = 1.0
|
||||||
|
self.reg_loss_weight = 1.0
|
||||||
|
|
||||||
|
# Input size bigger for better detection of small objects like babys
|
||||||
|
self.input_size = (832, 832)
|
||||||
|
self.test_size = (832, 832)
|
||||||
|
|
||||||
|
# Batch size
|
||||||
|
self.batch_size = 5 # Reduce if you have memory issues
|
||||||
26
backend/project_54/54/exp_infer.py
Normal file
26
backend/project_54/54/exp_infer.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/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_54_train.json"
|
||||||
|
self.val_ann = "coco_project_54_valid.json"
|
||||||
|
self.test_ann = "coco_project_54_test.json"
|
||||||
|
self.num_classes = 2
|
||||||
|
self.pretrained_ckpt = r'/home/kitraining/Yolox/YOLOX-main/pretrained/YOLOX-s.pth'
|
||||||
|
self.depth = 1
|
||||||
|
self.width = 1
|
||||||
|
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
|
||||||
496
backend/routes/api.js
Normal file
496
backend/routes/api.js
Normal file
@@ -0,0 +1,496 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const multer = require('multer');
|
||||||
|
const upload = multer();
|
||||||
|
const TrainingProject = require('../models/TrainingProject.js');
|
||||||
|
const LabelStudioProject = require('../models/LabelStudioProject.js')
|
||||||
|
const { seedLabelStudio, updateStatus } = require('../services/seed-label-studio.js');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const {generateTrainingJson} = require('../services/generate-json-yolox.js')
|
||||||
|
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// Ensure JSON bodies are parsed for all routes
|
||||||
|
router.use(express.json());
|
||||||
|
|
||||||
|
router.get('/seed', async (req, res) => {
|
||||||
|
const result = await seedLabelStudio();
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Trigger generate-json-yolox.js
|
||||||
|
|
||||||
|
router.post('/generate-yolox-json', async (req, res) => {
|
||||||
|
const { project_id } = req.body;
|
||||||
|
if (!project_id) {
|
||||||
|
return res.status(400).json({ message: 'Missing project_id in request body' });
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Generate COCO JSONs
|
||||||
|
// Find all TrainingProjectDetails for this project
|
||||||
|
const TrainingProjectDetails = require('../models/TrainingProjectDetails.js');
|
||||||
|
const detailsRows = await TrainingProjectDetails.findAll({ where: { project_id } });
|
||||||
|
if (!detailsRows || detailsRows.length === 0) {
|
||||||
|
return res.status(404).json({ message: 'No TrainingProjectDetails found for project ' + project_id });
|
||||||
|
}
|
||||||
|
// For each details row, generate coco.jsons and exp.py in projectfolder/project_details_id
|
||||||
|
const Training = require('../models/training.js');
|
||||||
|
const { saveYoloxExp } = require('../services/generate-yolox-exp.js');
|
||||||
|
const TrainingProject = require('../models/TrainingProject.js');
|
||||||
|
const trainingProject = await TrainingProject.findByPk(project_id);
|
||||||
|
const projectName = trainingProject.name ? trainingProject.name.replace(/\s+/g, '_') : `project_${project_id}`;
|
||||||
|
for (const details of detailsRows) {
|
||||||
|
const detailsId = details.id;
|
||||||
|
await generateTrainingJson(detailsId);
|
||||||
|
const trainings = await Training.findAll({ where: { project_details_id: detailsId } });
|
||||||
|
if (trainings.length === 0) continue;
|
||||||
|
// For each training, save exp.py in projectfolder/project_details_id
|
||||||
|
const outDir = path.join(__dirname, '..', projectName, String(detailsId));
|
||||||
|
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
||||||
|
for (const training of trainings) {
|
||||||
|
const expFilePath = path.join(outDir, 'exp.py');
|
||||||
|
await saveYoloxExp(training.id, expFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all trainings for this project
|
||||||
|
// ...existing code...
|
||||||
|
res.json({ message: 'YOLOX JSON and exp.py generated for project ' + project_id });
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error generating YOLOX JSON:', err);
|
||||||
|
res.status(500).json({ message: 'Failed to generate YOLOX JSON', error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start YOLOX training
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
router.post('/start-yolox-training', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { project_id, training_id } = req.body;
|
||||||
|
// Get project name
|
||||||
|
const trainingProject = await TrainingProject.findByPk(project_id);
|
||||||
|
const projectName = trainingProject.name ? trainingProject.name.replace(/\s+/g, '_') : `project_${project_id}`;
|
||||||
|
// Look up training row by id or project_details_id
|
||||||
|
const Training = require('../models/training.js');
|
||||||
|
let trainingRow = await Training.findByPk(training_id);
|
||||||
|
if (!trainingRow) {
|
||||||
|
trainingRow = await Training.findOne({ where: { project_details_id: training_id } });
|
||||||
|
}
|
||||||
|
if (!trainingRow) {
|
||||||
|
return res.status(404).json({ error: `Training row not found for id or project_details_id ${training_id}` });
|
||||||
|
}
|
||||||
|
const project_details_id = trainingRow.project_details_id;
|
||||||
|
// Use the generated exp.py from the correct project folder
|
||||||
|
const outDir = path.join(__dirname, '..', projectName, String(project_details_id));
|
||||||
|
const yoloxMainDir = '/home/kitraining/Yolox/YOLOX-main';
|
||||||
|
const expSrc = path.join(outDir, 'exp.py');
|
||||||
|
if (!fs.existsSync(expSrc)) {
|
||||||
|
return res.status(500).json({ error: `exp.py not found at ${expSrc}` });
|
||||||
|
}
|
||||||
|
// Source venv and run YOLOX training in YOLOX-main folder
|
||||||
|
const yoloxVenv = '/home/kitraining/Yolox/yolox_venv/bin/activate';
|
||||||
|
// Determine model argument based on selected_model and transfer_learning
|
||||||
|
let modelArg = '';
|
||||||
|
let cmd = '';
|
||||||
|
if (
|
||||||
|
trainingRow.transfer_learning &&
|
||||||
|
typeof trainingRow.transfer_learning === 'string' &&
|
||||||
|
trainingRow.transfer_learning.toLowerCase() === 'coco'
|
||||||
|
) {
|
||||||
|
// If transfer_learning is 'coco', add -o and modelArg
|
||||||
|
modelArg = ` -c /home/kitraining/Yolox/YOLOX-main/pretrained/${trainingRow.selected_model}`;
|
||||||
|
cmd = `bash -c 'source ${yoloxVenv} && python tools/train.py -f ${expSrc} -d 1 -b 8 --fp16 -o ${modelArg}.pth --cache'`;
|
||||||
|
} else if (
|
||||||
|
trainingRow.selected_model &&
|
||||||
|
trainingRow.selected_model.toLowerCase() === 'coco' &&
|
||||||
|
(!trainingRow.transfer_learning || trainingRow.transfer_learning === false)
|
||||||
|
) {
|
||||||
|
// If selected_model is 'coco' and not transfer_learning, add modelArg only
|
||||||
|
modelArg = ` -c /pretrained/${trainingRow.selected_model}`;
|
||||||
|
cmd = `bash -c 'source ${yoloxVenv} && python tools/train.py -f ${expSrc} -d 1 -b 8 --fp16 -o ${modelArg}.pth --cache'`;
|
||||||
|
} else {
|
||||||
|
// Default: no modelArg
|
||||||
|
cmd = `bash -c 'source ${yoloxVenv} && python tools/train.py -f ${expSrc} -d 1 -b 8 --fp16' --cache`;
|
||||||
|
}
|
||||||
|
console.log(cmd)
|
||||||
|
const child = spawn(cmd, { shell: true, cwd: yoloxMainDir });
|
||||||
|
child.stdout.pipe(process.stdout);
|
||||||
|
child.stderr.pipe(process.stderr);
|
||||||
|
|
||||||
|
res.json({ message: 'Training started' });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: 'Failed to start training', details: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get YOLOX training log
|
||||||
|
router.get('/training-log', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { project_id, training_id } = req.query;
|
||||||
|
const trainingProject = await TrainingProject.findByPk(project_id);
|
||||||
|
const projectName = trainingProject.name ? trainingProject.name.replace(/\s+/g, '_') : `project_${project_id}`;
|
||||||
|
const outDir = path.join(__dirname, '..', projectName, String(training_id));
|
||||||
|
const logPath = path.join(outDir, 'training.log');
|
||||||
|
if (!fs.existsSync(logPath)) {
|
||||||
|
return res.status(404).json({ error: 'Log not found' });
|
||||||
|
}
|
||||||
|
const logData = fs.readFileSync(logPath, 'utf8');
|
||||||
|
res.json({ log: logData });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: 'Failed to fetch log', details: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/training-projects', upload.single('project_image'), async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { title, description } = req.body;
|
||||||
|
const classes = JSON.parse(req.body.classes);
|
||||||
|
const project_image = req.file ? req.file.buffer : null;
|
||||||
|
const project_image_type = req.file ? req.file.mimetype : null;
|
||||||
|
await TrainingProject.create({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
classes,
|
||||||
|
project_image,
|
||||||
|
project_image_type
|
||||||
|
});
|
||||||
|
res.json({ message: 'Project created!' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating project:', error);
|
||||||
|
res.status(500).json({ message: 'Failed to create project', error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/training-projects', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const projects = await TrainingProject.findAll();
|
||||||
|
// Convert BLOB to base64 data URL for each project
|
||||||
|
const serialized = projects.map(project => {
|
||||||
|
const plain = project.get({ plain: true });
|
||||||
|
if (plain.project_image) {
|
||||||
|
const base64 = Buffer.from(plain.project_image).toString('base64');
|
||||||
|
const mimeType = plain.project_image_type || 'image/png';
|
||||||
|
plain.project_image = `data:${mimeType};base64,${base64}`;
|
||||||
|
}
|
||||||
|
return plain;
|
||||||
|
});
|
||||||
|
res.json(serialized);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: 'Failed to fetch projects', error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/update-status', async (req, res) => {
|
||||||
|
res.json(updateStatus)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/label-studio-projects', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const LabelStudioProject = require('../models/LabelStudioProject.js');
|
||||||
|
const Image = require('../models/Images.js');
|
||||||
|
const Annotation = require('../models/Annotation.js');
|
||||||
|
const labelStudioProjects = await LabelStudioProject.findAll();
|
||||||
|
const projectsWithCounts = await Promise.all(labelStudioProjects.map(async project => {
|
||||||
|
const plain = project.get({ plain: true });
|
||||||
|
// Get all images for this project
|
||||||
|
const images = await Image.findAll({ where: { project_id: plain.project_id } });
|
||||||
|
let annotationCounts = {};
|
||||||
|
if (images.length > 0) {
|
||||||
|
const imageIds = images.map(img => img.image_id);
|
||||||
|
// Get all annotations for these images
|
||||||
|
const annotations = await Annotation.findAll({ where: { image_id: imageIds } });
|
||||||
|
// Count by label
|
||||||
|
for (const ann of annotations) {
|
||||||
|
const label = ann.Label;
|
||||||
|
annotationCounts[label] = (annotationCounts[label] || 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
plain.annotationCounts = annotationCounts;
|
||||||
|
return plain;
|
||||||
|
}));
|
||||||
|
res.json(projectsWithCounts);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: 'Failed to fetch projects', error: error.message });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// POST endpoint to create TrainingProjectDetails with all fields
|
||||||
|
router.post('/training-project-details', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
project_id,
|
||||||
|
annotation_projects,
|
||||||
|
class_map,
|
||||||
|
description
|
||||||
|
} = req.body;
|
||||||
|
if (!project_id || !annotation_projects) {
|
||||||
|
return res.status(400).json({ message: 'Missing required fields' });
|
||||||
|
}
|
||||||
|
const TrainingProjectDetails = require('../models/TrainingProjectDetails.js');
|
||||||
|
const created = await TrainingProjectDetails.create({
|
||||||
|
project_id,
|
||||||
|
annotation_projects,
|
||||||
|
class_map: class_map || null,
|
||||||
|
description: description || null
|
||||||
|
});
|
||||||
|
res.json({ message: 'TrainingProjectDetails created', details: created });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: 'Failed to create TrainingProjectDetails', error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET endpoint to fetch all TrainingProjectDetails
|
||||||
|
router.get('/training-project-details', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const TrainingProjectDetails = require('../models/TrainingProjectDetails.js');
|
||||||
|
const details = await TrainingProjectDetails.findAll();
|
||||||
|
res.json(details);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: 'Failed to fetch TrainingProjectDetails', error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// PUT endpoint to update class_map and description in TrainingProjectDetails
|
||||||
|
router.put('/training-project-details', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { project_id, class_map, description } = req.body;
|
||||||
|
if (!project_id || !class_map || !description) {
|
||||||
|
return res.status(400).json({ message: 'Missing required fields' });
|
||||||
|
}
|
||||||
|
const TrainingProjectDetails = require('../models/TrainingProjectDetails.js');
|
||||||
|
const details = await TrainingProjectDetails.findOne({ where: { project_id } });
|
||||||
|
if (!details) {
|
||||||
|
return res.status(404).json({ message: 'TrainingProjectDetails not found' });
|
||||||
|
}
|
||||||
|
details.class_map = class_map;
|
||||||
|
details.description = description;
|
||||||
|
await details.save();
|
||||||
|
res.json({ message: 'Class map and description updated', details });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: 'Failed to update class map or description', error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST endpoint to receive YOLOX settings and save to DB (handles multipart/form-data)
|
||||||
|
router.post('/yolox-settings', upload.any(), async (req, res) => {
|
||||||
|
try {
|
||||||
|
const settings = req.body;
|
||||||
|
// Debug: Log all received fields and types
|
||||||
|
console.log('--- YOLOX settings received ---');
|
||||||
|
console.log('settings:', settings);
|
||||||
|
if (req.files && req.files.length > 0) {
|
||||||
|
console.log('Files received:', req.files.map(f => ({ fieldname: f.fieldname, originalname: f.originalname, size: f.size })));
|
||||||
|
}
|
||||||
|
// Declare requiredFields once
|
||||||
|
const requiredFields = ['project_details_id', 'exp_name', 'max_epoch', 'depth', 'width', 'activation', 'train', 'valid', 'test', 'selected_model', 'transfer_learning'];
|
||||||
|
// Log types of required fields
|
||||||
|
requiredFields.forEach(field => {
|
||||||
|
console.log(`Field '${field}': value='${settings[field]}', type='${typeof settings[field]}'`);
|
||||||
|
});
|
||||||
|
// Map select_model to selected_model if present
|
||||||
|
if (settings && settings.select_model && !settings.selected_model) {
|
||||||
|
settings.selected_model = settings.select_model;
|
||||||
|
delete settings.select_model;
|
||||||
|
}
|
||||||
|
// Lookup project_details_id from project_id
|
||||||
|
if (!settings.project_id || isNaN(Number(settings.project_id))) {
|
||||||
|
throw new Error('Missing or invalid project_id in request. Cannot assign training to a project.');
|
||||||
|
}
|
||||||
|
const TrainingProjectDetails = require('../models/TrainingProjectDetails.js');
|
||||||
|
let details = await TrainingProjectDetails.findOne({ where: { project_id: settings.project_id } });
|
||||||
|
if (!details) {
|
||||||
|
details = await TrainingProjectDetails.create({
|
||||||
|
project_id: settings.project_id,
|
||||||
|
annotation_projects: [],
|
||||||
|
class_map: null,
|
||||||
|
description: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
settings.project_details_id = details.id;
|
||||||
|
// Map 'act' from frontend to 'activation' for DB
|
||||||
|
if (settings.act !== undefined) {
|
||||||
|
settings.activation = settings.act;
|
||||||
|
delete settings.act;
|
||||||
|
}
|
||||||
|
// Type conversion for DB compatibility
|
||||||
|
[
|
||||||
|
'max_epoch', 'depth', 'width', 'warmup_epochs', 'warmup_lr', 'no_aug_epochs', 'min_lr_ratio', 'weight_decay', 'momentum', 'print_interval', 'eval_interval', 'test_conf', 'nmsthre', 'multiscale_range', 'degrees', 'translate', 'shear', 'train', 'valid', 'test'
|
||||||
|
].forEach(f => {
|
||||||
|
if (settings[f] !== undefined) settings[f] = Number(settings[f]);
|
||||||
|
});
|
||||||
|
// Improved boolean conversion
|
||||||
|
['ema', 'enable_mixup', 'save_history_ckpt'].forEach(f => {
|
||||||
|
if (settings[f] !== undefined) {
|
||||||
|
if (typeof settings[f] === 'string') {
|
||||||
|
settings[f] = settings[f].toLowerCase() === 'true';
|
||||||
|
} else {
|
||||||
|
settings[f] = Boolean(settings[f]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Improved array conversion
|
||||||
|
['mosaic_scale', 'mixup_scale', 'scale'].forEach(f => {
|
||||||
|
if (settings[f] && typeof settings[f] === 'string') {
|
||||||
|
settings[f] = settings[f]
|
||||||
|
.split(',')
|
||||||
|
.map(s => Number(s.trim()))
|
||||||
|
.filter(n => !isNaN(n));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Trim all string fields
|
||||||
|
Object.keys(settings).forEach(f => {
|
||||||
|
if (typeof settings[f] === 'string') settings[f] = settings[f].trim();
|
||||||
|
});
|
||||||
|
// Set default for transfer_learning if missing
|
||||||
|
if (settings.transfer_learning === undefined) settings.transfer_learning = false;
|
||||||
|
// Convert empty string seed to null
|
||||||
|
if ('seed' in settings && (settings.seed === '' || settings.seed === undefined)) {
|
||||||
|
settings.seed = null;
|
||||||
|
}
|
||||||
|
// Validate required fields for training table
|
||||||
|
for (const field of requiredFields) {
|
||||||
|
if (settings[field] === undefined || settings[field] === null || settings[field] === '') {
|
||||||
|
console.error('Missing required field:', field, 'Value:', settings[field]);
|
||||||
|
throw new Error('Missing required field: ' + field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('Received YOLOX settings:', settings);
|
||||||
|
// Handle uploaded model file (ckpt_upload)
|
||||||
|
if (req.files && req.files.length > 0) {
|
||||||
|
const ckptFile = req.files.find(f => f.fieldname === 'ckpt_upload');
|
||||||
|
if (ckptFile) {
|
||||||
|
const uploadDir = path.join(__dirname, '..', 'uploads');
|
||||||
|
if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir);
|
||||||
|
const filename = ckptFile.originalname || `uploaded_model_${settings.project_id}.pth`;
|
||||||
|
const filePath = path.join(uploadDir, filename);
|
||||||
|
fs.writeFileSync(filePath, ckptFile.buffer);
|
||||||
|
settings.model_upload = filePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Save settings to DB only (no file)
|
||||||
|
const { pushYoloxExpToDb } = require('../services/push-yolox-exp.js');
|
||||||
|
const training = await pushYoloxExpToDb(settings);
|
||||||
|
res.json({ message: 'YOLOX settings saved to DB', training });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in /api/yolox-settings:', error.stack || error);
|
||||||
|
res.status(500).json({ message: 'Failed to save YOLOX settings', error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST endpoint to receive binary model file and save to disk (not DB)
|
||||||
|
router.post('/yolox-settings/upload', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const projectId = req.query.project_id;
|
||||||
|
if (!projectId) return res.status(400).json({ message: 'Missing project_id in query' });
|
||||||
|
// Save file to disk
|
||||||
|
const uploadDir = path.join(__dirname, '..', 'uploads');
|
||||||
|
if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir);
|
||||||
|
const filename = req.headers['x-upload-filename'] || `uploaded_model_${projectId}.pth`;
|
||||||
|
const filePath = path.join(uploadDir, filename);
|
||||||
|
const chunks = [];
|
||||||
|
req.on('data', chunk => chunks.push(chunk));
|
||||||
|
req.on('end', async () => {
|
||||||
|
const buffer = Buffer.concat(chunks);
|
||||||
|
fs.writeFile(filePath, buffer, async err => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error saving file:', err);
|
||||||
|
return res.status(500).json({ message: 'Failed to save model file', error: err.message });
|
||||||
|
}
|
||||||
|
// Update latest training row for this project with file path
|
||||||
|
try {
|
||||||
|
const TrainingProjectDetails = require('../models/TrainingProjectDetails.js');
|
||||||
|
const Training = require('../models/training.js');
|
||||||
|
// Find details row for this project
|
||||||
|
const details = await TrainingProjectDetails.findOne({ where: { project_id: projectId } });
|
||||||
|
if (!details) return res.status(404).json({ message: 'No TrainingProjectDetails found for project_id' });
|
||||||
|
// Find latest training for this details row
|
||||||
|
const training = await Training.findOne({ where: { project_details_id: details.id }, order: [['createdAt', 'DESC']] });
|
||||||
|
if (!training) return res.status(404).json({ message: 'No training found for project_id' });
|
||||||
|
// Save file path to model_upload field
|
||||||
|
training.model_upload = filePath;
|
||||||
|
await training.save();
|
||||||
|
res.json({ message: 'Model file uploaded and saved to disk', filename, trainingId: training.id });
|
||||||
|
} catch (dbErr) {
|
||||||
|
console.error('Error updating training with file path:', dbErr);
|
||||||
|
res.status(500).json({ message: 'File saved but failed to update training row', error: dbErr.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in /api/yolox-settings/upload:', error.stack || error);
|
||||||
|
res.status(500).json({ message: 'Failed to upload model file', error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET endpoint to fetch all trainings (optionally filtered by project_id)
|
||||||
|
router.get('/trainings', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const project_id = req.query.project_id;
|
||||||
|
const TrainingProjectDetails = require('../models/TrainingProjectDetails.js');
|
||||||
|
const Training = require('../models/training.js');
|
||||||
|
if (project_id) {
|
||||||
|
// Find all details rows for this project
|
||||||
|
const detailsRows = await TrainingProjectDetails.findAll({ where: { project_id } });
|
||||||
|
if (!detailsRows || detailsRows.length === 0) return res.json([]);
|
||||||
|
// Get all trainings linked to any details row for this project
|
||||||
|
const detailsIds = detailsRows.map(d => d.id);
|
||||||
|
const trainings = await Training.findAll({ where: { project_details_id: detailsIds } });
|
||||||
|
return res.json(trainings);
|
||||||
|
} else {
|
||||||
|
// Return all trainings if no project_id is specified
|
||||||
|
const trainings = await Training.findAll();
|
||||||
|
return res.json(trainings);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: 'Failed to fetch trainings', error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// DELETE endpoint to remove a training by id
|
||||||
|
router.delete('/trainings/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const Training = require('../models/training.js');
|
||||||
|
const id = req.params.id;
|
||||||
|
const deleted = await Training.destroy({ where: { id } });
|
||||||
|
if (deleted) {
|
||||||
|
res.json({ message: 'Training deleted' });
|
||||||
|
} else {
|
||||||
|
res.status(404).json({ message: 'Training not found' });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: 'Failed to delete training', error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// DELETE endpoint to remove a training project and all related entries
|
||||||
|
router.delete('/training-projects/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const projectId = req.params.id;
|
||||||
|
const TrainingProject = require('../models/TrainingProject.js');
|
||||||
|
const TrainingProjectDetails = require('../models/TrainingProjectDetails.js');
|
||||||
|
const Training = require('../models/training.js');
|
||||||
|
// Find details row(s) for this project
|
||||||
|
const detailsRows = await TrainingProjectDetails.findAll({ where: { project_id: projectId } });
|
||||||
|
const detailsIds = detailsRows.map(d => d.id);
|
||||||
|
// Delete all trainings linked to these details
|
||||||
|
if (detailsIds.length > 0) {
|
||||||
|
await Training.destroy({ where: { project_details_id: detailsIds } });
|
||||||
|
await TrainingProjectDetails.destroy({ where: { project_id: projectId } });
|
||||||
|
}
|
||||||
|
// Delete the project itself
|
||||||
|
const deleted = await TrainingProject.destroy({ where: { project_id: projectId } });
|
||||||
|
if (deleted) {
|
||||||
|
res.json({ message: 'Training project and all related entries deleted' });
|
||||||
|
} else {
|
||||||
|
res.status(404).json({ message: 'Training project not found' });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: 'Failed to delete training project', error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
34
backend/server.js
Normal file
34
backend/server.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const cors = require('cors');
|
||||||
|
const path = require('path');
|
||||||
|
const sequelize = require('./database/database');
|
||||||
|
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
app.use(express.json());
|
||||||
|
const port = 3000;
|
||||||
|
|
||||||
|
const apiRouter = require('./routes/api.js');
|
||||||
|
app.use('/api', apiRouter);
|
||||||
|
|
||||||
|
|
||||||
|
app.use(cors());
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(express.static(path.join(__dirname, '..')));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Initialize DB and start server
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
await sequelize.authenticate();
|
||||||
|
console.log('DB connection established.');
|
||||||
|
await sequelize.sync(); // Only if you want Sequelize to ensure schema matches
|
||||||
|
|
||||||
|
app.listen(port, '0.0.0.0', () =>
|
||||||
|
console.log(`Server running at http://0.0.0.0:${port}`)
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to start:', err);
|
||||||
|
}
|
||||||
|
})();
|
||||||
92
backend/services/fetch-labelstudio.js
Normal file
92
backend/services/fetch-labelstudio.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
const API_URL = 'http://192.168.1.19:8080/api';
|
||||||
|
const API_TOKEN = 'c1cef980b7c73004f4ee880a42839313b863869f';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
|
async function fetchLableStudioProject(projectid) {
|
||||||
|
// 1. Trigger export
|
||||||
|
const exportUrl = `${API_URL}/projects/${projectid}/export?exportType=JSON_MIN`;
|
||||||
|
const headers = { Authorization: `Token ${API_TOKEN}` };
|
||||||
|
let res = await fetch(exportUrl, { headers });
|
||||||
|
if (!res.ok) {
|
||||||
|
let errorText = await res.text().catch(() => '');
|
||||||
|
console.error(`Failed to trigger export: ${res.status} ${res.statusText} - ${errorText}`);
|
||||||
|
throw new Error(`Failed to trigger export: ${res.status} ${res.statusText}`);
|
||||||
|
}
|
||||||
|
let data = await res.json();
|
||||||
|
// If data is an array, it's ready
|
||||||
|
if (Array.isArray(data)) return data;
|
||||||
|
// If not, poll for the export file
|
||||||
|
let fileUrl = data.download_url || data.url || null;
|
||||||
|
let tries = 0;
|
||||||
|
while (!fileUrl && tries < 20) {
|
||||||
|
await new Promise(r => setTimeout(r, 2000));
|
||||||
|
res = await fetch(exportUrl, { headers });
|
||||||
|
if (!res.ok) {
|
||||||
|
let errorText = await res.text().catch(() => '');
|
||||||
|
console.error(`Failed to poll export: ${res.status} ${res.statusText} - ${errorText}`);
|
||||||
|
throw new Error(`Failed to poll export: ${res.status} ${res.statusText}`);
|
||||||
|
}
|
||||||
|
data = await res.json();
|
||||||
|
fileUrl = data.download_url || data.url || null;
|
||||||
|
tries++;
|
||||||
|
}
|
||||||
|
if (!fileUrl) throw new Error('Label Studio export did not become ready');
|
||||||
|
// 2. Download the export file
|
||||||
|
res = await fetch(fileUrl.startsWith('http') ? fileUrl : `${API_URL.replace('/api','')}${fileUrl}`, { headers });
|
||||||
|
if (!res.ok) {
|
||||||
|
let errorText = await res.text().catch(() => '');
|
||||||
|
console.error(`Failed to download export: ${res.status} ${res.statusText} - ${errorText}`);
|
||||||
|
throw new Error(`Failed to download export: ${res.status} ${res.statusText}`);
|
||||||
|
}
|
||||||
|
return await res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async function fetchProjectIdsAndTitles() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_URL}/projects/`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Token ${API_TOKEN}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
let errorText = await response.text().catch(() => '');
|
||||||
|
console.error(`Failed to fetch projects: ${response.status} ${response.statusText} - ${errorText}`);
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!data.results || !Array.isArray(data.results)) {
|
||||||
|
throw new Error('API response does not contain results array');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract id and title from each project
|
||||||
|
const projects = data.results.map(project => ({
|
||||||
|
id: project.id,
|
||||||
|
title: project.title
|
||||||
|
}));
|
||||||
|
console.log(projects)
|
||||||
|
return projects;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch projects:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { fetchLableStudioProject, fetchProjectIdsAndTitles };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//getLableStudioProject(20)
|
||||||
|
//fetchProjectIdsAndTitles()
|
||||||
176
backend/services/generate-json-yolox.js
Normal file
176
backend/services/generate-json-yolox.js
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
const TrainingProject = require('../models/TrainingProject.js');
|
||||||
|
const TrainingProjectDetails = require('../models/TrainingProjectDetails.js')
|
||||||
|
const LabelStudioProject = require('../models/LabelStudioProject.js')
|
||||||
|
const Annotation = require('../models/Annotation.js')
|
||||||
|
const Images = require('../models/Images.js')
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
|
||||||
|
async function generateTrainingJson(trainingId){
|
||||||
|
// trainingId is now project_details_id
|
||||||
|
const trainingProjectDetails = await TrainingProjectDetails.findByPk(trainingId);
|
||||||
|
if (!trainingProjectDetails) throw new Error('No TrainingProjectDetails found for project_details_id ' + trainingId);
|
||||||
|
const detailsObj = trainingProjectDetails.get({ plain: true });
|
||||||
|
// Get parent project for name
|
||||||
|
const trainingProject = await TrainingProject.findByPk(detailsObj.project_id);
|
||||||
|
// Get split percentages (assume they are stored as train_percent, valid_percent, test_percent)
|
||||||
|
const trainPercent = detailsObj.train_percent || 85;
|
||||||
|
const validPercent = detailsObj.valid_percent || 10;
|
||||||
|
const testPercent = detailsObj.test_percent || 5;
|
||||||
|
|
||||||
|
let cocoImages = [];
|
||||||
|
let cocoAnnotations = [];
|
||||||
|
let cocoCategories = [];
|
||||||
|
let categoryMap = {};
|
||||||
|
let categoryId = 0;
|
||||||
|
let imageid = 0;
|
||||||
|
let annotationid = 0;
|
||||||
|
|
||||||
|
for (const cls of detailsObj.class_map) {
|
||||||
|
const asgMap = [];
|
||||||
|
const listAsg = cls[1];
|
||||||
|
for(const asg of listAsg){
|
||||||
|
asgMap.push ({ original: asg[0], mapped: asg[1] });
|
||||||
|
// Build category list and mapping
|
||||||
|
if (asg[1] && !(asg[1] in categoryMap)) {
|
||||||
|
categoryMap[asg[1]] = categoryId;
|
||||||
|
cocoCategories.push({ id: categoryId, name: asg[1], supercategory: '' });
|
||||||
|
categoryId++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const images = await Images.findAll({ where: { project_id: cls[0] } });
|
||||||
|
for(const image of images){
|
||||||
|
imageid += 1;
|
||||||
|
let fileName = image.image_path;
|
||||||
|
if (fileName.includes('%20')) {
|
||||||
|
fileName = fileName.replace(/%20/g, ' ');
|
||||||
|
}
|
||||||
|
if (fileName && fileName.startsWith('/data/local-files/?d=')) {
|
||||||
|
fileName = fileName.replace('/data/local-files/?d=', '');
|
||||||
|
fileName = fileName.replace('/home/kitraining/home/kitraining/', '');
|
||||||
|
}
|
||||||
|
if (fileName && fileName.startsWith('home/kitraining/To_Annotate/')) {
|
||||||
|
fileName = fileName.replace('home/kitraining/To_Annotate/','');
|
||||||
|
}
|
||||||
|
// Get annotations for this image
|
||||||
|
const annotations = await Annotation.findAll({ where: { image_id: image.image_id } });
|
||||||
|
// Use image.width and image.height from DB (populated from original_width/original_height)
|
||||||
|
cocoImages.push({
|
||||||
|
id: imageid,
|
||||||
|
file_name: fileName,
|
||||||
|
width: image.width || 0,
|
||||||
|
height: image.height || 0
|
||||||
|
});
|
||||||
|
for (const annotation of annotations) {
|
||||||
|
// Translate class name using asgMap
|
||||||
|
let mappedClass = annotation.Label;
|
||||||
|
for (const mapEntry of asgMap) {
|
||||||
|
if (annotation.Label === mapEntry.original) {
|
||||||
|
mappedClass = mapEntry.mapped;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Only add annotation if mappedClass is valid
|
||||||
|
if (mappedClass && mappedClass in categoryMap) {
|
||||||
|
annotationid += 1;
|
||||||
|
let area = 0;
|
||||||
|
if (annotation.width && annotation.height) {
|
||||||
|
area = annotation.width * annotation.height;
|
||||||
|
}
|
||||||
|
cocoAnnotations.push({
|
||||||
|
id: annotationid,
|
||||||
|
image_id: imageid,
|
||||||
|
category_id: categoryMap[mappedClass],
|
||||||
|
bbox: [annotation.x, annotation.y, annotation.width, annotation.height],
|
||||||
|
area: area,
|
||||||
|
iscrowd: annotation.iscrowd || 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shuffle images for random split using seed
|
||||||
|
function seededRandom(seed) {
|
||||||
|
let x = Math.sin(seed++) * 10000;
|
||||||
|
return x - Math.floor(x);
|
||||||
|
}
|
||||||
|
function shuffle(array, seed) {
|
||||||
|
for (let i = array.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(seededRandom(seed + i) * (i + 1));
|
||||||
|
[array[i], array[j]] = [array[j], array[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Use seed from detailsObj if present, else default to 42
|
||||||
|
const splitSeed = detailsObj.seed !== undefined && detailsObj.seed !== null ? Number(detailsObj.seed) : 42;
|
||||||
|
shuffle(cocoImages, splitSeed);
|
||||||
|
|
||||||
|
// Split images
|
||||||
|
const totalImages = cocoImages.length;
|
||||||
|
const trainCount = Math.floor(totalImages * trainPercent / 100);
|
||||||
|
const validCount = Math.floor(totalImages * validPercent / 100);
|
||||||
|
const testCount = totalImages - trainCount - validCount;
|
||||||
|
|
||||||
|
const trainImages = cocoImages.slice(0, trainCount);
|
||||||
|
const validImages = cocoImages.slice(trainCount, trainCount + validCount);
|
||||||
|
const testImages = cocoImages.slice(trainCount + validCount);
|
||||||
|
|
||||||
|
// Helper to get image ids for each split
|
||||||
|
const trainImageIds = new Set(trainImages.map(img => img.id));
|
||||||
|
const validImageIds = new Set(validImages.map(img => img.id));
|
||||||
|
const testImageIds = new Set(testImages.map(img => img.id));
|
||||||
|
|
||||||
|
// Split annotations
|
||||||
|
const trainAnnotations = cocoAnnotations.filter(ann => trainImageIds.has(ann.image_id));
|
||||||
|
const validAnnotations = cocoAnnotations.filter(ann => validImageIds.has(ann.image_id));
|
||||||
|
const testAnnotations = cocoAnnotations.filter(ann => testImageIds.has(ann.image_id));
|
||||||
|
|
||||||
|
// Build final COCO JSONs with info section
|
||||||
|
const buildCocoJson = (images, annotations, categories) => ({
|
||||||
|
images,
|
||||||
|
annotations,
|
||||||
|
categories
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build COCO JSONs with info section
|
||||||
|
const trainJson = buildCocoJson(trainImages, trainAnnotations, cocoCategories);
|
||||||
|
const validJson = buildCocoJson(validImages, validAnnotations, cocoCategories);
|
||||||
|
const testJson = buildCocoJson(testImages, testAnnotations, cocoCategories);
|
||||||
|
|
||||||
|
// Create output directory: projectname/trainingid/annotations
|
||||||
|
const projectName = trainingProject && trainingProject.name ? trainingProject.name.replace(/\s+/g, '_') : `project_${detailsObj.project_id}`;
|
||||||
|
const outDir = `${projectName}/${trainingId}`;
|
||||||
|
const annotationsDir = `/home/kitraining/To_Annotate/annotations`;
|
||||||
|
if (!fs.existsSync(annotationsDir)) {
|
||||||
|
fs.mkdirSync(annotationsDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to files in the annotations directory
|
||||||
|
const trainPath = `${annotationsDir}/coco_project_${trainingId}_train.json`;
|
||||||
|
const validPath = `${annotationsDir}/coco_project_${trainingId}_valid.json`;
|
||||||
|
const testPath = `${annotationsDir}/coco_project_${trainingId}_test.json`;
|
||||||
|
fs.writeFileSync(trainPath, JSON.stringify(trainJson, null, 2));
|
||||||
|
fs.writeFileSync(validPath, JSON.stringify(validJson, null, 2));
|
||||||
|
fs.writeFileSync(testPath, JSON.stringify(testJson, null, 2));
|
||||||
|
console.log(`COCO JSON splits written to ${annotationsDir} for trainingId ${trainingId}`);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Also generate inference exp.py in the same output directory as exp.py (project folder in workspace)
|
||||||
|
const { generateYoloxInferenceExp } = require('./generate-yolox-exp');
|
||||||
|
const path = require('path');
|
||||||
|
const projectFolder = path.join(__dirname, '..', projectName, String(trainingId));
|
||||||
|
if (!fs.existsSync(projectFolder)) {
|
||||||
|
fs.mkdirSync(projectFolder, { recursive: true });
|
||||||
|
}
|
||||||
|
const inferenceExpPath = path.join(projectFolder, 'exp_infer.py');
|
||||||
|
generateYoloxInferenceExp(trainingId).then(expContent => {
|
||||||
|
fs.writeFileSync(inferenceExpPath, expContent);
|
||||||
|
console.log(`Inference exp.py written to ${inferenceExpPath}`);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('Failed to generate inference exp.py:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {generateTrainingJson};
|
||||||
135
backend/services/generate-yolox-exp.js
Normal file
135
backend/services/generate-yolox-exp.js
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const Training = require('../models/training.js');
|
||||||
|
const TrainingProject = require('../models/TrainingProject.js');
|
||||||
|
|
||||||
|
// Remove Python comments and legacy code
|
||||||
|
const exp_names = [
|
||||||
|
'YOLOX-s',
|
||||||
|
'YOLOX-m',
|
||||||
|
'YOLOX-l',
|
||||||
|
'YOLOX-x',
|
||||||
|
'YOLOX-Darknet53', //todo
|
||||||
|
'YOLOX-Nano',
|
||||||
|
'YOLOX-Tiny'
|
||||||
|
]
|
||||||
|
|
||||||
|
//TODO: Clean up generation of exp_names.py and remove second exp creation!!!
|
||||||
|
|
||||||
|
|
||||||
|
// Refactored: Accept trainingId, fetch info from DB
|
||||||
|
async function generateYoloxExp(trainingId) {
|
||||||
|
// Fetch training row from DB by project_details_id if not found by PK
|
||||||
|
let training = await Training.findByPk(trainingId);
|
||||||
|
if (!training) {
|
||||||
|
training = await Training.findOne({ where: { project_details_id: trainingId } });
|
||||||
|
}
|
||||||
|
if (!training) throw new Error('Training not found for trainingId or project_details_id: ' + trainingId);
|
||||||
|
|
||||||
|
// If transfer_learning is 'coco', just return the path to the default exp.py
|
||||||
|
if (training.transfer_learning === 'coco') {
|
||||||
|
const selectedModel = training.selected_model.toLowerCase().replace('-', '_');
|
||||||
|
const expSourcePath = `/home/kitraining/Yolox/YOLOX-main/exps/default/${selectedModel}.py`;
|
||||||
|
if (!fs.existsSync(expSourcePath)) {
|
||||||
|
throw new Error(`Default exp.py not found for model: ${selectedModel} at ${expSourcePath}`);
|
||||||
|
}
|
||||||
|
// Copy to project folder (e.g., /home/kitraining/coco_tool/backend/project_XX/YY/exp.py)
|
||||||
|
const projectDetailsId = training.project_details_id;
|
||||||
|
const projectFolder = path.resolve(__dirname, `../project_23/${projectDetailsId}`);
|
||||||
|
if (!fs.existsSync(projectFolder)) {
|
||||||
|
fs.mkdirSync(projectFolder, { recursive: true });
|
||||||
|
}
|
||||||
|
const expDestPath = path.join(projectFolder, 'exp.py');
|
||||||
|
fs.copyFileSync(expSourcePath, expDestPath);
|
||||||
|
return { type: 'default', expPath: expDestPath };
|
||||||
|
}
|
||||||
|
|
||||||
|
// If transfer_learning is 'sketch', generate a custom exp.py as before
|
||||||
|
if (training.transfer_learning === 'sketch') {
|
||||||
|
// ...existing custom exp.py generation logic here (copy from previous implementation)...
|
||||||
|
// For brevity, you can call generateYoloxInferenceExp or similar here, or inline the logic.
|
||||||
|
// Example:
|
||||||
|
const expContent = await generateYoloxInferenceExp(trainingId);
|
||||||
|
return { type: 'custom', expContent };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Unknown transfer_learning type: ' + training.transfer_learning);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveYoloxExp(trainingId, outPath) {
|
||||||
|
const expResult = await generateYoloxExp(trainingId);
|
||||||
|
if (expResult.type === 'custom' && expResult.expContent) {
|
||||||
|
fs.writeFileSync(outPath, expResult.expContent);
|
||||||
|
return outPath;
|
||||||
|
} else if (expResult.type === 'default' && expResult.expPath) {
|
||||||
|
// Optionally copy the file if outPath is different
|
||||||
|
if (expResult.expPath !== outPath) {
|
||||||
|
fs.copyFileSync(expResult.expPath, outPath);
|
||||||
|
}
|
||||||
|
return outPath;
|
||||||
|
} else {
|
||||||
|
throw new Error('Unknown expResult type or missing content');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a second exp.py for inference, using the provided template and DB values
|
||||||
|
async function generateYoloxInferenceExp(trainingId, options = {}) {
|
||||||
|
let training = await Training.findByPk(trainingId);
|
||||||
|
if (!training) {
|
||||||
|
training = await Training.findOne({ where: { project_details_id: trainingId } });
|
||||||
|
}
|
||||||
|
if (!training) throw new Error('Training not found for trainingId or project_details_id: ' + trainingId);
|
||||||
|
// Always use the trainingId (project_details_id) for annotation file names
|
||||||
|
const projectDetailsId = training.project_details_id;
|
||||||
|
const dataDir = options.data_dir || '/home/kitraining/To_Annotate/';
|
||||||
|
const trainAnn = options.train_ann || `coco_project_${trainingId}_train.json`;
|
||||||
|
const valAnn = options.val_ann || `coco_project_${trainingId}_valid.json`;
|
||||||
|
const testAnn = options.test_ann || `coco_project_${trainingId}_test.json`;
|
||||||
|
// Get num_classes from TrainingProject.classes JSON
|
||||||
|
let numClasses = 80;
|
||||||
|
try {
|
||||||
|
const trainingProject = await TrainingProject.findByPk(projectDetailsId);
|
||||||
|
if (trainingProject && trainingProject.classes) {
|
||||||
|
let classesArr = trainingProject.classes;
|
||||||
|
if (typeof classesArr === 'string') {
|
||||||
|
classesArr = JSON.parse(classesArr);
|
||||||
|
}
|
||||||
|
if (Array.isArray(classesArr)) {
|
||||||
|
numClasses = classesArr.filter(c => c !== null && c !== undefined && c !== '').length;
|
||||||
|
} else if (typeof classesArr === 'object' && classesArr !== null) {
|
||||||
|
numClasses = Object.keys(classesArr).filter(k => classesArr[k] !== null && classesArr[k] !== undefined && classesArr[k] !== '').length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not determine num_classes from TrainingProject.classes:', e);
|
||||||
|
}
|
||||||
|
const depth = options.depth || training.depth || 1.00;
|
||||||
|
const width = options.width || training.width || 1.00;
|
||||||
|
const inputSize = options.input_size || training.input_size || [640, 640];
|
||||||
|
const mosaicScale = options.mosaic_scale || training.mosaic_scale || [0.1, 2];
|
||||||
|
const randomSize = options.random_size || training.random_size || [10, 20];
|
||||||
|
const testSize = options.test_size || training.test_size || [640, 640];
|
||||||
|
const expName = options.exp_name || 'inference_exp';
|
||||||
|
const enableMixup = options.enable_mixup !== undefined ? options.enable_mixup : false;
|
||||||
|
let expContent = '';
|
||||||
|
expContent += `#!/usr/bin/env python3\n# -*- coding:utf-8 -*-\n# Copyright (c) Megvii, Inc. and its affiliates.\n\nimport os\n\nfrom yolox.exp import Exp as MyExp\n\n\nclass Exp(MyExp):\n def __init__(self):\n super(Exp, self).__init__()\n self.data_dir = "${dataDir}"\n self.train_ann = "${trainAnn}"\n self.val_ann = "${valAnn}"\n self.test_ann = "coco_project_${trainingId}_test.json"\n self.num_classes = ${numClasses}\n`;
|
||||||
|
// Set pretrained_ckpt if transfer_learning is 'coco'
|
||||||
|
if (training.transfer_learning && typeof training.transfer_learning === 'string' && training.transfer_learning.toLowerCase() === 'coco') {
|
||||||
|
const yoloxBaseDir = '/home/kitraining/Yolox/YOLOX-main';
|
||||||
|
const selectedModel = training.selected_model ? training.selected_model.replace(/\.pth$/i, '') : '';
|
||||||
|
if (selectedModel) {
|
||||||
|
expContent += ` self.pretrained_ckpt = r'${yoloxBaseDir}/pretrained/${selectedModel}.pth'\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expContent += ` self.depth = ${depth}\n self.width = ${width}\n self.input_size = (${Array.isArray(inputSize) ? inputSize.join(', ') : inputSize})\n self.mosaic_scale = (${Array.isArray(mosaicScale) ? mosaicScale.join(', ') : mosaicScale})\n self.random_size = (${Array.isArray(randomSize) ? randomSize.join(', ') : randomSize})\n self.test_size = (${Array.isArray(testSize) ? testSize.join(', ') : testSize})\n self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]\n self.enable_mixup = ${enableMixup ? 'True' : 'False'}\n`;
|
||||||
|
return expContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save inference exp.py to a custom path
|
||||||
|
async function saveYoloxInferenceExp(trainingId, outPath, options = {}) {
|
||||||
|
const expContent = await generateYoloxInferenceExp(trainingId, options);
|
||||||
|
fs.writeFileSync(outPath, expContent);
|
||||||
|
return outPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { generateYoloxExp, saveYoloxExp, generateYoloxInferenceExp, saveYoloxInferenceExp };
|
||||||
48
backend/services/push-yolox-exp.js
Normal file
48
backend/services/push-yolox-exp.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
const Training = require('../models/training.js');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
async function pushYoloxExpToDb(settings) {
|
||||||
|
// Normalize boolean and array fields for DB
|
||||||
|
const normalized = { ...settings };
|
||||||
|
// Map 'act' from frontend to 'activation' for DB
|
||||||
|
if (normalized.act !== undefined) {
|
||||||
|
normalized.activation = normalized.act;
|
||||||
|
delete normalized.act;
|
||||||
|
}
|
||||||
|
// Convert 'on'/'off' to boolean for save_history_ckpt
|
||||||
|
if (typeof normalized.save_history_ckpt === 'string') {
|
||||||
|
normalized.save_history_ckpt = normalized.save_history_ckpt === 'on' ? true : false;
|
||||||
|
}
|
||||||
|
// Convert comma-separated strings to arrays for input_size, test_size, mosaic_scale, mixup_scale
|
||||||
|
['input_size', 'test_size', 'mosaic_scale', 'mixup_scale'].forEach(key => {
|
||||||
|
if (typeof normalized[key] === 'string') {
|
||||||
|
const arr = normalized[key].split(',').map(v => parseFloat(v.trim()));
|
||||||
|
normalized[key] = arr.length === 1 ? arr[0] : arr;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Find TrainingProjectDetails for this project
|
||||||
|
const TrainingProjectDetails = require('../models/TrainingProjectDetails.js');
|
||||||
|
const details = await TrainingProjectDetails.findOne({ where: { project_id: normalized.project_id } });
|
||||||
|
if (!details) throw new Error('TrainingProjectDetails not found for project_id ' + normalized.project_id);
|
||||||
|
normalized.project_details_id = details.id;
|
||||||
|
// Create DB row
|
||||||
|
const training = await Training.create(normalized);
|
||||||
|
return training;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateYoloxExpFromDb(trainingId) {
|
||||||
|
// Fetch training row from DB
|
||||||
|
const training = await Training.findByPk(trainingId);
|
||||||
|
if (!training) throw new Error('Training not found');
|
||||||
|
// Template for exp.py
|
||||||
|
const expTemplate = `#!/usr/bin/env python3\n# Copyright (c) Megvii Inc. All rights reserved.\n\nimport os\nimport random\n\nimport torch\nimport torch.distributed as dist\nimport torch.nn as nn\n\nfrom .base_exp import BaseExp\n\n__all__ = [\"Exp\", \"check_exp_value\"]\n\nclass Exp(BaseExp):\n def __init__(self):\n super().__init__()\n\n # ---------------- model config ---------------- #\n self.num_classes = ${training.num_classes || 80}\n self.depth = ${training.depth || 1.00}\n self.width = ${training.width || 1.00}\n self.act = \"${training.activation || training.act || 'silu'}\"\n\n # ---------------- dataloader config ---------------- #\n self.data_num_workers = ${training.data_num_workers || 4}\n self.input_size = (${Array.isArray(training.input_size) ? training.input_size.join(', ') : '640, 640'})\n self.multiscale_range = ${training.multiscale_range || 5}\n self.data_dir = ${training.data_dir ? `\"${training.data_dir}\"` : 'None'}\n self.train_ann = \"${training.train_ann || 'instances_train2017.json'}\"\n self.val_ann = \"${training.val_ann || 'instances_val2017.json'}\"\n self.test_ann = \"${training.test_ann || 'instances_test2017.json'}\"\n\n # --------------- transform config ----------------- #\n self.mosaic_prob = ${training.mosaic_prob !== undefined ? training.mosaic_prob : 1.0}\n self.mixup_prob = ${training.mixup_prob !== undefined ? training.mixup_prob : 1.0}\n self.hsv_prob = ${training.hsv_prob !== undefined ? training.hsv_prob : 1.0}\n self.flip_prob = ${training.flip_prob !== undefined ? training.flip_prob : 0.5}\n self.degrees = ${training.degrees !== undefined ? training.degrees : 10.0}\n self.translate = ${training.translate !== undefined ? training.translate : 0.1}\n self.mosaic_scale = (${Array.isArray(training.mosaic_scale) ? training.mosaic_scale.join(', ') : '0.1, 2'})\n self.enable_mixup = ${training.enable_mixup !== undefined ? training.enable_mixup : true}\n self.mixup_scale = (${Array.isArray(training.mixup_scale) ? training.mixup_scale.join(', ') : '0.5, 1.5'})\n self.shear = ${training.shear !== undefined ? training.shear : 2.0}\n\n # -------------- training config --------------------- #\n self.warmup_epochs = ${training.warmup_epochs !== undefined ? training.warmup_epochs : 5}\n self.max_epoch = ${training.max_epoch !== undefined ? training.max_epoch : 300}\n self.warmup_lr = ${training.warmup_lr !== undefined ? training.warmup_lr : 0}\n self.min_lr_ratio = ${training.min_lr_ratio !== undefined ? training.min_lr_ratio : 0.05}\n self.basic_lr_per_img = ${training.basic_lr_per_img !== undefined ? training.basic_lr_per_img : 0.01 / 64.0}\n self.scheduler = \"${training.scheduler || 'yoloxwarmcos'}\"\n self.no_aug_epochs = ${training.no_aug_epochs !== undefined ? training.no_aug_epochs : 15}\n self.ema = ${training.ema !== undefined ? training.ema : true}\n self.weight_decay = ${training.weight_decay !== undefined ? training.weight_decay : 5e-4}\n self.momentum = ${training.momentum !== undefined ? training.momentum : 0.9}\n self.print_interval = ${training.print_interval !== undefined ? training.print_interval : 10}\n self.eval_interval = ${training.eval_interval !== undefined ? training.eval_interval : 10}\n self.save_history_ckpt = ${training.save_history_ckpt !== undefined ? training.save_history_ckpt : true}\n self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(\".\")[0]\n\n # ----------------- testing config ------------------ #\n self.test_size = (${Array.isArray(training.test_size) ? training.test_size.join(', ') : '640, 640'})\n self.test_conf = ${training.test_conf !== undefined ? training.test_conf : 0.01}\n self.nmsthre = ${training.nmsthre !== undefined ? training.nmsthre : 0.65}\n\n # ... rest of the template ...\n\ndef check_exp_value(exp: Exp):\n h, w = exp.input_size\n assert h % 32 == 0 and w % 32 == 0, \"input size must be multiples of 32\"\n`;
|
||||||
|
// Save to file in output directory
|
||||||
|
const outDir = path.join(__dirname, '../../', training.project_id ? `project_${training.project_id}/${trainingId}` : 'exp_files');
|
||||||
|
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
||||||
|
const filePath = path.join(outDir, 'exp.py');
|
||||||
|
fs.writeFileSync(filePath, expTemplate);
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { pushYoloxExpToDb, generateYoloxExpFromDb };
|
||||||
120
backend/services/seed-label-studio.js
Normal file
120
backend/services/seed-label-studio.js
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
const sequelize = require('../database/database.js');
|
||||||
|
const { Project, Img, Ann } = require('../models');
|
||||||
|
const { fetchLableStudioProject, fetchProjectIdsAndTitles } = require('./fetch-labelstudio.js');
|
||||||
|
|
||||||
|
const updateStatus = { running: false };
|
||||||
|
|
||||||
|
async function seedLabelStudio() {
|
||||||
|
updateStatus.running = true;
|
||||||
|
console.log('Seeding started');
|
||||||
|
try {
|
||||||
|
await sequelize.sync();
|
||||||
|
const projects = await fetchProjectIdsAndTitles();
|
||||||
|
|
||||||
|
for (const project of projects) {
|
||||||
|
console.log(`Processing project ${project.id} (${project.title})`);
|
||||||
|
|
||||||
|
// Upsert project in DB
|
||||||
|
await Project.upsert({ project_id: project.id, title: project.title });
|
||||||
|
|
||||||
|
// Fetch project data (annotations array)
|
||||||
|
const data = await fetchLableStudioProject(project.id);
|
||||||
|
if (!Array.isArray(data) || data.length === 0) {
|
||||||
|
console.log(`No annotation data for project ${project.id}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove old images and annotations for this project
|
||||||
|
const oldImages = await Img.findAll({ where: { project_id: project.id } });
|
||||||
|
const oldImageIds = oldImages.map(img => img.image_id);
|
||||||
|
if (oldImageIds.length > 0) {
|
||||||
|
await Ann.destroy({ where: { image_id: oldImageIds } });
|
||||||
|
await Img.destroy({ where: { project_id: project.id } });
|
||||||
|
console.log(`Deleted ${oldImageIds.length} old images and their annotations for project ${project.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare arrays
|
||||||
|
const imagesBulk = [];
|
||||||
|
const annsBulk = [];
|
||||||
|
|
||||||
|
for (const ann of data) {
|
||||||
|
// Extract width/height
|
||||||
|
let width = null;
|
||||||
|
let height = null;
|
||||||
|
if (Array.isArray(ann.label_rectangles) && ann.label_rectangles.length > 0) {
|
||||||
|
width = ann.label_rectangles[0].original_width;
|
||||||
|
height = ann.label_rectangles[0].original_height;
|
||||||
|
} else if (Array.isArray(ann.label) && ann.label.length > 0 && ann.label[0].original_width && ann.label[0].original_height) {
|
||||||
|
width = ann.label[0].original_width;
|
||||||
|
height = ann.label[0].original_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only push image and annotations if width and height are valid
|
||||||
|
if (width && height) {
|
||||||
|
imagesBulk.push({
|
||||||
|
project_id: project.id,
|
||||||
|
image_path: ann.image,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle multiple annotations per image
|
||||||
|
if (Array.isArray(ann.label_rectangles)) {
|
||||||
|
for (const ann_detail of ann.label_rectangles) {
|
||||||
|
annsBulk.push({
|
||||||
|
image_path: ann.image,
|
||||||
|
x: (ann_detail.x * width) / 100,
|
||||||
|
y: (ann_detail.y * height) / 100,
|
||||||
|
width: (ann_detail.width * width) / 100,
|
||||||
|
height: (ann_detail.height * height) / 100,
|
||||||
|
Label: Array.isArray(ann_detail.rectanglelabels) ? (ann_detail.rectanglelabels[0] || 'unknown') : (ann_detail.rectanglelabels || 'unknown')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(ann.label)) {
|
||||||
|
for (const ann_detail of ann.label) {
|
||||||
|
annsBulk.push({
|
||||||
|
image_path: ann.image,
|
||||||
|
x: (ann_detail.x * width) / 100,
|
||||||
|
y: (ann_detail.y * height) / 100,
|
||||||
|
width: (ann_detail.width * width) / 100,
|
||||||
|
height: (ann_detail.height * height) / 100,
|
||||||
|
Label: Array.isArray(ann_detail.rectanglelabels) ? (ann_detail.rectanglelabels[0] || 'unknown') : (ann_detail.rectanglelabels || 'unknown')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) Insert images and get generated IDs
|
||||||
|
const insertedImages = await Img.bulkCreate(imagesBulk, { returning: true });
|
||||||
|
|
||||||
|
// 2) Map image_path -> image_id
|
||||||
|
const imageMap = {};
|
||||||
|
for (const img of insertedImages) {
|
||||||
|
imageMap[img.image_path] = img.image_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Assign correct image_id to each annotation
|
||||||
|
for (const ann of annsBulk) {
|
||||||
|
ann.image_id = imageMap[ann.image_path];
|
||||||
|
delete ann.image_path; // cleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) Insert annotations
|
||||||
|
await Ann.bulkCreate(annsBulk);
|
||||||
|
|
||||||
|
console.log(`Inserted ${imagesBulk.length} images and ${annsBulk.length} annotations for project ${project.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Seeding done');
|
||||||
|
return { success: true, message: 'Data inserted successfully!' };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error inserting data:', error);
|
||||||
|
return { success: false, message: error.message };
|
||||||
|
} finally {
|
||||||
|
updateStatus.running = false;
|
||||||
|
console.log('updateStatus.running set to false');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { seedLabelStudio, updateStatus };
|
||||||
549
edit-training.html
Normal file
549
edit-training.html
Normal file
@@ -0,0 +1,549 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="globals.css" />
|
||||||
|
<link rel="stylesheet" href="styleguide.css" />
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
<style>
|
||||||
|
#projects-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
align-items: start;
|
||||||
|
background: rgb(234, 247, 250);
|
||||||
|
color: #009eac;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||||
|
padding: 10px 18px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row-inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-right: 18px;
|
||||||
|
color: #333;
|
||||||
|
min-width: 140px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row input[type="number"],
|
||||||
|
.setting-row input[type="text"] {
|
||||||
|
width: 200px;
|
||||||
|
|
||||||
|
min-width: 120px;
|
||||||
|
max-width: 230px;
|
||||||
|
margin-right: 18px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 1em;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #009eac;
|
||||||
|
background: #f8f8f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row input[type="checkbox"] {
|
||||||
|
margin-right: 18px;
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-desc {
|
||||||
|
font-size: 1em;
|
||||||
|
color: #009eac;
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
background: #eaf7fa;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
width: 100%;
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#exp-setup {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 32px auto;
|
||||||
|
padding: 24px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
#parameters-setup{
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 32px auto;
|
||||||
|
padding: 24px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-section {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-section h3 {
|
||||||
|
color: #009eac;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.config-section-foldable {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
background: linear-gradient(90deg, #b7f2ff 0%, #eaf7fa 100%);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-section-foldable h3 {
|
||||||
|
color: #009eac;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-section-foldable.nested {
|
||||||
|
background: #d2f0f7;
|
||||||
|
border-left: 6px solid #009eac;
|
||||||
|
margin-left: 16px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 158, 172, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-section-foldable.nested h3 {
|
||||||
|
color: #007080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-section-foldable.nested .fold-icon {
|
||||||
|
color: #007080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-desc-foldable {
|
||||||
|
background: #eaf7fa;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-desc-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #009eac;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-bottom: 1px solid #b2e4ef;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
background: #eaf7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-desc-content {
|
||||||
|
padding: 6px 10px;
|
||||||
|
color: #009eac;
|
||||||
|
background: #eaf7fa;
|
||||||
|
border-radius: 0 0 6px 6px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<!-- TODO: Fix Train from Skretch and disable model select if skratch -->
|
||||||
|
|
||||||
|
<body onload="pollStatus()">
|
||||||
|
<div>
|
||||||
|
<div id="header">
|
||||||
|
<icon class="header-icon" onclick="window.location.href='/index.html'" , onmouseover=""
|
||||||
|
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>
|
||||||
|
<div class="button-row">
|
||||||
|
<button id="Add Training Project" onclick="window.location.href='/add-project.html'"
|
||||||
|
class="button-red">Add Training Project</button>
|
||||||
|
<button id="seed-db-btn" class="button">
|
||||||
|
Seed Database
|
||||||
|
<div class="loader" id="loader" style="display: none"></div>
|
||||||
|
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="projects-list">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.getElementById('seed-db-btn').addEventListener('click', function () {
|
||||||
|
const elLoader = document.getElementById("loader")
|
||||||
|
elLoader.style.display = "inherit"
|
||||||
|
|
||||||
|
fetch('/api/seed')
|
||||||
|
.finally(() => {
|
||||||
|
// Instead of hiding loader immediately, poll /api/update-status until done
|
||||||
|
function pollStatus() {
|
||||||
|
fetch('/api/update-status')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(status => {
|
||||||
|
if (status && status.running) {
|
||||||
|
// Still running, poll again after short delay
|
||||||
|
setTimeout(pollStatus, 5000);
|
||||||
|
} else {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pollStatus();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show loader if backend is still processing on page load
|
||||||
|
|
||||||
|
function pollStatus() {
|
||||||
|
const elLoader = document.getElementById("loader");
|
||||||
|
fetch('/api/update-status')
|
||||||
|
.then(res => res.json())
|
||||||
|
|
||||||
|
.then(status => {
|
||||||
|
if (status && status.running) {
|
||||||
|
elLoader.style.display = "inherit";
|
||||||
|
setTimeout(pollStatus, 5000);
|
||||||
|
} else {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const projectId = urlParams.get('id');
|
||||||
|
fetch('/api/training-projects')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(projects => {
|
||||||
|
const project = projects.find(p => p.project_id == projectId || p.id == projectId);
|
||||||
|
if (project) {
|
||||||
|
document.getElementById('project-title-label').textContent = '/' + project.title;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 32px; justify-content: center; align-items: flex-start; margin-top:32px;">
|
||||||
|
<div id="exp-setup" style="flex: 1 1 420px; min-width: 340px; max-width: 600px; margin:0;">
|
||||||
|
<h2 style="margin-bottom:18px;color:#009eac;">YOLOX Training Settings</h2>
|
||||||
|
<form id="settings-form">
|
||||||
|
<!-- Autogenerated/Keep Variables -->
|
||||||
|
<!-- <div class="config-section">
|
||||||
|
<h3>Dataset & Class Settings</h3>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><label class="setting-label" for="num_classes">Num Classes</label><input type="number" id="num_classes" name="num_classes" value="80" min="1" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Number of object classes to detect in your dataset.<br>Example: 1–100 (COCO has 80)</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Train Ann</span><input type="text" name="train_ann" value="instances_train2017.json" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Filenames of annotation files (usually in COCO format) for training, validation, and testing respectively.<br>Examples: instances_train2017.json</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Val Ann</span><input type="text" name="val_ann" value="instances_val2017.json" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Validation annotation file name.</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Test Ann</span><input type="text" name="test_ann" value="instances_test2017.json" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Test annotation file name.</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Data Dir</span><input type="text" name="data_dir" value="" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Path to the directory containing your dataset images and annotations.</div></div></div>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<!-- Main Parameters -->
|
||||||
|
<div class="config-section-foldable">
|
||||||
|
<h3>Main Parameters</h3>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Exp Name</span><input type="text" name="exp_name" value="custom_exp" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Name to identify this training run/experiment. Used for logging and saving files.</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Max Epoch</span><input type="number" name="max_epoch" value="300" min="1" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Total number of training epochs.<br>Typical: 100–300</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><label class="setting-label" for="depth">Depth</label><input type="number" id="depth" step="any" name="depth" value="1.00" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Controls the depth (number of layers) of the backbone. Higher depth improves accuracy but may slow down training.<br>Typical values: 0.33 (nano), 0.67 (tiny), 1.0 (s, m, l)</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><label class="setting-label" for="width">Width</label><input type="number" id="width" step="any" name="width" value="1.00" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Controls the width (number of channels) in each layer. Wider models capture more detail but use more memory.<br>Typical values: 0.25–1.33</div></div></div>
|
||||||
|
|
||||||
|
<!-- Details Foldable -->
|
||||||
|
<div class="config-section-foldable nested">
|
||||||
|
<h3 style="cursor:pointer;display:flex;align-items:center;" onclick="toggleFold(this)"><span>Details</span><span class="fold-icon" style="margin-left:8px;font-size:1.2em;">▼</span></h3>
|
||||||
|
<div class="foldable-content">
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><label class="setting-label" for="act">Activation</label><input type="text" id="act" name="act" value="silu" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Activation function used in the network.<br>Options: silu, relu, leaky_relu</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Warmup Epochs</span><input type="number" name="warmup_epochs" value="5" min="0" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Number of epochs at the beginning with a slowly increasing learning rate.<br>Common: 3–5</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Warmup LR</span><input type="number" step="any" name="warmup_lr" value="0" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Starting learning rate during warmup phase. Usually set to 0.</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Scheduler</span><input type="text" name="scheduler" value="yoloxwarmcos" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Learning rate scheduler.<br>Default: yoloxwarmcos</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">No Aug Epochs</span><input type="number" name="no_aug_epochs" value="15" min="0" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Number of final epochs with no data augmentation to improve final accuracy.<br>Typical: 10–20</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Min LR Ratio</span><input type="number" step="any" name="min_lr_ratio" value="0.05" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Minimum ratio between the final and initial learning rate.<br>Default: 0.05</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">EMA</span><input type="checkbox" name="ema" checked /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Enable Exponential Moving Average of model weights for smoother training results.</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Weight Decay</span><input type="number" step="any" name="weight_decay" value="0.0005" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Regularization term to reduce overfitting.<br>Typical: 0.0001–0.001</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Momentum</span><input type="number" step="any" name="momentum" value="0.9" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Momentum for optimizer.<br>Default: 0.9</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Input Size</span><input type="text" name="input_size" value="640,640" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Size of input images during training, formatted as width,height.<br>Common values: 640,640 or 512,512</div></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Logs Foldable -->
|
||||||
|
<div class="config-section-foldable nested">
|
||||||
|
<h3 style="cursor:pointer;display:flex;align-items:center;" onclick="toggleFold(this)"><span>Logs</span><span class="fold-icon" style="margin-left:8px;font-size:1.2em;">▼</span></h3>
|
||||||
|
<div class="foldable-content">
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Print Interval</span><input type="number" name="print_interval" value="10" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">How often to print training logs (in iterations).<br>Example: 10</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Eval Interval</span><input type="number" name="eval_interval" value="10" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">How often to evaluate on validation set (in epochs).<br>Example: 10</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Save History CKPT</span><input type="checkbox" name="save_history_ckpt" checked /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Save model checkpoints periodically for backup or resuming.</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Test Size</span><input type="text" name="test_size" value="640,640" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Input size for evaluation, formatted as width,height.<br>Example: 640,640</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Test Conf</span><input type="number" step="any" name="test_conf" value="0.01" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Confidence score threshold for predictions during evaluation.<br>Typical: 0.01–0.3</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">NMS Thre</span><input type="number" step="any" name="nmsthre" value="0.65" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">IoU threshold for non-maximum suppression (NMS).<br>Typical: 0.5–0.7</div></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Transformation Settings Foldable -->
|
||||||
|
<div class="config-section-foldable nested">
|
||||||
|
<h3 style="cursor:pointer;display:flex;align-items:center;" onclick="toggleFold(this)"><span>Transformation Settings</span><span class="fold-icon" style="margin-left:8px;font-size:1.2em;">▼</span></h3>
|
||||||
|
<div class="foldable-content">
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Multiscale Range</span><input type="number" name="multiscale_range" value="5" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Controls how much to vary image sizes during training for robustness.<br>Range: 0–10 (e.g. 5 = ±5 scale levels)</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Enable Mixup</span><input type="checkbox" name="enable_mixup" checked /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Toggle mixup augmentation on or off. Improves generalization.</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Mosaic Prob</span><input type="number" step="any" name="mosaic_prob" value="1.0" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Probability of applying mosaic augmentation, which combines 4 images into 1.<br>Range: 0.0–1.0</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Mixup Prob</span><input type="number" step="any" name="mixup_prob" value="1.0" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Probability of applying mixup augmentation, blending two images and labels.<br>Range: 0.0–1.0</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">HSV Prob</span><input type="number" step="any" name="hsv_prob" value="1.0" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Probability of applying HSV (color) augmentation to images.<br>Range: 0.0–1.0</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Flip Prob</span><input type="number" step="any" name="flip_prob" value="0.5" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Probability of flipping the image horizontally.<br>Default: 0.5</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Degrees</span><input type="number" step="any" name="degrees" value="10.0" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Maximum rotation angle for random rotation.<br>Typical: 0–15°</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Mosaic Scale</span><input type="text" name="mosaic_scale" value="0.1,2" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Scale range for mosaic augmentation, formatted as min,max.<br>Example: 0.1,2.0</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Mixup Scale</span><input type="text" name="mixup_scale" value="0.5,1.5" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Scale range for mixup augmentation.<br>Example: 0.5,1.5</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Translate</span><input type="number" step="any" name="translate" value="0.1" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Maximum translation ratio. A value of 0.1 means 10% shift in image position.<br>Range: 0.0–0.3</div></div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner"><span class="setting-label">Shear</span><input type="number" step="any" name="shear" value="2.0" /></div><div class="setting-desc-foldable"><div class="setting-desc-header" onclick="toggleDescFold(this)"><span class="desc-fold-icon" style="margin-right:8px;">►</span>Description</div><div class="setting-desc-content">Maximum shear angle in degrees for geometric distortion.<br>Typical: 0.0–5.0</div></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="parameters-setup" style="flex: 1 1 420px; min-width: 340px; max-width: 600px; margin:0;">
|
||||||
|
<!-- Parameters Section -->
|
||||||
|
|
||||||
|
<h2 style="margin-bottom:18px;color:#009eac;">Parameters</h2>
|
||||||
|
<form id="parameters-form">
|
||||||
|
<div class="config-section-foldable">
|
||||||
|
<h3>Category: Split</h3>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner">
|
||||||
|
<label class="setting-label" for="seed">Seed</label>
|
||||||
|
<input type="number" id="seed" name="seed" value="" />
|
||||||
|
</div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner">
|
||||||
|
<label class="setting-label" for="train-slider">Train</label>
|
||||||
|
<input type="range" id="train-slider" name="train" min="0" max="100" value="70" step="1" style="width:120px;">
|
||||||
|
<span id="train-value">70%</span>
|
||||||
|
</div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner">
|
||||||
|
<label class="setting-label" for="valid-slider">Valid</label>
|
||||||
|
<input type="range" id="valid-slider" name="valid" min="0" max="100" value="20" step="1" style="width:120px;">
|
||||||
|
<span id="valid-value">20%</span>
|
||||||
|
</div></div>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner">
|
||||||
|
<label class="setting-label" for="test-slider">Test</label>
|
||||||
|
<input type="range" id="test-slider" name="test" min="0" max="100" value="10" step="1" style="width:120px;">
|
||||||
|
<span id="test-value">10%</span>s
|
||||||
|
</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="config-section-foldable" style="margin-top:24px;">
|
||||||
|
<h3>Model Settings</h3>
|
||||||
|
<div class="setting-row"><div class="setting-row-inner">
|
||||||
|
<label class="setting-label" for="select-model">Select Model</label>
|
||||||
|
<select id="select-model" name="select_model">
|
||||||
|
<option value="YOLOX-s">YOLOX-s</option>
|
||||||
|
<option value="YOLOX-m">YOLOX-m</option>
|
||||||
|
<option value="YOLOX-l">YOLOX-l</option>
|
||||||
|
<option value="YOLOX-x">YOLOX-x</option>
|
||||||
|
<option value="YOLOX-Darknet53">YOLOX-Darknet53</option>
|
||||||
|
<option value="YOLOX-Nano">YOLOX-Nano</option>
|
||||||
|
<option value="YOLOX-Tiny">YOLOX-Tiny</option>
|
||||||
|
</select>
|
||||||
|
</div></div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-row-inner">
|
||||||
|
<label class="setting-label" for="transfer-learning">Transfer Learning</label>
|
||||||
|
<select id="transfer-learning" name="transfer_learning" onchange="toggleCkptUpload()">
|
||||||
|
<option value="sketch">Train from sketch</option>
|
||||||
|
<option value="coco">Train on coco</option>
|
||||||
|
<option value="custom">Train on custom</option>
|
||||||
|
</select>
|
||||||
|
</div></div>
|
||||||
|
<div class="setting-row" id="ckpt-upload-row" style="display:none;">
|
||||||
|
<div class="setting-row-inner">
|
||||||
|
<label class="setting-label" for="ckpt-upload">Upload .pth file</label>
|
||||||
|
<input type="file" id="ckpt-upload" name="ckpt_upload" accept=".pth" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="button" style="margin-top:18px;">Save Parameters</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function toggleFold(header) {
|
||||||
|
const section = header.parentElement;
|
||||||
|
const content = section.querySelector('.foldable-content');
|
||||||
|
const icon = header.querySelector('.fold-icon');
|
||||||
|
if (content.style.display === 'none') {
|
||||||
|
content.style.display = '';
|
||||||
|
icon.textContent = '▼';
|
||||||
|
} else {
|
||||||
|
content.style.display = 'none';
|
||||||
|
icon.textContent = '►';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('DOMContentLoaded', function () {
|
||||||
|
document.querySelectorAll('.config-section-foldable .foldable-content').forEach(function (content) {
|
||||||
|
const icon = content.parentElement.querySelector('.fold-icon');
|
||||||
|
if (content.parentElement.classList.contains('nested')) {
|
||||||
|
content.style.display = 'none';
|
||||||
|
if (icon) icon.textContent = '►';
|
||||||
|
} else {
|
||||||
|
content.style.display = '';
|
||||||
|
if (icon) icon.textContent = '▼';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
function toggleDescFold(header) {
|
||||||
|
const content = header.nextElementSibling;
|
||||||
|
const icon = header.querySelector('.desc-fold-icon');
|
||||||
|
if (content.style.display === 'none' || content.style.display === '') {
|
||||||
|
content.style.display = 'block';
|
||||||
|
icon.textContent = '▼';
|
||||||
|
} else {
|
||||||
|
content.style.display = 'none';
|
||||||
|
icon.textContent = '►';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('DOMContentLoaded', function () {
|
||||||
|
document.querySelectorAll('.setting-desc-content').forEach(function (content) {
|
||||||
|
content.style.display = 'none';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script src="js/start-training.js"></script>
|
||||||
|
<script>
|
||||||
|
function toggleCkptUpload() {
|
||||||
|
var select = document.getElementById('transfer-learning');
|
||||||
|
var row = document.getElementById('ckpt-upload-row');
|
||||||
|
if (select.value === 'custom') {
|
||||||
|
row.style.display = '';
|
||||||
|
} else {
|
||||||
|
row.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('DOMContentLoaded', function() {
|
||||||
|
toggleCkptUpload();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
function syncSplitSliders(changed) {
|
||||||
|
let train = parseInt(document.getElementById('train-slider').value);
|
||||||
|
let valid = parseInt(document.getElementById('valid-slider').value);
|
||||||
|
let test = 100 - train - valid;
|
||||||
|
// Clamp valid so test is never negative
|
||||||
|
if (test < 0) {
|
||||||
|
valid = 100 - train;
|
||||||
|
document.getElementById('valid-slider').value = valid;
|
||||||
|
test = 0;
|
||||||
|
}
|
||||||
|
document.getElementById('test-slider').value = test;
|
||||||
|
document.getElementById('train-value').textContent = train + '%';
|
||||||
|
document.getElementById('valid-value').textContent = valid + '%';
|
||||||
|
document.getElementById('test-value').textContent = test + '%';
|
||||||
|
}
|
||||||
|
document.getElementById('train-slider').addEventListener('input', function() { syncSplitSliders('train'); });
|
||||||
|
document.getElementById('valid-slider').addEventListener('input', function() { syncSplitSliders('valid'); });
|
||||||
|
document.getElementById('test-slider').setAttribute('disabled', 'disabled');
|
||||||
|
window.addEventListener('DOMContentLoaded', function() { syncSplitSliders('train'); });
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
document.getElementById('parameters-form').addEventListener('submit', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
// Collect YOLOX settings from #settings-form
|
||||||
|
const settingsForm = document.getElementById('settings-form');
|
||||||
|
const settingsData = {};
|
||||||
|
Array.from(settingsForm.elements).forEach(el => {
|
||||||
|
if (el.name && el.type !== 'submit') {
|
||||||
|
if (el.type === 'checkbox') {
|
||||||
|
settingsData[el.name] = el.checked;
|
||||||
|
} else {
|
||||||
|
settingsData[el.name] = el.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Collect parameters from #parameters-form
|
||||||
|
const paramsForm = document.getElementById('parameters-form');
|
||||||
|
Array.from(paramsForm.elements).forEach(el => {
|
||||||
|
if (el.name && el.type !== 'submit') {
|
||||||
|
if (el.type === 'checkbox') {
|
||||||
|
settingsData[el.name] = el.checked;
|
||||||
|
} else if (el.type === 'file') {
|
||||||
|
if (el.files && el.files[0]) {
|
||||||
|
settingsData[el.name] = el.files[0]; // Will handle below
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
settingsData[el.name] = el.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Always add test value from slider (even if disabled)
|
||||||
|
settingsData['test'] = document.getElementById('test-slider').value;
|
||||||
|
// Handle file upload (ckpt_upload)
|
||||||
|
const formData = new FormData();
|
||||||
|
for (const key in settingsData) {
|
||||||
|
if (settingsData[key] instanceof File) {
|
||||||
|
formData.append(key, settingsData[key]);
|
||||||
|
} else {
|
||||||
|
formData.append(key, settingsData[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add project id if available
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const projectId = urlParams.get('id');
|
||||||
|
if (projectId) formData.append('project_id', projectId);
|
||||||
|
// Send to backend
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/yolox-settings', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
const result = await res.json();
|
||||||
|
alert('Parameters saved!');
|
||||||
|
window.location.href = '/overview-training.html?id=' + projectId;
|
||||||
|
} else {
|
||||||
|
const err = await res.json();
|
||||||
|
alert('Error saving parameters: ' + err.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('Error saving parameters: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
20
globals.css
Normal file
20
globals.css
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
@import url("https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css");
|
||||||
|
* {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
/* a blue color as a generic focus style */
|
||||||
|
button:focus-visible {
|
||||||
|
outline: 2px solid #4a90e2 !important;
|
||||||
|
outline: -webkit-focus-ring-color auto 5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
96
index.html
Normal file
96
index.html
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="globals.css" />
|
||||||
|
<link rel="stylesheet" href="styleguide.css" />
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
<style>
|
||||||
|
#projects-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body onload="pollStatus()">
|
||||||
|
<div>
|
||||||
|
<div id="header">
|
||||||
|
<icon class="header-icon" onclick="window.location.href='/index.html'" , onmouseover="" style="cursor: pointer;"
|
||||||
|
src="./media/logo.png" alt="Logo"></icon>
|
||||||
|
<div class="button-row">
|
||||||
|
<button id="Add Training Project" onclick="window.location.href='/add-project.html'" class="button-red">Add
|
||||||
|
Training Project</button>
|
||||||
|
<button id="seed-db-btn" class="button">
|
||||||
|
Seed Database
|
||||||
|
<div class="loader" id="loader" style="display: none"></div>
|
||||||
|
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="projects-list">
|
||||||
|
<script src="js/dashboard.js"></script>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.getElementById('seed-db-btn').addEventListener('click', function () {
|
||||||
|
const elLoader = document.getElementById("loader")
|
||||||
|
elLoader.style.display = "inherit"
|
||||||
|
|
||||||
|
fetch('/api/seed')
|
||||||
|
.finally(() => {
|
||||||
|
// Instead of hiding loader immediately, poll /api/update-status until done
|
||||||
|
function pollStatus() {
|
||||||
|
fetch('/api/update-status')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(status => {
|
||||||
|
if (status && status.running) {
|
||||||
|
// Still running, poll again after short delay
|
||||||
|
setTimeout(pollStatus, 5000);
|
||||||
|
} else {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pollStatus();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show loader if backend is still processing on page load
|
||||||
|
|
||||||
|
function pollStatus() {
|
||||||
|
const elLoader = document.getElementById("loader");
|
||||||
|
fetch('/api/update-status')
|
||||||
|
.then(res => res.json())
|
||||||
|
|
||||||
|
.then(status => {
|
||||||
|
if (status && status.running) {
|
||||||
|
elLoader.style.display = "inherit";
|
||||||
|
setTimeout(pollStatus, 5000);
|
||||||
|
} else {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
154
js/add-class.js
Normal file
154
js/add-class.js
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
export function addClass() {
|
||||||
|
const input_class = document.querySelector('.add-category input.div-wrapper');
|
||||||
|
|
||||||
|
let existingClasses;
|
||||||
|
|
||||||
|
const input_project_name = document.getElementById('project_name_input')
|
||||||
|
const description = document.getElementById('project_description_input');
|
||||||
|
const button_addClass = document.querySelector('.add-category .upload-button-text-wrapper');
|
||||||
|
const button_addProject = document.querySelector('.popup .confirm-button-datasetcreation')
|
||||||
|
const classWrapper = document.querySelector('.add-class-wrapper');
|
||||||
|
|
||||||
|
|
||||||
|
button_addProject.addEventListener('click', () => {
|
||||||
|
const title = input_project_name.value.trim();
|
||||||
|
const descriptionText = description.value.trim();
|
||||||
|
const classes = Array.from(classWrapper.querySelectorAll('.overlap-group')).map(el => el.textContent.trim());
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('title', title);
|
||||||
|
formData.append('description', descriptionText);
|
||||||
|
formData.append('classes', JSON.stringify(classes));
|
||||||
|
if (imgBlob) {
|
||||||
|
formData.append('project_image', imgBlob, 'project_image.png'); // or the correct file type
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('/api/training-projects', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
alert(data.message || 'Project created!');
|
||||||
|
window.location.href = '/index.html';
|
||||||
|
})
|
||||||
|
.catch(err => alert('Error: ' + err));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
button_addClass.addEventListener('click', () => {
|
||||||
|
|
||||||
|
const className = input_class.value.trim();
|
||||||
|
|
||||||
|
if (!className) {
|
||||||
|
alert('Please enter a class name');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
existingClasses = classWrapper.querySelectorAll('.overlap-group');
|
||||||
|
for (const el of existingClasses) {
|
||||||
|
if (el.textContent.trim().toLowerCase() === className.toLowerCase()) {
|
||||||
|
alert(`Class name "${className}" already exists.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const newClassDiv = document.createElement('div');
|
||||||
|
newClassDiv.classList.add('add-class');
|
||||||
|
newClassDiv.style.position = 'relative';
|
||||||
|
newClassDiv.style.width = '335px';
|
||||||
|
newClassDiv.style.height = '25px';
|
||||||
|
newClassDiv.style.marginBottom = '5px';
|
||||||
|
|
||||||
|
|
||||||
|
const overlapGroup = document.createElement('div');
|
||||||
|
overlapGroup.classList.add('overlap-group');
|
||||||
|
overlapGroup.style.position = 'absolute';
|
||||||
|
overlapGroup.style.width = '275px';
|
||||||
|
overlapGroup.style.height = '25px';
|
||||||
|
overlapGroup.style.top = '0';
|
||||||
|
overlapGroup.style.left = '0';
|
||||||
|
overlapGroup.style.backgroundColor = '#30bffc80';
|
||||||
|
overlapGroup.style.borderRadius = '5px';
|
||||||
|
overlapGroup.style.display = 'flex';
|
||||||
|
overlapGroup.style.alignItems = 'center';
|
||||||
|
overlapGroup.style.paddingLeft = '10px';
|
||||||
|
overlapGroup.style.color = '#000';
|
||||||
|
overlapGroup.style.fontFamily = 'var(--m3-body-small-font-family)';
|
||||||
|
overlapGroup.style.fontWeight = 'var(--m3-body-small-font-weight)';
|
||||||
|
overlapGroup.style.fontSize = 'var(--m3-body-small-font-size)';
|
||||||
|
overlapGroup.style.letterSpacing = 'var(--m3-body-small-letter-spacing)';
|
||||||
|
overlapGroup.style.lineHeight = 'var(--m3-body-small-line-height)';
|
||||||
|
overlapGroup.textContent = className;
|
||||||
|
|
||||||
|
|
||||||
|
const overlap = document.createElement('div');
|
||||||
|
overlap.classList.add('overlap');
|
||||||
|
overlap.style.position = 'absolute';
|
||||||
|
overlap.style.width = '50px';
|
||||||
|
overlap.style.height = '25px';
|
||||||
|
overlap.style.top = '0';
|
||||||
|
overlap.style.left = '285px';
|
||||||
|
|
||||||
|
|
||||||
|
const rectangle = document.createElement('div');
|
||||||
|
rectangle.classList.add('rectangle');
|
||||||
|
rectangle.style.width = '50px';
|
||||||
|
rectangle.style.height = '25px';
|
||||||
|
rectangle.style.backgroundColor = '#ff0f43';
|
||||||
|
rectangle.style.borderRadius = '5px';
|
||||||
|
rectangle.style.display = 'flex';
|
||||||
|
rectangle.style.alignItems = 'center';
|
||||||
|
rectangle.style.justifyContent = 'center';
|
||||||
|
rectangle.style.cursor = 'pointer';
|
||||||
|
|
||||||
|
rectangle.addEventListener('mouseenter', () => {
|
||||||
|
rectangle.style.backgroundColor = '#bb032b';
|
||||||
|
});
|
||||||
|
rectangle.addEventListener('mouseleave', () => {
|
||||||
|
rectangle.style.backgroundColor = '#ff0f43';
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const minusText = document.createElement('div');
|
||||||
|
minusText.classList.add('text-wrapper-4');
|
||||||
|
minusText.style.position = 'absolute';
|
||||||
|
minusText.style.top = '-18px';
|
||||||
|
minusText.style.left = '18px';
|
||||||
|
minusText.style.fontFamily = 'var(--m3-display-large-font-family)';
|
||||||
|
minusText.style.fontWeight = 'var(--m3-display-large-font-weight)';
|
||||||
|
minusText.style.color = '#000000';
|
||||||
|
minusText.style.fontSize = 'var(--minus-for-button-size)';
|
||||||
|
minusText.style.letterSpacing = 'var(--m3-display-large-letter-spacing)';
|
||||||
|
minusText.style.lineHeight = 'var(--m3-display-large-line-height)';
|
||||||
|
minusText.style.whiteSpace = 'nowrap';
|
||||||
|
minusText.style.cursor = 'pointer';
|
||||||
|
minusText.style.fontStyle = 'var(--m3-display-large-font-style)';
|
||||||
|
minusText.textContent = '_';
|
||||||
|
|
||||||
|
|
||||||
|
rectangle.appendChild(minusText);
|
||||||
|
|
||||||
|
|
||||||
|
rectangle.addEventListener('click', () => {
|
||||||
|
classWrapper.removeChild(newClassDiv);
|
||||||
|
|
||||||
|
document.dispatchEvent(new CustomEvent('classListUpdated'));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
overlap.appendChild(rectangle);
|
||||||
|
|
||||||
|
|
||||||
|
newClassDiv.appendChild(overlapGroup);
|
||||||
|
newClassDiv.appendChild(overlap);
|
||||||
|
|
||||||
|
|
||||||
|
classWrapper.appendChild(newClassDiv);
|
||||||
|
|
||||||
|
|
||||||
|
input_class.value = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
38
js/add-image.js
Normal file
38
js/add-image.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
//Global Variable
|
||||||
|
var imgBlob;
|
||||||
|
var imgMimeType
|
||||||
|
|
||||||
|
// Create a hidden file input dynamically
|
||||||
|
const fileInput = document.createElement('input');
|
||||||
|
fileInput.type = 'file';
|
||||||
|
fileInput.accept = 'image/*';
|
||||||
|
fileInput.style.display = 'none';
|
||||||
|
document.body.appendChild(fileInput);
|
||||||
|
|
||||||
|
|
||||||
|
function uploadButtonHandler() {
|
||||||
|
fileInput.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
fileInput.addEventListener('change', () => {
|
||||||
|
const imageDiv = document.querySelector('.popup .image');
|
||||||
|
const file = fileInput.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
imageDiv.innerHTML = ''; // clear previous content
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = e.target.result;
|
||||||
|
img.alt = 'Uploaded Image';
|
||||||
|
img.style.width = '100%';
|
||||||
|
img.style.height = '100%';
|
||||||
|
img.style.objectFit = 'cover';
|
||||||
|
img.style.borderRadius = '10px';
|
||||||
|
imageDiv.appendChild(img);
|
||||||
|
// Use the original file as the blob and store its MIME type
|
||||||
|
imgBlob = file;
|
||||||
|
imgMimeType = file.type;
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
137
js/dashboard-label-studio.js
Normal file
137
js/dashboard-label-studio.js
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
// Fetch LabelStudioProjects from backend and render as selectable cards
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
let projectsList = document.getElementById('projects-list');
|
||||||
|
const selectedIds = new Set();
|
||||||
|
if (!projectsList) {
|
||||||
|
// Try to create the container if missing
|
||||||
|
projectsList = document.createElement('div');
|
||||||
|
projectsList.id = 'projects-list';
|
||||||
|
document.body.appendChild(projectsList);
|
||||||
|
}
|
||||||
|
else{console.log("noep")}
|
||||||
|
fetch('/api/label-studio-projects')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(projects => {
|
||||||
|
projectsList.innerHTML = '';
|
||||||
|
if (!projects || projects.length === 0) {
|
||||||
|
projectsList.innerHTML = '<div>No Label Studio projects found</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const project of projects) {
|
||||||
|
// Only show card if there is at least one non-empty annotation class
|
||||||
|
const annotationClasses = Object.entries(project.annotationCounts || {})
|
||||||
|
.filter(([label, count]) => label && label.trim() !== '');
|
||||||
|
if (annotationClasses.length === 0) continue;
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'card';
|
||||||
|
card.style.background = '#f5f5f5';
|
||||||
|
card.style.borderRadius = '12px';
|
||||||
|
card.style.overflow = 'hidden';
|
||||||
|
card.style.boxShadow = '0 2px 8px rgba(0,0,0,0)';
|
||||||
|
card.style.display = 'flex';
|
||||||
|
card.style.background = 'white';
|
||||||
|
card.style.cursor = 'pointer';
|
||||||
|
card.tabIndex = 0;
|
||||||
|
card.setAttribute('role', 'button');
|
||||||
|
card.setAttribute('aria-label', `Open project ${project.title || project.project_id}`);
|
||||||
|
|
||||||
|
// Selection logic
|
||||||
|
card.dataset.projectId = project.project_id;
|
||||||
|
card.addEventListener('click', () => {
|
||||||
|
card.classList.toggle('selected');
|
||||||
|
if (card.classList.contains('selected')) {
|
||||||
|
card.style.background = '#009eac'; // main dif color for card
|
||||||
|
selectedIds.add(project.project_id);
|
||||||
|
} else {
|
||||||
|
card.style.background = 'white'; // revert card color
|
||||||
|
selectedIds.delete(project.project_id);
|
||||||
|
}
|
||||||
|
// Debug: log selected ids array
|
||||||
|
console.log(Array.from(selectedIds));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Info
|
||||||
|
const infoDiv = document.createElement('div');
|
||||||
|
infoDiv.className = 'info';
|
||||||
|
infoDiv.style.background = 'rgba(210, 238, 240)';
|
||||||
|
infoDiv.style.flex = '1';
|
||||||
|
infoDiv.style.padding = '16px';
|
||||||
|
infoDiv.innerHTML = `
|
||||||
|
<h3 style="margin:0 0 8px 0;font-size:1.5em;font-weight:bold;">${project.project_id ?? 'N/A'} ${project.title || 'Untitled'}</h3>
|
||||||
|
<div class="label-classes" style="font-size:1em;">
|
||||||
|
${annotationClasses.map(([label, count]) => `<p>${label}: ${count}</p>`).join('')}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
card.appendChild(infoDiv);
|
||||||
|
projectsList.appendChild(card);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
projectsList.innerHTML = '<div>Error loading Label Studio projects</div>';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add Next button at the bottom right of the page
|
||||||
|
const nextBtn = document.createElement('button');
|
||||||
|
nextBtn.id = 'next-btn';
|
||||||
|
nextBtn.className = 'button';
|
||||||
|
nextBtn.textContent = 'Next';
|
||||||
|
nextBtn.style.position = 'fixed';
|
||||||
|
nextBtn.style.right = '32px';
|
||||||
|
nextBtn.style.bottom = '32px';
|
||||||
|
nextBtn.style.zIndex = '1000';
|
||||||
|
document.body.appendChild(nextBtn);
|
||||||
|
|
||||||
|
// Get training_project_id from URL
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const trainingProjectId = urlParams.get('id');
|
||||||
|
|
||||||
|
// Next button click handler
|
||||||
|
nextBtn.addEventListener('click', () => {
|
||||||
|
console.log(trainingProjectId)
|
||||||
|
if (!trainingProjectId) {
|
||||||
|
alert('No training project selected.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectedIds.size === 0) {
|
||||||
|
alert('Please select at least one Label Studio project.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const annotationProjectsJson = JSON.stringify(Array.from(selectedIds));
|
||||||
|
fetch('/api/training-project-details', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
project_id: Number(trainingProjectId),
|
||||||
|
annotation_projects: Array.from(selectedIds)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
alert('TrainingProjectDetails saved!');
|
||||||
|
console.log(data);
|
||||||
|
// Redirect to start-training.html with id
|
||||||
|
window.location.href = `/setup-training-project.html?id=${trainingProjectId}`;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
alert('Error saving TrainingProjectDetails');
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add description field above the project cards
|
||||||
|
const descDiv = document.createElement('div');
|
||||||
|
descDiv.id = 'dashboard-description';
|
||||||
|
descDiv.style.width = '100%';
|
||||||
|
descDiv.style.maxWidth = '900px';
|
||||||
|
descDiv.style.margin = '0 auto 24px auto';
|
||||||
|
descDiv.style.padding = '18px 24px';
|
||||||
|
descDiv.style.background = '#eaf7fa';
|
||||||
|
descDiv.style.borderRadius = '12px';
|
||||||
|
descDiv.style.boxShadow = '0 2px 8px rgba(0,0,0,0.04)';
|
||||||
|
descDiv.style.fontSize = '1.15em';
|
||||||
|
descDiv.style.color = '#009eac';
|
||||||
|
descDiv.style.textAlign = 'center';
|
||||||
|
descDiv.textContent = 'Select one or more Label Studio projects by clicking the cards below. The annotation summary for each project is shown. Click Next to continue.';
|
||||||
|
projectsList.parentNode.insertBefore(descDiv, projectsList);
|
||||||
|
});
|
||||||
171
js/dashboard.js
Normal file
171
js/dashboard.js
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
function renderProjects(projects) {
|
||||||
|
const projectsList = document.getElementById('projects-list');
|
||||||
|
projectsList.innerHTML = '';
|
||||||
|
|
||||||
|
if (projects.length === 0) {
|
||||||
|
projectsList.innerHTML = '<div>No projects found</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const project of projects) {
|
||||||
|
const labelCounts = project.labelCounts || {};
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'card';
|
||||||
|
card.style.background = '#f5f5f5';
|
||||||
|
card.style.borderRadius = '12px';
|
||||||
|
card.style.overflow = 'hidden';
|
||||||
|
card.style.boxShadow = '0 2px 8px rgba(0,0,0,0)';
|
||||||
|
card.style.display = 'flex';
|
||||||
|
card.style.background = 'white';
|
||||||
|
card.style.cursor = 'pointer';
|
||||||
|
card.tabIndex = 0;
|
||||||
|
card.setAttribute('role', 'button');
|
||||||
|
card.setAttribute('aria-label', `Open project ${project.title || project.id}`);
|
||||||
|
card.style.position = 'relative'; // For absolute positioning of delete button
|
||||||
|
card.addEventListener('click', (e) => {
|
||||||
|
// Prevent click if delete button is pressed
|
||||||
|
if (e.target.classList.contains('delete-btn')) return;
|
||||||
|
if (project.hasTraining) {
|
||||||
|
window.location.href = `/overview-training.html?id=${project.id}`;
|
||||||
|
} else if (project.hasDetails) {
|
||||||
|
// Find details for this project
|
||||||
|
const detailsEntry = window._trainingProjectDetails?.find(d => d.project_id == project.id);
|
||||||
|
if (detailsEntry && Array.isArray(detailsEntry.class_map) && detailsEntry.class_map.length > 0) {
|
||||||
|
// If classes are assigned, skip to start-training.html
|
||||||
|
window.location.href = `/edit-training.html?id=${project.id}`;
|
||||||
|
} else {
|
||||||
|
window.location.href = `/setup-training-project.html?id=${project.id}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
window.location.href = `/project-details.html?id=${project.id}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Image
|
||||||
|
let imageHTML = '';
|
||||||
|
if (project.project_image) {
|
||||||
|
imageHTML = `<img src="${project.project_image}" alt="img" style="width:120px;height:120px;object-fit:cover;display:block;" />`;
|
||||||
|
}
|
||||||
|
const imgContainer = document.createElement('div');
|
||||||
|
imgContainer.className = 'img-container';
|
||||||
|
imgContainer.style.background = '#009eac2d'
|
||||||
|
imgContainer.style.flex = '0 0 120px';
|
||||||
|
imgContainer.style.display = 'flex';
|
||||||
|
imgContainer.style.alignItems = 'center';
|
||||||
|
imgContainer.style.justifyContent = 'center';
|
||||||
|
imgContainer.innerHTML = imageHTML;
|
||||||
|
|
||||||
|
// Info
|
||||||
|
const infoDiv = document.createElement('div');
|
||||||
|
infoDiv.className = 'info';
|
||||||
|
infoDiv.style.background = '#009eac2d'
|
||||||
|
infoDiv.style.flex = '1';
|
||||||
|
infoDiv.style.padding = '16px';
|
||||||
|
infoDiv.innerHTML = `
|
||||||
|
<h3 style="margin:0 0 8px 0;font-size:1.5em;font-weight:bold;">${project.id ?? 'N/A'}     ${project.title || 'Untitled'}</h3>
|
||||||
|
<div class="label-classes" style="font-size:1em;">
|
||||||
|
${getClassesAsParagraphs(project, labelCounts)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Delete button
|
||||||
|
const deleteBtn = document.createElement('button');
|
||||||
|
deleteBtn.textContent = 'Delete';
|
||||||
|
deleteBtn.style.width = '70px';
|
||||||
|
deleteBtn.style.height = '28px';
|
||||||
|
deleteBtn.className = 'button-red delete-btn';
|
||||||
|
deleteBtn.style.position = 'absolute';
|
||||||
|
deleteBtn.style.bottom = '0px';
|
||||||
|
deleteBtn.style.right = '15px';
|
||||||
|
deleteBtn.style.zIndex = '2';
|
||||||
|
deleteBtn.style.fontSize = '14px';
|
||||||
|
deleteBtn.style.padding = '0';
|
||||||
|
deleteBtn.style.borderRadius = '6px';
|
||||||
|
deleteBtn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.08)';
|
||||||
|
deleteBtn.style.display = 'flex';
|
||||||
|
deleteBtn.style.alignItems = 'center';
|
||||||
|
deleteBtn.style.justifyContent = 'center';
|
||||||
|
deleteBtn.addEventListener('click', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (confirm('Are you sure you want to delete this training project?')) {
|
||||||
|
fetch(`/api/training-projects/${project.id}`, { method: 'DELETE' })
|
||||||
|
.then(res => {
|
||||||
|
if (res.ok) {
|
||||||
|
card.remove();
|
||||||
|
} else {
|
||||||
|
alert('Failed to delete project.');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => alert('Failed to delete project.'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
card.appendChild(imgContainer);
|
||||||
|
card.appendChild(infoDiv);
|
||||||
|
card.appendChild(deleteBtn);
|
||||||
|
projectsList.appendChild(card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to render classes as <p> elements
|
||||||
|
function getClassesAsParagraphs(project, labelCounts) {
|
||||||
|
let classes = [];
|
||||||
|
let labelConfig = project.parsed_label_config;
|
||||||
|
if (typeof labelConfig === 'string') {
|
||||||
|
try { labelConfig = JSON.parse(labelConfig); } catch { labelConfig = null; }
|
||||||
|
}
|
||||||
|
if (labelConfig) {
|
||||||
|
Object.values(labelConfig).forEach(cfg => {
|
||||||
|
if (cfg.labels && Array.isArray(cfg.labels)) {
|
||||||
|
cfg.labels.forEach(label => {
|
||||||
|
classes.push(label);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (classes.length === 0 && project.prompts && project.prompts.length > 0) {
|
||||||
|
const prompt = project.prompts[0];
|
||||||
|
if (prompt.output_classes && prompt.output_classes.length > 0) {
|
||||||
|
classes = prompt.output_classes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (classes.length === 0 && Object.keys(labelCounts).length > 0) {
|
||||||
|
classes = Object.keys(labelCounts);
|
||||||
|
}
|
||||||
|
return classes.map(cls => `<p>${cls}${labelCounts && labelCounts[cls] !== undefined ? ' ' : ''}</p>`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch and render TrainingProjects from the backend
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
Promise.all([
|
||||||
|
fetch('/api/training-projects').then(res => res.json()),
|
||||||
|
fetch('/api/training-project-details').then(res => res.json()),
|
||||||
|
fetch('/api/trainings').then(res => res.json())
|
||||||
|
]).then(([projects, details, trainings]) => {
|
||||||
|
window._trainingProjectDetails = details; // Store globally for click handler
|
||||||
|
// Build a set of project IDs that have details
|
||||||
|
const detailsProjectIds = new Set(details.map(d => d.project_id));
|
||||||
|
// Build a set of project IDs that have trainings
|
||||||
|
const detailsIdToProjectId = {};
|
||||||
|
details.forEach(d => { detailsIdToProjectId[d.id] = d.project_id; });
|
||||||
|
const trainingProjectIds = new Set(trainings.map(t => detailsIdToProjectId[t.project_details_id]));
|
||||||
|
// Map project_id to id for frontend compatibility
|
||||||
|
projects.forEach(project => {
|
||||||
|
if (project.project_id !== undefined) project.id = project.project_id;
|
||||||
|
if (Array.isArray(project.classes)) {
|
||||||
|
project.labelCounts = {};
|
||||||
|
project.classes.forEach(cls => project.labelCounts[cls] = 0);
|
||||||
|
}
|
||||||
|
// Attach a flag for details existence
|
||||||
|
project.hasDetails = detailsProjectIds.has(project.id);
|
||||||
|
// Attach a flag for training existence
|
||||||
|
project.hasTraining = trainingProjectIds.has(project.id);
|
||||||
|
});
|
||||||
|
renderProjects(projects);
|
||||||
|
}).catch(err => {
|
||||||
|
document.getElementById('projects-list').innerHTML = '<div>Error loading projects</div>';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
0
js/overview-training.js
Normal file
0
js/overview-training.js
Normal file
216
js/setup-training-project.js
Normal file
216
js/setup-training-project.js
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
// Fetch and display training project name in nav bar
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const trainingProjectId = urlParams.get('id');
|
||||||
|
if (!trainingProjectId) return;
|
||||||
|
|
||||||
|
// Fetch training project, details, and all LabelStudioProjects
|
||||||
|
Promise.all([
|
||||||
|
fetch(`/api/training-projects`).then(res => res.json()),
|
||||||
|
fetch(`/api/training-project-details`).then(res => res.json()),
|
||||||
|
fetch(`/api/label-studio-projects`).then(res => res.json())
|
||||||
|
]).then(([projects, detailsList, labelStudioProjects]) => {
|
||||||
|
// Find the selected training project
|
||||||
|
const project = projects.find(p => p.project_id == trainingProjectId || p.id == trainingProjectId);
|
||||||
|
// Find the details entry for this project
|
||||||
|
const details = detailsList.find(d => d.project_id == trainingProjectId);
|
||||||
|
if (!project || !details) return;
|
||||||
|
// Get the stored classes from training project
|
||||||
|
const storedClasses = Array.isArray(project.classes) ? project.classes : [];
|
||||||
|
// Get related LabelStudioProject IDs
|
||||||
|
const relatedIds = Array.isArray(details.annotation_projects) ? details.annotation_projects : [];
|
||||||
|
// Filter LabelStudioProjects to only those related
|
||||||
|
const relatedProjects = labelStudioProjects.filter(lp => relatedIds.includes(lp.project_id));
|
||||||
|
|
||||||
|
// Render cards for each related LabelStudioProject
|
||||||
|
const detailsDiv = document.getElementById('details');
|
||||||
|
detailsDiv.innerHTML = '';
|
||||||
|
// Find the longest label name for sizing
|
||||||
|
let maxLabelLength = 0;
|
||||||
|
relatedProjects.forEach(lp => {
|
||||||
|
const classNames = Object.keys(lp.annotationCounts || {});
|
||||||
|
classNames.forEach(className => {
|
||||||
|
if (className && className.trim() !== '' && className.length > maxLabelLength) {
|
||||||
|
maxLabelLength = className.length;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// Use ch unit for width to fit the longest text
|
||||||
|
const labelWidth = `${maxLabelLength + 2}ch`;
|
||||||
|
|
||||||
|
// Find the longest project name for sizing
|
||||||
|
let maxProjectNameLength = 0;
|
||||||
|
relatedProjects.forEach(lp => {
|
||||||
|
const nameLength = (lp.title || String(lp.project_id)).length;
|
||||||
|
if (nameLength > maxProjectNameLength) maxProjectNameLength = nameLength;
|
||||||
|
});
|
||||||
|
const projectNameWidth = `${maxProjectNameLength + 2}ch`;
|
||||||
|
|
||||||
|
// Find the card with the most classes
|
||||||
|
let maxClassCount = 0;
|
||||||
|
relatedProjects.forEach(lp => {
|
||||||
|
const classNames = Object.keys(lp.annotationCounts || {});
|
||||||
|
if (classNames.length > maxClassCount) maxClassCount = classNames.length;
|
||||||
|
});
|
||||||
|
// Set a fixed width for the class rows container
|
||||||
|
const classRowHeight = 38; // px, adjust if needed
|
||||||
|
const classRowsWidth = `${maxClassCount * 180}px`;
|
||||||
|
|
||||||
|
relatedProjects.forEach(lp => {
|
||||||
|
// Get original class names from annotationCounts
|
||||||
|
const classNames = Object.keys(lp.annotationCounts || {});
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'card';
|
||||||
|
card.style.margin = '18px 0';
|
||||||
|
card.style.padding = '18px';
|
||||||
|
card.style.borderRadius = '12px';
|
||||||
|
card.style.background = '#f5f5f5';
|
||||||
|
card.style.boxShadow = '0 2px 8px rgba(0,0,0,0.04)';
|
||||||
|
// Extra div for project name
|
||||||
|
const nameDiv = document.createElement('div');
|
||||||
|
nameDiv.textContent = lp.title || lp.project_id;
|
||||||
|
nameDiv.style.fontSize = '1.2em';
|
||||||
|
nameDiv.style.fontWeight = 'bold';
|
||||||
|
nameDiv.style.marginBottom = '12px';
|
||||||
|
nameDiv.style.background = '#eaf7fa';
|
||||||
|
nameDiv.style.padding = '8px 16px';
|
||||||
|
nameDiv.style.borderRadius = '8px';
|
||||||
|
nameDiv.style.width = projectNameWidth;
|
||||||
|
nameDiv.style.minWidth = projectNameWidth;
|
||||||
|
nameDiv.style.maxWidth = projectNameWidth;
|
||||||
|
nameDiv.style.display = 'inline-block';
|
||||||
|
card.appendChild(nameDiv);
|
||||||
|
|
||||||
|
// Container for class rows
|
||||||
|
const classRowsDiv = document.createElement('div');
|
||||||
|
classRowsDiv.style.display = 'inline-block';
|
||||||
|
classRowsDiv.style.verticalAlign = 'top';
|
||||||
|
classRowsDiv.style.width = classRowsWidth;
|
||||||
|
|
||||||
|
classNames.forEach(className => {
|
||||||
|
// Row for class name and dropdown
|
||||||
|
const row = document.createElement('div');
|
||||||
|
row.className = 'class-row'; // Mark as class row
|
||||||
|
row.style.display = 'flex';
|
||||||
|
row.style.alignItems = 'center';
|
||||||
|
row.style.marginBottom = '10px';
|
||||||
|
// Original class name
|
||||||
|
const labelSpan = document.createElement('span');
|
||||||
|
labelSpan.textContent = className;
|
||||||
|
labelSpan.style.fontWeight = 'bold';
|
||||||
|
labelSpan.style.marginRight = '16px';
|
||||||
|
labelSpan.style.width = labelWidth;
|
||||||
|
labelSpan.style.minWidth = labelWidth;
|
||||||
|
labelSpan.style.maxWidth = labelWidth;
|
||||||
|
labelSpan.style.display = 'inline-block';
|
||||||
|
// Dropdown for reassigning
|
||||||
|
const select = document.createElement('select');
|
||||||
|
select.style.marginLeft = '8px';
|
||||||
|
select.style.padding = '4px 8px';
|
||||||
|
select.style.borderRadius = '6px';
|
||||||
|
select.style.border = '1px solid #009eac';
|
||||||
|
// Add blank item
|
||||||
|
const blankOption = document.createElement('option');
|
||||||
|
blankOption.value = '';
|
||||||
|
blankOption.textContent = '';
|
||||||
|
select.appendChild(blankOption);
|
||||||
|
storedClasses.forEach(cls => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = cls;
|
||||||
|
option.textContent = cls;
|
||||||
|
select.appendChild(option);
|
||||||
|
});
|
||||||
|
row.appendChild(labelSpan);
|
||||||
|
row.appendChild(select);
|
||||||
|
classRowsDiv.appendChild(row);
|
||||||
|
});
|
||||||
|
card.appendChild(classRowsDiv);
|
||||||
|
// Description field (right side, last element)
|
||||||
|
const descDiv = document.createElement('div');
|
||||||
|
descDiv.className = 'card-description';
|
||||||
|
descDiv.style.flex = '1';
|
||||||
|
descDiv.style.marginLeft = '32px';
|
||||||
|
descDiv.style.display = 'flex';
|
||||||
|
descDiv.style.flexDirection = 'column';
|
||||||
|
descDiv.style.justifyContent = 'flex-start';
|
||||||
|
descDiv.style.alignItems = 'flex-start';
|
||||||
|
descDiv.style.width = '220px';
|
||||||
|
// Add a label and textarea for description
|
||||||
|
const descLabel = document.createElement('label');
|
||||||
|
descLabel.textContent = 'Description:';
|
||||||
|
descLabel.style.fontWeight = 'bold';
|
||||||
|
descLabel.style.marginBottom = '4px';
|
||||||
|
const descTextarea = document.createElement('textarea');
|
||||||
|
descTextarea.style.width = '220px';
|
||||||
|
descTextarea.style.height = '48px';
|
||||||
|
descTextarea.style.borderRadius = '6px';
|
||||||
|
descTextarea.style.border = '1px solid #009eac';
|
||||||
|
descTextarea.style.padding = '6px';
|
||||||
|
descTextarea.style.resize = 'none';
|
||||||
|
descTextarea.value = lp.description || '';
|
||||||
|
descDiv.appendChild(descLabel);
|
||||||
|
descDiv.appendChild(descTextarea);
|
||||||
|
card.appendChild(descDiv);
|
||||||
|
detailsDiv.appendChild(card);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add Next button at the bottom right of the page
|
||||||
|
const nextBtn = document.createElement('button');
|
||||||
|
nextBtn.id = 'next-btn';
|
||||||
|
nextBtn.className = 'button';
|
||||||
|
nextBtn.textContent = 'Next';
|
||||||
|
nextBtn.style.position = 'fixed';
|
||||||
|
nextBtn.style.right = '32px';
|
||||||
|
nextBtn.style.bottom = '32px';
|
||||||
|
nextBtn.style.zIndex = '1000';
|
||||||
|
document.body.appendChild(nextBtn);
|
||||||
|
|
||||||
|
// Next button click handler: collect class mappings and update TrainingProjectDetails
|
||||||
|
nextBtn.addEventListener('click', () => {
|
||||||
|
// Array of arrays: [[labelStudioProjectId, [[originalClass, mappedClass], ...]], ...]
|
||||||
|
const mappings = [];
|
||||||
|
const descriptions = [];
|
||||||
|
detailsDiv.querySelectorAll('.card').forEach((card, idx) => {
|
||||||
|
const projectId = relatedProjects[idx].project_id;
|
||||||
|
const classMap = [];
|
||||||
|
// Only iterate over actual class rows
|
||||||
|
card.querySelectorAll('.class-row').forEach(row => {
|
||||||
|
const labelSpan = row.querySelector('span');
|
||||||
|
const select = row.querySelector('select');
|
||||||
|
if (labelSpan && select) {
|
||||||
|
const className = labelSpan.textContent.trim();
|
||||||
|
const mappedValue = select.value.trim();
|
||||||
|
if (className !== '' && mappedValue !== '') {
|
||||||
|
classMap.push([className, mappedValue]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mappings.push([projectId, classMap]);
|
||||||
|
// Get description from textarea
|
||||||
|
const descTextarea = card.querySelector('textarea');
|
||||||
|
descriptions.push([projectId, descTextarea ? descTextarea.value : '']);
|
||||||
|
});
|
||||||
|
// Update TrainingProjectDetails in DB
|
||||||
|
fetch('/api/training-project-details', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
project_id: Number(trainingProjectId),
|
||||||
|
class_map: mappings,
|
||||||
|
description: descriptions // array of [projectId, description]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
alert('Class assignments and descriptions updated!');
|
||||||
|
console.log(data);
|
||||||
|
// Redirect to start-training.html with id
|
||||||
|
window.location.href = `/edit-training.html?id=${trainingProjectId}`;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
alert('Error updating class assignments or descriptions');
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
107
js/start-training.js
Normal file
107
js/start-training.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// Render helper descriptions for YOLOX settings and handle form submission
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
// Get the form element at the top
|
||||||
|
const form = document.getElementById('settings-form');
|
||||||
|
|
||||||
|
// Auto-set num_classes from training_project classes array
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const projectId = urlParams.get('id');
|
||||||
|
if (projectId && form) {
|
||||||
|
fetch('/api/training-projects')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(projects => {
|
||||||
|
const project = projects.find(p => p.project_id == projectId || p.id == projectId);
|
||||||
|
if (project && project.classes) {
|
||||||
|
let classesArr = project.classes;
|
||||||
|
// If classes is a stringified JSON, parse it
|
||||||
|
if (typeof classesArr === 'string') {
|
||||||
|
try {
|
||||||
|
classesArr = JSON.parse(classesArr);
|
||||||
|
} catch (e) {
|
||||||
|
classesArr = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let numClasses = 0;
|
||||||
|
if (Array.isArray(classesArr)) {
|
||||||
|
numClasses = classesArr.length;
|
||||||
|
} else if (typeof classesArr === 'object' && classesArr !== null) {
|
||||||
|
numClasses = Object.keys(classesArr).length;
|
||||||
|
}
|
||||||
|
// Fix: Only set num_classes if input exists
|
||||||
|
const numClassesInput = form.querySelector('[name="num_classes"]');
|
||||||
|
if (numClassesInput) {
|
||||||
|
numClassesInput.value = numClasses;
|
||||||
|
numClassesInput.readOnly = true;
|
||||||
|
numClassesInput.dispatchEvent(new Event('input'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle form submission
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
console.log("Form submitted");
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const settings = {};
|
||||||
|
let fileToUpload = null;
|
||||||
|
for (const [key, value] of formData.entries()) {
|
||||||
|
if (key === 'model_upload' && form.elements[key].files.length > 0) {
|
||||||
|
fileToUpload = form.elements[key].files[0];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (key === 'ema' || key === 'enable_mixup') {
|
||||||
|
settings[key] = form.elements[key].checked;
|
||||||
|
} else if (key === 'scale' || key === 'mosaic_scale') {
|
||||||
|
settings[key] = value.split(',').map(v => parseFloat(v.trim()));
|
||||||
|
} else if (!isNaN(value) && value !== '') {
|
||||||
|
settings[key] = parseFloat(value);
|
||||||
|
} else {
|
||||||
|
settings[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Attach project id from URL
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const projectId = urlParams.get('id');
|
||||||
|
if (projectId) settings.project_id = Number(projectId);
|
||||||
|
|
||||||
|
// First, send settings JSON (without file)
|
||||||
|
fetch('/api/yolox-settings', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(settings)
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
// If file selected, send it as binary
|
||||||
|
if (fileToUpload) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function(e) {
|
||||||
|
fetch(`/api/yolox-settings/upload?project_id=${settings.project_id}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/octet-stream' },
|
||||||
|
body: e.target.result
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data2 => {
|
||||||
|
alert('YOLOX settings and model file saved!');
|
||||||
|
window.location.href = `/overview-training.html?id=${settings.project_id}`;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
alert('Error uploading model file');
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(fileToUpload);
|
||||||
|
} else {
|
||||||
|
alert('YOLOX settings saved!');
|
||||||
|
window.location.href = `/overview-training.html?id=${settings.project_id}`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
alert('Error saving YOLOX settings');
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
11
js/storage.js
Normal file
11
js/storage.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// js/storage.js
|
||||||
|
export function getStoredProjects() {
|
||||||
|
try {
|
||||||
|
return JSON.parse(localStorage.getItem('ls_projects') || '{}');
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function setStoredProjects(projectsObj) {
|
||||||
|
localStorage.setItem('ls_projects', JSON.stringify(projectsObj));
|
||||||
|
}
|
||||||
BIN
media/logo.png
Normal file
BIN
media/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
254
overview-training.html
Normal file
254
overview-training.html
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="globals.css" />
|
||||||
|
<link rel="stylesheet" href="styleguide.css" />
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
<style>
|
||||||
|
#projects-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body onload="pollStatus()">
|
||||||
|
<div>
|
||||||
|
<div id="header">
|
||||||
|
<icon class="header-icon" onclick="window.location.href='/index.html'" , onmouseover="" 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;">Project</label>
|
||||||
|
<div class="button-row">
|
||||||
|
<button id="Add Training Project" onclick="window.location.href='/add-project.html'" class="button-red">Add
|
||||||
|
Training Project</button>
|
||||||
|
<button id="seed-db-btn" class="button">
|
||||||
|
Seed Database
|
||||||
|
<div class="loader" id="loader" style="display: none"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button id="generate-yolox-json-btn" class="button">
|
||||||
|
Generate YOLOX JSON
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</button>
|
||||||
|
<button id="setup-details" class="button">
|
||||||
|
Show Details
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('seed-db-btn').addEventListener('click', function () {
|
||||||
|
const elLoader = document.getElementById("loader")
|
||||||
|
elLoader.style.display = "inherit"
|
||||||
|
|
||||||
|
fetch('/api/seed')
|
||||||
|
.finally(() => {
|
||||||
|
// Instead of hiding loader immediately, poll /api/update-status until done
|
||||||
|
function pollStatus() {
|
||||||
|
fetch('/api/update-status')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(status => {
|
||||||
|
if (status && status.running) {
|
||||||
|
// Still running, poll again after short delay
|
||||||
|
setTimeout(pollStatus, 5000);
|
||||||
|
} else {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pollStatus();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show loader if backend is still processing on page load
|
||||||
|
|
||||||
|
function pollStatus() {
|
||||||
|
const elLoader = document.getElementById("loader");
|
||||||
|
fetch('/api/update-status')
|
||||||
|
.then(res => res.json())
|
||||||
|
|
||||||
|
.then(status => {
|
||||||
|
if (status && status.running) {
|
||||||
|
elLoader.style.display = "inherit";
|
||||||
|
setTimeout(pollStatus, 5000);
|
||||||
|
} else {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
// Declare urlParams and projectId once
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const projectId = urlParams.get('id');
|
||||||
|
// Set project title in header
|
||||||
|
fetch('/api/training-projects')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(projects => {
|
||||||
|
const project = projects.find(p => p.project_id == projectId || p.id == projectId);
|
||||||
|
if (project) {
|
||||||
|
const titleLabel = document.getElementById('project-title-label');
|
||||||
|
if (titleLabel) titleLabel.textContent = '/' + project.title;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Render trainings
|
||||||
|
function renderTrainings(trainings) {
|
||||||
|
const list = document.getElementById('projects-list');
|
||||||
|
list.innerHTML = '';
|
||||||
|
if (!Array.isArray(trainings) || trainings.length === 0) {
|
||||||
|
list.innerHTML = '<div style="color:#009eac;padding:16px;">No trainings found for this project.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
trainings.forEach(training => {
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'dataset-card';
|
||||||
|
card.style = 'border:1px solid #009eac;padding:12px;margin:8px;border-radius:8px;background:#eaf7fa;position:relative;min-width:320px;min-height:120px;display:flex;flex-direction:row;justify-content:space-between;align-items:stretch;';
|
||||||
|
|
||||||
|
// Info section (left)
|
||||||
|
const infoDiv = document.createElement('div');
|
||||||
|
infoDiv.style = 'flex:1; text-align:left;';
|
||||||
|
infoDiv.innerHTML = `<b>${training.exp_name || 'Training'}</b><br>Epochs: ${training.max_epoch}<br>Depth: ${training.depth}<br>Width: ${training.width}<br>Activation: ${training.activation || training.act || ''}`;
|
||||||
|
|
||||||
|
// Buttons section (right)
|
||||||
|
const btnDiv = document.createElement('div');
|
||||||
|
btnDiv.style = 'display:flex;flex-direction:column;align-items:flex-end;gap:8px;min-width:160px;';
|
||||||
|
|
||||||
|
// Start Training button
|
||||||
|
const startBtn = document.createElement('button');
|
||||||
|
startBtn.textContent = 'Start YOLOX Training';
|
||||||
|
startBtn.style = 'background:#009eac;color:white;border:none;border-radius:6px;padding:6px 12px;cursor:pointer;';
|
||||||
|
startBtn.onclick = function() {
|
||||||
|
startBtn.disabled = true;
|
||||||
|
fetch('/api/start-yolox-training', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ project_id: projectId, training_id: training.id })
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(result => {
|
||||||
|
alert(result.message || 'Training started');
|
||||||
|
startBtn.disabled = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
alert('Failed to start training');
|
||||||
|
startBtn.disabled = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
btnDiv.appendChild(startBtn);
|
||||||
|
|
||||||
|
// View Log button
|
||||||
|
const logBtn = document.createElement('button');
|
||||||
|
logBtn.textContent = 'View Training Log';
|
||||||
|
logBtn.style = 'background:#666;color:white;border:none;border-radius:6px;padding:6px 12px;cursor:pointer;';
|
||||||
|
logBtn.onclick = function() {
|
||||||
|
showLogModal(training.id);
|
||||||
|
};
|
||||||
|
btnDiv.appendChild(logBtn);
|
||||||
|
|
||||||
|
// Remove button
|
||||||
|
const removeBtn = document.createElement('button');
|
||||||
|
removeBtn.textContent = 'Remove';
|
||||||
|
removeBtn.style = 'background:#ff4d4f;color:white;border:none;border-radius:6px;padding:6px 12px;cursor:pointer;';
|
||||||
|
removeBtn.onclick = function() {
|
||||||
|
if (confirm('Are you sure you want to delete this training?')) {
|
||||||
|
fetch(`/api/trainings/${training.id}`, { method: 'DELETE' })
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(result => {
|
||||||
|
alert(result.message || 'Training deleted');
|
||||||
|
fetchTrainings(); // Refresh list
|
||||||
|
})
|
||||||
|
.catch(() => alert('Failed to delete training'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
btnDiv.appendChild(removeBtn);
|
||||||
|
|
||||||
|
card.appendChild(infoDiv);
|
||||||
|
card.appendChild(btnDiv);
|
||||||
|
list.appendChild(card);
|
||||||
|
});
|
||||||
|
// Modal for log display
|
||||||
|
if (!document.getElementById('log-modal')) {
|
||||||
|
const modal = document.createElement('div');
|
||||||
|
modal.id = 'log-modal';
|
||||||
|
modal.style = 'display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;background:rgba(0,0,0,0.5);z-index:9999;justify-content:center;align-items:center;';
|
||||||
|
modal.innerHTML = `<div style="background:#fff;padding:24px;border-radius:8px;max-width:800px;width:90vw;max-height:80vh;overflow:auto;position:relative;"><pre id='log-content' style='font-size:13px;white-space:pre-wrap;word-break:break-all;max-height:60vh;overflow:auto;background:#f7f7f7;padding:12px;border-radius:6px;'></pre><button id='close-log-modal' style='position:absolute;top:8px;right:8px;background:#009eac;color:#fff;border:none;border-radius:4px;padding:6px 12px;cursor:pointer;'>Close</button></div>`;
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
document.getElementById('close-log-modal').onclick = function() {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to show log modal and poll log
|
||||||
|
function showLogModal(trainingId) {
|
||||||
|
const modal = document.getElementById('log-modal');
|
||||||
|
const logContent = document.getElementById('log-content');
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
function fetchLog() {
|
||||||
|
fetch(`/api/training-log?project_id=${projectId}&training_id=${trainingId}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
logContent.textContent = data.log || 'No log found.';
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
logContent.textContent = 'Failed to fetch log.';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fetchLog();
|
||||||
|
// Poll every 5 seconds while modal is open
|
||||||
|
let poller = setInterval(() => {
|
||||||
|
if (modal.style.display === 'flex') fetchLog();
|
||||||
|
else clearInterval(poller);
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fetch trainings for project
|
||||||
|
function fetchTrainings() {
|
||||||
|
if (!projectId) return;
|
||||||
|
fetch(`/api/trainings?project_id=${projectId}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(trainings => {
|
||||||
|
renderTrainings(trainings);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
window.addEventListener('DOMContentLoaded', fetchTrainings);
|
||||||
|
document.getElementById('generate-yolox-json-btn').addEventListener('click', function () {
|
||||||
|
fetch('/api/generate-yolox-json', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ project_id: projectId })
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(result => {
|
||||||
|
alert('YOLOX JSON generated!');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
alert('Failed to generate YOLOX JSON');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<div id="projects-list"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
116
project-details.html
Normal file
116
project-details.html
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="globals.css" />
|
||||||
|
<link rel="stylesheet" href="styleguide.css" />
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
<style>
|
||||||
|
#projects-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body onload="pollStatus()">
|
||||||
|
<div>
|
||||||
|
<div id="header">
|
||||||
|
<icon class="header-icon" onclick="window.location.href='/index.html'" , onmouseover=""
|
||||||
|
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>
|
||||||
|
<div class="button-row">
|
||||||
|
<button id="Add Training Project" onclick="window.location.href='/add-project.html'"
|
||||||
|
class="button-red">Add Training Project</button>
|
||||||
|
<button id="seed-db-btn" class="button">
|
||||||
|
Seed Database
|
||||||
|
<div class="loader" id="loader" style="display: none"></div>
|
||||||
|
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="projects-list">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.getElementById('seed-db-btn').addEventListener('click', function () {
|
||||||
|
const elLoader = document.getElementById("loader")
|
||||||
|
elLoader.style.display = "inherit"
|
||||||
|
|
||||||
|
fetch('/api/seed')
|
||||||
|
.finally(() => {
|
||||||
|
// Instead of hiding loader immediately, poll /api/update-status until done
|
||||||
|
function pollStatus() {
|
||||||
|
fetch('/api/update-status')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(status => {
|
||||||
|
if (status && status.running) {
|
||||||
|
// Still running, poll again after short delay
|
||||||
|
setTimeout(pollStatus, 5000);
|
||||||
|
} else {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pollStatus();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show loader if backend is still processing on page load
|
||||||
|
|
||||||
|
function pollStatus() {
|
||||||
|
const elLoader = document.getElementById("loader");
|
||||||
|
fetch('/api/update-status')
|
||||||
|
.then(res => res.json())
|
||||||
|
|
||||||
|
.then(status => {
|
||||||
|
if (status && status.running) {
|
||||||
|
elLoader.style.display = "inherit";
|
||||||
|
setTimeout(pollStatus, 5000);
|
||||||
|
} else {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const projectId = urlParams.get('id');
|
||||||
|
fetch('/api/training-projects')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(projects => {
|
||||||
|
const project = projects.find(p => p.project_id == projectId || p.id == projectId);
|
||||||
|
if (project) {
|
||||||
|
document.getElementById('project-title-label').textContent = '/' + project.title;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script src="./js/dashboard-label-studio.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
109
setup-training-project.html
Normal file
109
setup-training-project.html
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="globals.css" />
|
||||||
|
<link rel="stylesheet" href="styleguide.css" />
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
<style>
|
||||||
|
#projects-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body onload="pollStatus()">
|
||||||
|
<div>
|
||||||
|
<div id="header">
|
||||||
|
<icon class="header-icon" onclick="window.location.href='/index.html'" , onmouseover="" 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>
|
||||||
|
<div class="button-row">
|
||||||
|
<button id="Add Training Project" onclick="window.location.href='/add-project.html'" class="button-red">Add
|
||||||
|
Training Project</button>
|
||||||
|
<button id="seed-db-btn" class="button">
|
||||||
|
Seed Database
|
||||||
|
<div class="loader" id="loader" style="display: none"></div>
|
||||||
|
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="details">
|
||||||
|
<script src="js/setup-training-project.js"></script>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.getElementById('seed-db-btn').addEventListener('click', function () {
|
||||||
|
const elLoader = document.getElementById("loader")
|
||||||
|
elLoader.style.display = "inherit"
|
||||||
|
|
||||||
|
fetch('/api/seed')
|
||||||
|
.finally(() => {
|
||||||
|
// Instead of hiding loader immediately, poll /api/update-status until done
|
||||||
|
function pollStatus() {
|
||||||
|
fetch('/api/update-status')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(status => {
|
||||||
|
if (status && status.running) {
|
||||||
|
// Still running, poll again after short delay
|
||||||
|
setTimeout(pollStatus, 5000);
|
||||||
|
} else {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pollStatus();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show loader if backend is still processing on page load
|
||||||
|
|
||||||
|
function pollStatus() {
|
||||||
|
const elLoader = document.getElementById("loader");
|
||||||
|
fetch('/api/update-status')
|
||||||
|
.then(res => res.json())
|
||||||
|
|
||||||
|
.then(status => {
|
||||||
|
if (status && status.running) {
|
||||||
|
elLoader.style.display = "inherit";
|
||||||
|
setTimeout(pollStatus, 5000);
|
||||||
|
} else {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
elLoader.style.display = "none";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const projectId = urlParams.get('id');
|
||||||
|
fetch('/api/training-projects')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(projects => {
|
||||||
|
const project = projects.find(p => p.project_id == projectId || p.id == projectId);
|
||||||
|
if (project) {
|
||||||
|
document.getElementById('project-title-label').textContent = '/' + project.title;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
0
start-training.html
Normal file
0
start-training.html
Normal file
663
style.css
Normal file
663
style.css
Normal file
@@ -0,0 +1,663 @@
|
|||||||
|
|
||||||
|
|
||||||
|
.card h1,h2,h3,p{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.card {
|
||||||
|
display: flex;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: red;
|
||||||
|
border-radius: 8px;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-container {
|
||||||
|
display: flex;
|
||||||
|
background-color: rgb(223, 223, 223);
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-container img{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info{
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
background-color: rgb(223, 223, 223);
|
||||||
|
padding: 1rem;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info .label-classes{
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2,130px);
|
||||||
|
column-gap: 1rem;
|
||||||
|
row-gap: 3px;
|
||||||
|
align-items: start;
|
||||||
|
justify-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info .label-classes p{
|
||||||
|
background-color: lightgray;
|
||||||
|
padding-inline: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card {
|
||||||
|
width: 500px;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card .div {
|
||||||
|
position: relative;
|
||||||
|
height: 120px;
|
||||||
|
background-color: #d9d9d9;
|
||||||
|
border-radius: 9px;
|
||||||
|
}
|
||||||
|
.dataset-card:hover .div {
|
||||||
|
background-color: #4a90e2; /* Change this to any color you want */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card .image {
|
||||||
|
position: absolute;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
background-color: #747474;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card .info-card {
|
||||||
|
position: absolute;
|
||||||
|
width: 362px;
|
||||||
|
height: 100px;
|
||||||
|
top: 10px;
|
||||||
|
left: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card .overlap-group {
|
||||||
|
position: relative;
|
||||||
|
width: 360px;
|
||||||
|
height: 100px;
|
||||||
|
background-color: #747373;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card .classes {
|
||||||
|
position: absolute;
|
||||||
|
width: 249px;
|
||||||
|
height: 60px;
|
||||||
|
top: 35px;
|
||||||
|
left: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card .frame {
|
||||||
|
position: relative;
|
||||||
|
height: 60px;
|
||||||
|
background-color: #747474;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card .div-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
width: 120px;
|
||||||
|
height: 16px;
|
||||||
|
top: 3px;
|
||||||
|
left: 3px;
|
||||||
|
background-color: #d9d9d9;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card .text-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
left: 5px;
|
||||||
|
font-family: var(--m3-body-small-font-family);
|
||||||
|
font-weight: var(--m3-body-small-font-weight);
|
||||||
|
color: #000000;
|
||||||
|
font-size: var(--m3-body-small-font-size);
|
||||||
|
letter-spacing: var(--m3-body-small-letter-spacing);
|
||||||
|
line-height: var(--m3-body-small-line-height);
|
||||||
|
white-space: nowrap;
|
||||||
|
font-style: var(--m3-body-small-font-style);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card .frame-2 {
|
||||||
|
position: absolute;
|
||||||
|
width: 120px;
|
||||||
|
height: 16px;
|
||||||
|
top: 22px;
|
||||||
|
left: 3px;
|
||||||
|
background-color: #d9d9d9;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card .frame-3 {
|
||||||
|
top: 41px;
|
||||||
|
left: 3px;
|
||||||
|
position: absolute;
|
||||||
|
width: 120px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: #d9d9d9;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card .frame-4 {
|
||||||
|
top: 3px;
|
||||||
|
left: 126px;
|
||||||
|
position: absolute;
|
||||||
|
width: 120px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: #d9d9d9;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card .frame-5 {
|
||||||
|
top: 22px;
|
||||||
|
left: 126px;
|
||||||
|
position: absolute;
|
||||||
|
width: 120px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: #d9d9d9;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card .frame-6 {
|
||||||
|
top: 41px;
|
||||||
|
left: 126px;
|
||||||
|
position: absolute;
|
||||||
|
width: 120px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: #d9d9d9;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataset-card .text-wrapper-2 {
|
||||||
|
position: absolute;
|
||||||
|
width: 300px;
|
||||||
|
top: 4px;
|
||||||
|
left: 14px;
|
||||||
|
font-family: var(--title2-regular-font-family);
|
||||||
|
font-weight: var(--title2-regular-font-weight);
|
||||||
|
color: #ff715e;
|
||||||
|
font-size: var(--title2-regular-font-size);
|
||||||
|
letter-spacing: var(--title2-regular-letter-spacing);
|
||||||
|
line-height: var(--title2-regular-line-height);
|
||||||
|
white-space: nowrap;
|
||||||
|
font-style: var(--title2-regular-font-style);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #009dac;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
background-color: #0095a3;
|
||||||
|
}
|
||||||
|
.button-red {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #ff0f43;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-red:hover {
|
||||||
|
background-color: #b60202;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px; /* Abstand zwischen den Buttons */
|
||||||
|
|
||||||
|
}
|
||||||
|
#header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between; /* space between title and buttons */
|
||||||
|
align-items: center;
|
||||||
|
background-color: #fff;
|
||||||
|
height: 70px;
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 2px solid #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.header-icon {
|
||||||
|
width: 222px;
|
||||||
|
height: 53px;
|
||||||
|
background-image: url("./media/logo.png");
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.popup {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%);
|
||||||
|
/* Optional box style */
|
||||||
|
background-color: white;
|
||||||
|
width: 800px;
|
||||||
|
height: 600px;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
|
||||||
|
box-shadow: 10px 10px 6.8px 3px #00000040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .image {
|
||||||
|
position: absolute;
|
||||||
|
width: 250px;
|
||||||
|
height: 250px;
|
||||||
|
top: 43px;
|
||||||
|
left: 43px;
|
||||||
|
background-color: #d9d9d9;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .upload-button {
|
||||||
|
width: 80px;
|
||||||
|
height: 25px;
|
||||||
|
background-color: #009dac;
|
||||||
|
border-radius: 5px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 303px;
|
||||||
|
left: 128px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .upload-button:hover{
|
||||||
|
width: 80px;
|
||||||
|
height: 25px;
|
||||||
|
background-color: #0095a3;
|
||||||
|
border-radius: 5px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 303px;
|
||||||
|
left: 128px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .upload-button-text {
|
||||||
|
font-family: var(--m3-body-small-font-family);
|
||||||
|
font-weight: var(--m3-body-small-font-weight);
|
||||||
|
color: #FFFF;
|
||||||
|
font-size: var(--m3-body-small-font-size);
|
||||||
|
letter-spacing: var(--m3-body-small-letter-spacing);
|
||||||
|
line-height: var(--m3-body-small-line-height);
|
||||||
|
white-space: nowrap;
|
||||||
|
font-style: var(--m3-body-small-font-style);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .div {
|
||||||
|
position: absolute;
|
||||||
|
width: 395px;
|
||||||
|
height: 501px;
|
||||||
|
top: 43px;
|
||||||
|
left: 343px;
|
||||||
|
background-color: #d9d9d9;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .add-category {
|
||||||
|
position: absolute;
|
||||||
|
width: 335px;
|
||||||
|
height: 25px;
|
||||||
|
top: 144px;
|
||||||
|
left: 30px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .div-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 240px;
|
||||||
|
height: 25px;
|
||||||
|
top: 0;
|
||||||
|
left: 95px;
|
||||||
|
background-color: #efefef;
|
||||||
|
border-radius: 5px;
|
||||||
|
border-color: #009dac;
|
||||||
|
outline: none;
|
||||||
|
font-family: var(--m3-body-small-font-family);
|
||||||
|
font-size: var(--m3-body-small-font-size);
|
||||||
|
line-height: var(--m3-body-small-line-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .div-wrapper:focus {
|
||||||
|
border-color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .div-wrapper::placeholder {
|
||||||
|
color: #000;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .div-wrapper:focus::placeholder {
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.popup .upload-button-text-wrapper {
|
||||||
|
all: unset;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 80px;
|
||||||
|
height: 25px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: #009dac;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.popup .upload-button-text-wrapper:hover {
|
||||||
|
all: unset;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 80px;
|
||||||
|
height: 25px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: #0095a3;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .confirm-button-datasetcreation {
|
||||||
|
all: unset;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 59px;
|
||||||
|
width: 80px;
|
||||||
|
height: 25px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
background-color: #009dac;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.popup .confirm-button-datasetcreation:hover {
|
||||||
|
all: unset;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 59px;
|
||||||
|
width: 80px;
|
||||||
|
height: 25px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
background-color: #0095a3;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .confirm-button-datasetcreation:disabled {
|
||||||
|
all: unset;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 59px;
|
||||||
|
width: 80px;
|
||||||
|
height: 25px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
background-color: #66c4cd;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .button-text-upload {
|
||||||
|
|
||||||
|
font-family: var(--m3-body-small-font-family);
|
||||||
|
font-weight: var(--m3-body-small-font-weight);
|
||||||
|
color: #FFFF;
|
||||||
|
font-size: var(--m3-body-small-font-size);
|
||||||
|
letter-spacing: var(--m3-body-small-letter-spacing);
|
||||||
|
line-height: var(--m3-body-small-line-height);
|
||||||
|
white-space: nowrap;
|
||||||
|
font-style: var(--m3-body-small-font-style);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .project-name {
|
||||||
|
position: absolute;
|
||||||
|
width: 335px;
|
||||||
|
height: 25px;
|
||||||
|
top: 27px;
|
||||||
|
left: 30px;
|
||||||
|
background-color: #efefef;
|
||||||
|
border-radius: 5px;
|
||||||
|
border-color: #009dac;
|
||||||
|
outline: none;
|
||||||
|
font-family: var(--m3-body-small-font-family);
|
||||||
|
font-size: var(--m3-body-small-font-size);
|
||||||
|
line-height: var(--m3-body-small-line-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .project-name:focus {
|
||||||
|
border-color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .project-name::placeholder {
|
||||||
|
color: #000;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .project-name:focus::placeholder {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.popup .add-description {
|
||||||
|
position: absolute;
|
||||||
|
width: 335px;
|
||||||
|
height: 50px;
|
||||||
|
top: 73px;
|
||||||
|
left: 30px;
|
||||||
|
background-color: #efefef;
|
||||||
|
border-radius: 5px;
|
||||||
|
border-color: #009dac;
|
||||||
|
padding: 4px;
|
||||||
|
outline: none;
|
||||||
|
resize: none;
|
||||||
|
font-family: var(--m3-body-small-font-family);
|
||||||
|
font-size: var(--m3-body-small-font-size);
|
||||||
|
line-height: var(--m3-body-small-line-height);
|
||||||
|
}
|
||||||
|
.popup .add-description:focus {
|
||||||
|
border-color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .add-description::placeholder {
|
||||||
|
color: #000;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .add-description:focus::placeholder {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.popup .add-class-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
width: 353px;
|
||||||
|
height: 302px;
|
||||||
|
top: 183px;
|
||||||
|
left: 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .add-class {
|
||||||
|
position: relative;
|
||||||
|
width: 335px;
|
||||||
|
height: 25px;
|
||||||
|
top: 9px;
|
||||||
|
left: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .overlap-group {
|
||||||
|
position: absolute;
|
||||||
|
width: 275px;
|
||||||
|
height: 25px;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: #30bffc80;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .overlap {
|
||||||
|
position: absolute;
|
||||||
|
width: 50px;
|
||||||
|
height: 25px;
|
||||||
|
left: 285px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .text-wrapper-overlap {
|
||||||
|
position: absolute;
|
||||||
|
top: 4%;
|
||||||
|
left: 5px;
|
||||||
|
font-family: var(--m3-body-small-font-family);
|
||||||
|
font-weight: var(--m3-body-small-font-weight);
|
||||||
|
color: #000000;
|
||||||
|
font-size: var(--m3-body-small-font-size);
|
||||||
|
letter-spacing: var(--m3-body-small-letter-spacing);
|
||||||
|
line-height: var(--m3-body-small-line-height);
|
||||||
|
white-space: nowrap;
|
||||||
|
font-style: var(--m3-body-small-font-style);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .rectangle {
|
||||||
|
position: absolute;
|
||||||
|
width: 50px;
|
||||||
|
height: 25px;
|
||||||
|
background-color: #ff0f43;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .rectangle:hover {
|
||||||
|
position: absolute;
|
||||||
|
width: 50px;
|
||||||
|
height: 25px;
|
||||||
|
background-color: #bb032b;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .text-wrapper-4 {
|
||||||
|
position: absolute;
|
||||||
|
top: -18px;
|
||||||
|
left: 18px;
|
||||||
|
font-family: var(--m3-display-large-font-family);
|
||||||
|
font-weight: var(--m3-display-large-font-weight);
|
||||||
|
color: #000000;
|
||||||
|
font-size: var(--minus-for-button-size);
|
||||||
|
letter-spacing: var(--m3-display-large-letter-spacing);
|
||||||
|
line-height: var(--m3-display-large-line-height);
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
font-style: var(--m3-display-large-font-style);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.centered-div {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%);
|
||||||
|
/* Optional box style */
|
||||||
|
background-color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
top: 4px;
|
||||||
|
left: 43%;
|
||||||
|
border: 5px solid #f3f3f3; /* Light grey background */
|
||||||
|
border-top: 5px solid #3498db; /* Blue top to create the spinning effect */
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
16
styleguide.css
Normal file
16
styleguide.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
:root {
|
||||||
|
--m3-body-small-font-family: "Roboto", Helvetica;
|
||||||
|
--m3-body-small-font-weight: 400;
|
||||||
|
--m3-body-small-font-size: 12px;
|
||||||
|
--m3-body-small-letter-spacing: 0.4000000059604645px;
|
||||||
|
--m3-body-small-line-height: 16px;
|
||||||
|
--m3-body-small-font-style: normal;
|
||||||
|
--title2-regular-font-family: "SF Pro", Helvetica;
|
||||||
|
--title2-regular-font-weight: 400;
|
||||||
|
--title2-regular-font-size: 22px;
|
||||||
|
--title2-regular-letter-spacing: -0.25999999046325684px;
|
||||||
|
--title2-regular-line-height: 28px;
|
||||||
|
--title2-regular-font-style: normal;
|
||||||
|
|
||||||
|
--minus-for-button-size: 30px;
|
||||||
|
}
|
||||||
40
text.css
Normal file
40
text.css
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
.popup .upload-button {
|
||||||
|
width: 80px;
|
||||||
|
height: 25px;
|
||||||
|
background-color: #4cdb0085;
|
||||||
|
border-radius: 5px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 303px;
|
||||||
|
left: 128px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .upload-button:hover{
|
||||||
|
width: 80px;
|
||||||
|
height: 25px;
|
||||||
|
background-color: #36990085;
|
||||||
|
border-radius: 5px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 303px;
|
||||||
|
left: 128px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup .upload-button-text {
|
||||||
|
font-family: var(--m3-body-small-font-family);
|
||||||
|
font-weight: var(--m3-body-small-font-weight);
|
||||||
|
color: #000000;
|
||||||
|
font-size: var(--m3-body-small-font-size);
|
||||||
|
letter-spacing: var(--m3-body-small-letter-spacing);
|
||||||
|
line-height: var(--m3-body-small-line-height);
|
||||||
|
white-space: nowrap;
|
||||||
|
font-style: var(--m3-body-small-font-style);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user