Parsing PVL text

From a File

The pvl.load() function parses PVL text from a file or stream and returns a dict-like object (pvl.PVLModule by default) containing information from that text. This documentation will explain how to use the module as well as some sample code to use the module efficiently.

Simple Use

How to use pvl.load() to get a single value:

>>> from pathlib import Path
>>> import pvl
>>> path = Path('tests/data/pds3/simple_image_1.lbl')
>>> pvl.load(path)['RECORD_TYPE']
'FIXED_LENGTH'

>>> import pvl
>>> img = 'tests/data/pds3/simple_image_1.lbl'
>>> pvl.load(img)['RECORD_TYPE']
'FIXED_LENGTH'

>>> import pvl
>>> img = 'tests/data/pds3/simple_image_1.lbl'
>>> with open(img, 'r+') as r:
...    print(pvl.load(r)['RECORD_TYPE'])
FIXED_LENGTH

Detailed Use

To view the image label of an ISIS cube as a dictionary:

>>> import pvl
>>> img = 'tests/data/pattern.cub'
>>> module = pvl.load(img)
>>> print(module)
PVLModule([
  ('IsisCube',
   {'Core': {'Dimensions': {'Bands': 1,
                            'Lines': 90,
                            'Samples': 90},
             'Format': 'Tile',
             'Pixels': {'Base': 0.0,
                        'ByteOrder': 'Lsb',
                        'Multiplier': 1.0,
                        'Type': 'Real'},
             'StartByte': 65537,
             'TileLines': 128,
             'TileSamples': 128}})
  ('Label', PVLObject([
    ('Bytes', 65536)
  ]))
])

Not all image labels are formatted the same so different labels will have different information that you can obtain. To view what information you can extract use the .keys() function:

>>> import pvl
>>> img = 'tests/data/pds3/simple_image_1.lbl'
>>> lbl = pvl.load(img)
>>> lbl.keys()
KeysView(['PDS_VERSION_ID', 'RECORD_TYPE', 'RECORD_BYTES', 'LABEL_RECORDS', 'FILE_RECORDS', '^IMAGE', 'IMAGE'])

… now you can just copy and paste from this list:

>>> lbl['RECORD_TYPE']
'FIXED_LENGTH'

The list .keys() returns is out of order, to see the keys in the order of the dictionary use .items() function:

>>> import pvl
>>> img = 'tests/data/pds3/simple_image_1.lbl'
>>> for item in pvl.load(img).items():
...    print(item[0])
PDS_VERSION_ID
RECORD_TYPE
RECORD_BYTES
LABEL_RECORDS
FILE_RECORDS
^IMAGE
IMAGE

We can take advantage of the fact .items() returns a list in order and use the index number of the key instead of copying and pasting. This will make extracting more than one piece of information at time more convenient. For example, if you want to print out the first 5 pieces of information:

>>> import pvl
>>> img = 'tests/data/pds3/simple_image_1.lbl'
>>> pvl_items = pvl.load(img).items()
>>> for n in range(0, 5):
...    print(pvl_items[n][0], pvl_items[n][1])
PDS_VERSION_ID PDS3
RECORD_TYPE FIXED_LENGTH
RECORD_BYTES 824
LABEL_RECORDS 1
FILE_RECORDS 601

… some values have sub-dictionaries. You can access those by:

>>> print(pvl.load(img)['IMAGE'].keys())
KeysView(['LINES', 'LINE_SAMPLES', 'SAMPLE_TYPE', 'SAMPLE_BITS', 'MEAN', 'MEDIAN', 'MINIMUM', 'MAXIMUM', 'STANDARD_DEVIATION', 'CHECKSUM'])
>>> print(pvl.load(img)['IMAGE']['SAMPLE_BITS'])
8

Another way of using pvl.load() is to use Python’s with open() command. Otherwise using this method is very similar to using the methods described above:

>>> import pvl
>>> with open('tests/data/pattern.cub','r') as r:
...    print(pvl.load(r)['Label']['Bytes'])
65536

From a String

The pvl.loads() function returns a Python object (typically a pvl.PVLModule object which is dict-like) based on parsing the PVL text in the string parameter that it is given.

Simple Use

How to use pvl.loads():

>>> import pvl
>>> s = """String = 'containing the label of the image'
... key = value
... END
... """
>>> pvl.loads(s).keys()
KeysView(['String', 'key'])

>>> pvl.loads(s)['key']
'value'

