diff --git a/backend/src/router.py b/backend/src/router.py index 7e5ebc6a..87764c17 100644 --- a/backend/src/router.py +++ b/backend/src/router.py @@ -6,7 +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 basegun_ml.ocr import LowQuality, MissingText, is_alarm_weapon from fastapi import ( APIRouter, BackgroundTasks, @@ -37,6 +37,7 @@ def home(): def version(): return APP_VERSION + @router.post("/upload") async def imageupload( request: Request, @@ -78,7 +79,7 @@ async def imageupload( except Exception as e: extras_logging["bg_error_type"] = e.__class__.__name__ - logging.exception(e, extra=extras_logging) + 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 @@ -109,6 +110,7 @@ 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(...), @@ -138,6 +140,7 @@ async def imageblankgun( 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)): res = await request.json() diff --git a/backend/tests/blank_gun.jpg b/backend/tests/blank_gun.jpg new file mode 100755 index 00000000..936f38fc Binary files /dev/null and b/backend/tests/blank_gun.jpg differ diff --git a/backend/tests/low_quality.jpg b/backend/tests/low_quality.jpg new file mode 100644 index 00000000..6682df31 Binary files /dev/null and b/backend/tests/low_quality.jpg differ diff --git a/backend/tests/no_text.jpg b/backend/tests/no_text.jpg new file mode 100644 index 00000000..d11abd33 Binary files /dev/null and b/backend/tests/no_text.jpg differ diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index 36727d95..9aaa2518 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -187,3 +187,41 @@ def test_403(self): response = client.post("/api/expert-contact") response.data = response.json() assert response.status_code == 403 + + +class TestBlankGunUpload: + def test_blank_gun(self): + with open("./tests/blank_gun.jpg", "rb") as f: + response = client.post( + "/api/identification-blank-gun", + files={"image": f}, + ) + response.data = response.json() + assert response.status_code == 200 + response.data["alarm_model"] == "Alarm_model" + not response.data["missing_text"] + not response.data["low_quality"] + + def test_bad_quality(self): + with open("./tests/low_quality.jpg", "rb") as f: + response = client.post( + "/api/identification-blank-gun", + files={"image": f}, + ) + response.data = response.json() + assert response.status_code == 200 + response.data["alarm_model"] is None + not response.data["missing_text"] + response.data["missing_text"] + + def test_missing_text(self): + with open("./tests/no_text.jpg", "rb") as f: + response = client.post( + "/api/identification-blank-gun", + files={"image": f}, + ) + response.data = response.json() + assert response.status_code == 200 + response.data["alarm_model"] is None + response.data["missing_text"] + not response.data["low_quality"] diff --git a/frontend/cypress/e2e/blank-gun-detection.cy.js b/frontend/cypress/e2e/blank-gun-detection.cy.js new file mode 100644 index 00000000..859a55d1 --- /dev/null +++ b/frontend/cypress/e2e/blank-gun-detection.cy.js @@ -0,0 +1,53 @@ +describe("Blank Gun Detection", () => { + it("should identificate real blank gun", () => { + cy.Identification(); + + cy.getByDataTestid("select-file").as("fileInput"); + cy.intercept("POST", "/api/upload").as("upload"); + cy.get("@fileInput").selectFile("./cypress/images/blank-gun.jpg", { + force: true, + }); + cy.wait("@upload").then(({ response }) => { + expect(response.statusCode).to.eq(200); + }); + cy.getByDataTestid("next-step").click(); + cy.IdentificationPistoletSemiAuto(); + cy.wait(5000); + cy.url().should("contain", "/guide-identification/resultat-final"); + cy.getByDataTestid("arm-category").should("contain", "Catégorie C"); + }); + + it("should identificate firearm with missing text", () => { + cy.Identification(); + + cy.getByDataTestid("select-file").as("fileInput"); + cy.intercept("POST", "/api/upload").as("upload"); + cy.get("@fileInput").selectFile("./cypress/images/no-text.jpg", { + force: true, + }); + cy.wait("@upload").then(({ response }) => { + expect(response.statusCode).to.eq(200); + }); + cy.getByDataTestid("next-step").click(); + cy.IdentificationBlankGunMissingText(); + cy.url().should("contain", "/guide-identification/resultat-final"); + cy.getByDataTestid("arm-category").should("contain", "Catégorie B"); + }); + + it("should identificate firearm with low quality", () => { + cy.Identification(); + + cy.getByDataTestid("select-file").as("fileInput"); + cy.intercept("POST", "/api/upload").as("upload"); + cy.get("@fileInput").selectFile("./cypress/images/low-quality.jpg", { + force: true, + }); + cy.wait("@upload").then(({ response }) => { + expect(response.statusCode).to.eq(200); + }); + cy.getByDataTestid("next-step").click(); + cy.IdentificationBlankGunLowQuality(); + cy.url().should("contain", "/guide-identification/resultat-final"); + cy.getByDataTestid("arm-category").should("contain", "Catégorie B"); + }); +}); diff --git a/frontend/cypress/e2e/firearm-confidence.cy.js b/frontend/cypress/e2e/firearm-confidence.cy.js index f84e8222..f882016c 100644 --- a/frontend/cypress/e2e/firearm-confidence.cy.js +++ b/frontend/cypress/e2e/firearm-confidence.cy.js @@ -12,6 +12,7 @@ describe("Firearm Confidence", () => { }); cy.getByDataTestid("next-step").click(); cy.IdentificationPistoletSemiAuto(); + cy.wait(5000); cy.url().should("contain", "/guide-identification/resultat-final"); cy.getByDataTestid("arm-category").should("contain", "Catégorie B"); }); diff --git a/frontend/cypress/e2e/firearm-identification.cy.js b/frontend/cypress/e2e/firearm-identification.cy.js index 1b2ecc93..838ca590 100644 --- a/frontend/cypress/e2e/firearm-identification.cy.js +++ b/frontend/cypress/e2e/firearm-identification.cy.js @@ -11,6 +11,7 @@ describe("Firearm Identification", () => { }); cy.getByDataTestid("next-step").click(); cy.IdentificationPistoletSemiAuto(); + cy.wait(5000); cy.url().should("contain", "/guide-identification/resultat-final"); cy.getByDataTestid("arm-category").should("contain", "Catégorie B"); cy.getByDataTestid("return-to-home-end").click(); diff --git a/frontend/cypress/e2e/firearm-securing.cy.js b/frontend/cypress/e2e/firearm-securing.cy.js index bf3888ba..fb5538af 100644 --- a/frontend/cypress/e2e/firearm-securing.cy.js +++ b/frontend/cypress/e2e/firearm-securing.cy.js @@ -26,6 +26,7 @@ describe("Securing Firearm and Identification", () => { cy.contains("p", "Basegun a identifié votre arme"); cy.getByDataTestid("next-step").click(); cy.IdentificationPistoletSemiAuto(); + cy.wait(5000); cy.url().should("contain", "/guide-identification/resultat-final"); cy.getByDataTestid("arm-category").should("contain", "Catégorie B"); cy.getByDataTestid("return-to-home-end").click(); diff --git a/frontend/cypress/images/blank-gun.jpg b/frontend/cypress/images/blank-gun.jpg new file mode 100755 index 00000000..936f38fc Binary files /dev/null and b/frontend/cypress/images/blank-gun.jpg differ diff --git a/frontend/cypress/images/low-quality.jpg b/frontend/cypress/images/low-quality.jpg new file mode 100644 index 00000000..6682df31 Binary files /dev/null and b/frontend/cypress/images/low-quality.jpg differ diff --git a/frontend/cypress/images/no-text.jpg b/frontend/cypress/images/no-text.jpg new file mode 100644 index 00000000..d11abd33 Binary files /dev/null and b/frontend/cypress/images/no-text.jpg differ diff --git a/frontend/cypress/support/commands.js b/frontend/cypress/support/commands.js index 5cb05b48..d3541a89 100644 --- a/frontend/cypress/support/commands.js +++ b/frontend/cypress/support/commands.js @@ -109,11 +109,11 @@ Cypress.Commands.add("IdentificationPistoletSemiAuto", () => { cy.contains("Cartouches").first().click(); cy.getByDataTestid("next-step").should("not.have.attr", "disabled"); cy.getByDataTestid("next-step").click(); - cy.url().should("contain", "/guide-identification/armes-alarme"); - cy.getByDataTestid("instruction-armeAlarme").should("contain", "Votre arme"); - cy.getByDataTestid("next-step").click(); - cy.getByDataTestid("aucune-correspondance").click(); - cy.getByDataTestid("next-step").click(); + cy.url().should("contain", "/guide-identification/qualite-image"); + cy.getByDataTestid("title-page").should( + "contain", + "Identification d'une arme d'alarme", + ); }); Cypress.Commands.add("IdentificationRevolver", () => { @@ -133,11 +133,9 @@ Cypress.Commands.add("IdentificationRevolver", () => { cy.contains("Balles").first().click(); cy.getByDataTestid("next-step").should("not.have.attr", "disabled"); cy.getByDataTestid("next-step").click(); - cy.url().should("contain", "/guide-identification/armes-alarme"); - cy.getByDataTestid("instruction-armeAlarme").should("contain", "Votre arme"); - cy.getByDataTestid("next-step").click(); - cy.getByDataTestid("aucune-correspondance").click(); - cy.getByDataTestid("next-step").click(); + cy.url().should("contain", "/guide-identification/qualite-image"); + cy.getByDataTestid("title-page").should("contain", "Marquages non détéctés"); + cy.get('.fr-col-12 > [data-testid="next-step"]').click(); }); Cypress.Commands.add("arrierePlatRevolver", () => { @@ -198,3 +196,38 @@ Cypress.Commands.add("pasDeGuide", () => { cy.getByDataTestid("go-to-identification").click(); cy.url().should("contain", "/guide-identification/resultat-typologie"); }); + +Cypress.Commands.add("IdentificationBlankGunMissingText", () => { + cy.url().should( + "contain", + "guide-identification/informations-complementaires", + ); + cy.getByDataTestid("next-step").click(); + cy.url().should("contain", "/guide-identification/munition-type"); + cy.getByDataTestid("next-step").should("have.attr", "disabled"); + cy.contains("Cartouches").first().click(); + cy.getByDataTestid("next-step").should("not.have.attr", "disabled"); + cy.getByDataTestid("next-step").click(); + cy.url().should("contain", "/guide-identification/qualite-image"); + cy.getByDataTestid("title-page").should("contain", "Marquages non détéctés"); + cy.get('.fr-col-12 > [data-testid="next-step"]').click(); +}); + +Cypress.Commands.add("IdentificationBlankGunLowQuality", () => { + cy.url().should( + "contain", + "guide-identification/informations-complementaires", + ); + cy.getByDataTestid("next-step").click(); + cy.url().should("contain", "/guide-identification/munition-type"); + cy.getByDataTestid("next-step").should("have.attr", "disabled"); + cy.contains("Cartouches").first().click(); + cy.getByDataTestid("next-step").should("not.have.attr", "disabled"); + cy.getByDataTestid("next-step").click(); + cy.url().should("contain", "/guide-identification/qualite-image"); + cy.getByDataTestid("title-page").should( + "contain", + "Qualité d'image insuffisante", + ); + cy.get('.fr-col-12 > [data-testid="next-step"]').click(); +}); diff --git a/frontend/src/auto-imports.d.ts b/frontend/src/auto-imports.d.ts index 79f81b43..3ac3a82f 100644 --- a/frontend/src/auto-imports.d.ts +++ b/frontend/src/auto-imports.d.ts @@ -425,115 +425,3 @@ declare module "vue" { >; } } -declare module "@vue/runtime-core" { - interface GlobalComponents {} - interface ComponentCustomProperties { - readonly EffectScope: UnwrapRef<(typeof import("vue"))["EffectScope"]>; - readonly OhVueIcon: UnwrapRef<(typeof import("oh-vue-icons"))["OhVueIcon"]>; - readonly addIcons: UnwrapRef<(typeof import("oh-vue-icons"))["addIcons"]>; - readonly computed: UnwrapRef<(typeof import("vue"))["computed"]>; - readonly createApp: UnwrapRef<(typeof import("vue"))["createApp"]>; - readonly customRef: UnwrapRef<(typeof import("vue"))["customRef"]>; - readonly defineAsyncComponent: UnwrapRef< - (typeof import("vue"))["defineAsyncComponent"] - >; - readonly defineComponent: UnwrapRef< - (typeof import("vue"))["defineComponent"] - >; - readonly effectScope: UnwrapRef<(typeof import("vue"))["effectScope"]>; - readonly getCurrentInstance: UnwrapRef< - (typeof import("vue"))["getCurrentInstance"] - >; - readonly getCurrentScope: UnwrapRef< - (typeof import("vue"))["getCurrentScope"] - >; - readonly h: UnwrapRef<(typeof import("vue"))["h"]>; - readonly inject: UnwrapRef<(typeof import("vue"))["inject"]>; - readonly isProxy: UnwrapRef<(typeof import("vue"))["isProxy"]>; - readonly isReactive: UnwrapRef<(typeof import("vue"))["isReactive"]>; - readonly isReadonly: UnwrapRef<(typeof import("vue"))["isReadonly"]>; - readonly isRef: UnwrapRef<(typeof import("vue"))["isRef"]>; - readonly markRaw: UnwrapRef<(typeof import("vue"))["markRaw"]>; - readonly nextTick: UnwrapRef<(typeof import("vue"))["nextTick"]>; - readonly onActivated: UnwrapRef<(typeof import("vue"))["onActivated"]>; - readonly onBeforeMount: UnwrapRef<(typeof import("vue"))["onBeforeMount"]>; - readonly onBeforeRouteLeave: UnwrapRef< - (typeof import("vue-router"))["onBeforeRouteLeave"] - >; - readonly onBeforeRouteUpdate: UnwrapRef< - (typeof import("vue-router"))["onBeforeRouteUpdate"] - >; - readonly onBeforeUnmount: UnwrapRef< - (typeof import("vue"))["onBeforeUnmount"] - >; - readonly onBeforeUpdate: UnwrapRef< - (typeof import("vue"))["onBeforeUpdate"] - >; - readonly onDeactivated: UnwrapRef<(typeof import("vue"))["onDeactivated"]>; - readonly onErrorCaptured: UnwrapRef< - (typeof import("vue"))["onErrorCaptured"] - >; - readonly onMounted: UnwrapRef<(typeof import("vue"))["onMounted"]>; - readonly onRenderTracked: UnwrapRef< - (typeof import("vue"))["onRenderTracked"] - >; - readonly onRenderTriggered: UnwrapRef< - (typeof import("vue"))["onRenderTriggered"] - >; - readonly onScopeDispose: UnwrapRef< - (typeof import("vue"))["onScopeDispose"] - >; - readonly onServerPrefetch: UnwrapRef< - (typeof import("vue"))["onServerPrefetch"] - >; - readonly onUnmounted: UnwrapRef<(typeof import("vue"))["onUnmounted"]>; - readonly onUpdated: UnwrapRef<(typeof import("vue"))["onUpdated"]>; - readonly provide: UnwrapRef<(typeof import("vue"))["provide"]>; - readonly reactive: UnwrapRef<(typeof import("vue"))["reactive"]>; - readonly readonly: UnwrapRef<(typeof import("vue"))["readonly"]>; - readonly ref: UnwrapRef<(typeof import("vue"))["ref"]>; - readonly resolveComponent: UnwrapRef< - (typeof import("vue"))["resolveComponent"] - >; - readonly shallowReactive: UnwrapRef< - (typeof import("vue"))["shallowReactive"] - >; - readonly shallowReadonly: UnwrapRef< - (typeof import("vue"))["shallowReadonly"] - >; - readonly shallowRef: UnwrapRef<(typeof import("vue"))["shallowRef"]>; - readonly toRaw: UnwrapRef<(typeof import("vue"))["toRaw"]>; - readonly toRef: UnwrapRef<(typeof import("vue"))["toRef"]>; - readonly toRefs: UnwrapRef<(typeof import("vue"))["toRefs"]>; - readonly toValue: UnwrapRef<(typeof import("vue"))["toValue"]>; - readonly triggerRef: UnwrapRef<(typeof import("vue"))["triggerRef"]>; - readonly unref: UnwrapRef<(typeof import("vue"))["unref"]>; - readonly useAttrs: UnwrapRef<(typeof import("vue"))["useAttrs"]>; - readonly useCssModule: UnwrapRef<(typeof import("vue"))["useCssModule"]>; - readonly useCssVars: UnwrapRef<(typeof import("vue"))["useCssVars"]>; - readonly useLink: UnwrapRef<(typeof import("vue-router"))["useLink"]>; - readonly useRoute: UnwrapRef<(typeof import("vue-router"))["useRoute"]>; - readonly useRouter: UnwrapRef<(typeof import("vue-router"))["useRouter"]>; - readonly useScheme: UnwrapRef< - (typeof import("@gouvminint/vue-dsfr"))["useScheme"] - >; - readonly useSlots: UnwrapRef<(typeof import("vue"))["useSlots"]>; - readonly useSnackbarStore: UnwrapRef< - (typeof import("./stores/snackbar"))["useSnackbarStore"] - >; - readonly useStore: UnwrapRef< - (typeof import("./stores/result"))["useStore"] - >; - readonly useTabs: UnwrapRef< - (typeof import("@gouvminint/vue-dsfr"))["useTabs"] - >; - readonly watch: UnwrapRef<(typeof import("vue"))["watch"]>; - readonly watchEffect: UnwrapRef<(typeof import("vue"))["watchEffect"]>; - readonly watchPostEffect: UnwrapRef< - (typeof import("vue"))["watchPostEffect"] - >; - readonly watchSyncEffect: UnwrapRef< - (typeof import("vue"))["watchSyncEffect"] - >; - } -} diff --git a/frontend/src/views/GuideIdentificationFirearm/GuideIdentificationFirearm.vue b/frontend/src/views/GuideIdentificationFirearm/GuideIdentificationFirearm.vue index 6fa6c421..aee1610b 100644 --- a/frontend/src/views/GuideIdentificationFirearm/GuideIdentificationFirearm.vue +++ b/frontend/src/views/GuideIdentificationFirearm/GuideIdentificationFirearm.vue @@ -292,6 +292,7 @@ watch(alarmModel, (newVal) => { {