def separate_particles_into_groups(s, region_size=<NUM_LIT>, bounds=None,<EOL>doshift=False):
imtile = s.oshape.translate(-s.pad)<EOL>bounding_tile = (imtile if bounds is None else Tile(bounds[<NUM_LIT:0>], bounds[<NUM_LIT:1>]))<EOL>rs = (np.ones(bounding_tile.dim, dtype='<STR_LIT:int>')*region_size if<EOL>np.size(region_size) == <NUM_LIT:1> else np.array(region_size))<EOL>n_translate = np.ceil(bounding_tile.shape.astype('<STR_LIT:float>')/rs).astype('<STR_LIT:int>')<EOL>particle_groups = []<EOL>tile = Tile(left=bounding_tile.l, right=bounding_tile.l + rs)<EOL>if doshift == '<STR_LIT>':<EOL><INDENT>doshift = np.random.choice([True, False])<EOL><DEDENT>if doshift:<EOL><INDENT>shift = rs // <NUM_LIT:2><EOL>n_translate += <NUM_LIT:1><EOL><DEDENT>else:<EOL><INDENT>shift = <NUM_LIT:0><EOL><DEDENT>deltas = np.meshgrid(*[np.arange(i) for i in n_translate])<EOL>positions = s.obj_get_positions()<EOL>if bounds is None:<EOL><INDENT>positions = np.clip(positions, imtile.l+<NUM_LIT>, imtile.r-<NUM_LIT>)<EOL><DEDENT>groups = list(map(lambda *args: find_particles_in_tile(positions,<EOL>tile.translate( np.array(args) * rs - shift)), *[d.ravel()<EOL>for d in deltas]))<EOL>for i in range(len(groups)-<NUM_LIT:1>, -<NUM_LIT:1>, -<NUM_LIT:1>):<EOL><INDENT>if groups[i].size == <NUM_LIT:0>:<EOL><INDENT>groups.pop(i)<EOL><DEDENT><DEDENT>assert _check_groups(s, groups)<EOL>return groups<EOL>
Separates particles into convenient groups for optimization.

Given a state, returns a list of groups of particles. Each group of
particles are located near each other in the image. Every particle
located in the desired region is contained in exactly 1 group.

Parameters
----------
s : :class:`peri.states.ImageState`
    The peri state to find particles in.
region_size : Int or 3-element list-like of ints, optional
    The size of the box. Groups particles into boxes of shape
    (region_size[0], region_size[1], region_size[2]). If region_size
    is a scalar, the box is a cube of length region_size.
    Default is 40.
bounds : 2-element list-like of 3-element lists, optional
    The sub-region of the image over which to look for particles.
        bounds[0]: The lower-left  corner of the image region.
        bounds[1]: The upper-right corner of the image region.
    Default (None -> ([0,0,0], s.oshape.shape)) is a box of the entire
    image size, i.e. the default places every particle in the image
    somewhere in the groups.
doshift : {True, False, `'rand'`}, optional
    Whether or not to shift the tile boxes by half a region size, to
    prevent the same particles to be chosen every time. If `'rand'`,
    randomly chooses either True or False. Default is False

Returns
-------
particle_groups : List
    Each element of particle_groups is an int numpy.ndarray of the
    group of nearby particles. Only contains groups with a nonzero
    number of particles, so the elements don't necessarily correspond
    to a given image region.