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 bhuman #55

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -208,3 +208,6 @@ ENV/

# Torch models
*.pth

# Input data
input/
21 changes: 19 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -3,13 +3,30 @@
{
"type": "debugpy",
"request": "launch",
"name": "Launch DDLitLab2024 import",
"name": "Launch Bit-Bots import",
"program": "${workspaceFolder}/ddlitlab2024/dataset/cli/run.py",
"args": [
"import",
"rosbag",
"bit-bots",
"${workspaceFolder}/input/ID_donna_2024-07-18T12_40_29_0.mcap"
]
},
{
"type": "debugpy",
"request": "launch",
"name": "Launch B-Human import",
"program": "${workspaceFolder}/ddlitlab2024/dataset/cli/run.py",
"args": [
"import",
"b-human",
//"${workspaceFolder}/input/b-human/2024/SharedAutonomy/2024-05-03-14-32/Joerg/Joerg_Joerg_SharedAutonomyAttacker_Projektraum__Invisibles_1stHalf_5.log",
"${workspaceFolder}/input/b-human/2024/RoboCup/2024_07_19/vsNao-Devils/DasKaenguru/DasKaenguru_DasKaenguru_AttackingGoalkeeper_Default__Nao-Devils_1stHalf_2.log",
//"${workspaceFolder}/input/b-human/selection/2023_RoboCup/2023-07-09/2023-07-09-13-30_HTWK-Robots/OttoVon/OttoVon_OttoVon_CompetitionWalk_RoboCup2023__HTWK-Robots_2ndHalf_6_(01).log",
//"${workspaceFolder}/input/b-human/selection/2023_GORE/2023-04-29/B-Human_Naova/Herta/Herta_Herta_CompetitionWalk_Default__Naova_1stHalf_6.log",
"DummyLocation",
"--caching",
"--video",
]
}
]
}
14 changes: 12 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
{
"cSpell.words": [
"bigendian",
"dateutil",
"ddlitlab",
"dtype",
"dtypes",
"frombuffer",
"ignoretz",
"mcap",
"nanosec",
"ndarray",
"ndim",
"nullable",
"ollama",
"pybh",
"rclpy",
"resampler",
"Resampler",
"rosbag",
"sessionmaker",
"sqlalchemy",
"sqlite",
"tobytes"
"tobytes",
"yuyv"
],
"python.analysis.typeCheckingMode": "basic",
"python.testing.pytestArgs": ["tests"],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
"python.testing.pytestEnabled": true,
"ros.distro": "iron"
}
8 changes: 6 additions & 2 deletions ddlitlab2024/dataset/cli/args.py
Original file line number Diff line number Diff line change
@@ -8,7 +8,8 @@


class ImportType(str, Enum):
ROS_BAG = "rosbag"
BIT_BOTS = "bit-bots"
B_HUMAN = "b-human"


class CLICommand(str, Enum):
@@ -71,6 +72,9 @@ def add_import_command_parser(self, subparsers):
self.import_parser = subparsers.add_parser(CLICommand.IMPORT.value, help="Import data into the database")
self.import_parser.add_argument("type", type=ImportType, help="Type of import to perform")
self.import_parser.add_argument("file", type=Path, help="File to import")
self.import_parser.add_argument("location", type=str, help="Location of the data")
self.import_parser.add_argument("--caching", action="store_true", help="Enable file caching")
self.import_parser.add_argument("--video", action="store_true", help="Show video while importing")

def parse_args(self) -> Namespace:
return self.validate_args(self.parser.parse_args())
@@ -87,7 +91,7 @@ def import_validation(self, args):
if not args.file.exists():
raise CLIArgumentError(f"File does not exist: {args.file}")

if args.type == ImportType.ROS_BAG and not args.file.suffix == ".mcap":
if args.type == ImportType.BIT_BOTS and not args.file.suffix == ".mcap":
raise CLIArgumentError(f"Rosbag import file not '*.mcap': {args.file}")

def db_validation(self, args):
73 changes: 54 additions & 19 deletions ddlitlab2024/dataset/cli/run.py
Original file line number Diff line number Diff line change
@@ -2,25 +2,27 @@

import typing

from ddlitlab2024.dataset.converters.game_state_converter import GameStateConverter
from ddlitlab2024.dataset.converters.image_converter import ImageConverter
from ddlitlab2024.dataset.converters.synced_data_converter import SyncedDataConverter
from ddlitlab2024.dataset.resampling.max_rate_resampler import MaxRateResampler
from ddlitlab2024.dataset.resampling.original_rate_resampler import OriginalRateResampler
from ddlitlab2024.dataset.resampling.previous_interpolation_resampler import PreviousInterpolationResampler

if typing.TYPE_CHECKING:
from argparse import Namespace

import os
import sys
from pathlib import Path

from rich.console import Console

