Write additionnals images in a tiff file without erasing existing images - python

What I'm actually doing is saving images in a tiff file, using imageio.mimwrite(). But in my script, I open and close the file several times, so it erases existing images before saving news images. I want to keep existing images in the tiff file, and just add new images without erasing the previous ones. I did not find anything in the documentation which can helps me.
I'm actually using this :
imageio.mimwrite("example.tiff", image, format=".tiff")
image is an array which contains arrays of integers, each array representing an image.
This code opens example.tiff, erase existing images (if they exist), and write news images. But I want to add like open("file.txt", "a") does.

I made three differently sized TIFF images with ImageMagick like this for testing:
convert -size 640x480 xc:green green.tif
convert -size 1024x768 xc:blue blue.tif
convert -size 400x100 gradient:cyan-yellow gradient.tif
Then I used the tool tiffcp which is distributed with the TIFF library and the -a option to append the blue and gradient image to the green one like this:
tiffcp -a blue.tif gradient.tif green.tif
If I then check the contents of green.tiff with ImageMagick identify, I see it looks correct:
magick identify green.tif
green.tif[0] TIFF 640x480 640x480+0+0 16-bit sRGB 6.49355MiB 0.000u 0:00.000
green.tif[1] TIFF 1024x768 1024x768+0+0 16-bit sRGB 0.000u 0:00.000
green.tif[1] TIFF 400x100 400x100+0+0 16-bit sRGB 0.000u 0:00.000
And if I preview the file, all three images are there with the correct sizes and colours:
So, I am suggesting you consider using subprocess.run() to shell out to tiffcp.

