This page is restricted.
Please enter the access password.
Digitizing, storing, analysing and exploring the FISHMORPH database of freshwater fish morphology.
Rfishmorph collects the FISHMORPH routines originally developed inside
intraitR into a stand-alone package dedicated to the
FISHMORPH morphological database of freshwater fishes
(Brosse et al. 2021). It reconstructs the 21 FISHMORPH landmarks, computes the 11 body segments and the
9 dimensionless morphological ratios, standardizes landmark geometry and enforces the FISHMORPH digitizing
conventions, projects specimens into the FISHMORPH functional trait space, and provides visualization and
quality-control tools to compare segment- and landmark-derived measurements. A guided workflow adds new species,
using rfishbase to validate taxonomy and build a global list of freshwater fishes.
Three Shiny applications cover the ends of the workflow: a guided digitizer that turns specimen photographs into landmarks, an explorer of the global morphological space of freshwater fishes, and a drainage-basin explorer that reads that space through a world map of basins, their species composition and their functional-diversity indices.
| 21 landmarks | anatomical points digitized on a lateral photograph (+ scale bar) | fishmorph_schema()$landmark_labels |
| 11 segments | Bl, Bd, Hd, Eh, Mo, PFi, PFl, Ed, Jl, CPd, CFd | fishmorph_segment_names() |
| 9 ratios | BEl, VEp, REs, OGp, RMl, BLs, PFv, PFs, CPt | fishmorph_ratio_names() |
Ratios are dimensionless (each is a quotient of two segments of the same specimen), hence scale-invariant and comparable across studies.
The core functions depend only on base R. Optional features use ggplot2 (plots),
rfishbase (taxonomy / freshwater list), readxl/writexl (Excel I/O),
shiny/jpeg/png (digitizer), DBI/duckdb/arrow
(database and archives) and sf/leaflet (basin explorer).
# install.packages("remotes")
remotes::install_github("FunTraits/Rfishmorph")
library(Rfishmorph)
lm <- read_landmarks_csv(
system.file("extdata", "example_landmarks.csv", package = "Rfishmorph"))
seg <- fishmorph_segments(lm, scale_cm = 1) # 11 segments (cm)
rat <- fishmorph_ratios(seg) # 9 ratios
plot_landmarks(lm, specimen = 1) # points + segments
ref <- load_fishmorph_reference() # bundled 400-species sample
ts <- fishmorph_trait_space(ref, groups = ref$Order) # frozen PCA
plot_trait_space(ts, style = "none", arrows = TRUE)
proj <- project_fishmorph(specimens, reference = ref) # focal individuals
plot(proj, style = "hull") # density heatmap + species hulls
plot(proj, style = "spider", itv_reference = TRUE)
cmp <- compare_segments_landmarks(landmarks = lm, published = published_segments,
id_col = "Genus.species")
summary(cmp)
plot_segment_landmark_agreement(cmp, type = "scatter")
check_infinite_ratios(rat) # zero-denominator / outlier ratios
check_geometry_conventions(lm) # departure from the 5 digitizing rules
validate_species_names(c("Salmo trutta", "Esox_lucius")) # needs rfishbase
rec <- new_fishmorph_species("Genus novus", segments = list(Bl = 10, Bd = 3.2, ...),
family = "Cyprinidae", order = "Cypriniformes")
ref2 <- add_fishmorph_species(ref, rec)
fw <- freshwater_fish_list() # global freshwater list
Photographs stay on your machine: only their file name and pixel dimensions are ever recorded.
launch_fishmorph_digitizer(xlsx_path = "FISHMORPH_PUBLI_9556sp.xlsx",
photo_dir = "Photos", operator = "AT", mode = "new")
jdir <- "landmark_journal"
fm_journal_status(jdir) # what is in the journal?
base <- fishmorph_consolidate(jdir) # wide table, one row per key
qc <- fishmorph_journal_qc(jdir) # never-verified points, gaps
res <- fishmorph_build_db(jdir, db_path = "fishmorph.duckdb",
export_dir = "exports") # Parquet + CSV
con <- fishmorph_db_connect("fishmorph.duckdb")
DBI::dbGetQuery(con, "FROM v_ratios WHERE species = ?", params = list("Coilia.nasus"))
launch_fishmorph_space() # global morphospace, 9 556 species
launch_fishmorph_basins() # drainage-basin explorer
Digitizing never overwrites what is already written. The journal is the only durable layer; the database and
the workbook are derived artefacts that fishmorph_build_db() rebuilds from scratch in seconds. Each
point carries a status (placed, seeded, derived, na)
that a wide coordinate table cannot express — filter on it before any validation analysis.
reconstruct_fishmorph_landmarks() is the inverse of
fishmorph_segments(): the free placement parameters change only where landmarks sit, never
the segment lengths. check_reconstruction_roundtrip() verifies that
segments → landmarks → segments returns the input to machine precision.
| Family | Main functions |
|---|---|
| Scheme & traits | fishmorph_schema() fishmorph_segments() fishmorph_ratios()
fishmorph_traits() fishmorph_landmarks() reconstruct_fishmorph_landmarks() |
| Geometry & quality control | standardize_geometry() check_geometry_conventions()
correct_geometry_conventions() check_landmark_order()
check_infinite_ratios() impute_landmarks() impute_traits()
compare_segments_landmarks() agreement_metrics() |
| Trait space & plots | fishmorph_trait_space() project_fishmorph() plot_trait_space()
plot_functional_space() plot_trait_distributions()
plot_segment_landmark_agreement() fishmorph_basin_indices() |
| Reference data & phylogeny | load_fishmorph_reference() fishmorph_space_data()
load_fishmorph_phylogeny() load_fishmorph_phylo_axes() phylo_pcoa() |
| Taxonomy & new species | validate_species_names() update_fishmorph_taxonomy()
freshwater_fish_list() fishmorph_fishbase_size()
new_fishmorph_species() add_fishmorph_species() |
| Journal & database | fm_journal_open() fm_journal_append() fm_journal_status()
fishmorph_consolidate() fishmorph_journal_qc()
build_fishmorph_landmark_table() fishmorph_build_db()
fishmorph_db_connect() |
| Shiny applications | launch_fishmorph_digitizer() launch_fishmorph_reconstructor()
launch_fishmorph_space() launch_fishmorph_basins()
prepare_fishmorph_basins() |