LensSerious 0.1
Lens-correction mathematics as data, not as a library of callbacks
Loading...
Searching...
No Matches
lensserious_db.c File Reference
#include "lensserious_db.h"
#include <sqlite3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+ Include dependency graph for lensserious_db.c:

Go to the source code of this file.

Data Structures

struct  ls_db_t
 
struct  ls_tokens_t
 A tokenised name, with everything the comparison needs precomputed. More...
 

Macros

#define LS_DB_SCHEMA_VERSION   4
 

Enumerations

enum  {
  LS_MAX_TOKENS = 32 ,
  LS_TOKEN_LEN = 48
}
 

Functions

static void _db_err (ls_db_t *db, const char *what)
 
static char * _db_build_uri (const char *path)
 Build the URI this library insists on, whatever the caller passed.
 
ls_db_tls_db_open (const char *path)
 Open a database for reading.
 
void ls_db_close (ls_db_t *db)
 Release a handle. Safe on NULL.
 
const char * ls_db_error (const ls_db_t *db)
 The last error on db, as a string owned by db, or NULL.
 
int ls_db_schema_version (const ls_db_t *db)
 Schema version of the open file, or -1. Bumped when the layout changes.
 
size_t ls_db_normalize (const char *in, char *out, size_t out_size)
 Case-fold ASCII, drop punctuation, collapse whitespace.
 
static int _fill_calibrations (ls_db_t *db, long long lens_id, ls_lens_t *out)
 
int ls_db_lens_by_id (ls_db_t *db, long long lens_id, ls_lens_t *out)
 Load a lens by its database id, for a caller that already resolved one.
 
static long long _find_lens_id (ls_db_t *db, const char *maker, const char *model, float crop)
 The lens id whose names match, preferring the calibration crop nearest crop.
 
int ls_db_find_lens (ls_db_t *db, const char *maker, const char *model, float crop, ls_lens_t *out)
 Find a lens by maker and model, and fill out with its coefficients.
 
int ls_db_find_camera (ls_db_t *db, const char *maker, const char *model, ls_camera_t *out)
 Find a camera by maker and model.
 
int ls_db_lens_fits_mount (ls_db_t *db, long long lens_id, long long mount_id)
 Does a lens fit a camera's mount, upstream's compatibility table included?
 
int ls_db_list_lenses (ls_db_t *db, long long *out_ids, int max)
 Enumerate lens ids, oldest-inserted first, for tests and for a GUI's lens picker.
 
int ls_db_lens_name (ls_db_t *db, long long lens_id, char *maker, size_t maker_size, char *model, size_t model_size)
 The lens's maker/model, as stored.
 
int ls_db_lens_range (ls_db_t *db, long long lens_id, float *min_focal, float *max_focal, float *min_aperture, float *max_aperture)
 The lens's focal and aperture range, for a picker that lists it.
 
int ls_db_lens_mounts (ls_db_t *db, long long lens_id, char *out, size_t out_size)
 The mounts a lens is made for, joined with ", ".
 
int ls_db_list_cameras (ls_db_t *db, long long *out_ids, int max)
 Enumerate camera ids, for a GUI's camera picker.
 
int ls_db_camera_name (ls_db_t *db, long long camera_id, char *maker, size_t maker_size, char *model, size_t model_size, char *variant, size_t variant_size)
 A camera's maker, model and variant, as stored.
 
int ls_db_camera_by_id (ls_db_t *db, long long camera_id, ls_camera_t *out)
 Load a camera by its database id, for a caller that already resolved one.
 
int ls_db_mount_name (ls_db_t *db, long long mount_id, char *out, size_t out_size)
 A mount's name.
 
int ls_db_lenses_for_mount (ls_db_t *db, long long mount_id, long long *out_ids, int max)
 The lenses made for one mount.
 
int ls_db_meta (ls_db_t *db, const char *key, char *out, size_t out_size)
 A meta value by key (built_utc, source, lensfun_db_version, ...).
 
int ls_db_tokenize (const char *norm, char *out_tokens, int max, int stride)
 Split a normalised name on spaces, and split letter/digit runs apart.
 
unsigned ls_db_token_hash (const char *token)
 FNV-1a of a token, the hash the stored digest and the matcher both use.
 
