Search code examples
imageoffsettiffcalibrationdm-script

How to import tif calibration from TVIPS camera into DM


I am currently using TVIPS camera with the software EM-menu to get TEM images. When I analyze the data (TIF files) using DigitalMicrograph (DM), some problem appears because the calibration is unavailable to DM. I am aware that a similar question has been answered before: how to import tif calibration into DM. But the calibrations of the TIF files are stored in X Resolution and Y Resolution (Rational type, the values are identical), which is different from FEI and Zeiss. I tried to modify the code in the how to import tif calibration into DM, but what I got is the offset of X Resolution and Y Resolution, instead of the real value. I am not familiar with how to assign the values of the specific offset (in this case, the offset is 82110 for X Resolution and 82118 for Y Resoltuion) in TIF files to DM. Below is the code I modified from the question noted. Any suggestion is much appreciated. Raw TIF file is provided to help address the question.

// Auxilliary method for stream-reading of values
// BmyGuest's March 10, 2016 code modified to read FEI TEM TIF
// Import and calibrate TVIPS Tiff images
number ReadValueOfType(object fStream, string type, number byteOrder)
{
    number val = 0
    TagGroup tg = NewTagGroup()
    if ( type == "bool" )
    {
        tg.TagGroupSetTagAsBoolean( type, 0 )
        tg.TagGroupReadTagDataFromStream( type, fstream, byteOrder ) 
        tg.TagGroupGetTagAsBoolean( type, val )
    }
    else if ( type == "uint16" )
    {
        tg.TagGroupSetTagAsUInt16( type, 0 )
        tg.TagGroupReadTagDataFromStream( type, fstream, byteOrder ) 
        tg.TagGroupGetTagAsUInt16( type, val )
    }
    else if ( type == "uint32" )
    {
        tg.TagGroupSetTagAsUInt32( type, 0 )
        tg.TagGroupReadTagDataFromStream( type, fstream, byteOrder ) 
        tg.TagGroupGetTagAsUInt32( type, val )
    }
    else Throw("Invalid read-type:"+type)
    return val
}

string ExtractTextFromTiff( string path )
{
    string txt
    if ( !DoesFileExist(path) ) 
        Throw("File not found.\n"+path)

    // Open Stream 
    number fileID = OpenFileForReading( path )
    object fStream = NewStreamFromFileReference(fileID,1)

    // Read data byte order. (1 = big Endian, 2= little Endian for Gatan)
    number val
    number byteOrder = 0
    val = fStream.ReadValueOfType( "uint16", byteOrder )
    byteOrder = ( 0x4949 == val  ) ? 2 : ( 0x4D4D == val ? 1 : 0 )
    //Result("\n TIFF endian:"+byteOrder)

    // Verify TIFF image
    val = fStream.ReadValueOfType( "uint16", byteOrder )
    if ( val != 42 ) Throw( "Not a valid TIFF image" )

    // Browse all directories
    number offset = fStream.ReadValueOfType( "uint32", byteOrder )

    while( 0 != offset )
    {
        fStream.StreamSetPos( 0, offset ) // Start of IFD
        number nEntries = fStream.ReadValueOfType( "uint16", byteOrder )
        for ( number e=0;e<nEntries;e++)
        {
            number tag        = fStream.ReadValueOfType( "uint16", byteOrder )
            number typ        = fStream.ReadValueOfType( "uint16", byteOrder )
            number count      = fStream.ReadValueOfType( "uint32", byteOrder )
            number dataOffset = fStream.ReadValueOfType( "uint32", byteOrder )
            Result("\n entry # "+e+": ID["+tag+"]\ttyp="+typ+"\tcount="+count+"\t offset @ "+dataOffset)
            if ( 5 == typ ) // Rational
            {
                number currentPos = fStream.StreamGetPos()
                fStream.StreamSetPos( 0, dataOffset )
                string textField = fStream.StreamReadAsText( 0, count )
                txt+=textField
                fStream.StreamSetPos( 0, currentPos )
            }   
        }
        offset = fStream.ReadValueOfType( "uint32", byteOrder ) // this is 0000 for the last directory according to spec
    }

    return txt
}

