Writing out PVL text

This documentation explains how you can use pvl.dump() and pvl.dumps() so you can change, add, and/or write out a Python dict-like object as PVL text either to a str or a file. This documentation assumes that you’ve read about how to parse PVL text and know how pvl.load() and pvl.loads() work.

The examples primarily use an ISIS Cube image label format, which typically doesn’t conform to PDS 3 standards, so pay attention to the differences between the PVL text that is loaded, versus the PDS 3-compliant PVL text that is dumped.

However, this library can write/alter any PVL compliant label.

Writing PVL Text to a File

The pvl.dump() function allows you to write out a dict-like Python object (typically a pvl.PVLModule object) to a file as PVL text.

Simple Use

Read a label from a file:

>>> import pvl
>>> pvl_file = 'tests/data/pds3/tiny1.lbl'
>>> label = pvl.load(pvl_file)
>>> print(label)
PVLModule([
  ('PDS_VERSION_ID', 'PDS3')
])

… then you can change a value:

>>> label['PDS_VERSION_ID'] = 42
>>> print(label)
PVLModule([
  ('PDS_VERSION_ID', 42)
])

… then add keys to the label object:

>>> label['New_Key'] = 'New_Value'
>>> print(label)
PVLModule([
  ('PDS_VERSION_ID', 42)
  ('New_Key', 'New_Value')
])

… and then write out the PVL text to a file:

>>> pvl.dump(label, 'new.lbl')
54

pvl.dump() returns the number of characters written to the file.

Changing A Key

More complicated parameter value change.

Load some PVL text from a file:

>>> import pvl
>>> img = 'tests/data/pattern.cub'
>>> label = pvl.load(img)
>>> print(label['IsisCube']['Core']['Format'])
Tile

… then change key ‘Format’ to ‘Changed_Value’:

>>> label['IsisCube']['Core']['Format'] = 'Changed_Value'

… then writing out file with new value:

>>> new_file = 'new.lbl'
>>> pvl.dump(label, new_file)
494

If you then try to show the changed value in the file, you’ll get an error:

>>> new_label = pvl.load(new_file)
>>> print(new_label['IsisCube']['Core']['Format'])
Traceback (most recent call last):
   ...
KeyError: 'Format'

This is because the default for pvl.dump() and pvl.dumps() is to write out PDS3-Standards-compliant PVL, in which the parameter values (but not the aggregation block names) are uppercased:

>>> print(new_label['IsisCube']['Core'].keys())
KeysView(['STARTBYTE', 'FORMAT', 'TILESAMPLES', 'TILELINES', 'Dimensions', 'Pixels'])
>>> print(new_label['IsisCube']['Core']['FORMAT'])
Changed_Value

Clean up:

>>> import os
>>> os.remove(new_file)

Yes, this case difference is weird, yes, this means that you need to be aware of the case of different keys in your pvl.PVLModule objects.

Writing PVL Text to a String

The pvl.dumps() function allows you to convert a dict-like Python object (typically a pvl.PVLModule object) to a Python str object which contains the PVL text.

Simple Use

Get started, as above:

>>> import pvl
>>> pvl_file = 'tests/data/pds3/tiny1.lbl'
>>> label = pvl.load(pvl_file)
>>> print(label)
PVLModule([
  ('PDS_VERSION_ID', 'PDS3')
])

… then change a value, and add keys:

>>> label['PDS_VERSION_ID'] = 42
>>> label['New_Param'] = 'New_Value'
>>> print(label)
PVLModule([
  ('PDS_VERSION_ID', 42)
  ('New_Param', 'New_Value')
])

… then write to a string:

>>> print(pvl.dumps(label))
PDS_VERSION_ID = 42
NEW_PARAM      = New_Value
END

Here we can see the effects of the PDS3LabelEncoder in the default behavior of pvl.dumps(): it uppercases the parameters, and puts a blank line after the END statement. If we were to use the PVLEncoder, you can see different behavior:

>>> print(pvl.dumps(label, encoder=pvl.encoder.PVLEncoder()))
PDS_VERSION_ID = 42;
New_Param      = New_Value;
END;

Adding A Key

More complicated:

>>> import pvl
>>> pvl_file = 'tests/data/pds3/group1.lbl'
>>> label = pvl.load(pvl_file)
>>> print(label)
PVLModule([
  ('PDS_VERSION_ID', 'PDS3')
  ('IMAGE',
   {'CHECKSUM': 25549531,
    'MAXIMUM': 255,
    'STANDARD_DEVIATION': 16.97019})
  ('SHUTTER_TIMES', PVLGroup([
    ('START', 1234567)
    ('STOP', 2123232)
  ]))
])

… then add a new key and value to a sub group:

>>> label['New_Key'] = 'New_Value'
>>> label['IMAGE']['New_SubKey'] = 'New_SubValue'
>>> print(label)
PVLModule([
  ('PDS_VERSION_ID', 'PDS3')
  ('IMAGE',
   {'CHECKSUM': 25549531,
    'MAXIMUM': 255,
    'New_SubKey': 'New_SubValue',
    'STANDARD_DEVIATION': 16.97019})
  ('SHUTTER_TIMES', PVLGroup([
    ('START', 1234567)
    ('STOP', 2123232)
  ]))
  ('New_Key', 'New_Value')
])

… then when we dump, the default is to write PDS3 Labels, so the parameters are uppercased:

