Here at Thalmic Labs, we're pretty focused on the future of human computer interaction. That's why we built the Myo armband as a new way to effortlessly interact with your technology. Turns out we also built a pretty great little wireless sensor package you can wear on your arm. There are a lot of people interested in playing with the data provided by the 8 EMG pods and the 9-axis IMU included in the Myo armband, but traditionally to do so you would have needed a bit of programming knowledge. That's a barrier that no longer applies, thanks to this week's #MyoCraft.  Check it out!

You can get the Myo Data Capture application on the Myo Market. When you launch it, the app will stream the accelerometer, gyroscope, orientation, orientation in Euler angles (as opposed to Quaternions), and EMG data out to the file system into four different timestamped CSV files. The EMG data comes in at 200Hz, and the IMU data (everything else) is at 50Hz. Those speeds can't be changed (at all, not just in the app). Also, any time the Myo armband is re-connected a new set of log files will be created. Note that this will only work for a single armband.

It was built with a modified version of the EMG sample application included in our SDK. The source is in the application folder, and below:

// Copyright (C) 2013-2014 Thalmic Labs Inc.
// Distributed under the Myo SDK license agreement. See LICENSE.txt for details.

// This sample illustrates how to log EMG and IMU data. EMG streaming is only supported for one Myo at a time, and this entire sample is geared to one armband

#define _USE_MATH_DEFINES
#include <cmath>
#include <iomanip>
#include <algorithm>
#include <array>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <fstream>
#include <time.h>

#include <myo/myo.hpp>

class DataCollector : public myo::DeviceListener {  
public:  
    DataCollector()
    {
        openFiles();
    }

    void openFiles() {
        time_t timestamp = std::time(0);

        // Open file for EMG log
        if (emgFile.is_open()) {
            emgFile.close();
        }
        std::ostringstream emgFileString;
        emgFileString << "emg-" << timestamp << ".csv";
        emgFile.open(emgFileString.str(), std::ios::out);
        emgFile << "timestamp,emg1,emg2,emg3,emg4,emg5,emg6,emg7,emg8" << std::endl;

        // Open file for gyroscope log
        if (gyroFile.is_open()) {
            gyroFile.close();
        }
        std::ostringstream gyroFileString;
        gyroFileString << "gyro-" << timestamp << ".csv";
        gyroFile.open(gyroFileString.str(), std::ios::out);
        gyroFile << "timestamp,x,y,z" << std::endl;

        // Open file for accelerometer log
        if (accelerometerFile.is_open()) {
            accelerometerFile.close();
        }
        std::ostringstream accelerometerFileString;
        accelerometerFileString << "accelerometer-" << timestamp << ".csv";
        accelerometerFile.open(accelerometerFileString.str(), std::ios::out);
        accelerometerFile << "timestamp,x,y,z" << std::endl;

        // Open file for orientation log
        if (orientationFile.is_open()) {
            orientationFile.close();
        }
        std::ostringstream orientationFileString;
        orientationFileString << "orientation-" << timestamp << ".csv";
        orientationFile.open(orientationFileString.str(), std::ios::out);
        orientationFile << "timestamp,x,y,z,w" << std::endl;

        // Open file for orientation (Euler angles) log
        if (orientationEulerFile.is_open()) {
            orientationEulerFile.close();
        }
        std::ostringstream orientationEulerFileString;
        orientationEulerFileString << "orientationEuler-" << timestamp << ".csv";
        orientationEulerFile.open(orientationEulerFileString.str(), std::ios::out);
        orientationEulerFile << "timestamp,roll,pitch,yaw" << std::endl;

    }

    // onEmgData() is called whenever a paired Myo has provided new EMG data, and EMG streaming is enabled.
    void onEmgData(myo::Myo* myo, uint64_t timestamp, const int8_t* emg)
    {

        emgFile << timestamp;
        for (size_t i = 0; i < 8; i++) {
            emgFile << ',' << static_cast<int>(emg[i]);

        }
        emgFile << std::endl;
    }

    // onOrientationData is called whenever new orientation data is provided
    // Be warned: This will not make any distiction between data from other Myo armbands
    void onOrientationData(myo::Myo *myo, uint64_t timestamp, const myo::Quaternion< float > &rotation) {
        orientationFile << timestamp
            << ',' << rotation.x()
            << ',' << rotation.y()
            << ',' << rotation.z()
            << ',' << rotation.w()
            << std::endl;

        using std::atan2;
        using std::asin;
        using std::sqrt;
        using std::max;
        using std::min;

        // Calculate Euler angles (roll, pitch, and yaw) from the unit quaternion.
        float roll = atan2(2.0f * (rotation.w() * rotation.x() + rotation.y() * rotation.z()),
            1.0f - 2.0f * (rotation.x() * rotation.x() + rotation.y() * rotation.y()));
        float pitch = asin(max(-1.0f, min(1.0f, 2.0f * (rotation.w() * rotation.y() - rotation.z() * rotation.x()))));
        float yaw = atan2(2.0f * (rotation.w() * rotation.z() + rotation.x() * rotation.y()),
            1.0f - 2.0f * (rotation.y() * rotation.y() + rotation.z() * rotation.z()));

        orientationEulerFile << timestamp
            << ',' << roll
            << ',' << pitch
            << ',' << yaw
            << std::endl;
    }

    // onAccelerometerData is called whenever new acceleromenter data is provided
    // Be warned: This will not make any distiction between data from other Myo armbands
    void onAccelerometerData(myo::Myo *myo, uint64_t timestamp, const myo::Vector3< float > &accel) {

        printVector(accelerometerFile, timestamp, accel);

    }

    // onGyroscopeData is called whenever new gyroscope data is provided
    // Be warned: This will not make any distiction between data from other Myo armbands
    void onGyroscopeData(myo::Myo *myo, uint64_t timestamp, const myo::Vector3< float > &gyro) {
        printVector(gyroFile, timestamp, gyro);

    }

    void onConnect(myo::Myo *myo, uint64_t timestamp, myo::FirmwareVersion firmwareVersion) {
        //Reneable streaming
        myo->setStreamEmg(myo::Myo::streamEmgEnabled);
        openFiles();
    }

    // Helper to print out accelerometer and gyroscope vectors
    void printVector(std::ofstream &file, uint64_t timestamp, const myo::Vector3< float > &vector) {
        file << timestamp
            << ',' << vector.x()
            << ',' << vector.y()
            << ',' << vector.z()
            << std::endl;
    }

    // The files we are logging to
    std::ofstream emgFile;
    std::ofstream gyroFile;
    std::ofstream orientationFile;
    std::ofstream orientationEulerFile;
    std::ofstream accelerometerFile;

};

int main(int argc, char** argv)  
{
    // We catch any exceptions that might occur below -- see the catch statement for more details.
    try {

    // First, we create a Hub with our application identifier. Be sure not to use the com.example namespace when
    // publishing your application. The Hub provides access to one or more Myos.
    myo::Hub hub("com.undercoveryeti.myo-data-capture");

    std::cout << "Attempting to find a Myo..." << std::endl;

    // Next, we attempt to find a Myo to use. If a Myo is already paired in Myo Connect, this will return that Myo
    // immediately.
    // waitForMyo() takes a timeout value in milliseconds. In this case we will try to find a Myo for 10 seconds, and
    // if that fails, the function will return a null pointer.
    myo::Myo* myo = hub.waitForMyo(10000);

    // If waitForMyo() returned a null pointer, we failed to find a Myo, so exit with an error message.
    if (!myo) {
        throw std::runtime_error("Unable to find a Myo!");
    }

    // We've found a Myo.
    std::cout << "Connected to a Myo armband! Logging to the file system. Check the folder this appliation lives in." << std::endl << std::endl;

    // Next we enable EMG streaming on the found Myo.
    myo->setStreamEmg(myo::Myo::streamEmgEnabled);

    // Next we construct an instance of our DeviceListener, so that we can register it with the Hub.
    DataCollector collector;

    // Hub::addListener() takes the address of any object whose class inherits from DeviceListener, and will cause
    // Hub::run() to send events to all registered device listeners.
    hub.addListener(&collector);

    // Finally we enter our main loop.
    while (1) {
        // In each iteration of our main loop, we run the Myo event loop for a set number of milliseconds.
        // In this case, we wish to update our display 50 times a second, so we run for 1000/20 milliseconds.
        hub.run(1);
    }

    // If a standard exception occurred, we print out its message and exit.
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        std::cerr << "Press enter to continue.";
        std::cin.ignore();
        return 1;
    }
}

Once you have the data you want, you can then... well, that's up to you! For example,  you could make a sweet chart of the acceleration your arm experiences when moving around:

Oooh, data.Oooh, data. Acceleration is in units of G

Let us know if you learn something cool! And as always, if you've got a cool hack you want to talk about, let us know at MyoCraft@thalmic.com, or shoot me a tweet at @PBernhardt.

Otherwise, see you next week!

Newsletter

Enter your email address and get all latest content delivered to your inbox every now and then.