Resize image with Python and keep EXIF and XMP metadata - python

Its been asked a lot of times how to resize an image and keep the existing exif data. I'm able to do that easily with PIL:
from PIL import Image
im = Image.open("image.jpeg")
exif = im.info['exif']
# process the image, for example resize:
im_resized = im.resize((1920, 1080), resample=PIL.Image.LANCZOS)
im_resized.save("resized.jpeg", quality=70, exif=exif)
I was wondering is there a way to keep the XMP metadata from the original image? There's a lot of GPS data in the XMP which I would love to keep in the resized version.

Not even the successor Pillow can read XMP (and IPTC). Also you're not really keeping anything - you create a whole new file and add a copy of the other EXIF data (including now potentially invalid information like width/height).
JFIF files aren't black magic - their file format can be parsed rather easily; XMP is most likely found in an APP1 segment (just like EXIF). In rare cases it spans over multiple segments. You could first use PIL/Pillow to create your file and then modify it - inserting additional segments is trivial and needs no additional work.

Related

loading and saving a JPEG image results in different file content

Here's a script that reads a JPG image and then writes 2 JPG images:
import cv2
# https://github.com/opencv/opencv/blob/master/samples/data/baboon.jpg
input_path = './baboon.jpg'
# Read image
im = cv2.imread(input_path)
# Write image using default quality (95)
cv2.imwrite('./baboon_out.jpg', im)
# Write image using best quality
cv2.imwrite('./baboon_out_100.jpg', im, [cv2.IMWRITE_JPEG_QUALITY, 100])
after running the above script, here's what the files look like:
.
├── baboon.jpg
├── baboon_out.jpg
├── baboon_out_100.jpg
└── main.py
However, the MD5 checksums of the JPGs created by the script do not match the original:
>>> md5 ./baboon.jpg
MD5 (./baboon.jpg) = 9a7171af1d6c6f0901d36d04e1bd68ad
>>> md5 ./baboon_out.jpg
MD5 (./baboon_out.jpg) = 1014782b9e228e848bc63bfba3fb49d9
>>> md5 ./baboon_out_100.jpg
MD5 (./baboon_out_100.jpg) = dbadd2fadad900e289e285393778ad89
Is there anyway to preserve the original image content with OpenCV? In which step is the data being modified?
It's a simplification, but the image will probably always change the checksum if you're reading a JPEG image because it's a lossy format being re-encoded and read by an avalanching hash and definitely will if you do any meaningful work on it, even if (especially if) you directly manipulated the bits of the file rather than loading it as an image
If you want the hash to be the same, just copy the file!
If you're looking for pixel-perfect manipulation, try a lossless format like PNG and understand if you are comparing the pixels or meta attributes a-la Exif (ie. should the Google, GitHub (Microsoft), and Twitter-affiliated fields from your baboon.jpg be included in your comparison? what about color data or depth information..?)
$ exiftool baboon.jpg | grep Description
Description : Open Source Computer Vision Library. Contribute to opencv/opencv development by creating an account on GitHub.
Twitter Description : Open Source Computer Vision Library. Contribute to opencv/opencv development by creating an account on GitHub.
Finally, watch out specifically when using MD5 as it's largely considered broken - a more modern checksum like SHA-256 might help with unforseen issues
There's a fundamental misconception here about the way images are stored and what lossy/lossless means.
Let's imagine we have a really simple 1-pixel grey image and that pixel has brightness 100. Let's make our own format for storing our image... I'm going to choose to store it as a Python script. Ok, here's our file:
print(100)
Now, let's do another lossless version, like PNG might:
print(10*10)
Now we have recreated our image losslessly, because we still get 100, but the MD5 hash is clearly going to be different. PNG does filtering before compressing and it checks whether each pixel is similar to the ones above and to the left in order to save space. One encoder might decide it can do better by looking at the pixel above, another one might think it can do better by looking at the pixel to the left. The result is the same.
Now let's make a new version of the file that stores the image:
# Updated 2022-08-12T21:15:46.544Z
print(100)
Now there's a new comment. The metadata about the image has changed, and so has the MD5 hash. But no pixel data has changed so it is a lossless change. PNG images often store the date/time in the file like this, so even two bit-for-bit identical images will have a different hash for the same pixels if saved 1 second apart.
Now let's make our single pixel image like a JPEG:
print(99)
According to JPEG, that'll look pretty similar, and it's 1-byte shorter, so it's fine. How do you think the MD5 hash will compare?
Now there's a different JPEG library, it thinks this is pretty close too:
print(98)
Again, the image will appear indistinguishable from the original, but your MD5 hash will be different.
TL/DR;
If you want an identical, bitwise perfect copy of your file, use the copy command.
Further food for thought... when you read an image with OpenCV, you actually get a Numpy array of pixels, don't you. Where is the comment that was in your image? The copyright? The GPS location? The camera make and model and shutter speed? The ICC colour profile? They are not in your Numpy array of pixels, are they? They are lost. So, even if the PNG/JPEG encoder made exactly the same decisions as the original encoder, your MD5 hash would still be different because all that other data is missing.

