Parsing PVL text¶
Table of Contents
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.