Skip to content Skip to sidebar Skip to footer

How To List An Image Sequence In An Efficient Way? Numercial Sequence Comparison In Python

I have a directory of 9 images: image_0001, image_0002, image_0003 image_0010, image_0011 image_0011-1, image_0011-2, image_0011-3 image_9999 I would like to be able to list them

Solution 1:

Here is a working implementation of what you want to achieve, using the code you added as a starting point:

#!/usr/bin/env pythonimport itertools
import re

# This algorithm only works if DATA is sorted.
DATA = ["image_0001", "image_0002", "image_0003",
        "image_0010", "image_0011",
        "image_0011-1", "image_0011-2", "image_0011-3",
        "image_0100", "image_9999"]

defextract_number(name):
    # Match the last number in the name and return it as a string,# including leading zeroes (that's important for formatting below).return re.findall(r"\d+$", name)[0]

defcollapse_group(group):
    iflen(group) == 1:
        return group[0][1]  # Unique names collapse to themselves.
    first = extract_number(group[0][1])  # Fetch range
    last = extract_number(group[-1][1])  # of this group.# Cheap way to compute the string length of the upper bound,# discarding leading zeroes.
    length = len(str(int(last)))
    # Now we have the length of the variable part of the names,# the rest is only formatting.return"%s[%s-%s]" % (group[0][1][:-length],
        first[-length:], last[-length:])

groups = [collapse_group(tuple(group)) \
    for key, group in itertools.groupby(enumerate(DATA),
        lambda(index, name): index - int(extract_number(name)))]

print groups

This prints ['image_000[1-3]', 'image_00[10-11]', 'image_0011-[1-3]', 'image_0100', 'image_9999'], which is what you want.

HISTORY: I initially answered the question backwards, as @Mark Ransom pointed out below. For the sake of history, my original answer was:

You're looking for glob. Try:

importglobimages= glob.glob("image_[0-9]*")

Or, using your example:

images = [glob.glob(pattern) for pattern in ("image_000[1-3]*",
    "image_00[10-11]*", "image_0011-[1-3]*", "image_9999*")]
images = [image for seq in images for image in seq]  # flatten the list

Solution 2:

Okay, so I found your question to be a fascinating puzzle. I've left how to "compress" the numeric ranges up to you (marked as a TODO), as there are different ways to accomplish that depending on how you like it formatted and if you want the minimum number of elements or the minimum string description length.

This solution uses a simple regular expression (digit strings) to classify each string into two groups: static and variable. After the data is classified, I use groupby to collect the static data into longest matching groups to achieve the summary effect. I mix integer index sentinals into the result (in matchGrouper) so I can re-select the varying parts from all elements (in unpack).

import re
import glob
from itertools import groupby
from operator import itemgetter

defclassifyGroups(iterable, reObj=re.compile('\d+')):
    """Yields successive match lists, where each item in the list is either
    static text content, or a list of matching values.

     * `iterable` is a list of strings, such as glob('images/*')
     * `reObj` is a compiled regular expression that describes the
            variable section of the iterable you want to match and classify
    """defclassify(text, pos=0):
        """Use a regular expression object to split the text into match and non-match sections"""
        r = []
        for m in reObj.finditer(text, pos):
            m0 = m.start()
            r.append((False, text[pos:m0]))
            pos = m.end()
            r.append((True, text[m0:pos]))
        r.append((False, text[pos:]))
        return r

    defmatchGrouper(each):
        """Returns index of matches or origional text for non-matches"""return [(i if t else v) for i,(t,v) inenumerate(each)]

    defunpack(k,matches):
        """If the key is an integer, unpack the value array from matches"""ifisinstance(k, int):
            k = [m[k][1] for m in matches]
        return k

    # classify each item into matches
    matchLists = (classify(t) for t in iterable)

    # group the matches by their static contentfor key, matches in groupby(matchLists, matchGrouper):
        matches = list(matches)
        # Yield a list of content matches.  Each entry is either text# from static content, or a list of matchesyield [unpack(k, matches) for k in key]

Finally, we add enough logic to perform pretty printing of the output, and run an example.

defmakeResultPretty(res):
    """Formats data somewhat like the question"""
    r = []
    for e in res:
        ifisinstance(e, list):
            # TODO: collapse and simplify ranges as desired hereiflen(set(e))<=1:
                # it's a list of the same element
                e = e[0]
            else: 
                # prettify the list
                e = '['+' '.join(e)+']'
        r.append(e)
    return''.join(r)

fnList = sorted(glob.glob('images/*'))
re_digits = re.compile(r'\d+')
for res in classifyGroups(fnList, re_digits):
    print makeResultPretty(res)

My directory of images was created from your example. You can replace fnList with the following list for testing:

fnList = [
 'images/image_0001.jpg',
 'images/image_0002.jpg',
 'images/image_0003.jpg',
 'images/image_0010.jpg',
 'images/image_0011-1.jpg',
 'images/image_0011-2.jpg',
 'images/image_0011-3.jpg',
 'images/image_0011.jpg',
 'images/image_9999.jpg']

And when I run against this directory, my output looks like:

StackOverflow/3926936%pythonclassify.pyimages/image_[00010002 0003 0010].jpgimages/image_0011-[123].jpgimages/image_[00119999].jpg

Solution 3:

def ranges(sorted_list):
    first=Nonefor x in sorted_list:
        if firstisNone:
            first=last= x
        elif x == increment(last):
            last= x
        else:
            yield first, lastfirst=last= x
    if firstisnotNone:
        yield first, last

The increment function is left as an exercise for the reader.

Edit: here's an example of how it would be used with integers instead of strings as input.

defincrement(x): return x+1list(ranges([1,2,3,4,6,7,8,10]))
[(1, 4), (6, 8), (10, 10)]

For each contiguous range in the input you get a pair indicating the start and end of the range. If an element isn't part of a range, the start and end values are identical.

Post a Comment for "How To List An Image Sequence In An Efficient Way? Numercial Sequence Comparison In Python"