| Title: | Petrographic Thin Section Analysis with Deep Learning |
|---|---|
| Description: | Automated petrographic analysis of thin section images using deep learning models. Provides tools for training RF-DETR detection and segmentation models, running predictions with SAHI (Slicing Aided Hyper Inference), calculating morphological properties, and analyzing results. Supports both local and HPC training workflows with seamless R-Python integration via reticulate. |
| Authors: | Nicolas Gauthier [aut, cre] (ORCID: <https://orcid.org/0000-0002-2225-5827>), Ashley Rutkoski [aut] |
| Maintainer: | Nicolas Gauthier <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.0.0.9000 |
| Built: | 2026-05-21 09:57:01 UTC |
| Source: | https://github.com/flmnh-ai/petrographer |
Aligns a PPL (plane-polarized light) image to an XPL (cross-polarized light) reference image using SIFT feature matching. Designed for images of the same slide taken after remounting (e.g., adding polarizing filters). Automatically validates that scale and rotation are reasonable and falls back to translation-only if needed.
align_images(ppl_path, xpl_path, output_path, method = "similarity")align_images(ppl_path, xpl_path, output_path, method = "similarity")
ppl_path |
Path to PPL image |
xpl_path |
Path to XPL image (reference) |
output_path |
Path to save aligned PPL image. If a directory, generates filename from input. |
method |
Alignment method: "similarity" (rotation+scale+translation with automatic fallback to translation if checks fail) or "translation" (translation only, no rotation/scale) |
List with n_matches and method_used (invisibly)
This is the higher-level segmentation path: predict a folder, save overlays, write per-object measurements, and write per-image summaries in one call. It currently uses direct RF-DETR segmentation inference; revisit once SAHI supports Roboflow segmentation models cleanly.
analyze_segmentation_dir( input_dir, model, output_dir = "results/segmentation_batch", save_visualizations = TRUE, save_measurements = TRUE, save_summary = TRUE, save_population_stats = TRUE )analyze_segmentation_dir( input_dir, model, output_dir = "results/segmentation_batch", save_visualizations = TRUE, save_measurements = TRUE, save_summary = TRUE, save_population_stats = TRUE )
input_dir |
Directory containing images |
model |
Segmentation |
output_dir |
Output directory for overlays and tables |
save_visualizations |
Whether to save overlay images |
save_measurements |
Whether to write per-object measurements CSV |
save_summary |
Whether to write per-image summary CSV |
save_population_stats |
Whether to write a JSON population summary |
A list with detections, per-image summary, population stats, and output directory
When train_model() is called with data_dir rather than dataset_id, it
auto-pins the dataset as _temp_<timestamp> (tagged temp = TRUE in
metadata) so training is reproducible. Those pins persist in
.petrographer/datasets/ and accumulate over time — each is a tar.gz of
the full dataset. This helper removes them.
clean_temp_datasets(board = NULL, confirm = interactive())clean_temp_datasets(board = NULL, confirm = interactive())
board |
Pins board ( |
confirm |
If |
Character vector of deleted dataset ids (invisibly).
For each image, greedily match predicted boxes to ground-truth boxes by IoU
(highest-scoring prediction matches first). Predictions that match a GT box
with IoU >= iou_threshold contribute a (gt_class, pred_class) cell.
Unmatched predictions land in the "background" row (false positives);
unmatched GT boxes land in the "background" column (false negatives).
confusion_matrix( predictions, annotation_json, iou_threshold = 0.5, score_threshold = 0 )confusion_matrix( predictions, annotation_json, iou_threshold = 0.5, score_threshold = 0 )
predictions |
Tibble of predictions with |
annotation_json |
Path to the COCO annotation JSON the predictions were produced against. |
iou_threshold |
Minimum IoU for a prediction to count as matching a GT box (default 0.5). |
score_threshold |
Filter predictions below this score before matching (default 0; keep all). |
Detection doesn't have a clean confusion-matrix definition (unmatched predictions and unmatched GT don't map onto classification confusion cleanly), so treat this as a diagnostic heatmap rather than a calibrated metric.
A list with:
long: long-format tibble (gt_label, pred_label, count)
matrix: wide tibble, one row per GT label
class_names: character vector of class labels (includes "background")
iou_threshold: the threshold used
Runs SAHI inference across the validation set described by a COCO-style
annotation file and computes COCO metrics using pycocotools. The function
returns a tidy summary of the standard 12 bbox metrics alongside the raw
prediction table for further analysis.
evaluate_model_sahi( model, annotation_json, image_dir = NULL, use_slicing = TRUE, slice_size = NULL, overlap = 0.2, max_images = NULL, save_predictions = NULL, iou_type = "bbox", max_dets = 100 )evaluate_model_sahi( model, annotation_json, image_dir = NULL, use_slicing = TRUE, slice_size = NULL, overlap = 0.2, max_images = NULL, save_predictions = NULL, iou_type = "bbox", max_dets = 100 )
model |
A |
annotation_json |
Path to COCO annotation JSON (e.g. |
image_dir |
Directory containing the images referenced in the
annotation file. If |
use_slicing |
Whether to use SAHI sliced inference (default |
slice_size |
Slice size for SAHI inference (pixels, default: use model's resolution) |
overlap |
Overlap ratio between slices (default 0.2). |
max_images |
Optional maximum number of images to evaluate (useful for smoke tests). |
save_predictions |
Optional path to write COCO-format predictions JSON. |
iou_type |
IoU type to evaluate ( |
max_dets |
Maximum detections per image for evaluation. For dense detection (100+ objects), set to 300 or higher (default: 100). |
A list with elements summary (tibble of COCO metrics),
predictions (tibble of detections), and coco_eval (pycocotools object).
Parses RF-DETR training metrics and exports:
training_metrics.csv: per-epoch losses, learning rate, class error
validation_metrics.csv: per-epoch validation losses + AP/AR + summary map
validation_classwise.csv: per-class AP/precision/recall (when logged)
evaluate_training( model_id = NULL, model_dir = NULL, board = NULL, output_dir = "results/evaluation" )evaluate_training( model_id = NULL, model_dir = NULL, board = NULL, output_dir = "results/evaluation" )
model_id |
Model ID (will be resolved from local board) |
model_dir |
Directory containing trained model (alternative to model_id) |
board |
Pins board (NULL = local board, only used if model_id provided) |
output_dir |
Output directory for results (default: 'results/evaluation') |
Source-file preference (first one found wins):
metrics.csv — RF-DETR >= 1.6.0 (PyTorch Lightning CSVLogger)
log.txt — RF-DETR < 1.6.0 (native training loop, JSONL)
List with parsed tibbles and summary statistics
Loads RF-DETR models from local training board, hub, or custom board. By default, checks local models first, then falls back to the public hub.
from_pretrained( model_id, version = NULL, board = NULL, device = "cpu", confidence = 0.5, resolution = NULL )from_pretrained( model_id, version = NULL, board = NULL, device = "cpu", confidence = 0.5, resolution = NULL )
model_id |
Model name (e.g., "shell_v3") |
version |
Specific version (NULL for latest) |
board |
Board to load from:
|
device |
Device: "cpu", "cuda", or "mps" |
confidence |
Detection threshold |
resolution |
Input image resolution for RF-DETR (default: auto-detect from variant). Defaults by variant:
Override only if you know your dataset wants a different input size. |
PetrographyModel object containing RF-DETR model
## Not run: # Smart loading (checks local first, then hub) model <- from_pretrained("my_model") # Force local only model <- from_pretrained("my_model", board = "local") # Force hub only hub_board <- pins::board_url("https://flmnh-ai.s3.us-east-1.amazonaws.com/.petrographer/models/") model <- from_pretrained("public_model", board = hub_board) ## End(Not run)## Not run: # Smart loading (checks local first, then hub) model <- from_pretrained("my_model") # Force local only model <- from_pretrained("my_model", board = "local") # Force hub only hub_board <- pins::board_url("https://flmnh-ai.s3.us-east-1.amazonaws.com/.petrographer/models/") model <- from_pretrained("public_model", board = hub_board) ## End(Not run)
Returns filesystem path to a pinned dataset tar.gz file. The tar.gz should be extracted at training time.
get_dataset_path(dataset_id, board = "local", version = NULL)get_dataset_path(dataset_id, board = "local", version = NULL)
dataset_id |
Dataset name |
board |
Pins board (or board object) |
version |
Specific version to retrieve (NULL = latest) |
Path to dataset tar.gz file
Get overall population statistics
get_population_stats(.data)get_population_stats(.data)
.data |
Data frame with detections |
Named list of population-level statistics
Retrieves the exact dataset version that was used to train a model. This ensures reproducibility by downloading the specific version from pins.
get_training_dataset(model_id, board = NULL)get_training_dataset(model_id, board = NULL)
model_id |
Model name |
board |
Pins board to load model from (NULL = local training board) |
Path to the dataset tar.gz file
## Not run: # Retrieve the exact dataset version used to train a model. Returns a # .tar.gz path; `train_model()` and `validate_dataset()` accept this directly # and extract transparently. dataset_path <- get_training_dataset("my_model") # Retrain on the same data train_model(data_dir = dataset_path, model_id = "my_model_v2", ...) # Or inspect without retraining validate_dataset(dataset_path) ## End(Not run)## Not run: # Retrieve the exact dataset version used to train a model. Returns a # .tar.gz path; `train_model()` and `validate_dataset()` accept this directly # and extract transparently. dataset_path <- get_training_dataset("my_model") # Retrain on the same data train_model(data_dir = dataset_path, model_id = "my_model_v2", ...) # Or inspect without retraining validate_dataset(dataset_path) ## End(Not run)
Lists all pinned datasets on a board.
list_datasets(board = "local")list_datasets(board = "local")
board |
Pins board (NULL = local board, "local" = local board) |
List available models
list_models(board = NULL)list_models(board = NULL)
board |
Pins board (NULL = public hub, "local" = local training board) |
List all locally trained models
list_trained_models()list_trained_models()
Character vector of pinned model names
Get model info
model_info(model_id, board = NULL)model_info(model_id, board = NULL)
model_id |
Model name |
board |
Pins board (NULL = public hub, "local" = local training board) |
Draws ground-truth bounding boxes (and segmentation masks when present) from
a COCO-style annotation file on top of an image. Rendering is delegated to
Roboflow's supervision library via reticulate, so GT overlays stay visually
consistent with prediction overlays produced by predict_image().
pg_plot_annotations( image_path, annotation_json, categories = NULL, output = NULL, display = NULL, draw_labels = TRUE, mask_opacity = 0.4 )pg_plot_annotations( image_path, annotation_json, categories = NULL, output = NULL, display = NULL, draw_labels = TRUE, mask_opacity = 0.4 )
image_path |
Path to the image file. |
annotation_json |
Path to the COCO annotations JSON containing the image. |
categories |
Optional vector of category names or ids to keep.
|
output |
Destination PNG path. Defaults to a tempfile. |
display |
Whether to render the annotated image on the current graphics
device. Defaults to |
draw_labels |
Whether to draw category labels. Default |
mask_opacity |
Opacity for mask fills (segmentation datasets). Default |
The path to the written PNG, invisibly.
Plot a sample image from a dataset directory
pg_plot_dataset_image( dataset_dir, split = c("train", "valid", "test"), image_name = NULL, annotation_json = NULL, ... )pg_plot_dataset_image( dataset_dir, split = c("train", "valid", "test"), image_name = NULL, annotation_json = NULL, ... )
dataset_dir |
Directory containing dataset splits (train, valid, or optionally test). |
split |
Which split to draw from ( |
image_name |
Optional specific image file name; otherwise a random one. |
annotation_json |
Optional explicit path to annotation JSON. |
... |
Additional arguments passed to |
The path to the written PNG, invisibly.
Convenience helper that writes a preview.png alongside the dataset so the
pkgdown catalog can embed a thumbnail.
pg_save_dataset_preview( dataset_dir, split = "valid", output = fs::path(dataset_dir, "preview.png"), overwrite = TRUE, ... )pg_save_dataset_preview( dataset_dir, split = "valid", output = fs::path(dataset_dir, "preview.png"), overwrite = TRUE, ... )
dataset_dir |
Dataset directory (containing split subfolders). |
split |
Which split to sample (default |
output |
Path for the preview image (default |
overwrite |
Whether to overwrite an existing preview. |
... |
Additional arguments passed to |
The path to the written preview image (invisibly).
Pins a COCO-format dataset directory to a pins board for versioning and reuse. The dataset is compressed as tar.gz before pinning.
pin_dataset(data_dir, dataset_id, board = NULL, metadata = list())pin_dataset(data_dir, dataset_id, board = NULL, metadata = list())
data_dir |
Path to dataset directory, or a |
dataset_id |
Name for the pinned dataset |
board |
Pins board (NULL = local board at .petrographer/) |
metadata |
Optional metadata list |
Uploads model files to a pins board for versioning and sharing.
Maintainers should call pins::write_board_manifest() after pinning
to update the board manifest for board_url() consumers.
pin_model(model_dir, model_id, board, metadata = list())pin_model(model_dir, model_id, board, metadata = list())
model_dir |
Directory with model files |
model_id |
Name for the model |
board |
Pins board to pin to |
metadata |
Optional metadata list |
Predict objects in a single image
predict_image( image_path, model, use_slicing = TRUE, slice_size = NULL, overlap = 0.2, output_dir = NULL, save_visualizations = TRUE )predict_image( image_path, model, use_slicing = TRUE, slice_size = NULL, overlap = 0.2, output_dir = NULL, save_visualizations = TRUE )
image_path |
Path to image file |
model |
PetrographyModel object from from_pretrained() |
use_slicing |
Whether to use SAHI sliced inference (default: TRUE) |
slice_size |
Size of slices for SAHI in pixels (default: use model's resolution). Must be divisible by 56 for RF-DETR. |
overlap |
Overlap ratio between slices (default: 0.2) |
output_dir |
Output directory (auto-generated if NULL) |
save_visualizations |
Whether to save prediction visualization (default: TRUE) |
Tibble with detection results and morphological properties
Predict objects in multiple images (directory)
predict_images( input_dir, model, use_slicing = TRUE, slice_size = NULL, overlap = 0.2, output_dir = "results/batch", save_visualizations = TRUE )predict_images( input_dir, model, use_slicing = TRUE, slice_size = NULL, overlap = 0.2, output_dir = "results/batch", save_visualizations = TRUE )
input_dir |
Directory containing images |
model |
PetrographyModel object from from_pretrained() |
use_slicing |
Whether to use SAHI sliced inference (default: TRUE) |
slice_size |
Size of slices for SAHI in pixels (default: use model's resolution) |
overlap |
Overlap ratio between slices (default: 0.2) |
output_dir |
Output directory (default: 'results/batch') |
save_visualizations |
Whether to save prediction visualizations (default: TRUE) |
Tibble with detection results for all images
S3 method for stats::predict() that runs inference on an image with a
PetrographyModel. Delegates to predict_image().
## S3 method for class 'PetrographyModel' predict( object, image_path, use_slicing = TRUE, slice_size = NULL, overlap = 0.2, save_visualizations = FALSE, output_dir = NULL, ... )## S3 method for class 'PetrographyModel' predict( object, image_path, use_slicing = TRUE, slice_size = NULL, overlap = 0.2, save_visualizations = FALSE, output_dir = NULL, ... )
object |
A |
image_path |
Path to image file. |
use_slicing |
Whether to use SAHI sliced inference (default |
slice_size |
Slice size in pixels (default: model's resolution). |
overlap |
Overlap ratio between slices (default |
save_visualizations |
Whether to save prediction visualization. |
output_dir |
Output directory (auto-generated if |
... |
Unused; present for generic compatibility. |
Tibble with detection results.
Uses SAHI to slice images and annotations into tiles. Images smaller than
slice_size are treated as single slices (no fragmentation). Larger images
are split into overlapping tiles, which increases training samples and
improves detection of small objects in large images.
slice_dataset( input_dir, output_dir, slice_size = 1024, overlap = 0.2, min_area_ratio = 0.1, output_format = ".jpg" )slice_dataset( input_dir, output_dir, slice_size = 1024, overlap = 0.2, min_area_ratio = 0.1, output_format = ".jpg" )
input_dir |
Input directory with |
output_dir |
Output directory for sliced dataset (will be created) |
slice_size |
Slice size in pixels (default: 1024). Images smaller than this will not be fragmented. |
overlap |
Overlap ratio between adjacent slices (default: 0.2, or 20%) |
min_area_ratio |
Minimum area ratio to keep object fragments (default: 0.1). Objects cut by slice boundaries with less than 10% visible area are dropped. |
output_format |
Output image format: ".jpg" or ".png" (default: ".jpg"). JPG minimizes storage (~10x smaller) with minimal quality loss. Use PNG for lossless slicing. |
This is particularly useful for dense detection datasets with varying image
sizes. For the inclusions dataset, slicing with slice_size = 1024 will:
Keep small images (<1024px) intact as single slices
Split large images (>1024px) into 2-4 overlapping tiles
Result: ~2x more training images with better small object coverage
Path to sliced dataset directory (invisibly)
## Not run: # Slice dataset for training sliced_dir <- slice_dataset( input_dir = "data/processed/inclusions", output_dir = "data/processed/inclusions_sliced", slice_size = 1024, overlap = 0.2 ) # Train on sliced dataset train_model(data_dir = sliced_dir, ...) ## End(Not run)## Not run: # Slice dataset for training sliced_dir <- slice_dataset( input_dir = "data/processed/inclusions", output_dir = "data/processed/inclusions_sliced", slice_size = 1024, overlap = 0.2 ) # Train on sliced dataset train_model(data_dir = sliced_dir, ...) ## End(Not run)
Summarize detections by image
summarize_by_image(.data)summarize_by_image(.data)
.data |
Data frame with detections |
Summary tibble with per-image statistics
Orchestrates local or HPC training using RF-DETR. Models are automatically pinned to the local board (.petrographer/) for versioning.
train_model( dataset_id = NULL, data_dir = NULL, model_id = NULL, model_variant = "nano", resolution = NULL, epochs = 10, batch_size = NA, grad_accum_steps = NA, learning_rate = NULL, device = "cuda", use_amp = NULL, amp_dtype = "bf16", gradient_checkpointing = NULL, num_workers = NULL, time_hours = 4, validate_every = 2L, early_stopping_patience = NULL )train_model( dataset_id = NULL, data_dir = NULL, model_id = NULL, model_variant = "nano", resolution = NULL, epochs = 10, batch_size = NA, grad_accum_steps = NA, learning_rate = NULL, device = "cuda", use_amp = NULL, amp_dtype = "bf16", gradient_checkpointing = NULL, num_workers = NULL, time_hours = 4, validate_every = 2L, early_stopping_patience = NULL )
dataset_id |
Name of pinned dataset to use for training (preferred). |
data_dir |
Path to dataset directory (alternative to dataset_id; will be auto-pinned with temp ID). |
model_id |
Name for the trained model (used for pins). Defaults to |
model_variant |
RF-DETR model variant. Detection: "nano" (default), "small", "medium", "large". Segmentation: "seg_nano", "seg_small", "seg_medium", "seg_large", "seg_xlarge", "seg_2xlarge". Legacy: "seg_preview". |
resolution |
Image resolution for training. Auto-detected from variant if not specified. |
epochs |
Number of training epochs. Default: 10. |
batch_size |
Batch size for training. If NA (default), uses 2. |
grad_accum_steps |
Gradient accumulation steps. If NA (default), auto-calculated as 16 / batch_size for effective batch size of 16. |
learning_rate |
Learning rate. If NULL (default), uses model default. |
device |
Device for local training: 'cpu', 'cuda', or 'mps' (default: 'cuda'). |
use_amp |
Use automatic mixed precision training (default: TRUE for CUDA, FALSE otherwise). Reduces memory usage by ~40%. |
amp_dtype |
AMP dtype: 'bf16' (recommended for modern GPUs) or 'fp16' (default: 'bf16'). |
gradient_checkpointing |
Enable gradient checkpointing (default: FALSE). Reduces memory usage by ~30%. |
num_workers |
Number of data loading workers (default: 8). |
time_hours |
Time limit for HPC training in hours (default: 3). Examples: 4 = 4 hours, 0.5 = 30 minutes, 1.5 = 1.5 hours. Ignored for local training. |
validate_every |
Validate every N epochs (default: 1). Set to NULL to use model default. |
early_stopping_patience |
Stop training if validation loss doesn't improve for N epochs (default: 10). Set to NULL to disable early stopping. |
Training mode (local vs HPC) is auto-detected based on hipergator configuration.
For HPC training, call hipergator::hpg_configure() before train_model() to set
connection details (host, user, base_dir).
Model ID (can be loaded with from_pretrained(model_id)).
Performs existence checks for expected splits and annotations, then runs annotation diagnostics (counts, size distribution, potential issues) for each split.
validate_dataset(data_dir, quiet = FALSE)validate_dataset(data_dir, quiet = FALSE)
data_dir |
Directory containing 'train' and 'valid' subdirectories, or
a |
quiet |
If TRUE, suppress CLI output while still returning diagnostics |
A list with validation flags, counts, size metrics, and diagnostics