選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

111 行
4.2 KiB

  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. return (target_time_min, target_time_max)
  40. def _features_total_time(self, features: list) -> float:
  41. """Calculate the total duration of all Features.
  42. Returns the total time in seconds.
  43. Pulled out for unit testing.
  44. """
  45. return float(sum([x.duration for x in features]))
  46. def __init__(self, features: list=[],
  47. target_time: int|float=_DEFAULT_TARGET_TIME,
  48. margin: int|float=_DEFAULT_MARGIN,
  49. strategy=_STRATEGY.ABSOLUTE):
  50. """Initialize the Adjuster with Features and a target time.
  51. Default target time is 60 seconds (1 minute). Even if the desired target time is 60s exactly, it is recommended to specify it explicitly.
  52. """
  53. super().__init__(features)
  54. self.target_time = float(target_time)
  55. self.margin = float(margin)
  56. self.strategy = strategy
  57. def adjust(self) -> list:
  58. """Drop Features until the target time within the margin is reached.
  59. Approach:
  60. Sort list of Features by score (primary) and by time (secondary).
  61. Drop lowest scoring Features until the target time is reached;
  62. if dropping a Feature would result in missing the margin, skip dropping that Feature
  63. if no Features can be dropped without missing the margin,
  64. drop the lowest scoring Feature until we are under the target time (with margin)
  65. Returns a list of Features, and also modifies the internal list of Features.
  66. """
  67. # check for early exit
  68. if not self.features:
  69. return []
  70. # figure out our margins
  71. target_time_min, target_time_max = self._determine_margin(self.target_time, self.margin, self.strategy)
  72. # calculate total time of all Features
  73. total_time = self._features_total_time(features=self.features)
  74. # if we are already within the target time, return the Features as-is
  75. if total_time <= target_time_max:
  76. return self.features
  77. # sort list of Features by score (primary) and by duration (secondary)
  78. sorted_features = sorted(self.features, key=lambda x: (x.score, x.time))