From 42fc0e5b74104b580bdb45ec48c05e10c7f56933 Mon Sep 17 00:00:00 2001 From: Jb win Date: Tue, 6 May 2025 16:23:17 +0700 Subject: [PATCH 1/6] UNSTBL - Refacto + Exit CaptureImage faster in case of problem + Register cameras manually in Episode SubSystem + Make FilePath optional to keep the correct default value in saved games - Image capture doesn't work, image is empty --- Content/Levels/kitchenLevel/BP-Sensor.uasset | Bin 0 -> 23132 bytes .../Levels/kitchenLevel/kitchenLevel1.umap | Bin 151989 -> 154913 bytes .../Private/LuckyDataTransferSubsystem.cpp | 131 +++++++++--------- .../Public/LuckyDataTransferSubsystem.h | 6 +- .../Private/Episode/EpisodeSubSystem.cpp | 32 ++++- .../RobotPilotSO100Component.cpp | 5 - .../LuckyWorldV2/Private/Robot/RobotPawn.cpp | 15 -- .../Public/Episode/EpisodeSubSystem.h | 6 + Source/LuckyWorldV2/Public/Robot/RobotPawn.h | 7 - 9 files changed, 107 insertions(+), 95 deletions(-) create mode 100644 Content/Levels/kitchenLevel/BP-Sensor.uasset diff --git a/Content/Levels/kitchenLevel/BP-Sensor.uasset b/Content/Levels/kitchenLevel/BP-Sensor.uasset new file mode 100644 index 0000000000000000000000000000000000000000..bb01a116b64ac8116bf24c9182723535c4514a01 GIT binary patch literal 23132 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 dZOU2p<6j_J&Z4im48C02GV$rh7D{w(gNL6MFi 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.bActive && Sensor->GetCamera() && Sensor->GetCaptureComponent()) { - if (IsValid(Sensor) && Sensor->SensorInfo.bActive && Sensor->GetCamera() && Sensor->GetCaptureComponent()) + // TODO Pass the robot name in params - use RobotType and EnumToString + FString Robot = TEXT("Robot_Name"); + + FString Episode = SessionID; + + ENQUEUE_RENDER_COMMAND(ReadPixelsAsync)([Sensor, Path, Timestamp, Comment, Episode](FRHICommandListImmediate& RHICmdList) { - FString Robot = TEXT("Robot_Name"); + FTextureResource* Resource = Sensor->RenderTarget->GetResource(); + FRHITexture* ResourceRHI = Resource->GetTexture2DRHI(); - FString Episode = SessionID; + TArray OutPixels; - ENQUEUE_RENDER_COMMAND(ReadPixelsAsync)([Sensor, Path, Timestamp, Comment, Episode](FRHICommandListImmediate& RHICmdList) + // Fail and exit + if (!ensure(ResourceRHI)) { - FTextureResource* Resource = Sensor->RenderTarget->GetResource(); - FRHITexture* ResourceRHI = Resource->GetTexture2DRHI(); + UE_LOG(LogTemp, Warning, TEXT("ResourceRHI not found")); + return; + } + + OutPixels.SetNum(Resource->GetSizeX() * Resource->GetSizeY()); + RHICmdList.ReadSurfaceData( + ResourceRHI, + FIntRect(0, 0, Resource->GetSizeX(), Resource->GetSizeY()), + OutPixels, + FReadSurfaceDataFlags() + ); - TArray OutPixels; + UE_LOG(LogTemp, Warning, TEXT("Logged pixels: %d"), OutPixels.Num()) - if (ensure(ResourceRHI)) - { - OutPixels.SetNum(Resource->GetSizeX() * Resource->GetSizeY()); - RHICmdList.ReadSurfaceData( - ResourceRHI, - FIntRect(0, 0, Resource->GetSizeX(), Resource->GetSizeY()), - OutPixels, - FReadSurfaceDataFlags() - ); + FImageWriteTask* ImageTask = new FImageWriteTask(); - UE_LOG(LogTemp, Warning, TEXT("Logged pixels: %d"), OutPixels.Num()) + // TODO What's that for? + if (Path.Right(1) == "/") + { + //Path = Path.Left(Path.Len() - 1); + } - FImageWriteTask* ImageTask = new FImageWriteTask(); - - if (Path.Right(1) == "/") - { - //Path = Path.Left(Path.Len() - 1); - } + const FString RobotName = TEXT("Robot_Name"); + + const FString Filename = FString::Printf( + TEXT("%s/TrainingData/%s/%s/%s/%s_%s_%s_%d"), + *Path, + *RobotName, + *Episode, + *Sensor->SensorInfo.Name, + *RobotName, + *Sensor->SensorInfo.Name, + *Comment, + FMath::Floor(Timestamp * 1000) + ); + + UE_LOG(LogTemp, Warning, TEXT("FileName %s"), *Filename); - FString Robot = TEXT("Robot_Name"); - - const FString Filename = FString::Printf( - TEXT("LuckRobotData/%s/%s/%s/%s/%s_%s_%s_%d"), - *Path, - *Robot, - *Episode, - *Sensor->SensorInfo.Name, - *Robot, - *Sensor->SensorInfo.Name, - *Comment, - FMath::Floor(Timestamp * 1000) - ); - - //UE_LOG(LogTemp, Warning, TEXT("Evan requested a longer string describing the inner workings of the following string which describes in great detail the file path for the image you've just written to disk. It is: %s"), *Filename); + ImageTask->Format = EImageFormat::PNG; + ImageTask->Filename = Filename; + ImageTask->PixelData = MakeUnique>(FIntPoint(Sensor->RenderTarget->GetSurfaceWidth(), Sensor->RenderTarget->GetSurfaceHeight()), TArray64(OutPixels)); + ImageTask->bOverwriteFile = true; + ImageTask->CompressionQuality = static_cast(EImageCompressionQuality::Default); + + // Add to write queue (async) + FModuleManager::LoadModuleChecked("ImageWriteQueue").GetWriteQueue().Enqueue(TUniquePtr(ImageTask)); - ImageTask->Format = EImageFormat::PNG; - ImageTask->Filename = Filename; - ImageTask->PixelData = MakeUnique>(FIntPoint(Sensor->RenderTarget->GetSurfaceWidth(), Sensor->RenderTarget->GetSurfaceHeight()), TArray64(OutPixels)); - ImageTask->bOverwriteFile = true; - ImageTask->CompressionQuality = (int32)EImageCompressionQuality::Default; - - //Add to write queue (async) - FModuleManager::LoadModuleChecked("ImageWriteQueue").GetWriteQueue().Enqueue(TUniquePtr(ImageTask)); - - ImageTask->OnCompleted = [](bool bSuccess) { - UE_LOG(LogTemp, Warning, TEXT("Image write completed: %s"), bSuccess ? TEXT("Success") : TEXT("Failed")); - }; - - return true; - } - - return false; - }); - } + ImageTask->OnCompleted = [](bool bSuccess) { + UE_LOG(LogTemp, Warning, TEXT("Image write completed: %s"), bSuccess ? TEXT("Success") : TEXT("Failed")); + }; + }); } } diff --git a/Plugins/LuckyDataTransfer/Source/LuckyDataTransfer/Public/LuckyDataTransferSubsystem.h b/Plugins/LuckyDataTransfer/Source/LuckyDataTransfer/Public/LuckyDataTransferSubsystem.h index 120913ad..4f7ee3dd 100644 --- a/Plugins/LuckyDataTransfer/Source/LuckyDataTransfer/Public/LuckyDataTransferSubsystem.h +++ b/Plugins/LuckyDataTransfer/Source/LuckyDataTransfer/Public/LuckyDataTransferSubsystem.h @@ -51,9 +51,9 @@ public: ULuckyDataTransferSubsystem(); - virtual void Initialize(FSubsystemCollectionBase& Collection); + virtual void Initialize(FSubsystemCollectionBase& Collection) override; - virtual void Deinitialize(); + virtual void Deinitialize() override; TSharedPtr Socket; @@ -126,7 +126,7 @@ public: void RegisterSensor(ALuckySensorPawnBase* Sensor); UFUNCTION(BlueprintCallable, Meta = (AutoCreateRefTerm = "Path, Comment"), Category = "Capture") - bool WriteImageToDisk(const FString& Path, const double Timestamp, const FString& Comment = ""); + bool WriteImageToDisk(const double Timestamp, const FString& InPath = "", const FString& Comment = ""); //-------------------------------------------------------// }; diff --git a/Source/LuckyWorldV2/Private/Episode/EpisodeSubSystem.cpp b/Source/LuckyWorldV2/Private/Episode/EpisodeSubSystem.cpp index b3398ab4..b313574d 100644 --- a/Source/LuckyWorldV2/Private/Episode/EpisodeSubSystem.cpp +++ b/Source/LuckyWorldV2/Private/Episode/EpisodeSubSystem.cpp @@ -184,6 +184,21 @@ void UEpisodeSubSystem::FindRobotPawnFromScene() } } +void UEpisodeSubSystem::InitCameras() +{ + // TODO Fix the spawning of sensors in Cpp and spawn them using a config? + // TODO How people can move the camera themselves? + + // Find all sensors in the scene + TArray Sensors; + UGameplayStatics::GetAllActorsOfClass(this->GetWorld(), ALuckySensorPawnBase::StaticClass(), Sensors); + + for (const auto Sensor : Sensors) + { + if (const auto Camera = Cast(Sensor)) Cameras.Add(Camera); + } +} + void UEpisodeSubSystem::ConfigureDataCapture() { if (ULuckyDataTransferSubsystem* DataTransfer = GetWorld()->GetSubsystem()) @@ -192,6 +207,13 @@ void UEpisodeSubSystem::ConfigureDataCapture() //Connect to websocket and create session id DataTransfer->ConnectToWebsocket("ws://127.0.0.1:3000", ""); DataTransfer->CreateCaptureSessionID(); + + InitCameras(); + for (const auto& Cam : Cameras) + { + DataTransfer->RegisterSensor(Cam.Get()); + Cam->SensorInfo.bActive = true; + } } } @@ -211,17 +233,23 @@ FObservationPayload UEpisodeSubSystem::CreatePayload() // enter a message here - FString, // TMap of FString (Actuator name or index), and Float (value of actuator) // Camera info struct goes here, don't worry about this for now, just use TArray() - // What about episode success? + // What about episode success? -> can be stated after the result is known // How to invalidate data + + // Anuj -> How many frames do we need to store in a single parquet chunk + // Exact data structure with correct data types }; } void UEpisodeSubSystem::SendEpisodeData(const FObservationPayload& Payload) const { + // PayloadBuffer.Add(Payload) + // Every X frames -> Write parquet chunk + if (ULuckyDataTransferSubsystem* DataTransfer = GetWorld()->GetSubsystem()) { // Here generate the path for each image? - // DataTransfer->WriteImageToDisk(BaseImageDataPath, 0.f); + DataTransfer->WriteImageToDisk(CurrentRobot->PhysicsSceneProxy->GetMujocoData().time); // Don't send data if socket is disconnected if (!DataTransfer->Socket->IsConnected()) return; diff --git a/Source/LuckyWorldV2/Private/Robot/PilotComponent/RobotPilotSO100Component.cpp b/Source/LuckyWorldV2/Private/Robot/PilotComponent/RobotPilotSO100Component.cpp index 851d11bb..9ecd187d 100644 --- a/Source/LuckyWorldV2/Private/Robot/PilotComponent/RobotPilotSO100Component.cpp +++ b/Source/LuckyWorldV2/Private/Robot/PilotComponent/RobotPilotSO100Component.cpp @@ -54,11 +54,6 @@ FTransform URobotPilotSO100Component::GetReachableTransform() RewardAxis.Z = 0; // Nullify Z to keep a 2D vector -> ensure the geometry roll/pitch are 0 const FRotator TowardPivotRotation = UKismetMathLibrary::MakeRotFromXZ(RewardAxis, FVector::UpVector); - // Debug - // DrawDebugLine(this->GetWorld(), ArmPivotLocation + ArmWorldRotation.GetForwardVector() * 70, ArmPivotLocation, FColor::Green, true); - // DrawDebugLine(this->GetWorld(), ArmPivotLocation + FVector::UpVector * 70, ArmPivotLocation, FColor::Red, true); - // DrawDebugLine(this->GetWorld(), RandomLocation, RandomLocation + TowardPivotRotation.Quaternion().GetForwardVector() * -50 , FColor::Blue, true); - // Return the Object Transform return FTransform(TowardPivotRotation, RandomLocation); } diff --git a/Source/LuckyWorldV2/Private/Robot/RobotPawn.cpp b/Source/LuckyWorldV2/Private/Robot/RobotPawn.cpp index 2f0848f2..8335967b 100644 --- a/Source/LuckyWorldV2/Private/Robot/RobotPawn.cpp +++ b/Source/LuckyWorldV2/Private/Robot/RobotPawn.cpp @@ -20,7 +20,6 @@ void ARobotPawn::BeginPlay() void ARobotPawn::InitRobot() { InitPilotComponent(); - InitCamera(); } void ARobotPawn::InitPilotComponent() @@ -70,17 +69,3 @@ void ARobotPawn::InitPilotComponent() } } -void ARobotPawn::InitCamera() -{ - // TODO Fix the spawning of sensors in Cpp and spawn them using a config? - // TODO How people can move the camera themselves? - - // Find all sensors in the scene - TArray Sensors; - UGameplayStatics::GetAllActorsOfClass(this->GetWorld(), ALuckySensorPawnBase::StaticClass(), Sensors); - - for (const auto Sensor : Sensors) - { - if (const auto Camera = Cast(Sensor)) Cameras.Add(Camera); - } -} diff --git a/Source/LuckyWorldV2/Public/Episode/EpisodeSubSystem.h b/Source/LuckyWorldV2/Public/Episode/EpisodeSubSystem.h index 92cd95be..755de0b2 100644 --- a/Source/LuckyWorldV2/Public/Episode/EpisodeSubSystem.h +++ b/Source/LuckyWorldV2/Public/Episode/EpisodeSubSystem.h @@ -6,6 +6,7 @@ #include "EpisodeSubSystem.generated.h" +class ALuckySensorPawnBase; class ATextRenderActor; class AMujocoStaticMeshActor; class ARobotPawn; @@ -85,6 +86,11 @@ private: UPROPERTY() TObjectPtr CurrentRobot; + // --------------------- + // ------ SENSORS ------ + // --------------------- + void InitCameras(); + TArray> Cameras; diff --git a/Source/LuckyWorldV2/Public/Robot/RobotPawn.h b/Source/LuckyWorldV2/Public/Robot/RobotPawn.h index 623f08cb..1d1df691 100644 --- a/Source/LuckyWorldV2/Public/Robot/RobotPawn.h +++ b/Source/LuckyWorldV2/Public/Robot/RobotPawn.h @@ -40,11 +40,4 @@ public: URobotPilotComponent* RobotPilotComponent = nullptr; UFUNCTION(BlueprintCallable) void InitPilotComponent(); // This should have Robot type as parameter? - - // --------------------- - // ------ SENSORS ------ - // --------------------- - void InitCamera(); - UPROPERTY(EditAnywhere, BlueprintReadWrite) - TArray> Cameras; }; From d39a9a47294b410d07359f69e3815adbe837a6b1 Mon Sep 17 00:00:00 2001 From: JB Briant Date: Tue, 6 May 2025 19:12:14 +0700 Subject: [PATCH 2/6] REFAC - Rename things and remove includes --- .../Private/LuckyDataTransferSubsystem.cpp | 36 +++++++------------ .../Public/LuckyDataTransferSubsystem.h | 21 +++++------ 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/Plugins/LuckyDataTransfer/Source/LuckyDataTransfer/Private/LuckyDataTransferSubsystem.cpp b/Plugins/LuckyDataTransfer/Source/LuckyDataTransfer/Private/LuckyDataTransferSubsystem.cpp index 63e32093..68c5746b 100644 --- a/Plugins/LuckyDataTransfer/Source/LuckyDataTransfer/Private/LuckyDataTransferSubsystem.cpp +++ b/Plugins/LuckyDataTransfer/Source/LuckyDataTransfer/Private/LuckyDataTransferSubsystem.cpp @@ -2,31 +2,19 @@ #include "LuckyDataTransferSubsystem.h" - -#include "AutomationBlueprintFunctionLibrary.h" -#include "ImageUtils.h" #include "RenderingThread.h" #include "RenderUtils.h" -#include "RenderGraphUtils.h" #include "RHI.h" #include "RHICommandList.h" #include "ImageWriteQueue.h" #include "ImageWriteTask.h" #include "ImagePixelData.h" -#include "JsonUtilities.h" #include "JsonObjectConverter.h" -#include "ReviewComments.h" #include "WebSocketsModule.h" #include "IWebSocket.h" -#include "Kismet/KismetStringLibrary.h" -#include "Camera/CameraActor.h" #include "Camera/CameraComponent.h" #include "Components/SceneCaptureComponent2D.h" -#include "Kismet/GameplayStatics.h" -#include "Kismet/KismetMathLibrary.h" #include "Kismet/KismetRenderingLibrary.h" -#include "Slate/SceneViewport.h" -#include "Virtualization/VirtualizationTypes.h" ULuckyDataTransferSubsystem::ULuckyDataTransferSubsystem() { @@ -89,16 +77,16 @@ void ULuckyDataTransferSubsystem::Callback_OnConnected() UE_LOG(LogTemp, Warning, TEXT("WebSocket connected successfully")); } -void ULuckyDataTransferSubsystem::Callback_OnConnectionError(const FString& Error) +void ULuckyDataTransferSubsystem::Callback_OnConnectionError(const FString& Error) const { UE_LOG(LogTemp, VeryVerbose, TEXT("Websocket connection error: %s"), *Error) } -void ULuckyDataTransferSubsystem::Callback_OnMessage(const FString& Message) +void ULuckyDataTransferSubsystem::Callback_OnMessage(const FString& Message) const { if (!Message.IsEmpty()) { - CommandReady(InterpretData(Message)); + CommandReady(ParseJsonPayload(Message)); return; } @@ -130,9 +118,9 @@ void ULuckyDataTransferSubsystem::SendMessage(const FString& Message) UE_LOG(LogTemp, Warning, TEXT("WebSocket outgoing message failed")); } -FPayload ULuckyDataTransferSubsystem::InterpretData(const FString& Message) +FRemoteControlPayload ULuckyDataTransferSubsystem::ParseJsonPayload(const FString& Message) { - FPayload Payload = FPayload(); + FRemoteControlPayload Payload = FRemoteControlPayload(); if (!Message.IsEmpty()) { @@ -143,9 +131,9 @@ FPayload ULuckyDataTransferSubsystem::InterpretData(const FString& Message) { for (auto& Elem : JsonObj->Values) { - FCommand Command = FCommand(); - Command.Key = FString(Elem.Key); - Command.Value = Elem.Value->AsNumber(); + FRemoteControlActuatorCommand Command = FRemoteControlActuatorCommand(); + Command.ActuatorName = FString(Elem.Key); + Command.ActuatorValue = Elem.Value->AsNumber(); Payload.Commands.Add(Command); } @@ -161,7 +149,7 @@ void IncomingMessage(const FString& Message) UE_LOG(LogTemp, Warning, TEXT("Incoming message: %s"), *outMessage); } -void ULuckyDataTransferSubsystem::CommandReady(const FPayload& Payload) +void ULuckyDataTransferSubsystem::CommandReady(const FRemoteControlPayload& Payload) const { if (OnCommandReady.IsBound()) { @@ -254,6 +242,7 @@ bool ULuckyDataTransferSubsystem::WriteImageToDisk(const double Timestamp, const FReadSurfaceDataFlags() ); + // TODO This logs 1, it doesn't work? UE_LOG(LogTemp, Warning, TEXT("Logged pixels: %d"), OutPixels.Num()) FImageWriteTask* ImageTask = new FImageWriteTask(); @@ -264,6 +253,7 @@ bool ULuckyDataTransferSubsystem::WriteImageToDisk(const double Timestamp, const //Path = Path.Left(Path.Len() - 1); } + // TODO Replace by what? const FString RobotName = TEXT("Robot_Name"); const FString Filename = FString::Printf( @@ -278,7 +268,7 @@ bool ULuckyDataTransferSubsystem::WriteImageToDisk(const double Timestamp, const FMath::Floor(Timestamp * 1000) ); - UE_LOG(LogTemp, Warning, TEXT("FileName %s"), *Filename); + UE_LOG(LogTemp, VeryVerbose, TEXT("FileName %s"), *Filename); ImageTask->Format = EImageFormat::PNG; ImageTask->Filename = Filename; @@ -290,7 +280,7 @@ bool ULuckyDataTransferSubsystem::WriteImageToDisk(const double Timestamp, const FModuleManager::LoadModuleChecked("ImageWriteQueue").GetWriteQueue().Enqueue(TUniquePtr(ImageTask)); ImageTask->OnCompleted = [](bool bSuccess) { - UE_LOG(LogTemp, Warning, TEXT("Image write completed: %s"), bSuccess ? TEXT("Success") : TEXT("Failed")); + UE_LOG(LogTemp, VeryVerbose, TEXT("Image write completed: %s"), bSuccess ? TEXT("Success") : TEXT("Failed")); }; }); } diff --git a/Plugins/LuckyDataTransfer/Source/LuckyDataTransfer/Public/LuckyDataTransferSubsystem.h b/Plugins/LuckyDataTransfer/Source/LuckyDataTransfer/Public/LuckyDataTransferSubsystem.h index 4f7ee3dd..42910c97 100644 --- a/Plugins/LuckyDataTransfer/Source/LuckyDataTransfer/Public/LuckyDataTransferSubsystem.h +++ b/Plugins/LuckyDataTransfer/Source/LuckyDataTransfer/Public/LuckyDataTransferSubsystem.h @@ -13,32 +13,32 @@ * */ USTRUCT(BlueprintType) -struct FCommand +struct FRemoteControlActuatorCommand { GENERATED_BODY() public: UPROPERTY(BlueprintReadOnly, Category = "Command") - FString Key = FString(); + FString ActuatorName = FString(); UPROPERTY(BlueprintReadOnly, Category = "Command") - float Value = 0.f; + float ActuatorValue = 0.f; }; USTRUCT(BlueprintType) -struct FPayload +struct FRemoteControlPayload { GENERATED_BODY() public: UPROPERTY(BlueprintReadOnly, Category = "Command") - TArray Commands; + TArray Commands; UPROPERTY(BlueprintReadOnly, Category = "Command") int32 Index = 0; }; -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FCommandReady, const FPayload&, Payload); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FCommandReady, const FRemoteControlPayload&, Payload); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSocketReady, bool, bSuccessful); UCLASS() @@ -61,7 +61,8 @@ public: void Internal_OpenWebsocket(const FString& URL, const FString& Protocol); - FPayload InterpretData(const FString& Message); + // Parse a JSON Payload received from a websocket connection into a Cpp struct + static FRemoteControlPayload ParseJsonPayload(const FString& Message); UPROPERTY(BlueprintAssignable) FCommandReady OnCommandReady; @@ -74,10 +75,10 @@ public: void Callback_OnConnected(); UFUNCTION() - void Callback_OnConnectionError(const FString& Error); + void Callback_OnConnectionError(const FString& Error) const; UFUNCTION() - void Callback_OnMessage(const FString& Message); + void Callback_OnMessage(const FString& Message) const; UFUNCTION() void Internal_OnMessageSent(const FString& Message); @@ -93,7 +94,7 @@ public: void SendMessage(const FString& Message); UFUNCTION() - void CommandReady(const FPayload& Payload); + void CommandReady(const FRemoteControlPayload& Payload) const; //---Observations (Sent to server from Unreal)--------------// //Feature Data declarations From bf58329dc443d396c13deae4f8335873446a98d2 Mon Sep 17 00:00:00 2001 From: JB Briant Date: Tue, 6 May 2025 19:13:41 +0700 Subject: [PATCH 3/6] FT - Primitive WebSocket Actuators Control + Optionally allow remote control of the robot + Direct set of actuators, this should pass by the layer controler for proper interpolation --- .../Levels/kitchenLevel/kitchenLevel1.umap | Bin 154913 -> 155104 bytes .../PilotComponent/RobotPilotComponent.cpp | 4 ++++ .../RobotPilotSO100Component.cpp | 10 ++++++++++ .../LuckyWorldV2/Private/Robot/RobotPawn.cpp | 17 +++++++++++++++++ .../PilotComponent/RobotPilotComponent.h | 5 +++++ .../PilotComponent/RobotPilotSO100Component.h | 3 +++ Source/LuckyWorldV2/Public/Robot/RobotPawn.h | 2 ++ 7 files changed, 41 insertions(+) diff --git a/Content/Levels/kitchenLevel/kitchenLevel1.umap b/Content/Levels/kitchenLevel/kitchenLevel1.umap index 62a4b7f5a8e9fd638702162becebdaa5332abcb0..471da775335e40763ea5edbe8bcc13ae82739bd1 100644 GIT binary patch delta 32318 zcmeHw349dA^8fZiAPWR??Li0uLO>9R5EcaG2saq+Q*uBK!Wkey08u0es9YkjLaT^? z`n*6zQSo?kt00FQDk2DohzcHvM~oN$s-EiE-ptG!YSWZoXb2ia_|z?HkT z&Gy0)o2?vtKeEhby8xex%WbxkW2+n-H{!)kPdIkpGPj`SlvzI{Em&Le_gm(LuBbGl z$HFNL@N?ng@Fm&@#1DrO8!|wFa$JT34H*H@jTxIBxBqC5a{<3&)|A}7lPBiqPA-_y zXIk#$$zw+3PO%-_|H`UOD??n7iS+@~E2|)P@|di=9uuY%WKGD)H7|3zVfmjoZE#1F ztLcO?ee$z%#uUuxl{IBtr@S%wgR}Cc=GwmRvaj)`w_;}3WbgUfwQ9qD@cl3}Dy$w_ zPe&1-)Q@?5$6Hkn`4ud_mw%QeE~~Nm@@r*Z)z($`ZT(n8I22k)g(n00X8|cm{l!2X z5P)4o5;#zcbnIfHt^n$C1N8x*`V3SbP#-W*?*r;G1NBazzN}N1k~XaK{RV20q((XO z3Al{TDEtw&l-J*rCipm zh1LQWTeFr}vzA)3x>=i%V$E7)I-*#p0Q?4widYFy3|H%)+^SmH?3K3S+n+A zTX?{lwZmHAPHWaKYu1<6x=vV^`qY|r(wY@!%{pbxI&IDR%$jx9n$^Ub^_4a2Cu`QP z)~vRIRai0~Vqa!71Q%(kQv5Z;*~^G4Z;flICt_4w!5SEV%QmmpG!XH=HS3@?>yS0; zusLfg=sIG}`arPSmh@TylnNoBmM4eXP$6tdQvvkn65BXfZpfz?bmZlh9> zH9B=aRRpWIPN!}o>_b3ZAgB?5lhlTrS1OM&&HIcpKH&TA|g_hV!z79L^E%C%;Vv}TR6W{tLH zjj?8pwPua8X60G4##^%{ShFTtv+}K3ldM^j1xxSGYEbYd2D`Lbwp4Ki*3ze1v!+?I zrdzXavu4e(X5DVhy2F|^)0%asHEWhNtI(P?+nRNkHEWJF%O04u)OeK>EQj6?d4~Zk z3h&>l=vh;RhgeIhV9g4(X3e#>dLFVwqW_e#1Lnuabm~ck`nXOFBkCl;`Ui#63ib&d zdzPqpKP6_zLR%A}{tDEmb*!Y~?VgeP6HvcEtpC2@oT9RdIcqwU+Ks*udcUFqDC&l{4JMIC_HwG#5fcm?EIv1#a7^riA`lo?<7f}B)P-g@6vVmF%#BUAMSwQ_x zr~XQQ!-n)Po!XYD81VZ{sXrK~7^D{s)LVgCtW)I-6SY05Q{{XTwLNP}-D*nx+?4v8 zpyI?pl?x$~QzuErWt9(g5#?xlb_Y=JL28t9E_p%D+;A56lwkRO8JJ)X>%tFBV=+8= zGOOb2ILPaqPxw$+Da!%ZI|+-{0wbz1|8iX{a{<^bCP&_fbF{2POF485-`w6(Dpm zs;xw}?NNb|sb1G|1r$aBInp;kTCDHURFBK20LS6FBpj#vE5J*sUN?FaHKEGRq%vg) z;}J7ibzjUv3|g#jR+`7Tf|wXM>CyXr>(kQQD(5_i!Q-u489$iGoW7(%jeWz?J?h(jzRT8ZLJWT9kyC5TP;fb3*Jj1;W7HSc%v3NS>p+gj z^%~%0ySasA-B#$w#gFj1URPwjOm^*<w1ZVA@r_F zT7h5J1qx$ReK(ADyEZ9M9Cq-Su|a{(8SQm{LLjs&hKyUd!LKuWjMpVc0Ia}MCv4R=u04?EESaULI9{~OeX*!Q2MM00|Cb+71(jC*R_|-LTQ5q#Af^YO+{M#xCGxv zW5WfoA^=oi`+o-jy{-d_v@hxV3Kb;U**K4Dp91ufH)d{8q}lVluKfxqp1yC~rhrh| zl01*=FffsiwtYjDIJ`X&Xg_Q7EzNTYEaLEUmatoPDA@kvy{->PAo9})^w>@XGCt%EwdPC-d5 zjOt_sU)!VDbU5EDK+^DL)T#Caf||xO0|}}$3H)upBJHI~Ue{?wrEH^(4l5w6VY|s5 zw|oGkj=jGeA>-#v?(CWfD&_G5eM$I0ku-Tq)0nEjFatV23;=pv@}36;Nof^63IHYe zDinmfyyR^J%6UBoFH2!f!!m;)!8IQAzlLa0Z&d_BZJ+5JFojz7nCQbDU zP!(Weh$JZPmkeep$a~GD%xSAg!5t4~#vV^5(4`VmBFMAe&IG{WX36|O_sbx5=|P~w^8;;JNRE4~ zCjmMx=fvoA-!_ef`pzu~cTWSWUvUI1MVLzF%e&cfLa=PR<))!^zILF$FoJb0_r7 zotB%|%=SC*zt~i?Y)DRdZ=yG8Q~#gS`mkbtu%A8JF^Ejq&a-;5=tRu!c(m0{`rq&| z^wmdaYypM5-&*F59H65#Lv447)$bTV#^qEzI*}ecA_{c+b-;0|2 z+IC}M)pru$<-ayf-8S%F-5=`l>*oV*?DlRE&*{eMI%Ki_{QhpN4lCj>c4N`k$wJIl zTeL1zRzQNVz4j=`9Y4i3oPX1e)$`X(+P#yfWw1KFVTGZ$*nWW}Qz|F|Ay<>IN^&&_ zWAPe%u!hy)Q@+wX+viXY8l^ZI;a|~W_%q#ESeUdK@$ZNhRv`&vH(K*(Cw?dg8c+IK zR;ALn7<|NK@;W_OR6x;m-k}FO%{FVLw)2*^uqT`g0gH#Kqm$;&3Ms6mvkwX@!SBQS zrLb6MT@zL`>Tq+2gu^#|S&aK)`@SZqbe`3~p1@BHvWGhxnevxax5x8#Jy~RgY)7>T zw@!4+(KwIAqhz2^u}Xt*wQunzHN&f~e|d@!NW_ST?7G$e|oH5J7D8W{e{(bHf7uGq=RbunMomqpi? zBfkNAJ7DE-Ud;FQh42^iYz`wFt^=cFg3!;nwMaXQKV1tD%qS8$X^@^`9|)gGt>enpa&(E&Ug}4M5f>O zp{ZuR_d=c+nab)J-5yR=)h63cF?9ifYRxHn=Tlj@^AM_q!-U@`jfFepBHudR>|JQx zK^(atjd`3gCXPt4TPKhW0}O>Ezs&cZ8baTFuJ z?~kFOVHKgC+eK|{w&ncibXKR59_mQ`M+U3GZ_Z#<{lsDZyS+tNms4GECOoW}APrPVNu!^EBJSE^0*!7~nXc4f)sQCV>DcpGNrEDB zBq#<*>M-~hE$D;c5)UMgehM@RD!gwa4y3w?x9eKBv2m$Ls(<(b*6 zfihS@S<{^UQMSnG-)BQ*^hrp)99G+ZmqrS0ZyKi=&BU0(m*g;q^L4_>#*7hooo~p2 z8=c)Itlj)mU={8mmU?F@hNZQ{kX^PnUCW+Fmp0pVR*YI!jJke?uWzASt|!Qm5e=CfEduP}=BWIcJm`&o5ss1fFehljvoh4<` z88@{1`V~5bv49OnY&gGZEX!a_*s2Jgv=9;&?(pQIR`nM`t*#r#>LfNGX@*Fb65;w~ zi!g8YB=hLE^m;t`XX99Ol4S&OW@sJ2i27%q+i+oPLD!{~w~yZ#wyJ9pt`J1=QF-9f zG6$gh_$vrNl?ec@ETYA>m^77rQ^399Q()Hf0V3;^bbqahN?b7nO?du9Ht0X%{UN3H zrnS(gEMt-X7wqY$Mq<*Lw&jb?bSR(QTvp#_l(F4UDvQU*l)>4pdGQq1%vnl{w$51} z1PjSqS^PF${LiO#~R!ZJw9Ywsd=mk zUve9(R}L)UY&sOdKNQ$#J*$zO=odW$X^Ew;4|3b*(^(_?jg(p2Sap|t&bpQslRV2T zgRj_Fjw8*OW?|_ufjPI{@_FAGa0?N{>nmmmuRl4X^ns%_|8NB)V{mu^UvWF6gy5rx z=5;>!4%W!ni?-jPCJ!-$gv?|~&Q%l!wO1-*utWD9p9G6SDVE-ntJKn49Z0TWNbsj` zTxCkp9Y=UPiP0Hv9TSLX5E8_{DR&D0rr!y3PZIFV2Cgzd)C&t7-1_%N!P7`he6F@{e`v3O*ps0AIoEPNc@w zb0IYr1)t)HkvNohn8%h|&+S^=&2G2iuCVXWyliJ?xlRoBm{VcjrE@5SLG_H*Wp5T< zw(O%j$)%^QU0eTA7CxdE@eSQhc5cfi93E^Ea1q3oeRR5Iz6e3u{L=Ge3m&-urp>{# zV+$V83wqjjY9Y0r-@dmHgu)w8<6#7Azlim-LQ-F;wph=*mHdO;@{ulIMxjH$84%#w zcv~G3B#<2Mf70~aOYy*y$P~CHU>Rk(P9THTl$y%`l7g2jdBg*7r^kt89Z%T)VA+TA z!HTlSv%wM(&(=%I5>E(K>G9lmKY;r1CQI2E(@rDi)5n*xe7l|T#HB2UZ(PRuSwmqi z(SzT=9CD*!{%@Fix8OKQ`9F_fVyO{at42^vP8?;5*}4Z9Y!N`Y=2~lM547sb8oN8he&MES8Se zKg{YD${t2&Z7(@q7{<4zKs-P&0}~%#?6+d#Qp!GJ2ABhL>*lHOTGgmku!5aUbt>kw zSHV=fX!D9yY#6KWkYo~oCy_u7K-XdV?novE5a{sym zcIn1Xpm}g~AffmjpI?f-JGAElFX8CMKYo<8b3_tyFt4?Sd0N)8GFJvPjsR-PTqL$P zR29o~qcrKZFN}+WUA6VAyq?VA$R}6_@g&kArDuv#^;Ks@BNC>6`fEYsspwv#hPzQi zZRb6NP#eXK+CGZaYhh)wKGi|xXK4JTGgt^ex)%I?9f8*Ii)&f*0BPYgXf zH_;k~P#HG?)K;@ZEU89pAl9pN-p)5Z!J_pgExG+q+oj@MLnyV<)LWXaXW_UK=&Ejl z`9G@vVKU{4>wlAM273KT5}rh!vSps{@?Yr@rl%S({8yodhq>!0v{`zlgPt&Ug~Jpy!j% zwv!3tUSbyWe(Pba;0Rr+;wWLRgoMe8;0Fn#^#>@vp@m_1KNiYQtY@L@WqxP_OLRy* zBYErw*4kY_B5`8TF?ZCM34QajX4t;sc^gCqjTg>gOLFTC1Xsb*2w_t}x+1vn+yTZCYZDRP>L_;mG-mV{_lE>)s zGMy1LmEXCI#Y7!zFWaZzx6QV&=r~K?W`pYjow@Fxb!Kz0&eEB+c+0KKY3)t1mp0sO zOD?-V3wM!Q)Xn0hTGLLQwji~VJAp+_aQ^ah7M zDq9Wv3N?7YhDXT>aJasqN#l#ZB6VSt);gWp?3mxNUEEVXu$^7c-Vzgj^F4DY#tWY! z->g<1=zWeI=Gz;3?-24v?SKU-O&)-s+QAy@?NgYBf9lXTLtVZnh8o9~;`X5$R!Pj+ zwbm{c*LliL7G^Jo=RHrbs80Br4IYME5(UKP1j~d8D-|}};EzW!-?%ei%kBQ1=5>wS zCFbE`K`Q9EoHdTFO)ze@7J34RBRx+&g zZH9+-L!Y*uWpH z<=ywQd~I*XuKlowx`_Y0pH1--LWG-n;Tv$@z4ZVa>gND-(EBPB2sK`1)p^`Omg=6~?tV#$mT|5KLK`3Qy~ z+Ov4A-v|ui`lzG9w<)IsU%wsF+G$(rdxmD815H^Ug!uZS=Huf93R7W?Z>bi zz+qS@>neT}Y@lFT>Kg%UU7vtze?-4RW{^Q|@5j=Ly0Oh|rP!g#(e2<_#{woCaA~@C z^vPpF$JS%)dgI1)zqHz)1W1ERQ)#)M2x+rEVKIGWlF}=!OVcToUU2<6o`{BAr#p{- zWmFn*oZ+_muE$y9(&AJ~yBLi&$VS88J8oXTY9~bf8k}H>!Mv4!BEVaC87x&E)}&rI z!5SLhud?*o*Z_-DJ~g*^>8HZtXFk2^mOM_?N72HQXvxS7(vruW6jsbT$-GxkL%a?0 z$K?By<~8)560-Z9gKebaPq9ejYb%y69Iq;X@A42;LS~RE{jRBvKFyk4L6s(eP=A$X zo;I)2_S4MMPo3>L&@#NC=rlDu*Er3!tl-1|b=QApu5RLI!h_IjJ*`b9(U#Kl1Ee3b zkREr&FTME{*gh#h`usBn+h05*IzO77?Y#oQlU2d+T^=Ip$P5zonV;*G$!>84I;VhG zJ?>H=oP|zg2GQB*tY2r#E6|B+ewI3s8ARtNiq0FaKzO+G7Tg7Zm($yOm)74M?iJ!jud9B~P#zju&RoMr-G_T2yw*2xzfeQ_A;&w?DhDk4iI$oU z*g)?%+R>z|!>bor4BPzHw`>p#b|1F(65I@!ub9{AWB0;3L$l!?_1oGdN*hM$U;IBhf$X+IsLU>?T+Oeez&|Wf?t+s$TM8_%3{R=8zp*#6|ZavzwM%X zeDku__DFk)#~*9K*dX^}|NhI;{qSyXEgpKAIfKPuguxf_{jBQlH+1=BcDbTOzsDb& zsrn$w_>BY&;D>s$21&QmCEoJk@tt-Of5mdU{6XSV#L(VAZmBJc2U&Ir;+{pJc8~KM z$-_k+Jg|ksuhQfGjJf`u|E5he%ita*T&*1T!GsZcYi`_-a9tx&&!2_lP#NVExst}A2LY%biyyqf`ve{ ziuXyfhX!jU|Ex)UT3DI{CfDJsTiBzlt%>C~wYFE`-?p$<@%N~!2%GTI)2)J=)Td3U zCMkLNYOP^yuFe`Cl>g^^liL1jtf|GnZ~srW=S>~2#+q2(rIYF#>E|i{c+=TzW*b@;q54SU+u%V(;muQ1hwpWy#J!}_-p-{`31*sf{B z6u(C1{9jUqlZz&^R~%Zte&2+6E-ZI3VeuHKsRBa+R1ibdL0v~B$A)VaRf z7TIm56Q4UO_XXJgb8a3`HdPc^c?y@$0IxF0|I6BM{F|QFvccDB_OQ5%6c0l_X$_2< kMK*xZtwFlIv)M1ktwD70Gu_Ej=|e-93$PzyIFn|LS>$?ymFJsk7DE z)#2=$AsgNZd1)dEdZr_>HE{o&SkwLU&M@~n5)@X!ZnM1zzw$Plt)=^AlG63p{x;jI z@U*qH&DME|%~lTX$1bzk=ECnO_!ZTiHn$Z?G-6OY zOb-jhWI+ES(VWEo)x%x3fa~YaugHtx0>Oj=@QMk;g@U#Ma7!)V9tYfGx;S3HO)_)? zaG5TUt1oen0&a-~-2H%i*#!4!ks$sCz{O@jLAz>zn;_^?C=_s&3BS(5okcSMxY`8R zO#pue+;R)J!T>n8;Bg6X1E85O_Ig2>Eh@$!T%l7jHaJX=3)(EeZLx^kuHl6Lk&zz5 z9U2f3Jn7+HwTOGo7l-xhG2H13)WOXJjbE^c+hc%Z`b!fc!=na3)Lq4WW&ww>HP;-+ z?T^4HtO1f53%-^?oP)yx;$$ag&abGfOtxurU|HEC=9)0EP{2nm;#dgCeK>LO=33kQn*OB;>{KrzGxBmiXjg4mr;$rd)*M|jeEl) z?n8f#y%k3LW5pi@0t(!2ixrnx#1*|`5%`To0Z&-u_XWol1&sbLEi$ZR5qHuHC*of( z`cHWQx%zsz(-v{(^l-BMsX^FC4=Bv9;=ZwnyJUhZQW=(-099ODi@587aYZcp%K`%# zj))m@pdPNAMO+>c_BUgAFEq~&EeMFXk1XOomN@Bubgyr4A;V83Q21XDHw|!yEaDFP z;8^{>6;B0*b_*t)0=Or980ra#xF8*Vu#VvR%K-#?!ZkV!<$^;FAh?Wq(*!5SkKY0J zybew({DSoVIRHkP0=fM^1>6?l3cZ+-(=o(7X90Jp2!KZ{RQwX)n(E-BW#mYR7F=tB zlLHdso&p@Rzp9C3_?BR@3V;upFqF8JfGe->V`c}5TMoDb9)6zqmleM)hzkMut_LWL z=~<*o+ycOzv4DFRaC`l5P`_`*4+3zXAJ88+8*uw|a2158--+vEfcx19hx$w4BLKXh z1C-JFxu79m+!Vm1x=s&wcL1Db|KS0EygHlh9*ejU)^J6(k=B5=Q5JEdE#k&79Oxg& zgkvo-9A^-lbi3KaI&!xdP>73$!M^cYSAAU5SdmYJl(P>i@0e4al-!h1_W~ZPq&DhVG%b|?*9Zb^(>1F@3V-TEpT4@e|o;V-y*{~7I8ib z4`p`Mt5}RB0fSJM)wLjfpdYCoP{$zAxO79;wn;);!#fcb~3n?7>*Xshg*m5B?32EMcz%l)$3FiUucLN~mFL84L_b&^$2LShnIgZ=E7=YKzfmpw- z2yow9z|8^N4<aTX0(#NvmK>+;KgyD5T zn*q3uCb%*|Lq#?zIBtIsLRPE-cPT)Cu%w4OYY}(VAD5w9@t^)cKODX=`&7fx4Oygm zniv=F1JXQ=Bqg`mL6=}2oAl34e;ELYTnf)Wf>4;M^&xUXat z+F$DA9xv`YCNieu_Oh`5(z#nAP+ zq`Z4$PFgg)MPo@V-fpl0nwQ(eot_&Wy+Ws+@!r5R2kz%cZP@E%;Jtb<*XClbE9_aI zBOlb}iu=c$8t#PrV0Ye7SG1ffpd5Lksx?CtIW}h1b6*|mbUY`B$l?V2F#RhVtB3<; zmE7I)QXEh6Y*y9pVw^sbjuyn2M?A z-kl%rco_hQLHS<78%Td+IBYZx+_w#LI$jYNq{;Bk8t&osd{&&B0&cs)OU@rNMtE>P z3`=1cR1xdDUj$IbI1lE7yE&$Yz>E->eTDuQBq5)L0@HA+2ZJ&V9L{Az4F2W|+rBbG z!3|Gw>`>%BA%Z@2wt@j(t?qF;UIm;Cmu`Yr)glk>k$Y0y+lGZ!+9Bi_Jl8`*d7?)+ z-Die}R{Bab?68MD^l>9n96J^ME5!W!;$jb``^b9kvJv47S6S$^QsCyhRUE_=@WRv_ ze%L56#jf^XP@ZEWovc%mI?drQTRy|Zy5mQ=9LEJ8v|psK&1pejI?5IOo+yKz+LJ=> zU)Hh)U*BDMbc$n}V1T&Bl{k9o1{8z^Skris_W|_XoNBW*fg2f&m=_@kZ#>V-of++l zJ|_60OtC_+51tnl-!ZmXY$G5@%~ASIg2^{8awfaSxE%PR2l=S<#alT&X)LEp>s&>X zLu#>22oLmwigjey)aXr4uR4y?D?%CMFX_b^y_tL4xN!HGv7wdZPT1#yb`i?Hr|?b@ z3cmFg=bbm+<#=DA*Ae%Zc60hCK>q~tFdOUs5q_Aezr*S26F41d`1=}(kjeyo8_;q0 zT(Gstx2Wg$@bd8mu4sIVhjeTR(n0O^3J0tyNO9m|4FOm|sNmFVFK19z;Bw#^O&a$w zU@&FxU^ns9a{9&G7G}#p` z7mDhr*6N5N;N{6_jsmogNSDNQ#1YOUZ;DHoKHw;)e**MIqGBv6%V&JX>FHBlj^+wo zP90i*>8xx}sYNItsZ_)PL@CMMxa-GNw5lI2o7S)0YA`{}^XfHqFK8p^%SOmh`;i z-gN2r-J*Qk6P#XnuPY4sAcq3BKHqbYV#MoRIlm0);fZ|SI{?p z$?3ZVJyFo#7W9!PIX!6xFOU7UDs{b6oG!J(E(htdQ!Eqosxx_c?AMSk*V6}2^YUlz z=k(hJeLq|=Ej`2O7e#rTFk$(vNQ9JimeccQ@$yNKhxBbogjDXF;D0ZtH-kKt{)!L1 zxetBVSKj55edyumz3DA{=(~OBEq&+(UwfBN^`Y1N#+weu554U7p`;7@p>Ft-QRV&z zFE9OrgA~$_!w*xz1y0{R$K`0lv)I2CMW}cFmeZ4pT#jUshX6_M@g1kH1NvsAd}YyH z_y3;Ls}{SW<)ROpPcDWxq`V)M<}nxojE zfaVpquF)5?QZ+3V)t-t`faT_UbE_t1SD)5WK*LA&s-rS_!er=$D z&~DQo^wxB+FmQz?3UKd(-rOW8FUbqky2cG7yLmmDb40gsxEOw>VZe2rI%2{U+fbU-h1B;bcU6|A%W_F|T0E3gA*FO{7vdZ#H$G55xnU6t z_i}?H2JXvOnr}M~LOdqeG!|F9Xi~y`1I!4=e;`S1&}Q$lV5*ReW5+0NU>8m3O6t<$ zt|Wxy(wAG>tB0v22GhfPA(r0hO5P){@e(C;T{rSf)IwwqNkzBkkyqHAq()^5C#m#I z#x0h$wgeq|1h37WN$N-S(4h~e%?H?1q8b>H>d;M@q?%^$ngX|;H@qBaFTePe&0h^7 z7A?}M^|`M8O?H?d^ree}?F~B57LT%=yf!o_Z<`t&VVTE4rBZBBJ5w*H$NdVxK7B{M5pe9h_k8Mm@%Yn zxF6Ym^0Xn>F#x5QXRD5l32TR4kFnznndArPreUNZG{D2dNITM;%`LhB zXtSq9-wltA7$F1hzF-78>uwk)PVoCpaIZDAsr0?ONdrEF&`QJg=NIY2S=%2poYeM= z9ZQCj1pi*@4c5fGW)RI-SKnbXs)@hX;fG1;t>)o9PL5>Sk2eRzlZ)ndg=NdCA`($_ zP~`bd?$-%|4Ei48SvD8(XtO065#o&qbvy*Q=ny%wvNy%E9M({s1)hn2(GEd>MqK@* zmkYys0^lokcmo51?Jfg?)+d0BQZs=5O~6$roI^?D$XP9xQt``&GXm)+>lQ(f%Ksvs zOK9_vBr^H3d=yu_VBcsv3%^Tv14*r(4;c}Ee&42a)19Fm7q_c6^5oj|o-9eUv4GL;s-Q$W zZ8nWWhCD8!sD$?E12a}#YvTr5Ttwyc>YWCWvl-i~_XxcbHUCU0-8juk$2R2NKpop0 zayuO`9ZbfXP>3gR-=6LjxMB3~Zdj*h&Hyi@@(%S5y;iVoIq7W8utoSSo8b`A)UshK zDuxvj;e`zthyDx{#n)%DD6TroiaqzwvSQCY_i6U@8z@j`MM&INht8uhEtpM`qgD%w zI<)zXtVQ^ZqM_O3RX&k|H$NkLA@7@n) zw(&l4qF{s5*~lRFCHErHJ5*{H#OtT;@o@X|B zJ7i=t*2mSH{ajYht%Z4AAKG!W?XUZDUyG@<2bE9AXXfkbOt-U-wIhKUej7LZ&DMJ>Kbn_#mk99uSe(qbWbnPG77M58Q?F+}J-(Day~K*62QLjY2I7+HHJISg zu8)D2deX*}j0y2=N^Hv9aT%FtFSF6iC&&(c1WYF;`Oi&!Ce?gy@}K6b^=7`piHT)j z(Zm&OAZfM2ii3VyVZ}l3uLP8tZ@}_hX`LrXgMbd|N|&tC8k91F^z%}$)iCMqrPFbS z(7Qf`F(2P>Hj>RmURe{UA{v6)+ltQekJT{hwWE=1NL~LurEjz$EvOl=_{j5!+b|z$ zk1-A5@dM*Z4y^$PxqvF&d3_BTN=OCFiTN1XU9u>vHtOk~?VH37g5F$QOT-l~ ztYyP&!`6qt4HIV^q9NyJ|2?sejBEOBSO85qP}tLNlSK|RSvm$+2AZfrI`nz0lG{1~ z|6s2k5;AnUXl%?O8-a$Y9Ub-$5?NK8c;I=qj`Xf|q}@Qay$f0WM4d>*U0z#IFwcn- z1V3$LIMfgT)zFLBF@q|M(MaS5={(=CchQKaU~T?<76}O{Pil?A=O>{u@AnZwEUwo=6yN55;3JVY z&7Mz_2(PW#`YivpY^N4HKS!AY-jjIBgm@xts1`f9Jj%4^1FN3x=x5?~$st$Ln>Ywzcn zdjYHB{$E<=u#nq|Wd`1$Wdv)u@UYDPzSDoJnO?pDGc~<2Gtslp!d}jQt6|>00mIy6 z{CxCbX1f4p(z@sz|9&Z(^*^=PD|~Fcf4MWMas!=7+Z&1nwNp647P<9>dEwHC%?PX- zAj97Pow)(n(N_xw2+?{|I_z1J5N6hACR$UIO@1<=7qzOzL)z#?6J8+YqdpbpPz@zU zsjL@p#)~^c+JsQEU8#1*yCNJD&;r}(V=t1X_@`w36N|G5yE0Ri#p{c}9XKW?6!?pw4r#rlr+{fP?b)w6+kzpc#XZ%a#gUV5(v2IAq z`Grj(_<`MYX>X*kL99q%RBc$6F_XsDnS7_2I%o}es15omYy{k;iE!te&V;B_&X{!o zXf5PYonm}aMQfs-1B5Zjz`^kNMNPWCK2MC!xgYE`%c^h!UEiqsV zkigJ@@w9X|i83}IJM$VIZFBu)6Bg|imQbDZtzsS3gmW;zr|_m4_e%^4_{H#20URf; z*Fu$&%n@;VQHDk`RE%f*vz4H?(u&_Ezpz+&gsoxf_=WIdfe8~jh!=;( zhv?|a10*~OUfQsv27wr6j{#jf&=dINT+4%c-<%k$&^@pULN>gxWd*hsV}b`mJ5rZX6TJ5GItP?3Y zLK4AZUSBG(!?gZiSQ!q?adf~@IOcQfzHNw-c0;qEqe@_Q;lOvKPP5~!8K37S}jVoc=>qTQ>X+2eJ8hH|x0mc()Bo!+3Y_ zIH})a1E4YaZH;vh4O$HyhUB%}1NW?!7`!uS7mH5F%IdakVf%~K(Pm%3G=ggdx4Ivo zjQE08H0KM_$jCG!rO@=E0h4-PGA6L^?2K&VKgKfMYmEXnwc&@!uZ!YFL5PV_e&p2z zog_{EvIlU=$}O_$q`pNyKFO51aFR4Nvd9>XNzN&KCd*DSCeNKB4UL%K<}8;a_Ow2e zUZ)w8(Wh@B;&4{ajf;0C#%*+lRo3AQNimXmyyQjSKBF)3WzHo00!eV+p6dDX4+`?c zn*n}h$A!W>6QSNX%Y-^|mNYjKszCCh-OuR@ReX*(o2WBGw*7!}=nm8F9$^r!LHM?U zyXSWUzD>Vk!m+5=nOSmGCu(dqf8{6MJ6{o8VlWOZj0Bk^ImFKl)2t&F%$)41WC3AZlLZE-Ke z8U@+C8ZW#v;dPbsf(DRm#A~|bMHgQ1iwHb!NZ-D|1cG*7w`Iy-?fipg$a-S%&P3)_ z-?Hjn`IgkF6L_SdX#s8bZ?M~g|FYwtZZYRwg2>a%>i5~c8_a{F>FFOy0&HC1xj}bHme`mJ z%K?D*MrQ~dJ7lTd&fPzgSh92WPjJ4_Y#N<+876ZkN?z+_=V$nxd^a#GJ3ys{_FG!r z5VPvyu8^SMrny{YzuAuQ=3*Qjcg<`LTaWI##x~~h6AHemJQC#a7`Jy@do{byuJU?7 zn5>%aD>E~6JUvhb+xPsV4PN6tewx?mZKSCt*{gl*Wfh~0H@9P-Ko-9VK62jzgJzaz zg_=B5(C-#kzxlWPBP*u_fPc@K+Dkn8%{%2h-NZiKLhR0{uLT2Dum?PXh0GOn)lS3w z?4-1Ut0DF;%b5jaxhg*K{cj4m{SHvi@M}z+fXg#ei#P5kop$ra-7pg>HEi5<=!{Hz zHLsY>n)to@5}K0*H!6fkkBGu~W)QSm+k3^x&YE% zMw&QUzN1ed4L0e4DGULx?`+n|UdMDyo!7-org}6`H|*@{W1*oYGKq=q#%{NIEX)m9 z>1#cqPM-nsNhoD9AZ~~0v{}1WrrYdq>aaf(Mq0DI(~8i#_*M!0?J(V4##eiTGuzRR zd)w=gyV#M3TI$p-6lYI3*K^|yB%fCSw|vp){ifmn*eie2&i6G;m0{qITempwAZOgn z&-hQiSQ)O@a^wj|+gX=>#BLa5uSpN}w?`XKTx-*sS@!DwdoT{|?Nv*B-}#5ARE@@9 z%&RO2-r|Pa@w4?L(v8LaJit86&K!8{J$%g81-0hmYkA}~NU&1(C&XV)8>~ZOe+4C_ z-#|fHFRpk&iD4j%>pXRobotActor->GetActorRotation().Quaternion().GetRightVector(), DirectionToTarget) > 0.f; } +void URobotPilotSO100Component::ReceiveRemoteCommand(const FRemoteControlPayload& RemoteRobotPayload) +{ + for (const auto& [ActuatorName, ActuatorValue] : RemoteRobotPayload.Commands) + { + // Will print an error message if it doesn't exists + RobotOwner->PhysicsSceneProxy->SetActuatorValue(ActuatorName, ActuatorValue); + } +} void URobotPilotSO100Component::PrintCurrentActuators() const { diff --git a/Source/LuckyWorldV2/Private/Robot/RobotPawn.cpp b/Source/LuckyWorldV2/Private/Robot/RobotPawn.cpp index 8335967b..587adb17 100644 --- a/Source/LuckyWorldV2/Private/Robot/RobotPawn.cpp +++ b/Source/LuckyWorldV2/Private/Robot/RobotPawn.cpp @@ -1,5 +1,6 @@ #include "Robot/RobotPawn.h" +#include "LuckyDataTransferSubsystem.h" #include "LuckySensorPawnBase.h" #include "Kismet/GameplayStatics.h" #include "Kismet/KismetMathLibrary.h" @@ -69,3 +70,19 @@ void ARobotPawn::InitPilotComponent() } } +void ARobotPawn::EnableRemoteControl() +{ + // Get subsystem + if (ULuckyDataTransferSubsystem* DataTransfer = GetWorld()->GetSubsystem()) + { + // Connect first if necessary + if (!DataTransfer->Socket->IsConnected()) + { + DataTransfer->ConnectToWebsocket("ws://127.0.0.1:3000", ""); + } + + // TODO Should we wait for connection to be successful before binding OnCommandReady? + DataTransfer->OnCommandReady.AddDynamic(this->RobotPilotComponent, &URobotPilotComponent::ReceiveRemoteCommand); + } +} + diff --git a/Source/LuckyWorldV2/Public/Robot/PilotComponent/RobotPilotComponent.h b/Source/LuckyWorldV2/Public/Robot/PilotComponent/RobotPilotComponent.h index 4b610192..b1b82568 100644 --- a/Source/LuckyWorldV2/Public/Robot/PilotComponent/RobotPilotComponent.h +++ b/Source/LuckyWorldV2/Public/Robot/PilotComponent/RobotPilotComponent.h @@ -2,6 +2,8 @@ #include "CoreMinimal.h" #include "RobotPilotComponent.generated.h" +struct FRemoteControlPayload; + USTRUCT(BlueprintType) struct FRobotActuators { @@ -33,6 +35,9 @@ public: UFUNCTION(BlueprintCallable) virtual void SetRobotTarget(const FTransform& TargetTransformIn); virtual void SetRobotCurrentRewardZone(const FTransform& RewardTransformIn); + + UFUNCTION() + virtual void ReceiveRemoteCommand(const FRemoteControlPayload& RemoteRobotPayload); protected: // Child class need access diff --git a/Source/LuckyWorldV2/Public/Robot/PilotComponent/RobotPilotSO100Component.h b/Source/LuckyWorldV2/Public/Robot/PilotComponent/RobotPilotSO100Component.h index 14ae6c7c..768f7e47 100644 --- a/Source/LuckyWorldV2/Public/Robot/PilotComponent/RobotPilotSO100Component.h +++ b/Source/LuckyWorldV2/Public/Robot/PilotComponent/RobotPilotSO100Component.h @@ -3,6 +3,8 @@ #include "Robot/PilotComponent/RobotPilotComponent.h" #include "RobotPilotSO100Component.generated.h" +struct FRemoteControlPayload; + USTRUCT(BlueprintType) struct FSo100Actuators { @@ -42,6 +44,7 @@ public: virtual void SetRobotTarget(const FTransform& TargetTransformIn) override; virtual void SetRobotCurrentRewardZone(const FTransform& RewardTransformIn) override; + virtual void ReceiveRemoteCommand(const FRemoteControlPayload& RemoteRobotPayload) override; private: diff --git a/Source/LuckyWorldV2/Public/Robot/RobotPawn.h b/Source/LuckyWorldV2/Public/Robot/RobotPawn.h index 1d1df691..e2bd9240 100644 --- a/Source/LuckyWorldV2/Public/Robot/RobotPawn.h +++ b/Source/LuckyWorldV2/Public/Robot/RobotPawn.h @@ -40,4 +40,6 @@ public: URobotPilotComponent* RobotPilotComponent = nullptr; UFUNCTION(BlueprintCallable) void InitPilotComponent(); // This should have Robot type as parameter? + + void EnableRemoteControl(); }; From 58301dd039d0a158a7da7d3362304a5ad5c5354d Mon Sep 17 00:00:00 2001 From: Noah Date: Tue, 6 May 2025 09:26:19 -0400 Subject: [PATCH 4/6] Defaulted resolution --- .../Source/LuckyDataTransfer/Private/LuckySensorPawnBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/LuckyDataTransfer/Source/LuckyDataTransfer/Private/LuckySensorPawnBase.cpp b/Plugins/LuckyDataTransfer/Source/LuckyDataTransfer/Private/LuckySensorPawnBase.cpp index 5a3896c6..4c348dd0 100644 --- a/Plugins/LuckyDataTransfer/Source/LuckyDataTransfer/Private/LuckySensorPawnBase.cpp +++ b/Plugins/LuckyDataTransfer/Source/LuckyDataTransfer/Private/LuckySensorPawnBase.cpp @@ -29,7 +29,7 @@ ALuckySensorPawnBase::ALuckySensorPawnBase() RenderTarget = CreateDefaultSubobject(TEXT("RenderTarget")); RenderTarget->UpdateResourceImmediate(true); - RenderTarget->ResizeTarget(1, 1); + RenderTarget->ResizeTarget(640, 360); CaptureComponent->CaptureSource = SCS_FinalColorLDR; From e130c0897561e370d070e5cc07ec2211ac9ab794 Mon Sep 17 00:00:00 2001 From: JB Briant Date: Wed, 7 May 2025 15:43:18 +0700 Subject: [PATCH 5/6] WIP - Json/Parquet + Compute stats for each episode + Skeletal for json / parquet functions --- .../RobotPawnActors/BP_mujokoSO_100.uasset | Bin 1264443 -> 1264920 bytes .../Wdev/Robots/BP_SoArm100robot.uasset | Bin 112100 -> 112270 bytes Source/LuckyWorldV2/LuckyWorldV2.Build.cs | 9 +- .../Private/Episode/EpisodeSubSystem.cpp | 178 +++++++++++------ .../PilotComponent/RobotPilotComponent.cpp | 16 ++ .../RobotPilotSO100Component.cpp | 179 +++++++++++++++++- .../Public/Episode/EpisodeSubSystem.h | 48 +++-- .../PilotComponent/RobotPilotComponent.h | 19 +- .../PilotComponent/RobotPilotSO100Component.h | 9 + 9 files changed, 376 insertions(+), 82 deletions(-) diff --git a/Content/Blueprint/RobotPawnActors/BP_mujokoSO_100.uasset b/Content/Blueprint/RobotPawnActors/BP_mujokoSO_100.uasset index c208af642dd74057e5b0e8d2dfeb3469d40316f9..87a704d663d0761961a2300d17e3daf9755e0329 100644 GIT binary patch delta 44012 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 delta 46105 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 j&oI%tAl#gmkQ)8=lr(*~|m^B4tp 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 diff --git a/Source/LuckyWorldV2/LuckyWorldV2.Build.cs b/Source/LuckyWorldV2/LuckyWorldV2.Build.cs index e7a8cf2f..8a50f741 100644 --- a/Source/LuckyWorldV2/LuckyWorldV2.Build.cs +++ b/Source/LuckyWorldV2/LuckyWorldV2.Build.cs @@ -8,11 +8,18 @@ public class LuckyWorldV2 : ModuleRules { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", + PublicDependencyModuleNames.AddRange(new string[] { + "Core", + "CoreUObject", + "Engine", + "InputCore", + "EnhancedInput", "ChaosVehicles", "PhysicsCore", "AsyncLoadingScreen", "BlueprintJson", + "Json", + "JsonUtilities", "FileHelper", "LuckyMujoco", "LuckyTextWrite", diff --git a/Source/LuckyWorldV2/Private/Episode/EpisodeSubSystem.cpp b/Source/LuckyWorldV2/Private/Episode/EpisodeSubSystem.cpp index b313574d..c5848e5a 100644 --- a/Source/LuckyWorldV2/Private/Episode/EpisodeSubSystem.cpp +++ b/Source/LuckyWorldV2/Private/Episode/EpisodeSubSystem.cpp @@ -7,6 +7,11 @@ #include "LuckyDataTransferSubsystem.h" #include "Components/TextRenderComponent.h" #include "Engine/TextRenderActor.h" +#include "Misc/FileHelper.h" +#include "Misc/Paths.h" +#include "Dom/JsonObject.h" +#include "Serialization/JsonWriter.h" +#include "Serialization/JsonSerializer.h" UEpisodeSubSystem::UEpisodeSubSystem() { @@ -16,12 +21,15 @@ UEpisodeSubSystem::UEpisodeSubSystem() void UEpisodeSubSystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); + if (ULuckyDataTransferSubsystem* DataTransferSubSystem = GetWorld()->GetSubsystem()) + { + DataTransfer = DataTransferSubSystem; + } } void UEpisodeSubSystem::Deinitialize() { - bTickEnabled = false; - FTSTicker::GetCoreTicker().RemoveTicker(TickHandle); + StopTicking(); Super::Deinitialize(); } @@ -45,16 +53,19 @@ void UEpisodeSubSystem::Tick(float DeltaTime) { const bool bIsEpisodeCompleted = CheckEpisodeCompletion(); + // Write Image on the disk + if (DataTransfer) DataTransfer->WriteImageToDisk(CurrentRobot->PhysicsSceneProxy->GetMujocoData().time); + EpisodeFrames++; + if (bIsEpisodeCompleted && CapturedEpisodes <= EpisodesToCapture) { - return StartEpisode(); + EndEpisode(); + StartEpisode(); + } + else + { + EndTraining(); } - - // Here shouldn't we rewrite the frames to know if the episode was a success or a failure? - - // Maybe this should not be done in the tick but after episode completion - const auto Payload = CreatePayload(); - SendEpisodeData(Payload); } } @@ -68,6 +79,12 @@ void UEpisodeSubSystem::StartTicking() TickHandle = FTSTicker::GetCoreTicker().AddTicker(TickDelegate); } +void UEpisodeSubSystem::StopTicking() +{ + bTickEnabled = false; + FTSTicker::GetCoreTicker().RemoveTicker(TickHandle); +} + void UEpisodeSubSystem::UpdateDebugTextActor() const { if (!IsValid(DebugTextActor)) return; @@ -77,7 +94,7 @@ void UEpisodeSubSystem::UpdateDebugTextActor() const TextRender->SetText(FText::FromString(Txt)); } -void UEpisodeSubSystem::StartNewEpisodesSeries(const int32 EpisodesCountIn, FString BaseImageDataPathIn) +void UEpisodeSubSystem::StartTraining(const int32 EpisodesCountIn, FString BaseImageDataPathIn, FString TaskDescriptionIn) { // Debug const auto DebugTextActorPtr = UGameplayStatics::GetActorOfClass(this->GetWorld(), ATextRenderActor::StaticClass()); @@ -97,10 +114,17 @@ void UEpisodeSubSystem::StartNewEpisodesSeries(const int32 EpisodesCountIn, FStr // Data ConfigureDataCapture(); BaseImageDataPath = BaseImageDataPathIn; + TaskDescription = TaskDescriptionIn; StartTicking(); } +void UEpisodeSubSystem::EndTraining() +{ + StopTicking(); + // Create jsonl files +} + void UEpisodeSubSystem::StartEpisode() { // Robot should be in its ready state - overriden per PilotComponent @@ -134,6 +158,24 @@ void UEpisodeSubSystem::StartEpisode() UpdateDebugTextActor(); } +void UEpisodeSubSystem::EndEpisode() +{ + // Gather the robot data + const FTrainingEpisodeData TrainingEpisodeData = CurrentRobot->RobotPilotComponent->GetTrainingEpisodeData(); + + // Create episodes_stats.jsonl single line and append to EpisodeStatLines + CreateEpisodeStatJsonLine(TrainingEpisodeData); + + // create a parquet file + CreateEpisodeParquetFile(); + + // Convert images into video + // TODO Find a good FFMPEG plugin - maybe the Unreal base one is good + + // Reset values for the next episode + EpisodeFrames = 0; +} + bool UEpisodeSubSystem::CheckEpisodeCompletion() { const auto GeomTransform = CurrentRobot->PhysicsSceneProxy->GetGeometryTransform(EpisodeTargetObject->MainActorBody.GetName()); @@ -201,64 +243,76 @@ void UEpisodeSubSystem::InitCameras() void UEpisodeSubSystem::ConfigureDataCapture() { - if (ULuckyDataTransferSubsystem* DataTransfer = GetWorld()->GetSubsystem()) + if (!DataTransfer) return; + DataTransfer->CreateCaptureSessionID(); + InitCameras(); + for (const auto& Cam : Cameras) { - //Do this before your tick operation - shouldn't happen on tick - //Connect to websocket and create session id - DataTransfer->ConnectToWebsocket("ws://127.0.0.1:3000", ""); - DataTransfer->CreateCaptureSessionID(); - - InitCameras(); - for (const auto& Cam : Cameras) - { - DataTransfer->RegisterSensor(Cam.Get()); - Cam->SensorInfo.bActive = true; - } + DataTransfer->RegisterSensor(Cam.Get()); + Cam->SensorInfo.bActive = true; } } -FObservationPayload UEpisodeSubSystem::CreatePayload() +void UEpisodeSubSystem::CreateEpisodeStatJsonLine(const FTrainingEpisodeData& TrainingEpisodeData) { - // CurrentRobot->Cameras - // CurrentRobot -> Tell JB what he should expose on the RobotPawn - // const auto TimeStamp = CurrentRobot->PhysicsSceneProxy->GetMujocoData().time; - // const auto So100PilotCmp = Cast(CurrentRobot->RobotPilotComponent); - // const auto Joints = So100PilotCmp->GetCurrentControlsFromPhysicScene(); + // EpisodeStatLines. + const TSharedPtr Root = MakeShared(); + Root->SetNumberField("episode_index", CapturedEpisodes); - // Tick operation - // Create the payload - return FObservationPayload { - // timestamp goes here - FString, - // "observation", //just leave this because this is what ethan and anuj will expect - // enter a message here - FString, - // TMap of FString (Actuator name or index), and Float (value of actuator) - // Camera info struct goes here, don't worry about this for now, just use TArray() - // What about episode success? -> can be stated after the result is known - // How to invalidate data + const TSharedPtr Stats = MakeShared(); + Stats->SetObjectField("action", MakeShared(TrainingEpisodeData.ControlsStats)); + Stats->SetObjectField("observation.state", MakeShared(TrainingEpisodeData.JointsStats)); - // Anuj -> How many frames do we need to store in a single parquet chunk - // Exact data structure with correct data types - }; -} - -void UEpisodeSubSystem::SendEpisodeData(const FObservationPayload& Payload) const -{ - // PayloadBuffer.Add(Payload) - // Every X frames -> Write parquet chunk + // TODO Once all json and parquet files are written on disk and the PR is merged into main and tested, we will do it + // TODO "observation.images.webcam" + // TODO "timestamp" + // TODO "frame_index" + // TODO "episode_index" + // TODO "index" + // TODO "task_index" - if (ULuckyDataTransferSubsystem* DataTransfer = GetWorld()->GetSubsystem()) - { - // Here generate the path for each image? - DataTransfer->WriteImageToDisk(CurrentRobot->PhysicsSceneProxy->GetMujocoData().time); - - // Don't send data if socket is disconnected - if (!DataTransfer->Socket->IsConnected()) return; - - // Send the Data - //Queue and convert the payload to json - DataTransfer->CreateJsonPayload_Observation(Payload); - - //Send the payload over websocket - DataTransfer->SendMessage(DataTransfer->ObservationPayloadString); - } + // Append + Root->SetObjectField("stats", Stats); + + // Serialize into FString + FString Output; + const TSharedRef> Writer = TJsonWriterFactory<>::Create(&Output); + FJsonSerializer::Serialize(Root.ToSharedRef(), Writer); + EpisodeStatLines.Add(Output); } + +void UEpisodeSubSystem::CreateEpisodeParquetFile() +{ + // TODO Use Anuj plugin to create one parquet file per episode +} + +void UEpisodeSubSystem::ConvertImagesToVideo() +{ + // TODO Once every json and parquet tasks are done +} + +void UEpisodeSubSystem::CreateEpisodesStatsJsonFile() +{ + // TODO Do not use FJsonObject - simply concat the FStrings into a file + + // Create a jsonl file and store in the correct directory + // concat TArray EpisodeStatLines into a single file + // https://huggingface.co/datasets/youliangtan/so100_strawberry_grape/blob/main/meta/episodes_stats.jsonl +} + +void UEpisodeSubSystem::CreateEpisodesJsonFile() +{ + // Create a jsonl file and store in the correct directory + // https://huggingface.co/datasets/youliangtan/so100_strawberry_grape/blob/main/meta/episodes.jsonl +} + +void UEpisodeSubSystem::CreateInfoJsonFile() +{ + // https://huggingface.co/datasets/youliangtan/so100_strawberry_grape/blob/main/meta/info.json +} + +void UEpisodeSubSystem::CreateTasksJsonFile() +{ + // https://huggingface.co/datasets/youliangtan/so100_strawberry_grape/blob/main/meta/tasks.jsonl +} + diff --git a/Source/LuckyWorldV2/Private/Robot/PilotComponent/RobotPilotComponent.cpp b/Source/LuckyWorldV2/Private/Robot/PilotComponent/RobotPilotComponent.cpp index 5d796682..b5a8ddb9 100644 --- a/Source/LuckyWorldV2/Private/Robot/PilotComponent/RobotPilotComponent.cpp +++ b/Source/LuckyWorldV2/Private/Robot/PilotComponent/RobotPilotComponent.cpp @@ -64,3 +64,19 @@ void URobotPilotComponent::SetRobotCurrentRewardZone(const FTransform& RewardTra void URobotPilotComponent::ReceiveRemoteCommand(const FRemoteControlPayload& RemoteRobotPayload) { } + +FJsonObject URobotPilotComponent::GetBufferedControlsData() +{ + return FJsonObject(); +} + +FJsonObject URobotPilotComponent::GetBufferedJointsData() +{ + return FJsonObject(); +} + +FTrainingEpisodeData URobotPilotComponent::GetTrainingEpisodeData() +{ + return FTrainingEpisodeData(); +} + diff --git a/Source/LuckyWorldV2/Private/Robot/PilotComponent/RobotPilotSO100Component.cpp b/Source/LuckyWorldV2/Private/Robot/PilotComponent/RobotPilotSO100Component.cpp index 38144155..b0c56686 100644 --- a/Source/LuckyWorldV2/Private/Robot/PilotComponent/RobotPilotSO100Component.cpp +++ b/Source/LuckyWorldV2/Private/Robot/PilotComponent/RobotPilotSO100Component.cpp @@ -24,10 +24,9 @@ void URobotPilotSO100Component::BeginPlay() void URobotPilotSO100Component::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { - // Super::TickComponent(DeltaTime, TickType, ThisTickFunction); - // const auto Joints = GetCurrentJointsFromPhysicsScene(); - // const auto Controls = GetCurrentControlsFromPhysicScene(); - // UE_LOG(LogTemp, Log, TEXT("Joint: %f - Control: %f - Delta: %f"), Joints.Jaw, Controls.Jaw, Controls.Jaw - Joints.Jaw); + // We can buffer data in the tick because it will never be called in parallel of MujocoVolumeActor tick + ControlsDataBuffer.Add(GetCurrentControlsFromPhysicScene()); + JointsDataBuffer.Add(GetCurrentJointsFromPhysicsScene()); } FTransform URobotPilotSO100Component::GetReachableTransform() @@ -44,8 +43,6 @@ FTransform URobotPilotSO100Component::GetReachableTransform() const float RandomYaw = MaxYaw * FMath::RandRange(0., 1.) * (FMath::RandBool() ? 1 : -1); const FQuat RandomRotation = FRotator{0,RandomYaw,0}.Quaternion() * ArmWorldRotation; - - // Compute Random Range within reach of the arm and add this to pivot location // Add a bit more than the Jaw Offset - TODO Offsets must be better computed const float RandomRange = JawOffset.X + MaxRange * FMath::RandRange(0.1f, 1.f); @@ -91,11 +88,179 @@ void URobotPilotSO100Component::ReceiveRemoteCommand(const FRemoteControlPayload { for (const auto& [ActuatorName, ActuatorValue] : RemoteRobotPayload.Commands) { - // Will print an error message if it doesn't exists + // Will print an error message if it doesn't exist RobotOwner->PhysicsSceneProxy->SetActuatorValue(ActuatorName, ActuatorValue); } } +FTrainingEpisodeData URobotPilotSO100Component::GetTrainingEpisodeData() +{ + const auto TrainingData = FTrainingEpisodeData + { + GetBufferedJointsData(), + GetBufferedControlsData(), + GetStats(ControlsDataBuffer), + GetStats(JointsDataBuffer) + }; + + ControlsDataBuffer.Empty(); + JointsDataBuffer.Empty(); + return TrainingData; +} + +FJsonObject URobotPilotSO100Component::GetBufferedControlsData() +{ + auto BufferedData = FJsonObject(); + TArray> ControlsArray; + + for (const auto& Control : ControlsDataBuffer) + { + TSharedPtr ControlJson = MakeShared(); + + ControlJson->SetNumberField(TEXT("Rotation"), Control.Rotation); + ControlJson->SetNumberField(TEXT("Pitch"), Control.Pitch); + ControlJson->SetNumberField(TEXT("Elbow"), Control.Elbow); + ControlJson->SetNumberField(TEXT("WristPitch"), Control.WristPitch); + ControlJson->SetNumberField(TEXT("WristRoll"), Control.WristRoll); + ControlJson->SetNumberField(TEXT("Jaw"), Control.Jaw); + + ControlsArray.Add(MakeShared(ControlJson)); + } + + BufferedData.SetArrayField(TEXT("action"), ControlsArray); + + return BufferedData; +} + +FJsonObject URobotPilotSO100Component::GetBufferedJointsData() +{ + auto BufferedData = FJsonObject(); + TArray> ControlsArray; + + for (const auto& Joint : JointsDataBuffer) + { + TSharedPtr ControlJson = MakeShared(); + + ControlJson->SetNumberField(TEXT("Rotation"), Joint.Rotation); + ControlJson->SetNumberField(TEXT("Pitch"), Joint.Pitch); + ControlJson->SetNumberField(TEXT("Elbow"), Joint.Elbow); + ControlJson->SetNumberField(TEXT("WristPitch"), Joint.WristPitch); + ControlJson->SetNumberField(TEXT("WristRoll"), Joint.WristRoll); + ControlJson->SetNumberField(TEXT("Jaw"), Joint.Jaw); + + ControlsArray.Add(MakeShared(ControlJson)); + } + + BufferedData.SetArrayField(TEXT("observation.state"), ControlsArray); + + return BufferedData; +} + +FJsonObject URobotPilotSO100Component::GetStats(const TArray& ActuatorStates) +{ + FSo100Actuators Min; + FSo100Actuators Max; + FSo100Actuators Sum = FSo100Actuators(); + FSo100Actuators Variance = FSo100Actuators(); + FSo100Actuators Mean; + FSo100Actuators StdDev; + + // Compute Sum + for (const auto& State : ActuatorStates) + { + Sum.Rotation += State.Rotation; + Sum.Pitch += State.Pitch; + Sum.Elbow += State.Elbow; + Sum.WristPitch += State.WristPitch; + Sum.WristRoll += State.WristRoll; + Sum.Jaw += State.Jaw; + + // Assign Max + if(State.Rotation > Max.Rotation) Max.Rotation = State.Rotation; + if(State.Pitch > Max.Pitch) Max.Pitch = State.Pitch; + if(State.Elbow > Max.Elbow) Max.Elbow = State.Elbow; + if(State.WristPitch > Max.WristPitch) Max.WristPitch = State.WristPitch; + if(State.WristRoll > Max.WristRoll) Max.WristRoll = State.WristRoll; + if(State.Jaw > Max.Jaw) Max.Jaw = State.Jaw; + + // Assing min + if(State.Rotation < Min.Rotation) Min.Rotation = State.Rotation; + if(State.Pitch < Min.Pitch) Min.Pitch = State.Pitch; + if(State.Elbow < Min.Elbow) Min.Elbow = State.Elbow; + if(State.WristPitch < Min.WristPitch) Min.WristPitch = State.WristPitch; + if(State.WristRoll < Min.WristRoll) Min.WristRoll = State.WristRoll; + if(State.Jaw < Min.Jaw) Min.Jaw = State.Jaw; + + } + + // Compute Mean + Mean.Rotation = Sum.Rotation / ActuatorStates.Num(); + Mean.Pitch = Sum.Pitch / ActuatorStates.Num(); + Mean.Elbow = Sum.Elbow / ActuatorStates.Num(); + Mean.WristPitch = Sum.WristPitch / ActuatorStates.Num(); + Mean.WristRoll = Sum.WristRoll / ActuatorStates.Num(); + Mean.Jaw = Sum.Jaw / ActuatorStates.Num(); + + // Compute Variance + // Pre step + for (const auto& State : ActuatorStates) + { + const double DiffRotation = State.Rotation - Mean.Rotation; + const double DiffPitch = State.Pitch - Mean.Pitch; + const double DiffElbow = State.Elbow - Mean.Elbow; + const double DiffWristPitch = State.WristPitch - Mean.WristPitch; + const double DiffWristRoll = State.WristRoll - Mean.WristRoll; + const double DiffJaw = State.Jaw - Mean.Jaw; + + Variance.Rotation += DiffRotation * DiffRotation; + Variance.Pitch += DiffPitch * DiffPitch; + Variance.Elbow += DiffElbow * DiffElbow; + Variance.WristPitch += DiffWristPitch * DiffWristPitch; + Variance.WristRoll += DiffWristRoll * DiffWristRoll; + Variance.Jaw += DiffJaw * DiffJaw; + } + + // Final Variance Value per actuator + Variance.Rotation /= ActuatorStates.Num(); + Variance.Pitch /= ActuatorStates.Num(); + Variance.Elbow /= ActuatorStates.Num(); + Variance.WristPitch /= ActuatorStates.Num(); + Variance.WristRoll /= ActuatorStates.Num(); + Variance.Jaw /= ActuatorStates.Num(); + + // Standard Deviation + StdDev.Rotation = FMath::Sqrt(Variance.Rotation); + StdDev.Pitch = FMath::Sqrt(Variance.Pitch); + StdDev.Elbow = FMath::Sqrt(Variance.Elbow); + StdDev.WristPitch = FMath::Sqrt(Variance.WristPitch); + StdDev.WristRoll = FMath::Sqrt(Variance.WristRoll); + StdDev.Jaw = FMath::Sqrt(Variance.Jaw); + + // Here create a json in the following format + FJsonObject StatsJson = FJsonObject(); + + // Helper lambda to convert FSo100Actuators to TArray> + auto ActuatorsToJsonArray = [](const FSo100Actuators& Actuators) -> TArray> + { + return { + MakeShared(Actuators.Rotation), + MakeShared(Actuators.Pitch), + MakeShared(Actuators.Elbow), + MakeShared(Actuators.WristPitch), + MakeShared(Actuators.WristRoll), + MakeShared(Actuators.Jaw) + }; + }; + + StatsJson.SetArrayField(TEXT("min"), ActuatorsToJsonArray(Min)); + StatsJson.SetArrayField(TEXT("max"), ActuatorsToJsonArray(Max)); + StatsJson.SetArrayField(TEXT("mean"), ActuatorsToJsonArray(Mean)); + StatsJson.SetArrayField(TEXT("std"), ActuatorsToJsonArray(StdDev)); + StatsJson.SetArrayField(TEXT("count"), { MakeShared(ActuatorStates.Num()) }); + + return StatsJson; +} + void URobotPilotSO100Component::PrintCurrentActuators() const { PrintActuators(GetCurrentControlsFromPhysicScene()); diff --git a/Source/LuckyWorldV2/Public/Episode/EpisodeSubSystem.h b/Source/LuckyWorldV2/Public/Episode/EpisodeSubSystem.h index 755de0b2..6f232aac 100644 --- a/Source/LuckyWorldV2/Public/Episode/EpisodeSubSystem.h +++ b/Source/LuckyWorldV2/Public/Episode/EpisodeSubSystem.h @@ -1,16 +1,34 @@ #pragma once #include "CoreMinimal.h" -#include "ObservationData.h" +#include "Robot/PilotComponent/RobotPilotComponent.h" #include "Subsystems/WorldSubsystem.h" #include "Stats/Stats.h" #include "EpisodeSubSystem.generated.h" +class ULuckyDataTransferSubsystem; class ALuckySensorPawnBase; class ATextRenderActor; class AMujocoStaticMeshActor; class ARobotPawn; +USTRUCT() +struct FTrainingEpisode +{ + GENERATED_BODY() + UPROPERTY() + int32 EpisodeIndex = -1; + + UPROPERTY() + TArray Tasks; + + UPROPERTY() + int32 Length = -1; +}; + + + + UCLASS() class LUCKYWORLDV2_API UEpisodeSubSystem : public UWorldSubsystem { @@ -30,6 +48,7 @@ public: // It will allows us to remove all the episode logic from the SubSystem and having different types of episodes void Tick(float DeltaTime); void StartTicking(); + void StopTicking(); FTSTicker::FDelegateHandle TickHandle; bool bTickEnabled = true; @@ -51,8 +70,11 @@ public: * Called by the UI when pressing the "Capture" button */ UFUNCTION(BlueprintCallable) - void StartNewEpisodesSeries(int32 EpisodesCountIn, FString BaseImageDataPathIn); + void StartTraining(int32 EpisodesCountIn, FString BaseImageDataPathIn, FString TaskDescriptionIn); + void EndTraining(); + UPROPERTY() + ULuckyDataTransferSubsystem* DataTransfer = nullptr; private: @@ -60,6 +82,7 @@ private: // ------- FLOW -------- // --------------------- void StartEpisode(); + void EndEpisode(); FTransform EpisodeRewardZone = FTransform::Identity; float EpisodeRewardZoneRadius = 5.f; // TODO Not hardcode it - or only in the Robot? - Maybe we want different scenarios for the robot bool CheckEpisodeCompletion(); @@ -92,23 +115,26 @@ private: void InitCameras(); TArray> Cameras; - - // -------------------- // ------- DATA ------- // -------------------- FString BaseImageDataPath; - + FString TaskDescription; + int32 EpisodeFrames = 0; // Noah here add anything you need void ConfigureDataCapture(); - FObservationPayload CreatePayload(); - - void SendEpisodeData(const FObservationPayload& Payload) const; - - - + // End of episode tasks + void CreateEpisodeStatJsonLine(const FTrainingEpisodeData& TrainingEpisodeData); + void CreateEpisodeParquetFile(); + void ConvertImagesToVideo(); + // End of training files + TArray EpisodeStatLines; + void CreateEpisodesStatsJsonFile(); + void CreateEpisodesJsonFile(); + void CreateInfoJsonFile(); + void CreateTasksJsonFile(); }; diff --git a/Source/LuckyWorldV2/Public/Robot/PilotComponent/RobotPilotComponent.h b/Source/LuckyWorldV2/Public/Robot/PilotComponent/RobotPilotComponent.h index b1b82568..f2108a4e 100644 --- a/Source/LuckyWorldV2/Public/Robot/PilotComponent/RobotPilotComponent.h +++ b/Source/LuckyWorldV2/Public/Robot/PilotComponent/RobotPilotComponent.h @@ -12,6 +12,17 @@ struct FRobotActuators // What will be in common? }; +USTRUCT(BlueprintType) +struct FTrainingEpisodeData +{ + GENERATED_BODY() + FJsonObject JointsStates; // The total series of joint values per frame + FJsonObject ControlsStates; // The total series of joint values per frame + FJsonObject JointsStats; // The min / max / mean / std for joints series + FJsonObject ControlsStats; // The min / max / mean / std for controls series +}; + + class ARobotPawn; UCLASS(Blueprintable) @@ -38,7 +49,13 @@ public: UFUNCTION() virtual void ReceiveRemoteCommand(const FRemoteControlPayload& RemoteRobotPayload); - + + // Data + virtual FJsonObject GetBufferedControlsData(); + virtual FJsonObject GetBufferedJointsData(); + virtual FTrainingEpisodeData GetTrainingEpisodeData(); + + protected: // Child class need access // Only to easy access within the component diff --git a/Source/LuckyWorldV2/Public/Robot/PilotComponent/RobotPilotSO100Component.h b/Source/LuckyWorldV2/Public/Robot/PilotComponent/RobotPilotSO100Component.h index 768f7e47..aa3daec1 100644 --- a/Source/LuckyWorldV2/Public/Robot/PilotComponent/RobotPilotSO100Component.h +++ b/Source/LuckyWorldV2/Public/Robot/PilotComponent/RobotPilotSO100Component.h @@ -46,6 +46,15 @@ public: virtual void SetRobotCurrentRewardZone(const FTransform& RewardTransformIn) override; virtual void ReceiveRemoteCommand(const FRemoteControlPayload& RemoteRobotPayload) override; + // ------------------ + // ------ DATA ------ + // ------------------ + virtual FTrainingEpisodeData GetTrainingEpisodeData() override; + virtual FJsonObject GetBufferedControlsData() override; + virtual FJsonObject GetBufferedJointsData() override; + TArray ControlsDataBuffer; + TArray JointsDataBuffer; + static FJsonObject GetStats(const TArray& ActuatorStates); private: FTransform TargetTransform; From 08d6570ea3b7bd22302ab5d8c9b4f98c18073534 Mon Sep 17 00:00:00 2001 From: JB Briant Date: Wed, 7 May 2025 20:04:41 +0700 Subject: [PATCH 6/6] FT - Write Jsonl file --- .../RobotPawnActors/BP_mujokoSO_100.uasset | Bin 1264920 -> 1264942 bytes .../Private/LuckyDataTransferSubsystem.cpp | 4 +-- .../Private/Episode/EpisodeSubSystem.cpp | 16 +++++++----- .../LuckyWorldV2/Private/_Utils/FileUtils.cpp | 24 ++++++++++++++++++ Source/LuckyWorldV2/Public/_Utils/FileUtils.h | 14 ++++++++++ 5 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 Source/LuckyWorldV2/Private/_Utils/FileUtils.cpp create mode 100644 Source/LuckyWorldV2/Public/_Utils/FileUtils.h diff --git a/Content/Blueprint/RobotPawnActors/BP_mujokoSO_100.uasset b/Content/Blueprint/RobotPawnActors/BP_mujokoSO_100.uasset index 87a704d663d0761961a2300d17e3daf9755e0329..4d8a6bcb136c8c45691a441a288f802689d2b10e 100644 GIT binary patch delta 10031 zcmb`Nd3;mVw#U1BPMXt!Hi1%53T?0qg+d!zIzXWfLqVnz1(e~^20<<|oJIyg#wc0_ zv4kxw0kOmpF9Hn+fy-E#M1&Fs0U6qWAj4INI3YvdZ|C%0`uO=ZA6d2PXg0; z3oTvwJMSm4dc6F7l^x^eQk6~OF31ROT&*%YcR+r|{g7{Pn@eTs+zaX9mJd|cfx98M za?^(@Gjk{8B+fokr5|0PTwjGtKF*kmyvc22^nZf;#Te)owY?VE-#iKt`FGgZ2=saEwbs2<5p|4^k%TWVFG z{6`WS#@SkxRd73GckWwTQ19PCn5bw$IJ2Wxmv=r-V!e6!=c-h7pr(4>hBW@xx@h(% zH?JGY@1vB&jq6o5mOCKFa6jZOZrh-;2arQ~Qdu<1=9V&*?dERC#oXi;9ylS#a<)-r zI&Oz7aBkV9vR_ah$|H71vs7-{E%MC?8Nt~e z6;ZT9F5o`MdE8pAvL(D6(#y?Xs?5M$konxWS7mQ=2jmg%hm7F1eFgu?(;GE;`T_UW zs+~|rTe!ElWxp!@xwdAaJs&3U`}-55Yh|@6#vMpt-*V>xmF?r~U|{BuN4XEOm|MS6 z*+O0p*^QeIiCDM}snY#xngLQGStc*~+Q4$S<7<`8=YGf!xb3jYUf^EHKHTz+%8c9% z`7JjcQKdhO;o3qrJe^*in%Q_M=$5#x;#fMNd>&D^wQE%OPFdd`y+Dw|mrg ziPylUaHCgc9l65`pK@y2{y3h=`yV&JVcT()HRWE&m$>DG%96PovIRGtRHX|e9+~2| zPa3d-?35~99b3!Fu~P=tllxAojB)F?Dm%c-A@_3gX%Q?J&cpd#VL}#2&YJPRf6=(#m8$uN3H+b#WuJdxp1>F2ERf;OAHR6v39Noqr z#S!g*^m9MtMsE8_Wh=NBav`_eQrXMg4Oz%dx5cPVNC#(rmEGia$h+JJna8a^2j&)# z<~u6;mAfE6=Eh%CHiA1KD&kTF1OxQC0$${EyiLU;{P6_n z7??y30YCW#tYnjbMDhy!L>3wFkXyh_CLJ(|oC33mg#gcy9grfnJsOd6^%(L9S@me& zMdboSW(8{O&?B{Fq0Ar!fAgqu$D6H~I(g?1Xb1+J5+9uPxLffK}{fq7&X=tMq&pUGMuI85aN zMP!ZvUL%(VV*`9@Mh<}{>8E~B( z0$Jo27)-Y2fQ`HY7s%2AC?>bSAu_cD_LEa!5V1s{FWCi#kWXMXSz7@gQMte&GPed! zkxRfw#w6fJatJISKOjZD8x>e+n%4%)I*+_<(6)puZGrd5EwGDB$v_o31)e090wj=K zAdh?ke}KxsSq z1**x`5g1Bdfl{($1DDAy(1J{zfU)Eh*g&i^aEI&y8RQd~PSzY?H$NZ;@YM5ZRssT98-Z5?P)HJ|VY2CYc5Uh2#`yN^A)18={QbItJ_?8v^^s z$%huUhpa<^*Qi_or`Ir`9k~RM-+u!xkwf4Z`307cZ8$K8yns}{Lv6#W3t;#(xeK7M zf=q?LJaP&QCT0U#kzL>(`2=>6bp%jC@I&OnP{0$TQv zX#%?LB&WbMf>7DhWEa3OBhZhmMZhsC7wAD|J1~-50+Y%3GO&~!0$a#0aFc8kFp3 zj3x6_;P2!T=u5_Fz!Y)_V9N`%B-^V%Px1=b$ub?oL=LPOMvK7=43kHu8BlnUoB}Tp zn+Z%JyTD=c3EU*>EMOj$3zU+1Ht-F(1d_=(2Z$kuz;W^mc*y1e8j%-}g67u_#p>5E z=v{KZ28E_%dL8IOPJtX^bDlx_$-D^YPcDI0WPBU=CpiS#kY8Xb*-C)5zVu2au5sryBSm$%`9k5P>5Eu>}zCw zo$r}ayzU_LtUDOI^$IOoCuP#ab<&emxlT%<{i2k(Udok>K_Rrd5(&9&y_CZ0(v9_! zo7JQEgOY`!_6I9;WrOq*i>Cf%(sQgn?Kv+c)9ErP7yhEE8EtqHI^Eq;Th@Te4oGdt z<(9h8L{yM*qtt~xp_y8`QJR2PLCOVmPu(QtlC()`PH|qQkZY3^jSJz}CCNmmH%T$9 zF&WlNPf*E3SdRKas*7zHaOXj@;I`!aLVBLXX@;KI3_s#EKW1-+`z9*eEcHf`2K00G zVy5G_NWIuonrm0Kz@Y?9zo0Fw5A;Y0xYz=If9gSFbFF9VtsJR=pMi|^C#Y(LZ(m4%+o6)UjQRUhurDKQ3Uq@_;X9h>o z!ZO?;&!Y3BpeaG)gC@g7k~UPqKQUAr&4s9_;AqO(D#cR0?NU!X(F4xR-wusr%^Cl8 zsX6vQAXZV;2xQg{1k$Wk6dcCT>JDhM)7EVN4#X-|vy!?KR?=w7PH8qaDhwx6vt4LT z*P8h*G-qgKX*bH5noBe9VUUvD(o{S{1KzaXgT@Y;H`N9V`0*a8CF`g)-V=@4T6t_a z%AK_GIZ^Jcm3x1Qa*no`JHLbnx&K+Y1pOl216qs`5JHcVJVV@L+ z`z?@_*Z09dcdgTN`_UO@!B9UNtJi&vdaG8REXvPl zc}Pu^BH{0- zqtcT&R}lg6rRVlhX)^npHt*RL$co`wd3OaiK!MgR^qAC#E~AmucQL~^j$tM?&H9~V zus%ZTlJ74l>$CUobtG!74j!ZT7n6`a9&nZE=Y-ZN5De9I1;eMiotqI#`lptuNZ?3}YW1*dA( zke%2oXQc?dQ35C1&a*h#Ue%IB`VI%+iL;nUWkaahzr!x?`W-%&ERrH<_jj1wOl^dc zlh|^t&%w)CS`3`$Fq7HZ7(bqqX3>dr@C-3%N$bwzsC0-C#2XYRt;$}f)fXfWzBvUl z`i+a|`-Wz?;57{V>qRLZFZsaqlPhsR7i-(EvQiq3w?p8t9$$q8oL|#PBv#2zDTLVP z!O?go26{&Mq&$492$WTy)RPAK;F8xTb;CO`5T^=nuz`AC!s@?Gg_q!0iHJ!CTdXw) zZh`ifrD)P$MnYWp1*6QqjL8?V1g=kEs-d8dAm9=_qyNaIgi8&W4LU9@Oo3e6dkwkvL9L-fuS+p>4qYgD z1wzpGIu5&!w9cPim%2zF<6bE|h^sp522%JFns7tn>{HD^>EK|cIQgc8Um3qT{Dydv zZ|bkjrq=(`_rZx>HCIWcaY{cbouE|JZb_V=OrZ;7ZNV3Q4qba$rY@kk4m6lX83;wTqS&60x!*qQpSue*>!78~I z-F;b!q{RJ70-0Baw4$6P`e&q6_*(U*E?{W{?#rrYFcht}=^9YU z6u2;b3OouwEKdm3r?ikWDDIb#;dJY)E{5V3DZ~Hc#hW$rDtr~ud401^iw=!V(nZiy zH{~QmML&d+--TCEuPF`5F3YWOAH&B6qx z$srNcV+KmS6ZK*A%4L)~9?^X%48_p28HiHB_?n>MzAyYlnMn(0!p<0T$O78GS21W7 z5vFJgpCx8ATX~-DoYi5~MZhrebgHh<$NV?f4DbAJt_9Y_OhvNXn)2r;a}c1voI*e^ z6?!4{=~l54OXrH=z~KY(NGg3(SwP7dhBll96nFXZ>G|e4lTs|FU07YQ0c460G7IJk-q(ZU!+~6n}-+(ZA83?l$n2}^%MQR zP-*E|bVWYS=vD`;Sm@hIlBZy?(uGmhVg)yUqWlcy*VpZ$f`3VoB4BlCXgs1;Q5T*k zuhLDWl}@dTfd%$O8j`7SrqY^LZh@0+ViYZv)9CgvIC<*{Ws*puo>b8=Bs!2`a~dhV zDW|=DWUcTbW7DW=zB2QF3Nhs@SK5(fm)yLz_hXkUNmQ^|nIpVSqY|Cmly=*7*|cb^ z4qIWJ5<)9iVpbbQ=>~YJlR}1QF1N?_IQELt-IM>Fu5K_jS_9J;-p2_rxUFsmRhP;e zJxJokQeb}VF~5`{`b4Q9t@j9&ji*W43F@@koGet~!f#_YRB znd>RtEO(Gda>I7_b?~92RPOIt>&EvvN_8t!J(Z>MkeWrv-wsDJHYv?Lm`i7d1&rUT zw4toc=$5<`Y3v`SPpcKm{IfWi3brcW(BUoURBJf{mRfHUmKG}p&AbPd*^KV&geN<9 zXrA=*EZ?Q{lJF+R=f?tA`K6K@*t%3%uC$_xDa!LWTmNe3=FiZF(2>{lt?22!$}G>J z{Yo?=qZ21n+}Ai*`~`Ag+Wi7?Ob^QP%ex`VQ21KT3xWA&gJ zGHFk?t|Jv(z#;0pFCC?6H*pFMy{H@~ZT^Tw6%}9$VW{h}Et>qK}r+f>LPDtGX6cau=3xNtV&yewM;K z`B#6O<9Nrd*W}S$a}+rSU-hc)OBG_avDD@UGGf$4i=y|Bqpq5IAtCB3oWeaS!mUsEm+PC^MFNShM`Qm~Y3sxFyy`%ZSchs7OEH46KZmJ9u&mL%NCcE)CD1R8LVg%+uxd$l&~^ z^$js3ZqeuD#7|4Q>ybMTLLLTOYv)z9sGcQ(s6MqC(5)$L{)mk0Bv^w>6z^;sTr287IU}m7IXVlf+w#QTj$ilZzO)BJk~jei24c1Cx4$3S615ZL)$w~ z&)r795N7#!oyW1%kftQuKc3{T*I-hwId8xC{p6oMPhSqz$LsKyL&HXD+t=v!WS1KB zD-!M+43i(GjL4(;RnzMFr?lOTPLJ1VoeDM^+@7yH7=KGixM}G4;qcr!eO52;Z27X$ z{y)A!r^oArQ!g-2kMYL!VF^QiwBLP?&*@dR#Zi=;dUR$5Iy_#djZ#&KQD#gt=0qmM zezqa{bV2)WyFJ4D elU^?g|1GRuLyF6-7qO*dy<*w3w|l*T`u_qPj5#3y delta 10243 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 = EImageFormat::PNG; + ImageTask->Format = EImageFormat::JPEG; ImageTask->Filename = Filename; ImageTask->PixelData = MakeUnique>(FIntPoint(Sensor->RenderTarget->GetSurfaceWidth(), Sensor->RenderTarget->GetSurfaceHeight()), TArray64(OutPixels)); ImageTask->bOverwriteFile = true; diff --git a/Source/LuckyWorldV2/Private/Episode/EpisodeSubSystem.cpp b/Source/LuckyWorldV2/Private/Episode/EpisodeSubSystem.cpp index c5848e5a..41971e10 100644 --- a/Source/LuckyWorldV2/Private/Episode/EpisodeSubSystem.cpp +++ b/Source/LuckyWorldV2/Private/Episode/EpisodeSubSystem.cpp @@ -12,6 +12,7 @@ #include "Dom/JsonObject.h" #include "Serialization/JsonWriter.h" #include "Serialization/JsonSerializer.h" +#include "_Utils/FileUtils.h" UEpisodeSubSystem::UEpisodeSubSystem() { @@ -57,9 +58,11 @@ void UEpisodeSubSystem::Tick(float DeltaTime) if (DataTransfer) DataTransfer->WriteImageToDisk(CurrentRobot->PhysicsSceneProxy->GetMujocoData().time); EpisodeFrames++; - if (bIsEpisodeCompleted && CapturedEpisodes <= EpisodesToCapture) + if (!bIsEpisodeCompleted) return; + + EndEpisode(); + if (CapturedEpisodes < EpisodesToCapture) { - EndEpisode(); StartEpisode(); } else @@ -122,6 +125,7 @@ void UEpisodeSubSystem::StartTraining(const int32 EpisodesCountIn, FString BaseI void UEpisodeSubSystem::EndTraining() { StopTicking(); + CreateEpisodesStatsJsonFile(); // Create jsonl files } @@ -138,9 +142,6 @@ void UEpisodeSubSystem::StartEpisode() RobotTransform.GetLocation() + RobotTransform.GetRotation().GetForwardVector() * HardCodedRewardDistanceFromRobotPivot * (FMath::RandBool() ? 1 : -1) }; - // DrawDebugLine(this->GetWorld(), EpisodeRewardZone.GetLocation() + FVector::UpVector * 70, EpisodeRewardZone.GetLocation(), FColor::Red, true); - // DrawDebugLine(this->GetWorld(), RobotTransform.GetLocation() + FVector::UpVector * 70, RobotTransform.GetLocation(), FColor::Blue, true); - // Ask the bot to give a reachable location for the Training Object Transform EpisodeObjectBaseTransform = CurrentRobot->RobotPilotComponent->GetReachableTransform(); @@ -276,7 +277,7 @@ void UEpisodeSubSystem::CreateEpisodeStatJsonLine(const FTrainingEpisodeData& Tr // Serialize into FString FString Output; - const TSharedRef> Writer = TJsonWriterFactory<>::Create(&Output); + const TSharedRef< TJsonWriter< TCHAR, TCondensedJsonPrintPolicy > > Writer = TJsonWriterFactory< TCHAR, TCondensedJsonPrintPolicy >::Create(&Output); FJsonSerializer::Serialize(Root.ToSharedRef(), Writer); EpisodeStatLines.Add(Output); } @@ -294,7 +295,8 @@ void UEpisodeSubSystem::ConvertImagesToVideo() void UEpisodeSubSystem::CreateEpisodesStatsJsonFile() { // TODO Do not use FJsonObject - simply concat the FStrings into a file - + UFileUtils::WriteJsonlFile(EpisodeStatLines, FPaths::ProjectSavedDir(), FString("episodes_stats")); + // Create a jsonl file and store in the correct directory // concat TArray EpisodeStatLines into a single file // https://huggingface.co/datasets/youliangtan/so100_strawberry_grape/blob/main/meta/episodes_stats.jsonl diff --git a/Source/LuckyWorldV2/Private/_Utils/FileUtils.cpp b/Source/LuckyWorldV2/Private/_Utils/FileUtils.cpp new file mode 100644 index 00000000..d4bb9b0b --- /dev/null +++ b/Source/LuckyWorldV2/Private/_Utils/FileUtils.cpp @@ -0,0 +1,24 @@ +#include "_Utils/FileUtils.h" +#include "Misc/FileHelper.h" +#include "Misc/Paths.h" + +UFileUtils::UFileUtils() +{ +} + + + +bool UFileUtils::WriteJsonlFile(const TArray& JsonLines, const FString& BasePath, const FString& FileName) +{ + // Ensure the directory exists + IFileManager::Get().MakeDirectory(*BasePath, true); + + // Construct the full file path + const FString FullFilePath = FPaths::Combine(BasePath, FileName + TEXT(".jsonl")); + + // Join the array into one string with line breaks + const FString FileContent = FString::Join(JsonLines, TEXT("")); + + // Write to file + return FFileHelper::SaveStringToFile(FileContent, *FullFilePath); +} \ No newline at end of file diff --git a/Source/LuckyWorldV2/Public/_Utils/FileUtils.h b/Source/LuckyWorldV2/Public/_Utils/FileUtils.h new file mode 100644 index 00000000..e0a58af7 --- /dev/null +++ b/Source/LuckyWorldV2/Public/_Utils/FileUtils.h @@ -0,0 +1,14 @@ +#pragma once +#include "CoreMinimal.h" +#include "FileUtils.generated.h" + +UCLASS() +class LUCKYWORLDV2_API UFileUtils : public UObject +{ + GENERATED_BODY() + +public: + UFileUtils(); + + static bool WriteJsonlFile(const TArray& JsonLines, const FString& BasePath, const FString& FileName); +}; \ No newline at end of file