Skip to content
Snippets Groups Projects
Select Git revision
  • 6ded2f7ed3561e1e6536587077f3eef70d70a999
  • 5.5 default
  • feat/ProximityGazing
  • 5.4
  • 5.3
  • ViveProEye
  • deprecated/4.26
  • v1.2
  • v1.1
  • v1.0
10 results

SFParticipant.cpp

  • Malte Kögel's avatar
    Malte Christian Kögel authored
    Added quick fix for header files breaking GenerateTestStudyRuns. GenerateTestStudyRuns still does not work because of EyeTracking but if this is changed in the future, it should work with the Header writing as well. Possibly later: proper fix by moving WriteHeaderRows call somewhere else.
    6ded2f7e
    History
    SFParticipant.cpp 10.70 KiB
    // Fill out your copyright notice in the Description page of Project Settings.
    
    
    #include "SFParticipant.h"
    
    
    #include "IUniversalLogging.h"
    #include "SFGameInstance.h"
    #include "Help/SFUtils.h"
    #include "Logging/SFLoggingBPLibrary.h"
    #include "Logging/SFLoggingUtils.h"
    
    USFParticipant::USFParticipant()
    {
    }
    
    USFParticipant::~USFParticipant()
    {
    }
    
    bool USFParticipant::Initialize(int Participant)
    {
    	ParticipantID = Participant;
    
    
    	const FString Timestamp = FDateTime::Now().ToString();
    	const FString Filename = "LogParticipant-" + FString::FromInt(ParticipantID) + "_" + Timestamp + ".txt";
    	ILogStream* ParticipantLog = UniLog.NewLogStream("ParticipantLog", "StudyFramework/StudyLogs/ParticipantLogs",
    	                                                 Filename, false);
    	ILogStream* PositionLog = UniLog.NewLogStream("PositionLog", "StudyFramework/StudyLogs/PositionLogs",
    		"Position"+Filename, false);
    	if(USFGameInstance::Get()->GetStudySetup()->UseGazeTracker != EGazeTrackerMode::NotTracking)
    	{
    		ILogStream* GazeTrackingLog = UniLog.NewLogStream("GazeTrackingLog", "StudyFramework/StudyLogs/GazeTrackingLogs",
    			"GazeTracking" + Filename, false);
    	}
    	StartTime = FPlatformTime::Seconds();
    	if (USFGameInstance::Get())
    	{
    		USFGameInstance::Get()->GetLogObject()->WritePositionLogHeaderRow();
    		USFGameInstance::Get()->GetLogObject()->WriteGazeTrackingLogHeaderRow();
    	}
    	else
    	{
    		FSFLoggingUtils::Log("GameInstance not set up yet, no header rows are written to participant logs.");
    	}
    	return true;
    }
    
    void USFParticipant::SetStudyConditions(TArray<USFCondition*> NewConditions)
    {
    	Conditions = NewConditions;
    
    	// Create initial Json file
    	GenerateExecutionJsonFile();
    }
    
    void USFParticipant::GenerateExecutionJsonFile() const
    {
    	TSharedPtr<FJsonObject> Json = MakeShared<FJsonObject>();
    
    	Json->SetNumberField("ParticipantID", ParticipantID);
    
    	TArray<TSharedPtr<FJsonValue>> ConditionsArray;
    	for (auto Condition : Conditions)
    	{
    		TSharedRef<FJsonValueObject> JsonValue = MakeShared<FJsonValueObject>(Condition->GetAsJson());
    		ConditionsArray.Add(JsonValue);
    	}
    	Json->SetArrayField("Conditions", ConditionsArray);
    
    	FSFUtils::WriteJsonToFile(Json, "StudyRuns/Participant_" + FString::FromInt(ParticipantID) + ".txt");
    }
    
    TArray<USFCondition*> USFParticipant::ReadExecutionJsonFile(int ParticipantID)
    {
    	if(ParticipantID==-1)
    	{
    		FSFLoggingUtils::Log(
    			"[USFParticipant::ReadExecutionJsonFile] participant json file for participant " +
    			FString::FromInt(ParticipantID) + " is not to be read, probably called on init so everything is fine!", false);
    		return {};
    	}
    	TSharedPtr<FJsonObject> Json = FSFUtils::ReadJsonFromFile(
    		"StudyRuns/Participant_" + FString::FromInt(ParticipantID) + ".txt");
    	TArray<USFCondition*> LoadedConditions;
    	if (Json == nullptr)
    	{
    		FSFLoggingUtils::Log(
    			"[USFParticipant::ReadExecutionJsonFile] participant json file for participant " +
    			FString::FromInt(ParticipantID) + " cannot be read!", true);
    		return {};
    	}
    
    	TArray<TSharedPtr<FJsonValue>> ConditionsArray = Json->GetArrayField("Conditions");
    	for (TSharedPtr<FJsonValue> ConditionJson : ConditionsArray)
    	{
    		USFCondition* Condition = NewObject<USFCondition>();
    		Condition->FromJson(ConditionJson->AsObject());
    		LoadedConditions.Add(Condition);
    	}
    	return LoadedConditions;
    }
    
    FString USFParticipant::GetCurrentTime() const
    {
    	const double CurrTime = FPlatformTime::Seconds() - StartTime;
    	return FString::Printf(TEXT("%.3f"), CurrTime);
    }
    
    void USFParticipant::StoreInPhaseLongTable() const
    {
    	USFCondition* CurrCondition = GetCurrentCondition();
    
    	FString Filename = FPaths::ProjectDir() + "StudyFramework/StudyLogs/Phase_" + CurrCondition->PhaseName + ".csv";
    
    	if (!FPaths::FileExists(Filename))
    	{
    		FString Header = "ParticipantID";
    		Header += ",Map";
    		for (auto Factor : CurrCondition->FactorLevels)
    		{
    			Header += "," + Factor.Key;
    		}
    		for (auto Var : CurrCondition->DependentVariablesValues)
    		{
    			Header += "," + Var.Key->Name;
    		}
    		Header += ",Time\n";
    		FFileHelper::SaveStringToFile(*Header, *Filename);
    	}
    
    	FString ConditionResults = FString::FromInt(ParticipantID);
    	ConditionResults += "," + CurrCondition->Map;
    	for (auto Factor : CurrCondition->FactorLevels)
    	{
    		ConditionResults += "," + Factor.Value;
    	}
    	for (auto Var : CurrCondition->DependentVariablesValues)
    	{
    		ConditionResults += "," + Var.Value;
    	}
    	ConditionResults += "," + FString::Printf(TEXT("%.2f"), CurrCondition->GetTimeTaken());
    	//append this
    	ConditionResults += "\n";
    	FFileHelper::SaveStringToFile(*ConditionResults, *Filename, FFileHelper::EEncodingOptions::AutoDetect,
    	                              &IFileManager::Get(), EFileWrite::FILEWRITE_Append);
    }
    
    bool USFParticipant::StartStudy()
    {
    	// Set first condition
    	CurrentConditionIdx = -1;
    
    	USFLoggingBPLibrary::LogComment("Start Study for ParticipantID: " + FString::FromInt(ParticipantID));
    
    	return true;
    }
    
    void USFParticipant::EndStudy()
    {
    	USFLoggingBPLibrary::LogComment("EndStudy");
    	LogCurrentParticipant();
    	StoreInPhaseLongTable();
    }
    
    USFCondition* USFParticipant::GetCurrentCondition() const
    {
    	if (CurrentConditionIdx >= 0 && CurrentConditionIdx < Conditions.Num())
    		return Conditions[CurrentConditionIdx];
    
    	return nullptr;
    }
    
    USFCondition* USFParticipant::GetNextCondition() const
    {
    	// Get next Condition
    	if (CurrentConditionIdx + 1 >= Conditions.Num())
    	{
    		FSFLoggingUtils::Log("[USFParticipant::NextCondition()]: All conditions already ran, no NextCondition", false);
    		return nullptr;
    	}
    	USFCondition* UpcomingCondition = Conditions[CurrentConditionIdx + 1];
    	return UpcomingCondition;
    }
    
    const TArray<USFCondition*> USFParticipant::GetAllConditions() const
    {
    	return Conditions;
    }
    
    int USFParticipant::GetCurrentConditionNumber() const
    {
    	return CurrentConditionIdx;
    }
    
    int USFParticipant::GetID() const
    {
    	return ParticipantID;
    }
    
    TArray<USFCondition*> USFParticipant::GetLastParticipantsConditions()
    {
    	return ReadExecutionJsonFile(GetLastParticipantId());
    }
    
    int USFParticipant::GetLastParticipantId()
    {
    	TSharedPtr<FJsonObject> ParticipantJson = FSFUtils::ReadJsonFromFile("StudyRuns/LastParticipant.txt");
    	if (ParticipantJson == nullptr)
    	{
    		//file does not exist or something else went wrong
    		return -1;
    	}
    	return ParticipantJson->GetNumberField("ParticipantID");
    }
    
    int USFParticipant::GetLastParticipantLastConditionStarted()
    {
    	TSharedPtr<FJsonObject> ParticipantJson = FSFUtils::ReadJsonFromFile("StudyRuns/LastParticipant.txt");
    	if (ParticipantJson == nullptr)
    	{
    		//file does not exist or something else went wrong
    		return -1;
    	}
    	return ParticipantJson->GetNumberField("CurrentConditionIdx");
    }
    
    bool USFParticipant::GetLastParticipantFinished()
    {
    	TSharedPtr<FJsonObject> ParticipantJson = FSFUtils::ReadJsonFromFile("StudyRuns/LastParticipant.txt");
    	if (ParticipantJson == nullptr)
    	{
    		//file does not exist or something else went wrong
    		return true;
    	}
    	return ParticipantJson->GetBoolField("Finished");
    }
    
    ASFStudySetup* USFParticipant::GetLastParticipantSetup()
    {
    	TSharedPtr<FJsonObject> ParticipantJson = FSFUtils::ReadJsonFromFile("StudyRuns/LastParticipant.txt");
    	if (ParticipantJson == nullptr || !ParticipantJson->HasField("StudySetup"))
    	{
    		//file does not exist or something else went wrong
    		return nullptr;
    	}
    	FString SetupFile = ParticipantJson->GetStringField("StudySetup");
    	ASFStudySetup* Setup = NewObject<ASFStudySetup>();
    	Setup->JsonFile = SetupFile;
    	Setup->LoadFromJson();
    
    	return Setup;
    }
    
    bool USFParticipant::LoadConditionsFromJson()
    {
    	if (ParticipantID == -1)
    	{
    		FSFLoggingUtils::Log("[USFParticipant::LoadConditionsFromJson] ParticipantID == -1, maybe nothing stored?", true);
    		return false;
    	}
    
    	Conditions = ReadExecutionJsonFile(ParticipantID);
    
    	if (Conditions.Num() == 0)
    	{
    		FSFLoggingUtils::Log(
    			"[USFParticipant::LoadContitionsFromJson] No Conditions could be loaded for Participant " +
    			FString::FromInt(ParticipantID), true);
    		return false;
    	}
    	return true;
    }
    
    void USFParticipant::RecoverStudyResultsOfFinishedConditions()
    {
    	//this is not the most effective way of recovering but the most trivial (long tables will be read multiple times)
    	for (USFCondition* Condition : Conditions)
    	{
    		const FString Filename = FPaths::ProjectDir() + "StudyFramework/StudyLogs/Phase_" + Condition->PhaseName + ".csv";
    		TArray<FString> Lines;
    		if (!FFileHelper::LoadANSITextFileToStrings(*Filename, nullptr, Lines))
    		{
    			FSFLoggingUtils::Log("[USFParticipant::RecoverStudyResultsOfFinishedConditions] Cannot read file: " + Filename, true);
    		}
    
    		TArray<FString> HeaderEntries;
    		if(Lines.Num()>0)
    		{
    			Lines[0].ParseIntoArray(HeaderEntries,TEXT(","), false);
    		}
    
    		for (int i=1; i<Lines.Num(); ++i)
    		{
    			TArray<FString> Entries;
    			Lines[i].ParseIntoArray(Entries,TEXT(","), false);
    
    			if(Entries.Num()>0 && FCString::Atoi(*Entries[0]) == ParticipantID)
    			{
    				Condition->RecoverStudyResults(HeaderEntries, Entries);
    			}
    		}
    	}
    }
    
    void USFParticipant::ClearPhaseLongtables(ASFStudySetup* StudySetup)
    {
    	for (int i = 0; i < StudySetup->GetNumberOfPhases(); ++i)
    	{
    		const FString PhaseName = StudySetup->GetPhase(i)->PhaseName;
    		const FString Filename = FPaths::ProjectDir() + "StudyFramework/StudyLogs/Phase_" + PhaseName + ".csv";
    		if (FPaths::FileExists(Filename))
    		{
    			IFileManager& FileManager = IFileManager::Get();
    			FileManager.Delete(*Filename);
    		}
    	}
    }
    
    bool USFParticipant::SetCondition(const USFCondition* NextCondition)
    {
    	if (!NextCondition)
    		return false;
    
    	if (GetCurrentCondition() && GetCurrentCondition()->WasStarted())
    	{
    		//we already ran a condition so store it
    		if (GetCurrentCondition()->IsFinished())
    		{
    			StoreInPhaseLongTable();
    		}
    		else
    		{
    			FSFLoggingUtils::Log(
    				"[USFParticipant::SetCondition] Not storing unfinished last condition, when going to next. Make sure all required dependent variables received data!",
    				true);
    		}
    	}
    
    	for (int i = 0; i < Conditions.Num(); ++i)
    	{
    		if (Conditions[i] == NextCondition)
    		{
    			CurrentConditionIdx = i;
    			LogCurrentParticipant();
    			return true;
    		}
    	}
    	FSFLoggingUtils::Log("[USFParticipant::SetCondition()]: Requested condition is not one of my conditions!", true);
    	return false;
    }
    
    void USFParticipant::LogCurrentParticipant() const
    {
    	TSharedPtr<FJsonObject> Json = MakeShared<FJsonObject>();
    
    	Json->SetNumberField("ParticipantID", ParticipantID);
    	bool bFinished = true;
    	for (USFCondition* Condition : Conditions)
    	{
    		bFinished = bFinished && (Condition->IsFinished() || !Condition->HasRequiredVariables());
    	}
    	Json->SetBoolField("Finished", bFinished);
    	Json->SetNumberField("CurrentConditionIdx", CurrentConditionIdx);
    	if (USFGameInstance::Get() && USFGameInstance::Get()->GetStudySetup())
    	{
    		Json->SetStringField("StudySetup", USFGameInstance::Get()->GetStudySetup()->JsonFile);
    	}
    	else
    	{
    		FSFLoggingUtils::Log("[USFParticipant::LogCurrentParticipant] StudySetup not accessible!", true);
    	}
    
    
    	FSFUtils::WriteJsonToFile(Json, "StudyRuns/LastParticipant.txt");
    }