Пример #1
0
def mask_percent_truncation(data_array, top_percent, bottom_percent):
    """ This mask truncates the top and bottom percent of values 
    provided.

    The values ``top_percent`` and ``bottom_percent`` notate the 
    percentage of values from top and bottom of the data array 
    (in number of values) that should be masked. The values masked 
    are independent on the previous masks applied.

    If the percentage of values leads to a non-integer number of
    values to be masked or where many of the same value is present,
    values are kept.

    Parameters
    ----------
    data_array : ndarray
        The data array that the mask will be calculated from. 
    top_percent : float
        The percent of values from the top (highest value) of 
        the array that is to be masked. Must be between 0 and 1.
    bottom_percent : float
        The percent of values from the bottom (lowest value) of 
        the array that is to be masked. Must be between 0 and 1.

    Returns
    -------
    final_mask : ndarray
        The mask as computed by this function.
    """
    # For higher precision, in a way. It also gets around rounding
    # errors for the percentile to count conversion.
    n_data_points = int(np.size(data_array))
    top_percent = decimal.Decimal(str(top_percent))
    bottom_percent = decimal.Decimal(str(bottom_percent))
    ONE = decimal.Decimal('1.0')

    # Ensure that they are percentages.
    if (not (0 <= top_percent <= 1)):
        raise mono.InputError("The top percent must be between 0 and 1.")
    if (not (0 <= bottom_percent <= 1)):
        raise mono.InputError("The bottom percent must be between 0 and 1.")

    # The percentage cuts are just fancy count cuts. We will apply
    # them as so.
    top_count = np.floor(top_percent * n_data_points)
    bottom_count = np.floor(bottom_percent * n_data_points)
    # We rely on the mask count truncation being kept.
    final_mask = mask_count_truncation(data_array=data_array, 
                                       top_count=top_count, 
                                       bottom_count=bottom_count)
    # Finally return
    return final_mask
Пример #2
0
def get_module_file(module):
    """ This gets the absolute pathname of a module's .py file.
    If the entry is not a module, an error is raised.

    Credit: https://stackoverflow.com/a/12154601

    Parameters
    ----------
    module : module
        The module that its file's pathname will be found.

    Returns
    -------
    pathname : string
        The pathname of the module file, as defined by the 
        operating system.
    """
    # Make sure it is module.
    if (not inspect.ismodule(module)):
        raise mono.InputError("The inputed object is not a module. Its "
                              "type is: `{ty}`".format(ty=type(module)))
    else:
        # Naming convention.
        pathname = inspect.getfile(module)
        return pathname
    # The code should not reach here.
    raise mono.BrokenLogicError
    return None
Пример #3
0
def email_sendgrid(from_email, to_email, subject, content, api_key=None):
    """ This function uses SendGrid's API to send an email. There
    is a limit on 100 emails/day.

    Parameters
    ----------
    from_email : string
        The email address from which the email is sent from.
    to_email: string
        The email addresses which the email is sent to.
    subject : string
        The email subject line. It is suggested that this is a 
        concise message.
    content : string
        The content of the email. It may be formatted using HTML.

    Returns
    -------
    None
    
    """
    # Using SendGrid's Python Library
    # https://github.com/sendgrid/sendgrid-python

    # Compile the message.
    message = sendgrid.helpers.mail.Mail(from_email=from_email,
                                         to_emails=to_email,
                                         subject=subject,
                                         html_content=content)

    # Send the message. This is an API key, it can either be
    # hard coded here or used as an input.
    api_key = api_key
    if (api_key is None):
        raise mono.InputError("The SendGrid API key is not provided.")
    else:
        using_api_key = api_key
    try:
        sg = sendgrid.SendGridAPIClient(using_api_key)
        response = sg.send(message)
        # Test to see if the email was accepted by the server.
        if (response.status_code == 202):
            # It was accepted.
            pass
        else:
            # It was not formally accepted, there is a different
            # status code.
            mono.warn(mono.APIWarning,
                      ("The email may not have been sent. The HTTP status "
                       "code for this transaction is: {code}".format(
                           code=response.status_code)))
    except Exception:
        raise

    return None
