Tracking system: Is it possible to submit more than 1 row at a time?
JAQuent opened this issue · 3 comments
I've been working on a tracking system for what is currently on the screen by shooting rays from the camera and that all works fine. My aim is to detect whether or not a number of rays are hitting objects and if they do what object they hit.
My code works fine if I only have one ray but it doesn't when I use multiple rays because want to submit the result for each ray as a new row.
I really want to avoid having one tracker for each ray or even a new colum for each ray so I am wondering whether something like this is possible?
The problem with having a colum for each ray is that I wouldn't know how to header etc. Could that be done in for loop?
When I provide more than one ray I get the following error arises form the for loop in GetCurrentValues()
:
InvalidOperationException: The row does not contain values for the same columns as the columns in the table!
Table: time, rayIndex, x, y, objectDetected
Row: rayIndex, x, y, objectDetected, rayIndex, x, y, objectDetected, time
UXF.UXFDataTable.AddCompleteRow (UXF.UXFDataRow newRow) (at Assets/UXF/Scripts/Etc/UXFDataTable.cs:109)
Here is my script:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UXF;
public class screenTracker : Tracker {
// Public vars
public Camera cam;
public List<float> x = new List<float>();
public List<float> y = new List<float>();
public int numRays;
public bool showDebugRays = true;
public string noObjectString = "NA";
// Private vars
public List<string> objectDetected = new List<string>();
// // Start calc
// void Start(){
// numRays = y.Count;
// }
// Set up header
protected override void SetupDescriptorAndHeader(){
measurementDescriptor = "objectsOnScreenTracker";
customHeader = new string[]
{
"rayIndex",
"x",
"y",
"objectDetected"
};
}
// Get values
protected override UXFDataRow GetCurrentValues(){
objectDetected = ray2detectObjects(x, y, cam);
var values = new UXFDataRow();
// Write rows in a loop
for(int i = 0; i < numRays; i++){
values.Add(("rayIndex", i));
values.Add(("x", x[i]));
values.Add(("y", y[i]));
values.Add(("objectDetected", objectDetected[i]));
}
return values;
}
// Function to detect objects on screen by rays
List<string> ray2detectObjects(List<float> x, List<float> y, Camera cam){
// Get number of rays
numRays = y.Count;
// Create var
List<string> nameOfObjects = new List<string>();
List<Ray> rays = new List<Ray>();
for (int i = 0; i < numRays; i++){
// Cast the ray and add to list
Ray ray = cam.ViewportPointToRay(new Vector3(x[i], y[i], 0));
rays.Add (ray);
if(showDebugRays){
Debug.DrawRay(rays[i].origin, rays[i].direction * 50, Color.red);
}
RaycastHit hit1;
if (Physics.Raycast(rays[i], out hit1)){
if(showDebugRays){
Debug.DrawRay(rays[i].origin, rays[i].direction * 50, Color.green);
}
print("I'm looking at " + hit1.transform.name + " with ray " + i);
nameOfObjects.Add(hit1.transform.name);
} else {
nameOfObjects.Add(noObjectString);
}
}
return nameOfObjects;
}
}
The way you are trying it will not work - you are only creating a single row, just adding duplicate items to that row.
There are a few ways to do what you want to do.
First, you need to set the Update Type
field in the inspector to Manual
. That way you can manually record multiple rows per frame.
I think the best way would to record multiple lines per frame is with a coroutine. I modified your script, now it calls RecordRow manually multiple times per frame. The for loop just stores a row each iteration, then GetCurrentValues just grabs that stored row. GetCurrentValues is called internally within RecordRow.
There may be a better way if I change UXF internally but this should work for now.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UXF;
public class screenTracker : Tracker {
// Public vars
public Camera cam;
public List<float> x = new List<float>();
public List<float> y = new List<float>();
public int numRays;
public bool showDebugRays = true;
public string noObjectString = "NA";
// Private vars
public List<string> objectDetected = new List<string>();
private UXFDataRow currentRow;
// Start calc
void Start(){
StartCoroutine(RecordRoutine());
}
IEnumerator RecordRoutine()
{
while (true)
{
if (recording)
{
objectDetected = ray2detectObjects(x, y, cam);
for(int i = 0; i < numRays; i++){
var values = new UXFDataRow();
values.Add(("rayIndex", i));
values.Add(("x", x[i]));
values.Add(("y", y[i]));
values.Add(("objectDetected", objectDetected[i]));
currentRow = values;
RecordRow(); // record for each ray
currentRow = null;
}
}
yield return null; // wait until next frame
}
}
// Set up header
protected override void SetupDescriptorAndHeader(){
measurementDescriptor = "objectsOnScreenTracker";
customHeader = new string[]
{
"rayIndex",
"x",
"y",
"objectDetected"
};
}
// Get values
protected override UXFDataRow GetCurrentValues(){
return currentRow;
}
// Function to detect objects on screen by rays
List<string> ray2detectObjects(List<float> x, List<float> y, Camera cam){
// Get number of rays
numRays = y.Count;
// Create var
List<string> nameOfObjects = new List<string>();
List<Ray> rays = new List<Ray>();
for (int i = 0; i < numRays; i++){
// Cast the ray and add to list
Ray ray = cam.ViewportPointToRay(new Vector3(x[i], y[i], 0));
rays.Add (ray);
if(showDebugRays){
Debug.DrawRay(rays[i].origin, rays[i].direction * 50, Color.red);
}
RaycastHit hit1;
if (Physics.Raycast(rays[i], out hit1)){
if(showDebugRays){
Debug.DrawRay(rays[i].origin, rays[i].direction * 50, Color.green);
}
print("I'm looking at " + hit1.transform.name + " with ray " + i);
nameOfObjects.Add(hit1.transform.name);
} else {
nameOfObjects.Add(noObjectString);
}
}
return nameOfObjects;
}
}
Unfortunately with the new version of UXF this is not wroking any more. I get the following messages:
I'd also love make this general & versatile enough to be used by other people. The basic idea is that you supply a bunch of ray coordiantes and during the trial each time the ray is hitting an object it is recorded. After my preliminary testing, I think this could be useful (e.g. for recording when an object was first on the screen, where it was etc.). If you think it worth developing further should I use this issue or open another so it can be developed as a feature?
Current Code
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UXF;
using System.Linq;
public class screenTracker : Tracker {
// Public vars
public Camera cam;
public List<float> x = new List<float>();
public List<float> y = new List<float>();
public int numRays;
public bool debugMode = true; // Prints log entry and shows debug rays
public string noObjectString = "NA";
public bool saveNoObject = false;
// Private vars
public List<string> objectDetected = new List<string>();
private UXFDataRow currentRow;
private bool recording = false;
// Start calc
void Start(){
// Read .txt file
string[] rayCoordinates = readText("input_data/rayCoordinates.txt");
// Parsing information for rays
int indexCount = 2; // because there are 7 variables with 1 column for each variable
int n = rayCoordinates.Length/indexCount;
// First var
int indexMin = 0;
x = parseFloatListFromString(indexMin, indexCount, rayCoordinates);
// Second var
indexMin = 1;
y = parseFloatListFromString(indexMin, indexCount, rayCoordinates);
// Start the recoding
StartCoroutine(RecordRoutine());
}
// Start and stop recoding functions to be added to UXF.rig Events (Start and End of trial)
void StartRecording(){
recording = true;
}
// See above
void StopRecording(){
recording = false;
}
IEnumerator RecordRoutine(){
while (true){
if (recording){
objectDetected = ray2detectObjects(x, y, cam);
for(int i = 0; i < numRays; i++){
// When no object was detected save only if saveNoObject is true
if(objectDetected[i] == noObjectString){
if(saveNoObject){
var values = new UXFDataRow();
values.Add(("rayIndex", i));
values.Add(("x", x[i]));
values.Add(("y", y[i]));
values.Add(("objectDetected", objectDetected[i]));
currentRow = values;
RecordRow(); // record for each ray
currentRow = null;
}
} else {
var values = new UXFDataRow();
values.Add(("rayIndex", i));
values.Add(("x", x[i]));
values.Add(("y", y[i]));
values.Add(("objectDetected", objectDetected[i]));
currentRow = values;
RecordRow(); // record for each ray
currentRow = null;
}
}
}
yield return null; // wait until next frame
}
}
// Set up header
protected override void SetupDescriptorAndHeader(){
measurementDescriptor = "objectsOnScreenTracker";
customHeader = new string[]{
"rayIndex",
"x",
"y",
"objectDetected"
};
}
// Get values
protected override UXFDataRow GetCurrentValues(){
return currentRow;
}
string[] readText(string fileName){
// Loads .txt file and split by \t and \n
string inputText = System.IO.File.ReadAllText(fileName);
string[] stringList = inputText.Split('\t', '\n'); //splits by tabs and lines
return stringList;
}
// Parsing informatiom from text file converting to list of floats
List<float> parseFloatListFromString(int indexMin, int indexCount, string[] stringList){
// Selects items, converts strings to floats and then creates a list for floats
int indexMax = stringList.Length - indexCount + indexMin;
var index = Enumerable.Repeat(indexMin, (int)((indexMax - indexMin) / indexCount) + 1).Select((tr, ti) => tr + (indexCount * ti)).ToList();
List<float> floatList = index.Select(x => float.Parse(stringList[x])).ToList();
return floatList;
}
// Function to detect objects on screen by rays
List<string> ray2detectObjects(List<float> x, List<float> y, Camera cam){
// Get number of rays
numRays = y.Count;
// Create var
List<string> nameOfObjects = new List<string>();
for (int i = 0; i < numRays; i++){
// Cast the ray and add to list
Ray ray = cam.ViewportPointToRay(new Vector3(x[i], y[i], 0));
// Display ray for debugging
if(debugMode){
Debug.DrawRay(ray.origin, ray.direction * 50, Color.red);
}
// Raycast and check if something is hit
RaycastHit hit1;
if (Physics.Raycast(ray, out hit1)){
if(debugMode){
Debug.DrawRay(ray.origin, ray.direction * 50, Color.green);
print("I'm looking at " + hit1.transform.name + " with ray " + i);
}
// Add name of GameObject that was hit
nameOfObjects.Add(hit1.transform.name);
} else {
// Add noObjectString becuase no object was hit by ray
nameOfObjects.Add(noObjectString);
}
}
return nameOfObjects;
}
}
Hi, yes I recently updated UXF Tracker implementation. I knew this would break things, but it was needed to fix a critical bug, and is better for the future. It should not be difficult to adapt the code. See the Custom Tracker section on the Wiki.
To develop as a general purpose feature it looks a little complex, and the coding style would need to be changed a lot to fit within UXF that is maintained. You can upload as a Gist and I can link this in the wiki. But as always feel free to make a pull request if you know how, but it may be a while before it is considered to be added to UXF (no time for active development at the moment)