//  [python]
def generate_slice(pixels, rois, fname, yolo_cfg, rnd_hue, rnd_orientation, repeat_tiles, rnd_distmax, idx):
    # dynamic image shapes
    img_shape = tf.cast(tf.shape(pixels), tf.float32)  # known shape [height, width, 3]
    img_shape = tf.reshape(img_shape, [3])  # tensorflow needs help here
    img_h, img_w, _ = tf.unstack(img_shape)

    # dynamic number of rois
    rois = tf.reshape(rois, [-1, 4])  # I know the shape but Tensorflow does not
    rois_n = tf.shape(rois)[0] # known shape [n, 4]

    TILE_INTERSECT_FRACTION = 0.75

    # random displacements around each ROI (typically 1.4 to 3.0. Fixed at 2.0 for all evals)
    # adjusted so that tiles with planes and no planes happen with roughly equal frequency
    RANDOM_MAX_DISTANCE = rnd_distmax*settings.TILE_SIZE
    N_RANDOM_POSITIONS = repeat_tiles  # repeat_tiles * max nb of planes in one input image = nb of tiles generated in RAM (watch out!)
    # you can increase sdtev to reach more zones without airplanes
    rnd_x = tf.round(tf.truncated_normal([N_RANDOM_POSITIONS], mean=0.0, stddev=RANDOM_MAX_DISTANCE/2.0))
    rnd_y = tf.round(tf.truncated_normal([N_RANDOM_POSITIONS], mean=0.0, stddev=RANDOM_MAX_DISTANCE/2.0))

    def many_tiles_around_this_one(roi):
        roi_x1, roi_y1, roi_x2, roi_y2 = tf.unstack(roi, axis=0)
        # center coordinates of the roi
        roi_x = (roi_x1 + roi_x2) / 2.0
        roi_y = (roi_y1 + roi_y2) / 2.0
        # create N_RANDOM_POSITIONS rois centered on the original
        # but with a random translation and of size [TILE_SIZE, TILE_SIZE]
        roi_x = tf.add(roi_x, rnd_x)  # broadcasting !
        roi_y = tf.add(roi_y, rnd_y)  # broadcasting !
        roi_x1 = tf.add(roi_x, -settings.TILE_SIZE/2.0)
        roi_y1 = tf.add(roi_y, -settings.TILE_SIZE/2.0)
        roi_x2 = tf.add(roi_x, settings.TILE_SIZE/2.0)
        roi_y2 = tf.add(roi_y, settings.TILE_SIZE/2.0)
        roisx = tf.stack([roi_x1, roi_y1, roi_x2, roi_y2], axis=1)
        return roisx

    # for each roi, generate N_RANDOM_POSITIONS translated ROIs
    tiles = tf.map_fn(many_tiles_around_this_one, rois, dtype=tf.float32, name="jitter")
    tiles = tf.reshape(tiles, [-1, 4])  # flatten all generated random ROIs
    # dynamic number of tiles
    tiles_n = tf.shape(tiles)[0]  # known shape [n, 4]

    def count_planes(roi):
        inter = box.boxintersect(roi, rois, TILE_INTERSECT_FRACTION)
        return tf.reduce_sum(tf.cast(inter, dtype=tf.int32))

    # plane counting
    plane_counts = tf.map_fn(count_planes, tiles, dtype=tf.int32)
    # count up to 1 max (planes/no planes)
    # or count up to 3 max (0, 1, 2, lots of planes)
    plane_counts = tf.minimum(plane_counts, 1)

    # debug
    #plane_counts3 = tf.count_nonzero(tf.floor_div(plane_counts, 3))
    #plane_counts2 = tf.count_nonzero(tf.floor_div(plane_counts, 2)) - plane_counts3
    plane_counts1 = tf.count_nonzero(tf.floor_div(plane_counts, 1)) #- plane_counts3 - plane_counts2
    plane_counts0 = tf.count_nonzero(tf.add(plane_counts, 1))       - plane_counts1 #- plane_counts2 - plane_counts3
    tf.Print(tiles_n, [tiles_n, plane_counts0, plane_counts1],
             "Generating tiles [total tiles][tiles with no planes][tiles with 1+ planes]: ")

    # Vocabulary:
    # "tile": a 256x256 region under consideration
    # "cell": tiles are evenly divided into 4 x 4 = 16 cells
    # "roi": a plane bounding box (ground truth)

    # Compute ground truth ROIs
    target_rois = box.rois_in_tiles_relative_and_pad(tiles, rois, settings.MAX_TARGET_ROIS_PER_TILE)  # shape [n_tiles, MAX_TARGET_ROIS_PER_TILE, 4]

    # resize rois to units used by crop_and_resize
    # TODO: refactor unit conversion into utils_box
    tile_x1, tile_y1, tile_x2, tile_y2 = tf.unstack(tiles, axis=1)
    tile_y1 = tile_y1 / img_h
    tile_x1 = tile_x1 / img_w
    tile_y2 = tile_y2 / img_h
    tile_x2 = tile_x2 / img_w
    # crop_and_resize expects coordinates in format [y1, x1, y2, x2]
    tiles = tf.stack([tile_y1, tile_x1, tile_y2, tile_x2], axis=1)

    indices = tf.zeros([tiles_n], dtype=tf.int32) # all the rois refer to image #0 (there is only one)
    # expand_dims needed because crop_and_resize expects a batch of images
    image_tiles = tf.image.crop_and_resize(tf.expand_dims(pixels, 0), tiles, indices, [settings.TILE_SIZE, settings.TILE_SIZE])
    # crop_and_resize does not output a defined pixel depth but the convolutional layers need it
    image_tiles = tf.reshape(image_tiles, [-1, settings.TILE_SIZE, settings.TILE_SIZE, 3])  # 3 for r, g, b
    image_tiles = tf.cast(image_tiles, tf.uint8)

    if rnd_orientation:
        image_tiles, target_rois = batch_random_orientation(image_tiles, target_rois, 1.0)

    # Compute ground truth ROIs assigned to YOLO grid cells
    yolo_target_rois = batch_yolo_roi_attribution(tiles, target_rois, yolo_cfg)

    if rnd_hue:  # random hue shift for all training images
        image_tiles = random_hue(image_tiles)

    # filename containing the airport name for logging and debugging
    filenames = tf.tile([fname], [tiles_n])

    features, labels = features_and_labels(image_tiles, yolo_target_rois, target_rois, plane_counts, filenames)
    return tf.data.Dataset.from_tensor_slices((features, labels))