import imagen as ig
line=ig.Line(xdensity=5, ydensity=5, smoothing=0)

"""
Explanation: <center><img src="https://ioam.github.io/imagen/_images/patterntypes_small.png"/></center>
The ImaGen package provides comprehensive support for creating resolution-independent two-dimensional pattern distributions. ImaGen consists of a large library of spatial patterns, including mathematical functions, geometric primitives, and images read from files, along with many ways to combine or select from any other patterns. These patterns can be used in any Python program that needs configurable patterns or streams of patterns.  Basically, as long as the code can accept a Python callable and will call it each time it needs a new pattern, users can then specify any pattern possible in ImaGen's simple declarative pattern language, and the downstream code need not worry about any of the details about how the pattern is specified or generated.  This approach gives users full flexibility about which patterns they wish to use, while relieving the downstream code from having to implement anything about patterns. The detailed examples below should help make this clear.
Usage
To create a pattern, just import imagen, then instantiate one of ImaGen's PatternGenerator classes.  Each of these classes support various parameters, which are each described in the Reference Manual or via help(pattern-object-or-class).  Any parameter values specified on instantiation become the defaults for that object:
End of explanation
"""


line()

"""
Explanation: Then whenever the line object is called, you'll get a new NumPy array:
End of explanation
"""


import numpy as np
np.set_printoptions(1)
line(smoothing=0.1,orientation=0.8,thickness=0.4)

"""
Explanation: Here the parameters xdensity and ydensity specified that a continuous 1.0&times;1.0 region in (x,y) space should be sampled on a 5&times;5 grid.  The line object can now be called repeatedly to get new arrays of data, with any parameter values specified to override those declared above:
End of explanation
"""


line.set_param(xdensity=72,ydensity=72,orientation=np.pi/4, thickness=0.1, smoothing=0.02)

line.pil(orientation=3*np.pi/4)

"""
Explanation: As you can see, the results are in the form of a Numpy array, and here we have used very small arrays to avoid generating a lot of numeric output.  For larger arrays, it is convenient to view them as images, which you can do easily if you have the PIL or pillow libraries installed:
End of explanation
"""


import holoviews as hv
hv.notebook_extension("matplotlib")

line[:]

"""
Explanation: As you can see, the .pil() method accepts the same options as supported elsewhere, making it simple to create the image that you need to generate.  .pil() returns a PIL image, which can be exported to files on disk using the methods described in the PIL documentation.
ImaGen depends only on NumPy, Param, and HoloViews, none of which have any other required dependencies, and it is thus easy to incorporate ImaGen into your own code to generate or use patterns freely. That said, HoloViews supports various plotting libraries, including Matplotlib and Bokeh, and if you have one of those installed then imagen provides a convenient way to plot the pattern objects with axes and labels:
End of explanation
"""


import numbergen as ng
from holoviews import NdLayout
import param
param.Dynamic.time_dependent=True
NdLayout(ig.SineGrating(orientation=np.pi*ng.UniformRandom()).anim(3))

"""
Explanation: We will use this plotting interface to show off the remaining patterns, but please remember that the main purpose of ImaGen is to generate arrays for use in other programs, not simply to draw pretty patterns for plotting!
Dynamic parameter values
As you can see above, PatternGenerator objects return different patterns depending on their parameter values. An important feature of these parameter values is that any of them can be set to "dynamic" values, which will then result in a different pattern each time (see the Param package and its numbergen module for details). With dynamic parameters, PatternGenerators provide streams of patterns, not just individual patterns.  For example, let's define a SineGrating object with a random orientation, collect four of them at different times (using the .anim() method), and lay them out next to each other (using the NdLayout class from HoloViews):
End of explanation
"""


%%opts Image (cmap='gray')
sine_disk = ig.SineGrating(orientation=np.pi*ng.UniformRandom(),
                           scale=0.25*ng.ExponentialDecay(time_constant=3),
                           frequency=4+7*ng.UniformRandom(),
                           x=0.3*ng.NormalRandom(seed=1),
                           y=0.2*ng.UniformRandom(seed=2)-0.1,
                           mask_shape=ig.Disk(size=0.5,smoothing=0.01))
NdLayout(sine_disk.anim(3))

"""
Explanation: As you can see, each time the sine grating was rendered, the pattern differed, because the parameter value for orientation was chosen randomly.  Of course, you can set any combination of patterns to dynamic values, to get arbitrarily complex variation over time:
End of explanation
"""


