<commit_before>#!/usr/bin/env python
#-*- coding: utf-8 -*-

"""Create mosaics of input images.

The module offers the possibility to create poster-alike images compound
by small tiles representing input photos. Moreover, the module could be
used both as a standalone application and as a standard python library.

Given a list of input images, the first of them will be chosen as
target, i.e. the final image. On the other hand, other images will be
chosen in turn, modified, and finally placed in the right position of
the final mosaic image.

During the creation of a mosaic, we need to arrange the images used
a tiles, inside a data structure which make it possible to extract
images by color. Image, at least for early implementations, this
structure to be a simple list of images.

Moreover, in order to avoid long waits due to indexing of very large
images, we could implement a sort of filter, or a chain of filters, that
could eventually be used to scale down input images, or either quantize
their colors.

The next step is to analyze the target image and look for needed tiles
for the final mosaic. Depending on the specified number of
tiles-per-side, we are going to divide the original image in small
tiles. Then, for each tile, we are going to compute its *fingerprint*,
which in our case corresponds to its average color.

At this point everything is ready to actually create the mosaic. For
each tile extracted from the target image, look inside the efficient
data structure and spot which of the available tiles has an average color
the most similar to the current one. Then we have to paste such found
tile in place of the original one.

Once we are done with all the tiles of the target image, we will be able
to either show the image on screen - watch out from large images ;-) - or
save it to a disk.

TODO:

    - Use a KD-Tree for the internal logic of the ``ImageList`` object.

    - Resize input if too large. Maybe we could implement an adaptive
      way to compute the target size depending on the number of
      specified tiles and zoom level.

    - In order to reduce the amount of work load, we could quantize
      target and source images.

    - While iterating over the colors of very large images, we could
      think of using the color histogram to reduce the length of output
      array.

    - While cropping an image to modify the ratio, we could probably use
      the image barycenter in order to throw away useless parts of the
      image.

"""

from __future__ import division
import itertools
import multiprocessing
import operator
from collections import namedtuple
from optparse import OptionParser
from optparse import OptionGroup
from functools import partial

from PIL import Image



# Mode of quantization of color components
QUANTIZATION_MODES = 'bottom middle top'.split()



def splitter(n, iterable):
    """Split `iterable` into `n` separate buckets.
    
    >>> list(splitter(3, range(3)))
    [[0], [1], [2]]
    >>> list(splitter(3, range(4)))
    [[0, 1], [2], [3]]
    >>> list(splitter(3, range(5)))
    [[0, 1], [2, 3], [4]]
    >>> list(splitter(3, range(6)))
    [[0, 1], [2, 3], [4, 5]]
    >>> list(splitter(3, range(7)))
    [[0, 1, 2], [3, 4], [5, 6]]
    """
    items_per_bucket = len(iterable) / n
    cutoff = 1
    acc = []
    for (i, elem) in enumerate(iterable):
        if i < cutoff * items_per_bucket:
            acc += [elem]
        else:
            yield acc
            cutoff += 1
            acc = [elem]
    if acc:
        yield acc


def flatten(iterable):
    """Flatten the input iterable.
    
    >>> list(flatten([[0, 1], [2, 3]]))
    [0, 1, 2, 3]
    >>> list(flatten([[0, 1], [2, 3, 4, 5]]))
    [0, 1, 2, 3, 4, 5]
    """
    return itertools.chain.from_iterable(iterable)


def dotproduct(vec1, vec2):
    """Return dot product of given vectors.
    
    >>> v1 = [1, 2, 3, 4]
    >>> v2 = [5, 6, 7, 8]
    >>> v3 = [0, 0, 0, 0]

    >>> dotproduct(v1, v2)
    70
    >>> dotproduct(v1, v3)
    0
    """
    return sum(itertools.imap(operator.mul, vec1, vec2))


def difference(vec1, vec2):
    """Return difference between given vectors.
    
    >>> v1 = [1, 2, 3, 4]
    >>> v2 = [5, 6, 7, 8]
    >>> v3 = [0, 0, 0, 0]

    >>> difference(v2, v1)
    [4, 4, 4, 4]
    >>> difference(v2, v3)
    [5, 6, 7, 8]
    """
    return map(operator.sub, vec1, vec2)


