This commit is contained in:
IDONTSUDO 2024-04-25 12:22:21 +03:00
parent e005a42254
commit e155b4a2a1
26 changed files with 468 additions and 36 deletions

View file

@ -1,7 +1,9 @@
{
"cSpell.words": [
"Ведите",
"навык",
"Навыки",
"skils",
"typedataset"
]
}

View file

@ -38,8 +38,9 @@ git clone https://gitlab.com/robossembler/webservice
Для работы Генератора Датасетов нужно задать следующие переменные в окружении `bash`
```bash
export PYTHON_BLENDER="путь_к_директории_сайлами_из_rcg_pipeline"
export PYTHON_BLENDER_PROC="путь_к_генераторуатасетов_renderBOPdataset.py"
export PYTHON_BLENDER="/путь_к_директории_сайлами_из/rcg_pipeline"
export PYTHON_BLENDER_PROC="/путь_к_генераторуатасетов_/renderBOPdataset.py"
export PYTHON_EDUCATION="absolute_path/webp/education.py"
```
## Запуск сервера

View file

@ -1,9 +1,15 @@
import { BehaviorTreesPresentation } from "../../features/behavior_trees/behavior_trees";
import { DatasetsPresentation } from "../../features/datasets/datasets_presentation";
import { WeightsPresentation } from "../../features/weights/weights_presentation";
import { ProjectsPresentation } from "../../features/projects/projects_presentation";
import { extensions } from "../extensions/extensions";
import { Routes } from "../interfaces/router";
extensions();
export const httpRoutes: Routes[] = [new ProjectsPresentation(), new DatasetsPresentation(), new BehaviorTreesPresentation()].map((el) => el.call());
export const httpRoutes: Routes[] = [
new ProjectsPresentation(),
new DatasetsPresentation(),
new BehaviorTreesPresentation(),
new WeightsPresentation(),
].map((el) => el.call());

View file

@ -9,11 +9,15 @@ import { IProjectModel, ProjectDBModel } from "../../_projects/models/project_da
import { DatasetDBModel } from "../models/dataset_database_model";
import { DatasetValidationModel, ProcessStatus } from "../models/dataset_validation_model";
export class ProcessWatcherAndDatabaseUpdateService extends TypedEvent<Result<ExecError | SpawnError, ExecutorResult>> {
export class ProcessWatcherAndDatabaseUpdateService<A> extends TypedEvent<
Result<ExecError | SpawnError, ExecutorResult>
> {
databaseId: ObjectId;
constructor(databaseId: ObjectId) {
model: A;
constructor(databaseId: ObjectId, model: A) {
super();
this.databaseId = databaseId;
this.model = model;
this.on((event) => this.lister(event));
}
@ -21,7 +25,9 @@ export class ProcessWatcherAndDatabaseUpdateService extends TypedEvent<Result<Ex
event.fold(
async (success) => {
if (success.event == EXEC_EVENT.END) {
const dbModel = await DatasetDBModel.findById(this.databaseId);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
const dbModel = await this.model.findById(this.databaseId);
if (dbModel !== null) {
dbModel.local_path;
dbModel.processStatus = ProcessStatus.END;
@ -31,7 +37,9 @@ export class ProcessWatcherAndDatabaseUpdateService extends TypedEvent<Result<Ex
}
},
async (error) => {
const dbModel = await DatasetDBModel.findById(this.databaseId);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
const dbModel = await this.model.findById(this.databaseId);
if (dbModel !== null) {
dbModel.processStatus = ProcessStatus.ERROR;
dbModel.processLogs = error.message;

View file

@ -14,14 +14,13 @@ export class ExecDatasetProcessScenario extends CallbackStrategyWithIdQuery {
return (await new ReadByIdDataBaseModelUseCase<IDatasetModel>(DatasetDBModel).call(id)).map(async (model) => {
return (await new IsHaveActiveProcessUseCase().call()).map(async () => {
await DatasetDBModel.findById(id).updateOne({ processStatus: "RUN" });
console.log(`blenderproc run $PYTHON_BLENDER_PROC --cfg '${JSON.stringify(model)}'`);
return new ExecProcessUseCase().call(
`${model.project.rootDir}/`,
`blenderproc run $PYTHON_BLENDER_PROC --cfg '${JSON.stringify(model)}'`,
id,
new ProcessWatcherAndDatabaseUpdateService(id as unknown as ObjectId)
new ProcessWatcherAndDatabaseUpdateService(id as unknown as ObjectId, DatasetDBModel)
);
});
});
};
}
}

View file

@ -1,7 +1,7 @@
import { Mongoose, Schema, model } from "mongoose";
import { Schema, model } from "mongoose";
import { IDatasetModel } from "./dataset_validation_model";
import { projectSchema } from "../../_projects/models/project_database_model";
export const DatasetSchema = new Schema({
name: {
type: String,

View file

@ -0,0 +1,32 @@
import { ObjectId } from "mongoose";
import { CallbackStrategyWithIdQuery, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
import { ExecProcessUseCase, IsHaveActiveProcessUseCase } from "../../../core/usecases/exec_process_usecase";
import { ReadByIdDataBaseModelUseCase } from "../../../core/usecases/read_by_id_database_model_usecase";
import { MongoIdValidation } from "../../../core/validations/mongo_id_validation";
import { ProcessWatcherAndDatabaseUpdateService } from "../../datasets/domain/create_dataset_scenario";
import { WeightDBModel, IWeightModel } from "../models/weights_validation_model";
export class ExecWeightProcessScenario extends CallbackStrategyWithIdQuery {
idValidationExpression = new MongoIdValidation();
call = async (id: string): ResponseBase => {
return (await new ReadByIdDataBaseModelUseCase<IWeightModel>(WeightDBModel).call(id)).map(async (model) => {
return (await new IsHaveActiveProcessUseCase().call()).map(async () => {
await WeightDBModel.findById(id).updateOne({ processStatus: "RUN" });
if (typeof model.project === "object" && typeof model.datasetId === "object") {
console.log(
`python3 $PYTHON_EDUCATION --path ${model.project.rootDir} --name ${model.name} --datasetName ${model.datasetId.name}`
);
return new ExecProcessUseCase().call(
`${model.project.rootDir}/`,
`python3 $PYTHON_EDUCATION --path ${model.project.rootDir} --name ${model.name} --datasetName ${model.datasetId.name}`,
id,
new ProcessWatcherAndDatabaseUpdateService(id as unknown as ObjectId, WeightDBModel)
);
}
return Result.error("model project is not object");
});
});
};
}

View file

@ -0,0 +1,11 @@
import { IsMongoId, IsString } from "class-validator";
import { IWeightModel } from "./weights_validation_model";
export class WeightValidationModel implements IWeightModel {
@IsString()
public name: string;
@IsMongoId()
public datasetId: string;
@IsMongoId()
public project: string;
}

View file

@ -0,0 +1,49 @@
import { Schema, model } from "mongoose";
import { IProjectModel, projectSchema } from "../../_projects/models/project_database_model";
import { datasetSchema } from "../../datasets/models/dataset_database_model";
import { IDatasetModel } from "../../datasets/models/dataset_validation_model";
export interface IWeightModel {
name: string;
datasetId: string | IDatasetModel;
project: string | IProjectModel;
}
export const WeightSchema = new Schema({
name: {
type: String,
},
local_path: {
type: String,
},
neuralNetworkName: {
type: String,
},
processStatus: {
type: String,
default: "none",
},
// the user selects
isFinished: {
type: Boolean,
default: false,
},
datasetId: {
type: Schema.Types.ObjectId,
ref: datasetSchema,
autopopulate: true,
require: true,
},
project: {
type: Schema.Types.ObjectId,
ref: projectSchema,
autopopulate: true,
require: true,
},
processLogs: {
type: String,
},
}).plugin(require("mongoose-autopopulate"));
export const weightSchema = "Weight";
export const WeightDBModel = model<IWeightModel>(weightSchema, WeightSchema);

View file

@ -0,0 +1,19 @@
import { CrudController } from "../../core/controllers/crud_controller";
import { ExecWeightProcessScenario } from "./domain/exec_weights_process_scenario";
import { WeightValidationModel } from "./models/weights_database_model";
import { WeightDBModel } from "./models/weights_validation_model";
export class WeightsPresentation extends CrudController<WeightValidationModel, typeof WeightDBModel> {
constructor() {
super({
url: "weights",
validationModel: WeightValidationModel,
databaseModel: WeightDBModel,
});
this.subRoutes.push({
method: "GET",
subUrl: "exec",
fn: new ExecWeightProcessScenario(),
});
}
}

View file

@ -54,7 +54,7 @@ export const ArrayExtensions = () => {
if ([].repeat === undefined) {
// eslint-disable-next-line no-extend-native
Array.prototype.repeat = function (quantity) {
return Array(quantity).fill(this[0]);
return Array(quantity).fill(this).flat(1);
};
}
};

View file

@ -0,0 +1,3 @@
export interface IStyle{
style?: React.CSSProperties;
}

View file

@ -1,12 +1,12 @@
import * as React from "react";
import { CoreText, CoreTextType } from "../text/text";
import { IStyle } from "../../model/style";
export interface IButtonProps {
export interface IButtonProps extends IStyle {
block?: boolean;
filled?: boolean;
text?: string;
onClick?: any;
style?: React.CSSProperties;
}
export function CoreButton(props: IButtonProps) {

View file

@ -1,9 +1,9 @@
import * as React from "react";
import { Result } from "../../helper/result";
import { IStyle } from "../../model/style";
export interface IIconsProps {
export interface IIconsProps extends IStyle {
type: string;
style?: React.CSSProperties;
onClick?: Function;
}

View file

@ -1,11 +1,11 @@
import * as React from "react";
import { CoreText, CoreTextType } from "../text/text";
import { IStyle } from "../../model/style";
interface IInputProps {
interface IInputProps extends IStyle {
label: string;
value?: string;
onChange?: (value: string) => void;
style?: React.CSSProperties;
validation?: (value: string) => boolean;
error?: string;
}

View file

@ -1,13 +1,13 @@
import * as React from "react";
import { Typography } from "antd";
import { useNavigate } from "react-router-dom";
import { IStyle } from "../../model/style";
const { Link } = Typography;
export interface ILinkTypography {
export interface ILinkTypography extends IStyle {
path: string;
text: string;
style?: React.CSSProperties;
}
export const LinkTypography: React.FunctionComponent<ILinkTypography> = (

View file

@ -1,12 +1,12 @@
import React from "react";
import { CoreText, CoreTextType } from "../text/text";
import { IStyle } from "../../model/style";
interface ISelectCoreProps {
interface ISelectCoreProps extends IStyle {
items: string[];
value: string;
label: string;
onChange: (value: string) => void;
style?: React.CSSProperties;
}
export const SelectCore = (props: ISelectCoreProps) => {
const ref = React.useRef<HTMLDivElement>(null);

View file

@ -133,7 +133,6 @@ export const CardDataSet = (props: ICardDataSetProps) => {
/>):null}
{props.processStatus === ProcessStatus.RUN ? (<>
<CoreButton
onClick={() => {
if (props.type.isEqual(CardDataSetType.COMPLETED) && props.onClickButton && props.id) {
props.onClickButton(props.id);

View file

@ -1 +0,0 @@

View file

@ -0,0 +1,74 @@
import { CoreButton } from "../../core/ui/button/button";
import { Icon } from "../../core/ui/icons/icons";
import { CoreText, CoreTextType } from "../../core/ui/text/text";
import { ProcessStatus } from "../dataset/dataset_model";
export interface ISkillCardProps {
processStatus?: string;
name?: string;
emptyOnClick?: Function;
empty: boolean;
}
export const SkillCard = (props: ISkillCardProps) => {
return (
<div
style={{
margin: 10,
backgroundColor: "#f7f2fa",
borderRadius: 10,
padding: 10,
width: 150,
height: 100,
alignContent: "center",
}}
>
{props.empty ? (
<div
onClick={() => {
if (props.empty && props.emptyOnClick) props.emptyOnClick();
}}
style={{ display: "flex", justifyContent: "center", alignItems: "center" }}
>
<Icon type="PlusCircle" />
</div>
) : (
<>
<CoreText text={props.name ?? ""} type={CoreTextType.medium} />
{props.processStatus === ProcessStatus.END ? (
<CoreButton
onClick={() => {
// if (props.type.isEqual(CardDataSetType.COMPLETED) && props.onClickButton && props.id) {
// props.onClickButton(props.id);
// }
}}
text="Завершен"
/>
) : null}
{props.processStatus === ProcessStatus.RUN ? (
<>
<CoreButton
onClick={() => {
// if (props.type.isEqual(CardDataSetType.COMPLETED) && props.onClickButton && props.id) {
// props.onClickButton(props.id);
}}
block={true}
text="Стоп"
/>
</>
) : null}
{props.processStatus === ProcessStatus.ERROR ? (
<CoreButton
style={{
backgroundColor: "red",
}}
onClick={() => {}}
filled={true}
text="Ошибка"
/>
) : null}
</>
)}
</div>
);
};

View file

@ -0,0 +1,32 @@
import { Result } from "../../core/helper/result";
import { HttpError, HttpMethod, HttpRepository } from "../../core/repository/http_repository";
import { UUID } from "../all_projects/data/project_repository";
import { IDatasetModel } from "../dataset/dataset_model";
import { SkillModel } from "./skills_store";
export interface IEducations {
_id: string;
name: string;
processStatus: string;
isFinished: boolean;
datasetId: any;
project: any;
__v: number;
}
export class SkillsRepository extends HttpRepository {
getAllSkills = async () => {
return this._jsonRequest<IEducations[]>(HttpMethod.GET, "/weights");
};
deleteSkill = async (id: string) => {
return this._jsonRequest<void>(HttpMethod.DELETE, `/weights?id=${id}`);
};
addNewSkill = async (model: SkillModel) => {
return this._jsonRequest(HttpMethod.POST, "/weights", model);
};
getDatasetsActiveProject = async (): Promise<Result<HttpError, IDatasetModel[]>> => {
return this._jsonRequest<IDatasetModel[]>(HttpMethod.GET, "/datasets");
};
getActiveProjectId(): Promise<Result<HttpError, UUID>> {
return this._jsonRequest<UUID>(HttpMethod.GET, "/projects/get/active/project/id");
}
}

View file

@ -1,20 +1,97 @@
import React from "react";
import { MainPage } from "../../core/ui/pages/main_page";
import { CoreText, CoreTextType } from "../../core/ui/text/text";
import { DrawersSkill, SkillStore } from "./skills_store";
import { observer } from "mobx-react-lite";
import { SkillCard } from "./skil_card";
import { Drawer } from "antd";
import { CoreInput } from "../../core/ui/input/input";
import { CoreButton } from "../../core/ui/button/button";
import { CoreSwitch } from "../../core/ui/switch/switch";
interface IItem {
name: string;
isActive: boolean;
}
const skills: IItem[] = [{ name: "ML", isActive: true }];
export const SkillPath = "/skills";
export const SkillScreen = () => {
export const SkillScreen = observer(() => {
const [store] = React.useState(() => new SkillStore());
React.useEffect(() => {
store.init();
}, [store]);
return (
<>
<MainPage
page={"Навыки"}
panelChildren={
<div style={{ justifyContent: "center", display: "flex", padding: 10 }}>
{skills.map((el) => (
<CoreText text={el.name} type={CoreTextType.header} />
))}
</div>
}
bodyChildren={
<>
<div style={{ display: "flex", width: "100%" }}>
</div>
</>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(150px,1px))",
width: "100%",
gap: 20,
margin: 12,
overflow: "auto",
height: window.innerHeight,
}}
>
{store.skils?.map((el) => (
<SkillCard name={el.name} processStatus={el.processStatus} empty={false} />
))}
<SkillCard empty={true} emptyOnClick={() => store.edtDrawer(DrawersSkill.NEW_SKILL, true)} />
</div>
}
/>
<Drawer
title={store.titleDrawer}
destroyOnClose={true}
onClose={() => store.edtDrawer(DrawersSkill.NEW_SKILL, false)}
open={store.drawers.find((el) => el.name === DrawersSkill.NEW_SKILL)?.status}
>
<div style={{ display: "flex", flexDirection: "column", justifyContent: "space-between", height: "100%" }}>
<CoreInput value={store.skill.name} label={"Имя навыка"} onChange={(e) => store.changeSkillName(e)} />
<div style={{ height: "100%" }}>
{store.datasets?.map((el) => (
<div
style={{
border: "1px solid rgb(103, 80, 164)",
backgroundColor: "rgb(254, 247, 255)",
borderRadius: 12,
margin: 10,
padding: 10,
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
{el.name}
<CoreSwitch
isSelected={store.skill.datasetId.isEqual(el._id)}
id={el._id}
onChange={() => store.selectDataset(el._id)}
/>
</div>
))}
</div>
<div style={{ display: "flex" }}>
<CoreButton text="Сохранить" filled={true} onClick={() => store.saveSkill()} />
<div style={{ width: 10 }} />
<CoreButton text="Отмена" onClick={() => store.edtDrawer(DrawersSkill.NEW_SKILL, false)} />
</div>
</div>
</Drawer>
</>
);
};
});

View file

@ -1,3 +1,93 @@
export class SkillStore{
}
import { NavigateFunction } from "react-router-dom";
import { HttpError } from "../../core/repository/http_repository";
import { UiErrorState } from "../../core/store/base_store";
import makeAutoObservable from "mobx-store-inheritance";
import { IEducations as ISkils, SkillsRepository as SkillsHttpRepository } from "./skills_repository";
import { Drawer } from "../dataset/dataset_store";
import { IDatasetModel } from "../dataset/dataset_model";
import { Result } from "../../core/helper/result";
import { message } from "antd";
import { UUID } from "../all_projects/data/project_repository";
export enum DrawersSkill {
NEW_SKILL = "Новый навык",
}
export class SkillModel {
constructor(public name: string, public datasetId: string, public project: string) {
makeAutoObservable(this);
}
static empty() {
return new SkillModel("", "", "");
}
valid(): Result<string, SkillModel> {
if (this.name.isEmpty()) {
return Result.error("name is empty");
}
if (this.datasetId.isEmpty()) {
return Result.error("datasetId is empty");
}
if (this.project.isEmpty()) {
return Result.error("project is empty");
}
return Result.ok(this);
}
}
export class SkillStore extends UiErrorState<HttpError> {
drawers: Drawer[];
skillsHttpRepository: SkillsHttpRepository;
skils?: ISkils[];
datasets?: IDatasetModel[];
activeProjectId?: UUID;
skill: SkillModel;
titleDrawer: string = DrawersSkill.NEW_SKILL;
constructor() {
super();
this.drawers = Object.entries(DrawersSkill).map((k, v) => {
return {
name: k.at(1) ?? "",
status: false,
};
});
this.skill = SkillModel.empty();
this.skillsHttpRepository = new SkillsHttpRepository();
makeAutoObservable(this);
}
errorHandingStrategy: (error: HttpError) => {};
init = async (navigate?: NavigateFunction | undefined) => {
await this.mapOk("skils", this.skillsHttpRepository.getAllSkills());
await this.mapOk("datasets", this.skillsHttpRepository.getDatasetsActiveProject());
await this.mapOk("activeProjectId", this.skillsHttpRepository.getActiveProjectId());
};
changeSkillName(name: string): void {
this.skill.name = name;
}
saveSkill() {
console.log(this.activeProjectId);
this.skill.project = this.activeProjectId?.id ?? "";
this.skill.valid().fold(
async (model) => {
(await this.skillsHttpRepository.addNewSkill(model)).fold(
(s) => {
message.success("Новый ");
this.skill = SkillModel.empty();
},
(e) => message.error(e.message)
);
},
async (e) => message.error(e)
);
}
selectDataset(id: string): void {
this.skill.datasetId = id;
}
edtDrawer(drawerName: DrawersSkill, status: boolean): void {
this.drawers = this.drawers.map((el) => {
if (el.name === drawerName) {
el.status = status;
}
return el;
});
}
}

28
web_p/education.py Normal file
View file

@ -0,0 +1,28 @@
import shutil
import argparse
import os.path
from pathlib import Path
parser = argparse.ArgumentParser()
parser.add_argument("--path")
parser.add_argument("--name")
args = parser.parse_args()
def copy_and_move_folder(src, dst, folder):
try:
if os.path.exists(dst + "/education/") is False:
Path(dst + "/education/").mkdir(parents=True, exist_ok=True)
if os.path.exists(dst + "/education/" + folder + "/"):
shutil.rmtree(dst + "/education/" + folder + "/")
shutil.copytree(src, dst + "/education/" + folder + "/")
except shutil.Error as e:
print(f"Error: {e}")
source_folder = os.path.dirname(os.path.abspath(__file__)) + "/education"
copy_and_move_folder(source_folder, args.path, args.name)

View file

@ -0,0 +1,3 @@
{
"numberOfEpochs": 1
}

BIN
web_p/education/yolov8n.pt Normal file

Binary file not shown.