from ddlitlab2024 import DEFAULT_RESAMPLE_RATE_HZ, IMAGE_MAX_RESAMPLE_RATE_HZ, __version__
from ddlitlab2024.dataset import logger
from ddlitlab2024.dataset.cli.args import CLIArgs, CLICommand, DBCommand, ImportType
from ddlitlab2024.dataset.converters.game_state_converter.b_human_game_state_converter import BHumanGameStateConverter
from ddlitlab2024.dataset.converters.game_state_converter.bit_bots_game_state_converter import BitBotsGameStateConverter
from ddlitlab2024.dataset.converters.image_converter import BHumanImageConverter, BitbotsImageConverter, ImageConverter
from ddlitlab2024.dataset.converters.synced_data_converter import SyncedDataConverter
from ddlitlab2024.dataset.db import Database
from ddlitlab2024.dataset.imports.model_importer import ImportStrategy
from ddlitlab2024.dataset.resampling.max_rate_resampler import MaxRateResampler
from ddlitlab2024.dataset.resampling.original_rate_resampler import OriginalRateResampler
from ddlitlab2024.dataset.resampling.previous_interpolation_resampler import PreviousInterpolationResampler

err_console = Console(stderr=True)

@@ -56,31 +58,64 @@ def main():
case CLICommand.IMPORT:
from ddlitlab2024.dataset.imports.model_importer import ImportMetadata, ModelImporter

import_strategy: ImportStrategy
import_path: Path = Path(args.file)
upper_image_converter: ImageConverter
location: str = args.location

match args.type:
case ImportType.ROS_BAG:
from ddlitlab2024.dataset.imports.strategies.bitbots import BitBotsImportStrategy
case ImportType.BIT_BOTS:
from ddlitlab2024.dataset.imports.strategies.bit_bots import BitBotsImportStrategy

logger.info(f"Trying to import file '{args.file}' to database...")
metadata = ImportMetadata(
allow_public=True,
team_name="Bit-Bots",
robot_type="Wolfgang-OP",
location="RoboCup2024",
location=location,
simulated=False,
)
image_converter = ImageConverter(MaxRateResampler(IMAGE_MAX_RESAMPLE_RATE_HZ))
game_state_converter = GameStateConverter(OriginalRateResampler())
upper_image_converter = BitbotsImageConverter(MaxRateResampler(IMAGE_MAX_RESAMPLE_RATE_HZ))
game_state_converter = BitBotsGameStateConverter(OriginalRateResampler())
synced_data_converter = SyncedDataConverter(
PreviousInterpolationResampler(DEFAULT_RESAMPLE_RATE_HZ)
)
import_strategy = BitBotsImportStrategy(
metadata, upper_image_converter, game_state_converter, synced_data_converter
)

