From e53b51faa5ee8cc5c732ce9191baa946711cd1c5 Mon Sep 17 00:00:00 2001 From: wrobell Date: Wed, 30 Dec 2020 23:57:25 +0000 Subject: [PATCH] Add vectorized version of df and typecode functions. --- pyModeS/__init__.py | 8 +------ pyModeS/common.py | 48 ++++++++++++++++++++++++++++++++++++++++ pyModeS/vec/__init__.py | 0 pyModeS/vec/common.py | 34 ++++++++++++++++++++++++++++ pyModeS/vec/ctor.py | 15 +++++++++++++ pyModeS/vec/types.py | 9 ++++++++ pyModeS/vec/util.py | 16 ++++++++++++++ tests/vec/__init__.py | 0 tests/vec/data.py | 26 ++++++++++++++++++++++ tests/vec/test_common.py | 20 +++++++++++++++++ 10 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 pyModeS/common.py create mode 100644 pyModeS/vec/__init__.py create mode 100644 pyModeS/vec/common.py create mode 100644 pyModeS/vec/ctor.py create mode 100644 pyModeS/vec/types.py create mode 100644 pyModeS/vec/util.py create mode 100644 tests/vec/__init__.py create mode 100644 tests/vec/data.py create mode 100644 tests/vec/test_common.py diff --git a/pyModeS/__init__.py b/pyModeS/__init__.py index 60ef606..61ad228 100644 --- a/pyModeS/__init__.py +++ b/pyModeS/__init__.py @@ -1,13 +1,7 @@ import os import warnings -try: - from . import c_common as common - from .c_common import * -except: - from . import py_common as common - from .py_common import * - +from .common import * from .decoder import tell from .decoder import adsb from .decoder import commb diff --git a/pyModeS/common.py b/pyModeS/common.py new file mode 100644 index 0000000..6e9096c --- /dev/null +++ b/pyModeS/common.py @@ -0,0 +1,48 @@ +import typing as tp +from functools import singledispatch + +from .vec import common as common_vec +from .vec.types import InputData +try: + from . import c_common as common_str +except: + from . import py_common as common_str + +@singledispatch +def df(msg: tp.Any) -> int: + raise NotImplementedError('Only string and NumPy arrays supported') + +@df.register +def _df_str(msg: str) -> int: + return common_str.df(msg) + +@df.register +def _df_vec(msg: InputData) -> int: + return common_vec.df(msg) + +@singledispatch +def typecode(msg: tp.Any) -> int: + raise NotImplementedError('Only string and NumPy arrays supported') + +@typecode.register +def _tc_str(msg: str) -> int: + return common_str.typecode(msg) + +@typecode.register +def _tc_vec(msg: InputData) -> int: + return common_vec.typecode(msg) + +icao = common_str.icao +altitude = common_str.altitude +altcode = common_str.altcode +allzeros = common_str.allzeros +data = common_str.data +wrongstatus = common_str.wrongstatus +idcode = common_str.idcode +floor = common_str.floor +cprNL = common_str.cprNL +crc = common_str.crc +squawk = common_str.squawk +hex2bin = common_str.hex2bin +hex2bin = common_str.hex2bin +bin2int = common_str.bin2int diff --git a/pyModeS/vec/__init__.py b/pyModeS/vec/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyModeS/vec/common.py b/pyModeS/vec/common.py new file mode 100644 index 0000000..dc98021 --- /dev/null +++ b/pyModeS/vec/common.py @@ -0,0 +1,34 @@ +import numpy as np +import typing as tp + +from .util import create_array +from .types import InputData, DownlinkFormat, TypeCode + + +def df(data: InputData) -> DownlinkFormat: + """ + Parse downlink format address from ADS-B messages. + + :param data: ADS-B messages. + + ..seealso:: `Message structure `_ + """ + result = (data[:, 0] & 0xf8) >> 3 + result[result > 24] = 24 + return result + +def typecode(data: InputData, df_data: tp.Optional[DownlinkFormat]=None) -> TypeCode: + """ + Parse type code information from ADS-B messages. + + :param data: ADS-B messages. + :param df_data: Optional downlink format information for each ADS-B + message. + + ..seealso:: `ADS-B message types `_ + """ + result = np.zeros(len(data), dtype=np.uint8) + df_v = df(data) if df_data is None else df_data + idx = (df_v == 17) | (df_v == 18) + result[idx] = data[idx, 4] >> 3 + return create_array(result, idx) diff --git a/pyModeS/vec/ctor.py b/pyModeS/vec/ctor.py new file mode 100644 index 0000000..66e71b2 --- /dev/null +++ b/pyModeS/vec/ctor.py @@ -0,0 +1,15 @@ +import numpy as np +import typing as tp + +from .types import InputData + +def array(data: tp.Sequence[bytes]) -> InputData: + """ + Create Numpy array, which is input for parsing functions. + + :param data: Collection of ADS-B messages. + """ + vec = np.array(data) + result = vec.view(dtype=np.uint8) + return result.reshape(-1, 14) + diff --git a/pyModeS/vec/types.py b/pyModeS/vec/types.py new file mode 100644 index 0000000..fbfc34e --- /dev/null +++ b/pyModeS/vec/types.py @@ -0,0 +1,9 @@ +import numpy as np + +# define aliases for basic types to support static type analysis; these for +# convenience +# NOTE: more specific types can be defined when numpy 1.20 is released, +# i.e. use of np.ndarray[dtype, shape] shall be possible +InputData = np.ndarray +DownlinkFormat = np.ndarray +TypeCode = np.ndarray diff --git a/pyModeS/vec/util.py b/pyModeS/vec/util.py new file mode 100644 index 0000000..0617e4b --- /dev/null +++ b/pyModeS/vec/util.py @@ -0,0 +1,16 @@ +import numpy as np + +def create_array(data, idx): + """ + Create NumPy masked array. + + Note, that this function differes from NumPy constructor semantics. The + index indicates the valid values (not the invalid as in the default + masked array in NumPy). + + :param data: Input data. + :param idx: Index of valid values. + """ + return np.ma.array(data, mask=~idx) + +# vim: sw=4:et:ai diff --git a/tests/vec/__init__.py b/tests/vec/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/vec/data.py b/tests/vec/data.py new file mode 100644 index 0000000..890bf55 --- /dev/null +++ b/tests/vec/data.py @@ -0,0 +1,26 @@ +import numpy as np +from binascii import unhexlify + +from pyModeS.vec.ctor import array +from pyModeS.vec.common import df + +import pytest + +@pytest.fixture +def message(): + data = [ + '904ca3a33219741c85465ae1b4c3', # df = 18 + 'a8281d3030000000000000850d4a', # df = 21 + '8d406b902015a678d4d220000000', # example from https://mode-s.org/decode/content/ads-b/8-error-control.html + '904ca3da121010603d04f5df3ecf', # df = 18, tc = 2 + '977ba7ca0daa3e1915d83237c86e', # df = 18, tc = 1 + '8d4ca2d4234994b5452820e5b7ab', # df = 17, tc = 4 + ] + return array(np.array([unhexlify(v) for v in data])) + +@pytest.fixture +def df_message(message): + return df(message) + +# vim: sw=4:et:ai + diff --git a/tests/vec/test_common.py b/tests/vec/test_common.py new file mode 100644 index 0000000..35c87ee --- /dev/null +++ b/tests/vec/test_common.py @@ -0,0 +1,20 @@ +import numpy as np + +from pyModeS.common import df, typecode + +from .data import message + +def test_df(message): + """ + Test parsing of ADS-B downlink format. + """ + result = df(message) + assert np.array_equal([18, 21, 17, 18, 18, 17], result) + +def test_typecode(message): + """ + Test ADS-B type code parsing. + """ + result = typecode(message) + assert np.array_equal([6, 0, 4, 2, 1, 4], result) + assert np.array_equal([6, 4, 2, 1, 4], result.compressed())