gs = ig.Composite(operator=np.add,
                  generators=[ig.Gaussian(size=0.15,
                                          x=ng.UniformRandom(seed=i+1)-0.5,
                                          y=ng.UniformRandom(seed=i+2)-0.5,
                                          orientation=np.pi*ng.UniformRandom(seed=i+3))
                                for i in range(10)])
                 
NdLayout(gs.anim(4)).cols(5)

"""
Explanation: Composite patterns
As you can see above, PatternGenerator objects can also be used as a mask for another PatternGenerator, which is one simple way to combine them.  
PatternGenerators can also be combined directly with each other to create Composite PatternGenerators, which can make any possible 2D pattern.  For instance, we can easily sum 10 oriented Gaussian patterns, each with random positions and orientations, giving a different overall pattern at each time:
End of explanation
"""


%%opts Image.Pattern (cmap='Blues_r')
l1 = ig.Line(orientation=-np.pi/4)
l2 = ig.Line(orientation=+np.pi/4)
cross = l1 | l2
cross.orientation=ng.ScaledTime()*(np.pi/-20)
l1.anim(20) + l2.anim(20) + cross.anim(20)

"""
Explanation: Once it has been defined, a Composite pattern works just like any other pattern, so that it can be placed, rotated, combined with others, etc., allowing you to build up arbitrarily complex objects out of simple primitives.  Here we created a Composite pattern explicitly, but it's usually easier to create them by simply using any of the usual Python operators (+, -, *, /, **, %, &amp; (min), and | (max)) as in the examples below.
For instance, here's an example using np.maximum (via the | operator on PatternGenerators), rotating the composite pattern together as a unit.  We also leave it as a HoloViews animation rather than laying it out over space:
End of explanation
"""


%opts Image (cmap='gray')
import param

param.Dynamic.time_fn.advance(1)

print("The current nominal time value is %s" % param.Dynamic.time_fn())

%%output backend='matplotlib' holomap='gif'
lefteye    = ig.Disk(aspect_ratio=0.7, x=0.04, y=0.10, size=0.08,smoothing=0.005)
leftpupil  = ig.Disk(aspect_ratio=1.0, x=0.03, y=0.08, size=0.04,smoothing=0.005)
righteye   = ig.Disk(aspect_ratio=0.7, x=0.04, y=-0.1, size=0.08,smoothing=0.005)
rightpupil = ig.Disk(aspect_ratio=1.0, x=0.03, y=-0.08,size=0.04,smoothing=0.005)
nose   = ig.Gaussian(aspect_ratio=0.8, x=-0.1, y=0.00, size=0.04)
mouth  = ig.Gaussian(aspect_ratio=0.8, x=-0.2, y=0.00, size=0.06)
head   = ig.Disk(    aspect_ratio=1.5, x=-0.02,y=0.00, size=0.40, scale=0.70,smoothing=0.005)
face=head + lefteye - 1.6*leftpupil + righteye - 1.6*rightpupil - 0.5*nose - 0.8*mouth
face.set_param(x=0.2, y=0.1, offset=0.5, size=0.75)
face.orientation=ng.ScaledTime()*np.pi/20

line  = ig.Line(y=0.6-ng.ScaledTime()*0.03)
disk = ig.Disk(smoothing=0.01, size=0.4, x=-0.2, y=-0.2)

(face + line*disk).anim(39)

"""
Explanation: The .anim() method collects results at different times conveniently.  What it's doing repeatedly is getting a copy of each pattern, then running param.Dynamic.time_fn.advance(1.0) to advance the nominal time, then getting another copy of each pattern until 20 different times have been sampled.  The values are "time dependent" (because we set them to be so above), so that any randomness changes only when the time changes, and the randomness is computed as a function of time.  That way, regardless of the order you generate the patterns, or even if you go back and forward in time, you will always get the same results at a given nominal time.  In your own code, you can turn off time dependence (param.Dynamic.time_dependent=False), in which case new parameter values will be generated for every call to the PatternGenerator.  Or, if you are working in a domain that has a clear temporal component, such as simulation, you can set param.Dynamic.time_fn to a function based on your own nominal time, advancing it as appropriate.  You can even set that function to real time, in which case you'll get completely unpredictable randomness, which may be appropriate in some circumstances.  Whenever there is some notion of time that governs the patterns you want to see, setting time_dependent=True is a good idea, so that you have precise control over the randomness to ensure reproducible results.
We used one operator above to make the cross image, but we can combine operators in any combination, here to build a cartoon face and add the result to a sweeping Line pattern masked with a Disk, creating an animated GIF of the results with HoloViews:
End of explanation
"""


