# Comparing image resolutions to ratios

I made this Python 3.6 program that generates a list of images that fail to compare against a given ratio. Each image has a resolution and with a bit of math, the program is supposed to calculate the width using the ratio and compare it with the actual width. If the calculated width falls from the range of (width-1, width+1), the image is outputted and considered "not respecting the ratio".

Important: The program uses the PIL library to get the resolutions. I used pip --user and it seems to work perfectly.

For example, an image with the resolution of 1920x1080 is in a directory. The program, imageres.py, takes the resolution, and uses this formula:

$$\text{cal_width} = \frac{\text{width_ratio} * \text{image_height}}{\text{height_ratio}}$$

If the ratio (default, actually) is 16:9, the formula turns out to be this:

$$\text{cal_width} = \frac{16 * 1080}{9}$$

$$\text{cal_width} = 1920$$

The program does the above and compares the final result to the image's actual resolution. Because both of them are equal, the image is not outputted and moves on to the next one.

Do you have any suggestions or methods I can use to make my code more robust? I noticed PyLint kept telling me on structure, so if anyone has any suggestion to make the code more readable would be awesome.

My biggest concern, though, is the mathematics and floating-point numbers behind the program. If you understand what I'm trying to do, I would like to hear your opinions! I have some reservations about this, as it seems more of a hack than an actual solution.

imageres.py

(I hope I was able to keep the PyLint stuff on.)

"""
imgres.py

this is a simple python image resoultion ratio checker.  quite loaded
sentence, but basically it gets an image's width and height and calculates
the width using the hardcoded default ratio of 16:9, which is what some
monitors have (like 1920x1080 is a equal ratio to 16:9)

so far it only spits out the images that are not equal to the width,
however it is quite possible that calculations are a pixel above/below the
image width, due to precison error.
^- not exactly solved, but it checks by an offset of 1

requires the PIL (Python Image Library) locally via pip or else where
"""
from os import listdir
from os.path import isfile, join
from argparse import ArgumentParser
from math import trunc

import sys

from PIL import Image

# the parser so we can present a user-friendly interface
PARSER = ArgumentParser(description='Compare image resolutions to ratio.')
PARSER.add_argument('DIRECTORY', metavar='D', type=str, help="The folder where the images reside")
PARSER.add_argument('--wr', default=16, metavar='W', type=int, help="The width ratio. DEFAULT: 16")
PARSER.add_argument('--hr', default=9, metavar='H', type=int, help="The height ratio. DEFAULT: 9")

ARGS = PARSER.parse_args()

# debug; check that the args work
# print("%s %d %d" % (ARGS.DIRECTORY, ARGS.wr, ARGS.hr))

# validate directory is real and accessible
# found the error code list from here:
# http://www-numi.fnal.gov/offline_software/srt_public_context/WebDocs/Errors/unix_system_errors.html
try:
ONLYFILES = [
f for f in listdir(ARGS.DIRECTORY)
if isfile(join(ARGS.DIRECTORY, f)) and f.endswith(('jpg', 'png'))
]

# get only the files from the directory. does not transverse into
# sub-directories. uses a tuple to get only jpg and png extensioned files
for img_filepath in ONLYFILES:
img = Image.open(join(ARGS.DIRECTORY, img_filepath))
# debug; check it's possible to get the image resolution
# print(img.size)
img_width, img_height = img.size

# calculate the width of the image with the provided ratio
calculated_width = trunc((ARGS.wr * img_height) / float(ARGS.hr))

# check if the calculated_width falls between the range with +- offset
# if yes, output the filename with resolution and calculated_width
if not calculated_width in range(img_width-1, img_width+1):
print("%30s -- (%d,%d) ~ C_W %f" %
(img_filepath, img_width, img_height, calculated_width)
)
except FileNotFoundError as inst:
print("error: the specified argument does not exist.\n", inst)
sys.exit(2)
print("error: the specified argument is not an directory.\n", inst)
sys.exit(20) Here are some of the notes about the code you've posted:

• I think pillow project is a much more active player than PIL, consider switching. There is also a highly-optimized Pillow-SIMD that can bring dramatically better performance (some benchmarks).
• variable naming - PARSER, ARGS, ONLYFILES are not constants (well, there are no constants in Python, it's just that it is recommended to name things that don't change in an upper case, PEP8 about constants) and should be defined in a lower case
• you don't need to define ONLY_FILES list and then iterate over it, you can use a single loop and, moreover, use a glob pattern with glob.iglob():

import glob

for filename in glob.iglob(args.directory + "/*.{jpg,png}", recursive=True):

• when opening the image files, use the with context manager:

with Image.open(filename) as img:

• when you check the calculated_width to be in a specified range, use comparison operators instead of creating a extra "range" (please check the following on the off-by-one errors):

if not(img_width - 1 <= calculated_width <= img_width):

• you can have a custom argparse directory type

• I don't see much sense in keeping a filename inside a "docstring" - the filename can change and you would easily forget to update it in the docstring
• the "requires the PIL (Python Image Library) locally via pip or else where" should be better handled by requirements.txt file accompanied by a "README" file (typically README.md or README.rst) with installation, usage and license instructions
• it might probably be a good idea to configure the --help of your CLI program, argparse would by default generate one, but adding more to the usage instructions and potential problems may be helpful
• the commented "debug" section of the code, should be replaced by a proper logging
• organize imports as per PEP8 recommendations

I am focusing on your question about the mathematics. In python 3.x, it is useless to apply float() to ARGS.hr since /does not match floor division any more (// has been introduced) but real division.

I suppose your main concern is about trunc(). I don't know what is better. I would have used round()` instead because I expect image converting softwares to handle the problem that way when performing a resize. However, I did not check and you can bet different softwares apply different policies... Did you try to shrink a 1920x1080 pic of, say, 30% to see what rule was used ?