String TruncWhiteSpaceBeforeAndAfter( string input )
{
    string work = input
    if ( len(work) == 0 ) return ""
    while ( " " == left(work,1) )
    {
        work = right( work, len(work) - 1 )
        if ( len(work) == 0 ) return "" 
    }
    while ( " " == right(work,1) )
    {
        work = left( work, len(work) - 1 )
        if ( len(work) == 0 ) return "" 
    }
    return work
}


// INPUT:  String with line-wise information
// OUTPUT: TagGroup
// Assumptions:  
//  - Groups are specified in a line in the format:             [GroupName]
//  - The string contains information line-wise in the format:  KeyName=Vale
TagGroup CreateTagsFromString( string input )
{
    TagGroup tg = NewTagGroup()
    string work = input

    string eoL          = "\n"
    string GroupLeadIn  = "["
    string GroupLeadOut = "]"
    string keyToValueSep= "="
    string groupName = ""

    number pos = find(work,eoL )
    while( -1 != pos )
    {
        string line = left(work,pos)
        work = right(work,len(work)-pos-len(eoL))
        number leadIn  = find(line,GroupLeadIn)
        number leadOut = find(line,GroupLeadOut)
        number sep = find(line,keyToValueSep)
        if ( ( -1 < leadIn ) && ( -1 < leadOut ) && ( leadIn < leadOut ) ) // Is it a new group? "[GROUPNAME]"
        {
            groupName = mid(line,leadIn+len(GroupLeadIn),leadOut-leadIn-len(GroupLeadOut))
            groupName = TruncWhiteSpaceBeforeAndAfter(groupName)
        }
        else if( -1 < sep )                                                 // Is it a value? "KEY=VALUE" ?
        {
            string key  = left(line,sep)
            string value= right(line,len(line)-sep-len(keyToValueSep))
            key   = TruncWhiteSpaceBeforeAndAfter(key)
            value = TruncWhiteSpaceBeforeAndAfter(value)
            string tagPath = groupName + ( "" == groupName ? "" : ":" ) + key
            tg.TagGroupSetTagAsString( tagPath, value )
        }
        pos = find(work,eoL)        
    }
    return tg
}

void ImportTIFFWithTags()
{
    string path = GetApplicationDirectory("open_save",0)
    if (!OpenDialog(NULL,"Select TIFF file",path, path)) exit(0)

    string extractedText = ExtractTextFromTiff(path)
    /*
    if ( TwoButtonDialog("Show extracted text?","Yes","No") )
        result(extractedtext)
    */

    tagGroup infoAsTags = CreateTagsFromString(extractedText )
    /*
    if ( TwoButtonDialog("Output tagstructure?","Yes","No") )
        infoAsTags.TagGroupOpenBrowserWindow(path,0)
    */
result(extractedtext)
//result(infoAsTags) 
// infoAsTags is blank. ZZ
    // Import data and add info-tags
    image imported := OpenImage(path)
    imported.ImageGetTagGroup().TagGroupSetTagAsTagGroup("TIFF Tags",infoAsTags)
    imported.ShowImage()

    // Calibrate image, if info is found
    // It seems FEI stores this value as [m] in the tags PixelHeight and PixelWidth
    // while ZEISS images contain the size of the FOV in the tags "Height" and "Width" as string including unit
    number scaleX = 0
    number scaleY = 0
    string unitX 
    string unitY
    string scaletemp
    number scalestart, scaleend
    string hStr
    string wStr
    if ( imported.GetNumberNote("TIFF Tags:XResolution", scaleX ) )
    {
        unitX = "nm"
        scaleX = 1e7/scaleX
    }
    if ( imported.GetNumberNote("TIFF Tags:YResolution", scaleY ) )
    {
        unitY = "nm"
        scaleY = 1e7/scaleY
    }
        /*
    if ( imported.GetStringNote("TIFF Tags:<X unit", scaletemp ) )
    {
        unitX = "nm"
        scalestart = scaletemp.find("\">") + 2
        scaleend = scaletemp.find("</X>")
        scaleX = 1e7/val(scaletemp.mid(scalestart,scaleend-scalestart))
    }
    if ( imported.GetStringNote("TIFF Tags:<Y unit", scaletemp ) )
    {
        unitY = "nm"
        scalestart = scaletemp.find("\">") + 2
        scaleend =scaletemp.find("</Y>")
        scaleY = 1e7/val(scaletemp.mid(scalestart,scaleend-scalestart))
    }
        */
        /*
    if ( imported.GetStringNote("TIFF Tags:Width", wStr ) )
    {
        number pos = find( wStr, " " )
        if ( -1 < pos )
        {
            scaleX = val( left(wStr,pos) )
            scaleX /= imported.ImageGetDimensionSize(0)
            unitX  = right( wStr, len(wStr)-pos-1 )
        }
    }
    if ( imported.GetStringNote("TIFF Tags:Height", hStr ) )
    {
        number pos = find( hStr, " " )
        if ( -1 < pos )
        {
            scaleY = val( left(hStr,pos) )
            scaleY /= imported.ImageGetDimensionSize(1)
            unitY  = right( hStr, len(hStr)-pos-1 )
        }
    }
        */
    if (0 < scaleX )
    {
        imported.ImageSetDimensionScale(0,scaleX)
        imported.ImageSetDimensionUnitString(0,unitX)
    }
    if (0 < scaleY )
    {
        imported.ImageSetDimensionScale(1,scaleY)
        imported.ImageSetDimensionUnitString(1,unitY)
    }

result("\n" + scaleX + "\n")
result(unitX)
// imported.ImageSetDimensionUnitString(0,unitX)
}
ImportTIFFWithTags()

