views:

60

answers:

4

Hi every one.

I am new programmer in Obj-C and cocoa. Im a trying to write a framework which will be used to read a binary files (Flexible Image Transport System or FITS binary files, usually used by astronomers). The binary data, that I am interested to extract, can have various formats and I get its properties by reading the header of the FITS file.

Up to now, I manage to create a class to store the content of the FITS file and to isolate the header into a NSString object and the binary data into a NSData object. I also manage to write method which allow me to extract the key values from the header that are very valuable to interpret the binary data.

I am now trying to convert the NSData object into a primitive array (array of double, int, short ...). But, here, I get stuck and would appreciate any help.

According to the documentation I have about the FITS file, I have 5 possibilities to interpret the binary data depending on the value of the BITPIX key:

BITPIX value | Data represented
  8          | Char or unsigned binary int
 16          | 16-bit two's complement binary integer
 32          | 32-bit two's complement binary integer
 64          | 64-bit two's complement binary integer
-32          | IEEE single precision floating-point
-64          | IEEE double precision floating-point

I already write the peace of code, shown bellow, to try to convert the NSData into a primitive array.

// self reefer to my FITS class which contain a NSString object  
// with the content of the header and a NSData object with the binary data. 

-(void*) GetArray
{
switch (BITPIX)
{
    case 8:
        return [self GetArrayOfUInt];
        break;
    case 16:
        return [self GetArrayOfInt];
        break;
    case 32:
        return [self GetArrayOfLongInt];
        break;
    case 64:
        return [self GetArrayOfLongLong];
        break;
    case -32:
        return [self GetArrayOfFloat];
        break;
    case -64:
        return [self GetArrayOfDouble];
        break;
    default:
        return NULL;
}
}

// then I show you the method to convert the NSData into a primitive array.
// I restrict my example to the case of 'double'. Code is similar for other methods
// just change double by 'unsigned int' (BITPIX 8), 'short' (BITPIX 16)
// 'int' (BITPIX 32) 'long lon' (BITPIX 64), 'float' (BITPIX -32). 

-(double*) GetArrayOfDouble
{
int Nelements=[self NPIXEL]; // Metod to extract, from the header 
                             // the number of element into the array
NSLog(@"TOTAL NUMBER OF ELEMENTS [%i]\n",Nelements);

//CREATE THE ARRAY
double (*array)[Nelements];

// Get the total number of bits in the binary data
int Nbit = abs(BITPIX)*GCOUNT*(PCOUNT + Nelements); // GCOUNT and PCOUNT are defined
                                                        // into the header
NSLog(@"TOTAL NUMBER OF BIT [%i]\n",Nbit);
int i=0;

    //FILL THE ARRAY
double Value;

for(int bit=0; bit < Nbit; bit+=sizeof(double))
{
    [Img getBytes:&Value range:NSMakeRange(bit,sizeof(double))];
    NSLog(@"[%i]:(%u)%.8G\n",i,bit,Value);
        (*array)[i]=Value;
    i++;

}

return (*array);

}

However, the value I print in the loop are very different from the expected values (compared using official FITS software). Therefore, I think that the Obj-C double does not use the IEEE-754 convention as well as the Obj-C int are not twos-complement. I am really not familiar with this two convention (IEEE and twos-complement) and would like to know how I can do this conversion with Obj-C.

In advance many thanks for any help or information.

+3  A: 

Objective-C uses IEEE-754 floats (like practically every system does) as well as two's-complement integers. I think your conjecture is false. Maybe you are having endianness problems?

Carl Norum
+1  A: 

Virtually all systems use two's complement and IEEE 754, simply because it is rarely worth it to come up with a brand new representation of these things - you just don't do that unless you have very specialized circumstances that require it.

I would suggest you display the number in hexadecimal - it is much more likely to be an issue with endianness than the data types being of a special format, and printing the values in hexadecimal makes it easier to tell if that is the problem.

Michael Madsen
I see. So, I guess I should use function `NSSwapHostIntToLittle` or `NSSwapHostIntToBig` of the Foundation framework. But, it seems to me that this function just changes the bit order not the real value, right ? Also, the software I plane to build will open FITS file coming from different platform which may be encoded with different endian type, unless their is convention for FITS file which recommend to use only big or little endian. I have not find any information on that subject. If you have an other suggestion for changing the endian type of the NSData object, it will be welcome.
William GILLARD
@William: I'm afraid I don't really know anything about FITS, so I can't tell you what endianness you should be using, but changing the order of the bytes is exactly what you'd do to fix the endianness. The bytes 0x01 0x00 mean 1 if read as little-endian, but 256 if read as big-endian. Swapping those two bytes will also swap the two values read in the different endiannesses - so if the little-endian value was correct, you would convert the endianness on a big-endian system to read the value properly.
Michael Madsen
@Michael: Thanks a lot. This was the solution.
William GILLARD
A: 

Are you sure that

double (*array)[NElements];

is what you want? This simple program:

int main( int argc, char** argv)
{
    double (*array)[5];
    (*array)[4] = 0;
    return 0;
}

Crashes miserably on my machine.

zenerino
I agree this function is experimental. My first step is to get the correct conversion from binary to primitive. I also want to precise that I am more familiar with C++ with which I would write `double *array=new double[Nelements]`. I want "array" to be a pointer, since `new` is not available in Obj-C I use instead `double (*array)[size]`. Thereafter, when I want to fill my array I still need to use `(*array)` otherwise the compiler is not happy. It seems the program is not crashing at execution. Any way, I will consider your remark and search other solution which may be more stable.
William GILLARD
A: 

Thanks a lot. The problem was really related to the endian and I was not aware of this. Honestly, I was never really aware of the endian type because, up to now, I was never confronted to the problem.

By the way, I find the solution, thanks all for your remarks. Because it can be useful for other developer, I will propose a peace of code which allow to convert NSData to primitive, what ever the endian type. This is the solution I find (work with the Foundation and cocoa framework on Os X 10.5 and 10.6) and it may not be the best, so, use at your own risk ;)

// 1- CREATE AN ARRAY OF SIZE Nelements;
int Nelements = XXX;
double (*array)[Nelements];

// 2- Create the swapped double -> a double with the wrong bit order
NSSwappedDouble swappedValue;

// 3- The correct value
double Value;

// 4- Fill your array
int i=0, bit=0;
int BitStep=sizeof(double);

while(i<Nelements)
{
    [MyNSData getBytes:&swappedValue range:NSMakeRange(bit,step)];
    Value = NSSwapBigDoubleToHost(swappedValue);   // or NSSwapLittleDoubleToHost depending of your endian type.
    (*array)[i]=Value;
    i++; bit+=BitStep;

}

Hope this can be useful.

William GILLARD