//
// fMRIData.m
// fMRI Visualizer
//
// Created by Paolo on 29/03/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import "fMRIData.h"
#import <Accelerate/Accelerate.h>
#import "timing.h"
#define DataChunk 1024L
@interface fMRIData (Private)
- (void)_buildGeometryFromPath: (NSString *)dataPath;
- (void)_addFilteredFiberAtIndex: (GLint)vertexIndex;
- (void)_addFiberAtIndex: (GLint)vertexIndex count: (GLint)vertexCount;
- (void)_addVertexAtX: (GLfloat)x Y: (GLfloat)y Z: (GLfloat)z;
@end
@implementation fMRIData
+ (id)dataWithFile: (NSString *)dataFilePath
{
return [[[fMRIData alloc] initWithFile: dataFilePath] autorelease];
}
- (id)initWithFile: (NSString *)dataFilePath
{
if (self = [super initWithName: [dataFilePath lastPathComponent]]) {
if (![[NSFileManager defaultManager] fileExistsAtPath: dataFilePath]) {
[self release];
return nil;
}
[self _buildGeometryFromPath: dataFilePath];
}
return self;
}
- (void)dealloc
{
if (filteredIndexes != indexes)
free(filteredIndexes);
free(indexes);
if (filteredCounts != counts)
free(filteredCounts);
free(counts);
free(points);
[super dealloc];
}
- (void)_drawPointArrays
{
if (numFilteredFibers > 0) {
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(3, GL_FLOAT, sizeof(fMRIPoint), &((GLfloat *)points)[0]);
glColorPointer(3, GL_FLOAT, sizeof(fMRIPoint), &((GLfloat *)points)[4]);
glMultiDrawArrays(GL_LINE_STRIP, filteredIndexes, filteredCounts, numFilteredFibers);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
}
}
#ifdef USE_DISPLAY_LIST
// The standard draw() uses display list already
#else
// We redraw the array every time: slower, but data can change dynamically
- (void)draw
{
[self _drawPointArrays];
}
#endif
- (void)filterLengthLongerThan: (float)len
{
NSInteger ii;
if (filteredIndexes != indexes) {
free(filteredIndexes);
}
if (filteredCounts != counts) {
free(filteredCounts);
}
filteredIndexes = nil;
filteredCounts = nil;
numFilteredFibers = 0;
for (ii = 0; ii < numFibers; ii++) {
if (lengths[ii] >= len)
[self _addFilteredFiberAtIndex: ii];
}
}
- (void)swapCoordsType: (SwapCoordType)sType
{
vImage_Buffer dst = {points, numPoints, 2, sizeof(fMRIPoint)};
uint8_t permMap[4] = {0, 1, 2, 3}; // This is the original order
switch (sType) {
case kSwapXY:
permMap[0] = 1;
permMap[1] = 0;
break;
case kSwapXZ:
permMap[0] = 2;
permMap[2] = 0;
break;
case kSwapYZ:
permMap[1] = 2;
permMap[2] = 1;
break;
default:
break;
}
vImagePermuteChannels_ARGBFFFF(&dst, &dst, permMap, kvImageDoNotTile);
}
- (_C3DTBounds)bounds
{
return bounds;
}
@end
@implementation fMRIData (Private)
- (void)_addFilteredFiberAtIndex: (GLint)vertexIndex
{
if (!filteredIndexes) {
filteredIndexes = calloc(DataChunk, sizeof(GLint));
filteredCounts = calloc(DataChunk, sizeof(GLint));
allocFilteredFibers = DataChunk;
} else if (numFilteredFibers == allocFilteredFibers) {
filteredIndexes = realloc(filteredIndexes, (numFilteredFibers + DataChunk) * sizeof(GLint));
filteredCounts = realloc(filteredCounts, (numFilteredFibers + DataChunk) * sizeof(GLint));
allocFilteredFibers += DataChunk;
}
filteredIndexes[numFilteredFibers] = indexes[vertexIndex];
filteredCounts[numFilteredFibers] = counts[vertexIndex];
numFilteredFibers++;
}
- (void)_addFiberAtIndex: (GLint)vertexIndex count: (GLint)vertexCount
{
NSInteger ii;
fMRIPoint initPt;
GLfloat length;
// Check data consistency: can't have negative indexes or counts!
if ((vertexIndex < 0) || (vertexCount < 0)) {
NSLog(@"%s: Can't add vertex %d and/or count %d!", __PRETTY_FUNCTION__, vertexIndex, vertexCount);
return;
}
if (!indexes) {
indexes = calloc(DataChunk, sizeof(GLint));
counts = calloc(DataChunk, sizeof(GLint));
lengths = calloc(DataChunk, sizeof(GLfloat));
numFibers = 0;
allocFibers = DataChunk;
} else if (numFibers == allocFibers) {
indexes = realloc(indexes, (numFibers + DataChunk) * sizeof(GLint));
counts = realloc(counts, (numFibers + DataChunk) * sizeof(GLint));
lengths = realloc(lengths, (numFibers + DataChunk) * sizeof(GLfloat));
allocFibers += DataChunk;
}
// Calculate total fiber length for later filtering
initPt = points[vertexIndex];
for (ii = 1, length = 0; ii < vertexCount; ii++) {
fMRIPoint nextPt = points[ii + vertexIndex];
_C3DTVector vec1, vec2;
vec1.x = initPt.x;
vec1.y = initPt.y;
vec1.z = initPt.z;
vec2.x = nextPt.x;
vec2.y = nextPt.y;
vec2.z = nextPt.z;
length += vectorLength(vectorSubtract(vec1, vec2));
initPt = nextPt;
}
indexes[numFibers] = vertexIndex;
counts[numFibers] = vertexCount;
lengths[numFibers] = length;
numFibers++;
}
- (void)_addVertexAtX: (GLfloat)x Y: (GLfloat)y Z: (GLfloat)z
{
fMRIPoint aPoint = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
if (!points) {
points = calloc(DataChunk, sizeof(fMRIData));
numPoints = 0;
allocPoints = DataChunk;
} else if (numPoints == allocPoints) {
points = realloc(points, (numPoints + DataChunk) * sizeof(fMRIData));
allocPoints += DataChunk;
}
aPoint.x = x;
aPoint.y = y;
aPoint.z = z;
points[numPoints++] = aPoint;
}
- (_C3DTVector)_calculateEigenVectorAtIndex: (GLint)startPtIdx endIndex: (GLint)endPtIdx
{
_C3DTVector fiberDirection;
fiberDirection.x = points[endPtIdx].x - points[startPtIdx].x;
fiberDirection.y = points[endPtIdx].y - points[startPtIdx].y;
fiberDirection.z = points[endPtIdx].z - points[startPtIdx].z;
return vectorNormalize(fiberDirection);
}
- (void)_buildGeometryFromPath: (NSString *)dataPath
{
float minX = (float)LONG_MAX;
float maxX = (float)LONG_MIN;
float minY = (float)LONG_MAX;
float maxY = (float)LONG_MIN;
float minZ = (float)LONG_MAX;
float maxZ = (float)LONG_MIN;
float halfWidth, halfHeight, halfDepth;
NSData *fileData = [NSData dataWithContentsOfFile: dataPath];
NSInteger length = [fileData length];
uint8_t *fileBytes = (uint8_t *)[fileData bytes];
uint8_t *bytesEnd = fileBytes + length;
uint32_t jj;
uint64_t startTime = usecs();
// WARN: This code only works if we're on a little-endian machine!
// Plus, it assumes "float" is a 32-bit entity
for(fileBytes += 1000; fileBytes < bytesEnd; /* no increment */){
uint32_t cntPts;
uint32_t initPt = numPoints;
cntPts = *(uint32_t *)fileBytes;
fileBytes += sizeof(int);
for (jj = 0; jj < cntPts; jj++) {
float x, y, z;
x = *(float *)fileBytes;
fileBytes += sizeof(float);
y = *(float *)fileBytes;
fileBytes += sizeof(float);
z = *(float *)fileBytes;
fileBytes += sizeof(float);
if (x < minX)
minX = x;
if (x > maxX)
maxX = x;
if (y < minY)
minY = y;
if (y > maxY)
maxY = y;
if (z < minZ)
minZ = z;
if (z > maxZ)
maxZ = z;
[self _addVertexAtX: x Y: y Z: z];
}
[self _addFiberAtIndex: initPt count: jj];
}
halfWidth = (maxX - minX) / 2.0;
halfHeight = (maxY - minY) / 2.0;
halfDepth = (maxZ - minZ) / 2.0;
// Now that we have the whole geometry, assign colors according to our rules
for (jj = 0; jj < numFibers; jj++) {
GLint startPtIdx = indexes[jj];
GLint endPtIdx = startPtIdx + counts[jj] - 1;
_C3DTVector fiberDirection = [self _calculateEigenVectorAtIndex: startPtIdx endIndex: endPtIdx];
int jj;
// Assign colors and auto-center
for (jj = startPtIdx; jj <= endPtIdx; jj++) {
points[jj].x -= minX + halfWidth;
points[jj].y -= minY + halfHeight;
points[jj].z -= minZ + halfDepth;
points[jj].r = fabs(fiberDirection.x);
points[jj].g = fabs(fiberDirection.y);
points[jj].b = fabs(fiberDirection.z);
}
}
filteredIndexes = indexes;
filteredCounts = counts;
numFilteredFibers = numFibers;
NSLog(@"File read in %ld usecs", (long)(usecs() - startTime));
#ifdef USE_DISPLAY_LIST
[self startDisplayList];
[self _drawPointArrays];
[self stopDisplayList];
#endif
bounds.bottomLeftNear.x = -halfWidth;
bounds.bottomLeftNear.y = -halfHeight;
bounds.bottomLeftNear.z = -halfDepth;
bounds.topRightFar.x = halfWidth;
bounds.topRightFar.y = halfHeight;
bounds.topRightFar.z = halfDepth;
}
@end