Skip to content

Instantly share code, notes, and snippets.

@julenka
Last active August 11, 2023 05:50
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save julenka/20ece5141e821cb6d0a9cb530d3dc96d to your computer and use it in GitHub Desktop.
Save julenka/20ece5141e821cb6d0a9cb530d3dc96d to your computer and use it in GitHub Desktop.
Log data to CSV on HoloLens from a Unity project. Writes to Pictures folder instead of applicationDataPath so that files can be accessed from File Explorer (File Explorer -> HoloLens -> Pictures -> YourFolder). Doesn't require using Device portal.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
#if WINDOWS_UWP
using Windows.Storage;
#endif
namespace holoutils
{
/// <summary>
/// Component that Logs data to a CSV.
/// Assumes header is fixed.
/// Copy and paste this logger to create your own CSV logger.
/// CSV Logger breaks data up into settions (starts when application starts) which are folders
/// and instances which are files
/// A session starts when the application starts, it ends when the session ends.
///
/// In Editor, writes to MyDocuments/SessionFolderRoot folder
/// On Device, saves data in the Pictures/SessionFolderRoot
///
/// How to use:
/// Find the csvlogger
/// if it has not started a CSV, create one.
/// every frame, log stuff
/// Flush data regularly
///
/// **Important: Requires the PicturesLibrary capability!**
/// </summary>
public class CSVLogger : MonoBehaviour
{
#region Constants to modify
private const string DataSuffix = "data";
private const string CSVHeader = "Timestamp,SessionID,RecordingID," +
"blah,blah,blah";
private const string SessionFolderRoot = "CSVLogger";
#endregion
#region private members
private string m_sessionPath;
private string m_filePath;
private string m_recordingId;
private string m_sessionId;
private StringBuilder m_csvData;
#endregion
#region public members
public string RecordingInstance => m_recordingId;
#endregion
// Use this for initialization
async void Start()
{
await MakeNewSession();
}
async Task MakeNewSession()
{
m_sessionId = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string rootPath = "";
#if WINDOWS_UWP
StorageFolder sessionParentFolder = await KnownFolders.PicturesLibrary
.CreateFolderAsync(SessionFolderRoot,
CreationCollisionOption.OpenIfExists);
rootPath = sessionParentFolder.Path;
#else
rootPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), SessionFolderRoot);
if (!Directory.Exists(rootPath)) Directory.CreateDirectory(rootPath);
#endif
m_sessionPath = Path.Combine(rootPath, m_sessionId);
Directory.CreateDirectory(m_sessionPath);
Debug.Log("CSVLogger logging data to " + m_sessionPath);
}
public void StartNewCSV()
{
m_recordingId = DateTime.Now.ToString("yyyyMMdd_HHmmssfff");
var filename = m_recordingId + "-" + DataSuffix + ".csv";
m_filePath = Path.Combine(m_sessionPath, filename);
if (m_csvData != null)
{
EndCSV();
}
m_csvData = new StringBuilder();
m_csvData.AppendLine(CSVHeader);
}
public void EndCSV()
{
if (m_csvData == null)
{
return;
}
using (var csvWriter = new StreamWriter(m_filePath, true))
{
csvWriter.Write(m_csvData.ToString());
}
m_recordingId = null;
m_csvData = null;
}
public void OnDestroy()
{
EndCSV();
}
public void AddRow(List<String> rowData)
{
AddRow(string.Join(",", rowData.ToArray()));
}
public void AddRow(string row)
{
m_csvData.AppendLine(row);
}
/// <summary>
/// Writes all current data to current file
/// </summary>
public void FlushData()
{
using (var csvWriter = new StreamWriter(m_filePath, true))
{
csvWriter.Write(m_csvData.ToString());
}
m_csvData.Clear();
}
/// <summary>
/// Returns a row populated with common start data like
/// recording id, session id, timestamp
/// </summary>
/// <returns></returns>
public List<String> RowWithStartData()
{
List<String> rowData = new List<String>();
rowData.Add(Time.timeSinceLevelLoad.ToString("##.000"));
rowData.Add(m_recordingId);
rowData.Add(m_recordingId);
return rowData;
}
}
}
@mattycorbett
Copy link

