One second of raw activity samples packed into 12-bit values in YXZ order. Activity data is stored continuously for every sample the device records over one second. Each sample contains all three axis of data in the following order: y-axis, x-axis, and z-axis.
To help conserve space, activity samples are bit-packed. A single 3-axis sample takes up 36 bits of data (12 bits per axis). To parse this data, you will have to portion the byte data into nibbles.
The activity samples are encoded as 12-bit two's complement values. Two's complement is the standard signed integer encoding used in modern architectures.
To convert the 12-bit values to 16-bit signed integers (Int16) for use, they must be sign-extended. Endianness doesn't exactly apply for 12-bit values, but it is basically big-endian. In other words, the bits are in order from most-significant to least-significant.
- If an axis value is greater than 2047, you need to bitwise OR it with 0xF000 (or just add 61440 to the value). This is the sign-extension from above.
- If there are an odd number of samples, the last nibble of data is not used in parsing. See the the last Z-axis in the example below.
Once a sample has been unpacked and the special conditions are accounted for, we must:
- Cast the UInt16 values into signed 16-bit values.
- Scale the resultant by the scale factor (this gives us an acceleration value in g's). Device serial numbers starting with NEO and CLE use a scale factor of 341 LSB/g (±6g). MOS devices use a 256 LSB/g scale factor (±8g). If a LOG_PARAMETER record is preset, then the ACCEL_SCALE value should be used.
- Round the value from #2 to three decimal places.
An 'Activity' (id: 0x00) log record type with a 1-byte payload is captured on a USB connection event (and does not represent a reading from the activity monitor's accelerometer). This event is captured upon docking the activity monitor (via USB) to a PC or CentrePoint Data Hub (CDH) device. Therefore such records cannot be parsed as the traditional activity log records and can be ignored.
ActiGraph wGT3X-BT firmware version 1.6.0, released on Dec. 28, 2015, incorrectly rotates the axes of the accelerometer by 90 degrees about the Z-axis. Acceleration measured along the X-axis will appear as acceleration along the Y-axis, and acceleration measured along the Y-axis will appear along the X-axis. For a reference of the accelerometer orientation, see the wGT3X-BT illustration here.
To account for this, please perform the following adjust to Activity data from MOS devices with 1.6.0 firmware:
var fixedX = sample.Y;
var fixedY = -1 * sample.X;
sample.Y = fixedY;
sample.X = fixedX;
For more information, check out this help article: https://help.theactigraph.com/entries/107929323
We have a .gt3x file with the following information:
- The device used to record the data was a GT3X+ (serial number starts with "NEO")
- Device started recording at 2008/3/29 12:00:00
- The data in the activity.bin file is: "00 60 08 EB D0 07 00 9E BF 00 70 08 EB F0"
Sample Count | Axis | Bytes to Use | Binary Equivalent | UInt16 from binary | After Special Conditions | Cast to Int16 | Scaling | Rounding |
---|---|---|---|---|---|---|---|---|
1 | Y | 0x0060 | 000000000110 |
6 | 6 | 6 | 0.0175953 | 0.018 |
1 | X | 0x6008 | 8 | 8 | 8 | 0.023460 | 0.023 | |
1 | Z | 0xEBD0 | 111010111101 |
3773 | 65213 | -323 | -0.947214 | -0.947 |
2 | Y | 0xD007 | 7 | 7 | 7 | 0.0205278 | 0.021 | |
2 | X | 0x009E | 000000001001 |
9 | 9 | 9 | 0.0263929 | 0.026 |
2 | Z | 0x9EBF | 3775 | 65215 | -321 | -0.941348 | -0.941 | |
3 | Y | 0x0070 | 000000000111 |
7 | 7 | 7 | 0.0205278 | 0.021 |
3 | X | 0x7008 | 8 | 8 | 8 | 0.023460 | 0.023 | |
3 | Z | 0xEBF0 | 111010111111 |
3775 | 65215 | -321 | -0.941348 | -0.941 |
*binary values that are have strikethrough are the nibbles that are ignored.
/// <summary> Parse activity data from a stream of data </summary>
/// <param name="stream">The activity.bin stream of data to parse.</param>
/// <returns>All of the activity samples in a stream.</returns>
private IEnumerable<AccelerationSample> ParseAcceleration(Stream stream)
{
if (!stream.CanRead)
throw new Exception("Unable to read from activity stream.");
double[] sample = new double[3];
int offset = 0;
int current = 0;
var timestampHelper = new TimestampHelper(1000, SampleRateInHz);
long milliSeconds = 0;
while (true)
{
for (int axis = 0; axis < 3; ++axis)
{
UInt16 shifter;
if (0 == (offset & 0x7))
{
current = stream.ReadByte();
if (current == -1)
{
yield break;
}
shifter = (UInt16)((current & 0xFF) << 4);
offset += 8;
current = stream.ReadByte();
if (current == -1)
{
yield break;
}
shifter |= (UInt16)((current & 0xF0) >> 4);
offset += 4;
}
else
{
shifter = (UInt16)((current & 0x0F) << 8);
offset += 4;
current = stream.ReadByte();
if (current == -1)
{
yield break;
}
shifter |= (UInt16)(current & 0xFF);
offset += 8;
}
if (0 != (shifter & 0x0800))
shifter |= 0xF000;
sample[axis] = (Int16)shifter / ACCELERATION_SCALE_FACTOR;
}
//round to 3 decimal places
sample[0] = Math.Round(sample[0], 3, MidpointRounding.AwayFromZero);
sample[1] = Math.Round(sample[1], 3, MidpointRounding.AwayFromZero);
sample[2] = Math.Round(sample[2], 3, MidpointRounding.AwayFromZero);
yield return
new AccelerationSample(sample[1], sample[0], sample[2],
FirstSample.AddTicks(milliSeconds*TimeSpan.TicksPerMillisecond));
milliSeconds += timestampHelper.Next();
}
}