cgohlke / tifffile

Read and write TIFF files
https://pypi.org/project/tifffile
BSD 3-Clause "New" or "Revised" License
544 stars 154 forks source link

TiffWriter: How to define the background color #150

Closed juanenofbit closed 2 years ago

juanenofbit commented 2 years ago

Hi, I have not found a way to set the background color to white (or any desired value) when writing a pyramid and tiled image with TiffWriter.

I am reading images with white background:

file_input

The written images has black color in the background tiles:

file_output

Using the following code:

stack = tifffile.imread(file_input) 
page = tif.pages[0]
subresolutions = numpy.fix(math.log2(max(page.shape)/1000)).astype(int)
pixelsize = 0.2197 ##micrometer

with tifffile.TiffWriter(file_output, bigtiff=True) as tif:
    options = dict(photometric='rgb', tile=(512, 512), compression=('jpeg',90), resolutionunit='CENTIMETER')
    tif.write(
        stack,
        subifds=subresolutions,
        resolution=(1e4 / pixelsize, 1e4 / pixelsize, 'CENTIMETER'),
        metadata={
            'axes': 'YXS',
            'PhysicalSizeX': pixelsize,
            'PhysicalSizeY': pixelsize,
            'PhysicalSizeXUnit': 'µm',
            'PhysicalSizeYUnit': 'µm',
        },
        **options,
    )
    # write pyramid levels to the subifds
    # in production use resampling to generate sub-resolutions
    for level in range(subresolutions):
            mag = 2**(level + 1)
            tif.write(
                stack[::mag, ::mag, :],
                subfiletype=1,
                resolution=(1e4 / mag / pixelsize, 1e4 / mag / pixelsize, 'CENTIMETER'),
                **options
        )

Thank you in advance!

cgohlke commented 2 years ago

TIFF doesn't have the concept of a background color. That would be application specific. Which software was used to create the file input, and which software is used to display the image? Can you post the output of the command line python -m tifffile --maxplots 0 input_file.tif?

It could be that some tiles in the input files are missing and tifffile replaces the missing values with TiffPage.nodata, which is 0 by default. There is currently no way to set the "color" of missing tiles in tifffile since the color samples could be stored in separate tiles. Instead, use numpy to replace black colors with another color before saving.

juanenofbit commented 2 years ago

The input image is exported from a Zeiss scanner in czi format (it has not been acquired in my lab and I do not know the details of the software). I was displaying it with QuPath and also with Zen lite (from Zeiss) and in both cases the white background is visible.

