Yes, it is doable but not trivial. I've found an old script of mine in which I was doing that. I'm no longer in the position to test it, so I'm not 100% certain it will work.
Also, it is a bit too long for posting here, so I've also put it under this pastebin link.
// Some constants (See TIFF Format)
number kLittleEndianTIFF = 0x4949
number kBigEndianTIFF = 0x4D4D
number kMagicIDNumberTIFF = 42
number FALSE = 0
number TRUE = 1
Interface I_ImportTiffWithTags
{
// Aux. method to simplify single value read from stream
number ReadValueOfType( object self, string type );
// Aux. method to strip multiple pre- and post- string occurances from a string
string ClipText( object self, string input, string leadIn, string leadOut, number doMultiple );
string ClipTextWhiteSpaces( object self, string input );
// Aux. method to convert ASCII text of specific format into a TagStructure
TagGroup CreateTagsFromString( object self, string input, string entrySep, string keyToValueSep, string GroupLeadIn, string GroupLeadOut );
TagGroup CreateTagsFromString_FEI_Default( object self, string input );
TagGroup CreateTagsFromString_ZEISS_Default( object self, string input );
// Get all TIFF tags from file in path as tagGroup
TagGroup GetTIFFTagsAsTG( object self, string path, number doTextFields, number doSingleValues, number includedSkipped );
TagGroup GetTIFFTagsAsTG( object self, string path, number includedSkipped ); // doTextField = true, doSingleValues = true
TagGroup GetTIFFTagsAsTG( object self, string path ); // doTextField = true, doSingleValues = true, includedSkipped = false
// Get all ASCII TIFF tags as single string
String GetTIFFAscii( object self, string path, number splitSeparateTags );
String GetTIFFAscii( object self, string path); // splitSeparateTags = true
// Import Image and append TIFF tags as TagGroup "TIFF Tags" to image
image OpenTIFFWithTags( object self, string path );
// Import TIFF Image stored by FEI SEMs, import info from ASCII in image, calibrate if possible
image OpenFEI_TIFF( object self, string path, number withTiffTags );
image OpenFEI_TIFF( object self, string path );
// Import TIFF Image stored by ZEISS SEMs, import info from ASCII in image, calibrate if possible
image OpenZEISS_TIFF( object self, string path, number withTiffTags );
image OpenZEISS_TIFF( object self, string path);
// Aux. to find calibration from the tags, format specific
number GetCalibrationFromTags_FEI( object self, tagGroup FEItgs, number &sx, number &ox, string &ux, number &sy, number &oy, string &uy );
number GetCalibrationFromTags_ZEISS( object self, tagGroup ZEISStags, number &sx, number &ox, string &ux, number &sy, number &oy, string &uy );
}
Class CImportTIFFWithTags
{
object fStream
number byteOrder
number verbose
number kMaxTextShow
CImportTIFFWithTags( object self )
{
verbose = FALSE
kMaxTextShow = 20
}
number ReadValueOfType( object self, string type )
{
if ( !fStream.ScriptObjectIsValid() ) Throw( "Invalid file stream." )
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 == "uint8" )
{
string str = fStream.StreamReadAsText(0,1)
val = asc(str)
}
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 == "float" )
{
tg.TagGroupSetTagAsFloat( type, 0 )
tg.TagGroupReadTagDataFromStream( type, fstream, byteOrder )
tg.TagGroupGetTagAsFloat( type, val )
}
else if ( type == "double" )
{
tg.TagGroupSetTagAsDouble( type, 0 )
tg.TagGroupReadTagDataFromStream( type, fstream, byteOrder )
tg.TagGroupGetTagAsDouble( type, val )
}
else Throw("Invalid read-type:"+type)
return val
}
string ClipText( object self, string input, string leadIn, string leadOut, number doMultiple )
{
string work = input
if ( len(leadIn) <= len(work) )
{
while ( leadIn == left(work,len(leadIn)) )
{
work = right( work, len(work) - len(leadIn) )
if ( !doMultiple ) break
if ( len(work) < len(leadin) ) break
}
}
if ( len(leadOut) <= len(work) )
{
while ( leadOut == right(work,len(leadout)) )
{
work = left( work, len(work) - len(leadOut) )
if ( !doMultiple ) break
if ( len(leadOut) <= len(work) ) break
}
}
return work
}
string ClipTextWhiteSpaces( object self, string input )
{
return self.ClipText( input, " ", " ", TRUE );
}
TagGroup CreateTagsFromString( object self, string input, string entrySep, string keyToValueSep, string GroupLeadIn, string GroupLeadOut )
{
TagGroup tg = NewTagGroup()
number bCheckGroup = ( len(GroupLeadIn) && len(GroupLeadOut) ) // true if groupLeads are specified
string work = input
string groupName = ""
if ( "" == entrySep )
entrySep = "\n"
number pos = find( work, entrySep )
number skipped = 0
while( -1 != pos )
{
//OKDialog("work:"+work)
//OKDialog("Entry:"+len(entrySep) )
string entry = left( work, pos )
work = right( work, len(work) - len(entry) - len(entrySep) + 1)
number sep = find( entry, keyToValueSep )
if( -1 < sep )
{
// Entry matches format "KEY=VALUE" with '=' being the defined entrySep
string key = left( entry, sep )
string value= right( entry, len(entry) - sep - len(keyToValueSep) )
// Truncate trailing white spaces
key = self.ClipTextWhiteSpaces(key)
value = self.ClipTextWhiteSpaces(value)
string tagPath = groupName + ( "" == groupName ? "" : ":" ) + key
tg.TagGroupSetTagAsString( tagPath, value )
}
else if ( bCheckGroup )
{
// Entry does not match format "KEY=VALUE", check if it is a groupname
number leadIn = find( entry, GroupLeadIn )
number leadOut = find( entry, GroupLeadOut )
if ( ( -1 < leadIn ) && ( -1 < leadOut ) && ( leadIn < leadOut ) ) // Is it a new group? "[GROUPNAME]"
{
groupName = mid( entry, leadIn + len(GroupLeadIn), leadOut - leadIn - len(GroupLeadOut) )
}
else
skipped++
}
else
{
skipped++
}
// Find next entry
pos = find( work, entrySep )
}
if ( 0 != len(work) )
skipped++
if ( verbose )
{
if ( skipped )
Result( "\nText To Tag skipped " + skipped + " lines.\n" )
}
return tg
}
TagGroup CreateTagsFromString_FEI_Default( object self, string input )
{
return self.CreateTagsFromString( input, "\n" , "=", "[", "]" )
}
TagGroup CreateTagsFromString_ZEISS_Default( object self, string input )
{
return self.CreateTagsFromString( input, "\n" , "=", "", "" )
}
TagGroup GetTIFFTagsAsTG( object self, string path, number doTextFields, number doSingleValues, number includedSkipped )
{
if ( !DoesFileExist( path ) ) Throw( "File not found.\n"+path)
// Open Stream
number fileID = OpenFileForReading( path )
fStream = NewStreamFromFileReference(fileID,1)
// Find Byte Order
number val
byteOrder = 0
val = self.ReadValueOfType( "uint16" )
byteOrder = ( kLittleEndianTIFF == val ) ? 2 : ( kBigEndianTIFF == val ? 1 : 0 )
// Verify TIFF image
val = self.ReadValueOfType( "uint16" )
if ( val != kMagicIDNumberTIFF ) Throw( "Not a valid TIFF image" )
// Find first directory start
number offset = self.ReadValueOfType( "uint32" )
// Browse all directories
number nIFD = 0
TagGroup AllTiffTagsTGlist = NewTagList()
TagGroup TiffTGs
while( 0 != offset )
{
// Set to start of directory and read it in
nIFD++
TiffTGs = NewTagGroup()
if ( verbose ) Result( "Reading IFD #" + nIFD + " at \t" + offset )
fStream.StreamSetPos( 0, offset )
number nEntries = self.ReadValueOfType( "uint16" )
for ( number e=0; e<nEntries; e++ )
{
number tag = self.ReadValueOfType( "uint16" )
string tagStr = "ID("+tag+")"
number typ = self.ReadValueOfType( "uint16" )
number count = self.ReadValueOfType( "uint32" )
number dataOffset = self.ReadValueOfType( "uint32" )
if ( verbose )
Result( "\n DirEntry # "+Format(e,"%3.0f") + ": TiffIDTag: " + tag + "]ttyp: " + typ + "\tcount: " + count )
// Read only, if it is either a single value or a string
if ( ( doTextFields ) && ( 2 == typ ) )// ASCII
{
number currentPos = fStream.StreamGetPos()
fStream.StreamSetPos( 0, dataOffset )
string textField = fStream.StreamReadAsText( 0, count )
fStream.StreamSetPos( 0, currentPos )
if ( verbose ) Result( "\t value: " + left( textField, min(len(textField), kMaxTextShow ) ) )
TiffTGs.TagGroupSetTagAsString( tagStr, textField )
}
else if ( ( doSingleValues ) && ( 1 == count ) ) // Single Value.
{
// Note that the 'dataOffset'-4-bytes are already the value for data fitting in 4-bytes!
// Otherwise, it specifies the staring offset for the value
if ( 1 == typ ) // uInt8
{
TiffTGs.TagGroupSetTagAsShort( tagStr, dataOffset )
if ( verbose ) Result("\t value:" + dataOffset )
}
else if ( 3 == typ ) // uInt16
{
TiffTGs.TagGroupSetTagAsUInt16( tagStr, dataOffset )
if ( verbose ) Result("\t value:" + dataOffset )
}
else if ( 4 == typ ) // uInt32
{
TiffTGs.TagGroupSetTagAsUInt32( tagStr, dataOffset )
if ( verbose ) Result("\t value:" + dataOffset )
}
else if ( 5 == typ ) // uInt32 / uInt32 (rational)
{
number currentPos = fStream.StreamGetPos()
number val1 = self.ReadValueOfType( "uint32" )
number val2 = self.ReadValueOfType( "uint32" )
TiffTGs.TagGroupSetTagAsLongPoint( tagStr, val1, val2 )
fStream.StreamSetPos( 0, currentPos )
if ( verbose ) Result("\t value:" + val1 + "/" + val2 )
}
else if ( 11 == typ ) // float
{
TiffTGs.TagGroupSetTagAsFloat( tagStr, dataOffset )
if ( verbose ) Result("\t value:" + dataOffset )
}
else if ( 12 == typ ) // double
{
number currentPos = fStream.StreamGetPos()
number val = self.ReadValueOfType( "double" )
TiffTGs.TagGroupSetTagAsDouble( tagStr, val )
fStream.StreamSetPos( 0, currentPos )
if ( verbose ) Result("\t value:" + val )
}
else if ( includedSkipped )
{
if ( verbose ) Result("\t value: SKIPPED DATA TYPE" )
TiffTGs.TagGroupSetTagAsString( tagStr, "Not imported, TagType (TIFF "+typ+")" )
}
}
else if ( includedSkipped )
{
// Multiple value entries
if ( verbose ) Result("\t value: Multiple values. Arrays are not read." )
TiffTGs.TagGroupSetTagAsString( tagStr, "Not imported, TagType (TIFF "+typ+") "+count+" values" )
}
}
// Read next directory offset.
// This is 0000 for the last directory according to spec
offset = self.ReadValueOfType( "uint32" )
AllTiffTagsTGlist.TagGroupAddTagGroupAtEnd(TiffTGs)
}
// Return list if entries or just one group if there is one directory only
if ( 1 < nIFD )
return AllTiffTagsTGlist
else
return TiffTGs
}
TagGroup GetTIFFTagsAsTG( object self, string path, number includedSkipped )
{
return self.GetTIFFTagsAsTG( path, TRUE, TRUE, includedSkipped )
}
TagGroup GetTIFFTagsAsTG( object self, string path )
{
return self.GetTIFFTagsAsTG( path, TRUE, TRUE, FALSE )
}
String GetTIFFAscii( object self, string path, number splitSeparateTags )
{
String allText
TagGroup tgs = self.GetTIFFTagsAsTG(path, TRUE, FALSE, FALSE )
number nTags = tgs.TagGroupCountTags()
for ( number n=0; n<nTags; n++ )
{
string label = tgs.TagGroupGetTagLabel(n)
if ( splitSeparateTags )
{
if ( 1<nTags )
allText += (n?"\n":"")+"TIFF Tag " + label + ":\n"
}
string text
if ( tgs.TagGroupGetTagAsString( label, text ) )
allText += text + "\n"
}
return allText
}
String GetTIFFAscii( object self, string path)
{
return self.GetTIFFAscii( path, true )
}
image OpenTIFFWithTags( object self, string path )
{
image Imported := OpenImage( path )
TagGroup TIFFTags = self.GetTIFFTagsAsTG(path, TRUE, TRUE, TRUE)
Imported.ImageGetTagGroup().TagGroupSetTagAsTagGroup( "TIFF Tags", TIFFTags )
return Imported
}
image OpenFEI_TIFF( object self, string path, number withTiffTags )
{
image Imported := OpenImage( path )
if ( withTiffTags )
{
TagGroup TIFFTags = self.GetTIFFTagsAsTG(path, TRUE, TRUE, FALSE )
Imported.ImageGetTagGroup().TagGroupSetTagAsTagGroup( "TIFF Tags", TIFFTags )
}
string ASCIITags = self.GetTIFFAscii(path)
TagGroup FEITags = self.CreateTagsFromString_FEI_Default( ASCIITags )
Imported.ImageGetTagGroup().TagGroupSetTagAsTagGroup( "Info", FEITags )
number sx, sy, ox, oy
string ux, uy
if ( self.GetCalibrationFromTags_FEI(FEITags, sx, ox, ux, sy, oy, uy ) )
{
Imported.ImageSetDimensionCalibration(0,ox,sx,ux,0)
Imported.ImageSetDimensionCalibration(1,oy,sy,uy,0)
}
else
{
if ( verbose )
Result( "\n FEI calibration not found. Image remains uncalibrated." )
}
return Imported
}
number GetCalibrationFromTags_FEI( object self, tagGroup FEItags, number &sx, number &ox, string &ux, number &sy, number &oy, string &uy )
{
if ( !FEItags.TagGroupIsValid() ) return FALSE
number foundSomething = FALSE
ox=0
oy=0
sx=1
sy=1
ux=""
uy=""
number value
if ( FEITags.TagGroupGetTagAsNumber( "Scan:PixelWidth", value ) )
{
foundSomething = TRUE
// [value] is in [m], scale to nm or um
if ( value < 1e-6 )
{
if ( value < 1e-9 )
{
ux = "nm"
sx = value * 1e9
}
else
{
ux = "µm"
sx = value * 1e6
}
}
}
if ( FEITags.TagGroupGetTagAsNumber( "Scan:PixelHeight", value ) )
{
foundSomething = TRUE
// [value] is in [m], scale to nm or um
if ( value < 1e-6 )
{
if ( value < 1e-9 )
{
uy = "nm"
sy = value * 1e9
}
else
{
uy = "µm"
sy = value * 1e6
}
}
}
return foundSomething
}
image OpenFEI_TIFF( object self, string path )
{
return self.OpenFEI_TIFF( path, FALSE )
}
image OpenZEISS_TIFF( object self, string path, number withTiffTags )
{
image Imported := OpenImage( path )
if ( withTiffTags )
{
TagGroup TIFFTags = self.GetTIFFTagsAsTG(path, TRUE, TRUE, FALSE )
Imported.ImageGetTagGroup().TagGroupSetTagAsTagGroup( "TIFF Tags", TIFFTags )
}
string ASCIITags = self.GetTIFFAscii(path)
TagGroup ZEISSTags = self.CreateTagsFromString_ZEISS_Default( ASCIITags )
Imported.ImageGetTagGroup().TagGroupSetTagAsTagGroup( "Info", ZEISSTags )
number sx, sy, ox, oy
string ux, uy
if ( self.GetCalibrationFromTags_ZEISS(ZEISSTags, sx, ox, ux, sy, oy, uy ) )
{
// "scale" gives full FOV!
if ( sx!= 1)
Imported.ImageSetDimensionCalibration(0,ox,sx/Imported.ImageGetDimensionSize(0),ux,0)
if ( sy!= 1)
Imported.ImageSetDimensionCalibration(1,oy,sy/Imported.ImageGetDimensionSize(1),uy,0)
}
else
{
if ( verbose )
Result( "\n ZEISS calibration not found. Image remains uncalibrated." )
}
return Imported
}
image OpenZEISS_TIFF( object self, string path )
{
return self.OpenZEISS_TIFF( path, FALSE )
}
number GetCalibrationFromTags_ZEISS( object self, tagGroup ZEISStags, number &sx, number &ox, string &ux, number &sy, number &oy, string &uy )
{
if ( !ZEISStags.TagGroupIsValid() ) return FALSE
number foundSomething = FALSE
ox=0
oy=0
sx=1
sy=1
ux=""
uy=""
string value
if ( ZEISStags.TagGroupGetTagAsString( "Width", value ) )
{
number pos = find( value, " " ) // separate unit!
if ( -1 < pos )
{
sx = val( left(value,pos) ) // FOV width not scale!!
ux = right( value, len(value)-pos-1 )
foundSomething = TRUE
}
}
if ( ZEISStags.TagGroupGetTagAsString( "Height", value ) )
{
number pos = find( value, " " ) // separate unit!
if ( -1 < pos )
{
sy = val( left(value,pos) ) // FOV width not scale!!
uy = right( value, len(value)-pos-1 )
foundSomething = TRUE
}
}
return foundSomething
}
}
///////////////////
// CALL EXAMPLES //
///////////////////
number option = 1
string msg
msg += "IMPORT TIFF - EXAMPLE\n"
msg += "Select what you want to do:\n"
msg += "(1)\t Import and calibrate FEI TIFF.\n"
msg += "(2)\t Import and calibrate ZEISS TIFF.\n"
msg += "(3)\t Show text content of TIFF.\n"
msg += "(4)\t Show TIFF tags in browser.\n"
while ( 4<option || 1>option )
{
if ( !GetNumber(msg,option,option)) exit(0)
}
string path = GetApplicationDirectory("open_save",0)
if (!OpenDialog(NULL,"Select TIFF file",path, path)) exit(0)
if ( 1 == option )
Alloc(CImportTIFFWithTags).OpenFEI_TIFF(path).ShowImage()
else if ( 2 == option )
Alloc(CImportTIFFWithTags).OpenZEISS_TIFF(path).ShowImage()
else if ( 3 == option )
Result( "\n\n\n\n_____________________________\n" +Alloc(CImportTIFFWithTags).GetTIFFAscii(path) )
else if ( 4 == option )
Alloc(CImportTIFFWithTags).GetTIFFTagsAsTG(path,true,true,true).TagGroupOpenBrowserWindow(path,0)