Пример #4
0
def _extract_oeis_values(sequence_key, index, count):
    """ This uses the OEIS and obtains values from it according to
    the values provided.

    Parameters
    ----------
    sequence_key : string
        The name of the sequence that will be used. If it is not 
        an OEIS sequence, an error is raised.
    index : integer
        The 0-indexed index of the location within the sequence 
        that the index should start from.
    count : integer
        The number of values including the index that should be 
        obtained.

    Return
    ------
    oeis_sequence : list
        The sequence from the OEIS.
    """
    if (index < 0):
        # Reverse indexing are not allowed as many OEIS sequences
        # are infinity long.
        raise mono.InputError("The index from which the sequence will start "
                              "from must be greater than 0.")
    if (count < 1):
        raise mono.InputError("The number count of prime numbers "
                              "returned must be greater than one.")

    # Obtain the OEIS sequence.
    oeis_sequence = getattr(oeis, str(sequence_key), None)
    # Test that the sequence is valid.
    if (oeis_sequence is None):
        raise mono.InputError("The OEIS sequence name is not a valid "
                              "sequence.")
    else:
        # It should be a valid sequence, extract the numbers.
        return oeis_sequence[index:index + count]
    # The code should not reach here.
    raise mono.BrokenLogicError
    return None
Пример #5
0
def mask_single_pixels(data_array, column_indexes, row_indexes):
    """ This applies a single mask on a single pixel(s)

    As the name implies, this function masks a single pixel value or 
    a list of single pixel pairs. 

    Parameters
    ----------
    data_array : ndarray
        The data array that the mask will be calculated from.
    column_indexes : list or ndarray
        The successive 0-indexed list of column indexes that specify 
        the pixel to be masked.
    row_indexes : list or ndarray
        The successive 0-indexed list of row indexes that specify the 
        pixel to be masked.

    Returns
    -------
    final_mask : ndarray
        A boolean array for pixels that are masked (True) or are 
        valid (False).

    """
    # Flatten the column and row indexes in the event that they are
    # stacked?
    column_indexes = np.ravel(np.array(column_indexes, dtype=int))
    row_indexes = np.ravel(np.array(row_indexes, dtype=int))

    # Input validation. Both should be ordered pairs and thus have
    # the same size.
    if (column_indexes.size != row_indexes.size):
        raise mono.InputError(
            "The column and row indexes should be "
            "parallel arrays, the current inputs are of "
            "different length. "
            "\n Column:  {col_index} \n Row:  {row_index}".format(
                col_index=column_indexes, row_index=row_indexes))

    # Taking a template mask to then change.
    masked_array = mask_nothing(data_array=data_array)

    # Loop over all of the pixel pairs, making as you proceed.
    for columndex, rowdex in zip(column_indexes, row_indexes):
        masked_array[rowdex, columndex] = True

    # Finished.
    final_mask = masked_array
    return final_mask
Пример #6
0
def combine_modules(*args, name=None, docstring=None, **kwargs):
    """ This function combines the name-spaces of multiple modules.
    In essence, it is similar to importing many things under a 
    a single module name.

    Parameters
    ----------
    *args : modules
        The modules to be combined. The right most module will
        always overwrite the left most.
    name : string (optional)
        The name of the module to be done, defaults to 'synthesis'.
    docstring : string (optional)
        The docstring of this module, defaults to 
        'synthesis docstring'
    **kwargs : parameters
        There should be no other keyword arguments, will raise 
        if there are.

    Returns
    -------
    combined_module : module
        The combined module.
    """
    # The defaults.
    name = str(name) if (name is not None) else 'synthesis'
    docstring = (str(docstring) if
                 (docstring is not None) else 'synthesis docstring')

    # There should be no keyword arguments.
    if (len(kwargs) >= 1):
        raise mono.InputError("There should be no keyword arguments for "
                              "the combining of modules.")
    # It should be as simple as combining all of the members of each
    # module into one dictionary.
    combine_module = types.ModuleType(name, doc=docstring)
    for moduledex in args:
        combine_module.__dict__.update(moduledex.__dict__)
    # All done.
    return combine_module