Extracting small region of image from BMP file in Python

I have a BMP image and I want to extract a small portion of it and save it as a new BMP image file.
I was able to load image and read it however I was not able to extract the small portion of BMP image as I am new to manipulating BMP image with python and also it not same as reading text file.
Things I have to do is
load image.
extract small portion of image.
eg. I have to extract 40X40 pixel image from 900X900 image file
then save extracted image as new file. eg new.bmp
I am trying to do this for last 3 days also I have searched a lot in the net but got solution which uses Pillow library however I need it to do this without using any external module of Python. Stackoverflow is my last hope I need some guidance from a expert people present here, please provide my some guidance.

Python PIL (Pillow) resizes my pictures after modifying exif data

i made a small script in Python, which can set the exif data of my old Whatsapp pictures based on their filename.
I use the piexif and the PIL (Pillow) package.
import piexif
from PIL import Image
from collections import defaultdict
img = Image.open(fname)
try:
exif_dict = piexif.load(img.info["exif"])
except KeyError:
exif_dict = defaultdict(dict)
exif_dict['Exif'][piexif.ExifIFD.DateTimeOriginal] = exiftime(date)
exif_dict['Exif'][piexif.ExifIFD.DateTimeDigitized] = exiftime(date)
exif_bytes = piexif.dump(exif_dict)
img.save('%s' % fname, "jpeg", exif=exif_bytes)
The exiftime() function is only for formatting the date.
However, the script is setting some exif fields, i don't modify compression or someting like that.
My problem is, that the pictures get much smaller, after running that script.
I tested this script with some sample images, e.g. a picture shot with a Nikon D5300 with an resolution of 6000x4000. The original file has about 12Mb, after the script it has only 4Mb.
Does the script cause a quality loss of the picture, or is it just a better compression?
Pillow's .save automatically compresses with a default of 75% quality according to the documentation. You can bump that up to 100% (add quality=100), which will minimize compression and looks like it will skip some compression components completely, but Pillow apparently doesn't have the ability to skip compression altogether. Very few packages do this, and I'm unaware of any in the form of a Python module. Note that the docs say not to raise quality over 95, and I can attest that doing so outputs a BIGGER file.. weird.
A bit of a late answer, but a similar post pointed out a solution here to only write exif information in the file (using piexif).
As a consequence the content of the image is not altered, since no compression (through the "save" command) is done.

Extract tiles from tiled TIFF and store in numpy array

