frame side management
Управление со стороны рамы — техническая документация
🎯 Обзор
Интерактивный просмотрщик фотографий в кадре с возможностью масштабирования, панорамирования, переключения наложения обнаружения и возможности аннотаций. Поддерживает прогрессивную загрузку изображений, рисование на холсте и визуализацию обнаружения в реальном времени для комплексного анализа кадров.
🏗️ Архитектура
Компоненты
- FrameSide: основной компонент контейнера, извлекающий данные на стороне кадра и управляющий состоянием загрузки.
- FrameSideDrawing: основной компонент холста, обрабатывающий наложения рисования, масштабирования, панорамирования и обнаружения.
- DrawingCanvas: рендеринг холста со встроенной визуализацией обнаружения и пользовательскими аннотациями.
- uploadFile: компонент загрузки файла для начальной загрузки фотографии кадра.
Услуги
- image-splitter: сохраняет загруженные изображения, предоставляет версии с измененным размером, управляет данными обнаружения в формате JSON.
- swarm-api: сохраняет структуру фрейма/блока и ссылкиframe_side.
- web-app: интерфейсное приложение React с кэшированием локальной базы данных Dexie.
📋 Технические характеристики
Схема базы данных
erDiagram
frames ||--o{ frames_sides : "has_two"
frames_sides ||--o{ files_frame_side_rel : "has_photos"
files_frame_side_rel ||--|| files : "references"
files ||--o{ file_resizes : "has_versions"
files_frame_side_rel ||--o{ files_frame_side_cells : "has_cells"
files_frame_side_rel ||--o{ files_frame_side_queen_cups : "has_cups"
frames {
int id PK
int box_id FK
int position
int left_id FK
int right_id FK
enum type
boolean active
}
frames_sides {
int id PK
int user_id
}
files {
int id PK
int user_id
varchar hash
varchar ext
int width
int height
}
file_resizes {
int id PK
int file_id FK
int max_dimension_px
varchar url
}
files_frame_side_rel {
int frame_side_id FK
int file_id FK
int user_id
int inspection_id "NULL for current"
json strokeHistory "user drawings"
json detected_bees "array of bee detections"
json detected_queens "array of queen detections"
json detected_varroa "array of varroa detections"
int worker_bee_count
int drone_count
int queen_count
int varroa_count
boolean queen_detected
datetime added_time
}
files_frame_side_cells {
int frame_side_id FK
int file_id FK
int user_id
int inspection_id
json cells "array of cell detections"
int brood
int capped_brood
int eggs
int pollen
int honey
datetime added_time
}
files_frame_side_queen_cups {
int frame_side_id FK
int file_id FK
int user_id
int inspection_id
json cups "array of queen cup detections"
datetime added_time
}
GraphQL API
type FrameSide {
id: ID!
frameId: ID
isQueenConfirmed: Boolean
file: File
cells: FrameSideCells
frameSideFile: FrameSideFile
inspections: [FrameSideInspection]
}
type FrameSideFile {
file: File!
frameSideId: ID
hiveId: ID
strokeHistory: JSON
detectedBees: JSON
detectedQueenCount: Int
detectedWorkerBeeCount: Int
detectedDroneCount: Int
isBeeDetectionComplete: Boolean
detectedCells: JSON
isCellsDetectionComplete: Boolean
detectedQueenCups: JSON
isQueenCupsDetectionComplete: Boolean
isQueenDetectionComplete: Boolean
queenDetected: Boolean!
workerCount: Int
droneCount: Int
detectedVarroa: JSON
varroaCount: Int
}
type File {
id: ID!
url: URL!
resizes: [FileResize]
}
type FileResize {
id: ID!
file_id: ID!
max_dimension_px: Int!
url: URL!
}
type FrameSideCells {
id: ID!
broodPercent: Int
cappedBroodPercent: Int
eggsPercent: Int
pollenPercent: Int
honeyPercent: Int
}
type FrameSideInspection {
frameSideId: ID!
inspectionId: ID!
file: File
cells: FrameSideCells
frameSideFile: FrameSideFile
}
type Query {
hiveFrameSideFile(frameSideId: ID!): FrameSideFile
hiveFrameSideCells(frameSideId: ID!): FrameSideCells
frameSidesInspections(frameSideIds: [ID], inspectionId: ID!): [FrameSideInspection]
file(id: ID!): File
hiveFiles(hiveId: ID!): [FrameSideFile]
boxFiles(boxId: ID!, inspectionId: ID): [BoxFile]
}
type Mutation {
uploadFrameSide(file: Upload!): File
addFileToFrameSide(frameSideId: ID!, fileId: ID!, hiveId: ID!): Boolean
filesStrokeEditMutation(files: [FilesUpdateInput]): Boolean
updateFrameSideCells(cells: FrameSideCellsInput!): Boolean!
confirmFrameSideQueen(frameSideId: ID!, isConfirmed: Boolean!): Boolean!
cloneFramesForInspection(frameSideIDs: [ID], inspectionId: ID!): Boolean!
}
input FilesUpdateInput {
frameSideId: ID!
fileId: ID!
strokeHistory: JSON!
}
input FrameSideCellsInput {
id: ID!
broodPercent: Int
cappedBroodPercent: Int
eggsPercent: Int
pollenPercent: Int
honeyPercent: Int
}
🔧 Детали реализации
Интерфейс (web-app)
- Framework: Реагируйте с помощью TypeScript.
- Рендеринг на холсте: HTML5 Canvas API.
- Рисование: события указателя API для поддержки мыши и сенсорного ввода.
- Масштабирование: преобразование CSS с ускорением GPU.
- Загрузка изображения: прогрессивный JPEG с эффектом размытия.
Прогрессивная загрузка изображений
1. Initial load: Select best resize from file_resizes table (>128px width)
2. Image loaded via HTMLImageElement and drawn to canvas
3. Canvas size calculated based on viewport width and device pixel ratio
4. Resizes stored with max_dimension_px and URL in database
Реализация масштабирования и панорамирования
- Диапазон масштабирования: от MIN_ZOOM (1x) до MAX_ZOOM (100x).
- Средний зум: MED_ZOOM (2x) для обнаружения мобильных устройств.
- Механизм масштабирования: масштабирование холста с помощью globalCameraZoom.
- Панорамирование: перемещение на основе перетаскивания со смещением (координаты x, y).
- Управление панорамированием: флаг isPanning, startPanPosition, отслеживание InitialPanOffset.
- Мобильные устройства: масштабирование отключено в области просмотра шириной менее 1200 пикселей.
- Ограничения: запретить масштабирование ниже 1x и выше 100x.
Реализация инструмента рисования
- Рисование от руки: захват событий перемещения указателя с поддержкой давления.
- Хранилище путей: массив StrokeHistory из массивов DrawingLine, каждый из которых содержит DrawingPoint (x, y, lineWidth, цвет).
- Точки рисования: нормализованные координаты (0–1) относительно размеров холста.
- Рендеринг линий: квадратичные кривые для плавных обводок с помощьюquadaticCurveTo.
- Отменить: извлечь последний штрих из массива StrokeHistory.
- Очистить: пустой массив StrokeHistory.
- Постоянство: filesStrokeEditMutation сохраняется на серверной стороне, updateStrokeHistoryData обновляет кэш Dexie.
Поток данных
graph TB
A[User opens frame side] --> B[FRAME_SIDE_QUERY fetches data]
B --> C[getFrameSideFile from Dexie]
C --> D[getThumbnailUrl selects best resize]
D --> E[loadImage and draw to canvas]
E --> F[Render detections]
G[User zooms or pans] --> H[Update globalCameraZoom or offsetsum]
H --> I[redrawCurrentCanvas]
I --> J[drawCanvasLayers with transforms]
K[User toggles detection] --> L[Update visibility state]
L --> M[forceRedraw triggers re-render]
N[User draws stroke] --> O[Capture normalized points]
O --> P[drawStrokeSegment to canvas]
P --> Q[On mouseup: filesStrokeEditMutate]
Q --> R[updateStrokeHistoryData to Dexie]
S[useFrameSideSubscriptions] --> T[Listen to Redis pubsub]
T --> U[appendDetectionData]
U --> V[Update Dexie via modify]
🧪 Тестирование
Модульные тесты
- Расчеты масштабирования и ограничения
- Логика рендеринга наложения обнаружения
- Захват и хранение пути рисования.
- Выбор изображения URL в зависимости от уровня масштабирования.
- Управление состоянием переключения обнаружения
Интеграционные тесты
- Прогрессивная последовательность загрузки изображений
- Синхронизация данных обнаружения с серверной частью
- Сохранение аннотаций
- Производительность рендеринга холста
- Обработка сенсорных событий на мобильном телефоне
E2E-тесты
- Пользователь загружает фотографию и просматривает ее.
- Пользователь плавно увеличивает и уменьшает масштаб
- Пользователь включает/выключает типы обнаружения.
- Пользователь рисует аннотации и отменяет действия.
- Пользователь переключается между сторонами кадра
📊 Вопросы производительности
Оптимизации
- Мемоизация холста: React.memo при наложении обнаружения.
- Регулирование рендеринга: RequestAnimationFrame для плавного масштабирования.
- Предварительная загрузка изображения: предварительная загрузка следующего уровня масштабирования.
- Пакетирование обнаружения: рендеринг всех обнаружений одного типа за один проход.
- Ускорение GPU: CSS-преобразования для масштабирования и панорамирования.
- Ленивый рендеринг: отображать только видимые обнаружения.
Метрики
- Начальная загрузка: менее 500 мс (маленькое изображение)
- Переход масштабирования: менее 100 мс (60 кадров в секунду)
- Переключение обнаружения: менее 50 мс
- Скорость отклика при рисовании: менее 16 мс (60 кадров в секунду)
- Память: до 100 МБ для больших изображений.
Узкие места
– Большие изображения (более 4000x3000 пикселей) могут повлиять на работу мобильных устройств.
- Множество обнаружений (более 1000) медленного рендеринга наложения.
- Рисование с большим количеством путей (более 100) влияет на производительность отмены.
🚫 Технические ограничения
- Максимальное увеличение 100x (может пикселизироваться при экстремальных уровнях масштабирования)
- Инструменты рисования ограничены от руки (без фигур и текста).
- Нет совместных аннотаций (только для одного пользователя)
- Аннотации не версионированы (перезаписываются при сохранении)
- Производительность мобильных устройств снижается при использовании очень больших изображений.
- Масштабирование отключено на мобильных устройствах (ширина < 1200 пикселей).
🔗 Сопутствующая документация
- Загрузка фотографии в рамке Техническая документация
- Техническая документация по обнаружению королевы
📚 Ресурсы для разработки
- web-app компоненты рамы
- image-splitter GraphQL резольверы
- Холст API Документация
- API событий указателя
💬 Технические примечания
- Реализована поддержка панорамирования, но ее можно улучшить с помощью сенсорных жестов.
- Средство рендеринга WebGL может повысить производительность при многих обнаружениях.
- Рассмотрите формат WebP для изображений меньшего размера с тем же качеством.
- Инструмент рисования может выиграть от наложения SVG вместо Canvas для лучшего качества.
- Сенсорные жесты (масштабирование щипком) улучшат работу на мобильных устройствах (в настоящее время масштабирование отключено на мобильных устройствах).
– Рассмотрите возможность добавления инструментов измерения (линейка, калькулятор площади).
Последнее обновление: 5 декабря 2025 г.