[ome-devel] TIFF pyramid support in Bio-Formats - reference files for review
Damir Sudar
dsudar at lbl.gov
Mon Mar 26 23:41:26 BST 2018
Hi Roger,
I'm having a bit of fun with creating type "C" pyramidal OME-Tiffs. The
makepyramid-ptif script works for single-channel files but I expanded it
a bit to allow creation of multi-channel ones as well. Enclosed is
makepyramid-multi. That didn't make the script any prettier but as you
said, this is only a hack to create some test data and better understand
how best to structure the tiff and associated ome-xml. Using the multi
script, I generated a 2-channel image with pyramids inside and I
attached bogus "label" and "macro" images (had to hand-edit the XML to
get them to meet OME-XML requirements). I uploaded that file to the QA
system for your enjoyment.
>> - the Leica-Fluorescence-1.scn (and thus all its result files) has the
>> channels stored in a way that is not exactly conform how one would
>> normally store the channels in the OME-standard way. Am I correct in
>> thinking that those channels would normally be in separate top-level
>> IFDs each and have their sub-resolutions arranged in SubIFDs under each
>> of those top-levels?
>
> Both are permitted, even combined, though this way is certainly less
> typical for fluorescence. For RGB data it's expected though. You're
> absolutely correct about each top-level IFD having separate
> sub-resolutions.
One thing I noticed about the Leica-Fluorescence-1 type "C" ome-tiff
result file, is that while it passes xmlvalid, showinf doesn't really
like it. The file I uploaded appears to pass all tests and I even
uploaded it to OMERO which of course doesn't even see the sub-resolution
SubIFDs but has no issues with it.
If the team indeed decides to go with the "C" approach, this indicates
that it should ??always?? be possible to create the pyramid OME-TIFF
file in such a way that any old-style OME-TIFF reader that is compatible
with the current standard, should be able to read the new-style files
and simply ignore the subresolutions.
>
>> - also, all these examples carry some of those ancillary images such as
>> slide labels, overview images, etc. While the scripts handled those
>> correctly as far as I can see, a reader would have to be pretty smart to
>> figure out which of the contents of such an output file is the actual
>> image and which are those ancillary images.
>
> Absolutely. We will need to do something about these as a followup
> task, to add the metadata to identify these as labels, overviews, etc.
>
In the file I uploaded the mocked up label and macro/overview images are
stored as top-level IFDs and in the OME-XML coded as separate series.
Would this be an appropriate way to store such ancillary images? If so,
then we could codify where in the file they should be (at the end?),
standardize what name they should have to identify them, and anything else?
Cheers,
- Damir
>
> Kind regards,
> Roger
>
> The University of Dundee is a registered Scottish Charity, No: SC015096
--
Damir Sudar - Affiliate Scientist
Lawrence Berkeley Natl Laboratory / MBIB
One Cyclotron Road, MS 977, Berkeley, CA 94720, USA
T: 510/486-5346 - F: 510/486-5586 - E: DSudar at lbl.gov
http://biosciences.lbl.gov/profiles/damir-sudar-2/
Visiting Scientist, Oregon Health & Science University
-------------- next part --------------
#!/bin/bash
set -e
set -x
# Check if file is BigTIFF
# $1 file
bigtiff() {
if [ "0000000 4949 002b" != "$(dd if="$1" iflag=count_bytes count=4 | od -x | head -n1)" ]; then
return 1
fi
return 0
}
# Get fieldoffset for TIFF
# $1 file
fieldoffset() {
if bigtiff "$1"; then
echo 8
else
echo 2
fi
}
# Get fieldsize for TIFF
# $1 file
fieldsize() {
if bigtiff "$1"; then
echo 20
else
echo 12
fi
}
# Get IFD offsets
# $1=IFD number
# $2=file
diroffsets() {
tiffinfo "$dest" | grep "TIFF Directory at offset" | sed -e 's;.*(\(.*\))$;\1;'
}
# Get offset for IFD
# $1=IFD number
# $2=file
diroffset() {
diroffsets "$2" | head -n$(($1 + 1)) | tail -n1
}
# Get number of tags in directory
# $1=IFD offset
# $2=file
ntags() {
echo "od -j $1 -N 2 -d \"$2\"" >&2
od -j $1 -N 2 -d "$2" | head -n1 | sed -e 's;^[0-9]* *\(.*\);\1;'
}
# Offset of next pointer in IFD
# $1=IFD offset
# $2=file
nextoffset() {
echo "$(($1 + $(fieldoffset "$2") + ($(ntags $1 "$2") * $(fieldsize "$2"))))"
}
# Write uint64 little endian value to binary file
# $1=value
# $2=destination file
# $3=offset in file
update_uint64_le() {
if bigtiff "$2"; then
printf "$(printf %.16x $1 | sed -e 's;\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\);\8\7\6\5\4\3\2\1;' | sed -e 's;\(..\);\\x\1;g')" | dd of="$2" conv=notrunc,nocreat oflag=seek_bytes seek=$3
else
printf "$(printf %.8x $1 | sed -e 's;\(..\)\(..\)\(..\)\(..\);\4\3\2\1;' | sed -e 's;\(..\);\\x\1;g')" | dd of="$2" conv=notrunc,nocreat oflag=seek_bytes seek=$3
fi
}
makeuuid() {
echo "urn:uuid:$(uuidgen)"
}
flist=''
planelist=''
cnt=0
chan_cnt=0
for arg in "$@"; do
if [ "$flist" == "" ]; then
firstname=$arg
fi
# TODO: check whether valid ptif file and extract C, Z, T, coding per Bio-Formats pattern strategy
# and re-order as necessary. For now, it assumes an ordered collection of single-channel tiffs or ptifs
is_chan=$(echo "$arg" | grep "_C" | wc -l)
if [ "$is_chan" == "1" ]; then
planelist=$(echo "$planelist" "$chan_cnt" "$cnt")
let chan_cnt+=1
fi
let cnt+=1
flist=$(echo $flist "$arg")
# set the first IFD in each ptif/tif to be the full-res image
tiffset -d 0 -s 254 0 "$arg"
done
base="$(basename $firstname)"
dest="${base%.ptif}.ome.tiff"
tiffcp $flist "$dest"
# unnecessary indent
nifds=$(tiffinfo "$dest" | grep "TIFF Directory at offset" | wc -l)
echo "IFD count: $nifds"
ifds=$(seq 0 $(($nifds - 1)))
mainifds=''
subifds=''
for ifd in $ifds; do
is_main=$(tiffinfo -"$ifd" "$dest" | grep "Subfile Type: (0 = 0x0)" | wc -l)
if [ "$is_main" == "1" ]; then
mainifds=$(echo "$mainifds" "$ifd")
else
subifds=$(echo "$subifds" "$ifd")
fi
done
nmainifds=$(echo "$mainifds" | wc -w)
nsubifds=$(echo "$subifds" | wc -w)
echo "$nifds - IFDs: $ifds"
echo "$nmainifds - Main IFDs: $mainifds"
echo "$nsubifds - SUBIFDs: $subifds"
# Main header
tiffset -d 0 -s 270 "OME Pyramid TIFF test (from $base)" "$dest"
tiffset -d 0 -s 305 "A gnarly shell script (makepyramid-multi)" "$dest"
tiffset -d 0 -s 315 "Roger Leigh <rleigh at dundee.ac.uk>" "$dest"
# NewSubFileType
for ifd in $mainifds; do
# superfluous but keeping it for now
tiffset -d $ifd -s 254 0 "$dest"
done
for ifd in $subifds; do
tiffset -d $ifd -s 254 1 "$dest"
done
# SubIFDs
n=1
for mainifd in $mainifds; do
let n+=1
nextmainifd=$(echo $mainifds | cut -d" " -f$n)
let nn=$mainifd+2
if [ -z $nextmainifd ] || [ $nn -gt $nextmainifd ]; then
continue
fi
subifdslist=$(echo $ifds | cut -d" " -f$(($mainifd + 2))-$nextmainifd)
subifds=$(echo $subifdslist | tr " " "\n")
nsubifds=$(echo "$subifds" | wc -l)
subifds_start=$(echo "$subifds" | head -n1)
subifds_end=$(echo "$subifds" | tail -n1)
subifds_diroffs=$(echo $(tiffinfo "$dest" | grep "TIFF Directory at offset" | sed -e 's;.*(\(.*\))$;\1;' | head -n$(($subifds_end+1)) | tail -n$nsubifds))
echo "SubIFDs for series $mainifd: $subifds_diroffs"
tiffset -d $mainifd -s 330 $nsubifds $subifds_diroffs "$dest"
subifds_diroffs=$(echo $(tiffinfo "$dest" | grep "TIFF Directory at offset" | sed -e 's;.*(\(.*\))$;\1;' | head -n$(($subifds_end+1)) | tail -n$nsubifds))
echo "Updated SubIFDs for series $mainifd: $subifds_diroffs"
done
echo "New directories:"
diroffsets "$dest"
# Relink IFDs to elide SubIFDs. Run backward to keep the file readable between iterations.
nextdiroff=0
nextmainifd=''
for mainifd in $(echo "$mainifds" | tr " " "\n" | tac); do
let nn=$mainifd+2
if [ -z $nextmainifd ] || [ $nn -gt $nextmainifd ]; then
nextmainifd=$mainifd
else
subifdslist=$(echo $ifds | cut -d" " -f$(($mainifd + 2))-$nextmainifd)
nextmainifd=$mainifd
subifds=$(echo $subifdslist | tr " " "\n")
nsubifds=$(echo "$subifds" | wc -l)
subifds_start=$(echo "$subifds" | head -n1)
subifds_end=$(echo "$subifds" | tail -n1)
subifds_diroffs=$(echo $(tiffinfo "$dest" | grep "TIFF Directory at offset" | sed -e 's;.*(\(.*\))$;\1;' | head -n$(($subifds_end+1)) | tail -n$nsubifds))
for offset in $subifds_diroffs; do
noffset="$(nextoffset $offset "$dest")"
update_uint64_le 0 "$dest" $noffset
done
fi
maindir="$(diroffset $mainifd "$dest")"
noffset="$(nextoffset $maindir "$dest")"
update_uint64_le $nextdiroff "$dest" $noffset
nextdiroff="$(diroffset $mainifd "$dest")"
done
tiffinfo "$dest"
# Create OME-XML metadata for the file
bfomexml="$(showinf -nopix -noflat -omexml -omexml-only "$dest")"
# Add TiffData elements.
uuid="$(makeuuid)"
ome_attr="Creator=\"makepyramid-multi\" UUID=\"${uuid}\""
tiffdata_fmt="<TiffData FirstC=\"%d\" FirstT=\"0\" FirstZ=\"0\" IFD=\"%d\" PlaneCount=\"1\"><UUID FileName=\"$(basename "${dest}")\">${uuid}</UUID></TiffData>"
tiffdata_blk="$(printf "$tiffdata_fmt" $planelist)"
omexml_fmt="$(echo "$bfomexml" | sed -e "s;\(<OME.*\)\(\">\);\1\" ${ome_attr}>;" -e "s;\(SizeC=\"\)[0-9]*\";\1${chan_cnt}\";" -e "s;\(SizeT=\)\"[0-9]*\";\1\"1\";" -e "s;<MetadataOnly\/>;${tiffdata_blk};")"
omexml="$(printf "$omexml_fmt")"
tiffset -d 0 -s 270 "$omexml" "$dest"
More information about the ome-devel
mailing list