Converts object detection annotations (ground truth or predictions) to
    unstacked format (an `SArray` where each element is a list of object
    instances).

    Parameters
    ----------
    annotations_sframe: SFrame
        An `SFrame` with stacked predictions, produced by the
        `stack_annotations` function.

    num_rows: int
        Optionally specify the number of rows in your original dataset, so that
        all get represented in the unstacked format, regardless of whether or
        not they had instances or not.

    Returns
    -------
    annotations_sarray: An `SArray` with unstacked annotations.

    See also
    --------
    stack_annotations

    Examples
    --------
    If you have annotations in stacked format:

    >>> stacked_predictions
    Data:
    +--------+------------+-------+-------+-------+-------+--------+
    | row_id | confidence | label |   x   |   y   | width | height |
    +--------+------------+-------+-------+-------+-------+--------+
    |   0    |    0.98    |  dog  | 123.0 | 128.0 |  80.0 | 182.0  |
    |   0    |    0.67    |  cat  | 150.0 | 183.0 | 129.0 | 101.0  |
    |   1    |    0.8     |  dog  |  50.0 | 432.0 |  65.0 |  98.0  |
    +--------+------------+-------+-------+-------+-------+--------+
    [3 rows x 7 columns]

    They can be converted to unstacked format using this function:

    >>> turicreate.object_detector.util.unstack_annotations(stacked_predictions)[0]
    [{'confidence': 0.98,
      'coordinates': {'height': 182.0, 'width': 80.0, 'x': 123.0, 'y': 128.0},
      'label': 'dog',
      'type': 'rectangle'},
     {'confidence': 0.67,
      'coordinates': {'height': 101.0, 'width': 129.0, 'x': 150.0, 'y': 183.0},
      'label': 'cat',
      'type': 'rectangle'}]

def unstack_annotations(annotations_sframe, num_rows=None):
    """
    Converts object detection annotations (ground truth or predictions) to
    unstacked format (an `SArray` where each element is a list of object
    instances).

    Parameters
    ----------
    annotations_sframe: SFrame
        An `SFrame` with stacked predictions, produced by the
        `stack_annotations` function.

    num_rows: int
        Optionally specify the number of rows in your original dataset, so that
        all get represented in the unstacked format, regardless of whether or
        not they had instances or not.

    Returns
    -------
    annotations_sarray: An `SArray` with unstacked annotations.

    See also
    --------
    stack_annotations

    Examples
    --------
    If you have annotations in stacked format:

    >>> stacked_predictions
    Data:
    +--------+------------+-------+-------+-------+-------+--------+
    | row_id | confidence | label |   x   |   y   | width | height |
    +--------+------------+-------+-------+-------+-------+--------+
    |   0    |    0.98    |  dog  | 123.0 | 128.0 |  80.0 | 182.0  |
    |   0    |    0.67    |  cat  | 150.0 | 183.0 | 129.0 | 101.0  |
    |   1    |    0.8     |  dog  |  50.0 | 432.0 |  65.0 |  98.0  |
    +--------+------------+-------+-------+-------+-------+--------+
    [3 rows x 7 columns]

    They can be converted to unstacked format using this function:

    >>> turicreate.object_detector.util.unstack_annotations(stacked_predictions)[0]
    [{'confidence': 0.98,
      'coordinates': {'height': 182.0, 'width': 80.0, 'x': 123.0, 'y': 128.0},
      'label': 'dog',
      'type': 'rectangle'},
     {'confidence': 0.67,
      'coordinates': {'height': 101.0, 'width': 129.0, 'x': 150.0, 'y': 183.0},
      'label': 'cat',
      'type': 'rectangle'}]
    """
    _raise_error_if_not_sframe(annotations_sframe, variable_name="annotations_sframe")

    cols = ['label', 'type', 'coordinates']
    has_confidence = 'confidence' in annotations_sframe.column_names()
    if has_confidence:
        cols.append('confidence')

    if num_rows is None:
        if len(annotations_sframe) == 0:
            num_rows = 0
        else:
            num_rows = annotations_sframe['row_id'].max() + 1

    sf = annotations_sframe
    sf['type'] = 'rectangle'
    sf = sf.pack_columns(['x', 'y', 'width', 'height'], dtype=dict,
                         new_column_name='coordinates')
    sf = sf.pack_columns(cols, dtype=dict, new_column_name='ann')
    sf = sf.unstack('ann', new_column_name='annotations')
    sf_all_ids = _tc.SFrame({'row_id': range(num_rows)})
    sf = sf.join(sf_all_ids, on='row_id', how='right')
    sf = sf.fillna('annotations', [])
    sf = sf.sort('row_id')

    annotations_sarray = sf['annotations']
    # Sort the confidences again, since the unstack does not preserve the order
    if has_confidence:
        annotations_sarray = annotations_sarray.apply(
                lambda x: sorted(x, key=lambda ann: ann['confidence'], reverse=True),
                dtype=list)
    return annotations_sarray