###############################################################
Using lsst.afw.detection.Footprint to represent detection areas
###############################################################

The fundamental unit of a detection in the LSST pipeline is an instance of the
``Footprint`` class. This class contains the x, y locations for pixels that are
part of a detection, as well as the x, y location and intensity of pixels which
are considered local peaks within the detection area.

Spans
=====

Internally the pixel locations of a detection are stored in an instance of a
``SpanSet`` which can be retrieved through the ``getSpans`` function (or the
Footprint.spans property in Python). The ``SpanSet`` provides methods for
working with the detected area as a mathematical set. Of important note is that
``SpanSet``\s are immutable once created. If the area of a ``Footprint`` needs
to be updated after initialization a new ``SpanSet`` must be created which will
then replace the original ``SpanSet`` held by the ``Footprint`` instance. This
can be done by calling the ``setSpan``\s method (or assigning to the
Footprint.spans property in Python). This does not modify the ``PeakCatalog``,
and there may be peaks which no longer fall inside the footprint. Call
``removeOrphanPeaks`` to remove these peaks. Below modifying a ``SpanSet`` is
demonstrated.

.. code-block:: python

    from lsst.afw.geom import SpanSet
    from lsst.afw.detection import Footprint

    # Create a SpanSet and use it to construct a Footprint
    radius = 5
    ss = SpanSet.fromShape(radius, offset=(10, 10))
    foot = Footprint(ss)

    # Demo the SpanSet is the same
    assert(ss == foot.spans)

    # Create a new SpanSet and assign it to the Footprint
    newRadius = 4
    ss2 = SpanSet.fromShape(newRadius, offset=(7, 7))
    foot.spans = ss2

    # Show that the SpanSet held by the Footprint is no longer the original
    assert(ss != foot.spans)

Some common operations which involve calling ``SpanSet`` methods, and
possibly setting the results back to a ``Footprint``, have convenience methods
defined in the ``Footprint`` class. Unlike the ``SpanSet`` class a
``Footprint`` is mutable, such that calling the some convenience methods (e.g.
``dilate``, ``erode``) modifies the ``Footprint``, as shown below.

.. code-block:: python

    from lsst.afw.geom import SpanSet
    from lsst.afw.detection import Footprint

    # Create a SpanSet and use it to construct a Footprint
    radius = 5
    ss = SpanSet.fromShape(radius, offset=(10, 10))
    foot = Footprint(ss)

    # Grab the SpanSet back from the Footprint and show it is the same
    ssFromFoot = foot.spans
    assert(ss is ssFromFoot)

    # Modify the Footprint in place (automatically update the internal reference
    # to the SpanSet), show that they are now different
    kernelRad = 2
    foot.erode(kernelRad)
    newSsFromFoot = foot.spans
    assert(ss is not newSsFromFoot)

In cases where the location information is all that is needed, it is strongly
suggested to use a ``SpanSet`` directly and avoid the overhead of carrying
around an empty ``PeakCatalog``. The constructors for a ``Footprint`` have been
structured to remind users of this by first requiring a SpanSet to be created.

Peaks
=====

Information on the location and intensity of the detected peaks within a
``Footprint`` are kept in a ``PeakCatalog`` which can be retrieved with the
``getPeaks`` method (or the .peaks property within Python). Like with a
``Footprint``'s ``SpanSet`` it is possible to set a ``Footprint's``
``PeakCatalog``. Unlike with the ``SpanSet`` member it is only possible to set a
new ``PeakCatalog`` if the existing catalog is empty. It is also possible to
change the schema that defines the ``PeakCatalog`` but the existing catalog must
be empty for this operation as well. Another asymmetry between the ``SpanSet``
and ``PeakCatalog`` members is in the behavior of the Python property accessor.
In Python the .spans property is both readable and writable, while the .peaks
property is read only. This behavior is intended to make a programmer cognisant
of the fact that the ``PeakCatalog`` can only be set if it is currently empty.
The ``PeakCatalog`` can be populated by calling methods on the catalog itself,
or with the convenience method ``addPeak`` supplied with the ``Footprint``
class. An additional convince method ``sortPeaks`` is also provided to increase
the ease of sorting the catalog. The following is an example of using a
``Footprint``'s ``PeakCatalog``.

.. code-block:: python

    from lsst.afw.geom import SpanSet
    from lsst.afw.detection import Footprint

    # Create a Footprint from a SpanSet
    radius = 5
    ss = SpanSet.fromShape(radius, offset=(10, 10))
    foot = Footprint(ss)

    # Add a few peaks to the PeakCatalog (x, y, intensity)
    foot.addPeak(7, 7, 95)
    foot.addPeak(8, 8, 103)
    foot.addPeak(9, 9, 100)

    # Sort the peaks according to the intensity
    foot.sortPeaks()

    # Print the peaks in the Footprint
    for peak in foot.peaks:
        print(peak.getPeakValue())

    # Output:
    # 103.0
    # 100.0
    # 95.0

Regions
=======

The ``Footprint`` class also contains a few miscellaneous data members and
methods unrelated to the main data containers mentioned above. One such data
member is the``region`` which defines the boundary of the image in which the
detection was made. This property can be retrieved or set with ``getRegion``
and ``setRegion`` respectively.

Transformations
===============

A method named ``transform`` is provided which operates on both the ``SpanSet``
and ``PeakCatalog`` transforming the x, y values into a new coordinate system.
The transform method returns a newly
created Footprint.

Handling discontinuous Footprints
=================================

To split apart a footprint which may have a discontinuous area into continuous
regions which contain only peaks which fall in the region use the ``split``
method. Occasionally, as mentioned above, operations on the ``SpanSet`` may
create an area which no longer contains the x, y locations of peaks in the
``PeakCatalog``. When this occurs the ``removeOrphanPeaks`` may be used to trim
peaks which fall outside the new area.