Detailed Use

To view the image label dictionary:

>>> import pvl
>>> string = """Object = IsisCube
...   Object = Core
...     StartByte   = 65537
...     Format      = Tile
...     TileSamples = 128
...     TileLines   = 128
...
...   End_Object
... End_Object
...
... Object = Label
...   Bytes = 65536
... End_Object
... End"""
>>> print(pvl.loads(string))
PVLModule([
  ('IsisCube',
   {'Core': {'Format': 'Tile',
             'StartByte': 65537,
             'TileLines': 128,
             'TileSamples': 128}})
  ('Label', PVLObject([
    ('Bytes', 65536)
  ]))
])

… to view the keys available:

>>> print(pvl.loads(string).keys())
KeysView(['IsisCube', 'Label'])

… and to see the information contained in the keys:

>>> print(pvl.loads(string)['Label'])
PVLObject([
  ('Bytes', 65536)
])

… and what is in the sub-dictionary:

>>> print(pvl.loads(string)['Label']['Bytes'])
65536

By default, pvl.loads() and pvl.load() are very permissive, and do their best to attempt to parse a wide variety of PVL ‘flavors.’

If a parsed label has a parameter with a missing value, the default behavior of these functions will be to assign a pvl.parser.EmptyValueAtLine object as the value:

>>> string = """
... Object = Label
...   A =
... End_Object
... End"""

>>> print(pvl.loads(string))
PVLModule([
  ('Label',
   {'A': EmptyValueAtLine(3 does not have a value. Treat as an empty string)})
])

Stricter parsing can be accomplished by passing a different grammar object (e.g. pvl.grammar.PVLGrammar, pvl.grammar.ODLGrammar) to pvl.loads() or pvl.load():

>>> import pvl
>>> some_pvl = """Comments = "PVL and ODL only allow /* */ comments"
... /* like this */
... # but people use hash-comments all the time
... END
... """
>>> print(pvl.loads(some_pvl))
PVLModule([
  ('Comments', 'PVL and ODL only allow /* */ comments')
])
>>> pvl.loads(some_pvl, grammar=pvl.grammar.PVLGrammar())
Traceback (most recent call last):
  ...
pvl.exceptions.LexerError: (LexerError(...), 'Expecting an Aggregation Block, an Assignment Statement, or an End Statement, but found "#" : line 3 column 1 (char 67) near "like this */\n# but people"')

From a URL

The pvl.loadu() function returns a Python object (typically a pvl.PVLModule object which is dict-like) based on parsing the PVL text in the data returned from a URL.

This is very similar to parsing PVL text from a file, but you use pvl.loadu() instead:

>>> import pvl
>>> url = 'https://hirise-pds.lpl.arizona.edu/PDS/RDR/ESP/ORB_017100_017199/ESP_017173_1715/ESP_017173_1715_RED.LBL'
>>> pvl.loadu(url)['VIEWING_PARAMETERS']['PHASE_ANGLE']
Quantity(value=50.784875, units='DEG')

Of course, other kinds of URLs, like file, ftp, rsync, sftp and more can be used.

Return non-standard objects

The “loaders” return a dict-like filled with Python objects based on the types inferred from the PVL-text. Sometimes you may want the pvl library to return different types in the dict-like, and pvl has some limited capacity for that (so far just real and quantity types).

Normally real number values in the PVL-text will be returned as Python float objects. However, what if you wanted all of the real values to be returned in the dict-like as Python decimal.Decimal objects (because you wanted to preserve numeric precision)? You can do that by providing the object type you want via the real_cls argument of a decoder constructor, like so:

>>> from decimal import Decimal
>>> import pvl
>>> text = "gigawatts = 1.210"
>>>
>>> flo = pvl.loads(text)
>>> print(flo)
PVLModule([
  ('gigawatts', 1.21)
])
>>>
>>> print(type(flo["gigawatts"]))
<class 'float'>
>>> dec = pvl.loads(text, decoder=pvl.decoder.OmniDecoder(real_cls=Decimal))
>>> print(dec)
PVLModule([
  ('gigawatts', Decimal('1.210'))
])
>>> print(type(dec["gigawatts"]))
<class 'decimal.Decimal'>

Any class that can be passed a str object to initialize an object can be provided to real_cls, but it should emit a ValueError if it is given a string that should not be converted to a real number value.

To learn more about quantity classes in pvl, please see Quantities: Values and Units.