rpps 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rpps-0.0.1/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2024 Anonoei
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
rpps-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.1
2
+ Name: rpps
3
+ Version: 0.0.1
4
+ Summary: RF Parameter Processor Suite
5
+ Author-email: Anonoei <dev@anonoei.com>
6
+ Project-URL: Homepage, https://github.com/anonoei/RPPS
7
+ Project-URL: Documentation, https://anonoei.github.io/RPPS/
8
+ Project-URL: Repository, https://github.com/Anonoei/RPPS.git
9
+ Project-URL: Issues, https://github.com/Anonoei/RPPS/issues
10
+ Project-URL: Source, https://github.com/anonoei/RPPS
11
+ Keywords: anonoei,dsp,sdr
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Operating System :: OS Independent
17
+ Requires-Python: >=3.6
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Provides-Extra: dev
21
+ Requires-Dist: twine; extra == "dev"
22
+ Requires-Dist: build; extra == "dev"
23
+ Requires-Dist: black; extra == "dev"
24
+ Requires-Dist: bumpver; extra == "dev"
25
+ Requires-Dist: pytest; extra == "dev"
26
+ Requires-Dist: pdoc3; extra == "dev"
27
+
28
+ # RPPS
29
+ RF Parameter Processor Suite
30
+
31
+ RPPS is a generic signal processor/generator library.
32
+
33
+ - [Documentation](https://anonoei.github.io/RPPS/)
34
+ - [PyPI](https://pypi.org/project/rpps/)
35
+
36
+ ## Install
37
+ 1. Run `python3 -m pip install rpps`
38
+ 2. In your project, `import rpps as rp`
39
+
40
+ ## Roadmap
41
+ - [ ] Pre-processing
42
+ - [ ] Modulation
43
+ - [ ] PSK
44
+ - [X] QPSK
45
+ - [ ] QAM
46
+ - [ ] APSK
47
+ - [ ] ASK
48
+ - [ ] FSK
49
+ - [ ] Coding
50
+ - [ ] Block
51
+ - [X] Repetition
52
+ - [ ] LDPC
53
+ - [ ] TPC
54
+ - [ ] Convolutional
55
+ - [ ] Viterbi
56
+ - [ ] De-scramble
57
+ - [ ] v.35
58
+ - [ ] De-frame
59
+ - [ ] HDLC
60
+ - [ ] PPP
rpps-0.0.1/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # RPPS
2
+ RF Parameter Processor Suite
3
+
4
+ RPPS is a generic signal processor/generator library.
5
+
6
+ - [Documentation](https://anonoei.github.io/RPPS/)
7
+ - [PyPI](https://pypi.org/project/rpps/)
8
+
9
+ ## Install
10
+ 1. Run `python3 -m pip install rpps`
11
+ 2. In your project, `import rpps as rp`
12
+
13
+ ## Roadmap
14
+ - [ ] Pre-processing
15
+ - [ ] Modulation
16
+ - [ ] PSK
17
+ - [X] QPSK
18
+ - [ ] QAM
19
+ - [ ] APSK
20
+ - [ ] ASK
21
+ - [ ] FSK
22
+ - [ ] Coding
23
+ - [ ] Block
24
+ - [X] Repetition
25
+ - [ ] LDPC
26
+ - [ ] TPC
27
+ - [ ] Convolutional
28
+ - [ ] Viterbi
29
+ - [ ] De-scramble
30
+ - [ ] v.35
31
+ - [ ] De-frame
32
+ - [ ] HDLC
33
+ - [ ] PPP
@@ -0,0 +1,54 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "rpps"
7
+ version = "0.0.1"
8
+ description = "RF Parameter Processor Suite"
9
+ dependencies = []
10
+ requires-python = ">=3.6"
11
+ authors = [
12
+ {name = "Anonoei", email="dev@anonoei.com"}
13
+ ]
14
+ readme = "README.md"
15
+ keywords = ["anonoei", "dsp", "sdr"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+
19
+ "Intended Audience :: Developers",
20
+
21
+ "License :: OSI Approved :: MIT License",
22
+
23
+ "Programming Language :: Python :: 3",
24
+ "Operating System :: OS Independent",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/anonoei/RPPS"
29
+ Documentation = "https://anonoei.github.io/RPPS/"
30
+ Repository = "https://github.com/Anonoei/RPPS.git"
31
+ Issues = "https://github.com/Anonoei/RPPS/issues"
32
+ Source = "https://github.com/anonoei/RPPS"
33
+
34
+ [project.optional-dependencies]
35
+ dev = ["twine", "build", "black", "bumpver", "pytest", "pdoc3"]
36
+
37
+ [tool.bumpver]
38
+ current_version = "0.0.1"
39
+ version_pattern = "MAJOR.MINOR.PATCH"
40
+ commit_message = "bump version {old_version} -> {new_version}"
41
+ tag_message = "{new_version}"
42
+ tag_scope = "default"
43
+ pre_commit_hook = ""
44
+ post_commit_hook = ""
45
+ commit = true
46
+ tag = true
47
+ push = false
48
+
49
+ [tool.bumpver.file_patterns]
50
+ "pyproject.toml" = [
51
+ 'current_version = "{version}"',
52
+ 'version = "{version}"'
53
+ ]
54
+ "src/RPPS/__init__.py" = ["{version}"]
rpps-0.0.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ """
2
+ Initialize RPPS
3
+ """
4
+
5
+ __version__ = "0.0.1"
6
+ __author__ = "Anonoei <dev@anonoei.com>"
7
+
8
+
9
+ from pyboiler.logger import Logger, Level
10
+
11
+ Logger("RPPS", Level.TRACE)
12
+
13
+ # Import helpers/globals
14
+ from .helpers import bitarray, Stream
15
+ from .meta import Meta
16
+
17
+
18
+ from . import coding
19
+ from . import mod
20
+ from . import viz
@@ -0,0 +1,14 @@
1
+ # Import helpers/globals
2
+ from ..helpers.bitarray import bitarray
3
+ from ..helpers.stream import Stream
4
+ from ..meta import Meta
5
+
6
+ # -----
7
+
8
+ from .coding import Coding
9
+
10
+ from .block import Block
11
+ from .convolutional import Convolutional
12
+
13
+ from ._blk import *
14
+ from ._cnv import *
@@ -0,0 +1 @@
1
+ from .repetition import Repetition
@@ -0,0 +1,27 @@
1
+ from ..block import Block
2
+ from .. import Stream
3
+ from .. import bitarray
4
+
5
+ import numpy as np
6
+
7
+ class Repetition(Block):
8
+ def __init__(self, num, den):
9
+ super().__init__(num, den, den)
10
+
11
+ def encode(self, data: Stream):
12
+ encoded_data = bitarray()
13
+ for bit in data.bitarray:
14
+ for _ in range(self.length):
15
+ encoded_data.append(bit)
16
+ return encoded_data
17
+
18
+ def decode(self, encoded_data: bitarray):
19
+ decoded_data = bitarray()
20
+ for i in range(0, len(encoded_data), self.length):
21
+ bits = encoded_data[i:i+self.length]
22
+ diff = self.length - sum(bits)
23
+ if self.length/2 > diff:
24
+ decoded_data.append(1)
25
+ else:
26
+ decoded_data.append(0)
27
+ return decoded_data
@@ -0,0 +1 @@
1
+ from .viterbi import Viterbi
@@ -0,0 +1,77 @@
1
+ from ..convolutional import Convolutional
2
+ from .. import Stream
3
+ from .. import bitarray
4
+
5
+ import numpy as np
6
+
7
+ class Viterbi(Convolutional):
8
+ def encode(self, data: Stream):
9
+ """
10
+ SOVA (Soft-Output Viterbi Algorithm) encoding
11
+ :param data: stream of arbitrary data
12
+ :param rate: encoding rate (e.g. 7/8)
13
+ :return: encoded data and trellis diagram
14
+ """
15
+ # Calculate the number of systematic bits and redundant bits
16
+ N_systematic = int(self._rate * len(data.bitarray))
17
+ N_redundant = len(data.bitarray) - N_systematic
18
+
19
+ # Initialize the trellis diagram
20
+ T = np.zeros((2, 2), dtype=int)
21
+ T[0, :] = [1, 0] # initial state
22
+
23
+ # Encode the data
24
+ encoded_data = bitarray()
25
+ for i in range(len(data.bitarray)):
26
+ if i < N_systematic:
27
+ x = data.bitarray[i]
28
+ else:
29
+ x = np.random.randint(2) # add a random bit for redundancy
30
+ y = T[:, -1].copy() # get the previous state
31
+ T[:, -1] = [x, x ^ y[0]] # update the trellis diagram
32
+ encoded_data.append(int(y[0]))
33
+ encoded_data.append(int(y[1]))
34
+ #encoded_data.extend([int(y[0]), int(y[1])])
35
+
36
+ return encoded_data, T
37
+
38
+ def decode(self, encoded_data):
39
+ """
40
+ SOVA (Soft-Output Viterbi Algorithm) decoding
41
+ :param encoded_data: encoded data
42
+ :param rate: encoding rate (e.g. 7/8)
43
+ :return: decoded data
44
+ """
45
+ # Calculate the number of systematic bits and redundant bits
46
+ N_systematic = int(self._rate * len(encoded_data.bitarray))
47
+ N_redundant = len(encoded_data.bitarray) - N_systematic
48
+
49
+ # Initialize the trellis diagram
50
+ T = np.zeros((2, 2), dtype=int)
51
+ T[0, :] = [1, 0] # initial state
52
+
53
+ # Define the transition probabilities (trellis diagram)
54
+ P = np.array([[1/2, 1/2], [1/2, 1/2]]) # for a rate-1/2 convolutional code
55
+ P = np.kron(P, np.ones((2, 2))) # repeat the transition probabilities
56
+
57
+
58
+ # Initialize the likelihoods and state sequences
59
+ L = np.zeros((len(encoded_data.bitarray), 2)) # likelihoods
60
+ S = np.zeros(len(encoded_data.bitarray), dtype=int) # state sequence
61
+
62
+ # Iterate over the codeword
63
+ for i in range(len(encoded_data.bitarray)):
64
+ # Calculate the likelihoods for each possible state
65
+ #L[i, :] = P[T[:, -1].copy(), :] * (encoded_data.bitarray[i] == T[-1, :])
66
+ L[i, :] = P[:, S[i]] * (encoded_data.bitarray[i] == T[0, :])
67
+
68
+ # Update the state sequence and trellis diagram
69
+ S[i] = np.argmax(L[i])
70
+ T[:, -1] = T[S[i], :] # update the trellis diagram
71
+
72
+ # Extract the decoded data from the state sequence
73
+ decoded_data = bitarray()
74
+ for i in range(N_systematic):
75
+ decoded_data.append(S[N_systematic + i])
76
+
77
+ return decoded_data
@@ -0,0 +1,7 @@
1
+
2
+ from .coding import Coding
3
+
4
+ class Block(Coding):
5
+ def __init__(self, num, den, length):
6
+ super().__init__(num, den)
7
+ self.length = length
@@ -0,0 +1,11 @@
1
+ class Coding:
2
+ def __init__(self, num, den):
3
+ self.num = num
4
+ self.den = den
5
+ self._rate = self.num/self.den
6
+
7
+ def encode(self, data):
8
+ pass
9
+
10
+ def decode(self, data):
11
+ pass
@@ -0,0 +1,5 @@
1
+
2
+ from .coding import Coding
3
+
4
+ class Convolutional(Coding):
5
+ pass
@@ -0,0 +1,3 @@
1
+ from . import binary
2
+ from .bitarray import bitarray
3
+ from .stream import Stream
@@ -0,0 +1,43 @@
1
+ # Encoding
2
+
3
+ def bytes_to_hex(data: bytes):
4
+ return data.hex()
5
+
6
+ def hex_to_bits(data):
7
+ bits = ""
8
+ for i in range(0, len(data), 2):
9
+ int_data = int(data[i:i+2], 16)
10
+ bit_data = bin(int_data)[2:]
11
+ padding = (len(bit_data) % 8)
12
+ if not padding == 0:
13
+ bit_data = "0" * (8 - padding) + bit_data
14
+ bits += bit_data
15
+ #input(f"0x{data[i:i+2]} = 0i{int_data}, 0b{bit_data}")
16
+ return bits
17
+
18
+ def bytes_to_bits(data):
19
+ return hex_to_bits(bytes_to_hex(data))
20
+
21
+
22
+ # Decoding
23
+
24
+ def bits_to_hex(bits):
25
+ data_hex = ""
26
+ for i in range(0, len(bits), 8):
27
+ int_data = int(bits[i:i+8], 2)
28
+ hex_data = hex(int_data)[2:]
29
+ padding = (len(hex_data) % 2)
30
+ if not padding == 0:
31
+ hex_data = "0" * (2 - padding) + hex_data
32
+ data_hex += hex_data
33
+ #input(f"0b{bits[i:i+8]} = 0i{int_data}, 0x{hex_data}")
34
+ return data_hex
35
+
36
+ def hex_to_bytes(data):
37
+ return bytes.fromhex(data)
38
+
39
+ def bits_to_bytes(data):
40
+ return hex_to_bytes(bits_to_hex(data))
41
+
42
+ class Encoding:
43
+ pass
@@ -0,0 +1,48 @@
1
+ import numpy as np
2
+
3
+ class bitarray:
4
+ def __init__(self, arr=None):
5
+ if arr is None:
6
+ arr = np.array([], dtype=np.bool)
7
+ self.arr = arr
8
+
9
+ def __str__(self):
10
+ return str(self.arr)
11
+
12
+ def __len__(self):
13
+ return len(self.arr)
14
+
15
+ def append(self, bit):
16
+ self.arr = np.append(self.arr, bit)
17
+
18
+ @staticmethod
19
+ def from_encoding(enc):
20
+ return bitarray(
21
+ np.unpackbits(
22
+ np.array(bytearray(enc.bytes), dtype=np.uint8)
23
+ )
24
+ )
25
+
26
+ def to_bytes(self) -> bytes:
27
+ return bytes(np.packbits(self.arr))
28
+
29
+ def bits(self):
30
+ return self.arr
31
+
32
+ def bytes(self):
33
+ return np.packbits(self.arr)
34
+
35
+ def astype(self, *args, **kwargs):
36
+ return self.arr.astype(*args, **kwargs)
37
+
38
+ def __iter__(self):
39
+ return self.arr.__iter__()
40
+
41
+ def __getattr__(self, key):
42
+ return self.arr
43
+
44
+ def __getitem__(self, key):
45
+ return self.arr[key]
46
+
47
+ def __setitem__(self, key, val):
48
+ self.arr[key] = val
@@ -0,0 +1,107 @@
1
+ from .bitarray import bitarray
2
+
3
+ class Stream:
4
+ __slots__ = ("cache", "length")
5
+
6
+ def __init__(self):
7
+ self.cache = {
8
+ "bin": None,
9
+ "hex": None,
10
+ "bytes": None,
11
+ "int": None,
12
+ "bitarray": None,
13
+ }
14
+ self.length = None
15
+
16
+ def __str__(self):
17
+ return f"0x{self.hex}"
18
+
19
+ def __repr__(self):
20
+ return f"<Encoded data>"
21
+
22
+ @property
23
+ def bin(self):
24
+ if self.cache["bin"] is None:
25
+ #self.cache["bin"] = Encoding._pad(bin(self.cache["int"])[2:], 8)
26
+ self.cache["bin"] = "".join([item for item in self.bitarray.astype(str)])
27
+ return self.cache["bin"]
28
+
29
+ @bin.setter
30
+ def bin(self, data):
31
+ self.cache["bin"] = data
32
+
33
+ @property
34
+ def hex(self):
35
+ if self.cache["hex"] is None:
36
+ self.cache["hex"] = Stream._pad(hex(self.cache["int"])[2:], 2)
37
+ return self.cache["hex"]
38
+
39
+ @hex.setter
40
+ def hex(self, data):
41
+ self.cache["hex"] = data
42
+
43
+ @property
44
+ def bytes(self):
45
+ if self.cache["bytes"] is None:
46
+ if self.cache["hex"] is None:
47
+ self.hex
48
+ self.cache["bytes"] = bytes.fromhex(self.cache["hex"])
49
+ return self.cache["bytes"]
50
+
51
+ @bytes.setter
52
+ def bytes(self, data):
53
+ self.cache["bytes"] = data
54
+
55
+ @property
56
+ def int(self):
57
+ return self.cache["int"]
58
+
59
+ @property
60
+ def bitarray(self):
61
+ if self.cache["bitarray"] is None:
62
+ self.cache["bitarray"] = bitarray.from_encoding(self)
63
+ return self.cache["bitarray"]
64
+
65
+ @staticmethod
66
+ def from_bin(binary, length = None):
67
+ enc = Stream()
68
+ enc.cache["bin"] = Stream._pad(binary, 8)
69
+ enc.cache["int"] = int(binary, 2)
70
+ enc.length = length
71
+ return enc
72
+
73
+ @classmethod
74
+ def from_hex(hexadecimal, length = None):
75
+ enc = Stream()
76
+ enc.cache["hex"] = Stream._pad(hexadecimal, 2)
77
+ enc.cache["int"] = int(hexadecimal, 16)
78
+ enc.length = length
79
+ return enc
80
+
81
+ @staticmethod
82
+ def from_bytes(byte, length = None):
83
+ enc = Stream()
84
+ enc.cache["bytes"] = byte
85
+ enc.cache["int"] = int.from_bytes(byte)
86
+ enc.length = length
87
+ return enc
88
+
89
+ @staticmethod
90
+ def from_int(int, length = None):
91
+ enc = Stream()
92
+ enc.cache["int"] = int
93
+ enc.length = length
94
+ return enc
95
+
96
+ @staticmethod
97
+ def _pad(data, mod, fill = "0"):
98
+ padding = len(data) % mod
99
+ if not padding == 0:
100
+ data = fill * (mod - padding) + data
101
+ return data
102
+
103
+ def slice(self, field, size):
104
+ idx = 0
105
+ while idx < len(self.cache[field]):
106
+ yield self.cache[field][idx:idx+size]
107
+ idx += size
@@ -0,0 +1,38 @@
1
+ import pathlib
2
+
3
+ class Meta:
4
+ mod = ""
5
+ fmt = ""
6
+ fec = ""
7
+
8
+ def serialize(self, data):
9
+ path = pathlib.Path(f"data/{self.to_name()}")
10
+ data.tofile(path) # Save to file
11
+ return path
12
+
13
+ def to_name(self):
14
+ fields = [
15
+ self.mod.lower(),
16
+ self.fmt,
17
+ "rpps",
18
+ "iq",
19
+ ]
20
+ return ".".join(fields)
21
+
22
+ @staticmethod
23
+ def from_name(name: str, meta = None):
24
+ if meta is None:
25
+ meta = Meta()
26
+ fields = name.split(".")
27
+ meta.mod = fields[0].upper()
28
+ meta.fmt = fields[1]
29
+ return meta
30
+
31
+ @staticmethod
32
+ def from_file(path, meta = None):
33
+ import numpy as np
34
+ if isinstance(path, str):
35
+ path = pathlib.Path(path)
36
+ meta = Meta.from_name(path.name, meta)
37
+ symbols = np.fromfile(path, getattr(np, meta.fmt))
38
+ return meta, symbols
@@ -0,0 +1,11 @@
1
+ # Import helpers/globals
2
+ from ..helpers.bitarray import bitarray
3
+ from ..helpers.stream import Stream
4
+ from ..meta import Meta
5
+
6
+ # -----
7
+
8
+ from .modulation import Modulation
9
+ from .constellation import Mapping, Maps, Points, Constellation
10
+
11
+ from ._psk import *
@@ -0,0 +1 @@
1
+ from .qpsk import QPSK
@@ -0,0 +1,31 @@
1
+ from ..psk import PSK
2
+
3
+ from ..constellation import Mapping, Maps, Points
4
+
5
+ class QPSK(PSK):
6
+ points = Points(
7
+ [
8
+ .7 - .7j, -.7 - .7j,
9
+ .7 + .7j, -.7 + .7j
10
+ ]
11
+ )
12
+ maps = Maps(
13
+ [
14
+ Mapping([
15
+ 0, 1,
16
+ 2, 3,
17
+ ]),
18
+ Mapping([
19
+ 0, 2,
20
+ 1, 3,
21
+ ]),
22
+ Mapping([
23
+ 3, 1,
24
+ 2, 0,
25
+ ]),
26
+ Mapping([
27
+ 3, 2,
28
+ 1, 0,
29
+ ]),
30
+ ]
31
+ )
@@ -0,0 +1,239 @@
1
+ import math
2
+ import numpy as np
3
+
4
+ from pyboiler.logger import Logger, Level
5
+
6
+ from . import Stream
7
+ from . import Meta
8
+
9
+
10
+ class Mapping:
11
+ __slots__ = ("arr")
12
+
13
+ def __init__(self, map=None):
14
+ if isinstance(map, int):
15
+ map = np.array([0] * map)
16
+ elif not isinstance(map, np.ndarray):
17
+ map = np.array(map)
18
+ elif map is None:
19
+ map = np.array([])
20
+ self.arr = map
21
+
22
+ @staticmethod
23
+ def new(map):
24
+ return Mapping(map)
25
+
26
+ @staticmethod
27
+ def empty(length: int):
28
+ return Mapping(length)
29
+
30
+ def __len__(self):
31
+ return len(self.arr)
32
+
33
+ def __str__(self):
34
+ return str(self.arr)
35
+
36
+ def __getitem__(self, item):
37
+ return self.arr[item]
38
+
39
+ def __setitem__(self, item, val):
40
+ self.arr[item] = val
41
+
42
+
43
+ class Maps:
44
+ __slots__ = ("maps")
45
+
46
+ def __init__(self, maps):
47
+ self.maps = maps
48
+
49
+ def __len__(self):
50
+ return len(self.maps)
51
+
52
+ def __getitem__(self, item):
53
+ return self.maps[item]
54
+
55
+ def __setitem__(self, item, val):
56
+ self.maps[item] = val
57
+
58
+ class Points:
59
+ __slots__ = ("arr")
60
+
61
+ def __init__(self, points):
62
+ self.arr = np.array(points)
63
+
64
+ def __len__(self):
65
+ return len(self.arr)
66
+
67
+ def __str__(self):
68
+ return str(self.arr)
69
+
70
+ def __getitem__(self, item):
71
+ return self.arr[item]
72
+
73
+ def __setitem__(self, item, val):
74
+ self.arr[item] = val
75
+
76
+
77
+ class Constellation:
78
+ __slots__ = ("log", "_points", "_mapping", "_bps")
79
+
80
+ def __init__(self, points: Points, mapping = None, log=Logger().Child("Modulation")):
81
+ self.log = log.Child("Constellation", Level.WARN)
82
+ if not isinstance(points, Points):
83
+ points = Points(points)
84
+ self._points = points
85
+
86
+ if mapping is None:
87
+ mapping = Mapping()
88
+ self._mapping = mapping
89
+
90
+ self._bps = int(math.log2(len(self.points))) # Bits per symbol
91
+
92
+ def __str__(self) -> str:
93
+ return f"Points: {self._points}, Map: {self._mapping}"
94
+
95
+ def __repr__(self) -> str:
96
+ return f"<Constellation: {self._bps}>"
97
+
98
+ def __len__(self) -> int:
99
+ return len(self._points)
100
+
101
+ @property
102
+ def points(self):
103
+ return self._points
104
+
105
+ @points.setter
106
+ def points(self, points):
107
+ self._points = np.array(points)
108
+ self._bps = len(self.points) // 2
109
+
110
+ @property
111
+ def mapping(self):
112
+ return self._mapping
113
+
114
+ @mapping.setter
115
+ def mapping(self, map):
116
+ self._mapping = np.array(map)
117
+
118
+ @property
119
+ def bits_per_symbol(self):
120
+ return self._bps
121
+
122
+
123
+ def modulate(self, data: Stream, meta: Meta, noise: bool = True):
124
+ indexes = self.index(data, meta)
125
+ points = self.map(indexes, meta)
126
+ symbols = self.to_symbols(points, meta, noise=noise)
127
+ return symbols
128
+
129
+ def demodulate(self, symbols, meta: Meta):
130
+ points = self.from_symbols(symbols, meta)
131
+ indexes = self.unmap(points, meta)
132
+ data = self.unindex(indexes, meta)
133
+ return data
134
+
135
+
136
+ ##############################
137
+ # Modulate
138
+ ##############################
139
+
140
+ def index(self, data: Stream, meta):
141
+ self.log.trace(f"Data is {data}")
142
+ self.log.trace(f"Data bitarray is {data.bitarray}")
143
+
144
+ padding = len(data.bin) % self._bps
145
+ if not padding == 0:
146
+ self.log.trace(f"Padding by {padding}")
147
+ for _ in range(0, (self._bps - padding)):
148
+ data.bitarray.append(0)
149
+ num_symbols = len(data.bitarray) // self._bps
150
+
151
+ self.log.trace(f"Data requires {num_symbols} indexes to encode")
152
+
153
+ indexes = np.split(data.bitarray.arr, num_symbols)
154
+ indexes = [int(f"{data[0]}{data[1]}", 2) for data in indexes]
155
+ #for i in range(0, len(data.bin), self._bps):
156
+ # bit_int = int(data.bin[i:i+self._bps], 2)
157
+ # indexes.append(int(bit_int))
158
+
159
+ self.log.trace(f"Indexes are {indexes}")
160
+ return indexes
161
+
162
+ def map(self, indexes, meta):
163
+ self.log.trace(f"Using mapping: {self.mapping}")
164
+ points = []
165
+ for idx in indexes:
166
+ points.append(
167
+ int(
168
+ np.where(self.mapping.arr == idx)[0][0]
169
+ )
170
+ )
171
+ self.log.trace(f"Points are {points}")
172
+ return points
173
+
174
+ def to_symbols(self, points, meta, noise: bool = True):
175
+ points = np.array(points)
176
+ symbols = self.points[points]
177
+ self.log.trace(f"Symbols are:\n{symbols}")
178
+ # Add noise
179
+ if noise:
180
+ n = (np.random.randn(len(symbols)) + 1j*np.random.randn(len(symbols)))/np.sqrt(2) # AWGN with unity power
181
+ symbols = symbols + n * np.sqrt(0.01) # noise power of 0.01
182
+
183
+ symbols = symbols.astype(np.complex64)
184
+ meta.fmt = type(symbols[0]).__name__
185
+ self.log.trace(f"Symbols are: {symbols}")
186
+ return symbols
187
+
188
+ ##############################
189
+ # Demodulate
190
+ ##############################
191
+
192
+ def from_symbols(self, symbols, meta, clean: bool=True):
193
+ #self.log.trace(f"Symbols are:\n{symbols}")
194
+ points = []
195
+ if clean:
196
+ syms = []
197
+ for symbol in symbols: # Clean up the symbols
198
+ diff = np.abs(self.points - symbol)
199
+ index = np.argmin(diff)
200
+ closest = self.points[index]
201
+ syms.append(closest)
202
+ syms = np.array(syms)
203
+ self.log.trace(f"Symbols are:\n{syms}")
204
+
205
+ for symbol in syms:
206
+ points.append(int(np.where(self.points == symbol)[0][0]))
207
+ else:
208
+ self.log.trace(f"Symbols are:\n{symbols}")
209
+ for symbol in symbols: # Clean up the symbols
210
+ diff = np.abs(self.points - symbol)
211
+ index = np.argmin(diff)
212
+ points.append(int(index))
213
+ self.log.trace(f"Points are {points}")
214
+ return points
215
+
216
+ def unmap(self, points, meta):
217
+ self.log.trace(f"Using mapping: {self.mapping}")
218
+ indexes = []
219
+ for pnt in points:
220
+ indexes.append(int(self.mapping[pnt]))
221
+ self.log.trace(f"Indexes are {indexes}")
222
+ return indexes
223
+
224
+ def unindex(self, indexes, meta):
225
+ bits = ""
226
+ for i in range(0, len(indexes), 1):
227
+ cur_bits = bin(indexes[i])[2:]
228
+ if len(cur_bits) == 1:
229
+ cur_bits = "0" + cur_bits
230
+ bits += cur_bits
231
+
232
+ data = Stream.from_bin(bits)
233
+
234
+ self.log.trace(f"Data bits are {data.bin}")
235
+
236
+ self.log.trace(f"Data hex is {data.hex}")
237
+ self.log.trace(f"Data is {data.bytes}")
238
+
239
+ return data
@@ -0,0 +1,3 @@
1
+
2
+ def Identify():
3
+ pass
@@ -0,0 +1,61 @@
1
+ import numpy as np
2
+
3
+ from pyboiler.logger import Logger, Level
4
+
5
+ from . import Meta
6
+ from . import Stream
7
+
8
+ from .constellation import Maps, Points
9
+ from .constellation import Constellation
10
+
11
+
12
+ class Modulation:
13
+ __slots__ = ("log", "constellation", "map")
14
+ points = Points([])
15
+ maps = Maps([])
16
+
17
+ def __init__(self, map_idx: int = None): # type: ignore
18
+ self.log = Logger().Child("Modulation").Child(type(self).__name__)
19
+ self.map = map_idx
20
+ if self.map is not None:
21
+ self.constellation = Constellation(
22
+ type(self).points,
23
+ type(self).maps[self.map],
24
+ log=self.log)
25
+ else:
26
+ self.constellation = Constellation(
27
+ type(self).points,
28
+ log=self.log)
29
+
30
+ def demodulate(self, symbols: np.ndarray, meta=Meta()):
31
+ if self.map is None:
32
+ raise Exception("A map must be provided to demodulate!")
33
+
34
+ data = self.constellation.demodulate(symbols, meta)
35
+
36
+ return data, meta
37
+
38
+ def modulate(self, data: Stream, meta=Meta()):
39
+ if self.map is None:
40
+ raise Exception("A map must be provided to modulate!")
41
+
42
+ symbols = self.constellation.modulate(data, meta)
43
+
44
+ meta.mod = type(self).__name__
45
+ return symbols, meta
46
+
47
+
48
+ class ASK(Modulation):
49
+ pass
50
+
51
+
52
+ class FSK(Modulation):
53
+ pass
54
+
55
+
56
+ class APSK(Modulation):
57
+ pass
58
+
59
+
60
+ class QAM(Modulation):
61
+ pass
@@ -0,0 +1,4 @@
1
+ from . import Modulation
2
+
3
+ class PSK(Modulation):
4
+ pass
@@ -0,0 +1,9 @@
1
+ # Import helpers/globals
2
+ from ..helpers.bitarray import bitarray
3
+ from ..helpers.stream import Stream
4
+ from ..meta import Meta
5
+
6
+ # -----
7
+
8
+ from .generic import show
9
+ from .constellation import draw as DrawConstellation
@@ -0,0 +1,22 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+
4
+ from . import Meta
5
+
6
+ def draw(data, meta: Meta):
7
+ fig = plt.figure()
8
+ ax = fig.add_subplot()
9
+ plt.plot(np.real(data), np.imag(data), '.')
10
+
11
+ plt.grid(True)
12
+ plt.title(f"{meta.mod} encoded data")
13
+ plt.xlim(1.2, -1.2)
14
+ plt.ylim(1.2, -1.2)
15
+ ax.set_aspect('equal', adjustable='box')
16
+
17
+ # Draw unit circle
18
+ angle = np.linspace( 0 , 2 * np.pi , 150 )
19
+ radius = 1
20
+ x = radius * np.cos( angle )
21
+ y = radius * np.sin( angle )
22
+ plt.plot(x, y)
@@ -0,0 +1,4 @@
1
+ import matplotlib.pyplot as plt
2
+
3
+ def show():
4
+ plt.show()
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.1
2
+ Name: rpps
3
+ Version: 0.0.1
4
+ Summary: RF Parameter Processor Suite
5
+ Author-email: Anonoei <dev@anonoei.com>
6
+ Project-URL: Homepage, https://github.com/anonoei/RPPS
7
+ Project-URL: Documentation, https://anonoei.github.io/RPPS/
8
+ Project-URL: Repository, https://github.com/Anonoei/RPPS.git
9
+ Project-URL: Issues, https://github.com/Anonoei/RPPS/issues
10
+ Project-URL: Source, https://github.com/anonoei/RPPS
11
+ Keywords: anonoei,dsp,sdr
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Operating System :: OS Independent
17
+ Requires-Python: >=3.6
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Provides-Extra: dev
21
+ Requires-Dist: twine; extra == "dev"
22
+ Requires-Dist: build; extra == "dev"
23
+ Requires-Dist: black; extra == "dev"
24
+ Requires-Dist: bumpver; extra == "dev"
25
+ Requires-Dist: pytest; extra == "dev"
26
+ Requires-Dist: pdoc3; extra == "dev"
27
+
28
+ # RPPS
29
+ RF Parameter Processor Suite
30
+
31
+ RPPS is a generic signal processor/generator library.
32
+
33
+ - [Documentation](https://anonoei.github.io/RPPS/)
34
+ - [PyPI](https://pypi.org/project/rpps/)
35
+
36
+ ## Install
37
+ 1. Run `python3 -m pip install rpps`
38
+ 2. In your project, `import rpps as rp`
39
+
40
+ ## Roadmap
41
+ - [ ] Pre-processing
42
+ - [ ] Modulation
43
+ - [ ] PSK
44
+ - [X] QPSK
45
+ - [ ] QAM
46
+ - [ ] APSK
47
+ - [ ] ASK
48
+ - [ ] FSK
49
+ - [ ] Coding
50
+ - [ ] Block
51
+ - [X] Repetition
52
+ - [ ] LDPC
53
+ - [ ] TPC
54
+ - [ ] Convolutional
55
+ - [ ] Viterbi
56
+ - [ ] De-scramble
57
+ - [ ] v.35
58
+ - [ ] De-frame
59
+ - [ ] HDLC
60
+ - [ ] PPP
@@ -0,0 +1,32 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/rpps/__init__.py
5
+ src/rpps/meta.py
6
+ src/rpps.egg-info/PKG-INFO
7
+ src/rpps.egg-info/SOURCES.txt
8
+ src/rpps.egg-info/dependency_links.txt
9
+ src/rpps.egg-info/requires.txt
10
+ src/rpps.egg-info/top_level.txt
11
+ src/rpps/coding/__init__.py
12
+ src/rpps/coding/block.py
13
+ src/rpps/coding/coding.py
14
+ src/rpps/coding/convolutional.py
15
+ src/rpps/coding/_blk/__init__.py
16
+ src/rpps/coding/_blk/repetition.py
17
+ src/rpps/coding/_cnv/__init__.py
18
+ src/rpps/coding/_cnv/viterbi.py
19
+ src/rpps/helpers/__init__.py
20
+ src/rpps/helpers/binary.py
21
+ src/rpps/helpers/bitarray.py
22
+ src/rpps/helpers/stream.py
23
+ src/rpps/mod/__init__.py
24
+ src/rpps/mod/constellation.py
25
+ src/rpps/mod/identify.py
26
+ src/rpps/mod/modulation.py
27
+ src/rpps/mod/psk.py
28
+ src/rpps/mod/_psk/__init__.py
29
+ src/rpps/mod/_psk/qpsk.py
30
+ src/rpps/viz/__init__.py
31
+ src/rpps/viz/constellation.py
32
+ src/rpps/viz/generic.py
@@ -0,0 +1,8 @@
1
+
2
+ [dev]
3
+ twine
4
+ build
5
+ black
6
+ bumpver
7
+ pytest
8
+ pdoc3
@@ -0,0 +1 @@
1
+ rpps