Module raster

raster.py provides methods to generate and manipulate raster-based geometries for LAMMPS input files. It includes tools for creating, plotting, and exporting geometric objects, with advanced features for particle systems such as emulsions and core-shell dispersions. This module is part of the Pizza3 toolkit.


Features

1. Raster Area Creation

Create a raster object with customizable dimensions and properties:

R = raster(width=200, height=200, dpi=300)

2. Geometric Object Insertion

Define and manipulate objects such as rectangles, circles, triangles, diamonds, and polygons:

R.rectangle(10, 20, 15, 30, name='rect1', beadtype=1)
R.circle(50, 50, 10, name='circle1', beadtype=2, angle=45)
R.triangle(30, 40, 10, name='triangle1', beadtype=3, angle=30)

3. Collections and Paths

Group objects into collections or copy them along specified paths:

R.collection(object1, object2, name='my_collection', beadtype=1)
R.copyalongpath(object1, name='path_collection', path=linear, n=5)

4. Scatter and Emulsions

Generate random distributions of particles: - Emulsions: Place circular particles within defined boundaries. - Core-Shell Particles: Insert particles with inner and outer radii.

e = emulsion(xmin=10, ymin=10, xmax=100, ymax=100)
e.insertion([10, 20, 30], beadtype=1)

5. Visualization

Preview objects and raster layouts:

R.plot()
R.show(extra="label", contour=True)

6. Exporting to LAMMPS

Generate a pizza.data3.data object for exporting to LAMMPS:

data_obj = R.data(scale=(1, 1), center=(0, 0))
data_obj.write("output_file.lmp")

Advanced Features

  • Overlay Images: Import and convert images to raster objects with beads: python R.overlay(50, 50, filename="image.jpg", ncolors=4, beadtype=2)
  • Hexagonal Packing: Generate hex-packed data for particle arrangements.
  • Labeling and Masking: Add labels to objects or define masked regions.

Usage Examples

Basic Example

R = raster(width=100, height=100)
R.rectangle(10, 20, 15, 30, name='rect1', beadtype=1)
R.circle(50, 50, 10, name='circle1', beadtype=2)
R.plot()
R.show(extra="label")

Emulsion Generation

E = raster(width=400, height=400)
e = emulsion(xmin=10, ymin=10, xmax=390, ymax=390)
e.insertion([60, 50, 40, 30], beadtype=1)
E.scatter(e, name="emulsion")
E.plot()
E.show()

Core-Shell Dispersion

C = raster(width=400, height=400)
cs = coreshell(xmin=10, ymin=10, xmax=390, ymax=390)
cs.insertion([60, 50, 40], beadtype=(1, 2), thickness=4)
C.scatter(cs, name="core-shell")
C.plot()
C.show()

Requirements

Since version 0.40, image overlays and live previews depend on the Pizza3.pizza.private.PIL library. For compatibility, the customized version of PIL must be compiled:

cd Pizza3/pizza/private/PIL
python3 setup.py install

Authors and Credits

  • Author: Olivier Vitrac
  • Email: olivier.vitrac@agroparistech.fr
  • License: GPLv3
  • Credits: [Olivier Vitrac, Pizza3 Development Team]

Old help

RASTER method to generate LAMMPS input files (in 2D for this version)

Generate a raster area
    R = raster()
    R = raster(width=200, height=200, dpi=300)

Set objects (rectangle, circle, triangle, diamond...)
    R.rectangle(1,24,2,20,name='rect1')
    R.rectangle(60,80,50,81,name='rect2',beadtype=2,angle=40)
    R.rectangle(50,50,10,10,mode="center",angle=45,beadtype=1)
    R.circle(45,20,5,name='C1',beadtype=3)
    R.circle(35,10,5,name='C2',beadtype=3)

    R.circle(15,30,10,name='p1',beadtype=4,shaperatio=0.2,angle=-30)
    R.circle(12,40,8,name='p2',beadtype=4,shaperatio=0.2,angle=20)
    R.circle(12,80,22,name='p3',beadtype=4,shaperatio=1.3,angle=20)

    R.triangle(85,20,10,name='T1',beadtype=5,angle=20)
    R.diamond(85,35,5,name='D1',beadtype=5,angle=20)
    R.pentagon(50,35,5,name='P1',beadtype=5,angle=90)
    R.hexagon(47,85,12,name='H1',beadtype=5,angle=90)

List simple objects
    R.list()
    R.get("p1")
    R.p1
    R.C1

List objects in a collection
    R.C1.get("p1")
    R.C1.p1 shows the object p1 in the collection C1

Build objects and show them
    R.plot()
    R.show()

Show and manage labels
    R.show(extra="label",contour=True)
    R.label("rect003")
    R.unlabel('rect1')

Manage objects, update and show

Get the image and convert the image to text
    I = R.numeric()
    T = R.string()
    R.print()

Create a pizza.dump3.dump object
    X = R.data()
    X=R.data(scale=(1,1),center=(0,0))
    X.write("/tmp/myfile")


Build an emulsion/suspension
    C = raster(width=400,height=400)
    e = emulsion(xmin=10, ymin=10, xmax=390, ymax=390)
    e.insertion([60,50,40,30,20,15,15,10,8,20,12,8,6,4,11,13],beadtype=1)
    C.scatter(e,name="emulsion")
    C.plot()
    C.show()


Build a core-shell dispersion
    D = raster(width=400,height=400)
    cs = coreshell(xmin=10, ymin=10, xmax=390, ymax=390)
    cs.insertion([60,50,40,30,20,15,15,10,8,20,12,8,11,13],beadtype=(1,2),thickness = 4)
    D.scatter(cs,name="core-shell")
    D.plot()
    D.show()


More advanced features enable object copy, duplication along a path
Contruction of scattered particles
See: copyalongpath(), scatter(), emulsion(), coreshell()

Examples follow in the __main__ section

--------------------------------------------------------------------
BUILDING REQUIREMENTS:
    Since version 0.40, overlay(), torgb() and live previews
    use Pizza3.pizza.private.PIL library

    The customized version of PIL needs to be compiled for your system
    By assuming that anaconda is used:

        condainit

        cd Pizza3/pizza/private/PIL
        python3 setup.py install

        unzip -l dist/UNKNOWN-9.1.0-py3.9-linux-x86_64.egg
        unzip -j "dist/UNKNOWN-9.1.0-py3.9-linux-x86_64.egg" "PIL/_imaging.cpython-39-x86_64-linux-gnu.so" .

        rm -rf dist/
        rm -rf build/
        rm -rf ../UNKNOWN.egg-info

--------------------------------------------------------------------
Expand source code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""

`raster.py` provides methods to generate and manipulate raster-based geometries for LAMMPS input files. It includes tools for creating, plotting, and exporting geometric objects, with advanced features for particle systems such as emulsions and core-shell dispersions. This module is part of the **Pizza3** toolkit.

---

## Features

### 1. **Raster Area Creation**
Create a raster object with customizable dimensions and properties:
```python
R = raster(width=200, height=200, dpi=300)
```

### 2. **Geometric Object Insertion**
Define and manipulate objects such as rectangles, circles, triangles, diamonds, and polygons:
```python
R.rectangle(10, 20, 15, 30, name='rect1', beadtype=1)
R.circle(50, 50, 10, name='circle1', beadtype=2, angle=45)
R.triangle(30, 40, 10, name='triangle1', beadtype=3, angle=30)
```

### 3. **Collections and Paths**
Group objects into collections or copy them along specified paths:
```python
R.collection(object1, object2, name='my_collection', beadtype=1)
R.copyalongpath(object1, name='path_collection', path=linear, n=5)
```

### 4. **Scatter and Emulsions**
Generate random distributions of particles:
- **Emulsions**: Place circular particles within defined boundaries.
- **Core-Shell Particles**: Insert particles with inner and outer radii.
```python
e = emulsion(xmin=10, ymin=10, xmax=100, ymax=100)
e.insertion([10, 20, 30], beadtype=1)
```

### 5. **Visualization**
Preview objects and raster layouts:
```python
R.plot()
R.show(extra="label", contour=True)
```

### 6. **Exporting to LAMMPS**
Generate a `pizza.data3.data` object for exporting to LAMMPS:
```python
data_obj = R.data(scale=(1, 1), center=(0, 0))
data_obj.write("output_file.lmp")
```

---

## Advanced Features
- **Overlay Images**: Import and convert images to raster objects with beads:
  ```python
  R.overlay(50, 50, filename="image.jpg", ncolors=4, beadtype=2)
  ```
- **Hexagonal Packing**: Generate hex-packed data for particle arrangements.
- **Labeling and Masking**: Add labels to objects or define masked regions.

---

## Usage Examples

### Basic Example
```python
R = raster(width=100, height=100)
R.rectangle(10, 20, 15, 30, name='rect1', beadtype=1)
R.circle(50, 50, 10, name='circle1', beadtype=2)
R.plot()
R.show(extra="label")
```

### Emulsion Generation
```python
E = raster(width=400, height=400)
e = emulsion(xmin=10, ymin=10, xmax=390, ymax=390)
e.insertion([60, 50, 40, 30], beadtype=1)
E.scatter(e, name="emulsion")
E.plot()
E.show()
```

### Core-Shell Dispersion
```python
C = raster(width=400, height=400)
cs = coreshell(xmin=10, ymin=10, xmax=390, ymax=390)
cs.insertion([60, 50, 40], beadtype=(1, 2), thickness=4)
C.scatter(cs, name="core-shell")
C.plot()
C.show()
```

---

## Requirements
Since version 0.40, image overlays and live previews depend on the `Pizza3.pizza.private.PIL` library. For compatibility, the customized version of PIL must be compiled:
```bash
cd Pizza3/pizza/private/PIL
python3 setup.py install
```

---

## Authors and Credits
- **Author**: Olivier Vitrac
- **Email**: olivier.vitrac@agroparistech.fr
- **License**: GPLv3
- **Credits**: [Olivier Vitrac, Pizza3 Development Team]

---

## Old help

    RASTER method to generate LAMMPS input files (in 2D for this version)

    Generate a raster area
        R = raster()
        R = raster(width=200, height=200, dpi=300)

    Set objects (rectangle, circle, triangle, diamond...)
        R.rectangle(1,24,2,20,name='rect1')
        R.rectangle(60,80,50,81,name='rect2',beadtype=2,angle=40)
        R.rectangle(50,50,10,10,mode="center",angle=45,beadtype=1)
        R.circle(45,20,5,name='C1',beadtype=3)
        R.circle(35,10,5,name='C2',beadtype=3)

        R.circle(15,30,10,name='p1',beadtype=4,shaperatio=0.2,angle=-30)
        R.circle(12,40,8,name='p2',beadtype=4,shaperatio=0.2,angle=20)
        R.circle(12,80,22,name='p3',beadtype=4,shaperatio=1.3,angle=20)

        R.triangle(85,20,10,name='T1',beadtype=5,angle=20)
        R.diamond(85,35,5,name='D1',beadtype=5,angle=20)
        R.pentagon(50,35,5,name='P1',beadtype=5,angle=90)
        R.hexagon(47,85,12,name='H1',beadtype=5,angle=90)

    List simple objects
        R.list()
        R.get("p1")
        R.p1
        R.C1

    List objects in a collection
        R.C1.get("p1")
        R.C1.p1 shows the object p1 in the collection C1

    Build objects and show them
        R.plot()
        R.show()

    Show and manage labels
        R.show(extra="label",contour=True)
        R.label("rect003")
        R.unlabel('rect1')

    Manage objects, update and show

    Get the image and convert the image to text
        I = R.numeric()
        T = R.string()
        R.print()

    Create a pizza.dump3.dump object
        X = R.data()
        X=R.data(scale=(1,1),center=(0,0))
        X.write("/tmp/myfile")


    Build an emulsion/suspension
        C = raster(width=400,height=400)
        e = emulsion(xmin=10, ymin=10, xmax=390, ymax=390)
        e.insertion([60,50,40,30,20,15,15,10,8,20,12,8,6,4,11,13],beadtype=1)
        C.scatter(e,name="emulsion")
        C.plot()
        C.show()


    Build a core-shell dispersion
        D = raster(width=400,height=400)
        cs = coreshell(xmin=10, ymin=10, xmax=390, ymax=390)
        cs.insertion([60,50,40,30,20,15,15,10,8,20,12,8,11,13],beadtype=(1,2),thickness = 4)
        D.scatter(cs,name="core-shell")
        D.plot()
        D.show()


    More advanced features enable object copy, duplication along a path
    Contruction of scattered particles
    See: copyalongpath(), scatter(), emulsion(), coreshell()

    Examples follow in the __main__ section

    --------------------------------------------------------------------
    BUILDING REQUIREMENTS:
        Since version 0.40, overlay(), torgb() and live previews
        use Pizza3.pizza.private.PIL library

        The customized version of PIL needs to be compiled for your system
        By assuming that anaconda is used:

            condainit

            cd Pizza3/pizza/private/PIL
            python3 setup.py install

            unzip -l dist/UNKNOWN-9.1.0-py3.9-linux-x86_64.egg
            unzip -j "dist/UNKNOWN-9.1.0-py3.9-linux-x86_64.egg" "PIL/_imaging.cpython-39-x86_64-linux-gnu.so" .

            rm -rf dist/
            rm -rf build/
            rm -rf ../UNKNOWN.egg-info

    --------------------------------------------------------------------