But it seems to be as you say that it is an application-specific issue, because when opening the image in python using the czifile library (https://github.com/cgohlke/czifile) czifile.imread(file_input) the image has black background.

This image is then written via tifffile and effectively there is no choice but to modify the black area manually before writing it.

As the input image is czi, I exported it using QuPath to tiff format to execute the suggested command. This is the output:

Reading TIFF header: 0.147693 s
Generating report:   0.065720 s

TiffFile '12-181-01-ptq_32161…uPath2.LZW.ome.tif'  1078.72 MiB  big-endian  ome

TiffPageSeries 0  16327x24678x3  uint8  YXS  OME  5 Levels  1 Pages

TiffPage 0 @8  16327x24678x3  uint8  rgb tiled lzw ome

TiffTag 256 ImageWidth @10 LONG @18 = 24678
TiffTag 257 ImageLength @22 LONG @30 = 16327
TiffTag 258 BitsPerSample @34 SHORT[3] @230 = (8, 8, 8)
TiffTag 259 Compression @46 SHORT @54 = LZW
TiffTag 262 PhotometricInterpretation @58 SHORT @66 = RGB
TiffTag 270 ImageDescription @70 ASCII[1558] @1131116345 = <?xml version="1.0"
TiffTag 277 SamplesPerPixel @82 SHORT @90 = 3
TiffTag 282 XResolution @94 RATIONAL @298 = (45516613, 1000)
TiffTag 283 YResolution @106 RATIONAL @306 = (45516613, 1000)
TiffTag 284 PlanarConfiguration @118 SHORT @126 = CONTIG
TiffTag 296 ResolutionUnit @130 SHORT @138 = CENTIMETER
TiffTag 305 Software @142 ASCII[22] @314 = OME Bio-Formats 6.7.0
TiffTag 322 TileWidth @154 LONG @162 = 512
TiffTag 323 TileLength @166 LONG @174 = 512
TiffTag 324 TileOffsets @178 LONG[1568] @336 = (12880, 14480, 20880, 16080, 176
TiffTag 325 TileByteCounts @190 LONG[1568] @6608 = (1600, 1600, 1600, 1600, 160
TiffTag 330 SubIFDs @202 LONG[4] @1131117903 = (871273217, 1066809211, 11154975
TiffTag 339 SampleFormat @214 SHORT @222 = UINT

ImageDescription ASCII[1558]
<?xml version='1.0' encoding='UTF-8'?>
<!-- Warning: this comment is an OME-XML metadata block, which contains crucial
<OME xmlns="http://www.openmicroscopy.org/Schemas/OME/2016-06" xmlns:xsi="http:
 <Image ID="Image:0">
  <Pixels BigEndian="true" DimensionOrder="XYCZT" ID="Pixels:0" Interleaved="tr
   <Channel ID="Channel:0" SamplesPerPixel="3">
    <LightPath/>
   </Channel>
   <TiffData FirstC="0" FirstT="0" FirstZ="0" IFD="0" PlaneCount="1">
    <UUID FileName="12-181-01-ptq_32161009-010.LZW.ome.tif">urn:uuid:8418675d-6
   </TiffData>
  </Pixels>
 </Image>
 <StructuredAnnotations>
  <MapAnnotation ID="Annotation:Resolution:0" Namespace="openmicroscopy.org/Pyr
   <Value>
    <M K="1">12339 8163</M>
    <M K="2">6169 4081</M>
    <M K="3">3084 2040</M>
    <M K="4">1542 1020</M>
   </Value>
  </MapAnnotation>
 </StructuredAnnotations>
</OME>

TileOffsets LONG[1568]
(12880, 14480, 20880, 16080, 17680, 19280, 22480, 25680, 27280, 24080, 28880,
 32080, 33680, 30480, 38480, 35280, 36880, 40080, 41680, 44880, 46480, 43280,
 48080, 51280, 52880, 49680, 54480, 460095, 56080, 461695, 219101, 683061,
 888681, 1116349, 1363388, 1773634, 2001157, 1565954, 2695204, 2238937,
...
 868860750, 868867150, 868865550, 868870350, 868868750, 868873550, 868871950,
 868875150, 868879950, 868878350, 868876750, 868883150, 868881550, 868884750,
 868887950, 868886350, 868889550, 869177394, 868965749, 869376520, 869800108,
 869588031, 870000704, 870610447, 870212731, 870423965, 870816416, 871019895,
 871232785)

TileByteCounts LONG[1568]
(1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600,
 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600,
 1600, 1600, 163021, 221366, 240994, 205620, 227668, 247039, 202566, 227523,
 237780, 207680, 241744, 233228, 223039, 233170, 241610, 222508, 236629,
...
 221498, 242968, 45050, 1600, 1600, 1600, 1600, 1600, 67046, 193155, 200533,
 193274, 191913, 201762, 185340, 196101, 211401, 85831, 1600, 1600, 1600, 1600,
 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600,
 1600, 1600, 1600, 1600, 76199, 199126, 211645, 211511, 200596, 212077, 212027,
 205969, 211234, 186482, 203479, 212890, 40432)

SubIFDs LONG[4]
(871273217, 1066809211, 1115497576, 1127905432)

OME_METADATA
<?xml version='1.0' encoding='UTF-8'?>
<!-- Warning: this comment is an OME-XML metadata block, which contains crucial
<OME xmlns="http://www.openmicroscopy.org/Schemas/OME/2016-06" xmlns:xsi="http:
 <Image ID="Image:0">
  <Pixels BigEndian="true" DimensionOrder="XYCZT" ID="Pixels:0" Interleaved="tr
   <Channel ID="Channel:0" SamplesPerPixel="3">
    <LightPath/>
   </Channel>
   <TiffData FirstC="0" FirstT="0" FirstZ="0" IFD="0" PlaneCount="1">
    <UUID FileName="12-181-01-ptq_32161009-010.LZW.ome.tif">urn:uuid:8418675d-6
   </TiffData>
  </Pixels>
 </Image>
 <StructuredAnnotations>
  <MapAnnotation ID="Annotation:Resolution:0" Namespace="openmicroscopy.org/Pyr
   <Value>
    <M K="1">12339 8163</M>
    <M K="2">6169 4081</M>
    <M K="3">3084 2040</M>
    <M K="4">1542 1020</M>
   </Value>
  </MapAnnotation>
 </StructuredAnnotations>
</OME>
cgohlke commented 2 years ago

How does the intermediate OME.TIFF file, which was exported from QuPath, render in QuPath? Is the background white or black? I suspect it is black and this is not a tifffile issue.

juanenofbit commented 2 years ago

The OME.TIFF file exported from QuPath render with white background when the tile size is 512:

Image_exported_with_qupath_tiles512

But when exported with a tile size equal to 1024, the background is black (although it also retains white parts, unlike the exported tifffile, which contains no white parts when loaded in QuPath)

Image_exported_with_qupath_tiles1024

Written with tifffile: Image_tiffile_tiles512

This is the qupath script used to export the first two images:

extension = "tif"
server = getCurrentServer()
path= server.getBuilder().getURIs()[0].toString()
imageData = getCurrentImageData()
baseFilePath = path.substring(6, path.lastIndexOf(".")+1)
def pathInput = baseFilePath + extension
def pathOutput = baseFilePath + "LZW.ome.tif"
def compression = OMEPyramidWriter.CompressionType.LZW
def PIXELSIZE = 0.2197
def MAGNIFICATION = 20.0
def TILESIZE = 512

println 'Reading image...'
//def img = ij.IJ.openImage(pathInput).getBufferedImage()
//def server = new WrappedBufferedImageServer("Anything", img)
def metadataNew = new ImageServerMetadata.Builder(server.getMetadata())
    .pixelSizeMicrons(PIXELSIZE, PIXELSIZE)
    .preferredTileSize(TILESIZE, TILESIZE)
    .magnification(MAGNIFICATION)
    .build();

imageData.updateServerMetadata(metadataNew);
println 'Writing OME-TIFF'
new OMEPyramidWriter.Builder(server)
        .parallelize()
        .tileSize(TILESIZE)
        .scaledDownsampling(1, 2)
        .compression(compression)
        .build()
        .writePyramid(pathOutput)
println 'Done!'

import qupath.lib.images.writers.ome.OMEPyramidWriter
import qupath.lib.images.servers.*
import javax.imageio.*
import qupath.lib.images.servers.ImageServerMetadata;
cgohlke commented 2 years ago

I tried to reproduce the issue with a CZI file with empty/missing tiles. I could not find anything wrong on the tifffile side. On my system, QuPath displays and exports all those tiles as black with pixel values (0, 0, 0). However, QuPath reads/shows pixel values of (255, 255, 255) for some of those tiles when hovering over with the mouse...