# Source code for astronify.utils.pitch_mapping

```
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
Pitch mapping functionality
===========================
Functionality for taking arbitrary data values to audible pitches.
"""
import warnings
import numpy as np
from astropy.visualization import (SqrtStretch, LogStretch, AsinhStretch, SinhStretch,
LinearStretch, MinMaxInterval, ManualInterval,
AsymmetricPercentileInterval)
from .exceptions import InputWarning, InvalidInputError
__all__ = ['data_to_pitch']
[docs]def data_to_pitch(data_array, pitch_range=[100, 10000], center_pitch=440, zero_point="median",
stretch='linear', minmax_percent=None, minmax_value=None, invert=False):
"""
Map data array to audible pitches in the given range, and apply stretch and scaling
as required.
Parameters
----------
data_array : array-like
Data to map to pitch values. Individual data values should be floats.
pitch_range : array
Optional, default [100,10000]. Range of acceptable pitches in Hz.
center_pitch : float
Optional, default 440. The pitch in Hz where that the the zero point of the
data will be mapped to.
zero_point : str or float
Optional, default "median". The data value that will be mapped to the center
pitch. Options are mean, median, or a specified data value (float).
stretch : str
Optional, default 'linear'. The stretch to apply to the data array.
Valid values are: asinh, sinh, sqrt, log, linear
minmax_percent : array
Optional. Interval based on a keeping a specified fraction of data values
(can be asymmetric) when scaling the data. The format is [lower percentile,
upper percentile], where data values below the lower percentile and above the upper
percentile are clipped. Only one of minmax_percent and minmax_value should be specified.
minmax_value : array
Optional. Interval based on user-specified data values when scaling the data array.
The format is [min value, max value], where data values below the min value and above
the max value are clipped.
Only one of minmax_percent and minmax_value should be specified.
invert : bool
Optional, default False. If True the pitch array is inverted (low pitches become high
and vice versa).
Returns
-------
response : array
The normalized data array, with values in given pitch range.
"""
# Parsing the zero point
if zero_point in ("med", "median"):
zero_point = np.median(data_array)
if zero_point in ("ave", "mean", "average"):
zero_point = np.mean(data_array)
# The center pitch cannot be >= max() pitch range, or <= min() of pitch range.
# If it is, fall back to using the mean of the pitch range provided.
if center_pitch <= pitch_range[0] or center_pitch >= pitch_range[1]:
warnings.warn("Given center pitch is outside the pitch range, defaulting to the mean.",
InputWarning)
center_pitch = np.mean(pitch_range)
if (data_array == zero_point).all(): # All values are the same, no more calculation needed
return np.full(len(data_array), center_pitch)
# Normalizing the data_array and adding the zero point (so it can go through the same transform)
data_array = np.append(np.array(data_array), zero_point)
# Setting up the transform with the stretch
if stretch == 'asinh':
transform = AsinhStretch()
elif stretch == 'sinh':
transform = SinhStretch()
elif stretch == 'sqrt':
transform = SqrtStretch()
elif stretch == 'log':
transform = LogStretch()
elif stretch == 'linear':
transform = LinearStretch()
else:
raise InvalidInputError("Stretch {} is not supported!".format(stretch))
# Adding the scaling to the transform
if minmax_percent is not None:
transform += AsymmetricPercentileInterval(*minmax_percent)
if minmax_value is not None:
warnings.warn("Both minmax_percent and minmax_value are set, minmax_value will be ignored.",
InputWarning)
elif minmax_value is not None:
transform += ManualInterval(*minmax_value)
else: # Default, scale the entire image range to [0,1]
transform += MinMaxInterval()
# Performing the transform and then putting it into the pich range
pitch_array = transform(data_array)
if invert:
pitch_array = 1 - pitch_array
zero_point = pitch_array[-1]
pitch_array = pitch_array[:-1]
# In rare cases, the zero-point at this stage might be 0.0.
# One example is an input array of two values where the median() is the same as the
# lowest of the two values. In this case, the zero-point is 0.0 and will lead to error
# (divide by zero). Change to small value to avoid dividing by zero (in reality the choice
# of zero-point calculation by the user was probably poor, but not in purview to mandate or
# change user's choice here. May want to consider providing info back to the user about the
# distribution of pitches actually used based on their sonification options in some way.
if zero_point == 0.0:
zero_point = 1E-6
if ((1/zero_point)*(center_pitch - pitch_range[0]) + pitch_range[0]) <= pitch_range[1]:
pitch_array = (pitch_array/zero_point)*(center_pitch - pitch_range[0]) + pitch_range[0]
else:
pitch_array = (((pitch_array-zero_point)/(1-zero_point))*(pitch_range[1] - center_pitch) +
center_pitch)
return pitch_array
```