>>> print(pvl.dumps(label))
PDS_VERSION_ID = PDS3
OBJECT = IMAGE
  MAXIMUM            = 255
  STANDARD_DEVIATION = 16.97019
  CHECKSUM           = 25549531
  NEW_SUBKEY         = New_SubValue
END_OBJECT = IMAGE
GROUP = SHUTTER_TIMES
  START = 1234567
  STOP  = 2123232
END_GROUP = SHUTTER_TIMES
NEW_KEY        = New_Value
END

Example with an ISIS cube file

>>> import pvl
>>> img = 'tests/data/pattern.cub'
>>> label = pvl.load(img)
>>> label['New_Key'] = 'New_Value'
>>> label_string = pvl.dumps(label)
>>> print(label_string)
OBJECT = IsisCube
  OBJECT = Core
    STARTBYTE   = 65537
    FORMAT      = Tile
    TILESAMPLES = 128
    TILELINES   = 128
    GROUP = Dimensions
      SAMPLES = 90
      LINES   = 90
      BANDS   = 1
    END_GROUP = Dimensions
    GROUP = Pixels
      TYPE       = Real
      BYTEORDER  = Lsb
      BASE       = 0.0
      MULTIPLIER = 1.0
    END_GROUP = Pixels
  END_OBJECT = Core
END_OBJECT = IsisCube
OBJECT = Label
  BYTES = 65536
END_OBJECT = Label
NEW_KEY      = New_Value
END

PVL text for ISIS program consumption

There are a number of ISIS programs that take PVL text files as a way of allowing users to provide more detailed inputs. To write PVL text that is readable by ISIS, you can use the pvl.encoder.ISISEncoder. Here’s an example of creating a map file used by the ISIS program cam2map. Since cam2map needs the ‘Mapping’ aggregation to be a PVL Group, you must use the pvl.PVLGroup object to assign to ‘Mapping’ rather than just a dict-like (which gets encoded as a PVL Object by default). You’d normally use pvl.dump() to write to a file, but we use pvl.dumps() here to show what you’d get:

>>> import pvl
>>> subsc_lat = 10
>>> subsc_lon = 10
>>> map_pvl = {'Mapping': pvl.PVLGroup({'ProjectionName': 'Orthographic',
...                                     'CenterLatitude': subsc_lat,
...                                     'CenterLongitude': subsc_lon})}
>>> print(pvl.dumps(map_pvl, encoder=pvl.encoder.ISISEncoder()))
Group = Mapping
  ProjectionName  = Orthographic
  CenterLatitude  = 10
  CenterLongitude = 10
End_Group = Mapping
END

Pre-1.0 pvl dump behavior

If you don’t like the new default behavior of writing out PDS3 Label Compliant PVL text, then just using an encoder with some different settings will get you the old style:

>>> import pvl
>>> img = 'tests/data/pattern.cub'
>>> label = pvl.load(img)
>>> print(pvl.dumps(label))
OBJECT = IsisCube
  OBJECT = Core
    STARTBYTE   = 65537
    FORMAT      = Tile
    TILESAMPLES = 128
    TILELINES   = 128
    GROUP = Dimensions
      SAMPLES = 90
      LINES   = 90
      BANDS   = 1
    END_GROUP = Dimensions
    GROUP = Pixels
      TYPE       = Real
      BYTEORDER  = Lsb
      BASE       = 0.0
      MULTIPLIER = 1.0
    END_GROUP = Pixels
  END_OBJECT = Core
END_OBJECT = IsisCube
OBJECT = Label
  BYTES = 65536
END_OBJECT = Label
END

>>> print(pvl.dumps(label, encoder=pvl.PVLEncoder(end_delimiter=False)))
...
BEGIN_OBJECT = IsisCube
  BEGIN_OBJECT = Core
    StartByte   = 65537
    Format      = Tile
    TileSamples = 128
    TileLines   = 128
    BEGIN_GROUP = Dimensions
      Samples = 90
      Lines   = 90
      Bands   = 1
    END_GROUP = Dimensions
    BEGIN_GROUP = Pixels
      Type       = Real
      ByteOrder  = Lsb
      Base       = 0.0
      Multiplier = 1.0
    END_GROUP = Pixels
  END_OBJECT = Core
END_OBJECT = IsisCube
BEGIN_OBJECT = Label
  Bytes = 65536
END_OBJECT = Label
END

… of course, to really get the true old behavior, you should also use the carriage return/newline combination line endings, and encode the string as a bytearray, since that is the Python type that the pre-1.0 library produced:

>>> print(pvl.dumps(label, encoder=pvl.PVLEncoder(end_delimiter=False,
...                                               newline='\r\n')).encode())
b'BEGIN_OBJECT = IsisCube\r\n  BEGIN_OBJECT = Core\r\n    StartByte   = 65537\r\n    Format      = Tile\r\n    TileSamples = 128\r\n    TileLines   = 128\r\n    BEGIN_GROUP = Dimensions\r\n      Samples = 90\r\n      Lines   = 90\r\n      Bands   = 1\r\n    END_GROUP = Dimensions\r\n    BEGIN_GROUP = Pixels\r\n      Type       = Real\r\n      ByteOrder  = Lsb\r\n      Base       = 0.0\r\n      Multiplier = 1.0\r\n    END_GROUP = Pixels\r\n  END_OBJECT = Core\r\nEND_OBJECT = IsisCube\r\nBEGIN_OBJECT = Label\r\n  Bytes = 65536\r\nEND_OBJECT = Label\r\nEND'