Awesome, thank you! I'm trying to use this to log GazeProvider data, and Im calling this every frame (Update()). Seems like theres an issue with the threading though. The folder gets made but no .csv. SOmetimes the folder from a previous run gets made when I run the program the second time, which is why I think its a threading issue. Thoughts?

@julenka
Copy link
Author

julenka commented Sep 29, 2021

Hi, glad you found this!. Did you make sure to call FlushData() after calling AddRow()?

@mattycorbett
Copy link

I did! Thanks for replying. I managed to mitigate the issue by flushing and writing only once per second as opposed to once per frame. It seems the size of the buffer doesn't matter, but no matter how small the buffer, the flush causes a hiccup. Thanks for writing this code!

@Ramanpreet1992
Copy link

Ramanpreet1992 commented Oct 20, 2021

Hi,
This code has been so helpful. But this code is not creating a file in the folder.Would be glad if you could help.

@yvngac
Copy link

yvngac commented Jan 17, 2022

I tried using it and the folder isn't creating in the pictures file on the HoloLens. Is there something I'm missing as I didn't make any changes?

@julenka
Copy link
Author

julenka commented Jan 20, 2022

Hi, I was able to recently test this code. Make sure that you are 1) flushing data regularly (once per second is good), and then 2) that you have enabled the PicturesLibrary capability when deploying to HoloLens. See this for more info: https://docs.microsoft.com/en-us/windows/mixed-reality/develop/unity/Configure-Unity-Project

@hanweiss
Copy link

Hi, thank you for providing this code! I am having a similar issue to those above. I enabled the PicturesLibrary capability in Unity settings and I doubled checked the capabilities in the package.appxmanifest. The session folder gets created in the PictureLibarary, however the file fails to generate. I regularly flushed the data within Update() and I also attempted sampling once per second with InvokeRepeating. Any thoughts?

@Ramanpreet1992
Copy link

I have modified the code to dump the data into the Hololens Device portal. It's working now for me.

@julenka
Copy link
Author

julenka commented Jan 27, 2022

Hi, make sure you are also calling MakeNewSession() to make the folder and StartNewCSV() to create a CSV. First verify that the CSV is being created when you are using the editor. The folder and file should go into my documents. If you have that and the PicturesLibrary capability set, then you should see a folder also being created in My Pictures.

@mattycorbett
Copy link

Hello, I was using your code for two projects without issues, but recently, the call to Directory.CreateDirectory(m_sessionPath) on line 74 stopped working very similar to the above. I updated the MakeNewSession code to m_sessionId = DateTime.Now.ToString("yyyyMMdd_HHmmss"); string rootPath = ""; m_sessionPath = Path.Combine(SessionFolderRoot, m_sessionId); #if WINDOWS_UWP StorageFolder sessionParentFolder = await KnownFolders.PicturesLibrary .CreateFolderAsync(m_sessionPath, CreationCollisionOption.OpenIfExists); rootPath = sessionParentFolder.Path; UnityEngine.Debug.Log("Logger logging data to " + sessionParentFolder.Path); #endif and it now seems to work. Maybe something changed in the .NET namespace?

@Lilaxc
Copy link

Lilaxc commented Mar 3, 2023

Hello, thank you for the code it works well in unity but the data is quite delayed on the Hololens. At first, I didn't include FlushData() in the code, .csv file was created properly in the unity editor but it was not created on the Hololens. But right after I put FlushData(), I can see the file created in Hololens's picture folder but it makes the game and sensor data delayed more than I expected which is around 30sec from the normal 4sec (before adding flush). Do you have any suggestions or recommendations?

@kukalbriiwa7
Copy link

Thank you for the code. I have just one question, where should I call the StartNewCSV() and FlushData() functions? The code that you provided has a async void Start() function which does not allow me to have my basic void start(){}. I'm guessing I should use the FlushData() in the Update() function. right?

@stonycat
Copy link

stonycat commented Aug 11, 2023

I think StartNewCSV() here is to create a CSV file with the unity editor. If you want to create a CSV file in the Hololens, you need to use the Windows.Storage for UWP. Referring to https://learn.microsoft.com/en-us/windows/uwp/files/quickstart-reading-and-writing-files

For more file-accessing codes for UWP, I found this helpful:
https://github.com/Microsoft/Windows-universal-samples/tree/main/Samples/FileAccess

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment