<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def ellipse(data, channels, center, a, b, theta=0, log=False, full_output=False):
 """ Gate that preserves events inside an ellipse-shaped region. Events are kept if they satisfy the following relationship:: (x/a)**2 + (y/b)**2 <= 1 where `x` and `y` are the coordinates of the event list, after substracting `center` and rotating by -`theta`. This is mathematically equivalent to maintaining the events inside an ellipse with major axis `a`, minor axis `b`, center at `center`, and tilted by `theta`. Parameters data : FCSData or numpy array NxD flow cytometry data where N is the number of events and D is the number of parameters (aka channels). channels : list of int, list of str Two channels on which to perform gating. center, a, b, theta (optional) : float Ellipse parameters. `a` is the major axis, `b` is the minor axis. log : bool, optional Flag specifying that log10 transformation should be applied to `data` before gating. full_output : bool, optional Flag specifying to return additional outputs. If true, the outputs are given as a namedtuple. Returns ------- gated_data : FCSData or numpy array Gated flow cytometry data of the same format as `data`. mask : numpy array of bool, only if ``full_output==True`` Boolean gate mask used to gate data such that ``gated_data = data[mask]``. contour : list of 2D numpy arrays, only if ``full_output==True`` List of 2D numpy array(s) of x-y coordinates tracing out the edge of the gated region. Raises ------ ValueError If more or less than 2 channels are specified. """

    # Extract channels in which to gate
    if len(channels) != 2:
        raise ValueError('2 channels should be specified.')
    data_ch = data[:,channels].view(np.ndarray)

    # Log if necessary
    if log:
        data_ch = np.log10(data_ch)

    # Center
    center = np.array(center)
    data_centered = data_ch - center

    # Rotate
    R = np.array([[np.cos(theta), np.sin(theta)],
                    [-np.sin(theta), np.cos(theta)]])
    data_rotated = np.dot(data_centered, R.T)

    # Generate mask
    mask = ((data_rotated[:,0]/a)**2 + (data_rotated[:,1]/b)**2 <= 1)

    # Gate
    data_gated = data[mask]

    if full_output:
        # Calculate contour
        t = np.linspace(0,1,100)*2*np.pi
        ci = np.array([a*np.cos(t), b*np.sin(t)]).T
        ci = np.dot(ci, R) + center
        if log:
            ci = 10**ci
        cntr = [ci]

        # Build output namedtuple
        EllipseGateOutput = collections.namedtuple(
            'EllipseGateOutput',
            ['gated_data', 'mask', 'contour'])
        return EllipseGateOutput(
            gated_data=data_gated, mask=mask, contour=cntr)
    else:
        return data_gated