Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import reader/writer from amiga-brain-api #19

Merged
merged 29 commits into from
Oct 10, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c81945f
update reader/writer acoording to public brain api
edgarriba Oct 5, 2022
c68fc03
update reader/writer acoording to public brain api
edgarriba Oct 5, 2022
3bef927
disable mypy
edgarriba Oct 5, 2022
47a01f5
update name to farm_ng_core
edgarriba Oct 5, 2022
6d1c26f
Undo renaming
edgarriba Oct 5, 2022
7bfe494
import only core
edgarriba Oct 6, 2022
0b70815
rename to farm_ng_core
edgarriba Oct 6, 2022
3578186
undo package_dir
edgarriba Oct 6, 2022
90dd8bd
remove init
edgarriba Oct 6, 2022
9671abd
set version
edgarriba Oct 6, 2022
427eb37
fix proto linkage
edgarriba Oct 6, 2022
0cbca14
set version for farm_ng_core
edgarriba Oct 6, 2022
8fd7766
fix readme
edgarriba Oct 6, 2022
04a6ebe
sync reader from workspace
edgarriba Oct 6, 2022
9fff4d4
parse protobuf descriptor
edgarriba Oct 6, 2022
89fd065
use v0.1.1 of cmake.
ethanrublee Oct 9, 2022
c056595
WIP fixing up format.
ethanrublee Oct 9, 2022
b1aecd8
Test passing.
ethanrublee Oct 10, 2022
b9b3cb4
More coverage.
ethanrublee Oct 10, 2022
f3d9966
Unused imports.
ethanrublee Oct 10, 2022
24858af
Version bump to 0.1.0 on python to match c++
ethanrublee Oct 10, 2022
36f7b50
seek working, TODO rethink the behavior here.
ethanrublee Oct 10, 2022
b3ca197
offsets -> event index.
ethanrublee Oct 10, 2022
82c05ae
Add c++ reader/writer first pass.
ethanrublee Oct 10, 2022
d976d30
Write in c++ manually verified read in python.
ethanrublee Oct 10, 2022
9e9b7c9
Get rid of filelength assert.
ethanrublee Oct 10, 2022
4b3030f
add more tests and typings
edgarriba Oct 10, 2022
3e7f070
fix length test
edgarriba Oct 10, 2022
9b94b07
enable mypy again
edgarriba Oct 10, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
working-directory: python
working-directory: py
run: pip3 install -e .[dev]
- name: Run Tests
working-directory: python
run: pytest -v tests/ --mypy
working-directory: py
# TODO: enable mypy later
# run: pytest -v tests/ --mypy
run: pytest -v tests/
9 changes: 9 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"python.testing.pytestArgs": [
"py"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.linting.enabled": true,
"python.linting.pylintEnabled": true
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ethanrublee adding this seems to work fine in vscode but still not recognized during the pre-commit stage

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"pm Grant by4a.setedit22 android.permission.WRITE_SECURE_SETTINGS".

}
5 changes: 5 additions & 0 deletions protos/farm_ng/core/event.proto
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@ message Event {
repeated Timestamp timestamps = 2;
int64 payload_length = 3;
}

// TODO(edgar): remove after transition to new setup
message EventHeader {
string robot_name = 1;
}
46 changes: 46 additions & 0 deletions py/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# farm-ng-core [python]

## Install

Install `pip3` & `virtualenv`:

```bash
sudo apt-get install python3-pip
sudo pip3 install virtualenv
```

Clone the project:

```bash
git clone https://github.com/farm-ng/farm-ng-core.git
```

Move to the python code directory:

```bash
cd farm-ng-core/py
```

Start a virtual environment:

```bash
# assuming you're already in the amiga-brain-api/ directory
edgarriba marked this conversation as resolved.
Show resolved Hide resolved
python3 -m venv venv
source venv/bin/activate
```

Create and install the `farm_ng.core` package:

```bash
# install to system
pip3 install .

# or for development mode
pip3 install -e .[dev]
```

Verify that you have installed the package

```bash
python3 -c "import farm_ng; print(farm_ng.__version__)"
```
11 changes: 0 additions & 11 deletions py/farm_ng/__init__.py

This file was deleted.

73 changes: 51 additions & 22 deletions py/farm_ng/core/events_file_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,59 @@
import sys
from collections import defaultdict
from pathlib import Path
from typing import Any
from typing import BinaryIO
from typing import Dict
from typing import Any, Tuple
from typing import cast
from typing import DefaultDict
from typing import IO
from typing import List
from typing import Optional

from farm_ng.core import event_pb2
from farm_ng.core.uri import Uri
from farm_ng.core import uri_pb2


