From 8cc69939265078eca1eda2c77322d2ff4de18b9f Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 19 Aug 2023 14:44:26 -0400 Subject: [PATCH] feat(web): Show face box --- cli/src/api/open-api/api.ts | 49 +++++++ mobile/openapi/.openapi-generator/FILES | 3 + mobile/openapi/README.md | 1 + mobile/openapi/doc/FaceGeometryDto.md | 20 +++ mobile/openapi/doc/PersonResponseDto.md | 1 + mobile/openapi/lib/api.dart | 1 + mobile/openapi/lib/api_client.dart | 2 + .../openapi/lib/model/face_geometry_dto.dart | 138 ++++++++++++++++++ .../lib/model/person_response_dto.dart | 19 ++- mobile/openapi/pubspec.yaml | 2 +- .../openapi/test/face_geometry_dto_test.dart | 52 +++++++ .../test/person_response_dto_test.dart | 5 + server/immich-openapi-specs.json | 34 +++++ server/src/domain/person/person.dto.ts | 37 ++++- web/src/api/open-api/api.ts | 49 +++++++ .../asset-viewer/detail-panel.svelte | 22 ++- 16 files changed, 424 insertions(+), 11 deletions(-) create mode 100644 mobile/openapi/doc/FaceGeometryDto.md create mode 100644 mobile/openapi/lib/model/face_geometry_dto.dart create mode 100644 mobile/openapi/test/face_geometry_dto_test.dart diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 16ba34df5..5a97c68e3 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -1376,6 +1376,49 @@ export interface ExifResponseDto { */ 'timeZone'?: string | null; } +/** + * + * @export + * @interface FaceGeometryDto + */ +export interface FaceGeometryDto { + /** + * + * @type {number} + * @memberof FaceGeometryDto + */ + 'boundingBoxX1': number; + /** + * + * @type {number} + * @memberof FaceGeometryDto + */ + 'boundingBoxX2': number; + /** + * + * @type {number} + * @memberof FaceGeometryDto + */ + 'boundingBoxY1': number; + /** + * + * @type {number} + * @memberof FaceGeometryDto + */ + 'boundingBoxY2': number; + /** + * + * @type {number} + * @memberof FaceGeometryDto + */ + 'imageHeight': number; + /** + * + * @type {number} + * @memberof FaceGeometryDto + */ + 'imageWidth': number; +} /** * * @export @@ -1883,6 +1926,12 @@ export interface PersonResponseDto { * @memberof PersonResponseDto */ 'birthDate': string | null; + /** + * + * @type {FaceGeometryDto} + * @memberof PersonResponseDto + */ + 'geometry'?: FaceGeometryDto; /** * * @type {string} diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 93b704976..b6c043dff 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -51,6 +51,7 @@ doc/DownloadArchiveInfo.md doc/DownloadInfoDto.md doc/DownloadResponseDto.md doc/ExifResponseDto.md +doc/FaceGeometryDto.md doc/ImportAssetDto.md doc/JobApi.md doc/JobCommand.md @@ -197,6 +198,7 @@ lib/model/download_archive_info.dart lib/model/download_info_dto.dart lib/model/download_response_dto.dart lib/model/exif_response_dto.dart +lib/model/face_geometry_dto.dart lib/model/import_asset_dto.dart lib/model/job_command.dart lib/model/job_command_dto.dart @@ -314,6 +316,7 @@ test/download_archive_info_test.dart test/download_info_dto_test.dart test/download_response_dto_test.dart test/exif_response_dto_test.dart +test/face_geometry_dto_test.dart test/import_asset_dto_test.dart test/job_api_test.dart test/job_command_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index fb3873a32..826b16af3 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -225,6 +225,7 @@ Class | Method | HTTP request | Description - [DownloadInfoDto](doc//DownloadInfoDto.md) - [DownloadResponseDto](doc//DownloadResponseDto.md) - [ExifResponseDto](doc//ExifResponseDto.md) + - [FaceGeometryDto](doc//FaceGeometryDto.md) - [ImportAssetDto](doc//ImportAssetDto.md) - [JobCommand](doc//JobCommand.md) - [JobCommandDto](doc//JobCommandDto.md) diff --git a/mobile/openapi/doc/FaceGeometryDto.md b/mobile/openapi/doc/FaceGeometryDto.md new file mode 100644 index 000000000..5d8efbed1 --- /dev/null +++ b/mobile/openapi/doc/FaceGeometryDto.md @@ -0,0 +1,20 @@ +# openapi.model.FaceGeometryDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**boundingBoxX1** | **int** | | +**boundingBoxX2** | **int** | | +**boundingBoxY1** | **int** | | +**boundingBoxY2** | **int** | | +**imageHeight** | **int** | | +**imageWidth** | **int** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/doc/PersonResponseDto.md b/mobile/openapi/doc/PersonResponseDto.md index c2acbacd1..487d465a0 100644 --- a/mobile/openapi/doc/PersonResponseDto.md +++ b/mobile/openapi/doc/PersonResponseDto.md @@ -9,6 +9,7 @@ import 'package:openapi/api.dart'; Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **birthDate** | [**DateTime**](DateTime.md) | | +**geometry** | [**FaceGeometryDto**](FaceGeometryDto.md) | | [optional] **id** | **String** | | **isHidden** | **bool** | | **name** | **String** | | diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 7797555bf..732e60e90 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -87,6 +87,7 @@ part 'model/download_archive_info.dart'; part 'model/download_info_dto.dart'; part 'model/download_response_dto.dart'; part 'model/exif_response_dto.dart'; +part 'model/face_geometry_dto.dart'; part 'model/import_asset_dto.dart'; part 'model/job_command.dart'; part 'model/job_command_dto.dart'; diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 2a3ebeb64..cb35f89ea 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -269,6 +269,8 @@ class ApiClient { return DownloadResponseDto.fromJson(value); case 'ExifResponseDto': return ExifResponseDto.fromJson(value); + case 'FaceGeometryDto': + return FaceGeometryDto.fromJson(value); case 'ImportAssetDto': return ImportAssetDto.fromJson(value); case 'JobCommand': diff --git a/mobile/openapi/lib/model/face_geometry_dto.dart b/mobile/openapi/lib/model/face_geometry_dto.dart new file mode 100644 index 000000000..2ad83429e --- /dev/null +++ b/mobile/openapi/lib/model/face_geometry_dto.dart @@ -0,0 +1,138 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class FaceGeometryDto { + /// Returns a new [FaceGeometryDto] instance. + FaceGeometryDto({ + required this.boundingBoxX1, + required this.boundingBoxX2, + required this.boundingBoxY1, + required this.boundingBoxY2, + required this.imageHeight, + required this.imageWidth, + }); + + int boundingBoxX1; + + int boundingBoxX2; + + int boundingBoxY1; + + int boundingBoxY2; + + int imageHeight; + + int imageWidth; + + @override + bool operator ==(Object other) => identical(this, other) || other is FaceGeometryDto && + other.boundingBoxX1 == boundingBoxX1 && + other.boundingBoxX2 == boundingBoxX2 && + other.boundingBoxY1 == boundingBoxY1 && + other.boundingBoxY2 == boundingBoxY2 && + other.imageHeight == imageHeight && + other.imageWidth == imageWidth; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (boundingBoxX1.hashCode) + + (boundingBoxX2.hashCode) + + (boundingBoxY1.hashCode) + + (boundingBoxY2.hashCode) + + (imageHeight.hashCode) + + (imageWidth.hashCode); + + @override + String toString() => 'FaceGeometryDto[boundingBoxX1=$boundingBoxX1, boundingBoxX2=$boundingBoxX2, boundingBoxY1=$boundingBoxY1, boundingBoxY2=$boundingBoxY2, imageHeight=$imageHeight, imageWidth=$imageWidth]'; + + Map toJson() { + final json = {}; + json[r'boundingBoxX1'] = this.boundingBoxX1; + json[r'boundingBoxX2'] = this.boundingBoxX2; + json[r'boundingBoxY1'] = this.boundingBoxY1; + json[r'boundingBoxY2'] = this.boundingBoxY2; + json[r'imageHeight'] = this.imageHeight; + json[r'imageWidth'] = this.imageWidth; + return json; + } + + /// Returns a new [FaceGeometryDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static FaceGeometryDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return FaceGeometryDto( + boundingBoxX1: mapValueOfType(json, r'boundingBoxX1')!, + boundingBoxX2: mapValueOfType(json, r'boundingBoxX2')!, + boundingBoxY1: mapValueOfType(json, r'boundingBoxY1')!, + boundingBoxY2: mapValueOfType(json, r'boundingBoxY2')!, + imageHeight: mapValueOfType(json, r'imageHeight')!, + imageWidth: mapValueOfType(json, r'imageWidth')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = FaceGeometryDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = FaceGeometryDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of FaceGeometryDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = FaceGeometryDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'boundingBoxX1', + 'boundingBoxX2', + 'boundingBoxY1', + 'boundingBoxY2', + 'imageHeight', + 'imageWidth', + }; +} + diff --git a/mobile/openapi/lib/model/person_response_dto.dart b/mobile/openapi/lib/model/person_response_dto.dart index 5e65d947a..6329bd5c5 100644 --- a/mobile/openapi/lib/model/person_response_dto.dart +++ b/mobile/openapi/lib/model/person_response_dto.dart @@ -14,6 +14,7 @@ class PersonResponseDto { /// Returns a new [PersonResponseDto] instance. PersonResponseDto({ required this.birthDate, + this.geometry, required this.id, required this.isHidden, required this.name, @@ -22,6 +23,14 @@ class PersonResponseDto { DateTime? birthDate; + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + FaceGeometryDto? geometry; + String id; bool isHidden; @@ -33,6 +42,7 @@ class PersonResponseDto { @override bool operator ==(Object other) => identical(this, other) || other is PersonResponseDto && other.birthDate == birthDate && + other.geometry == geometry && other.id == id && other.isHidden == isHidden && other.name == name && @@ -42,13 +52,14 @@ class PersonResponseDto { int get hashCode => // ignore: unnecessary_parenthesis (birthDate == null ? 0 : birthDate!.hashCode) + + (geometry == null ? 0 : geometry!.hashCode) + (id.hashCode) + (isHidden.hashCode) + (name.hashCode) + (thumbnailPath.hashCode); @override - String toString() => 'PersonResponseDto[birthDate=$birthDate, id=$id, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath]'; + String toString() => 'PersonResponseDto[birthDate=$birthDate, geometry=$geometry, id=$id, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath]'; Map toJson() { final json = {}; @@ -56,6 +67,11 @@ class PersonResponseDto { json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); } else { // json[r'birthDate'] = null; + } + if (this.geometry != null) { + json[r'geometry'] = this.geometry; + } else { + // json[r'geometry'] = null; } json[r'id'] = this.id; json[r'isHidden'] = this.isHidden; @@ -73,6 +89,7 @@ class PersonResponseDto { return PersonResponseDto( birthDate: mapDateTime(json, r'birthDate', ''), + geometry: FaceGeometryDto.fromJson(json[r'geometry']), id: mapValueOfType(json, r'id')!, isHidden: mapValueOfType(json, r'isHidden')!, name: mapValueOfType(json, r'name')!, diff --git a/mobile/openapi/pubspec.yaml b/mobile/openapi/pubspec.yaml index e61130a38..00043ba66 100644 --- a/mobile/openapi/pubspec.yaml +++ b/mobile/openapi/pubspec.yaml @@ -10,7 +10,7 @@ environment: sdk: '>=2.12.0 <3.0.0' dependencies: http: '>=0.13.0 <0.14.0' - intl: '^0.18.0' + intl: '^0.17.0' meta: '^1.1.8' dev_dependencies: test: '>=1.16.0 <1.18.0' diff --git a/mobile/openapi/test/face_geometry_dto_test.dart b/mobile/openapi/test/face_geometry_dto_test.dart new file mode 100644 index 000000000..5cff5e4e7 --- /dev/null +++ b/mobile/openapi/test/face_geometry_dto_test.dart @@ -0,0 +1,52 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + +// tests for FaceGeometryDto +void main() { + // final instance = FaceGeometryDto(); + + group('test FaceGeometryDto', () { + // int boundingBoxX1 + test('to test the property `boundingBoxX1`', () async { + // TODO + }); + + // int boundingBoxX2 + test('to test the property `boundingBoxX2`', () async { + // TODO + }); + + // int boundingBoxY1 + test('to test the property `boundingBoxY1`', () async { + // TODO + }); + + // int boundingBoxY2 + test('to test the property `boundingBoxY2`', () async { + // TODO + }); + + // int imageHeight + test('to test the property `imageHeight`', () async { + // TODO + }); + + // int imageWidth + test('to test the property `imageWidth`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/person_response_dto_test.dart b/mobile/openapi/test/person_response_dto_test.dart index 0ba730611..4f063c28c 100644 --- a/mobile/openapi/test/person_response_dto_test.dart +++ b/mobile/openapi/test/person_response_dto_test.dart @@ -21,6 +21,11 @@ void main() { // TODO }); + // FaceGeometryDto geometry + test('to test the property `geometry`', () async { + // TODO + }); + // String id test('to test the property `id`', () async { // TODO diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 8b482a8e2..fa8473895 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -5814,6 +5814,37 @@ }, "type": "object" }, + "FaceGeometryDto": { + "properties": { + "boundingBoxX1": { + "type": "integer" + }, + "boundingBoxX2": { + "type": "integer" + }, + "boundingBoxY1": { + "type": "integer" + }, + "boundingBoxY2": { + "type": "integer" + }, + "imageHeight": { + "type": "integer" + }, + "imageWidth": { + "type": "integer" + } + }, + "required": [ + "imageWidth", + "imageHeight", + "boundingBoxX1", + "boundingBoxY1", + "boundingBoxX2", + "boundingBoxY2" + ], + "type": "object" + }, "ImportAssetDto": { "properties": { "assetPath": { @@ -6211,6 +6242,9 @@ "nullable": true, "type": "string" }, + "geometry": { + "$ref": "#/components/schemas/FaceGeometryDto" + }, "id": { "type": "string" }, diff --git a/server/src/domain/person/person.dto.ts b/server/src/domain/person/person.dto.ts index 71fe0bd41..357218108 100644 --- a/server/src/domain/person/person.dto.ts +++ b/server/src/domain/person/person.dto.ts @@ -103,6 +103,21 @@ export class PersonSearchDto { withHidden?: boolean = false; } +export class FaceGeometryDto { + @ApiProperty({ type: 'integer' }) + imageWidth!: number; + @ApiProperty({ type: 'integer' }) + imageHeight!: number; + @ApiProperty({ type: 'integer' }) + boundingBoxX1!: number; + @ApiProperty({ type: 'integer' }) + boundingBoxY1!: number; + @ApiProperty({ type: 'integer' }) + boundingBoxX2!: number; + @ApiProperty({ type: 'integer' }) + boundingBoxY2!: number; +} + export class PersonResponseDto { id!: string; name!: string; @@ -110,6 +125,7 @@ export class PersonResponseDto { birthDate!: Date | null; thumbnailPath!: string; isHidden!: boolean; + geometry?: FaceGeometryDto; } export class PeopleResponseDto { @@ -132,6 +148,23 @@ export function mapPerson(person: PersonEntity): PersonResponseDto { }; } -export function mapFace(face: AssetFaceEntity): PersonResponseDto { - return mapPerson(face.person); +export function mapGeometry(entity: AssetFaceEntity) { + return { + imageWidth: entity.imageWidth, + imageHeight: entity.imageHeight, + boundingBoxX1: entity.boundingBoxX1, + boundingBoxY1: entity.boundingBoxY1, + boundingBoxX2: entity.boundingBoxX2, + boundingBoxY2: entity.boundingBoxY2, + }; +} +export function mapFace(entity: AssetFaceEntity): PersonResponseDto { + return { + id: entity.person.id, + name: entity.person.name, + birthDate: entity.person.birthDate, + thumbnailPath: entity.person.thumbnailPath, + isHidden: entity.person.isHidden, + geometry: mapGeometry(entity), + }; } diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 16ba34df5..5a97c68e3 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -1376,6 +1376,49 @@ export interface ExifResponseDto { */ 'timeZone'?: string | null; } +/** + * + * @export + * @interface FaceGeometryDto + */ +export interface FaceGeometryDto { + /** + * + * @type {number} + * @memberof FaceGeometryDto + */ + 'boundingBoxX1': number; + /** + * + * @type {number} + * @memberof FaceGeometryDto + */ + 'boundingBoxX2': number; + /** + * + * @type {number} + * @memberof FaceGeometryDto + */ + 'boundingBoxY1': number; + /** + * + * @type {number} + * @memberof FaceGeometryDto + */ + 'boundingBoxY2': number; + /** + * + * @type {number} + * @memberof FaceGeometryDto + */ + 'imageHeight': number; + /** + * + * @type {number} + * @memberof FaceGeometryDto + */ + 'imageWidth': number; +} /** * * @export @@ -1883,6 +1926,12 @@ export interface PersonResponseDto { * @memberof PersonResponseDto */ 'birthDate': string | null; + /** + * + * @type {FaceGeometryDto} + * @memberof PersonResponseDto + */ + 'geometry'?: FaceGeometryDto; /** * * @type {string} diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte index 2c77cd8af..db5579333 100644 --- a/web/src/lib/components/asset-viewer/detail-panel.svelte +++ b/web/src/lib/components/asset-viewer/detail-panel.svelte @@ -77,23 +77,23 @@ }; -
+
-

Info

+

Info