"""


__project__ = "Pizza3"
__author__ = "Olivier Vitrac"
__copyright__ = "Copyright 2022"
__credits__ = ["Olivier Vitrac"]
__license__ = "GPLv3"
__maintainer__ = "Olivier Vitrac"
__email__ = "olivier.vitrac@agroparistech.fr"
__version__ = "0.99991"


# INRAE\Olivier Vitrac - rev. 2024-12-08
# contact: olivier.vitrac@agroparistech.fr, han.chen@inrae.fr

# History
# 2022-02-05 first alpha version
# 2022-02-06 RC for 2D
# 2022-02-08 add count(), update the display method
# 2022-02-10 add figure(), newfigure(), count()
# 2022-02-11 improve display, add data()
# 2022-02-12 major release, fully compatible with pizza.data3.data
# 2022-02-13 the example (<F5>) has been modified R.plot() should precedes R.list()
# 2022-02-28 update write files for SMD, add scale and center to R.data()
# 2022-03-02 fix data(): xlo and ylow (beads should not overlap the boundary), scale radii, volumes
# 2022-03-20 major update, add collection, duplication, translation, scatter(), emulsion()
# 2022-03-22 update raster to insert customized beadtypes
# 2022-03-23 add coreshell()
# 2022-03-23 fix nattempt, add arc
# 2022-04-01 add maxtype to  raster.data(), e.g. raster.data(maxtype=4)
# 2022-04-08 add beadtype2(alternative beadtype, ratio) to salt objects
# 2022-04-13 descale volume in data() for stability reason
# 2022-04-23 very first overlay implementation (alpha version) -- version 0.40
# 2022-04-24 full implementation of overlay (not fully tested yet, intended to behave has a regular object)
# 2022-04-25 full integration of PIL
# 2022-04-26 add torgb(), thumbnails, add angle, scale=(scalex,scaley) to overlay()
# 2022-04-26 add building instructions, version 0.421
# 2022-04-27 add scale to the representation of overlay objects (0.422)
# 2022-04-28 fix len(raster object) - typo error (0.4221)
# 2022-05-03 add hexpacking to data(), enables you to reproduces an hexgonal packaging
# 2023-01-03 workaround to have raster working on Windows without restrictions
# 2024-12-08 improved help

# %% Imports and private library
import os
from platform import system as sys
from copy import copy as duplicate
from copy import deepcopy as deepduplicate
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.path as path
import matplotlib.patches as patches
import matplotlib.cm as cmap
from IPython.display import display
from pizza.data3 import data as data3
from pizza.private.mstruct import struct

if sys()=="Windows":
    try:
        from PIL import Image, ImagePalette
        PILavailable = True
    except ImportError:
        print("WARNING: no image capabilities in Windows")
        PILavailable = False
else:
    from pizza.private.PIL import Image, ImagePalette
    PILavailable = True

__all__ = ['Circle', 'Collection', 'Diamond', 'Hexagon', 'Pentagon', 'Rectangle', 'Triangle', 'arc', 'collection', 'coregeometry', 'coreshell', 'emulsion', 'genericpolygon', 'imagesc', 'ind2rgb', 'linear', 'overlay', 'raster', 'scatter']


def _rotate(x0,y0,xc,yc,angle):
    angle = np.pi * angle / 180.0
    x1 = (x0 - xc)*np.cos(angle) - (y0 - yc)*np.sin(angle) + xc
    y1 = (x0 - xc)*np.sin(angle) + (y0 - yc)*np.cos(angle) + yc
    return x1, y1

def _extents(f):
    halftick = ( f[1] - f[0] ) / 2
    return [f[0] - halftick, f[-1] + halftick]

# wrapper of imagesc (note that the origin is bottom left)
# usage: data = np.random.randn(5,10)
#        imagesc(data)
def imagesc(im,x=None,y=None):
    """  imagesc à la Matlab
            imagesc(np2array) """
    if x==None: x=np.arange(1,np.shape(im)[1]+1)
    if y==None: y=np.arange(1,np.shape(im)[0]+1)
    plt.imshow(im, extent=_extents(x) + _extents(y),
               aspect="auto", origin="lower", interpolation="none")

# convert indexed image to RGB (using PIL)
# rgbim = ind2rgb(im,ncolors=number of colors)
def ind2rgb(im,ncolors=64):
    """ Convert indexed image (NumPy array) to RGB
            rgb = ind2rgb(np2array,ncolors=nc)
            use rgb.save("/path/filename.png") for saving
    """
    raw = Image.fromarray(np.flipud(im),"P")
    col0 = np.array(np.round(255*cmap.get_cmap("viridis",ncolors).colors[:,:3]),dtype="uint8")
    col = bytearray(np.resize(col0,(256,3)).flatten())
    pal = ImagePalette.ImagePalette(mode="RGB",palette=col)
    #Image.convert(mode=None, matrix=None, dither=None, palette=Palette.WEB, colors=256)
    raw.putpalette(pal)
    return raw

# helper for parametric functions
def linear(xmin=10,ymin=10,xmax=80,ymax=80,n=5,USER=struct()):
    """  Equispaced points along a trajectory
            X,Y = linear(xmin=value,ymin=value,xmax=value,ymax=value,n=int)
    """
    return np.linspace(xmin,xmax,n), np.linspace(ymin,ymax,n)

def arc(xmin=10,ymin=50,xmax=80,ymax=50,n=5,USER=struct(radius=20,direction=1)):
    """  Point distributed along an arc
            X,Y = arc(xmin=value,ymin=value,xmax=value,ymax=value,n=int,
                      USER=struct(radius=value,direction=1))
            Use direction to choose the upward +1 or downward -1 circle
        see: https://rosettacode.org/wiki/Circles_of_given_radius_through_two_points
    """
    R = 0 if "radius" not in USER else USER.radius
    direction = +1 if "direction" not in USER else USER.direction
    dx,dy = xmax-xmin, ymax-ymin
    q = np.sqrt(dx**2+dy**2) # distance
    R = max(R,q/2) # radius constraint
    d = np.sqrt(R**2-(q/2)**2) # distance along the mirror line
    xc = (xmin+xmax)/2 - direction * d*dy/q
    yc = (ymin+ymax)/2 + direction * d*dx/q
    thmin,thmax = np.arctan((ymin-yc)/(xmin-xc)), np.arctan((ymax-yc)/(xmax-xc))
    if d==0: thmax = thmin + np.pi
    th = np.linspace(thmin,thmax,n)
    return xc+np.cos(th)*R,yc+np.sin(th)*R

# %% raster class
class raster:
    """ raster class for LAMMPS SMD

    Constructor

        R = raster(width=100,height=100...)

        Extra properties
            dpi, fontsize

        additional properties for R.data()
            scale, center : full scaling
            mass, volume, radius, contactradius, velocities, forces: bead scaling
            filename

        List of available properties = default values

                   name = "default raster"
                  width = 100
                 height = 100
                    dpi = 200
               fontsize = 10
                   mass = 1
                 volume = 1
                 radius = 1.5
          contactradius = 0.5
             velocities = [0, 0, 0]
                 forces = [0, 0, 0]
                preview = True
           previewthumb = (128,128)
               filename = ["%dx%d raster (%s)" % (self.width,self.height,self.name)]

    Graphical objects

        R.rectangle(xleft,xright,ybottom,ytop [, beadtype=1,mode="lower", angle=0, ismask=False])
        R.rectangle(xcenter,ycenter,width,height [, beadtype=1,mode="center", angle=0, ismask=False])
        R.circle(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False], resolution=20, shiftangle=0)
        R.triangle(...)
        R.diamond(...)
        R.pentagon(...)
        R.hexagon(...)

        R.overlay(xleft,xright,filename=="valid/image.ext",color=2,beadtype=1)

        note: use fake=True to generate an object without inserting it

        R.collection(...) generates collection of existing or fake objects
        R.object.copy(...) enables to copy an object

    Display methods (precedence affects the result)
        R.plot()
        R.show(), R.show(extra="label",contour=True,what="beadtype" or "objindex")
        R.show(extra="labels")
        R.list()
        R.get("object")
        R.print()
        R.label("object")
        R.unlabel("object")
        R.figure()
        R.newfigure(dpi=300)

        R.numeric()
        R.string(), R.string(what="beadtype" or "objindex"))
        R.names()
        R.print()

    Clear and delete
        R.clear()
        R.clear("all")
        R.delete("object")

    Copy objects
        R.copyalongpath(....)
        R.scatter()

    Generate an input data object
        X = R.data() or X=R.data(scale=(1,1),center=(0,0))
        X.write("/tmp/myfile")

    """

    # CONSTRUCTOR ----------------------------
    def __init__(self,
                 # raster properties
                 name="default raster",
                 width=100,
                 height=100,
                 # printing and display
                 dpi=200,
                 fontsize=10,
                 # for data conversion
                 mass=1,
                 volume=1,
                 radius=1.5,
                 contactradius=0.5,
                 velocities=[0,0,0],
                 forces=[0,0,0],
                 preview=True,
                 previewthumb = (128,128),
                 filename=""
                 ):

        """ initialize raster """
        self.name = name
        self.width = width
        self.height = height
        self.xcenter= width/2
        self.ycenter = height/2
        self.objects = {}
        self.nobjects = 0    # total number of objects (alive)
        self.nbeads = 0
        self.counter = { "triangle":0,
                          "diamond":0,
                        "rectangle":0,
                         "pentagon":0,
                          "hexagon":0,
                           "circle":0,
                          "overlay":0,
                       "collection":0,
                              "all":0
                    }
        self.fontsize = 10   # font size for labels
        self.imbead = np.zeros((height,width),dtype=np.int8)
        self.imobj = np.zeros((height,width),dtype=np.int8)
        self.hfig = [] # figure handle
        self.dpi = dpi
        # generic SMD properties (to be rescaled)
        self.volume = volume
        self.mass = mass
        self.radius = radius
        self.contactradius = contactradius
        self.velocities = velocities
        self.forces =forces

        self.preview = preview
        self.previewthumb = previewthumb

        if filename == "":
            self.filename = ["%dx%d raster (%s)" % (self.width,self.height,self.name)]
        else:
            self.filename = filename


    # DATA ----------------------------
    def data(self,scale=(1,1),center=(0,0),maxtype=None,hexpacking=None):
        """
            return a pizza.data object
                data()
                data(scale=(scalex,scaley),
                     center=(centerx,centery),
                     maxtype=number,
                     hexpacking=(0.5,0))
        """
        if not isinstance(scale,tuple) or len(scale)!=2:
            raise ValueError("scale must be tuple (scalex,scaley)")
        if not isinstance(center,tuple) or len(scale)!=2:
            raise ValueError("center must be tuple (centerx,centery)")
        scalez = np.sqrt(scale[0]*scale[1])
        scalevol = scale[0]*scale[1] #*scalez
        maxtypeheader = self.count()[-1][0] if maxtype is None else maxtype
        n = self.length()
        i,j = self.imbead.nonzero() # x=j+0.5 y=i+0.5
        x = (j+0.5-center[0])*scale[0]
        y = (i+0.5-center[1])*scale[1]
        if hexpacking is not None:
            if isinstance(hexpacking,tuple) and len(hexpacking)==2:
                for k in range(len(i)):
                    if i[k] % 2:
                        x[k] = (j[k]+0.5+hexpacking[1]-center[0])*scale[0]
                    else:
                        x[k] = (j[k]+0.5+hexpacking[0]-center[0])*scale[0]
            else:
                raise ValueError("hexpacking should be a tuple (shiftodd,shifteven)")
        X = data3()  # empty pizza.data3.data object
        X.title = self.name + "(raster)"
        X.headers = {'atoms': n,
                      'atom types': maxtypeheader,
                      'xlo xhi': ((0.0-center[0])*scale[0], (self.width-0.0-center[0])*scale[0]),
                      'ylo yhi': ((0.0-center[1])*scale[1], (self.height-0.0-center[1])*scale[1]),
                      'zlo zhi': (0, scalez)}
        # [ATOMS] section
        X.append('Atoms',list(range(1,n+1)),True,"id")       # id
        X.append('Atoms',self.imbead[i,j],True,"type")       # Type
        X.append('Atoms',1,True,"mol")                       # mol
        X.append('Atoms',self.volume*scalevol,False,"c_vol") # c_vol
        X.append('Atoms',self.mass*scalevol,False,"mass")    # mass
        X.append('Atoms',self.radius*scalez,False,"radius")         # radius
        X.append('Atoms',self.contactradius*scalez,False,"c_contact_radius") # c_contact_radius
        X.append('Atoms',x,False,"x")        # x
        X.append('Atoms',y,False,"y")        # y
        X.append('Atoms',0,False,"z")        # z
        X.append('Atoms',x,False,"x0")       # x0
        X.append('Atoms',y,False,"y0")       # y0
        X.append('Atoms',0,False,"z0")       # z0
        # [VELOCITIES] section
        X.append('Velocities',list(range(1,n+1)),True,"id") # id
        X.append('Velocities',self.velocities[0],False,"vx") # vx
        X.append('Velocities',self.velocities[1],False,"vy") # vy
        X.append('Velocities',self.velocities[2],False,"vz") # vz
        # pseudo-filename
        X.flist = self.filename
        return X

    # LENGTH ----------------------------
    def length(self,t=None,what="beadtype"):
        """ returns the total number of beads length(type,"beadtype") """
        if what == "beadtype":
            num = self.imbead
        elif what == "objindex":
            num = self.imobj
        else:
            raise ValueError('"beadtype" and "objindex" are the only acceptable values')
        if t==None:
            return np.count_nonzero(num>0)
        else:
            return np.count_nonzero(num==t)

    # NUMERIC ----------------------------
    def numeric(self):
        """ retrieve the image as a numpy.array """
        return self.imbead, self.imobj

    # STRING ----------------------------
    def string(self,what="beadtype"):
        """ convert the image as ASCII strings """
        if what == "beadtype":
            num = np.flipud(duplicate(self.imbead))
        elif what == "objindex":
            num = np.flipud(duplicate(self.imobj))
        else:
            raise ValueError('"beadtype" and "objindex" are the only acceptable values')
        num[num>0] = num[num>0] + 65
        num[num==0] = 32
        num = list(num)
        return ["".join(map(chr,x)) for x in num]

    # GET -----------------------------
    def get(self,name):
        """ returns the object """
        if name in self.objects:
            return self.objects[name]
        else:
            raise ValueError('the object "%s" does not exist, use list()' % name)

    # GETATTR --------------------------
    def __getattr__(self,key):
        """ get attribute override """
        return self.get(key)

    # CLEAR ----------------------------
    def clear(self,what="nothing"):
        """ clear the plotting area, use clear("all")) to remove all objects """
        self.imbead = np.zeros((self.height,self.width),dtype=np.int8)
        self.imobj = np.zeros((self.height,self.width),dtype=np.int8)
        for o in self.names():
            if what=="all":
                self.delete(o)
            else:
                self.objects[o].isplotted = False
                self.objects[o].islabelled = False
                if not self.objects[o].ismask:
                    self.nbeads -= self.objects[o].nbeads
                self.objects[o].nbeads = 0  # number of beads (plotted)
        self.figure()
        plt.cla()
        self.show()

    # DISP method ----------------------------
    def __repr__(self):
        """ display method """
        ctyp = self.count() # count objects (not beads)
        print("-"*40)
        print('RASTER area "%s" with %d objects' % (self.name,self.nobjects))
        print("-"*40)
        print("<- grid size ->")
        print("\twidth: %d" % self.width)
        print("\theight: %d" % self.height)
        print("<- bead types ->")
        nbt = 0
        if len(ctyp):
            for i,c in enumerate(ctyp):
                nb = self.length(c[0])
                nbt += nb
                print("\t type=%d (%d objects, %d beads)" % (c[0],c[1],nb))
        else:
            print("\tno bead assigned")
        print("-"*40)
        if self.preview and len(self)>0 and self.max>0:
            if PILavailable:
                display(self.torgb("beadtype",self.previewthumb))
                display(self.torgb("objindex",self.previewthumb))
            else:
                print("no PIL image")
        return "RASTER AREA %d x %d with %d objects (%d types, %d beads)." % \
        (self.width,self.height,self.nobjects,len(ctyp),nbt)

    # TORGB method ----------------------------
    def torgb(self,what="beadtype",thumb=None):
        """ converts bead raster to image
                rgb = raster.torgb(what="beadtype")
                thumbnail = raster.torgb(what="beadtype",(128,128))
                use: rgb.save("/path/filename.png") for saving

            what = "beadtype" or "objindex"
        """
        if what=="beadtype":
            rgb = ind2rgb(self.imbead,ncolors=self.max+1)
        elif what == "objindex":
            rgb = ind2rgb(self.imobj,ncolors=len(self)+1)
        else:
            raise ValueError('"beadtype" and "objindex" are the only acceptable values')
        if thumb is not None: rgb.thumbnail(thumb)
        return rgb

    # COUNT method ----------------------------
    def count(self):
        """ count objects by type """
        typlist = []
        for  o in self.names():
            if isinstance(self.objects[o].beadtype,list):
                typlist += self.objects[o].beadtype
            else:
                typlist.append(self.objects[o].beadtype)
        utypes = list(set(typlist))
        c = []
        for t in utypes:
            c.append((t,typlist.count(t)))
        return c

    # max method ------------------------------
    @property
    def max(self):
        """ max bead type """
        typlist = []
        for  o in self.names():
            if isinstance(self.objects[o].beadtype,list):
                typlist += self.objects[o].beadtype
            else:
                typlist.append(self.objects[o].beadtype)
        return max(typlist)

    # len method ------------------------------
    def __len__(self):
        """ len method """
        return len(self.objects)

    # NAMES method ----------------------------
    def names(self):
        """ return the names of objects sorted as index """
        namesunsorted=namessorted=list(self.objects.keys())
        nobj = len(namesunsorted)
        for iobj in range(nobj):
            namessorted[self.objects[namesunsorted[iobj]].index-1] = namesunsorted[iobj]
        return namessorted

    # LIST method ----------------------------
    def list(self):
        """ list objects """
        fmt = "%%%ss:" % max(10,max([len(n) for n in self.names()])+2)
        print("RASTER with %d objects" % self.nobjects)
        for o in self.objects.keys():
            print(fmt % self.objects[o].name,"%-10s" % self.objects[o].kind,
                  "(beadtype=%d,object index=[%d,%d], n=%d)" % \
                      (self.objects[o].beadtype,
                       self.objects[o].index,
                       self.objects[o].subindex,
                       self.objects[o].nbeads))

    # EXIST method ----------------------------
    def exist(self,name):
        """ exist object """
        return name in self.objects

    # DELETE method ----------------------------
    def delete(self,name):
        """ delete object """
        if name in self.objects:
            if not self.objects[name].ismask:
                self.nbeads -= self.objects[name].nbeads
            del self.objects[name]
            self.nobjects -= 1
        else:
            raise ValueError("%d is not a valid name (use list()) to list valid objects" % name)
        self.clear()
        self.plot()
        self.show(extra="label")

    # VALID method
    def valid(self,x,y):
        """ validation of coordinates """
        return min(self.width,max(0,round(x))),min(self.height,max(0,round(y)))

    # frameobj method
    def frameobj(self,obj):
        """ frame coordinates by taking into account translation """
        if obj.hasclosefit:
            envelope = 0
        else:
            envelope = 1
        xmin, ymin = self.valid(obj.xmin-envelope, obj.ymin-envelope)
        xmax, ymax = self.valid(obj.xmax+envelope, obj.ymax+envelope)
        return xmin, ymin, xmax, ymax

    # RECTANGLE ----------------------------
    def rectangle(self,a,b,c,d,
                  mode="lowerleft",name=None,angle=0,
                  beadtype=None,ismask=False,fake=False,beadtype2=None):
        """
        rectangle object
            rectangle(xleft,xright,ybottom,ytop [, beadtype=1,mode="lower", angle=0, ismask=False])
            rectangle(xcenter,ycenter,width,height [, beadtype=1,mode="center", angle=0, ismask=False])

            use rectangle(...,beadtype2=(type,ratio)) to salt an object with beads
            from another type and with a given ratio
        """
        # object creation
        self.counter["all"] += 1
        self.counter["rectangle"] += 1
        R = Rectangle((self.counter["all"],self.counter["rectangle"]))
        if (name != None) and (name != ""):
            if self.exist(name):
                print('RASTER:: the object "%s" is overwritten',name)
                self.delete(name)
            R.name = name
        else:
            name = R.name
        if beadtype is not None: R.beadtype = int(np.floor(beadtype))
        if beadtype2 is not None:
            if not isinstance(beadtype2,tuple) or len(beadtype2)!=2:
                raise AttributeError("beadtype2 must be a tuple (beadtype,ratio)")
        R.beadtype2 = beadtype2
        if ismask: R.beadtype = 0
        R.ismask = R.beadtype==0
        # build vertices
        if mode == "lowerleft":
            R.xcenter0 = (a+b)/2
            R.ycenter0 = (c+d)/2
            R.vertices = [
                _rotate(a,c,R.xcenter0,R.ycenter0,angle),
                _rotate(b,c,R.xcenter0,R.ycenter0,angle),
                _rotate(b,d,R.xcenter0,R.ycenter0,angle),
                _rotate(a,d,R.xcenter0,R.ycenter0,angle),
                _rotate(a,c,R.xcenter0,R.ycenter0,angle)
                ] # anti-clockwise, closed (last point repeated)
        elif mode == "center":
            R.xcenter0 = a
            R.ycenter0 = b
            R.vertices = [
                _rotate(a-c/2,b-d/2,R.xcenter0,R.ycenter0,angle),
                _rotate(a+c/2,b-d/2,R.xcenter0,R.ycenter0,angle),
                _rotate(a+c/2,b+d/2,R.xcenter0,R.ycenter0,angle),
                _rotate(a-c/2,b+d/2,R.xcenter0,R.ycenter0,angle),
                _rotate(a-c/2,b-d/2,R.xcenter0,R.ycenter0,angle)
                ]
        else:
            raise ValueError('"%s" is not a recognized mode, use "lowerleft" (default) and "center" instead')
        # build path object and range
        R.codes =    [ path.Path.MOVETO,
                     path.Path.LINETO,
                     path.Path.LINETO,
                     path.Path.LINETO,
                     path.Path.CLOSEPOLY
                    ]
        R.nvertices = len(R.vertices)-1
        R.xmin0, R.ymin0, R.xmax0, R.ymax0 = R.corners()
        R.xmin0, R.ymin0 = self.valid(R.xmin0,R.ymin0)
        R.xmax0, R.ymax0 = self.valid(R.xmax0,R.ymax0)
        R.angle = angle
        # store the object (if not fake)
        if fake:
            self.counter["all"] -= 1
            self.counter["rectangle"] -= 1
            return R
        else:
            self.objects[name] = R
            self.nobjects += 1
            return None


    # CIRCLE ----------------------------
    def circle(self,xc,yc,radius,
                  name=None,shaperatio=1,angle=0,beadtype=None,ismask=False,
                  resolution=20,shiftangle=0,fake=False,beadtype2=None):
        """
        circle object (or any regular polygon)
            circle(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False], resolution=20, shiftangle=0)
            use circle(...,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio
        """
        # object creation
        self.counter["all"] += 1
        if resolution==3:
            typ = "triangle"
            self.counter["triangle"] += 1
            G = Triangle((self.counter["all"],self.counter["triangle"]))
        elif resolution==4:
            typ = "diamond"
            self.counter["diamond"] += 1
            G = Diamond((self.counter["all"],self.counter["diamond"]))
        elif resolution==5:
            typ = "pentagon"
            self.counter["pentagon"] += 1
            G = Pentagon((self.counter["all"],self.counter["pentagon"]))
        elif resolution==6:
            typ = "hexagon"
            self.counter["hexagon"] += 1
            G = Hexagon((self.counter["all"],self.counter["hexagon"]))
        else:
            typ = "circle"
            self.counter["circle"] += 1
            G = Circle((self.counter["all"],self.counter["circle"]),resolution=resolution)
        if (name != None) and (name != ""):
            if self.exist(name):
                print('RASTER:: the object "%s" is overwritten',name)
                self.delete(name)
            G.name = name
        else:
            name = G.name
        if beadtype is not None: G.beadtype = int(np.floor(beadtype))
        if beadtype2 is not None:
            if not isinstance(beadtype2,tuple) or len(beadtype2)!=2:
                raise AttributeError("beadtype2 must be a tuple (beadtype,ratio)")
        G.beadtype2 = beadtype2
        if ismask: G.beadtype = 0
        G.ismask = G.beadtype==0
        # build vertices
        th = np.linspace(0,2*np.pi,G.resolution+1) +shiftangle*np.pi/180
        xgen = xc + radius * np.cos(th)
        ygen = yc + radius * shaperatio * np.sin(th)
        G.xcenter0, G.ycenter0, G.radius = xc, yc, radius
        G.vertices, G.codes = [], []
        for i in range(G.resolution+1):
            G.vertices.append(_rotate(xgen[i],ygen[i],xc,yc,angle))
            if i==0:
                G.codes.append(path.Path.MOVETO)
            elif i==G.resolution:
                G.codes.append(path.Path.CLOSEPOLY)
            else:
                G.codes.append(path.Path.LINETO)
        G.nvertices = len(G.vertices)-1
        # build path object and range
        G.xmin0, G.ymin0, G.xmax0, G.ymax0 = G.corners()
        G.xmin0, G.ymin0 = self.valid(G.xmin0,G.ymin0)
        G.xmax0, G.ymax0 = self.valid(G.xmax0,G.ymax0)
        G.angle, G.shaperatio = angle, shaperatio
        # store the object
        if fake:
            self.counter["all"] -= 1
            self.counter[typ] -= 1
            return G
        else:
            self.objects[name] = G
            self.nobjects += 1
            return None

    # OVERLAY -------------------------------
    def overlay(self,x0,y0,
                name = None,
                filename = None,
                color = 1,
                colormax = None,
                ncolors = 4,
                beadtype = None,
                beadtype2 = None,
                ismask = False,
                fake = False,
                flipud = True,
                angle = 0,
                scale= (1,1)
                ):
        """
            overlay object: made from an image converted to nc colors
            the object is made from the level ranged between ic and jc (bounds included)
            note: if palette found, no conversion is applied

            O = overlay(x0,y0,filename="/this/is/my/image.png",ncolors=nc,color=ic,colormax=jc,beadtype=b)
            O = overlay(...angle=0,scale=(1,1)) to induce rotation and change of scale
            O = overlay(....ismask=False,fake=False)

            note use overlay(...flipud=False) to prevent image fliping (standard)

            Outputs:
                O.original original image (PIL)
                O.raw image converted to ncolors if needed

        """
        if filename is None or filename=="":
            raise ValueError("filename is required (valid image)")
        O = overlay(counter=(self.counter["all"]+1,self.counter["overlay"]+1),
                    filename = filename,
                    xmin = x0,
                    ymin = y0,
                    ncolors = ncolors,
                    flipud = flipud,
                    angle = angle,
                    scale = scale
                    )
        O.select(color=color, colormax=colormax)
        if (name is not None) and (name !=""):
            if self.exist(name):
                print('RASTER:: the object "%s" is overwritten',name)
                self.delete(name)
            O.name = name
        else:
            name = O.name
        if beadtype is not None: O.beadtype = int(np.floor(beadtype))
        if beadtype2 is not None:
            if not isinstance(beadtype2,tuple) or len(beadtype2)!=2:
                raise AttributeError("beadtype2 must be a tuple (beadtype,ratio)")
        O.beadtype2 = beadtype2
        if ismask: O.beadtype = 0
        O.ismask = O.beadtype==0
        self.counter["all"] += 1
        self.counter["overlay"] += 1
        if fake:
            self.counter["all"] -= 1
            self.counter["overlay"] -= 1
            return O
        else:
            self.objects[name] = O
            self.nobjects += 1
            return None


    # COLLECTION ----------------------------
    def collection(self,*obj,
                   name=None,
                   beadtype=None,
                   ismask=None,
                   translate = [0.0,0.0],
                   fake = False,
                   **kwobj):
        """
            collection of objects:
                collection(draftraster,name="mycollect" [,beadtype=1,ismask=True]
                collection(name="mycollect",newobjname1 = obj1, newobjname2 = obj2...)
        """
        self.counter["all"] += 1
        self.counter["collection"] += 1
        C = Collection((self.counter["all"],self.counter["collection"]))
        # name
        if name != None:
            if self.exist(name):
                print('RASTER:: the object "%s" is overwritten',name)
                self.delete(name)
            C.name = name
        else:
            name = C.name
        # build the collection
        C.collection = collection(*obj,**kwobj)
        xmin = ymin = +1e99
        xmax = ymax = -1e99
        # apply modifications (beadtype, ismask)
        for o in C.collection.keys():
            tmp = C.collection.getattr(o)
            tmp.translate[0] += translate[0]
            tmp.translate[1] += translate[1]
            xmin, xmax = min(xmin,tmp.xmin), max(xmax,tmp.xmax)
            ymin, ymax = min(ymin,tmp.ymin), max(ymax,tmp.ymax)
            if beadtype != None: tmp.beadtype = beadtype
            if ismask != None: tmp.ismask = ismask
            C.collection.setattr(o,tmp)
        C.xmin, C.xmax, C.ymin, C.ymax = xmin, xmax, ymin, ymax
        C.width, C.height = xmax-xmin, ymax-ymin
        if fake:
            return C
        else:
            self.objects[name] = C
            self.nobjects += 1
            return None

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # =========== pseudo methods connected to circle() ===========
    # TRIANGLE, DIAMOND, PENTAGON, HEXAGON, -----------------------
    def triangle(self,xc,yc,radius,name=None,
                 shaperatio=1,angle=0,beadtype=None,ismask=False,shiftangle=0,fake=False,beadtype2=None):
        """
        triangle object
            triangle(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False]
            use triangle(...,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio
        """
        self.circle(xc,yc,radius,name=name,shaperatio=shaperatio,resolution=3,
           angle=angle,beadtype=beadtype,ismask=ismask,shiftangle=0,fake=fake,beadtype2=beadtype2)

    def diamond(self,xc,yc,radius,name=None,
                 shaperatio=1,angle=0,beadtype=None,ismask=False,shiftangle=0,fake=False,beadtype2=None):
        """
        diamond object
            diamond(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False]
            use diamond(...,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio
        """
        self.circle(xc,yc,radius,name=name,shaperatio=shaperatio,resolution=4,
            angle=angle,beadtype=beadtype,ismask=ismask,shiftangle=0,fake=fake,beadtype2=beadtype2)

    def pentagon(self,xc,yc,radius,name=None,
                 shaperatio=1,angle=0,beadtype=None,ismask=False,shiftangle=0,fake=False,beadtype2=None):
        """
        pentagon object
            pentagon(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False]
            use pentagon(...,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio
        """
        self.circle(xc,yc,radius,name=name,shaperatio=shaperatio,resolution=5,
            angle=angle,beadtype=beadtype,ismask=ismask,shiftangle=0,fake=fake,beadtype2=beadtype2)

    def hexagon(self,xc,yc,radius,name=None,
                 shaperatio=1,angle=0,beadtype=None,ismask=False,shiftangle=0,fake=False,beadtype2=None):
        """
        hexagon object
            hexagon(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False]
            use hexagon(...,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio
        """
        self.circle(xc,yc,radius,name=name,shaperatio=shaperatio,resolution=6,
            angle=angle,beadtype=beadtype,ismask=ismask,shiftangle=0,fake=fake,beadtype2=beadtype2)

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    # label method ----------------------------
    def label(self,name,**fmt):
        """
            label:
                label(name [, contour=True,edgecolor="orange",facecolor="none",linewidth=2, ax=plt.gca()])
        """
        self.figure()
        if name in self.objects:
            if not self.objects[name].islabelled:
                if self.objects[name].alike == "mixed":
                    for o in self.objects[name].collection:
                        self.labelobj(o,**fmt)
                else:
                    self.labelobj(self.objects[name],**fmt)
                plt.show()
                self.objects[name].islabelled = True
        else:
            raise ValueError("%d is not a valid name (use list()) to list valid objects" % name)

    # label object method -----------------------------
    def labelobj(self,obj,contour=True,edgecolor="orange",facecolor="none",linewidth=2,ax=plt.gca()):
        """
            labelobj:
                labelobj(obj [, contour=True,edgecolor="orange",facecolor="none",linewidth=2, ax=plt.gca()])
        """
        if contour: contour = obj.hascontour # e.g. overlays do not have contour

        if contour:
            patch = patches.PathPatch(obj.polygon2plot,
                                      facecolor=facecolor,
                                      edgecolor=edgecolor,
                                      lw=linewidth)
            obj.hlabel["contour"] = ax.add_patch(patch)
        else:
            obj.hlabel["contour"] = None
        obj.hlabel["text"] = \
        plt.text(obj.xcenter,
                 obj.ycenter,
                 "%s\n(t=$%d$,$n_p$=%d)" % (obj.name, obj.beadtype,obj.nbeads),
                 horizontalalignment = "center",
                 verticalalignment = "center_baseline",
                 fontsize=self.fontsize
                 )


    def unlabel(self,name):
        """ unlabel """
        if name in self.objects:
            if  self.objects[name].islabelled:
                self.objects[name].hlabel["contour"].remove()
                self.objects[name].hlabel["text"].remove()
                self.objects[name].hlabel = {'contour':[], 'text':[]}
                self.objects[name].islabelled = False
        else:
            raise ValueError("%d is not a valid name (use list()) to list valid objects" % name)


    # PLOT method ----------------------------
    def plot(self):
        """ plot """
        for o in self.objects:
            if not self.objects[o].isplotted:
                if self.objects[o].alike == "mixed":
                    for o2 in self.objects[o].collection:
                        self.plotobj(o2)
                else:
                    self.plotobj(self.objects[o])
                # store it as plotted
                self.objects[o].isplotted = True
                if not self.objects[o].ismask:
                    self.nbeads += self.objects[o].nbeads


    # PLOTobj method  -----------------------
    def plotobj(self,obj):
        """ plotobj(obj) """
        if obj.alike == "circle":
            xmin, ymin, xmax, ymax = self.frameobj(obj)
            j,i = np.meshgrid(range(xmin,xmax), range(ymin,ymax))
            points = np.vstack((j.flatten(),i.flatten())).T
            npoints = points.shape[0]
            inside = obj.polygon.contains_points(points)
            if obj.beadtype2 is None:          # -- no salting --
                for k in range(npoints):
                    if inside[k] and \
                        points[k,0]>=0 and \
                        points[k,0]<self.width and \
                        points[k,1]>=0 and \
                        points[k,1]<self.height:
                            self.imbead[points[k,1],points[k,0]] = obj.beadtype
                            self.imobj[points[k,1],points[k,0]] = obj.index
                            obj.nbeads += 1
            else:
                for k in range(npoints):       # -- salting --
                    if inside[k] and \
                        points[k,0]>=0 and \
                        points[k,0]<self.width and \
                        points[k,1]>=0 and \
                        points[k,1]<self.height:
                            if np.random.rand()<obj.beadtype2[1]:
                                self.imbead[points[k,1],points[k,0]] = obj.beadtype2[0]
                            else:
                                self.imbead[points[k,1],points[k,0]] = obj.beadtype
                            self.imobj[points[k,1],points[k,0]] = obj.index
                            obj.nbeads += 1

        elif obj.alike == "overlay":
            xmin, ymin, xmax, ymax = self.frameobj(obj)
            j,i = np.meshgrid(range(xmin,xmax), range(ymin,ymax))
            points = np.vstack((j.flatten(),i.flatten())).T
            npoints = points.shape[0]
            inside = obj.select()
            if obj.beadtype2 is None:          # -- no salting --
                for k in range(npoints):
                    if inside[ points[k,1]-ymin, points[k,0]-xmin ] and \
                        points[k,0]>=0 and \
                        points[k,0]<self.width and \
                        points[k,1]>=0 and \
                        points[k,1]<self.height:
                            self.imbead[points[k,1],points[k,0]] = obj.beadtype
                            self.imobj[points[k,1],points[k,0]] = obj.index
                            obj.nbeads += 1
            else:
                for k in range(npoints):       # -- salting --
                    if inside[ points[k,0]-ymin, points[k,0]-xmin ] and \
                        points[k,0]>=0 and \
                        points[k,0]<self.width and \
                        points[k,1]>=0 and \
                        points[k,1]<self.height:
                            if np.random.rand()<obj.beadtype2[1]:
                                self.imbead[points[k,1],points[k,0]] = obj.beadtype2[0]
                            else:
                                self.imbead[points[k,1],points[k,0]] = obj.beadtype
                            self.imobj[points[k,1],points[k,0]] = obj.index
                            obj.nbeads += 1

        else:
            raise ValueError("This object type is notimplemented")

    # SHOW method ----------------------------
    def show(self,extra="none",contour=True,what="beadtype"):
        """ show method: show(extra="label",contour=True,what="beadtype") """
        self.figure()
        if what=="beadtype":
            imagesc(self.imbead)
        elif what == "objindex":
            imagesc(self.imobj)
        else:
            raise ValueError('"beadtype" and "objindex" are the only acceptable values')
        if extra == "label":
            ax = plt.gca()
            for o in self.names():
                if not self.objects[o].ismask:
                    self.label(o,ax=ax,contour=contour)
            ax.set_title("raster area: %s \n (n=%d, $n_p$=%d)" %\
                      (self.name,self.length(),self.nbeads) )
            plt.show()

    # SHOW method ----------------------------
    def print(self,what="beadtype"):
        """ print method """
        txt = self.string(what=what)
        for i in range(len(txt)):
            print(txt[i],end="\n")


    # FIGURE method ----------------------------
    def figure(self):
        """ set the current figure """
        if self.hfig==[] or not plt.fignum_exists(self.hfig.number):
            self.newfigure()
        plt.figure(self.hfig.number)

    # NEWFIGURE method ----------------------------
    def newfigure(self):
        """ create a new figure (dpi=200) """
        self.hfig = plt.figure(dpi=self.dpi)

    # COPY OBJECT ALONG a contour -----------------
    def copyalongpath(self,obj,
                      name="path",
                  beadtype=None,
                      path=linear,
                      xmin=10,
                      ymin=10,
                      xmax=70,
                      ymax=90,
                         n=7,
                      USER=struct()):
        """

        The method enable to copy an existing object (from the current raster,
        from another raster or a fake object) amp,g

        Parameters
        ----------
        obj : real or fake object
            the object to be copied.
        name : string, optional
            the name of the object collection. The default is "path".
        beadtype : integet, optional
            type of bead (can override existing value). The default is None.
        path : function, optional
            parametric function returning x,y. The default is linear.
            x is between xmin and xmax, and y between ymin, ymax
        xmin : int64 or float, optional
            left x corner position. The default is 10.
        ymin : int64 or float, optional
            bottom y corner position. The default is 10.
        xmax : int64 or float, optional
            right x corner position. The default is 70.
        ymax : int64 or float, optional
            top y corner position. The default is 90.
        n : integet, optional
            number of copies. The default is 7.
     USER : structure to pass specific parameters

        Returns
        -------
        None.

        """
        if not isinstance(USER,struct):
            raise TypeError("USER should be a structure")
        x,y = path(xmin=xmin,ymin=ymin,xmax=xmax,ymax=ymax,n=n,USER=USER)
        btyp = obj.beadtype if beadtype == None else beadtype
        collect = {}
        for i in range(n):
            nameobj = "%s_%s_%02d" % (name,obj.name,i)
            x[i], y[i] = self.valid(x[i], y[i])
            translate = [ x[i]-obj.xcenter, y[i]-obj.ycenter ]
            collect[nameobj] = obj.copy(translate=translate,
                                        name=nameobj,
                                        beadtype=btyp)
        self.collection(**collect,name=name)


    # SCATTER -------------------------------
    def scatter(self,
                 E,
                 name="emulsion",
                 beadtype=None,
                 ismask = False
                 ):
        """


        Parameters
        ----------
        E : scatter or emulsion object
            codes for x,y and r.
        name : string, optional
            name of the collection. The default is "emulsion".
        beadtype : integer, optional
            for all objects. The default is 1.
        ismask : logical, optional
            Set it to true to force a mask. The default is False.

        Raises
        ------
        TypeError
            Return an error of the object is not a scatter type.

        Returns
        -------
        None.

        """
        if isinstance(E,scatter):
            collect = {}
            for i in range(E.n):
                b = E.beadtype[i] if beadtype==None else beadtype
                nameobj = "glob%02d" % i
                collect[nameobj] = self.circle(E.x[i],E.y[i],E.r[i],
                            name=nameobj,beadtype=b,ismask=ismask,fake=True)
            self.collection(**collect,name=name)
        else:
            raise TypeError("the first argument must be an emulsion object")


# %% PRIVATE SUB-CLASSES
# Use the equivalent methods of raster() to call these constructors
#   raster.rectangle, raster.circle, raster.triangle... raster.collection
#
# Two counters are used for automatic naming
#   counter[0] is the overall index (total number of objects created)
#   counter[1] is the index of objects of this type (total number of objects created for this class)
#
#   Overview:
#       genericpolygon --> Rectancle, Circle
#       Circle --> Triangle, Diamond, Pentagon, Hexagon
#       Collection --> graphical object for collections (many properties are dynamic)
#       struct --> collection is the low-level class container of Collection

class coregeometry:
    """ core geometry object"""

    @property
    def xcenter(self):
        """ xcenter with translate """
        return self.xcenter0 + self.translate[0]
    @property
    def ycenter(self):
        """ xcenter with translate """
        return self.ycenter0 + self.translate[1]
    @property
    def xmin(self):
        """ xleft position """
        return self.xmin0 + self.translate[0]
    @property
    def xmax(self):
        """ xright position """
        return self.xmax0 + self.translate[0]
    @property
    def ymin(self):
        """ yleft position """
        return self.ymin0 + self.translate[1]
    @property
    def ymax(self):
        """ yright position """
        return self.ymax0 + self.translate[1]
    @property
    def width(self):
        """ oibject width range """
        return self.xmax - self.xmin
    @property
    def height(self):
        """ oibject height range """
        return self.ymax - self.ymin

    def copy(self,translate=None,beadtype=None,name=""):
        """ returns a copy of the graphical object """
        if self.alike != "mixed":
            dup = deepduplicate(self)
            if translate != None: # applies translation
                dup.translate[0] += translate[0]
                dup.translate[1] += translate[1]
            if beadtype != None: # update beadtype
                dup.beadtype = beadtype
            if name != "": # update name
                dup.name = name
            return dup
        else:
            raise ValueError("collections cannot be copied, regenerate the collection instead")


class overlay(coregeometry):
    """ generic overlay class """

    hascontour = False
    hasclosefit = True

    def __init__(self,
                 counter = (0,0),
                 filename="./sandbox/image.jpg",
                 xmin = 0,
                 ymin = 0,
                 ncolors = 4,
                 flipud = True,
                 angle = 0,
                 scale = (1,1)
                 ):
        """ generate an overlay from file
                overlay(counter=(c1,c2),filename="this/is/myimage.jpg",xmin=x0,ymin=y0,colors=4)
                additional options
                    overlay(...,flipud=True,angle=0,scale=(1,1))
        """
        self.name = "over%03d" % counter[1]
        self.kind = "overlay"       # kind of object
        self.alike = "overlay"      # similar object for plotting
        self.beadtype = 1           # bead type
        self.beadtype2 = None       # bead type 2 (alternative beadtype, ratio)
        self.nbeads = 0             # number of beads
        self.ismask = False         # True if beadtype == 0
        self.isplotted = False      # True if plotted
        self.islabelled = False     # True if labelled
        self.resolution = None      # resolution is undefined
        self.hlabel = {'contour':[], 'text':[]}
        self.index = counter[0]
        self.subindex = counter[1]
        self.translate = [0.0,0.0]  # modification used when an object is duplicated
        if scale is None: scale = 1
        if not isinstance(scale,(tuple,list)): scale = (scale,scale)
        self.scale = scale
        if angle is None: angle = 0
        self.angle = angle
        self.flipud = flipud
        if not os.path.isfile(filename):
            raise IOError(f'the file "{filename}" does not exist')
        self.filename = filename
        self.ncolors = ncolors
        self.color = None
        self.colormax = None
        self.original,self.raw,self.im,self.map = self.load()
        self.xmin0 = xmin
        self.ymin0 = ymin
        self.xmax0 = xmin + self.im.shape[1]
        self.ymax0 = ymin + self.im.shape[0]
        self.xcenter0 = (self.xmin+self.xmax)/2
        self.ycenter0 = (self.ymin+self.ymax)/2

    def select(self,color=None,colormax=None,scale=None,angle=None):
        """ select the color index:
                select(color = c) peeks pixels = c
                select(color = c, colormax = cmax) peeks pixels>=c and pixels<=cmax
        """
        if color is None:
            color = self.color
        else:
            self.color = color
        if (colormax is None) and (self.colormax is not None) and (self.colormax > self.color):
                colormax = self.colormax
        else:
            colormax = self.colormax = color
        if isinstance(color,int) and color<len(self.map):
            S = np.logical_and(self.im>=color,self.im<=colormax)
            self.nbeads = np.count_nonzero(S)
            return np.flipud(S) if self.flipud else S
        raise ValueError("color must be an integer lower than %d" % len(self.map))

    def load(self):
        """ load image and process it
                returns the image, the indexed image and its color map (à la Matlab, such as imread)

                note: if the image contains a palette it is used, if not the
                image is converted to an indexed image without dihtering
        """
        I = Image.open(self.filename)
        if self.angle != 0:
            I= I.rotate(self.angle)
        if self.scale[0] * self.scale[1] != 1:
            I = I.resize((round(I.size[0]*self.scale[0]),round(I.size[1]*self.scale[1])))
        palette = I.getpalette()
        if palette is None:
            J=I.convert(mode="P",colors=self.ncolors,palette=Image.Palette.ADAPTIVE)
            palette = J.getpalette()
        else:
            J = I
        p = np.array(palette,dtype="uint8").reshape((int(len(palette)/3),3))
        ncolors = len(p.sum(axis=1).nonzero()[0]);
        if ncolors<self.ncolors:
            print(f"only {ncolors} are available")
        return I,J, np.array(J,dtype="uint8"), p[:ncolors,:]

    def __repr__(self):
        """ display for rectangle class """
        print("%s - %s object" % (self.name, self.kind))
        print(f'\tfilename: "{self.filename}"')
        print("\tangle = %0.4g (original image)" % self.angle)
        print("\tscale = [%0.4g, %0.4g] (original image)" % self.scale)
        print(f"\tncolors = {self.ncolors} (selected={self.color})")
        print("\trange x = [%0.4g %0.4g]" % (self.xmin,self.xmax))
        print("\trange y = [%0.4g %0.4g]" % (self.ymin,self.ymax))
        print("\tcenter = [%0.4g %0.4g]" % (self.xcenter,self.ycenter))
        print("\ttranslate = [%0.4g %0.4g]" % (self.translate[0],self.translate[1]))
        print("note: use the attribute origina,raw to see the raw image")
        return "%s object: %s (beadtype=%d)" % (self.kind,self.name,self.beadtype)



class genericpolygon(coregeometry):
    """ generic polygon methods """

    hascontour = True
    hasclosefit = False

    @property
    def polygon(self):
        """
        R.polygon = path.Path(R.vertices,R.codes,closed=True)
        """
        v = self.vertices
        if self.translate != None:
            vtmp = list(map(list,zip(*v)))
            for i in range(len(vtmp[0])):
                vtmp[0][i] += self.translate[0]
                vtmp[1][i] += self.translate[1]
            v = list(zip(*vtmp))
        return path.Path(v,self.codes,closed=True)

    @property
    def polygon2plot(self):
        """
        R.polygon2plot = path.Path(R.polygon.vertices+ np.array([1,1]),R.codes,closed=True)
        """
        return path.Path(self.polygon.vertices+ np.array([1,1]),self.codes,closed=True)

    def corners(self):
        """ returns xmin, ymin, xmax, ymax """
        return min([self.vertices[k][0] for k in range(self.nvertices)])+self.translate[0], \
               min([self.vertices[k][1] for k in range(self.nvertices)])+self.translate[1], \
               max([self.vertices[k][0] for k in range(self.nvertices)])+self.translate[0], \
               max([self.vertices[k][1] for k in range(self.nvertices)])+self.translate[1]


class Rectangle(genericpolygon):
    """ Rectangle class """
    def __init__(self,counter):
        self.name = "rect%03d" % counter[1]
        self.kind = "rectangle"     # kind of object
        self.alike = "circle"       # similar object for plotting
        self.beadtype = 1           # bead type
        self.beadtype2 = None       # bead type 2 (alternative beadtype, ratio)
        self.nbeads = 0             # number of beads
        self.ismask = False         # True if beadtype == 0
        self.isplotted = False      # True if plotted
        self.islabelled = False     # True if labelled
        self.resolution = None      # resolution is undefined
        self.hlabel = {'contour':[], 'text':[]}
        self.index = counter[0]
        self.subindex = counter[1]
        self.translate = [0.0,0.0]  # modification used when an object is duplicated

    def __repr__(self):
        """ display for rectangle class """
        print("%s - %s object" % (self.name, self.kind))
        print("\trange x = [%0.4g %0.4g]" % (self.xmin,self.xmax))
        print("\trange y = [%0.4g %0.4g]" % (self.ymin,self.ymax))
        print("\tcenter = [%0.4g %0.4g]" % (self.xcenter,self.ycenter))
        print("\tangle = %0.4g" % self.angle)
        print("\ttranslate = [%0.4g %0.4g]" % (self.translate[0],self.translate[1]))
        return "%s object: %s (beadtype=%d)" % (self.kind,self.name,self.beadtype)


class Circle(genericpolygon):
    """ Circle class """
    def __init__(self,counter,resolution=20):
        self.name = "circ%03d" % counter[1]
        self.kind = "circle"         # kind of object
        self.alike = "circle"        # similar object for plotting
        self.resolution = resolution # default resolution
        self.beadtype = 1            # bead type
        self.beadtype2 = None        # bead type 2 (alternative beadtype, ratio)
        self.nbeads = 0              # number of beads
        self.ismask = False          # True if beadtype == 0
        self.isplotted = False       # True if plotted
        self.islabelled = False      # True if labelled
        self.hlabel = {'contour':[], 'text':[]}
        self.index = counter[0]
        self.subindex = counter[1]
        self.translate = [0.0,0.0]   # modification used when an object is duplicated


    def __repr__(self):
        """ display circle """
        print("%s - %s object" % (self.name,self.kind) )
        print("\trange x = [%0.4g %0.4g]" % (self.xmin,self.xmax))
        print("\trange y = [%0.4g %0.4g]" % (self.ymin,self.ymax))
        print("\tcenter = [%0.4g %0.4g]" % (self.xcenter,self.ycenter))
        print("\tradius = %0.4g" % self.radius)
        print("\tshaperatio = %0.4g" % self.shaperatio)
        print("\tangle = %0.4g" % self.angle)
        print("\ttranslate = [%0.4g %0.4g]" % (self.translate[0],self.translate[1]))
        return "%s object: %s (beadtype=%d)" % (self.kind, self.name,self.beadtype)

class Triangle(Circle):
    """ Triangle class """
    def __init__(self,counter):
        super().__init__(counter,resolution=3)
        self.name = "tri%03d" % counter[1]
        self.kind = "triangle"     # kind of object


class Diamond(Circle):
    """ Diamond class """
    def __init__(self,counter):
        super().__init__(counter,resolution=4)
        self.name = "diam%03d" % counter[1]
        self.kind = "diamond"     # kind of object


class Pentagon(Circle):
    """ Pentagon class """
    def __init__(self,counter):
        super().__init__(counter,resolution=5)
        self.name = "penta%03d" % counter[1]
        self.kind = "pentagon"     # kind of object


class Hexagon(Circle):
    """ Hexagon class """
    def __init__(self,counter):
        super().__init__(counter,resolution=6)
        self.name = "hex%03d" % counter[1]
        self.kind = "Hexagon"     # kind of object

class collection(struct):
    """ collection class container (not to be called directly) """
    _type = "collect"               # object type
    _fulltype = "Collections"    # full name
    _ftype = "collection"        # field name
    def __init__(self,*obj,**kwobj):
        # store the objects with their alias
        super().__init__(**kwobj)
        # store objects with their real names
        for o in obj:
            if isinstance(o,raster):
                s = struct.dict2struct(o.objects)
                list_s = s.keys()
                for i in range(len(list_s)): self.setattr(list_s[i], s[i].copy())
            elif o!=None:
                self.setattr(o.name, o.copy())

class Collection:
    """ Collection object """
    def __init__(self,counter):
        self.name = "collect%03d" % counter[1]
        self.kind = "collection"    # kind of object
        self.alike = "mixed"        # similar object for plotting
        self.nbeads = 0             # number of beads
        self.ismask = False         # True if beadtype == 0
        self.isplotted = False      # True if plotted
        self.islabelled = False     # True if labelled
        self.resolution = None      # resolution is undefined
        self.hlabel = {'contour':[], 'text':[]}
        self.index = counter[0]
        self.subindex = counter[1]
        self.collection = collection()
        self.translate = [0.0,0.0]

    def __repr__(self):
        keylengths = [len(key) for key in self.collection.keys()]
        width = max(10,max(keylengths)+2)
        fmt = "%%%ss:" % width
        line = ( fmt % ('-'*(width-2)) ) + ( '-'*(min(40,width*5)) )
        print("%s - %s object" % (self.name, self.kind))
        print(line)
        print("\trange x = [%0.4g %0.4g]" % (self.xmin,self.xmax))
        print("\trange y = [%0.4g %0.4g]" % (self.ymin,self.ymax))
        print("\tcenter = [%0.4g %0.4g]" % self.xycenter)
        print("\ttranslate = [%0.4g %0.4g]" % (self.translate[0],self.translate[1]))
        print(line,'  name: type "original name" [centerx centery] [translatex translatey]',line,sep="\n")
        for key,value in self.collection.items():
            print(fmt % key,value.kind,
                  '"%s"' % value.name,
                  "[%0.4g %0.4g]" % (value.xcenter,value.ycenter),
                  "[%0.4g %0.4g]" % (value.translate[0],value.translate[1]))
        print(line)
        return "%s object: %s (beadtype=[%s])" % (self.kind,self.name,", ".join(map(str,self.beadtype)))

    # GET -----------------------------
    def get(self,name):
        """ returns the object """
        if name in self.collection:
            return self.collection.getattr(name)
        else:
            raise ValueError('the object "%s" does not exist, use list()' % name)

    # GETATTR --------------------------
    def __getattr__(self,key):
        """ get attribute override """
        return self.get(key)

    @property
    def xycenter(self):
        """ returns the xcenter and ycenter of  the collection """
        sx = sy = 0
        n = len(self.collection)
        for o in self.collection:
            sx += o.xcenter
            sy += o.ycenter
        return sx/n, sy/n

    @property
    def xcenter(self):
        """ returns xcenter """
        xc,_ = self.xycenter

    @property
    def ycenter(self):
        """ returns ycenter """
        _,yc = self.xycenter

    @property
    def beadtype(self):
        """ returns the xcenter and ycenter of the collection """
        b = []
        for o in self.collection:
            if o.beadtype not in b:
                b.append(o.beadtype)
        if len(b)==0:
            return 1
        else:
            return b


# %% scatter class and emulsion class
#    Simplified scatter and emulsion generator

class scatter():
    """ generic top scatter class """
    def __init__(self):
        """
        The scatter class provides an easy constructor
        to distribute in space objects according to their
        positions x,y, size r (radius) and beadtype.

        The class is used to derive emulsions.

        Returns
        -------
        None.

        """
        self.x = np.array([],dtype=int)
        self.y = np.array([],dtype=int)
        self.r = np.array([],dtype=int)
        self.beadtype = []

    @property
    def n(self):
        return len(self.x)

    def pairdist(self,x,y):
        """ pair distance to the surface of all disks/spheres """
        if self.n==0:
            return np.Inf
        else:
            return np.floor(np.sqrt((x-self.x)**2+(y-self.y)**2)-self.r)


class emulsion(scatter):
    """ emulsion generator """

    def __init__(self, xmin=10, ymin=10, xmax=90, ymax=90,
                 maxtrials=1000, beadtype=1, forcedinsertion=True):
        """


        Parameters
        ----------
        The insertions are performed between xmin,ymin and xmax,ymax
        xmin : int64 or real, optional
            x left corner. The default is 10.
        ymin : int64 or real, optional
            y bottom corner. The default is 10.
        xmax : int64 or real, optional
            x right corner. The default is 90.
        ymax : int64 or real, optional
            y top corner. The default is 90.
        beadtype : default beadtype to apply if not precised at insertion
        maxtrials : integer, optional
            Maximum of attempts for an object. The default is 1000.
        forcedinsertion : logical, optional
            Set it to true to force the next insertion. The default is True.

        Returns
        -------
        None.

        """
        super().__init__()
        self.xmin, self.xmax, self.ymin, self.ymax = xmin, xmax, ymin, ymax
        self.lastinsertion = (None,None,None,None) # x,y,r, beadtype
        self.width = xmax-xmin
        self.height = ymax-ymin
        self.defautbeadtype = beadtype
        self.maxtrials = maxtrials
        self.forcedinsertion = forcedinsertion

    def __repr__(self):
        print(f" Emulsion object\n\t{self.width}x{self.height} starting at x={self.xmin}, y={self.ymin}")
        print(f"\tcontains {self.n} insertions")
        print("\tmaximum insertion trials:", self.maxtrials)
        print("\tforce next insertion if previous fails:", self.forcedinsertion)
        return f"emulsion with {self.n} insertions"


    def walldist(self,x,y):
        """ shortest distance to the wall """
        return min(abs(x-self.xmin),abs(y-self.ymin),abs(x-self.xmax),abs(y-self.ymax))

    def dist(self,x,y):
        """ shortest distance of the center (x,y) to the wall or any object"""
        return np.minimum(np.min(self.pairdist(x,y)),self.walldist(x,y))

    def accepted(self,x,y,r):
        """ acceptation criterion """
        return self.dist(x,y)>r

    def rand(self):
        """ random position x,y  """
        return  np.round(np.random.uniform(low=self.xmin,high=self.xmax)), \
                np.round(np.random.uniform(low=self.ymin,high=self.ymax))

    def setbeadtype(self,beadtype):
        """ set the default or the supplied beadtype  """
        if beadtype == None:
            self.beadtype.append(self.defautbeadtype)
            return self.defautbeadtype
        else:
            self.beadtype.append(beadtype)
            return beadtype

    def insertone(self,x=None,y=None,r=None,beadtype=None,overlap=False):
        """
            insert one object of radius r
            properties:
                x,y coordinates (if missing, picked randomly from uniform distribution)
                r radius (default = 2% of diagonal)
                beadtype (default = defautbeadtype)
                overlap = False (accept only if no overlap)
        """
        attempt, success = 0, False
        random = (x==None) or (y==None)
        if r==None:
            r = 0.02*np.sqrt(self.width**2+self.height**2)
        while not success and attempt<self.maxtrials:
            attempt += 1
            if random: x,y = self.rand()
            if overlap:
                success = True
            else:
                success = self.accepted(x,y,r)
        if success:
            self.x = np.append(self.x,x)
            self.y = np.append(self.y,y)
            self.r = np.append(self.r,r)
            b=self.setbeadtype(beadtype)
            self.lastinsertion = (x,y,r,b)
        return success

    def insertion(self,rlist,beadtype=None):
        """
            insert a list of objects
                nsuccess=insertion(rlist,beadtype=None)
                beadtype=b forces the value b
                if None, defaultbeadtype is used instead
        """
        rlist.sort(reverse=True)
        ntodo = len(rlist)
        n = nsuccess = 0
        stop = False
        while not stop:
            n += 1
            success = self.insertone(r=rlist[n-1],beadtype=beadtype)
            if success: nsuccess += 1
            stop = (n==ntodo) or (not success and not self.forcedinsertion)
        if nsuccess==ntodo:
            print(f"{nsuccess} objects inserted successfully")
        else:
            print(f"partial success: {nsuccess} of {ntodo} objects inserted")
        return nsuccess


class coreshell(emulsion):
    """
        coreshell generator
            inherited from emulsion
            the method insertion has been modified to integrate
                thickess = shell thickness value
                beadtype = (shell beadtype, core beadtype)
    """

    def insertion(self,rlist,thickness=None, beadtype=(1,2)):
        """
            insert a list of objects
                nsuccess=insertion(...)

                List of properties
                    rlist = [r1, r2,...]
                    thickness = shell thcikness value
                    beadtype = (shell beadtype, core beadtype)
        """
        # check arguments
        if thickness==None:
            raise AttributeError("set a value for the shell thickness")
        if not isinstance(beadtype,tuple):
            raise TypeError("beadtype must be a turple")
        # prepare the work
        rlist.sort(reverse=True)
        ntodo = len(rlist)
        n = nsuccess = 0
        stop = False
        while not stop:
            # next insertion and check rcore
            n += 1
            rshell = rlist[n-1]
            rcore = rshell - thickness
            if rcore<=0:
                raise ValueError(
 f"The external radius={rshell} cannot be smaller than the shell thickness={thickness}")
            # do the insertion of the shell (largest radius)
            success = self.insertone(r=rshell,beadtype=beadtype[0],overlap=False)
            if success:
                success = self.insertone(
                    x = self.lastinsertion[0],
                    y = self.lastinsertion[1],
                    r=rcore,
                    beadtype=beadtype[1],
                    overlap=True)
                nsuccess += 1
            stop = (n==ntodo) or (not success and not self.forcedinsertion)
        if nsuccess==ntodo:
            print(f"{nsuccess} objects inserted successfully")
        else:
            print(f"partial success: {nsuccess} of {ntodo} objects inserted")
        return nsuccess

# %% debug section - generic code to test methods (press F5)
# ===================================================
# main()
# ===================================================
# for debugging purposes (code called as a script)
# the code is called from here
# ===================================================
if __name__ == '__main__':

# %% basic example

    plt.close("all")
    R = raster()
    R.rectangle(1,24,2,20,name='rect1')
    R.rectangle(60,80,50,81,name='rect2',beadtype=2,angle=40,beadtype2=(9,0.2))
    R.rectangle(50,50,10,10,mode="center",angle=45,beadtype=1)
    R.circle(45,20,5,name='C1',beadtype=3,beadtype2=(8,0.25))
    R.circle(35,10,5,name='C2',beadtype=3)

    R.circle(15,30,10,name='p1',beadtype=4,shaperatio=0.2,angle=-30)
    R.circle(12,40,8,name='p2',beadtype=4,shaperatio=0.2,angle=20)
    R.circle(12,80,22,name='p3',beadtype=4,shaperatio=1.3,angle=20,beadtype2=(9,0.1))

    R.triangle(85,20,10,name='T1',beadtype=5,angle=20)
    R.diamond(85,35,5,name='D1',beadtype=5,angle=20,beadtype2=(9,0.5))
    R.pentagon(50,35,5,name='P1',beadtype=5,angle=90)
    R.hexagon(47,85,12,name='H1',beadtype=5,angle=90)

    R.label("rect003")
    R.plot()

    R.list()
    R.show()

    R.clear()
    R.show()
    R.plot()
    R.show(extra="label")
    R.label("rect003")
    R.unlabel('rect1')

    X=R.data()

# %% another example
    S = raster(width=1000,height=1000)
    S.rectangle(150,850,850,1000,name="top",beadtype=1)
    S.rectangle(150,850,0,150,name="bottom",beadtype=2)
    S.circle(500,500,480,name="mask",ismask=True,resolution=500)
    S.triangle(250,880,80,name='tooth1',angle=60,beadtype=1)
    S.triangle(750,880,80,name='tooth2',angle=-0,beadtype=1)
    S.circle(500,200,300,name="tongue",beadtype=5,shaperatio=0.3,resolution=300)
    S.rectangle(500,450,320,320,name="food",mode="center",beadtype=3)
    S.plot()
    S.show(extra="label",contour=False)



# %% advanced example
    #plt.close("all")
    draft = raster()
    draft.rectangle(1,24,2,20,name='rect1'),
    draft.rectangle(60,80,50,81,name='rect2',beadtype=2,angle=40),
    draft.rectangle(50,50,10,10,mode="center",angle=45,beadtype=1),
    draft.circle(45,20,5,name='C1',beadtype=3),
    draft.circle(35,10,5,name='C2',beadtype=3),
    draft.circle(10,10,2,name="X",beadtype=4)

    A = raster()
    A.collection(draft,name="C1",beadtype=1,translate=[10,30])
    repr(A)
    A.objects
    A.plot()
    A.show(extra="label")
    A.objects

    B = raster()
    #B.collection(X=draft.X,beadtype=1,translate=[50,50])
    B.copyalongpath(draft.X,name="PX",beadtype=2,
                    path=arc,
                     xmin=10,
                     ymin=10,
                     xmax=90,
                     ymax=50,
                        n=12)
    B.plot()
    B.show(extra="label")


# %% emulsion example
    C = raster(width=400,height=400)
    e = emulsion(xmin=10, ymin=10, xmax=390, ymax=390)
    e.insertion([60,50,40,30,20,15,15,10,8,20,12,8,6,4,11,13],beadtype=1)
    e.insertion([30,10,20,2,4,5,5,10,12,20,25,12,14,16,17],beadtype=2)
    e.insertion([40,2,8,6,6,5,5,2,3,4,4,4,4,4,10,16,12,14,13],beadtype=3)
    C.scatter(e,name="emulsion")
    C.plot()
    C.show()


# %% core-shell example
    D = raster(width=400,height=400)
    cs = coreshell(xmin=10, ymin=10, xmax=390, ymax=390)
    cs.insertion([60,50,40,30,20,15,15,10,8,20,12,8,11,13],beadtype=(1,2),thickness = 4)
    D.scatter(cs,name="core-shell")
    D.plot()
    D.show()

# %% overlay example
    I = raster(width=600,height=600)
    I.overlay(30,100,name="pix0",filename="./sandbox/image.jpg",ncolors=4,color=0,beadtype=1,angle=10,scale=(1.1,1.1))
    I.overlay(30,100,name="pix2",filename="./sandbox/image.jpg",ncolors=4,color=2,beadtype=2,angle=10,scale=(1.1,1.1))
    I.label("pix0")
    I.plot()
    I.show(extra="label")
    I.pix0.original
    I.pix0.raw
    a = I.torgb("objindex",(512,512))
    a.show()
    a.save("./tmp/preview.png")

Functions

def arc(xmin=10, ymin=50, xmax=80, ymax=50, n=5, USER=structure (struct object) with 2 fields)

Point distributed along an arc X,Y = arc(xmin=value,ymin=value,xmax=value,ymax=value,n=int, USER=struct(radius=value,direction=1)) Use direction to choose the upward +1 or downward -1 circle see: https://rosettacode.org/wiki/Circles_of_given_radius_through_two_points

Expand source code
def arc(xmin=10,ymin=50,xmax=80,ymax=50,n=5,USER=struct(radius=20,direction=1)):
    """  Point distributed along an arc
            X,Y = arc(xmin=value,ymin=value,xmax=value,ymax=value,n=int,
                      USER=struct(radius=value,direction=1))
            Use direction to choose the upward +1 or downward -1 circle
        see: https://rosettacode.org/wiki/Circles_of_given_radius_through_two_points
    """
    R = 0 if "radius" not in USER else USER.radius
    direction = +1 if "direction" not in USER else USER.direction
    dx,dy = xmax-xmin, ymax-ymin
    q = np.sqrt(dx**2+dy**2) # distance
    R = max(R,q/2) # radius constraint
    d = np.sqrt(R**2-(q/2)**2) # distance along the mirror line
    xc = (xmin+xmax)/2 - direction * d*dy/q
    yc = (ymin+ymax)/2 + direction * d*dx/q
    thmin,thmax = np.arctan((ymin-yc)/(xmin-xc)), np.arctan((ymax-yc)/(xmax-xc))
    if d==0: thmax = thmin + np.pi
    th = np.linspace(thmin,thmax,n)
    return xc+np.cos(th)*R,yc+np.sin(th)*R
def imagesc(im, x=None, y=None)

imagesc à la Matlab imagesc(np2array)

Expand source code
def imagesc(im,x=None,y=None):
    """  imagesc à la Matlab
            imagesc(np2array) """
    if x==None: x=np.arange(1,np.shape(im)[1]+1)
    if y==None: y=np.arange(1,np.shape(im)[0]+1)
    plt.imshow(im, extent=_extents(x) + _extents(y),
               aspect="auto", origin="lower", interpolation="none")
def ind2rgb(im, ncolors=64)

Convert indexed image (NumPy array) to RGB rgb = ind2rgb(np2array,ncolors=nc) use rgb.save("/path/filename.png") for saving

Expand source code
def ind2rgb(im,ncolors=64):
    """ Convert indexed image (NumPy array) to RGB
            rgb = ind2rgb(np2array,ncolors=nc)
            use rgb.save("/path/filename.png") for saving
    """
    raw = Image.fromarray(np.flipud(im),"P")
    col0 = np.array(np.round(255*cmap.get_cmap("viridis",ncolors).colors[:,:3]),dtype="uint8")
    col = bytearray(np.resize(col0,(256,3)).flatten())
    pal = ImagePalette.ImagePalette(mode="RGB",palette=col)
    #Image.convert(mode=None, matrix=None, dither=None, palette=Palette.WEB, colors=256)
    raw.putpalette(pal)
    return raw
def linear(xmin=10, ymin=10, xmax=80, ymax=80, n=5, USER=structure (struct object) with 0 fields)

Equispaced points along a trajectory X,Y = linear(xmin=value,ymin=value,xmax=value,ymax=value,n=int)

Expand source code
def linear(xmin=10,ymin=10,xmax=80,ymax=80,n=5,USER=struct()):
    """  Equispaced points along a trajectory
            X,Y = linear(xmin=value,ymin=value,xmax=value,ymax=value,n=int)
    """
    return np.linspace(xmin,xmax,n), np.linspace(ymin,ymax,n)

Classes

class Circle (counter, resolution=20)

Circle class

Expand source code
class Circle(genericpolygon):
    """ Circle class """
    def __init__(self,counter,resolution=20):
        self.name = "circ%03d" % counter[1]
        self.kind = "circle"         # kind of object
        self.alike = "circle"        # similar object for plotting
        self.resolution = resolution # default resolution
        self.beadtype = 1            # bead type
        self.beadtype2 = None        # bead type 2 (alternative beadtype, ratio)
        self.nbeads = 0              # number of beads
        self.ismask = False          # True if beadtype == 0
        self.isplotted = False       # True if plotted
        self.islabelled = False      # True if labelled
        self.hlabel = {'contour':[], 'text':[]}
        self.index = counter[0]
        self.subindex = counter[1]
        self.translate = [0.0,0.0]   # modification used when an object is duplicated


    def __repr__(self):
        """ display circle """
        print("%s - %s object" % (self.name,self.kind) )
        print("\trange x = [%0.4g %0.4g]" % (self.xmin,self.xmax))
        print("\trange y = [%0.4g %0.4g]" % (self.ymin,self.ymax))
        print("\tcenter = [%0.4g %0.4g]" % (self.xcenter,self.ycenter))
        print("\tradius = %0.4g" % self.radius)
        print("\tshaperatio = %0.4g" % self.shaperatio)
        print("\tangle = %0.4g" % self.angle)
        print("\ttranslate = [%0.4g %0.4g]" % (self.translate[0],self.translate[1]))
        return "%s object: %s (beadtype=%d)" % (self.kind, self.name,self.beadtype)

Ancestors

Subclasses

Inherited members

class Collection (counter)

Collection object

Expand source code
class Collection:
    """ Collection object """
    def __init__(self,counter):
        self.name = "collect%03d" % counter[1]
        self.kind = "collection"    # kind of object
        self.alike = "mixed"        # similar object for plotting
        self.nbeads = 0             # number of beads
        self.ismask = False         # True if beadtype == 0
        self.isplotted = False      # True if plotted
        self.islabelled = False     # True if labelled
        self.resolution = None      # resolution is undefined
        self.hlabel = {'contour':[], 'text':[]}
        self.index = counter[0]
        self.subindex = counter[1]
        self.collection = collection()
        self.translate = [0.0,0.0]

    def __repr__(self):
        keylengths = [len(key) for key in self.collection.keys()]
        width = max(10,max(keylengths)+2)
        fmt = "%%%ss:" % width
        line = ( fmt % ('-'*(width-2)) ) + ( '-'*(min(40,width*5)) )
        print("%s - %s object" % (self.name, self.kind))
        print(line)
        print("\trange x = [%0.4g %0.4g]" % (self.xmin,self.xmax))
        print("\trange y = [%0.4g %0.4g]" % (self.ymin,self.ymax))
        print("\tcenter = [%0.4g %0.4g]" % self.xycenter)
        print("\ttranslate = [%0.4g %0.4g]" % (self.translate[0],self.translate[1]))
        print(line,'  name: type "original name" [centerx centery] [translatex translatey]',line,sep="\n")
        for key,value in self.collection.items():
            print(fmt % key,value.kind,
                  '"%s"' % value.name,
                  "[%0.4g %0.4g]" % (value.xcenter,value.ycenter),
                  "[%0.4g %0.4g]" % (value.translate[0],value.translate[1]))
        print(line)
        return "%s object: %s (beadtype=[%s])" % (self.kind,self.name,", ".join(map(str,self.beadtype)))

    # GET -----------------------------
    def get(self,name):
        """ returns the object """
        if name in self.collection:
            return self.collection.getattr(name)
        else:
            raise ValueError('the object "%s" does not exist, use list()' % name)

    # GETATTR --------------------------
    def __getattr__(self,key):
        """ get attribute override """
        return self.get(key)

    @property
    def xycenter(self):
        """ returns the xcenter and ycenter of  the collection """
        sx = sy = 0
        n = len(self.collection)
        for o in self.collection:
            sx += o.xcenter
            sy += o.ycenter
        return sx/n, sy/n

    @property
    def xcenter(self):
        """ returns xcenter """
        xc,_ = self.xycenter

    @property
    def ycenter(self):
        """ returns ycenter """
        _,yc = self.xycenter

    @property
    def beadtype(self):
        """ returns the xcenter and ycenter of the collection """
        b = []
        for o in self.collection:
            if o.beadtype not in b:
                b.append(o.beadtype)
        if len(b)==0:
            return 1
        else:
            return b

Instance variables

var beadtype

returns the xcenter and ycenter of the collection

Expand source code
@property
def beadtype(self):
    """ returns the xcenter and ycenter of the collection """
    b = []
    for o in self.collection:
        if o.beadtype not in b:
            b.append(o.beadtype)
    if len(b)==0:
        return 1
    else:
        return b
var xcenter

returns xcenter

Expand source code
@property
def xcenter(self):
    """ returns xcenter """
    xc,_ = self.xycenter
var xycenter

returns the xcenter and ycenter of the collection

Expand source code
@property
def xycenter(self):
    """ returns the xcenter and ycenter of  the collection """
    sx = sy = 0
    n = len(self.collection)
    for o in self.collection:
        sx += o.xcenter
        sy += o.ycenter
    return sx/n, sy/n
var ycenter

returns ycenter

Expand source code
@property
def ycenter(self):
    """ returns ycenter """
    _,yc = self.xycenter

Methods

def get(self, name)

returns the object

Expand source code
def get(self,name):
    """ returns the object """
    if name in self.collection:
        return self.collection.getattr(name)
    else:
        raise ValueError('the object "%s" does not exist, use list()' % name)
class Diamond (counter)

Diamond class

Expand source code
class Diamond(Circle):
    """ Diamond class """
    def __init__(self,counter):
        super().__init__(counter,resolution=4)
        self.name = "diam%03d" % counter[1]
        self.kind = "diamond"     # kind of object

Ancestors

Inherited members

class Hexagon (counter)

Hexagon class

Expand source code
class Hexagon(Circle):
    """ Hexagon class """
    def __init__(self,counter):
        super().__init__(counter,resolution=6)
        self.name = "hex%03d" % counter[1]
        self.kind = "Hexagon"     # kind of object

Ancestors

Inherited members

class Pentagon (counter)

Pentagon class

Expand source code
class Pentagon(Circle):
    """ Pentagon class """
    def __init__(self,counter):
        super().__init__(counter,resolution=5)
        self.name = "penta%03d" % counter[1]
        self.kind = "pentagon"     # kind of object

Ancestors

Inherited members

class Rectangle (counter)

Rectangle class

Expand source code
class Rectangle(genericpolygon):
    """ Rectangle class """
    def __init__(self,counter):
        self.name = "rect%03d" % counter[1]
        self.kind = "rectangle"     # kind of object
        self.alike = "circle"       # similar object for plotting
        self.beadtype = 1           # bead type
        self.beadtype2 = None       # bead type 2 (alternative beadtype, ratio)
        self.nbeads = 0             # number of beads
        self.ismask = False         # True if beadtype == 0
        self.isplotted = False      # True if plotted
        self.islabelled = False     # True if labelled
        self.resolution = None      # resolution is undefined
        self.hlabel = {'contour':[], 'text':[]}
        self.index = counter[0]
        self.subindex = counter[1]
        self.translate = [0.0,0.0]  # modification used when an object is duplicated

    def __repr__(self):
        """ display for rectangle class """
        print("%s - %s object" % (self.name, self.kind))
        print("\trange x = [%0.4g %0.4g]" % (self.xmin,self.xmax))
        print("\trange y = [%0.4g %0.4g]" % (self.ymin,self.ymax))
        print("\tcenter = [%0.4g %0.4g]" % (self.xcenter,self.ycenter))
        print("\tangle = %0.4g" % self.angle)
        print("\ttranslate = [%0.4g %0.4g]" % (self.translate[0],self.translate[1]))
        return "%s object: %s (beadtype=%d)" % (self.kind,self.name,self.beadtype)

Ancestors

Inherited members

class Triangle (counter)

Triangle class

Expand source code
class Triangle(Circle):
    """ Triangle class """
    def __init__(self,counter):
        super().__init__(counter,resolution=3)
        self.name = "tri%03d" % counter[1]
        self.kind = "triangle"     # kind of object

Ancestors

Inherited members

class collection (*obj, **kwobj)

collection class container (not to be called directly)

constructor

Expand source code
class collection(struct):
    """ collection class container (not to be called directly) """
    _type = "collect"               # object type
    _fulltype = "Collections"    # full name
    _ftype = "collection"        # field name
    def __init__(self,*obj,**kwobj):
        # store the objects with their alias
        super().__init__(**kwobj)
        # store objects with their real names
        for o in obj:
            if isinstance(o,raster):
                s = struct.dict2struct(o.objects)
                list_s = s.keys()
                for i in range(len(list_s)): self.setattr(list_s[i], s[i].copy())
            elif o!=None:
                self.setattr(o.name, o.copy())

Ancestors

  • pizza.private.mstruct.struct
class coregeometry

core geometry object

Expand source code
class coregeometry:
    """ core geometry object"""

    @property
    def xcenter(self):
        """ xcenter with translate """
        return self.xcenter0 + self.translate[0]
    @property
    def ycenter(self):
        """ xcenter with translate """
        return self.ycenter0 + self.translate[1]
    @property
    def xmin(self):
        """ xleft position """
        return self.xmin0 + self.translate[0]
    @property
    def xmax(self):
        """ xright position """
        return self.xmax0 + self.translate[0]
    @property
    def ymin(self):
        """ yleft position """
        return self.ymin0 + self.translate[1]
    @property
    def ymax(self):
        """ yright position """
        return self.ymax0 + self.translate[1]
    @property
    def width(self):
        """ oibject width range """
        return self.xmax - self.xmin
    @property
    def height(self):
        """ oibject height range """
        return self.ymax - self.ymin

    def copy(self,translate=None,beadtype=None,name=""):
        """ returns a copy of the graphical object """
        if self.alike != "mixed":
            dup = deepduplicate(self)
            if translate != None: # applies translation
                dup.translate[0] += translate[0]
                dup.translate[1] += translate[1]
            if beadtype != None: # update beadtype
                dup.beadtype = beadtype
            if name != "": # update name
                dup.name = name
            return dup
        else:
            raise ValueError("collections cannot be copied, regenerate the collection instead")

Subclasses

Instance variables

var height

oibject height range

Expand source code
@property
def height(self):
    """ oibject height range """
    return self.ymax - self.ymin
var width

oibject width range

Expand source code
@property
def width(self):
    """ oibject width range """
    return self.xmax - self.xmin
var xcenter

xcenter with translate

Expand source code
@property
def xcenter(self):
    """ xcenter with translate """
    return self.xcenter0 + self.translate[0]
var xmax

xright position

Expand source code
@property
def xmax(self):
    """ xright position """
    return self.xmax0 + self.translate[0]
var xmin

xleft position

Expand source code
@property
def xmin(self):
    """ xleft position """
    return self.xmin0 + self.translate[0]
var ycenter

xcenter with translate

Expand source code
@property
def ycenter(self):
    """ xcenter with translate """
    return self.ycenter0 + self.translate[1]
var ymax

yright position

Expand source code
@property
def ymax(self):
    """ yright position """
    return self.ymax0 + self.translate[1]
var ymin

yleft position

Expand source code
@property
def ymin(self):
    """ yleft position """
    return self.ymin0 + self.translate[1]

Methods

def copy(self, translate=None, beadtype=None, name='')

returns a copy of the graphical object

Expand source code
def copy(self,translate=None,beadtype=None,name=""):
    """ returns a copy of the graphical object """
    if self.alike != "mixed":
        dup = deepduplicate(self)
        if translate != None: # applies translation
            dup.translate[0] += translate[0]
            dup.translate[1] += translate[1]
        if beadtype != None: # update beadtype
            dup.beadtype = beadtype
        if name != "": # update name
            dup.name = name
        return dup
    else:
        raise ValueError("collections cannot be copied, regenerate the collection instead")
class coreshell (xmin=10, ymin=10, xmax=90, ymax=90, maxtrials=1000, beadtype=1, forcedinsertion=True)

coreshell generator inherited from emulsion the method insertion has been modified to integrate thickess = shell thickness value beadtype = (shell beadtype, core beadtype)

Parameters

The insertions are performed between xmin,ymin and xmax,ymax
xmin : int64 or real, optional
x left corner. The default is 10.
ymin : int64 or real, optional
y bottom corner. The default is 10.
xmax : int64 or real, optional
x right corner. The default is 90.
ymax : int64 or real, optional
y top corner. The default is 90.
beadtype : default beadtype to apply if not precised at insertion
 
maxtrials : integer, optional
Maximum of attempts for an object. The default is 1000.
forcedinsertion : logical, optional
Set it to true to force the next insertion. The default is True.

Returns

None.

Expand source code
class coreshell(emulsion):
    """
        coreshell generator
            inherited from emulsion
            the method insertion has been modified to integrate
                thickess = shell thickness value
                beadtype = (shell beadtype, core beadtype)
    """

    def insertion(self,rlist,thickness=None, beadtype=(1,2)):
        """
            insert a list of objects
                nsuccess=insertion(...)

                List of properties
                    rlist = [r1, r2,...]
                    thickness = shell thcikness value
                    beadtype = (shell beadtype, core beadtype)
        """
        # check arguments
        if thickness==None:
            raise AttributeError("set a value for the shell thickness")
        if not isinstance(beadtype,tuple):
            raise TypeError("beadtype must be a turple")
        # prepare the work
        rlist.sort(reverse=True)
        ntodo = len(rlist)
        n = nsuccess = 0
        stop = False
        while not stop:
            # next insertion and check rcore
            n += 1
            rshell = rlist[n-1]
            rcore = rshell - thickness
            if rcore<=0:
                raise ValueError(
 f"The external radius={rshell} cannot be smaller than the shell thickness={thickness}")
            # do the insertion of the shell (largest radius)
            success = self.insertone(r=rshell,beadtype=beadtype[0],overlap=False)
            if success:
                success = self.insertone(
                    x = self.lastinsertion[0],
                    y = self.lastinsertion[1],
                    r=rcore,
                    beadtype=beadtype[1],
                    overlap=True)
                nsuccess += 1
            stop = (n==ntodo) or (not success and not self.forcedinsertion)
        if nsuccess==ntodo:
            print(f"{nsuccess} objects inserted successfully")
        else:
            print(f"partial success: {nsuccess} of {ntodo} objects inserted")
        return nsuccess

Ancestors

Methods

def insertion(self, rlist, thickness=None, beadtype=(1, 2))

insert a list of objects nsuccess=insertion(…)

List of properties
    rlist = [r1, r2,...]
    thickness = shell thcikness value
    beadtype = (shell beadtype, core beadtype)
Expand source code
   def insertion(self,rlist,thickness=None, beadtype=(1,2)):
       """
           insert a list of objects
               nsuccess=insertion(...)

               List of properties
                   rlist = [r1, r2,...]
                   thickness = shell thcikness value
                   beadtype = (shell beadtype, core beadtype)
       """
       # check arguments
       if thickness==None:
           raise AttributeError("set a value for the shell thickness")
       if not isinstance(beadtype,tuple):
           raise TypeError("beadtype must be a turple")
       # prepare the work
       rlist.sort(reverse=True)
       ntodo = len(rlist)
       n = nsuccess = 0
       stop = False
       while not stop:
           # next insertion and check rcore
           n += 1
           rshell = rlist[n-1]
           rcore = rshell - thickness
           if rcore<=0:
               raise ValueError(
f"The external radius={rshell} cannot be smaller than the shell thickness={thickness}")
           # do the insertion of the shell (largest radius)
           success = self.insertone(r=rshell,beadtype=beadtype[0],overlap=False)
           if success:
               success = self.insertone(
                   x = self.lastinsertion[0],
                   y = self.lastinsertion[1],
                   r=rcore,
                   beadtype=beadtype[1],
                   overlap=True)
               nsuccess += 1
           stop = (n==ntodo) or (not success and not self.forcedinsertion)
       if nsuccess==ntodo:
           print(f"{nsuccess} objects inserted successfully")
       else:
           print(f"partial success: {nsuccess} of {ntodo} objects inserted")
       return nsuccess

Inherited members

class emulsion (xmin=10, ymin=10, xmax=90, ymax=90, maxtrials=1000, beadtype=1, forcedinsertion=True)

emulsion generator

Parameters

The insertions are performed between xmin,ymin and xmax,ymax
xmin : int64 or real, optional
x left corner. The default is 10.
ymin : int64 or real, optional
y bottom corner. The default is 10.
xmax : int64 or real, optional
x right corner. The default is 90.
ymax : int64 or real, optional
y top corner. The default is 90.
beadtype : default beadtype to apply if not precised at insertion
 
maxtrials : integer, optional
Maximum of attempts for an object. The default is 1000.
forcedinsertion : logical, optional
Set it to true to force the next insertion. The default is True.

Returns

None.

Expand source code
class emulsion(scatter):
    """ emulsion generator """

    def __init__(self, xmin=10, ymin=10, xmax=90, ymax=90,
                 maxtrials=1000, beadtype=1, forcedinsertion=True):
        """


        Parameters
        ----------
        The insertions are performed between xmin,ymin and xmax,ymax
        xmin : int64 or real, optional
            x left corner. The default is 10.
        ymin : int64 or real, optional
            y bottom corner. The default is 10.
        xmax : int64 or real, optional
            x right corner. The default is 90.
        ymax : int64 or real, optional
            y top corner. The default is 90.
        beadtype : default beadtype to apply if not precised at insertion
        maxtrials : integer, optional
            Maximum of attempts for an object. The default is 1000.
        forcedinsertion : logical, optional
            Set it to true to force the next insertion. The default is True.

        Returns
        -------
        None.

        """
        super().__init__()
        self.xmin, self.xmax, self.ymin, self.ymax = xmin, xmax, ymin, ymax
        self.lastinsertion = (None,None,None,None) # x,y,r, beadtype
        self.width = xmax-xmin
        self.height = ymax-ymin
        self.defautbeadtype = beadtype
        self.maxtrials = maxtrials
        self.forcedinsertion = forcedinsertion

    def __repr__(self):
        print(f" Emulsion object\n\t{self.width}x{self.height} starting at x={self.xmin}, y={self.ymin}")
        print(f"\tcontains {self.n} insertions")
        print("\tmaximum insertion trials:", self.maxtrials)
        print("\tforce next insertion if previous fails:", self.forcedinsertion)
        return f"emulsion with {self.n} insertions"


    def walldist(self,x,y):
        """ shortest distance to the wall """
        return min(abs(x-self.xmin),abs(y-self.ymin),abs(x-self.xmax),abs(y-self.ymax))

    def dist(self,x,y):
        """ shortest distance of the center (x,y) to the wall or any object"""
        return np.minimum(np.min(self.pairdist(x,y)),self.walldist(x,y))

    def accepted(self,x,y,r):
        """ acceptation criterion """
        return self.dist(x,y)>r

    def rand(self):
        """ random position x,y  """
        return  np.round(np.random.uniform(low=self.xmin,high=self.xmax)), \
                np.round(np.random.uniform(low=self.ymin,high=self.ymax))

    def setbeadtype(self,beadtype):
        """ set the default or the supplied beadtype  """
        if beadtype == None:
            self.beadtype.append(self.defautbeadtype)
            return self.defautbeadtype
        else:
            self.beadtype.append(beadtype)
            return beadtype

    def insertone(self,x=None,y=None,r=None,beadtype=None,overlap=False):
        """
            insert one object of radius r
            properties:
                x,y coordinates (if missing, picked randomly from uniform distribution)
                r radius (default = 2% of diagonal)
                beadtype (default = defautbeadtype)
                overlap = False (accept only if no overlap)
        """
        attempt, success = 0, False
        random = (x==None) or (y==None)
        if r==None:
            r = 0.02*np.sqrt(self.width**2+self.height**2)
        while not success and attempt<self.maxtrials:
            attempt += 1
            if random: x,y = self.rand()
            if overlap:
                success = True
            else:
                success = self.accepted(x,y,r)
        if success:
            self.x = np.append(self.x,x)
            self.y = np.append(self.y,y)
            self.r = np.append(self.r,r)
            b=self.setbeadtype(beadtype)
            self.lastinsertion = (x,y,r,b)
        return success

    def insertion(self,rlist,beadtype=None):
        """
            insert a list of objects
                nsuccess=insertion(rlist,beadtype=None)
                beadtype=b forces the value b
                if None, defaultbeadtype is used instead
        """
        rlist.sort(reverse=True)
        ntodo = len(rlist)
        n = nsuccess = 0
        stop = False
        while not stop:
            n += 1
            success = self.insertone(r=rlist[n-1],beadtype=beadtype)
            if success: nsuccess += 1
            stop = (n==ntodo) or (not success and not self.forcedinsertion)
        if nsuccess==ntodo:
            print(f"{nsuccess} objects inserted successfully")
        else:
            print(f"partial success: {nsuccess} of {ntodo} objects inserted")
        return nsuccess

Ancestors

Subclasses

Methods

def accepted(self, x, y, r)

acceptation criterion

Expand source code
def accepted(self,x,y,r):
    """ acceptation criterion """
    return self.dist(x,y)>r
def dist(self, x, y)

shortest distance of the center (x,y) to the wall or any object

Expand source code
def dist(self,x,y):
    """ shortest distance of the center (x,y) to the wall or any object"""
    return np.minimum(np.min(self.pairdist(x,y)),self.walldist(x,y))
def insertion(self, rlist, beadtype=None)

insert a list of objects nsuccess=insertion(rlist,beadtype=None) beadtype=b forces the value b if None, defaultbeadtype is used instead

Expand source code
def insertion(self,rlist,beadtype=None):
    """
        insert a list of objects
            nsuccess=insertion(rlist,beadtype=None)
            beadtype=b forces the value b
            if None, defaultbeadtype is used instead
    """
    rlist.sort(reverse=True)
    ntodo = len(rlist)
    n = nsuccess = 0
    stop = False
    while not stop:
        n += 1
        success = self.insertone(r=rlist[n-1],beadtype=beadtype)
        if success: nsuccess += 1
        stop = (n==ntodo) or (not success and not self.forcedinsertion)
    if nsuccess==ntodo:
        print(f"{nsuccess} objects inserted successfully")
    else:
        print(f"partial success: {nsuccess} of {ntodo} objects inserted")
    return nsuccess
def insertone(self, x=None, y=None, r=None, beadtype=None, overlap=False)

insert one object of radius r properties: x,y coordinates (if missing, picked randomly from uniform distribution) r radius (default = 2% of diagonal) beadtype (default = defautbeadtype) overlap = False (accept only if no overlap)

Expand source code
def insertone(self,x=None,y=None,r=None,beadtype=None,overlap=False):
    """
        insert one object of radius r
        properties:
            x,y coordinates (if missing, picked randomly from uniform distribution)
            r radius (default = 2% of diagonal)
            beadtype (default = defautbeadtype)
            overlap = False (accept only if no overlap)
    """
    attempt, success = 0, False
    random = (x==None) or (y==None)
    if r==None:
        r = 0.02*np.sqrt(self.width**2+self.height**2)
    while not success and attempt<self.maxtrials:
        attempt += 1
        if random: x,y = self.rand()
        if overlap:
            success = True
        else:
            success = self.accepted(x,y,r)
    if success:
        self.x = np.append(self.x,x)
        self.y = np.append(self.y,y)
        self.r = np.append(self.r,r)
        b=self.setbeadtype(beadtype)
        self.lastinsertion = (x,y,r,b)
    return success
def rand(self)

random position x,y

Expand source code
def rand(self):
    """ random position x,y  """
    return  np.round(np.random.uniform(low=self.xmin,high=self.xmax)), \
            np.round(np.random.uniform(low=self.ymin,high=self.ymax))
def setbeadtype(self, beadtype)

set the default or the supplied beadtype

Expand source code
def setbeadtype(self,beadtype):
    """ set the default or the supplied beadtype  """
    if beadtype == None:
        self.beadtype.append(self.defautbeadtype)
        return self.defautbeadtype
    else:
        self.beadtype.append(beadtype)
        return beadtype
def walldist(self, x, y)

shortest distance to the wall

Expand source code
def walldist(self,x,y):
    """ shortest distance to the wall """
    return min(abs(x-self.xmin),abs(y-self.ymin),abs(x-self.xmax),abs(y-self.ymax))

Inherited members

class genericpolygon

generic polygon methods

Expand source code
class genericpolygon(coregeometry):
    """ generic polygon methods """

    hascontour = True
    hasclosefit = False

    @property
    def polygon(self):
        """
        R.polygon = path.Path(R.vertices,R.codes,closed=True)
        """
        v = self.vertices
        if self.translate != None:
            vtmp = list(map(list,zip(*v)))
            for i in range(len(vtmp[0])):
                vtmp[0][i] += self.translate[0]
                vtmp[1][i] += self.translate[1]
            v = list(zip(*vtmp))
        return path.Path(v,self.codes,closed=True)

    @property
    def polygon2plot(self):
        """
        R.polygon2plot = path.Path(R.polygon.vertices+ np.array([1,1]),R.codes,closed=True)
        """
        return path.Path(self.polygon.vertices+ np.array([1,1]),self.codes,closed=True)

    def corners(self):
        """ returns xmin, ymin, xmax, ymax """
        return min([self.vertices[k][0] for k in range(self.nvertices)])+self.translate[0], \
               min([self.vertices[k][1] for k in range(self.nvertices)])+self.translate[1], \
               max([self.vertices[k][0] for k in range(self.nvertices)])+self.translate[0], \
               max([self.vertices[k][1] for k in range(self.nvertices)])+self.translate[1]

Ancestors

Subclasses

Class variables

var hasclosefit
var hascontour

Instance variables

var polygon

R.polygon = path.Path(R.vertices,R.codes,closed=True)

Expand source code
@property
def polygon(self):
    """
    R.polygon = path.Path(R.vertices,R.codes,closed=True)
    """
    v = self.vertices
    if self.translate != None:
        vtmp = list(map(list,zip(*v)))
        for i in range(len(vtmp[0])):
            vtmp[0][i] += self.translate[0]
            vtmp[1][i] += self.translate[1]
        v = list(zip(*vtmp))
    return path.Path(v,self.codes,closed=True)
var polygon2plot

R.polygon2plot = path.Path(R.polygon.vertices+ np.array([1,1]),R.codes,closed=True)

Expand source code
@property
def polygon2plot(self):
    """
    R.polygon2plot = path.Path(R.polygon.vertices+ np.array([1,1]),R.codes,closed=True)
    """
    return path.Path(self.polygon.vertices+ np.array([1,1]),self.codes,closed=True)

Methods

def corners(self)

returns xmin, ymin, xmax, ymax

Expand source code
def corners(self):
    """ returns xmin, ymin, xmax, ymax """
    return min([self.vertices[k][0] for k in range(self.nvertices)])+self.translate[0], \
           min([self.vertices[k][1] for k in range(self.nvertices)])+self.translate[1], \
           max([self.vertices[k][0] for k in range(self.nvertices)])+self.translate[0], \
           max([self.vertices[k][1] for k in range(self.nvertices)])+self.translate[1]

Inherited members

class overlay (counter=(0, 0), filename='./sandbox/image.jpg', xmin=0, ymin=0, ncolors=4, flipud=True, angle=0, scale=(1, 1))

generic overlay class

generate an overlay from file overlay(counter=(c1,c2),filename="this/is/myimage.jpg",xmin=x0,ymin=y0,colors=4) additional options overlay(…,flipud=True,angle=0,scale=(1,1))

Expand source code
class overlay(coregeometry):
    """ generic overlay class """

    hascontour = False
    hasclosefit = True

    def __init__(self,
                 counter = (0,0),
                 filename="./sandbox/image.jpg",
                 xmin = 0,
                 ymin = 0,
                 ncolors = 4,
                 flipud = True,
                 angle = 0,
                 scale = (1,1)
                 ):
        """ generate an overlay from file
                overlay(counter=(c1,c2),filename="this/is/myimage.jpg",xmin=x0,ymin=y0,colors=4)
                additional options
                    overlay(...,flipud=True,angle=0,scale=(1,1))
        """
        self.name = "over%03d" % counter[1]
        self.kind = "overlay"       # kind of object
        self.alike = "overlay"      # similar object for plotting
        self.beadtype = 1           # bead type
        self.beadtype2 = None       # bead type 2 (alternative beadtype, ratio)
        self.nbeads = 0             # number of beads
        self.ismask = False         # True if beadtype == 0
        self.isplotted = False      # True if plotted
        self.islabelled = False     # True if labelled
        self.resolution = None      # resolution is undefined
        self.hlabel = {'contour':[], 'text':[]}
        self.index = counter[0]
        self.subindex = counter[1]
        self.translate = [0.0,0.0]  # modification used when an object is duplicated
        if scale is None: scale = 1
        if not isinstance(scale,(tuple,list)): scale = (scale,scale)
        self.scale = scale
        if angle is None: angle = 0
        self.angle = angle
        self.flipud = flipud
        if not os.path.isfile(filename):
            raise IOError(f'the file "{filename}" does not exist')
        self.filename = filename
        self.ncolors = ncolors
        self.color = None
        self.colormax = None
        self.original,self.raw,self.im,self.map = self.load()
        self.xmin0 = xmin
        self.ymin0 = ymin
        self.xmax0 = xmin + self.im.shape[1]
        self.ymax0 = ymin + self.im.shape[0]
        self.xcenter0 = (self.xmin+self.xmax)/2
        self.ycenter0 = (self.ymin+self.ymax)/2

    def select(self,color=None,colormax=None,scale=None,angle=None):
        """ select the color index:
                select(color = c) peeks pixels = c
                select(color = c, colormax = cmax) peeks pixels>=c and pixels<=cmax
        """
        if color is None:
            color = self.color
        else:
            self.color = color
        if (colormax is None) and (self.colormax is not None) and (self.colormax > self.color):
                colormax = self.colormax
        else:
            colormax = self.colormax = color
        if isinstance(color,int) and color<len(self.map):
            S = np.logical_and(self.im>=color,self.im<=colormax)
            self.nbeads = np.count_nonzero(S)
            return np.flipud(S) if self.flipud else S
        raise ValueError("color must be an integer lower than %d" % len(self.map))

    def load(self):
        """ load image and process it
                returns the image, the indexed image and its color map (à la Matlab, such as imread)

                note: if the image contains a palette it is used, if not the
                image is converted to an indexed image without dihtering
        """
        I = Image.open(self.filename)
        if self.angle != 0:
            I= I.rotate(self.angle)
        if self.scale[0] * self.scale[1] != 1:
            I = I.resize((round(I.size[0]*self.scale[0]),round(I.size[1]*self.scale[1])))
        palette = I.getpalette()
        if palette is None:
            J=I.convert(mode="P",colors=self.ncolors,palette=Image.Palette.ADAPTIVE)
            palette = J.getpalette()
        else:
            J = I
        p = np.array(palette,dtype="uint8").reshape((int(len(palette)/3),3))
        ncolors = len(p.sum(axis=1).nonzero()[0]);
        if ncolors<self.ncolors:
            print(f"only {ncolors} are available")
        return I,J, np.array(J,dtype="uint8"), p[:ncolors,:]

    def __repr__(self):
        """ display for rectangle class """
        print("%s - %s object" % (self.name, self.kind))
        print(f'\tfilename: "{self.filename}"')
        print("\tangle = %0.4g (original image)" % self.angle)
        print("\tscale = [%0.4g, %0.4g] (original image)" % self.scale)
        print(f"\tncolors = {self.ncolors} (selected={self.color})")
        print("\trange x = [%0.4g %0.4g]" % (self.xmin,self.xmax))
        print("\trange y = [%0.4g %0.4g]" % (self.ymin,self.ymax))
        print("\tcenter = [%0.4g %0.4g]" % (self.xcenter,self.ycenter))
        print("\ttranslate = [%0.4g %0.4g]" % (self.translate[0],self.translate[1]))
        print("note: use the attribute origina,raw to see the raw image")
        return "%s object: %s (beadtype=%d)" % (self.kind,self.name,self.beadtype)

Ancestors

Class variables

var hasclosefit
var hascontour

Methods

def load(self)

load image and process it returns the image, the indexed image and its color map (à la Matlab, such as imread)

note: if the image contains a palette it is used, if not the image is converted to an indexed image without dihtering

Expand source code
def load(self):
    """ load image and process it
            returns the image, the indexed image and its color map (à la Matlab, such as imread)

            note: if the image contains a palette it is used, if not the
            image is converted to an indexed image without dihtering
    """
    I = Image.open(self.filename)
    if self.angle != 0:
        I= I.rotate(self.angle)
    if self.scale[0] * self.scale[1] != 1:
        I = I.resize((round(I.size[0]*self.scale[0]),round(I.size[1]*self.scale[1])))
    palette = I.getpalette()
    if palette is None:
        J=I.convert(mode="P",colors=self.ncolors,palette=Image.Palette.ADAPTIVE)
        palette = J.getpalette()
    else:
        J = I
    p = np.array(palette,dtype="uint8").reshape((int(len(palette)/3),3))
    ncolors = len(p.sum(axis=1).nonzero()[0]);
    if ncolors<self.ncolors:
        print(f"only {ncolors} are available")
    return I,J, np.array(J,dtype="uint8"), p[:ncolors,:]
def select(self, color=None, colormax=None, scale=None, angle=None)

select the color index: select(color = c) peeks pixels = c select(color = c, colormax = cmax) peeks pixels>=c and pixels<=cmax

Expand source code
def select(self,color=None,colormax=None,scale=None,angle=None):
    """ select the color index:
            select(color = c) peeks pixels = c
            select(color = c, colormax = cmax) peeks pixels>=c and pixels<=cmax
    """
    if color is None:
        color = self.color
    else:
        self.color = color
    if (colormax is None) and (self.colormax is not None) and (self.colormax > self.color):
            colormax = self.colormax
    else:
        colormax = self.colormax = color
    if isinstance(color,int) and color<len(self.map):
        S = np.logical_and(self.im>=color,self.im<=colormax)
        self.nbeads = np.count_nonzero(S)
        return np.flipud(S) if self.flipud else S
    raise ValueError("color must be an integer lower than %d" % len(self.map))

Inherited members

class raster (name='default raster', width=100, height=100, dpi=200, fontsize=10, mass=1, volume=1, radius=1.5, contactradius=0.5, velocities=[0, 0, 0], forces=[0, 0, 0], preview=True, previewthumb=(128, 128), filename='')

raster class for LAMMPS SMD

Constructor

R = raster(width=100,height=100...)

Extra properties
    dpi, fontsize

additional properties for R.data()
    scale, center : full scaling
    mass, volume, radius, contactradius, velocities, forces: bead scaling
    filename

List of available properties = default values

           name = "default raster"
          width = 100
         height = 100
            dpi = 200
       fontsize = 10
           mass = 1
         volume = 1
         radius = 1.5
  contactradius = 0.5
     velocities = [0, 0, 0]
         forces = [0, 0, 0]
        preview = True
   previewthumb = (128,128)
       filename = ["%dx%d raster (%s)" % (self.width,self.height,self.name)]

Graphical objects

R.rectangle(xleft,xright,ybottom,ytop [, beadtype=1,mode="lower", angle=0, ismask=False])
R.rectangle(xcenter,ycenter,width,height [, beadtype=1,mode="center", angle=0, ismask=False])
R.circle(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False], resolution=20, shiftangle=0)
R.triangle(...)
R.diamond(...)
R.pentagon(...)
R.hexagon(...)

R.overlay(xleft,xright,filename=="valid/image.ext",color=2,beadtype=1)

note: use fake=True to generate an object without inserting it

R.collection(...) generates collection of existing or fake objects
R.object.copy(...) enables to copy an object

Display methods (precedence affects the result) R.plot() R.show(), R.show(extra="label",contour=True,what="beadtype" or "objindex") R.show(extra="labels") R.list() R.get("object") R.print() R.label("object") R.unlabel("object") R.figure() R.newfigure(dpi=300)

R.numeric()
R.string(), R.string(what="beadtype" or "objindex"))
R.names()
R.print()

Clear and delete R.clear() R.clear("all") R.delete("object")

Copy objects R.copyalongpath(....) R.scatter()

Generate an input data object X = R.data() or X=R.data(scale=(1,1),center=(0,0)) X.write("/tmp/myfile")

initialize raster

Expand source code
class raster:
    """ raster class for LAMMPS SMD

    Constructor

        R = raster(width=100,height=100...)

        Extra properties
            dpi, fontsize

        additional properties for R.data()
            scale, center : full scaling
            mass, volume, radius, contactradius, velocities, forces: bead scaling
            filename

        List of available properties = default values

                   name = "default raster"
                  width = 100
                 height = 100
                    dpi = 200
               fontsize = 10
                   mass = 1
                 volume = 1
                 radius = 1.5
          contactradius = 0.5
             velocities = [0, 0, 0]
                 forces = [0, 0, 0]
                preview = True
           previewthumb = (128,128)
               filename = ["%dx%d raster (%s)" % (self.width,self.height,self.name)]

    Graphical objects

        R.rectangle(xleft,xright,ybottom,ytop [, beadtype=1,mode="lower", angle=0, ismask=False])
        R.rectangle(xcenter,ycenter,width,height [, beadtype=1,mode="center", angle=0, ismask=False])
        R.circle(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False], resolution=20, shiftangle=0)
        R.triangle(...)
        R.diamond(...)
        R.pentagon(...)
        R.hexagon(...)

        R.overlay(xleft,xright,filename=="valid/image.ext",color=2,beadtype=1)

        note: use fake=True to generate an object without inserting it

        R.collection(...) generates collection of existing or fake objects
        R.object.copy(...) enables to copy an object

    Display methods (precedence affects the result)
        R.plot()
        R.show(), R.show(extra="label",contour=True,what="beadtype" or "objindex")
        R.show(extra="labels")
        R.list()
        R.get("object")
        R.print()
        R.label("object")
        R.unlabel("object")
        R.figure()
        R.newfigure(dpi=300)

        R.numeric()
        R.string(), R.string(what="beadtype" or "objindex"))
        R.names()
        R.print()

    Clear and delete
        R.clear()
        R.clear("all")
        R.delete("object")

    Copy objects
        R.copyalongpath(....)
        R.scatter()

    Generate an input data object
        X = R.data() or X=R.data(scale=(1,1),center=(0,0))
        X.write("/tmp/myfile")

    """

    # CONSTRUCTOR ----------------------------
    def __init__(self,
                 # raster properties
                 name="default raster",
                 width=100,
                 height=100,
                 # printing and display
                 dpi=200,
                 fontsize=10,
                 # for data conversion
                 mass=1,
                 volume=1,
                 radius=1.5,
                 contactradius=0.5,
                 velocities=[0,0,0],
                 forces=[0,0,0],
                 preview=True,
                 previewthumb = (128,128),
                 filename=""
                 ):

        """ initialize raster """
        self.name = name
        self.width = width
        self.height = height
        self.xcenter= width/2
        self.ycenter = height/2
        self.objects = {}
        self.nobjects = 0    # total number of objects (alive)
        self.nbeads = 0
        self.counter = { "triangle":0,
                          "diamond":0,
                        "rectangle":0,
                         "pentagon":0,
                          "hexagon":0,
                           "circle":0,
                          "overlay":0,
                       "collection":0,
                              "all":0
                    }
        self.fontsize = 10   # font size for labels
        self.imbead = np.zeros((height,width),dtype=np.int8)
        self.imobj = np.zeros((height,width),dtype=np.int8)
        self.hfig = [] # figure handle
        self.dpi = dpi
        # generic SMD properties (to be rescaled)
        self.volume = volume
        self.mass = mass
        self.radius = radius
        self.contactradius = contactradius
        self.velocities = velocities
        self.forces =forces

        self.preview = preview
        self.previewthumb = previewthumb

        if filename == "":
            self.filename = ["%dx%d raster (%s)" % (self.width,self.height,self.name)]
        else:
            self.filename = filename


    # DATA ----------------------------
    def data(self,scale=(1,1),center=(0,0),maxtype=None,hexpacking=None):
        """
            return a pizza.data object
                data()
                data(scale=(scalex,scaley),
                     center=(centerx,centery),
                     maxtype=number,
                     hexpacking=(0.5,0))
        """
        if not isinstance(scale,tuple) or len(scale)!=2:
            raise ValueError("scale must be tuple (scalex,scaley)")
        if not isinstance(center,tuple) or len(scale)!=2:
            raise ValueError("center must be tuple (centerx,centery)")
        scalez = np.sqrt(scale[0]*scale[1])
        scalevol = scale[0]*scale[1] #*scalez
        maxtypeheader = self.count()[-1][0] if maxtype is None else maxtype
        n = self.length()
        i,j = self.imbead.nonzero() # x=j+0.5 y=i+0.5
        x = (j+0.5-center[0])*scale[0]
        y = (i+0.5-center[1])*scale[1]
        if hexpacking is not None:
            if isinstance(hexpacking,tuple) and len(hexpacking)==2:
                for k in range(len(i)):
                    if i[k] % 2:
                        x[k] = (j[k]+0.5+hexpacking[1]-center[0])*scale[0]
                    else:
                        x[k] = (j[k]+0.5+hexpacking[0]-center[0])*scale[0]
            else:
                raise ValueError("hexpacking should be a tuple (shiftodd,shifteven)")
        X = data3()  # empty pizza.data3.data object
        X.title = self.name + "(raster)"
        X.headers = {'atoms': n,
                      'atom types': maxtypeheader,
                      'xlo xhi': ((0.0-center[0])*scale[0], (self.width-0.0-center[0])*scale[0]),
                      'ylo yhi': ((0.0-center[1])*scale[1], (self.height-0.0-center[1])*scale[1]),
                      'zlo zhi': (0, scalez)}
        # [ATOMS] section
        X.append('Atoms',list(range(1,n+1)),True,"id")       # id
        X.append('Atoms',self.imbead[i,j],True,"type")       # Type
        X.append('Atoms',1,True,"mol")                       # mol
        X.append('Atoms',self.volume*scalevol,False,"c_vol") # c_vol
        X.append('Atoms',self.mass*scalevol,False,"mass")    # mass
        X.append('Atoms',self.radius*scalez,False,"radius")         # radius
        X.append('Atoms',self.contactradius*scalez,False,"c_contact_radius") # c_contact_radius
        X.append('Atoms',x,False,"x")        # x
        X.append('Atoms',y,False,"y")        # y
        X.append('Atoms',0,False,"z")        # z
        X.append('Atoms',x,False,"x0")       # x0
        X.append('Atoms',y,False,"y0")       # y0
        X.append('Atoms',0,False,"z0")       # z0
        # [VELOCITIES] section
        X.append('Velocities',list(range(1,n+1)),True,"id") # id
        X.append('Velocities',self.velocities[0],False,"vx") # vx
        X.append('Velocities',self.velocities[1],False,"vy") # vy
        X.append('Velocities',self.velocities[2],False,"vz") # vz
        # pseudo-filename
        X.flist = self.filename
        return X

    # LENGTH ----------------------------
    def length(self,t=None,what="beadtype"):
        """ returns the total number of beads length(type,"beadtype") """
        if what == "beadtype":
            num = self.imbead
        elif what == "objindex":
            num = self.imobj
        else:
            raise ValueError('"beadtype" and "objindex" are the only acceptable values')
        if t==None:
            return np.count_nonzero(num>0)
        else:
            return np.count_nonzero(num==t)

    # NUMERIC ----------------------------
    def numeric(self):
        """ retrieve the image as a numpy.array """
        return self.imbead, self.imobj

    # STRING ----------------------------
    def string(self,what="beadtype"):
        """ convert the image as ASCII strings """
        if what == "beadtype":
            num = np.flipud(duplicate(self.imbead))
        elif what == "objindex":
            num = np.flipud(duplicate(self.imobj))
        else:
            raise ValueError('"beadtype" and "objindex" are the only acceptable values')
        num[num>0] = num[num>0] + 65
        num[num==0] = 32
        num = list(num)
        return ["".join(map(chr,x)) for x in num]

    # GET -----------------------------
    def get(self,name):
        """ returns the object """
        if name in self.objects:
            return self.objects[name]
        else:
            raise ValueError('the object "%s" does not exist, use list()' % name)

    # GETATTR --------------------------
    def __getattr__(self,key):
        """ get attribute override """
        return self.get(key)

    # CLEAR ----------------------------
    def clear(self,what="nothing"):
        """ clear the plotting area, use clear("all")) to remove all objects """
        self.imbead = np.zeros((self.height,self.width),dtype=np.int8)
        self.imobj = np.zeros((self.height,self.width),dtype=np.int8)
        for o in self.names():
            if what=="all":
                self.delete(o)
            else:
                self.objects[o].isplotted = False
                self.objects[o].islabelled = False
                if not self.objects[o].ismask:
                    self.nbeads -= self.objects[o].nbeads
                self.objects[o].nbeads = 0  # number of beads (plotted)
        self.figure()
        plt.cla()
        self.show()

    # DISP method ----------------------------
    def __repr__(self):
        """ display method """
        ctyp = self.count() # count objects (not beads)
        print("-"*40)
        print('RASTER area "%s" with %d objects' % (self.name,self.nobjects))
        print("-"*40)
        print("<- grid size ->")
        print("\twidth: %d" % self.width)
        print("\theight: %d" % self.height)
        print("<- bead types ->")
        nbt = 0
        if len(ctyp):
            for i,c in enumerate(ctyp):
                nb = self.length(c[0])
                nbt += nb
                print("\t type=%d (%d objects, %d beads)" % (c[0],c[1],nb))
        else:
            print("\tno bead assigned")
        print("-"*40)
        if self.preview and len(self)>0 and self.max>0:
            if PILavailable:
                display(self.torgb("beadtype",self.previewthumb))
                display(self.torgb("objindex",self.previewthumb))
            else:
                print("no PIL image")
        return "RASTER AREA %d x %d with %d objects (%d types, %d beads)." % \
        (self.width,self.height,self.nobjects,len(ctyp),nbt)

    # TORGB method ----------------------------
    def torgb(self,what="beadtype",thumb=None):
        """ converts bead raster to image
                rgb = raster.torgb(what="beadtype")
                thumbnail = raster.torgb(what="beadtype",(128,128))
                use: rgb.save("/path/filename.png") for saving

            what = "beadtype" or "objindex"
        """
        if what=="beadtype":
            rgb = ind2rgb(self.imbead,ncolors=self.max+1)
        elif what == "objindex":
            rgb = ind2rgb(self.imobj,ncolors=len(self)+1)
        else:
            raise ValueError('"beadtype" and "objindex" are the only acceptable values')
        if thumb is not None: rgb.thumbnail(thumb)
        return rgb

    # COUNT method ----------------------------
    def count(self):
        """ count objects by type """
        typlist = []
        for  o in self.names():
            if isinstance(self.objects[o].beadtype,list):
                typlist += self.objects[o].beadtype
            else:
                typlist.append(self.objects[o].beadtype)
        utypes = list(set(typlist))
        c = []
        for t in utypes:
            c.append((t,typlist.count(t)))
        return c

    # max method ------------------------------
    @property
    def max(self):
        """ max bead type """
        typlist = []
        for  o in self.names():
            if isinstance(self.objects[o].beadtype,list):
                typlist += self.objects[o].beadtype
            else:
                typlist.append(self.objects[o].beadtype)
        return max(typlist)

    # len method ------------------------------
    def __len__(self):
        """ len method """
        return len(self.objects)

    # NAMES method ----------------------------
    def names(self):
        """ return the names of objects sorted as index """
        namesunsorted=namessorted=list(self.objects.keys())
        nobj = len(namesunsorted)
        for iobj in range(nobj):
            namessorted[self.objects[namesunsorted[iobj]].index-1] = namesunsorted[iobj]
        return namessorted

    # LIST method ----------------------------
    def list(self):
        """ list objects """
        fmt = "%%%ss:" % max(10,max([len(n) for n in self.names()])+2)
        print("RASTER with %d objects" % self.nobjects)
        for o in self.objects.keys():
            print(fmt % self.objects[o].name,"%-10s" % self.objects[o].kind,
                  "(beadtype=%d,object index=[%d,%d], n=%d)" % \
                      (self.objects[o].beadtype,
                       self.objects[o].index,
                       self.objects[o].subindex,
                       self.objects[o].nbeads))

    # EXIST method ----------------------------
    def exist(self,name):
        """ exist object """
        return name in self.objects

    # DELETE method ----------------------------
    def delete(self,name):
        """ delete object """
        if name in self.objects:
            if not self.objects[name].ismask:
                self.nbeads -= self.objects[name].nbeads
            del self.objects[name]
            self.nobjects -= 1
        else:
            raise ValueError("%d is not a valid name (use list()) to list valid objects" % name)
        self.clear()
        self.plot()
        self.show(extra="label")

    # VALID method
    def valid(self,x,y):
        """ validation of coordinates """
        return min(self.width,max(0,round(x))),min(self.height,max(0,round(y)))

    # frameobj method
    def frameobj(self,obj):
        """ frame coordinates by taking into account translation """
        if obj.hasclosefit:
            envelope = 0
        else:
            envelope = 1
        xmin, ymin = self.valid(obj.xmin-envelope, obj.ymin-envelope)
        xmax, ymax = self.valid(obj.xmax+envelope, obj.ymax+envelope)
        return xmin, ymin, xmax, ymax

    # RECTANGLE ----------------------------
    def rectangle(self,a,b,c,d,
                  mode="lowerleft",name=None,angle=0,
                  beadtype=None,ismask=False,fake=False,beadtype2=None):
        """
        rectangle object
            rectangle(xleft,xright,ybottom,ytop [, beadtype=1,mode="lower", angle=0, ismask=False])
            rectangle(xcenter,ycenter,width,height [, beadtype=1,mode="center", angle=0, ismask=False])

            use rectangle(...,beadtype2=(type,ratio)) to salt an object with beads
            from another type and with a given ratio
        """
        # object creation
        self.counter["all"] += 1
        self.counter["rectangle"] += 1
        R = Rectangle((self.counter["all"],self.counter["rectangle"]))
        if (name != None) and (name != ""):
            if self.exist(name):
                print('RASTER:: the object "%s" is overwritten',name)
                self.delete(name)
            R.name = name
        else:
            name = R.name
        if beadtype is not None: R.beadtype = int(np.floor(beadtype))
        if beadtype2 is not None:
            if not isinstance(beadtype2,tuple) or len(beadtype2)!=2:
                raise AttributeError("beadtype2 must be a tuple (beadtype,ratio)")
        R.beadtype2 = beadtype2
        if ismask: R.beadtype = 0
        R.ismask = R.beadtype==0
        # build vertices
        if mode == "lowerleft":
            R.xcenter0 = (a+b)/2
            R.ycenter0 = (c+d)/2
            R.vertices = [
                _rotate(a,c,R.xcenter0,R.ycenter0,angle),
                _rotate(b,c,R.xcenter0,R.ycenter0,angle),
                _rotate(b,d,R.xcenter0,R.ycenter0,angle),
                _rotate(a,d,R.xcenter0,R.ycenter0,angle),
                _rotate(a,c,R.xcenter0,R.ycenter0,angle)
                ] # anti-clockwise, closed (last point repeated)
        elif mode == "center":
            R.xcenter0 = a
            R.ycenter0 = b
            R.vertices = [
                _rotate(a-c/2,b-d/2,R.xcenter0,R.ycenter0,angle),
                _rotate(a+c/2,b-d/2,R.xcenter0,R.ycenter0,angle),
                _rotate(a+c/2,b+d/2,R.xcenter0,R.ycenter0,angle),
                _rotate(a-c/2,b+d/2,R.xcenter0,R.ycenter0,angle),
                _rotate(a-c/2,b-d/2,R.xcenter0,R.ycenter0,angle)
                ]
        else:
            raise ValueError('"%s" is not a recognized mode, use "lowerleft" (default) and "center" instead')
        # build path object and range
        R.codes =    [ path.Path.MOVETO,
                     path.Path.LINETO,
                     path.Path.LINETO,
                     path.Path.LINETO,
                     path.Path.CLOSEPOLY
                    ]
        R.nvertices = len(R.vertices)-1
        R.xmin0, R.ymin0, R.xmax0, R.ymax0 = R.corners()
        R.xmin0, R.ymin0 = self.valid(R.xmin0,R.ymin0)
        R.xmax0, R.ymax0 = self.valid(R.xmax0,R.ymax0)
        R.angle = angle
        # store the object (if not fake)
        if fake:
            self.counter["all"] -= 1
            self.counter["rectangle"] -= 1
            return R
        else:
            self.objects[name] = R
            self.nobjects += 1
            return None


    # CIRCLE ----------------------------
    def circle(self,xc,yc,radius,
                  name=None,shaperatio=1,angle=0,beadtype=None,ismask=False,
                  resolution=20,shiftangle=0,fake=False,beadtype2=None):
        """
        circle object (or any regular polygon)
            circle(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False], resolution=20, shiftangle=0)
            use circle(...,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio
        """
        # object creation
        self.counter["all"] += 1
        if resolution==3:
            typ = "triangle"
            self.counter["triangle"] += 1
            G = Triangle((self.counter["all"],self.counter["triangle"]))
        elif resolution==4:
            typ = "diamond"
            self.counter["diamond"] += 1
            G = Diamond((self.counter["all"],self.counter["diamond"]))
        elif resolution==5:
            typ = "pentagon"
            self.counter["pentagon"] += 1
            G = Pentagon((self.counter["all"],self.counter["pentagon"]))
        elif resolution==6:
            typ = "hexagon"
            self.counter["hexagon"] += 1
            G = Hexagon((self.counter["all"],self.counter["hexagon"]))
        else:
            typ = "circle"
            self.counter["circle"] += 1
            G = Circle((self.counter["all"],self.counter["circle"]),resolution=resolution)
        if (name != None) and (name != ""):
            if self.exist(name):
                print('RASTER:: the object "%s" is overwritten',name)
                self.delete(name)
            G.name = name
        else:
            name = G.name
        if beadtype is not None: G.beadtype = int(np.floor(beadtype))
        if beadtype2 is not None:
            if not isinstance(beadtype2,tuple) or len(beadtype2)!=2:
                raise AttributeError("beadtype2 must be a tuple (beadtype,ratio)")
        G.beadtype2 = beadtype2
        if ismask: G.beadtype = 0
        G.ismask = G.beadtype==0
        # build vertices
        th = np.linspace(0,2*np.pi,G.resolution+1) +shiftangle*np.pi/180
        xgen = xc + radius * np.cos(th)
        ygen = yc + radius * shaperatio * np.sin(th)
        G.xcenter0, G.ycenter0, G.radius = xc, yc, radius
        G.vertices, G.codes = [], []
        for i in range(G.resolution+1):
            G.vertices.append(_rotate(xgen[i],ygen[i],xc,yc,angle))
            if i==0:
                G.codes.append(path.Path.MOVETO)
            elif i==G.resolution:
                G.codes.append(path.Path.CLOSEPOLY)
            else:
                G.codes.append(path.Path.LINETO)
        G.nvertices = len(G.vertices)-1
        # build path object and range
        G.xmin0, G.ymin0, G.xmax0, G.ymax0 = G.corners()
        G.xmin0, G.ymin0 = self.valid(G.xmin0,G.ymin0)
        G.xmax0, G.ymax0 = self.valid(G.xmax0,G.ymax0)
        G.angle, G.shaperatio = angle, shaperatio
        # store the object
        if fake:
            self.counter["all"] -= 1
            self.counter[typ] -= 1
            return G
        else:
            self.objects[name] = G
            self.nobjects += 1
            return None

    # OVERLAY -------------------------------
    def overlay(self,x0,y0,
                name = None,
                filename = None,
                color = 1,
                colormax = None,
                ncolors = 4,
                beadtype = None,
                beadtype2 = None,
                ismask = False,
                fake = False,
                flipud = True,
                angle = 0,
                scale= (1,1)
                ):
        """
            overlay object: made from an image converted to nc colors
            the object is made from the level ranged between ic and jc (bounds included)
            note: if palette found, no conversion is applied

            O = overlay(x0,y0,filename="/this/is/my/image.png",ncolors=nc,color=ic,colormax=jc,beadtype=b)
            O = overlay(...angle=0,scale=(1,1)) to induce rotation and change of scale
            O = overlay(....ismask=False,fake=False)

            note use overlay(...flipud=False) to prevent image fliping (standard)

            Outputs:
                O.original original image (PIL)
                O.raw image converted to ncolors if needed

        """
        if filename is None or filename=="":
            raise ValueError("filename is required (valid image)")
        O = overlay(counter=(self.counter["all"]+1,self.counter["overlay"]+1),
                    filename = filename,
                    xmin = x0,
                    ymin = y0,
                    ncolors = ncolors,
                    flipud = flipud,
                    angle = angle,
                    scale = scale
                    )
        O.select(color=color, colormax=colormax)
        if (name is not None) and (name !=""):
            if self.exist(name):
                print('RASTER:: the object "%s" is overwritten',name)
                self.delete(name)
            O.name = name
        else:
            name = O.name
        if beadtype is not None: O.beadtype = int(np.floor(beadtype))
        if beadtype2 is not None:
            if not isinstance(beadtype2,tuple) or len(beadtype2)!=2:
                raise AttributeError("beadtype2 must be a tuple (beadtype,ratio)")
        O.beadtype2 = beadtype2
        if ismask: O.beadtype = 0
        O.ismask = O.beadtype==0
        self.counter["all"] += 1
        self.counter["overlay"] += 1
        if fake:
            self.counter["all"] -= 1
            self.counter["overlay"] -= 1
            return O
        else:
            self.objects[name] = O
            self.nobjects += 1
            return None


    # COLLECTION ----------------------------
    def collection(self,*obj,
                   name=None,
                   beadtype=None,
                   ismask=None,
                   translate = [0.0,0.0],
                   fake = False,
                   **kwobj):
        """
            collection of objects:
                collection(draftraster,name="mycollect" [,beadtype=1,ismask=True]
                collection(name="mycollect",newobjname1 = obj1, newobjname2 = obj2...)
        """
        self.counter["all"] += 1
        self.counter["collection"] += 1
        C = Collection((self.counter["all"],self.counter["collection"]))
        # name
        if name != None:
            if self.exist(name):
                print('RASTER:: the object "%s" is overwritten',name)
                self.delete(name)
            C.name = name
        else:
            name = C.name
        # build the collection
        C.collection = collection(*obj,**kwobj)
        xmin = ymin = +1e99
        xmax = ymax = -1e99
        # apply modifications (beadtype, ismask)
        for o in C.collection.keys():
            tmp = C.collection.getattr(o)
            tmp.translate[0] += translate[0]
            tmp.translate[1] += translate[1]
            xmin, xmax = min(xmin,tmp.xmin), max(xmax,tmp.xmax)
            ymin, ymax = min(ymin,tmp.ymin), max(ymax,tmp.ymax)
            if beadtype != None: tmp.beadtype = beadtype
            if ismask != None: tmp.ismask = ismask
            C.collection.setattr(o,tmp)
        C.xmin, C.xmax, C.ymin, C.ymax = xmin, xmax, ymin, ymax
        C.width, C.height = xmax-xmin, ymax-ymin
        if fake:
            return C
        else:
            self.objects[name] = C
            self.nobjects += 1
            return None

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # =========== pseudo methods connected to circle() ===========
    # TRIANGLE, DIAMOND, PENTAGON, HEXAGON, -----------------------
    def triangle(self,xc,yc,radius,name=None,
                 shaperatio=1,angle=0,beadtype=None,ismask=False,shiftangle=0,fake=False,beadtype2=None):
        """
        triangle object
            triangle(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False]
            use triangle(...,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio
        """
        self.circle(xc,yc,radius,name=name,shaperatio=shaperatio,resolution=3,
           angle=angle,beadtype=beadtype,ismask=ismask,shiftangle=0,fake=fake,beadtype2=beadtype2)

    def diamond(self,xc,yc,radius,name=None,
                 shaperatio=1,angle=0,beadtype=None,ismask=False,shiftangle=0,fake=False,beadtype2=None):
        """
        diamond object
            diamond(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False]
            use diamond(...,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio
        """
        self.circle(xc,yc,radius,name=name,shaperatio=shaperatio,resolution=4,
            angle=angle,beadtype=beadtype,ismask=ismask,shiftangle=0,fake=fake,beadtype2=beadtype2)

    def pentagon(self,xc,yc,radius,name=None,
                 shaperatio=1,angle=0,beadtype=None,ismask=False,shiftangle=0,fake=False,beadtype2=None):
        """
        pentagon object
            pentagon(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False]
            use pentagon(...,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio
        """
        self.circle(xc,yc,radius,name=name,shaperatio=shaperatio,resolution=5,
            angle=angle,beadtype=beadtype,ismask=ismask,shiftangle=0,fake=fake,beadtype2=beadtype2)

    def hexagon(self,xc,yc,radius,name=None,
                 shaperatio=1,angle=0,beadtype=None,ismask=False,shiftangle=0,fake=False,beadtype2=None):
        """
        hexagon object
            hexagon(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False]
            use hexagon(...,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio
        """
        self.circle(xc,yc,radius,name=name,shaperatio=shaperatio,resolution=6,
            angle=angle,beadtype=beadtype,ismask=ismask,shiftangle=0,fake=fake,beadtype2=beadtype2)

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    # label method ----------------------------
    def label(self,name,**fmt):
        """
            label:
                label(name [, contour=True,edgecolor="orange",facecolor="none",linewidth=2, ax=plt.gca()])
        """
        self.figure()
        if name in self.objects:
            if not self.objects[name].islabelled:
                if self.objects[name].alike == "mixed":
                    for o in self.objects[name].collection:
                        self.labelobj(o,**fmt)
                else:
                    self.labelobj(self.objects[name],**fmt)
                plt.show()
                self.objects[name].islabelled = True
        else:
            raise ValueError("%d is not a valid name (use list()) to list valid objects" % name)

    # label object method -----------------------------
    def labelobj(self,obj,contour=True,edgecolor="orange",facecolor="none",linewidth=2,ax=plt.gca()):
        """
            labelobj:
                labelobj(obj [, contour=True,edgecolor="orange",facecolor="none",linewidth=2, ax=plt.gca()])
        """
        if contour: contour = obj.hascontour # e.g. overlays do not have contour

        if contour:
            patch = patches.PathPatch(obj.polygon2plot,
                                      facecolor=facecolor,
                                      edgecolor=edgecolor,
                                      lw=linewidth)
            obj.hlabel["contour"] = ax.add_patch(patch)
        else:
            obj.hlabel["contour"] = None
        obj.hlabel["text"] = \
        plt.text(obj.xcenter,
                 obj.ycenter,
                 "%s\n(t=$%d$,$n_p$=%d)" % (obj.name, obj.beadtype,obj.nbeads),
                 horizontalalignment = "center",
                 verticalalignment = "center_baseline",
                 fontsize=self.fontsize
                 )


    def unlabel(self,name):
        """ unlabel """
        if name in self.objects:
            if  self.objects[name].islabelled:
                self.objects[name].hlabel["contour"].remove()
                self.objects[name].hlabel["text"].remove()
                self.objects[name].hlabel = {'contour':[], 'text':[]}
                self.objects[name].islabelled = False
        else:
            raise ValueError("%d is not a valid name (use list()) to list valid objects" % name)


    # PLOT method ----------------------------
    def plot(self):
        """ plot """
        for o in self.objects:
            if not self.objects[o].isplotted:
                if self.objects[o].alike == "mixed":
                    for o2 in self.objects[o].collection:
                        self.plotobj(o2)
                else:
                    self.plotobj(self.objects[o])
                # store it as plotted
                self.objects[o].isplotted = True
                if not self.objects[o].ismask:
                    self.nbeads += self.objects[o].nbeads


    # PLOTobj method  -----------------------
    def plotobj(self,obj):
        """ plotobj(obj) """
        if obj.alike == "circle":
            xmin, ymin, xmax, ymax = self.frameobj(obj)
            j,i = np.meshgrid(range(xmin,xmax), range(ymin,ymax))
            points = np.vstack((j.flatten(),i.flatten())).T
            npoints = points.shape[0]
            inside = obj.polygon.contains_points(points)
            if obj.beadtype2 is None:          # -- no salting --
                for k in range(npoints):
                    if inside[k] and \
                        points[k,0]>=0 and \
                        points[k,0]<self.width and \
                        points[k,1]>=0 and \
                        points[k,1]<self.height:
                            self.imbead[points[k,1],points[k,0]] = obj.beadtype
                            self.imobj[points[k,1],points[k,0]] = obj.index
                            obj.nbeads += 1
            else:
                for k in range(npoints):       # -- salting --
                    if inside[k] and \
                        points[k,0]>=0 and \
                        points[k,0]<self.width and \
                        points[k,1]>=0 and \
                        points[k,1]<self.height:
                            if np.random.rand()<obj.beadtype2[1]:
                                self.imbead[points[k,1],points[k,0]] = obj.beadtype2[0]
                            else:
                                self.imbead[points[k,1],points[k,0]] = obj.beadtype
                            self.imobj[points[k,1],points[k,0]] = obj.index
                            obj.nbeads += 1

        elif obj.alike == "overlay":
            xmin, ymin, xmax, ymax = self.frameobj(obj)
            j,i = np.meshgrid(range(xmin,xmax), range(ymin,ymax))
            points = np.vstack((j.flatten(),i.flatten())).T
            npoints = points.shape[0]
            inside = obj.select()
            if obj.beadtype2 is None:          # -- no salting --
                for k in range(npoints):
                    if inside[ points[k,1]-ymin, points[k,0]-xmin ] and \
                        points[k,0]>=0 and \
                        points[k,0]<self.width and \
                        points[k,1]>=0 and \
                        points[k,1]<self.height:
                            self.imbead[points[k,1],points[k,0]] = obj.beadtype
                            self.imobj[points[k,1],points[k,0]] = obj.index
                            obj.nbeads += 1
            else:
                for k in range(npoints):       # -- salting --
                    if inside[ points[k,0]-ymin, points[k,0]-xmin ] and \
                        points[k,0]>=0 and \
                        points[k,0]<self.width and \
                        points[k,1]>=0 and \
                        points[k,1]<self.height:
                            if np.random.rand()<obj.beadtype2[1]:
                                self.imbead[points[k,1],points[k,0]] = obj.beadtype2[0]
                            else:
                                self.imbead[points[k,1],points[k,0]] = obj.beadtype
                            self.imobj[points[k,1],points[k,0]] = obj.index
                            obj.nbeads += 1

        else:
            raise ValueError("This object type is notimplemented")

    # SHOW method ----------------------------
    def show(self,extra="none",contour=True,what="beadtype"):
        """ show method: show(extra="label",contour=True,what="beadtype") """
        self.figure()
        if what=="beadtype":
            imagesc(self.imbead)
        elif what == "objindex":
            imagesc(self.imobj)
        else:
            raise ValueError('"beadtype" and "objindex" are the only acceptable values')
        if extra == "label":
            ax = plt.gca()
            for o in self.names():
                if not self.objects[o].ismask:
                    self.label(o,ax=ax,contour=contour)
            ax.set_title("raster area: %s \n (n=%d, $n_p$=%d)" %\
                      (self.name,self.length(),self.nbeads) )
            plt.show()

    # SHOW method ----------------------------
    def print(self,what="beadtype"):
        """ print method """
        txt = self.string(what=what)
        for i in range(len(txt)):
            print(txt[i],end="\n")


    # FIGURE method ----------------------------
    def figure(self):
        """ set the current figure """
        if self.hfig==[] or not plt.fignum_exists(self.hfig.number):
            self.newfigure()
        plt.figure(self.hfig.number)

    # NEWFIGURE method ----------------------------
    def newfigure(self):
        """ create a new figure (dpi=200) """
        self.hfig = plt.figure(dpi=self.dpi)

    # COPY OBJECT ALONG a contour -----------------
    def copyalongpath(self,obj,
                      name="path",
                  beadtype=None,
                      path=linear,
                      xmin=10,
                      ymin=10,
                      xmax=70,
                      ymax=90,
                         n=7,
                      USER=struct()):
        """

        The method enable to copy an existing object (from the current raster,
        from another raster or a fake object) amp,g

        Parameters
        ----------
        obj : real or fake object
            the object to be copied.
        name : string, optional
            the name of the object collection. The default is "path".
        beadtype : integet, optional
            type of bead (can override existing value). The default is None.
        path : function, optional
            parametric function returning x,y. The default is linear.
            x is between xmin and xmax, and y between ymin, ymax
        xmin : int64 or float, optional
            left x corner position. The default is 10.
        ymin : int64 or float, optional
            bottom y corner position. The default is 10.
        xmax : int64 or float, optional
            right x corner position. The default is 70.
        ymax : int64 or float, optional
            top y corner position. The default is 90.
        n : integet, optional
            number of copies. The default is 7.
     USER : structure to pass specific parameters

        Returns
        -------
        None.

        """
        if not isinstance(USER,struct):
            raise TypeError("USER should be a structure")
        x,y = path(xmin=xmin,ymin=ymin,xmax=xmax,ymax=ymax,n=n,USER=USER)
        btyp = obj.beadtype if beadtype == None else beadtype
        collect = {}
        for i in range(n):
            nameobj = "%s_%s_%02d" % (name,obj.name,i)
            x[i], y[i] = self.valid(x[i], y[i])
            translate = [ x[i]-obj.xcenter, y[i]-obj.ycenter ]
            collect[nameobj] = obj.copy(translate=translate,
                                        name=nameobj,
                                        beadtype=btyp)
        self.collection(**collect,name=name)


    # SCATTER -------------------------------
    def scatter(self,
                 E,
                 name="emulsion",
                 beadtype=None,
                 ismask = False
                 ):
        """


        Parameters
        ----------
        E : scatter or emulsion object
            codes for x,y and r.
        name : string, optional
            name of the collection. The default is "emulsion".
        beadtype : integer, optional
            for all objects. The default is 1.
        ismask : logical, optional
            Set it to true to force a mask. The default is False.

        Raises
        ------
        TypeError
            Return an error of the object is not a scatter type.

        Returns
        -------
        None.

        """
        if isinstance(E,scatter):
            collect = {}
            for i in range(E.n):
                b = E.beadtype[i] if beadtype==None else beadtype
                nameobj = "glob%02d" % i
                collect[nameobj] = self.circle(E.x[i],E.y[i],E.r[i],
                            name=nameobj,beadtype=b,ismask=ismask,fake=True)
            self.collection(**collect,name=name)
        else:
            raise TypeError("the first argument must be an emulsion object")

Instance variables

var max

max bead type

Expand source code
@property
def max(self):
    """ max bead type """
    typlist = []
    for  o in self.names():
        if isinstance(self.objects[o].beadtype,list):
            typlist += self.objects[o].beadtype
        else:
            typlist.append(self.objects[o].beadtype)
    return max(typlist)

Methods

def circle(self, xc, yc, radius, name=None, shaperatio=1, angle=0, beadtype=None, ismask=False, resolution=20, shiftangle=0, fake=False, beadtype2=None)

circle object (or any regular polygon) circle(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False], resolution=20, shiftangle=0) use circle(…,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio

Expand source code
def circle(self,xc,yc,radius,
              name=None,shaperatio=1,angle=0,beadtype=None,ismask=False,
              resolution=20,shiftangle=0,fake=False,beadtype2=None):
    """
    circle object (or any regular polygon)
        circle(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False], resolution=20, shiftangle=0)
        use circle(...,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio
    """
    # object creation
    self.counter["all"] += 1
    if resolution==3:
        typ = "triangle"
        self.counter["triangle"] += 1
        G = Triangle((self.counter["all"],self.counter["triangle"]))
    elif resolution==4:
        typ = "diamond"
        self.counter["diamond"] += 1
        G = Diamond((self.counter["all"],self.counter["diamond"]))
    elif resolution==5:
        typ = "pentagon"
        self.counter["pentagon"] += 1
        G = Pentagon((self.counter["all"],self.counter["pentagon"]))
    elif resolution==6:
        typ = "hexagon"
        self.counter["hexagon"] += 1
        G = Hexagon((self.counter["all"],self.counter["hexagon"]))
    else:
        typ = "circle"
        self.counter["circle"] += 1
        G = Circle((self.counter["all"],self.counter["circle"]),resolution=resolution)
    if (name != None) and (name != ""):
        if self.exist(name):
            print('RASTER:: the object "%s" is overwritten',name)
            self.delete(name)
        G.name = name
    else:
        name = G.name
    if beadtype is not None: G.beadtype = int(np.floor(beadtype))
    if beadtype2 is not None:
        if not isinstance(beadtype2,tuple) or len(beadtype2)!=2:
            raise AttributeError("beadtype2 must be a tuple (beadtype,ratio)")
    G.beadtype2 = beadtype2
    if ismask: G.beadtype = 0
    G.ismask = G.beadtype==0
    # build vertices
    th = np.linspace(0,2*np.pi,G.resolution+1) +shiftangle*np.pi/180
    xgen = xc + radius * np.cos(th)
    ygen = yc + radius * shaperatio * np.sin(th)
    G.xcenter0, G.ycenter0, G.radius = xc, yc, radius
    G.vertices, G.codes = [], []
    for i in range(G.resolution+1):
        G.vertices.append(_rotate(xgen[i],ygen[i],xc,yc,angle))
        if i==0:
            G.codes.append(path.Path.MOVETO)
        elif i==G.resolution:
            G.codes.append(path.Path.CLOSEPOLY)
        else:
            G.codes.append(path.Path.LINETO)
    G.nvertices = len(G.vertices)-1
    # build path object and range
    G.xmin0, G.ymin0, G.xmax0, G.ymax0 = G.corners()
    G.xmin0, G.ymin0 = self.valid(G.xmin0,G.ymin0)
    G.xmax0, G.ymax0 = self.valid(G.xmax0,G.ymax0)
    G.angle, G.shaperatio = angle, shaperatio
    # store the object
    if fake:
        self.counter["all"] -= 1
        self.counter[typ] -= 1
        return G
    else:
        self.objects[name] = G
        self.nobjects += 1
        return None
def clear(self, what='nothing')

clear the plotting area, use clear("all")) to remove all objects

Expand source code
def clear(self,what="nothing"):
    """ clear the plotting area, use clear("all")) to remove all objects """
    self.imbead = np.zeros((self.height,self.width),dtype=np.int8)
    self.imobj = np.zeros((self.height,self.width),dtype=np.int8)
    for o in self.names():
        if what=="all":
            self.delete(o)
        else:
            self.objects[o].isplotted = False
            self.objects[o].islabelled = False
            if not self.objects[o].ismask:
                self.nbeads -= self.objects[o].nbeads
            self.objects[o].nbeads = 0  # number of beads (plotted)
    self.figure()
    plt.cla()
    self.show()
def collection(self, *obj, name=None, beadtype=None, ismask=None, translate=[0.0, 0.0], fake=False, **kwobj)

collection of objects: collection(draftraster,name="mycollect" [,beadtype=1,ismask=True] collection(name="mycollect",newobjname1 = obj1, newobjname2 = obj2…)

Expand source code
def collection(self,*obj,
               name=None,
               beadtype=None,
               ismask=None,
               translate = [0.0,0.0],
               fake = False,
               **kwobj):
    """
        collection of objects:
            collection(draftraster,name="mycollect" [,beadtype=1,ismask=True]
            collection(name="mycollect",newobjname1 = obj1, newobjname2 = obj2...)
    """
    self.counter["all"] += 1
    self.counter["collection"] += 1
    C = Collection((self.counter["all"],self.counter["collection"]))
    # name
    if name != None:
        if self.exist(name):
            print('RASTER:: the object "%s" is overwritten',name)
            self.delete(name)
        C.name = name
    else:
        name = C.name
    # build the collection
    C.collection = collection(*obj,**kwobj)
    xmin = ymin = +1e99
    xmax = ymax = -1e99
    # apply modifications (beadtype, ismask)
    for o in C.collection.keys():
        tmp = C.collection.getattr(o)
        tmp.translate[0] += translate[0]
        tmp.translate[1] += translate[1]
        xmin, xmax = min(xmin,tmp.xmin), max(xmax,tmp.xmax)
        ymin, ymax = min(ymin,tmp.ymin), max(ymax,tmp.ymax)
        if beadtype != None: tmp.beadtype = beadtype
        if ismask != None: tmp.ismask = ismask
        C.collection.setattr(o,tmp)
    C.xmin, C.xmax, C.ymin, C.ymax = xmin, xmax, ymin, ymax
    C.width, C.height = xmax-xmin, ymax-ymin
    if fake:
        return C
    else:
        self.objects[name] = C
        self.nobjects += 1
        return None
def copyalongpath(self, obj, name='path', beadtype=None, path=<function linear>, xmin=10, ymin=10, xmax=70, ymax=90, n=7, USER=structure (struct object) with 0 fields)

The method enable to copy an existing object (from the current raster, from another raster or a fake object) amp,g

Parameters


obj : real or fake object the object to be copied. name : string, optional the name of the object collection. The default is "path". beadtype : integet, optional type of bead (can override existing value). The default is None. path : function, optional parametric function returning x,y. The default is linear. x is between xmin and xmax, and y between ymin, ymax xmin : int64 or float, optional left x corner position. The default is 10. ymin : int64 or float, optional bottom y corner position. The default is 10. xmax : int64 or float, optional right x corner position. The default is 70. ymax : int64 or float, optional top y corner position. The default is 90. n : integet, optional number of copies. The default is 7. USER : structure to pass specific parameters

Returns


None.

Expand source code
def copyalongpath(self,obj,
                  name="path",
              beadtype=None,
                  path=linear,
                  xmin=10,
                  ymin=10,
                  xmax=70,
                  ymax=90,
                     n=7,
                  USER=struct()):
    """

    The method enable to copy an existing object (from the current raster,
    from another raster or a fake object) amp,g

    Parameters
    ----------
    obj : real or fake object
        the object to be copied.
    name : string, optional
        the name of the object collection. The default is "path".
    beadtype : integet, optional
        type of bead (can override existing value). The default is None.
    path : function, optional
        parametric function returning x,y. The default is linear.
        x is between xmin and xmax, and y between ymin, ymax
    xmin : int64 or float, optional
        left x corner position. The default is 10.
    ymin : int64 or float, optional
        bottom y corner position. The default is 10.
    xmax : int64 or float, optional
        right x corner position. The default is 70.
    ymax : int64 or float, optional
        top y corner position. The default is 90.
    n : integet, optional
        number of copies. The default is 7.
 USER : structure to pass specific parameters

    Returns
    -------
    None.

    """
    if not isinstance(USER,struct):
        raise TypeError("USER should be a structure")
    x,y = path(xmin=xmin,ymin=ymin,xmax=xmax,ymax=ymax,n=n,USER=USER)
    btyp = obj.beadtype if beadtype == None else beadtype
    collect = {}
    for i in range(n):
        nameobj = "%s_%s_%02d" % (name,obj.name,i)
        x[i], y[i] = self.valid(x[i], y[i])
        translate = [ x[i]-obj.xcenter, y[i]-obj.ycenter ]
        collect[nameobj] = obj.copy(translate=translate,
                                    name=nameobj,
                                    beadtype=btyp)
    self.collection(**collect,name=name)
def count(self)

count objects by type

Expand source code
def count(self):
    """ count objects by type """
    typlist = []
    for  o in self.names():
        if isinstance(self.objects[o].beadtype,list):
            typlist += self.objects[o].beadtype
        else:
            typlist.append(self.objects[o].beadtype)
    utypes = list(set(typlist))
    c = []
    for t in utypes:
        c.append((t,typlist.count(t)))
    return c
def data(self, scale=(1, 1), center=(0, 0), maxtype=None, hexpacking=None)

return a pizza.data object data() data(scale=(scalex,scaley), center=(centerx,centery), maxtype=number, hexpacking=(0.5,0))

Expand source code
def data(self,scale=(1,1),center=(0,0),maxtype=None,hexpacking=None):
    """
        return a pizza.data object
            data()
            data(scale=(scalex,scaley),
                 center=(centerx,centery),
                 maxtype=number,
                 hexpacking=(0.5,0))
    """
    if not isinstance(scale,tuple) or len(scale)!=2:
        raise ValueError("scale must be tuple (scalex,scaley)")
    if not isinstance(center,tuple) or len(scale)!=2:
        raise ValueError("center must be tuple (centerx,centery)")
    scalez = np.sqrt(scale[0]*scale[1])
    scalevol = scale[0]*scale[1] #*scalez
    maxtypeheader = self.count()[-1][0] if maxtype is None else maxtype
    n = self.length()
    i,j = self.imbead.nonzero() # x=j+0.5 y=i+0.5
    x = (j+0.5-center[0])*scale[0]
    y = (i+0.5-center[1])*scale[1]
    if hexpacking is not None:
        if isinstance(hexpacking,tuple) and len(hexpacking)==2:
            for k in range(len(i)):
                if i[k] % 2:
                    x[k] = (j[k]+0.5+hexpacking[1]-center[0])*scale[0]
                else:
                    x[k] = (j[k]+0.5+hexpacking[0]-center[0])*scale[0]
        else:
            raise ValueError("hexpacking should be a tuple (shiftodd,shifteven)")
    X = data3()  # empty pizza.data3.data object
    X.title = self.name + "(raster)"
    X.headers = {'atoms': n,
                  'atom types': maxtypeheader,
                  'xlo xhi': ((0.0-center[0])*scale[0], (self.width-0.0-center[0])*scale[0]),
                  'ylo yhi': ((0.0-center[1])*scale[1], (self.height-0.0-center[1])*scale[1]),
                  'zlo zhi': (0, scalez)}
    # [ATOMS] section
    X.append('Atoms',list(range(1,n+1)),True,"id")       # id
    X.append('Atoms',self.imbead[i,j],True,"type")       # Type
    X.append('Atoms',1,True,"mol")                       # mol
    X.append('Atoms',self.volume*scalevol,False,"c_vol") # c_vol
    X.append('Atoms',self.mass*scalevol,False,"mass")    # mass
    X.append('Atoms',self.radius*scalez,False,"radius")         # radius
    X.append('Atoms',self.contactradius*scalez,False,"c_contact_radius") # c_contact_radius
    X.append('Atoms',x,False,"x")        # x
    X.append('Atoms',y,False,"y")        # y
    X.append('Atoms',0,False,"z")        # z
    X.append('Atoms',x,False,"x0")       # x0
    X.append('Atoms',y,False,"y0")       # y0
    X.append('Atoms',0,False,"z0")       # z0
    # [VELOCITIES] section
    X.append('Velocities',list(range(1,n+1)),True,"id") # id
    X.append('Velocities',self.velocities[0],False,"vx") # vx
    X.append('Velocities',self.velocities[1],False,"vy") # vy
    X.append('Velocities',self.velocities[2],False,"vz") # vz
    # pseudo-filename
    X.flist = self.filename
    return X
def delete(self, name)

delete object

Expand source code
def delete(self,name):
    """ delete object """
    if name in self.objects:
        if not self.objects[name].ismask:
            self.nbeads -= self.objects[name].nbeads
        del self.objects[name]
        self.nobjects -= 1
    else:
        raise ValueError("%d is not a valid name (use list()) to list valid objects" % name)
    self.clear()
    self.plot()
    self.show(extra="label")
def diamond(self, xc, yc, radius, name=None, shaperatio=1, angle=0, beadtype=None, ismask=False, shiftangle=0, fake=False, beadtype2=None)

diamond object diamond(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False] use diamond(…,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio

Expand source code
def diamond(self,xc,yc,radius,name=None,
             shaperatio=1,angle=0,beadtype=None,ismask=False,shiftangle=0,fake=False,beadtype2=None):
    """
    diamond object
        diamond(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False]
        use diamond(...,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio
    """
    self.circle(xc,yc,radius,name=name,shaperatio=shaperatio,resolution=4,
        angle=angle,beadtype=beadtype,ismask=ismask,shiftangle=0,fake=fake,beadtype2=beadtype2)
def exist(self, name)

exist object

Expand source code
def exist(self,name):
    """ exist object """
    return name in self.objects
def figure(self)

set the current figure

Expand source code
def figure(self):
    """ set the current figure """
    if self.hfig==[] or not plt.fignum_exists(self.hfig.number):
        self.newfigure()
    plt.figure(self.hfig.number)
def frameobj(self, obj)

frame coordinates by taking into account translation

Expand source code
def frameobj(self,obj):
    """ frame coordinates by taking into account translation """
    if obj.hasclosefit:
        envelope = 0
    else:
        envelope = 1
    xmin, ymin = self.valid(obj.xmin-envelope, obj.ymin-envelope)
    xmax, ymax = self.valid(obj.xmax+envelope, obj.ymax+envelope)
    return xmin, ymin, xmax, ymax
def get(self, name)

returns the object

Expand source code
def get(self,name):
    """ returns the object """
    if name in self.objects:
        return self.objects[name]
    else:
        raise ValueError('the object "%s" does not exist, use list()' % name)
def hexagon(self, xc, yc, radius, name=None, shaperatio=1, angle=0, beadtype=None, ismask=False, shiftangle=0, fake=False, beadtype2=None)

hexagon object hexagon(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False] use hexagon(…,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio

Expand source code
def hexagon(self,xc,yc,radius,name=None,
             shaperatio=1,angle=0,beadtype=None,ismask=False,shiftangle=0,fake=False,beadtype2=None):
    """
    hexagon object
        hexagon(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False]
        use hexagon(...,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio
    """
    self.circle(xc,yc,radius,name=name,shaperatio=shaperatio,resolution=6,
        angle=angle,beadtype=beadtype,ismask=ismask,shiftangle=0,fake=fake,beadtype2=beadtype2)
def label(self, name, **fmt)

label: label(name [, contour=True,edgecolor="orange",facecolor="none",linewidth=2, ax=plt.gca()])

Expand source code
def label(self,name,**fmt):
    """
        label:
            label(name [, contour=True,edgecolor="orange",facecolor="none",linewidth=2, ax=plt.gca()])
    """
    self.figure()
    if name in self.objects:
        if not self.objects[name].islabelled:
            if self.objects[name].alike == "mixed":
                for o in self.objects[name].collection:
                    self.labelobj(o,**fmt)
            else:
                self.labelobj(self.objects[name],**fmt)
            plt.show()
            self.objects[name].islabelled = True
    else:
        raise ValueError("%d is not a valid name (use list()) to list valid objects" % name)
def labelobj(self, obj, contour=True, edgecolor='orange', facecolor='none', linewidth=2, ax=<Axes: >)

labelobj: labelobj(obj [, contour=True,edgecolor="orange",facecolor="none",linewidth=2, ax=plt.gca()])

Expand source code
def labelobj(self,obj,contour=True,edgecolor="orange",facecolor="none",linewidth=2,ax=plt.gca()):
    """
        labelobj:
            labelobj(obj [, contour=True,edgecolor="orange",facecolor="none",linewidth=2, ax=plt.gca()])
    """
    if contour: contour = obj.hascontour # e.g. overlays do not have contour

    if contour:
        patch = patches.PathPatch(obj.polygon2plot,
                                  facecolor=facecolor,
                                  edgecolor=edgecolor,
                                  lw=linewidth)
        obj.hlabel["contour"] = ax.add_patch(patch)
    else:
        obj.hlabel["contour"] = None
    obj.hlabel["text"] = \
    plt.text(obj.xcenter,
             obj.ycenter,
             "%s\n(t=$%d$,$n_p$=%d)" % (obj.name, obj.beadtype,obj.nbeads),
             horizontalalignment = "center",
             verticalalignment = "center_baseline",
             fontsize=self.fontsize
             )
def length(self, t=None, what='beadtype')

returns the total number of beads length(type,"beadtype")

Expand source code
def length(self,t=None,what="beadtype"):
    """ returns the total number of beads length(type,"beadtype") """
    if what == "beadtype":
        num = self.imbead
    elif what == "objindex":
        num = self.imobj
    else:
        raise ValueError('"beadtype" and "objindex" are the only acceptable values')
    if t==None:
        return np.count_nonzero(num>0)
    else:
        return np.count_nonzero(num==t)
def list(self)

list objects

Expand source code
def list(self):
    """ list objects """
    fmt = "%%%ss:" % max(10,max([len(n) for n in self.names()])+2)
    print("RASTER with %d objects" % self.nobjects)
    for o in self.objects.keys():
        print(fmt % self.objects[o].name,"%-10s" % self.objects[o].kind,
              "(beadtype=%d,object index=[%d,%d], n=%d)" % \
                  (self.objects[o].beadtype,
                   self.objects[o].index,
                   self.objects[o].subindex,
                   self.objects[o].nbeads))
def names(self)

return the names of objects sorted as index

Expand source code
def names(self):
    """ return the names of objects sorted as index """
    namesunsorted=namessorted=list(self.objects.keys())
    nobj = len(namesunsorted)
    for iobj in range(nobj):
        namessorted[self.objects[namesunsorted[iobj]].index-1] = namesunsorted[iobj]
    return namessorted
def newfigure(self)

create a new figure (dpi=200)

Expand source code
def newfigure(self):
    """ create a new figure (dpi=200) """
    self.hfig = plt.figure(dpi=self.dpi)
def numeric(self)

retrieve the image as a numpy.array

Expand source code
def numeric(self):
    """ retrieve the image as a numpy.array """
    return self.imbead, self.imobj
def overlay(self, x0, y0, name=None, filename=None, color=1, colormax=None, ncolors=4, beadtype=None, beadtype2=None, ismask=False, fake=False, flipud=True, angle=0, scale=(1, 1))

overlay object: made from an image converted to nc colors the object is made from the level ranged between ic and jc (bounds included) note: if palette found, no conversion is applied

O = overlay(x0,y0,filename="/this/is/my/image.png",ncolors=nc,color=ic,colormax=jc,beadtype=b) O = overlay(…angle=0,scale=(1,1)) to induce rotation and change of scale O = overlay(....ismask=False,fake=False)

note use overlay(…flipud=False) to prevent image fliping (standard)

Outputs

O.original original image (PIL) O.raw image converted to ncolors if needed

Expand source code
def overlay(self,x0,y0,
            name = None,
            filename = None,
            color = 1,
            colormax = None,
            ncolors = 4,
            beadtype = None,
            beadtype2 = None,
            ismask = False,
            fake = False,
            flipud = True,
            angle = 0,
            scale= (1,1)
            ):
    """
        overlay object: made from an image converted to nc colors
        the object is made from the level ranged between ic and jc (bounds included)
        note: if palette found, no conversion is applied

        O = overlay(x0,y0,filename="/this/is/my/image.png",ncolors=nc,color=ic,colormax=jc,beadtype=b)
        O = overlay(...angle=0,scale=(1,1)) to induce rotation and change of scale
        O = overlay(....ismask=False,fake=False)

        note use overlay(...flipud=False) to prevent image fliping (standard)

        Outputs:
            O.original original image (PIL)
            O.raw image converted to ncolors if needed

    """
    if filename is None or filename=="":
        raise ValueError("filename is required (valid image)")
    O = overlay(counter=(self.counter["all"]+1,self.counter["overlay"]+1),
                filename = filename,
                xmin = x0,
                ymin = y0,
                ncolors = ncolors,
                flipud = flipud,
                angle = angle,
                scale = scale
                )
    O.select(color=color, colormax=colormax)
    if (name is not None) and (name !=""):
        if self.exist(name):
            print('RASTER:: the object "%s" is overwritten',name)
            self.delete(name)
        O.name = name
    else:
        name = O.name
    if beadtype is not None: O.beadtype = int(np.floor(beadtype))
    if beadtype2 is not None:
        if not isinstance(beadtype2,tuple) or len(beadtype2)!=2:
            raise AttributeError("beadtype2 must be a tuple (beadtype,ratio)")
    O.beadtype2 = beadtype2
    if ismask: O.beadtype = 0
    O.ismask = O.beadtype==0
    self.counter["all"] += 1
    self.counter["overlay"] += 1
    if fake:
        self.counter["all"] -= 1
        self.counter["overlay"] -= 1
        return O
    else:
        self.objects[name] = O
        self.nobjects += 1
        return None
def pentagon(self, xc, yc, radius, name=None, shaperatio=1, angle=0, beadtype=None, ismask=False, shiftangle=0, fake=False, beadtype2=None)

pentagon object pentagon(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False] use pentagon(…,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio

Expand source code
def pentagon(self,xc,yc,radius,name=None,
             shaperatio=1,angle=0,beadtype=None,ismask=False,shiftangle=0,fake=False,beadtype2=None):
    """
    pentagon object
        pentagon(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False]
        use pentagon(...,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio
    """
    self.circle(xc,yc,radius,name=name,shaperatio=shaperatio,resolution=5,
        angle=angle,beadtype=beadtype,ismask=ismask,shiftangle=0,fake=fake,beadtype2=beadtype2)
def plot(self)

plot

Expand source code
def plot(self):
    """ plot """
    for o in self.objects:
        if not self.objects[o].isplotted:
            if self.objects[o].alike == "mixed":
                for o2 in self.objects[o].collection:
                    self.plotobj(o2)
            else:
                self.plotobj(self.objects[o])
            # store it as plotted
            self.objects[o].isplotted = True
            if not self.objects[o].ismask:
                self.nbeads += self.objects[o].nbeads
def plotobj(self, obj)

plotobj(obj)

Expand source code
def plotobj(self,obj):
    """ plotobj(obj) """
    if obj.alike == "circle":
        xmin, ymin, xmax, ymax = self.frameobj(obj)
        j,i = np.meshgrid(range(xmin,xmax), range(ymin,ymax))
        points = np.vstack((j.flatten(),i.flatten())).T
        npoints = points.shape[0]
        inside = obj.polygon.contains_points(points)
        if obj.beadtype2 is None:          # -- no salting --
            for k in range(npoints):
                if inside[k] and \
                    points[k,0]>=0 and \
                    points[k,0]<self.width and \
                    points[k,1]>=0 and \
                    points[k,1]<self.height:
                        self.imbead[points[k,1],points[k,0]] = obj.beadtype
                        self.imobj[points[k,1],points[k,0]] = obj.index
                        obj.nbeads += 1
        else:
            for k in range(npoints):       # -- salting --
                if inside[k] and \
                    points[k,0]>=0 and \
                    points[k,0]<self.width and \
                    points[k,1]>=0 and \
                    points[k,1]<self.height:
                        if np.random.rand()<obj.beadtype2[1]:
                            self.imbead[points[k,1],points[k,0]] = obj.beadtype2[0]
                        else:
                            self.imbead[points[k,1],points[k,0]] = obj.beadtype
                        self.imobj[points[k,1],points[k,0]] = obj.index
                        obj.nbeads += 1

    elif obj.alike == "overlay":
        xmin, ymin, xmax, ymax = self.frameobj(obj)
        j,i = np.meshgrid(range(xmin,xmax), range(ymin,ymax))
        points = np.vstack((j.flatten(),i.flatten())).T
        npoints = points.shape[0]
        inside = obj.select()
        if obj.beadtype2 is None:          # -- no salting --
            for k in range(npoints):
                if inside[ points[k,1]-ymin, points[k,0]-xmin ] and \
                    points[k,0]>=0 and \
                    points[k,0]<self.width and \
                    points[k,1]>=0 and \
                    points[k,1]<self.height:
                        self.imbead[points[k,1],points[k,0]] = obj.beadtype
                        self.imobj[points[k,1],points[k,0]] = obj.index
                        obj.nbeads += 1
        else:
            for k in range(npoints):       # -- salting --
                if inside[ points[k,0]-ymin, points[k,0]-xmin ] and \
                    points[k,0]>=0 and \
                    points[k,0]<self.width and \
                    points[k,1]>=0 and \
                    points[k,1]<self.height:
                        if np.random.rand()<obj.beadtype2[1]:
                            self.imbead[points[k,1],points[k,0]] = obj.beadtype2[0]
                        else:
                            self.imbead[points[k,1],points[k,0]] = obj.beadtype
                        self.imobj[points[k,1],points[k,0]] = obj.index
                        obj.nbeads += 1

    else:
        raise ValueError("This object type is notimplemented")
def print(self, what='beadtype')

print method

Expand source code
def print(self,what="beadtype"):
    """ print method """
    txt = self.string(what=what)
    for i in range(len(txt)):
        print(txt[i],end="\n")
def rectangle(self, a, b, c, d, mode='lowerleft', name=None, angle=0, beadtype=None, ismask=False, fake=False, beadtype2=None)

rectangle object rectangle(xleft,xright,ybottom,ytop [, beadtype=1,mode="lower", angle=0, ismask=False]) rectangle(xcenter,ycenter,width,height [, beadtype=1,mode="center", angle=0, ismask=False])

use rectangle(...,beadtype2=(type,ratio)) to salt an object with beads
from another type and with a given ratio
Expand source code
def rectangle(self,a,b,c,d,
              mode="lowerleft",name=None,angle=0,
              beadtype=None,ismask=False,fake=False,beadtype2=None):
    """
    rectangle object
        rectangle(xleft,xright,ybottom,ytop [, beadtype=1,mode="lower", angle=0, ismask=False])
        rectangle(xcenter,ycenter,width,height [, beadtype=1,mode="center", angle=0, ismask=False])

        use rectangle(...,beadtype2=(type,ratio)) to salt an object with beads
        from another type and with a given ratio
    """
    # object creation
    self.counter["all"] += 1
    self.counter["rectangle"] += 1
    R = Rectangle((self.counter["all"],self.counter["rectangle"]))
    if (name != None) and (name != ""):
        if self.exist(name):
            print('RASTER:: the object "%s" is overwritten',name)
            self.delete(name)
        R.name = name
    else:
        name = R.name
    if beadtype is not None: R.beadtype = int(np.floor(beadtype))
    if beadtype2 is not None:
        if not isinstance(beadtype2,tuple) or len(beadtype2)!=2:
            raise AttributeError("beadtype2 must be a tuple (beadtype,ratio)")
    R.beadtype2 = beadtype2
    if ismask: R.beadtype = 0
    R.ismask = R.beadtype==0
    # build vertices
    if mode == "lowerleft":
        R.xcenter0 = (a+b)/2
        R.ycenter0 = (c+d)/2
        R.vertices = [
            _rotate(a,c,R.xcenter0,R.ycenter0,angle),
            _rotate(b,c,R.xcenter0,R.ycenter0,angle),
            _rotate(b,d,R.xcenter0,R.ycenter0,angle),
            _rotate(a,d,R.xcenter0,R.ycenter0,angle),
            _rotate(a,c,R.xcenter0,R.ycenter0,angle)
            ] # anti-clockwise, closed (last point repeated)
    elif mode == "center":
        R.xcenter0 = a
        R.ycenter0 = b
        R.vertices = [
            _rotate(a-c/2,b-d/2,R.xcenter0,R.ycenter0,angle),
            _rotate(a+c/2,b-d/2,R.xcenter0,R.ycenter0,angle),
            _rotate(a+c/2,b+d/2,R.xcenter0,R.ycenter0,angle),
            _rotate(a-c/2,b+d/2,R.xcenter0,R.ycenter0,angle),
            _rotate(a-c/2,b-d/2,R.xcenter0,R.ycenter0,angle)
            ]
    else:
        raise ValueError('"%s" is not a recognized mode, use "lowerleft" (default) and "center" instead')
    # build path object and range
    R.codes =    [ path.Path.MOVETO,
                 path.Path.LINETO,
                 path.Path.LINETO,
                 path.Path.LINETO,
                 path.Path.CLOSEPOLY
                ]
    R.nvertices = len(R.vertices)-1
    R.xmin0, R.ymin0, R.xmax0, R.ymax0 = R.corners()
    R.xmin0, R.ymin0 = self.valid(R.xmin0,R.ymin0)
    R.xmax0, R.ymax0 = self.valid(R.xmax0,R.ymax0)
    R.angle = angle
    # store the object (if not fake)
    if fake:
        self.counter["all"] -= 1
        self.counter["rectangle"] -= 1
        return R
    else:
        self.objects[name] = R
        self.nobjects += 1
        return None
def scatter(self, E, name='emulsion', beadtype=None, ismask=False)

Parameters

E : scatter or emulsion object
codes for x,y and r.
name : string, optional
name of the collection. The default is "emulsion".
beadtype : integer, optional
for all objects. The default is 1.
ismask : logical, optional
Set it to true to force a mask. The default is False.

Raises

TypeError
Return an error of the object is not a scatter type.

Returns

None.

Expand source code
def scatter(self,
             E,
             name="emulsion",
             beadtype=None,
             ismask = False
             ):
    """


    Parameters
    ----------
    E : scatter or emulsion object
        codes for x,y and r.
    name : string, optional
        name of the collection. The default is "emulsion".
    beadtype : integer, optional
        for all objects. The default is 1.
    ismask : logical, optional
        Set it to true to force a mask. The default is False.

    Raises
    ------
    TypeError
        Return an error of the object is not a scatter type.

    Returns
    -------
    None.

    """
    if isinstance(E,scatter):
        collect = {}
        for i in range(E.n):
            b = E.beadtype[i] if beadtype==None else beadtype
            nameobj = "glob%02d" % i
            collect[nameobj] = self.circle(E.x[i],E.y[i],E.r[i],
                        name=nameobj,beadtype=b,ismask=ismask,fake=True)
        self.collection(**collect,name=name)
    else:
        raise TypeError("the first argument must be an emulsion object")
def show(self, extra='none', contour=True, what='beadtype')

show method: show(extra="label",contour=True,what="beadtype")

Expand source code
def show(self,extra="none",contour=True,what="beadtype"):
    """ show method: show(extra="label",contour=True,what="beadtype") """
    self.figure()
    if what=="beadtype":
        imagesc(self.imbead)
    elif what == "objindex":
        imagesc(self.imobj)
    else:
        raise ValueError('"beadtype" and "objindex" are the only acceptable values')
    if extra == "label":
        ax = plt.gca()
        for o in self.names():
            if not self.objects[o].ismask:
                self.label(o,ax=ax,contour=contour)
        ax.set_title("raster area: %s \n (n=%d, $n_p$=%d)" %\
                  (self.name,self.length(),self.nbeads) )
        plt.show()
def string(self, what='beadtype')

convert the image as ASCII strings

Expand source code
def string(self,what="beadtype"):
    """ convert the image as ASCII strings """
    if what == "beadtype":
        num = np.flipud(duplicate(self.imbead))
    elif what == "objindex":
        num = np.flipud(duplicate(self.imobj))
    else:
        raise ValueError('"beadtype" and "objindex" are the only acceptable values')
    num[num>0] = num[num>0] + 65
    num[num==0] = 32
    num = list(num)
    return ["".join(map(chr,x)) for x in num]
def torgb(self, what='beadtype', thumb=None)

converts bead raster to image rgb = raster.torgb(what="beadtype") thumbnail = raster.torgb(what="beadtype",(128,128)) use: rgb.save("/path/filename.png") for saving

what = "beadtype" or "objindex"

Expand source code
def torgb(self,what="beadtype",thumb=None):
    """ converts bead raster to image
            rgb = raster.torgb(what="beadtype")
            thumbnail = raster.torgb(what="beadtype",(128,128))
            use: rgb.save("/path/filename.png") for saving

        what = "beadtype" or "objindex"
    """
    if what=="beadtype":
        rgb = ind2rgb(self.imbead,ncolors=self.max+1)
    elif what == "objindex":
        rgb = ind2rgb(self.imobj,ncolors=len(self)+1)
    else:
        raise ValueError('"beadtype" and "objindex" are the only acceptable values')
    if thumb is not None: rgb.thumbnail(thumb)
    return rgb
def triangle(self, xc, yc, radius, name=None, shaperatio=1, angle=0, beadtype=None, ismask=False, shiftangle=0, fake=False, beadtype2=None)

triangle object triangle(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False] use triangle(…,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio

Expand source code
def triangle(self,xc,yc,radius,name=None,
             shaperatio=1,angle=0,beadtype=None,ismask=False,shiftangle=0,fake=False,beadtype2=None):
    """
    triangle object
        triangle(xcenter,ycenter,radius [, beadtype=1,shaperatio=1, angle=0, ismask=False]
        use triangle(...,beadtype2=(type,ratio)) to salt an object with beads from another type and with a given ratio
    """
    self.circle(xc,yc,radius,name=name,shaperatio=shaperatio,resolution=3,
       angle=angle,beadtype=beadtype,ismask=ismask,shiftangle=0,fake=fake,beadtype2=beadtype2)
def unlabel(self, name)

unlabel

Expand source code
def unlabel(self,name):
    """ unlabel """
    if name in self.objects:
        if  self.objects[name].islabelled:
            self.objects[name].hlabel["contour"].remove()
            self.objects[name].hlabel["text"].remove()
            self.objects[name].hlabel = {'contour':[], 'text':[]}
            self.objects[name].islabelled = False
    else:
        raise ValueError("%d is not a valid name (use list()) to list valid objects" % name)
def valid(self, x, y)

validation of coordinates

Expand source code
def valid(self,x,y):
    """ validation of coordinates """
    return min(self.width,max(0,round(x))),min(self.height,max(0,round(y)))
class scatter

generic top scatter class

The scatter class provides an easy constructor to distribute in space objects according to their positions x,y, size r (radius) and beadtype.

The class is used to derive emulsions.

Returns

None.

Expand source code
class scatter():
    """ generic top scatter class """
    def __init__(self):
        """
        The scatter class provides an easy constructor
        to distribute in space objects according to their
        positions x,y, size r (radius) and beadtype.

        The class is used to derive emulsions.

        Returns
        -------
        None.

        """
        self.x = np.array([],dtype=int)
        self.y = np.array([],dtype=int)
        self.r = np.array([],dtype=int)
        self.beadtype = []

    @property
    def n(self):
        return len(self.x)

    def pairdist(self,x,y):
        """ pair distance to the surface of all disks/spheres """
        if self.n==0:
            return np.Inf
        else:
            return np.floor(np.sqrt((x-self.x)**2+(y-self.y)**2)-self.r)

Subclasses

Instance variables

var n
Expand source code
@property
def n(self):
    return len(self.x)

Methods

def pairdist(self, x, y)

pair distance to the surface of all disks/spheres

Expand source code
def pairdist(self,x,y):
    """ pair distance to the surface of all disks/spheres """
    if self.n==0:
        return np.Inf
    else:
        return np.floor(np.sqrt((x-self.x)**2+(y-self.y)**2)-self.r)