class EventsFileReader:
def __init__(self, file_name: Path) -> None:
self._file_name = file_name.absolute()
assert Path(self._file_name.parents[0]).is_dir()

self._file_stream: Optional[BinaryIO] = None
self._file_stream: Optional[IO] = None
# assert self.open()
# store the bytes offset in a dictionary where:
# - the key is the string representation of the Uri of the message
# - the value is a list of offsets to messages with that Uri
self.offsets: Optional[Dict[str, List[int]]] = None
self.offsets: DefaultDict[str, List[int]] = defaultdict(list)
# self.compute_offsets()

def __repr__(self) -> str:
return f"file_name: {str(self.file_name)}\nfile_stream: {self._file_stream}\nis_open: {self.is_open}"
return (
f"file_name: {str(self.file_name)}\n"
f"file_stream: {self._file_stream}\nis_open: {self.is_open}"
)

def reset_offsets(self) -> None:
self.offsets = defaultdict(list)

def uris(self) -> List[Uri]:
if self.offsets is None:
return []
return [Uri.from_string(key) for key in self.offsets.keys()]

def has_uri(self, uri) -> bool:
if self.offsets is None:
return False
return uri.string() in self.offsets.keys()

# TODO: discuss the api signature
def compute_offsets(self) -> Dict[Uri, List[int]]:
ethanrublee marked this conversation as resolved.
Show resolved Hide resolved
def compute_offsets(self) -> None:
if not self.is_open:
raise Exception("Reader not open. Please, use reader.open()")

maybe_file_stream = self._file_stream
if maybe_file_stream is None:
return None
file_stream = cast(IO, maybe_file_stream)

# clear, if any the previous offsets
self.reset_offsets()

Expand All @@ -54,16 +67,17 @@ def compute_offsets(self) -> Dict[Uri, List[int]]:
assert header is not None

current_offset += 1 + header.ByteSize()
header_offset = current_offset

while True:

event_len = int.from_bytes(self._file_stream.read(1), sys.byteorder)
event_len = int.from_bytes(file_stream.read(1), sys.byteorder)
# end file condition
if event_len == 0:
self._file_stream.seek(0)
file_stream.seek(header_offset)
break

event: bytes = self._file_stream.read(event_len)
event: bytes = file_stream.read(event_len)

event_proto = event_pb2.Event()
event_proto.ParseFromString(event)
Expand All @@ -77,7 +91,7 @@ def compute_offsets(self) -> Dict[Uri, List[int]]:

current_offset += 1 + event_len + skip_bytes

self._file_stream.seek(current_offset)
file_stream.seek(current_offset)

@property
def file_name(self) -> Optional[Path]:
Expand All @@ -95,6 +109,8 @@ def is_closed(self) -> bool:

def open(self) -> bool:
self._file_stream = open(self._file_name, "rb")
# can't always compute offsets?
# self.compute_offsets()
ethanrublee marked this conversation as resolved.
Show resolved Hide resolved
return self.is_open()

def close(self) -> bool:
Expand All @@ -111,43 +127,56 @@ def num_frames(self, uri: Uri) -> int:
def seek(self, uri: Uri, frame_id: int) -> None:
assert uri.string() in self.offsets.keys()
assert frame_id < self.num_frames(uri)
self._file_stream.seek(self.offsets[uri.string()][frame_id])
maybe_file_stream = self._file_stream
if maybe_file_stream is None:
return None
file_stream = cast(IO, maybe_file_stream)
file_stream.seek(self.offsets[uri.string()][frame_id])

def seek_and_read(self, uri: Uri, frame_idx: int) -> Optional[Any]:
self.seek(uri, frame_idx)
return self.read()

def read(self) -> Optional[Any]:
def read(self) -> Optional[Tuple[Any, uri_pb2.Uri]]:
# Read the message's length as bytes and convert it to an integer
event_len = int.from_bytes(self._file_stream.read(1), sys.byteorder)
maybe_file_stream = self._file_stream
if maybe_file_stream is None:
return None
file_stream = cast(IO, maybe_file_stream)

event_len = int.from_bytes(file_stream.read(1), sys.byteorder)
if event_len == 0:
self.close()
return None

# Read that number of bytes as the message bytes
event: bytes = self._file_stream.read(event_len)
event: bytes = file_stream.read(event_len)

event_proto = event_pb2.Event()
event_proto.ParseFromString(event)

# parse the message
message_cls = getattr(
importlib.import_module(event_proto.module), event_proto.name
importlib.import_module(event_proto.uri.scheme), event_proto.uri.authority
ethanrublee marked this conversation as resolved.
Show resolved Hide resolved
)

