feat: add multi-source acquisition ingestion
This commit is contained in:
parent
961d92fff3
commit
7886b38553
7 changed files with 721 additions and 3 deletions
|
|
@ -17,6 +17,37 @@ est acceptée sans nouvelle ligne ni migration de schéma. Elle ne crée aucune
|
||||||
image logique, ne modifie aucune sélection et ne réalise aucun appariement
|
image logique, ne modifie aucune sélection et ne réalise aucun appariement
|
||||||
automatique.
|
automatique.
|
||||||
|
|
||||||
|
### S3-E — Orchestration d'ingestion multi-source v1
|
||||||
|
|
||||||
|
**IMPLEMENTED / VALIDATION PENDING.** S3-E reçoit un ScanSet explicite et au
|
||||||
|
plus 64 chemins source explicitement fournis par l'appelant ; il ne parcourt
|
||||||
|
jamais un répertoire. Chaque fichier est d'abord publié comme asset immuable
|
||||||
|
géré, puis son évidence S3-D est extraite depuis ces octets publiés. Le
|
||||||
|
regroupement automatique ne crée un Capture commun que pour une relation
|
||||||
|
`SAME_ACQUISITION_STRONG` unique et mutuelle. Les noms, chemins, SHA-256,
|
||||||
|
timestamps, modèle, évidence faible, contradictions et ambiguïtés ne sont
|
||||||
|
jamais une identité de Capture et ne donnent jamais lieu à un rapprochement
|
||||||
|
automatique. Deux assets identiques dans une requête sont rejetés de façon
|
||||||
|
déterministe.
|
||||||
|
|
||||||
|
L'appelant peut aussi déclarer explicitement des groupes. Ce résultat porte la
|
||||||
|
basis `CALLER_EXPLICIT`, distincte de la basis scientifique `STRONG`; cette
|
||||||
|
provenance est un résultat de l'opération v1, non une nouvelle colonne durable.
|
||||||
|
Les siblings sont persistés exclusivement avec S3-C. Pour la représentation,
|
||||||
|
un JPEG caméra reste un asset `SOURCE` et devient une image logique du même
|
||||||
|
Capture via la publication source-image transactionnelle; un RAW reste
|
||||||
|
`SOURCE` et son image est produite seulement par S3-B1, avec son PNG `DERIVED`.
|
||||||
|
RAW et JPEG d'une même acquisition ne créent pas deux Captures. La sélection ne
|
||||||
|
change que si l'appelant demande explicitement la représentation sélectionnée.
|
||||||
|
|
||||||
|
Project DB reste strictement v19, sans migration ni fusion de Captures. Après
|
||||||
|
qu'un Capture ID a été renvoyé ou durablement retenu, une reprise doit fournir
|
||||||
|
explicitement `resume_capture_id`: publication, attaches S3-C et représentation
|
||||||
|
convergent alors sans réhoming ni doublon logique. Une panne avant que
|
||||||
|
l'appelant ait retenu cet ID ne peut pas offrir une reprise whole-request
|
||||||
|
exactly-once en v19; S3-E n'infère volontairement jamais une identité de
|
||||||
|
reprise depuis SHA, chemin, basename, timestamp, métadonnées ou `image_id`.
|
||||||
|
|
||||||
`asset_derivations` est volontairement limité à un parent asset et un enfant
|
`asset_derivations` est volontairement limité à un parent asset et un enfant
|
||||||
asset, avec kind/version et fingerprint canonique de 32 octets. Il n'est pas un
|
asset, avec kind/version et fingerprint canonique de 32 octets. Il n'est pas un
|
||||||
DAG générique et ne réalise aucun développement RAW ni extraction vidéo. La
|
DAG générique et ne réalise aucun développement RAW ni extraction vidéo. La
|
||||||
|
|
@ -44,9 +75,8 @@ assets sont différents. Une valeur absente n'est pas un conflit. Une ambiguït
|
||||||
n'est jamais résolue par l'ordre des assets.
|
n'est jamais résolue par l'ordre des assets.
|
||||||
|
|
||||||
S3-D n'effectue aucune écriture en base ni changement de schéma : Project DB
|
S3-D n'effectue aucune écriture en base ni changement de schéma : Project DB
|
||||||
reste v19 et S3-C attach demeure l'unique primitive de persistance. S3-E, la
|
reste v19 et S3-C attach demeure l'unique primitive de persistance. S3-E
|
||||||
future intégration d'ingestion multi-source avant `Capture`, n'est pas
|
réutilise cette évidence sans la redéfinir.
|
||||||
implémentée.
|
|
||||||
|
|
||||||
## Phase H — décision Project DB v18
|
## Phase H — décision Project DB v18
|
||||||
|
|
||||||
|
|
|
||||||
81
include/lardon3d/acquisition_ingest.h
Normal file
81
include/lardon3d/acquisition_ingest.h
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
#ifndef LARDON3D_ACQUISITION_INGEST_H
|
||||||
|
#define LARDON3D_ACQUISITION_INGEST_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <lardon3d/app_state.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define LARDON3D_ACQUISITION_INGEST_MAX_SOURCES 64u
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LARDON3D_ACQUISITION_INGEST_OK = 0,
|
||||||
|
LARDON3D_ACQUISITION_INGEST_INVALID_ARGUMENT,
|
||||||
|
LARDON3D_ACQUISITION_INGEST_SCANSET_NOT_FOUND,
|
||||||
|
LARDON3D_ACQUISITION_INGEST_PUBLISH_ERROR,
|
||||||
|
LARDON3D_ACQUISITION_INGEST_METADATA_ERROR,
|
||||||
|
LARDON3D_ACQUISITION_INGEST_DUPLICATE_ASSET,
|
||||||
|
LARDON3D_ACQUISITION_INGEST_DB_ERROR,
|
||||||
|
LARDON3D_ACQUISITION_INGEST_DEVELOPMENT_ERROR,
|
||||||
|
LARDON3D_ACQUISITION_INGEST_CONSTRAINT,
|
||||||
|
LARDON3D_ACQUISITION_INGEST_INTERNAL_ERROR
|
||||||
|
} Lardon3DAcquisitionIngestResult;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LARDON3D_ACQUISITION_GROUP_AUTOMATIC = 1,
|
||||||
|
LARDON3D_ACQUISITION_GROUP_CALLER_EXPLICIT = 2
|
||||||
|
} Lardon3DAcquisitionGroupingMode;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LARDON3D_ACQUISITION_GROUP_SINGLETON = 0,
|
||||||
|
LARDON3D_ACQUISITION_GROUP_STRONG = 1,
|
||||||
|
LARDON3D_ACQUISITION_GROUP_EXPLICIT = 2
|
||||||
|
} Lardon3DAcquisitionGroupBasis;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LARDON3D_ACQUISITION_SELECT_JPEG_SOURCE = 1,
|
||||||
|
LARDON3D_ACQUISITION_SELECT_DEVELOP_RAW = 2
|
||||||
|
} Lardon3DAcquisitionRepresentation;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *source_path; /* Borrowed for the duration of the call. */
|
||||||
|
uint32_t explicit_group;
|
||||||
|
} Lardon3DAcquisitionIngestSource;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Lardon3DAcquisitionGroupingMode grouping;
|
||||||
|
Lardon3DAcquisitionRepresentation representation;
|
||||||
|
uint8_t select_representation;
|
||||||
|
uint8_t reserved[7];
|
||||||
|
uint64_t resume_capture_id;
|
||||||
|
uint64_t producer_task_id;
|
||||||
|
int64_t imported_at;
|
||||||
|
uint64_t max_source_bytes;
|
||||||
|
} Lardon3DAcquisitionIngestOptions;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t capture_id;
|
||||||
|
Lardon3DAcquisitionGroupBasis basis;
|
||||||
|
size_t source_count;
|
||||||
|
size_t source_indices[LARDON3D_ACQUISITION_INGEST_MAX_SOURCES];
|
||||||
|
} Lardon3DAcquisitionIngestGroup;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
size_t group_count;
|
||||||
|
Lardon3DAcquisitionIngestGroup groups[LARDON3D_ACQUISITION_INGEST_MAX_SOURCES];
|
||||||
|
} Lardon3DAcquisitionIngestOutput;
|
||||||
|
|
||||||
|
Lardon3DAcquisitionIngestResult lardon3d_acquisition_ingest(
|
||||||
|
Lardon3DAppState *state, uint64_t scanset_id,
|
||||||
|
const Lardon3DAcquisitionIngestSource *sources, size_t source_count,
|
||||||
|
const Lardon3DAcquisitionIngestOptions *options, Lardon3DAcquisitionIngestOutput *output);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -510,6 +510,11 @@ Lardon3DProjectDbResult lardon3d_project_db_publish_derived_capture_image(
|
||||||
const char *original_name, const char *source_path, uint64_t producer_task_id,
|
const char *original_name, const char *source_path, uint64_t producer_task_id,
|
||||||
int64_t imported_at, Lardon3DProjectDbImageRegisterStatus *status,
|
int64_t imported_at, Lardon3DProjectDbImageRegisterStatus *status,
|
||||||
Lardon3DProjectDbImage *image);
|
Lardon3DProjectDbImage *image);
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_publish_source_capture_image(
|
||||||
|
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t asset_id,
|
||||||
|
const char *original_name, const char *source_path, uint64_t producer_task_id,
|
||||||
|
int64_t imported_at, bool select_image, Lardon3DProjectDbImageRegisterStatus *status,
|
||||||
|
Lardon3DProjectDbImage *image);
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_load_image(Lardon3DProjectDb *database,
|
Lardon3DProjectDbResult lardon3d_project_db_load_image(Lardon3DProjectDb *database,
|
||||||
uint64_t image_id,
|
uint64_t image_id,
|
||||||
Lardon3DProjectDbImage *image,
|
Lardon3DProjectDbImage *image,
|
||||||
|
|
|
||||||
22
meson.build
22
meson.build
|
|
@ -150,6 +150,7 @@ executable(
|
||||||
'src/image_catalog_persistent.c',
|
'src/image_catalog_persistent.c',
|
||||||
'src/raw_development.cpp',
|
'src/raw_development.cpp',
|
||||||
'src/acquisition_pairing.cpp',
|
'src/acquisition_pairing.cpp',
|
||||||
|
'src/acquisition_ingest.cpp',
|
||||||
'src/feature_extractor_opencv.cpp',
|
'src/feature_extractor_opencv.cpp',
|
||||||
'src/feature_store.c',
|
'src/feature_store.c',
|
||||||
'src/feature_extractor_opencv.cpp',
|
'src/feature_extractor_opencv.cpp',
|
||||||
|
|
@ -333,6 +334,27 @@ acquisition_pairing_test = executable(
|
||||||
|
|
||||||
test('acquisition-pairing', acquisition_pairing_test, timeout: 30)
|
test('acquisition-pairing', acquisition_pairing_test, timeout: 30)
|
||||||
|
|
||||||
|
acquisition_ingest_test = executable(
|
||||||
|
'test-acquisition-ingest',
|
||||||
|
sources: [
|
||||||
|
'tests/test_acquisition_ingest.cpp',
|
||||||
|
'src/app_state.c',
|
||||||
|
'src/image_catalog_persistent.c',
|
||||||
|
'src/acquisition_ingest.cpp',
|
||||||
|
'src/acquisition_pairing.cpp',
|
||||||
|
'src/raw_development.cpp',
|
||||||
|
'src/project_db.c', 'src/project_db_sparse_sfm.c',
|
||||||
|
'src/task.c',
|
||||||
|
'src/resource_governor.c',
|
||||||
|
'src/resource_snapshot.c',
|
||||||
|
],
|
||||||
|
include_directories: include_directories('include'),
|
||||||
|
cpp_args: ['-DLARDON3D_ACQUISITION_INGEST_TESTING'],
|
||||||
|
dependencies: [threads, sqlite3, openssl, opencv, libraw, libexif, libpng, libdeflate],
|
||||||
|
)
|
||||||
|
|
||||||
|
test('acquisition-ingest', acquisition_ingest_test, timeout: 30)
|
||||||
|
|
||||||
feature_store_test = executable(
|
feature_store_test = executable(
|
||||||
'test-feature-store',
|
'test-feature-store',
|
||||||
sources: [
|
sources: [
|
||||||
|
|
|
||||||
195
src/acquisition_ingest.cpp
Normal file
195
src/acquisition_ingest.cpp
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
#include <lardon3d/acquisition_ingest.h>
|
||||||
|
|
||||||
|
#include <lardon3d/acquisition_pairing.h>
|
||||||
|
extern "C" {
|
||||||
|
#include <lardon3d/image_catalog.h>
|
||||||
|
}
|
||||||
|
#include <lardon3d/raw_development.h>
|
||||||
|
|
||||||
|
#include <climits>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#ifdef LARDON3D_ACQUISITION_INGEST_TESTING
|
||||||
|
const Lardon3DAcquisitionMetadata *testing_metadata = nullptr;
|
||||||
|
size_t testing_metadata_count = 0u;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct PublishedSource {
|
||||||
|
Lardon3DProjectDbImageAsset asset;
|
||||||
|
Lardon3DAcquisitionMetadata metadata;
|
||||||
|
char managed_path[PATH_MAX];
|
||||||
|
};
|
||||||
|
|
||||||
|
bool valid_options(const Lardon3DAcquisitionIngestOptions &options) {
|
||||||
|
return (options.grouping == LARDON3D_ACQUISITION_GROUP_AUTOMATIC ||
|
||||||
|
options.grouping == LARDON3D_ACQUISITION_GROUP_CALLER_EXPLICIT) &&
|
||||||
|
(options.representation == LARDON3D_ACQUISITION_SELECT_JPEG_SOURCE ||
|
||||||
|
options.representation == LARDON3D_ACQUISITION_SELECT_DEVELOP_RAW) &&
|
||||||
|
options.select_representation <= 1u && options.imported_at >= 0 &&
|
||||||
|
options.max_source_bytes != 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *base_name(const char *path) {
|
||||||
|
const char *slash = std::strrchr(path, '/');
|
||||||
|
return slash == nullptr ? path : slash + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DAcquisitionIngestResult db_result(Lardon3DProjectDbResult result) {
|
||||||
|
if (result == LARDON3D_PROJECT_DB_CONSTRAINT) return LARDON3D_ACQUISITION_INGEST_CONSTRAINT;
|
||||||
|
return result == LARDON3D_PROJECT_DB_NOT_FOUND ? LARDON3D_ACQUISITION_INGEST_SCANSET_NOT_FOUND
|
||||||
|
: LARDON3D_ACQUISITION_INGEST_DB_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool add_to_group(Lardon3DAcquisitionIngestGroup &group, size_t source_index) {
|
||||||
|
if (group.source_count >= LARDON3D_ACQUISITION_INGEST_MAX_SOURCES) return false;
|
||||||
|
group.source_indices[group.source_count++] = source_index;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
#ifdef LARDON3D_ACQUISITION_INGEST_TESTING
|
||||||
|
extern "C" void lardon3d_acquisition_ingest_test_set_metadata(
|
||||||
|
const Lardon3DAcquisitionMetadata *metadata, size_t count) {
|
||||||
|
testing_metadata = metadata;
|
||||||
|
testing_metadata_count = count;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern "C" Lardon3DAcquisitionIngestResult lardon3d_acquisition_ingest(
|
||||||
|
Lardon3DAppState *state, uint64_t scanset_id,
|
||||||
|
const Lardon3DAcquisitionIngestSource *sources, size_t source_count,
|
||||||
|
const Lardon3DAcquisitionIngestOptions *options, Lardon3DAcquisitionIngestOutput *output) {
|
||||||
|
if (output != nullptr) std::memset(output, 0, sizeof(*output));
|
||||||
|
if (state == nullptr || !state->project_loaded || state->project_db == nullptr || scanset_id == 0u ||
|
||||||
|
sources == nullptr || source_count == 0u ||
|
||||||
|
source_count > LARDON3D_ACQUISITION_INGEST_MAX_SOURCES || options == nullptr ||
|
||||||
|
!valid_options(*options) || output == nullptr) return LARDON3D_ACQUISITION_INGEST_INVALID_ARGUMENT;
|
||||||
|
Lardon3DProjectDbScanSet scanset{};
|
||||||
|
Lardon3DProjectDbResult database_result =
|
||||||
|
lardon3d_project_db_load_scanset(state->project_db, scanset_id, &scanset);
|
||||||
|
if (database_result != LARDON3D_PROJECT_DB_OK) return db_result(database_result);
|
||||||
|
if (options->grouping == LARDON3D_ACQUISITION_GROUP_CALLER_EXPLICIT) {
|
||||||
|
for (size_t i = 0; i < source_count; ++i)
|
||||||
|
if (sources[i].explicit_group == 0u) return LARDON3D_ACQUISITION_INGEST_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
PublishedSource published[LARDON3D_ACQUISITION_INGEST_MAX_SOURCES]{};
|
||||||
|
for (size_t i = 0; i < source_count; ++i) {
|
||||||
|
if (sources[i].source_path == nullptr || sources[i].source_path[0] == '\0')
|
||||||
|
return LARDON3D_ACQUISITION_INGEST_INVALID_ARGUMENT;
|
||||||
|
if (lardon3d_image_catalog_publish_asset_file(state, sources[i].source_path,
|
||||||
|
options->imported_at, options->max_source_bytes, &published[i].asset) !=
|
||||||
|
LARDON3D_IMAGE_CATALOG_ASSET_PUBLISHED) return LARDON3D_ACQUISITION_INGEST_PUBLISH_ERROR;
|
||||||
|
for (size_t previous = 0; previous < i; ++previous)
|
||||||
|
if (published[previous].asset.asset_id == published[i].asset.asset_id)
|
||||||
|
return LARDON3D_ACQUISITION_INGEST_DUPLICATE_ASSET;
|
||||||
|
int length = std::snprintf(published[i].managed_path, sizeof(published[i].managed_path),
|
||||||
|
"%s/%s", state->project_path, published[i].asset.path);
|
||||||
|
if (length <= 0 || static_cast<size_t>(length) >= sizeof(published[i].managed_path))
|
||||||
|
return LARDON3D_ACQUISITION_INGEST_INVALID_ARGUMENT;
|
||||||
|
if (lardon3d_acquisition_extract_metadata(published[i].managed_path,
|
||||||
|
&published[i].metadata) != LARDON3D_ACQUISITION_OK)
|
||||||
|
return LARDON3D_ACQUISITION_INGEST_METADATA_ERROR;
|
||||||
|
#ifdef LARDON3D_ACQUISITION_INGEST_TESTING
|
||||||
|
if (testing_metadata != nullptr) {
|
||||||
|
if (testing_metadata_count != source_count) return LARDON3D_ACQUISITION_INGEST_INTERNAL_ERROR;
|
||||||
|
published[i].metadata = testing_metadata[i];
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool assigned[LARDON3D_ACQUISITION_INGEST_MAX_SOURCES]{};
|
||||||
|
if (options->grouping == LARDON3D_ACQUISITION_GROUP_CALLER_EXPLICIT) {
|
||||||
|
for (size_t i = 0; i < source_count; ++i) {
|
||||||
|
if (assigned[i]) continue;
|
||||||
|
Lardon3DAcquisitionIngestGroup &group = output->groups[output->group_count++];
|
||||||
|
group.basis = LARDON3D_ACQUISITION_GROUP_EXPLICIT;
|
||||||
|
for (size_t j = i; j < source_count; ++j) {
|
||||||
|
if (sources[j].explicit_group == sources[i].explicit_group) {
|
||||||
|
(void)add_to_group(group, j);
|
||||||
|
assigned[j] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
size_t strong_count[LARDON3D_ACQUISITION_INGEST_MAX_SOURCES]{};
|
||||||
|
size_t strong_partner[LARDON3D_ACQUISITION_INGEST_MAX_SOURCES]{};
|
||||||
|
for (size_t i = 0; i < source_count; ++i) for (size_t j = i + 1; j < source_count; ++j) {
|
||||||
|
Lardon3DAcquisitionPairResult pair{};
|
||||||
|
if (lardon3d_acquisition_compare(&published[i].metadata, &published[j].metadata, 0, 0,
|
||||||
|
&pair) != LARDON3D_ACQUISITION_OK)
|
||||||
|
return LARDON3D_ACQUISITION_INGEST_INTERNAL_ERROR;
|
||||||
|
if (pair.decision == LARDON3D_ACQUISITION_SAME_ACQUISITION_STRONG) {
|
||||||
|
++strong_count[i]; ++strong_count[j]; strong_partner[i] = j; strong_partner[j] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < source_count; ++i) {
|
||||||
|
if (assigned[i]) continue;
|
||||||
|
Lardon3DAcquisitionIngestGroup &group = output->groups[output->group_count++];
|
||||||
|
(void)add_to_group(group, i); assigned[i] = true;
|
||||||
|
size_t partner = strong_partner[i];
|
||||||
|
if (strong_count[i] == 1u && strong_count[partner] == 1u && strong_partner[partner] == i) {
|
||||||
|
(void)add_to_group(group, partner); assigned[partner] = true;
|
||||||
|
group.basis = LARDON3D_ACQUISITION_GROUP_STRONG;
|
||||||
|
} else group.basis = LARDON3D_ACQUISITION_GROUP_SINGLETON;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options->resume_capture_id != 0u && output->group_count != 1u)
|
||||||
|
return LARDON3D_ACQUISITION_INGEST_INVALID_ARGUMENT;
|
||||||
|
|
||||||
|
for (size_t group_index = 0; group_index < output->group_count; ++group_index) {
|
||||||
|
Lardon3DAcquisitionIngestGroup &group = output->groups[group_index];
|
||||||
|
Lardon3DProjectDbCapture capture{};
|
||||||
|
if (options->resume_capture_id != 0u) {
|
||||||
|
database_result = lardon3d_project_db_load_capture(state->project_db,
|
||||||
|
options->resume_capture_id, &capture);
|
||||||
|
if (database_result != LARDON3D_PROJECT_DB_OK) return db_result(database_result);
|
||||||
|
if (capture.scanset_id != scanset_id) return LARDON3D_ACQUISITION_INGEST_CONSTRAINT;
|
||||||
|
} else {
|
||||||
|
database_result = lardon3d_project_db_create_capture(state->project_db, scanset_id,
|
||||||
|
options->imported_at, &capture);
|
||||||
|
if (database_result != LARDON3D_PROJECT_DB_OK) return db_result(database_result);
|
||||||
|
}
|
||||||
|
group.capture_id = capture.capture_id;
|
||||||
|
size_t jpeg = SIZE_MAX, raw = SIZE_MAX;
|
||||||
|
for (size_t member = 0; member < group.source_count; ++member) {
|
||||||
|
size_t index = group.source_indices[member];
|
||||||
|
database_result = lardon3d_project_db_attach_capture_source_asset(
|
||||||
|
state->project_db, capture.capture_id, published[index].asset.asset_id);
|
||||||
|
if (database_result != LARDON3D_PROJECT_DB_OK) return db_result(database_result);
|
||||||
|
if (published[index].metadata.source_kind == LARDON3D_ACQUISITION_SOURCE_JPEG && jpeg == SIZE_MAX)
|
||||||
|
jpeg = index;
|
||||||
|
if (published[index].metadata.source_kind == LARDON3D_ACQUISITION_SOURCE_RAW && raw == SIZE_MAX)
|
||||||
|
raw = index;
|
||||||
|
}
|
||||||
|
bool use_jpeg = jpeg != SIZE_MAX &&
|
||||||
|
(options->representation == LARDON3D_ACQUISITION_SELECT_JPEG_SOURCE || raw == SIZE_MAX);
|
||||||
|
if (use_jpeg) {
|
||||||
|
Lardon3DProjectDbImage image{};
|
||||||
|
Lardon3DProjectDbImageRegisterStatus status{};
|
||||||
|
database_result = lardon3d_project_db_publish_source_capture_image(
|
||||||
|
state->project_db, capture.capture_id, published[jpeg].asset.asset_id,
|
||||||
|
base_name(sources[jpeg].source_path), sources[jpeg].source_path,
|
||||||
|
options->producer_task_id, options->imported_at, options->select_representation != 0u,
|
||||||
|
&status, &image);
|
||||||
|
if (database_result != LARDON3D_PROJECT_DB_OK) return db_result(database_result);
|
||||||
|
} else if (raw != SIZE_MAX) {
|
||||||
|
Lardon3DRawDevelopmentOutput developed{};
|
||||||
|
Lardon3DRawDevelopmentResult result = lardon3d_raw_develop_to_capture(
|
||||||
|
state, capture.capture_id, published[raw].managed_path, options->producer_task_id,
|
||||||
|
options->imported_at, &developed);
|
||||||
|
if (result != LARDON3D_RAW_DEVELOPMENT_OK)
|
||||||
|
return LARDON3D_ACQUISITION_INGEST_DEVELOPMENT_ERROR;
|
||||||
|
if (options->select_representation) {
|
||||||
|
database_result = lardon3d_project_db_set_selected_capture_image(
|
||||||
|
state->project_db, capture.capture_id, developed.image.image_id);
|
||||||
|
if (database_result != LARDON3D_PROJECT_DB_OK) return db_result(database_result);
|
||||||
|
}
|
||||||
|
} else return LARDON3D_ACQUISITION_INGEST_CONSTRAINT;
|
||||||
|
}
|
||||||
|
return LARDON3D_ACQUISITION_INGEST_OK;
|
||||||
|
}
|
||||||
122
src/project_db.c
122
src/project_db.c
|
|
@ -2950,6 +2950,128 @@ Lardon3DProjectDbResult lardon3d_project_db_publish_derived_capture_image(
|
||||||
&(Lardon3DProjectDbImageAsset){0});
|
&(Lardon3DProjectDbImageAsset){0});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_publish_source_capture_image(
|
||||||
|
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t asset_id,
|
||||||
|
const char *original_name, const char *source_path, uint64_t producer_task_id,
|
||||||
|
int64_t imported_at, bool select_image, Lardon3DProjectDbImageRegisterStatus *status,
|
||||||
|
Lardon3DProjectDbImage *image) {
|
||||||
|
if (!database || !valid_catalog_id(capture_id) || !valid_catalog_id(asset_id) ||
|
||||||
|
!valid_original_name(original_name) ||
|
||||||
|
!bounded_text(source_path, LARDON3D_PROJECT_DB_PATH_CAPACITY, false) ||
|
||||||
|
(producer_task_id != 0 && !valid_task_id(producer_task_id)) || imported_at < 0 || !status ||
|
||||||
|
!image) {
|
||||||
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
memset(image, 0, sizeof(*image));
|
||||||
|
*status = LARDON3D_PROJECT_DB_IMAGE_REGISTERED;
|
||||||
|
(void)pthread_mutex_lock(&database->mutex);
|
||||||
|
Lardon3DProjectDbResult result =
|
||||||
|
execute(database, "BEGIN IMMEDIATE", "begin source capture image publication");
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
sqlite3_int64 scanset_id = 0;
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
result = prepare(database, "SELECT scanset_id FROM captures WHERE capture_id=?1", &statement);
|
||||||
|
}
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)capture_id);
|
||||||
|
int code = sqlite3_step(statement);
|
||||||
|
if (code == SQLITE_DONE) result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
||||||
|
else if (code != SQLITE_ROW || (scanset_id = sqlite3_column_int64(statement, 0)) <= 0)
|
||||||
|
result = code == SQLITE_ROW ? LARDON3D_PROJECT_DB_CORRUPT
|
||||||
|
: sqlite_result(database, code, "load source image capture");
|
||||||
|
(void)sqlite3_finalize(statement);
|
||||||
|
statement = NULL;
|
||||||
|
}
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
result = prepare(database, "SELECT 1 FROM image_assets WHERE asset_id=?1 AND state=1", &statement);
|
||||||
|
}
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)asset_id);
|
||||||
|
int code = sqlite3_step(statement);
|
||||||
|
if (code == SQLITE_DONE) result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
||||||
|
else if (code != SQLITE_ROW) result = sqlite_result(database, code, "load source image asset");
|
||||||
|
(void)sqlite3_finalize(statement);
|
||||||
|
statement = NULL;
|
||||||
|
}
|
||||||
|
int inserted = 0;
|
||||||
|
uint64_t image_id = 0;
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
result = insert_or_find_image_locked(database, (uint64_t)scanset_id, asset_id, original_name,
|
||||||
|
source_path, producer_task_id, imported_at, &inserted,
|
||||||
|
&image_id);
|
||||||
|
}
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
result = prepare(database, "SELECT capture_id FROM capture_images WHERE image_id=?1", &statement);
|
||||||
|
}
|
||||||
|
bool attach_image = false;
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)image_id);
|
||||||
|
int code = sqlite3_step(statement);
|
||||||
|
if (code == SQLITE_DONE) attach_image = true;
|
||||||
|
else if (code != SQLITE_ROW) result = sqlite_result(database, code, "find source image capture");
|
||||||
|
else if (sqlite3_column_int64(statement, 0) != (sqlite3_int64)capture_id)
|
||||||
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
||||||
|
(void)sqlite3_finalize(statement);
|
||||||
|
statement = NULL;
|
||||||
|
}
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
result = prepare(database, "SELECT role FROM capture_assets WHERE capture_id=?1 AND asset_id=?2",
|
||||||
|
&statement);
|
||||||
|
}
|
||||||
|
bool attach_asset = false;
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)capture_id);
|
||||||
|
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)asset_id);
|
||||||
|
int code = sqlite3_step(statement);
|
||||||
|
if (code == SQLITE_DONE) attach_asset = true;
|
||||||
|
else if (code != SQLITE_ROW) result = sqlite_result(database, code, "find source capture asset");
|
||||||
|
else if (sqlite3_column_int(statement, 0) != LARDON3D_DB_CAPTURE_ASSET_SOURCE)
|
||||||
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
||||||
|
(void)sqlite3_finalize(statement);
|
||||||
|
statement = NULL;
|
||||||
|
}
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK && attach_asset) {
|
||||||
|
result = prepare(database, "INSERT INTO capture_assets(capture_id,asset_id,role) VALUES(?1,?2,1)",
|
||||||
|
&statement);
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)capture_id);
|
||||||
|
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)asset_id);
|
||||||
|
result = step_done(database, statement, "attach source capture asset");
|
||||||
|
statement = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK && attach_image) {
|
||||||
|
result = prepare(database, "INSERT INTO capture_images(capture_id,image_id) VALUES(?1,?2)", &statement);
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)capture_id);
|
||||||
|
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)image_id);
|
||||||
|
result = step_done(database, statement, "attach source capture image");
|
||||||
|
statement = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK && select_image) {
|
||||||
|
result = prepare(database, "INSERT INTO capture_selections(capture_id,image_id) VALUES(?1,?2) "
|
||||||
|
"ON CONFLICT(capture_id) DO UPDATE SET image_id=excluded.image_id",
|
||||||
|
&statement);
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)capture_id);
|
||||||
|
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)image_id);
|
||||||
|
result = step_done(database, statement, "select source capture image");
|
||||||
|
statement = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK)
|
||||||
|
result = execute(database, "COMMIT", "commit source capture image publication");
|
||||||
|
if (result != LARDON3D_PROJECT_DB_OK)
|
||||||
|
(void)execute(database, "ROLLBACK", "rollback source capture image publication");
|
||||||
|
(void)pthread_mutex_unlock(&database->mutex);
|
||||||
|
if (result != LARDON3D_PROJECT_DB_OK) return result;
|
||||||
|
*status = inserted == 1 ? LARDON3D_PROJECT_DB_IMAGE_REGISTERED
|
||||||
|
: LARDON3D_PROJECT_DB_IMAGE_ALREADY_PRESENT;
|
||||||
|
return lardon3d_project_db_load_image(database, image_id, image,
|
||||||
|
&(Lardon3DProjectDbImageAsset){0});
|
||||||
|
}
|
||||||
|
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_load_image(Lardon3DProjectDb *database,
|
Lardon3DProjectDbResult lardon3d_project_db_load_image(Lardon3DProjectDb *database,
|
||||||
uint64_t image_id,
|
uint64_t image_id,
|
||||||
Lardon3DProjectDbImage *image,
|
Lardon3DProjectDbImage *image,
|
||||||
|
|
|
||||||
263
tests/test_acquisition_ingest.cpp
Normal file
263
tests/test_acquisition_ingest.cpp
Normal file
|
|
@ -0,0 +1,263 @@
|
||||||
|
#include <lardon3d/acquisition_ingest.h>
|
||||||
|
#include <lardon3d/acquisition_pairing.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <lardon3d/image_catalog.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <libexif/exif-data.h>
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <string>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
extern "C" void lardon3d_acquisition_ingest_test_set_metadata(
|
||||||
|
const Lardon3DAcquisitionMetadata *metadata, size_t count);
|
||||||
|
|
||||||
|
#define CHECK(condition) do { \
|
||||||
|
if (!(condition)) { std::fprintf(stderr, "Failure line %d: %s\\n", __LINE__, #condition); return false; } \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool remove_tree(const char *path) {
|
||||||
|
struct stat info {};
|
||||||
|
if (lstat(path, &info) != 0) return errno == ENOENT;
|
||||||
|
if (!S_ISDIR(info.st_mode)) return unlink(path) == 0;
|
||||||
|
DIR *directory = opendir(path);
|
||||||
|
if (directory == nullptr) return false;
|
||||||
|
bool ok = true;
|
||||||
|
for (dirent *entry = readdir(directory); entry != nullptr; entry = readdir(directory)) {
|
||||||
|
if (std::strcmp(entry->d_name, ".") == 0 || std::strcmp(entry->d_name, "..") == 0) continue;
|
||||||
|
char child[4096];
|
||||||
|
const int length = std::snprintf(child, sizeof(child), "%s/%s", path, entry->d_name);
|
||||||
|
if (length <= 0 || static_cast<size_t>(length) >= sizeof(child) || !remove_tree(child)) ok = false;
|
||||||
|
}
|
||||||
|
return closedir(directory) == 0 && rmdir(path) == 0 && ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_ascii(ExifData *data, ExifIfd ifd, ExifTag tag, const char *text) {
|
||||||
|
ExifEntry *entry = exif_entry_new();
|
||||||
|
entry->tag = tag;
|
||||||
|
entry->format = EXIF_FORMAT_ASCII;
|
||||||
|
entry->components = std::strlen(text) + 1u;
|
||||||
|
entry->size = static_cast<unsigned int>(entry->components);
|
||||||
|
entry->data = static_cast<unsigned char *>(std::malloc(entry->size));
|
||||||
|
std::memcpy(entry->data, text, entry->size);
|
||||||
|
exif_content_add_entry(data->ifd[ifd], entry);
|
||||||
|
exif_entry_unref(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool write_jpeg(const char *path, const char *unique_id) {
|
||||||
|
ExifData *data = exif_data_new();
|
||||||
|
if (data == nullptr) return false;
|
||||||
|
exif_data_set_byte_order(data, EXIF_BYTE_ORDER_INTEL);
|
||||||
|
add_ascii(data, EXIF_IFD_0, EXIF_TAG_MODEL, "Lardon test camera");
|
||||||
|
if (unique_id != nullptr) add_ascii(data, EXIF_IFD_EXIF, EXIF_TAG_IMAGE_UNIQUE_ID, unique_id);
|
||||||
|
unsigned char *encoded = nullptr;
|
||||||
|
unsigned int encoded_size = 0u;
|
||||||
|
exif_data_save_data(data, &encoded, &encoded_size);
|
||||||
|
FILE *file = std::fopen(path, "wb");
|
||||||
|
const unsigned char soi[] = {0xffu, 0xd8u, 0xffu, 0xe1u};
|
||||||
|
const unsigned int segment_size = encoded_size + 2u;
|
||||||
|
const unsigned char length[] = {static_cast<unsigned char>(segment_size >> 8u),
|
||||||
|
static_cast<unsigned char>(segment_size)};
|
||||||
|
const unsigned char eoi[] = {0xffu, 0xd9u};
|
||||||
|
const bool ok = file != nullptr && encoded != nullptr && encoded_size != 0u &&
|
||||||
|
segment_size <= UINT16_MAX && std::fwrite(soi, 1, sizeof(soi), file) == sizeof(soi) &&
|
||||||
|
std::fwrite(length, 1, sizeof(length), file) == sizeof(length) &&
|
||||||
|
std::fwrite(encoded, 1, encoded_size, file) == encoded_size &&
|
||||||
|
std::fwrite(eoi, 1, sizeof(eoi), file) == sizeof(eoi) && std::fclose(file) == 0;
|
||||||
|
if (file != nullptr && !ok) std::fclose(file);
|
||||||
|
std::free(encoded);
|
||||||
|
exif_data_unref(data);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Fixture {
|
||||||
|
char root[4096] = "/tmp/lardon3d-ingest-XXXXXX";
|
||||||
|
char database_path[4096]{};
|
||||||
|
Lardon3DProjectDb *database = nullptr;
|
||||||
|
Lardon3DAppState state{};
|
||||||
|
Lardon3DProjectDbScanSet scanset{};
|
||||||
|
|
||||||
|
bool open() {
|
||||||
|
if (mkdtemp(root) == nullptr || std::snprintf(database_path, sizeof(database_path), "%s/project.db", root) <= 0)
|
||||||
|
return false;
|
||||||
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{};
|
||||||
|
if (lardon3d_project_db_open(database_path, &database, error) != LARDON3D_PROJECT_DB_OK) return false;
|
||||||
|
state.project_loaded = true;
|
||||||
|
state.project_db = database;
|
||||||
|
if (std::snprintf(state.project_path, sizeof(state.project_path), "%s", root) <= 0) return false;
|
||||||
|
return lardon3d_project_db_create_scanset(database, "S3-E", &scanset) == LARDON3D_PROJECT_DB_OK;
|
||||||
|
}
|
||||||
|
void close() {
|
||||||
|
lardon3d_acquisition_ingest_test_set_metadata(nullptr, 0u);
|
||||||
|
if (database != nullptr) lardon3d_project_db_close(database);
|
||||||
|
database = nullptr;
|
||||||
|
if (root[0] != '\0') (void)remove_tree(root);
|
||||||
|
}
|
||||||
|
bool path(char output[4096], const char *name) const {
|
||||||
|
const int length = std::snprintf(output, 4096, "%s/%s", root, name);
|
||||||
|
return length > 0 && length < 4096;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Lardon3DAcquisitionIngestOptions options() {
|
||||||
|
Lardon3DAcquisitionIngestOptions value{};
|
||||||
|
value.grouping = LARDON3D_ACQUISITION_GROUP_AUTOMATIC;
|
||||||
|
value.representation = LARDON3D_ACQUISITION_SELECT_JPEG_SOURCE;
|
||||||
|
value.imported_at = 7;
|
||||||
|
value.max_source_bytes = 1024 * 1024;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DAcquisitionMetadata metadata(const char *uid, Lardon3DAcquisitionSourceKind kind = LARDON3D_ACQUISITION_SOURCE_JPEG) {
|
||||||
|
Lardon3DAcquisitionMetadata value{};
|
||||||
|
value.policy_version = LARDON3D_ACQUISITION_PAIRING_POLICY_VERSION;
|
||||||
|
value.source_kind = kind;
|
||||||
|
if (uid != nullptr) {
|
||||||
|
std::strcpy(value.image_unique_id, uid);
|
||||||
|
value.present_fields = LARDON3D_ACQUISITION_FIELD_IMAGE_UNIQUE_ID;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t capture_count(Lardon3DProjectDb *database, uint64_t scanset_id) {
|
||||||
|
Lardon3DProjectDbCapture captures[64]{};
|
||||||
|
size_t count = 0u;
|
||||||
|
return lardon3d_project_db_list_captures(database, scanset_id, 0, captures, 64, &count) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK ? count : SIZE_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool capture_assets(Lardon3DProjectDb *database, uint64_t capture_id, size_t expected) {
|
||||||
|
Lardon3DProjectDbCaptureAsset assets[64]{};
|
||||||
|
size_t count = 0u;
|
||||||
|
if (lardon3d_project_db_list_capture_assets(database, capture_id, 0, assets, 64, &count) !=
|
||||||
|
LARDON3D_PROJECT_DB_OK || count != expected) return false;
|
||||||
|
for (size_t i = 0; i < count; ++i)
|
||||||
|
if (assets[i].role != LARDON3D_DB_CAPTURE_ASSET_SOURCE) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_ingest_grouping_and_resume() {
|
||||||
|
Fixture fixture;
|
||||||
|
CHECK(fixture.open());
|
||||||
|
char a[4096], b[4096], c[4096], d[4096];
|
||||||
|
CHECK(fixture.path(a, "DSC03350.JPG") && fixture.path(b, "DSC03350.ARW") &&
|
||||||
|
fixture.path(c, "other.JPG") && fixture.path(d, "fourth.JPG"));
|
||||||
|
CHECK(write_jpeg(a, "A") && write_jpeg(b, "B") && write_jpeg(c, "C") && write_jpeg(d, "D"));
|
||||||
|
Lardon3DAcquisitionIngestSource sources[] = {{a, 0}, {b, 0}};
|
||||||
|
Lardon3DAcquisitionIngestOutput output{};
|
||||||
|
Lardon3DAcquisitionIngestOptions automatic_options = options();
|
||||||
|
|
||||||
|
/* Same basename and second-level timestamp are not passed as pairing hints. */
|
||||||
|
Lardon3DAcquisitionMetadata weak[] = {metadata(nullptr), metadata(nullptr)};
|
||||||
|
lardon3d_acquisition_ingest_test_set_metadata(weak, 2);
|
||||||
|
CHECK(lardon3d_acquisition_ingest(&fixture.state, fixture.scanset.scanset_id, sources, 2,
|
||||||
|
&automatic_options, &output) == LARDON3D_ACQUISITION_INGEST_OK);
|
||||||
|
CHECK(output.group_count == 2 && output.groups[0].basis == LARDON3D_ACQUISITION_GROUP_SINGLETON &&
|
||||||
|
output.groups[1].basis == LARDON3D_ACQUISITION_GROUP_SINGLETON && capture_count(fixture.database, fixture.scanset.scanset_id) == 2);
|
||||||
|
|
||||||
|
/* Controlled S3-D evidence: distinct source kinds, one unique mutual strong pair. */
|
||||||
|
Fixture strong;
|
||||||
|
CHECK(strong.open());
|
||||||
|
char raw_like[4096], jpeg[4096];
|
||||||
|
CHECK(strong.path(raw_like, "raw-like.bin") && strong.path(jpeg, "camera.JPG") &&
|
||||||
|
write_jpeg(raw_like, "physical-one") && write_jpeg(jpeg, "physical-two"));
|
||||||
|
Lardon3DAcquisitionIngestSource pair[] = {{raw_like, 0}, {jpeg, 0}};
|
||||||
|
Lardon3DAcquisitionMetadata same[] = {metadata("SHUTTER", LARDON3D_ACQUISITION_SOURCE_RAW),
|
||||||
|
metadata("SHUTTER", LARDON3D_ACQUISITION_SOURCE_JPEG)};
|
||||||
|
lardon3d_acquisition_ingest_test_set_metadata(same, 2);
|
||||||
|
Lardon3DAcquisitionIngestOutput paired{};
|
||||||
|
Lardon3DAcquisitionIngestOptions jpeg_options = options();
|
||||||
|
CHECK(lardon3d_acquisition_ingest(&strong.state, strong.scanset.scanset_id, pair, 2,
|
||||||
|
&jpeg_options, &paired) == LARDON3D_ACQUISITION_INGEST_OK);
|
||||||
|
CHECK(paired.group_count == 1 && paired.groups[0].basis == LARDON3D_ACQUISITION_GROUP_STRONG &&
|
||||||
|
paired.groups[0].source_count == 2 && capture_count(strong.database, strong.scanset.scanset_id) == 1 &&
|
||||||
|
capture_assets(strong.database, paired.groups[0].capture_id, 2));
|
||||||
|
|
||||||
|
/* A competing equal strong candidate remains entirely ungrouped. */
|
||||||
|
Fixture ambiguous;
|
||||||
|
CHECK(ambiguous.open());
|
||||||
|
char paths[3][4096];
|
||||||
|
const char *physical_ids[] = {"physical-a", "physical-b", "physical-c"};
|
||||||
|
for (size_t i = 0; i < 3; ++i) {
|
||||||
|
CHECK(ambiguous.path(paths[i], i == 0 ? "a.JPG" : i == 1 ? "b.JPG" : "c.JPG"));
|
||||||
|
CHECK(write_jpeg(paths[i], physical_ids[i]));
|
||||||
|
}
|
||||||
|
Lardon3DAcquisitionIngestSource triple[] = {{paths[0], 0}, {paths[1], 0}, {paths[2], 0}};
|
||||||
|
Lardon3DAcquisitionMetadata tied[] = {metadata("TIE"), metadata("TIE"), metadata("TIE")};
|
||||||
|
lardon3d_acquisition_ingest_test_set_metadata(tied, 3);
|
||||||
|
Lardon3DAcquisitionIngestOutput ambiguous_output{};
|
||||||
|
CHECK(lardon3d_acquisition_ingest(&ambiguous.state, ambiguous.scanset.scanset_id, triple, 3,
|
||||||
|
&jpeg_options, &ambiguous_output) == LARDON3D_ACQUISITION_INGEST_OK);
|
||||||
|
CHECK(ambiguous_output.group_count == 3 && capture_count(ambiguous.database, ambiguous.scanset.scanset_id) == 3);
|
||||||
|
for (size_t i = 0; i < 3; ++i) CHECK(ambiguous_output.groups[i].basis == LARDON3D_ACQUISITION_GROUP_SINGLETON);
|
||||||
|
|
||||||
|
/* Resume is only the known Capture ID and converges image/source state. */
|
||||||
|
Fixture resumed;
|
||||||
|
CHECK(resumed.open());
|
||||||
|
char resume_file[4096]; CHECK(resumed.path(resume_file, "resume.JPG") && write_jpeg(resume_file, "resume"));
|
||||||
|
Lardon3DProjectDbCapture existing{};
|
||||||
|
CHECK(lardon3d_project_db_create_capture(resumed.database, resumed.scanset.scanset_id, 7, &existing) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
Lardon3DAcquisitionIngestSource one[] = {{resume_file, 0}};
|
||||||
|
Lardon3DAcquisitionMetadata one_metadata[] = {metadata("RESUME")};
|
||||||
|
Lardon3DAcquisitionIngestOptions resume_options = options(); resume_options.resume_capture_id = existing.capture_id;
|
||||||
|
lardon3d_acquisition_ingest_test_set_metadata(one_metadata, 1);
|
||||||
|
Lardon3DAcquisitionIngestOutput first{};
|
||||||
|
CHECK(lardon3d_acquisition_ingest(&resumed.state, resumed.scanset.scanset_id, one, 1, &resume_options, &first) == LARDON3D_ACQUISITION_INGEST_OK && first.groups[0].capture_id == existing.capture_id);
|
||||||
|
Lardon3DAcquisitionIngestOutput retry{};
|
||||||
|
CHECK(lardon3d_acquisition_ingest(&resumed.state, resumed.scanset.scanset_id, one, 1, &resume_options, &retry) == LARDON3D_ACQUISITION_INGEST_OK && capture_count(resumed.database, resumed.scanset.scanset_id) == 1 && capture_assets(resumed.database, existing.capture_id, 1));
|
||||||
|
uint64_t images = 0; CHECK(lardon3d_project_db_count_images(resumed.database, resumed.scanset.scanset_id, &images) == LARDON3D_PROJECT_DB_OK && images == 1);
|
||||||
|
|
||||||
|
fixture.close(); strong.close(); ambiguous.close(); resumed.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_explicit_and_db_helper() {
|
||||||
|
Fixture fixture;
|
||||||
|
CHECK(fixture.open());
|
||||||
|
char a[4096], b[4096], c[4096], helper_path[4096];
|
||||||
|
CHECK(fixture.path(a, "a.JPG") && fixture.path(b, "b.JPG") && fixture.path(c, "c.JPG") &&
|
||||||
|
fixture.path(helper_path, "helper.JPG") && write_jpeg(a, "a") && write_jpeg(b, "b") &&
|
||||||
|
write_jpeg(c, "c") && write_jpeg(helper_path, "helper"));
|
||||||
|
Lardon3DAcquisitionIngestSource inputs[] = {{a, 9}, {b, 9}, {c, 10}};
|
||||||
|
Lardon3DAcquisitionIngestOptions explicit_options = options();
|
||||||
|
explicit_options.grouping = LARDON3D_ACQUISITION_GROUP_CALLER_EXPLICIT;
|
||||||
|
Lardon3DAcquisitionIngestOutput output{};
|
||||||
|
CHECK(lardon3d_acquisition_ingest(&fixture.state, fixture.scanset.scanset_id, inputs, 3,
|
||||||
|
&explicit_options, &output) == LARDON3D_ACQUISITION_INGEST_OK);
|
||||||
|
CHECK(output.group_count == 2 && output.groups[0].basis == LARDON3D_ACQUISITION_GROUP_EXPLICIT &&
|
||||||
|
output.groups[0].source_count == 2 && output.groups[1].basis == LARDON3D_ACQUISITION_GROUP_EXPLICIT);
|
||||||
|
|
||||||
|
Lardon3DProjectDbCapture capture{}, foreign{};
|
||||||
|
CHECK(lardon3d_project_db_create_capture(fixture.database, fixture.scanset.scanset_id, 8, &capture) == LARDON3D_PROJECT_DB_OK &&
|
||||||
|
lardon3d_project_db_create_capture(fixture.database, fixture.scanset.scanset_id, 9, &foreign) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
Lardon3DProjectDbImageAsset asset{};
|
||||||
|
CHECK(lardon3d_image_catalog_publish_asset_file(&fixture.state, helper_path, 8, UINT64_MAX, &asset) == LARDON3D_IMAGE_CATALOG_ASSET_PUBLISHED);
|
||||||
|
Lardon3DProjectDbImageRegisterStatus status{}; Lardon3DProjectDbImage image{};
|
||||||
|
CHECK(lardon3d_project_db_publish_source_capture_image(fixture.database, capture.capture_id, asset.asset_id,
|
||||||
|
"helper.JPG", helper_path, 0, 8, false, &status, &image) == LARDON3D_PROJECT_DB_OK && status == LARDON3D_PROJECT_DB_IMAGE_REGISTERED);
|
||||||
|
uint64_t selected = 0;
|
||||||
|
CHECK(lardon3d_project_db_get_selected_capture_image(fixture.database, capture.capture_id, &selected) == LARDON3D_PROJECT_DB_NOT_FOUND);
|
||||||
|
CHECK(lardon3d_project_db_publish_source_capture_image(fixture.database, capture.capture_id, asset.asset_id,
|
||||||
|
"renamed-on-resume.JPG", "/relocated/source.JPG", 0, 99, true, &status, &image) == LARDON3D_PROJECT_DB_OK && status == LARDON3D_PROJECT_DB_IMAGE_ALREADY_PRESENT &&
|
||||||
|
lardon3d_project_db_get_selected_capture_image(fixture.database, capture.capture_id, &selected) == LARDON3D_PROJECT_DB_OK && selected == image.image_id);
|
||||||
|
CHECK(lardon3d_project_db_publish_source_capture_image(fixture.database, foreign.capture_id, asset.asset_id,
|
||||||
|
"helper.JPG", helper_path, 0, 8, false, &status, &image) == LARDON3D_PROJECT_DB_CONSTRAINT);
|
||||||
|
fixture.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
return test_ingest_grouping_and_resume() && test_explicit_and_db_helper() ? 0 : 1;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue