CaSSIS (Colour and Stereo Surface Imaging System) is the pushframe stereo imager on the ExoMars Trace Gas Orbiter.
This repository provides a set of scripts and sample data that allow end-to-end replication of the process for turning CaSSIS framelet images into a digital terrain model (DEM) registered against an existing CTX terrain. The processing uses the NASA Ames Stereo Pipeline (ASP).
The methodology, results, and validation against CTX are described in the ASP documentation page for CaSSIS.
The work splits by WHERE a step runs. One group needs the network; the rest is pure compute. On a cluster, run the first group on a front-end (head) node and the second on a compute node.
Fetch (network only). Discover the two stereo look ids for the orbit (cassis_find_looks.sh), download the calibrated framelet images from the ESA Planetary Science Archive (cassis_fetch_pair.sh), and download the controlled CTX DTMs that cover the site (cassis_ctx_fetch.sh). These only talk to the network and do no heavy compute, so they are the only steps that need outside access. The single driver cassis_fetch.sh runs all three from the site config. The SPICE kernels are also downloaded here, once.
Compute (no network). Ingest the framelets to ISIS cubes, generate a CSM camera per framelet, blend the downloaded DTMs into the CTX reference DEM and its blurred mapproj DEM, then run the numbered stages (1 through 8) that produce the registered CaSSIS DEM. None of this needs the network; it is CPU and memory work for a compute node.
Older documentation called the fetch-plus-prep group "Tier 1" and the numbered stages "Tier 2". That still holds, but the sharper line for running on a cluster is network versus compute: only the fetch steps need outside access, and everything else (including the ingest, camera, and CTX-blend prep) runs on a compute node.
The published (prior vendor) CaSSIS DEM is downloaded only for optional comparison; it is not an input to the pipeline. A new site is processed without it (see "Processing a new site from scratch").
bin/ the pipeline scripts
config/ sample configuration files
README.md
LICENSE
Add the bin directory to the PATH. The master driver cassis_process.sh and the
individual stage scripts also self-locate their own bin directory and prepend it
to the PATH, so the pipeline's sibling scripts resolve whether the whole
pipeline is run through the master driver or a single stage script is run
directly for debugging. The ASP and ISIS tools, however, must still be put on the PATH
explicitly (see the Environment section below).
A reference Jezero site dataset is provided.
It has the site configuration, the framelet cubes, the original CSM cameras, the registered and distortion-corrected cameras, the CTX reference DEM, the coarser CTX mapprojection DEM, the preliminary CaSSIS linescan DEM aligned to CTX, and the final produced CaSSIS DEM with its evaluation products. Intermediate stereo, bundle-adjustment, and dense matches data are excluded, since they are large and can be regenerated.
Extract it in the current directory.
Each command below will be practiced on it. The commands will exit quickly if the outputs they are supposed to create already exist.
A site's inputs are specified in its config file, which the pipeline treats as shell variables (inputCassisDir, Llook, Rlook, refDem, mapprojDem). See Configuration below. For the sample commands below, source the Jezero config so the same variables are available in the shell:
source cassis_jezero.confHere is the contents of this file. We will use these as shell variables below.
inputCassisDir=data/jezero/MY36_016378_162 # any dir holding the two looks' cubs
Llook=838849161 # left look id (ESA)
Rlook=838849162 # right look id
refDem=ref/jezero_ctx/jezero_ctx_18m.tif # CTX reference DEM
mapprojDem=ref/jezero_ctx/jezero_ctx_18m_blur5.tif # blurred CTX DEMThe sections below practice each step on the shipped Jezero sample, which already has its cubes, cameras, and CTX reference. This section is the end-to-end recipe for a brand-new CaSSIS stereo pair, with no prior vendor DEM. It just ties the per-step tools together in order; read the per-step sections for detail.
Pick a stereo pair. From the ESA PSA (or an ODE search of CaSSIS stereo pairs) you get the orbit number and the observation footprint: its center latitude and east longitude, and its longitude/latitude bounding box. The two look ids are then discovered from the orbit:
cassis_find_looks.sh 4041 # -> "276712209 276712210" (left right), or discovered automatically belowWrite a site config (for example cassis_4041.conf) in the work directory. Besides the usual inputCassisDir, refDem, mapprojDem, a from-scratch site also sets the geometry the CTX fetch needs, since there is no DEM yet to read it from:
orbit=4041
inputCassisDir=data/MY34_004041_196
clat=-14.6501 # footprint center latitude
clon=175.6455 # footprint center east longitude
footprintBbox=175.4850,-15.0365,175.8016,-14.2430 # minLon,minLat,maxLon,maxLat (east lon)
refDem=ref/MY34_004041_196_ctx/MY34_004041_196_ctx_18m.tif
mapprojDem=ref/MY34_004041_196_ctx/MY34_004041_196_ctx_18m_blur5.tif
# Llook / Rlook are optional; if absent they are discovered from the orbit.Fetch (front-end / head node, network). One command does look discovery, framelets, and the CTX DTMs:
cassis_fetch.sh cassis_4041.conf $(pwd)The CTX fetch sizes its box as three times the footprint (the measured CaSSIS-to-CTX pointing offset is ~8 to 15 km, so three times covers it), using the footprint bbox directly: there is no DEM yet, so a tiny constant stand-in raster is synthesized over the bbox to define the extent. This is why the config carries footprintBbox. If the STAC query returns very few DTMs over the box, there is no usable controlled CTX at this site, so drop it before spending any compute. The STAC query and the downloads retry on transient network errors, so a passing 502 from the server does not abort the fetch.
Compute (compute node, no network). Run these in order. Ingest needs the ISIS environment; the camera step, the CTX blend, and the stages all use the usgscsm_cassis environment (see Environment). On a cluster this is one batch job that activates each environment in turn and calls the pipeline tools; it adds no logic of its own.
# 1. framelets -> ISIS cubes (ISIS environment)
cassis_ingest_cubes.sh $inputCassisDir
# 2. one CSM camera per cube (usgscsm_cassis)
cassis_make_cameras.sh $inputCassisDir
# 3. CTX reference DEM + blurred mapproj DEM (usgscsm_cassis). TAG matches the refDem basename.
cassis_ctx_blend.sh ref/MY34_004041_196_ctx MY34_004041_196 $clat $clon
# 4. linescan DEM and its alignment to CTX (stages 1 to 2)
cassis_process.sh cassis_4041.conf 1 2 site4041_out $(pwd)Inspect before going on. Hillshade the aligned linescan DEM (site4041_out/linescan/linescan_dem/align/aligned_oncoarse.tif) and the CTX reference, on a shared extent. Confirm the CTX covers the footprint, the CaSSIS DEM shows real relief to lock onto (not flat or garbled), and the alignment residual is small. Drop the site if the CTX is too sparse over the footprint, the CaSSIS DEM is too flat or weird, or the alignment did not take. This is the triage the dz and disparity numbers cannot do for you, because they hide missing coverage.
Finish the DEM (compute node). For a site that passed inspection, run the rest:
cassis_process.sh cassis_4041.conf 3 7 site4041_out $(pwd)The delivered DEM is site4041_out/frame/pass1_stereo/cassis_dem_on_ctx.tif. Always compare its extent and coverage to the aligned linescan DEM and the CTX (see the evaluation tools), since a truncated framelet DEM scores well on dz while missing ground.
Run on a local machine, with network access and inspection. Each step below is its own small tool. Tier 1 produces the inputs the site config names: the framelet cubs, the cameras, the reference CTX DEM, and its lower-resolution version for mapprojection.
This step downloads the framelets, and ingests them to ISIS cubes. It needs an ISIS environment. Set up the ISIS environment described in the ASP documentation, then activate it and set ISISROOT (the ISIS installation, its conda prefix) and ISISDATA (the ISIS data tree, which holds the TGO/CaSSIS kernels).
Fetch the calibrated PAN framelets for both looks (framelet collections) from the ESA PSA, into inputCassisDir (the fetch groups them into an L1 and an L2 subdirectory, named by look id, but the pipeline later finds each look by its id in the cube filenames, so any layout works) (Fetching the framelets):
Example for the Jezero site:
cassis_fetch_pair.sh 16378 $Llook $Rlook $inputCassisDirHere, 16378 is the orbit number, the middle field of the observation id MY36_016378_162. The observation id and its two look ids (Llook and Rlook) come from the ESA Planetary Science Archive when selecting a CaSSIS stereo pair. The two look ids can also be discovered from the orbit alone (they are the consecutive pair in the orbit listing):
cassis_find_looks.sh 16378 # prints "Llook Rlook"The cassis_fetch.sh driver uses this automatically when the config does not pin the looks, so a from-scratch site only needs the orbit (see "Processing a new site from scratch").
Download the SPICE kernels (Downloading the SPICE kernels).
Ingest each framelet to an ISIS cube (Ingesting to ISIS cubes), both in the ISIS environment. Example for the Jezero site:
conda activate isis10
export ISISROOT=$CONDA_PREFIX ISISDATA=/path/to/isisdata # ISIS install + data tree
cassis_ingest_cubes.sh $inputCassisDirThis step creates a CSM camera per cube with ALE. It needs the CaSSIS-capable ALE and USGSCSM environment, described under Creating the CaSSIS CSM cameras in the ASP documentation. Set it up and activate it. This environment also provides gdal, numpy, and scipy, so it is reused by the processing stages below.
Example for the Jezero site:
conda activate usgscsm_cassis
export ISISDATA=/path/to/isisdata # ISIS data directory
export ALESPICEROOT=$ISISDATA # ALE metakernel directory
cassis_make_cameras.sh $inputCassisDirThe camera step needs ISISDATA (the ISIS data tree) and ALESPICEROOT (the ALE SPICE root, where isd_generate finds the CaSSIS metakernels; no spiceinit is run). These are usually set to the same value.
Both ingest and camera scripts scan the per-look subdirectories under the given data root and are idempotent, so a re-run only fills in what is missing.
Do not mix the two environments.
Preparing the prior CaSSIS DEM used only for comparison is at Prior CaSSIS DEM.
The prior CaSSIS DEM is aligned to the CTX with the same hillshade correlation approach used later for the CaSSIS images, described at Alignment of prior CaSSIS DEMs to CTX.
The CTX reference is assembled from existing controlled CTX DEMs. See the documentation. Think of it as two steps, split by where they run: fetch (needs network) and process (heavy compute). On a cluster, fetch on the front-end node and process on a compute node.
Fetch computes the box, queries the USGS STAC, and downloads the controlled CTX DTMs. Run it where there is outside network access:
cassis_ctx_fetch.sh CTXDIR FOOTPRINT_DEM CLAT CLONFOOTPRINT_DEM is the DEM that defines the footprint (the linescan or vendor DEM). For a new site there may be no such DEM yet (the linescan DEM needs the CTX, which this step is building), so FOOTPRINT_DEM may instead be a bounding box written as bbox:minLon,minLat,maxLon,maxLat (east longitudes). A tiny constant stand-in raster is then synthesized over that bbox and used as the footprint; only its extent matters. The bbox comes from the observation footprint (ODE / the PSA pair record). CLAT and CLON are the site center; for Jezero they are near latitude 18.4, longitude 77.5. This writes box_te.txt, ctx_list.txt, and the raw DTMs under CTXDIR/dl/. The box is BOX_FACTOR times the footprint (default 3). The STAC query and the downloads retry on transient network errors.
Process warps the downloaded DTMs to the grid, crops to the box, culls the outlier DTMs, feather-blends the rest, and makes the blurred mapproj DEM. It is heavy, so run it on a compute node (qsub):
cassis_ctx_blend.sh CTXDIR TAG CLAT CLONHow the overlapping DEMs are combined matters. A plain first-valid or median stack leaves visible tile seams in the reference, and those seams then show up as artifacts in the dz difference. So the blend first culls outlier DTMs at a coarse 144 m resolution against their median, then does a single full-resolution feathered blend of the survivors, which is seamless while keeping the consensus elevations. The warps are cached, so re-running the blend to try a different combination costs minutes. The blurred mapproj DEM is always regenerated from the current reference, so it never lags behind a rebuilt reference and silently drops framelets.
This step needs attention, as some sites may lack good prior CTX coverage. Check that both the reference DEM and the mapproj DEM were produced, then set refDem and mapprojDem in the site config.
cassis_ctx_build.sh is the older all-in-one that does fetch and process in one script (with the same blend selectable as CTX_STACK=sub8). The two-step fetch-then-blend flow above is preferred, since it keeps the network step and the compute step cleanly separate.
The Jezero sample has these two products.
The numbered stages (1 through 8) run on the Tier 1 inputs. Two things must be in place first.
First, a recent ASP release is needed, from 2026/7 or later, from the releases page. Such a recent build has the CaSSIS camera support.
Second, the usgscsm_cassis environment from the camera step above should be activated. It provides gdal and proj. Activate it, and put the pipeline and ASP on the PATH:
conda activate usgscsm_cassis
export PATH=/path/to/CassisPipeline/bin:/path/to/StereoPipeline/bin:$PATHEach script also checks up front that the environment is active (CONDA_PREFIX set) and that the tools it needs are on PATH. Camera generation uses the environment's own isd_generate, via CONDA_PREFIX.
The normal entry point is the master driver cassis_process.sh, which runs the
stages in order. Each stage script (for example cassis_stereo.sh) can also be
run on its own for debugging: it prepends its own bin directory to the PATH, so
its sibling scripts and the per-pair worker cassis_stereo_pair.sh (launched
through GNU parallel) resolve without the caller having added the pipeline bin to
the PATH. Only the external ASP and ISIS tools still need to be on the PATH.
The scripts are generic; the pipeline learns about the data through two files, which live in the work directory, not in this repository. Example copies are in the config directory. Both are sourced as shell variables.
The config directory ships a ready per-site config for every site in the ASP documentation: cassis_jezero.conf, cassis_ox1.conf, cassis_ox2.conf, cassis_gusev.conf, and cassis_004756.conf, plus the shared cassis_common.conf. Copy the required pair into the work directory.
- cassis_common.conf holds the shared recipe constants (grid resolutions, bundle-adjustment uncertainties, the optimized and fixed lens coefficients, dense-match settings). It normally does not need editing. The lens coefficients are a global CaSSIS instrument constant, reused as is for every site.
- cassis_siteName.conf (for example cassis_jezero.conf) holds only the per-site inputs, listed above under Reference data: inputCassisDir, Llook, Rlook, refDem, and mapprojDem. The two looks are found by their id in the cube filenames, so inputCassisDir can be any directory that holds them. The output directory is not in the config (it changes per run); it is passed on the command line (see Running).
Every path in the site config is relative to the work directory unless it is absolute. Copy the two sample files into the work directory, edit every path to point at the data, and confirm each named file exists. The pipeline also checks this up front and fails fast, so a wrong path does not waste a long batch job. The Jezero sample already ships an edited cassis_jezero.conf, so for the sample there is nothing to change.
With the Tier 1 inputs in place (cubs, cameras, refDem, mapprojDem), the master script runs the standard processing chain, stages 1 through 7, which produce the delivered DEM:
cassis_process.sh cassis_siteName.conf 1 7 outDir /path/to/workdirThe five arguments are the site config, the first and last stage to run, the output directory, and the work directory. Stages 1 to 7 are the standard chain. Stage 8 is an optional refinement pass (see below). All outputs go under outDir, which can be any path and changes per run. Reuse an outDir to resume (each stage skips outputs that already exist); use a fresh outDir for a clean run. To run or inspect one stage at a time, set the same number for both, for example 1 1, then 2 2.
Each stage is also documented on its own below, so a stage can be run or inspected individually. The quickest way to try the pipeline without any preparation is to start from the provided reference dataset (see Reference data), which ships the cubes, cameras, and CTX already done, so only the processing stages need running. The CTX reference DEM and the blurred mapproj DEM are built in Tier 1 (see Build the CTX reference DEM and the blurred mapproj DEM), not here.
Merges each CaSSIS look into a single image, creates a linescan camera, and makes a first DEM, aligned to the coarse CTX reference whose grid and projection drive every output. Needs the framelet cubes and cameras from ingestion.
Example for the Jezero site, from inside the unpacked directory:
cassis_linescan_dem.sh cassis_jezero.conf jezero_out $(pwd)Check that the linescan DEM was produced under the work directory. Documented at Initial registration.
Carries the alignment transform found in stage 1 onto the tied linescan cameras, producing the CTX-aligned linescan camera states.
Example for the Jezero site:
cassis_align_cams.sh cassis_jezero.conf jezero_out $(pwd)It finds the stage-1 transform and writes the aligned camera states under outDir/linescan/linescan_dem/cams_aligned/. This and stage 3 consume stage-1 and stage-2 intermediates, which the sample does not ship (they are large and regenerated), so they run only in a from-scratch pass; starting from the sample the run skips straight to stage 5 with the provided cameras. Documented at Initial registration.
Splits the registered linescan cameras back into per-framelet frame cameras.
Example for the Jezero site (both looks in one call):
linescan2framelets.sh cassis_jezero.conf jezero_out $(pwd)Check that a frame camera was written per framelet. Documented at Initial registration.
Refits a single optimized and fixed transverse-distortion model and sets it on the cameras.
Example for the Jezero site (both looks in one call):
refit_transverse.sh cassis_jezero.conf jezero_out $(pwd)Check the registered, distortion-corrected cameras. Documented at Distortion refit.
These are meant to be run together as one batch job, not one at a time. On a PBS or qsub cluster, for example NASA Pleiades, submit them as a job and adapt the queue, account, node model, core count, and walltime to the target system:
Example for the Jezero site, from inside the unpacked directory (the master prints to the terminal and also writes its own log in the work directory):
qsub -V -N cassis -l select=1:ncpus=28 -l walltime=6:00:00 -j oe -o cassis_qsub.log -- \
/path/to/CassisPipeline/bin/cassis_process.sh \
cassis_jezero.conf 5 7 jezero_out $(pwd)This runs stages 5 to 7, delivering the processed DEM. To also run the optional refinement pass, set the last stage to 8 instead of 7.
The -V flag exports the activated environment (PATH, PROJ_DATA, ISISROOT, and the rest) to the compute node, which otherwise starts clean. The worker changes into the work directory and writes its own log there. This set of stages can also be run several times in parallel with different output directories and parameter choices to compare results.
The stages within this group are:
- Stage 5, apply the corrected lens distortion and refit the pose. Documented at Distortion refit.
- Stage 6, compute dense interest-point matches (cassis_stereo.sh). If matches are absent, start the run at stage 6. Documented at Dense matches.
- Stage 7: bundle adjustment, pairwise stereo, blending, and registration to CTX. The delivered DEM is this result. Documented at Bundle adjustment and Pairwise stereo and blending.
- Stage 8 (optional). It redoes the process with freshly generated cameras, DEM, and GCP from stage 7. It normally has a limited extra payoff. Documented at Optional refinement.
The delivered DEM is cassis_dem_on_ctx.tif, under the output directory, in outDir/frame/pass1_stereo/ (or pass2_stereo/ if the optional refinement pass was run). Beside it are its hillshade, the geodiff to CTX, and the max-triangulation-error mosaic. Compare the DEM to the CTX reference with geodiff for the vertical difference and by hillshade image correlation for the horizontal registration, as in Evaluation.
Two helper tools summarize a finished run visually. Both use a Python environment with matplotlib and the GDAL Python bindings.
cassis_eval_plots.py makes the six evaluation panels for one site as separate images, so a gallery can lay them three per row:
cassis_eval_plots.py TAG CTX_18M ALIGNED_LINESCAN FINAL_ON_CTX DZ_DIFF DDH DDV OUTDIRThe first row is a grayscale hillshade of the CTX reference, the aligned linescan DEM, and the final stage-7 DEM. These three must cover the same ground. The dz and disparity statistics hide missing coverage, so this row is how a dropped or truncated framelet DEM is caught. The second row is the elevation difference to CTX and the across-track and along-track disparity from hillshade image correlation. The tool also writes TAG_stats.tsv with the median and NMAD of each of the three difference bands.
cassis_gallery.py assembles a self-contained HTML gallery from a set of those panels and stats files:
cassis_gallery.py MANIFEST.tsv PANELS_DIR OUT.html [TITLE]The manifest has one tab-separated line per site with columns tag, id, coord, convergence, group, and note. The page shows each site's six panels and a summary table ranked by dz NMAD. Images are embedded so the file is portable.
An advanced, optional refinement. Rather than registering the CaSSIS images against the CTX reference DEM, they are matched at the pixel level against the CTX images themselves and the two sensors are then refined together. This removes the along-track jitter usually present in a single CTX stereo pair. It works when the CTX and CaSSIS images have similar (afternoon) illumination. The rationale, parameters, and result figures are documented at Joint CTX and CaSSIS jitter solving.
The scripts below are run in order. The last argument to each is the work directory; all other paths are relative to it. Each script finds its sibling scripts by its own location, but the ASP and ISIS tools and the data roots must be set up in the environment first, before any script is run:
conda activate usgscsm_cassis
export PATH=/path/to/CassisPipeline/bin:/path/to/StereoPipeline/bin:$PATH
export ISISDATA=/path/to/isisdata # ISIS data tree (kernels)
export ALESPICEROOT=$ISISDATA # ALE metakernel rootA packaged ASP release provides its own ISIS, so nothing more is needed. A
development ASP build additionally needs ISISROOT pointing at an ISIS-capable
environment so the CTX and CaSSIS .cub images can be read. The scripts do not
set any of this themselves; they rely on this environment.
-
Ingest the CTX pair to cubs and CSM linescan cameras following the CTX recipe (including the ctxcal radiometric calibration), then bundle-adjust the pair and run stereo with local epipolar alignment to get a rough CTX DEM. This uses the standard ASP tools.
-
Align the rough CTX DEM and its cameras to the reference CTX DEM (cassis_ctx_align.sh): coarsen both DEMs, one
pc_align(hillshade transform then ICP), and apply the transform to the cameras.cassis_ctx_align.sh ctx-DEM.tif ctx_ref.tif left.cub right.cub \ left.json right.json align $(pwd) -
Mapproject the CTX images and the CaSSIS framelets and run the joint bundle to produce the clean cross-sensor matches (cassis_ctx_bundle.sh; the camera solution is discarded). The image list is the CaSSIS framelets then the two CTX cubs, and the camera list is the matching cameras in the same order (the aligned CTX cameras and the CaSSIS cameras). The last-but-one argument is the
--ip-detect-method(0 gives many more cross-sensor matches here than 1).cassis_ctx_bundle.sh images.txt cameras.txt ctx_ref_blur.tif ctx_ref.tif 0 \ ba_joint/run $(pwd) -
Run the joint jitter solve, tied by those matches (cassis_ctx_jitter.sh).
cassis_ctx_jitter.sh images.txt cameras.txt ba_joint/run ctx_ref.tif \ jitter/run $(pwd) -
Remake the CTX DEM with the jitter-refined cameras (cassis_ctx_stereo.sh).
cassis_ctx_stereo.sh left.cub right.cub jitter/run-left.adjusted_state.json \ jitter/run-right.adjusted_state.json ctx_ref_blur.tif ctx_ref.tif \ ctx_dejittered $(pwd)The CaSSIS DEM is remade the usual way with cassis_stereo.sh, mapprojecting the framelets onto the de-jittered CTX.
Apache License 2.0. See the LICENSE file.