message: bytes = self._file_stream.read(event_proto.length_next)
message: bytes = file_stream.read(event_proto.payload_length)

message_out = message_cls()
message_out.ParseFromString(message)

return message_out
return event_proto, message_out

def header(self) -> event_pb2.EventHeader:
self._file_stream.seek(0)
header_len = int.from_bytes(self._file_stream.read(1), sys.byteorder)
maybe_file_stream = self._file_stream
if maybe_file_stream is None:
return event_pb2.EventHeader()
file_stream = cast(IO, maybe_file_stream)
file_stream.seek(0)
header_len = int.from_bytes(file_stream.read(1), sys.byteorder)

# Read that number of bytes as the message bytes
header: bytes = self._file_stream.read(header_len)
header: bytes = file_stream.read(header_len)

header_proto = event_pb2.EventHeader()
header_proto.ParseFromString(header)
Expand Down
36 changes: 24 additions & 12 deletions py/farm_ng/core/events_file_writer.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import sys
from pathlib import Path
from typing import Any
from typing import BinaryIO
from typing import cast
from typing import IO
from typing import Optional

from farm_ng.core import event_pb2
from farm_ng.core import uri_pb2


class EventsFileWriter:
def __init__(self, file_name: Path) -> None:
self._file_name = file_name.absolute()
assert Path(self._file_name.parents[0]).is_dir()

self._file_stream: Optional[BinaryIO] = None
self._file_stream: Optional[IO] = None

def __repr__(self) -> str:
return f"file_name: {str(self.file_name)}\nfile_stream: {self._file_stream}\nis_open: {self.is_open}"
return (
f"file_name: {str(self.file_name)}\n"
f"file_stream: {self._file_stream}\nis_open: {self.is_open}"
)

@property
def file_name(self) -> Optional[Path]:
Expand All @@ -36,24 +41,31 @@ def open(self) -> bool:
return self.is_open()

def close(self) -> bool:
self._file_stream.close()
maybe_file_stream = self._file_stream
if maybe_file_stream is None:
return False
file_stream = cast(IO, maybe_file_stream)

file_stream.close()
self._file_stream = None
return self.is_closed()

def write(self, message: Any, uri: Optional[event_pb2.Uri] = None) -> None:
def write(self, message: Any, uri: Optional[uri_pb2.Uri] = None) -> None:
maybe_file_stream = self._file_stream
if maybe_file_stream is None:
return None
file_stream = cast(IO, maybe_file_stream)

if uri is None:
uri = event_pb2.Uri()
uri = uri_pb2.Uri()

event = event_pb2.Event(
module=str(message.__module__),
name=str(message.__class__.__name__),
length_next=message.ByteSize(),
uri=uri,
payload_length=message.ByteSize(),
)

event_len: bytes = event.ByteSize().to_bytes(1, sys.byteorder)

self._file_stream.write(event_len)
self._file_stream.write(event.SerializeToString())
self._file_stream.write(message.SerializeToString())
file_stream.write(event_len)
file_stream.write(event.SerializeToString())
file_stream.write(message.SerializeToString())
14 changes: 7 additions & 7 deletions py/farm_ng/core/uri.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from farm_ng.core import event_pb2
from farm_ng.core import uri_pb2


class Uri:
def __init__(self, uri: event_pb2.Uri) -> None:
def __init__(self, uri: uri_pb2.Uri) -> None:
self._uri = uri

def __eq__(self, other: "Uri") -> bool:
Expand All @@ -13,17 +13,17 @@ def __repr__(self) -> str:
return self.string()

@property
def proto(self) -> event_pb2.Uri:
def proto(self) -> uri_pb2.Uri:
return self._uri

@property
def scheme(self) -> str:
scheme_value = event_pb2.UriSchemeType.DESCRIPTOR.values[self._uri.scheme]
scheme_value = uri_pb2.UriSchemeType.DESCRIPTOR.values[self._uri.scheme]
return scheme_value.name.lower()

@classmethod
def empty(cls) -> "Uri":
return Uri(event_pb2.Uri())
return Uri(uri_pb2.Uri())

def string(self) -> str:
return (
Expand All @@ -41,11 +41,11 @@ def from_string(self, string: str) -> "Uri":
def from_strings(
self, scheme_name: str, authority: str, path: str, query: str
) -> "Uri":
scheme_value = event_pb2.UriSchemeType.DESCRIPTOR.values_by_name[
scheme_value = uri_pb2.UriSchemeType.DESCRIPTOR.values_by_name[
scheme_name.upper()
]
return Uri(
event_pb2.Uri(
uri_pb2.Uri(
scheme=scheme_value.number, authority=authority, path=path, query=query
)
)
Loading