8.16. JunoCam

JunoCam is an optical camera on the Juno spacecraft. It has been taking images of Jupiter and its satellites since 2016.

This example shows how to produce terrain models and ortho images from JunoCam images for Ganymede, the largest moon of Jupiter. These will be registered to the Voyager-Galileo global mosaic of Ganymede.

8.16.1. Fetching the data

Set (in bash):

url=https://planetarydata.jpl.nasa.gov/img/data/juno/JNOJNC_0018/DATA/RDR/JUPITER/ORBIT_34/

Download the .IMG and .LBL files for two observations:

for f in JNCR_2021158_34C00001_V01 JNCR_2021158_34C00002_V01; do
  for ext in .IMG .LBL; do
    wget $url/$f$ext
  done
done

8.16.2. Preparing the data

Ensure ISIS is installed (Section 2.3.1).

Create ISIS .cub files:

for f in JNCR_2021158_34C00001_V01 JNCR_2021158_34C00002_V01; do
    junocam2isis fr = $f.LBL to = $f.cub fullccd = yes
done

This will result in many files, because JunoCam acquires multiple overlapping images in quick succession.

Run spiceinit to get the camera pointing and other information:

for f in *.cub; do
  spiceinit from = $f web = true
done

If the web = true option does not work, the ISIS data for the juno mission needs to be downloaded, per the ISIS documentation.

We will put all these files into an img subdirectory.

../_images/junocam_raw.png

Fig. 8.30 JunoCam images JNCR_2021158_34C00001_V01_0012 and JNCR_2021158_34C00002_V01_0010 as shown by stereo_gui (Section 16.70). The shared area and a couple of matching craters are highlighted.

A JunoCam image is made of 3 framelets, each about 128 pixels tall. The image width is 1648 pixels. Consecutive images have overlapping areas, which helps eliminate the effect of the gaps between the framelets.

8.16.3. External reference

We will pixel-level register the JunoCam images to the 1 km / pixel Ganymede Voyager - Galileo global mosaic.

Crop from it a portion that covers our area of interest as:

win="5745386 2069139 7893530 36002"
gdal_translate -projwin $win                        \
  Ganymede_Voyager_GalileoSSI_global_mosaic_1km.tif \
  galileo_ortho.tif

This will be stored in a subdirectory named ref.

8.16.4. Initial DEM

Both image registration and stereo DEM creation benefit from mapprojecting the JunoCam images onto a prior low-resolution DEM. A reasonably good such DEM can be created by considering the surface zero height corresponding to the earlier orthoimage clip.

Given that the elevations on Ganymede are on the order of 1 km, which is about the image resolution, this works well enough. Such a DEM is produced with image_calc (Section 16.34) as:

image_calc -c "var_0 * 0"     \
  --output-nodata-value -1e+6 \
  -d float32                  \
  ref/galileo_ortho.tif       \
  -o ref/flat_dem.tif

8.16.5. Image selection

We chose to focus on the highest resolution JunoCam images of Ganymede, as that resulted in the best terrain model. The quality of the terrain model degraded towards the limb, as expected. We worked with the images:

JNCR_2021158_34C00001_V01_0009
JNCR_2021158_34C00001_V01_0010
JNCR_2021158_34C00001_V01_0011
JNCR_2021158_34C00001_V01_0012
JNCR_2021158_34C00001_V01_0013

and corresponding images that have 34C00002 in their name.

8.16.6. Mapprojection

Each image was mapprojected (Section 16.42) at 1 km / pixel resolution, with a command such as:

mapproject --tr 1000                     \
  ref/flat_dem.tif                       \
  img/JNCR_2021158_34C00001_V01_0010.cub \
  map/JNCR_2021158_34C00001_V01_0010.map.tif

8.16.7. GCP creation

We will create GCP that ties each JunoCam image to the reference Voyager-Galileo image mosaic with gcp_gen (Section 16.24), invoked as follows:

f=JNCR_2021158_34C00001_V01_0010

