Search code examples
imageimporttiffdm-script

how to import tif calibration into DM


We need to treat the SEM images from FEI and Zeiss tools in DigitalMicrograph. They are stored as tif. DigitalMicrograph can read 2D tif but images appear uncalibrated in X,Y directions. Is there any import plugIn that transfers the calibration information ? Alternatively, I can imagine that the calibration can be red directly from a stream. Has anyone a clear idea about the offset where such numbers are stored in a stream of tif? I am not very familiar with organization of tif and I know some variations exist. In particular, FEI and Zeiss tifs seems to be organized differently.


Solution

  • Both FEI and ZEISS seem to store the calibration info in their own, custom ASCII text tags. Following the TIFF Format specifications (PDF) one can easily write a script which extracts all ASCII fields from the TIFF. From there on, one has to perform text-manipulations to get to the calibrations and set them to the image.

    The script below does this for both FEI and ZEISS images, using the following information:

    FEI

    The size information is in the text in Form:
    [Scan]
    PixelWidth=8.26823e-010
    PixelHeight=8.26823e-010
    This specifies the pixel size in [meter]

    ZEISS

    The size information is in the text in Form:
    AP_HEIGHT
    Height = 343.0 nm
    AP_WIDTH
    Width = 457.3 nm
    This specifies the FOV size.


    Script on PasteBin.


    // 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 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 ( 2 == typ ) // ASCII
                {
                    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)
        */
        
        // 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 hStr
        string wStr
        if ( imported.GetNumberNote( "TIFF Tags:Scan:PixelWidth", scaleX ) )
        {
            unitX = "nm"
            scaleX *= 1e9
        }
        if ( imported.GetNumberNote( "TIFF Tags:Scan:PixelHeight", scaleY ) )
        {
            unitY = "nm"
            scaleY *= 1e9
        }
        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)
        }
    }
    ImportTIFFWithTags()
    

    Edit 2021: I've also written a related script for full TIFF import posted as answer to a separate question.