I think I understand what you are trying to do. I have no idea of the relative performance of our two machines so maybe you can benchmark it yourself.

from PIL import Image
import numpy as np

# Load images, convert to RGB, then to numpy arrays and ravel into long, flat things
a=np.array(Image.open('a.png').convert('RGB')).ravel()
b=np.array(Image.open('b.png').convert('RGB')).ravel()

# Calculate the sum of the absolute differences divided by number of elements
MAE = np.sum(np.abs(np.subtract(a,b,dtype=np.float))) / a.shape[0]

The only "tricky" thing in there is the forcing of the result type of np.subtract() to a float which ensures I can store negative numbers. It may be worth trying with dtype=np.int16 on your hardware to see if that is faster.


A fast way to benchmark it is as follows. Start ipython and then type in the following:

from PIL import Image
import numpy as np

a=np.array(Image.open('a.png').convert('RGB')).ravel()
b=np.array(Image.open('b.png').convert('RGB')).ravel()

Now you can time my code with:

%timeit np.sum(np.abs(np.subtract(a,b,dtype=np.float))) / a.shape[0]
6.72 µs ± 21.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Or, you can try an int16 version like this:

%timeit np.sum(np.abs(np.subtract(a,b,dtype=np.int16))) / a.shape[0]
6.43 µs ± 30.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

If you want to time your code, paste in your function then use:

%timeit compare_images_pil(img1, img2)
Answer from Mark Setchell on Stack Overflow
Top answer
1 of 2
5

I think I understand what you are trying to do. I have no idea of the relative performance of our two machines so maybe you can benchmark it yourself.

from PIL import Image
import numpy as np

# Load images, convert to RGB, then to numpy arrays and ravel into long, flat things
a=np.array(Image.open('a.png').convert('RGB')).ravel()
b=np.array(Image.open('b.png').convert('RGB')).ravel()

# Calculate the sum of the absolute differences divided by number of elements
MAE = np.sum(np.abs(np.subtract(a,b,dtype=np.float))) / a.shape[0]

The only "tricky" thing in there is the forcing of the result type of np.subtract() to a float which ensures I can store negative numbers. It may be worth trying with dtype=np.int16 on your hardware to see if that is faster.


A fast way to benchmark it is as follows. Start ipython and then type in the following:

from PIL import Image
import numpy as np

a=np.array(Image.open('a.png').convert('RGB')).ravel()
b=np.array(Image.open('b.png').convert('RGB')).ravel()

Now you can time my code with:

%timeit np.sum(np.abs(np.subtract(a,b,dtype=np.float))) / a.shape[0]
6.72 µs ± 21.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Or, you can try an int16 version like this:

%timeit np.sum(np.abs(np.subtract(a,b,dtype=np.int16))) / a.shape[0]
6.43 µs ± 30.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

If you want to time your code, paste in your function then use:

%timeit compare_images_pil(img1, img2)
2 of 2
2

Digging a bit, I found this repository that takes a different approach that is more based on Pillow itself and seems to give similar results.

from PIL import Image
from PIL import ImageChops, ImageStat


def compare_images_pil(img1, img2):
    '''Calculate the difference between two images of the same size
    by comparing channel values at the pixel level.
    `delete_diff_file`: removes the diff image after ratio found
    `diff_img_file`: filename to store diff image

    Adapted from Nicolas Hahn:
    https://github.com/nicolashahn/diffimg/blob/master/diffimg/__init__.py
    '''

    # Don't compare if images are of different modes or different sizes.
    if (img1.mode != img2.mode) \
            or (img1.size != img2.size) \
            or (img1.getbands() != img2.getbands()):
        return None

    # Generate diff image in memory.
    diff_img = ImageChops.difference(img1, img2)

    # Calculate difference as a ratio.
    stat = ImageStat.Stat(diff_img)

    # Can be [r,g,b] or [r,g,b,a].
    sum_channel_values = sum(stat.mean)
    max_all_channels = len(stat.mean) * 255
    diff_ratio = sum_channel_values / max_all_channels

    return diff_ratio * 100

