# Anyone know how to read Analyze ObjectMap files?

**URL:** <https://discourse.slicer.org/t/anyone-know-how-to-read-analyze-objectmap-files/29074>\
**Category:** Development\
**Tags:** analyze\
**Created:** [April 23, 2023, 9:28pm UTC](https://discourse.slicer.org/t/anyone-know-how-to-read-analyze-objectmap-files/29074 "2023-04-23T21:28:30Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![pieper](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/pieper/32/8_2.png) [@pieper](https://discourse.slicer.org/u/pieper)\
**Post date:** [April 23, 2023, 9:28pm UTC](https://discourse.slicer.org/t/anyone-know-how-to-read-analyze-objectmap-files/29074/1 "2023-04-23T21:28:31Z")

</div>

@Ron Alkalay and I are trying to work with some segmentations generated by [Analyze](https://analyzedirect.com/). He was able to export it in .hdr/.img format, but the pixel to world (ijkToRAS) is identity so the data doesn’t match the CT as shown below.

Anonymized data is here: [object-map-debug - Google Drive](https://drive.google.com/drive/folders/1bgnk8msRqM_6Vz5OSywa-EEQLuRAx5oz?usp=sharing) in case anyone want’s to give it a try or knows any conventions about how it’s encoded.

 ![image](https://us1.discourse-cdn.com/flex002/uploads/slicer/original/3X/6/6/66222f5044eaf5ced04f15c3d30d4063580f9e19.jpeg)

 ![image](https://us1.discourse-cdn.com/flex002/uploads/slicer/original/3X/6/4/641d9c25cd5d1f5a86d2ed61fdec51bc65ac93ad.jpeg)

I also tried this code: [objparser/objparser.py at master · pkostandy/objparser · GitHub](https://github.com/pkostandy/objparser/blob/master/objparser.py)

but the headers don’t seem to have any geometry information either.

He has over a hundred of these, and worst case would be to just line it up manually somehow, or maybe it could be semi-automated, but we’d rather figure out how to either read this data or export it another way from Analyze where it looks like this:

![image](https://us1.discourse-cdn.com/flex002/uploads/slicer/original/3X/2/f/2f9df7c69939fd26ef1caff5283dcc621b7a3a96.jpeg)

![image](https://us1.discourse-cdn.com/flex002/uploads/slicer/original/3X/d/d/dd510e3fa2313fc6fe02e70421ba5b19779dd429.jpeg)

---

<div class="post-metadata">

**Author:** ![mau\_igna\_06](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/mau_igna_06/32/9056_2.png) [@mau\_igna\_06](https://discourse.slicer.org/u/mau_igna_06)\
**Post date:** [April 23, 2023, 10:12pm UTC](https://discourse.slicer.org/t/anyone-know-how-to-read-analyze-objectmap-files/29074/2 "2023-04-23T22:12:55Z")

</div>

Maybe you could use the exported CT data from Analize (e.g. nrrd if possible) to make a Elastix registration (or any other image registration algorithm) with the same CT image imported from the DICOM data to Slicer

Hope it helps

---

<div class="post-metadata">

**Author:** ![muratmaga](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/muratmaga/32/3622_2.png) [@muratmaga](https://discourse.slicer.org/u/muratmaga)\
**Post date:** [April 23, 2023, 10:24pm UTC](https://discourse.slicer.org/t/anyone-know-how-to-read-analyze-objectmap-files/29074/3 "2023-04-23T22:24:48Z")

</div>

I haven’t touched analyze since I switched to Slicer a decade ago. but from what I recall, during import there was an option to spacify “slice orientation (or ordering perhaps)”, with options like transverse, coronal, sagittal. So basically you can import the same image stack in different orientaitons, and I believe this wasn’t written to the file header. At least this was with the microCT scan sequences we had. Don’t know about DICOM or clinical images.

Try flipping in all cardinal directions, and see if it lines up. From the picture, the segmentation seems upside down (IS axis) and facing opposite direction (AP) with respect to the volume. So perhaps it is an easy flip of axes.

---

<div class="post-metadata">

**Author:** ![blezek](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/blezek/32/285_2.png) [@blezek](https://discourse.slicer.org/u/blezek)\
**Post date:** [April 24, 2023, 7:51pm UTC](https://discourse.slicer.org/t/anyone-know-how-to-read-analyze-objectmap-files/29074/4 "2023-04-24T19:51:10Z")

</div>

Hey @pieper !

Analyze Object Maps don’t contain any orientation nor spacing information, they are designed to be paired up with an original volume. I believe they are stored in `RPI` orientation, but using Analyze’s left-handed coordinate system. If you flip the object map in Y or do the coordinate system conversion to map from a left-handed, `RPI` orientation into (what looks to be) right-handed `LPS` coordinate system, it should line up correctly.

Once upon a time, ITK read the ObjectMap format (I couldn’t attach code ☹ ). Though the ITK Doxygen site references the code (see [here](https://itk.org/Doxygen50/html/classitk_1_1AnalyzeObjectLabelMapImageIO.html), [here](https://itk.org/Doxygen50/html/dir_f5f8fa485087d9ec419750b9df6227de.html), and [here](https://itk.org/Doxygen50/html/dir_6733826ecb0ee899e8e47cc177f7ace4.html), I’m not sure if this is just doxygen _cruft_ or not.

**Edit:** link rot got to Hans’ [ITK Journal article](https://doi.org/10.54294/w2jytv), not sure where the original code might be found. I have a copy if you’d like to DM me.

This is a short snippit of how to read ObjectMap files:

```auto
        // Try to load the objectmap
        typedef signed short PixelType;
        typedef itk::Image<PixelType, 3> ImageType;
        typedef itk::Image<itk::RGBPixel<PixelType>, 3> RGBImageType;
        typedef itk::ImageFileReader<ImageType> ReaderType;
        typedef itk::AnalyzeObjectMap<ImageType, RGBImageType> ObjectMapType;

        itk::AnalyzeObjectLabelMapImageIOFactory::Pointer metaFactory = itk::AnalyzeObjectLabelMapImageIOFactory::New();
        itk::ObjectFactoryBase::RegisterFactory(metaFactory);

        logger->debug("Starting to read");
        ReaderType::Pointer Reader = ReaderType::New();
        Reader->SetFileName(this->mFilename.c_str());

...

        ObjectMapType::Pointer itkObjectMap = ObjectMapType::New();
        itkObjectMap->ImageToObjectMap(Reader->GetOutput());

...
        // get info from the first object
        itk::AnalyzeObjectEntry::Pointer original = itkObjectMap->GetObjectEntry(0);

...

        // Loop over the ITK objects and create our guys
        for (int i = 0; i < itkObjectMap->GetNumberOfObjects(); i++) {
            ObjectEntry e;
            e.Name = itkObjectMap->GetObjectEntry(i)->GetName();
            e.Red = itkObjectMap->GetObjectEntry(i)->GetEndRed();
            e.Green = itkObjectMap->GetObjectEntry(i)->GetEndGreen();
            e.Blue = itkObjectMap->GetObjectEntry(i)->GetEndBlue();
            e.Alpha = itkObjectMap->GetObjectEntry(i)->GetBlendFactor();
        }

```

---

<div class="post-metadata">

**Author:** ![pieper](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/pieper/32/8_2.png) [@pieper](https://discourse.slicer.org/u/pieper)\
**Post date:** [April 24, 2023, 8:17pm UTC](https://discourse.slicer.org/t/anyone-know-how-to-read-analyze-objectmap-files/29074/5 "2023-04-24T20:17:26Z")

</div>

Super helpful @blezek - let me chew on this a bit!

---

<div class="post-metadata">

**Author:** ![issakomi](https://avatars.discourse-cdn.com/v4/letter/i/b9e5f3/32.png) [@issakomi](https://discourse.slicer.org/u/issakomi)\
**Post date:** [April 25, 2023, 7:13am UTC](https://discourse.slicer.org/t/anyone-know-how-to-read-analyze-objectmap-files/29074/6 "2023-04-25T07:13:13Z")

</div>

There are some options in ITK’s Nifti IO to control [Analyze75Flavor](https://github.com/InsightSoftwareConsortium/ITK/blob/7a89717c1a517a5d7d354c4dfcca8e0871817d2f/Modules/IO/NIFTI/include/itkNiftiImageIO.h#L42), specially the axis (AFAIK).  
Might be useful in addition to the post above (perhaps the behavior of Nifti IO has changed since 2009).

```auto
#include <itkImage.h>
#include <itkImageFileReader.h>
#include <itkImageFileWriter.h>
#include <itkNiftiImageIO.h>
#include <sstream>
#include <iostream>

int main(int argc, char ** argv)
{
	if (argc < 4)
	{
		std::cout <<
			"3 arguments are required\n"
			" input file name (Analyze),\n"
			" output file name (e.g. out.nrrd)\n"
			" Analyze75Flavor (0 - 4)"
				<< std::endl;
		return 0;
	}

	using ImageTypeUC = itk::Image<unsigned char, 3>;

	std::stringstream ss(argv[3]);
	int argv3;
	ss >> argv3;

	itk::NiftiImageIO::Pointer io = itk::NiftiImageIO::New();
	itk::ImageFileReader<ImageTypeUC>::Pointer reader =	itk::ImageFileReader<ImageTypeUC>::New();
	io->SetLegacyAnalyze75Mode(static_cast<itk::NiftiImageIOEnums::Analyze75Flavor>(argv3));
	const int analyse75Flavor = static_cast<int>(io->GetLegacyAnalyze75Mode());
	switch (analyse75Flavor)
	{
	case 0:
		std::cout << "Analyze75Flavor::AnalyzeReject" << std::endl;
		break;
	case 1:
		std::cout << "Analyze75Flavor::AnalyzeITK4Warning" << std::endl;
		break;
	case 2:
		std::cout << "Analyze75Flavor::AnalyzeSPM" << std::endl;
		break;
	case 3:
		std::cout << "Analyze75Flavor::AnalyzeFSL" << std::endl;
		break;
	case 4:
		std::cout << "Analyze75Flavor::AnalyzeITK4" << std::endl;
		break;
	default:
		std::cout << "Analyze75Flavor unknown" << std::endl;
		break;
	}
	reader->SetImageIO(io);
	reader->SetFileName(argv[1]);
	try
	{
		reader->Update();
	}
	catch (const itk::ExceptionObject & e)
	{
		std::cout << e.GetDescription() << std::endl;
		return 1;
	}
	
	itk::ImageFileWriter<ImageTypeUC>::Pointer writer =	itk::ImageFileWriter<ImageTypeUC>::New();
	writer->SetFileName(argv[2]);
	try
	{
		writer->SetInput(reader->GetOutput());
		writer->Update();
	}
	catch (const itk::ExceptionObject & e)
	{
		std::cout << e.GetDescription() << std::endl;
		return 1;
	}

	return 0;
}

```

---

<div class="post-metadata">

**Author:** ![pieper](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/pieper/32/8_2.png) [@pieper](https://discourse.slicer.org/u/pieper)\
**Post date:** [April 27, 2023, 2:21pm UTC](https://discourse.slicer.org/t/anyone-know-how-to-read-analyze-objectmap-files/29074/7 "2023-04-27T14:21:03Z")

</div>

Thanks very much for the advice everyone. 👍

Based on the clues given here, we were able to go back to Analyze and export both the CT image and the object map as Analyze 7.5 .hdr/.img files with the same pixel dimensions. Loading those in Slicer, the A/P direction was flipped and the label volume had identity IJKToRAS transform. Flipping in A/P and copying over the matrix with the python code below gave this image:

 ![image](https://us1.discourse-cdn.com/flex002/uploads/slicer/original/3X/c/d/cd4770c4fb624686b94a71ceff9b834fe74b1094.jpeg)

```auto
ctPath = "/opt/data/SlicerSpine/Muscle data/Muscle data/Muscle data/E15558S301.hdr"
objPath = "/opt/data/SlicerSpine/Muscle data/Muscle data/15558_ObjectMap.hdr"

ctNode = slicer.util.loadVolume(ctPath)
labelNode = slicer.util.loadLabelVolume(objPath)

ijkToRAS = vtk.vtkMatrix4x4()
ctNode.GetIJKToRASMatrix(ijkToRAS)
ijkToRAS.SetElement(1,1, -1 * ijkToRAS.GetElement(1,1))
ctNode.SetIJKToRASMatrix(ijkToRAS)
labelNode.SetIJKToRASMatrix(ijkToRAS)

```

Converting to a segmentation and using Fill between Slices gave this result, which looks promising. We have over 100 of these so we hope to be able to train a MONAI Label model to segment these muscles.

 ![image](https://us1.discourse-cdn.com/flex002/uploads/slicer/original/3X/0/2/024dd4ee68ee3607cd44a6f186ca9005a2e0cc41.jpeg)

I’ll also note that while the .hrd / .img files worked in the end, it looked like the same pixel data could be extracted using the objparser python code, but the header doesn’t include the pixel geometry so you need the .hdr of the source image for the object map to be of use (probably the ITK code could also work to get the pixels, but I didn’t have a chance to try). The object map file does include all the label identifiers so ultimately I will be using that information to assign the proper names to the muscles.
