Compare commits
2 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
90331a7036 | |
|
|
3baab00f80 |
|
|
@ -1 +1 @@
|
||||||
cpython-3.13.5-linux-x86_64-gnu
|
cpython-3.13.12-linux-x86_64-gnu
|
||||||
2
Makefile
2
Makefile
|
|
@ -14,7 +14,7 @@ format:
|
||||||
uv run ruff format
|
uv run ruff format
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
uv run mypy ./ --explicit-package-bases;
|
uv run mypy ./server --explicit-package-bases;
|
||||||
ruff check --fix
|
ruff check --fix
|
||||||
|
|
||||||
pipinstall:
|
pipinstall:
|
||||||
|
|
|
||||||
|
|
@ -35,4 +35,5 @@ class ResultsView(ModelView):
|
||||||
"result",
|
"result",
|
||||||
"path",
|
"path",
|
||||||
"beerd",
|
"beerd",
|
||||||
|
"probability"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@ from dataclasses import dataclass
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
import inject
|
import inject
|
||||||
from litestar import Controller, Request, Response, post
|
from litestar import Controller, Request, Response, get, post
|
||||||
from litestar.datastructures import UploadFile
|
from litestar.datastructures import UploadFile
|
||||||
from litestar.enums import RequestEncodingType
|
from litestar.enums import RequestEncodingType
|
||||||
from litestar.params import Body
|
from litestar.params import Body
|
||||||
|
from litestar.response import Template
|
||||||
|
|
||||||
|
from server.modules.attachments import AtachmentService
|
||||||
from server.modules.recognizer import RecognizerService
|
from server.modules.recognizer import RecognizerService
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -49,3 +51,17 @@ class BreedsController(Controller):
|
||||||
body = await data.read()
|
body = await data.read()
|
||||||
result = await recognizer_service.predict_cat_image(body)
|
result = await recognizer_service.predict_cat_image(body)
|
||||||
return result.to_serializable()
|
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])
|
||||||
|
return Template(
|
||||||
|
template_name="share.html",
|
||||||
|
context={
|
||||||
|
"result": result,
|
||||||
|
"attachment": attachments[0],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -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 ###
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
from server.modules.descriptions.repository.repository import (
|
from server.modules.descriptions.repository.repository import (
|
||||||
ACharactersRepository,
|
ACharactersRepository,
|
||||||
|
Breed,
|
||||||
CharactersRepository,
|
CharactersRepository,
|
||||||
PGCharactersRepository,
|
PGCharactersRepository,
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = ("CharactersRepository", "ACharactersRepository", "PGCharactersRepository")
|
__all__ = ("CharactersRepository", "ACharactersRepository", "PGCharactersRepository", "Breed")
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from server.config import get_app_config
|
from server.config import get_app_config
|
||||||
from server.infra.db.db_mapper import mapper_registry
|
from server.infra.db.db_mapper import mapper_registry
|
||||||
from server.modules.rate import domain
|
|
||||||
from server.modules.attachments.repository.attachments import Attachment
|
from server.modules.attachments.repository.attachments import Attachment
|
||||||
from server.modules.descriptions.repository.models import Beerds
|
from server.modules.descriptions.repository.models import Beerds
|
||||||
|
from server.modules.rate import domain
|
||||||
|
|
||||||
|
|
||||||
@mapper_registry.mapped
|
@mapper_registry.mapped
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
from server.modules.recognizer.repository.repository import (
|
from server.modules.recognizer.repository.repository import ARecognizerRepository, RecognizerRepository, ResultBeerds
|
||||||
ARecognizerRepository,
|
|
||||||
RecognizerRepository,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = ("RecognizerRepository", "ARecognizerRepository")
|
__all__ = ("RecognizerRepository", "ARecognizerRepository", "ResultBeerds")
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from sqlalchemy import (
|
||||||
Column,
|
Column,
|
||||||
DateTime,
|
DateTime,
|
||||||
ForeignKeyConstraint,
|
ForeignKeyConstraint,
|
||||||
|
Integer,
|
||||||
String,
|
String,
|
||||||
)
|
)
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
@ -62,3 +63,4 @@ class ResultBeerds(UJsonMixin):
|
||||||
id: str = field(metadata={"sa": Column(String(), primary_key=True, nullable=False)})
|
id: str = field(metadata={"sa": Column(String(), primary_key=True, nullable=False)})
|
||||||
recognizer_results_id: str = field(metadata={"sa": Column(String(), nullable=False)})
|
recognizer_results_id: str = field(metadata={"sa": Column(String(), nullable=False)})
|
||||||
beerd_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)})
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,12 @@ class ResultWithBeerds:
|
||||||
beerds: list[rm.ResultBeerds]
|
beerds: list[rm.ResultBeerds]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ResultBeerds:
|
||||||
|
beerd_id: str
|
||||||
|
probability: float
|
||||||
|
|
||||||
|
|
||||||
class ARecognizerRepository(metaclass=ABCMeta):
|
class ARecognizerRepository(metaclass=ABCMeta):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def images_dogs(self) -> dict:
|
async def images_dogs(self) -> dict:
|
||||||
|
|
@ -39,7 +45,7 @@ class ARecognizerRepository(metaclass=ABCMeta):
|
||||||
"""Получить **все** результаты (кэшируется)."""
|
"""Получить **все** результаты (кэшируется)."""
|
||||||
|
|
||||||
@abstractmethod
|
@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`.
|
Создать новый результат и сразу же вставить связанные `ResultBeerds`.
|
||||||
|
|
||||||
|
|
@ -74,7 +80,7 @@ class RecognizerRepository(ARecognizerRepository):
|
||||||
data_labels = f.read()
|
data_labels = f.read()
|
||||||
return ujson.loads(data_labels)
|
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`` и сразу же добавляет
|
||||||
одну запись в ``recognizer_results_beerds`` (если передан список
|
одну запись в ``recognizer_results_beerds`` (если передан список
|
||||||
|
|
@ -101,7 +107,8 @@ class RecognizerRepository(ARecognizerRepository):
|
||||||
{
|
{
|
||||||
"id": str(uuid4()),
|
"id": str(uuid4()),
|
||||||
"recognizer_results_id": result.id,
|
"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
|
for beerd_id in beerd_ids
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ import torch
|
||||||
from torchvision import transforms # type: ignore
|
from torchvision import transforms # type: ignore
|
||||||
|
|
||||||
from server.modules.attachments.domains.attachments import Attachment
|
from server.modules.attachments.domains.attachments import Attachment
|
||||||
from server.modules.descriptions.repository import ACharactersRepository
|
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)
|
TorchModel = NewType("TorchModel", torch.nn.Module)
|
||||||
|
|
||||||
|
|
@ -49,6 +49,26 @@ class RecognizerResult(UJsonMixin):
|
||||||
uploaded_attach_id: str | None
|
uploaded_attach_id: str | None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SharingBeerds(UJsonMixin):
|
||||||
|
alias: str
|
||||||
|
name: str
|
||||||
|
images: list[str]
|
||||||
|
probability: float
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SharingResult(UJsonMixin):
|
||||||
|
beerds: list[SharingBeerds]
|
||||||
|
attachment_id: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ResultNameBeerds:
|
||||||
|
name: str
|
||||||
|
probability: float
|
||||||
|
|
||||||
|
|
||||||
class RecognizerService:
|
class RecognizerService:
|
||||||
__slots__ = ("_repository", "_attachment_service", "_repository_characters")
|
__slots__ = ("_repository", "_attachment_service", "_repository_characters")
|
||||||
|
|
||||||
|
|
@ -68,7 +88,10 @@ class RecognizerService:
|
||||||
async def images_dogs(self) -> dict:
|
async def images_dogs(self) -> dict:
|
||||||
return await self._repository.images_dogs()
|
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()
|
characters = await self._repository_characters.get_characters()
|
||||||
await self._repository.create_result_with_beerds(
|
await self._repository.create_result_with_beerds(
|
||||||
models.Results(
|
models.Results(
|
||||||
|
|
@ -78,9 +101,33 @@ class RecognizerService:
|
||||||
device_id=device_id,
|
device_id=device_id,
|
||||||
created_at=datetime.now(UTC),
|
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:
|
||||||
|
results = await self._repository.get_results()
|
||||||
|
beerds_store: dict[str, Breed] = {b.id: b for b in await self._repository_characters.get_characters()}
|
||||||
|
images_dogs = await self._repository.images_dogs()
|
||||||
|
for r in results:
|
||||||
|
if r.result.id != result_id:
|
||||||
|
continue
|
||||||
|
beers: list[SharingBeerds] = []
|
||||||
|
for beerd in r.beerds:
|
||||||
|
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:
|
async def predict_dog_image(self, image: bytes, user_id: str, device_id: str | None) -> RecognizerResult:
|
||||||
if device_id is None:
|
if device_id is None:
|
||||||
device_id = "mobile"
|
device_id = "mobile"
|
||||||
|
|
@ -101,7 +148,14 @@ class RecognizerService:
|
||||||
)
|
)
|
||||||
description.setdefault(name, []).append(f"/dogs-characteristics/{name.replace(' ', '_')}")
|
description.setdefault(name, []).append(f"/dogs-characteristics/{name.replace(' ', '_')}")
|
||||||
results[probabilities] = name
|
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(
|
return RecognizerResult(
|
||||||
results=results, images=images, description=description, uploaded_attach_id=attachment.id
|
results=results, images=images, description=description, uploaded_attach_id=attachment.id
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block meta %}
|
||||||
|
<meta name="description" content="Порода по фото - поделиться результатом" />
|
||||||
|
{% endblock %}
|
||||||
|
{% block title %}Результат определение породы по фото{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>Мой результат определения породы по фото</h1>
|
||||||
|
{% endblock %}
|
||||||
|
{% block form %}
|
||||||
|
<div>
|
||||||
|
<div id="upload-image">
|
||||||
|
<div id="upload-image-text">Ваше изображение:</div>
|
||||||
|
<img id="image" style="max-width: 200px;" src="/attachments{{ attachment.path }}.original.jpg">
|
||||||
|
</div>
|
||||||
|
<div id="result">
|
||||||
|
<h3 class="image-results">Результаты</h3>
|
||||||
|
{% for result in result.beerds %}
|
||||||
|
<div class="image-block"><div class="image-text">{{ result.name }} (вероятность: {{ result.probability }}%) <br><a href="{{ result.alias }}" target="_blank">Описание и фото</a></div>
|
||||||
|
<div class="gallery-container">
|
||||||
|
<div class="main-image-container">
|
||||||
|
<img src="{{ result.images[0] }}" class="main-image"></div>
|
||||||
|
<div class="thumbnails" style="display:none;">
|
||||||
|
{% for image in result.images %}
|
||||||
|
<img src="{{ image }}" class="thumbnail">
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
Loading…
Reference in New Issue