From 90331a7036d51a59e2ccc3aa26da0394c0506dc8 Mon Sep 17 00:00:00 2001 From: artem Date: Sat, 7 Feb 2026 21:14:32 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B8=D0=BB=20=D0=B4=D0=BE?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BD=D1=86=D0=B0=20=D0=BF=D0=BE=D0=BB=D1=83?= =?UTF-8?q?=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=80=D0=B5=D0=B7=D1=83=D0=BB?= =?UTF-8?q?=D1=8C=D1=82=D0=B0=D1=82=D0=B0=20=D0=BF=D0=BE=20=D1=81=D1=81?= =?UTF-8?q?=D1=8B=D0=BB=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/admin/views/results.py | 1 + server/infra/web/recognizer.py | 9 ++-- ...2046-081eb0827a55-parent-commit-3baab00.py | 29 ++++++++++++ .../modules/recognizer/repository/__init__.py | 7 +-- .../modules/recognizer/repository/models.py | 2 + .../recognizer/repository/repository.py | 13 +++-- server/modules/recognizer/service.py | 47 +++++++++++++++---- server/templates/share.html | 20 ++++---- 8 files changed, 96 insertions(+), 32 deletions(-) create mode 100644 server/migration/versions/2026-02-07-2046-081eb0827a55-parent-commit-3baab00.py diff --git a/server/admin/views/results.py b/server/admin/views/results.py index 6388edb..c0f10eb 100644 --- a/server/admin/views/results.py +++ b/server/admin/views/results.py @@ -35,4 +35,5 @@ class ResultsView(ModelView): "result", "path", "beerd", + "probability" ] diff --git a/server/infra/web/recognizer.py b/server/infra/web/recognizer.py index 79d9951..d6bf633 100644 --- a/server/infra/web/recognizer.py +++ b/server/infra/web/recognizer.py @@ -39,7 +39,7 @@ class BreedsController(Controller): recognizer_service: RecognizerService = inject.instance(RecognizerService) body = await data.f.read() result = await recognizer_service.predict_dog_image(body, user_id, data.device_id) - return Response( + return Response( content=result.to_serializable(), media_type="application/json", cookies=new_cookies, @@ -51,18 +51,17 @@ class BreedsController(Controller): body = await data.read() result = await recognizer_service.predict_cat_image(body) return result.to_serializable() - + @get("/dogs/share/{result_id:str}") async def beerds_share(self, result_id: str) -> Template: recognizer_service: RecognizerService = inject.instance(RecognizerService) result = await recognizer_service.get_results(result_id) attach_service: AtachmentService = inject.instance(AtachmentService) - attachments = await attach_service.get_info_byid(session=None, attach_id= [result.attachment_id]) + attachments = await attach_service.get_info_byid(session=None, attach_id=[result.attachment_id]) return Template( template_name="share.html", context={ "result": result, - "attachments": attachments, + "attachment": attachments[0], }, ) - diff --git a/server/migration/versions/2026-02-07-2046-081eb0827a55-parent-commit-3baab00.py b/server/migration/versions/2026-02-07-2046-081eb0827a55-parent-commit-3baab00.py new file mode 100644 index 0000000..984fafb --- /dev/null +++ b/server/migration/versions/2026-02-07-2046-081eb0827a55-parent-commit-3baab00.py @@ -0,0 +1,29 @@ +"""3baab00 + +Revision ID: 081eb0827a55 +Revises: bebaddef3e8d +Create Date: 2026-02-07 20:46:53.971562 + +""" +from collections.abc import Sequence + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "081eb0827a55" +down_revision: str | None = "bebaddef3e8d" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("recognizer_results_beerds", sa.Column("probability", sa.Integer(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("recognizer_results_beerds", "probability") + # ### end Alembic commands ### diff --git a/server/modules/recognizer/repository/__init__.py b/server/modules/recognizer/repository/__init__.py index fa88a71..3e7ac06 100644 --- a/server/modules/recognizer/repository/__init__.py +++ b/server/modules/recognizer/repository/__init__.py @@ -1,6 +1,3 @@ -from server.modules.recognizer.repository.repository import ( - ARecognizerRepository, - RecognizerRepository, -) +from server.modules.recognizer.repository.repository import ARecognizerRepository, RecognizerRepository, ResultBeerds -__all__ = ("RecognizerRepository", "ARecognizerRepository") +__all__ = ("RecognizerRepository", "ARecognizerRepository", "ResultBeerds") diff --git a/server/modules/recognizer/repository/models.py b/server/modules/recognizer/repository/models.py index 1bd6da0..878b8a0 100644 --- a/server/modules/recognizer/repository/models.py +++ b/server/modules/recognizer/repository/models.py @@ -6,6 +6,7 @@ from sqlalchemy import ( Column, DateTime, ForeignKeyConstraint, + Integer, String, ) from sqlalchemy.orm import relationship @@ -62,3 +63,4 @@ class ResultBeerds(UJsonMixin): id: str = field(metadata={"sa": Column(String(), primary_key=True, nullable=False)}) recognizer_results_id: str = field(metadata={"sa": Column(String(), nullable=False)}) beerd_id: str = field(metadata={"sa": Column(String(), nullable=False)}) + probability: int = field(metadata={"sa": Column(Integer(), nullable=True)}) diff --git a/server/modules/recognizer/repository/repository.py b/server/modules/recognizer/repository/repository.py index 395c4ff..f0c20f0 100644 --- a/server/modules/recognizer/repository/repository.py +++ b/server/modules/recognizer/repository/repository.py @@ -17,6 +17,12 @@ class ResultWithBeerds: beerds: list[rm.ResultBeerds] +@dataclass +class ResultBeerds: + beerd_id: str + probability: float + + class ARecognizerRepository(metaclass=ABCMeta): @abstractmethod async def images_dogs(self) -> dict: @@ -39,7 +45,7 @@ class ARecognizerRepository(metaclass=ABCMeta): """Получить **все** результаты (кэшируется).""" @abstractmethod - async def create_result_with_beerds(self, result: rm.Results, beerd_ids: list[str]) -> None: + async def create_result_with_beerds(self, result: rm.Results, beerd_ids: list[ResultBeerds]) -> None: """ Создать новый результат и сразу же вставить связанные `ResultBeerds`. @@ -74,7 +80,7 @@ class RecognizerRepository(ARecognizerRepository): data_labels = f.read() return ujson.loads(data_labels) - async def create_result_with_beerds(self, result: rm.Results, beerd_ids: list[str]) -> None: + async def create_result_with_beerds(self, result: rm.Results, beerd_ids: list[ResultBeerds]) -> None: """ Создаёт запись в ``recognizer_results`` и сразу же добавляет одну запись в ``recognizer_results_beerds`` (если передан список @@ -101,7 +107,8 @@ class RecognizerRepository(ARecognizerRepository): { "id": str(uuid4()), "recognizer_results_id": result.id, - "beerd_id": beerd_id, + "beerd_id": beerd_id.beerd_id, + "probability": beerd_id.probability, } for beerd_id in beerd_ids ] diff --git a/server/modules/recognizer/service.py b/server/modules/recognizer/service.py index dc8668d..305d541 100644 --- a/server/modules/recognizer/service.py +++ b/server/modules/recognizer/service.py @@ -15,7 +15,7 @@ from torchvision import transforms # type: ignore from server.modules.attachments.domains.attachments import Attachment from server.modules.descriptions.repository import ACharactersRepository, Breed -from server.modules.recognizer.repository import ARecognizerRepository, models +from server.modules.recognizer.repository import ARecognizerRepository, ResultBeerds, models TorchModel = NewType("TorchModel", torch.nn.Module) @@ -48,11 +48,14 @@ class RecognizerResult(UJsonMixin): description: dict[str, list] | None uploaded_attach_id: str | None + @dataclass class SharingBeerds(UJsonMixin): alias: str name: str - images: list[ResultImages] + images: list[str] + probability: float + @dataclass class SharingResult(UJsonMixin): @@ -60,6 +63,12 @@ class SharingResult(UJsonMixin): attachment_id: str +@dataclass +class ResultNameBeerds: + name: str + probability: float + + class RecognizerService: __slots__ = ("_repository", "_attachment_service", "_repository_characters") @@ -79,7 +88,10 @@ class RecognizerService: async def images_dogs(self) -> dict: return await self._repository.images_dogs() - async def create_result(self, attachment: Attachment, user_id: str, device_id: str, beerd_names: list[str]): + async def create_result( + self, attachment: Attachment, user_id: str, device_id: str, beerd_results: list[ResultNameBeerds] + ): + beerd_names = {b.name: b for b in beerd_results} characters = await self._repository_characters.get_characters() await self._repository.create_result_with_beerds( models.Results( @@ -89,7 +101,11 @@ class RecognizerService: device_id=device_id, created_at=datetime.now(UTC), ), - [ch.id for ch in characters if ch.name in beerd_names], + [ + ResultBeerds(beerd_id=ch.id, probability=beerd_names[ch.name].probability) + for ch in characters + if ch.name in beerd_names + ], ) async def get_results(self, result_id: str) -> SharingResult: @@ -101,11 +117,15 @@ class RecognizerService: continue beers: list[SharingBeerds] = [] for beerd in r.beerds: - beers.append(SharingBeerds( - alias=beerds_store[beerd.beerd_id].alias, - name=beerds_store[beerd.beerd_id].name, - images = [ResultImages(name=beerds_store[beerd.beerd_id].name, url=[f"/static/assets/cat/{beerds_store[beerd.beerd_id].name}/{i}" for i in images_dogs[beerds_store[beerd.beerd_id].name.replace(" ", "_")]])] - )) + name = beerds_store[beerd.beerd_id].name.replace(" ", "_") + beers.append( + SharingBeerds( + alias=f"/dogs-characteristics/{beerds_store[beerd.beerd_id].alias}", + name=beerds_store[beerd.beerd_id].name, + probability=beerd.probability, + images=[f"/static/assets/dog/{name}/{i}" for i in images_dogs[name]], + ) + ) return SharingResult(beerds=beers, attachment_id=r.result.attachment_id) async def predict_dog_image(self, image: bytes, user_id: str, device_id: str | None) -> RecognizerResult: @@ -128,7 +148,14 @@ class RecognizerService: ) description.setdefault(name, []).append(f"/dogs-characteristics/{name.replace(' ', '_')}") results[probabilities] = name - asyncio.create_task(self.create_result(attachment, user_id, device_id, [results[key] for key in results])) + asyncio.create_task( + self.create_result( + attachment, + user_id, + device_id, + [ResultNameBeerds(name=results[key], probability=key*100) for key in results], + ) + ) return RecognizerResult( results=results, images=images, description=description, uploaded_attach_id=attachment.id ) diff --git a/server/templates/share.html b/server/templates/share.html index 90c938d..c7566ec 100644 --- a/server/templates/share.html +++ b/server/templates/share.html @@ -1,26 +1,28 @@ {% extends "base.html" %} {% block meta %} - + {% endblock %} -{% block title %}Определение породы кошки по фото бесплатно{% endblock %} +{% block title %}Результат определение породы по фото{% endblock %} {% block content %} -

Определить породу кошки по фото

-

Загрузите фото, чтобы опеределить породу собаки или щенка. Если порода смешанная (или порода определена неточно), после загрузки будет показана вероятность породы животного.

-

Определение породы происходит при помощи нейронной сети - точность опеределения составляет 60%, сеть обучена на 65 породах. Если на фото будет неизвестная порода будет предложено несколько похожих пород.

+

Мой результат определения породы по фото

{% endblock %} {% block form %}
+
+
Ваше изображение:
+ +

Результаты

- {% for result in result.beerds %} -
{{ result.name }} (вероятность: {{ result.percent }}%)
{{ attachments[result.attachment_id].path }}Описание и фото
+ {% for result in result.beerds %} +
{{ result.name }} (вероятность: {{ result.probability }}%)
Описание и фото