importer = ModelImporter(
db,
BitBotsImportStrategy(
metadata, image_converter, game_state_converter, synced_data_converter
),
case ImportType.B_HUMAN:
from ddlitlab2024.dataset.imports.strategies.b_human import BHumanImportStrategy

metadata = ImportMetadata(
allow_public=False,
team_name="B-Human",
robot_type="NAO6",
location=location,
simulated=False,
)
upper_image_converter = BHumanImageConverter(MaxRateResampler(IMAGE_MAX_RESAMPLE_RATE_HZ))
lower_image_converter = BHumanImageConverter(MaxRateResampler(IMAGE_MAX_RESAMPLE_RATE_HZ))
game_state_converter = BHumanGameStateConverter(OriginalRateResampler())
synced_data_converter = SyncedDataConverter(
PreviousInterpolationResampler(DEFAULT_RESAMPLE_RATE_HZ)
)

import_strategy = BHumanImportStrategy(
metadata,
upper_image_converter,
lower_image_converter,
game_state_converter,
synced_data_converter,
args.caching,
args.video,
)
importer.import_to_db(args.file)

case _:
raise ValueError(f"Unknown import type: {args.type}")

logger.info(f"Importing file '{import_path}' to database...")
importer = ModelImporter(db, import_strategy)
importer.import_to_db(import_path)

sys.exit(0)
except Exception as e:
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
from enum import Enum, auto

from ddlitlab2024.dataset import logger
from ddlitlab2024.dataset.converters.converter import Converter
from ddlitlab2024.dataset.imports.data import InputData, ModelData
from ddlitlab2024.dataset.models import GameState, Recording, RobotState, TeamColor
from ddlitlab2024.dataset.resampling.original_rate_resampler import OriginalRateResampler


class State(Enum): # Adapted from b-human's Src/Representations/Infrastructure/GameState.h
beforeHalf = 0
standby = auto()
afterHalf = auto()
timeout = auto()
playing = auto()
setupOwnKickOff = auto()
setupOpponentKickOff = auto()
waitForOwnKickOff = auto()
waitForOpponentKickOff = auto()
ownKickOff = auto()
opponentKickOff = auto()
setupOwnPenaltyKick = auto()
setupOpponentPenaltyKick = auto()
waitForOwnPenaltyKick = auto()
waitForOpponentPenaltyKick = auto()
ownPenaltyKick = auto()
opponentPenaltyKick = auto()
ownPushingFreeKick = auto()
opponentPushingFreeKick = auto()
ownKickIn = auto()
opponentKickIn = auto()
ownGoalKick = auto()
opponentGoalKick = auto()
ownCornerKick = auto()
opponentCornerKick = auto()
beforePenaltyShootout = auto()
waitForOwnPenaltyShot = auto()
waitForOpponentPenaltyShot = auto()
ownPenaltyShot = auto()
opponentPenaltyShot = auto()
afterOwnPenaltyShot = auto()
afterOpponentPenaltyShot = auto()

@classmethod
def is_playing(cls, state: int) -> bool:
return state in (
cls.playing.value,
cls.ownKickOff.value,
cls.opponentKickOff.value,
cls.ownPenaltyKick.value,
cls.opponentPenaltyKick.value,
cls.ownPushingFreeKick.value,
cls.opponentPushingFreeKick.value,
cls.ownKickIn.value,
cls.opponentKickIn.value,
cls.ownGoalKick.value,
cls.opponentGoalKick.value,
cls.ownCornerKick.value,
cls.opponentCornerKick.value,
cls.ownPenaltyShot.value,
cls.opponentPenaltyShot.value,
)

@classmethod
def is_stopped(cls, state: int) -> bool:
return state in (
cls.beforeHalf.value,
cls.standby.value,
cls.afterHalf.value,
cls.timeout.value,
cls.setupOwnKickOff.value,
cls.setupOpponentKickOff.value,
cls.waitForOwnKickOff.value,
cls.waitForOpponentKickOff.value,
cls.ownKickOff.value,
cls.opponentKickOff.value,
)

@classmethod
def is_positioning(cls, state: int) -> bool:
return state in (
cls.setupOwnKickOff.value,
cls.setupOpponentKickOff.value,
cls.setupOwnPenaltyKick.value,
cls.setupOpponentPenaltyKick.value,
)


class PlayerState(Enum): # Adapted from b-human's Src/Representations/Infrastructure/GameState.h
unstiff = 0
calibration = auto()
penalizedManual = auto()
penalizedIllegalBallContact = auto()
penalizedPlayerPushing = auto()
penalizedIllegalMotionInSet = auto()
penalizedInactivePlayer = auto()
penalizedIllegalPosition = auto()
penalizedLeavingTheField = auto()
penalizedRequestForPickup = auto()
penalizedLocalGameStuck = auto()
penalizedIllegalPositionInSet = auto()
penalizedPlayerStance = auto()
penalizedIllegalMotionInStandby = auto()
substitute = auto()
active = auto()

@classmethod
def is_penalized(cls, state: int) -> bool:
return state in (
cls.penalizedManual.value,
cls.penalizedIllegalBallContact.value,
cls.penalizedPlayerPushing.value,
cls.penalizedIllegalMotionInSet.value,
cls.penalizedInactivePlayer.value,
cls.penalizedIllegalPosition.value,
cls.penalizedLeavingTheField.value,
cls.penalizedRequestForPickup.value,
cls.penalizedLocalGameStuck.value,
cls.penalizedIllegalPositionInSet.value,
cls.penalizedIllegalMotionInStandby.value,
cls.penalizedPlayerStance.value,
cls.substitute.value,
)


class BHumanGameStateConverter(Converter):
def __init__(self, resampler: OriginalRateResampler) -> None:
self.resampler = resampler

def populate_recording_metadata(self, data, recording: Recording):
# B-Human uses an TeamColor-Enum with the same ordering as we do.
# They use an int-Enum, but we have a str-enum and expect a string.
# That's why we use the int as an index to the TeamColor str-enum.
team_color = list(TeamColor)[data.game_state["ownTeam"]["fieldPlayerColor"]].value

if recording.team_color is None:
recording.team_color = team_color

team_color_changed = recording.team_color != team_color

if team_color_changed:
logger.warning("The team color changed, during one recording! This will be ignored.")

def convert_to_model(self, data: InputData, relative_timestamp: float, recording: Recording) -> ModelData:
models = ModelData()

for sample in self.resampler.resample(data, relative_timestamp):
models.game_states.append(self._create_game_state(sample.data.game_state, sample.timestamp, recording))

return models

def _create_game_state(self, msg, sampling_timestamp: float, recording: Recording) -> GameState:
return GameState(stamp=sampling_timestamp, recording=recording, state=self._get_state(msg))

def _get_state(self, data) -> RobotState:
if State.is_positioning(data["state"]):
return RobotState.POSITIONING

if PlayerState.is_penalized(data["playerState"]) or State.is_stopped(data["state"]):
return RobotState.STOPPED

if State.is_playing(data["state"]):
return RobotState.PLAYING

return RobotState.UNKNOWN
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ class GameStateMessage(int, Enum):
FINISHED = 4


class GameStateConverter(Converter):
class BitBotsGameStateConverter(Converter):
def __init__(self, resampler: OriginalRateResampler) -> None:
self.resampler = resampler

Loading