from imagen.image import FileImage
inputs=[FileImage(filename=f, size=6.0,
                  x=ng.UniformRandom(lbound=-2,ubound=2),
                  y=ng.UniformRandom(lbound=-2,ubound=2),
                  orientation=ng.NormalRandom(sigma=0.1*np.pi))
        for f in ["images/ellen_arthur.pgm"]]

random_selection=ig.Selector(generators=inputs)
NdLayout(random_selection.anim(5)).cols(6)

"""
Explanation: Image patterns
ImaGen can load and manipulate photographic images just like other patterns, apart from them not being resolution independent.  For full functionality this requires the optional PIL or Pillow library, but support for Numpy arrays as images is provided with no further dependencies.  For instance, if you have a database of images (here consisting of only one image for simplicity), you can repeatedly select an image at random from the database using Selector, rotate it randomly if desired, and select a random patch of the image at each time:
End of explanation
"""


import imagen.transferfn as tf
from imagen.transferfn.sheet_tf import Convolve

(FileImage()[:] + \
FileImage(output_fns=[tf.BinaryThreshold()])[:] + \
FileImage(output_fns=[Convolve()])[:] + \
FileImage(output_fns=[Convolve(),tf.BinaryThreshold(threshold=0.45)])[:] + \
FileImage(output_fns=[Convolve(kernel_pattern=ig.DifferenceOfGaussians(size=0.12)),
                      tf.BinaryThreshold()])[:]).cols(5)

"""
Explanation: Applying functions to generated patterns
Once the pattern has been generated, but before it is returned, you can apply any function to the data that you like, via the output_fns parameter.  A variety of useful TransferFns are supplied for use as output_fns, such as thresholding functions, normalizing functions (L0, L1, L2, L-infinity, etc.), and convolutions. Any number of these or your own functions (anything that can operate on a 2D Numpy array) can be applied, in order:
End of explanation
"""


from imagen.image import ScaleChannels
ig.ComposeChannels(generators=[ig.Spiral(smoothing=0.02),ig.Spiral(),ig.Spiral(scale=0)])[:] + \
ig.ComposeChannels(generators=[ig.Line(orientation=np.pi/2),ig.Ring(),ig.SquareGrating()])[:]

"""
Explanation: Multi-channel patterns
The above examples all show "single-channel" PatternGenerator objects, which are very general and usable for a huge variety of applications, as they are simply Numpy arrays.  
PatternGenerator objects can have any number of channels, with each channel generating a Numpy array of the same size.  Multi-channel patterns are used less often, but are particularly useful for generating color images.  Color images loaded by the FileImage pattern will have four channels, one for the monochrome image (as above), and the other three for the red, green, and blue channels (accessed using object.channels()).  RGB images can also be constructed by colorizing a monochrome pattern, or out of combinations of any of the other patterns, using the ComposeChannels object:
End of explanation
"""


ig.ComposeChannels(generators=[ig.Line(orientation=np.pi/2),ig.Ring(),ig.SquareGrating(),ig.Disk()]).pil()

"""
Explanation: Three- or four-channel patterns support .pil(), generating RGB or RGBA color images.
End of explanation
"""


%opts Layout [sublabel_format="" horizontal_spacing=.1 vertical_spacing=.1]
%opts Image (cmap='gray') [xaxis=None yaxis=None show_frame=True] {+axiswise}

from imagen import *
from imagen.random import *
from imagen.image import *

pattern_classes = [x for _, x in sorted(locals().items()) if isinstance(x,type)
                   and issubclass(x,PatternGenerator) and not x.abstract]

all_patterns = [x()[:] for x in pattern_classes[10:]]

hv.Layout(all_patterns).cols(5)

"""
Explanation: Pattern types provided
Below are shown examples of each of the pattern types currently provided, using their default parameter values. Very many different parameter values can be chosen, to produce a much wider range of patterns, and of course new patterns can be created as Composite patterns as shown above.
End of explanation
"""