size_t ls_db_token_digest (const char *norm, unsigned char *out, size_t out_size)
 Pack a normalised name's tokens into the digest stored in lens_name.tokens.
 
static int _digest_load (const unsigned char *blob, int bytes, ls_tokens_t *out)
 Read a digest back into the arrays _score_tokens() compares.
 
static void _tokenize (const char *norm, ls_tokens_t *out)
 
static float _score_tokens (const ls_tokens_t *pat, const ls_tokens_t *cand)
 How well pat's tokens are covered by cand's, 0..100.
 
static void _parse_focal_range (const char *name, float *minf, float *maxf)
 Pull the focal range out of a lens NAME, the way upstream does.
 
static int _compare_num (const float a, const float b)
 Upstream's _lf_compare_num(): a numeric field as a filter, not a score.
 
static float _crop_score (const float cam, const float calib)
 Upstream's crop-factor rule, as a bonus ADDED to a candidate's score.
 
int ls_db_match_lens (ls_db_t *db, const char *maker, const char *model, long long mount_id, float crop, ls_db_match_t *out, int max)
 Find the lenses a free-text name most likely refers to.
 

Macro Definition Documentation

◆ LS_DB_SCHEMA_VERSION

#define LS_DB_SCHEMA_VERSION   4

Definition at line 19 of file lensserious_db.c.

Referenced by ls_db_open().

Enumeration Type Documentation

◆ anonymous enum

anonymous enum
Enumerator
LS_MAX_TOKENS 
LS_TOKEN_LEN 

Definition at line 730 of file lensserious_db.c.

Function Documentation

◆ _compare_num()

static int _compare_num ( const float a,
const float b )
static

Upstream's _lf_compare_num(): a numeric field as a filter, not a score.

Returns
-1 reject, +1 a strong yes, 0 no opinion (either side unknown).

Definition at line 999 of file lensserious_db.c.

Referenced by ls_db_match_lens().

◆ _crop_score()

static float _crop_score ( const float cam,
const float calib )
static

Upstream's crop-factor rule, as a bonus ADDED to a candidate's score.

Parameters
camthe camera's crop factor; 0 to ignore the rule entirely.
calibthe crop factor of the sensor the lens was CALIBRATED on.
Returns
-1 to reject the candidate outright, otherwise a bonus to add.

Ported from _lf_lens_compare_score() (lens.cpp), including its numbers. A calibration measured on a sensor more than 4% larger than the camera's is rejected: it does not cover the frame, and no amount of name similarity makes it the right answer. What survives is graded, and the grading is not monotonic in the obvious direction – the peak is a camera slightly SMALLER than the calibration sensor (1.01x to 1.11x), not an exact match, because a calibration measured on a marginally wider frame covers the whole of this one.

ADDED, not multiplied, and that distinction was measured rather than assumed. Upstream's crop term is 2 to 10 against a total in the tens, so it breaks ties between similarly named lenses without overriding the name. Scaling a 0..100 token score by it instead lets it dominate: a lens whose name matched 90% on a 1.534 sensor beat an EXACT name match on a 1.611 one, 90 against 50, and the whole-database check fell to 94.1%. Upstream's own numbers, on the same side of the operator upstream puts them, are the right port.

Definition at line 1028 of file lensserious_db.c.

Referenced by ls_db_match_lens().

◆ _db_build_uri()

static char * _db_build_uri ( const char * path)
static

Build the URI this library insists on, whatever the caller passed.

mode=ro and immutable=1 are not the caller's to choose: every promise in the header rests on them. A caller's own parameters are kept, so a path on a network share can still carry e.g. nolock=1.

A bare path has to be percent-encoded before it can become a URI – '?' and '#' in a directory name would otherwise be read as the start of the query, and SQLite would open some other file, or none.

Definition at line 46 of file lensserious_db.c.

Referenced by ls_db_open().

◆ _db_err()

◆ _digest_load()

static int _digest_load ( const unsigned char * blob,
int bytes,
ls_tokens_t * out )
static

Read a digest back into the arrays _score_tokens() compares.

Returns
token count.

Definition at line 817 of file lensserious_db.c.

References ls_tokens_t::bloom, ls_tokens_t::h, ls_tokens_t::len, LS_MAX_TOKENS, and ls_tokens_t::n.

