Python-Retain both geocoding and orientation while image save/overwite - python

I am using PIL to enhance my images. While saving I need both the geographic coordinates and the orientation angles written to the header of the enhanced image. So far I have failed find a way to write the orientation angles.
I could write the coordinates using piexif after reading Preserve exif data of image with PIL when resize(create thumbnail). But this seems not enough to write the orientation also, or maybe I am missing something.
im = Image.open(direc + '\\' + filename)
exif_dict = piexif.load(im.info["exif"])
exif_bytes = piexif.dump(exif_dict)
enhancer = ImageEnhance.Brightness(im)
enhanced_im = enhancer.enhance(1.8)
enhanced_im.save(s + 'enhanced\\' + directory + "\e_" + filename, "JPEG", exif=exif_bytes)
When I print my exif_dict I see two main keys 0th and Exif (with reasonable key-value pairs under each of them and a lot of \x00\x00\x00q\x00\x00\x00g\x00\x00\x00r\x00\x00\x00l\x00\x00\x0... such characters which continues even after the parenthesis of the dictionary has ended. Please advise.

You could write a world file for every image: https://en.wikipedia.org/wiki/World_file
Create a text file, calculate the values, write them in the text file and add the corresponding extension to the file name.
EDIT: If you need to change the exif values I would recommend looking at the tags which already are in the exif data and change/add the orientation tag (How to modify EXIF data in python).
If you search for exif orientation tag on google, you can find the explanation of the values. They are also explained on this page https://sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html.
This page also explains how to change the orientation https://magnushoff.com/jpeg-orientation.html.
Hopefully it helps.

Related

Extracting Powerpoint background images using python-pptx

I have several powerpoints that I need to shuffle through programmatically and extract images from. The images then need to be converted into OpenCV format for later processing/analysis. I have done this successfully for images in the pptx, using:
for slide in presentation:
for shape in slide.shapes
if 'Picture' in shape.name:
pic_list.append(shape)
for extraction, and:
img = cv2.imdecode(np.frombuffer(page[i].image.blob, np.uint8), cv2.IMREAD_COLOR)
for python-pptx Picture to OpenCV conversion. However, I am having a lot of trouble extracting and manipulating the backgrounds in a similar fashion.
slide.background
is sufficient to extract a "_Background" object, but I have not found a good way to convert it into a OpenCV object similar to Pictures. Does anyone know how to do this? I am using python-pptx for extraction, but am not adverse to other packages if it's not possible with that package.
After a fair bit of work I discovered how to do this -- i.e., you don't. As far as I can tell, there is no way to directly extract the backgrounds with either python-pptx or Aspose. Powerpoint -- which, as it turns out, is an archive that can be unzipped with 7zip -- keeps its backgrounds disassembled in the ppt/media (pics), ppt/slideLayouts and ppt/slideMasters (text, formatting), and they are only put together by the Powerpoint renderer. This means that to extract the backgrounds as displayed, you basically need to run Powerpoint and take pics of the slides after removing text/pictures/etc. from the foreground.
I did not need to do this, as I just needed to extract text from the backgrounds. This can be done by checking slideLayouts and slideMasters XMLs using BeautifulSoup, at the <a:t> tag. The code to do this is pretty simple:
import zipfile
with zipfile.ZipFile(pptx_path, 'r') as zip_ref:
zip_ref.extractall(extraction_directory)
This will extract the .pptx into its component files.
from glob import glob
layouts = glob(os.path.join(extr_dir, 'ppt\slideLayouts\*.xml'))
masters = glob(os.path.join(extr_dir, 'ppt\slideMasters\*.xml'))
files = layouts + masters
This gets you the paths for slide layouts/masters.
from bs4 import BeautifulSoup
text_list = []
for file in files:
with open(file) as f:
data = f.read()
bs_data = BeautifulSoup(data, "xml")
bs_a_t = bs_data.find_all('a:t')
for a_t in bs_a_t:
text_list.append(str(a_t.contents[0]))
This will get you the actual text from the XMLs.
Hopefully this will be useful to someone else in the future.

Is there a proper way to convert common picture file extensions into a .PGM "P2" using PIL or cv2?

Edit: Problem solved and code updated.
I apologize in advance for the long post. I wanted to bring as much as I could to the table. My question consists of two parts.
Background: I was in need of a simple Python script that would convert common picture file extensions into a .PGM ASCII file. I had no issues coming up with a naive solution as PGM seems pretty straight forward.
# convert-to-pgm.py is a script for converting image types supported by PIL into their .pgm
# ascii counterparts, as well as resizing the image to have a width of 909 and keeping the
# aspect ratio. Its main purpose will be to feed NOAA style images into an APT-encoder
# program.
from PIL import Image, ImageOps, ImageEnhance
import numpy as np
# Open image, convert to greyscale, check width and resize if necessary
im = Image.open(r"pics/NEKO.JPG").convert("L")
image_array = np.array(im)
print(f"Original 2D Picture Array:\n{image_array}") # data is stored differently depending on
# im.mode (RGB vs L vs P)
image_width, image_height = im.size
print(f"Size: {im.size}") # Mode: {im.mode}")
# im.show()
if image_width != 909:
print("Resizing to width of 909 keeping aspect ratio...")
new_width = 909
ratio = (new_width / float(image_width))
new_height = int((float(image_height) * float(ratio)))
im = im.resize((new_width, new_height))
print(f"New Size: {im.size}")
# im.show()
# Save image data in a numpy array and make it 1D.
image_array1 = np.array(im).ravel()
print(f"Picture Array: {image_array1}")
# create file w .pgm ext to store data in, first 4 lines are: pgm type, comment, image size,
# maxVal (=white, 0=black)
file = open("output.pgm", "w+")
file.write("P2\n# Created by convert-to-pgm.py \n%d %d\n255\n" % im.size)
# Storing greyscale data in file with \n delimiter
for number in image_array1:
# file.write(str(image_array1[number]) + '\n') #### This was the culprit of the hindered image quality...changed to line below. Thanks to Mark in comments.
file.write(str(number) + '\n')
file.close()
im = im.save(r"pics/NEKO-greyscale.jpg")
# Strings to replace the newline characters
WINDOWS_LINE_ENDING = b'\r\n'
UNIX_LINE_ENDING = b'\n'
with open('output.pgm', 'rb') as open_file:
content = open_file.read()
content = content.replace(WINDOWS_LINE_ENDING, UNIX_LINE_ENDING)
with open('output.pgm', 'wb') as open_file:
open_file.write(content)
open_file.close()
This produces a .PGM file that, when opened with a text editor, looks similar to the same image that was exported as a .PGM using GIMP (My prior solution was to use the GIMP export tool to manually convert the pictures and I couldn't find any other converters that supported the "P2" format). However, the quality of the resulting picture is severely diminished compared to what is produced using the GIMP export tool. I have tried a few methods of image enhancement (brightness, equalize, posterize, autocontrast, etc.) to get a better result, but none have been entirely successful. So my first question: what can I do differently to obtain a result that looks more like what GIMP produces? I am not looking for perfection, just a little clarity and a learning experience. How can I automatically adjust {insert whatever} for the best picture?
Below is the .PGM image produced by my version compared GIMP's version, open in a text editor, using the same input .jpg
My version vs. GIMP's version:
Below are comparisons of adding various enhancements before creating the .pgm file compared to the original .jpg and the original .jpg converted as a greyscale ("L"). All photos are opened through GIMP.
Original .jpg
Greyscale .jpg, after .convert("L") command
**This is ideally what I want my .PGM to look like. Why is the numpy array data close, yet different than the data in the GIMP .PGM file, even though the produced greyscale image looks identical to what GIMP produces?
Answer: Because it wasn't saving the correct data. :D
GIMP's Resulting .PGM
My Resulting .PGM
My Resulting .PGM with lower brightness, with Brightness.enhance(0.5)
Resulting .PGM with posterize, ImageOps.posterize(im, 4)
SECOND PROBLEM:
My last issue comes when viewing the .PGM picture using various PGM viewers, such as these online tools (here and here). The .PGM file is not viewable through one of the above links, but works "fine" when viewing with the other link or with GIMP. Likewise, the .PGM file I produce with my script is also not currently compatible with the program that I intend to use it for. This is most important to me, since its purpose is to feed the properly formatted PGM image into the program. I'm certain that something in the first four lines of the .PGM file is altering the program's ability to sense that it is indeed a PGM, and I'm pretty sure that it's something trivial, since some other viewers are also not capable of reading my PGM. So my main question is: Is there a proper way to do this conversion or, with the proper adjustments, is my script suitable? Am I missing something entirely obvious? I have minimal knowledge on image processing.
GitHub link to the program that I'm feeding the .PGM images into: here
More info on this particular issue: The program throws a fault when ran with one of my .PGM images, but works perfectly with the .PGM images produced with GIMP. The program is in C++ and the line "ASSERT(buf[2] == '\n')" returns the error, implying that my .PGM file is not in the correct format. If I comment this line out and recompile, another "ASSERT(width == 909)..." throws an error, implying that my .PGM does not have a width of 909 pixels. If I comment this line out as well and recompile, I am left with the infamous "segmentation fault (core dumped)." I compiled this on Windows, with cygwin64. Everything seems to be in place, so the program is having trouble reading the contents of the file (or understanding '\n'?). How could this be if both my version and GIMP's version are essentially identical in format, when viewed with a text editor?
Terminal output:
Thanks to all for the help, any and all insight/criticism is acceptable.
The first part of my question was answered in the comments, it was a silly mistake on my end as I'm still learning syntax. The above code now works as intended.
I was able to do a little more research on the second part of my problems and I noticed something very important, and also feel quite silly for missing it yesterday.
So of course the reason why my program was having a problem reading the '\n' character was simply because Windows encodes newline characters as CRLF aka '\r\n' as opposed to the Unix way of LF aka '\n'. So in my script at the very end I just add the simple code [taken from here]:
# replacement strings
WINDOWS_LINE_ENDING = b'\r\n'
UNIX_LINE_ENDING = b'\n'
with open('output.pgm', 'rb') as open_file:
content = open_file.read()
content = content.replace(WINDOWS_LINE_ENDING, UNIX_LINE_ENDING)
with open('output.pgm', 'wb') as open_file:
open_file.write(content)
Now, regardless on whether the text file is encoded with CRLF or LF, the script will work properly.

python image taken date and time

I'm trying to create an array that contains the filenames of all images in a folder in the first column and the "time taken" of the image in the second column. This time should be in hh:mm:ss:msmsms (or hhmmssmsmsms), where "ms" is milliseconds.
I found a piece of code that uses the Pillow library with the to pull the EXIFTAG data of the image. I realize that I would need the DateTimeOriginal and SubsecTimeOriginal tags to get the data I want.
Now the problem is that I just don't understand how the code bellow pulls the data from the image and how I would be able to create the desired array. If anyone knows how the .ExifTags and ._getexif() modules work, some explanation would be appreciated.
code:
from PIL import Image
from PIL.ExifTags import TAGS
file_path = 'IMG_20200528_125319.jpg'
results = {}
i = Image.open(file_path)
info = i._getexif()
for tag, value in info.items():
decoded = TAGS.get(tag, tag)
results[decoded] = value
print results
Sadly the info I was looking for is not in the exif tags of the picture. See Mark Setchell's comments.

Python - add arbitrary EXIF data to image (UserComment field)?

I need to add arbitrary data to a JPEG image. Specifically, I need to store two integers. From reading about EXIF data, I'm under the impression that it is not possible to make your own custom fields, but rather the EXIF standard fields must be used.
This post Custom Exif Tags however mentions a UserComment field which I gather it is possible to write a string to. If this is the only option it's fine since I can store two integers in a comma-delimited string, ex '2,5' to store the integers 2 and 5, so if I only have one string of storage to work with it's still sufficient.
I downloaded a few random images from a Google image search and found they don't seem to have EXIF data, perhaps it's stripped off purposefully by Google? Also I took a few images with my cell phone and found that as expected they have a significant amount of EXIF data (image size, GPS location, etc.)
Upon some Googleing I found this example on how to read/dump EXIF data:
from PIL import Image
image = Image.open('image.jpg')
exifData = image._getexif()
print('exifData = ' + str(exifData))
This works great, if I run this on an image with no EXIF data I get:
exifData = None
and if I run this on an image with EXIF data I get a dictionary showing the EXIF fields as expected.
Now my question is, how can I add to the EXIF data? Using the UserComment 37510 field mentioned in the above linked post, and also here https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif/usercomment.html, and using piexif this is my best attempt so far:
from PIL import Image
import piexif
image = Image.open('image.jpg')
exifData = image._getexif()
if exifData is None:
exifData = {}
# end if
exifData[37510] = 'my message'
exifDataBytes = piexif.dump(exifData)
image.save('image_mod.jpg', format='jpeg', exif=exifDataBytes)
If I then run the 1st code above on image_mod.jpg I get:
exifData = {}
So clearly the 37510 message was not properly written. I get this same empty dictionary result whether I'm using an image that has EXIF data or an image without EXIF data to begin with.
Before somebody marks this as a duplicate, I also tried what this post How can I insert EXIF/other metadata into a JPEG stored in a memory buffer? mentions in the highest-rated answer and got the same result when attempting to read the EXIF data (empty dictionary).
What am I doing wrong? How can I properly add custom EXIF data to an image using 37510, or any other means?
You're missing a step in handling the data passed to piexif.dump:
exif_ifd = {piexif.ExifIFD.UserComment: 'my message'.encode()}
exif_dict = {"0th": {}, "Exif": exif_ifd, "1st": {},
"thumbnail": None, "GPS": {}}
exif_dat = piexif.dump(exif_dict)
img.save('image_mod.jpg', exif=exif_dat)
You should be able to read it back out after this. See also this answer for dealing with custom metadata.
Rasterio tags are the easiest way to add metadata of any kind to an image. Easy and practical. example:
import rasterio
old_file=rasterio.open('old_image.tif')
profile=old_file.profile
data=old_file.read()
with rasterio.open('new_image.tif','w',**profile) as dst:
dst.update_tags(a='1', b='2')
dst.write(data)
dst.close()
#now access the tags like below:
im=rasterio.open('new_image.tif')
print(im.tags())

Can I convert PDF blob to image using Python and Wand?

I'm trying to convert a PDF's first page to an image. However, the PDF is coming straight from the database in a base64 format. I then convert it to a blob. I want to know if it's possible to convert the first page of the PDF to an image within my Python code.
I'm familiar with being able to use filename in the Image object:
Image(filename="test.pdf[0]") as img:
The issue I'm facing is there is not an actual filename, just a blob. This is what I have so far, any suggestions would be appreciated.
x = object['file']
fileBlob = base64.b64decode('x')
with Image(**what do I put here for pdf blob?**) as img:
more code
It works for me
all_pages = Image(blob=blob_pdf) # PDF will have several pages.
single_image = all_pages.sequence[0] # Just work on first page
with Image(single_image) as i:
...
Documentation says something about blobs.
So it should be:
with Image(blob=fileBlob):
#etc etc
I didn't test that but I think this is what you are after.

Categories

Resources