""".. _Template-AT-api:
   **Template_AT** --- Cube manipulation template.
   -----------------------------------------------
   This module defines the Template_AT class.
"""
import sys, os
import numpy as np
import admit
import admit.util.bdp_types as bt
[docs]class Template_AT(admit.Task):
    """
    Image cube manipulation sample template.
    This task demonstrates several ways of manipulating a generic
    position-position-frequency (or velocity) data cube.  It can be used as a
    template by users developing their own cube analysis routines. This
    template demonstrates the following transformations:
    - scale the intensity at all points in the cube by a specified value
    - extract a 2-D position-position image from the (scaled) cube at a 
      specified frequency (channel number)
    - extract a 1-D spectrum from the (scaled) cube at the specified spatial
      position (pixel coordinates)
    Each of these operations is controlled by keywords.
    **Keywords**
      **cubescale**: float
        The value by which to scale the cube intensity; default: 2.0.
      **imgslice**: int
        Frequency (pixel index) of the 2-D image plane to extract from the
        cube; default: 0 (the first spectral channel).
      **specpos**: 2-tuple of int
        The 2-D (spatial) pixel position from which to extract the 1-D cube
        spectrum; default: (0,0).
    **Input BDPs**
      **SpwCube_BDP**: count: 1
        Input 3-D data cube; e.g., as output from an
        `Ingest_AT <Ingest_AT.html>`_,
        `ContinuumSub_AT <ContinuumSub_AT.html>`_ or
        `LineCube_AT <LineCube_AT.html>`_.
    **Output BDPs**
      **SpwCube_BDP**: count: 1
        Output scaled 3-D data cube.
      **Image_BDP**: count: 1
        Output 2-D image sliced from the cube.
      **Table_BDP**: count: 1
        Output 1-D spectrum extracted from the cube.
    Parameters
    ----------
    keyval : dict, optional
      Dictionary of keyword:value pairs.
    Attributes
    ----------
    _version : str
      Version ID string.
    Notes
    -----
    Whenever the expected type or number of keywords or input/output BDPs
    changes, the __init__ method must be updated accordingly.
    """
    def __init__(self, **keyval):
        keys = {"cubescale" : 2.0, "imgslice" : 0, "specpos" : (0,0)}   
        admit.Task.__init__(self, keys, keyval)
        self._version   = "1.0.0"
        self.set_bdp_in ([(admit.SpwCube_BDP, 1, bt.REQUIRED)])
        self.set_bdp_out([(admit.SpwCube_BDP, 1), (admit.Image_BDP, 1),
                          (admit.Table_BDP, 1)])
[docs]    def summary(self):
        """
        Summary data dictionary.
        Template_AT adds the following to ADMIT summary:
        .. table::
           :class: borderless
           +----------+----------+-----------------------------------+
           |   Key    | type     |    Description                    |
           +==========+==========+===================================+
           | spectra  | list     | Plots of cube slice and spectrum. |
           +----------+----------+-----------------------------------+
           | template | table    | Template data product statistics. |
           +----------+----------+-----------------------------------+
        
        Parameters
        ----------
        None
        Returns
        -------
        dict
            Dictionary of SummaryEntry
        """
        if hasattr(self,"_summary"):
            return self._summary
        else:
            return {} 
[docs]    def userdata(self):
        """
        User data dictionary.
        Parameters
        ----------
        None
        
        Returns
        -------
        dict
            The user data dictionary from the AT, for merging into the ADMIT
            user data object.  
        """
        if hasattr(self,"_userdata"):
            return self._userdata
        else:
            return {} 