def squaredistance(vec1, vec2):
    """Return the square distance between given vectors.

    >>> v1 = [1, 2, 3, 4]
    >>> v2 = [5, 6, 7, 8]
    >>> v3 = [0, 0, 0, 0]

    >>> squaredistance(v1, v3)
    30
    >>> squaredistance(v2, v1)
    64
    """
    return sum(v ** 2 for v in difference(vec1, vec2))


def average_color(img):
    """Return the average color of the given image.

    The calculus of the average color has been implemented by looking at
    each pixel of the image and accumulate each rgb component inside
    separate counters.

    """
    print(img.__dict__)
    (width, height) = img.size
    num_pixels = width * height
    (total_red, total_green, total_blue) = (0, 0, 0)
    for (occurrences, (red, green, blue)) in img.colors:
        total_red += occurrences * red
        total_green += occurrences * green
        total_blue += occurrences * blue
    return (total_red // num_pixels,
            total_green // num_pixels,
            total_blue // num_pixels)


def quantize_color(color, levels=8, mode='middle'):
    """Reduce the spectrum of the given color.

    Each color component is forced to assume only certain values instead
    depending on the specified number of levels needed. If for example
    instead of 256 different levels, we need only a couple of them, each
    color component will be mapped into two ranges, namely [0, 128[ and
    [128, 256[.

    This way, given that multiple colors are possibly mapped on the same
    range of values and it is possible to decide to use as final output,
    the bottom, the middle or the top value of those ranges. Carrying on
    the example above, by default a color like (10, 10, 10), will match
    the first range of each component; hence, depending on the chosen
    mode, it will return (0, 0, 0), (64, 64, 64) or (127, 127, 127).<commit_msg>printing debug line<commit_after>#!/usr/bin/env python
#-*- coding: utf-8 -*-

"""Create mosaics of input images.

The module offers the possibility to create poster-alike images compound
by small tiles representing input photos. Moreover, the module could be
used both as a standalone application and as a standard python library.

Given a list of input images, the first of them will be chosen as
target, i.e. the final image. On the other hand, other images will be
chosen in turn, modified, and finally placed in the right position of
the final mosaic image.

During the creation of a mosaic, we need to arrange the images used
a tiles, inside a data structure which make it possible to extract
images by color. Image, at least for early implementations, this
structure to be a simple list of images.

Moreover, in order to avoid long waits due to indexing of very large
images, we could implement a sort of filter, or a chain of filters, that
could eventually be used to scale down input images, or either quantize
their colors.

The next step is to analyze the target image and look for needed tiles
for the final mosaic. Depending on the specified number of
tiles-per-side, we are going to divide the original image in small
tiles. Then, for each tile, we are going to compute its *fingerprint*,
which in our case corresponds to its average color.

At this point everything is ready to actually create the mosaic. For
each tile extracted from the target image, look inside the efficient
data structure and spot which of the available tiles has an average color
the most similar to the current one. Then we have to paste such found
tile in place of the original one.

Once we are done with all the tiles of the target image, we will be able
to either show the image on screen - watch out from large images ;-) - or
save it to a disk.

TODO:

    - Use a KD-Tree for the internal logic of the ``ImageList`` object.

    - Resize input if too large. Maybe we could implement an adaptive
      way to compute the target size depending on the number of
      specified tiles and zoom level.

    - In order to reduce the amount of work load, we could quantize
      target and source images.

    - While iterating over the colors of very large images, we could
      think of using the color histogram to reduce the length of output
      array.

    - While cropping an image to modify the ratio, we could probably use
      the image barycenter in order to throw away useless parts of the
      image.