Referenced by ls_db_match_lens().

◆ _fill_calibrations()

◆ _find_lens_id()

static long long _find_lens_id ( ls_db_t * db,
const char * maker,
const char * model,
float crop )
static

The lens id whose names match, preferring the calibration crop nearest crop.

Returns
the id, 0 for no match, -1 on error.

Definition at line 332 of file lensserious_db.c.

References _db_err(), ls_db_normalize(), and ls_db_t::sql.

Referenced by ls_db_find_lens().

◆ _parse_focal_range()

static void _parse_focal_range ( const char * name,
float * minf,
float * maxf )
static

Pull the focal range out of a lens NAME, the way upstream does.

Parameters
namethe RAW name, as the caller supplied it. Not the normalised form: that collapses punctuation, and the dash in "16-35mm" is load-bearing here.
minf,maxffilled with the focal range in mm, or left at 0 when the name carries none.

lensfun's FindLenses() runs GuessParameters() on the query string, which regexes the focal length and aperture out of it and then uses them as HARD FILTERS: a candidate whose focal range differs by more than 1% is rejected outright (_lf_compare_num returns -1 and the score becomes 0). Without this, a query is only a bag of tokens, and "Sigma 50mm f/1.4 DG HSM" can resolve to a Sigma 20mm f/1.4 DG HSM – measured, on a real image, with the calibration silently wrong by a factor of 2.5 in focal length.

Hand-scanned rather than regexed on purpose: <regex.h> is POSIX and this has to build with MSVC. The rule implemented is upstream's first and by far most common pattern – a number, optionally a dash and a second number, then "mm" – which is what real EXIF lens names carry. Names it cannot read leave the range at 0, which the filter treats as "no opinion", exactly as upstream's neutral case does. Agreement is measured over the whole database by tests/match_lensfun.c rather than argued from the regex.

Definition at line 934 of file lensserious_db.c.

Referenced by ls_db_match_lens().

◆ _score_tokens()

static float _score_tokens ( const ls_tokens_t * pat,
const ls_tokens_t * cand )
static

How well pat's tokens are covered by cand's, 0..100.

Asymmetric on purpose. The pattern is what the camera wrote and the candidate is the catalogue entry, so every pattern token ought to appear in the candidate – missing one is evidence against the match. The reverse is not true: the catalogue is more verbose than any EXIF field, and penalising it for that would favour the shortest name in the database for every query.

Unmatched candidate tokens are still worth a small penalty, or "35mm" would score the same against every 35mm lens ever made; it is just much smaller than the forward one.

Definition at line 865 of file lensserious_db.c.

References ls_tokens_t::h, ls_tokens_t::len, LS_MAX_TOKENS, and ls_tokens_t::n.

Referenced by ls_db_match_lens().

◆ _tokenize()

static void _tokenize ( const char * norm,
ls_tokens_t * out )
static

◆ ls_db_camera_by_id()

int ls_db_camera_by_id ( ls_db_t * db,
long long camera_id,
ls_camera_t * out )

Load a camera by its database id, for a caller that already resolved one.

Definition at line 616 of file lensserious_db.c.

References _db_err(), ls_camera_t::crop_factor, ls_camera_t::id, ls_camera_t::mount_id, and ls_db_t::sql.

◆ ls_db_camera_name()

int ls_db_camera_name ( ls_db_t * db,
long long camera_id,
char * maker,
size_t maker_size,
char * model,
size_t model_size,
char * variant,
size_t variant_size )

A camera's maker, model and variant, as stored.

Parameters
dban open database.
camera_idthe camera.
maker,model,variantcaller's buffers; any may be NULL. variant is set to the empty string for the cameras that have none, which is most of them.
maker_size,model_size,variant_sizetheir sizes.
Returns
1 when the camera exists, 0 when it does not, -1 on error.

Definition at line 585 of file lensserious_db.c.

References _db_err(), and ls_db_t::sql.

◆ ls_db_close()

void ls_db_close ( ls_db_t * db)

Release a handle. Safe on NULL.

Definition at line 126 of file lensserious_db.c.

References ls_db_t::sql.

Referenced by ls_db_open().

◆ ls_db_error()

const char * ls_db_error ( const ls_db_t * db)

