├── .github └── workflows │ ├── ml-api.yaml │ ├── ml-celery.yaml │ └── ml-client.yaml ├── .gitignore ├── README.md ├── docker-compose.dev.yaml ├── docker-compose.yaml ├── ml-api ├── Dockerfile ├── app │ ├── api │ │ ├── entities │ │ │ ├── account.py │ │ │ └── object_detection.py │ │ ├── r_v1.py │ │ └── resources │ │ │ └── v1 │ │ │ ├── account.py │ │ │ ├── object_detection │ │ │ ├── background.py │ │ │ └── router.py │ │ │ └── show_image.py │ ├── databases │ │ └── connect.py │ ├── environment.ini │ ├── helpers │ │ ├── storage.py │ │ └── time.py │ ├── main.py │ ├── mq_main.py │ ├── securities │ │ ├── password.py │ │ └── token.py │ └── settings │ │ └── config.py └── requirements.txt ├── ml-celery ├── Dockerfile ├── app │ ├── environment.ini │ ├── helpers │ │ ├── storage.py │ │ └── time.py │ ├── init_broker.py │ ├── init_redis.py │ ├── mq_main.py │ ├── settings │ │ ├── celery_config.py │ │ ├── config.py │ │ └── ml_config.py │ ├── tasks.py │ └── worker │ │ └── ml │ │ ├── helpers │ │ ├── image_utils.py │ │ ├── keypoint_ops.py │ │ ├── load_label_map.py │ │ ├── shape_utils.py │ │ ├── standard_fields.py │ │ ├── static_shape.py │ │ ├── string_int_label_map_pb2.py │ │ └── visualization_utils.py │ │ ├── model.py │ │ └── models │ │ ├── .gitkeep │ │ └── object_detection │ │ ├── checkpoint │ │ ├── checkpoint │ │ ├── ckpt-0.data-00000-of-00001 │ │ └── ckpt-0.index │ │ ├── label_map.pbtxt │ │ ├── pipeline.config │ │ └── saved_model │ │ ├── saved_model.pb │ │ └── variables │ │ ├── variables.data-00000-of-00001 │ │ └── variables.index └── requirements.txt ├── ml-client ├── .dockerignore ├── .env.staging ├── Dockerfile ├── custom_nginx.sh ├── local_nginx.conf ├── m_nginx.conf ├── nginx.conf ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── img │ │ └── logo.png │ ├── index.html │ ├── js │ │ ├── coalesce.js │ │ ├── swirl.js │ │ └── test.js │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt └── src │ ├── App.js │ ├── Routes.js │ ├── admin │ └── auth.js │ ├── assets │ └── images │ │ └── dashboard │ │ ├── Ads.png │ │ ├── Currency.png │ │ ├── Developer.png │ │ ├── Game.png │ │ ├── Ingame.png │ │ ├── Intagram.png │ │ ├── User.png │ │ ├── blog.ico │ │ ├── blog.png │ │ ├── donate.jpeg │ │ ├── github.png │ │ ├── gmail.png │ │ ├── hotmail.png │ │ ├── linkedin.png │ │ ├── mobile.jpeg │ │ ├── result.JPEG │ │ └── test.JPEG │ ├── components │ ├── account │ │ └── Account.js │ ├── box │ │ └── Box.js │ ├── buttom │ │ ├── Buttom.js │ │ └── chat │ │ │ └── ChatClient.js │ ├── chart │ │ ├── bar │ │ │ ├── HorizontalBar.js │ │ │ └── VerticalBar.js │ │ ├── line │ │ │ ├── LineChart.js │ │ │ └── MultiAxisLine.js │ │ └── other │ │ │ └── Polar.js │ ├── footer │ │ └── Footer.js │ ├── header │ │ └── AntHeader.js │ ├── input │ │ └── CustomInput.js │ └── loading │ │ └── SpinLoading.js │ ├── index.css │ ├── index.js │ ├── services │ └── api │ │ └── account.service.js │ ├── styles │ ├── jss │ │ ├── buttomStyle.js │ │ ├── customInputStyle.js │ │ └── material-kit-react.js │ ├── react-chess.scss │ └── react-chess │ │ ├── _components.scss │ │ ├── _views.scss │ │ ├── components │ │ ├── _account.scss │ │ ├── _ant-header.scss │ │ ├── _box.scss │ │ ├── _footer.scss │ │ ├── _horizontal-bar.scss │ │ ├── _line-chart.scss │ │ ├── _polar.scss │ │ └── _vertical-bar.scss │ │ └── views │ │ ├── _dashboard.scss │ │ ├── _login.scss │ │ └── _menu.scss │ └── views │ ├── DashBoard.js │ └── Login.js ├── ml-storages ├── .gitkeep ├── object_detection │ └── .gitkeep └── upload │ └── .gitkeep └── public ├── architecture.png └── test.png /.github/workflows/ml-api.yaml: -------------------------------------------------------------------------------- 1 | name: DEPLOY_API 2 | on: 3 | pull_request: 4 | types: [closed] 5 | branches: 6 | - main 7 | paths: 8 | - 'ml-api/**' 9 | env: 10 | DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} # dockerhub pass 11 | DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} # dockerhub user 12 | IMAGE: duynguyenngoc/mmip-api:latest # Image docker 13 | 14 | jobs: 15 | build: 16 | name: Build 17 | runs-on: ubuntu-latest 18 | environment: production 19 | steps: 20 | # [Step-1] check config git 21 | - name: Checkout 22 | uses: actions/checkout@v2 23 | # [Step-2] login dockerhub container 24 | - name: Login to DockerHub Registry 25 | run: | 26 | echo $DOCKERHUB_PASSWORD | docker login -u $DOCKERHUB_USERNAME --password-stdin 27 | # [Step-3] buid and tag image from Dockerfile 28 | - name: Build docker image 29 | run: | 30 | docker build -t $IMAGE --no-cache ./ml-api/ 31 | # [Step-4] Push image to docker hub 32 | - name: Push image to DockerHub 33 | run: | 34 | docker push $IMAGE -------------------------------------------------------------------------------- /.github/workflows/ml-celery.yaml: -------------------------------------------------------------------------------- 1 | name: DEPLOY_CELERY 2 | on: 3 | pull_request: 4 | types: [closed] 5 | branches: 6 | - main 7 | paths: 8 | - 'ml-celery/**' 9 | 10 | env: 11 | DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} # dockerhub pass 12 | DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} # dockerhub user 13 | IMAGE: duynguyenngoc/mmip-celery:latest # Image docker 14 | 15 | jobs: 16 | build: 17 | name: Build 18 | runs-on: ubuntu-latest 19 | environment: production 20 | steps: 21 | # [Step-1] check config git 22 | - name: Checkout 23 | uses: actions/checkout@v2 24 | # [Step-2] login dockerhub container 25 | - name: Login to DockerHub Registry 26 | run: | 27 | echo $DOCKERHUB_PASSWORD | docker login -u $DOCKERHUB_USERNAME --password-stdin 28 | # [Step-3] buid and tag image from Dockerfile 29 | - name: Build docker image 30 | run: | 31 | docker build -t $IMAGE --no-cache ./ml-celery/ 32 | # [Step-4] Push image to docker hub 33 | - name: Push image to DockerHub 34 | run: | 35 | docker push $IMAGE -------------------------------------------------------------------------------- /.github/workflows/ml-client.yaml: -------------------------------------------------------------------------------- 1 | name: DEPLOY_CLIENT 2 | on: 3 | pull_request: 4 | types: [closed] 5 | branches: 6 | - main 7 | paths: 8 | - 'ml-client/**' 9 | 10 | env: 11 | DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} # dockerhub pass 12 | DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} # dockerhub user 13 | IMAGE: duynguyenngoc/mmip-client:latest # Image docker 14 | 15 | jobs: 16 | build: 17 | name: Build 18 | runs-on: ubuntu-latest 19 | environment: production 20 | steps: 21 | # [Step-1] check config git 22 | - name: Checkout 23 | uses: actions/checkout@v2 24 | # [Step-2] login dockerhub container 25 | - name: Login to DockerHub Registry 26 | run: | 27 | echo $DOCKERHUB_PASSWORD | docker login -u $DOCKERHUB_USERNAME --password-stdin 28 | # [Step-3] buid and tag image from Dockerfile 29 | - name: Build docker image 30 | run: | 31 | docker build -t $IMAGE --no-cache ./ml-client/ 32 | # [Step-4] Push image to docker hub 33 | - name: Push image to DockerHub 34 | run: | 35 | docker push $IMAGE -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # core 2 | __pycache__ 3 | api-logs 4 | celery-logs 5 | .DS_Store 6 | 7 | # Celery 8 | # ml-celery/app/worker/ml/models/* 9 | # !ml-celery/app/worker/ml/models/.gitkeep 10 | 11 | # Storage 12 | ml-storages/upload/* 13 | ml-storages/object_detection/* 14 | !ml-storages/upload/.gitkeep 15 | !ml-storages/object_detection/.gitkeep 16 | 17 | # frontend and socket 18 | node_modules 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ml Models in Production 2 | [![python](https://img.shields.io/badge/python-3.9.5-green)](https://www.python.org/doc/) 3 | [![Celery](https://img.shields.io/badge/celery-5.2.3-green)](https://docs.celeryproject.org/en/stable/getting-started/introduction.html) 4 | [![rabbitmq](https://img.shields.io/badge/rabbitmq-3-orange)](https://www.rabbitmq.com/) 5 | [![redis](https://img.shields.io/badge/redis-6.2.6-orange)](https://redis.io/) 6 | [![react](https://img.shields.io/badge/react-17.0.2-lightgrey)](https://reactjs.org/) 7 | [![fastapi](https://img.shields.io/badge/fastapi-0.75.0-blue)](https://fastapi.tiangolo.com/) 8 | [![NPM](https://img.shields.io/badge/npm-1.0.1-green)](https://www.npmjs.com/package/package/v/1.0.1) 9 | [![Tensorflow](https://img.shields.io/badge/tensorflow-3.7-yellowgreen)](https://analyticsindiamag.com/tensorflow-2-7-0-released-all-major-updates-features/) 10 | 11 | 12 | This repo gives an introduction to how to make full working example to serve your model using asynchronous Celery tasks and FastAPI. This post walks through a working example for serving a ML model using Celery and FastAPI. All code can be found in this repository. We won’t specifically discuss the ML model used for this example however it was trained using coco dataset with 80 object class like cat, dog, bird ... more detail here [Coco Dataset](https://cocodataset.org/#home). The model have been train with tensorflow [Tensorflow](https://github.com/tensorflow/models) 13 | 14 | 15 | ## Contents 16 | - [Screenshots & Gifs](#screenshots--gifs) 17 | - [Demo](#demo) 18 | - [1. Install docker, docker-compose](#1-install-docker-and-docker-compose) 19 | - [2. Pull git repo](#2-pull-git-repo) 20 | - [3. Start Server](#3-start-server) 21 | - [Contact Us](#contact-us) 22 | 23 | 24 | ## Screenshots & Gifs 25 | 26 | **View System** 27 | 28 | ![Architecture](public/architecture.png) 29 | 30 | 31 | ## Demo 32 | 33 | ### 1. Install docker and docker-compose 34 | 35 | `https://www.docker.com/` 36 | 37 | ### 2. Pull git repo 38 | `git clone https://github.com/apot-group/ml-models-in-production.git` 39 | 40 | ### 3. Start Server 41 | `cd ml-models-in-production && docker-compose up` 42 | 43 | | Service | URL | 44 | | :-------------------: | :------------------------------: | 45 | | API docs | http://localhost/api/docs | 46 | | Demo Web | http://localhost | 47 | 48 | go to Demo web ```http://localhost``` and test with your picture. 49 | 50 | ![Test](public/test.png) 51 | 52 | 53 | ## Contact Us 54 | - Email-1: duynnguyenngoc@hotmail.com - Duy Nguyen :heart: :heart: :heart: 55 | -------------------------------------------------------------------------------- /docker-compose.dev.yaml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | 5 | # Redis 6 | redis: 7 | image: redis 8 | container_name: redis 9 | restart: unless-stopped 10 | command: redis-server --requirepass password 11 | volumes: 12 | - redis-data:/data 13 | ports: 14 | - 6379:6379 15 | networks: 16 | - mlnet 17 | 18 | # Rabbitmq 19 | rabbitmq: 20 | image: rabbitmq:3-management-alpine 21 | container_name: 'rabbitmq' 22 | ports: 23 | - 5672:5672 24 | - 15672:15672 25 | environment: 26 | - RABBITMQ_DEFAULT_USER=guest 27 | - RABBITMQ_DEFAULT_PASS=guest 28 | volumes: 29 | - rabbitmq-data:/var/lib/rabbitmq/ 30 | - rabbitmq-logs:/var/log/rabbitmq 31 | networks: 32 | - mlnet 33 | 34 | # API 35 | ml-api: 36 | build: 37 | context: ./ml-api 38 | dockerfile: ./Dockerfile 39 | container_name: ml-api 40 | restart: unless-stopped 41 | command: sh -c "uvicorn main:app --host 0.0.0.0 --port 8081 --reload" 42 | volumes: 43 | - ./ml-api/app:/app/ 44 | - ./ml-storages:/storages/ 45 | - ./api-logs:/logs/ 46 | ports: 47 | - 8081:8081 48 | networks: 49 | - mlnet 50 | 51 | # Celery object detection 52 | ml-celery: 53 | build: 54 | context: ./ml-celery 55 | dockerfile: ./Dockerfile 56 | container_name: ml-celery 57 | restart: unless-stopped 58 | command: sh -c "celery -A tasks worker --loglevel=info --concurrency=1 -E --logfile=/logs/celery.log" 59 | volumes: 60 | - ./ml-celery/app:/app/ 61 | - ./ml-storages:/storages/ 62 | - ./celery-logs:/logs/ 63 | networks: 64 | - mlnet 65 | 66 | # Client 67 | ml-client: 68 | build: 69 | context: ./ml-client 70 | dockerfile: ./Dockerfile 71 | environment: 72 | DOMAIN: localhost 73 | BE_HOST: ml-api 74 | BE_PORT: 8081 75 | container_name: ml-client 76 | restart: unless-stopped 77 | ports: 78 | - 80:80 79 | command: sh -c "./custom_nginx.sh && nginx -g 'daemon off;'" 80 | networks: 81 | - mlnet 82 | 83 | networks: 84 | mlnet: 85 | 86 | volumes: 87 | redis-data: 88 | rabbitmq-data: 89 | rabbitmq-logs: 90 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | 5 | # Redis 6 | redis: 7 | image: redis 8 | container_name: redis 9 | restart: unless-stopped 10 | command: redis-server --requirepass password 11 | volumes: 12 | - redis-data:/data 13 | ports: 14 | - 6379:6379 15 | networks: 16 | - mlnet 17 | 18 | # Rabbitmq 19 | rabbitmq: 20 | image: rabbitmq:3-management-alpine 21 | container_name: 'rabbitmq' 22 | ports: 23 | - 5672:5672 24 | - 15672:15672 25 | environment: 26 | - RABBITMQ_DEFAULT_USER=guest 27 | - RABBITMQ_DEFAULT_PASS=guest 28 | volumes: 29 | - rabbitmq-data:/var/lib/rabbitmq/ 30 | - rabbitmq-logs:/var/log/rabbitmq 31 | networks: 32 | - mlnet 33 | 34 | # API 35 | ml-api: 36 | image: duynguyenngoc/mmip-api:latest 37 | container_name: ml-api 38 | restart: unless-stopped 39 | command: sh -c "uvicorn main:app --host 0.0.0.0 --port 8081 --reload" 40 | volumes: 41 | - ./ml-storages:/storages/ 42 | - api-logs:/logs/ 43 | ports: 44 | - 8081:8081 45 | networks: 46 | - mlnet 47 | 48 | # Celery object detection 49 | ml-celery: 50 | image: duynguyenngoc/mmip-celery:latest 51 | container_name: ml-celery 52 | restart: unless-stopped 53 | command: sh -c "celery -A tasks worker --loglevel=info --concurrency=1 -E --logfile=/logs/celery.log" 54 | volumes: 55 | - ./ml-storages:/storages/ 56 | - celery-logs:/logs/ 57 | networks: 58 | - mlnet 59 | 60 | # Client demo 61 | ml-client: 62 | image: duynguyenngoc/mmip-client:latest 63 | environment: 64 | DOMAIN: localhost 65 | BE_HOST: ml-api 66 | BE_PORT: 8081 67 | container_name: ml-client 68 | restart: unless-stopped 69 | ports: 70 | - 80:80 71 | command: sh -c "./custom_nginx.sh && nginx -g 'daemon off;'" 72 | networks: 73 | - mlnet 74 | 75 | networks: 76 | mlnet: 77 | 78 | volumes: 79 | redis-data: 80 | rabbitmq-data: 81 | rabbitmq-logs: 82 | celery-logs: 83 | api-logs: 84 | -------------------------------------------------------------------------------- /ml-api/Dockerfile: -------------------------------------------------------------------------------- 1 | #=============================================== 2 | #====== Using Python 3.9 build Imange ========= 3 | #=============================================== 4 | 5 | # pull official base image 6 | FROM python:3.9.5-slim-buster 7 | 8 | # maintainer 9 | LABEL maintainer="Duy Nguyen " 10 | 11 | # set work directory 12 | WORKDIR /app 13 | 14 | # set environment variables 15 | ENV PYTHONDONTWRITEBYTECODE 1 16 | ENV PYTHONUNBUFFERED 1 17 | 18 | # install dependencies 19 | RUN pip install --upgrade pip 20 | 21 | 22 | RUN apt-get update 23 | RUN apt-get install ffmpeg libsm6 libxext6 -y 24 | 25 | # RUN apt-get install openssh-client -y 26 | 27 | # RUN pip3 install pipenv 28 | # COPY Pipfile* /app/ 29 | 30 | # ONBUILD RUN set -ex && pipenv install --deploy --system 31 | 32 | # RUN pipenv lock --requirements > requirements.txt 33 | 34 | COPY ./requirements.txt /app/requirements.txt 35 | RUN pip install -r requirements.txt 36 | 37 | # copy project 38 | COPY ./app/ /app/ 39 | 40 | EXPOSE 8081 41 | -------------------------------------------------------------------------------- /ml-api/app/api/entities/account.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from pydantic import BaseModel 3 | from datetime import datetime 4 | 5 | 6 | class Token(BaseModel): 7 | token_type: Optional[str] = 'bearer' 8 | access_token: Optional[str] 9 | refresh_token: Optional[str] 10 | expire_token: Optional[datetime] 11 | expire_refresh_token: Optional[datetime] 12 | 13 | 14 | class TokenCreate(BaseModel): 15 | user_name: Optional[str] 16 | password: Optional[str] 17 | 18 | 19 | class TokenPayload(BaseModel): 20 | user_name: Optional[str] 21 | password: Optional[str] -------------------------------------------------------------------------------- /ml-api/app/api/entities/object_detection.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from pydantic import BaseModel 3 | from typing import Optional 4 | 5 | 6 | class MlTimeHandle(BaseModel): 7 | start_upload: str = None 8 | end_upload: str = None 9 | start_detection: str = None 10 | end_detection: str = None 11 | 12 | 13 | class MlStatusHandle(BaseModel): 14 | general_status: str = "PENDING" 15 | upload_status: str = "PENDING" 16 | detection_status: str = None 17 | 18 | 19 | class MlResult(BaseModel): 20 | task_id: str 21 | status: dict = None 22 | time: dict = None 23 | upload_result: dict = None 24 | detection_result: list = None 25 | detection_draw_url: str = None 26 | error: Optional[str] = None 27 | 28 | 29 | class MlResponse(BaseModel): 30 | status: str = "PENDING" 31 | status_code: int 32 | time: datetime 33 | task_id: str 34 | -------------------------------------------------------------------------------- /ml-api/app/api/r_v1.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | from api.resources.v1 import account, show_image 3 | from api.resources.v1.object_detection import router as object_detection 4 | 5 | 6 | router_v1 = APIRouter() 7 | 8 | router_v1.include_router(account.router, prefix="/account", tags=["V1-Account"]) 9 | router_v1.include_router(object_detection.router, prefix="/object-detection", tags=["V1-Object-Detection"]) 10 | router_v1.include_router(show_image.router, prefix="/show-image", tags=["V1-Image"]) 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ml-api/app/api/resources/v1/account.py: -------------------------------------------------------------------------------- 1 | from fastapi import (APIRouter, Depends, Form, HTTPException) 2 | # from sqlalchemy.orm import Session 3 | from fastapi.security import OAuth2PasswordRequestForm 4 | 5 | # from databases.connect import get_db 6 | from api.entities import account as account_entity 7 | from securities import token as token_helper 8 | from settings import config 9 | 10 | 11 | router = APIRouter() 12 | 13 | 14 | @router.post("/login/access-token", response_model = account_entity.Token) 15 | def login_access_token( 16 | # db_session: Session = Depends(get_db), 17 | form_data: OAuth2PasswordRequestForm = Depends(OAuth2PasswordRequestForm) 18 | ): 19 | if form_data.username != config.USER or form_data.password != config.PASSWORD: 20 | raise HTTPException(status_code=400, detail="Incorrect username or password") 21 | 22 | token_create = account_entity.TokenCreate(user_name= form_data.username,password= form_data.password) 23 | access_token, access_token_expire = token_helper.create_access_token(token_create.__dict__) 24 | refresh_token, refresh_token_expire = token_helper.create_fresh_token(token_create.__dict__) 25 | 26 | token = account_entity.Token( 27 | access_token = access_token, 28 | refresh_token = refresh_token, 29 | expire_token= access_token_expire, 30 | expire_refresh_token= refresh_token_expire, 31 | ) 32 | return token 33 | 34 | 35 | @router.post("/login/refresh-token", response_model = account_entity.Token) 36 | def refresh_token( 37 | *, 38 | # db_session: Session = Depends(get_db), 39 | refresh_token: str = Form(...), 40 | ): 41 | token = token_helper.decrypt_renew(refresh_token) 42 | if token == False: 43 | raise HTTPException(status_code=403, detail="token wrong") 44 | if token['user_name'] != config.USER or token['password'] != config.PASSWORD: 45 | raise HTTPException(status_code=400, detail="token wrong") 46 | token_create = account_entity.TokenCreate( 47 | user_name= token['user_name'], 48 | password= token['password'], 49 | ) 50 | access_token, access_token_expire = token_helper.create_access_token(token_create.__dict__) 51 | refresh_token, refresh_token_expire = token_helper.create_fresh_token(token_create.__dict__) 52 | token = account_entity.Token( 53 | access_token = access_token, 54 | refresh_token = refresh_token, 55 | expire_token= access_token_expire, 56 | expire_refresh_token=refresh_token_expire, 57 | ) 58 | return token 59 | -------------------------------------------------------------------------------- /ml-api/app/api/resources/v1/object_detection/background.py: -------------------------------------------------------------------------------- 1 | 2 | import datetime 3 | import json 4 | from fastapi import UploadFile 5 | from api.entities.object_detection import MlResult 6 | from settings import config 7 | from helpers import time as time_helper 8 | from helpers.storage import upload_file_bytes, create_path 9 | from mq_main import redis, celery_execute 10 | 11 | 12 | def image_upload_background(file: UploadFile, 13 | task_id: str, 14 | time: datetime, 15 | data: MlResult): 16 | file_name = task_id + config.ML_IMAGE_TYPE 17 | dir_path = config.ML_STORAGE_UPLOAD_PATH + time_helper.str_yyyy_mm_dd(time) 18 | create_path(dir_path) 19 | file_path = dir_path + "/" + file_name 20 | file_bytes = file.file.read() 21 | try: 22 | upload_file_bytes(file_bytes, file_path) 23 | # data.status['upload_id'] = task_id 24 | data.time['end_upload'] = str(time_helper.now_utc().timestamp()) 25 | data.status['upload_status'] = "SUCCESS" 26 | data.upload_result = {"path": file_path, "file_type": config.ML_IMAGE_TYPE} 27 | data_dump = json.dumps(data.__dict__) 28 | redis.set(task_id, data_dump) 29 | # print(config.ML_QUERY_NAME, config.ML_OBJECT_DETECTION_TASK) 30 | celery_execute.send_task( 31 | name="{}.{}".format(config.ML_QUERY_NAME, config.ML_OBJECT_DETECTION_TASK), 32 | kwargs={ 33 | 'task_id': task_id, 34 | 'data': data_dump, 35 | }, 36 | queue= config.ML_QUERY_NAME 37 | ) 38 | except Exception as e: 39 | data.status['upload_status'] = "FAILED" 40 | data.status['general_status'] = "FAILED" 41 | data.error = str(e) 42 | redis.set(task_id, json.dumps(data.__dict__)) -------------------------------------------------------------------------------- /ml-api/app/api/resources/v1/object_detection/router.py: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------- 2 | # API paragraph and field detection for ancestry document 3 | # (C) 2021 Duy Nguyen, Ho Chi Minh, Viet Nam 4 | # email duynguyenngoc@hotmail.com 5 | # ----------------------------------------------------------- 6 | 7 | from fastapi import APIRouter, UploadFile, File, Form, BackgroundTasks, HTTPException, Depends 8 | from starlette.status import ( 9 | HTTP_200_OK, HTTP_400_BAD_REQUEST, HTTP_500_INTERNAL_SERVER_ERROR 10 | ) 11 | import uuid 12 | import json 13 | from mq_main import redis 14 | from securities import token as token_helper 15 | from helpers import time as time_helper 16 | from settings import config 17 | from api.entities.object_detection import MlTimeHandle, MlResult, MlStatusHandle, MlResponse 18 | from api.resources.v1.object_detection.background import image_upload_background 19 | 20 | 21 | router = APIRouter() 22 | 23 | 24 | @router.post("/process") 25 | async def ml_process( 26 | *, 27 | # current_user = Depends(token_helper.get_current_user), 28 | file: UploadFile = File(...), 29 | background_tasks: BackgroundTasks 30 | ): 31 | if file.content_type not in ["image/jpeg", "image/png"]: 32 | raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail="file_type not support!") 33 | time = time_helper.now_utc() 34 | task_id = str(uuid.uuid5(uuid.NAMESPACE_OID, config.ML_QUERY_NAME + "_"+ str(time))) 35 | time_handle = MlTimeHandle(start_upload=str(time.timestamp())).__dict__ 36 | status_hanlde = MlStatusHandle().__dict__ 37 | data = MlResult(task_id=task_id, time=time_handle, status=status_hanlde) 38 | redis.set(task_id, json.dumps(data.__dict__)) 39 | background_tasks.add_task(image_upload_background, file, task_id, time, data) 40 | return MlResponse(status="PENDING", time=time, status_code=HTTP_200_OK, task_id=task_id) 41 | 42 | 43 | @router.get("/status/{task_id}", response_model=MlResult) 44 | def ml_status( 45 | *, 46 | task_id: str, 47 | # current_user = Depends(token_helper.get_current_user), 48 | ): 49 | data = redis.get(task_id) 50 | if data == None: 51 | raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail='task id not found!') 52 | message = json.loads(data) 53 | return message 54 | -------------------------------------------------------------------------------- /ml-api/app/api/resources/v1/show_image.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | from fastapi.responses import StreamingResponse 3 | 4 | 5 | router = APIRouter() 6 | 7 | @router.get('/') 8 | async def show_image( 9 | path_image: str, 10 | ): 11 | def iterfile(): 12 | with open(path_image.replace("\\", "/"), mode="rb") as file_like: 13 | yield from file_like 14 | return StreamingResponse(iterfile(), media_type="image/jpeg") -------------------------------------------------------------------------------- /ml-api/app/databases/connect.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine, MetaData 2 | from sqlalchemy.ext.declarative import declarative_base 3 | from sqlalchemy.orm import scoped_session, sessionmaker 4 | from settings import config 5 | 6 | if config.ENVIRONMENT == 'production_no_db': 7 | SQLALCHEMY_DATABASE_URL = "sqlite:///./database.db" 8 | engine = create_engine( 9 | SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} 10 | ) 11 | Session = sessionmaker(autocommit=False, autoflush=False, bind=engine) 12 | 13 | Base = declarative_base() 14 | else: 15 | engine = create_engine( 16 | config.SQLALCHEMY_DATABASE_URL, pool_pre_ping=True, echo=True 17 | ) 18 | db_session = scoped_session( 19 | sessionmaker(autocommit=False, autoflush=False, bind=engine) 20 | ) 21 | Session = sessionmaker(autocommit=False, autoflush=False, bind=engine) 22 | Base = declarative_base(metadata=MetaData(schema=config.DATABASE_SCHEMA)) 23 | 24 | 25 | from starlette.requests import Request 26 | def get_db(request: Request): 27 | return request.state.db 28 | 29 | 30 | def get_engine(): 31 | return engine 32 | -------------------------------------------------------------------------------- /ml-api/app/environment.ini: -------------------------------------------------------------------------------- 1 | [project] 2 | name = ML Models In Production 3 | environment = production_no_db 4 | port = 8081 5 | host = localhost 6 | user = ml_user 7 | password = CBSy3NaBMxLF 8 | 9 | [nginx] 10 | host = localhost 11 | 12 | 13 | [authenticate] 14 | encode = utf-8 15 | digest = sha256 16 | algorithm = HS256 17 | rounds = 10 18 | salt_size = 16 19 | salt = 5\xe0v?\x17s\xdd:`Z\xbc\xb5\x85\xb43; 20 | access_expire = 30 21 | fresh_expire = 60 22 | secret_key = 1q2w3e4r 23 | 24 | 25 | [database] 26 | user = 27 | pass = 28 | host = 29 | port = 30 | database = 31 | type = 32 | schema = 33 | 34 | [redis] 35 | host = redis 36 | port = 6379 37 | pass = password 38 | db = 0 39 | 40 | 41 | [rabbitmq] 42 | host = rabbitmq 43 | post = 5672 44 | user = guest 45 | pass = guest 46 | vhost = 47 | 48 | [ml] 49 | image_type = .JPEG 50 | storage_path = /storages/ 51 | storage_upload_path = /storages/upload/ 52 | object_detection_task = object_detection 53 | query_name = ml_celery 54 | -------------------------------------------------------------------------------- /ml-api/app/helpers/storage.py: -------------------------------------------------------------------------------- 1 | import os 2 | from os import path 3 | 4 | 5 | def create_path(path_dir): 6 | if path.exists(path_dir): 7 | pass 8 | else: 9 | os.mkdir(path_dir) 10 | 11 | def upload_file_bytes(file_bytes, path): 12 | f = open(path, "wb") 13 | f.write(file_bytes) 14 | f.close() 15 | -------------------------------------------------------------------------------- /ml-api/app/helpers/time.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import pytz 3 | 4 | 5 | def now_utc(): 6 | now = datetime.datetime.utcnow() 7 | now = now.replace(tzinfo=pytz.utc) 8 | return now 9 | 10 | def str_yyyy_mm_from_int(year, month, space = '-'): 11 | if month < 10: 12 | str_month = '0'+ str(month) 13 | else: 14 | str_month = str(month) 15 | return str(year) + space + str_month 16 | 17 | def str_yyyy_mm_dd(time: datetime = now_utc(), space = "-"): 18 | str_year = str(time.year) 19 | if time.month < 10: 20 | str_month = '0'+ str(time.month) 21 | else: 22 | str_month = str(time.month) 23 | if time.day < 10: 24 | str_day = '0'+ str(time.day) 25 | else: 26 | str_day = str(time.day) 27 | return str_year + space + str_month + space + str_day 28 | 29 | def str_yyyy_mm(time = now_utc(), space = "-"): 30 | str_year = str(time.year) 31 | if time.month < 10: 32 | str_month = '0'+ str(time.month) 33 | else: 34 | str_month = str(time.month) 35 | return str_year + space + str_month 36 | -------------------------------------------------------------------------------- /ml-api/app/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | [summary] API for Paragraph, field detection of Ancestry Document project. 3 | [information] 4 | @author: Duy Nguyen 5 | @email: duynguyenngoc@hotmail.com 6 | @create: 2022-1-1 7 | """ 8 | 9 | import logging 10 | from logging.handlers import TimedRotatingFileHandler 11 | from fastapi import FastAPI 12 | from fastapi.middleware.cors import CORSMiddleware 13 | from starlette.requests import Request 14 | 15 | 16 | from settings import config 17 | from databases.connect import Session 18 | from api.r_v1 import router_v1 19 | from mq_main import redis 20 | 21 | 22 | 23 | # ++++++++++++++++++++++++++++++++++++++++++++ DEFINE APP +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 24 | app = FastAPI(title=config.PROJECT_NAME, openapi_url="/api/openapi.json", docs_url="/api/docs", redoc_url="/api/redoc") 25 | 26 | 27 | # ++++++++++++++++++++++++++++++++++++++++++++ HANDLE LOG FILE +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 28 | formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") 29 | handler = TimedRotatingFileHandler('/logs/{}-{}-{}_{}h-00p-00.log'.format( 30 | config.u.year, config.u.month, config.u.day , config.u.hour), when="midnight", interval=1, encoding='utf8') 31 | handler.suffix = "%Y-%m-%d" 32 | handler.setFormatter(formatter) 33 | logger = logging.getLogger() 34 | logger.setLevel(logging.INFO) 35 | logger.addHandler(handler) 36 | 37 | 38 | # ++++++++++++++++++++++++++++++++++++++++++++ ROUTER CONFIG ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 39 | app.include_router(router_v1, prefix="/api/v1") 40 | 41 | 42 | # ++++++++++++++++++++++++++++++++++++++++++++ CORS MIDDLEWARE ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 43 | origins = [ 44 | "http://{host}:{port}".format(host=config.HOST, port=config.PORT), 45 | "http://{host}:{port}".format(host=config.HOST, port=config.FE_PORT), 46 | "http://{host}".format(host=config.NGINX_HOST), 47 | ] 48 | 49 | app.add_middleware( 50 | CORSMiddleware, 51 | allow_origins=origins, 52 | allow_credentials=True, 53 | allow_methods=["*"], 54 | allow_headers=["*"], 55 | ) 56 | 57 | # ++++++++++++++++++++++++++++++++++++++++++++++ DB CONFIG ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 58 | @app.middleware("http") 59 | async def db_session_middleware(request: Request, call_next): 60 | request.state.db = Session() 61 | response = await call_next(request) 62 | request.state.db.close() 63 | return response -------------------------------------------------------------------------------- /ml-api/app/mq_main.py: -------------------------------------------------------------------------------- 1 | from redis import Redis 2 | from settings import config 3 | from celery import Celery 4 | 5 | 6 | redis = Redis(host=config.REDIS['host'], port=config.REDIS['port'], password=config.REDIS['pass'], db= config.REDIS['db']) 7 | 8 | 9 | celery_execute = Celery(broker=config.BROKER, backend=config.REDIS_BACKEND) 10 | -------------------------------------------------------------------------------- /ml-api/app/securities/password.py: -------------------------------------------------------------------------------- 1 | from passlib.context import CryptContext 2 | 3 | 4 | pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") 5 | 6 | 7 | def verify_password(plain_password, hashed_password): 8 | return pwd_context.verify(plain_password, hashed_password) 9 | 10 | def get_password_hash(password): 11 | return pwd_context.hash(password) 12 | -------------------------------------------------------------------------------- /ml-api/app/securities/token.py: -------------------------------------------------------------------------------- 1 | from fastapi.exceptions import HTTPException 2 | from datetime import (datetime, timedelta) 3 | from Crypto.Cipher import AES 4 | from passlib.utils.pbkdf2 import pbkdf2_hmac 5 | import binascii 6 | from jose import JWTError, jwt 7 | from fastapi import Security 8 | from fastapi.security import OAuth2PasswordBearer 9 | 10 | 11 | from api.entities import account as account_entity 12 | from settings import config 13 | 14 | 15 | # Config security 16 | secrect = config.SECRET_KEY.encode(config.ENCODE_TYPE) 17 | algorithm=config.ALGORITHM 18 | salt = config.SALT 19 | digest = config.DIGEST 20 | rounds = config.ROUNDS 21 | secret_key = pbkdf2_hmac(digest, secrect, salt, rounds) 22 | oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/account/login/access-token") 23 | 24 | 25 | def get_current_user(token: str = Security(oauth2_scheme)): 26 | try: 27 | token = decrypt(token) 28 | payload = jwt_decode(token) 29 | token_data = account_entity.TokenPayload(**payload) 30 | return token_data 31 | except JWTError: 32 | raise HTTPException(status_code=401, detail="token expire") 33 | 34 | def create_access_token(data): 35 | to_encode = data.copy() 36 | expire = datetime.utcnow() + timedelta(minutes=config.ACCESS_TOKEN_EXPIRE_MINUTES) 37 | to_encode.update({"exp": expire}) 38 | encoded_jwt = jwt.encode(to_encode, config.SECRET_KEY, algorithm=algorithm) 39 | return encrypt(encoded_jwt), expire 40 | 41 | def create_fresh_token(data): 42 | to_encode = data.copy() 43 | expire = datetime.utcnow() + timedelta(minutes=config.FRESH_TOKEN_EXPIRE_MINUTES) 44 | to_encode.update({"exp": expire}) 45 | encoded_jwt = jwt.encode(to_encode, config.SECRET_KEY, algorithm=algorithm) 46 | return encrypt(encoded_jwt), expire 47 | 48 | def encrypt(msg): 49 | ase_ciper = AES.new(secret_key, AES.MODE_GCM) 50 | cipher_text, auth_tag = ase_ciper.encrypt_and_digest(msg.encode(config.ENCODE_TYPE)) 51 | end = binascii.hexlify(cipher_text + auth_tag + ase_ciper.nonce).decode(config.ENCODE_TYPE) 52 | return end 53 | 54 | def decrypt(msg): 55 | try: 56 | byte_msg = binascii.unhexlify(msg.encode(config.ENCODE_TYPE)) 57 | cipher_text = byte_msg[:-2*config.SALT_SIZE] 58 | auth_tag = byte_msg[-2*config.SALT_SIZE:-config.SALT_SIZE] 59 | nonce = byte_msg[-config.SALT_SIZE:] 60 | ase_ciper = AES.new(secret_key, AES.MODE_GCM, nonce) 61 | decrypted = ase_ciper.decrypt_and_verify(cipher_text, auth_tag) 62 | end = decrypted.decode(config.ENCODE_TYPE) 63 | return end 64 | except Exception as e: 65 | print(e) 66 | return False 67 | 68 | def decrypt_renew(msg): 69 | try: 70 | byte_msg = binascii.unhexlify(msg.encode(config.ENCODE_TYPE)) 71 | cipher_text = byte_msg[:-2*config.SALT_SIZE] 72 | auth_tag = byte_msg[-2*config.SALT_SIZE:-config.SALT_SIZE] 73 | nonce = byte_msg[-config.SALT_SIZE:] 74 | ase_ciper = AES.new(secret_key, AES.MODE_GCM, nonce) 75 | decrypted = ase_ciper.decrypt_and_verify(cipher_text, auth_tag) 76 | end = decrypted.decode(config.ENCODE_TYPE) 77 | return jwt_decode(end) 78 | except Exception as e: 79 | print(e) 80 | return False 81 | 82 | def jwt_decode(msg: str): 83 | return jwt.decode(msg, config.SECRET_KEY, algorithms=[algorithm]) -------------------------------------------------------------------------------- /ml-api/app/settings/config.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import datetime 3 | import pytz 4 | 5 | 6 | cfg = configparser.ConfigParser() 7 | cfg.read('./environment.ini') 8 | 9 | 10 | #========================================================================= 11 | # TIMING CONFIG 12 | #========================================================================= 13 | u = datetime.datetime.utcnow() 14 | u = u.replace(tzinfo=pytz.timezone("Asia/Ho_Chi_Minh")) 15 | 16 | 17 | #========================================================================= 18 | # PROJECT INFORMATION 19 | #========================================================================= 20 | PROJECT = cfg['project'] 21 | PROJECT_NAME = PROJECT['name'] 22 | ENVIRONMENT = PROJECT['environment'] 23 | HOST = PROJECT['host'] 24 | PORT = PROJECT['port'] 25 | USER = PROJECT['user'] 26 | PASSWORD = PROJECT['password'] 27 | 28 | NGINX = cfg['nginx'] 29 | NGINX_HOST = NGINX['host'] 30 | FE_PORT = 3000 31 | 32 | 33 | 34 | #========================================================================= 35 | # AUTHENTICATE INFORMATION 36 | #========================================================================= 37 | AUTHENTICATE = cfg['authenticate'] 38 | ENCODE_TYPE = AUTHENTICATE['encode'] 39 | DIGEST = AUTHENTICATE['digest'] 40 | ALGORITHM = AUTHENTICATE['algorithm'] 41 | ROUNDS = AUTHENTICATE.getint('rounds') 42 | SALT_SIZE = AUTHENTICATE.getint('salt_size') 43 | SALT = bytes(AUTHENTICATE['salt'], "utf-8").decode('unicode_escape') 44 | ACCESS_TOKEN_EXPIRE_MINUTES = AUTHENTICATE.getint('access_expire') 45 | FRESH_TOKEN_EXPIRE_MINUTES = AUTHENTICATE.getint('fresh_expire') 46 | SECRET_KEY = AUTHENTICATE['secret_key'] 47 | 48 | 49 | #========================================================================= 50 | # DATABASE INFORMATION 51 | #========================================================================= 52 | DATABASE = cfg['database'] 53 | 54 | SQLALCHEMY_DATABASE_URL = "{type}://{user}:{pw}@{host}:{port}/{db_name}" \ 55 | .format( 56 | type = DATABASE['type'], 57 | user = DATABASE['user'], 58 | pw = DATABASE['pass'], 59 | host = DATABASE['host'], 60 | port = DATABASE['port'], 61 | db_name = DATABASE['database'], 62 | ) 63 | DATABASE_SCHEMA = DATABASE['schema'] 64 | 65 | #========================================================================= 66 | # REDIS INFORMATION 67 | #========================================================================= 68 | REDIS = cfg['redis'] 69 | REDIS_BACKEND = "redis://:{password}@{hostname}:{port}/{db}".format( 70 | hostname=REDIS['host'], 71 | password=REDIS['pass'], 72 | port=REDIS['port'], 73 | db=REDIS['db'] 74 | ) 75 | 76 | #========================================================================= 77 | # BROKER INFORMATION 78 | #========================================================================= 79 | RABBITMQ = cfg['rabbitmq'] 80 | BROKER = "amqp://{user}:{pw}@{hostname}:{port}/{vhost}".format( 81 | user=RABBITMQ['user'], 82 | pw=RABBITMQ['pass'], 83 | hostname=RABBITMQ['host'], 84 | port=RABBITMQ['post'], 85 | vhost=RABBITMQ['vhost'] 86 | ) 87 | 88 | #========================================================================= 89 | # ML INFORMATION 90 | #========================================================================= 91 | ML = cfg['ml'] 92 | ML_IMAGE_TYPE = ML['image_type'] 93 | ML_STORAGE_PATH = ML['storage_path'] 94 | ML_STORAGE_UPLOAD_PATH = ML['storage_upload_path'] 95 | ML_OBJECT_DETECTION_TASK = ML['object_detection_task'] 96 | ML_QUERY_NAME = ML['query_name'] 97 | 98 | 99 | -------------------------------------------------------------------------------- /ml-api/requirements.txt: -------------------------------------------------------------------------------- 1 | uvicorn[standard]==0.13.4 2 | gunicorn==20.1.0 3 | fastapi==0.63.0 4 | python-multipart==0.0.5 5 | psycopg2-binary==2.8.6 6 | configparser==5.0.2 7 | passlib==1.7.4 8 | python-jose==3.2.0 9 | pycryptodome==3.10.1 10 | alembic==1.6.5 11 | datetime==4.3 12 | celery==5.1.2 13 | flower==1.0.0 14 | redis==3.5.3 15 | numpy==1.21.2 16 | pillow==8.3.2 17 | opencv-python==4.5.3.56 -------------------------------------------------------------------------------- /ml-celery/Dockerfile: -------------------------------------------------------------------------------- 1 | #=============================================== 2 | #====== Using Python 3.9 build Imange ========= 3 | #=============================================== 4 | 5 | # pull official base image 6 | FROM python:3.9.5-slim-buster 7 | 8 | # maintainer 9 | LABEL maintainer="Duy Nguyen " 10 | 11 | # set work directory 12 | WORKDIR /app 13 | 14 | # set environment variables 15 | ENV PYTHONDONTWRITEBYTECODE 1 16 | ENV PYTHONUNBUFFERED 1 17 | 18 | # install dependencies 19 | RUN pip install --upgrade pip 20 | 21 | 22 | RUN apt-get update 23 | RUN apt-get install ffmpeg libsm6 libxext6 -y 24 | 25 | COPY ./requirements.txt /app/requirements.txt 26 | RUN pip install -r requirements.txt 27 | 28 | 29 | # copy project 30 | COPY ./app/ /app/ -------------------------------------------------------------------------------- /ml-celery/app/environment.ini: -------------------------------------------------------------------------------- 1 | [project] 2 | name = ML Models In Production 3 | be_port = 8081 4 | be_host = localhost 5 | 6 | 7 | [redis] 8 | host = redis 9 | port = 6379 10 | pass = password 11 | db = 0 12 | 13 | 14 | [rabbitmq] 15 | host = rabbitmq 16 | post = 5672 17 | user = guest 18 | pass = guest 19 | vhost = 20 | 21 | 22 | [celery] 23 | image_type = .JPEG 24 | query = ml_celery 25 | object_detection_task = object_detection 26 | storage_path = /storages/ 27 | storage_upload_path = /storages/upload/ 28 | storage_object_detection_path = /storages/object_detection/ 29 | 30 | [ml] 31 | model_path = ./worker/ml/models/object_detection/ 32 | label_path = ./worker/ml/models/object_detection/label_map.pbtxt 33 | mns_threshold = 0.4 34 | score_threshold = 0.6 35 | num_classes = 90 36 | max_class_out = 100 37 | -------------------------------------------------------------------------------- /ml-celery/app/helpers/storage.py: -------------------------------------------------------------------------------- 1 | import os 2 | from os import path 3 | 4 | 5 | def create_path(path_dir): 6 | if path.exists(path_dir): 7 | pass 8 | else: 9 | os.mkdir(path_dir) 10 | 11 | def upload_file_bytes(file_bytes, path): 12 | f = open(path, "wb") 13 | f.write(file_bytes) 14 | f.close() 15 | -------------------------------------------------------------------------------- /ml-celery/app/helpers/time.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import pytz 3 | 4 | 5 | def now_utc(): 6 | now = datetime.datetime.utcnow() 7 | now = now.replace(tzinfo=pytz.utc) 8 | return now 9 | 10 | def str_yyyy_mm_from_int(year, month, space = '-'): 11 | if month < 10: 12 | str_month = '0'+ str(month) 13 | else: 14 | str_month = str(month) 15 | return str(year) + space + str_month 16 | 17 | def str_yyyy_mm_dd(time: datetime = now_utc(), space = "-"): 18 | str_year = str(time.year) 19 | if time.month < 10: 20 | str_month = '0'+ str(time.month) 21 | else: 22 | str_month = str(time.month) 23 | if time.day < 10: 24 | str_day = '0'+ str(time.day) 25 | else: 26 | str_day = str(time.day) 27 | return str_year + space + str_month + space + str_day 28 | 29 | def str_yyyy_mm(time = now_utc(), space = "-"): 30 | str_year = str(time.year) 31 | if time.month < 10: 32 | str_month = '0'+ str(time.month) 33 | else: 34 | str_month = str(time.month) 35 | return str_year + space + str_month 36 | -------------------------------------------------------------------------------- /ml-celery/app/init_broker.py: -------------------------------------------------------------------------------- 1 | from kombu import Connection 2 | from kombu.exceptions import OperationalError 3 | from settings import config 4 | 5 | 6 | def is_broker_running(retries: int = 3) -> bool: 7 | try: 8 | conn = Connection(config.BROKER) 9 | conn.ensure_connection(max_retries=retries) 10 | except OperationalError as e: 11 | print("Failed to connect to RabbitMQ instance at %s", config.BROKER) 12 | print(str(e)) 13 | return False 14 | conn.close() 15 | return True -------------------------------------------------------------------------------- /ml-celery/app/init_redis.py: -------------------------------------------------------------------------------- 1 | from redis import Redis 2 | from redis.exceptions import ConnectionError 3 | from settings import config 4 | 5 | 6 | def is_backend_running() -> bool: 7 | try: 8 | conn = Redis( 9 | host=config.REDIS['host'], 10 | port=int(config.REDIS['port']), 11 | db=int(config.REDIS['db']), 12 | password=config.REDIS['pass'] 13 | ) 14 | conn.client_list() # Must perform an operation to check connection. 15 | except ConnectionError as e: 16 | print("Failed to connect to Redis instance at %s", config.REDIS_BACKEND) 17 | print(repr(e)) 18 | return False 19 | conn.close() 20 | return True -------------------------------------------------------------------------------- /ml-celery/app/mq_main.py: -------------------------------------------------------------------------------- 1 | from redis import Redis 2 | from settings import config 3 | 4 | 5 | redis = Redis( 6 | host=config.REDIS['host'], 7 | port=config.REDIS['port'], 8 | password=config.REDIS['pass'], 9 | db= config.REDIS['db'] 10 | ) 11 | -------------------------------------------------------------------------------- /ml-celery/app/settings/celery_config.py: -------------------------------------------------------------------------------- 1 | """Module with Celery configurations to Audio Length worker.""" 2 | from kombu import Queue 3 | import configparser 4 | 5 | 6 | cfg = configparser.ConfigParser() 7 | cfg.read('./environment.ini') 8 | 9 | #========================================================================= 10 | # CELERY INFORMATION 11 | #========================================================================= 12 | CELERY = cfg['celery'] 13 | 14 | # Set worker to ack only when return or failing (unhandled expection) 15 | task_acks_late = True 16 | 17 | # Worker only gets one task at a time 18 | worker_prefetch_multiplier = 1 19 | 20 | QUERY_NAME = CELERY["query"] 21 | 22 | # Create queue for worker 23 | task_queues = [Queue(name=QUERY_NAME)] 24 | 25 | # Set Redis key TTL (Time to live) 26 | result_expires = 60 * 60 * 48 # 48 hours in seconds 27 | 28 | 29 | # #========================================================================= 30 | # # ML INFORMATION 31 | # #========================================================================= 32 | ML_OBJECT_DETECTION_TASK_NAME = CELERY['object_detection_task'] 33 | ML_STORAGE_PATH = CELERY['storage_path'] 34 | ML_STORAGE_UPLOAD_PATH = CELERY['storage_upload_path'] 35 | ML_STORAGE_OBJECT_DETECTION_PATH = CELERY['storage_object_detection_path'] 36 | ML_IMAGE_TYPE = CELERY['image_type'] 37 | -------------------------------------------------------------------------------- /ml-celery/app/settings/config.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import datetime 3 | import pytz 4 | 5 | 6 | cfg = configparser.ConfigParser() 7 | cfg.read('./environment.ini') 8 | 9 | 10 | #========================================================================= 11 | # TIMING CONFIG 12 | #========================================================================= 13 | u = datetime.datetime.utcnow() 14 | u = u.replace(tzinfo=pytz.timezone("Asia/Ho_Chi_Minh")) 15 | 16 | #========================================================================= 17 | # PROJECT INFORMATION 18 | #========================================================================= 19 | PROJECT = cfg['project'] 20 | BE_HOST = PROJECT['be_host'] 21 | BE_PORT = PROJECT['be_port'] 22 | 23 | #========================================================================= 24 | # REDIS INFORMATION 25 | #========================================================================= 26 | REDIS = cfg['redis'] 27 | REDIS_BACKEND = "redis://:{password}@{hostname}:{port}/{db}".format( 28 | hostname=REDIS['host'], 29 | password=REDIS['pass'], 30 | port=REDIS['port'], 31 | db=REDIS['db'] 32 | ) 33 | 34 | #========================================================================= 35 | # BROKER INFORMATION 36 | #========================================================================= 37 | RABBITMQ = cfg['rabbitmq'] 38 | BROKER = "amqp://{user}:{pw}@{hostname}:{port}/{vhost}".format( 39 | user=RABBITMQ['user'], 40 | pw=RABBITMQ['pass'], 41 | hostname=RABBITMQ['host'], 42 | port=RABBITMQ['post'], 43 | vhost=RABBITMQ['vhost'] 44 | ) 45 | -------------------------------------------------------------------------------- /ml-celery/app/settings/ml_config.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | 3 | 4 | cfg = configparser.ConfigParser() 5 | cfg.read('./environment.ini') 6 | 7 | ML = cfg["ml"] 8 | MODEL_PATH = ML['model_path'] 9 | LABLE_PATH = ML['label_path'] 10 | 11 | NMS_THRESHOLD = float(ML['mns_threshold']) 12 | SCORE_THRESHOLD = float(ML['score_threshold']) 13 | NUMBER_CLASSES = int(ML['num_classes']) 14 | MAX_CLASS_OUT = int(ML['max_class_out']) -------------------------------------------------------------------------------- /ml-celery/app/tasks.py: -------------------------------------------------------------------------------- 1 | """[summary] 2 | @author Duy Nguyen Ngoc - duynguyenngoc@hotmail.com 3 | """ 4 | 5 | import json 6 | import logging 7 | import cv2 8 | from celery import Celery, Task 9 | from init_broker import is_broker_running 10 | from init_redis import is_backend_running 11 | 12 | from settings import config 13 | from mq_main import redis 14 | 15 | 16 | from worker.ml.model import CompletedModel 17 | from worker.ml.helpers import image_utils as ml_image_helper 18 | from helpers import time as time_helper 19 | from settings import (celery_config, config, ml_config) 20 | from helpers.storage import create_path 21 | 22 | 23 | if not is_backend_running(): exit() 24 | if not is_broker_running(): exit() 25 | 26 | 27 | app = Celery(celery_config.QUERY_NAME, broker=config.BROKER, backend=config.REDIS_BACKEND) 28 | app.config_from_object('settings.celery_config') 29 | 30 | 31 | class PredictTask(Task): 32 | """ 33 | Abstraction of Celery's Task class to support loading ML model. 34 | """ 35 | abstract = True 36 | 37 | def __init__(self): 38 | super().__init__() 39 | self.model = None 40 | 41 | def __call__(self, *args, **kwargs): 42 | """ 43 | Load model on first call (i.e. first task processed) 44 | Avoids the need to load model on each task request 45 | """ 46 | if not self.model: 47 | logging.info('Loading Model...') 48 | self.model = CompletedModel() 49 | logging.info('Model loaded') 50 | return self.run(*args, **kwargs) 51 | 52 | @app.task(bind=True, 53 | base=PredictTask, 54 | name="{query}.{task_name}".format( 55 | query=celery_config.QUERY_NAME, 56 | task_name=celery_config.ML_OBJECT_DETECTION_TASK_NAME)) 57 | def object_detection_task(self, task_id: str, data: bytes): 58 | """_summary_: object_detection by efi d2 model 59 | 60 | Args: 61 | task_id (str): _description_ 62 | data (bytes): _description_ 63 | 64 | Returns: 65 | _type_: _description_ 66 | """ 67 | data = json.loads(data) # load session data 68 | time = time_helper.now_utc() 69 | data['time']['start_detection'] = str(time_helper.now_utc().timestamp()) 70 | string_time = time_helper.str_yyyy_mm_dd(time) 71 | try: 72 | image = ml_image_helper.read_image_from_path_to_numpy(data['upload_result']['path']) 73 | image_draw = image.copy() 74 | height, width = image.shape[0:2] 75 | detections, category_index = self.model.detect(image) 76 | detection_boxes = detections['detection_boxes'] 77 | detection_scores = detections['detection_scores'] 78 | detection_classes = detections['detection_classes'] 79 | det_new = [] 80 | class_name_color = (0,255,0) 81 | box_color = (0,255,0) 82 | for j in range(len(detection_boxes)): 83 | box = detection_boxes[j] 84 | ymin, xmin, ymax, xmax = int(box[0]*height), int(box[1]*width), int(box[2]*height), int(box[3]*width) 85 | obj = {} 86 | obj['confidence_level'] = str(detection_scores[j]) 87 | obj['box'] = ",".join([str(xmin), str(ymin), str(xmax), str(ymax)]) 88 | obj['class_name'] = category_index[detection_classes[j]]['name'] 89 | det_new.append(obj) 90 | image_draw = cv2.rectangle(image_draw, (xmin, ymin), (xmax, ymax), box_color, 1) 91 | cv2.putText(image_draw, obj['class_name'], (xmin+5, ymin+20), cv2.FONT_HERSHEY_SIMPLEX, 0.9, class_name_color, 2) 92 | data['detection_draw_url'] = "http://{}:{}/api/v1/show-image/?path_image=".format(config.BE_HOST, config.BE_PORT) \ 93 | + celery_config.ML_STORAGE_OBJECT_DETECTION_PATH + string_time + '/' + str(task_id) + celery_config.ML_IMAGE_TYPE 94 | create_path(celery_config.ML_STORAGE_OBJECT_DETECTION_PATH + string_time) 95 | cv2.imwrite(celery_config.ML_STORAGE_OBJECT_DETECTION_PATH + string_time + '/' + str(task_id) + celery_config.ML_IMAGE_TYPE, image_draw) 96 | data['time']['end_detection'] = str(time_helper.now_utc().timestamp()) 97 | data['status']['detection_status'] = "SUCCESS" 98 | if len(det_new) > 0: 99 | data['detection_result'] = det_new 100 | data['status']['general_status'] = "SUCCESS" 101 | data_dump = json.dumps(data) 102 | redis.set(task_id, data_dump) 103 | except Exception as e: 104 | data['time']['end_detection'] = str(time_helper.now_utc().timestamp()) 105 | data['status']['detection_status'] = "FAILED" 106 | data['status']['general_status'] = "FAILED" 107 | data['error'] = str(e) 108 | data_dump = json.dumps(data) 109 | redis.set(task_id, data_dump) 110 | -------------------------------------------------------------------------------- /ml-celery/app/worker/ml/helpers/image_utils.py: -------------------------------------------------------------------------------- 1 | 2 | import cv2 3 | import requests 4 | import numpy as np 5 | from PIL import Image 6 | from io import BytesIO 7 | 8 | # import from file 9 | from settings import ml_config 10 | 11 | 12 | def read_image_from_path_to_numpy(path): 13 | image = Image.open(path) 14 | image = image.convert('RGB') 15 | return np.asarray(image) 16 | 17 | 18 | def read_image_file(file) -> Image.Image: 19 | image = Image.open(BytesIO(file)) 20 | return image 21 | 22 | 23 | def read_image_from_dir(path) -> Image.Image: 24 | image = Image.open(path) 25 | return image 26 | 27 | 28 | def read_bytes_image_from_url(url): 29 | response = requests.get(url) 30 | image_bytes = BytesIO(response.content) 31 | return image_bytes.read() 32 | 33 | 34 | def read_image_from_url(url): 35 | im = Image.open(requests.get(url, stream=True).raw) 36 | return im 37 | 38 | 39 | def io_bytes_to_numpy(io_bytes): 40 | image = Image.open(BytesIO(io_bytes)) 41 | image = image.convert('RGB') 42 | return np.asarray(image) 43 | 44 | 45 | 46 | def crop_and_recog_to_bytes(boxes, image): 47 | crop = [] 48 | def crop_bytes(ymin, xmin, ymax, xmax): 49 | crop_image = image[ymin:ymax, xmin:xmax] 50 | pil_im = Image.fromarray(crop_image) 51 | b = BytesIO() 52 | pil_im.save(b, 'jpeg') 53 | im_bytes = b.getvalue() 54 | return im_bytes 55 | if len(boxes) == 1: 56 | ymin, xmin, ymax, xmax = int(boxes[0][0]), int(boxes[0][1]), int(boxes[0][2]), int(boxes[0][3]) 57 | crop.append(crop_bytes(ymin, xmin, ymax, xmax)) 58 | else: 59 | for box in boxes: 60 | ymin, xmin, ymax, xmax = int(box[0]), int(box[1]), int(box[2]), int(box[3]) 61 | crop.append(crop_bytes(ymin, xmin, ymax, xmax)) 62 | return crop 63 | 64 | 65 | def get_center_point(coordinate_dict: dict): 66 | di = dict() 67 | for key in coordinate_dict.keys(): 68 | xmin, ymin, xmax, ymax = coordinate_dict[key] 69 | x_center = (xmin + xmax) / 2 70 | y_center = (ymin + ymax) / 2 71 | di[key] = (x_center, y_center) 72 | return di 73 | 74 | 75 | def sort_result_detection_follow_class_id(detection_boxes, detection_scores, detection_classes, num_class): 76 | detection_boxes = np.array(detection_boxes) 77 | detection_classes= np.array(detection_classes) 78 | detection_scores = np.array(detection_scores) 79 | end_boxes = [] 80 | end_score = [] 81 | end_class = [] 82 | for i in range(num_class): 83 | class_id = i + 1 84 | score = np.array(detection_scores[detection_classes == class_id]) 85 | boxes = np.array(detection_boxes[detection_classes == class_id]) 86 | end_boxes.append(boxes) 87 | end_score.append(score) 88 | end_class.append(class_id) 89 | return end_boxes, end_score, end_class -------------------------------------------------------------------------------- /ml-celery/app/worker/ml/helpers/keypoint_ops.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 The TensorFlow Authors. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | 16 | """Keypoint operations. 17 | 18 | Keypoints are represented as tensors of shape [num_instances, num_keypoints, 2], 19 | where the last dimension holds rank 2 tensors of the form [y, x] representing 20 | the coordinates of the keypoint. 21 | """ 22 | import numpy as np 23 | import tensorflow.compat.v1 as tf 24 | 25 | 26 | def scale(keypoints, y_scale, x_scale, scope=None): 27 | """Scales keypoint coordinates in x and y dimensions. 28 | 29 | Args: 30 | keypoints: a tensor of shape [num_instances, num_keypoints, 2] 31 | y_scale: (float) scalar tensor 32 | x_scale: (float) scalar tensor 33 | scope: name scope. 34 | 35 | Returns: 36 | new_keypoints: a tensor of shape [num_instances, num_keypoints, 2] 37 | """ 38 | with tf.name_scope(scope, 'Scale'): 39 | y_scale = tf.cast(y_scale, tf.float32) 40 | x_scale = tf.cast(x_scale, tf.float32) 41 | new_keypoints = keypoints * [[[y_scale, x_scale]]] 42 | return new_keypoints 43 | 44 | 45 | def clip_to_window(keypoints, window, scope=None): 46 | """Clips keypoints to a window. 47 | 48 | This op clips any input keypoints to a window. 49 | 50 | Args: 51 | keypoints: a tensor of shape [num_instances, num_keypoints, 2] 52 | window: a tensor of shape [4] representing the [y_min, x_min, y_max, x_max] 53 | window to which the op should clip the keypoints. 54 | scope: name scope. 55 | 56 | Returns: 57 | new_keypoints: a tensor of shape [num_instances, num_keypoints, 2] 58 | """ 59 | keypoints.get_shape().assert_has_rank(3) 60 | with tf.name_scope(scope, 'ClipToWindow'): 61 | y, x = tf.split(value=keypoints, num_or_size_splits=2, axis=2) 62 | win_y_min, win_x_min, win_y_max, win_x_max = tf.unstack(window) 63 | y = tf.maximum(tf.minimum(y, win_y_max), win_y_min) 64 | x = tf.maximum(tf.minimum(x, win_x_max), win_x_min) 65 | new_keypoints = tf.concat([y, x], 2) 66 | return new_keypoints 67 | 68 | 69 | def prune_outside_window(keypoints, window, scope=None): 70 | """Prunes keypoints that fall outside a given window. 71 | 72 | This function replaces keypoints that fall outside the given window with nan. 73 | See also clip_to_window which clips any keypoints that fall outside the given 74 | window. 75 | 76 | Args: 77 | keypoints: a tensor of shape [num_instances, num_keypoints, 2] 78 | window: a tensor of shape [4] representing the [y_min, x_min, y_max, x_max] 79 | window outside of which the op should prune the keypoints. 80 | scope: name scope. 81 | 82 | Returns: 83 | new_keypoints: a tensor of shape [num_instances, num_keypoints, 2] 84 | """ 85 | keypoints.get_shape().assert_has_rank(3) 86 | with tf.name_scope(scope, 'PruneOutsideWindow'): 87 | y, x = tf.split(value=keypoints, num_or_size_splits=2, axis=2) 88 | win_y_min, win_x_min, win_y_max, win_x_max = tf.unstack(window) 89 | 90 | valid_indices = tf.logical_and( 91 | tf.logical_and(y >= win_y_min, y <= win_y_max), 92 | tf.logical_and(x >= win_x_min, x <= win_x_max)) 93 | 94 | new_y = tf.where(valid_indices, y, np.nan * tf.ones_like(y)) 95 | new_x = tf.where(valid_indices, x, np.nan * tf.ones_like(x)) 96 | new_keypoints = tf.concat([new_y, new_x], 2) 97 | 98 | return new_keypoints 99 | 100 | 101 | def change_coordinate_frame(keypoints, window, scope=None): 102 | """Changes coordinate frame of the keypoints to be relative to window's frame. 103 | 104 | Given a window of the form [y_min, x_min, y_max, x_max], changes keypoint 105 | coordinates from keypoints of shape [num_instances, num_keypoints, 2] 106 | to be relative to this window. 107 | 108 | An example use case is data augmentation: where we are given groundtruth 109 | keypoints and would like to randomly crop the image to some window. In this 110 | case we need to change the coordinate frame of each groundtruth keypoint to be 111 | relative to this new window. 112 | 113 | Args: 114 | keypoints: a tensor of shape [num_instances, num_keypoints, 2] 115 | window: a tensor of shape [4] representing the [y_min, x_min, y_max, x_max] 116 | window we should change the coordinate frame to. 117 | scope: name scope. 118 | 119 | Returns: 120 | new_keypoints: a tensor of shape [num_instances, num_keypoints, 2] 121 | """ 122 | with tf.name_scope(scope, 'ChangeCoordinateFrame'): 123 | win_height = window[2] - window[0] 124 | win_width = window[3] - window[1] 125 | new_keypoints = scale(keypoints - [window[0], window[1]], 1.0 / win_height, 126 | 1.0 / win_width) 127 | return new_keypoints 128 | 129 | 130 | def keypoints_to_enclosing_bounding_boxes(keypoints, keypoints_axis=1): 131 | """Creates enclosing bounding boxes from keypoints. 132 | 133 | Args: 134 | keypoints: a [num_instances, num_keypoints, 2] float32 tensor with keypoints 135 | in [y, x] format. 136 | keypoints_axis: An integer indicating the axis that correspond to the 137 | keypoint dimension. 138 | 139 | Returns: 140 | A [num_instances, 4] float32 tensor that tightly covers all the keypoints 141 | for each instance. 142 | """ 143 | ymin = tf.math.reduce_min(keypoints[..., 0], axis=keypoints_axis) 144 | xmin = tf.math.reduce_min(keypoints[..., 1], axis=keypoints_axis) 145 | ymax = tf.math.reduce_max(keypoints[..., 0], axis=keypoints_axis) 146 | xmax = tf.math.reduce_max(keypoints[..., 1], axis=keypoints_axis) 147 | return tf.stack([ymin, xmin, ymax, xmax], axis=keypoints_axis) 148 | 149 | 150 | def to_normalized_coordinates(keypoints, height, width, 151 | check_range=True, scope=None): 152 | """Converts absolute keypoint coordinates to normalized coordinates in [0, 1]. 153 | 154 | Usually one uses the dynamic shape of the image or conv-layer tensor: 155 | keypoints = keypoint_ops.to_normalized_coordinates(keypoints, 156 | tf.shape(images)[1], 157 | tf.shape(images)[2]), 158 | 159 | This function raises an assertion failed error at graph execution time when 160 | the maximum coordinate is smaller than 1.01 (which means that coordinates are 161 | already normalized). The value 1.01 is to deal with small rounding errors. 162 | 163 | Args: 164 | keypoints: A tensor of shape [num_instances, num_keypoints, 2]. 165 | height: Maximum value for y coordinate of absolute keypoint coordinates. 166 | width: Maximum value for x coordinate of absolute keypoint coordinates. 167 | check_range: If True, checks if the coordinates are normalized. 168 | scope: name scope. 169 | 170 | Returns: 171 | tensor of shape [num_instances, num_keypoints, 2] with normalized 172 | coordinates in [0, 1]. 173 | """ 174 | with tf.name_scope(scope, 'ToNormalizedCoordinates'): 175 | height = tf.cast(height, tf.float32) 176 | width = tf.cast(width, tf.float32) 177 | 178 | if check_range: 179 | max_val = tf.reduce_max(keypoints) 180 | max_assert = tf.Assert(tf.greater(max_val, 1.01), 181 | ['max value is lower than 1.01: ', max_val]) 182 | with tf.control_dependencies([max_assert]): 183 | width = tf.identity(width) 184 | 185 | return scale(keypoints, 1.0 / height, 1.0 / width) 186 | 187 | 188 | def to_absolute_coordinates(keypoints, height, width, 189 | check_range=True, scope=None): 190 | """Converts normalized keypoint coordinates to absolute pixel coordinates. 191 | 192 | This function raises an assertion failed error when the maximum keypoint 193 | coordinate value is larger than 1.01 (in which case coordinates are already 194 | absolute). 195 | 196 | Args: 197 | keypoints: A tensor of shape [num_instances, num_keypoints, 2] 198 | height: Maximum value for y coordinate of absolute keypoint coordinates. 199 | width: Maximum value for x coordinate of absolute keypoint coordinates. 200 | check_range: If True, checks if the coordinates are normalized or not. 201 | scope: name scope. 202 | 203 | Returns: 204 | tensor of shape [num_instances, num_keypoints, 2] with absolute coordinates 205 | in terms of the image size. 206 | 207 | """ 208 | with tf.name_scope(scope, 'ToAbsoluteCoordinates'): 209 | height = tf.cast(height, tf.float32) 210 | width = tf.cast(width, tf.float32) 211 | 212 | # Ensure range of input keypoints is correct. 213 | if check_range: 214 | max_val = tf.reduce_max(keypoints) 215 | max_assert = tf.Assert(tf.greater_equal(1.01, max_val), 216 | ['maximum keypoint coordinate value is larger ' 217 | 'than 1.01: ', max_val]) 218 | with tf.control_dependencies([max_assert]): 219 | width = tf.identity(width) 220 | 221 | return scale(keypoints, height, width) 222 | 223 | 224 | def flip_horizontal(keypoints, flip_point, flip_permutation=None, scope=None): 225 | """Flips the keypoints horizontally around the flip_point. 226 | 227 | This operation flips the x coordinate for each keypoint around the flip_point 228 | and also permutes the keypoints in a manner specified by flip_permutation. 229 | 230 | Args: 231 | keypoints: a tensor of shape [num_instances, num_keypoints, 2] 232 | flip_point: (float) scalar tensor representing the x coordinate to flip the 233 | keypoints around. 234 | flip_permutation: integer list or rank 1 int32 tensor containing the 235 | keypoint flip permutation. This specifies the mapping from original 236 | keypoint indices to the flipped keypoint indices. This is used primarily 237 | for keypoints that are not reflection invariant. E.g. Suppose there are 3 238 | keypoints representing ['head', 'right_eye', 'left_eye'], then a logical 239 | choice for flip_permutation might be [0, 2, 1] since we want to swap the 240 | 'left_eye' and 'right_eye' after a horizontal flip. 241 | Default to None or empty list to keep the original order after flip. 242 | scope: name scope. 243 | 244 | Returns: 245 | new_keypoints: a tensor of shape [num_instances, num_keypoints, 2] 246 | """ 247 | keypoints.get_shape().assert_has_rank(3) 248 | with tf.name_scope(scope, 'FlipHorizontal'): 249 | keypoints = tf.transpose(keypoints, [1, 0, 2]) 250 | if flip_permutation: 251 | keypoints = tf.gather(keypoints, flip_permutation) 252 | v, u = tf.split(value=keypoints, num_or_size_splits=2, axis=2) 253 | u = flip_point * 2.0 - u 254 | new_keypoints = tf.concat([v, u], 2) 255 | new_keypoints = tf.transpose(new_keypoints, [1, 0, 2]) 256 | return new_keypoints 257 | 258 | 259 | def flip_vertical(keypoints, flip_point, flip_permutation=None, scope=None): 260 | """Flips the keypoints vertically around the flip_point. 261 | 262 | This operation flips the y coordinate for each keypoint around the flip_point 263 | and also permutes the keypoints in a manner specified by flip_permutation. 264 | 265 | Args: 266 | keypoints: a tensor of shape [num_instances, num_keypoints, 2] 267 | flip_point: (float) scalar tensor representing the y coordinate to flip the 268 | keypoints around. 269 | flip_permutation: integer list or rank 1 int32 tensor containing the 270 | keypoint flip permutation. This specifies the mapping from original 271 | keypoint indices to the flipped keypoint indices. This is used primarily 272 | for keypoints that are not reflection invariant. E.g. Suppose there are 3 273 | keypoints representing ['head', 'right_eye', 'left_eye'], then a logical 274 | choice for flip_permutation might be [0, 2, 1] since we want to swap the 275 | 'left_eye' and 'right_eye' after a horizontal flip. 276 | Default to None or empty list to keep the original order after flip. 277 | scope: name scope. 278 | 279 | Returns: 280 | new_keypoints: a tensor of shape [num_instances, num_keypoints, 2] 281 | """ 282 | keypoints.get_shape().assert_has_rank(3) 283 | with tf.name_scope(scope, 'FlipVertical'): 284 | keypoints = tf.transpose(keypoints, [1, 0, 2]) 285 | if flip_permutation: 286 | keypoints = tf.gather(keypoints, flip_permutation) 287 | v, u = tf.split(value=keypoints, num_or_size_splits=2, axis=2) 288 | v = flip_point * 2.0 - v 289 | new_keypoints = tf.concat([v, u], 2) 290 | new_keypoints = tf.transpose(new_keypoints, [1, 0, 2]) 291 | return new_keypoints 292 | 293 | 294 | def rot90(keypoints, rotation_permutation=None, scope=None): 295 | """Rotates the keypoints counter-clockwise by 90 degrees. 296 | 297 | Args: 298 | keypoints: a tensor of shape [num_instances, num_keypoints, 2] 299 | rotation_permutation: integer list or rank 1 int32 tensor containing the 300 | keypoint flip permutation. This specifies the mapping from original 301 | keypoint indices to the rotated keypoint indices. This is used primarily 302 | for keypoints that are not rotation invariant. 303 | Default to None or empty list to keep the original order after rotation. 304 | scope: name scope. 305 | Returns: 306 | new_keypoints: a tensor of shape [num_instances, num_keypoints, 2] 307 | """ 308 | keypoints.get_shape().assert_has_rank(3) 309 | with tf.name_scope(scope, 'Rot90'): 310 | keypoints = tf.transpose(keypoints, [1, 0, 2]) 311 | if rotation_permutation: 312 | keypoints = tf.gather(keypoints, rotation_permutation) 313 | v, u = tf.split(value=keypoints[:, :, ::-1], num_or_size_splits=2, axis=2) 314 | v = 1.0 - v 315 | new_keypoints = tf.concat([v, u], 2) 316 | new_keypoints = tf.transpose(new_keypoints, [1, 0, 2]) 317 | return new_keypoints 318 | 319 | 320 | 321 | 322 | def keypoint_weights_from_visibilities(keypoint_visibilities, 323 | per_keypoint_weights=None): 324 | """Returns a keypoint weights tensor. 325 | 326 | During training, it is often beneficial to consider only those keypoints that 327 | are labeled. This function returns a weights tensor that combines default 328 | per-keypoint weights, as well as the visibilities of individual keypoints. 329 | 330 | The returned tensor satisfies: 331 | keypoint_weights[i, k] = per_keypoint_weights[k] * keypoint_visibilities[i, k] 332 | where per_keypoint_weights[k] is set to 1 if not provided. 333 | 334 | Args: 335 | keypoint_visibilities: A [num_instances, num_keypoints] boolean tensor 336 | indicating whether a keypoint is labeled (and perhaps even visible). 337 | per_keypoint_weights: A list or 1-d tensor of length `num_keypoints` with 338 | per-keypoint weights. If None, will use 1 for each visible keypoint 339 | weight. 340 | 341 | Returns: 342 | A [num_instances, num_keypoints] float32 tensor with keypoint weights. Those 343 | keypoints deemed visible will have the provided per-keypoint weight, and 344 | all others will be set to zero. 345 | """ 346 | keypoint_visibilities.get_shape().assert_has_rank(2) 347 | if per_keypoint_weights is None: 348 | num_keypoints = keypoint_visibilities.shape.as_list()[1] 349 | per_keypoint_weight_mult = tf.ones((1, num_keypoints,), dtype=tf.float32) 350 | else: 351 | per_keypoint_weight_mult = tf.expand_dims(per_keypoint_weights, axis=0) 352 | return per_keypoint_weight_mult * tf.cast(keypoint_visibilities, tf.float32) 353 | 354 | 355 | def set_keypoint_visibilities(keypoints, initial_keypoint_visibilities=None): 356 | """Sets keypoint visibilities based on valid/invalid keypoints. 357 | 358 | Some keypoint operations set invisible keypoints (e.g. cropped keypoints) to 359 | NaN, without affecting any keypoint "visibility" variables. This function is 360 | used to update (or create) keypoint visibilities to agree with visible / 361 | invisible keypoint coordinates. 362 | 363 | Args: 364 | keypoints: a float32 tensor of shape [num_instances, num_keypoints, 2]. 365 | initial_keypoint_visibilities: a boolean tensor of shape 366 | [num_instances, num_keypoints]. If provided, will maintain the visibility 367 | designation of a keypoint, so long as the corresponding coordinates are 368 | not NaN. If not provided, will create keypoint visibilities directly from 369 | the values in `keypoints` (i.e. NaN coordinates map to False, otherwise 370 | they map to True). 371 | 372 | Returns: 373 | keypoint_visibilities: a bool tensor of shape [num_instances, num_keypoints] 374 | indicating whether a keypoint is visible or not. 375 | """ 376 | keypoints.get_shape().assert_has_rank(3) 377 | if initial_keypoint_visibilities is not None: 378 | keypoint_visibilities = tf.cast(initial_keypoint_visibilities, tf.bool) 379 | else: 380 | keypoint_visibilities = tf.ones_like(keypoints[:, :, 0], dtype=tf.bool) 381 | 382 | keypoints_with_nan = tf.math.reduce_any(tf.math.is_nan(keypoints), axis=2) 383 | keypoint_visibilities = tf.where( 384 | keypoints_with_nan, 385 | tf.zeros_like(keypoint_visibilities, dtype=tf.bool), 386 | keypoint_visibilities) 387 | return keypoint_visibilities 388 | -------------------------------------------------------------------------------- /ml-celery/app/worker/ml/helpers/load_label_map.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import logging 3 | from google.protobuf import text_format 4 | from worker.ml.helpers import string_int_label_map_pb2 5 | 6 | 7 | def _validate_label_map(label_map): 8 | """Checks if a label map is valid. 9 | 10 | Args: 11 | label_map: StringIntLabelMap to validate. 12 | 13 | Raises: 14 | ValueError: if label map is invalid. 15 | """ 16 | for item in label_map.item: 17 | if item.id < 0: 18 | raise ValueError('Label map ids should be >= 0.') 19 | if (item.id == 0 and item.name != 'background' and 20 | item.display_name != 'background'): 21 | raise ValueError('Label map id 0 is reserved for the background label') 22 | 23 | 24 | def create_category_index(categories): 25 | """Creates dictionary of COCO compatible categories keyed by category id. 26 | 27 | Args: 28 | categories: a list of dicts, each of which has the following keys: 29 | 'id': (required) an integer id uniquely identifying this category. 30 | 'name': (required) string representing category name 31 | e.g., 'cat', 'dog', 'pizza'. 32 | 33 | Returns: 34 | category_index: a dict containing the same entries as categories, but keyed 35 | by the 'id' field of each category. 36 | """ 37 | category_index = {} 38 | for cat in categories: 39 | category_index[cat['id']] = cat 40 | return category_index 41 | 42 | 43 | def convert_label_map_to_categories(label_map, 44 | max_num_classes, 45 | use_display_name=True): 46 | """Given label map proto returns categories list compatible with eval. 47 | 48 | This function converts label map proto and returns a list of dicts, each of 49 | which has the following keys: 50 | 'id': (required) an integer id uniquely identifying this category. 51 | 'name': (required) string representing category name 52 | e.g., 'cat', 'dog', 'pizza'. 53 | 'keypoints': (optional) a dictionary of keypoint string 'label' to integer 54 | 'id'. 55 | We only allow class into the list if its id-label_id_offset is 56 | between 0 (inclusive) and max_num_classes (exclusive). 57 | If there are several items mapping to the same id in the label map, 58 | we will only keep the first one in the categories list. 59 | 60 | Args: 61 | label_map: a StringIntLabelMapProto or None. If None, a default categories 62 | list is created with max_num_classes categories. 63 | max_num_classes: maximum number of (consecutive) label indices to include. 64 | use_display_name: (boolean) choose whether to load 'display_name' field as 65 | category name. If False or if the display_name field does not exist, uses 66 | 'name' field as category names instead. 67 | 68 | Returns: 69 | categories: a list of dictionaries representing all possible categories. 70 | """ 71 | categories = [] 72 | list_of_ids_already_added = [] 73 | if not label_map: 74 | label_id_offset = 1 75 | for class_id in range(max_num_classes): 76 | categories.append({ 77 | 'id': class_id + label_id_offset, 78 | 'name': 'category_{}'.format(class_id + label_id_offset) 79 | }) 80 | return categories 81 | for item in label_map.item: 82 | if not 0 < item.id <= max_num_classes: 83 | logging.info( 84 | 'Ignore item %d since it falls outside of requested ' 85 | 'label range.', item.id) 86 | continue 87 | if use_display_name and item.HasField('display_name'): 88 | name = item.display_name 89 | else: 90 | name = item.name 91 | if item.id not in list_of_ids_already_added: 92 | list_of_ids_already_added.append(item.id) 93 | category = {'id': item.id, 'name': name} 94 | if item.keypoints: 95 | keypoints = {} 96 | list_of_keypoint_ids = [] 97 | for kv in item.keypoints: 98 | if kv.id in list_of_keypoint_ids: 99 | raise ValueError('Duplicate keypoint ids are not allowed. ' 100 | 'Found {} more than once'.format(kv.id)) 101 | keypoints[kv.label] = kv.id 102 | list_of_keypoint_ids.append(kv.id) 103 | category['keypoints'] = keypoints 104 | categories.append(category) 105 | return categories 106 | 107 | 108 | def load_labelmap(path): 109 | """Loads label map proto. 110 | 111 | Args: 112 | path: path to StringIntLabelMap proto text file. 113 | Returns: 114 | a StringIntLabelMapProto 115 | """ 116 | with tf.io.gfile.GFile(path, 'r') as fid: 117 | label_map_string = fid.read() 118 | label_map = string_int_label_map_pb2.StringIntLabelMap() 119 | try: 120 | text_format.Merge(label_map_string, label_map) 121 | except text_format.ParseError: 122 | label_map.ParseFromString(label_map_string) 123 | _validate_label_map(label_map) 124 | return label_map 125 | 126 | 127 | def create_categories_from_labelmap(label_map_path, use_display_name=True): 128 | """Reads a label map and returns categories list compatible with eval. 129 | 130 | This function converts label map proto and returns a list of dicts, each of 131 | which has the following keys: 132 | 'id': an integer id uniquely identifying this category. 133 | 'name': string representing category name e.g., 'cat', 'dog'. 134 | 'keypoints': a dictionary of keypoint string label to integer id. It is only 135 | returned when available in label map proto. 136 | 137 | Args: 138 | label_map_path: Path to `StringIntLabelMap` proto text file. 139 | use_display_name: (boolean) choose whether to load 'display_name' field 140 | as category name. If False or if the display_name field does not exist, 141 | uses 'name' field as category names instead. 142 | 143 | Returns: 144 | categories: a list of dictionaries representing all possible categories. 145 | """ 146 | label_map = load_labelmap(label_map_path) 147 | max_num_classes = max(item.id for item in label_map.item) 148 | return convert_label_map_to_categories(label_map, max_num_classes, 149 | use_display_name) 150 | 151 | 152 | def create_category_index_from_labelmap(label_map_path, use_display_name=True): 153 | """Reads a label map and returns a category index. 154 | 155 | Args: 156 | label_map_path: Path to `StringIntLabelMap` proto text file. 157 | use_display_name: (boolean) choose whether to load 'display_name' field 158 | as category name. If False or if the display_name field does not exist, 159 | uses 'name' field as category names instead. 160 | 161 | Returns: 162 | A category index, which is a dictionary that maps integer ids to dicts 163 | containing categories, e.g. 164 | {1: {'id': 1, 'name': 'dog'}, 2: {'id': 2, 'name': 'cat'}, ...} 165 | """ 166 | categories = create_categories_from_labelmap(label_map_path, use_display_name) 167 | return create_category_index(categories) 168 | -------------------------------------------------------------------------------- /ml-celery/app/worker/ml/helpers/static_shape.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 The TensorFlow Authors. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | 16 | """Helper functions to access TensorShape values. 17 | 18 | The rank 4 tensor_shape must be of the form [batch_size, height, width, depth]. 19 | """ 20 | 21 | from __future__ import absolute_import 22 | from __future__ import division 23 | from __future__ import print_function 24 | 25 | 26 | def get_dim_as_int(dim): 27 | """Utility to get v1 or v2 TensorShape dim as an int. 28 | 29 | Args: 30 | dim: The TensorShape dimension to get as an int 31 | 32 | Returns: 33 | None or an int. 34 | """ 35 | try: 36 | return dim.value 37 | except AttributeError: 38 | return dim 39 | 40 | 41 | def get_batch_size(tensor_shape): 42 | """Returns batch size from the tensor shape. 43 | 44 | Args: 45 | tensor_shape: A rank 4 TensorShape. 46 | 47 | Returns: 48 | An integer representing the batch size of the tensor. 49 | """ 50 | tensor_shape.assert_has_rank(rank=4) 51 | return get_dim_as_int(tensor_shape[0]) 52 | 53 | 54 | def get_height(tensor_shape): 55 | """Returns height from the tensor shape. 56 | 57 | Args: 58 | tensor_shape: A rank 4 TensorShape. 59 | 60 | Returns: 61 | An integer representing the height of the tensor. 62 | """ 63 | tensor_shape.assert_has_rank(rank=4) 64 | return get_dim_as_int(tensor_shape[1]) 65 | 66 | 67 | def get_width(tensor_shape): 68 | """Returns width from the tensor shape. 69 | 70 | Args: 71 | tensor_shape: A rank 4 TensorShape. 72 | 73 | Returns: 74 | An integer representing the width of the tensor. 75 | """ 76 | tensor_shape.assert_has_rank(rank=4) 77 | return get_dim_as_int(tensor_shape[2]) 78 | 79 | 80 | def get_depth(tensor_shape): 81 | """Returns depth from the tensor shape. 82 | 83 | Args: 84 | tensor_shape: A rank 4 TensorShape. 85 | 86 | Returns: 87 | An integer representing the depth of the tensor. 88 | """ 89 | tensor_shape.assert_has_rank(rank=4) 90 | return get_dim_as_int(tensor_shape[3]) 91 | -------------------------------------------------------------------------------- /ml-celery/app/worker/ml/helpers/string_int_label_map_pb2.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: object_detection/protos/string_int_label_map.proto 3 | 4 | import sys 5 | _b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import message as _message 8 | from google.protobuf import reflection as _reflection 9 | from google.protobuf import symbol_database as _symbol_database 10 | from google.protobuf import descriptor_pb2 11 | # @@protoc_insertion_point(imports) 12 | 13 | _sym_db = _symbol_database.Default() 14 | 15 | 16 | 17 | 18 | DESCRIPTOR = _descriptor.FileDescriptor( 19 | name='object_detection/protos/string_int_label_map.proto', 20 | package='object_detection.protos', 21 | syntax='proto2', 22 | serialized_pb=_b('\n2object_detection/protos/string_int_label_map.proto\x12\x17object_detection.protos\"\xee\x01\n\x15StringIntLabelMapItem\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x14\n\x0c\x64isplay_name\x18\x03 \x01(\t\x12M\n\tkeypoints\x18\x04 \x03(\x0b\x32:.object_detection.protos.StringIntLabelMapItem.KeypointMap\x12\x14\n\x0c\x61ncestor_ids\x18\x05 \x03(\x05\x12\x16\n\x0e\x64\x65scendant_ids\x18\x06 \x03(\x05\x1a(\n\x0bKeypointMap\x12\n\n\x02id\x18\x01 \x01(\x05\x12\r\n\x05label\x18\x02 \x01(\t\"Q\n\x11StringIntLabelMap\x12<\n\x04item\x18\x01 \x03(\x0b\x32..object_detection.protos.StringIntLabelMapItem') 23 | ) 24 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 25 | 26 | 27 | 28 | 29 | _STRINGINTLABELMAPITEM_KEYPOINTMAP = _descriptor.Descriptor( 30 | name='KeypointMap', 31 | full_name='object_detection.protos.StringIntLabelMapItem.KeypointMap', 32 | filename=None, 33 | file=DESCRIPTOR, 34 | containing_type=None, 35 | fields=[ 36 | _descriptor.FieldDescriptor( 37 | name='id', full_name='object_detection.protos.StringIntLabelMapItem.KeypointMap.id', index=0, 38 | number=1, type=5, cpp_type=1, label=1, 39 | has_default_value=False, default_value=0, 40 | message_type=None, enum_type=None, containing_type=None, 41 | is_extension=False, extension_scope=None, 42 | options=None), 43 | _descriptor.FieldDescriptor( 44 | name='label', full_name='object_detection.protos.StringIntLabelMapItem.KeypointMap.label', index=1, 45 | number=2, type=9, cpp_type=9, label=1, 46 | has_default_value=False, default_value=_b("").decode('utf-8'), 47 | message_type=None, enum_type=None, containing_type=None, 48 | is_extension=False, extension_scope=None, 49 | options=None), 50 | ], 51 | extensions=[ 52 | ], 53 | nested_types=[], 54 | enum_types=[ 55 | ], 56 | options=None, 57 | is_extendable=False, 58 | syntax='proto2', 59 | extension_ranges=[], 60 | oneofs=[ 61 | ], 62 | serialized_start=278, 63 | serialized_end=318, 64 | ) 65 | 66 | _STRINGINTLABELMAPITEM = _descriptor.Descriptor( 67 | name='StringIntLabelMapItem', 68 | full_name='object_detection.protos.StringIntLabelMapItem', 69 | filename=None, 70 | file=DESCRIPTOR, 71 | containing_type=None, 72 | fields=[ 73 | _descriptor.FieldDescriptor( 74 | name='name', full_name='object_detection.protos.StringIntLabelMapItem.name', index=0, 75 | number=1, type=9, cpp_type=9, label=1, 76 | has_default_value=False, default_value=_b("").decode('utf-8'), 77 | message_type=None, enum_type=None, containing_type=None, 78 | is_extension=False, extension_scope=None, 79 | options=None), 80 | _descriptor.FieldDescriptor( 81 | name='id', full_name='object_detection.protos.StringIntLabelMapItem.id', index=1, 82 | number=2, type=5, cpp_type=1, label=1, 83 | has_default_value=False, default_value=0, 84 | message_type=None, enum_type=None, containing_type=None, 85 | is_extension=False, extension_scope=None, 86 | options=None), 87 | _descriptor.FieldDescriptor( 88 | name='display_name', full_name='object_detection.protos.StringIntLabelMapItem.display_name', index=2, 89 | number=3, type=9, cpp_type=9, label=1, 90 | has_default_value=False, default_value=_b("").decode('utf-8'), 91 | message_type=None, enum_type=None, containing_type=None, 92 | is_extension=False, extension_scope=None, 93 | options=None), 94 | _descriptor.FieldDescriptor( 95 | name='keypoints', full_name='object_detection.protos.StringIntLabelMapItem.keypoints', index=3, 96 | number=4, type=11, cpp_type=10, label=3, 97 | has_default_value=False, default_value=[], 98 | message_type=None, enum_type=None, containing_type=None, 99 | is_extension=False, extension_scope=None, 100 | options=None), 101 | _descriptor.FieldDescriptor( 102 | name='ancestor_ids', full_name='object_detection.protos.StringIntLabelMapItem.ancestor_ids', index=4, 103 | number=5, type=5, cpp_type=1, label=3, 104 | has_default_value=False, default_value=[], 105 | message_type=None, enum_type=None, containing_type=None, 106 | is_extension=False, extension_scope=None, 107 | options=None), 108 | _descriptor.FieldDescriptor( 109 | name='descendant_ids', full_name='object_detection.protos.StringIntLabelMapItem.descendant_ids', index=5, 110 | number=6, type=5, cpp_type=1, label=3, 111 | has_default_value=False, default_value=[], 112 | message_type=None, enum_type=None, containing_type=None, 113 | is_extension=False, extension_scope=None, 114 | options=None), 115 | ], 116 | extensions=[ 117 | ], 118 | nested_types=[_STRINGINTLABELMAPITEM_KEYPOINTMAP, ], 119 | enum_types=[ 120 | ], 121 | options=None, 122 | is_extendable=False, 123 | syntax='proto2', 124 | extension_ranges=[], 125 | oneofs=[ 126 | ], 127 | serialized_start=80, 128 | serialized_end=318, 129 | ) 130 | 131 | 132 | _STRINGINTLABELMAP = _descriptor.Descriptor( 133 | name='StringIntLabelMap', 134 | full_name='object_detection.protos.StringIntLabelMap', 135 | filename=None, 136 | file=DESCRIPTOR, 137 | containing_type=None, 138 | fields=[ 139 | _descriptor.FieldDescriptor( 140 | name='item', full_name='object_detection.protos.StringIntLabelMap.item', index=0, 141 | number=1, type=11, cpp_type=10, label=3, 142 | has_default_value=False, default_value=[], 143 | message_type=None, enum_type=None, containing_type=None, 144 | is_extension=False, extension_scope=None, 145 | options=None), 146 | ], 147 | extensions=[ 148 | ], 149 | nested_types=[], 150 | enum_types=[ 151 | ], 152 | options=None, 153 | is_extendable=False, 154 | syntax='proto2', 155 | extension_ranges=[], 156 | oneofs=[ 157 | ], 158 | serialized_start=320, 159 | serialized_end=401, 160 | ) 161 | 162 | _STRINGINTLABELMAPITEM_KEYPOINTMAP.containing_type = _STRINGINTLABELMAPITEM 163 | _STRINGINTLABELMAPITEM.fields_by_name['keypoints'].message_type = _STRINGINTLABELMAPITEM_KEYPOINTMAP 164 | _STRINGINTLABELMAP.fields_by_name['item'].message_type = _STRINGINTLABELMAPITEM 165 | DESCRIPTOR.message_types_by_name['StringIntLabelMapItem'] = _STRINGINTLABELMAPITEM 166 | DESCRIPTOR.message_types_by_name['StringIntLabelMap'] = _STRINGINTLABELMAP 167 | 168 | StringIntLabelMapItem = _reflection.GeneratedProtocolMessageType('StringIntLabelMapItem', (_message.Message,), dict( 169 | 170 | KeypointMap = _reflection.GeneratedProtocolMessageType('KeypointMap', (_message.Message,), dict( 171 | DESCRIPTOR = _STRINGINTLABELMAPITEM_KEYPOINTMAP, 172 | __module__ = 'object_detection.protos.string_int_label_map_pb2' 173 | # @@protoc_insertion_point(class_scope:object_detection.protos.StringIntLabelMapItem.KeypointMap) 174 | )) 175 | , 176 | DESCRIPTOR = _STRINGINTLABELMAPITEM, 177 | __module__ = 'object_detection.protos.string_int_label_map_pb2' 178 | # @@protoc_insertion_point(class_scope:object_detection.protos.StringIntLabelMapItem) 179 | )) 180 | _sym_db.RegisterMessage(StringIntLabelMapItem) 181 | _sym_db.RegisterMessage(StringIntLabelMapItem.KeypointMap) 182 | 183 | StringIntLabelMap = _reflection.GeneratedProtocolMessageType('StringIntLabelMap', (_message.Message,), dict( 184 | DESCRIPTOR = _STRINGINTLABELMAP, 185 | __module__ = 'object_detection.protos.string_int_label_map_pb2' 186 | # @@protoc_insertion_point(class_scope:object_detection.protos.StringIntLabelMap) 187 | )) 188 | _sym_db.RegisterMessage(StringIntLabelMap) 189 | 190 | 191 | # @@protoc_insertion_point(module_scope) 192 | -------------------------------------------------------------------------------- /ml-celery/app/worker/ml/model.py: -------------------------------------------------------------------------------- 1 | """ 2 | @Author by Duy Nguyen Ngoc 3 | @email: duynguyenngoc@hotmail.com 4 | @Date: 2021-09-15 5 | @Update-1: 2022-02-10 add non_max_suppression 6 | """ 7 | 8 | 9 | import numpy as np 10 | import tensorflow.compat.v2 as tf 11 | from worker.ml.helpers import load_label_map 12 | from settings import ml_config 13 | 14 | 15 | class DetectorTF2(object): 16 | """Load model with tensorflow version from >= 2.0.0 17 | @Param: path_to_model=path to your folder contains tensorflow models 18 | @Param: path_to_lables=path to file label_map.pbtxt 19 | @Param: score_threshold=min score of predict 20 | @param: nms_threshold=iou 21 | @Param: num_classes=number class of models 22 | @Param: max_classes_out=max number of box out 23 | @Response: {load_model} load tensorflow to backgroud system 24 | @Response: {predict} dict of [num_detections, detection_classes, score_detections] and category_index 25 | """ 26 | 27 | def __init__( 28 | self, 29 | path_to_model, 30 | path_to_labels, 31 | nms_threshold, 32 | score_threshold, 33 | num_classes, 34 | max_classes_out, 35 | ): 36 | self.path_to_model = path_to_model 37 | self.path_to_labels = path_to_labels 38 | self.nms_threshold = nms_threshold 39 | self.score_threshold = score_threshold 40 | self.num_classes = num_classes 41 | self.max_classes_out = max_classes_out 42 | self.category_index = load_label_map.create_category_index_from_labelmap( 43 | path_to_labels, use_display_name=True) 44 | self.path_to_saved_model = self.path_to_model + "/saved_model" 45 | self.detect_fn = self.load_model() 46 | 47 | def load_model(self): 48 | detect_fn = tf.saved_model.load(self.path_to_saved_model) 49 | return detect_fn 50 | 51 | def predict(self, image): 52 | # image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) 53 | # image_expanded = np.expand_dims(image_rgb, axis=0) 54 | 55 | # The input needs to be a tensor, convert it using `tf.convert_to_tensor`. 56 | input_tensor = tf.convert_to_tensor(image) 57 | 58 | # The model expects a batch of images, so add an axis with `tf.newaxis`. 59 | input_tensor = input_tensor[tf.newaxis, ...] 60 | 61 | # input_tensor = np.expand_dims(image_np, 0) 62 | detections = self.detect_fn(input_tensor) 63 | 64 | # All outputs are batches tensors. 65 | # Convert to numpy arrays, and take index [0] to remove the batch dimension. 66 | # We're only interested in the first num_detections. 67 | num_detections = int(detections.pop('num_detections')) 68 | detections = {key: value[0, :num_detections].numpy() 69 | for key, value in detections.items()} 70 | detections['num_detections'] = num_detections 71 | detections['detection_classes'] = detections['detection_classes'].astype(np.int64) 72 | 73 | detection_boxes = detections['detection_boxes'] 74 | detection_classes = detections['detection_classes'] 75 | detection_scores = detections['detection_scores'] 76 | 77 | # iou, score_min condition 78 | selected_indices = tf.image.non_max_suppression( 79 | detection_boxes, 80 | detection_scores, 81 | max_output_size=self.max_classes_out, 82 | iou_threshold=self.nms_threshold, 83 | score_threshold=self.score_threshold) 84 | 85 | selected_boxes = tf.gather(detection_boxes, selected_indices) 86 | selected_scores = tf.gather(detection_scores, selected_indices) 87 | selected_classes = tf.gather(detection_classes, selected_indices) 88 | 89 | # make new detection result 90 | detection_boxes = np.array(selected_boxes) 91 | detection_classes = np.array(selected_classes) 92 | detection_scores = np.array(selected_scores) 93 | detections_new = {} 94 | detections_new['detection_boxes'] = detection_boxes 95 | detections_new['detection_classes'] = detection_classes 96 | detections_new['detection_scores'] = detection_scores 97 | 98 | return detections_new, self.category_index 99 | 100 | 101 | 102 | class CompletedModel(object): 103 | """[summary] 104 | Complete model object detection with tensorflow >= 2.0 105 | Args: 106 | @param: _load_model: load model from tensorflow train 107 | @param: _detect: put image(np.array) return detection(boxes, score, class), and scategory_index for cover class_id to class_name 108 | """ 109 | def __init__(self): 110 | self.model = self._load_model() 111 | 112 | @staticmethod 113 | def _load_model(): 114 | return DetectorTF2( 115 | path_to_model=ml_config.MODEL_PATH, 116 | path_to_labels=ml_config.LABLE_PATH, 117 | nms_threshold=ml_config.NMS_THRESHOLD, 118 | score_threshold=ml_config.SCORE_THRESHOLD, 119 | num_classes=ml_config.NUMBER_CLASSES, 120 | max_classes_out=ml_config.MAX_CLASS_OUT 121 | ) 122 | 123 | def detect(self, image): 124 | detections, category_index = self.model.predict(image) 125 | return detections, category_index 126 | -------------------------------------------------------------------------------- /ml-celery/app/worker/ml/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-celery/app/worker/ml/models/.gitkeep -------------------------------------------------------------------------------- /ml-celery/app/worker/ml/models/object_detection/checkpoint/checkpoint: -------------------------------------------------------------------------------- 1 | model_checkpoint_path: "ckpt-0" 2 | all_model_checkpoint_paths: "ckpt-0" 3 | all_model_checkpoint_timestamps: 1594350066.2966561 4 | last_preserved_timestamp: 1594350064.4464133 5 | -------------------------------------------------------------------------------- /ml-celery/app/worker/ml/models/object_detection/checkpoint/ckpt-0.data-00000-of-00001: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-celery/app/worker/ml/models/object_detection/checkpoint/ckpt-0.data-00000-of-00001 -------------------------------------------------------------------------------- /ml-celery/app/worker/ml/models/object_detection/checkpoint/ckpt-0.index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-celery/app/worker/ml/models/object_detection/checkpoint/ckpt-0.index -------------------------------------------------------------------------------- /ml-celery/app/worker/ml/models/object_detection/label_map.pbtxt: -------------------------------------------------------------------------------- 1 | item { 2 | id: 1 3 | name: "person" 4 | } 5 | item { 6 | id: 2 7 | name: "bicycle" 8 | } 9 | item { 10 | id: 3 11 | name: "car" 12 | } 13 | item { 14 | id: 4 15 | name: "motorcycle" 16 | } 17 | item { 18 | id: 5 19 | name: "airplane" 20 | } 21 | item { 22 | id: 6 23 | name: "bus" 24 | } 25 | item { 26 | id: 7 27 | name: "train" 28 | } 29 | item { 30 | id: 8 31 | name: "truck" 32 | } 33 | item { 34 | id: 9 35 | name: "boat" 36 | } 37 | item { 38 | id: 10 39 | name: "traffic light" 40 | } 41 | item { 42 | id: 11 43 | name: "fire hydrant" 44 | } 45 | item { 46 | id: 13 47 | name: "stop sign" 48 | } 49 | item { 50 | id: 14 51 | name: "parking meter" 52 | } 53 | item { 54 | id: 15 55 | name: "bench" 56 | } 57 | item { 58 | id: 16 59 | name: "bird" 60 | } 61 | item { 62 | id: 17 63 | name: "cat" 64 | } 65 | item { 66 | id: 18 67 | name: "dog" 68 | } 69 | item { 70 | id: 19 71 | name: "horse" 72 | } 73 | item { 74 | id: 20 75 | name: "sheep" 76 | } 77 | item { 78 | id: 21 79 | name: "cow" 80 | } 81 | item { 82 | id: 22 83 | name: "elephant" 84 | } 85 | item { 86 | id: 23 87 | name: "bear" 88 | } 89 | item { 90 | id: 24 91 | name: "zebra" 92 | } 93 | item { 94 | id: 25 95 | name: "giraffe" 96 | } 97 | item { 98 | id: 27 99 | name: "backpack" 100 | } 101 | item { 102 | id: 28 103 | name: "umbrella" 104 | } 105 | item { 106 | id: 31 107 | name: "handbag" 108 | } 109 | item { 110 | id: 32 111 | name: "tie" 112 | } 113 | item { 114 | id: 33 115 | name: "suitcase" 116 | } 117 | item { 118 | id: 34 119 | name: "frisbee" 120 | } 121 | item { 122 | id: 35 123 | name: "skis" 124 | } 125 | item { 126 | id: 36 127 | name: "snowboard" 128 | } 129 | item { 130 | id: 37 131 | name: "sports ball" 132 | } 133 | item { 134 | id: 38 135 | name: "kite" 136 | } 137 | item { 138 | id: 39 139 | name: "baseball bat" 140 | } 141 | item { 142 | id: 40 143 | name: "baseball glove" 144 | } 145 | item { 146 | id: 41 147 | name: "skateboard" 148 | } 149 | item { 150 | id: 42 151 | name: "surfboard" 152 | } 153 | item { 154 | id: 43 155 | name: "tennis racket" 156 | } 157 | item { 158 | id: 44 159 | name: "bottle" 160 | } 161 | item { 162 | id: 46 163 | name: "wine glass" 164 | } 165 | item { 166 | id: 47 167 | name: "cup" 168 | } 169 | item { 170 | id: 48 171 | name: "fork" 172 | } 173 | item { 174 | id: 49 175 | name: "knife" 176 | } 177 | item { 178 | id: 50 179 | name: "spoon" 180 | } 181 | item { 182 | id: 51 183 | name: "bowl" 184 | } 185 | item { 186 | id: 52 187 | name: "banana" 188 | } 189 | item { 190 | id: 53 191 | name: "apple" 192 | } 193 | item { 194 | id: 54 195 | name: "sandwich" 196 | } 197 | item { 198 | id: 55 199 | name: "orange" 200 | } 201 | item { 202 | id: 56 203 | name: "broccoli" 204 | } 205 | item { 206 | id: 57 207 | name: "carrot" 208 | } 209 | item { 210 | id: 58 211 | name: "hot dog" 212 | } 213 | item { 214 | id: 59 215 | name: "pizza" 216 | } 217 | item { 218 | id: 60 219 | name: "donut" 220 | } 221 | item { 222 | id: 61 223 | name: "cake" 224 | } 225 | item { 226 | id: 62 227 | name: "chair" 228 | } 229 | item { 230 | id: 63 231 | name: "couch" 232 | } 233 | item { 234 | id: 64 235 | name: "potted plant" 236 | } 237 | item { 238 | id: 65 239 | name: "bed" 240 | } 241 | item { 242 | id: 67 243 | name: "dining table" 244 | } 245 | item { 246 | id: 70 247 | name: "toilet" 248 | } 249 | item { 250 | id: 72 251 | name: "tv" 252 | } 253 | item { 254 | id: 73 255 | name: "laptop" 256 | } 257 | item { 258 | id: 74 259 | name: "mouse" 260 | } 261 | item { 262 | id: 75 263 | name: "remote" 264 | } 265 | item { 266 | id: 76 267 | name: "keyboard" 268 | } 269 | item { 270 | id: 77 271 | name: "cell phone" 272 | } 273 | item { 274 | id: 78 275 | name: "microwave" 276 | } 277 | item { 278 | id: 79 279 | name: "oven" 280 | } 281 | item { 282 | id: 80 283 | name: "toaster" 284 | } 285 | item { 286 | id: 81 287 | name: "sink" 288 | } 289 | item { 290 | id: 82 291 | name: "refrigerator" 292 | } 293 | item { 294 | id: 84 295 | name: "book" 296 | } 297 | item { 298 | id: 85 299 | name: "clock" 300 | } 301 | item { 302 | id: 86 303 | name: "vase" 304 | } 305 | item { 306 | id: 87 307 | name: "scissors" 308 | } 309 | item { 310 | id: 88 311 | name: "teddy bear" 312 | } 313 | item { 314 | id: 89 315 | name: "hair drier" 316 | } 317 | item { 318 | id: 90 319 | name: "toothbrush" 320 | } -------------------------------------------------------------------------------- /ml-celery/app/worker/ml/models/object_detection/pipeline.config: -------------------------------------------------------------------------------- 1 | model { 2 | ssd { 3 | num_classes: 90 4 | image_resizer { 5 | keep_aspect_ratio_resizer { 6 | min_dimension: 768 7 | max_dimension: 768 8 | pad_to_max_dimension: true 9 | } 10 | } 11 | feature_extractor { 12 | type: "ssd_efficientnet-b2_bifpn_keras" 13 | conv_hyperparams { 14 | regularizer { 15 | l2_regularizer { 16 | weight: 3.9999998989515007e-05 17 | } 18 | } 19 | initializer { 20 | truncated_normal_initializer { 21 | mean: 0.0 22 | stddev: 0.029999999329447746 23 | } 24 | } 25 | activation: SWISH 26 | batch_norm { 27 | decay: 0.9900000095367432 28 | scale: true 29 | epsilon: 0.0010000000474974513 30 | } 31 | force_use_bias: true 32 | } 33 | bifpn { 34 | min_level: 3 35 | max_level: 7 36 | num_iterations: 5 37 | num_filters: 112 38 | } 39 | } 40 | box_coder { 41 | faster_rcnn_box_coder { 42 | y_scale: 1.0 43 | x_scale: 1.0 44 | height_scale: 1.0 45 | width_scale: 1.0 46 | } 47 | } 48 | matcher { 49 | argmax_matcher { 50 | matched_threshold: 0.5 51 | unmatched_threshold: 0.5 52 | ignore_thresholds: false 53 | negatives_lower_than_unmatched: true 54 | force_match_for_each_row: true 55 | use_matmul_gather: true 56 | } 57 | } 58 | similarity_calculator { 59 | iou_similarity { 60 | } 61 | } 62 | box_predictor { 63 | weight_shared_convolutional_box_predictor { 64 | conv_hyperparams { 65 | regularizer { 66 | l2_regularizer { 67 | weight: 3.9999998989515007e-05 68 | } 69 | } 70 | initializer { 71 | random_normal_initializer { 72 | mean: 0.0 73 | stddev: 0.009999999776482582 74 | } 75 | } 76 | activation: SWISH 77 | batch_norm { 78 | decay: 0.9900000095367432 79 | scale: true 80 | epsilon: 0.0010000000474974513 81 | } 82 | force_use_bias: true 83 | } 84 | depth: 112 85 | num_layers_before_predictor: 3 86 | kernel_size: 3 87 | class_prediction_bias_init: -4.599999904632568 88 | use_depthwise: true 89 | } 90 | } 91 | anchor_generator { 92 | multiscale_anchor_generator { 93 | min_level: 3 94 | max_level: 7 95 | anchor_scale: 4.0 96 | aspect_ratios: 1.0 97 | aspect_ratios: 2.0 98 | aspect_ratios: 0.5 99 | scales_per_octave: 3 100 | } 101 | } 102 | post_processing { 103 | batch_non_max_suppression { 104 | score_threshold: 9.99999993922529e-09 105 | iou_threshold: 0.5 106 | max_detections_per_class: 100 107 | max_total_detections: 100 108 | } 109 | score_converter: SIGMOID 110 | } 111 | normalize_loss_by_num_matches: true 112 | loss { 113 | localization_loss { 114 | weighted_smooth_l1 { 115 | } 116 | } 117 | classification_loss { 118 | weighted_sigmoid_focal { 119 | gamma: 1.5 120 | alpha: 0.25 121 | } 122 | } 123 | classification_weight: 1.0 124 | localization_weight: 1.0 125 | } 126 | encode_background_as_zeros: true 127 | normalize_loc_loss_by_codesize: true 128 | inplace_batchnorm_update: true 129 | freeze_batchnorm: false 130 | add_background_class: false 131 | } 132 | } 133 | train_config { 134 | batch_size: 128 135 | data_augmentation_options { 136 | random_horizontal_flip { 137 | } 138 | } 139 | data_augmentation_options { 140 | random_scale_crop_and_pad_to_square { 141 | output_size: 768 142 | scale_min: 0.10000000149011612 143 | scale_max: 2.0 144 | } 145 | } 146 | sync_replicas: true 147 | optimizer { 148 | momentum_optimizer { 149 | learning_rate { 150 | cosine_decay_learning_rate { 151 | learning_rate_base: 0.07999999821186066 152 | total_steps: 300000 153 | warmup_learning_rate: 0.0010000000474974513 154 | warmup_steps: 2500 155 | } 156 | } 157 | momentum_optimizer_value: 0.8999999761581421 158 | } 159 | use_moving_average: false 160 | } 161 | fine_tune_checkpoint: "PATH_TO_BE_CONFIGURED" 162 | num_steps: 300000 163 | startup_delay_steps: 0.0 164 | replicas_to_aggregate: 8 165 | max_number_of_boxes: 100 166 | unpad_groundtruth_tensors: false 167 | fine_tune_checkpoint_type: "classification" 168 | use_bfloat16: true 169 | fine_tune_checkpoint_version: V2 170 | } 171 | train_input_reader: { 172 | label_map_path: "PATH_TO_BE_CONFIGURED/label_map.txt" 173 | tf_record_input_reader { 174 | input_path: "PATH_TO_BE_CONFIGURED/train2017-?????-of-00256.tfrecord" 175 | } 176 | } 177 | 178 | eval_config: { 179 | metrics_set: "coco_detection_metrics" 180 | use_moving_averages: false 181 | batch_size: 1; 182 | } 183 | 184 | eval_input_reader: { 185 | label_map_path: "PATH_TO_BE_CONFIGURED/label_map.txt" 186 | shuffle: false 187 | num_epochs: 1 188 | tf_record_input_reader { 189 | input_path: "PATH_TO_BE_CONFIGURED/val2017-?????-of-00032.tfrecord" 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /ml-celery/app/worker/ml/models/object_detection/saved_model/saved_model.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-celery/app/worker/ml/models/object_detection/saved_model/saved_model.pb -------------------------------------------------------------------------------- /ml-celery/app/worker/ml/models/object_detection/saved_model/variables/variables.data-00000-of-00001: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-celery/app/worker/ml/models/object_detection/saved_model/variables/variables.data-00000-of-00001 -------------------------------------------------------------------------------- /ml-celery/app/worker/ml/models/object_detection/saved_model/variables/variables.index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-celery/app/worker/ml/models/object_detection/saved_model/variables/variables.index -------------------------------------------------------------------------------- /ml-celery/requirements.txt: -------------------------------------------------------------------------------- 1 | # fastapi 2 | psycopg2-binary==2.8.6 3 | celery==5.1.2 4 | flower==1.0.0 5 | redis==3.5.3 6 | SQLAlchemy==1.4.23 7 | starlette==0.16.0 8 | 9 | # Tensorflow 10 | tensorflow==2.7.0 11 | pillow==8.3.2 12 | opencv-python==4.5.3.56 13 | matplotlib 14 | 15 | -------------------------------------------------------------------------------- /ml-client/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .git 3 | .gitignore 4 | -------------------------------------------------------------------------------- /ml-client/.env.staging: -------------------------------------------------------------------------------- 1 | REACT_APP_API_V1="http://localhost:8081/api/v1" 2 | PORT=3000 -------------------------------------------------------------------------------- /ml-client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14.17-alpine3.13 as builder 2 | 3 | LABEL maintainer="Duy Nguyen " 4 | 5 | WORKDIR /app 6 | 7 | ENV PATH /app/node_modules/.bin:$PATH 8 | 9 | COPY ./package.json /app/package.json 10 | COPY ./package-lock.json /app/package-lock.json 11 | RUN npm i -g 12 | COPY . . 13 | RUN yarn build:production 14 | 15 | FROM nginx 16 | 17 | WORKDIR /usr/share/nginx/html 18 | 19 | RUN rm -v /etc/nginx/nginx.conf 20 | RUN rm -v /etc/nginx/conf.d/default.conf 21 | COPY ./nginx.conf /etc/nginx/ 22 | 23 | RUN rm -rf ./* 24 | 25 | COPY ./m_nginx.conf . 26 | COPY ./local_nginx.conf . 27 | COPY ./custom_nginx.sh . 28 | RUN chmod +x custom_nginx.sh 29 | 30 | COPY --from=builder /app/build . 31 | 32 | EXPOSE 80 443 -------------------------------------------------------------------------------- /ml-client/custom_nginx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ "${DOMAIN}" = "localhost" ]; then 3 | echo 'Deploy server on localhost..........' 4 | envsubst '$${DOMAIN} $${BE_HOST} $${BE_PORT}' < local_nginx.conf > /etc/nginx/conf.d/app.conf 5 | else 6 | echo 'Deploy server on staging or prodution.........' 7 | envsubst '$${DOMAIN} $${BE_HOST} $${BE_PORT}' < m_nginx.conf > /etc/nginx/conf.d/app.conf 8 | fi 9 | exec "$@" 10 | -------------------------------------------------------------------------------- /ml-client/local_nginx.conf: -------------------------------------------------------------------------------- 1 | # Đây là file cấu hình cho nigix localhost 2 | server { 3 | listen 80; 4 | charset utf-8; 5 | server_name ${DOMAIN}; 6 | location /api/ { 7 | proxy_pass http://${BE_HOST}:${BE_PORT}/api/; 8 | proxy_set_header Host $host; 9 | proxy_set_header X-Real-IP $remote_addr; 10 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 11 | } 12 | location / { 13 | root /usr/share/nginx/html; 14 | try_files $uri $uri.html $uri/ /index.html = 404; 15 | } 16 | } -------------------------------------------------------------------------------- /ml-client/m_nginx.conf: -------------------------------------------------------------------------------- 1 | # load frontend và backend ra port 80 2 | server { 3 | listen 80; 4 | charset utf-8; 5 | server_name ${DOMAIN}; 6 | } 7 | server { 8 | listen 443 ssl; 9 | server_name ${DOMAIN}; 10 | charset utf-8; 11 | ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem; 12 | ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem; 13 | 14 | location /api/ { 15 | proxy_pass http://${BE_HOST}:${BE_PORT}/api/; 16 | proxy_set_header Host $host; 17 | proxy_set_header X-Real-IP $remote_addr; 18 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 19 | client_max_body_size 16M; 20 | } 21 | location / { 22 | root /usr/share/nginx/html; 23 | try_files $uri $uri.html $uri/ /index.html =404; 24 | } 25 | } -------------------------------------------------------------------------------- /ml-client/nginx.conf: -------------------------------------------------------------------------------- 1 | #file này config cho phiên chạy nginx 2 | 3 | # ông chủ 4 | user nginx; 5 | 6 | # số lượng nhân viên 7 | worker_processes auto; 8 | 9 | # lấy lỗi 10 | error_log /var/log/nginx/error.log warn; 11 | 12 | # file chứa id cho nginx 13 | pid /var/run/nginx.pid; 14 | 15 | # maximum connect với worker processes 16 | events { 17 | worker_connections 1024; 18 | } 19 | 20 | # nigix cấu hình handle http 21 | http { 22 | # Include the file defining the list of file types that are supported by NGINX 23 | include /etc/nginx/mime.types; 24 | # Define the default file type that is returned to the user 25 | default_type text/html; 26 | 27 | # Define the format of log messages. 28 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 29 | '$status $body_bytes_sent "$http_referer" ' 30 | '"$http_user_agent" "$http_x_forwarded_for"'; 31 | 32 | # Define the location of the log of access attempts to NGINX 33 | access_log /var/log/nginx/access.log main; 34 | 35 | # Define the parameters to optimize the delivery of static content 36 | sendfile on; 37 | tcp_nopush on; 38 | tcp_nodelay on; 39 | 40 | client_max_body_size 20M; 41 | 42 | # Define the timeout value for keep-alive connections with the client 43 | keepalive_timeout 65; 44 | 45 | # Define the usage of the gzip compression algorithm to reduce the amount of data to transmit 46 | # gzip on; 47 | 48 | # Include additional parameters for virtual host(s)/server(s) 49 | include /etc/nginx/conf.d/*.conf; 50 | } -------------------------------------------------------------------------------- /ml-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.12.3", 7 | "@testing-library/jest-dom": "^5.11.4", 8 | "@testing-library/react": "^11.1.0", 9 | "@testing-library/user-event": "^12.1.10", 10 | "antd": "^4.16.13", 11 | "axios": "^0.23.0", 12 | "bootstrap": "^5.1.2", 13 | "classnames": "^2.3.1", 14 | "env-cmd": "^10.1.0", 15 | "react": "^17.0.2", 16 | "react-chartjs-2": "^3.1.0", 17 | "react-dom": "^17.0.2", 18 | "react-router-dom": "^5.3.0", 19 | "react-scripts": "^3.4.4", 20 | "sass": "^1.42.1", 21 | "socket.io-client": "^4.2.0", 22 | "validator": "^13.6.0", 23 | "web-vitals": "^1.0.1" 24 | }, 25 | "scripts": { 26 | "start": "react-scripts start", 27 | "build": "react-scripts build", 28 | "test": "react-scripts test", 29 | "eject": "react-scripts eject", 30 | "start:staging": "env-cmd -f ./.env.staging npm start", 31 | "build:production": "env-cmd -f ./.env.staging yarn build" 32 | }, 33 | "eslintConfig": { 34 | "extends": [ 35 | "react-app", 36 | "react-app/jest" 37 | ] 38 | }, 39 | "browserslist": { 40 | "production": [ 41 | ">0.2%", 42 | "not dead", 43 | "not op_mini all" 44 | ], 45 | "development": [ 46 | "last 1 chrome version", 47 | "last 1 firefox version", 48 | "last 1 safari version" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ml-client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/public/favicon.ico -------------------------------------------------------------------------------- /ml-client/public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/public/img/logo.png -------------------------------------------------------------------------------- /ml-client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Welcome to A. PoT Group 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /ml-client/public/js/coalesce.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { PI, cos, sin, abs, sqrt, pow, round, random, atan2 } = Math; 4 | const HALF_PI = 0.5 * PI; 5 | const TAU = 2 * PI; 6 | const TO_RAD = PI / 180; 7 | const floor = n => n | 0; 8 | const rand = n => n * random(); 9 | const randIn = (min, max) => rand(max - min) + min; 10 | const randRange = n => n - rand(2 * n); 11 | const fadeIn = (t, m) => t / m; 12 | const fadeOut = (t, m) => (m - t) / m; 13 | const fadeInOut = (t, m) => { 14 | let hm = 0.5 * m; 15 | return abs((t + hm) % m - hm) / (hm); 16 | }; 17 | const dist = (x1, y1, x2, y2) => sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2)); 18 | const angle = (x1, y1, x2, y2) => atan2(y2 - y1, x2 - x1); 19 | const lerp = (n1, n2, speed) => (1 - speed) * n1 + speed * n2; 20 | 21 | const particleCount = 700; 22 | const particlePropCount = 9; 23 | const particlePropsLength = particleCount * particlePropCount; 24 | const baseTTL = 100; 25 | const rangeTTL = 500; 26 | const baseSpeed = 0.1; 27 | const rangeSpeed = 1; 28 | const baseSize = 2; 29 | const rangeSize = 10; 30 | const baseHue = 10; 31 | const rangeHue = 100; 32 | const noiseSteps = 2; 33 | const xOff = 0.0025; 34 | const yOff = 0.005; 35 | const zOff = 0.0005; 36 | const backgroundColor = 'hsla(60,50%,3%,1)'; 37 | 38 | 39 | let container; 40 | let canvas; 41 | let ctx; 42 | let center; 43 | let gradient; 44 | let tick; 45 | let particleProps; 46 | let positions; 47 | let velocities; 48 | let lifeSpans; 49 | let speeds; 50 | let sizes; 51 | let hues; 52 | 53 | function setup() { 54 | createCanvas(); 55 | resize(); 56 | initParticles(); 57 | draw(); 58 | } 59 | 60 | function initParticles() { 61 | tick = 0; 62 | particleProps = new Float32Array(particlePropsLength); 63 | 64 | let i; 65 | 66 | for (i = 0; i < particlePropsLength; i += particlePropCount) { 67 | initParticle(i); 68 | } 69 | } 70 | 71 | function initParticle(i) { 72 | let theta, x, y, vx, vy, life, ttl, speed, size, hue; 73 | 74 | x = rand(canvas.a.width); 75 | y = rand(canvas.a.height); 76 | theta = angle(x, y, center[0], center[1]); 77 | vx = cos(theta) * 6; 78 | vy = sin(theta) * 6; 79 | life = 0; 80 | ttl = baseTTL + rand(rangeTTL); 81 | speed = baseSpeed + rand(rangeSpeed); 82 | size = baseSize + rand(rangeSize); 83 | hue = baseHue + rand(rangeHue); 84 | 85 | particleProps.set([x, y, vx, vy, life, ttl, speed, size, hue], i); 86 | } 87 | 88 | function drawParticles() { 89 | let i; 90 | 91 | for (i = 0; i < particlePropsLength; i += particlePropCount) { 92 | updateParticle(i); 93 | } 94 | } 95 | 96 | function updateParticle(i) { 97 | let i2=1+i, i3=2+i, i4=3+i, i5=4+i, i6=5+i, i7=6+i, i8=7+i, i9=8+i; 98 | let x, y, theta, vx, vy, life, ttl, speed, x2, y2, size, hue; 99 | 100 | x = particleProps[i]; 101 | y = particleProps[i2]; 102 | theta = angle(x, y, center[0], center[1]) + 0.75 * HALF_PI; 103 | vx = lerp(particleProps[i3], 2 * cos(theta), 0.05); 104 | vy = lerp(particleProps[i4], 2 * sin(theta), 0.05); 105 | life = particleProps[i5]; 106 | ttl = particleProps[i6]; 107 | speed = particleProps[i7]; 108 | x2 = x + vx * speed; 109 | y2 = y + vy * speed; 110 | size = particleProps[i8]; 111 | hue = particleProps[i9]; 112 | 113 | drawParticle(x, y, theta, life, ttl, size, hue); 114 | 115 | life++; 116 | 117 | particleProps[i] = x2; 118 | particleProps[i2] = y2; 119 | particleProps[i3] = vx; 120 | particleProps[i4] = vy; 121 | particleProps[i5] = life; 122 | 123 | life > ttl && initParticle(i); 124 | } 125 | 126 | function drawParticle(x, y, theta, life, ttl, size, hue) { 127 | let xRel = x - (0.5 * size), yRel = y - (0.5 * size); 128 | 129 | ctx.a.save(); 130 | ctx.a.lineCap = 'round'; 131 | ctx.a.lineWidth = 1; 132 | ctx.a.strokeStyle = `hsla(${hue},100%,60%,${fadeInOut(life, ttl)})`; 133 | ctx.a.beginPath(); 134 | ctx.a.translate(xRel, yRel); 135 | ctx.a.rotate(theta); 136 | ctx.a.translate(-xRel, -yRel); 137 | ctx.a.strokeRect(xRel, yRel, size, size); 138 | ctx.a.closePath(); 139 | ctx.a.restore(); 140 | } 141 | 142 | function createCanvas() { 143 | container = document.querySelector('.content--canvas'); 144 | canvas = { 145 | a: document.createElement('canvas'), 146 | b: document.createElement('canvas') 147 | }; 148 | canvas.b.style = ` 149 | position: fixed; 150 | top: 0; 151 | left: 0; 152 | width: 100%; 153 | height: 100%; 154 | `; 155 | container.appendChild(canvas.b); 156 | ctx = { 157 | a: canvas.a.getContext('2d'), 158 | b: canvas.b.getContext('2d') 159 | }; 160 | center = []; 161 | } 162 | 163 | function resize() { 164 | const { innerWidth, innerHeight } = window; 165 | 166 | canvas.a.width = innerWidth; 167 | canvas.a.height = innerHeight; 168 | 169 | ctx.a.drawImage(canvas.b, 0, 0); 170 | 171 | canvas.b.width = innerWidth; 172 | canvas.b.height = innerHeight; 173 | 174 | ctx.b.drawImage(canvas.a, 0, 0); 175 | 176 | center[0] = 0.5 * canvas.a.width; 177 | center[1] = 0.5 * canvas.a.height; 178 | } 179 | 180 | function renderGlow() { 181 | ctx.b.save(); 182 | ctx.b.filter = 'blur(8px) brightness(200%)'; 183 | ctx.b.globalCompositeOperation = 'lighter'; 184 | ctx.b.drawImage(canvas.a, 0, 0); 185 | ctx.b.restore(); 186 | 187 | ctx.b.save(); 188 | ctx.b.filter = 'blur(4px) brightness(200%)'; 189 | ctx.b.globalCompositeOperation = 'lighter'; 190 | ctx.b.drawImage(canvas.a, 0, 0); 191 | ctx.b.restore(); 192 | } 193 | 194 | function render() { 195 | ctx.b.save(); 196 | ctx.b.globalCompositeOperation = 'lighter'; 197 | ctx.b.drawImage(canvas.a, 0, 0); 198 | ctx.b.restore(); 199 | } 200 | 201 | function draw() { 202 | tick++; 203 | 204 | ctx.a.clearRect(0, 0, canvas.a.width, canvas.a.height); 205 | 206 | ctx.b.fillStyle = backgroundColor; 207 | ctx.b.fillRect(0, 0, canvas.a.width, canvas.a.height); 208 | 209 | drawParticles(); 210 | renderGlow(); 211 | render(); 212 | 213 | window.requestAnimationFrame(draw); 214 | } 215 | 216 | window.addEventListener('load', setup); 217 | window.addEventListener('resize', resize); -------------------------------------------------------------------------------- /ml-client/public/js/swirl.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { PI, cos, sin, abs, sqrt, pow, round, random, atan2 } = Math; 4 | const HALF_PI = 0.5 * PI; 5 | const TAU = 2 * PI; 6 | const TO_RAD = PI / 180; 7 | const floor = n => n | 0; 8 | const rand = n => n * random(); 9 | const randIn = (min, max) => rand(max - min) + min; 10 | const randRange = n => n - rand(2 * n); 11 | const fadeIn = (t, m) => t / m; 12 | const fadeOut = (t, m) => (m - t) / m; 13 | const fadeInOut = (t, m) => { 14 | let hm = 0.5 * m; 15 | return abs((t + hm) % m - hm) / (hm); 16 | }; 17 | const dist = (x1, y1, x2, y2) => sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2)); 18 | const angle = (x1, y1, x2, y2) => atan2(y2 - y1, x2 - x1); 19 | const lerp = (n1, n2, speed) => (1 - speed) * n1 + speed * n2; 20 | 21 | !function(){"use strict";var r=.5*(Math.sqrt(3)-1),e=(3-Math.sqrt(3))/6,t=1/6,a=(Math.sqrt(5)-1)/4,o=(5-Math.sqrt(5))/20;function i(r){var e;e="function"==typeof r?r:r?function(){var r=0,e=0,t=0,a=1,o=(i=4022871197,function(r){r=r.toString();for(var e=0;e>>0,i=(t*=i)>>>0,i+=4294967296*(t-=i)}return 2.3283064365386963e-10*(i>>>0)});var i;r=o(" "),e=o(" "),t=o(" ");for(var n=0;nc?(o=1,i=0):(o=0,i=1);var y=m-o+e,w=c-i+e,g=m-1+2*e,A=c-1+2*e,x=255&d,q=255&p,D=.5-m*m-c*c;if(D>=0){var S=3*n[x+f[q]];v=(D*=D)*D*(s[S]*m+s[S+1]*c)}var U=.5-y*y-w*w;if(U>=0){var b=3*n[x+o+f[q+i]];h=(U*=U)*U*(s[b]*y+s[b+1]*w)}var F=.5-g*g-A*A;if(F>=0){var N=3*n[x+1+f[q+1]];l=(F*=F)*F*(s[N]*g+s[N+1]*A)}return 70*(v+h+l)},noise3D:function(r,e,a){var o,i,n,f,s,v,h,l,u,d,p=this.permMod12,M=this.perm,m=this.grad3,c=(r+e+a)*(1/3),y=Math.floor(r+c),w=Math.floor(e+c),g=Math.floor(a+c),A=(y+w+g)*t,x=r-(y-A),q=e-(w-A),D=a-(g-A);x>=q?q>=D?(s=1,v=0,h=0,l=1,u=1,d=0):x>=D?(s=1,v=0,h=0,l=1,u=0,d=1):(s=0,v=0,h=1,l=1,u=0,d=1):qT?k++:z++,P>_?k++:B++,P>j?k++:E++,T>_?z++:B++,T>j?z++:E++,_>j?B++:E++;var G=P-(l=k>=3?1:0)+o,H=T-(u=z>=3?1:0)+o,I=_-(d=B>=3?1:0)+o,J=j-(p=E>=3?1:0)+o,K=P-(M=k>=2?1:0)+2*o,L=T-(m=z>=2?1:0)+2*o,O=_-(c=B>=2?1:0)+2*o,Q=j-(y=E>=2?1:0)+2*o,R=P-(w=k>=1?1:0)+3*o,V=T-(g=z>=1?1:0)+3*o,W=_-(A=B>=1?1:0)+3*o,X=j-(x=E>=1?1:0)+3*o,Y=P-1+4*o,Z=T-1+4*o,$=_-1+4*o,rr=j-1+4*o,er=255&U,tr=255&b,ar=255&F,or=255&N,ir=.6-P*P-T*T-_*_-j*j;if(ir<0)n=0;else{var nr=q[er+q[tr+q[ar+q[or]]]]%32*4;n=(ir*=ir)*ir*(D[nr]*P+D[nr+1]*T+D[nr+2]*_+D[nr+3]*j)}var fr=.6-G*G-H*H-I*I-J*J;if(fr<0)f=0;else{var sr=q[er+l+q[tr+u+q[ar+d+q[or+p]]]]%32*4;f=(fr*=fr)*fr*(D[sr]*G+D[sr+1]*H+D[sr+2]*I+D[sr+3]*J)}var vr=.6-K*K-L*L-O*O-Q*Q;if(vr<0)s=0;else{var hr=q[er+M+q[tr+m+q[ar+c+q[or+y]]]]%32*4;s=(vr*=vr)*vr*(D[hr]*K+D[hr+1]*L+D[hr+2]*O+D[hr+3]*Q)}var lr=.6-R*R-V*V-W*W-X*X;if(lr<0)v=0;else{var ur=q[er+w+q[tr+g+q[ar+A+q[or+x]]]]%32*4;v=(lr*=lr)*lr*(D[ur]*R+D[ur+1]*V+D[ur+2]*W+D[ur+3]*X)}var dr=.6-Y*Y-Z*Z-$*$-rr*rr;if(dr<0)h=0;else{var pr=q[er+1+q[tr+1+q[ar+1+q[or+1]]]]%32*4;h=(dr*=dr)*dr*(D[pr]*Y+D[pr+1]*Z+D[pr+2]*$+D[pr+3]*rr)}return 27*(n+f+s+v+h)}},i._buildPermutationTable=n,"undefined"!=typeof define&&define.amd&&define(function(){return i}),"undefined"!=typeof exports?exports.SimplexNoise=i:"undefined"!=typeof window&&(window.SimplexNoise=i),"undefined"!=typeof module&&(module.exports=i)}(); 22 | 23 | const particleCount = 700; 24 | const particlePropCount = 9; 25 | const particlePropsLength = particleCount * particlePropCount; 26 | const rangeY = 100; 27 | const baseTTL = 50; 28 | const rangeTTL = 150; 29 | const baseSpeed = 0.1; 30 | const rangeSpeed = 2; 31 | const baseRadius = 1; 32 | const rangeRadius = 4; 33 | const baseHue = 220; 34 | const rangeHue = 100; 35 | const noiseSteps = 8; 36 | const xOff = 0.00125; 37 | const yOff = 0.00125; 38 | const zOff = 0.0005; 39 | const backgroundColor = 'hsla(260,40%,5%,1)'; 40 | 41 | let container; 42 | let canvas; 43 | let ctx; 44 | let center; 45 | let gradient; 46 | let tick; 47 | let simplex; 48 | let particleProps; 49 | let positions; 50 | let velocities; 51 | let lifeSpans; 52 | let speeds; 53 | let sizes; 54 | let hues; 55 | 56 | function setup() { 57 | createCanvas(); 58 | resize(); 59 | initParticles(); 60 | draw(); 61 | } 62 | 63 | function initParticles() { 64 | tick = 0; 65 | simplex = new SimplexNoise(); 66 | particleProps = new Float32Array(particlePropsLength); 67 | 68 | let i; 69 | 70 | for (i = 0; i < particlePropsLength; i += particlePropCount) { 71 | initParticle(i); 72 | } 73 | } 74 | 75 | function initParticle(i) { 76 | let x, y, vx, vy, life, ttl, speed, radius, hue; 77 | 78 | x = rand(canvas.a.width); 79 | y = center[1] + randRange(rangeY); 80 | vx = 0; 81 | vy = 0; 82 | life = 0; 83 | ttl = baseTTL + rand(rangeTTL); 84 | speed = baseSpeed + rand(rangeSpeed); 85 | radius = baseRadius + rand(rangeRadius); 86 | hue = baseHue + rand(rangeHue); 87 | 88 | particleProps.set([x, y, vx, vy, life, ttl, speed, radius, hue], i); 89 | } 90 | 91 | function drawParticles() { 92 | let i; 93 | 94 | for (i = 0; i < particlePropsLength; i += particlePropCount) { 95 | updateParticle(i); 96 | } 97 | } 98 | 99 | function updateParticle(i) { 100 | let i2=1+i, i3=2+i, i4=3+i, i5=4+i, i6=5+i, i7=6+i, i8=7+i, i9=8+i; 101 | let n, x, y, vx, vy, life, ttl, speed, x2, y2, radius, hue; 102 | 103 | x = particleProps[i]; 104 | y = particleProps[i2]; 105 | n = simplex.noise3D(x * xOff, y * yOff, tick * zOff) * noiseSteps * TAU; 106 | vx = lerp(particleProps[i3], cos(n), 0.5); 107 | vy = lerp(particleProps[i4], sin(n), 0.5); 108 | life = particleProps[i5]; 109 | ttl = particleProps[i6]; 110 | speed = particleProps[i7]; 111 | x2 = x + vx * speed; 112 | y2 = y + vy * speed; 113 | radius = particleProps[i8]; 114 | hue = particleProps[i9]; 115 | 116 | drawParticle(x, y, x2, y2, life, ttl, radius, hue); 117 | 118 | life++; 119 | 120 | particleProps[i] = x2; 121 | particleProps[i2] = y2; 122 | particleProps[i3] = vx; 123 | particleProps[i4] = vy; 124 | particleProps[i5] = life; 125 | 126 | (checkBounds(x, y) || life > ttl) && initParticle(i); 127 | } 128 | 129 | function drawParticle(x, y, x2, y2, life, ttl, radius, hue) { 130 | ctx.a.save(); 131 | ctx.a.lineCap = 'round'; 132 | ctx.a.lineWidth = radius; 133 | ctx.a.strokeStyle = `hsla(${hue},100%,60%,${fadeInOut(life, ttl)})`; 134 | ctx.a.beginPath(); 135 | ctx.a.moveTo(x, y); 136 | ctx.a.lineTo(x2, y2); 137 | ctx.a.stroke() 138 | ctx.a.closePath(); 139 | ctx.a.restore(); 140 | } 141 | 142 | function checkBounds(x, y) { 143 | return( 144 | x > canvas.a.width || 145 | x < 0 || 146 | y > canvas.a.height || 147 | y < 0 148 | ); 149 | } 150 | 151 | function createCanvas() { 152 | container = document.querySelector('.content--canvas'); 153 | canvas = { 154 | a: document.createElement('canvas'), 155 | b: document.createElement('canvas') 156 | }; 157 | canvas.b.style = ` 158 | position: fixed; 159 | top: 0; 160 | left: 0; 161 | width: 100%; 162 | height: 100%; 163 | `; 164 | container.appendChild(canvas.b); 165 | ctx = { 166 | a: canvas.a.getContext('2d'), 167 | b: canvas.b.getContext('2d') 168 | }; 169 | center = []; 170 | } 171 | 172 | function resize() { 173 | const { innerWidth, innerHeight } = window; 174 | 175 | canvas.a.width = innerWidth; 176 | canvas.a.height = innerHeight; 177 | 178 | ctx.a.drawImage(canvas.b, 0, 0); 179 | 180 | canvas.b.width = innerWidth; 181 | canvas.b.height = innerHeight; 182 | 183 | ctx.b.drawImage(canvas.a, 0, 0); 184 | 185 | center[0] = 0.5 * canvas.a.width; 186 | center[1] = 0.5 * canvas.a.height; 187 | } 188 | 189 | function renderGlow() { 190 | ctx.b.save(); 191 | ctx.b.filter = 'blur(8px) brightness(200%)'; 192 | ctx.b.globalCompositeOperation = 'lighter'; 193 | ctx.b.drawImage(canvas.a, 0, 0); 194 | ctx.b.restore(); 195 | 196 | ctx.b.save(); 197 | ctx.b.filter = 'blur(4px) brightness(200%)'; 198 | ctx.b.globalCompositeOperation = 'lighter'; 199 | ctx.b.drawImage(canvas.a, 0, 0); 200 | ctx.b.restore(); 201 | } 202 | 203 | function renderToScreen() { 204 | ctx.b.save(); 205 | ctx.b.globalCompositeOperation = 'lighter'; 206 | ctx.b.drawImage(canvas.a, 0, 0); 207 | ctx.b.restore(); 208 | } 209 | 210 | function draw() { 211 | tick++; 212 | 213 | ctx.a.clearRect(0, 0, canvas.a.width, canvas.a.height); 214 | 215 | ctx.b.fillStyle = backgroundColor; 216 | ctx.b.fillRect(0, 0, canvas.a.width, canvas.a.height); 217 | 218 | drawParticles(); 219 | renderGlow(); 220 | renderToScreen(); 221 | 222 | window.requestAnimationFrame(draw); 223 | } 224 | 225 | window.addEventListener('load', setup); 226 | window.addEventListener('resize', resize); -------------------------------------------------------------------------------- /ml-client/public/js/test.js: -------------------------------------------------------------------------------- 1 | let c = init("canvas"), 2 | w = (canvas.width = window.innerWidth), 3 | h = (canvas.height = window.innerHeight); 4 | //initiation 5 | 6 | class firefly{ 7 | constructor(){ 8 | this.x = Math.random()*w; 9 | this.y = Math.random()*h; 10 | this.s = Math.random()*3; 11 | this.ang = Math.random()*2*Math.PI; 12 | this.v = this.s*this.s/4; 13 | } 14 | move(){ 15 | this.x += this.v*Math.cos(this.ang); 16 | this.y += this.v*Math.sin(this.ang); 17 | this.ang += Math.random()*20*Math.PI/180-10*Math.PI/180; 18 | } 19 | show(){ 20 | c.beginPath(); 21 | c.arc(this.x,this.y,this.s,0,2*Math.PI); 22 | var randomColor = Math.floor(Math.random()*16777215).toString(16); 23 | // c.fillStyle="#" + randomColor; 24 | c.fillStyle="red"; 25 | 26 | c.fill(); 27 | } 28 | } 29 | 30 | let f = []; 31 | 32 | function draw() { 33 | if(f.length < 100){ 34 | for(let j = 0; j < 10; j++){ 35 | f.push(new firefly()); 36 | } 37 | } 38 | //animation 39 | for(let i = 0; i < f.length; i++){ 40 | f[i].move(); 41 | f[i].show(); 42 | if(f[i].x < 0 || f[i].x > w || f[i].y < 0 || f[i].y > h){ 43 | f.splice(i,1); 44 | } 45 | } 46 | } 47 | 48 | let mouse = {}; 49 | let last_mouse = {}; 50 | 51 | canvas.addEventListener( 52 | "mousemove", 53 | function(e) { 54 | last_mouse.x = mouse.x; 55 | last_mouse.y = mouse.y; 56 | 57 | mouse.x = e.pageX - this.offsetLeft; 58 | mouse.y = e.pageY - this.offsetTop; 59 | }, 60 | false 61 | ); 62 | function init(elemid) { 63 | let canvas = document.getElementById(elemid), 64 | c = canvas.getContext("2d"), 65 | w = (canvas.width = window.innerWidth), 66 | h = (canvas.height = window.innerHeight); 67 | c.fillStyle = "rgba(30,30,30,1)"; 68 | c.fillRect(0, 0, w, h); 69 | return c; 70 | } 71 | 72 | window.requestAnimFrame = (function() { 73 | return ( 74 | window.requestAnimationFrame || 75 | window.webkitRequestAnimationFrame || 76 | window.mozRequestAnimationFrame || 77 | window.oRequestAnimationFrame || 78 | window.msRequestAnimationFrame || 79 | function(callback) { 80 | window.setTimeout(callback); 81 | } 82 | ); 83 | }); 84 | 85 | function loop() { 86 | window.requestAnimFrame(loop); 87 | c.clearRect(0, 0, w, h); 88 | draw(); 89 | } 90 | 91 | window.addEventListener("resize", function() { 92 | (w = canvas.width = window.innerWidth), 93 | (h = canvas.height = window.innerHeight); 94 | loop(); 95 | }); 96 | 97 | loop(); 98 | setInterval(loop, 1000 / 60); -------------------------------------------------------------------------------- /ml-client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/public/logo192.png -------------------------------------------------------------------------------- /ml-client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/public/logo512.png -------------------------------------------------------------------------------- /ml-client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /ml-client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /ml-client/src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Routes } from "./Routes"; 3 | import { 4 | BrowserRouter as Router 5 | } from "react-router-dom"; 6 | 7 | function App() { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | } 14 | 15 | export default App; -------------------------------------------------------------------------------- /ml-client/src/Routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Route, Switch, Redirect } from "react-router-dom"; 3 | import Login from './views/Login' 4 | import DashBoard from './views/DashBoard' 5 | import isAuth from "./admin/auth"; 6 | 7 | 8 | 9 | function PrivateRoute ({component: Component, authed, ...rest}) { 10 | return ( 11 | authed === true 14 | ? 15 | : } 16 | /> 17 | ) 18 | } 19 | 20 | export const Routes = () => { 21 | const auth = isAuth() 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | }; -------------------------------------------------------------------------------- /ml-client/src/admin/auth.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export const BASE_URL = process.env.REACT_APP_API_V1 4 | 5 | 6 | export default function isAuth(){ 7 | const freshToken = localStorage.getItem('freshToken'); 8 | const accessToken = localStorage.getItem('accessToken'); 9 | const isRemember = localStorage.getItem('isRemember'); 10 | var expireAccessToken = localStorage.getItem('expireAccessToken'); 11 | var expireFreshToken = localStorage.getItem('expireFreshToken'); 12 | expireAccessToken = new Date(expireAccessToken) 13 | expireFreshToken = new Date(expireFreshToken) 14 | 15 | var d1 = new Date(); 16 | var now = new Date( d1.getUTCFullYear(), d1.getUTCMonth(), d1.getUTCDate(), d1.getUTCHours(), d1.getUTCMinutes(), d1.getUTCSeconds() ); 17 | const bodyFreshToken = new FormData(); 18 | bodyFreshToken.append("refresh_token", freshToken); 19 | let check = false; 20 | 21 | 22 | if (accessToken === null) {check = false} // console.log('accessToken not on localStore!') 23 | else if (expireAccessToken > now) {check = true} // console.log("accessToken not expire yet!", expireAccessToken) 24 | else if (expireFreshToken < now) {return check} // console.log("freshToken expire!", expireFreshToken) 25 | else if (expireFreshToken > now && isRemember === 'true'){ 26 | console.log("refreshToken not expire yet!", expireFreshToken) 27 | axios({ 28 | method: "post", 29 | url: BASE_URL + "/account/login/refresh-token", 30 | data: bodyFreshToken, 31 | headers: { "Content-Type": "multipart/form-data" }, 32 | }).then(function (response) { 33 | if (response.status === 200){ 34 | check = true 35 | const data = response.data 36 | localStorage.setItem('tokenType', data.token_type) 37 | localStorage.setItem('accessToken', data.access_token) 38 | localStorage.setItem('freshToken', data.refresh_token) 39 | localStorage.setItem('expireAccessToken', data.expire_token) 40 | localStorage.setItem('expireFreshToken', data.expire_refresh_token) 41 | // console.log("new token create by fresh token!") 42 | } 43 | else { 44 | check = false 45 | localStorage.clear() 46 | // console.log("fresh error token") 47 | } 48 | }).catch(function (response) { 49 | check = false 50 | localStorage.clear(); 51 | // console.log("api error token") 52 | }); 53 | }else { 54 | check = false 55 | localStorage.clear(); 56 | // console.log("khac") 57 | } 58 | // console.log("Auth:", check) 59 | return check 60 | } -------------------------------------------------------------------------------- /ml-client/src/assets/images/dashboard/Ads.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/assets/images/dashboard/Ads.png -------------------------------------------------------------------------------- /ml-client/src/assets/images/dashboard/Currency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/assets/images/dashboard/Currency.png -------------------------------------------------------------------------------- /ml-client/src/assets/images/dashboard/Developer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/assets/images/dashboard/Developer.png -------------------------------------------------------------------------------- /ml-client/src/assets/images/dashboard/Game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/assets/images/dashboard/Game.png -------------------------------------------------------------------------------- /ml-client/src/assets/images/dashboard/Ingame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/assets/images/dashboard/Ingame.png -------------------------------------------------------------------------------- /ml-client/src/assets/images/dashboard/Intagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/assets/images/dashboard/Intagram.png -------------------------------------------------------------------------------- /ml-client/src/assets/images/dashboard/User.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/assets/images/dashboard/User.png -------------------------------------------------------------------------------- /ml-client/src/assets/images/dashboard/blog.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/assets/images/dashboard/blog.ico -------------------------------------------------------------------------------- /ml-client/src/assets/images/dashboard/blog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/assets/images/dashboard/blog.png -------------------------------------------------------------------------------- /ml-client/src/assets/images/dashboard/donate.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/assets/images/dashboard/donate.jpeg -------------------------------------------------------------------------------- /ml-client/src/assets/images/dashboard/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/assets/images/dashboard/github.png -------------------------------------------------------------------------------- /ml-client/src/assets/images/dashboard/gmail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/assets/images/dashboard/gmail.png -------------------------------------------------------------------------------- /ml-client/src/assets/images/dashboard/hotmail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/assets/images/dashboard/hotmail.png -------------------------------------------------------------------------------- /ml-client/src/assets/images/dashboard/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/assets/images/dashboard/linkedin.png -------------------------------------------------------------------------------- /ml-client/src/assets/images/dashboard/mobile.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/assets/images/dashboard/mobile.jpeg -------------------------------------------------------------------------------- /ml-client/src/assets/images/dashboard/result.JPEG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/assets/images/dashboard/result.JPEG -------------------------------------------------------------------------------- /ml-client/src/assets/images/dashboard/test.JPEG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/assets/images/dashboard/test.JPEG -------------------------------------------------------------------------------- /ml-client/src/components/account/Account.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Avatar } from 'antd'; 3 | import { UserOutlined } from '@ant-design/icons'; 4 | import AccountService from '../../services/api/account.service' 5 | 6 | 7 | export default class Account extends React.Component{ 8 | constructor(props){ 9 | super(props); 10 | this.state = { 11 | avatar_url: null 12 | } 13 | } 14 | componentDidMount(){ 15 | AccountService.getUserInfo(response =>{ 16 | this.setState({avatar_url: response.data.avatar_url}) 17 | },(err) => { 18 | alert(err); 19 | }); 20 | } 21 | render(){ 22 | return ( 23 |
24 | {this.state.avatar_url === null ? }/> : 25 | avatar 26 | } 27 |
28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ml-client/src/components/box/Box.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ArrowUpOutlined, ArrowDownOutlined} from '@ant-design/icons' 3 | 4 | 5 | export default class Box extends React.Component{ 6 | constructor(props){ 7 | super(props); 8 | this.state = { 9 | name: this.props.name, 10 | value: this.props.value, 11 | currency: this.props.currency, 12 | icon: this.props.icon, 13 | increaseValue: this.props.increaseValue || 12, 14 | isIncrease: this.props.isIncrease 15 | } 16 | 17 | } 18 | render(){ 19 | return( 20 |
21 |
22 | { this.state.isIncrease ? : } 23 |

{this.state.increaseValue}%

24 | {this.state.icon} 25 |
26 |
27 | {this.state.value}{this.state.currency} 28 |
29 |
30 | {this.state.name} 31 |
32 | 33 | 34 | 35 |
36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ml-client/src/components/buttom/Buttom.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | // nodejs library to set properties for components 3 | import PropTypes from "prop-types"; 4 | // nodejs library that concatenates classes 5 | import classNames from "classnames"; 6 | 7 | // @material-ui/core components 8 | import makeStyles from "@material-ui/core/styles/makeStyles"; 9 | import Button from "@material-ui/core/Button"; 10 | 11 | // core components 12 | 13 | import buttonStyle from "../../styles/jss/buttomStyle"; 14 | 15 | const makeComponentStyles = makeStyles(() => ({ 16 | ...buttonStyle 17 | })); 18 | 19 | const RegularButton = React.forwardRef((props, ref) => { 20 | const { 21 | color, 22 | round, 23 | children, 24 | fullWidth, 25 | disabled, 26 | simple, 27 | size, 28 | block, 29 | link, 30 | justIcon, 31 | className, 32 | ...rest 33 | } = props; 34 | 35 | const classes = makeComponentStyles(); 36 | 37 | const btnClasses = classNames({ 38 | [classes.button]: true, 39 | [classes[size]]: size, 40 | [classes[color]]: color, 41 | [classes.round]: round, 42 | [classes.fullWidth]: fullWidth, 43 | [classes.disabled]: disabled, 44 | [classes.simple]: simple, 45 | [classes.block]: block, 46 | [classes.link]: link, 47 | [classes.justIcon]: justIcon, 48 | [className]: className 49 | }); 50 | return ( 51 | 54 | ); 55 | }); 56 | 57 | RegularButton.propTypes = { 58 | color: PropTypes.oneOf([ 59 | "primary", 60 | "info", 61 | "success", 62 | "warning", 63 | "danger", 64 | "rose", 65 | "white", 66 | "facebook", 67 | "twitter", 68 | "google", 69 | "github", 70 | "transparent" 71 | ]), 72 | size: PropTypes.oneOf(["sm", "lg"]), 73 | simple: PropTypes.bool, 74 | round: PropTypes.bool, 75 | fullWidth: PropTypes.bool, 76 | disabled: PropTypes.bool, 77 | block: PropTypes.bool, 78 | link: PropTypes.bool, 79 | justIcon: PropTypes.bool, 80 | children: PropTypes.node, 81 | className: PropTypes.string 82 | }; 83 | 84 | export default RegularButton; -------------------------------------------------------------------------------- /ml-client/src/components/buttom/chat/ChatClient.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-client/src/components/buttom/chat/ChatClient.js -------------------------------------------------------------------------------- /ml-client/src/components/chart/bar/HorizontalBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Bar } from 'react-chartjs-2'; 3 | 4 | 5 | export default class HorizontalBar extends React.Component{ 6 | constructor(props){ 7 | super(props); 8 | this.state = { 9 | name: this.props.name || 'HorizontalBar', 10 | data: { 11 | labels: ['Duy', 'Hung', 'Nghia', 'My', 'An', 'Vin','Huy',"T. Nghia", "Nhue", "Loz"], 12 | datasets: [ 13 | { 14 | label: 'USD', 15 | data: [12, 19, 3, 5, 2, 3, 7,8,9,6], 16 | backgroundColor: [ 17 | 'rgba(255, 99, 132, 0.2)', 18 | 'rgba(54, 162, 235, 0.2)', 19 | 'rgba(255, 206, 86, 0.2)', 20 | 'rgba(75, 192, 192, 0.2)', 21 | 'rgba(153, 102, 255, 0.2)', 22 | 'rgba(255, 159, 64, 0.2)', 23 | ], 24 | borderColor: [ 25 | 'rgba(255, 99, 132, 1)', 26 | 'rgba(54, 162, 235, 1)', 27 | 'rgba(255, 206, 86, 1)', 28 | 'rgba(75, 192, 192, 1)', 29 | 'rgba(153, 102, 255, 1)', 30 | 'rgba(255, 159, 64, 1)', 31 | ], 32 | borderWidth: 1, 33 | }, 34 | ], 35 | }, 36 | options: { 37 | indexAxis: 'y', 38 | elements: { 39 | bar: { 40 | borderWidth: 2, 41 | }, 42 | }, 43 | responsive: true, 44 | plugins: { 45 | legend: { 46 | position: 'right', 47 | }, 48 | title: { 49 | display: false, 50 | }, 51 | }, 52 | } 53 | } 54 | } 55 | render(){ 56 | return( 57 |
58 |
59 |

{this.state.name}

60 |
61 | 62 |
63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ml-client/src/components/chart/bar/VerticalBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Bar } from 'react-chartjs-2'; 3 | 4 | 5 | export default class VerticalBar extends React.Component{ 6 | constructor(props){ 7 | super(props); 8 | this.state = { 9 | name: props.name || "Most Paid Users", 10 | data: { 11 | labels: ['Duy Nguyen', 'Hung Le', 'My Le', 'An Dang', 'Huy Hoang', 'Vin Tran', 'Heo', 'Ha', 'Leu Leu', 'Con Céc'], 12 | datasets: [ 13 | { 14 | label: 'Minute', 15 | data: [120, 190, 30, 70, 46, 43,111,111,111,111], 16 | backgroundColor: [ 17 | 'rgba(255, 99, 132, 0.2)', 18 | 'rgba(54, 162, 235, 0.2)', 19 | 'rgba(255, 206, 86, 0.2)', 20 | 'rgba(75, 192, 192, 0.2)', 21 | 'rgba(153, 102, 255, 0.2)', 22 | 'rgba(255, 159, 64, 0.2)', 23 | ], 24 | borderColor: [ 25 | 'rgba(255, 99, 132, 1)', 26 | 'rgba(54, 162, 235, 1)', 27 | 'rgba(255, 206, 86, 1)', 28 | 'rgba(75, 192, 192, 1)', 29 | 'rgba(153, 102, 255, 1)', 30 | 'rgba(255, 159, 64, 1)', 31 | ], 32 | borderWidth: 1, 33 | }, 34 | ] 35 | }, 36 | options: { 37 | scales: { 38 | yAxes: [ 39 | { 40 | ticks: { 41 | beginAtZero: true, 42 | }, 43 | }, 44 | ], 45 | }, 46 | } 47 | 48 | } 49 | } 50 | render(){ 51 | return ( 52 |
53 |
54 |

{this.state.name}

55 |
56 | 57 |
58 | ); 59 | } 60 | } 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /ml-client/src/components/chart/line/LineChart.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Line } from 'react-chartjs-2'; 3 | 4 | 5 | export default class LineChart extends React.Component { 6 | constructor(props){ 7 | super(props); 8 | this.state = { 9 | data: { 10 | labels: ['01/10', '02/10', '03/10', '04/10', '05/10', 11 | '06/10', '07/10', '08/10', '09/10', '10/10', 12 | '11/10', '12/10', '13/10', '14/10', '15/10', 13 | '16/10', '17/10', '18/10', '19/10', '20/10', 14 | '21/10', '22/10', '23/10', '24/10', '25/10', 15 | '26/10', '27/10', '28/10', '29/10', '30/10', 16 | 17 | ], 18 | datasets: [ 19 | { 20 | label: '# of USD', 21 | data: [12, 19, 3, 5, 2, 3,12, 19, 3, 5, 2, 3,12, 22 | 19, 3, 35, 2, 3,12, 19, 3, 65, 112, 3,12, 19, 43, 5, 42, 43,12, 19, 34, 54, 2, 3,], 23 | fill: false, 24 | backgroundColor: 'rgb(255, 99, 132)', 25 | borderColor: 'rgba(255, 99, 132, 0.2)', 26 | }, 27 | ], 28 | }, 29 | options: { 30 | scales: { 31 | y: { 32 | beginAtZero: true 33 | } 34 | } 35 | } 36 | } 37 | } 38 | render(){ 39 | return( 40 |
41 |
42 |

Line Chart

43 |
44 | 45 |
46 | ) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ml-client/src/components/chart/line/MultiAxisLine.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Line } from 'react-chartjs-2'; 3 | 4 | 5 | export default class MultiAxisLine extends React.Component{ 6 | constructor(props){ 7 | super(props); 8 | this.state = { 9 | name: this.props.name, 10 | data: { 11 | labels: [ 12 | '01/10', '02/10', '03/10', '04/10', '05/10', 13 | '06/10', '07/10', '08/10', '09/10', '10/10', 14 | '11/10', '12/10', '13/10', '14/10', '15/10', 15 | '16/10', '17/10', '18/10', '19/10', '20/10', 16 | '21/10', '22/10', '23/10', '24/10', '25/10', 17 | '26/10', '27/10', '28/10', '29/10', '30/10', 18 | ], 19 | datasets: [ 20 | { 21 | label: 'USD', 22 | data: [ 23 | 12, 19, 30, 50, 2000, 1730,1742, 494, 300, 750, 230, 30,12, 24 | 19, 30, 35, 20, 3,45, 19, 30, 65, 112, 30,120, 25 | 19, 43, 5, 42, 43,144, 19, 34, 54, 20, 30, 26 | ], 27 | fill: false, 28 | backgroundColor: 'rgb(255, 99, 132)', 29 | borderColor: 'rgba(255, 99, 132, 0.2)', 30 | yAxisID: 'y-axis-1', 31 | }, 32 | { 33 | label: 'Number of people', 34 | data: [ 12, 19, 30, 50, 534, 430,132, 619, 530, 150, 420, 30,12, 35 | 19, 30, 35, 20, 993,999, 1229, 30, 65, 112, 30,120, 36 | 19, 43, 5, 42, 43,144, 19, 34, 54, 20, 30,], 37 | fill: false, 38 | backgroundColor: 'rgb(54, 162, 235)', 39 | borderColor: 'rgba(54, 162, 235, 0.2)', 40 | yAxisID: 'y-axis-2', 41 | }, 42 | ], 43 | }, 44 | options: { 45 | scales: { 46 | yAxes: [ 47 | { 48 | type: 'linear', 49 | display: true, 50 | position: 'left', 51 | id: 'y-axis-1', 52 | }, 53 | { 54 | type: 'linear', 55 | display: true, 56 | position: 'right', 57 | id: 'y-axis-2', 58 | gridLines: { 59 | drawOnArea: false, 60 | }, 61 | }, 62 | ], 63 | }, 64 | } 65 | } 66 | } 67 | render(){ 68 | return ( 69 | 70 |
71 |
72 |

{this.state.name}

73 |
74 | 75 |
76 | ); 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /ml-client/src/components/chart/other/Polar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PolarArea } from 'react-chartjs-2'; 3 | 4 | export default class Polar extends React.Component { 5 | constructor(props){ 6 | super(props); 7 | this.state = { 8 | data: { 9 | labels: ['Geshin Impart', 'Tic Tac Ton', 'Minercraft', 'The Witcher', 'Don\'t Starve', 'React Chess'], 10 | datasets: [ 11 | { 12 | label: '# of USD', 13 | data: [12, 33, 14, 21, 24, 32], 14 | backgroundColor: [ 15 | 'rgba(255, 99, 132, 0.5)', 16 | 'rgba(54, 162, 235, 0.5)', 17 | 'rgba(255, 206, 86, 0.5)', 18 | 'rgba(75, 192, 192, 0.5)', 19 | 'rgba(153, 102, 255, 0.5)', 20 | 'rgba(255, 159, 64, 0.5)', 21 | ], 22 | borderWidth: 1, 23 | }, 24 | ], 25 | }, 26 | name: props.name || "Highest Grossing Game" 27 | } 28 | } 29 | render(){ 30 | return( 31 |
32 |
33 |

{this.state.name}

34 |
35 |
36 |
37 | 38 |
39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ml-client/src/components/footer/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Github from '../../assets/images/dashboard/github.png' 3 | import Donate from '../../assets/images/dashboard/donate.jpeg' 4 | import Blog from '../../assets/images/dashboard/blog.png' 5 | import Intagram from '../../assets/images/dashboard/Intagram.png' 6 | import LinkedIn from '../../assets/images/dashboard/linkedin.png' 7 | import Mobile from '../../assets/images/dashboard/mobile.jpeg' 8 | import Hotmail from '../../assets/images/dashboard/hotmail.png' 9 | import Gmail from '../../assets/images/dashboard/gmail.png' 10 | 11 | 12 | const Footer = (props) => { 13 | return( 14 | <> 15 | 93 | 94 | ); 95 | } 96 | 97 | export default Footer; -------------------------------------------------------------------------------- /ml-client/src/components/header/AntHeader.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default class AntHeader extends React.Component{ 4 | constructor(props){ 5 | super(props); 6 | this.state = { 7 | 8 | } 9 | } 10 | render(){ 11 | return ( 12 |
13 | 14 |
15 |
Demo ML Models In Production
16 |
17 |
18 | ) 19 | } 20 | } -------------------------------------------------------------------------------- /ml-client/src/components/input/CustomInput.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | // nodejs library to set properties for components 3 | import PropTypes from "prop-types"; 4 | // nodejs library that concatenates classes 5 | import classNames from "classnames"; 6 | // @material-ui/core components 7 | import { makeStyles } from "@material-ui/core/styles"; 8 | import FormControl from "@material-ui/core/FormControl"; 9 | import InputLabel from "@material-ui/core/InputLabel"; 10 | import Input from "@material-ui/core/Input"; 11 | 12 | import styles from "../../styles/jss/customInputStyle"; 13 | 14 | const useStyles = makeStyles(styles); 15 | 16 | export default function CustomInput(props) { 17 | const classes = useStyles(); 18 | const { 19 | formControlProps, 20 | labelText, 21 | id, 22 | labelProps, 23 | inputProps, 24 | error, 25 | white, 26 | inputRootCustomClasses, 27 | success, 28 | handleChange, 29 | type 30 | } = props; 31 | 32 | const labelClasses = classNames({ 33 | [" " + classes.labelRootError]: error, 34 | [" " + classes.labelRootSuccess]: success && !error 35 | }); 36 | const underlineClasses = classNames({ 37 | [classes.underlineError]: error, 38 | [classes.underlineSuccess]: success && !error, 39 | [classes.underline]: true, 40 | [classes.whiteUnderline]: white 41 | }); 42 | const marginTop = classNames({ 43 | [inputRootCustomClasses]: inputRootCustomClasses !== undefined 44 | }); 45 | const inputClasses = classNames({ 46 | [classes.input]: true, 47 | [classes.whiteInput]: white 48 | }); 49 | var formControlClasses; 50 | if (formControlProps !== undefined) { 51 | formControlClasses = classNames( 52 | formControlProps.className, 53 | classes.formControl 54 | ); 55 | } else { 56 | formControlClasses = classes.formControl; 57 | } 58 | return ( 59 | 60 | {labelText !== undefined ? ( 61 | 66 | {labelText} 67 | 68 | ) : null} 69 | 81 | 82 | ); 83 | } 84 | 85 | CustomInput.propTypes = { 86 | labelText: PropTypes.node, 87 | labelProps: PropTypes.object, 88 | id: PropTypes.string, 89 | inputProps: PropTypes.object, 90 | formControlProps: PropTypes.object, 91 | inputRootCustomClasses: PropTypes.string, 92 | error: PropTypes.bool, 93 | success: PropTypes.bool, 94 | white: PropTypes.bool 95 | }; 96 | -------------------------------------------------------------------------------- /ml-client/src/components/loading/SpinLoading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import 'antd/dist/antd.css'; 3 | import { Spin, Space } from 'antd'; 4 | 5 | export default class SpinLoading extends React.Component{ 6 | constructor(props){ 7 | super(props); 8 | this.state = { 9 | size: props.size || "" 10 | } 11 | } 12 | render(){ 13 | return( 14 | 15 | 16 | 17 | ) 18 | } 19 | } 20 | 21 | 22 | -------------------------------------------------------------------------------- /ml-client/src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | body { 7 | width: 100vw; 8 | height: 100vh; 9 | background-image: url("assets/triangleBackground.png"); 10 | background-size: cover; 11 | 12 | } 13 | #root { 14 | height: 100%; 15 | width: 100%; 16 | } 17 | 18 | .form-container { 19 | width: 100vw; 20 | height: 100vh; 21 | display: flex; 22 | justify-content: center; 23 | align-items: center; 24 | } 25 | #mainForm { 26 | width: 40%; 27 | height: 60%; 28 | } -------------------------------------------------------------------------------- /ml-client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | 5 | // import "bootstrap/dist/css/bootstrap.min.css"; 6 | import "./styles/react-chess.scss" 7 | import "antd/dist/antd.css"; 8 | 9 | ReactDOM.render( 10 |
11 | 12 |
, 13 | document.getElementById("root") 14 | ); -------------------------------------------------------------------------------- /ml-client/src/services/api/account.service.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | 4 | const API_URL = process.env.REACT_APP_API_V1 5 | 6 | 7 | const getUserInfo = (successcallback, errorcallback) => { 8 | const token = localStorage.getItem("accessToken"); 9 | axios.get(`${API_URL}/account/info`, { 10 | headers: { 11 | "Content-Type": "multipart/form-data", 12 | "Authorization": "Bearer " + token, 13 | }, 14 | }) 15 | .then(response => { 16 | if(successcallback != null){ 17 | successcallback(response); 18 | } 19 | }) 20 | .catch(err => { 21 | if(errorcallback != null){ 22 | errorcallback(err); 23 | } 24 | }) 25 | } 26 | 27 | const register = (username, email, password) => { 28 | return axios.post(`${API_URL}/account/signup`, { 29 | username, 30 | email, 31 | password, 32 | }); 33 | }; 34 | 35 | const logout = () => { 36 | localStorage.clear(); 37 | }; 38 | 39 | 40 | export default { 41 | getUserInfo, 42 | register, 43 | logout 44 | } -------------------------------------------------------------------------------- /ml-client/src/styles/jss/buttomStyle.js: -------------------------------------------------------------------------------- 1 | import { 2 | grayColor, 3 | roseColor, 4 | primaryColor, 5 | infoColor, 6 | successColor, 7 | warningColor, 8 | dangerColor 9 | } from "../../styles/jss/material-kit-react"; 10 | 11 | const gray = "#333"; 12 | 13 | const buttonStyle = { 14 | button: { 15 | minHeight: "auto", 16 | minWidth: "auto", 17 | backgroundColor: grayColor, 18 | color: "#FFFFFF", 19 | boxShadow: 20 | "0 2px 2px 0 rgba(153, 153, 153, 0.14), 0 3px 1px -2px rgba(153, 153, 153, 0.2), 0 1px 5px 0 rgba(153, 153, 153, 0.12)", 21 | border: "none", 22 | borderRadius: "3px", 23 | position: "relative", 24 | padding: "12px 30px", 25 | margin: ".3125rem 1px", 26 | fontSize: "12px", 27 | fontWeight: "400", 28 | textTransform: "uppercase", 29 | letterSpacing: "0", 30 | willChange: "box-shadow, transform", 31 | transition: 32 | "box-shadow 0.2s cubic-bezier(0.4, 0, 1, 1), background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1)", 33 | lineHeight: "1.42857143", 34 | textAlign: "center", 35 | whiteSpace: "nowrap", 36 | verticalAlign: "middle", 37 | touchAction: "manipulation", 38 | cursor: "pointer", 39 | "&:hover,&:focus": { 40 | color: "#FFFFFF", 41 | backgroundColor: grayColor, 42 | boxShadow: `0 14px 26px -12px ${primaryColor}, 0 4px 23px 0px ${primaryColor}, 0 8px 10px -5px ${primaryColor}` 43 | }, 44 | "& .fab,& .fas,& .far,& .fal,& .material-icons": { 45 | position: "relative", 46 | display: "inline-block", 47 | top: "0", 48 | fontSize: "1.1rem", 49 | marginRight: "4px", 50 | verticalAlign: "middle" 51 | }, 52 | "& svg": { 53 | position: "relative", 54 | display: "inline-block", 55 | top: "0", 56 | width: "18px", 57 | height: "18px", 58 | marginRight: "4px", 59 | verticalAlign: "middle" 60 | }, 61 | "&$justIcon": { 62 | "& .fab,& .fas,& .far,& .fal,& .material-icons": { 63 | marginRight: "0px", 64 | position: "absolute", 65 | width: "100%", 66 | transform: "none", 67 | left: "0px", 68 | top: "0px", 69 | height: "100%", 70 | lineHeight: "41px", 71 | fontSize: "20px" 72 | } 73 | } 74 | }, 75 | fullWidth: { 76 | width: "100%" 77 | }, 78 | primary: { 79 | backgroundColor: primaryColor, 80 | boxShadow: 81 | "0 2px 2px 0 rgba(156, 39, 176, 0.14), 0 3px 1px -2px rgba(156, 39, 176, 0.2), 0 1px 5px 0 rgba(156, 39, 176, 0.12)", 82 | "&:hover,&:focus": { 83 | backgroundColor: `${gray}`, 84 | boxShadow: `0 5px 10px -6px ${gray}` 85 | } 86 | }, 87 | info: { 88 | backgroundColor: infoColor, 89 | boxShadow: 90 | "0 2px 2px 0 rgba(0, 188, 212, 0.14), 0 3px 1px -2px rgba(0, 188, 212, 0.2), 0 1px 5px 0 rgba(0, 188, 212, 0.12)", 91 | "&:hover,&:focus": { 92 | backgroundColor: infoColor, 93 | boxShadow: 94 | "0 14px 26px -12px rgba(0, 188, 212, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 188, 212, 0.2)" 95 | } 96 | }, 97 | success: { 98 | backgroundColor: successColor, 99 | boxShadow: 100 | "0 2px 2px 0 rgba(76, 175, 80, 0.14), 0 3px 1px -2px rgba(76, 175, 80, 0.2), 0 1px 5px 0 rgba(76, 175, 80, 0.12)", 101 | "&:hover,&:focus": { 102 | backgroundColor: successColor, 103 | boxShadow: 104 | "0 14px 26px -12px rgba(76, 175, 80, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(76, 175, 80, 0.2)" 105 | } 106 | }, 107 | warning: { 108 | backgroundColor: warningColor, 109 | boxShadow: 110 | "0 2px 2px 0 rgba(255, 152, 0, 0.14), 0 3px 1px -2px rgba(255, 152, 0, 0.2), 0 1px 5px 0 rgba(255, 152, 0, 0.12)", 111 | "&:hover,&:focus": { 112 | backgroundColor: warningColor, 113 | boxShadow: 114 | "0 14px 26px -12px rgba(255, 152, 0, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(255, 152, 0, 0.2)" 115 | } 116 | }, 117 | danger: { 118 | backgroundColor: dangerColor, 119 | boxShadow: 120 | "0 2px 2px 0 rgba(244, 67, 54, 0.14), 0 3px 1px -2px rgba(244, 67, 54, 0.2), 0 1px 5px 0 rgba(244, 67, 54, 0.12)", 121 | "&:hover,&:focus": { 122 | backgroundColor: dangerColor, 123 | boxShadow: 124 | "0 14px 26px -12px rgba(244, 67, 54, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(244, 67, 54, 0.2)" 125 | } 126 | }, 127 | rose: { 128 | backgroundColor: roseColor, 129 | boxShadow: 130 | "0 2px 2px 0 rgba(233, 30, 99, 0.14), 0 3px 1px -2px rgba(233, 30, 99, 0.2), 0 1px 5px 0 rgba(233, 30, 99, 0.12)", 131 | "&:hover,&:focus": { 132 | backgroundColor: roseColor, 133 | boxShadow: 134 | "0 14px 26px -12px rgba(233, 30, 99, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(233, 30, 99, 0.2)" 135 | } 136 | }, 137 | white: { 138 | "&,&:focus,&:hover,&:visited": { 139 | backgroundColor: "#FFFFFF", 140 | color: grayColor 141 | } 142 | }, 143 | twitter: { 144 | backgroundColor: "#55acee", 145 | color: "#fff", 146 | boxShadow: 147 | "0 2px 2px 0 rgba(85, 172, 238, 0.14), 0 3px 1px -2px rgba(85, 172, 238, 0.2), 0 1px 5px 0 rgba(85, 172, 238, 0.12)", 148 | "&:hover,&:focus,&:visited": { 149 | backgroundColor: "#55acee", 150 | color: "#fff", 151 | boxShadow: 152 | "0 14px 26px -12px rgba(85, 172, 238, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(85, 172, 238, 0.2)" 153 | } 154 | }, 155 | facebook: { 156 | backgroundColor: "#3b5998", 157 | color: "#fff", 158 | boxShadow: 159 | "0 2px 2px 0 rgba(59, 89, 152, 0.14), 0 3px 1px -2px rgba(59, 89, 152, 0.2), 0 1px 5px 0 rgba(59, 89, 152, 0.12)", 160 | "&:hover,&:focus": { 161 | backgroundColor: "#3b5998", 162 | color: "#fff", 163 | boxShadow: 164 | "0 14px 26px -12px rgba(59, 89, 152, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(59, 89, 152, 0.2)" 165 | } 166 | }, 167 | google: { 168 | backgroundColor: "#dd4b39", 169 | color: "#fff", 170 | boxShadow: 171 | "0 2px 2px 0 rgba(221, 75, 57, 0.14), 0 3px 1px -2px rgba(221, 75, 57, 0.2), 0 1px 5px 0 rgba(221, 75, 57, 0.12)", 172 | "&:hover,&:focus": { 173 | backgroundColor: "#dd4b39", 174 | color: "#fff", 175 | boxShadow: 176 | "0 14px 26px -12px rgba(221, 75, 57, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(221, 75, 57, 0.2)" 177 | } 178 | }, 179 | github: { 180 | backgroundColor: "#333333", 181 | color: "#fff", 182 | boxShadow: 183 | "0 2px 2px 0 rgba(51, 51, 51, 0.14), 0 3px 1px -2px rgba(51, 51, 51, 0.2), 0 1px 5px 0 rgba(51, 51, 51, 0.12)", 184 | "&:hover,&:focus": { 185 | backgroundColor: "#333333", 186 | color: "#fff", 187 | boxShadow: 188 | "0 14px 26px -12px rgba(51, 51, 51, 0.42), 0 4px 23px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(51, 51, 51, 0.2)" 189 | } 190 | }, 191 | simple: { 192 | "&,&:focus,&:hover,&:visited": { 193 | color: "#FFFFFF", 194 | background: "transparent", 195 | boxShadow: "none" 196 | }, 197 | "&$primary": { 198 | "&,&:focus,&:hover,&:visited": { 199 | color: primaryColor 200 | } 201 | }, 202 | "&$info": { 203 | "&,&:focus,&:hover,&:visited": { 204 | color: infoColor 205 | } 206 | }, 207 | "&$success": { 208 | "&,&:focus,&:hover,&:visited": { 209 | color: successColor 210 | } 211 | }, 212 | "&$warning": { 213 | "&,&:focus,&:hover,&:visited": { 214 | color: warningColor 215 | } 216 | }, 217 | "&$rose": { 218 | "&,&:focus,&:hover,&:visited": { 219 | color: roseColor 220 | } 221 | }, 222 | "&$danger": { 223 | "&,&:focus,&:hover,&:visited": { 224 | color: dangerColor 225 | } 226 | }, 227 | "&$twitter": { 228 | "&,&:focus,&:hover,&:visited": { 229 | color: "#55acee" 230 | } 231 | }, 232 | "&$facebook": { 233 | "&,&:focus,&:hover,&:visited": { 234 | color: "#3b5998" 235 | } 236 | }, 237 | "&$google": { 238 | "&,&:focus,&:hover,&:visited": { 239 | color: "#dd4b39" 240 | } 241 | }, 242 | "&$github": { 243 | "&,&:focus,&:hover,&:visited": { 244 | color: "#333333" 245 | } 246 | } 247 | }, 248 | transparent: { 249 | "&,&:focus,&:hover,&:visited": { 250 | color: "inherit", 251 | background: "transparent", 252 | boxShadow: "none" 253 | } 254 | }, 255 | disabled: { 256 | opacity: "0.65", 257 | pointerEvents: "none" 258 | }, 259 | lg: { 260 | padding: "1.125rem 2.25rem", 261 | fontSize: "0.875rem", 262 | lineHeight: "1.333333", 263 | borderRadius: "0.2rem" 264 | }, 265 | sm: { 266 | padding: "0.40625rem 1.25rem", 267 | fontSize: "0.6875rem", 268 | lineHeight: "1.5", 269 | borderRadius: "0.2rem" 270 | }, 271 | round: { 272 | borderRadius: "30px" 273 | }, 274 | block: { 275 | width: "100% !important" 276 | }, 277 | link: { 278 | "&,&:hover,&:focus": { 279 | backgroundColor: "transparent", 280 | color: "#999999", 281 | boxShadow: "none" 282 | } 283 | }, 284 | justIcon: { 285 | paddingLeft: "12px", 286 | paddingRight: "12px", 287 | fontSize: "20px", 288 | height: "41px", 289 | minWidth: "41px", 290 | width: "41px", 291 | "& .fab,& .fas,& .far,& .fal,& svg,& .material-icons": { 292 | marginRight: "0px" 293 | }, 294 | "&$lg": { 295 | height: "57px", 296 | minWidth: "57px", 297 | width: "57px", 298 | lineHeight: "56px", 299 | "& .fab,& .fas,& .far,& .fal,& .material-icons": { 300 | fontSize: "32px", 301 | lineHeight: "56px" 302 | }, 303 | "& svg": { 304 | width: "32px", 305 | height: "32px" 306 | } 307 | }, 308 | "&$sm": { 309 | height: "30px", 310 | minWidth: "30px", 311 | width: "30px", 312 | "& .fab,& .fas,& .far,& .fal,& .material-icons": { 313 | fontSize: "17px", 314 | lineHeight: "29px" 315 | }, 316 | "& svg": { 317 | width: "17px", 318 | height: "17px" 319 | } 320 | } 321 | } 322 | }; 323 | 324 | export default buttonStyle; 325 | -------------------------------------------------------------------------------- /ml-client/src/styles/jss/customInputStyle.js: -------------------------------------------------------------------------------- 1 | import { 2 | primaryColor, 3 | dangerColor, 4 | successColor, 5 | defaultFont 6 | } from "./material-kit-react.js"; 7 | 8 | const customInputStyle = { 9 | disabled: { 10 | "&:before": { 11 | borderColor: "transparent !important" 12 | } 13 | }, 14 | underline: { 15 | "&:hover:not($disabled):before,&:before": { 16 | borderColor: "#D2D2D2 !important", 17 | borderWidth: "1px !important" 18 | }, 19 | "&:after": { 20 | borderColor: primaryColor 21 | } 22 | }, 23 | underlineError: { 24 | "&:after": { 25 | borderColor: dangerColor 26 | } 27 | }, 28 | underlineSuccess: { 29 | "&:after": { 30 | borderColor: successColor 31 | } 32 | }, 33 | whiteUnderline: { 34 | "&:hover:not($disabled):before,&:before": { 35 | borderColor: "#FFFFFF" 36 | }, 37 | "&:after": { 38 | borderColor: "#FFFFFF" 39 | } 40 | }, 41 | labelRoot: { 42 | ...defaultFont, 43 | color: "#AAAAAA !important", 44 | fontWeight: "400", 45 | fontSize: "14px", 46 | lineHeight: "1.42857", 47 | top: "10px", 48 | letterSpacing: "unset", 49 | "& + $underline": { 50 | marginTop: "0px" 51 | } 52 | }, 53 | labelRootError: { 54 | color: dangerColor + " !important" 55 | }, 56 | labelRootSuccess: { 57 | color: successColor + " !important" 58 | }, 59 | formControl: { 60 | margin: "0 0 17px 0", 61 | paddingTop: "27px", 62 | position: "relative", 63 | "& svg,& .fab,& .far,& .fal,& .fas,& .material-icons": { 64 | color: "#495057" 65 | } 66 | }, 67 | input: { 68 | color: "#495057", 69 | height: "unset", 70 | "&,&::placeholder": { 71 | fontSize: "14px", 72 | fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif', 73 | fontWeight: "400", 74 | lineHeight: "1.42857", 75 | opacity: "1" 76 | }, 77 | "&::placeholder": { 78 | color: "#AAAAAA" 79 | } 80 | }, 81 | whiteInput: { 82 | "&,&::placeholder": { 83 | color: "#FFFFFF", 84 | opacity: "1" 85 | } 86 | } 87 | }; 88 | 89 | export default customInputStyle; -------------------------------------------------------------------------------- /ml-client/src/styles/jss/material-kit-react.js: -------------------------------------------------------------------------------- 1 | /*! 2 | ========================================================= 3 | * Material Kit React - v1.8.0 based on Material Kit - v2.0.2 4 | ========================================================= 5 | * Product Page: https://www.creative-tim.com/product/material-kit-react 6 | * Copyright 2019 Creative Tim (https://www.creative-tim.com) 7 | * Licensed under MIT (https://github.com/creativetimofficial/material-kit-react/blob/master/LICENSE.md) 8 | ========================================================= 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | */ 11 | 12 | // ############################## 13 | // // // Variables - Styles that are used on more than one component 14 | // ############################# 15 | 16 | const drawerWidth = 260; 17 | 18 | const transition = { 19 | transition: "all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1)" 20 | }; 21 | 22 | const conatinerFluid = { 23 | paddingRight: "15px", 24 | paddingLeft: "15px", 25 | marginRight: "auto", 26 | marginLeft: "auto", 27 | width: "100%" 28 | }; 29 | const container = { 30 | ...conatinerFluid, 31 | "@media (min-width: 576px)": { 32 | maxWidth: "540px" 33 | }, 34 | "@media (min-width: 768px)": { 35 | maxWidth: "720px" 36 | }, 37 | "@media (min-width: 992px)": { 38 | maxWidth: "960px" 39 | }, 40 | "@media (min-width: 1200px)": { 41 | maxWidth: "1140px" 42 | } 43 | }; 44 | 45 | const boxShadow = { 46 | boxShadow: 47 | "0 10px 30px -12px rgba(0, 0, 0, 0.42), 0 4px 25px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.2)" 48 | }; 49 | 50 | const card = { 51 | display: "inline-block", 52 | position: "relative", 53 | width: "100%", 54 | margin: "25px 0", 55 | boxShadow: "0 1px 4px 0 rgba(0, 0, 0, 0.14)", 56 | borderRadius: "3px", 57 | color: "rgba(0, 0, 0, 0.87)", 58 | background: "#fff" 59 | }; 60 | 61 | const defaultFont = { 62 | fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif', 63 | fontWeight: "300", 64 | lineHeight: "1.5em" 65 | }; 66 | 67 | const primaryColor = "#a7ce3b"; 68 | const warningColor = "#ff9800"; 69 | const dangerColor = "#f44336"; 70 | const successColor = "#4caf50"; 71 | const infoColor = "#00acc1"; 72 | const roseColor = "#e91e63"; 73 | const grayColor = "#999999"; 74 | 75 | const primaryBoxShadow = { 76 | boxShadow: 77 | "0 12px 20px -10px rgba(156, 39, 176, 0.28), 0 4px 20px 0px rgba(0, 0, 0, 0.12), 0 7px 8px -5px rgba(156, 39, 176, 0.2)" 78 | }; 79 | const infoBoxShadow = { 80 | boxShadow: 81 | "0 12px 20px -10px rgba(0, 188, 212, 0.28), 0 4px 20px 0px rgba(0, 0, 0, 0.12), 0 7px 8px -5px rgba(0, 188, 212, 0.2)" 82 | }; 83 | const successBoxShadow = { 84 | boxShadow: 85 | "0 12px 20px -10px rgba(76, 175, 80, 0.28), 0 4px 20px 0px rgba(0, 0, 0, 0.12), 0 7px 8px -5px rgba(76, 175, 80, 0.2)" 86 | }; 87 | const warningBoxShadow = { 88 | boxShadow: 89 | "0 12px 20px -10px rgba(255, 152, 0, 0.28), 0 4px 20px 0px rgba(0, 0, 0, 0.12), 0 7px 8px -5px rgba(255, 152, 0, 0.2)" 90 | }; 91 | const dangerBoxShadow = { 92 | boxShadow: 93 | "0 12px 20px -10px rgba(244, 67, 54, 0.28), 0 4px 20px 0px rgba(0, 0, 0, 0.12), 0 7px 8px -5px rgba(244, 67, 54, 0.2)" 94 | }; 95 | const roseBoxShadow = { 96 | boxShadow: 97 | "0 4px 20px 0px rgba(0, 0, 0, 0.14), 0 7px 10px -5px rgba(233, 30, 99, 0.4)" 98 | }; 99 | 100 | const warningCardHeader = { 101 | color: "#fff", 102 | background: "linear-gradient(60deg, #ffa726, #fb8c00)", 103 | ...warningBoxShadow 104 | }; 105 | const successCardHeader = { 106 | color: "#fff", 107 | background: "linear-gradient(60deg, #66bb6a, #43a047)", 108 | ...successBoxShadow 109 | }; 110 | const dangerCardHeader = { 111 | color: "#fff", 112 | background: "linear-gradient(60deg, #ef5350, #e53935)", 113 | ...dangerBoxShadow 114 | }; 115 | const infoCardHeader = { 116 | color: "#fff", 117 | background: "linear-gradient(60deg, #26c6da, #00acc1)", 118 | ...infoBoxShadow 119 | }; 120 | const primaryCardHeader = { 121 | color: "#fff", 122 | background: "linear-gradient(60deg, #ab47bc, #8e24aa)", 123 | ...primaryBoxShadow 124 | }; 125 | const roseCardHeader = { 126 | color: "#fff", 127 | background: "linear-gradient(60deg, #ec407a, #d81b60)", 128 | ...roseBoxShadow 129 | }; 130 | const cardActions = { 131 | margin: "0 20px 10px", 132 | paddingTop: "10px", 133 | borderTop: "1px solid #eeeeee", 134 | height: "auto", 135 | ...defaultFont 136 | }; 137 | 138 | const cardHeader = { 139 | margin: "-30px 15px 0", 140 | borderRadius: "3px", 141 | padding: "15px" 142 | }; 143 | 144 | const defaultBoxShadow = { 145 | border: "0", 146 | borderRadius: "3px", 147 | boxShadow: 148 | "0 10px 20px -12px rgba(0, 0, 0, 0.42), 0 3px 20px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.2)", 149 | padding: "10px 0", 150 | transition: "all 150ms ease 0s" 151 | }; 152 | 153 | const title = { 154 | color: "#3C4858", 155 | margin: "1.75rem 0 0.875rem", 156 | textDecoration: "none", 157 | fontWeight: "700", 158 | fontFamily: `"Roboto Slab", "Times New Roman", serif` 159 | }; 160 | 161 | const cardTitle = { 162 | ...title, 163 | marginTop: ".625rem" 164 | }; 165 | 166 | const cardLink = { 167 | "& + $cardLink": { 168 | marginLeft: "1.25rem" 169 | } 170 | }; 171 | 172 | const cardSubtitle = { 173 | marginBottom: "0", 174 | marginTop: "-.375rem" 175 | }; 176 | 177 | export { 178 | //variables 179 | drawerWidth, 180 | transition, 181 | container, 182 | conatinerFluid, 183 | boxShadow, 184 | card, 185 | defaultFont, 186 | primaryColor, 187 | warningColor, 188 | dangerColor, 189 | successColor, 190 | infoColor, 191 | roseColor, 192 | grayColor, 193 | primaryBoxShadow, 194 | infoBoxShadow, 195 | successBoxShadow, 196 | warningBoxShadow, 197 | dangerBoxShadow, 198 | roseBoxShadow, 199 | warningCardHeader, 200 | successCardHeader, 201 | dangerCardHeader, 202 | infoCardHeader, 203 | primaryCardHeader, 204 | roseCardHeader, 205 | cardActions, 206 | cardHeader, 207 | defaultBoxShadow, 208 | title, 209 | cardTitle, 210 | cardLink, 211 | cardSubtitle 212 | }; 213 | -------------------------------------------------------------------------------- /ml-client/src/styles/react-chess.scss: -------------------------------------------------------------------------------- 1 | // Components 2 | @import "react-chess/components"; 3 | @import "react-chess/views"; 4 | -------------------------------------------------------------------------------- /ml-client/src/styles/react-chess/_components.scss: -------------------------------------------------------------------------------- 1 | 2 | @import "components/account"; 3 | @import "components/vertical-bar"; 4 | @import "components/horizontal-bar"; 5 | @import "components/polar"; 6 | @import "components/line-chart"; 7 | @import "components/box"; 8 | @import "components/ant-header"; 9 | @import "components/footer"; 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ml-client/src/styles/react-chess/_views.scss: -------------------------------------------------------------------------------- 1 | @import "views/login"; 2 | @import "views/dashboard"; 3 | @import "views/menu"; 4 | -------------------------------------------------------------------------------- /ml-client/src/styles/react-chess/components/_account.scss: -------------------------------------------------------------------------------- 1 | .avatar { 2 | position:absolute; 3 | z-index: 300; 4 | right:0; 5 | top:0; 6 | padding-top: 10px; 7 | padding-right: 30px; 8 | .image{ 9 | width: 50px; 10 | height: auto; 11 | border-radius: 50%; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ml-client/src/styles/react-chess/components/_ant-header.scss: -------------------------------------------------------------------------------- 1 | .ant-header-page{ 2 | padding-left: 3.5rem; 3 | padding-top: 24px; 4 | font: 400 14px/1.6 Graphik,sans-serif; 5 | position: absolute; 6 | top: 0; 7 | left: 0; 8 | height: 100px; 9 | width: 100%; 10 | 11 | .header-final{ 12 | margin-top: 15px; 13 | transition: color .4s cubic-bezier(.19, 1, .22, 1),opacity .4s cubic-bezier(.19, 1, .22, 1); 14 | top: 8px; 15 | left: 8px; 16 | width: calc(100% - 16px); 17 | height: 66px; 18 | display: inline-flex; 19 | align-items: center; 20 | padding: 0 calc(40px - 8px); 21 | h5{ 22 | color: #111; font-family: 'Helvetica Neue', sans-serif; font-size: 45px; font-weight: bold; letter-spacing: -1px; line-height: 1; text-align: center; 23 | } 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /ml-client/src/styles/react-chess/components/_box.scss: -------------------------------------------------------------------------------- 1 | .box{ 2 | display: block; 3 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji; 4 | font-variant: tabular-nums; 5 | line-height: 1.5715; 6 | font-feature-settings: 'tnum', "tnum"; 7 | width: 217px; 8 | height: 210px; 9 | box-shadow: rgba(143, 168, 191, 0.35) 0px 8px 20px 0px; 10 | .box-title{ 11 | font-size: .88rem; 12 | font-weight: 400; 13 | line-height: 1.5; 14 | color: #495057; 15 | text-align: center; 16 | opacity: .6; 17 | } 18 | .box-avatar{ 19 | padding-left: 2rem; 20 | align-items: center; 21 | display: flex; 22 | margin-top: -10px; 23 | text-align: center; 24 | img{ 25 | height: 110px; 26 | } 27 | p{ 28 | font-weight: bold; 29 | margin-top: 14px; 30 | color: #3ac47d; 31 | } 32 | .decrease{ 33 | margin-top: 14px; 34 | color: #c4433a; 35 | } 36 | } 37 | .box-value{ 38 | margin-top: -20px; 39 | font-size: 43px; 40 | font-weight: bold; 41 | color: #495057; 42 | text-align: center; 43 | padding-bottom: 2px; 44 | 45 | } 46 | } -------------------------------------------------------------------------------- /ml-client/src/styles/react-chess/components/_footer.scss: -------------------------------------------------------------------------------- 1 | .footer{ 2 | margin: 0 auto; 3 | padding-top: 4rem; 4 | // padding-left: 2rem; 5 | display: flex; 6 | float: center; 7 | font-family: Graphik,sans-serif; 8 | height: 100px; 9 | width: 97%; 10 | 11 | .footer-content{ 12 | display: block; 13 | padding-right: 20px; 14 | max-width: 450px; 15 | line-break: auto; 16 | width: 20%; 17 | height: 100%; 18 | margin-block-start: 1em; 19 | margin-block-end: 1em; 20 | margin-inline-start: 0px; 21 | margin-inline-end: 0px; 22 | padding-inline-start: 40px; 23 | h3{ 24 | font-size: 13px; 25 | font-weight: bolder; 26 | } 27 | p{ 28 | font-size: 13px; 29 | font-weight:lighter; 30 | } 31 | .footer-content-contact{ 32 | display: grid; 33 | .fcl-item{ 34 | margin-top: -5px; 35 | padding-top: 12px; 36 | .fcli-image{ 37 | padding-right: 7px; 38 | width: 27px; 39 | height: auto; 40 | } 41 | } 42 | } 43 | .footer-content-link{ 44 | display: grid; 45 | .fcl-item{ 46 | margin-top: -5px; 47 | padding-top: 12px; 48 | .fcli-image{ 49 | padding-right: 5px; 50 | width: 27px; 51 | height: auto; 52 | } 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /ml-client/src/styles/react-chess/components/_horizontal-bar.scss: -------------------------------------------------------------------------------- 1 | .horizontal-bar{ 2 | min-width: 600px; 3 | height: auto; 4 | } -------------------------------------------------------------------------------- /ml-client/src/styles/react-chess/components/_line-chart.scss: -------------------------------------------------------------------------------- 1 | .line-chart{ 2 | min-width: 600px; 3 | } -------------------------------------------------------------------------------- /ml-client/src/styles/react-chess/components/_polar.scss: -------------------------------------------------------------------------------- 1 | .polar{ 2 | max-width: 300px; 3 | // min-width: 300px; 4 | min-height: 390px; 5 | } -------------------------------------------------------------------------------- /ml-client/src/styles/react-chess/components/_vertical-bar.scss: -------------------------------------------------------------------------------- 1 | .vertical-bar{ 2 | min-width: 600px; 3 | .header{ 4 | } 5 | } -------------------------------------------------------------------------------- /ml-client/src/styles/react-chess/views/_dashboard.scss: -------------------------------------------------------------------------------- 1 | .dashboard{ 2 | margin: 0 auto; 3 | padding-left: 2rem; 4 | display: block; 5 | padding-top: 5rem; 6 | padding-bottom: 5rem; 7 | .col-one{ 8 | margin: 30px auto; 9 | display: flex; 10 | align-items: center; 11 | .item{ 12 | height: 600px; 13 | width: 720px ; 14 | box-shadow: rgba(143, 168, 191, 0.35) 0px 8px 20px 0px; 15 | margin-top: 40px; 16 | margin-left: 60px; 17 | padding-bottom: -10px; 18 | padding-top: 10px; 19 | padding-left: 10px; 20 | padding-right: 10px; 21 | .item-box{ 22 | .image{ 23 | img{ 24 | max-height: 480px; 25 | max-width: 600px; 26 | } 27 | margin: 5px auto; 28 | // background-color: black; 29 | width: 670px; 30 | min-height: 500px; 31 | height: auto; 32 | } 33 | .title{ 34 | display: inline-flex; 35 | h1{ 36 | padding-left: 20px; 37 | padding-right: 30px; 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | 45 | } -------------------------------------------------------------------------------- /ml-client/src/styles/react-chess/views/_login.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | .login { 5 | display: flex; 6 | flex-direction: column; 7 | width: 100vw; 8 | height: calc(100vh - 13rem); 9 | position: relative; 10 | justify-content: flex-start; 11 | align-items: center; 12 | .form { 13 | border-radius: 20px; 14 | position: absolute; 15 | left: 0; 16 | right: 0; 17 | margin-left: auto; 18 | margin-right: auto; 19 | max-width:420px; 20 | flex-direction: column; 21 | background-color: rgba(255, 255, 255, 0.685); //semi-transparent red 22 | // opacity: 0.0 - 0.4; 23 | // box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px; 24 | box-shadow: rgba(0, 0, 0, 0.16) 0px 10px 36px 0px, rgba(0, 0, 0, 0.06) 0px 0px 0px 1px; 25 | padding: 20px; 26 | margin-top: 320px; 27 | // position: absolute; 28 | .logo{ 29 | width: 200px; 30 | float: center; 31 | margin-left: auto; 32 | margin-right: auto; 33 | } 34 | 35 | .form__custom-button { 36 | margin-top: 30px; 37 | } 38 | 39 | } 40 | } 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /ml-client/src/styles/react-chess/views/_menu.scss: -------------------------------------------------------------------------------- 1 | .menu-wrapper { 2 | display: block; 3 | flex: 0 0 200px; 4 | max-width: 200px; 5 | min-width: 200px; 6 | width: 200px; 7 | height: 100vh; 8 | padding: 20px; 9 | background-color: #00152A; 10 | .menu-header { 11 | display: block; 12 | border-bottom: solid 1px #fff; 13 | padding-bottom: 20px; 14 | .menu-header-room-text { 15 | color: #1890ff; 16 | margin-right: 5px; 17 | } 18 | .menu-header-room-id { 19 | margin-top: 5px; 20 | margin-bottom: 10px; 21 | padding-top:5px; 22 | padding-bottom:5px; 23 | color: #f5222d; 24 | border-radius: 5px; 25 | background-color: #fff; 26 | display: inline-block; 27 | overflow-wrap: break-word; 28 | width: 100%; 29 | 30 | } 31 | } 32 | .menu-content { 33 | margin-top: 12px; 34 | .menu-content-item { 35 | font-family: Monospaced Number,Chinese Quote,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif; 36 | -webkit-box-sizing: border-box; 37 | box-sizing: border-box; 38 | margin: 0; 39 | position: relative; 40 | display: inline-block; 41 | padding: 2px 0px; 42 | width: 100%; 43 | height: 32px; 44 | font-size: 14px; 45 | line-height: 1.5; 46 | color: rgba(172, 59, 59, 0.65); 47 | background-color: #fff; 48 | border: none !important; 49 | border-radius: 4px; 50 | outline-width: 0 !important; 51 | outline: none !important; 52 | } 53 | .menu-content-join-btn { 54 | margin-top: 10px; 55 | display: block; 56 | float: right; 57 | line-height: 1.5; 58 | font-weight: 500; 59 | text-align: center; 60 | -ms-touch-action: manipulation; 61 | touch-action: manipulation; 62 | cursor: pointer; 63 | white-space: nowrap; 64 | padding: 0 15px; 65 | font-size: 14px; 66 | border-radius: 4px; 67 | height: 26px; 68 | -webkit-user-select: none; 69 | -moz-user-select: none; 70 | -ms-user-select: none; 71 | user-select: none; 72 | -webkit-transition: all .3s cubic-bezier(.645,.045,.355,1); 73 | -o-transition: all .3s cubic-bezier(.645,.045,.355,1); 74 | transition: all .3s cubic-bezier(.645,.045,.355,1); 75 | position: relative; 76 | color: #ffffff;; 77 | background-color: rgb(65, 126, 206); 78 | border-color: rgb(65, 126, 206); 79 | } 80 | } 81 | } 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /ml-client/src/views/DashBoard.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | import AntHeader from '../components/header/AntHeader'; 3 | import Footer from '../components/footer/Footer'; 4 | import DefaultImage from '../assets/images/dashboard/test.JPEG' 5 | import DefaultImageResult from '../assets/images/dashboard/result.JPEG' 6 | import { Upload, message, Button } from 'antd'; 7 | import { UploadOutlined } from '@ant-design/icons'; 8 | import axios from "axios"; 9 | 10 | 11 | const Dashboard = () => { 12 | const [urlFileObject, setUrlFileObject] = useState(DefaultImage) 13 | const [urlFileObjectResult, setUrlFileObjectResult] = useState(DefaultImageResult) 14 | const [progress, setProgress] = useState(0); 15 | const [predict, setPredict] = useState(false); 16 | let sessionIdRef = useRef(null) 17 | let processRef = useRef(null) 18 | 19 | 20 | const handleChange = ({file}) => { 21 | const url = URL.createObjectURL(file.originFileObj) 22 | setUrlFileObject(url) 23 | setUrlFileObjectResult(null) 24 | } 25 | 26 | const beforeUpload = (file) => { 27 | const isJPG = file.type === 'image/jpeg' || file.type === 'image/png'; 28 | if (!isJPG) { 29 | message.error('You can only upload JPG or PNG file!'); 30 | return false; 31 | } else { 32 | return true; 33 | } 34 | } 35 | const startUpload = async options => { 36 | const { onSuccess, onError, file, onProgress } = options; 37 | let fmData = new FormData(); 38 | fmData.append("file", file); 39 | const config = { 40 | headers: { "content-type": "multipart/form-data" }, 41 | onUploadProgress: event => { 42 | const percent = Math.floor((event.loaded / event.total) * 100); 43 | setProgress(percent); 44 | if (percent === 100) { 45 | setTimeout(() => setProgress(0), 1000); 46 | } 47 | onProgress({ percent: (event.loaded / event.total) * 100 }); 48 | } 49 | }; 50 | try { 51 | const res = await axios.post( 52 | "http://localhost:8081/api/v1/object-detection/process", 53 | fmData, 54 | config 55 | ); 56 | onSuccess("Ok"); 57 | console.log("server res: ", res, progress); 58 | sessionIdRef.current = res.data.task_id 59 | setPredict(true) 60 | } catch (err) { 61 | console.log("Eroor: ", err); 62 | const error = new Error("Some error"); 63 | console.log(error) 64 | onError({ err }); 65 | } 66 | } 67 | 68 | const getStatusSession = (sessionId, onUploadProgress) => { 69 | return axios.create({ 70 | baseURL: "http://localhost:8081/api/v1/", 71 | }).get("object-detection/status/" + sessionId, { 72 | headers: { 73 | "accept": "application/json", 74 | }, 75 | onUploadProgress, 76 | }).then(response => { 77 | return response 78 | }).catch(function (error) { 79 | return error.response 80 | }); 81 | } 82 | 83 | const stopLoading = () => { 84 | clearInterval(processRef.current) 85 | setPredict(false) 86 | } 87 | 88 | useEffect(() => { 89 | if (predict === true){ 90 | processRef.current = setInterval(() => { 91 | getStatusSession(sessionIdRef.current, (event) => {}) 92 | .then((response) => { 93 | setUrlFileObjectResult(response.data.detection_draw_url) 94 | if (response.data.status.general_status === "SUCCESS" || response.data.status.general_status === "FAILED") 95 | stopLoading() 96 | }) 97 | }, 3000) 98 | } 99 | else stopLoading() 100 | }, [predict]); 101 | 102 | return ( 103 |
104 | 105 |
106 |
107 |
108 |
109 |

Select Image

110 | 117 | 118 | 119 |
120 |
121 | 122 |
123 |
124 |
125 |
126 |
127 |

Result Image

128 |
129 | 130 |
131 |
132 |
133 |
134 |
135 |
136 | ); 137 | } 138 | 139 | export default Dashboard; 140 | -------------------------------------------------------------------------------- /ml-client/src/views/Login.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import CustomInput from "../components/input/CustomInput"; 4 | import Button from "../components/buttom/Buttom"; 5 | import axios from 'axios'; 6 | import { Alert } from 'antd'; 7 | 8 | 9 | export const BASE_URL = process.env.REACT_APP_API_V1 10 | 11 | 12 | export default class Login extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | email: "", 17 | password: "", 18 | isLogin: false, 19 | loading: false, 20 | isRemember: true, 21 | showAlert: false, 22 | alertDescription: '', 23 | makeLogin: false 24 | } 25 | this.handleChange = this.handleChange.bind(this) 26 | this.handleLogin = this.handleLogin.bind(this) 27 | } 28 | 29 | handleChange = e => this.setState({ [e.currentTarget.id]: e.currentTarget.value }) 30 | handleLogin(){this.setState({makeLogin: true})} 31 | 32 | handleAll = async () =>{ 33 | if (this.state.isLogin === true) window.location.href = "/" 34 | else if (this.state.makeLogin === true) 35 | { 36 | var bodyFormData = new FormData(); 37 | bodyFormData.append('username', this.state.email); 38 | bodyFormData.append('password', this.state.password); 39 | let isRemember = this.state.isRemember; 40 | const result = await axios({ 41 | method: "post", 42 | url: BASE_URL + "/account/login/access-token", 43 | data: bodyFormData, 44 | headers: { "Content-Type": "multipart/form-data" }, 45 | }) 46 | .then(function (response) { 47 | if (response.status === 200){ 48 | const data = response.data 49 | localStorage.setItem('isRemember', isRemember) 50 | localStorage.setItem('tokenType', data.token_type) 51 | localStorage.setItem('accessToken', data.access_token) 52 | localStorage.setItem('freshToken', data.refresh_token) 53 | localStorage.setItem('expireAccessToken', data.expire_token) 54 | localStorage.setItem('expireFreshToken', data.expire_refresh_token) 55 | // if (!data.role_name.length) {localStorage.setItem('role', null)} 56 | // else localStorage.setItem('role', data.role_name.map(t=>t).reduce((prev, curr) => [prev, curr])) 57 | return {isLogin: true, alertDescription:'', showAlert: false} 58 | } 59 | }) 60 | .catch(function (response) { 61 | console.log(response) 62 | if (response.response === undefined) return {showAlert: true, alertDescription:'error from Server!', isLogin:false} 63 | else if (response.response.status === 400) return {showAlert: true, alertDescription:'wrong email or password!', isLogin:false} 64 | else if (response.response.status === 404) return {showAlert: true, alertDescription:'Not found User!', isLogin:false} 65 | else if (response.response.status === 422) return {showAlert: true, alertDescription:'missing email or password!', isLogin:false} 66 | else return {showAlert: true, alertDescription:'error from Server!', isLogin: false} 67 | }); 68 | this.setState({makeLogin: false, isLogin: result.isLogin, showAlert: result.showAlert, alertDescription: result.alertDescription}) 69 | } 70 | } 71 | componentDidUpdate(){this.handleAll()} 72 | componentDidMount(){ 73 | const script = document.createElement("script"); 74 | 75 | script.src = process.env.PUBLIC_URL + "/js/test.js"; 76 | script.async = true; 77 | 78 | document.body.appendChild(script); 79 | } 80 | 81 | render() { 82 | return ( 83 |
84 | {/*
*/} 85 | 86 |
87 | { this.state.showAlert ? : ""} 88 | logo 89 | 98 | 107 | 108 | 111 | 112 | 113 |
114 | 115 | ); 116 | } 117 | } -------------------------------------------------------------------------------- /ml-storages/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-storages/.gitkeep -------------------------------------------------------------------------------- /ml-storages/object_detection/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-storages/object_detection/.gitkeep -------------------------------------------------------------------------------- /ml-storages/upload/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/ml-storages/upload/.gitkeep -------------------------------------------------------------------------------- /public/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/public/architecture.png -------------------------------------------------------------------------------- /public/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnguyenngoc/ml-models-in-production/15ca1f767ab4920eebaf9d567ae797f987b4475e/public/test.png --------------------------------------------------------------------------------