Sunday, 9 February 2014

VIsualising Data in Maya - A Journey in Python

As anyone whose following this blog knows, we have a collection of files containing data that was captured at a performance of Verdi's 'Requiem', using various sensors connected to several participants. The purpose of capturing this data was so that we could use it to create something in 3D that derives from that performance.

The files in question contain Comma Separated Values, simply known as .csv files. Since Autodesk Maya cannot read these files and no obvious solution (script/plug-in) could be found on the internet, it would seem like an impossible task to be able to make use of them in any meaningful way, but it's not 'game over' just yet!

My brief was to use the Python programming language to bridge the gap between the files and Maya. I would need to find a way to read the files, interpret the data and then find ways to represent that data in Maya.

In this post I will be outlining my progress on the script, covering my research and thinking behind it, and finishing with an overview of how it works and how to use it.


The CSV File
The first thing to understand about the .csv file type is that there are no strict rules on the format of the file; only that it is written in plain text (so it is humanly readable if opened in a text editor), that each line is a record containing fields separated by a delimiter (usually a comma), and that each line contains the same sequence of fields. While there are no requirements to label the fields, it is also common to use the first line to contain headings for each field. In our case all the .csv files we have use headings on the first line. Here is one of our examples:


What the Script Does
The script does several things:

The first function of the script is specifically for reading the data from any given file.
getCsvData("Path/to/a/file.csv")

This takes a file path, reads a file and it then returns a Python object containing the data in a format that can easily be used  by other Python functions. It does this by reading the file line by line, using the commas to split each line of text into their individual values.

It assumes the first line contains the headings, so it will use that information to create a Python Dictionary Object, using each heading to contain a list for its values. As the script continues to read each line, the values are appended to the corresponding list in the dictionary. It is this dictionary object that is then returned by this function. Here is an example of what a Python dictionary looks like:
{'timestamp':[], 'x':[], 'y':[], 'z':[], 'label':[]}
Each dictionary entry is a key:value pair. 'timestamp' being the key to access a Python List (the square brackets). The list would look something like this:
['0.031250', '0.062500', '0.093750', '0.125000']

Once a file has been read by this function and its output stored in a variable, it is possible to use the data within Python without needing to re-read the file.

As said previously, .csv files can contain any kind of data, some of which is not necessarily going to be useful to Maya. While the above function is designed to read any .csv file into Python, what we do next with this data is going to be specific for each file.

The next few functions written for the script are written specifically for the files that we are working with.

I shall use the heart rate file as an example of how I use the data.

This function creates an empty group node in Maya with the following animated attributes.

    speed
    pace
    heartRate
    averageSpeed
    averagePace
    averageHeartRate
    latitude
    longitude
    distance


This .csv data contains a "Workout Time (secs)" column. The function accesses the values in that column, (in this case, the current time in seconds), and for each one, the corresponding attribute values are accessed and a key frame is set at that time, and that value.

The script also uses a helper function to convert the time from seconds to frames, taking the scene frame rate into account automatically. Here is what the extracted animation data looks like:


Writing a function to read the csv data object is fairy straight forward as shown in this Python code snippet.
def printCsvColumn(csvData, columnName)
   
    for i in range(len(csvData[columnName])):
        print csvData[columnName][i]

This code takes a csvData object and the name of a column, and prints out the values of that column.


Basic Usage of the Script
For all team mates on project Requiem, here is a quick overview on how to use the script.

To install the script you need to put the .py file inside one of the 3 script folders in your local maya settings folder. As an example:

    Windows: <drive>:\Documents and Settings\<username>\My Documents\maya\<Version>\scripts
    Mac OS X: ~/Library/Preferences/Autodesk/maya/<version>/scripts
    Linux: ~/maya/<version>/scripts

Once this is done, run Maya and open up the script editor. Then inside a Python tab, run the following:

First we need to 'import' the script's functions. We use the namespace of 'csv' so we don't have to type out the full name.
import ecs_CsvToMaya as csv

Next we run the function 'getCsvData' and parse in the file path to a csv file as a string. The data is returned and stored in the variable 'csvData':
csvData = csv.getCsvData("Path/to/a/file.csv")

Note the quotation marks around the string, and the 'csv.' before the function name. If we didn't use the namespace when importing the module, we would have to write it like this:
ecs_CsvToMaya.getCsvData("Path/to/a/file.csv")

Now that we have the data available in Python we can use the other functions in the script to represent that data as keyframes. As each file is different we need to make sure we use the right function for the right file.

For the heart rate file, we need to use this function:
csv.createCsvHeartRateData(csvData)

This will create an empty group with all the relevant data animated on some custom attributes.

However for the rest of the files we can use this function:
csv.createCsvSensorData(csvData)

This will create a locator with the translate X, Y, and Z attributes animated.

So the completed code to run in Maya should look something like this:
import ecs_CsvToMaya as csv

csvData = csv.getCsvData("Path/to/the/heartRateFile.csv")
csv.createCsvHeartRateData(csvData)

csvData = csv.getCsvData("Path/to/another/file.csv")
csv.createCsvSensorData(csvData)

csvData = csv.getCsvData("Path/to/yet/another/file.csv")
csv.createCsvSensorData(csvData)

So for each file we need to read in the data, then use the correct function to get the data into Maya.
In this case the heart rate file is the only exception, and for the rest we can use the other function.

On last set of functions have also been included, that generate Nurbs Curves from the animation data.
To use these you can run either of the following commands in Python (replacing with correct node and attribute names):
csv.create2DCurve("nodeName", "attributeName")

csv.create3DCurve("nodeName")

You can use the Maya time slider to select a time range from which to generate the curve from.
create3DCurve() specifically works on the translate X, Y and Z attributes to generate a full 3D curve, while the create2DCurve() will generate a flat curve on any other attribute. Here is an example of what the 'pace' attribute generated from the heart rate file:


Taking the Script Further
Currently the script will not be made publicly available, as the script is very much geared towards this project only. However it is my intention to continue developing this script so that it can be used on other projects, and by other people. I'm thinking that the tool would need to be generalised so that it could be possible to analyse any csv file from within Maya and choose how to interpret the data, rather than needing to write specific functions on a per-file basis.

Perhaps it would be possible to provide a set of nodes that are designed to interpret the data in different ways, which could then be plugged into existing Maya nodes to animate objects, create effects or generate meshes on the fly etc; and of course, some kind of graphical user interface.

So there we have it! I hope that readers have found it interesting, and informative.

Ethan Shilling

1 comment: