如何将tif校准导入DM

how to import tif calibration into DM

我们需要在 DigitalMicrograph 中处理来自 FEI 和 Zeiss 工具的 SEM 图像。它们存储为 tif。 DigitalMicrograph 可以读取 2D tif,但图像在 X、Y 方向上未校准。是否有传输校准信息的导入插件?或者,我可以想象校准可以直接从流中变成红色。有没有人清楚这些数字存储在 tif 流中的偏移量?我对 tif 的组织不是很熟悉,我知道存在一些变化。特别是,FEI 和 Zeiss tifs 的组织方式似乎有所不同。

TIF 或 TIFF (Tagged Image File Format) 是相当通用的格式,其中任何附加信息都可以作为 "Tag" 存储在文件中。 (虽然这与 DM 中的标签不同。) 究竟写入标签的内容(以及写入位置)取决于写入文件的软件,即本例中的 FEI。我假设物理校准已写入这些标签,但我没有要测试的文件。 (你可以上传一个到你的问题吗?

我确实相信,但还没有检查过,DM 在 TIFF 导入时将所有或部分标签写入其自己的 TagGroup 结构中。你检查过了吗? (即,如果您通过 DM 从 FEI 导入一个 TIFF 文件并转到 "Image Display -> Tags",您会看到什么? 可能是,校准所需的信息就在那里,您可以编写一个简单的脚本来利用它进行校准。

或者,通常可以在文本编辑器中打开 TIFF 文件,以查看 - 除了图像的大量二进制文件 - ASCII 文本中的标签。这将使您了解内容存储在此特定 TIFF 文件中的位置。

有了这些知识,您就可以使用 RAW 流媒体命令找到相应的信息,并编写一个导入脚本来复制校准。

FEI 和 ZEISS 似乎都将校准信息存储在他们自己的自定义 ASCII 文本标签中。按照 TIFF Format specifications (PDF) 可以轻松编写一个脚本,从 TIFF 中提取所有 ASCII 字段。 从那里开始,必须执行文本操作才能进行校准并将其设置为图像。

下面的脚本使用以下信息对 FEI 和 ZEISS 图像执行此操作:

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 在 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()

编辑 2021:我还写了一篇相关的 script for full TIFF import 作为对一个单独问题的回答。

FEI 对每个工具集使用不同的 TIF 格式(SEM 与 TEM,两者都依赖于版本)。 TIA 的情况也是如此,新版本破坏了广泛使用的 SerReader 脚本。如果需要,我可以 post 对此进行简单的修复。

TEM 的 FEI TIFF 将所有元数据存储在格式为 XML 的单个标签中。比例尺也有单独的标签,但它的格式也为 XML。我对上面 BmyGuest 的代码做了一个小调整来读取比例。必须解析其他元数据。

// Auxilliary method for stream-reading of values
// BmyGuest's March 10, 2016 code modified to read FEI TEM TIF
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 scaletemp
    number scalestart, scaleend
    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:<X unit", scaletemp ) )
    {
        unitX = "nm"
        scalestart = scaletemp.find("\">") + 2
        scaleend = scaletemp.find("</X>")
        scaleX = val(scaletemp.mid(scalestart,scaleend-scalestart))*1e9
    }
    if ( imported.GetStringNote( "TIFF Tags:<Y unit", scaletemp ) )
    {
        unitY = "nm"
        scalestart = scaletemp.find("\">") + 2
        scaleend =scaletemp.find("</Y>")
        scaleY = val(scaletemp.mid(scalestart,scaleend-scalestart))*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()