Manual

Installation

Install Julia, at least version 1.9. If you plan to use GPU support, it is advisable to use the latest stable version.

Within Julia install Wahoo:

] Wahoo

Defining observation models

The user must define an observation model for every sensor. This is a function that computes the probability (density) of the observed signal given the location, p(y_t | s_t).

The function must have the following signature:

 p_obs(signals, t::Int, bathymetry_depth::Number, dist::Number)

where bathymetry_depth is the water depth at s_t and dist is the Euclidean distance from s_t to the sensor location. Note, the function must accept all four arguments, even if some are not used.

Note that if GPU use is planned, the function must be type-stable!

Using GPU

To use the GPU for computations, the packages CUDA.jl and cuDNN.jl must be imported. Currently, only CUDA-compatible GPUs are supported.

Export results

The results can be exported in different ways. We recommend hdf5 if interoperability is required or JLD2 for postprocessing in Julia.

HDF5

HDF5 is a generic format for array like data that can be read from most languages. The function below exports the result of track as hdf5. Note, you must install HDF5.jl additionally to Wahoo.

import HDF5

"""
Write the result from `Wahoo.track()` to `file` in the hdf5 format.
"""
function export_hdf5(res, file)

    # Open (and create) the HDF5 file in write mode
    HDF5.h5open(file, "w") do f

        # --- model outputs
        write(f, "timesteps", collect(res.tsave))

        f["pos", compress=3] =  dropdims(res.pos_smoother, dims=3)
        HDF5.attributes(f["pos"])["dimensions"] = "(y_coord, x_coord, timestep) of size $(size(res.pos_smoother))"

        f["residence_distribution", compress=3] = res.residence_dist
        HDF5.attributes(f["residence_distribution"])["dimensions"] = "(y_coord, x_coord) of size $(size(res.residence_dist))"

        if isdefined(res, :pos_filter)
            f["pos_filtered", compress=3] =  dropdims(res.pos_filter, dims=3)
            HDF5.attributes(f["pos_filtered"])["dimensions"] = "(y_coord, x_coord, timestep) of size $(size(res.pos_filter))"
        end

        write(f, "log_p", res.log_p)

        if !isnothing(res.trajectories)
            # Create a group for the 'trajectories' data
            grp = HDF5.create_group(f, "trajectories")
            HDF5.attributes(f["trajectories"])["description"] = "Each trajectory is a 2d array of shape (2 x time)." *
                                                                " Note, it contains all time steps, i.e. from 1:maximum(timesteps)." *
                                                                " The first row are the y-coordinates, the second the x-coordinates."

            # Write each matrix in the track array as its own dataset
            for (i, tr) in enumerate(res.trajectories)
                write(grp, "traj$(i)", tr)
            end
        end
    end
end
Read HDF5 with Python

The code below gives an example of how the data can be read with Python. Note, the order of the indices differs.

# /// script
# dependencies = [
#   "h5py",
#   "numpy"
# ]
# ///

import h5py
import numpy as np

# -------
# load data from hdf5 file

with h5py.File('Wahoo_results.hdf5', 'r') as f:
    print("structure of the file:")
    print(f.keys())
    print(f['trajectories'].keys())

    pos = f['pos'][:]
    timesteps = f['timesteps'][:]
    trajectories_group = f['trajectories']
    trajectories = [trajectories_group[key][:] for key in trajectories_group.keys()]

    residence_distribution = f['residence_distribution'][:]


print('\nProbabilities of the fish position, saved at `timesteps`:')
print('shape (time, x, y):', np.shape(pos))
# For example, pos[:,:,2] is the distribution of the position for at time = timesteps[2]

print('\nAll trajectories:')
for i, arr in enumerate(trajectories, start=1):
    print(f" - trajectory {i} shape: {np.shape(arr)}")
# For example, trajectories[1][2] is the position for time = 3

JLD2

JLD2 saves and loads Julia data structures in a format comprising a subset of HDF5. It is the recommended format if the data will be used by another Julia script. Note, JLD2.jl and CodecZlib.jl must be installed additionally to Wahoo.

The function below stores the inference results together with the corresponding inputs.

import JLD2
import CodecZlib                # for compression

function export_jld2(res, file; bathymetry, spatial_resolution,
                     acoustic_obs, acoustic_pos, depth_obs)

    # Open (and create) the JLD2 file in write mode
    JLD2.jldopen(file, "w"; compress = true) do f

        # --- model inputs
        f["bathymetry"] = bathymetry
        f["acoustic_obs"] = acoustic_obs
        f["acoustic_pos"] = acoustic_pos
        f["depth_obs"] = depth_obs
        f["spatial_resolution"] = spatial_resolution

        # --- model outputs
        f["timesteps"] = collect(res.tsave)
        f["pos"] = res.pos_smoother
        f["residence_distribution"] = res.residence_dist
        f["log_p"] = res.log_p

        if isdefined(res, :pos_filter)
            f["pos_filtered"] =  res.pos_filter
        end

        if !isnothing(res.trajectories)
            f["trajectories"] = res.trajectories
        end
    end
    file
end

Visualizations

todo