For my test images sample, the results seem to be the same (except for a few minor float rounding errors) and it runs considerably faster than the first version I had above.

🌐
NumPy
numpy.org › doc › 2.1 › reference › generated › numpy.absolute.html
numpy.absolute — NumPy v2.1 Manual
Calculate the absolute value element-wise. ... Input array. outndarray, None, or tuple of ndarray and None, optional
🌐
GeeksforGeeks
geeksforgeeks.org › python › python-program-to-find-sum-of-absolute-difference-between-all-pairs-in-a-list
Python program to find sum of absolute difference between all pairs in a list - GeeksforGeeks
July 11, 2025 - Convert the list to a NumPy array. Reshape the array to have two axes. Use broadcasting to compute the absolute differences between all pairs of elements. Sum the resulting array and divide by 2 to account for double-counting.
🌐
NumPy
numpy.org › doc › 2.3 › reference › generated › numpy.sum.html
numpy.sum — NumPy v2.3 Manual
For floating point numbers the numerical precision of sum (and np.add.reduce) is in general limited by directly adding each number individually to the result causing rounding errors in every step. However, often numpy will use a numerically better approach (partial pairwise summation) leading to improved precision in many use-cases.
🌐
Note.nkmk.me
note.nkmk.me › home › python › numpy
Calculate the absolute value element-wise (np.abs, np.fabs)
January 14, 2024 - You can calculate the absolute value element-wise in a NumPy array (ndarray) using np.abs(), np.absolute(), or np.fabs(). Note that np.abs() is simply an alias for np.absolute(). Additionally, the built-in abs() function can be used.
🌐
Codecademy
codecademy.com › docs › python:numpy › math methods › .abs()
Python:NumPy | Math Methods | .abs() | Codecademy
June 12, 2025 - An array containing the absolute value of each element in the input. For complex numbers, returns the magnitude calculated as √(real² + imaginary²). This example demonstrates the fundamental usage of numpy.abs() with different numeric data types:
🌐
Vultr Docs
docs.vultr.com › python › third-party › numpy › absolute
Python Numpy absolute() - Calculate Absolute Value | Vultr Docs
November 6, 2024 - For instance, 3+4j has a magnitude calculated as sqrt(3^2 + 4^2). The numpy.absolute() function is a versatile tool for computing the absolute values of elements within numpy arrays, including handling complex data types.
🌐
NumPy
numpy.org › devdocs › reference › generated › numpy.cumsum.html
numpy.cumsum — NumPy v2.5.dev0 Manual
See sum for more information. ... Try it in your browser! >>> import numpy as np >>> a = np.array([[1,2,3], [4,5,6]]) >>> a array([[1, 2, 3], [4, 5, 6]]) >>> np.cumsum(a) array([ 1, 3, 6, 10, 15, 21]) >>> np.cumsum(a, dtype=np.float64) # specifies type of output value(s) array([ 1., 3., 6., 10., 15., 21.])
🌐
NumPy
numpy.org › doc › stable › reference › generated › numpy.cumsum.html
numpy.cumsum — NumPy v2.4 Manual
See sum for more information. ... Try it in your browser! >>> import numpy as np >>> a = np.array([[1,2,3], [4,5,6]]) >>> a array([[1, 2, 3], [4, 5, 6]]) >>> np.cumsum(a) array([ 1, 3, 6, 10, 15, 21]) >>> np.cumsum(a, dtype=float) # specifies type of output value(s) array([ 1., 3., 6., 10., 15., 21.])
Find elsewhere
🌐
NumPy
numpy.org › doc › 2.2 › reference › generated › numpy.ediff1d.html
numpy.ediff1d — NumPy v2.2 Manual
If necessary, will be flattened before the differences are taken. ... Number(s) to append at the end of the returned differences.
Top answer
1 of 1
6

First of all, I want to say this is really well presented. It just looks neat, with docstrings and comments and such. If you really want to perfect it, check the Python conventions in PEP 8 and linked documents. For example,

An inline comment is a comment on the same line as a statement. Inline comments should be separated by at least two spaces from the statement. They should start with a # and a single space.

and