gcp_gen                               \
  --ip-detect-method 0                \
  --inlier-threshold 50               \
  --ip-per-image 20000                \
  --individually-normalize            \
  --camera-image img/${f}.cub         \
  --mapproj-image map/${f}.map.tif    \
  --ortho-image ref/galileo_ortho.tif \
  --dem ref/flat_dem.tif              \
  --gcp-sigma 1000                    \
  --output-prefix gcp/run             \
  --output-gcp gcp/${f}.gcp

We set --gcp-sigma 1000, which is rather high, as we do not know the exact DEM that was employed to produce the reference image mosaic. The option --individually-normalize was essential, as these images come from different sources.

../_images/junocam_galileo_matches.png

Fig. 8.31 Interest point matches between mapprojected image JNCR_2021158_34C00001_V01_0010 and the Voyager-Galileo mosaic. GCP are made from these matches.

8.16.8. Bundle adjustment

Bundle adjustment (Section 16.50) was run with the 10 images selected earlier and the GCP files:

parallel_bundle_adjust                           \
  img/JNCR_2021158_34C0000[1-2]_V01_000[9-9].cub \
  img/JNCR_2021158_34C0000[1-2]_V01_001[0-3].cub \
  --ip-per-image 20000                           \
  --num-iterations 500                           \
  gcp/*.gcp                                      \
  -o ba/run

8.16.9. Stereo terrain creation

We ran parallel_stereo on each pair of images having 34C00001 vs 34C00002 in their names. Most of these overlap and result in a DEM:

l=JNCR_2021158_34C00001_V01_0010
r=JNCR_2021158_34C00002_V01_0010
pref=stereo_map/${l}_${r}/run

parallel_stereo                      \
  map/$l.map.tif map/$r.map.tif      \
  img/$l.cub img/$r.cub              \
  --bundle-adjust-prefix ba/run      \
  --ip-per-image 20000               \
  --stereo-algorithm asp_mgm         \
  --subpixel-mode 9                  \
  --subpixel-kernel 7 7              \
  --nodata-value 0                   \
  --num-matches-from-disparity 10000 \
  ${pref}                            \
  ref/flat_dem.tif

Here we used a small subpixel-kernel of 7 x 7 pixels, to ensure as little as possible is eroded from the already narrow images. Note that the asp_mgm algorithm default corr-kernel value is 5 x 5 pixels (Section 17).

The stereo convergence angle (Section 8.1) was about 35 degrees, which is very good.

Set the output projection (the same as in the reference image mosaic):

proj='+proj=eqc +lat_ts=0 +lat_0=0 +lon_0=180 +x_0=0 +y_0=0 +R=2632344.9707 +units=m +no_defs'

Then, point2dem (Section 16.57) was run:

point2dem         \
  --t_srs "$proj" \
  --tr 1000       \
  ${pref}-PC.tif  \
  --orthoimage    \
  ${pref}-L.tif

This was followed by mosaicking of DEMs and orthoimages with dem_mosaic (Section 16.20), and colorization with colormap (Section 16.14).

../_images/junocam_dem_drg.png

Fig. 8.32 Left: Mosaicked DEM created from stereo of JunCam images. The color range corresponds to elevations between -1500 and 1500 meters. Right: produced JunoCam orthoimage overlaid on top of the Voyager-Galileo global mosaic.

The results of this processing are shown in the figure above. Three things are notable:

  • The image registration is pixel-level.

  • There are some seams at the top and bottom. Those can be eliminated with more images.

  • There are systematic artifacts in the elevations.

The latter issue is likely due to not well-modeled distortion and TDI effects, given the JunoCam camera design. This will be fixed in the next section.

8.16.10. Intrinsics refinement

../_images/junocam_dem_opt.png

Fig. 8.33 Left: The earlier mosaicked DEM created from stereo of JunCam images. Right: the produced DEM after optimizing the lens distortion with a DEM constraint. These are plotted with the same range of of elevations (-1500 to 1500 meters). The systematic artifacts are much less pronounced.

To address the systematic elevation artifacts, we will refine the intrinsics and extrinsics of the cameras, while using the zero elevation DEM as a ground constraint (with an uncertainty).

The approach in Section 12.2.3 is followed.

We will make use of dense matches from disparity, as in Section 12.2.4.2. The option for that, --num-matches-from-disparity, was already set in the stereo runs above. We got good results with sparse matches too, as produced by bundle_adjust, if there are a lot of them, but dense matches offer more control over the coverage.

These matches will augment existing sparse matches in the ba directory. For that, first the sparse matches will be copied to a new directory, called dense_matches. Then, we will copy on top the small number of dense matches from each stereo directory above, while removing the string -disp from each such file name, and ensuring each corresponding sparse match file is overwritten.

It is necessary to create CSM cameras (Section 8.12) for the JunoCam images, to be able to optimize the intrinsics. For the first camera, that is done with the cam_gen program (Section 16.8), with a command such as:

cam_gen img/JNCR_2021158_34C00001_V01_0010.cub          \
  --input-camera img/JNCR_2021158_34C00001_V01_0010.cub \
  --reference-dem ref/flat_dem.tif                      \
  --focal-length 1480.5905405405405405                  \
  --optical-center 814.21 600.0                         \
  --pixel-pitch 1                                       \
  --refine-camera                                       \
  --refine-intrinsics distortion                        \
  -o csm/JNCR_2021158_34C00001_V01_0010.json

The values for the focal length (in pixels) and optical center (in pixels) were obtained by peeking in the .cub file metadata.

The resulting lens distortion model is not the one for JunoCam, which has two distortion parameters, but rather the OpenCV radial-tangential model with five parameters (Section 20.3). To use the JunoCam lens distortion model, adjust the value of --distortion-type in cam_gen above.

The cam_test program (Section 16.9) can help validate that the camera is converted well.

The intrinsics of this camera are transferred without further optimization to the other cameras as:

sample=csm/JNCR_2021158_34C00001_V01_0010.json
for f in                                         \
  img/JNCR_2021158_34C0000[1-2]_V01_0009.cub     \
  img/JNCR_2021158_34C0000[1-2]_V01_001[0-3].cub \
  ; do
  g=${f/.cub/.json}
  g=csm/$(basename $g)
  cam_gen $f                                     \
    --input-camera $f                            \
    --sample-file $sample                        \
    --reference-dem ref/flat_dem.tif             \
    --pixel-pitch 1                              \
    --refine-camera                              \
    --refine-intrinsics none                     \
    -o $g
done

Next, bundle adjustment is run, with the previously optimized adjustments that reflect the registration to the reference Voyager-Galileo mosaic:

bundle_adjust                                     \
  img/JNCR_2021158_34C0000[1-2]_V01_0009.cub      \
  img/JNCR_2021158_34C0000[1-2]_V01_001[0-3].cub  \
  csm/JNCR_2021158_34C0000[1-2]_V01_0009.json     \
  csm/JNCR_2021158_34C0000[1-2]_V01_001[0-3].json \
  --input-adjustments-prefix ba/run               \
  --match-files-prefix dense_matches/run          \
  --num-iterations 50                             \
  --solve-intrinsics                              \
  --intrinsics-to-float all                       \
  --intrinsics-to-share all                       \
  --heights-from-dem ref/flat_dem.tif             \
  --heights-from-dem-uncertainty 5000             \
  gcp/*.gcp                                       \
  -o ba_rfne/run

Lastly, stereo is run with the optimized model state camera files (Section 8.12.6) saved in ba_rfne.

The result is in Fig. 8.33.

It was found that better DEMs are produced by re-mapprojecting with latest cameras and re-running stereo from scratch, rather than reusing stereo runs with the option --prev-run-prefix (Section 16.52). Likely that is because the cameras change in non-small ways.

With ISIS 9.0.0 and later, a CSM file produced as above can be embedded in the .cub file to be used with ISIS (Section 8.12.7).