#TODO: docstring

    :param fiContainer: #TODO: docstring
    :param timeLimit: #TODO: docstring
    :param massLimit: #TODO: docstring
    :param eucLimit: #TODO: docstring
    :param timeKey: #TODO: docstring
    :param massKey: #TODO: docstring
    :param massScalingFactor: #TODO: docstring
    :param categoryKey: #TODO: docstring
    :param charges: #TODO: docstring
    :param matchArraySelector: #TODO: docstring
    :param specfiles: limit grouping to these specfiles

    :returns: #TODO docstring, :class:`FgiContainer`

def lfqFeatureGrouping(fiContainer, timeLimit=40, massLimit=10*1e-6,
                       eucLimit=None, timeKey='rt', massKey='mz',
                       massScalingFactor=None, categoryKey='specfile',
                       charges=None, matchArraySelector=None, specfiles=None):
    """ #TODO: docstring

    :param fiContainer: #TODO: docstring
    :param timeLimit: #TODO: docstring
    :param massLimit: #TODO: docstring
    :param eucLimit: #TODO: docstring
    :param timeKey: #TODO: docstring
    :param massKey: #TODO: docstring
    :param massScalingFactor: #TODO: docstring
    :param categoryKey: #TODO: docstring
    :param charges: #TODO: docstring
    :param matchArraySelector: #TODO: docstring
    :param specfiles: limit grouping to these specfiles

    :returns: #TODO docstring, :class:`FgiContainer`
    """
    # --- perform the whole feature grouping process --- #
    targetChargeStates = range(1, 6) if charges is None else charges
    if matchArraySelector is None:
        matchArraySelector = lambda arr: numpy.any(arr['isAnnotated'])
    if massScalingFactor is None:
        massScalingFactor = timeLimit / massLimit
    if eucLimit is None:
        eucLimit = timeLimit
    if specfiles is None:
        specfiles = sorted(viewkeys(fiContainer.info))

    #'massToleranceMode': 'relative'
    #'timeToleranceMode': 'absolute'
    fgiContainer = FgiContainer(specfiles)

    logMassLimit = log2RelativeMassLimit(massLimit)
    logMassKey = 'logMass'

    logToleranceFactor = massLimit / log2RelativeMassLimit(massLimit)
    logMassScalingFactor = massScalingFactor * logToleranceFactor
    """ Note: because "a" is similar to "b"
    a = (1- 400 / 400.001) * massScalingFactor
    b = (numpy.log2(400.001) - numpy.log2(400)) * logMassScalingFactor
    """

    fiArrayKeys = [massKey, timeKey, 'isAnnotated', 'isMatched']

    for _charge in targetChargeStates:
        # - Prepare feature arrays - #
        fiSelector = lambda fi: fi.charge == _charge and fi.isValid
        fiArrays = fiContainer.getArrays(fiArrayKeys, specfiles, sort=massKey,
                                         selector=fiSelector)
        fiArrays['logMass'] = numpy.log2(fiArrays[massKey])
        if listvalues(fiArrays)[0].size == 0:
            continue

        # - group features which are in close mass and time proximity - #
        continuousGroups = massTimeContinuityGroups(fiArrays, logMassKey,
                                                    timeKey, logMassLimit,
                                                    timeLimit
                                                    )

        # - perform proximity grouping - #
        matchArrayKeys = list(viewkeys(fiArrays))

        for groupId in range(len(continuousGroups)):
            #Grab the arrays of the current feature continuity group
            groupPositions = continuousGroups[groupId]
            matchArr = getContGroupArrays(fiArrays, groupPositions,
                                          matchArrayKeys
                                          )
            if not matchArraySelector(matchArr):
                continue

            #Calculate a sorted list of all euclidean feature distances
            matchArr['mNorm'] = matchArr[logMassKey] * logMassScalingFactor
            distInfo = calcDistMatchArr(matchArr, timeKey, 'mNorm')

            #Group fi according to their proximity
            linkageGroups = proximityGrouping(matchArr, distInfo, eucLimit,
                                              categoryKey
                                              )

            #Generate feature groups from the linked features
            fgiIds = generateFeatureGroups(fgiContainer, linkageGroups,
                                           matchArr, timeKey, massKey,
                                           logMassKey, logMassScalingFactor
                                           )

            #Set charge manually
            for fgiId in fgiIds:
                fgiContainer.container[fgiId].charge = _charge

            #Mark overlapping groups as not valid (fgi.isValid = False)
            fgiDoOverlap = findFgiOverlaps(fgiContainer, fgiIds)

        #Add feature intensities to the feature groups
        fgiContainer.updateIntensities(fiContainer)
    return fgiContainer