Skip to content
WFS-FEMPublic

About

code to process GFISHER habitat and fish data

Resources

Stars

1 star

Watchers

1 watching

Forks

Repository files navigation

GFISHER

R pipeline that turns the FWRI GFISHER side-scan habitat mapping and the 3LABS video survey into inputs for the West Florida Shelf Ecospace model: sum-to-1 habitat basemaps, per-group MaxN heatmaps, and reef-class habitat affinities. One driver script, process GFISHER data.R, runs four numbered stages.

Stage Code Produces
1 Habitat basemaps R/habitat_basemaps.R Nine layers that sum to 1 in every water cell: six GFISHER reef classes (AL AM AH NL NM NH), rock (RCK), unconsolidated bottom (UNC), seagrass (SGR)
2 Video dataset R/video_dataset.R Station x model-group MaxN table from the 3LABS survey files and the species list
3 MaxN heatmaps R/maxn_maps.R One raster per map unit on the Ecospace grid, plus a sampling mask and an effort layer
3b MaxN reference maps (optional) R/GFISHER maxn sdmTMB.R Model-based MaxN predictions for Ecospace calibration, each with a CV layer. Off by default (do.sdmTMB)
4 Reef-class affinities R/habitat_affinity.R Habitat affinity of each model group for the six reef classes, from the class recorded at each video drop, with bootstrap intervals

R/legacy/habitat_maps_cellarea.R holds the retired cell-area habitat maps, kept so older Ecospace runs can be reproduced. It is not called by the driver.

Tested with R 4.5.1 on Windows 11. Reading the geodatabase in stage 1 is the slow part; the optional sdmTMB maps add roughly 20 minutes when enabled.

Quick start

  1. Clone the repo and open GFISHER.Rproj in RStudio (or setwd() to the repo root). The driver refuses to run from anywhere else.
  2. Install the packages (one line, printed by the driver if any is missing):
    install.packages(c("sf", "sp", "raster", "terra", "FNN", "colorRamps", "reshape2", "readxl"))
    Optional: mgcv (only for target='smooth' in stage 1), gstat/lwgeom/maps (only for R/legacy/), sdmTMB + fmesher (only for the stage 3b reference maps).
  3. Get the inputs that cannot ship with the repo (next section) and, if they are not in the default folders, copy config.local.example.R to config.local.R and point at them.
  4. source("process GFISHER data.R"), or from a terminal Rscript "process GFISHER data.R".

Before anything slow runs, the driver prints an input check and stops if a required input is missing:

Input check
----------------------------------------------------------------------------------------------------
  input         stage    need     how      status / path
  bathymetry    1,3,4    required repo     OK       .../data/bathymetry/depth 5min 66x78.asc
  species_list  2        required repo     OK       .../data/Master Species List.xlsx
  geodatabase   1,4      required manual   OK       .../data/April2026/GFISHER_EAST_Universe_2026.gdb
  survey_maxn   2        required manual   OK       .../data/April2026/maxn3LABS_93to24.csv
  survey_env    2,3,4    required manual   OK       .../data/April2026/env3LABS_93to24.csv
  survey_lens   2        required manual   OK       .../data/April2026/lens3LABS_93to24.csv
  dbseabed      1        required auto     OK       .../data/dbseabed
  seagrass      1        optional derived  OK       .../data/seagrass/seagrass_5min.asc

Getting the data

Everything the pipeline reads, where it comes from, and how a new user obtains it. Sizes are approximate.

Input Size How Where it goes
Depth grids, 5 and 15 arc-min 100 KB ships with the repo data/bathymetry/
Master Species List.xlsx (species to model-group key, size-at-age stanzas) 220 KB ships with the repo data/
GFISHER_EAST_Universe_2026.gdb (FWRI side-scan habitat mapping; layers East_Master_Hab_Data_FINAL_2026, East_Master_Microgrid_Mapped_2026) 275 MB by request from FWRI (Sean Keenan) or the repo author; cannot be redistributed on GitHub data/April2026/, or set dir.data / file.gdb
maxn3LABS_93to24.csv, env3LABS_93to24.csv, lens3LABS_93to24.csv (FWRI 3LABS video survey 1993 to 2024) 42 MB by request, same source data/April2026/, or set dir.data
dbSEABED raw grids Gmf_{RCK,GVL,SND,MUD}/gmf_*_val.asc 4.4 MB ships with the repo (public data from CSDMS dbSEABED; provenance in data/dbseabed/SOURCE.md). fn.pull_dbseabed() re-downloads them if the folder is removed data/dbseabed/, or set dir.dbseabed to another copy
seagrass_5min.asc (seagrass cover on the model grid, derived from FWC Seagrass Habitat in Florida by the EcospaceBasemap repo) 44 KB ships with the repo (provenance in data/seagrass/SOURCE.md); optional: without it the SGR layer is zero. No 15-minute version yet data/seagrass/, or set file.seagrass

