Module ctsimu.tiffy
TIFF image reader and writer.
Expand source code
# -*- coding: UTF-8 -*-
"""
TIFF image reader and writer.
"""
# Version 0.1 (2020-09-29)
import os # to check for files
import sys # for native byteorder
import struct # for C-style byte reading
import math # for floor
import copy # for deepcopy
import numpy # for image data arrays
# Define TIFF field types:
TIFF_BYTE = 1 # 8 bit unsigned
TIFF_ASCII = 2 # 8 bit character
TIFF_SHORT = 3 # 16 bit unsigned
TIFF_LONG = 4 # 32 bit unsigned
TIFF_RATIONAL = 5 # 2x32 bit unsigned (numerator, denominator)
TIFF_SBYTE = 6 # 8 bit signed
TIFF_UNDEFINED = 7 # 8 bit of unknown data
TIFF_SSHORT = 8 # 16 bit signed
TIFF_SLONG = 9 # 32 bit signed
TIFF_SRATIONAL = 10 # 2x32 bit signed (numerator, denominator)
TIFF_FLOAT = 11 # 4 byte single precision IEEE float
TIFF_DOUBLE = 12 # 8 byte double precision IEEE float
TIFF_BYTES_PER_TYPE = [1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8]
TIFF_STRUCT_CHAR_FOR_TYPE = ["B", "c", "H", "L", "LL", "b", "B", "h", "l", "ll", "f", "d"]
TIFF_INTTYPES = [TIFF_BYTE, TIFF_SHORT, TIFF_LONG, TIFF_SBYTE, TIFF_SSHORT, TIFF_SLONG]
# TIFF values
# Photometric Interpretation
TIFF_WHITE_IS_ZERO = 0
TIFF_BLACK_IS_ZERO = 1
TIFF_RGB = 2
# Compression
TIFF_NO_COMPRESSION = 1
TIFF_CCITT_COMPRESSION = 2
TIFF_PACKBITS_COMPRESSION = 32773
TIFF_LZW_COMPRESSION = 5
# Resolution unit
TIFF_RES_NONE = 1
TIFF_RES_INCH = 2
TIFF_RES_CENTIMETER = 3
# Sample format
TIFF_SAMPLEFORMAT_UINT = 1
TIFF_SAMPLEFORMAT_INT = 2
TIFF_SAMPLEFORMAT_IEEEFP = 3
TIFF_SAMPLEFORMAT_VOID = 4
# Planar configuration
TIFF_CHUNKY = 1
TIFF_PLANAR = 2
# Fill order
TIFF_MSB2LSB = 1
TIFF_LSB2MSB = 2
# Predictor
TIFF_NO_PREDICTOR = 1
TIFF_HORIZONTAL_DIFFERENCING = 2
TAGNAMES = {
"254": "New subfile type",
"256": "Cols",
"257": "Rows",
"258": "Bits per sample",
"259": "Compression",
"262": "Photometric interpretation",
"266": "Fill order",
"270": "Image description",
"273": "Strip offsets",
"274": "Orientation",
"277": "Samples per pixel",
"278": "Rows per strip",
"279": "Strip byte counts",
"282": "Resolution X",
"283": "Resolution Y",
"284": "Planar configuration",
"296": "Resolution unit",
"317": "Predictor",
"320": "Color map",
"339": "Sample format"
}
TIFFY_LOGLEVEL_ERROR = 0
TIFFY_LOGLEVEL_WARNING = 10
TIFFY_LOGLEVEL_INFO = 20
TIFFY_LOGLEVEL_DEBUG = 30
tiffy_currentLogLevel = TIFFY_LOGLEVEL_ERROR
def tiffyLog(level, message):
if level < tiffy_currentLogLevel:
print(message)
def getByteOrder(numpyArray):
byteOrder = numpyArray.dtype.byteorder
if byteOrder == "=": # native byte order
if sys.byteorder == "big":
byteOrder = ">"
else:
byteOrder = "<"
return byteOrder
def tagName(intTag):
strTag = "{}".format(intTag)
if strTag in TAGNAMES:
return TAGNAMES[strTag]
else:
return "Unknown"
def bytesPerTIFFtype(tp):
if tp > 0 and tp <= len(TIFF_BYTES_PER_TYPE):
return TIFF_BYTES_PER_TYPE[tp-1]
raise Exception("Unknown TIFF data type: {}".format(tp))
def TIFFtypeStructCharacter(tp):
if tp > 0 and tp <= len(TIFF_BYTES_PER_TYPE):
return TIFF_STRUCT_CHAR_FOR_TYPE[tp-1]
raise Exception("Unknown TIFF data type: {}".format(tp))
class bits:
def __init__(self):
self.data = bytearray()
def set(self, n):
""" Takes a number n and stores it in self.data in bitwise manner. """
self.data = bytearray()
pos = 0
while pos>0:
self.setBit(pos, n&1)
n = n >> 1
pos += 1
def __str__(self):
""" Print in binary representation. """
maxPos = len(self.data)*8
binString = ""
pos = 0
nChunks = 0
nBytes = 0
while pos <= maxPos:
if pos%9 == 0:
binString = "." + binString
nChunks += 1
if pos%8 == 0:
binString = "|" + binString
nBytes += 1
binString = "{}".format(self.getBit(pos)) + binString
pos += 1
binString += "\n{} chunks in {} bytes.".format(nChunks, nBytes)
return binString
def setBytes(self, b):
""" Takes a bytes object b and stores it in self.data. """
self.data = bytearray()
self.data += b
def reverseBitsInBytes(self):
for i in range(len(self.data)):
newByte = 0
for b in range(8):
newByte = newByte << 1
newByte += ((self.data[i] >> b) & 1)
self.data[i] = newByte
"""
def reverseBits(self, start, stop):
newBits = bits()
pos = 0
for i in range(stopBit-1, startBit-1, -1):
newBits.setBit(pos, self.getBit(i))
pos += 1
for i in range(startBit, stopBit):
self.setBit(i, self.getBit(i-startBit))
"""
def byteAndBit(self, pos):
""" From a given bit position, returns the byte index and the bit index within this byte. """
byteIdx = int(math.floor(pos/8))
inByte = pos%8
return byteIdx, inByte
def getInt(self, startBit, stopBit):
s = 0
if startBit < stopBit:
for i in range(stopBit-1, startBit-1, -1):
s = s << 1
s += self.getBit(i)
return int(s)
def getIntMSBtoLSB(self, startBit, stopBit):
s = 0
for i in range(startBit, stopBit): # MSB to LSB
s = s << 1
s += self.getBit(i)
return s
def getIntMSBtoLSB_inBytes(self, startByte, startBit, nBits):
s = 0
# Maximum of three masks required (for 9..12 bits)
mask1 = mask << startBit
mask2 = 255 << (nBits-7)
mask3 = 255
"""
i = 0
#print("StartByte: {}, StartBit: {}, nBits: {}".format(startByte, startBit, nBits))
while i<nBits: # MSB to LSB
s = s << 1 # Shift left by 1
s += ((self.data[startByte] >> (7-startBit)) & 1)
startBit += 1
if startBit > 7:
startBit = 0
startByte += 1
i += 1
"""
startBit += nBits
if startBit > 7:
startBit = 0
startByte += 1
return s, startByte+1, startBit+nBits
def getIntMSBtoLSB_faster(self, startByte, rightZeros, mask0, mask1, mask2):
s = ((self.data[startByte]&mask0)<<16) + ((self.data[startByte+1]&mask1)<<8) + (self.data[startByte+2]&mask2)
s = s >> rightZeros
#newByte = 0
#for b in range(9):
# newByte = newByte << 1
# newByte += ((s >> b) & 1)
#return newByte
return s
def getBit(self, pos):
byteIdx, inByte = self.byteAndBit(pos)
if(byteIdx >= len(self.data)):
return 0
return ((self.data[byteIdx] >> inByte) & 1)
def getBitInByte(self, byteIdx, bitPos):
return ((self.data[byteIdx] >> bitPos) & 1)
def setBit(self, pos, value=1):
byteIdx, inByte = self.byteAndBit(pos)
while(byteIdx > len(self.data)):
self.data += bytes(1)
mask = 1 << inByte
if value == 1: # set bit
self.data[byteIdx] = self.data[byteIdx] | mask
else: # clear bit
self.data[byteIdx] = self.data[byteIdx] & ~mask
def setBitInByte(self, byteIdx, bitPos, value=1):
while(byteIdx > len(self.data)):
self.data += bytes(1)
mask = 1 << bitPos
if value == 1: # set bit
self.data[byteIdx] = self.data[byteIdx] | mask
else: # clear bit
self.data[byteIdx] = self.data[byteIdx] & ~mask
class lzwStringTable:
def __init__(self):
self.byteStrings = []
self.init()
def init(self):
self.byteStrings = []
# Create all bytes:
for i in range(256):
self.byteStrings.append(bytearray(struct.pack("B", i)))
self.byteStrings.append(bytearray()) # ClearCode: 256
self.byteStrings.append(bytearray()) # EndOfInformation code: 257
def currentCodeBitWidth(self):
"""
l = len(self.byteStrings) + 1
bitWidth = 0
while l != 0:
l = l >> 1
bitWidth += 1
return bitWidth
"""
if len(self.byteStrings) < 255:
return 8
elif len(self.byteStrings) < 511:
return 9
elif len(self.byteStrings) < 1023:
return 10
elif len(self.byteStrings) < 2047:
return 11
elif len(self.byteStrings) < 4095:
return 12
elif len(self.byteStrings) < 8191:
return 13
elif len(self.byteStrings) < 16383:
return 14
else:
raise Exception("LZW: Dictionary is too big.")
def contains(self, code):
if code < len(self.byteStrings):
return True
return False
def add(self, b):
self.byteStrings.append(b)
def isClearCode(self, code):
if code == 256:
return True
return False
def isEndOfInformation(self, code):
if code == 257:
return True
return False
def stringFromCode(self, code, codeWidth):
#if code < 0:
# return bytearray()
return self.byteStrings[code]
"""
if code < len(self.byteStrings):
#print("String from code {}: {}".format(code, self.byteStrings[code]))
return self.byteStrings[code]
else:
raise Exception("LZW: Requested code #{} not in current string table. Current string table size: {}. Current bit width: {}".format(code, len(self.byteStrings), codeWidth))
"""
class lzwData:
def __init__(self):
self.compressed = None
self.umcompressed = None
self.stringTable = lzwStringTable()
self.currentBitPos = 0
self.currentByteIdx = 0
self.currentBitPosInByte = 0 # in byte
self.codeWidth = 9 # Starting with 9 bit wide codes
self.masks = {} #[[[0]*3]*8]*14
self.byteSkips = {} # [[0]*8]*14
self.leftAlignOffsets = {}
for codeWidth in range(9, 14):
fundMask = 2**codeWidth - 1
for shift in range(0, 8): # Bitshift / startBit
leftAlignOffset = 3*8 - codeWidth - shift
mask = fundMask << leftAlignOffset
mask0 = (mask >> 16) & 255 # 00000000 00000000 11111111
mask1 = (mask >> 8) & 255 # 00000000 11111111 00000000
mask2 = (mask) & 255 # 11111111 00000000 00000000
self.masks[codeWidth,shift,0] = mask0
self.masks[codeWidth,shift,1] = mask1
self.masks[codeWidth,shift,2] = mask2
self.byteSkips[codeWidth,shift] = int(math.ceil((codeWidth+shift+1)/8))-1
self.leftAlignOffsets[codeWidth,shift] = leftAlignOffset
def resetStringtable(self):
self.stringTable.init()
self.codeWidth = 9
def setCompressed(self, compressed):
self.compressed = bits()
self.compressed.setBytes(compressed)
self.compressed.data += bytes(3) # for integer conversion
def getNextCode(self):
#s1 = self.currentBitPos
#s2 = s1 + self.codeWidth
#code = self.compressed.getIntMSBtoLSB(startBit=s1, stopBit=s2)
#code, self.currentByteIdx, self.currentBitPosInByte = self.compressed.getIntMSBtoLSB_inBytes(startByte=self.currentByteIdx, startBit=self.currentBitPosInByte, nBits=self.codeWidth)
startByte = self.currentByteIdx
mask0=self.masks[self.codeWidth,self.currentBitPosInByte,0]
mask1=self.masks[self.codeWidth,self.currentBitPosInByte,1]
mask2=self.masks[self.codeWidth,self.currentBitPosInByte,2]
code = self.compressed.getIntMSBtoLSB_faster(startByte=startByte, rightZeros=self.leftAlignOffsets[self.codeWidth,self.currentBitPosInByte], mask0=mask0, mask1=mask1, mask2=mask2)
tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Requesting {}({}). Skip: {} CodeWidth: {} Offset: {} Masks: {:08b}.{:08b}.{:08b} -> {}".format(startByte, self.currentBitPosInByte, self.byteSkips[self.codeWidth,self.currentBitPosInByte], self.codeWidth, self.currentBitPosInByte, mask0, mask1, mask2, code))
self.currentByteIdx += self.byteSkips[self.codeWidth,self.currentBitPosInByte]
self.currentBitPosInByte += self.codeWidth
self.currentBitPosInByte = self.currentBitPosInByte % 8
return code
def decompress(self):
self.uncompressed = bytearray()
self.currentBitPos = 0
oldCode = -1
code = self.getNextCode()
self.stringTable.init() # If compressed data doesn't start with ClearCode
while not self.stringTable.isEndOfInformation(code):
if(self.stringTable.isClearCode(code)):
self.resetStringtable()
code = self.getNextCode()
if(self.stringTable.isEndOfInformation(code)):
break
self.uncompressed += self.stringTable.stringFromCode(code, self.codeWidth)
oldCode = code
else:
if self.stringTable.contains(code):
s = self.stringTable.stringFromCode(code, self.codeWidth)
self.uncompressed += s
self.stringTable.add(self.stringTable.stringFromCode(oldCode, self.codeWidth) + bytes([s[0]]))
else:
s = self.stringTable.stringFromCode(oldCode, self.codeWidth)
outString = s + bytes([s[0]])
self.uncompressed += outString
self.stringTable.add(outString)
oldCode = code
self.codeWidth = self.stringTable.currentCodeBitWidth()
code = self.getNextCode()
class ifdEntry:
def __init__(self):
self.tag = 0 # TIFF Tag ID
self.type = 0 # Field Type
self.count = 0 # Number of values (NOT bytes)
self.ifdEntryPos = 0
self.values = []
self.extraValuesOffset = 0 # storage offset (in byte) for any extra values that don't fit in the 4 values bytes of this IFD entry
def set(self, tag, typ, values):
self.setTagID(tag)
self.setType(typ)
self.setValues(values)
def setTagID(self, tagid):
self.tag = tagid
def setType(self, typ):
self.type = typ
def setValue(self, value):
if self.type in TIFF_INTTYPES:
value = int(value)
self.values = [value, ]
self.count = 1
def setValues(self, values):
self.values = [0] * len(values)
for i in range(len(self.values)):
if self.type in TIFF_INTTYPES:
self.values[i] = int(values[i])
else:
self.values[i] = values[i]
self.count = len(values)
def nValueBytes(self):
return self.count * bytesPerTIFFtype(self.type)
def read(self, offset, f, byteOrder):
f.seek(offset)
buff = f.read(8)
(self.tag, self.type, self.count) = struct.unpack("{endian}HHL".format(endian=byteOrder), buff)
# Calculate amount of bytes necessary for value(s):
self.nValueBytes = self.nValueBytes()
# Read value(s):
f.seek(offset+8)
buff = f.read(4)
if self.nValueBytes > 4: # The field's actual value bytes must be read from the provided pointer.
(valueOffset,) = struct.unpack("{endian}L".format(endian=byteOrder), buff)
f.seek(valueOffset)
buff = f.read(self.nValueBytes)
# Prepare a struct pattern:
structPattern = ""
structCharacter = TIFFtypeStructCharacter(self.type)
for i in range(self.count):
structPattern += structCharacter
# Fill rest of pattern with pad bytes:
if self.nValueBytes < 4:
for j in range(4 - self.nValueBytes):
structPattern += "x"
tup = struct.unpack("{endian}{pattern}".format(endian=byteOrder, pattern=structPattern), buff)
if self.type == TIFF_RATIONAL or self.type == TIFF_SRATIONAL:
for numerator, denominator in zip(tup, tup[1:]):
val = 0
if denominator != 0:
val = numerator / denominator
self.values.append(val)
else:
for val in tup:
self.values.append(val)
if len(self.values) < 5:
tiffyLog(TIFFY_LOGLEVEL_INFO, " -- Tag {tag} ({tagname}): {n} value(s) {val}".format(tag=self.tag, tagname=tagName(self.tag), n=len(self.values), val=self.values))
else:
tiffyLog(TIFFY_LOGLEVEL_INFO, " -- Tag {tag} ({tagname}): {n} value(s)".format(tag=self.tag, tagname=tagName(self.tag), n=len(self.values)))
def getValue(self):
if len(self.values) > 0:
return self.values[0]
else:
raise Exception("TIFF field with tag {tag} does not come with any value.".format(tag=self.tag))
def sizeInBytes(self):
size = 12
size += self.sizeOfExtraValues()
return size
def sizeOfExtraValues(self):
size = 0
# If the values don't fit in the IFD entry, they are stored somewhere else:
if (bytesPerTIFFtype(self.type)*len(self.values)) > 4:
size += bytesPerTIFFtype(self.type)*len(self.values)
return size
def printOffset(self):
tiffyLog(TIFFY_LOGLEVEL_INFO, " IFD entry, tag {t} {offset}".format(t=self.tag, offset=self.ifdEntryPos))
def printExtraDataOffset(self):
if self.nValueBytes() > 4:
tiffyLog(TIFFY_LOGLEVEL_INFO, " IFD entry, tag {t} {offset} Extra Data".format(t=self.tag, offset=self.extraValuesOffset))
def prepareDataOffsets(self, offset, extraDataOffset):
self.ifdEntryPos = offset
self.extraValuesOffset = extraDataOffset
return (offset+12), (extraDataOffset + self.sizeOfExtraValues())
def write(self, f, byteOrder):
tiffyLog(TIFFY_LOGLEVEL_INFO, "IDF Entry at pos {}".format(f.tell()))
buff = struct.pack("{endian}HHL".format(endian=byteOrder), self.tag, self.type, self.count)
f.write(buff)
if self.nValueBytes() > 4: # point to value storage offset
tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Packing for tag {tag} (type {typ}, count {cnt}): Pointing to {offset}".format(tag=self.tag, typ=self.type, cnt=self.count, endian=byteOrder, offset=self.extraValuesOffset))
buff = struct.pack("{endian}L".format(endian=byteOrder), self.extraValuesOffset)
f.write(buff)
else:
structChar = TIFFtypeStructCharacter(self.type)
padding = 4 - self.nValueBytes()
if padding > 0:
tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Packing for tag {tag} (type {typ}, count {cnt}) at pos {pos}: {endian}{count}{structchar}{padding}x {vals}".format(tag=self.tag, typ=self.type, cnt=self.count, pos=f.tell(), endian=byteOrder, count=self.count, structchar=structChar, padding=padding, vals=self.values))
buff = struct.pack("{endian}{count}{structchar}{padding}x".format(endian=byteOrder, count=self.count, structchar=structChar, padding=padding), *self.values)
f.write(buff)
else:
tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Packing for tag {tag} (type {typ}, count {cnt}) at pos {pos}: {endian}{count}{structchar} {vals}".format(tag=self.tag, typ=self.type, cnt=self.count, pos=f.tell(), endian=byteOrder, count=self.count, structchar=structChar, vals=self.values))
buff = struct.pack("{endian}{count}{structchar}".format(endian=byteOrder, count=self.count, structchar=structChar), *self.values)
f.write(buff)
def writeExtraValues(self, f, byteOrder):
structChar = TIFFtypeStructCharacter(self.type)
if self.nValueBytes() > 4:
tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Packing for tag {tag} (type {typ}, count {cnt}) at pos {pos}: {endian}{count}{structchar} {vals}".format(tag=self.tag, typ=self.type, cnt=self.count, pos=f.tell(), endian=byteOrder, count=self.count, structchar=structChar, vals=self.values))
buff = struct.pack("{endian}{count}{structchar}".format(endian=byteOrder, count=self.count, structchar=structChar), *self.values)
f.write(buff)
class ifd:
def __init__(self):
self.ifdPos = None
self.fieldBytes = []
self.fields = []
self.fieldCount = 0
self.nextIfdPos = 0
def addEntry(self, entry):
self.fields.append(entry)
def read(self, ifdPos, f, byteOrder):
self.ifdPos = ifdPos
tiffyLog(TIFFY_LOGLEVEL_INFO, " -- new IFD.")
f.seek(ifdPos)
buff = f.read(2)
offset = ifdPos + 2
(self.fieldCount,) = struct.unpack("{endian}H".format(endian=byteOrder), buff)
for i in range(self.fieldCount):
entry = ifdEntry()
entry.read(offset, f, byteOrder)
self.fields.append(entry)
offset += 12
f.seek(offset)
buff = f.read(4)
(self.nextIfdPos,) = struct.unpack("{endian}L".format(endian=byteOrder), buff)
def sizeInBytes(self):
size = 2 + 4 # Number of entries (2 bytes) + offset to next IFD (4 bytes)
for field in self.fields:
size += field.sizeInBytes()
return size
def prepareDataOffsets(self, offset):
self.ifdPos = offset
offset += 2 # number of entries (2 bytes)
extraDataOffset = offset + 12*len(self.fields) + 4 # offset for each entry (12 bytes) + pointer to next IFD (4 bytes)
for field in self.fields:
offset, extraDataOffset = field.prepareDataOffsets(offset, extraDataOffset)
return offset
def printOffset(self):
tiffyLog(TIFFY_LOGLEVEL_INFO, "+ IFD {offset} -> {nextIFD}".format(offset=self.ifdPos, nextIFD=self.nextIfdPos))
for entry in self.fields:
entry.printOffset()
for entry in self.fields:
entry.printExtraDataOffset()
def write(self, f, byteOrder):
buff = struct.pack("{endian}H".format(endian=byteOrder), len(self.fields))
f.write(buff)
tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Written IFD. Now at position {}".format(f.tell()))
# IFD entries:
for entry in self.fields:
entry.write(f, byteOrder)
buff = struct.pack("{endian}L".format(endian=byteOrder), self.nextIfdPos)
f.write(buff)
# extra data
for entry in self.fields:
entry.writeExtraValues(f, byteOrder)
class tiffSubfile:
def __init__(self):
self.px = [] # Array of numpy arrays to store image data (i.e. pixel values). One for each component/channel. Shape: (nChannel, nRows, nCols)
self.imageDataOffset = 0 # Location of beginning of image data
self.sampleFormat = [TIFF_SAMPLEFORMAT_UINT] # Standard: uint
self.filename = None
self.byteOrder = "<" # little endian
self.ifd = None
self.photometricInterpretation = TIFF_BLACK_IS_ZERO
self.compression = TIFF_NO_COMPRESSION
self.predictor = TIFF_NO_PREDICTOR
self.rows = 0
self.cols = 0
self.resolutionUnit = TIFF_RES_INCH
self.resX = 0
self.resY = 0
self.rowsPerStrip = 0
self.stripOffsets = []
self.stripByteCounts = []
self.bitsPerSample = (1,) # Bilevel images do not define this, so make 1 bit/sample the default.
self.samplesPerPixel = 1
self.planarConfig = TIFF_CHUNKY # Chunky (RGBRGBRGB...)
self.orientation = 1
self.colorMap = None # For palette-color images
def reset(self):
self.__init__()
def set(self, imageData, resX=0, resY=0):
if len(imageData) > 0:
self.reset()
self.px = imageData
shp = numpy.shape(self.px) # Shape must be 3-component tuple: (nChannels, height, width)
if len(shp) == 3:
self.samplesPerPixel = shp[0]
self.rows = shp[1]
self.cols = shp[2]
self.rowsPerStrip = self.rows
# self.stripByteCounts and self.stripOffsets will be set later by self.prepareDataOffsets()
# Byte order
self.byteOrder = getByteOrder(self.px)
# Sample format
if numpy.issubdtype(self.px.dtype, numpy.signedinteger):
self.sampleFormat = [TIFF_SAMPLEFORMAT_INT] * self.nChannels()
elif numpy.issubdtype(self.px.dtype, numpy.unsignedinteger):
self.sampleFormat = [TIFF_SAMPLEFORMAT_UINT] * self.nChannels()
elif numpy.issubdtype(self.px.dtype, numpy.floating):
self.sampleFormat = [TIFF_SAMPLEFORMAT_IEEEFP] * self.nChannels()
else:
raise Exception("Unsupported dtype ({}) of provided image data. Must be an integer or floating point type.".format(numpy.dtype(self.px)))
self.bitsPerSample = [self.px.dtype.itemsize*8] * self.samplesPerPixel
tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Setting bits per sample: {}".format(self.bitsPerSample))
tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Setting sample format: {}".format(self.sampleFormat))
self.resolutionUnit = TIFF_RES_NONE
self.resX = resX
self.resY = resY
self.compression = TIFF_NO_COMPRESSION
self.photometricInterpretation = TIFF_BLACK_IS_ZERO
if self.nChannels() == 3:
self.photometricInterpretation = TIFF_RGB
#if self.samplesPerPixel > 1:
# self.planarConfig = TIFF_PLANAR
#else:
# self.planarConfig = TIFF_CHUNKY
self.planarConfig = TIFF_CHUNKY
else:
raise Exception("Error setting image data. Please provide a numpy array of shape (nChannels, nRows, nColumns).")
def addIFDentry_shortOrLong(self, tag, values):
# find the right integer type for the provided values:
m = max(values)
typ = TIFF_LONG
if m < (2**16):
typ = TIFF_SHORT
self.addIFDentry(tag, typ, values)
def addIFDentry(self, tag, typ, values):
entry = ifdEntry()
entry.set(tag, typ, values)
self.ifd.addEntry(entry)
def setupIFD(self):
self.ifd = None
self.ifd = ifd()
self.addIFDentry_shortOrLong(256, (self.cols, )) # n columns
self.addIFDentry_shortOrLong(257, (self.rows, )) # n rows
self.addIFDentry(258, TIFF_SHORT, self.bitsPerSample) # bits per sample, already an array
self.addIFDentry(259, TIFF_SHORT, (TIFF_NO_COMPRESSION, )) # compression
self.addIFDentry(262, TIFF_SHORT, (self.photometricInterpretation, )) # photometric interpretation
self.addIFDentry(266, TIFF_SHORT, (TIFF_MSB2LSB, )) # fill order
self.addIFDentry_shortOrLong(273, (self.imageDataOffset, )) # offset location of strip
self.addIFDentry(274, TIFF_SHORT, (self.orientation, )) # orientation
self.addIFDentry(277, TIFF_SHORT, (self.samplesPerPixel, )) # samples per pixel
self.addIFDentry_shortOrLong(278, (self.rowsPerStrip, )) # rows per strip
self.addIFDentry_shortOrLong(279, (self.dataSizeInBytes(), )) # strip byte counts
# insert resolution entries here later...
self.addIFDentry(284, TIFF_SHORT, (self.planarConfig, )) # planar configuration
#self.addIFDentry(296, TIFF_SHORT, (self.resolutionUnit, )) # resolution unit
if self.predictor != TIFF_NO_PREDICTOR:
self.addIFDentry(317, TIFF_SHORT, (self.predictor, )) # predictor
# color map
self.addIFDentry(339, TIFF_SHORT, self.sampleFormat) # sample format, already an array
def dataSizeInBytes(self):
""" Size of pixel data (in bytes) """
size = 0
for bitsPerSample in self.bitsPerSample:
s = int(bitsPerSample * self.nPixels())
size += s
return size/8
def sizeInBytes(self):
size = 0
if self.ifd is not None:
size += self.ifd.sizeInBytes()
size += self.dataSizeInBytes()
return size
def prepareDataOffsets(self, offset):
if self.ifd is not None:
self.ifd.prepareDataOffsets(offset)
offset += self.ifd.sizeInBytes()
self.imageDataOffset = offset # image data starts here
self.stripOffsets = (self.imageDataOffset, )
self.stripByteCounts = self.dataSizeInBytes()
offset += self.dataSizeInBytes()
return offset
else:
return 0
def printOffset(self):
if self.ifd is not None:
self.ifd.printOffset()
tiffyLog(TIFFY_LOGLEVEL_INFO, " Image Data {offset}".format(offset=self.imageDataOffset))
def readMetaInformation(self, filename, byteOrder, imgFileDirectory):
self.filename = filename
self.byteOrder = byteOrder
self.ifd = imgFileDirectory
# Interpret IFD fields based on their TIFF tags:
for field in self.ifd.fields:
if field.tag == 256: # Number of columns
self.cols = field.getValue()
elif field.tag == 257: # Number of rows
self.rows = field.getValue()
elif field.tag == 258: # Bits per sample
self.bitsPerSample = field.values
elif field.tag == 259: # Compression
self.compression = field.getValue()
if self.compression == 0:
self.compression = TIFF_NO_COMPRESSION
elif field.tag == 262: # Photometric interpretation
self.photometricInterpretation = field.getValue()
elif field.tag == 273: # Strip offsets
self.stripOffsets = field.values
elif field.tag == 274: # Orientation
self.orientation = field.getValue()
elif field.tag == 277: # Samples per pixel
self.samplesPerPixel = field.getValue()
elif field.tag == 278: # Rows per strip
self.rowsPerStrip = field.getValue()
elif field.tag == 279: # Strip byte counts
self.stripByteCounts = field.values
elif field.tag == 282: # x resolution
self.resX = field.getValue()
elif field.tag == 283: # y resolution
self.resY = field.getValue()
elif field.tag == 284: # Planar configuration (order of pixel components)
self.planarConfig = field.getValue()
elif field.tag == 296: # Resolution unit
self.resolutionUnit = field.getValue()
elif field.tag == 317: # Predictor
self.predictor = field.getValue()
elif field.tag == 320: # Color map
pass # implement later...
elif field.tag == 339: # Sample format
self.sampleFormat = field.values
def nChannels(self):
return self.samplesPerPixel
def nPixels(self):
return self.rows*self.cols
def bitsPerPixel(self):
bits = 0
for b in self.bitsPerSample:
bits += b
return bits
def isPlanar(self):
return (self.samplesPerPixel == 1 or self.planarConfig == TIFF_PLANAR)
def isSet(self):
""" Check if image has a valid width and height. """
if(self.getHeight() > 0):
if(self.getWidth() > 0):
return True
return False
def getWidth(self):
return self.cols
def getHeight(self):
return self.rows
def rot90(self):
if self.isSet():
self.px = numpy.rot90(self.px, k=1, axes=(1,2))
self.cols, self.rows = self.rows, self.cols
def rot180(self):
if self.isSet():
self.px = numpy.rot90(self.px, k=2, axes=(1,2))
def rot270(self):
if self.isSet():
self.px = numpy.rot90(self.px, k=-1, axes=(1,2))
self.cols, self.rows = self.rows, self.cols
def rotate(self, rotation):
if rotation == "90":
self.rot90()
elif rotation == "180":
self.rot180()
elif rotation == "270":
self.rot270()
def flipHorizontal(self):
if self.isSet():
for i in range(self.nChannels()):
self.px[i] = numpy.fliplr(self.px[i])
def flipVertical(self):
if self.isSet():
for i in range(self.nChannels()):
self.px[i] = numpy.flipud(self.px[i])
def setFlip(self, horz=False, vert=False):
self.flipHorz = horz
self.flipVert = vert
def getHorizontalFlip(self):
return self.flipHorz
def getVerticalFlip(self):
return self.flipVert
def flip(self, horizontal=False, vertical=False):
if horizontal:
self.flipHorizontal()
if vertical:
self.flipVertical()
def numpyDatatype(self, sampleFormat, bitsPerSample):
formatString = self.byteOrder
if sampleFormat == TIFF_SAMPLEFORMAT_UINT: # unsigned
if bitsPerSample == 8:
formatString = "u1" # unsigned char (1 byte)
elif bitsPerSample == 16:
formatString += "u2" # unsigned short (2 bytes)
elif bitsPerSample == 32:
formatString += "u4" # unsigned long (4 bytes)
elif bitsPerSample == 64:
formatString += "u8" # unsigned long long (8 bytes)
elif sampleFormat == TIFF_SAMPLEFORMAT_INT: # signed
if bitsPerSample == 8:
formatString = "i1" # signed char (1 byte)
elif bitsPerSample == 16:
formatString += "i2" # signed short (2 bytes)
elif bitsPerSample == 32:
formatString += "i4" # signed long (4 bytes)
elif bitsPerSample == 64:
formatString += "i8" # signed long long (8 bytes)
elif sampleFormat == TIFF_SAMPLEFORMAT_IEEEFP:
if bitsPerSample == 16:
formatString += "f2" # float 16 bit
elif bitsPerSample == 32:
formatString += "f4" # float 32 bit
elif bitsPerSample == 64:
formatString += "f8" # double 64 bit
if len(formatString) >= 1:
return numpy.dtype(formatString)
raise Exception("Unsupported data type: {bps} bits per sample for TIFF sample format {sf}.".format(bps=bitsPerSample, sf=sampleFormat))
def structDataTypeString(self, sampleFormat, bitsPerSample):
formatString = self.byteOrder
if sampleFormat == TIFF_SAMPLEFORMAT_UINT: # unsigned
if bitsPerSample == 8:
formatString = "B" # unsigned char (1 byte)
elif bitsPerSample == 16:
formatString += "H" # unsigned short (2 bytes)
elif bitsPerSample == 32:
formatString += "L" # unsigned long (4 bytes)
elif bitsPerSample == 64:
formatString += "Q" # unsigned long long (8 bytes)
elif sampleFormat == TIFF_SAMPLEFORMAT_INT: # signed
if bitsPerSample == 8:
formatString = "b" # signed char (1 byte)
elif bitsPerSample == 16:
formatString += "h" # signed short (2 bytes)
elif bitsPerSample == 32:
formatString += "l" # signed long (4 bytes)
elif bitsPerSample == 64:
formatString += "q" # signed long long (8 bytes)
elif sampleFormat == TIFF_SAMPLEFORMAT_IEEEFP:
if bitsPerSample == 16:
formatString += "e" # float 16 bit
elif bitsPerSample == 32:
formatString += "f" # float 32 bit
elif bitsPerSample == 64:
formatString += "d" # double 64 bit
if len(formatString) >= 1:
return formatString
raise Exception("Unsupported data type: {bps} bits per sample for TIFF sample format {sf}.".format(bps=bitsPerSample, sf=sampleFormat))
def importFromUncompressedBuffer(self, pixelOffset, nPixels, datatype, channel, buff):
if self.isPlanar(): # Buffer contains data just for one channel.
self.px[channel][int(pixelOffset):int(pixelOffset+nPixels)] = numpy.frombuffer(buff, dtype=datatype)
else: # CHUNKY configuration. channel is irrelevant here (and wrong.)
bitsPerPixel = self.bitsPerPixel()
bytesPerPixel = int(bitsPerPixel / 8)
bytesPerSample = [x/8 for x in self.bitsPerSample]
buff1d = numpy.frombuffer(buff, dtype=datatype)
self.px[int(pixelOffset):int(pixelOffset+nPixels)] = numpy.reshape(buff1d, (nPixels, self.nChannels()))
"""
for pixel in range(nPixels):
channelOffset = 0
for c in range(self.samplesPerPixel):
# Current index in bytes
idxStart = int((pixel + pixelOffset)*bytesPerPixel + channelOffset/8)
idxStop = int(idxStart + bytesPerSample[c])
#print("Start: {}, Stop: {}".format(idxStart, idxStop))
self.px[c][pixel] = numpy.frombuffer(buff[idxStart:idxStop], dtype=dt)
channelOffset += self.bitsPerSample[c]
"""
def imageData(self, obeyOrientation=True):
# Read data into byte buffer:
if len(self.stripOffsets) > 0:
if len(self.stripOffsets) == len(self.stripByteCounts):
if os.path.isfile(self.filename):
with open(self.filename, "rb") as f:
# Initialize nChannels x nPixels array for each channel:
# Check if bits per sample is the same for each channel:
if self.bitsPerSample.count(self.bitsPerSample[0]) == len(self.bitsPerSample) and self.sampleFormat.count(self.sampleFormat[0]) == len(self.sampleFormat):
sampleFormat = self.sampleFormat[0]
bitsPerSample = self.bitsPerSample[0]
dt = self.numpyDatatype(sampleFormat, bitsPerSample)
if self.isPlanar():
self.px = numpy.zeros((self.nChannels(), self.nPixels()), dtype=dt)
else:
# Make array the shape of a chunky tiff configuration and reshape later...
self.px = numpy.zeros((self.nPixels(), self.nChannels()), dtype=dt)
nStrips = len(self.stripOffsets)
if self.compression == TIFF_NO_COMPRESSION or self.compression == TIFF_LZW_COMPRESSION:
pixel = 0
bitsPerPixel = self.bitsPerPixel()
c = 0 # channel id. Only necessary for PLANAR configuration.
nStripsPerChannel = int(nStrips / self.samplesPerPixel)
for i in range(nStrips):
if self.samplesPerPixel > 1:
if self.planarConfig == TIFF_PLANAR:
# Import next channel once all strips for one component channel have been imported.
if (i % nStripsPerChannel) == 0:
c += 1
offset = self.stripOffsets[i]
byteCount = self.stripByteCounts[i] # byte count after compression
#print("Strip #{}/{}, Byte Offset: {}, Byte Count: {}".format(i, nStrips, offset, byteCount))
f.seek(offset)
buff = f.read(byteCount)
if self.compression == TIFF_LZW_COMPRESSION:
compressed = lzwData()
compressed.setCompressed(buff)
#print("Decompressing LZW for strip {i}/{n}...".format(i=i, n=nStrips))
compressed.decompress()
buff = bytes(compressed.uncompressed)
byteCount = len(buff)
bitCount = 8*byteCount
if not self.isPlanar(): # Standard is chunky, also for only 1 sample/pixel.
pixelsInStrip = int(bitCount / bitsPerPixel)
else:
pixelsInStrip = int(bitCount / self.bitsPerSample[c])
self.importFromUncompressedBuffer(
pixelOffset = pixel,
nPixels = pixelsInStrip,
datatype = dt,
channel = c,
buff = buff)
pixel += pixelsInStrip
tiffyLog(TIFFY_LOGLEVEL_INFO, "{} strips imported.".format(nStrips))
else:
raise Exception("TIFF: Compression scheme {} not supported.".format(self.compression))
f.close()
if not(self.isPlanar()):
# Chunky mode. Swap axes from (pixels, channels) to (channels, pixels):
self.px = numpy.swapaxes(self.px, 0, 1)
# Reshape components into 2D arrays:
if len(self.px) > 0:
self.px = numpy.reshape(self.px, (self.nChannels(), self.rows, self.cols))
# Convert horizontal differences to absolute values:
tiffyLog(TIFFY_LOGLEVEL_INFO, "Applying horizontal differencing...")
if self.predictor == TIFF_HORIZONTAL_DIFFERENCING:
for col in range(1, self.cols):
self.px[...,col] = self.px[...,col-1] + self.px[...,col]
tiffyLog(TIFFY_LOGLEVEL_INFO, "Orientation is: {}".format(self.orientation))
if obeyOrientation:
# Rotate back to orientation 1 ((0,0) is upper left)
if self.orientation == 2: # (col0, row0) is (top, right)
self.flip(horizontal=True, vertical=False)
elif self.orientation == 3: # (col0, row0) is (bottom, right)
self.flip(horizontal=True, vertical=True)
elif self.orientation == 4: # (col0, row0) is bottom left
self.flip(horizontal=False, vertical=True)
elif self.orientation == 5: # (col0, row0) is (left, top)
self.rotate("90")
self.flip(horizontal=False, vertical=True)
elif self.orientation == 6: # (col0, row0) is (right, top)
self.rotate("270")
elif self.orientation == 7: # (col0, row0) is (right, bottom)
self.rotate("90")
self.flip(horizontal=True, vertical=False)
elif self.orientation == 8: # (col0, row0) is (left, bottom)
self.rotate("90")
self.orientation = 1
return self.px
else:
f.close()
raise Exception("Unsupported TIFF format: all channels must have the same type and size. Sample format: {}, Bits per sample: {}".format(self.sampleFormat, self.bitsPerSample))
raise Exception("File not available: {}".format(self.filename))
else:
raise Exception("Number of strip offsets ({nOffsets}) does not match number of strip byte counts ({nByteCounts}).".format(nOffsets=len(self.stripOffsets), nByteCounts=len(self.stripByteCounts)))
else:
raise Exception("No data strips found for requested subfile in {filename}.".format(filename=self.filename))
def write(self, f, byteOrder):
""" Expects an open, writable file pointer f. """
self.ifd.write(f, byteOrder)
dataByteOrder = getByteOrder(self.px)
if dataByteOrder != byteOrder:
self.px.byteswap(inplace=True)
# Write image data:
if self.samplesPerPixel == 1:
f.write(self.px)
else:
chunkyBytes = numpy.swapaxes(self.px, 0, 2) # channel <-> cols --> (cols, rows, channel)
chunkyBytes = numpy.swapaxes(chunkyBytes, 0, 1) # cols <-> rows --> (rows, cols, channel)
chunkyBytes.tofile(f, "")
"""
dataString = []
for c in range(self.nChannels()):
dataString.append(self.structDataTypeString(self.sampleFormat[c], self.bitsPerSample[c]))
for y in range(self.rows):
for x in range(self.cols):
# Chunky style.
for c in range(self.nChannels()):
buff = struct.pack("{endian}{ds}".format(endian=byteOrder, ds=dataString[c]), self.px[c][y][x])
f.write(buff)
"""
class tiff:
def __init__(self):
self.filename = None
self.byteOrder = "<"
self.subfiles = []
def reset(self):
self.__init__()
def read(self, filename=None):
self.filename = filename
if self.filename is not None:
if os.path.isfile(self.filename):
filesize = os.path.getsize(self.filename)
tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Size of {filename}: {size} Bytes.".format(filename=self.filename, size=filesize))
if filesize > 2:
with open(self.filename, "rb") as f:
buff = f.read(2)
(tiffformat,) = struct.unpack("<H", buff) # Just read as little endian. It's symmetric, it doesn't matter.
self.byteOrder = "<" # little endian
if tiffformat == 0x4949:
tiffyLog(TIFFY_LOGLEVEL_DEBUG, "II format. Little endian.")
self.byteOrder = "<"
elif tiffformat == 0x4d4d:
tiffyLog(TIFFY_LOGLEVEL_DEBUG, "MM format. Big endian.")
self.byteOrder = ">"
else:
f.close()
raise Exception("Invalid byte order for first two bytes in TIFF header. Must be 0x4949 (II) or 0x4d4d (MM).")
buff = f.read(2)
(magicByte,) = struct.unpack("{endian}H".format(endian=self.byteOrder), buff)
if magicByte == 42:
tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Magic Byte: {}".format(magicByte))
else:
raise Exception("TIFF magic byte is not 42: {}".format(self.filename))
# Get location of first image file directory (IFD):
buff = f.read(4)
(ifdPos,) = struct.unpack("{endian}L".format(endian=self.byteOrder), buff)
ifd0 = ifd()
ifd0.read(ifdPos, f, self.byteOrder)
self.subfiles = []
subfile0 = tiffSubfile()
subfile0.readMetaInformation(self.filename, self.byteOrder, ifd0)
self.subfiles.append(subfile0)
nextIfdPos = ifd0.nextIfdPos
while nextIfdPos != 0:
ifdn = ifd()
ifdn.read(nextIfdPos, f, self.byteOrder)
subfilen = tiffSubfile()
subfilen.readMetaInformation(self.filename, self.byteOrder, ifdn)
self.subfiles.append(subfilen)
nextIfdPos = ifdn.nextIfdPos
f.close()
else:
raise Exception("TIFF file does not contain any data: {}".format(self.filename))
else:
raise Exception("File not found: {}".format(self.filename))
def imageData(self, subfile=0, channel=None, obeyOrientation=True):
if subfile < len(self.subfiles):
data = self.subfiles[subfile].imageData()
if channel is None:
return data
else:
return data[0]
else:
raise Exception("Subfile id not available: {}".format(subfile))
def getNrSubfiles(self):
""" Return number of subfiles. """
return len(self.subfiles)
def getWidth(self, subfile=0):
if subfile < len(self.subfiles):
return self.subfiles[subfile].getWidth()
def getHeight(self, subfile=0):
if subfile < len(self.subfiles):
return self.subfiles[subfile].getHeight()
def sizeInBytes(self):
size = 8 # TIFF header
# Add size for each sub file:
for sub in self.subfiles:
size += sub.sizeInBytes()
return size
def prepareDataOffsets(self):
offset = 8 # TIFF header
for sub in self.subfiles:
offset = sub.prepareDataOffsets(offset)
def printOffset(self):
self.prepareDataOffsets()
tiffyLog(TIFFY_LOGLEVEL_INFO, "TIFF HEADER 0")
for sub in self.subfiles:
sub.printOffset()
def set(self, imageData, resX=0, resY=0):
self.reset()
self.addImgData(imageData, resX, resY)
def addImgData(self, imageData, resX=0, resY=0):
if len(numpy.shape(imageData)) == 2:
# We have to add another dimension for the channel...
imageData = numpy.expand_dims(a=imageData, axis=0)
if len(numpy.shape(imageData)) == 3:
img = tiffSubfile()
img.set(imageData, resX, resY)
self.subfiles.append(img)
# IFDs must be setup twice to ensure correct data offset pointers.
for sub in self.subfiles:
sub.setupIFD()
self.prepareDataOffsets()
for sub in self.subfiles:
sub.setupIFD()
else:
raise Exception("TIFF: adding image data failed. Image data must be a numpy array of shape (height, width) or (channels, height, width).")
def save(self, filename, endian="little"):
byteOrder = "<" # little endian is default
if endian == "big":
byteOrder = ">"
with open(filename, 'wb') as f:
# Write the TIFF header.
# Endian:
if byteOrder == ">":
buff = struct.pack("{endian}H".format(endian=byteOrder), 0x4d4d)
else:
buff = struct.pack("{endian}H".format(endian=byteOrder), 0x4949)
f.write(buff)
# Magic number:
buff = struct.pack("{endian}H".format(endian=byteOrder), 42)
f.write(buff)
# The IFD0 always follows the header in our case:
buff = struct.pack("{endian}L".format(endian=byteOrder), 8)
f.write(buff)
for sub in self.subfiles:
sub.write(f, byteOrder)
f.close()
Functions
def TIFFtypeStructCharacter(tp)
-
Expand source code
def TIFFtypeStructCharacter(tp): if tp > 0 and tp <= len(TIFF_BYTES_PER_TYPE): return TIFF_STRUCT_CHAR_FOR_TYPE[tp-1] raise Exception("Unknown TIFF data type: {}".format(tp))
def bytesPerTIFFtype(tp)
-
Expand source code
def bytesPerTIFFtype(tp): if tp > 0 and tp <= len(TIFF_BYTES_PER_TYPE): return TIFF_BYTES_PER_TYPE[tp-1] raise Exception("Unknown TIFF data type: {}".format(tp))
def getByteOrder(numpyArray)
-
Expand source code
def getByteOrder(numpyArray): byteOrder = numpyArray.dtype.byteorder if byteOrder == "=": # native byte order if sys.byteorder == "big": byteOrder = ">" else: byteOrder = "<" return byteOrder
def tagName(intTag)
-
Expand source code
def tagName(intTag): strTag = "{}".format(intTag) if strTag in TAGNAMES: return TAGNAMES[strTag] else: return "Unknown"
def tiffyLog(level, message)
-
Expand source code
def tiffyLog(level, message): if level < tiffy_currentLogLevel: print(message)
Classes
class bits
-
Expand source code
class bits: def __init__(self): self.data = bytearray() def set(self, n): """ Takes a number n and stores it in self.data in bitwise manner. """ self.data = bytearray() pos = 0 while pos>0: self.setBit(pos, n&1) n = n >> 1 pos += 1 def __str__(self): """ Print in binary representation. """ maxPos = len(self.data)*8 binString = "" pos = 0 nChunks = 0 nBytes = 0 while pos <= maxPos: if pos%9 == 0: binString = "." + binString nChunks += 1 if pos%8 == 0: binString = "|" + binString nBytes += 1 binString = "{}".format(self.getBit(pos)) + binString pos += 1 binString += "\n{} chunks in {} bytes.".format(nChunks, nBytes) return binString def setBytes(self, b): """ Takes a bytes object b and stores it in self.data. """ self.data = bytearray() self.data += b def reverseBitsInBytes(self): for i in range(len(self.data)): newByte = 0 for b in range(8): newByte = newByte << 1 newByte += ((self.data[i] >> b) & 1) self.data[i] = newByte """ def reverseBits(self, start, stop): newBits = bits() pos = 0 for i in range(stopBit-1, startBit-1, -1): newBits.setBit(pos, self.getBit(i)) pos += 1 for i in range(startBit, stopBit): self.setBit(i, self.getBit(i-startBit)) """ def byteAndBit(self, pos): """ From a given bit position, returns the byte index and the bit index within this byte. """ byteIdx = int(math.floor(pos/8)) inByte = pos%8 return byteIdx, inByte def getInt(self, startBit, stopBit): s = 0 if startBit < stopBit: for i in range(stopBit-1, startBit-1, -1): s = s << 1 s += self.getBit(i) return int(s) def getIntMSBtoLSB(self, startBit, stopBit): s = 0 for i in range(startBit, stopBit): # MSB to LSB s = s << 1 s += self.getBit(i) return s def getIntMSBtoLSB_inBytes(self, startByte, startBit, nBits): s = 0 # Maximum of three masks required (for 9..12 bits) mask1 = mask << startBit mask2 = 255 << (nBits-7) mask3 = 255 """ i = 0 #print("StartByte: {}, StartBit: {}, nBits: {}".format(startByte, startBit, nBits)) while i<nBits: # MSB to LSB s = s << 1 # Shift left by 1 s += ((self.data[startByte] >> (7-startBit)) & 1) startBit += 1 if startBit > 7: startBit = 0 startByte += 1 i += 1 """ startBit += nBits if startBit > 7: startBit = 0 startByte += 1 return s, startByte+1, startBit+nBits def getIntMSBtoLSB_faster(self, startByte, rightZeros, mask0, mask1, mask2): s = ((self.data[startByte]&mask0)<<16) + ((self.data[startByte+1]&mask1)<<8) + (self.data[startByte+2]&mask2) s = s >> rightZeros #newByte = 0 #for b in range(9): # newByte = newByte << 1 # newByte += ((s >> b) & 1) #return newByte return s def getBit(self, pos): byteIdx, inByte = self.byteAndBit(pos) if(byteIdx >= len(self.data)): return 0 return ((self.data[byteIdx] >> inByte) & 1) def getBitInByte(self, byteIdx, bitPos): return ((self.data[byteIdx] >> bitPos) & 1) def setBit(self, pos, value=1): byteIdx, inByte = self.byteAndBit(pos) while(byteIdx > len(self.data)): self.data += bytes(1) mask = 1 << inByte if value == 1: # set bit self.data[byteIdx] = self.data[byteIdx] | mask else: # clear bit self.data[byteIdx] = self.data[byteIdx] & ~mask def setBitInByte(self, byteIdx, bitPos, value=1): while(byteIdx > len(self.data)): self.data += bytes(1) mask = 1 << bitPos if value == 1: # set bit self.data[byteIdx] = self.data[byteIdx] | mask else: # clear bit self.data[byteIdx] = self.data[byteIdx] & ~mask
Methods
def byteAndBit(self, pos)
-
From a given bit position, returns the byte index and the bit index within this byte.
Expand source code
def byteAndBit(self, pos): """ From a given bit position, returns the byte index and the bit index within this byte. """ byteIdx = int(math.floor(pos/8)) inByte = pos%8 return byteIdx, inByte
def getBit(self, pos)
-
Expand source code
def getBit(self, pos): byteIdx, inByte = self.byteAndBit(pos) if(byteIdx >= len(self.data)): return 0 return ((self.data[byteIdx] >> inByte) & 1)
def getBitInByte(self, byteIdx, bitPos)
-
Expand source code
def getBitInByte(self, byteIdx, bitPos): return ((self.data[byteIdx] >> bitPos) & 1)
def getInt(self, startBit, stopBit)
-
Expand source code
def getInt(self, startBit, stopBit): s = 0 if startBit < stopBit: for i in range(stopBit-1, startBit-1, -1): s = s << 1 s += self.getBit(i) return int(s)
def getIntMSBtoLSB(self, startBit, stopBit)
-
Expand source code
def getIntMSBtoLSB(self, startBit, stopBit): s = 0 for i in range(startBit, stopBit): # MSB to LSB s = s << 1 s += self.getBit(i) return s
def getIntMSBtoLSB_faster(self, startByte, rightZeros, mask0, mask1, mask2)
-
Expand source code
def getIntMSBtoLSB_faster(self, startByte, rightZeros, mask0, mask1, mask2): s = ((self.data[startByte]&mask0)<<16) + ((self.data[startByte+1]&mask1)<<8) + (self.data[startByte+2]&mask2) s = s >> rightZeros #newByte = 0 #for b in range(9): # newByte = newByte << 1 # newByte += ((s >> b) & 1) #return newByte return s
def getIntMSBtoLSB_inBytes(self, startByte, startBit, nBits)
-
Expand source code
def getIntMSBtoLSB_inBytes(self, startByte, startBit, nBits): s = 0 # Maximum of three masks required (for 9..12 bits) mask1 = mask << startBit mask2 = 255 << (nBits-7) mask3 = 255 """ i = 0 #print("StartByte: {}, StartBit: {}, nBits: {}".format(startByte, startBit, nBits)) while i<nBits: # MSB to LSB s = s << 1 # Shift left by 1 s += ((self.data[startByte] >> (7-startBit)) & 1) startBit += 1 if startBit > 7: startBit = 0 startByte += 1 i += 1 """ startBit += nBits if startBit > 7: startBit = 0 startByte += 1 return s, startByte+1, startBit+nBits
def reverseBitsInBytes(self)
-
Expand source code
def reverseBitsInBytes(self): for i in range(len(self.data)): newByte = 0 for b in range(8): newByte = newByte << 1 newByte += ((self.data[i] >> b) & 1) self.data[i] = newByte
def set(self, n)
-
Takes a number n and stores it in self.data in bitwise manner.
Expand source code
def set(self, n): """ Takes a number n and stores it in self.data in bitwise manner. """ self.data = bytearray() pos = 0 while pos>0: self.setBit(pos, n&1) n = n >> 1 pos += 1
def setBit(self, pos, value=1)
-
Expand source code
def setBit(self, pos, value=1): byteIdx, inByte = self.byteAndBit(pos) while(byteIdx > len(self.data)): self.data += bytes(1) mask = 1 << inByte if value == 1: # set bit self.data[byteIdx] = self.data[byteIdx] | mask else: # clear bit self.data[byteIdx] = self.data[byteIdx] & ~mask
def setBitInByte(self, byteIdx, bitPos, value=1)
-
Expand source code
def setBitInByte(self, byteIdx, bitPos, value=1): while(byteIdx > len(self.data)): self.data += bytes(1) mask = 1 << bitPos if value == 1: # set bit self.data[byteIdx] = self.data[byteIdx] | mask else: # clear bit self.data[byteIdx] = self.data[byteIdx] & ~mask
def setBytes(self, b)
-
Takes a bytes object b and stores it in self.data.
Expand source code
def setBytes(self, b): """ Takes a bytes object b and stores it in self.data. """ self.data = bytearray() self.data += b
class ifd
-
Expand source code
class ifd: def __init__(self): self.ifdPos = None self.fieldBytes = [] self.fields = [] self.fieldCount = 0 self.nextIfdPos = 0 def addEntry(self, entry): self.fields.append(entry) def read(self, ifdPos, f, byteOrder): self.ifdPos = ifdPos tiffyLog(TIFFY_LOGLEVEL_INFO, " -- new IFD.") f.seek(ifdPos) buff = f.read(2) offset = ifdPos + 2 (self.fieldCount,) = struct.unpack("{endian}H".format(endian=byteOrder), buff) for i in range(self.fieldCount): entry = ifdEntry() entry.read(offset, f, byteOrder) self.fields.append(entry) offset += 12 f.seek(offset) buff = f.read(4) (self.nextIfdPos,) = struct.unpack("{endian}L".format(endian=byteOrder), buff) def sizeInBytes(self): size = 2 + 4 # Number of entries (2 bytes) + offset to next IFD (4 bytes) for field in self.fields: size += field.sizeInBytes() return size def prepareDataOffsets(self, offset): self.ifdPos = offset offset += 2 # number of entries (2 bytes) extraDataOffset = offset + 12*len(self.fields) + 4 # offset for each entry (12 bytes) + pointer to next IFD (4 bytes) for field in self.fields: offset, extraDataOffset = field.prepareDataOffsets(offset, extraDataOffset) return offset def printOffset(self): tiffyLog(TIFFY_LOGLEVEL_INFO, "+ IFD {offset} -> {nextIFD}".format(offset=self.ifdPos, nextIFD=self.nextIfdPos)) for entry in self.fields: entry.printOffset() for entry in self.fields: entry.printExtraDataOffset() def write(self, f, byteOrder): buff = struct.pack("{endian}H".format(endian=byteOrder), len(self.fields)) f.write(buff) tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Written IFD. Now at position {}".format(f.tell())) # IFD entries: for entry in self.fields: entry.write(f, byteOrder) buff = struct.pack("{endian}L".format(endian=byteOrder), self.nextIfdPos) f.write(buff) # extra data for entry in self.fields: entry.writeExtraValues(f, byteOrder)
Methods
def addEntry(self, entry)
-
Expand source code
def addEntry(self, entry): self.fields.append(entry)
def prepareDataOffsets(self, offset)
-
Expand source code
def prepareDataOffsets(self, offset): self.ifdPos = offset offset += 2 # number of entries (2 bytes) extraDataOffset = offset + 12*len(self.fields) + 4 # offset for each entry (12 bytes) + pointer to next IFD (4 bytes) for field in self.fields: offset, extraDataOffset = field.prepareDataOffsets(offset, extraDataOffset) return offset
def printOffset(self)
-
Expand source code
def printOffset(self): tiffyLog(TIFFY_LOGLEVEL_INFO, "+ IFD {offset} -> {nextIFD}".format(offset=self.ifdPos, nextIFD=self.nextIfdPos)) for entry in self.fields: entry.printOffset() for entry in self.fields: entry.printExtraDataOffset()
def read(self, ifdPos, f, byteOrder)
-
Expand source code
def read(self, ifdPos, f, byteOrder): self.ifdPos = ifdPos tiffyLog(TIFFY_LOGLEVEL_INFO, " -- new IFD.") f.seek(ifdPos) buff = f.read(2) offset = ifdPos + 2 (self.fieldCount,) = struct.unpack("{endian}H".format(endian=byteOrder), buff) for i in range(self.fieldCount): entry = ifdEntry() entry.read(offset, f, byteOrder) self.fields.append(entry) offset += 12 f.seek(offset) buff = f.read(4) (self.nextIfdPos,) = struct.unpack("{endian}L".format(endian=byteOrder), buff)
def sizeInBytes(self)
-
Expand source code
def sizeInBytes(self): size = 2 + 4 # Number of entries (2 bytes) + offset to next IFD (4 bytes) for field in self.fields: size += field.sizeInBytes() return size
def write(self, f, byteOrder)
-
Expand source code
def write(self, f, byteOrder): buff = struct.pack("{endian}H".format(endian=byteOrder), len(self.fields)) f.write(buff) tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Written IFD. Now at position {}".format(f.tell())) # IFD entries: for entry in self.fields: entry.write(f, byteOrder) buff = struct.pack("{endian}L".format(endian=byteOrder), self.nextIfdPos) f.write(buff) # extra data for entry in self.fields: entry.writeExtraValues(f, byteOrder)
class ifdEntry
-
Expand source code
class ifdEntry: def __init__(self): self.tag = 0 # TIFF Tag ID self.type = 0 # Field Type self.count = 0 # Number of values (NOT bytes) self.ifdEntryPos = 0 self.values = [] self.extraValuesOffset = 0 # storage offset (in byte) for any extra values that don't fit in the 4 values bytes of this IFD entry def set(self, tag, typ, values): self.setTagID(tag) self.setType(typ) self.setValues(values) def setTagID(self, tagid): self.tag = tagid def setType(self, typ): self.type = typ def setValue(self, value): if self.type in TIFF_INTTYPES: value = int(value) self.values = [value, ] self.count = 1 def setValues(self, values): self.values = [0] * len(values) for i in range(len(self.values)): if self.type in TIFF_INTTYPES: self.values[i] = int(values[i]) else: self.values[i] = values[i] self.count = len(values) def nValueBytes(self): return self.count * bytesPerTIFFtype(self.type) def read(self, offset, f, byteOrder): f.seek(offset) buff = f.read(8) (self.tag, self.type, self.count) = struct.unpack("{endian}HHL".format(endian=byteOrder), buff) # Calculate amount of bytes necessary for value(s): self.nValueBytes = self.nValueBytes() # Read value(s): f.seek(offset+8) buff = f.read(4) if self.nValueBytes > 4: # The field's actual value bytes must be read from the provided pointer. (valueOffset,) = struct.unpack("{endian}L".format(endian=byteOrder), buff) f.seek(valueOffset) buff = f.read(self.nValueBytes) # Prepare a struct pattern: structPattern = "" structCharacter = TIFFtypeStructCharacter(self.type) for i in range(self.count): structPattern += structCharacter # Fill rest of pattern with pad bytes: if self.nValueBytes < 4: for j in range(4 - self.nValueBytes): structPattern += "x" tup = struct.unpack("{endian}{pattern}".format(endian=byteOrder, pattern=structPattern), buff) if self.type == TIFF_RATIONAL or self.type == TIFF_SRATIONAL: for numerator, denominator in zip(tup, tup[1:]): val = 0 if denominator != 0: val = numerator / denominator self.values.append(val) else: for val in tup: self.values.append(val) if len(self.values) < 5: tiffyLog(TIFFY_LOGLEVEL_INFO, " -- Tag {tag} ({tagname}): {n} value(s) {val}".format(tag=self.tag, tagname=tagName(self.tag), n=len(self.values), val=self.values)) else: tiffyLog(TIFFY_LOGLEVEL_INFO, " -- Tag {tag} ({tagname}): {n} value(s)".format(tag=self.tag, tagname=tagName(self.tag), n=len(self.values))) def getValue(self): if len(self.values) > 0: return self.values[0] else: raise Exception("TIFF field with tag {tag} does not come with any value.".format(tag=self.tag)) def sizeInBytes(self): size = 12 size += self.sizeOfExtraValues() return size def sizeOfExtraValues(self): size = 0 # If the values don't fit in the IFD entry, they are stored somewhere else: if (bytesPerTIFFtype(self.type)*len(self.values)) > 4: size += bytesPerTIFFtype(self.type)*len(self.values) return size def printOffset(self): tiffyLog(TIFFY_LOGLEVEL_INFO, " IFD entry, tag {t} {offset}".format(t=self.tag, offset=self.ifdEntryPos)) def printExtraDataOffset(self): if self.nValueBytes() > 4: tiffyLog(TIFFY_LOGLEVEL_INFO, " IFD entry, tag {t} {offset} Extra Data".format(t=self.tag, offset=self.extraValuesOffset)) def prepareDataOffsets(self, offset, extraDataOffset): self.ifdEntryPos = offset self.extraValuesOffset = extraDataOffset return (offset+12), (extraDataOffset + self.sizeOfExtraValues()) def write(self, f, byteOrder): tiffyLog(TIFFY_LOGLEVEL_INFO, "IDF Entry at pos {}".format(f.tell())) buff = struct.pack("{endian}HHL".format(endian=byteOrder), self.tag, self.type, self.count) f.write(buff) if self.nValueBytes() > 4: # point to value storage offset tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Packing for tag {tag} (type {typ}, count {cnt}): Pointing to {offset}".format(tag=self.tag, typ=self.type, cnt=self.count, endian=byteOrder, offset=self.extraValuesOffset)) buff = struct.pack("{endian}L".format(endian=byteOrder), self.extraValuesOffset) f.write(buff) else: structChar = TIFFtypeStructCharacter(self.type) padding = 4 - self.nValueBytes() if padding > 0: tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Packing for tag {tag} (type {typ}, count {cnt}) at pos {pos}: {endian}{count}{structchar}{padding}x {vals}".format(tag=self.tag, typ=self.type, cnt=self.count, pos=f.tell(), endian=byteOrder, count=self.count, structchar=structChar, padding=padding, vals=self.values)) buff = struct.pack("{endian}{count}{structchar}{padding}x".format(endian=byteOrder, count=self.count, structchar=structChar, padding=padding), *self.values) f.write(buff) else: tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Packing for tag {tag} (type {typ}, count {cnt}) at pos {pos}: {endian}{count}{structchar} {vals}".format(tag=self.tag, typ=self.type, cnt=self.count, pos=f.tell(), endian=byteOrder, count=self.count, structchar=structChar, vals=self.values)) buff = struct.pack("{endian}{count}{structchar}".format(endian=byteOrder, count=self.count, structchar=structChar), *self.values) f.write(buff) def writeExtraValues(self, f, byteOrder): structChar = TIFFtypeStructCharacter(self.type) if self.nValueBytes() > 4: tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Packing for tag {tag} (type {typ}, count {cnt}) at pos {pos}: {endian}{count}{structchar} {vals}".format(tag=self.tag, typ=self.type, cnt=self.count, pos=f.tell(), endian=byteOrder, count=self.count, structchar=structChar, vals=self.values)) buff = struct.pack("{endian}{count}{structchar}".format(endian=byteOrder, count=self.count, structchar=structChar), *self.values) f.write(buff)
Methods
def getValue(self)
-
Expand source code
def getValue(self): if len(self.values) > 0: return self.values[0] else: raise Exception("TIFF field with tag {tag} does not come with any value.".format(tag=self.tag))
def nValueBytes(self)
-
Expand source code
def nValueBytes(self): return self.count * bytesPerTIFFtype(self.type)
def prepareDataOffsets(self, offset, extraDataOffset)
-
Expand source code
def prepareDataOffsets(self, offset, extraDataOffset): self.ifdEntryPos = offset self.extraValuesOffset = extraDataOffset return (offset+12), (extraDataOffset + self.sizeOfExtraValues())
def printExtraDataOffset(self)
-
Expand source code
def printExtraDataOffset(self): if self.nValueBytes() > 4: tiffyLog(TIFFY_LOGLEVEL_INFO, " IFD entry, tag {t} {offset} Extra Data".format(t=self.tag, offset=self.extraValuesOffset))
def printOffset(self)
-
Expand source code
def printOffset(self): tiffyLog(TIFFY_LOGLEVEL_INFO, " IFD entry, tag {t} {offset}".format(t=self.tag, offset=self.ifdEntryPos))
def read(self, offset, f, byteOrder)
-
Expand source code
def read(self, offset, f, byteOrder): f.seek(offset) buff = f.read(8) (self.tag, self.type, self.count) = struct.unpack("{endian}HHL".format(endian=byteOrder), buff) # Calculate amount of bytes necessary for value(s): self.nValueBytes = self.nValueBytes() # Read value(s): f.seek(offset+8) buff = f.read(4) if self.nValueBytes > 4: # The field's actual value bytes must be read from the provided pointer. (valueOffset,) = struct.unpack("{endian}L".format(endian=byteOrder), buff) f.seek(valueOffset) buff = f.read(self.nValueBytes) # Prepare a struct pattern: structPattern = "" structCharacter = TIFFtypeStructCharacter(self.type) for i in range(self.count): structPattern += structCharacter # Fill rest of pattern with pad bytes: if self.nValueBytes < 4: for j in range(4 - self.nValueBytes): structPattern += "x" tup = struct.unpack("{endian}{pattern}".format(endian=byteOrder, pattern=structPattern), buff) if self.type == TIFF_RATIONAL or self.type == TIFF_SRATIONAL: for numerator, denominator in zip(tup, tup[1:]): val = 0 if denominator != 0: val = numerator / denominator self.values.append(val) else: for val in tup: self.values.append(val) if len(self.values) < 5: tiffyLog(TIFFY_LOGLEVEL_INFO, " -- Tag {tag} ({tagname}): {n} value(s) {val}".format(tag=self.tag, tagname=tagName(self.tag), n=len(self.values), val=self.values)) else: tiffyLog(TIFFY_LOGLEVEL_INFO, " -- Tag {tag} ({tagname}): {n} value(s)".format(tag=self.tag, tagname=tagName(self.tag), n=len(self.values)))
def set(self, tag, typ, values)
-
Expand source code
def set(self, tag, typ, values): self.setTagID(tag) self.setType(typ) self.setValues(values)
def setTagID(self, tagid)
-
Expand source code
def setTagID(self, tagid): self.tag = tagid
def setType(self, typ)
-
Expand source code
def setType(self, typ): self.type = typ
def setValue(self, value)
-
Expand source code
def setValue(self, value): if self.type in TIFF_INTTYPES: value = int(value) self.values = [value, ] self.count = 1
def setValues(self, values)
-
Expand source code
def setValues(self, values): self.values = [0] * len(values) for i in range(len(self.values)): if self.type in TIFF_INTTYPES: self.values[i] = int(values[i]) else: self.values[i] = values[i] self.count = len(values)
def sizeInBytes(self)
-
Expand source code
def sizeInBytes(self): size = 12 size += self.sizeOfExtraValues() return size
def sizeOfExtraValues(self)
-
Expand source code
def sizeOfExtraValues(self): size = 0 # If the values don't fit in the IFD entry, they are stored somewhere else: if (bytesPerTIFFtype(self.type)*len(self.values)) > 4: size += bytesPerTIFFtype(self.type)*len(self.values) return size
def write(self, f, byteOrder)
-
Expand source code
def write(self, f, byteOrder): tiffyLog(TIFFY_LOGLEVEL_INFO, "IDF Entry at pos {}".format(f.tell())) buff = struct.pack("{endian}HHL".format(endian=byteOrder), self.tag, self.type, self.count) f.write(buff) if self.nValueBytes() > 4: # point to value storage offset tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Packing for tag {tag} (type {typ}, count {cnt}): Pointing to {offset}".format(tag=self.tag, typ=self.type, cnt=self.count, endian=byteOrder, offset=self.extraValuesOffset)) buff = struct.pack("{endian}L".format(endian=byteOrder), self.extraValuesOffset) f.write(buff) else: structChar = TIFFtypeStructCharacter(self.type) padding = 4 - self.nValueBytes() if padding > 0: tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Packing for tag {tag} (type {typ}, count {cnt}) at pos {pos}: {endian}{count}{structchar}{padding}x {vals}".format(tag=self.tag, typ=self.type, cnt=self.count, pos=f.tell(), endian=byteOrder, count=self.count, structchar=structChar, padding=padding, vals=self.values)) buff = struct.pack("{endian}{count}{structchar}{padding}x".format(endian=byteOrder, count=self.count, structchar=structChar, padding=padding), *self.values) f.write(buff) else: tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Packing for tag {tag} (type {typ}, count {cnt}) at pos {pos}: {endian}{count}{structchar} {vals}".format(tag=self.tag, typ=self.type, cnt=self.count, pos=f.tell(), endian=byteOrder, count=self.count, structchar=structChar, vals=self.values)) buff = struct.pack("{endian}{count}{structchar}".format(endian=byteOrder, count=self.count, structchar=structChar), *self.values) f.write(buff)
def writeExtraValues(self, f, byteOrder)
-
Expand source code
def writeExtraValues(self, f, byteOrder): structChar = TIFFtypeStructCharacter(self.type) if self.nValueBytes() > 4: tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Packing for tag {tag} (type {typ}, count {cnt}) at pos {pos}: {endian}{count}{structchar} {vals}".format(tag=self.tag, typ=self.type, cnt=self.count, pos=f.tell(), endian=byteOrder, count=self.count, structchar=structChar, vals=self.values)) buff = struct.pack("{endian}{count}{structchar}".format(endian=byteOrder, count=self.count, structchar=structChar), *self.values) f.write(buff)
class lzwData
-
Expand source code
class lzwData: def __init__(self): self.compressed = None self.umcompressed = None self.stringTable = lzwStringTable() self.currentBitPos = 0 self.currentByteIdx = 0 self.currentBitPosInByte = 0 # in byte self.codeWidth = 9 # Starting with 9 bit wide codes self.masks = {} #[[[0]*3]*8]*14 self.byteSkips = {} # [[0]*8]*14 self.leftAlignOffsets = {} for codeWidth in range(9, 14): fundMask = 2**codeWidth - 1 for shift in range(0, 8): # Bitshift / startBit leftAlignOffset = 3*8 - codeWidth - shift mask = fundMask << leftAlignOffset mask0 = (mask >> 16) & 255 # 00000000 00000000 11111111 mask1 = (mask >> 8) & 255 # 00000000 11111111 00000000 mask2 = (mask) & 255 # 11111111 00000000 00000000 self.masks[codeWidth,shift,0] = mask0 self.masks[codeWidth,shift,1] = mask1 self.masks[codeWidth,shift,2] = mask2 self.byteSkips[codeWidth,shift] = int(math.ceil((codeWidth+shift+1)/8))-1 self.leftAlignOffsets[codeWidth,shift] = leftAlignOffset def resetStringtable(self): self.stringTable.init() self.codeWidth = 9 def setCompressed(self, compressed): self.compressed = bits() self.compressed.setBytes(compressed) self.compressed.data += bytes(3) # for integer conversion def getNextCode(self): #s1 = self.currentBitPos #s2 = s1 + self.codeWidth #code = self.compressed.getIntMSBtoLSB(startBit=s1, stopBit=s2) #code, self.currentByteIdx, self.currentBitPosInByte = self.compressed.getIntMSBtoLSB_inBytes(startByte=self.currentByteIdx, startBit=self.currentBitPosInByte, nBits=self.codeWidth) startByte = self.currentByteIdx mask0=self.masks[self.codeWidth,self.currentBitPosInByte,0] mask1=self.masks[self.codeWidth,self.currentBitPosInByte,1] mask2=self.masks[self.codeWidth,self.currentBitPosInByte,2] code = self.compressed.getIntMSBtoLSB_faster(startByte=startByte, rightZeros=self.leftAlignOffsets[self.codeWidth,self.currentBitPosInByte], mask0=mask0, mask1=mask1, mask2=mask2) tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Requesting {}({}). Skip: {} CodeWidth: {} Offset: {} Masks: {:08b}.{:08b}.{:08b} -> {}".format(startByte, self.currentBitPosInByte, self.byteSkips[self.codeWidth,self.currentBitPosInByte], self.codeWidth, self.currentBitPosInByte, mask0, mask1, mask2, code)) self.currentByteIdx += self.byteSkips[self.codeWidth,self.currentBitPosInByte] self.currentBitPosInByte += self.codeWidth self.currentBitPosInByte = self.currentBitPosInByte % 8 return code def decompress(self): self.uncompressed = bytearray() self.currentBitPos = 0 oldCode = -1 code = self.getNextCode() self.stringTable.init() # If compressed data doesn't start with ClearCode while not self.stringTable.isEndOfInformation(code): if(self.stringTable.isClearCode(code)): self.resetStringtable() code = self.getNextCode() if(self.stringTable.isEndOfInformation(code)): break self.uncompressed += self.stringTable.stringFromCode(code, self.codeWidth) oldCode = code else: if self.stringTable.contains(code): s = self.stringTable.stringFromCode(code, self.codeWidth) self.uncompressed += s self.stringTable.add(self.stringTable.stringFromCode(oldCode, self.codeWidth) + bytes([s[0]])) else: s = self.stringTable.stringFromCode(oldCode, self.codeWidth) outString = s + bytes([s[0]]) self.uncompressed += outString self.stringTable.add(outString) oldCode = code self.codeWidth = self.stringTable.currentCodeBitWidth() code = self.getNextCode()
Methods
def decompress(self)
-
Expand source code
def decompress(self): self.uncompressed = bytearray() self.currentBitPos = 0 oldCode = -1 code = self.getNextCode() self.stringTable.init() # If compressed data doesn't start with ClearCode while not self.stringTable.isEndOfInformation(code): if(self.stringTable.isClearCode(code)): self.resetStringtable() code = self.getNextCode() if(self.stringTable.isEndOfInformation(code)): break self.uncompressed += self.stringTable.stringFromCode(code, self.codeWidth) oldCode = code else: if self.stringTable.contains(code): s = self.stringTable.stringFromCode(code, self.codeWidth) self.uncompressed += s self.stringTable.add(self.stringTable.stringFromCode(oldCode, self.codeWidth) + bytes([s[0]])) else: s = self.stringTable.stringFromCode(oldCode, self.codeWidth) outString = s + bytes([s[0]]) self.uncompressed += outString self.stringTable.add(outString) oldCode = code self.codeWidth = self.stringTable.currentCodeBitWidth() code = self.getNextCode()
def getNextCode(self)
-
Expand source code
def getNextCode(self): #s1 = self.currentBitPos #s2 = s1 + self.codeWidth #code = self.compressed.getIntMSBtoLSB(startBit=s1, stopBit=s2) #code, self.currentByteIdx, self.currentBitPosInByte = self.compressed.getIntMSBtoLSB_inBytes(startByte=self.currentByteIdx, startBit=self.currentBitPosInByte, nBits=self.codeWidth) startByte = self.currentByteIdx mask0=self.masks[self.codeWidth,self.currentBitPosInByte,0] mask1=self.masks[self.codeWidth,self.currentBitPosInByte,1] mask2=self.masks[self.codeWidth,self.currentBitPosInByte,2] code = self.compressed.getIntMSBtoLSB_faster(startByte=startByte, rightZeros=self.leftAlignOffsets[self.codeWidth,self.currentBitPosInByte], mask0=mask0, mask1=mask1, mask2=mask2) tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Requesting {}({}). Skip: {} CodeWidth: {} Offset: {} Masks: {:08b}.{:08b}.{:08b} -> {}".format(startByte, self.currentBitPosInByte, self.byteSkips[self.codeWidth,self.currentBitPosInByte], self.codeWidth, self.currentBitPosInByte, mask0, mask1, mask2, code)) self.currentByteIdx += self.byteSkips[self.codeWidth,self.currentBitPosInByte] self.currentBitPosInByte += self.codeWidth self.currentBitPosInByte = self.currentBitPosInByte % 8 return code
def resetStringtable(self)
-
Expand source code
def resetStringtable(self): self.stringTable.init() self.codeWidth = 9
def setCompressed(self, compressed)
-
Expand source code
def setCompressed(self, compressed): self.compressed = bits() self.compressed.setBytes(compressed) self.compressed.data += bytes(3) # for integer conversion
class lzwStringTable
-
Expand source code
class lzwStringTable: def __init__(self): self.byteStrings = [] self.init() def init(self): self.byteStrings = [] # Create all bytes: for i in range(256): self.byteStrings.append(bytearray(struct.pack("B", i))) self.byteStrings.append(bytearray()) # ClearCode: 256 self.byteStrings.append(bytearray()) # EndOfInformation code: 257 def currentCodeBitWidth(self): """ l = len(self.byteStrings) + 1 bitWidth = 0 while l != 0: l = l >> 1 bitWidth += 1 return bitWidth """ if len(self.byteStrings) < 255: return 8 elif len(self.byteStrings) < 511: return 9 elif len(self.byteStrings) < 1023: return 10 elif len(self.byteStrings) < 2047: return 11 elif len(self.byteStrings) < 4095: return 12 elif len(self.byteStrings) < 8191: return 13 elif len(self.byteStrings) < 16383: return 14 else: raise Exception("LZW: Dictionary is too big.") def contains(self, code): if code < len(self.byteStrings): return True return False def add(self, b): self.byteStrings.append(b) def isClearCode(self, code): if code == 256: return True return False def isEndOfInformation(self, code): if code == 257: return True return False def stringFromCode(self, code, codeWidth): #if code < 0: # return bytearray() return self.byteStrings[code] """ if code < len(self.byteStrings): #print("String from code {}: {}".format(code, self.byteStrings[code])) return self.byteStrings[code] else: raise Exception("LZW: Requested code #{} not in current string table. Current string table size: {}. Current bit width: {}".format(code, len(self.byteStrings), codeWidth)) """
Methods
def add(self, b)
-
Expand source code
def add(self, b): self.byteStrings.append(b)
def contains(self, code)
-
Expand source code
def contains(self, code): if code < len(self.byteStrings): return True return False
def currentCodeBitWidth(self)
-
l = len(self.byteStrings) + 1 bitWidth = 0 while l != 0: l = l >> 1 bitWidth += 1
return bitWidth
Expand source code
def currentCodeBitWidth(self): """ l = len(self.byteStrings) + 1 bitWidth = 0 while l != 0: l = l >> 1 bitWidth += 1 return bitWidth """ if len(self.byteStrings) < 255: return 8 elif len(self.byteStrings) < 511: return 9 elif len(self.byteStrings) < 1023: return 10 elif len(self.byteStrings) < 2047: return 11 elif len(self.byteStrings) < 4095: return 12 elif len(self.byteStrings) < 8191: return 13 elif len(self.byteStrings) < 16383: return 14 else: raise Exception("LZW: Dictionary is too big.")
def init(self)
-
Expand source code
def init(self): self.byteStrings = [] # Create all bytes: for i in range(256): self.byteStrings.append(bytearray(struct.pack("B", i))) self.byteStrings.append(bytearray()) # ClearCode: 256 self.byteStrings.append(bytearray()) # EndOfInformation code: 257
def isClearCode(self, code)
-
Expand source code
def isClearCode(self, code): if code == 256: return True return False
def isEndOfInformation(self, code)
-
Expand source code
def isEndOfInformation(self, code): if code == 257: return True return False
def stringFromCode(self, code, codeWidth)
-
Expand source code
def stringFromCode(self, code, codeWidth): #if code < 0: # return bytearray() return self.byteStrings[code] """ if code < len(self.byteStrings): #print("String from code {}: {}".format(code, self.byteStrings[code])) return self.byteStrings[code] else: raise Exception("LZW: Requested code #{} not in current string table. Current string table size: {}. Current bit width: {}".format(code, len(self.byteStrings), codeWidth)) """
class tiff
-
Expand source code
class tiff: def __init__(self): self.filename = None self.byteOrder = "<" self.subfiles = [] def reset(self): self.__init__() def read(self, filename=None): self.filename = filename if self.filename is not None: if os.path.isfile(self.filename): filesize = os.path.getsize(self.filename) tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Size of {filename}: {size} Bytes.".format(filename=self.filename, size=filesize)) if filesize > 2: with open(self.filename, "rb") as f: buff = f.read(2) (tiffformat,) = struct.unpack("<H", buff) # Just read as little endian. It's symmetric, it doesn't matter. self.byteOrder = "<" # little endian if tiffformat == 0x4949: tiffyLog(TIFFY_LOGLEVEL_DEBUG, "II format. Little endian.") self.byteOrder = "<" elif tiffformat == 0x4d4d: tiffyLog(TIFFY_LOGLEVEL_DEBUG, "MM format. Big endian.") self.byteOrder = ">" else: f.close() raise Exception("Invalid byte order for first two bytes in TIFF header. Must be 0x4949 (II) or 0x4d4d (MM).") buff = f.read(2) (magicByte,) = struct.unpack("{endian}H".format(endian=self.byteOrder), buff) if magicByte == 42: tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Magic Byte: {}".format(magicByte)) else: raise Exception("TIFF magic byte is not 42: {}".format(self.filename)) # Get location of first image file directory (IFD): buff = f.read(4) (ifdPos,) = struct.unpack("{endian}L".format(endian=self.byteOrder), buff) ifd0 = ifd() ifd0.read(ifdPos, f, self.byteOrder) self.subfiles = [] subfile0 = tiffSubfile() subfile0.readMetaInformation(self.filename, self.byteOrder, ifd0) self.subfiles.append(subfile0) nextIfdPos = ifd0.nextIfdPos while nextIfdPos != 0: ifdn = ifd() ifdn.read(nextIfdPos, f, self.byteOrder) subfilen = tiffSubfile() subfilen.readMetaInformation(self.filename, self.byteOrder, ifdn) self.subfiles.append(subfilen) nextIfdPos = ifdn.nextIfdPos f.close() else: raise Exception("TIFF file does not contain any data: {}".format(self.filename)) else: raise Exception("File not found: {}".format(self.filename)) def imageData(self, subfile=0, channel=None, obeyOrientation=True): if subfile < len(self.subfiles): data = self.subfiles[subfile].imageData() if channel is None: return data else: return data[0] else: raise Exception("Subfile id not available: {}".format(subfile)) def getNrSubfiles(self): """ Return number of subfiles. """ return len(self.subfiles) def getWidth(self, subfile=0): if subfile < len(self.subfiles): return self.subfiles[subfile].getWidth() def getHeight(self, subfile=0): if subfile < len(self.subfiles): return self.subfiles[subfile].getHeight() def sizeInBytes(self): size = 8 # TIFF header # Add size for each sub file: for sub in self.subfiles: size += sub.sizeInBytes() return size def prepareDataOffsets(self): offset = 8 # TIFF header for sub in self.subfiles: offset = sub.prepareDataOffsets(offset) def printOffset(self): self.prepareDataOffsets() tiffyLog(TIFFY_LOGLEVEL_INFO, "TIFF HEADER 0") for sub in self.subfiles: sub.printOffset() def set(self, imageData, resX=0, resY=0): self.reset() self.addImgData(imageData, resX, resY) def addImgData(self, imageData, resX=0, resY=0): if len(numpy.shape(imageData)) == 2: # We have to add another dimension for the channel... imageData = numpy.expand_dims(a=imageData, axis=0) if len(numpy.shape(imageData)) == 3: img = tiffSubfile() img.set(imageData, resX, resY) self.subfiles.append(img) # IFDs must be setup twice to ensure correct data offset pointers. for sub in self.subfiles: sub.setupIFD() self.prepareDataOffsets() for sub in self.subfiles: sub.setupIFD() else: raise Exception("TIFF: adding image data failed. Image data must be a numpy array of shape (height, width) or (channels, height, width).") def save(self, filename, endian="little"): byteOrder = "<" # little endian is default if endian == "big": byteOrder = ">" with open(filename, 'wb') as f: # Write the TIFF header. # Endian: if byteOrder == ">": buff = struct.pack("{endian}H".format(endian=byteOrder), 0x4d4d) else: buff = struct.pack("{endian}H".format(endian=byteOrder), 0x4949) f.write(buff) # Magic number: buff = struct.pack("{endian}H".format(endian=byteOrder), 42) f.write(buff) # The IFD0 always follows the header in our case: buff = struct.pack("{endian}L".format(endian=byteOrder), 8) f.write(buff) for sub in self.subfiles: sub.write(f, byteOrder) f.close()
Methods
def addImgData(self, imageData, resX=0, resY=0)
-
Expand source code
def addImgData(self, imageData, resX=0, resY=0): if len(numpy.shape(imageData)) == 2: # We have to add another dimension for the channel... imageData = numpy.expand_dims(a=imageData, axis=0) if len(numpy.shape(imageData)) == 3: img = tiffSubfile() img.set(imageData, resX, resY) self.subfiles.append(img) # IFDs must be setup twice to ensure correct data offset pointers. for sub in self.subfiles: sub.setupIFD() self.prepareDataOffsets() for sub in self.subfiles: sub.setupIFD() else: raise Exception("TIFF: adding image data failed. Image data must be a numpy array of shape (height, width) or (channels, height, width).")
def getHeight(self, subfile=0)
-
Expand source code
def getHeight(self, subfile=0): if subfile < len(self.subfiles): return self.subfiles[subfile].getHeight()
def getNrSubfiles(self)
-
Return number of subfiles.
Expand source code
def getNrSubfiles(self): """ Return number of subfiles. """ return len(self.subfiles)
def getWidth(self, subfile=0)
-
Expand source code
def getWidth(self, subfile=0): if subfile < len(self.subfiles): return self.subfiles[subfile].getWidth()
def imageData(self, subfile=0, channel=None, obeyOrientation=True)
-
Expand source code
def imageData(self, subfile=0, channel=None, obeyOrientation=True): if subfile < len(self.subfiles): data = self.subfiles[subfile].imageData() if channel is None: return data else: return data[0] else: raise Exception("Subfile id not available: {}".format(subfile))
def prepareDataOffsets(self)
-
Expand source code
def prepareDataOffsets(self): offset = 8 # TIFF header for sub in self.subfiles: offset = sub.prepareDataOffsets(offset)
def printOffset(self)
-
Expand source code
def printOffset(self): self.prepareDataOffsets() tiffyLog(TIFFY_LOGLEVEL_INFO, "TIFF HEADER 0") for sub in self.subfiles: sub.printOffset()
def read(self, filename=None)
-
Expand source code
def read(self, filename=None): self.filename = filename if self.filename is not None: if os.path.isfile(self.filename): filesize = os.path.getsize(self.filename) tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Size of {filename}: {size} Bytes.".format(filename=self.filename, size=filesize)) if filesize > 2: with open(self.filename, "rb") as f: buff = f.read(2) (tiffformat,) = struct.unpack("<H", buff) # Just read as little endian. It's symmetric, it doesn't matter. self.byteOrder = "<" # little endian if tiffformat == 0x4949: tiffyLog(TIFFY_LOGLEVEL_DEBUG, "II format. Little endian.") self.byteOrder = "<" elif tiffformat == 0x4d4d: tiffyLog(TIFFY_LOGLEVEL_DEBUG, "MM format. Big endian.") self.byteOrder = ">" else: f.close() raise Exception("Invalid byte order for first two bytes in TIFF header. Must be 0x4949 (II) or 0x4d4d (MM).") buff = f.read(2) (magicByte,) = struct.unpack("{endian}H".format(endian=self.byteOrder), buff) if magicByte == 42: tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Magic Byte: {}".format(magicByte)) else: raise Exception("TIFF magic byte is not 42: {}".format(self.filename)) # Get location of first image file directory (IFD): buff = f.read(4) (ifdPos,) = struct.unpack("{endian}L".format(endian=self.byteOrder), buff) ifd0 = ifd() ifd0.read(ifdPos, f, self.byteOrder) self.subfiles = [] subfile0 = tiffSubfile() subfile0.readMetaInformation(self.filename, self.byteOrder, ifd0) self.subfiles.append(subfile0) nextIfdPos = ifd0.nextIfdPos while nextIfdPos != 0: ifdn = ifd() ifdn.read(nextIfdPos, f, self.byteOrder) subfilen = tiffSubfile() subfilen.readMetaInformation(self.filename, self.byteOrder, ifdn) self.subfiles.append(subfilen) nextIfdPos = ifdn.nextIfdPos f.close() else: raise Exception("TIFF file does not contain any data: {}".format(self.filename)) else: raise Exception("File not found: {}".format(self.filename))
def reset(self)
-
Expand source code
def reset(self): self.__init__()
def save(self, filename, endian='little')
-
Expand source code
def save(self, filename, endian="little"): byteOrder = "<" # little endian is default if endian == "big": byteOrder = ">" with open(filename, 'wb') as f: # Write the TIFF header. # Endian: if byteOrder == ">": buff = struct.pack("{endian}H".format(endian=byteOrder), 0x4d4d) else: buff = struct.pack("{endian}H".format(endian=byteOrder), 0x4949) f.write(buff) # Magic number: buff = struct.pack("{endian}H".format(endian=byteOrder), 42) f.write(buff) # The IFD0 always follows the header in our case: buff = struct.pack("{endian}L".format(endian=byteOrder), 8) f.write(buff) for sub in self.subfiles: sub.write(f, byteOrder) f.close()
def set(self, imageData, resX=0, resY=0)
-
Expand source code
def set(self, imageData, resX=0, resY=0): self.reset() self.addImgData(imageData, resX, resY)
def sizeInBytes(self)
-
Expand source code
def sizeInBytes(self): size = 8 # TIFF header # Add size for each sub file: for sub in self.subfiles: size += sub.sizeInBytes() return size
class tiffSubfile
-
Expand source code
class tiffSubfile: def __init__(self): self.px = [] # Array of numpy arrays to store image data (i.e. pixel values). One for each component/channel. Shape: (nChannel, nRows, nCols) self.imageDataOffset = 0 # Location of beginning of image data self.sampleFormat = [TIFF_SAMPLEFORMAT_UINT] # Standard: uint self.filename = None self.byteOrder = "<" # little endian self.ifd = None self.photometricInterpretation = TIFF_BLACK_IS_ZERO self.compression = TIFF_NO_COMPRESSION self.predictor = TIFF_NO_PREDICTOR self.rows = 0 self.cols = 0 self.resolutionUnit = TIFF_RES_INCH self.resX = 0 self.resY = 0 self.rowsPerStrip = 0 self.stripOffsets = [] self.stripByteCounts = [] self.bitsPerSample = (1,) # Bilevel images do not define this, so make 1 bit/sample the default. self.samplesPerPixel = 1 self.planarConfig = TIFF_CHUNKY # Chunky (RGBRGBRGB...) self.orientation = 1 self.colorMap = None # For palette-color images def reset(self): self.__init__() def set(self, imageData, resX=0, resY=0): if len(imageData) > 0: self.reset() self.px = imageData shp = numpy.shape(self.px) # Shape must be 3-component tuple: (nChannels, height, width) if len(shp) == 3: self.samplesPerPixel = shp[0] self.rows = shp[1] self.cols = shp[2] self.rowsPerStrip = self.rows # self.stripByteCounts and self.stripOffsets will be set later by self.prepareDataOffsets() # Byte order self.byteOrder = getByteOrder(self.px) # Sample format if numpy.issubdtype(self.px.dtype, numpy.signedinteger): self.sampleFormat = [TIFF_SAMPLEFORMAT_INT] * self.nChannels() elif numpy.issubdtype(self.px.dtype, numpy.unsignedinteger): self.sampleFormat = [TIFF_SAMPLEFORMAT_UINT] * self.nChannels() elif numpy.issubdtype(self.px.dtype, numpy.floating): self.sampleFormat = [TIFF_SAMPLEFORMAT_IEEEFP] * self.nChannels() else: raise Exception("Unsupported dtype ({}) of provided image data. Must be an integer or floating point type.".format(numpy.dtype(self.px))) self.bitsPerSample = [self.px.dtype.itemsize*8] * self.samplesPerPixel tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Setting bits per sample: {}".format(self.bitsPerSample)) tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Setting sample format: {}".format(self.sampleFormat)) self.resolutionUnit = TIFF_RES_NONE self.resX = resX self.resY = resY self.compression = TIFF_NO_COMPRESSION self.photometricInterpretation = TIFF_BLACK_IS_ZERO if self.nChannels() == 3: self.photometricInterpretation = TIFF_RGB #if self.samplesPerPixel > 1: # self.planarConfig = TIFF_PLANAR #else: # self.planarConfig = TIFF_CHUNKY self.planarConfig = TIFF_CHUNKY else: raise Exception("Error setting image data. Please provide a numpy array of shape (nChannels, nRows, nColumns).") def addIFDentry_shortOrLong(self, tag, values): # find the right integer type for the provided values: m = max(values) typ = TIFF_LONG if m < (2**16): typ = TIFF_SHORT self.addIFDentry(tag, typ, values) def addIFDentry(self, tag, typ, values): entry = ifdEntry() entry.set(tag, typ, values) self.ifd.addEntry(entry) def setupIFD(self): self.ifd = None self.ifd = ifd() self.addIFDentry_shortOrLong(256, (self.cols, )) # n columns self.addIFDentry_shortOrLong(257, (self.rows, )) # n rows self.addIFDentry(258, TIFF_SHORT, self.bitsPerSample) # bits per sample, already an array self.addIFDentry(259, TIFF_SHORT, (TIFF_NO_COMPRESSION, )) # compression self.addIFDentry(262, TIFF_SHORT, (self.photometricInterpretation, )) # photometric interpretation self.addIFDentry(266, TIFF_SHORT, (TIFF_MSB2LSB, )) # fill order self.addIFDentry_shortOrLong(273, (self.imageDataOffset, )) # offset location of strip self.addIFDentry(274, TIFF_SHORT, (self.orientation, )) # orientation self.addIFDentry(277, TIFF_SHORT, (self.samplesPerPixel, )) # samples per pixel self.addIFDentry_shortOrLong(278, (self.rowsPerStrip, )) # rows per strip self.addIFDentry_shortOrLong(279, (self.dataSizeInBytes(), )) # strip byte counts # insert resolution entries here later... self.addIFDentry(284, TIFF_SHORT, (self.planarConfig, )) # planar configuration #self.addIFDentry(296, TIFF_SHORT, (self.resolutionUnit, )) # resolution unit if self.predictor != TIFF_NO_PREDICTOR: self.addIFDentry(317, TIFF_SHORT, (self.predictor, )) # predictor # color map self.addIFDentry(339, TIFF_SHORT, self.sampleFormat) # sample format, already an array def dataSizeInBytes(self): """ Size of pixel data (in bytes) """ size = 0 for bitsPerSample in self.bitsPerSample: s = int(bitsPerSample * self.nPixels()) size += s return size/8 def sizeInBytes(self): size = 0 if self.ifd is not None: size += self.ifd.sizeInBytes() size += self.dataSizeInBytes() return size def prepareDataOffsets(self, offset): if self.ifd is not None: self.ifd.prepareDataOffsets(offset) offset += self.ifd.sizeInBytes() self.imageDataOffset = offset # image data starts here self.stripOffsets = (self.imageDataOffset, ) self.stripByteCounts = self.dataSizeInBytes() offset += self.dataSizeInBytes() return offset else: return 0 def printOffset(self): if self.ifd is not None: self.ifd.printOffset() tiffyLog(TIFFY_LOGLEVEL_INFO, " Image Data {offset}".format(offset=self.imageDataOffset)) def readMetaInformation(self, filename, byteOrder, imgFileDirectory): self.filename = filename self.byteOrder = byteOrder self.ifd = imgFileDirectory # Interpret IFD fields based on their TIFF tags: for field in self.ifd.fields: if field.tag == 256: # Number of columns self.cols = field.getValue() elif field.tag == 257: # Number of rows self.rows = field.getValue() elif field.tag == 258: # Bits per sample self.bitsPerSample = field.values elif field.tag == 259: # Compression self.compression = field.getValue() if self.compression == 0: self.compression = TIFF_NO_COMPRESSION elif field.tag == 262: # Photometric interpretation self.photometricInterpretation = field.getValue() elif field.tag == 273: # Strip offsets self.stripOffsets = field.values elif field.tag == 274: # Orientation self.orientation = field.getValue() elif field.tag == 277: # Samples per pixel self.samplesPerPixel = field.getValue() elif field.tag == 278: # Rows per strip self.rowsPerStrip = field.getValue() elif field.tag == 279: # Strip byte counts self.stripByteCounts = field.values elif field.tag == 282: # x resolution self.resX = field.getValue() elif field.tag == 283: # y resolution self.resY = field.getValue() elif field.tag == 284: # Planar configuration (order of pixel components) self.planarConfig = field.getValue() elif field.tag == 296: # Resolution unit self.resolutionUnit = field.getValue() elif field.tag == 317: # Predictor self.predictor = field.getValue() elif field.tag == 320: # Color map pass # implement later... elif field.tag == 339: # Sample format self.sampleFormat = field.values def nChannels(self): return self.samplesPerPixel def nPixels(self): return self.rows*self.cols def bitsPerPixel(self): bits = 0 for b in self.bitsPerSample: bits += b return bits def isPlanar(self): return (self.samplesPerPixel == 1 or self.planarConfig == TIFF_PLANAR) def isSet(self): """ Check if image has a valid width and height. """ if(self.getHeight() > 0): if(self.getWidth() > 0): return True return False def getWidth(self): return self.cols def getHeight(self): return self.rows def rot90(self): if self.isSet(): self.px = numpy.rot90(self.px, k=1, axes=(1,2)) self.cols, self.rows = self.rows, self.cols def rot180(self): if self.isSet(): self.px = numpy.rot90(self.px, k=2, axes=(1,2)) def rot270(self): if self.isSet(): self.px = numpy.rot90(self.px, k=-1, axes=(1,2)) self.cols, self.rows = self.rows, self.cols def rotate(self, rotation): if rotation == "90": self.rot90() elif rotation == "180": self.rot180() elif rotation == "270": self.rot270() def flipHorizontal(self): if self.isSet(): for i in range(self.nChannels()): self.px[i] = numpy.fliplr(self.px[i]) def flipVertical(self): if self.isSet(): for i in range(self.nChannels()): self.px[i] = numpy.flipud(self.px[i]) def setFlip(self, horz=False, vert=False): self.flipHorz = horz self.flipVert = vert def getHorizontalFlip(self): return self.flipHorz def getVerticalFlip(self): return self.flipVert def flip(self, horizontal=False, vertical=False): if horizontal: self.flipHorizontal() if vertical: self.flipVertical() def numpyDatatype(self, sampleFormat, bitsPerSample): formatString = self.byteOrder if sampleFormat == TIFF_SAMPLEFORMAT_UINT: # unsigned if bitsPerSample == 8: formatString = "u1" # unsigned char (1 byte) elif bitsPerSample == 16: formatString += "u2" # unsigned short (2 bytes) elif bitsPerSample == 32: formatString += "u4" # unsigned long (4 bytes) elif bitsPerSample == 64: formatString += "u8" # unsigned long long (8 bytes) elif sampleFormat == TIFF_SAMPLEFORMAT_INT: # signed if bitsPerSample == 8: formatString = "i1" # signed char (1 byte) elif bitsPerSample == 16: formatString += "i2" # signed short (2 bytes) elif bitsPerSample == 32: formatString += "i4" # signed long (4 bytes) elif bitsPerSample == 64: formatString += "i8" # signed long long (8 bytes) elif sampleFormat == TIFF_SAMPLEFORMAT_IEEEFP: if bitsPerSample == 16: formatString += "f2" # float 16 bit elif bitsPerSample == 32: formatString += "f4" # float 32 bit elif bitsPerSample == 64: formatString += "f8" # double 64 bit if len(formatString) >= 1: return numpy.dtype(formatString) raise Exception("Unsupported data type: {bps} bits per sample for TIFF sample format {sf}.".format(bps=bitsPerSample, sf=sampleFormat)) def structDataTypeString(self, sampleFormat, bitsPerSample): formatString = self.byteOrder if sampleFormat == TIFF_SAMPLEFORMAT_UINT: # unsigned if bitsPerSample == 8: formatString = "B" # unsigned char (1 byte) elif bitsPerSample == 16: formatString += "H" # unsigned short (2 bytes) elif bitsPerSample == 32: formatString += "L" # unsigned long (4 bytes) elif bitsPerSample == 64: formatString += "Q" # unsigned long long (8 bytes) elif sampleFormat == TIFF_SAMPLEFORMAT_INT: # signed if bitsPerSample == 8: formatString = "b" # signed char (1 byte) elif bitsPerSample == 16: formatString += "h" # signed short (2 bytes) elif bitsPerSample == 32: formatString += "l" # signed long (4 bytes) elif bitsPerSample == 64: formatString += "q" # signed long long (8 bytes) elif sampleFormat == TIFF_SAMPLEFORMAT_IEEEFP: if bitsPerSample == 16: formatString += "e" # float 16 bit elif bitsPerSample == 32: formatString += "f" # float 32 bit elif bitsPerSample == 64: formatString += "d" # double 64 bit if len(formatString) >= 1: return formatString raise Exception("Unsupported data type: {bps} bits per sample for TIFF sample format {sf}.".format(bps=bitsPerSample, sf=sampleFormat)) def importFromUncompressedBuffer(self, pixelOffset, nPixels, datatype, channel, buff): if self.isPlanar(): # Buffer contains data just for one channel. self.px[channel][int(pixelOffset):int(pixelOffset+nPixels)] = numpy.frombuffer(buff, dtype=datatype) else: # CHUNKY configuration. channel is irrelevant here (and wrong.) bitsPerPixel = self.bitsPerPixel() bytesPerPixel = int(bitsPerPixel / 8) bytesPerSample = [x/8 for x in self.bitsPerSample] buff1d = numpy.frombuffer(buff, dtype=datatype) self.px[int(pixelOffset):int(pixelOffset+nPixels)] = numpy.reshape(buff1d, (nPixels, self.nChannels())) """ for pixel in range(nPixels): channelOffset = 0 for c in range(self.samplesPerPixel): # Current index in bytes idxStart = int((pixel + pixelOffset)*bytesPerPixel + channelOffset/8) idxStop = int(idxStart + bytesPerSample[c]) #print("Start: {}, Stop: {}".format(idxStart, idxStop)) self.px[c][pixel] = numpy.frombuffer(buff[idxStart:idxStop], dtype=dt) channelOffset += self.bitsPerSample[c] """ def imageData(self, obeyOrientation=True): # Read data into byte buffer: if len(self.stripOffsets) > 0: if len(self.stripOffsets) == len(self.stripByteCounts): if os.path.isfile(self.filename): with open(self.filename, "rb") as f: # Initialize nChannels x nPixels array for each channel: # Check if bits per sample is the same for each channel: if self.bitsPerSample.count(self.bitsPerSample[0]) == len(self.bitsPerSample) and self.sampleFormat.count(self.sampleFormat[0]) == len(self.sampleFormat): sampleFormat = self.sampleFormat[0] bitsPerSample = self.bitsPerSample[0] dt = self.numpyDatatype(sampleFormat, bitsPerSample) if self.isPlanar(): self.px = numpy.zeros((self.nChannels(), self.nPixels()), dtype=dt) else: # Make array the shape of a chunky tiff configuration and reshape later... self.px = numpy.zeros((self.nPixels(), self.nChannels()), dtype=dt) nStrips = len(self.stripOffsets) if self.compression == TIFF_NO_COMPRESSION or self.compression == TIFF_LZW_COMPRESSION: pixel = 0 bitsPerPixel = self.bitsPerPixel() c = 0 # channel id. Only necessary for PLANAR configuration. nStripsPerChannel = int(nStrips / self.samplesPerPixel) for i in range(nStrips): if self.samplesPerPixel > 1: if self.planarConfig == TIFF_PLANAR: # Import next channel once all strips for one component channel have been imported. if (i % nStripsPerChannel) == 0: c += 1 offset = self.stripOffsets[i] byteCount = self.stripByteCounts[i] # byte count after compression #print("Strip #{}/{}, Byte Offset: {}, Byte Count: {}".format(i, nStrips, offset, byteCount)) f.seek(offset) buff = f.read(byteCount) if self.compression == TIFF_LZW_COMPRESSION: compressed = lzwData() compressed.setCompressed(buff) #print("Decompressing LZW for strip {i}/{n}...".format(i=i, n=nStrips)) compressed.decompress() buff = bytes(compressed.uncompressed) byteCount = len(buff) bitCount = 8*byteCount if not self.isPlanar(): # Standard is chunky, also for only 1 sample/pixel. pixelsInStrip = int(bitCount / bitsPerPixel) else: pixelsInStrip = int(bitCount / self.bitsPerSample[c]) self.importFromUncompressedBuffer( pixelOffset = pixel, nPixels = pixelsInStrip, datatype = dt, channel = c, buff = buff) pixel += pixelsInStrip tiffyLog(TIFFY_LOGLEVEL_INFO, "{} strips imported.".format(nStrips)) else: raise Exception("TIFF: Compression scheme {} not supported.".format(self.compression)) f.close() if not(self.isPlanar()): # Chunky mode. Swap axes from (pixels, channels) to (channels, pixels): self.px = numpy.swapaxes(self.px, 0, 1) # Reshape components into 2D arrays: if len(self.px) > 0: self.px = numpy.reshape(self.px, (self.nChannels(), self.rows, self.cols)) # Convert horizontal differences to absolute values: tiffyLog(TIFFY_LOGLEVEL_INFO, "Applying horizontal differencing...") if self.predictor == TIFF_HORIZONTAL_DIFFERENCING: for col in range(1, self.cols): self.px[...,col] = self.px[...,col-1] + self.px[...,col] tiffyLog(TIFFY_LOGLEVEL_INFO, "Orientation is: {}".format(self.orientation)) if obeyOrientation: # Rotate back to orientation 1 ((0,0) is upper left) if self.orientation == 2: # (col0, row0) is (top, right) self.flip(horizontal=True, vertical=False) elif self.orientation == 3: # (col0, row0) is (bottom, right) self.flip(horizontal=True, vertical=True) elif self.orientation == 4: # (col0, row0) is bottom left self.flip(horizontal=False, vertical=True) elif self.orientation == 5: # (col0, row0) is (left, top) self.rotate("90") self.flip(horizontal=False, vertical=True) elif self.orientation == 6: # (col0, row0) is (right, top) self.rotate("270") elif self.orientation == 7: # (col0, row0) is (right, bottom) self.rotate("90") self.flip(horizontal=True, vertical=False) elif self.orientation == 8: # (col0, row0) is (left, bottom) self.rotate("90") self.orientation = 1 return self.px else: f.close() raise Exception("Unsupported TIFF format: all channels must have the same type and size. Sample format: {}, Bits per sample: {}".format(self.sampleFormat, self.bitsPerSample)) raise Exception("File not available: {}".format(self.filename)) else: raise Exception("Number of strip offsets ({nOffsets}) does not match number of strip byte counts ({nByteCounts}).".format(nOffsets=len(self.stripOffsets), nByteCounts=len(self.stripByteCounts))) else: raise Exception("No data strips found for requested subfile in {filename}.".format(filename=self.filename)) def write(self, f, byteOrder): """ Expects an open, writable file pointer f. """ self.ifd.write(f, byteOrder) dataByteOrder = getByteOrder(self.px) if dataByteOrder != byteOrder: self.px.byteswap(inplace=True) # Write image data: if self.samplesPerPixel == 1: f.write(self.px) else: chunkyBytes = numpy.swapaxes(self.px, 0, 2) # channel <-> cols --> (cols, rows, channel) chunkyBytes = numpy.swapaxes(chunkyBytes, 0, 1) # cols <-> rows --> (rows, cols, channel) chunkyBytes.tofile(f, "") """ dataString = [] for c in range(self.nChannels()): dataString.append(self.structDataTypeString(self.sampleFormat[c], self.bitsPerSample[c])) for y in range(self.rows): for x in range(self.cols): # Chunky style. for c in range(self.nChannels()): buff = struct.pack("{endian}{ds}".format(endian=byteOrder, ds=dataString[c]), self.px[c][y][x]) f.write(buff) """
Methods
def addIFDentry(self, tag, typ, values)
-
Expand source code
def addIFDentry(self, tag, typ, values): entry = ifdEntry() entry.set(tag, typ, values) self.ifd.addEntry(entry)
def addIFDentry_shortOrLong(self, tag, values)
-
Expand source code
def addIFDentry_shortOrLong(self, tag, values): # find the right integer type for the provided values: m = max(values) typ = TIFF_LONG if m < (2**16): typ = TIFF_SHORT self.addIFDentry(tag, typ, values)
def bitsPerPixel(self)
-
Expand source code
def bitsPerPixel(self): bits = 0 for b in self.bitsPerSample: bits += b return bits
def dataSizeInBytes(self)
-
Size of pixel data (in bytes)
Expand source code
def dataSizeInBytes(self): """ Size of pixel data (in bytes) """ size = 0 for bitsPerSample in self.bitsPerSample: s = int(bitsPerSample * self.nPixels()) size += s return size/8
def flip(self, horizontal=False, vertical=False)
-
Expand source code
def flip(self, horizontal=False, vertical=False): if horizontal: self.flipHorizontal() if vertical: self.flipVertical()
def flipHorizontal(self)
-
Expand source code
def flipHorizontal(self): if self.isSet(): for i in range(self.nChannels()): self.px[i] = numpy.fliplr(self.px[i])
def flipVertical(self)
-
Expand source code
def flipVertical(self): if self.isSet(): for i in range(self.nChannels()): self.px[i] = numpy.flipud(self.px[i])
def getHeight(self)
-
Expand source code
def getHeight(self): return self.rows
def getHorizontalFlip(self)
-
Expand source code
def getHorizontalFlip(self): return self.flipHorz
def getVerticalFlip(self)
-
Expand source code
def getVerticalFlip(self): return self.flipVert
def getWidth(self)
-
Expand source code
def getWidth(self): return self.cols
def imageData(self, obeyOrientation=True)
-
Expand source code
def imageData(self, obeyOrientation=True): # Read data into byte buffer: if len(self.stripOffsets) > 0: if len(self.stripOffsets) == len(self.stripByteCounts): if os.path.isfile(self.filename): with open(self.filename, "rb") as f: # Initialize nChannels x nPixels array for each channel: # Check if bits per sample is the same for each channel: if self.bitsPerSample.count(self.bitsPerSample[0]) == len(self.bitsPerSample) and self.sampleFormat.count(self.sampleFormat[0]) == len(self.sampleFormat): sampleFormat = self.sampleFormat[0] bitsPerSample = self.bitsPerSample[0] dt = self.numpyDatatype(sampleFormat, bitsPerSample) if self.isPlanar(): self.px = numpy.zeros((self.nChannels(), self.nPixels()), dtype=dt) else: # Make array the shape of a chunky tiff configuration and reshape later... self.px = numpy.zeros((self.nPixels(), self.nChannels()), dtype=dt) nStrips = len(self.stripOffsets) if self.compression == TIFF_NO_COMPRESSION or self.compression == TIFF_LZW_COMPRESSION: pixel = 0 bitsPerPixel = self.bitsPerPixel() c = 0 # channel id. Only necessary for PLANAR configuration. nStripsPerChannel = int(nStrips / self.samplesPerPixel) for i in range(nStrips): if self.samplesPerPixel > 1: if self.planarConfig == TIFF_PLANAR: # Import next channel once all strips for one component channel have been imported. if (i % nStripsPerChannel) == 0: c += 1 offset = self.stripOffsets[i] byteCount = self.stripByteCounts[i] # byte count after compression #print("Strip #{}/{}, Byte Offset: {}, Byte Count: {}".format(i, nStrips, offset, byteCount)) f.seek(offset) buff = f.read(byteCount) if self.compression == TIFF_LZW_COMPRESSION: compressed = lzwData() compressed.setCompressed(buff) #print("Decompressing LZW for strip {i}/{n}...".format(i=i, n=nStrips)) compressed.decompress() buff = bytes(compressed.uncompressed) byteCount = len(buff) bitCount = 8*byteCount if not self.isPlanar(): # Standard is chunky, also for only 1 sample/pixel. pixelsInStrip = int(bitCount / bitsPerPixel) else: pixelsInStrip = int(bitCount / self.bitsPerSample[c]) self.importFromUncompressedBuffer( pixelOffset = pixel, nPixels = pixelsInStrip, datatype = dt, channel = c, buff = buff) pixel += pixelsInStrip tiffyLog(TIFFY_LOGLEVEL_INFO, "{} strips imported.".format(nStrips)) else: raise Exception("TIFF: Compression scheme {} not supported.".format(self.compression)) f.close() if not(self.isPlanar()): # Chunky mode. Swap axes from (pixels, channels) to (channels, pixels): self.px = numpy.swapaxes(self.px, 0, 1) # Reshape components into 2D arrays: if len(self.px) > 0: self.px = numpy.reshape(self.px, (self.nChannels(), self.rows, self.cols)) # Convert horizontal differences to absolute values: tiffyLog(TIFFY_LOGLEVEL_INFO, "Applying horizontal differencing...") if self.predictor == TIFF_HORIZONTAL_DIFFERENCING: for col in range(1, self.cols): self.px[...,col] = self.px[...,col-1] + self.px[...,col] tiffyLog(TIFFY_LOGLEVEL_INFO, "Orientation is: {}".format(self.orientation)) if obeyOrientation: # Rotate back to orientation 1 ((0,0) is upper left) if self.orientation == 2: # (col0, row0) is (top, right) self.flip(horizontal=True, vertical=False) elif self.orientation == 3: # (col0, row0) is (bottom, right) self.flip(horizontal=True, vertical=True) elif self.orientation == 4: # (col0, row0) is bottom left self.flip(horizontal=False, vertical=True) elif self.orientation == 5: # (col0, row0) is (left, top) self.rotate("90") self.flip(horizontal=False, vertical=True) elif self.orientation == 6: # (col0, row0) is (right, top) self.rotate("270") elif self.orientation == 7: # (col0, row0) is (right, bottom) self.rotate("90") self.flip(horizontal=True, vertical=False) elif self.orientation == 8: # (col0, row0) is (left, bottom) self.rotate("90") self.orientation = 1 return self.px else: f.close() raise Exception("Unsupported TIFF format: all channels must have the same type and size. Sample format: {}, Bits per sample: {}".format(self.sampleFormat, self.bitsPerSample)) raise Exception("File not available: {}".format(self.filename)) else: raise Exception("Number of strip offsets ({nOffsets}) does not match number of strip byte counts ({nByteCounts}).".format(nOffsets=len(self.stripOffsets), nByteCounts=len(self.stripByteCounts))) else: raise Exception("No data strips found for requested subfile in {filename}.".format(filename=self.filename))
def importFromUncompressedBuffer(self, pixelOffset, nPixels, datatype, channel, buff)
-
Expand source code
def importFromUncompressedBuffer(self, pixelOffset, nPixels, datatype, channel, buff): if self.isPlanar(): # Buffer contains data just for one channel. self.px[channel][int(pixelOffset):int(pixelOffset+nPixels)] = numpy.frombuffer(buff, dtype=datatype) else: # CHUNKY configuration. channel is irrelevant here (and wrong.) bitsPerPixel = self.bitsPerPixel() bytesPerPixel = int(bitsPerPixel / 8) bytesPerSample = [x/8 for x in self.bitsPerSample] buff1d = numpy.frombuffer(buff, dtype=datatype) self.px[int(pixelOffset):int(pixelOffset+nPixels)] = numpy.reshape(buff1d, (nPixels, self.nChannels())) """ for pixel in range(nPixels): channelOffset = 0 for c in range(self.samplesPerPixel): # Current index in bytes idxStart = int((pixel + pixelOffset)*bytesPerPixel + channelOffset/8) idxStop = int(idxStart + bytesPerSample[c]) #print("Start: {}, Stop: {}".format(idxStart, idxStop)) self.px[c][pixel] = numpy.frombuffer(buff[idxStart:idxStop], dtype=dt) channelOffset += self.bitsPerSample[c] """
def isPlanar(self)
-
Expand source code
def isPlanar(self): return (self.samplesPerPixel == 1 or self.planarConfig == TIFF_PLANAR)
def isSet(self)
-
Check if image has a valid width and height.
Expand source code
def isSet(self): """ Check if image has a valid width and height. """ if(self.getHeight() > 0): if(self.getWidth() > 0): return True return False
def nChannels(self)
-
Expand source code
def nChannels(self): return self.samplesPerPixel
def nPixels(self)
-
Expand source code
def nPixels(self): return self.rows*self.cols
def numpyDatatype(self, sampleFormat, bitsPerSample)
-
Expand source code
def numpyDatatype(self, sampleFormat, bitsPerSample): formatString = self.byteOrder if sampleFormat == TIFF_SAMPLEFORMAT_UINT: # unsigned if bitsPerSample == 8: formatString = "u1" # unsigned char (1 byte) elif bitsPerSample == 16: formatString += "u2" # unsigned short (2 bytes) elif bitsPerSample == 32: formatString += "u4" # unsigned long (4 bytes) elif bitsPerSample == 64: formatString += "u8" # unsigned long long (8 bytes) elif sampleFormat == TIFF_SAMPLEFORMAT_INT: # signed if bitsPerSample == 8: formatString = "i1" # signed char (1 byte) elif bitsPerSample == 16: formatString += "i2" # signed short (2 bytes) elif bitsPerSample == 32: formatString += "i4" # signed long (4 bytes) elif bitsPerSample == 64: formatString += "i8" # signed long long (8 bytes) elif sampleFormat == TIFF_SAMPLEFORMAT_IEEEFP: if bitsPerSample == 16: formatString += "f2" # float 16 bit elif bitsPerSample == 32: formatString += "f4" # float 32 bit elif bitsPerSample == 64: formatString += "f8" # double 64 bit if len(formatString) >= 1: return numpy.dtype(formatString) raise Exception("Unsupported data type: {bps} bits per sample for TIFF sample format {sf}.".format(bps=bitsPerSample, sf=sampleFormat))
def prepareDataOffsets(self, offset)
-
Expand source code
def prepareDataOffsets(self, offset): if self.ifd is not None: self.ifd.prepareDataOffsets(offset) offset += self.ifd.sizeInBytes() self.imageDataOffset = offset # image data starts here self.stripOffsets = (self.imageDataOffset, ) self.stripByteCounts = self.dataSizeInBytes() offset += self.dataSizeInBytes() return offset else: return 0
def printOffset(self)
-
Expand source code
def printOffset(self): if self.ifd is not None: self.ifd.printOffset() tiffyLog(TIFFY_LOGLEVEL_INFO, " Image Data {offset}".format(offset=self.imageDataOffset))
def readMetaInformation(self, filename, byteOrder, imgFileDirectory)
-
Expand source code
def readMetaInformation(self, filename, byteOrder, imgFileDirectory): self.filename = filename self.byteOrder = byteOrder self.ifd = imgFileDirectory # Interpret IFD fields based on their TIFF tags: for field in self.ifd.fields: if field.tag == 256: # Number of columns self.cols = field.getValue() elif field.tag == 257: # Number of rows self.rows = field.getValue() elif field.tag == 258: # Bits per sample self.bitsPerSample = field.values elif field.tag == 259: # Compression self.compression = field.getValue() if self.compression == 0: self.compression = TIFF_NO_COMPRESSION elif field.tag == 262: # Photometric interpretation self.photometricInterpretation = field.getValue() elif field.tag == 273: # Strip offsets self.stripOffsets = field.values elif field.tag == 274: # Orientation self.orientation = field.getValue() elif field.tag == 277: # Samples per pixel self.samplesPerPixel = field.getValue() elif field.tag == 278: # Rows per strip self.rowsPerStrip = field.getValue() elif field.tag == 279: # Strip byte counts self.stripByteCounts = field.values elif field.tag == 282: # x resolution self.resX = field.getValue() elif field.tag == 283: # y resolution self.resY = field.getValue() elif field.tag == 284: # Planar configuration (order of pixel components) self.planarConfig = field.getValue() elif field.tag == 296: # Resolution unit self.resolutionUnit = field.getValue() elif field.tag == 317: # Predictor self.predictor = field.getValue() elif field.tag == 320: # Color map pass # implement later... elif field.tag == 339: # Sample format self.sampleFormat = field.values
def reset(self)
-
Expand source code
def reset(self): self.__init__()
def rot180(self)
-
Expand source code
def rot180(self): if self.isSet(): self.px = numpy.rot90(self.px, k=2, axes=(1,2))
def rot270(self)
-
Expand source code
def rot270(self): if self.isSet(): self.px = numpy.rot90(self.px, k=-1, axes=(1,2)) self.cols, self.rows = self.rows, self.cols
def rot90(self)
-
Expand source code
def rot90(self): if self.isSet(): self.px = numpy.rot90(self.px, k=1, axes=(1,2)) self.cols, self.rows = self.rows, self.cols
def rotate(self, rotation)
-
Expand source code
def rotate(self, rotation): if rotation == "90": self.rot90() elif rotation == "180": self.rot180() elif rotation == "270": self.rot270()
def set(self, imageData, resX=0, resY=0)
-
Expand source code
def set(self, imageData, resX=0, resY=0): if len(imageData) > 0: self.reset() self.px = imageData shp = numpy.shape(self.px) # Shape must be 3-component tuple: (nChannels, height, width) if len(shp) == 3: self.samplesPerPixel = shp[0] self.rows = shp[1] self.cols = shp[2] self.rowsPerStrip = self.rows # self.stripByteCounts and self.stripOffsets will be set later by self.prepareDataOffsets() # Byte order self.byteOrder = getByteOrder(self.px) # Sample format if numpy.issubdtype(self.px.dtype, numpy.signedinteger): self.sampleFormat = [TIFF_SAMPLEFORMAT_INT] * self.nChannels() elif numpy.issubdtype(self.px.dtype, numpy.unsignedinteger): self.sampleFormat = [TIFF_SAMPLEFORMAT_UINT] * self.nChannels() elif numpy.issubdtype(self.px.dtype, numpy.floating): self.sampleFormat = [TIFF_SAMPLEFORMAT_IEEEFP] * self.nChannels() else: raise Exception("Unsupported dtype ({}) of provided image data. Must be an integer or floating point type.".format(numpy.dtype(self.px))) self.bitsPerSample = [self.px.dtype.itemsize*8] * self.samplesPerPixel tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Setting bits per sample: {}".format(self.bitsPerSample)) tiffyLog(TIFFY_LOGLEVEL_DEBUG, "Setting sample format: {}".format(self.sampleFormat)) self.resolutionUnit = TIFF_RES_NONE self.resX = resX self.resY = resY self.compression = TIFF_NO_COMPRESSION self.photometricInterpretation = TIFF_BLACK_IS_ZERO if self.nChannels() == 3: self.photometricInterpretation = TIFF_RGB #if self.samplesPerPixel > 1: # self.planarConfig = TIFF_PLANAR #else: # self.planarConfig = TIFF_CHUNKY self.planarConfig = TIFF_CHUNKY else: raise Exception("Error setting image data. Please provide a numpy array of shape (nChannels, nRows, nColumns).")
def setFlip(self, horz=False, vert=False)
-
Expand source code
def setFlip(self, horz=False, vert=False): self.flipHorz = horz self.flipVert = vert
def setupIFD(self)
-
Expand source code
def setupIFD(self): self.ifd = None self.ifd = ifd() self.addIFDentry_shortOrLong(256, (self.cols, )) # n columns self.addIFDentry_shortOrLong(257, (self.rows, )) # n rows self.addIFDentry(258, TIFF_SHORT, self.bitsPerSample) # bits per sample, already an array self.addIFDentry(259, TIFF_SHORT, (TIFF_NO_COMPRESSION, )) # compression self.addIFDentry(262, TIFF_SHORT, (self.photometricInterpretation, )) # photometric interpretation self.addIFDentry(266, TIFF_SHORT, (TIFF_MSB2LSB, )) # fill order self.addIFDentry_shortOrLong(273, (self.imageDataOffset, )) # offset location of strip self.addIFDentry(274, TIFF_SHORT, (self.orientation, )) # orientation self.addIFDentry(277, TIFF_SHORT, (self.samplesPerPixel, )) # samples per pixel self.addIFDentry_shortOrLong(278, (self.rowsPerStrip, )) # rows per strip self.addIFDentry_shortOrLong(279, (self.dataSizeInBytes(), )) # strip byte counts # insert resolution entries here later... self.addIFDentry(284, TIFF_SHORT, (self.planarConfig, )) # planar configuration #self.addIFDentry(296, TIFF_SHORT, (self.resolutionUnit, )) # resolution unit if self.predictor != TIFF_NO_PREDICTOR: self.addIFDentry(317, TIFF_SHORT, (self.predictor, )) # predictor # color map self.addIFDentry(339, TIFF_SHORT, self.sampleFormat) # sample format, already an array
def sizeInBytes(self)
-
Expand source code
def sizeInBytes(self): size = 0 if self.ifd is not None: size += self.ifd.sizeInBytes() size += self.dataSizeInBytes() return size
def structDataTypeString(self, sampleFormat, bitsPerSample)
-
Expand source code
def structDataTypeString(self, sampleFormat, bitsPerSample): formatString = self.byteOrder if sampleFormat == TIFF_SAMPLEFORMAT_UINT: # unsigned if bitsPerSample == 8: formatString = "B" # unsigned char (1 byte) elif bitsPerSample == 16: formatString += "H" # unsigned short (2 bytes) elif bitsPerSample == 32: formatString += "L" # unsigned long (4 bytes) elif bitsPerSample == 64: formatString += "Q" # unsigned long long (8 bytes) elif sampleFormat == TIFF_SAMPLEFORMAT_INT: # signed if bitsPerSample == 8: formatString = "b" # signed char (1 byte) elif bitsPerSample == 16: formatString += "h" # signed short (2 bytes) elif bitsPerSample == 32: formatString += "l" # signed long (4 bytes) elif bitsPerSample == 64: formatString += "q" # signed long long (8 bytes) elif sampleFormat == TIFF_SAMPLEFORMAT_IEEEFP: if bitsPerSample == 16: formatString += "e" # float 16 bit elif bitsPerSample == 32: formatString += "f" # float 32 bit elif bitsPerSample == 64: formatString += "d" # double 64 bit if len(formatString) >= 1: return formatString raise Exception("Unsupported data type: {bps} bits per sample for TIFF sample format {sf}.".format(bps=bitsPerSample, sf=sampleFormat))
def write(self, f, byteOrder)
-
Expects an open, writable file pointer f.
Expand source code
def write(self, f, byteOrder): """ Expects an open, writable file pointer f. """ self.ifd.write(f, byteOrder) dataByteOrder = getByteOrder(self.px) if dataByteOrder != byteOrder: self.px.byteswap(inplace=True) # Write image data: if self.samplesPerPixel == 1: f.write(self.px) else: chunkyBytes = numpy.swapaxes(self.px, 0, 2) # channel <-> cols --> (cols, rows, channel) chunkyBytes = numpy.swapaxes(chunkyBytes, 0, 1) # cols <-> rows --> (rows, cols, channel) chunkyBytes.tofile(f, "") """ dataString = [] for c in range(self.nChannels()): dataString.append(self.structDataTypeString(self.sampleFormat[c], self.bitsPerSample[c])) for y in range(self.rows): for x in range(self.cols): # Chunky style. for c in range(self.nChannels()): buff = struct.pack("{endian}{ds}".format(endian=byteOrder, ds=dataString[c]), self.px[c][y][x]) f.write(buff) """