With tifffile writing one page at a time (in the example a CTYX multipage), you can just write straight from a n-D array if you have enough RAM for it with tifffile.imwrite(filename,array)
https://pypi.org/project/tifffile/ :
import tifffile as tf
with tf.TiffWriter("filenametest.tiff",
#bigtiff=True,
#If you want to add on top of an existing tiff file (slower) uncomment below
#append = True,
imagej=False,) as tif:
for time in range(rgb.shape[1]):
tif.save(rgb[:,time,:,:].,
#compress= 3,
photometric='minisblack',
metadata= None,
contiguous=False,
)
tif.close()
With python-bioformats:
https://pythonhosted.org/python-bioformats/
bioformats.write_image(pathname, pixels, pixel_type, c=0, z=0, t=0, size_c=1, size_z=1, size_t=1, channel_names=None)[source]
if you have:
4 time points with
3 color zstacks (11 Z's)
with XY at 1024 pixels.
with independent numpy arrays being [3,11,1024,1024] (one for each timepoint),
16bits,
and named: a,b,c,d.
This should do the trick
import bioformats as bf
import numpy as np
#do something here to load a,b,c, and d
i=0
for t in [a,b,c,d]:
for c in range(3):
for z in range(11):
#t here is the numpy array
bf.write_image('/path/to/file.tiff'
, t
, bf.PT_UINT16
, c = c, z = z , t = i, size_c= 3, size_z=11, size_t=4
)
i+=1

Related

Convert Image to array and array to image using python, does the array contain metadata or other info?

Sorry for my english but it's not my first language.
I would like to create a program that:
Transform a jpeg or png image into an array (very important: I would like an array composed only of the values that the pixels of the image have and not metadata or other information. Where I can select each specific pixel of the image).
Save this array in a txt file.
Transform this array composed of only the pixel values of the image back into jpg or png image and save it in a file.
Requests:
Is the array I created with the program I wrote composed only of the pixel values of the image? is there also metadata or other information?
Is this a valid way to remove metadata from an image?
Is this a valid way to create the array representing that image pixel by pixel?
Is this a valid way to convert png images to jpeg or jpeg to png?
Thank you!
This is the program I created, any opinion?
import numpy as np
from PIL import Image
import sys
img_data = Image.open("imagea.jpeg")
img_arr = np.array(img_data)
np.set_printoptions(threshold=sys.maxsize)
print(img_arr.shape)
new_img = Image.fromarray(img_arr)
new_img.save("imageb.jpeg")
print("Image saved!")
file = open("file1.txt", "w+")
content = str(img_arr)
file.write(content)
file.close()
print("Finished!")
Loading an image and converting it to a Numpy array is a perfectly legitimate way of discarding all metadata including:
EXIF data, copyright data,
IPTC and XMP data,
ICC colour profile data
You can tell it's all gone by thinking about the Numpy array you hold and its dimensions and data type.
Note that you need to be careful with PNG palette images and images with an alpha channel.
Note that you can achieve this more simply on the command-line with ImageMagick using:
magick mogrify -strip IMAGE.JPG
Or with exiftool.
Note that you can achieve this by using a format that doesn't support metadata, such as NetPBM, with extension .ppm e.g.:
magick INPUT.JPG -strip -compress none RESULT.PPM # gives P3/plain ASCII file
magick INPUT.JPG -strip RESULT.PPM # gives P6/binary file
You can also read/write PPM files with PIL.

Python Pillow - RLE compression of BMP image

I'm working on a script, which builds an image, combines it with another image and saves it locally as an 8-bit BMP-file.
The image is then read by a ESP32 microcontroller, but the problem is that due to memorylimitations, the allowed file size is somewhat limited.
As a consequence, I made a BMP decoder for the ESP32, which supports RLE. In theory, the allowed number of bytes can still be exceeded, but only text and simple icons are to be read, so it will most likely never happen.
It uses Pillow for image processing, which now supports RLE-compression from version 9.1.0
https://github.com/python-pillow/Pillow/blob/main/docs/handbook/image-file-formats.rst
Pillow reads and writes Windows and OS/2 BMP files containing 1, L, P,
or RGB data. 16-colour images are read as P images. Support for
reading 8-bit run-length encoding was added in Pillow 9.1.0. Support
for reading 4-bit run-length encoding was added in Pillow 9.3.0.
Here's the part of the code, that combines two existing images into a new one and saves them:
img_buf = io.BytesIO() # Start converting from Matplotlib to PIL
# Supported: eps, jpeg, jpg, pdf, pgf, png, ps, raw, rgba, svg, svgz, tif, tiff, webp
plt.savefig(img_buf, format='png', transparent=True)
graph = Image.open(img_buf)
# Create empty, 8-bit canvas
new_image = Image.new('P',(600,448), (255,255,255)) # P = 8-bit indexed
new_image.paste(img,(0,0)) # Insert image 1 into canvas
new_image.paste(graph,(0,200)) # Insert image 2 into canvas at y:200
new_image.save("../output/priceeast.bmp", compression=1) # According to the docs, 1 = RLE
It saves the image, alright, but not RLE-encoded and I can't work out, how to enable it... or is RLE only supported when reading BMP, not saving?
UPDATE:
I added this line below:
subprocess.call('magick ../output/priceeast.png -type palette -compress RLE ../output/priceeast.bmp ', shell=True)
Pillow does not support writing BMP files with compression, which can be determined by investigating the source. BmpImagePlugin._write:
# bitmap info header
fp.write(
o32(header) # info header size
+ o32(im.size[0]) # width
+ o32(im.size[1]) # height
+ o16(1) # planes
+ o16(bits) # depth
+ o32(0) # compression (0=uncompressed)
+ o32(image) # size of bitmap
+ o32(ppm[0]) # resolution
+ o32(ppm[1]) # resolution
+ o32(colors) # colors used
+ o32(colors) # colors important
)
We can see here that the compression field in the output file header is hard-coded to none, indicating that compression is not supported when writing a file.
If you would like a work-around, ImageMagick can convert any image format to 8-bit RLE BMP like this:
magick INPUTIMAGE.xxx -type palette -compress RLE result.bmp # where XXX is PNG, JPG, TIFF, GIF, TGA etc
Check the result with exiftool like this:
exiftool -filename -filesize -compression result.bmp
File Name : result.bmp
File Size : 1168 bytes
Compression : 8-Bit RLE
Note that there are Python bindings to ImageMagick via wand, so you can achieve the same effect in Python like this:
#!/usr/bin/env python3
# You may need this before running on macOS
# export MAGICK_HOME=/opt/homebrew
from wand.image import Image
# Load image , or create pseudo image
with Image(filename='gradient:red-blue') as img:
img.type = 'palette'
img.compression = 'rle'
img.save(filename='result.bmp')

Save 16-bit numpy arrays as 16-bit PNG image

I'm trying to save a 16-bit numpy array as a 16-bit PNG but what I obtain is only a black picture. I put here a minimum example of what I'm talking aboout.
im = np.random.randint(low=1, high=6536, size=65536).reshape(256,256) #sample numpy array to save as image
plt.imshow(im, cmap=plt.cm.gray)
Given the above numpy array this is the image I see with matplotlib, but when then I save the image as 16-bit png I obtain the picture below:
import imageio
imageio.imwrite('result.png', im)
Image saved:
where some light grey spots are visible but the image is substantially black. Anyway when I read back the image and visualize it again with matplotlib I see the same starting image. I also tried other libraries instead of imageio (like PIL or PyPNG) but with the same result.
I know that 16-bit image values range from 0 to 65535 and in the array numpy array here there only values from 1 to 6536, but I need to save numpy arrays images similar to this, i.e. where the maximum value represented in the image isn't the maximum representable value. I think that some sort of nornalization is involved in the saving process. I need to save the array exactly as I see them in matplotlib at their maximum resolution and without compression or shrinkage in their values (so division by 255 or conversion to 8-bit array are not suitable).
It looks like imageio.imwrite will do the right thing if you convert the data type of the array to numpy.uint16 before writing the PNG file:
imageio.imwrite('result.png', im.astype(np.uint16))
When I do that, result.png is a 16 bit gray-scale PNG file.
If you want the image to have the full grayscale range from black to white, you'll have to scale the values to the range [0, 65535]. E.g. something like:
im2 = (65535*(im - im.min())/im.ptp()).astype(np.uint16)
Then you can save that array with
imageio.imwrite('result2.png', im2)
For writing a NumPy array to a PNG file, an alternative is numpngw (a package that I created). For example,
from numpngw import write_png
im2 = (65535*(im - im.min())/im.ptp()).astype(np.uint16)
write_png('result2.png', im2)
If you are already using imageio, there is probably no signficant advantage to using numpngw. It is, however, a much lighter dependency than imageio--it depends only on NumPy (no dependence on PIL/Pillow and no dependence on libpng).

Convert multi-channel numpy array to photoshop PSD file

I have a numpy array with 3 RGB channels and two alpha channels. (I am using python)
I want to convert it to a Photoshop .psd file, so I can later apply transformations in photoshop on the annotated alpha layers.
I guess it a very simple task but I haven't find any way to do it from the packages I found by googling it.
I guess it should be something in the lines of the following:
>> im.shape
(.., .., 4)
psd = PSD()
psd.add_layers_from_numpy(im, names=["R", "G", "B", "alpha-1", "alpha-2")
with open(of, 'wb') as f:
psd.write(f)
If you know how to do this, please let me know.
Thanks in advance!
I opened your PSD file in Photoshop and saved it as a TIFF. I then checked with tiffinfo and determined that your file is saved as RGB with 3 layers of "Extra samples":
tiffinfo MULTIPLE_ALPHA.tif
TIFF Directory at offset 0x8 (8)
Subfile Type: (0 = 0x0)
Image Width: 1000 Image Length: 1430
Resolution: 72, 72 pixels/inch
Bits/Sample: 8
Compression Scheme: None
Photometric Interpretation: RGB color <--- HERE
Extra Samples: 3<unspecified, unspecified, unspecified> <--- HERE
Orientation: row 0 top, col 0 lhs
Samples/Pixel: 6
Rows/Strip: 1430
Planar Configuration: single image plane
Software: Adobe Photoshop 21.2 (Macintosh)
DateTime: 2020:09:08 19:34:38
You can load that into Python with:
import tifffile
import numpy as np
# Load TIFF saved by Photoshop
im = tifffile.imread('MULTIPLE_ALPHA.tif')
# Check shape
print(im.shape) # prints (1430, 1000, 6)
# Save with 'tifffile'
tifffile.imwrite('saved.tif', im, photometric='RGB')
And now check that Photoshop looks at and treats the tifffile image the same as your original:
You may want to experiment with the compress parameter. I noticed your file comes out as 8.5MB uncompressed, but as 2.4MB of lossless Zip-compressed data if I use:
tifffile.imwrite('saved.tif', im, photometric='RGB', compress=1)
Note that reading/writing with compression requires you to install imagecodecs:
pip install imagecodecs
Note that I am not suggesting it is impossible with a PSD-writer package, I am just saying I believe you can get what you want with a TIFF.
Keywords: Image processing, Photoshop, PSD, TIFF, save multi-channel, multi-layer, multi-image, multiple alpha, tifffile, Python

pixel encoding using PIL

I have a naive question, but after a long day, I am not still able to get my answer.
I am currently loading my png image using PIL, it works well. However, some of my png
images are 16-bit per pixel. I am trying desperately to query this information, but I am not able to get it, using PIL. Indeed, if I am simply using the file system binary it works.
$ file flower_16b.png
flower_16b.png: PNG image data, 660 x 600, 16-bit/color RGB, non-interlaced
However in my python code:
img = Image.open(filename, "r")
print(img.mode)
I get RGB. Following the documentation PIL RGB means (3x8-bit pixels, true color), it look likes the image has been casted. So does it exist a way to get the depth of an image, using PIL or an other python module ?
PIL/Pillow doesn't support 48-bit images like that. One option might be OpenCV but be aware it comes as BGR not RGB:
import cv2
# Read with whatever bit depth is specified in the image file
BGR = cv2.imread('image.png', cv2.IMREAD_ANYDEPTH|cv2.IMREAD_ANYCOLOR)
# Check dtype and number of channels
print(BGR.dtype, BGR.shape)
dtype('uint16'), (768, 1024, 3)
Another option may be pyvips, which works a slightly different way, but has some good benefits:
import pyvips
im = pyvips.Image.new_from_file('image.png', access="sequential")
print(im)
<pyvips.Image 1024x768 ushort, 3 bands, rgb16>
If you are really, really stuck and can't/won't install OpenCV or pyvips, you have a couple more options with ImageMagick...
You could reduce your 3 RGB channels (16-bits each) to 3 RGB channels (8-bits each) with:
magick input.png PNG24:output.png # then open "output.png" with PIL
Or, you could separate the 3 RGB channels into 3 separate 16-bit files and process them separately with PIL/Pillow:
magick input.png -separate channel-%d.png
and you will get the red channel as a 16-bit image in channel-0.png which you can open with PIL/Pillow, the green as channel-1.png and the blue as channel-2.png

Categories

Resources