Expected data/ tree once everything is in place:

data/
  bathymetry/            depth 5min 66x78.asc, depth 15min 22x26.asc, excl layer *.asc   (tracked)
  Master Species List.xlsx                                                               (tracked)
  April2026/             GFISHER_EAST_Universe_2026.gdb/, maxn/env/lens 3LABS_93to24.csv (gitignored)
  dbseabed/              Gmf_RCK/ Gmf_GVL/ Gmf_SND/ Gmf_MUD/, SOURCE.md                   (tracked)
  seagrass/              seagrass_5min.asc, SOURCE.md                                    (tracked; 15min not yet made)

If you keep the FWRI files somewhere else (a shared drive, a OneDrive sync), do not copy them: point dir.data at that folder in config.local.R. On Windows a directory junction (New-Item -ItemType Junction -Path data\April2026 -Target <folder>) also works and is ignored by git.

Configuration

Defaults live in the SETTINGS block of the driver and are repo-relative. Override any of them in config.local.R (gitignored; config.local.example.R documents every key). The driver sources it after the defaults and before reading any input.

Key Default Purpose
res 5 Grid resolution in arc-minutes, 5 or 15
group.scheme 'mice' Species grouping: 'mice' or 'ecospace' columns of the species list
seed 1 Seed for the stage 2 length resampling (see Known caveats). NULL = unseeded
dir.data data/April2026 Folder with the geodatabase and the three survey CSVs
file.gdb NULL Geodatabase path; NULL finds the single GFISHER_EAST_Universe*.gdb in dir.data
dir.bathy data/bathymetry Depth grids
file.spplist data/Master Species List.xlsx Species list
dir.dbseabed data/dbseabed Raw dbSEABED grids (ship with the repo); set only to use another copy
file.seagrass NULL Seagrass raster; NULL = data/seagrass/seagrass_<res>min.asc
dir.ecospace.maps NULL The author's external Ecospace maps tree; sets file.seagrass and dir.ewemaps the former way
dir.maps output/maps Output root for stage 3
dir.ewemaps NULL Where MaxN heatmaps are written; NULL = dir.maps

The original author's setup is two lines in his config.local.R: dir.dbseabed and dir.ecospace.maps pointing at his existing trees, and nothing else.

Outputs

output/
  basemaps/<res>min/       habitat_<CODE>_<res>min_<rows>x<cols>.asc  (9 layers)      tracked deliverable
                           habitat_basemap_QC_<res>min.csv, habitat_basemaps_<res>min.png
  maps/GFISHER/<res>min/maxn/<scheme>/
                           GFISHER_maxn_mod<N>_<group>_<res>min_<rows>x<cols>.asc (one per MAP UNIT)
                           GFISHER_maxn_mask_<res>min_<rows>x<cols>.asc    1 = sampled, NA = background
                           GFISHER_maxn_effort_<res>min_<rows>x<cols>.asc  drops per cell
                           GFISHER_maxn_heatmaps_<res>min_<rows>x<cols>.pdf
  maps/GFISHER/<res>min/maxn_sdmTMB/<scheme>/
                           GFISHER_sdmTMB_{est,cv,pres}_mod<N>_*.asc, mask, effort,
                           sdmTMB_fit_summary.csv, GFISHER_sdmTMB_maps_<res>min_*.pdf
  affinity_<scheme>/       reefclass_A_wide.csv          affinity, best class = 1  <- Ecospace input
                           reefclass_w_area_wide.csv     density vs the area-weighted reef average
                           reefclass_affinity_long.csv, reefclass_coverage.csv,
                           reefclass_diagnostics.pdf
                           invideo_crosscheck.csv / .pdf  (diagnostic, not a model input)
  GFISHER_species_fg_<scheme>.csv   survey taxon -> model group key (gitignored)

Stage 1 writes into output/basemaps/ in place, so a rebuild shows up in git diff. That is deliberate: the basemaps are the versioned deliverable that feeds Ecospace.

The stage 1 panel figure:

Habitat basemaps, 5min

Stage 3: map units, not model groups

The video cannot resolve six gag or six red grouper stanzas, so several model groups share one map layer and the unmappable ones get none. Nineteen MICE groups become eleven layers: gag 2, 3 and 4 pool into one gag 2-4 layer, red grouper 3 and 4 into red grouper 3-4, and gag 0, gag 1 and red grouper 0, 1, 2 get no layer at all. The spec and its reasoning are MAXN.MAPS.MICE / MAXN.DROP.MICE in R/maxn_maps.R. This is map-only: the Ecospace group structure and the affinity tables still carry every stanza separately.

The cell-mean layers set unsampled water to zero, so 2,707 of 3,838 water cells carry a value nobody observed. Any objective function scored on these layers must multiply by GFISHER_maxn_mask_* first, or it is mostly scoring where the boat went. For calibration proper, prefer the stage 3b sdmTMB maps, which return NA outside the predictable domain instead of guessing.