Пример #7
0
def delete_substrings(string, substrings):
    """ Deletes all occurrences of any substrings provided from the 
    original string.

    Parameters
    ----------
    string : string
        The string to be purged of all substrings.
    substrings : string or list
        The substrings in a list, or a string by itself.

    Returns
    -------
    purged_string : string
        The string after it had all substring occurrences taken out.    
    """
    # Just in case.
    original_string = copy.deepcopy(string)

    # Check if the substring is singular or is a list of substrings.
    # It is better to handle a one long list.
    if (isinstance(substrings, str)):
        substrings = [
            substrings,
        ]
    elif (isinstance(substrings, (list, tuple))):
        pass
    else:
        # It must be a string or a list of strings.
        raise mono.InputError("The substring input must be a string or "
                              "list of strings.")

    # Purge all substrings using the built-in replace method and
    # going through.
    for substringdex in substrings:
        original_string = original_string.replace(substringdex, '')

    # Naming convention.
    purged_string = original_string
    return purged_string
Пример #8
0
def ravel_dictionary(dictionary, conflict):
    """ This function unravels a dictionary, un-nesting
    nested dictionaries into a single dictionary. If
    conflicts arise, then the conflict rule is used.
    
    The keys of dictionary entries that have dictionary
    values are discarded.
    
    Parameters
    ----------
    dictionary : dictionary
        The dictionary to be unraveled.
    conflict : string
        The conflict rule. It may be one of these:
        
        * 'raise'
            If a conflict is detected, a 
            sparrowmonolith.DataError will be raised.
        * 'superior'
            If there is a conflict, the least 
            nested dictionary takes precedence. Equal
            levels will prioritize via alphabetical. 
        * 'inferior'
            If there is a conflict, the most
            nested dictionary takes precedence. Equal
            levels will prioritize via anti-alphabetical.
        
    Returns
    -------
    raveled_dictionary : dictionary
        The unraveled dictionary. Conflicts were replaced
        using the conflict rule.
    """
    # Reaffirm that this is a dictionary.
    if (not isinstance(dictionary, dict)):
        dictionary = dict(dictionary)
    else:
        # All good.
        pass
    # Ensure the conflict is a valid conflict type.
    conflict = str(conflict).lower()
    if (conflict not in ('raise', 'superior', 'inferior')):
        raise mono.InputError("The conflict parameter must be one the "
                              "following: 'raise', 'superior', 'inferior'.")

    # The unraveled dictionary.
    raveled_dictionary = dict()
    # Sorted current dictionary. This sorting helps
    # with priorities prescribed by `conflict`.
    sorted_dictionary = dict(sorted(dictionary.items()))
    for keydex, itemdex in sorted_dictionary.items():
        # If this entry is a dictionary, then
        # recursively go through it like a tree search.
        if (isinstance(itemdex, dict)):
            temp_dict = ravel_dictionary(dictionary=itemdex, conflict=conflict)
        else:
            # It is a spare item, create a dictionary.
            temp_dict = {keydex: itemdex}
        # Combine the dictionary, but, first, check for
        # intersection conflicts.
        if (len(raveled_dictionary.keys() & temp_dict.keys()) != 0):
            # There are intersections. Handle them based
            # on `conflict`.
            if (conflict == 'raise'):
                raise mono.DataError("There are conflicts in these two "
                                     "dictionaries: \n"
                                     "Temp : {temp} \n Ravel : {ravel}".format(
                                         temp=temp_dict,
                                         ravel=raveled_dictionary))
            elif (conflict == 'superior'):
                # Preserve the previous entries as they are
                # either higher up in the tree or are
                # ahead alphabetically.
                raveled_dictionary = {**temp_dict, **raveled_dictionary}
            elif (conflict == 'inferior'):
                # Preserve the new entires as they are
                # either lower in the tree or are behind
                # alphabetically.
                raveled_dictionary = {**raveled_dictionary, **temp_dict}
            else:
                # The code should not get here.
                raise mono.BrokenLogicError("The input checking of conflict "
                                            "should have caught this.")
        else:
            # They can just be combined as normal. Taking superior
            # as the default.
            raveled_dictionary = {**temp_dict, **raveled_dictionary}

    # All done.
    return raveled_dictionary
