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 000000000..df6c4ab67
Binary files /dev/null and b/frontend/src/assets/missing_marking.png differ
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);
+ }
+});
@@ -120,7 +158,7 @@ const showDiv = ref(false);
:steps="steps"
:current-step="currentStep"
/>
-
+
-
-
-
+
+
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 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+
+
+
+
+
Voyez-vous des marquages sur l'arme ?
+
+
+
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),
);