Thursday, 18 December 2014

Venus Express

Since this is the week that the Venus Express mission ended then let's look at the format of data from the Venus Monitoring Camera, the VMC.

Getting the data

The Venus Express data sets are collected at the ESA Project page, and the VMC sets are at the associated FTP directory.

Looking under the per-mission catalogues then the DATA/ directories are split into orbits, and image files are in there as .IMG files.

The Sensor

The VMC actually has a single 1024*1024 CCD sensor which has four independent optical channels over the sensor - see the Calibration Report (PDF) for details, but the end result is: we have four fixed channels available - Visible, UV and two IR (centred at .935 & 1.01 uM respectively).

The data file contains a tag "DETECTOR_ID" which is either VEX_VMC_VIS,  VEX_VMC_UV, VEX_VMC_NIR-1 or VEX_VMC_NIR-2 depending on the source. This is also used for the filename so;
  • V*_UV1.IMG is UV
  • V*_VI2.IMG is Visible
  • V*_UV21.IMG is UV #1
  • V*_UV22.IMG is UV #2

The Data

The data is actually the same as the Vicar record from Cassini, but with the addition of a PDS data header . To decode this with our existing code is fairly simple;
  • Check that the file starts with a "PDS_VERSION_ID" value of "PDS3"
  • Parse through a set of (line delimited) LABEL=VALUE records
  • When we hit a single line "END" then we're done
  • At this point we look for RECORD_BYTES and LABEL_RECORDS from the data we ingested
  • Skip "RECORD_BYTES * LABEL_RECORDS" bytes from the start of the file to move over the header
  • Load a Vicar/Cassini style image from that point

Image Level Correction

One minor gotcha is that we have a visible border from the imaging on these pictures - these leave a peak white "Hexagon" frame around the image. We can just ignore this during processing, however when we level correct then these values will swamp our actual image data. We could look at a more complex framing filter on the cv calls, but it's simpler for this case to implement a custom level min/max detect rather than use the stock cv::minMaxLoc() call, and have our version simply ignore these peak 16 bit values (65535) on the input; i.e.
static void getMinMax(cv::Mat img, double* min, double* max)
int minVal, maxVal;

  minVal = 65536;
  maxVal = 0;
  for(int i=0; i<img.rows; i++)
    for(int j=0; j<img.cols; j++)
    uint16_t v;
        v =<uint16_t>(i,j);
        if (v < minVal)
          minVal = v;
        else if ((v > maxVal) && (v < 65535))
          maxVal = v;
  *min = (double)minVal;
  *max = (double)maxVal;

These are fairly simple tweaks and putting them together: From VMC Orbit 50, Image52, UV2

And we're (more or less) done.