/* * Copyright 2016 The Cartographer Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using CartographerSharp.Common.Math; namespace CartographerSharp.Mapping; /// /// Probability value conversion utilities. /// public static class ProbabilityValues { public const double kMinProbability = 0.1; public const double kMaxProbability = 1.0 - kMinProbability; public const double kMinCorrespondenceCost = 1.0 - kMaxProbability; public const double kMaxCorrespondenceCost = 1.0 - kMinProbability; private const ushort kUnknownProbabilityValue = 0; private const ushort kUnknownCorrespondenceValue = kUnknownProbabilityValue; private const ushort kUpdateMarker = (ushort)(1u << 15); private const int kValueCount = 32768; // Precomputed lookup tables private static readonly double[] kValueToProbability; private static readonly double[] kValueToCorrespondenceCost; static ProbabilityValues() { kValueToProbability = PrecomputeValueToProbability(); kValueToCorrespondenceCost = PrecomputeValueToCorrespondenceCost(); } /// /// Converts probability to odds. /// public static double Odds(double probability) { return probability / (1.0 - probability); } /// /// Converts odds to probability. /// public static double ProbabilityFromOdds(double odds) { return odds / (odds + 1.0); } /// /// Converts probability to correspondence cost. /// public static double ProbabilityToCorrespondenceCost(double probability) { return 1.0 - probability; } /// /// Converts correspondence cost to probability. /// public static double CorrespondenceCostToProbability(double correspondenceCost) { return 1.0 - correspondenceCost; } /// /// Clamps probability to be in the range [kMinProbability, kMaxProbability]. /// public static double ClampProbability(double probability) { return MathUtils.Clamp(probability, kMinProbability, kMaxProbability); } /// /// Clamps correspondence cost to be in the range [kMinCorrespondenceCost, kMaxCorrespondenceCost]. /// public static double ClampCorrespondenceCost(double correspondenceCost) { return MathUtils.Clamp(correspondenceCost, kMinCorrespondenceCost, kMaxCorrespondenceCost); } /// /// Converts a correspondence_cost to a uint16 in the [1, 32767] range. /// public static ushort CorrespondenceCostToValue(double correspondenceCost) { return BoundedFloatToValue(correspondenceCost, kMinCorrespondenceCost, kMaxCorrespondenceCost); } /// /// Converts a probability to a uint16 in the [1, 32767] range. /// public static ushort ProbabilityToValue(double probability) { return BoundedFloatToValue(probability, kMinProbability, kMaxProbability); } /// /// Converts a uint16 (which may or may not have the update marker set) to a /// probability in the range [kMinProbability, kMaxProbability]. /// public static double ValueToProbability(ushort value) { if (value >= kValueToProbability.Length) { return kMinProbability; } return kValueToProbability[value]; } /// /// Converts a uint16 (which may or may not have the update marker set) to a /// correspondence cost in the range [kMinCorrespondenceCost, kMaxCorrespondenceCost]. /// public static double ValueToCorrespondenceCost(ushort value) { if (value >= kValueToCorrespondenceCost.Length) { return kMaxCorrespondenceCost; } return kValueToCorrespondenceCost[value]; } /// /// Converts probability value to correspondence cost value. /// public static ushort ProbabilityValueToCorrespondenceCostValue(ushort probabilityValue) { if (probabilityValue == kUnknownProbabilityValue) { return kUnknownCorrespondenceValue; } bool updateCarry = false; if (probabilityValue > kUpdateMarker) { probabilityValue -= kUpdateMarker; updateCarry = true; } ushort result = CorrespondenceCostToValue( ProbabilityToCorrespondenceCost(ValueToProbability(probabilityValue))); if (updateCarry) { result += kUpdateMarker; } return result; } /// /// Converts correspondence cost value to probability value. /// public static ushort CorrespondenceCostValueToProbabilityValue(ushort correspondenceCostValue) { if (correspondenceCostValue == kUnknownCorrespondenceValue) { return kUnknownProbabilityValue; } bool updateCarry = false; if (correspondenceCostValue > kUpdateMarker) { correspondenceCostValue -= kUpdateMarker; updateCarry = true; } ushort result = ProbabilityToValue(CorrespondenceCostToProbability( ValueToCorrespondenceCost(correspondenceCostValue))); if (updateCarry) { result += kUpdateMarker; } return result; } /// /// Computes lookup table to apply odds. /// public static List ComputeLookupTableToApplyOdds(double odds) { var result = new List(kValueCount) { (ushort)(ProbabilityToValue(ProbabilityFromOdds(odds)) + kUpdateMarker) }; for (int cell = 1; cell != kValueCount; cell++) { result.Add((ushort)(ProbabilityToValue(ProbabilityFromOdds( odds * Odds(ValueToProbability((ushort)cell)))) + kUpdateMarker)); } return result; } /// /// Computes lookup table to apply correspondence cost odds. /// public static List ComputeLookupTableToApplyCorrespondenceCostOdds(double odds) { var result = new List(kValueCount) { (ushort)(CorrespondenceCostToValue(ProbabilityToCorrespondenceCost( ProbabilityFromOdds(odds))) + kUpdateMarker) }; for (int cell = 1; cell != kValueCount; cell++) { result.Add((ushort)(CorrespondenceCostToValue( ProbabilityToCorrespondenceCost(ProbabilityFromOdds( odds * Odds(CorrespondenceCostToProbability( ValueToCorrespondenceCost((ushort)cell)))))) + kUpdateMarker)); } return result; } /// /// Converts a bounded double value to a uint16 value. /// private static ushort BoundedFloatToValue(double floatValue, double lowerBound, double upperBound) { var clamped = MathUtils.Clamp(floatValue, lowerBound, upperBound); // Match C++: common::RoundToInt uses std::lround which rounds to nearest integer // with rounding half away from zero (not banker's rounding) var value = (int)Math.Round( (clamped - lowerBound) * (32766.0 / (upperBound - lowerBound)), MidpointRounding.AwayFromZero) + 1; // Clamp to valid range value = Math.Max(1, Math.Min(32767, value)); return (ushort)value; } /// /// Precomputes value to probability lookup table. /// private static double[] PrecomputeValueToProbability() { return PrecomputeValueToBoundedFloat( kUnknownProbabilityValue, kMinProbability, kMinProbability, kMaxProbability); } /// /// Precomputes value to correspondence cost lookup table. /// private static double[] PrecomputeValueToCorrespondenceCost() { return PrecomputeValueToBoundedFloat( kUnknownCorrespondenceValue, kMaxCorrespondenceCost, kMinCorrespondenceCost, kMaxCorrespondenceCost); } /// /// Precomputes value to bounded double lookup table. /// private static double[] PrecomputeValueToBoundedFloat( ushort unknownValue, double unknownResult, double lowerBound, double upperBound) { // Repeat two times, so that both values with and without the update marker // can be converted to a probability. const int kRepetitionCount = 2; var result = new double[kRepetitionCount * kValueCount]; for (int repeat = 0; repeat != kRepetitionCount; repeat++) { for (int value = 0; value != kValueCount; value++) { result[repeat * kValueCount + value] = SlowValueToBoundedFloat( (ushort)value, unknownValue, unknownResult, lowerBound, upperBound); } } return result; } /// /// Slow conversion from value to bounded double (used for precomputation). /// private static double SlowValueToBoundedFloat( ushort value, ushort unknownValue, double unknownResult, double lowerBound, double upperBound) { if (value == unknownValue) { return unknownResult; } // Match C++: const double kScale = (upper_bound - lower_bound) / 32766.f; // kValueCount = 32768, so kValueCount - 2.0 = 32766.0 // Use explicit 32766.0 for consistency with C++ and ValueConversionTables var kScale = (upperBound - lowerBound) / 32766.0; return value * kScale + (lowerBound - kScale); } }