Пример #9
0
def mask_sigma_value(data_array, sigma_multiple, sigma_iterations=1):
    """
    This applies a mask on values outside a given multiple of a 
    sigma value.
    
    This function masks values if they are outsize of a sigma range 
    from the mean. The mean and sigma values are automatically 
    calculated from the array provided. 

    Parameters
    ----------
    data_array : ndarray
        The data array that the mask will be calculated from. 
    sigma_multiple : float or array-like
        The multiple of sigma which will be applied. Unequal 
        bottom-top bounds may be set as a list-like input. The 
        first element is the bottom bound; the last element is the 
        top bound.
    sigma_iterations : int
        The number of iterations this filler will run through to
        develop the proper mask.

    Returns
    -------
    final_mask : ndarray
        The mask as computed by this function.
    """
    # It does not make sense to run this mask with no iterations.
    if (sigma_iterations < 1):
        raise mono.InputError("It does not make sense to do this "
                              "mask with less than 1 iteration.")

    # Check if the sigma limits are the same, or the user 
    # provided for a lower and upper sigma to use.
    if (isinstance(sigma_multiple,(int,float))):
        # It is just a number, apply them evenly.
        bottom_sigma_multiple = sigma_multiple
        top_sigma_multiple = sigma_multiple
    elif (np.array(sigma_multiple).size == 1):
        # It is likely that this is a single number, but just 
        # embedded in an array.
        flat_sigma_multiple = np.squeeze(np.array(sigma_multiple))
        bottom_sigma_multiple = flat_sigma_multiple
        top_sigma_multiple = flat_sigma_multiple
    else:
        # The sigma multiple is most likely some sort of array for 
        # uneven values.
        flat_sigma_multiple = np.squeeze(np.array(sigma_multiple))
        bottom_sigma_multiple = flat_sigma_multiple[0]
        top_sigma_multiple = flat_sigma_multiple[-1]

    # The number of iterations are accomplished by just doing loops.
    final_mask = mono.mask.mask_nothing(data_array=data_array)
    for iterdex in range(sigma_iterations):
        # Calculate the mean and the sigma values of the data array.
        # masked values mean it was caught in previous iterations.
        mean = mono.math.statistics.arithmetic_mean(
            array=np_ma.array(data_array, mask=final_mask).compressed())
        stddev = mono.math.statistics.standard_deviation(
            array=np_ma.array(data_array, mask=final_mask).compressed())
        
        # Calculating the two individual masks and combining them.
        min_mask = mask_minimum_value(
            data_array=data_array, 
            minimum_value=(mean - stddev * bottom_sigma_multiple))
        max_mask = mask_maximum_value(
            data_array=data_array, 
            maximum_value=(mean + stddev * top_sigma_multiple))
        
        # The mask based version is proper, the difference between a 
        # mask and a mask is just semantics. Also, keep track
        # of the previous masks all run through the iterations.
        final_mask = mono.mask.combine_masks_lor(
            final_mask, min_mask, max_mask)

    return final_mask