The last error on db, as a string owned by db, or NULL.

Definition at line 133 of file lensserious_db.c.

References ls_db_t::error.

◆ ls_db_find_camera()

int ls_db_find_camera ( ls_db_t * db,
const char * maker,
const char * model,
ls_camera_t * out )

Find a camera by maker and model.

Matching is exact on the normalised form of each name – case-folded, with punctuation and runs of whitespace collapsed – against every spelling the database holds for that camera, translations included. maker may be NULL to search on the model alone.

This is NOT lensfun's fuzzy scorer, which also tolerates missing words, reordered tokens and vendor prefixes. Porting it is separate work; until then a caller that needs that behaviour should treat a miss here as "not found" rather than as "no such camera".

Returns
1 on a match, 0 if nothing matched, -1 on error.

Definition at line 374 of file lensserious_db.c.

References _db_err(), ls_camera_t::crop_factor, ls_camera_t::id, ls_db_normalize(), ls_camera_t::mount_id, and ls_db_t::sql.

◆ ls_db_find_lens()

int ls_db_find_lens ( ls_db_t * db,
const char * maker,
const char * model,
float crop,
ls_lens_t * out )

Find a lens by maker and model, and fill out with its coefficients.

Same matching as ls_db_find_camera(). When several lenses share a name – upstream distinguishes them by the sensor they were calibrated on – the one whose crop factor is closest to crop wins, matching how a caller would pick by hand; pass 0 to take the first.

out is filled completely, calibration arrays included, and is thereafter independent of db: the handle may be closed and the value stays valid. That is the whole shape of this library – a lens is data, not a handle into a database.

Returns
1 on a match, 0 if nothing matched, -1 on error (including a lens carrying more than LS_MAX_CALIB entries of any kind, which is a database this build cannot represent).

Definition at line 363 of file lensserious_db.c.

References _find_lens_id(), ls_db_lens_by_id(), and ls_db_t::sql.

◆ ls_db_lens_by_id()

int ls_db_lens_by_id ( ls_db_t * db,
long long lens_id,
ls_lens_t * out )

Load a lens by its database id, for a caller that already resolved one.

Definition at line 295 of file lensserious_db.c.

References _db_err(), _fill_calibrations(), ls_lens_t::aspect_ratio, ls_lens_t::center_x, ls_lens_t::center_y, ls_lens_t::crop_factor, ls_lens_t::max_focal, ls_lens_t::min_focal, ls_db_t::sql, and ls_lens_t::type.

Referenced by ls_db_find_lens().

◆ ls_db_lens_fits_mount()

int ls_db_lens_fits_mount ( ls_db_t * db,
long long lens_id,
long long mount_id )

Does a lens fit a camera's mount, upstream's compatibility table included?

Returns
1 yes, 0 no, -1 on error.

Definition at line 411 of file lensserious_db.c.

References _db_err(), and ls_db_t::sql.

◆ ls_db_lens_mounts()

int ls_db_lens_mounts ( ls_db_t * db,
long long lens_id,
char * out,
size_t out_size )

The mounts a lens is made for, joined with ", ".

Parameters
dban open database.
lens_idthe lens.
outcaller's buffer; always NUL-terminated when out_size is non-zero.
out_sizeits size.
Returns
the number of mounts written, or -1 on error.
Note
These are the lens's OWN mounts, not the mounts it is compatible with. Asking whether a particular camera can take it is ls_db_lens_fits_mount(), which does consult upstream's compatibility table; this is the descriptive string a picker shows.

Definition at line 527 of file lensserious_db.c.

References _db_err(), and ls_db_t::sql.

◆ ls_db_lens_name()

int ls_db_lens_name ( ls_db_t * db,
long long lens_id,
char * maker,
size_t maker_size,
char * model,
size_t model_size )

The lens's maker/model, as stored.

Returns
bytes written, or -1.

Definition at line 460 of file lensserious_db.c.

References _db_err(), and ls_db_t::sql.

◆ ls_db_lens_range()

int ls_db_lens_range ( ls_db_t * db,
long long lens_id,
float * min_focal,
float * max_focal,
float * min_aperture,
float * max_aperture )

The lens's focal and aperture range, for a picker that lists it.