"""

from __future__ import division
import itertools
import multiprocessing
import operator
from collections import namedtuple
from optparse import OptionParser
from optparse import OptionGroup
from functools import partial

from PIL import Image



# Mode of quantization of color components
QUANTIZATION_MODES = 'bottom middle top'.split()



def splitter(n, iterable):
    """Split `iterable` into `n` separate buckets.
    
    >>> list(splitter(3, range(3)))
    [[0], [1], [2]]
    >>> list(splitter(3, range(4)))
    [[0, 1], [2], [3]]
    >>> list(splitter(3, range(5)))
    [[0, 1], [2, 3], [4]]
    >>> list(splitter(3, range(6)))
    [[0, 1], [2, 3], [4, 5]]
    >>> list(splitter(3, range(7)))
    [[0, 1, 2], [3, 4], [5, 6]]
    """
    items_per_bucket = len(iterable) / n
    cutoff = 1
    acc = []
    for (i, elem) in enumerate(iterable):
        if i < cutoff * items_per_bucket:
            acc += [elem]
        else:
            yield acc
            cutoff += 1
            acc = [elem]
    if acc:
        yield acc


def flatten(iterable):
    """Flatten the input iterable.
    
    >>> list(flatten([[0, 1], [2, 3]]))
    [0, 1, 2, 3]
    >>> list(flatten([[0, 1], [2, 3, 4, 5]]))
    [0, 1, 2, 3, 4, 5]
    """
    return itertools.chain.from_iterable(iterable)


def dotproduct(vec1, vec2):
    """Return dot product of given vectors.
    
    >>> v1 = [1, 2, 3, 4]
    >>> v2 = [5, 6, 7, 8]
    >>> v3 = [0, 0, 0, 0]

    >>> dotproduct(v1, v2)
    70
    >>> dotproduct(v1, v3)
    0
    """
    return sum(itertools.imap(operator.mul, vec1, vec2))


def difference(vec1, vec2):
    """Return difference between given vectors.
    
    >>> v1 = [1, 2, 3, 4]
    >>> v2 = [5, 6, 7, 8]
    >>> v3 = [0, 0, 0, 0]

    >>> difference(v2, v1)
    [4, 4, 4, 4]
    >>> difference(v2, v3)
    [5, 6, 7, 8]
    """
    return map(operator.sub, vec1, vec2)


def squaredistance(vec1, vec2):
    """Return the square distance between given vectors.

    >>> v1 = [1, 2, 3, 4]
    >>> v2 = [5, 6, 7, 8]
    >>> v3 = [0, 0, 0, 0]

    >>> squaredistance(v1, v3)
    30
    >>> squaredistance(v2, v1)
    64
    """
    return sum(v ** 2 for v in difference(vec1, vec2))


def average_color(img):
    """Return the average color of the given image.

    The calculus of the average color has been implemented by looking at
    each pixel of the image and accumulate each rgb component inside
    separate counters.

    """
    print(img)
    print(img.__dict__)
    (width, height) = img.size
    num_pixels = width * height
    (total_red, total_green, total_blue) = (0, 0, 0)
    for (occurrences, (red, green, blue)) in img.colors:
        total_red += occurrences * red
        total_green += occurrences * green
        total_blue += occurrences * blue
    return (total_red // num_pixels,
            total_green // num_pixels,
            total_blue // num_pixels)


def quantize_color(color, levels=8, mode='middle'):
    """Reduce the spectrum of the given color.

    Each color component is forced to assume only certain values instead
    depending on the specified number of levels needed. If for example
    instead of 256 different levels, we need only a couple of them, each
    color component will be mapped into two ranges, namely [0, 128[ and
    [128, 256[.

    This way, given that multiple colors are possibly mapped on the same
    range of values and it is possible to decide to use as final output,
    the bottom, the middle or the top value of those ranges. Carrying on
    the example above, by default a color like (10, 10, 10), will match
    the first range of each component; hence, depending on the chosen
    mode, it will return (0, 0, 0), (64, 64, 64) or (127, 127, 127).
{'fraction_non_alphanumeric': 0.09652292620252148, 'fraction_numerical': 0.02955111218180358, 'mean_word_length': 2.9386464263124603, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 135, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5114328', 'n_tokens_mistral': 3978, 'n_tokens_neox': 3663, 'n_words': 1762}