The docstring is a phrase ending in a period. It prescribes the function or method's effect as a command ("Do this", "Return that"), not as a description; e.g. don't write "Returns the pathname ...".


I also very much appreciate the case analysis that has gone into the code to define those four cases, and so to reduce the naively quadratic function to a linear one. (Refering back to comments, that case analysis would not be at all amiss in a comment just after the docstring explaining the internal logic of the function)

Because, per the code and your case analysis, max1 and min1 always contain the same data, you could make the code shorter and reduce its space requirements by only having one of those. (And likewise for max2 and min2) However, see the next section.


In terms of the code itself, I suggest getting rid of those arrays. If they are only filled so that you can reduce them to a single maximum or minimum value, you might as well just track the rolling maximum or minimum as you go. This definitely saves space and probably saves time.

I would name the rolling reductions max_sum and max_difference rather than max1 and max2.


In terms of testing examples, do look for pathological cases. One case that always deserves to be checked, although it's not always obvious what the behaviour should be, is the case of an empty list.


Finally, it is generally worth using Python 3 in preference to Python 2 for new code, and especially practice code.

🌐
Wikipedia
en.wikipedia.org › wiki › Mean_absolute_difference
Mean absolute difference - Wikipedia
August 30, 2025 - The mean absolute difference (univariate) is a measure of statistical dispersion equal to the average absolute difference of two independent values drawn from a probability distribution. A related statistic is the relative mean absolute difference, which is the mean absolute difference divided ...
🌐
Rahianmess
rahianmess.ir › lq5pmdyd › xkaglv5j7z.php
We cannot provide a description for this page right now
🌐
GeeksforGeeks
geeksforgeeks.org › absolute-deviation-and-absolute-mean-deviation-using-numpy-python
Absolute Deviation and Absolute Mean Deviation using NumPy | Python | GeeksforGeeks
July 31, 2024 - NumPy's sum() function is extremely useful for summing all elements of a given array in Python. In this article, we'll be going over how to utilize this function and how to quickly use this to advance your code's functionality.
🌐
Python Data Science Handbook
jakevdp.github.io › PythonDataScienceHandbook › 02.03-computation-on-arrays-ufuncs.html
Computation on NumPy Arrays: Universal Functions | Python Data Science Handbook
Each of these arithmetic operations are simply convenient wrappers around specific functions built into NumPy; for example, the + operator is a wrapper for the add function: ... Additionally there are Boolean/bitwise operators; we will explore these in Comparisons, Masks, and Boolean Logic. Just as NumPy understands Python's built-in arithmetic operators, it also understands Python's built-in absolute value function:
🌐
GeeksforGeeks
geeksforgeeks.org › dsa › maximum-sum-absolute-difference-array
Maximum sum of absolute difference of any permutation - GeeksforGeeks
March 27, 2023 - This is done to get maximum difference. ... We will sort the array. Calculate the final sequence by taking one smallest element and largest element from the sorted array and make one vector array of this final sequence. Finally, calculate the sum of absolute difference between the elements of the array.
🌐
NumPy
numpy.org › doc › stable › reference › generated › numpy.diff.html
numpy.diff — NumPy v2.4 Manual
The first difference is given by out[i] = a[i+1] - a[i] along the given axis, higher differences are calculated by using diff recursively. ... The number of times values are differenced.
🌐
NumPy
numpy.org › doc › stable › reference › generated › numpy.allclose.html
numpy.allclose — NumPy v2.4 Manual
The relative difference (rtol * abs(b)) and the absolute difference atol are added together to compare against the absolute difference between a and b. ... The default atol is not appropriate for comparing numbers with magnitudes much smaller than one (see Notes). NaNs are treated as equal if they are in the same place and if equal_nan=True. Infs are treated as equal if they are in the same place and of the same sign in both arrays.
🌐
NumPy
numpy.org › doc › stable › reference › generated › numpy.isclose.html
numpy.isclose — NumPy v2.4 Manual
The relative difference (rtol * abs(b)) and the absolute difference atol are added together to compare against the absolute difference between a and b. ... The default atol is not appropriate for comparing numbers with magnitudes much smaller than one (see Notes).