From 8d337ec9a1cff58da9365205ac8fd845e3ca9d00 Mon Sep 17 00:00:00 2001 From: David MENDY Date: Thu, 29 Aug 2024 06:14:31 +0200 Subject: [PATCH] feat: :sparkles: implementation ocr module for blank guns --- backend/Dockerfile | 1 + backend/requirements.txt | 2 +- backend/src/router.py | 36 +++- frontend/src/api/api-client.ts | 11 ++ frontend/src/api/api-routes.ts | 2 + frontend/src/assets/missing_marking.png | Bin 0 -> 5512 bytes frontend/src/components/ResultPage.vue | 3 +- frontend/src/router/index.ts | 11 +- frontend/src/stores/result.ts | 12 ++ frontend/src/utils/firearms-utils/index.ts | 8 +- .../GuideIdentificationFirearm.vue | 155 ++++++++++++------ .../IdentificationBlankGun.vue | 144 ---------------- .../IdentificationQualityImage.vue | 147 +++++++++++++++++ frontend/src/views/InstructionsPage.vue | 14 +- 14 files changed, 339 insertions(+), 207 deletions(-) create mode 100644 frontend/src/assets/missing_marking.png delete mode 100644 frontend/src/views/GuideIdentificationFirearm/IdentificationBlankGun.vue create mode 100644 frontend/src/views/GuideIdentificationFirearm/IdentificationQualityImage.vue diff --git a/backend/Dockerfile b/backend/Dockerfile index f03566ccc..f536f9f60 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -7,6 +7,7 @@ RUN apt update && apt install -y \ libglib2.0-0 \ curl \ gcc \ + patch \ && rm -rf /var/lib/apt/lists/* # install python libraries diff --git a/backend/requirements.txt b/backend/requirements.txt index 35543273a..1e8bf9628 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -12,7 +12,7 @@ autodynatrace==2.0.0 PyJWT==2.8.0 cryptography==42.0.8 # ML -basegun-ml==1.0.1 +basegun-ml==2.0.1 # Dev pytest==7.4.3 coverage==7.3.2 diff --git a/backend/src/router.py b/backend/src/router.py index 7325b1130..558a306c5 100644 --- a/backend/src/router.py +++ b/backend/src/router.py @@ -6,6 +6,7 @@ from basegun_ml.classification import get_typology from basegun_ml.measure import get_lengths +from basegun_ml.ocr import is_alarm_weapon, LowQuality, MissingText from fastapi import ( APIRouter, BackgroundTasks, @@ -37,7 +38,6 @@ def home(): def version(): return APP_VERSION - @router.post("/upload") async def imageupload( request: Request, @@ -74,8 +74,12 @@ async def imageupload( gun_length, gun_barrel_length, conf_card = None, None, None if label in TYPOLOGIES_MEASURED and confidence_level != "low": - gun_length, gun_barrel_length, conf_card = get_lengths(img_bytes) + try: + gun_length, gun_barrel_length, conf_card = get_lengths(img_bytes) + except Exception as e: + extras_logging["bg_error_type"] = e.__class__.__name__ + logging.exception(e, extra=extras_logging) # Temporary fix while ML package send 0 instead of None # https://github.com/dnum-mi/basegun-ml/issues/14 gun_length = None if gun_length == 0 else gun_length @@ -106,6 +110,34 @@ async def imageupload( logging.exception(e, extra=extras_logging) raise HTTPException(status_code=500, detail=str(e)) +@router.post("/identification-blank-gun") +async def imageblankgun( + image: UploadFile = File(...), +): + try: + img_bytes = image.file.read() + # Process image with ML models + alarm_model = is_alarm_weapon(img_bytes) + return { + "alarm_model": alarm_model, + "missing_text": False, + "low_quality": False, + } + + except LowQuality: + return { + "alarm_model": None, + "low_quality": True, + "missing_text": False, + } + except MissingText: + return { + "alarm_model": None, + "low_quality": False, + "missing_text": True, + } + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) @router.post("/identification-feedback") async def log_feedback(request: Request, user_id: Union[str, None] = Cookie(None)): diff --git a/frontend/src/api/api-client.ts b/frontend/src/api/api-client.ts index f6471ea40..d6ca54822 100644 --- a/frontend/src/api/api-client.ts +++ b/frontend/src/api/api-client.ts @@ -6,6 +6,7 @@ import { IDENTIFICATION_FEEDBACK_ROUTE, TUTORIAL_FEEDBACK_ROUTE, UPLOAD_PHOTO_FOR_DETECTION_ROUTE, + UPLOAD_PHOTO_FOR_BLANK_GUN_DETECTION_ROUTE, } from "./api-routes"; export const uploadPhotoForDetection = async (file: File) => { @@ -50,3 +51,13 @@ export const sendExpertiseForm = async ( console.error("Erreur lors de l'envoi du formulaire :", error); } }; + +export const uploadPhotoForBlankGunDetection = async (file: File) => { + const fd = new FormData(); + fd.append("image", file, file.name); + const { data } = await axios.post( + UPLOAD_PHOTO_FOR_BLANK_GUN_DETECTION_ROUTE, + fd, + ); + return data; +}; diff --git a/frontend/src/api/api-routes.ts b/frontend/src/api/api-routes.ts index f134236c8..fc71730e4 100644 --- a/frontend/src/api/api-routes.ts +++ b/frontend/src/api/api-routes.ts @@ -3,3 +3,5 @@ export const TUTORIAL_FEEDBACK_ROUTE = "/tutorial-feedback"; export const IDENTIFICATION_FEEDBACK_ROUTE = "/identification-feedback"; export const IDENTIFICATION_DUMMY_ROUTE = "/identification-dummy"; export const ASK_FOR_OPINION_ROUTE = "/expert-contact"; +export const UPLOAD_PHOTO_FOR_BLANK_GUN_DETECTION_ROUTE = + "/identification-blank-gun"; diff --git a/frontend/src/assets/missing_marking.png b/frontend/src/assets/missing_marking.png new file mode 100644 index 0000000000000000000000000000000000000000..df6c4ab67f4284fa3e885423681ce43bd46434d4 GIT binary patch literal 5512 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 KMNUMnLSTZ*S+36j literal 0 HcmV?d00001 diff --git a/frontend/src/components/ResultPage.vue b/frontend/src/components/ResultPage.vue index 7e29ef306..104cb774f 100644 --- a/frontend/src/components/ResultPage.vue +++ b/frontend/src/components/ResultPage.vue @@ -5,6 +5,7 @@ import SnackbarAlert from "@/components/SnackbarAlert.vue"; import { TYPOLOGIES, MEASURED_GUNS_TYPOLOGIES, + isAlarmGun, } from "@/utils/firearms-utils/index"; import { isUserUsingCrosscall } from "@/utils/isUserUsingCrosscall"; import { useSnackbarStore } from "@/stores/snackbar"; @@ -129,7 +130,7 @@ function sendFeedback(isCorrect: boolean) {

Arme factice de type {{ label }}

-

+

Arme d'alarme de type {{ label }}

diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 2777fe086..5f000cc92 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -9,6 +9,7 @@ import { clearLocalStorage } from "@/utils/storage-utils.js"; import { mgr } from "@/utils/authentication"; import MissingCardPage from "@/views/MissingCardPage.vue"; +import IdentificationQualityImage from "@/views/GuideIdentificationFirearm/IdentificationQualityImage.vue"; import ExpertiseForm from "@/views/GuideAskingExpertise/ExpertiseForm.vue"; const HomePage = () => import("@/views/HomePage.vue"); @@ -41,8 +42,6 @@ const IdentificationFurtherInformations = () => ); const IdentificationSelectAmmo = () => import("@/views/GuideIdentificationFirearm/IdentificationSelectAmmo.vue"); -const IdentificationBlankGun = () => - import("@/views/GuideIdentificationFirearm/IdentificationBlankGun.vue"); const ExpertSituation = () => import("@/views/GuideContactExpert/ExpertSituation.vue"); @@ -129,9 +128,9 @@ const routes: RouteRecordRaw[] = [ component: IdentificationSelectAmmo, }, { - path: "armes-alarme", - name: "IdentificationBlankGun", - component: IdentificationBlankGun, + path: "qualite-image", + name: "IdentificationQualityImage", + component: IdentificationQualityImage, }, { path: "resultat-final", @@ -247,7 +246,7 @@ const routes: RouteRecordRaw[] = [ } } catch (error) { console.error("Erreur signin callback:", error); - next({ name: "AuthRedirect" }); + next({ name: "ErrorPage" }); } }, }, diff --git a/frontend/src/stores/result.ts b/frontend/src/stores/result.ts index d0b794ed3..7d18c2b3d 100644 --- a/frontend/src/stores/result.ts +++ b/frontend/src/stores/result.ts @@ -10,11 +10,15 @@ export const useStore = defineStore("result", { const gunBarrelLength = ref(null); const img = ref(null); const imgUrl = ref(null); + const unresizeImage = ref(null); const securingTutorial = ref(false); const selectedOptions = ref([]); const selectedAmmo = ref(undefined); const selectedAlarmGun = ref(undefined); + const alarmModel = ref(null); + const isAlarmGunMissingText = ref(null); + const isAlarmGunLowQuality = ref(null); const isDummy = computed(() => !!(selectedAmmo.value === "billes")); const isModalTransparentAmmoOpened = ref(null); @@ -26,11 +30,15 @@ export const useStore = defineStore("result", { gunBarrelLength.value = null; img.value = null; imgUrl.value = null; + unresizeImage.value = null; securingTutorial.value = false; selectedOptions.value = []; selectedAmmo.value = undefined; selectedAlarmGun.value = undefined; + alarmModel.value = null; + isAlarmGunMissingText.value = null; + isAlarmGunLowQuality.value = null; isModalTransparentAmmoOpened.value = null; } @@ -42,10 +50,14 @@ export const useStore = defineStore("result", { gunBarrelLength, img, imgUrl, + unresizeImage, securingTutorial, selectedOptions, selectedAmmo, selectedAlarmGun, + alarmModel, + isAlarmGunMissingText, + isAlarmGunLowQuality, isDummy, isModalTransparentAmmoOpened, $reset, diff --git a/frontend/src/utils/firearms-utils/index.ts b/frontend/src/utils/firearms-utils/index.ts index f4f34dd0d..7121afb50 100644 --- a/frontend/src/utils/firearms-utils/index.ts +++ b/frontend/src/utils/firearms-utils/index.ts @@ -34,7 +34,7 @@ export const TYPOLOGIES = { const IdentificationTypologyResult = "IdentificationTypologyResult"; const IdentificationFurtherInformations = "IdentificationFurtherInformations"; const IdentificationSelectAmmo = "IdentificationSelectAmmo"; -const IdentificationBlankGun = "IdentificationBlankGun"; +const IdentificationQualityImage = "IdentificationQualityImage"; const IdentificationFinalResult = "IdentificationFinalResult"; export const identificationGuideSteps = [ @@ -48,7 +48,7 @@ export const identificationGuideStepsWithArmeAlarme = [ IdentificationTypologyResult, IdentificationFurtherInformations, IdentificationSelectAmmo, - IdentificationBlankGun, + IdentificationQualityImage, IdentificationFinalResult, ] as const; @@ -69,7 +69,7 @@ export const identificationRoutePathsWithArmeAlarme = [ "resultat-typologie", "informations-complementaires", "munition-type", - "armes-alarme", + "qualite-image", "resultat-final", ] as const; @@ -81,7 +81,7 @@ export function isAlarmGun() { ) { return false; } - return store.selectedAlarmGun ? true : undefined; + return store.alarmModel === "Alarm_model" ? true : undefined; } export const MEASURED_GUNS_TYPOLOGIES = [ diff --git a/frontend/src/views/GuideIdentificationFirearm/GuideIdentificationFirearm.vue b/frontend/src/views/GuideIdentificationFirearm/GuideIdentificationFirearm.vue index 5df828031..6fa6c421c 100644 --- a/frontend/src/views/GuideIdentificationFirearm/GuideIdentificationFirearm.vue +++ b/frontend/src/views/GuideIdentificationFirearm/GuideIdentificationFirearm.vue @@ -9,6 +9,7 @@ import { isAlarmGun, } from "@/utils/firearms-utils/index"; import { useStore } from "@/stores/result"; +import { uploadPhotoForBlankGunDetection } from "@/api/api-client"; const store = useStore(); const router = useRouter(); @@ -16,6 +17,9 @@ const route = useRoute(); const confidenceLevel = computed(() => store.confidenceLevel); const typology = computed(() => store.typology); +const alarmModel = computed(() => store.alarmModel); +const isAlarmGunLowQuality = computed(() => store.isAlarmGunLowQuality); +const isAlarmGunMissingText = computed(() => store.isAlarmGunMissingText); const currentStep = ref(1); const isLowConfidence = confidenceLevel.value === "low"; @@ -56,10 +60,6 @@ const goToNewRouteWithArmeAlarme = () => name: identificationGuideStepsWithArmeAlarme[currentStep.value - 1], }); -const isArmeAlarme = computed( - () => route.path === "/guide-identification/armes-alarme", -); - const goOnAndFollow = computed(() => currentStep.value === 1 ? "Continuer" @@ -74,26 +74,7 @@ const arrowOrCircleIcon = () => const calculateRoute = (store) => { return store.selectedAmmo === "billes" ? { name: "IdentificationFinalResult" } - : { name: "IdentificationBlankGun" }; -}; - -// showDiv is used to create a mini steper for alarm guns. Need to be reworked. -const backStepButtonAction = () => { - if (showDiv.value === false) { - currentStep.value--; - goToNewRouteWithArmeAlarme(); - } else { - showDiv.value = false; - } -}; - -const nextStepButtonAction = () => { - if (showDiv.value === false) { - showDiv.value = true; - } else { - currentStep.value++; - goToNewRouteWithArmeAlarme(); - } + : { name: "IdentificationQualityImage" }; }; function handlePreviousButtonClick() { @@ -105,7 +86,64 @@ function handlePreviousButtonClick() { } } -const showDiv = ref(false); +async function onFileSelected(event) { + const uploadedFile = event.target.files[0]; + + if (!uploadedFile) { + console.error("Aucun fichier sélectionné."); + return; + } + + console.log("Fichier sélectionné :", uploadedFile); + + try { + const result = await uploadPhotoForBlankGunDetection(uploadedFile); + console.log("Résultat de l'envoi :", result); + + if (result) { + const { alarm_model, missing_text, low_quality } = result; + if (alarm_model === "Alarm_model") { + store.$patch({ alarmModel: alarm_model }); + console.log("Modèle d'arme d'alarme détecté :", alarm_model); + + currentStep.value++; + router.push({ name: "IdentificationFinalResult" }); + } else if (low_quality === true) { + store.$patch({ isAlarmGunLowQuality: true }); + } else if (missing_text === true) { + store.$patch({ isAlarmGunMissingText: true }); + } + } else { + console.error("La réponse est indéfinie ou mal formée :", result); + } + } catch (error) { + console.error("Erreur lors de l'envoi de l'image :", error); + } +} + +const handledImageTypes = "image/jpeg, image/png, image/jpg"; + +const footerValue = computed(() => { + if (isAlarmGunLowQuality) { + return { + next: "Pas de marquages, passer à l'étape suivante", + picture: "Reprendre la photo", + }; + } else { + return { + next: "Non, passer à l'étape suivante", + picture: "Oui, en prendre une photo rapprochée", + }; + } +}); + +watch(alarmModel, (newVal) => { + if (newVal === "Alarm_model" || newVal === "Not_alarm") { + setTimeout(() => { + currentStep.value++; + }, 5000); + } +}); diff --git a/frontend/src/views/GuideIdentificationFirearm/IdentificationQualityImage.vue b/frontend/src/views/GuideIdentificationFirearm/IdentificationQualityImage.vue new file mode 100644 index 000000000..a4767e4f3 --- /dev/null +++ b/frontend/src/views/GuideIdentificationFirearm/IdentificationQualityImage.vue @@ -0,0 +1,147 @@ + + + diff --git a/frontend/src/views/InstructionsPage.vue b/frontend/src/views/InstructionsPage.vue index b1e2d4c06..f6c477a1c 100644 --- a/frontend/src/views/InstructionsPage.vue +++ b/frontend/src/views/InstructionsPage.vue @@ -92,12 +92,24 @@ async function srcToFile(src: string, fileName: string, mimeType: string) { return new File([buf], fileName, { type: mimeType }); } -function onFileSelected( +function fileToBase64(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => resolve(reader.result as string); + reader.onerror = (error) => reject(error); + }); +} + +async function onFileSelected( event: InputEvent & { target: InputEvent["target"] & { files: File[] } }, ) { loading.value = true; const uploadedFile = event.target?.files[0]; + const unresizeImage = await fileToBase64(uploadedFile); + store.$patch({ unresizeImage: unresizeImage }); + resizeImage(uploadedFile).then((resizedBase64Image) => uploadImage(resizedBase64Image, uploadedFile.name), );