My overall goal is to crop several regions from an input mirax (.mrxs) slide image to JPEG output files.
Here is what one of these images looks like:
Note that the darker grey area is part of the image, and the regions I ultimately wish to extract in JPEG format are the 3 black square regions.
Now, for the specifics:
I'm able to extract the color channels from the mirax image into 3 separate TIFF files using vips on the command line:
vips extract_band INPUT.mrxs OUTPUT.tiff[tile,compression=jpeg] C --n 1
Where C corresponds to the channel number (0-2), and each output file is about 250 MB in size.
The next job is to somehow recognize and extract the regions of interest from the images, so I turned to several python imaging libraries, and this is where I encountered difficulties.
When I try to load any of the TIFFs using OpenCV using:
i = cv2.imread('/home/user/input_img.tiff',cv2.IMREAD_ANYDEPTH)
I get an error error: (-211) The total matrix size does not fit to "size_t" type in function setSize
I managed to get a little more traction with Pillow, by doing:
from PIL import Image
tiff = Image.open('/home/user/input_img.tiff')
print len(tiff.tile)
print tiff.tile[0]
print tiff.info
which outputs:
636633
('jpeg', (0, 0, 128, 128), 8, ('L', ''))
{'compression': 'jpeg', 'dpi': (25.4, 25.4)}
However, beyond loading the image, I can't seem to perform any useful operations; for example doing tiff.tostring() results in a MemoryError (I do this in an attempt to convert the PIL object to a numpy array) I'm not sure this operation is even valid given the existence of tiles.
From my limited understanding, these TIFFs store the image data in 'tiles' (of which the above image contains 636633) in a JPEG-compressed format.
It's not clear to me, however, how would one would extract these tiles for use as regular JPEG images, or even whether the sequence of steps in the above process I outlined is a potentially useful way of accomplishing the overall goal of extracting the ROIs from the mirax image.
If I'm on the right track, then some guidance would be appreciated, or, if there's another way to accomplish my goal using vips/openslide without python I would be interested in hearing ideas. Additionally, more information about how I could deal with or understand the TIFF files I described would also be helpful.
The ideal situations would include:
1) Some kind of autocropping feature in vips/openslide which can generate JPEGs from either the TIFFs or original mirax image, along the lines of what the following command does, but without generated tens of thousands of images:
vips dzsave CMU-1.mrxs[autocrop] pyramid
2) Being able to extract tiles from the TIFFs and store the data corresponding to the image region as a numpy array in order to detect the 3 ROIs using OpenCV or another methd.
I would use the vips Python binding, it's very like PIL but can handle these huge images. Try something like:
from gi.repository import Vips
slide = Vips.Image.new_from_file(sys.argv[1])
tile = slide.extract_area(left, top, width, height)
tile.write_to_file(sys.argv[2])
You can also extract areas on the command-line, of course:
$ vips extract_area INPUT.mrxs OUTPUT.tiff left top width height
Though that will be a little slower than a loop in Python. You can use crop as a synonym for extract_area.
openslide attaches a lot of metadata to the image describing the layout and position of the various subimages. Try:
$ vipsheader -a myslide.mrxs
And have a look through the output. You might be able to calculate the position of your subimages from that. I would also ask on the openslide mailing list, they are very expert and very helpful.
One more thing you could try: get a low-res overview, corner-detect on that, then extract the tiles from the high-res image. To get a low-res version of your slide, try:
$ vips copy myslide.mrxs[level=7] overview.tif
Level 7 is downsampled by 2 ** 7, so 128x.

Using gdcm (Grassroots DICOM) to Decompress DICOM Image Data

Is it possible to use the Python wrappers for GDCM to decode the image data in a DICOM file?
I have a byte array / string with the bytes of the image data in a DICOM file (i.e. the contents of tag 7fe0,0010 ("Pixel Data")) and I want to decode the image to something raw RGB or greyscale.
I am thinking of something along the lines of this but working with just the image data and not a path to the actual DICOM file itself.
You can read the examples, there is one that shows how one can take an input compressed DICOM and convert it to an uncompressed one. See the code online here:
Decompress Image
If you are a big fan of NumPy, checkout:
This module add support for converting a gdcm.Image to a numpy array.
This is sort of a low level example, which shows how to retrieve that actual raw buffer of the image.
A much nicer class for handling Transfer Syntax conversion would be to use gdcm.ImageChangeTransferSyntax class (allow decompression of icon)
gdcm::ImageChangeTransferSyntax Class Reference
If you do not mind reading a little C++, you can trivially convert the following code from C++ to Python:
Compress Image
Pay attention that this example is actually compressing the image rather than decompressing it.
Finally if you really only have access to the data values contains in the Pixel Data attribute, then you should really have a look at (C# syntax should be close to Python syntax):
Decompress JPEG File
This example shows how one can decompress a JPEG Lossless, Nonhierarchical, First- Order Prediction file. The JPEG data is read from file but it should work if the data is already in memory for your case.

Categories