Solution

  • Okay, following the information found here, the XResolution and YResolution tags have the ID of 282 and 283, respectively.

    Using the template script from above and just looking at the informative text output when running on your example data, one gets:

     entry # 0: ID[256] typ=4   count=1  offset @ 4096
     entry # 1: ID[257] typ=4   count=1  offset @ 4096
     entry # 2: ID[258] typ=3   count=1  offset @ 16
     entry # 3: ID[259] typ=3   count=1  offset @ 1
     entry # 4: ID[262] typ=3   count=1  offset @ 1
     entry # 5: ID[273] typ=4   count=4096   offset @ 50970
     entry # 6: ID[278] typ=4   count=1  offset @ 1
     entry # 7: ID[279] typ=4   count=4096   offset @ 67354
     entry # 8: ID[282] typ=5   count=1  offset @ 83738
     entry # 9: ID[283] typ=5   count=1  offset @ 83746
     entry # 10: ID[296]    typ=3   count=1  offset @ 3
     entry # 11: ID[339]    typ=3   count=1  offset @ 1
     entry # 12: ID[37706]  typ=4   count=1  offset @ 83754
     entry # 13: ID[37707]  typ=1   count=1616   offset @ 49168
     entry # 14: ID[37708]  typ=7   count=6312   offset @ 83754
    

    So you can see, that your TIFF image has 15 directory entries, and that indeed tags with ID 282 and 283 exist and are of type 5. Which (again using the source here) should be of type rational, just as you've commented in the modified script. The type is defined as two long (int32) values.

    So, the overall TIFF structure browsing is successful, and you just need to adapt the section of reading out the tag. You've already filtered for type 5, but it it is possibly better to additionally filter for the ID values as well. Then you need to read out the values. They are no longer text, so the original script uses the incorrect commands. Essentially, instead of

    if ( 2 == typ ) // ASCII
    {
        number currentPos = fStream.StreamGetPos()
        fStream.StreamSetPos( 0, dataOffset )
        string textField = fStream.StreamReadAsText( 0, count )
        txt+=textField
        fStream.StreamSetPos( 0, currentPos )
    }     
    

    your want to do

    if ( 5 == typ ) // Rational (2 int32 values)
    {
        number currentPos = fStream.StreamGetPos() // Remember Stream Pos
        fStream.StreamSetPos( 0, dataOffset ) // Set Stream to offset value as specified
        number n1,n2
        if ( 282 == tag )  // XResolution
        {
            n1 = fStream.ReadValueOfType( "long", byteOrder ) // Read long
            n2 = fStream.ReadValueOfType( "long", byteOrder ) // continue to read next long
            txt += "XResoltion:" + n1 + " / " + n2
        }
        else if ( 283 == tag )  // YResolution
        {
            n1 = fStream.ReadValueOfType( "long", byteOrder )
            n2 = fStream.ReadValueOfType( "long", byteOrder )
            txt += "YResoltion:" + n1 + " / " + n2
        }
        fStream.StreamSetPos( 0, currentPos )
    }   
    

    Note, that ReadValueOfType really is just a self-made convenience command defined in the script. The underlying DM-script technique is to create a TagGroup object of specific type and use that as a proxy in the TagGroupReadTagDataFromStream command. The original script didn't have that for values of type long, so you need to expand this function, i.e.:

    else if ( type == "long" )
    {
        tg.TagGroupSetTagAsLong( type, 0 )
        tg.TagGroupReadTagDataFromStream( type, fstream, byteOrder ) 
        tg.TagGroupGetTagAsLong( type, val )
    }
    

    You possibly also want to restructure the whole script a bit, I guess, as you do not need to read out those values and then convert them into text etc. So a streamlined adjusted script might look something like this:

    // Auxilliary method for stream-reading of values
    number ReadValueOfType(object fStream, string type, number byteOrder)
    {
        number val = 0
        TagGroup tg = NewTagGroup()
        if ( type == "bool" )
        {
            tg.TagGroupSetTagAsBoolean( type, 0 )
            tg.TagGroupReadTagDataFromStream( type, fstream, byteOrder ) 
            tg.TagGroupGetTagAsBoolean( type, val )
        }
        else if ( type == "uint16" )
        {
            tg.TagGroupSetTagAsUInt16( type, 0 )
            tg.TagGroupReadTagDataFromStream( type, fstream, byteOrder ) 
            tg.TagGroupGetTagAsUInt16( type, val )
        }
        else if ( type == "uint32" )
        {
            tg.TagGroupSetTagAsUInt32( type, 0 )
            tg.TagGroupReadTagDataFromStream( type, fstream, byteOrder ) 
            tg.TagGroupGetTagAsUInt32( type, val )
        }
        else if ( type == "long" )
        {
            tg.TagGroupSetTagAsLong( type, 0 )
            tg.TagGroupReadTagDataFromStream( type, fstream, byteOrder ) 
            tg.TagGroupGetTagAsLong( type, val )
        }
        else Throw("Invalid read-type:"+type)
        return val
    }
    
    number ExtractRationalTagOfIDFromTiff( string path, number ID, number &n1, number &n2, number ShowTIFFInfo )
    {
        string txt
        if ( !DoesFileExist(path) ) 
            Throw("File not found.\n"+path)
    
        // Open Stream 
        number fileID = OpenFileForReading( path )
        object fStream = NewStreamFromFileReference(fileID,1)
    
        // Read data byte order. (1 = big Endian, 2= little Endian for Gatan)
        number val
        number byteOrder = 0
        val = fStream.ReadValueOfType( "uint16", byteOrder )
        byteOrder = ( 0x4949 == val  ) ? 2 : ( 0x4D4D == val ? 1 : 0 )
        if ( ShowTIFFInfo )
            Result("\n TIFF endian:"+byteOrder)
    
        // Verify TIFF image
        val = fStream.ReadValueOfType( "uint16", byteOrder )
        if ( val != 42 ) Throw( "Not a valid TIFF image" )
    
        // Browse all directories
        number offset = fStream.ReadValueOfType( "uint32", byteOrder )
    
        number success = 0
        while( 0 != offset )
        {
            fStream.StreamSetPos( 0, offset ) // Start of IFD
            number nEntries = fStream.ReadValueOfType( "uint16", byteOrder )
            for ( number e=0;e<nEntries;e++)
            {
                number tag        = fStream.ReadValueOfType( "uint16", byteOrder )
                number typ        = fStream.ReadValueOfType( "uint16", byteOrder )
                number count      = fStream.ReadValueOfType( "uint32", byteOrder )
                number dataOffset = fStream.ReadValueOfType( "uint32", byteOrder )
                if ( ShowTIFFInfo )
                    Result("\n entry # "+e+": ID["+tag+"]\ttyp="+typ+"\tcount="+count+"\t offset @ "+dataOffset)
    
                if ( ( ID == tag )  && ( 5 == typ ) ) // Rational (2 long values)
                {               
                    number currentPos = fStream.StreamGetPos()
                    fStream.StreamSetPos( 0, dataOffset )
                    n1 = fStream.ReadValueOfType( "long", byteOrder )
                    n2 = fStream.ReadValueOfType( "long", byteOrder )
                    success = 1
                    fStream.StreamSetPos( 0, currentPos )
    
                    if ( ShowTIFFInfo )
                        Result( " ==>" + n1 + " / " + n2 )
                }   
            }
            offset = fStream.ReadValueOfType( "uint32", byteOrder ) // this is 0000 for the last directory according to spec
        }
    
        return success
    }
    
    // Import and calibrate TVIPS Tiff images
    void ImportCalibratedTVIPS_TIFF()
    {
        string path = GetApplicationDirectory("open_save",0)
        if (!OpenDialog(NULL,"Select TVIPS TIFF file",path, path)) exit(0)
    
        // Import data 
        image imported := OpenImage(path)
        imported.ShowImage()
    
        // Calibrate image, stored as XResolution and YResolution tags
        number n1,n2
        number scaleX = 0
        number scaleY = 0
        if ( ExtractRationalTagOfIDFromTiff( path, 282,  n1, n2, 1 ) )
        {
            scaleX = n1/n2
            Result("\n X Resolution:" + Format( scaleX, "%g" ))
        }
        else
        {
            Result("\n X Resolution: NOT FOUND")
        }
        if ( ExtractRationalTagOfIDFromTiff( path, 283,  n1, n2, 0 ) )
        {
            scaleY = n1/n2
            Result("\n Y Resolution:" + Format( scaleY , "%g" ))
        }
        else
        {
            Result("\n Y Resolution: NOT FOUND")
        }
        if ( 0 != scaleX )
            imported.ImageSetDimensionScale( 1, scaleX )
        if ( 0 != scaleY )
            imported.ImageSetDimensionScale( 1, scaleY )
    }
    
    clearResults()
    ImportCalibratedTVIPS_TIFF()
    

    Running the script on your image data, I get:

     X Resolution:3.90786e+08
     Y Resolution:3.90786e+08
    

    I don't know the unit that calibration is supposed to be in, but the value seems a bit high... (in particular for an TEM image)? It is, however, the same for X and Y as you've specified it should be.


    As an aside, the provided TIFF images seems to contain the following meta-info:

    ID[256] = Image Size X
    ID[257] = Image Size Y
    ID[258] = BitsPerSample
    ID[259] = Compression
    ID[262] = PhotometricInterpretation
    ID[273] = StripOffsets
    ID[278] = RowsPerStrip
    ID[279] = StripByteCounts
    ID[282] = XResolution
    ID[283] = YResolution
    ID[296] = ResolutionUnit
    ID[339] = SampleFormat
    ID[37706] = ????
    ID[37707] = ????
    ID[37708] = ????
    

    Reading out the resolution unit from your image gives:
    Resolution Unit:24576