[docs]    def run(self):
        """
        Task run method.
        Outputs three sample data products from a single input data cube:
        1. A cube scaled by the value of *cubescale*.
        2. An image sliced from the cube at frequency *imgfreq*.
        3. A spectrum extracted from the cube at position *specpos*.
        Parameters
        ----------
        None
        
        Returns
        -------
        None
        """
        self._summary = {}
        # BDP output uses the alias name if provided, else a flow-unique name.
        stem = self._alias
        if not stem: stem = "template%d" % (self.id())
        # The input data cube BDP.
        ibdp = self._bdp_in[0]
        # The data cube is a CASA image on disk.
        # Get its file name, read it in and convert to a NumPy array.
        # The entire cube is read into memory (large cubes may exceed RAM!)
        # Any masked pixels are converted to zero.
        istem = ibdp.getimagefile(bt.CASA)
        ifile = ibdp.baseDir() + istem
        icube = admit.casautil.getdata(ifile, zeromask=True).data
        assert len(icube.shape) == 3, "Only 3-D data cubes supported"
        admit.logging.info((istem + " dimensions: (%d, %d, %d)") % icube.shape)
        # ===================================================================
        # 1. Scale the entire cube by 'cubescale'.
        # ===================================================================
        cubescale = self.getkey('cubescale')
        ocube = icube * cubescale
        ocubestem = "%s_cube" % stem
        ocubefile = self.baseDir() + ocubestem
        #
        # Create a CASA ouput image (no mask information).
        admit.casautil.putdata_raw(ocubefile+".im", ocube, ifile)
        # 
        # Output the result as a new SpwCube.
        image = admit.Image(description="Template 3-D Cube")
        image.addimage(admit.imagedescriptor(ocubefile+".im", format=bt.CASA))
        obdp1 = admit.SpwCube_BDP(ocubestem)
        obdp1.addimage(image)
        self.addoutput(obdp1)
        # ===================================================================
        # 2. Slice a 2-D image from the cube at frequency 'imgslice'.
        # ===================================================================
        #
        # The frequency axis is the third array index.
        # Clip slice to be in range.
        imgslice = max(0, min(ocube.shape[2]-1, self.getkey('imgslice')))
        oimg = ocube[:, :, imgslice]
        oimgstem = "%s_img" % stem
        oimgfile = self.baseDir() + oimgstem
        #
        # Create a CASA format ouput image.
        # @TODO Does not work for 3-D -> 2-D output.
        #admit.casautil.putdata_raw(oimgfile+".im", oimg, ifile)
        #
        # Create a PNG format output image.
        # Image data must be rotated to match matplotlib axis order.
        oimgtitle="Template Image Slice @ channel %d" % imgslice
        aplot = admit.APlot(figno=1, abspath=self.baseDir(),
                            pmode=admit.PlotControl.BATCH,
                            ptype=admit.PlotControl.PNG)
        aplot.map1(np.rot90(oimg),
                   xlab="R.A. (pixels)", ylab="Dec (pixels)",
                   title=oimgtitle, figname=oimgstem)
        aplot.final()
        #
        # Output data product referencing both image formats.
        image = admit.Image(description="Template 2-D Image")
        image.addimage(admit.imagedescriptor(oimgfile+".im",  format=bt.CASA))
        image.addimage(admit.imagedescriptor(oimgstem+".png", format=bt.PNG))
        obdp2 = admit.Image_BDP(oimgstem)
        obdp2.addimage(image)
        self.addoutput(obdp2)
        # ===================================================================
        # 3. Extract a 1-D spectrum from the cube at position 'specpos'.
        # ===================================================================
        #
        # Clip position to be in range.
        specpos = self.getkey('specpos')
        specpos = (max(0, min(ocube.shape[0]-1, specpos[0])),
                   max(0, min(ocube.shape[1]-1, specpos[1])))
        ospec = ocube[specpos[0], specpos[1], :]
        ochan = np.arange(ospec.shape[0])
        ospecstem = "%s_spec" % stem
        #
        # Create a PNG plot (standalone).
        ospectitle = "Template Spectrum @ position %s" % str(specpos)
        aplot = admit.APlot(figno=2, abspath=self.baseDir(),
                            pmode=admit.PlotControl.BATCH,
                            ptype=admit.PlotControl.PNG)
        aplot.plotter(x=ochan, y=[ospec],
                      xlab="Channel", ylab="Intensity",
                      title=ospectitle, figname=ospecstem)
        aplot.final()
        #
        # Output data product (spectrum table).
        obdp3 = admit.Table_BDP(ospecstem)
	obdp3.table.description = "Template 1-D Spectrum"
	obdp3.table.columns = ["Channel", "Spectrum @ (%d, %d)" % specpos]
	obdp3.table.setData(np.transpose(np.vstack([ochan, ospec])))
        self.addoutput(obdp3)
        # ===================================================================
        # Populate example summary object.
        # ===================================================================
        #
        # NOTE: Summary.py, etc., must be updated to recognize this and
        #       for Template_AT to appear in the HTML summary.
        stats = admit.Table()
        stats.description = "Template Data Product Statistics"
        stats.columns = ["Statistic", "InCube", "OutCube", "Image", "Spectrum"]
        stats.setData(
        np.array([
         ["Min", np.amin(icube), np.amin(ocube), np.amin(oimg), np.amin(ospec)],
         ["Max", np.amax(icube), np.amax(ocube), np.amax(oimg), np.amax(ospec)]
        ]))
        #
        # Data product statistics table.
        keys = "cubescale=%s imgslice=%d specpos=%s" % \
            
(str(cubescale), imgslice, str(specpos))
        self._summary["template"] = admit.SummaryEntry([stats.serialize()],
                                                       "Template_AT",
                                                       self.id(True), keys)
        #
        # Plots for the image and spectrum.
        ocubeim = ocubestem+".im"
        spec_description = [
          [0, 0, "", "",
           oimgstem+".png", oimgstem+"_thumb.png", oimgtitle, ocubeim],
          [specpos[0], specpos[1], "", "Channel",
           ospecstem+".png", ospecstem+"_thumb.png", ospectitle, ocubeim]]
        self._summary["spectra"] = admit.SummaryEntry(spec_description,
                                                      "Template_AT",
                                                      self.id(True), keys)