# Low-level API

The most common usage of the pyodc module will be for encoding and decoding ODB-2 data. However, the module also contains a number of classes that facilitate the exploration of the data. Together, they provide an API for exploring the structure and contents of ODB-2 data without actually decoding it.

## The Reader Object

The `Reader` class provides an access point to further details.

For example, by creating a reader instance it’s possible to see how many frames are encoded in ODB-2 data:

    [1]:

    import sys
    import os
    sys.path.insert(0, os.path.abspath('../../..'))

    [2]:

    import pyodc as odc

    r = odc.Reader('example-1.odb')

    print('frame(s):', len(r.frames))

    frame(s): 1

## The Frame Object

The `Frame` class describes specific frames in the stream.

For example, to check how many rows and columns are present in particular frames, we can inspect their properties:

    [3]:

    print('row(s):   ', [f.nrows for f in r.frames])
    print('column(s):', [f.ncolumns for f in r.frames])

    row(s):    [10]
    column(s): [7]

Next, we can inspect which columns are present in the frame and their types:

    [4]:

    print('column types:', *[f.columns for f in r.frames])

    column types: [expver:integer, date@hdr:integer, statid@hdr:string, wigos@hdr:string, obsvalue@body:double, integer_missing:integer, double_missing:double]

## Column Properties

The `ColumnInfo` class abstracts each column of a frame. To access its properties, simply refer to a particular column in a frame:

    [5]:

    obsvalue = r.frames[0].columns[4]
    print('column name:     ', obsvalue.name)
    print('column data type:', obsvalue.dtype)
    print('column index:    ', obsvalue.index)
    print('column data size:', obsvalue.datasize)

    column name:      obsvalue@body
    column data type: DataType.DOUBLE
    column index:     4
    column data size: 8

### Bitfield Columns

Bitfield columns provide additional metadata, including flag names, their offsets and sizes.

    [6]:

    r_bf = odc.Reader('example-6.odb')

    bitfield_column = r_bf.frames[0].columns[0]
    print('flag names:   ', [flag.name for flag in bitfield_column.bitfields])
    print('flag sizes:   ', [flag.size for flag in bitfield_column.bitfields])
    print('flag offsets: ', [flag.offset for flag in bitfield_column.bitfields])

    flag names:    ['flag_a', 'flag_b', 'flag_c']
    flag sizes:    [1, 2, 5]
    flag offsets:  [0, 1, 3]

## Decoding Data

Data can also be decoded directly at the frame level, by calling member function `dataframe` on a specific frame object:

    [7]:

    df_decoded = r.frames[0].dataframe()
    print(df_decoded)

       expver  date@hdr statid@hdr        wigos@hdr  obsvalue@body  \
    0       1  20210420     stat00  0-12345-0-67890         0.0000
    1       1  20210420     stat01  0-12345-0-67891        12.3456
    2       1  20210420     stat02  0-12345-0-67892        24.6912
    3       1  20210420     stat03  0-12345-0-67893        37.0368
    4       1  20210420     stat04  0-12345-0-67894        49.3824
    5       1  20210420     stat05  0-12345-0-67895        61.7280
    6       1  20210420     stat06  0-12345-0-67896        74.0736
    7       1  20210420     stat07  0-12345-0-67897        86.4192
    8       1  20210420     stat08  0-12345-0-67898        98.7648
    9       1  20210420     stat09  0-12345-0-67899       111.1104

       integer_missing  double_missing
    0           1234.0           12.34
    1           4321.0           43.21
    2              NaN             NaN
    3           1234.0           12.34
    4           4321.0           43.21
    5              NaN             NaN
    6           1234.0           12.34
    7           4321.0           43.21
    8              NaN             NaN
    9           1234.0           12.34