///|
pub fn profile_values(samples : Array[ProfileSample]) -> Array[Double] {
samples.map(fn(sample) { sample.value })
}
///|
pub fn profile_gradients(samples : Array[ProfileSample]) -> Array[Double] {
samples.map(fn(sample) { sample.gradient })
}
///|
pub fn profile_from_values(
values : Array[Double],
start : Point,
finish : Point,
) -> Array[ProfileSample] {
let result : Array[ProfileSample] = []
let denominator = Double::from_int((values.length() - 1).max(1))
for i in 0.. Array[ProfileSample] {
if samples.is_empty() || count <= 0 {
[]
} else {
let values = profile_values(samples)
let start = samples[0].point
let finish = samples[samples.length() - 1].point
let result_values : Array[Double] = []
for i in 0.. Array[ProfileSample] {
profile_from_values(
moving_average(profile_values(samples), window),
if samples.is_empty() {
Point::Point(x=0.0, y=0.0)
} else {
samples[0].point
},
if samples.is_empty() {
Point::Point(x=0.0, y=0.0)
} else {
samples[samples.length() - 1].point
},
)
}
///|
pub fn profile_exponential_smooth(
samples : Array[ProfileSample],
alpha : Double,
) -> Array[ProfileSample] {
let values = exponential_smooth(profile_values(samples), alpha)
profile_from_values(
values,
if samples.is_empty() {
Point::Point(x=0.0, y=0.0)
} else {
samples[0].point
},
if samples.is_empty() {
Point::Point(x=0.0, y=0.0)
} else {
samples[samples.length() - 1].point
},
)
}
///|
pub fn profile_normalize(
samples : Array[ProfileSample],
) -> Array[ProfileSample] {
let values = profile_values(samples)
let low = minimum_value(values)
let high = maximum_value(values)
let span = high - low
samples.map(fn(sample) {
{
..sample,
value: if span <= 0.0 {
0.0
} else {
(sample.value - low) / span
},
}
})
}
///|
pub fn profile_derivative(
samples : Array[ProfileSample],
) -> Array[ProfileSample] {
let result : Array[ProfileSample] = []
for i in 0.. Array[ProfileSample] {
profile_derivative(profile_derivative(samples))
}
///|
pub fn profile_peaks(
samples : Array[ProfileSample],
threshold : Double,
) -> Array[EdgePoint] {
let result : Array[EdgePoint] = []
if samples.length() >= 3 {
for i in 1..<(samples.length() - 1) {
let current = samples[i].gradient
if current.abs() >= threshold &&
current.abs() >= samples[i - 1].gradient.abs() &&
current.abs() >= samples[i + 1].gradient.abs() {
result.push({
point: samples[i].point,
strength: current.abs(),
polarity: if current >= 0.0 {
1
} else {
-1
},
})
}
}
}
result
}
///|
pub fn profile_valleys(
samples : Array[ProfileSample],
threshold : Double,
) -> Array[EdgePoint] {
let inverted = samples.map(fn(sample) {
{ ..sample, gradient: -sample.gradient }
})
profile_peaks(inverted, threshold)
}
///|
pub fn profile_zero_crossings(samples : Array[ProfileSample]) -> Array[Point] {
let result : Array[Point] = []
for i in 1.. Double {
let mut result = 0.0
for sample in samples {
result += sample.value * sample.value
}
result
}
///|
pub fn profile_gradient_energy(samples : Array[ProfileSample]) -> Double {
let mut result = 0.0
for sample in samples {
result += sample.gradient * sample.gradient
}
result
}
///|
pub fn profile_cross_correlation(
first : Array[ProfileSample],
second : Array[ProfileSample],
) -> Double {
let count = first.length().min(second.length())
if count == 0 {
0.0
} else {
let first_values : Array[Double] = []
let second_values : Array[Double] = []
for i in 0.. Int {
let limit = maximum_shift.max(0)
let mut best_shift = 0
let mut best_score = -2.0
for shift in -limit..<=limit {
let left : Array[Double] = []
let right : Array[Double] = []
for i in 0..= 0 && j < second.length() {
left.push(first[i].value)
right.push(second[j].value)
}
}
let score = correlation_values(left, right)
if score > best_score {
best_score = score
best_shift = shift
}
}
best_shift
}
///|
pub fn profile_quantized(
samples : Array[ProfileSample],
levels : Int,
) -> Array[ProfileSample] {
let count = levels.max(2)
samples.map(fn(sample) {
let index = (sample.value.clamp(min=0.0, max=1.0) *
Double::from_int(count - 1))
.round()
.to_int()
{ ..sample, value: Double::from_int(index) / Double::from_int(count - 1) }
})
}
///|
pub fn profile_outlier_repaired(
samples : Array[ProfileSample],
z_limit : Double,
) -> Array[ProfileSample] {
let values = profile_values(samples)
let repaired = reject_outliers(values, z_limit)
if repaired.length() == values.length() {
samples
} else {
profile_from_values(
repaired,
samples[0].point,
samples[samples.length() - 1].point,
)
}
}
///|
pub fn profile_window(
samples : Array[ProfileSample],
first : Int,
last : Int,
) -> Array[ProfileSample] {
let start = first.max(0).min(samples.length())
let finish = last.max(start).min(samples.length())
let result : Array[ProfileSample] = []
for i in start.. Double {
if samples.length() < 2 {
0.0
} else {
distance(samples[0].point, samples[samples.length() - 1].point)
}
}
///|
pub fn profile_sample_spacing(samples : Array[ProfileSample]) -> Double {
if samples.length() < 2 {
0.0
} else {
profile_distance(samples) / Double::from_int(samples.length() - 1)
}
}