Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/14 allow to limit the movement of the camera #52

Merged
merged 109 commits into from
Nov 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
d7ee3c5
add function to_360_degree
Jul 20, 2022
5aef63b
change type of parameters of get_angles_in_cmd from int to float
Jul 20, 2022
9f2af26
add CameraAngleLimitController
Jul 20, 2022
af74a98
test CameraAngleLimitController
Jul 20, 2022
a4e8985
limit angles for all modes except "angle"
Jul 20, 2022
e27d5db
manage angle limits in UpdatableConfiguration
Jul 24, 2022
e22cc9d
update limits configuration on request PUT /api/configuration
Jul 24, 2022
4a93227
add menu "limits" to web UI to configure angle limits
Jul 24, 2022
a8d2a56
add option to enable limit in manual mode
Jul 27, 2022
d04c49d
use initial values from CameraAngleLimitController in UpdatableConfig…
Jul 27, 2022
8496848
add simple CameraZoomLimitController: stops after the limit is reached
Aug 6, 2022
b4523d1
limit camera zoom
Aug 6, 2022
10815eb
fix: PEP 8: E501 line too long (81 > 80 characters)
Aug 6, 2022
5fee56f
add zoom limit to UpdatableConfiguration
Aug 6, 2022
c73aeaf
use default initial values of inputs instead of setting the value to 0
Aug 6, 2022
129450b
support float in number inputs
Aug 6, 2022
a385f8f
add inputs to configure minimum and maximum zoom ratio
Aug 6, 2022
6b99270
fix: PEP 8: E302 expected 2 blank lines, found 1
Aug 6, 2022
3ef3934
add script to analyze the zoom of a camera
Aug 6, 2022
3d2cc6f
measure time to zoom to each step
Aug 7, 2022
82ca223
analyze zoom with slow and fast zoom speed
Aug 7, 2022
dec31c9
optimize imports
Aug 7, 2022
7a10d76
write results of analysis to output file
Aug 7, 2022
3da707c
zoom step by step to check if zoom is stopped at the right time (zoom…
Aug 17, 2022
a3bf4cd
remove done TODO analyze with zoom in fast
Aug 17, 2022
15d58f9
refactor: extract method `_wait_for_zoom_step_to_be_reached`
Aug 28, 2022
41ef990
fix: add missing space character in assertion message
Sep 7, 2022
94d5c41
add exit code to `quit`
Sep 7, 2022
74cd21a
oly exit if current thread is not the thread of the camera manager
Sep 7, 2022
42bd221
fix: error is not shown and reset zoom before quitting app
Sep 7, 2022
d54b1c0
feat: find more reliable stop-zoom-in times independent of camera model
Sep 7, 2022
dbb6005
refactor: extract function `analyze_zoom_steps`
Sep 10, 2022
ff3af61
optimize zoom-in time of steps using minimum total zoom-in times
Sep 11, 2022
cdd0321
remove duplicated log 'cancel zoom analyzer'
Sep 11, 2022
b2e5cf1
fix: add state CANCELED to prevent changes while analyzer is getting …
Sep 11, 2022
0b473f1
retry 3 times to zoom back
Sep 11, 2022
d6343cc
print slow zoom steps as TSV (tab seperated values)
Sep 11, 2022
7f29819
refactor: extract class `ZoomStep` to new module `robot_cameraman.zoom`
Sep 11, 2022
fed2e17
refactor: rename `ShowSpeedsInStatusBar` to `StatusBar`
Sep 18, 2022
57a9e38
feat: show zoom ratio in status bar of native UI
Sep 18, 2022
ccd2740
add method to get current elapsed time without resetting the timer
Sep 18, 2022
e10b401
fix: add method `update_zoom_ratio` to `CameraZoomLimitController`
Sep 18, 2022
f03e015
feat: add class `ZoomSteps` to manage and function to parse `ZoomStep`s
Sep 18, 2022
ea84f02
doc: add comment to ExHeader1.b about its relationship to the zoom ratio
Oct 1, 2022
1c005e4
feat: write ExHeader to CSV (hardcoded disabled)
Oct 1, 2022
cdf6e70
refactor: rename CSS class angle-limit to range-limit
Oct 19, 2022
82d5338
refactor: optimize imports
Oct 22, 2022
5f90f76
feat: experiment with mean and manual changes to measurements (uncomm…
Oct 22, 2022
aff43e0
feat: add tools to analyze zoom indices of Panasonic camera
Oct 22, 2022
f0b501f
refactor: move analyze_zoom_of_camera.py to tools
Oct 22, 2022
d86e4ff
refactor: extract methods of CameraZoomLimitController
Oct 22, 2022
81b91a4
feat: add PredictiveCameraZoomLimitController (only zoom in is predic…
Oct 22, 2022
5b2eb19
fix: time in ex-header prefix is always zero
Oct 22, 2022
6991f9d
fix: duplicate rows are written to CSV
Oct 22, 2022
e372e9d
fix: forgot to commit classes used in analyze tools
Oct 22, 2022
f1e4c70
ide: prefer short indent as project code style
Oct 22, 2022
df15014
feat: add argument --camera-zoom-steps to use PredictiveCameraZoomLim…
Oct 22, 2022
a972db8
feat: add range function (similar to Python) to util.js
Oct 23, 2022
050cd1f
feat: add zoom index to observable camera properties
Oct 23, 2022
d29fe3c
feat: add `parse_zoom_ratio_index_ranges`
Oct 23, 2022
c0675e5
feat: show zoom index in status bar
Oct 23, 2022
e518739
feat: add `CameraZoomIndexLimitController`
Oct 23, 2022
e1b96ef
feat: add argument --camera-zoom-ratio-index-ranges to use CameraZoom…
Oct 23, 2022
75089d8
feat: add limit and range of zoom indices to updatable configuration
Oct 23, 2022
867ea06
feat: add UI to set limit of zoom indices
Oct 23, 2022
514dff6
feat: add zoom-limit configuration files of Panasonic DMC-LF1
Oct 23, 2022
67d06b6
refactor: rename `CameraZoomLimitController` to `CameraZoomRatioLimit…
Oct 23, 2022
418b432
refactor: extract protocol `CameraZoomLimitController`
Oct 23, 2022
7285d6b
feat: add status bar to web UI
Oct 23, 2022
b5cbddd
feat: show debugLog (status bar) between live-view and menu in portra…
Oct 23, 2022
c1beeea
move `ZoomSpeed` and `CameraSpeeds` to module `robot_cameraman.camera…
Oct 28, 2022
9fd3ac9
add TODOs to CameraSpeeds
Oct 28, 2022
18c75dc
feat: add EventEmitter
Oct 29, 2022
7bff7f4
feat: generic way to add updatable property to `MaxSpeedAndAccelerati…
Oct 29, 2022
e1f90a9
feat: add class `Angles` that does not depend on module `simplebgc`
Oct 29, 2022
c63536f
feat: read angles of gimbal in `CameramanModeManager` instead of `Cam…
Oct 29, 2022
81d09a3
feat: show current angles in status bar
Oct 29, 2022
fe8c212
fix: pan and tilt speed should be formatted as floats in status bar
Oct 30, 2022
e7257b7
refactor: update TODO about degree sign in status bar
Oct 30, 2022
4233d1e
feat: add strategy to search target at static position (pan and tilt …
Oct 30, 2022
bdc8cf1
fix: PEP 8: E501 line too long (81 > 80 characters)
Oct 30, 2022
45fd1c8
refactor: emit observable camera properties using EventEmitter
Oct 30, 2022
74b3f86
fix: static search at tilt angle not working with angles >180
Oct 30, 2022
0e59274
refactor: add type hints for camera limit controllers
Oct 30, 2022
43a2dc5
feat: support target zoom in static search strategy
Oct 30, 2022
38530c8
feat: add function `get_angle_distance`
Nov 2, 2022
da85bf1
feat: move camera fast and accurate to searching target location
Nov 2, 2022
735e7ad
refactor: simplify calculation of pan and tilt "close speed"
Nov 2, 2022
fdf2b29
refactor: invert if condition to move case "max speed" before others
Nov 2, 2022
2faca23
feat. add option to zoom not before pan and tilt searching targets ar…
Nov 2, 2022
1cb5f4c
fix: avoid verbose error logs of ssdp module
Nov 5, 2022
203b2cd
fix: change log level of werkzeug to WARNING
Nov 5, 2022
9fb1de4
fix: only update camera pan and tilt speed if target and current angl…
Nov 5, 2022
20d0dad
refactor: rename `_camera_speeds` to `_camera_base_speeds`
Nov 5, 2022
b48959e
doc: document `_camera_base_speeds`
Nov 5, 2022
a7ed92a
fix: uncomment test targets used in early development
Nov 5, 2022
4427405
fix: update camera base speeds if target is set after start
Nov 5, 2022
a2c1e12
fix: remove assertions that current angles have to be set at start
Nov 5, 2022
f735432
fix: update camera base speeds if current angles are set after target
Nov 5, 2022
f55034a
fix: update camera base speeds if zoom index is set after target
Nov 5, 2022
3b8c572
refactor: remove done TODO update zoom index and ratio
Nov 5, 2022
25441bf
feat: add dummy live view
Nov 5, 2022
69e6447
fix: only update defined targets
Nov 6, 2022
ce7681e
feat: add UI for static search strategy
Nov 6, 2022
2fb47ef
refactor: rename `_is_update_od_camera_base_speeds_required` to `_is_…
Nov 6, 2022
5e93628
fix: optimize UI for Samsung Galaxy S9
Nov 6, 2022
7f29210
refactor: remove done TODO keep None as default
Nov 9, 2022
0e2b1fb
refactor: remove unused method _get_angle_limits
Nov 9, 2022
4aef5c3
refactor: remove outdated comment "The second row is the height of ..."
Nov 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .idea/codeStyles/codeStyleConfig.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions panasonic_camera/live_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ def unpack(cls, ex_header_data: BytesReader):
@dataclass()
class ExHeader1(ExHeader):
zoomRatio: int
# b seems to be related to the zoom ratio.
# It's value increases/decreases relative to zoomRatio.
b: int
c: int
zoomRatioPos: int
Expand Down
182 changes: 162 additions & 20 deletions robot_cameraman/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import threading
# noinspection Mypy
from pathlib import Path
from typing import Optional

import PIL.Image
import PIL.ImageFont
Expand All @@ -13,30 +14,35 @@
from panasonic_camera.camera_manager import PanasonicCameraManager
from robot_cameraman.annotation import ImageAnnotator
from robot_cameraman.camera_controller import SmoothCameraController, \
SpeedManager
from robot_cameraman.camera_observable import \
PanasonicCameraObservable, ObservableCameraProperty
SpeedManager, CameraAngleLimitController, \
PredictiveCameraZoomRatioLimitController, CameraZoomRatioLimitController, \
CameraZoomIndexLimitController
from robot_cameraman.camera_observable import PanasonicCameraObservable
from robot_cameraman.camera_speeds import ZoomSpeed, CameraSpeeds
from robot_cameraman.cameraman import Cameraman
from robot_cameraman.cameraman_mode_manager import CameramanModeManager
from robot_cameraman.configuration import read_configuration_file
from robot_cameraman.detection_engine.color import ColorDetectionEngine, \
ColorDetectionEngineUI
from robot_cameraman.events import EventEmitter, Event
from robot_cameraman.gimbal import DummyGimbal, TiltInvertedGimbal, \
create_simple_bgc_gimbal
from robot_cameraman.image_detection import DummyDetectionEngine, \
EdgeTpuDetectionEngine
from robot_cameraman.live_view import WebcamLiveView, PanasonicLiveView, \
ImageSize
ImageSize, DummyLiveView
from robot_cameraman.max_speed_and_acceleration_updater import \
MaxSpeedAndAccelerationUpdater
from robot_cameraman.object_tracking import ObjectTracker
from robot_cameraman.resource import read_label_file
from robot_cameraman.server import run_server, ImageContainer
from robot_cameraman.tracking import Destination, StopIfLostTrackingStrategy, \
RotateSearchTargetStrategy, CameraSpeeds, ConfigurableTrackingStrategy, \
ConfigurableAlignTrackingStrategy, ConfigurableTrackingStrategyUi, ZoomSpeed
from robot_cameraman.ui import ShowSpeedsInStatusBar
RotateSearchTargetStrategy, ConfigurableTrackingStrategy, \
ConfigurableAlignTrackingStrategy, ConfigurableTrackingStrategyUi, \
StaticSearchTargetStrategy
from robot_cameraman.ui import StatusBar
from robot_cameraman.updatable_configuration import UpdatableConfiguration
from robot_cameraman.zoom import parse_zoom_steps, parse_zoom_ratio_index_ranges

to_exit: threading.Event
server_image: ImageContainer
Expand All @@ -50,6 +56,14 @@ def create_video_writer(output_file: Path, image_size: ImageSize):
image_size)


def create_angle_limit_controller(
event_emitter: EventEmitter) -> CameraAngleLimitController:
controller = CameraAngleLimitController()
event_emitter.add_listener(Event.ANGLES,
controller.update_current_angles)
return controller


class RobotCameramanArguments(Protocol):
config: Path
detectionEngine: str
Expand All @@ -68,6 +82,7 @@ class RobotCameramanArguments(Protocol):
font: Path
fontSize: int
debug: bool
search_strategy: str
rotatingSearchSpeed: int
rotationalAccelerationPerSecond: int
tiltingAccelerationPerSecond: int
Expand All @@ -76,6 +91,8 @@ class RobotCameramanArguments(Protocol):
liveViewHeight: int
cameraMinFocalLength: float
cameraMaxFocalLength: float
camera_zoom_steps: Optional[Path]
camera_zoom_ratio_index_ranges: Optional[Path]
ssl_key: Path
ssl_certificate: Path

Expand Down Expand Up @@ -125,7 +142,7 @@ def parse_arguments() -> RobotCameramanArguments:
parser.add_argument('--liveView', type=str,
default='Panasonic',
help="The live view (camera) to use."
"Either 'Panasonic' or 'Webcam'")
"Either 'Panasonic', 'Webcam' or 'Dummy'")
parser.add_argument('--ip', type=str,
default='0.0.0.0',
help="UDP Socket IP address of Panasonic live view.")
Expand Down Expand Up @@ -155,6 +172,19 @@ def parse_arguments() -> RobotCameramanArguments:
parser.add_argument('--debug',
action='store_true',
help="Enable debug logging")
parser.add_argument('--search-strategy',
type=str, default='rotate',
help="If target is lost,"
" the camera searches for a new target."
" If the strategy 'rotate' (default) is used,"
" the gimbal pans clockwise at constant speed"
" (given by argument --rotatingSearchSpeed)."
" If the strategy 'static' is used,"
" the gimbal moves (pans, tilts and zooms)"
" with constant speed"
" (given by argument --rotatingSearchSpeed)"
" to a certain position"
" (can be configured in the web UI).")
parser.add_argument('--rotatingSearchSpeed',
type=int, default=0,
help="If target is lost, search for new target by"
Expand Down Expand Up @@ -187,6 +217,28 @@ def parse_arguments() -> RobotCameramanArguments:
help="Maximum focal length in millimeter"
"of the used camera. The actual focal length"
"and not the 35mm equivalent is expected.")
parser.add_argument(
'--camera-zoom-steps',
type=Path,
default=None,
help="Path to file that contains the configuration of the camera's"
" zoom-steps (see"
" robot_cameraman/tools/analyze_zoom_of_camera.py)."
" If this argument is given,"
" the PredictiveCameraZoomRatioLimitController is used to limit"
" the zoom of the camera.")
parser.add_argument(
'--camera-zoom-ratio-index-ranges',
type=Path,
default=None,
help="Path to file that contains the configuration of the camera's"
" zoom-ratio-index-ranges (see"
" analyze_zoom_indices_of_panasonic_camera.py and"
" analyze_zoom_indices_of_panasonic_camera_interactively.py"
" in directory robot_cameraman/tools)."
" If this argument is given,"
" the CameraZoomIndexLimitController is used to limit the"
" zoom of the camera.")
parser.add_argument(
'--ssl-key',
type=Path,
Expand Down Expand Up @@ -239,10 +291,20 @@ def configure_logging():
# add the handler to the root logger
logging.getLogger('').addHandler(console)

# some devices on the network may cause upnpclient.discover to log quite
# verbose error messages. The log level is changed to avoid this
logging.getLogger('ssdp').setLevel(logging.CRITICAL)
# The web UI does frequent requests regarding the status bar
# that spam the logs. Usually, those INFO logs are not important,
# since the relevant interaction is covered by logs of other modules.
# Hence, the log level of werkzeug is changed.
logging.getLogger('werkzeug').setLevel(logging.WARNING)


args = parse_arguments()
configure_logging()
configuration = read_configuration_file(args.config)
event_emitter = EventEmitter()
labels = read_label_file(args.labels)
font = PIL.ImageFont.truetype(str(args.font), args.fontSize)
live_view_image_size = ImageSize(args.liveViewWith, args.liveViewHeight)
Expand Down Expand Up @@ -271,18 +333,65 @@ def configure_logging():
configurable_align_tracking_strategy = \
ConfigurableAlignTrackingStrategy(
destination, live_view_image_size, max_allowed_speed=16)


def create_camera_zoom_limit_controller():
global event_emitter
if args.camera_zoom_ratio_index_ranges is not None:
controller = CameraZoomIndexLimitController()
elif args.camera_zoom_steps is not None:
controller = PredictiveCameraZoomRatioLimitController(
zoom_steps=parse_zoom_steps(args.camera_zoom_steps))
else:
controller = CameraZoomRatioLimitController()
if hasattr(controller, 'update_zoom_ratio'):
event_emitter.add_listener(
Event.ZOOM_RATIO,
controller.update_zoom_ratio)
if hasattr(controller, 'update_zoom_index'):
event_emitter.add_listener(
Event.ZOOM_INDEX,
controller.update_zoom_index)
return controller


camera_zoom_limit_controller = create_camera_zoom_limit_controller()

if args.search_strategy == 'rotate':
search_target_strategy = max_speed_and_acceleration_updater.add(
RotateSearchTargetStrategy(args.rotatingSearchSpeed))
elif args.search_strategy == 'static':
search_target_strategy = StaticSearchTargetStrategy(
pan_speed=args.rotatingSearchSpeed,
tilt_speed=args.rotatingSearchSpeed,
camera_zoom_limit_controller=create_camera_zoom_limit_controller(),
camera_angle_limit_controller=create_angle_limit_controller(
event_emitter))
event_emitter.add_listener(
Event.ANGLES, search_target_strategy.update_current_angles)
event_emitter.add_listener(
Event.ZOOM_INDEX, search_target_strategy.update_current_zoom_index)
event_emitter.add_listener(
Event.ZOOM_RATIO, search_target_strategy.update_current_zoom_ratio)
else:
print(f"Unknown search strategy {args.search_strategy}")
exit(1)
camera_angle_limit_controller = create_angle_limit_controller(event_emitter)
# noinspection PyUnboundLocalVariable
cameraman_mode_manager = CameramanModeManager(
camera_controller=SmoothCameraController(
gimbal,
camera_manager,
rotate_speed_manager=rotate_speed_manager,
tilt_speed_manager=tilt_speed_manager),
camera_zoom_limit_controller=camera_zoom_limit_controller,
camera_angle_limit_controller=camera_angle_limit_controller,
align_tracking_strategy=max_speed_and_acceleration_updater.add(
configurable_align_tracking_strategy),
tracking_strategy=tracking_strategy,
search_target_strategy=max_speed_and_acceleration_updater.add(
RotateSearchTargetStrategy(args.rotatingSearchSpeed)),
gimbal=gimbal)
search_target_strategy=search_target_strategy,
gimbal=gimbal,
event_emitter=event_emitter)

# noinspection PyListCreation
user_interfaces = []
Expand All @@ -291,12 +400,13 @@ def configure_logging():
ConfigurableTrackingStrategyUi(
tracking_strategy=configurable_tracking_strategy,
align_strategy=configurable_align_tracking_strategy))

# noinspection PyProtectedMember
user_interfaces.append(
ShowSpeedsInStatusBar(
pan_speed_manager=rotate_speed_manager,
tilt_speed_manager=tilt_speed_manager,
camera_speeds=cameraman_mode_manager._camera_speeds))
status_bar = StatusBar(pan_speed_manager=rotate_speed_manager,
tilt_speed_manager=tilt_speed_manager,
camera_speeds=cameraman_mode_manager._camera_speeds)
event_emitter.add_listener(Event.ANGLES, status_bar.update_current_angles)
user_interfaces.append(status_bar)

if args.detectionEngine == 'Dummy':
detection_engine = DummyDetectionEngine()
Expand All @@ -319,14 +429,37 @@ def configure_logging():

if args.liveView == 'Webcam':
live_view = WebcamLiveView()
elif args.liveView == 'Dummy':
live_view = DummyLiveView(live_view_image_size)
elif args.liveView == 'Panasonic':
live_view = PanasonicLiveView(args.ip, args.port)
camera_observable = PanasonicCameraObservable(
min_focal_length=args.cameraMinFocalLength)
min_focal_length=args.cameraMinFocalLength,
event_emitter=event_emitter)
live_view.add_ex_header_listener(camera_observable.on_ex_header)
camera_observable.add_listener(
ObservableCameraProperty.ZOOM_RATIO,
# TODO add CLI argument to enable ExHeaderToCsvWriter
# noinspection PyUnreachableCode
if False:
from robot_cameraman.camera_observable import ExHeaderToCsvWriter

live_view.add_ex_header_listener(ExHeaderToCsvWriter().on_ex_header)
event_emitter.add_listener(
Event.ZOOM_RATIO,
max_speed_and_acceleration_updater.on_zoom_ratio)
event_emitter.add_listener(
Event.ZOOM_INDEX,
status_bar.update_zoom_index)
event_emitter.add_listener(
Event.ZOOM_RATIO,
status_bar.update_zoom_ratio)
if hasattr(camera_zoom_limit_controller, 'update_zoom_ratio'):
event_emitter.add_listener(
Event.ZOOM_RATIO,
camera_zoom_limit_controller.update_zoom_ratio)
if hasattr(camera_zoom_limit_controller, 'update_zoom_index'):
event_emitter.add_listener(
Event.ZOOM_INDEX,
camera_zoom_limit_controller.update_zoom_index)
else:
print(f"Unknown live view {args.liveView}")
exit(1)
Expand Down Expand Up @@ -358,12 +491,21 @@ def configure_logging():
cameraman_thread = threading.Thread(target=run_cameraman, daemon=True)
cameraman_thread.start()
print('Open https://localhost:9000/index.html in your browser')
camera_zoom_ratio_index_ranges = (
None if args.camera_zoom_ratio_index_ranges is None
else parse_zoom_ratio_index_ranges(args.camera_zoom_ratio_index_ranges))
run_server(_to_exit=to_exit,
_cameraman_mode_manager=cameraman_mode_manager,
_server_image=server_image,
_manual_camera_speeds=manual_camera_speeds,
_updatable_configuration=UpdatableConfiguration(
detection_engine=detection_engine,
configuration_file=args.config),
cameraman_mode_manager=cameraman_mode_manager,
camera_zoom_limit_controller=camera_zoom_limit_controller,
camera_angle_limit_controller=camera_angle_limit_controller,
configuration_file=args.config,
camera_zoom_ratio_index_ranges=camera_zoom_ratio_index_ranges,
search_target_strategy=search_target_strategy),
_status_bar=status_bar,
ssl_certificate=args.ssl_certificate,
ssl_key=args.ssl_key)
17 changes: 17 additions & 0 deletions robot_cameraman/angle.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,20 @@ def get_delta_angle_counter_clockwise(left: float, right: float) -> float:
return left - right
else:
return abs(360 - right) + left


def get_angle_distance(left: float, right: float) -> float:
"""Returns the smaller delta angle."""
# To increase floating point precision,
# get_delta_angle_clockwise and get_delta_angle_counter_clockwise
# are not reused. Thereby, the required operations to calculate the result
# can be reduced.
if left >= right:
delta = left - right
if delta <= 180:
return delta
return 360 - delta
delta = abs(360 - right) + left
if delta <= 180:
return delta
return right - left
Loading