def maximum_time_observable(target_dec,
                            obs_latitude,
                            minimum_alt,
                            angle_unit='radian'):
    """ This function returns the maximum duration of time, in 
    hours, of how long an astronomical object can be observed over 
    some minimum altitude. (The day of observation is assumed to be
    one of the better nights of the year.) Arrays are supported.

    Parameters
    ----------
    target_dec : float
        The declination of the observing target. It must be in the 
        angular units specified by `angle_unit`. 
    obs_latitude : float
        The latitude of the observer (i.e. telescope) on the Earth.
        It must be in the angular units specified by `angle_unit`. 
    minimum_alt : float 
        The minimum altitude that the object must be above to be 
        considered observable. This is mostly used for observing
        above a given airmass. It must be in the angular units 
        specified by `angle_unit`.
    angle_unit : string (optional)
        The unit of angle that the values are inputted as; it can
        be either `radian` for radians or `degree` for degrees.

    Return
    ------
    hours_observeable : float
        The number of hours that the target is considered 
        observable. The hours are in decimal form.
    """
    # Convert the angles to radians as that is what Numpy natively
    # uses.
    angle_unit = str(angle_unit).lower()
    if (angle_unit == 'radian'):
        # The angles are already in radians, there is no need for
        # conversion. But, still use Numpy arrays for broadcasting.
        target_dec = np.array(target_dec)
        obs_latitude = np.array(obs_latitude)
        minimum_alt = np.array(minimum_alt)
    elif (angle_unit == 'degree'):
        # Convert all of the angles to radians and arrays for
        # broadcasting.
        target_dec = np.deg2rad(target_dec)
        obs_latitude = np.deg2rad(obs_latitude)
        minimum_alt = np.deg2rad(minimum_alt)
    else:
        # The angle unit is unknown.
        raise mono.InputError("The angular unit provided is not a valid "
                              "angular unit for this function. Accepted "
                              "units are: `radian`, `degree`")

    # Double check that the angular measurements falls within
    # physical boundaries. (Using decimal/sympy for
    # higher numerical precision.)
    precision = 20
    ZERO = decimal.Decimal('0')
    PI_HALF = decimal.Decimal(str(sy.N(sy.pi / 2, precision)))
    if (np.where((-PI_HALF <= target_dec) & (target_dec <= PI_HALF), False,
                 True).any()):
        # Target declinations are outside +/- pi/2 radians.
        raise mono.InputError(
            "There exists at least one target declination "
            "outside the polar limits of +/- pi/2. "
            "Target declinations: {t_dec}".format(t_dec=target_dec))
    if (np.where((-PI_HALF <= obs_latitude) & (obs_latitude <= PI_HALF), False,
                 True).any()):
        # Observatory latitudes are outside +/- pi/2 radians.
        raise mono.InputError(
            "There exists at least one observatory "
            "latitude outside the polar limits "
            "of +/- pi/2. "
            "Observatory latitudes: {o_lat}".format(o_lat=obs_latitude))
    if (np.where((ZERO <= minimum_alt) & (minimum_alt <= PI_HALF), False,
                 True).any()):
        # Minimum altitudes are outside 0 to pi/2 radians.
        raise mono.InputError(
            "There exists at least one minimum altitude "
            "outside the horizon-zenith limits "
            "of 0 to +pi/2. "
            "Minimum altitudes: {m_alt}".format(m_alt=minimum_alt))

    # Using an equation to compute the maximum time observable.
    # This equation comes from using Equatorial in AltAz and finding
    # the limiting hour angles on both sides of the meridian.
    # See ``Sparrow's Notes Maximum Astronomical Observing Time``
    # for more information.
    def _extended_arccos_function(input):
        """ The arccos function is only defined between a domain of
        0 <= x <= 1. However, the internal value of this arccos 
        lends itself to have input values outside of this range.
        This arccos extension applies the physical meaning that 
        if an input of less than -1 is given, the target has an 
        observing time of 24 hours; if it has an input greater 
        than 1, it has an observing time of 0 hours. Justification  
        is not mathematical, but physical; out math's logical limits.
        """
        # Array for broadcasting
        input = np.asarray(input)
        output = np.zeros_like(input)
        # Calculate the arccos, deal with the unique values after.
        with mono.silence_specific_warning(RuntimeWarning):
            output = np.arccos(input)
        # Internal value less than -1 --> 24 hours ~~> pi rad one HA.
        output[np.asarray(input <= -1).nonzero()] = np.pi
        # Internal value more than 1 --> 0 hours ~~> 0 rad one HA.
        output[np.asarray(1 <= input).nonzero()] = 0
        # Retain the higher precision where possible.
        return output

    # Calculate the angular observable duration.
    _internal = (
        (np.sin(minimum_alt) - np.sin(obs_latitude) * np.sin(target_dec)) /
        (np.cos(obs_latitude) * np.cos(target_dec)))
    radians_observeable = 2 * _extended_arccos_function(input=_internal)

    # Convert the radian angle result to units of hours
    # via 1 hr = 15 deg = pi/12 rad. (The declination dependence was
    # already accounted for in the previous equation.)
    hours_observeable = radians_observeable * (12 / np.pi)
    # Sanity check and all done.
    assert np.all(hours_observeable <= 24), "Too many hours in a day."
    return hours_observeable