Parameters
dban open database.
lens_idthe lens.
min_focal,max_focal,min_aperture,max_aperturefilled in; any may be NULL.
Returns
1 when the lens exists, 0 when it does not, -1 on error.

Definition at line 496 of file lensserious_db.c.

References _db_err(), and ls_db_t::sql.

◆ ls_db_lenses_for_mount()

int ls_db_lenses_for_mount ( ls_db_t * db,
long long mount_id,
long long * out_ids,
int max )

The lenses made for one mount.

Parameters
dban open database.
mount_idthe mount.
out_idscaller's array, or NULL to count only.
maxthe length of out_ids.
Returns
how many ids were written (or how many exist, if out_ids is NULL), or -1.
Note
The lens's OWN mounts, without upstream's compatibility table. A fixed-lens camera has a mount of its own and exactly the lenses built into it, and widening that to compatible mounts would answer with lenses that physically cannot be on the camera.

Definition at line 668 of file lensserious_db.c.

References _db_err(), and ls_db_t::sql.

◆ ls_db_list_cameras()

int ls_db_list_cameras ( ls_db_t * db,
long long * out_ids,
int max )

Enumerate camera ids, for a GUI's camera picker.

Parameters
dban open database.
out_idscaller's array, or NULL to count only.
maxthe length of out_ids.
Returns
how many ids were written (or how many exist, if out_ids is NULL), or -1.

Definition at line 564 of file lensserious_db.c.

References _db_err(), and ls_db_t::sql.

◆ ls_db_list_lenses()

int ls_db_list_lenses ( ls_db_t * db,
long long * out_ids,
int max )

Enumerate lens ids, oldest-inserted first, for tests and for a GUI's lens picker.

Parameters
dban open database.
out_idscaller's array, or NULL to count only.
maxthe length of out_ids.
Returns
how many ids were written (or how many exist, if out_ids is NULL), or -1.

Definition at line 436 of file lensserious_db.c.

References _db_err(), and ls_db_t::sql.

◆ ls_db_match_lens()

int ls_db_match_lens ( ls_db_t * db,
const char * maker,
const char * model,
long long mount_id,
float crop,
ls_db_match_t * out,
int max )

Find the lenses a free-text name most likely refers to.

ls_db_find_lens() answers "is there a lens called exactly this". This answers the question a raw file actually asks, where the EXIF string is a vendor's abbreviation of the name upstream chose – "16-35mm f/4G ED VR" against "Nikon AF-S Nikkor 16-35mm f/4G ED VR", with tokens missing, reordered, or spelled differently.

Scoring is token-based and deliberately simple; see the implementation for what each weight is and why. It is calibrated against liblensfun's own decisions rather than against a specification: tests/match_lensfun.c asks both this and lf_db_find_lenses_hd() the same questions over the whole database and reports where they disagree.

Parameters
makermay be NULL. When given it is scored, not required – vendors disagree with upstream about their own name often enough that requiring it loses more than it saves.
mount_idwhen > 0, only lenses that fit this mount are considered (ls_db_find_camera() supplies it). 0 considers every lens.
cropthe CAMERA's crop factor (ls_db_find_camera() supplies that too), or 0 to ignore it. It is not a tie-break: upstream REJECTS a lens whose calibration sensor is more than 4% larger than the camera's – such a calibration does not cover the frame – and then grades what is left by how closely the two match. Several lenses share a name and differ only in the sensor they were calibrated on, so without this the matcher can return a name-identical lens with the wrong calibration: measured on a Nikon D5300 (crop 1.534), a full-frame row instead of the 1.528 one, and 0.19 px of geometry.
dban open database.
modelthe free-text name to resolve. Required.
outcaller's array, max entries, filled best-first.
maxthe length of out.
Returns
how many candidates were written, or -1 on error.

Definition at line 1042 of file lensserious_db.c.

References _compare_num(), _crop_score(), _db_err(), _digest_load(), _parse_focal_range(), _score_tokens(), _tokenize(), ls_tokens_t::bloom, ls_db_match_t::lens_id, ls_db_normalize(), LS_MAX_TOKENS, LS_TOKEN_LEN, ls_tokens_t::n, ls_db_match_t::score, ls_db_t::sql, and ls_tokens_t::t.

◆ ls_db_meta()

int ls_db_meta ( ls_db_t * db,
const char * key,
char * out,
size_t out_size )

A meta value by key (built_utc, source, lensfun_db_version, ...).

Returns
bytes written excluding the terminator, or -1 if absent.

Definition at line 691 of file lensserious_db.c.

References _db_err(), and ls_db_t::sql.

◆ ls_db_mount_name()

int ls_db_mount_name ( ls_db_t * db,
long long mount_id,
char * out,
size_t out_size )

A mount's name.

Parameters
dban open database.
mount_idthe mount, as ls_db_find_camera() reported it.
outcaller's buffer; always NUL-terminated when out_size is non-zero.
out_sizeits size.
Returns
1 when the mount exists, 0 when it does not, -1 on error.
Note
Upstream encodes a real fact in the SPELLING: a mount whose name starts with a lower-case letter belongs to a fixed-lens camera. That is how a consumer tells a compact from an interchangeable-lens body, so the name has to be reachable, not just its id.

Definition at line 641 of file lensserious_db.c.

References _db_err(), and ls_db_t::sql.

◆ ls_db_normalize()

size_t ls_db_normalize ( const char * in,
char * out,
size_t out_size )

Case-fold ASCII, drop punctuation, collapse whitespace.

The comparison form of a name: ASCII case-folded, punctuation dropped, runs of whitespace collapsed to one space.

Deliberately ASCII-only and byte-wise: it must produce the same bytes in the importer and here, on every platform, without a locale or an ICU version in the middle of it. Non-ASCII bytes pass through untouched, so a UTF-8 maker name still matches itself; it just does not case-fold, which no upstream name needs.

Definition at line 156 of file lensserious_db.c.

Referenced by _find_lens_id(), insert_name(), ls_db_find_camera(), and ls_db_match_lens().

◆ ls_db_open()

ls_db_t * ls_db_open ( const char * path)

Open a database for reading.

Parameters
patha filesystem path, or a file: URI. Either way it is opened read-only and immutable; any query parameters the caller supplies in a URI are preserved, but mode and immutable are forced.
Returns
NULL if the file cannot be opened or is not a LensSerious database of a schema version this build understands. Never fails for concurrency reasons: there are none.

Definition at line 87 of file lensserious_db.c.

References _db_build_uri(), ls_db_close(), LS_DB_SCHEMA_VERSION, ls_db_t::schema_version, and ls_db_t::sql.

◆ ls_db_schema_version()

int ls_db_schema_version ( const ls_db_t * db)

Schema version of the open file, or -1. Bumped when the layout changes.

Definition at line 138 of file lensserious_db.c.

References ls_db_t::schema_version.

◆ ls_db_token_digest()

size_t ls_db_token_digest ( const char * norm,
unsigned char * out,
size_t out_size )

Pack a normalised name's tokens into the digest stored in lens_name.tokens.

Layout: uint16 n, then n x uint32 hash, then n x uint8 length. The matcher scores on those alone – it never sees the token text – which is what lets the candidate query read a blob out of a covering index instead of decoding and re-tokenising text on every lookup.

Returns
bytes written, or 0 if out is too small.

Definition at line 794 of file lensserious_db.c.

References ls_db_token_hash(), ls_db_tokenize(), LS_MAX_TOKENS, and LS_TOKEN_LEN.

Referenced by insert_name().

◆ ls_db_token_hash()

unsigned ls_db_token_hash ( const char * token)

FNV-1a of a token, the hash the stored digest and the matcher both use.

Definition at line 783 of file lensserious_db.c.

Referenced by _tokenize(), and ls_db_token_digest().

◆ ls_db_tokenize()

int ls_db_tokenize ( const char * norm,
char * out_tokens,
int max,
int stride )

Split a normalised name on spaces, and split letter/digit runs apart.

The tokens ls_db_match_lens() compares, from an already-normalised name.

"16-35mm" normalises to "1635mm" – no. Normalisation drops '-', so the two halves of a focal range would fuse into one meaningless token and stop matching the catalogue's "16 35mm". Splitting where a digit meets a letter, and where a letter meets a digit, keeps "16", "35", "mm", "f", "4g" as separate comparable units.

Definition at line 758 of file lensserious_db.c.

Referenced by _tokenize(), insert_name(), and ls_db_token_digest().