No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

adjusters.py 4.3 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. """adjusters.py -- adjust the gathered Features
  2. This is usually done to either modify to reduce the Features in some way.
  3. For example:
  4. - TargetTimeAdjuster: drop Features until the target time is reached
  5. - FeatureCountAdjuster: drop Features until the target number of Features is reached
  6. TODO: Consider eg a generic PredicateAdjuster -- supply a predicate/lambda that will be used to determine whether to keep a Feature or not.
  7. """
  8. from enum import Enum
  9. class Adjuster():
  10. """Generic Adjuster class. Expects a list of Features and returns a list of Features."""
  11. def __init__(self, features: list=[]):
  12. """Initialize the Adjuster with Features.
  13. NOTE: an empty feature list is permitted, since a FeatureExtractor may not produce features. Adjusters subclassing should be aware of this.
  14. """
  15. self.features = features
  16. def adjust(self) -> list:
  17. """Adjust the Features. Override this method in subclasses."""
  18. return self.features
  19. class TargetTimeAdjuster(Adjuster):
  20. """Adjuster that drops Features until the target time is reached."""
  21. _STRATEGY = Enum("MarginStrategy", ["ABSOLUTE", "PERCENT"])
  22. _DEFAULT_TARGET_TIME = 60.0 # 1 minute
  23. _DEFAULT_MARGIN = 10 # can be percent or absolute value
  24. def _determine_margin(self, time: float, margin: float, strategy: _STRATEGY) -> tuple:
  25. """Determine the target time margins.
  26. If the strategy is ABSOLUTE, the margin is a fixed value in seconds.
  27. If the strategy is PERCENT, the margin is a percentage of the target time.
  28. Returns a tuple of (min, max) times.
  29. Pulled out for unit testing
  30. """
  31. target_time_min = target_time_max = None
  32. if strategy == self._STRATEGY.ABSOLUTE:
  33. # both specified in seconds
  34. target_time_min = time - margin
  35. target_time_max = time + margin
  36. elif strategy == self._STRATEGY.PERCENT:
  37. target_time_max = time + (time * margin / 100)
  38. target_time_min = time - (time * margin / 100)
  39. # ensure we don't have negative times
  40. if type(target_time_min) is float and target_time_min < 0:
  41. target_time_min = 0.0
  42. return (target_time_min, target_time_max)
  43. def _features_total_time(self, features: list) -> float:
  44. """Calculate the total duration of all Features.
  45. Returns the total time in seconds.
  46. Pulled out for unit testing.
  47. """
  48. return float(sum([x.duration for x in features]))
  49. def __init__(self, features: list=[],
  50. target_time: int|float=_DEFAULT_TARGET_TIME,
  51. margin: int|float=_DEFAULT_MARGIN,
  52. strategy=_STRATEGY.ABSOLUTE):
  53. """Initialize the Adjuster with Features and a target time.
  54. Default target time is 60 seconds (1 minute). Even if the desired target time is 60s exactly, it is recommended to specify it explicitly.
  55. """
  56. super().__init__(features)
  57. self.target_time = float(target_time)
  58. self.margin = float(margin)
  59. self.strategy = strategy
  60. def adjust(self) -> list:
  61. """Drop Features until the target time within the margin is reached.
  62. Approach:
  63. Sort list of Features by score (primary) and by time (secondary).
  64. Drop lowest scoring Features until the target time is reached;
  65. if dropping a Feature would result in missing the margin, skip dropping that Feature
  66. if no Features can be dropped without missing the margin,
  67. drop the lowest scoring Feature until we are under the target time (with margin)
  68. Returns a list of Features, and also modifies the internal list of Features.
  69. """
  70. # check for early exit
  71. if not self.features:
  72. return []
  73. # figure out our margins
  74. target_time_min, target_time_max = self._determine_margin(self.target_time, self.margin, self.strategy)
  75. # calculate total time of all Features
  76. total_time = self._features_total_time(features=self.features)
  77. # if we are already within the target time, return the Features as-is
  78. if total_time <= target_time_max:
  79. return self.features
  80. # sort list of Features by score (primary) and by duration (secondary)
  81. sorted_features = sorted(self.features, key=lambda x: (x.score, x.time))