Skip to main content

cumulus3d_features/
lib.rs

1/*
2 * lib.rs
3 *
4 * Copyright (c) 2026 ParkSangWoo
5 *
6 * Author(s):
7 *
8 *      ParkSangWoo <dev.parksangwoo@gmail.com>
9 *
10 *
11 * This file is part of cumulus3d.
12 *
13 * Licensed under the Apache License, Version 2.0 (the "License");
14 * you may not use this file except in compliance with the License.
15 * You may obtain a copy of the License at
16 *
17 *      http://www.apache.org/licenses/LICENSE-2.0
18 *
19 * Unless required by applicable law or agreed to in writing, software
20 * distributed under the License is distributed on an "AS IS" BASIS,
21 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 * See the License for the specific language governing permissions and
23 * limitations under the License.
24 *
25 * SPDX-License-Identifier: Apache-2.0
26 */
27
28//! Image reading and SIFT feature extraction.
29//!
30//! - [`gray`]: decoding, grayscale conversion, downscaling, 90° rotation.
31//! - [`exif_info`], [`camera_init`]: EXIF focal-length rules and initial camera values.
32//! - [`sift`]: deterministic SIFT (feature-count limit per level); the backend can be swapped via [`sift::SiftEngine`].
33//! - [`extract`]: camera grouping + incremental `FeatureStore` writes.
34//!
35//! # Usage example
36//!
37//! ```
38//! use cumulus3d_core::FeatureStore;
39//! use cumulus3d_features::{
40//!     CpuSift, ExifInfo, ExtractionOptions, FeatureExtractor, GrayImage, ImageSource, ImageStatus, SiftEngine,
41//!     SiftOptions,
42//! };
43//!
44//! // Synthetic image: three bright blobs.
45//! let blob = |x: usize, y: usize, cx: f32, cy: f32, s: f32| {
46//!     let (dx, dy) = (x as f32 - cx, y as f32 - cy);
47//!     (-(dx * dx + dy * dy) / (2.0 * s * s)).exp()
48//! };
49//! let img = GrayImage::from_f32(320, 240, |x, y| {
50//!     0.2 + 0.6 * (blob(x, y, 80.0, 60.0, 6.0) + blob(x, y, 200.0, 150.0, 9.0) + blob(x, y, 260.0, 70.0, 5.0))
51//! });
52//!
53//! // 1) Call the SIFT backend directly: feature i <-> descriptor row i.
54//! let out = CpuSift::new().extract(&img, &SiftOptions::default())?;
55//! assert!(!out.is_empty());
56//! assert_eq!(out.features.len(), out.descriptors.len());
57//!
58//! // 2) Write to the store (camera assignment + keypoints and descriptors).
59//! let store = FeatureStore::new();
60//! let src = ImageSource { name: "camF/0001.jpg".into(), gray: img, exif: ExifInfo::default() };
61//! let reports = FeatureExtractor::new().extract_inputs(&store, vec![src], &ExtractionOptions::default())?;
62//! assert!(matches!(reports[0].status, ImageStatus::Extracted { .. }));
63//! assert_eq!(store.num_images(), 1);
64//! # Ok::<(), cumulus3d_core::Error>(())
65//! ```
66
67#![warn(missing_docs)]
68
69pub mod camera_init;
70pub mod exif_info;
71pub mod extract;
72pub mod gray;
73pub mod sift;
74
75pub use camera_init::{infer_focal, init_camera, lookup_sensor_width, FocalEstimate};
76pub use exif_info::ExifInfo;
77pub use extract::{
78    extract_for_camera, image_folder, read_image_list, CameraMode, ExtractionOptions, FeatureExtractor, ImageReport, ImageSource,
79    ImageStatus, ReaderOptions,
80};
81pub use gray::{limited_size, read_gray, rgb_to_gray, rotate_keypoint_ccw, GrayImage};
82pub use sift::{CpuSift, DescriptorNormalization, FeatureSelection, SiftEngine, SiftFeature, SiftOptions, SiftOutput};