What the basemaps mean

The six reef classes are a two-letter code from the FWRI NewHabStrat field: A/N for artificial or natural, L/M/H for low, medium or high relief. Reef density is the mapped reef area divided by the scanned area of each cell (GFISHER has scanned about 3% of the domain), then shrunk toward a region x depth-bin average in cells with little scanning. Rock comes from the raw dbSEABED rock fraction; unconsolidated bottom is what remains; seagrass is area-averaged from the seagrass raster. The nine layers are normalised to sum to exactly 1 in every water cell, and reef is forced to zero below 300 m. The header of R/habitat_basemaps.R records the alternatives that were tried and why they were rejected.

What the affinities mean, and what they do not cover

Stage 4 estimates the affinity of each model group for the six reef classes only:

  • use is MaxN at a video drop, assigned to the one class that drop sat on (HAB_STRAT in env3LABS, cross-checked against REEF_TYPE); absences are retained.
  • availability is the mapped area of each class over the side-scan footprint, taken from stage 1's own microgrid table. It is deliberately not the share of drops: the survey put about 32x more effort into AH than its area share, so drop-based availability would measure the survey design rather than the habitat.
  • depth is held out as a separate stratum effect, because Ecospace applies its own depth response function on top of habitat capacity. An affinity that had absorbed depth preference would be applied twice.

Two scales are reported from the same fit: reefclass_A_wide.csv (affinity, best class = 1 -- the Ecospace input) and reefclass_w_area_wide.csv (density relative to the area-weighted reef mosaic average, so >1 means more fish per unit area than average reef). They rank identically by construction; that is what a categorical habitat gives you.

No non-reef affinities are produced. The survey does not identify rock, sand, mud or seagrass at the drop, and estimating them from cell-averaged availability does not work: the side-scan footprint covers a median 12% of a 5-min cell, so the reef patches the cameras target are largely absent from the availability layer and the counts get attributed to mud, which came out top for most reef groups including every gag stanza. Restricting to well-mapped cells does not rescue it -- at >=40% footprint only 5 cells and 373 drops survive. The code for that cell-scale analysis (fn.batch_affinities, LAYER.SPEC) is still in R/habitat_affinity.R but nothing calls it.

For collaborators

  • Your paths are yours. Nothing machine-specific belongs in tracked files; it goes in config.local.R.
  • Contributing changes. Open an issue, create the branch from its Development sidebar (N-short-description), commit with (issue #N) in the subject, and open a pull request against main with Fixes #N in the body. Split code, documentation, regenerated outputs and housekeeping into separate commits.
  • Review history. The October 2026 reproducibility review is documented in docs/issue2-review-gfisher-repo-plan.md (findings, evidence, decisions). Note that it predates the stage 4 rework described above, so its 4a/4b/4c sections describe modules that no longer exist.

Known caveats

  1. Stanza assignment depends on an imputation. Only 31% of counted gag and 54% of red grouper carry a measured length, so stage 2 imputes the rest by a stratified hot deck: resampling real measured lengths from the finest depth-band x era stratum that has at least 30 of them. Totals per station and species are exact; the split among stanzas changes with the seed. With seed = 1 the run is reproducible, but the stanza-level maps remain one realisation. How much this matters for the affinities is tracked in issue #5.
  2. Seagrass. The shipped data/seagrass/seagrass_5min.asc comes from EcospaceBasemap. The author's original basemaps used his own rasterisation of the same FWC layer; the two agree in all but 45 of 3,838 water cells.
  3. Stations on grid lines. Eleven survey stations sit exactly on a 5-minute latitude line. Which cell they fall in depends on the template grid's header; the driver checks that all grids share one template so the assignment is consistent within a run.
  4. Artificial reef is thinly sampled even at the drop. AL/AM/AH are flagged by reefclass_coverage.csv; read it before using those columns.
  5. Windows plot windows. Interactive runs on Windows open a recording graphics window; Rscript and other platforms skip it. Figures are written to files either way.
  6. Legacy geodatabases. Two older geodatabases (East_Master_Hab_data_Dissolve_byMicro_13Sept24.gdb, FWRI_East_Gulf_Mapping_2023.gdb) were tracked until October 2026 and remain in git history. They are inputs only to the retired legacy module and are not needed.
  7. fn.make_GFISHER_habitat_maps exists in two files -- R/GFISHER habitat maps.R and R/legacy/habitat_maps_cellarea.R. Both are superseded by stage 1; one should be removed.

Reproducibility

  • A fresh clone with only config.local.R added runs end to end under Rscript.
  • Stage 1 basemaps regenerate byte-identical given the same inputs.
  • Stages 2 to 4 are deterministic given seed.
  • MD5 comparison of outputs: tools::md5sum(list.files("output/basemaps/5min", "\\.asc$", full.names=TRUE)).

About

code to process GFISHER habitat and fish data

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages