diff --git a/track/__main__.py b/track/__main__.py index e07399a..8d945b7 100755 --- a/track/__main__.py +++ b/track/__main__.py @@ -65,7 +65,7 @@ def camera_separation_callback( def main(): - """See module docstring""" + """See module docstring.""" def gamepad_callback(_unused: Tracker) -> bool: """Callback for gamepad control. diff --git a/track/align.py b/track/align.py index 541e264..a1cf53b 100755 --- a/track/align.py +++ b/track/align.py @@ -43,7 +43,7 @@ class Position(Destination): - """A position for use in alignment procedure""" + """A position for use in alignment procedure.""" def __init__( self, @@ -59,7 +59,7 @@ def __init__( self.mount = mount def distance_to(self, other_destination: "Position") -> int: - """Returns the cost of moving the mount from this position to another position + """Returns the cost of moving the mount from this position to another position. Assumes that the slew rate is the same for all mount axes. @@ -178,7 +178,6 @@ def attempt_plate_solving( Returns: True on success, False on failure. """ - # Make all physical measurements (clock, camera, mount encoders) near same time timestamp = time.time() frame = camera.get_frame() @@ -223,7 +222,6 @@ def remove_empty_dir(directory: str) -> None: def main(): """Run the alignment procedure! See module docstring for a description.""" - parser = ArgParser(additional_config_files=[os.path.join(CONFIG_PATH, 'align.cfg')]) align_group = parser.add_argument_group( title='Alignment Configuration', diff --git a/track/autofocus.py b/track/autofocus.py index 530fabc..c96546d 100755 --- a/track/autofocus.py +++ b/track/autofocus.py @@ -1,8 +1,6 @@ #!/usr/bin/env python3 -""" -Automatic focus script and associated algorithms. -""" +"""Automatic focus script and associated algorithms.""" import atexit from datetime import datetime @@ -259,7 +257,6 @@ def autofocus( def main(): """Run the full autofocus algorithm.""" - parser = ArgParser(additional_config_files=[os.path.join(CONFIG_PATH, 'autofocus.cfg')]) parser.add_argument( '--meridian-side', diff --git a/track/bootstrap.py b/track/bootstrap.py index 5fdbd83..b3a48e6 100755 --- a/track/bootstrap.py +++ b/track/bootstrap.py @@ -87,7 +87,6 @@ def run_program(args: list[str]) -> None: def main(): """See module docstring.""" - parser = ArgParser() logs.add_program_arguments(parser) gps_client.add_program_arguments(parser) diff --git a/track/cameras.py b/track/cameras.py index 57a6538..dd2be0c 100644 --- a/track/cameras.py +++ b/track/cameras.py @@ -34,7 +34,7 @@ class Camera(AbstractContextManager): - """Abstract base class for cameras""" + """Abstract base class for cameras.""" @property @abstractmethod @@ -183,11 +183,11 @@ def pixel_to_angle(self, position_x: float, position_y: float) -> Angle: class CameraTimeout(Exception): - """Raised when a timeout expires""" + """Raised when a timeout expires.""" class ASICamera(Camera): - """ZWO ASI Cameras""" + """ZWO ASI Cameras.""" class BitDepth(enum.IntEnum): """Bit depth of pixels.""" @@ -196,7 +196,7 @@ class BitDepth(enum.IntEnum): RAW16 = asi.ASI_IMG_RAW16 def bytes_per_pixel(self) -> int: - """Number of bytes per pixel in raw array of frame data retrieved from ASI driver""" + """Number of bytes per pixel in raw array of frame data retrieved from ASI driver.""" return 1 if self == self.RAW8 else 2 @staticmethod @@ -224,7 +224,7 @@ def add_program_arguments(parser: ArgParser) -> None: @staticmethod def from_program_args(args: Namespace) -> ASICamera: - """Factory to make a WebCam instance from program arguments + """Factory to make a WebCam instance from program arguments. Args: args: Set of program arguments. @@ -311,17 +311,17 @@ def _get_ctrl(self, ctrl): @property def pixel_scale(self) -> float: - """Scale of a pixel in degrees per pixel""" + """Scale of a pixel in degrees per pixel.""" return self._pixel_scale @property def binning(self) -> int: - """Binning configuration""" + """Binning configuration.""" return self._binning @property def frame_shape(self) -> tuple[int, int]: - """Shape of array returned by get_frame()""" + """Shape of array returned by get_frame().""" return self._frame_shape @property @@ -331,12 +331,12 @@ def field_of_view(self) -> tuple[float, float]: @property def video_mode(self) -> bool: - """True if video mode is enabled""" + """True if video mode is enabled.""" return self._video_mode @video_mode.setter def video_mode(self, enabled: bool) -> None: - """Enable or disable video mode""" + """Enable or disable video mode.""" self._bit_depth = self.BitDepth.RAW8 if enabled else self.BitDepth.RAW16 height, width = self._frame_shape self._frame_size_bytes = width * height * self._bit_depth.bytes_per_pixel() @@ -361,32 +361,32 @@ def video_mode(self, enabled: bool) -> None: @property def gain(self) -> int: - """Camera gain""" + """Camera gain.""" return self._get_ctrl(asi.ASI_GAIN)[0] @gain.setter def gain(self, gain: int) -> None: - """Set camera gain""" + """Set camera gain.""" self._set_ctrl(asi.ASI_GAIN, gain) @property def exposure(self) -> float: - """Exposure time in seconds""" + """Exposure time in seconds.""" return self._get_ctrl(asi.ASI_EXPOSURE)[0] / 1e6 @exposure.setter def exposure(self, exposure: float) -> None: - """Set exposure time in seconds""" + """Set exposure time in seconds.""" self._set_ctrl(asi.ASI_EXPOSURE, int(exposure * 1e6)) def _reshape_frame_data(self, frame: np.ndarray) -> np.ndarray: - """Reshape raw byte array from ASI driver to a 2D array image""" + """Reshape raw byte array from ASI driver to a 2D array image.""" if self._bit_depth == self.BitDepth.RAW16: frame = frame.view(dtype=np.uint16) return np.reshape(frame, self._frame_shape) def get_dropped_frames(self) -> int: - """Get number of dropped frames so far""" + """Get number of dropped frames so far.""" return ASICheck(asi.ASIGetDroppedFrames(self.info.CameraID)) def get_frame(self, timeout: float = inf) -> np.ndarray: @@ -481,7 +481,7 @@ def add_program_arguments(parser: ArgParser) -> None: @staticmethod def from_program_args(args: Namespace) -> WebCam: - """Factory to make a WebCam instance from program arguments""" + """Factory to make a WebCam instance from program arguments.""" return WebCam( dev_path=args.webcam_dev, ctrl_exposure=args.webcam_exposure, @@ -555,12 +555,12 @@ def binning(self) -> int: @property def video_mode(self) -> bool: - """Always returns True because this camera can only be in video mode""" + """Always returns True because this camera can only be in video mode.""" return True @video_mode.setter def video_mode(self, enabled: bool) -> None: - """Video mode is always enabled for this camera""" + """Video mode is always enabled for this camera.""" if not enabled: raise ValueError("Can't disable video mode for Webcam") @@ -576,7 +576,6 @@ def get_frame(self, timeout: float = inf) -> np.ndarray: Returns: The frame as a numpy array. """ - self.block_until_frame_ready(timeout) frames = [] @@ -620,12 +619,12 @@ def block_until_frame_ready(self, timeout: float = inf) -> bool: return len(dev_fd_ready) > 0 def has_frames_available(self) -> bool: - """query whether the webcam has at least one frame ready for us to read (non-blocking)""" + """Query whether the webcam has at least one frame ready for us to read (non-blocking).""" readable, _, _ = select.select((self.dev_fd,), (), (), 0.0) return len(readable) != 0 def start(self) -> None: - """tell the camera to start capturing""" + """Tell the camera to start capturing.""" if not self.started: self._v4l2_ioctl( v4l2.VIDIOC_STREAMON, ctypes.c_int(int(v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE)) @@ -633,7 +632,7 @@ def start(self) -> None: self.started = True def stop(self) -> None: - """tell the camera to stop capturing""" + """Tell the camera to stop capturing.""" if self.started: self._v4l2_ioctl( v4l2.VIDIOC_STREAMOFF, ctypes.c_int(int(v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE)) @@ -721,7 +720,7 @@ def _set_jpeg_quality(self, quality): ) def _set_format(self, shape_wanted: tuple[int, int], fourcc) -> tuple[int, int]: - """roughly equivalent to v4l2capture's set_format""" + """Roughly equivalent to v4l2capture's set_format.""" assert not self.started fmt = v4l2.v4l2_format(type=v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE) @@ -739,7 +738,7 @@ def _set_format(self, shape_wanted: tuple[int, int], fourcc) -> tuple[int, int]: return (fmt.fmt.pix.height, fmt.fmt.pix.width) def _setup_buffers(self, buf_count: int) -> None: - """roughly equivalent to v4l2capture's create_buffers""" + """Roughly equivalent to v4l2capture's create_buffers.""" assert not self.started assert len(self.bufmaps) == 0 @@ -759,7 +758,7 @@ def _setup_buffers(self, buf_count: int) -> None: ] def _queue_all_buffers(self) -> None: - """roughly equivalent to v4l2capture's queue_all_buffers""" + """Roughly equivalent to v4l2capture's queue_all_buffers.""" assert not self.started assert len(self.bufmaps) != 0 @@ -770,7 +769,7 @@ def _queue_all_buffers(self) -> None: self._v4l2_ioctl(v4l2.VIDIOC_QBUF, buf) def _read_and_queue(self): - """roughly equivalent to v4l2capture's read_and_queue""" + """Roughly equivalent to v4l2capture's read_and_queue.""" assert self.started buf = v4l2.v4l2_buffer(type=v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE, memory=v4l2.V4L2_MEMORY_MMAP) diff --git a/track/compvis.py b/track/compvis.py index aed4d61..bcf9512 100644 --- a/track/compvis.py +++ b/track/compvis.py @@ -1,4 +1,4 @@ -"""Computer vision algorithms for identifying targets in a camera frame""" +"""Computer vision algorithms for identifying targets in a camera frame.""" import numpy as np import cv2 @@ -13,7 +13,6 @@ def find_features(frame: np.ndarray) -> list[cv2.KeyPoint]: Returns: A list of keypoints. """ - if frame.dtype != np.uint8: # No fundamental reason; just hasn't been implemented raise ValueError('Only uint8 frames are supported') @@ -59,7 +58,7 @@ def find_features(frame: np.ndarray) -> list[cv2.KeyPoint]: class PreviewWindow: - """Generates an annotated camera frame for display in an OpenCV window""" + """Generates an annotated camera frame for display in an OpenCV window.""" def __init__( self, @@ -108,7 +107,6 @@ def show_annotated_frame( keypoints: List of all keypoints. target_keypoint: The keypoint identified as the target. """ - frame_annotated = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR) # add grey crosshairs, leaving a gap in the middle so the crosshairs don't obscure objects diff --git a/track/config.py b/track/config.py index 4194563..46a3457 100644 --- a/track/config.py +++ b/track/config.py @@ -1,4 +1,4 @@ -"""Package configuration utilities +"""Package configuration utilities. * Defines paths to configuration and data files. * Specifies the filename of the default shared configuration file used by all programs. diff --git a/track/control.py b/track/control.py index b8afd0a..e2b22f9 100644 --- a/track/control.py +++ b/track/control.py @@ -60,7 +60,7 @@ def smallest_allowed_error( target_enc_position: float | np.ndarray, no_cross_position: float | None = None, ) -> np.ndarray: - """Compute error term for a single axis taking into account no-cross positions + """Compute error term for a single axis taking into account no-cross positions. The shortest path on an axis is just the difference between the target and mount encoder positions wrapped to [-180, +180] degrees. However the shortest path is not always allowed @@ -81,7 +81,6 @@ def smallest_allowed_error( Returns: The error term(s) for this axis in degrees. """ - # shortest path to target prelim_error = (target_enc_position - mount_enc_position + 180) % 360 - 180 @@ -156,7 +155,7 @@ def __init__( prediction_horizon: float, control_cycle_period: float, ): - """Construct an instance of ModelPredictiveController + """Construct an instance of ModelPredictiveController. Args: target: The target being tracked. @@ -183,7 +182,7 @@ def __init__( @property def target(self) -> Target: - """Get target""" + """Get target.""" return self._target @target.setter @@ -192,14 +191,13 @@ def target(self, new_target: Target) -> None: self._init_prediction_arrays(Time.now() + 2 * self.control_cycle_period) def _init_prediction_arrays(self, time_start: Time) -> None: - """Initialize arrays of predicted target positions and slew rates out to prediction horizon + """Initialize arrays of predicted target positions and slew rates out to prediction horizon. Args: time_start: The first target position in the arrays will correspond to this absolute time. The arrays will be populated with predictions that start at this time and end at this time plus the prediction horizon. """ - num_items = int(self.prediction_horizon / self.control_cycle_period) times_from_start = TimeDelta(self.control_cycle_period * np.arange(num_items), format='sec') @@ -216,7 +214,7 @@ def _init_prediction_arrays(self, time_start: Time) -> None: self.slew_rates_predicted = {axis: np.zeros(num_items) for axis in self.axes} def _refresh_target_positions(self) -> None: - """Refresh all of the elements of the predicted target position arrays + """Refresh all of the elements of the predicted target position arrays. Can't assume for all target types that predicted positions will remain unchanged as time progresses. For example, a prediction ten seconds into the future may have low confidence @@ -278,7 +276,6 @@ def update(self, mount_state: MountState) -> SlewRateCommand: be sent. The caller should wait until that time to send the commands rather than sending them immediately. """ - # refresh arrays of predicted target positions looking into the future while True: time_next_cmd = self._advance_prediction_arrays() @@ -361,7 +358,6 @@ def _objective( Returns: The predicted mean error magnitude over the prediction window. """ - # predict mount axis position in the future assuming this set of slew rate commands positions_mount, _ = self.mount.predict( times_from_start, slew_rate_commands, position_axis_start, slew_rate_start @@ -448,7 +444,7 @@ def __init__( @property def target(self) -> Target: - """Get target""" + """Get target.""" return self._target @target.setter @@ -544,7 +540,6 @@ def _finish_control_cycle( callback_override: bool = False, ) -> "Tracker.StopReason": """Final tasks to perform at the end of each control cycle.""" - # list of telemetry points to be populated points = [] diff --git a/track/crop_and_stabilize.py b/track/crop_and_stabilize.py index 620232e..c45bb5f 100755 --- a/track/crop_and_stabilize.py +++ b/track/crop_and_stabilize.py @@ -16,7 +16,6 @@ def main(): """See module docstring at the top of this file.""" - # initialize blob detector params = cv2.SimpleBlobDetector_Params() params.filterByColor = False diff --git a/track/fetch_tles.py b/track/fetch_tles.py index 1e7c50c..093dffc 100755 --- a/track/fetch_tles.py +++ b/track/fetch_tles.py @@ -23,8 +23,7 @@ def urlify(s: str) -> str: - """Transform a string into a string that is a valid part of a URL""" - + """Transform a string into a string that is a valid part of a URL.""" # Remove all non-word characters (everything except numbers and letters) s = re.sub(r"[^\w\s]", '', s) @@ -50,7 +49,7 @@ def date_to_monthnum(date: datetime.date) -> int: def print_timezone_help(): - """Print help message for how to use the timezones 'tz' program argument""" + """Print help message for how to use the timezones 'tz' program argument.""" tz_soup = BeautifulSoup( requests.get('http://heavens-above.com/SelectLocation.aspx', timeout=10).text, 'lxml' ) @@ -65,7 +64,6 @@ def print_timezone_help(): def main(): """See module docstring at the top of this file.""" - parser = ArgParser() parser.add_argument('outdir', help='output directory') parser.add_argument( diff --git a/track/focusers.py b/track/focusers.py index 16c0ad1..2974e11 100644 --- a/track/focusers.py +++ b/track/focusers.py @@ -14,7 +14,7 @@ class Focuser(ABC): - """Abstract base class for focusers""" + """Abstract base class for focusers.""" @property @abstractmethod @@ -107,14 +107,14 @@ class ReadTimeoutException(Exception): @staticmethod def add_program_arguments(parser: ArgParser) -> None: - """Add Moonlite-specific program arguments""" + """Add Moonlite-specific program arguments.""" parser.add_argument( '--focuser-dev', help='Moonlite focuser serial device node path', default='/dev/ttyUSB0' ) @staticmethod def from_program_args(args: Namespace) -> MoonliteFocuser: - """Factory to make a MoonliteFocuser instance from program arguments""" + """Factory to make a MoonliteFocuser instance from program arguments.""" return MoonliteFocuser( device=args.focuser_dev, min_position=args.focuser_min, @@ -164,7 +164,6 @@ def _send_command(self, command: bytearray, response_len: int) -> bytearray | No ResponseException: When the response length does not match the value of the response_len argument. """ - # Eliminate any stale data sitting in the read buffer which could be left over from prior # command responses. self._serial.read(self._serial.in_waiting) diff --git a/track/gamepad.py b/track/gamepad.py index a461c54..ed3ca9c 100644 --- a/track/gamepad.py +++ b/track/gamepad.py @@ -173,7 +173,7 @@ def register_callback( self.callbacks[event_code] = callback def _update_analog(self, stick: str) -> None: - """Convert integer analog stick values to floating point""" + """Convert integer analog stick values to floating point.""" if stick == 'left': raw_vector = self.state['ABS_X'] - 1j * self.state['ABS_Y'] elif stick == 'right': @@ -200,8 +200,7 @@ def _update_analog(self, stick: str) -> None: self.right_y = scaled_vector.imag def __get_input(self): - """Thread for reading input from gamepad""" - + """Thread for reading input from gamepad.""" # Make sure this thread does not have realtime priority os.sched_setscheduler(0, os.SCHED_OTHER, os.sched_param(0)) @@ -249,7 +248,6 @@ def __integrator(self): integrators can continue running even when the input thread is blocked waiting for new events from the controller. """ - # Make sure this thread does not have realtime priority os.sched_setscheduler(0, os.SCHED_OTHER, os.sched_param(0)) @@ -275,7 +273,6 @@ def __integrator(self): def get_telem_points(self) -> list[Point]: """Called by telemetry logger. See `TelemSource` abstract base class.""" - point = Point('gamepad') # attributes of this object to be captured as fields in the telemetry measurement names = ['left_x', 'left_y', 'right_x', 'right_y', 'int_x', 'int_y', 'integrator_mode'] @@ -295,8 +292,7 @@ def get_telem_points(self) -> list[Point]: def main(): - """Prints all gamepad events received""" - + """Prints all gamepad events received.""" logging.basicConfig( level=logging.DEBUG, format='%(message)s', diff --git a/track/gamepad_control.py b/track/gamepad_control.py index b62a410..6b68ca0 100755 --- a/track/gamepad_control.py +++ b/track/gamepad_control.py @@ -19,8 +19,7 @@ def main(): - """See module docstring""" - + """See module docstring.""" parser = ArgParser() mounts.add_program_arguments(parser) telem.add_program_arguments(parser) diff --git a/track/gps_client.py b/track/gps_client.py index 45177b3..654a4f1 100755 --- a/track/gps_client.py +++ b/track/gps_client.py @@ -35,7 +35,7 @@ class GPSFixType(IntEnum): - """Improved enum-ized version of the 'MODE_' constants from the gps module""" + """Improved enum-ized version of the 'MODE_' constants from the gps module.""" ZERO = 0 NO_FIX = 1 @@ -277,7 +277,6 @@ def _check_criteria( Returns: An instance of FailureReason with flags set to indicate checks that failed. """ - fail_reasons = self.FailureReason.NONE min_fix = GPSFixType.FIX_3D if need_3d else GPSFixType.FIX_2D @@ -426,7 +425,6 @@ def main(): Note that the ERR_MAX and MARGINS values in this function are extremely lax, allowing almost any 3D fix to pass muster. Adjust these to be more restrictive as desired. """ - # parameters timeout = 10.0 need_3d = True diff --git a/track/iss.py b/track/iss.py index 1f8a354..c6b818c 100755 --- a/track/iss.py +++ b/track/iss.py @@ -13,8 +13,7 @@ def main(): - """See module docstring""" - + """See module docstring.""" parser = ArgParser() parser.add_argument('--lat', required=True, help='latitude of observer (+N)') parser.add_argument('--lon', required=True, help='longitude of observer (+E)') diff --git a/track/logs.py b/track/logs.py index cde6545..e6db24d 100644 --- a/track/logs.py +++ b/track/logs.py @@ -1,4 +1,4 @@ -"""Configure logging for the package""" +"""Configure logging for the package.""" from enum import IntEnum from importlib import metadata @@ -20,7 +20,7 @@ class LogLevel(IntEnum): - """Define an enum for common levels since the logging module doesn't""" + """Define an enum for common levels since the logging module doesn't.""" CRITICAL = logging.CRITICAL ERROR = logging.ERROR @@ -82,7 +82,6 @@ def setup( file_level: Log level for the file handler. enable_console_logging: Enable logging to the console (stdout). """ - log_filename = os.path.join(LOG_PATH, f'{program_name}.log') root_logger = logging.getLogger(__package__) @@ -173,7 +172,7 @@ def add_program_arguments(parser: ArgParser) -> None: def setup_logging_from_args(args: Namespace, program_name: str) -> None: - """Setup logging using program arguments""" + """Setup logging using program arguments.""" setup( program_name, console_level=LogLevel[args.console_log_level.upper()], diff --git a/track/model.py b/track/model.py index 144f378..e1754a1 100644 --- a/track/model.py +++ b/track/model.py @@ -231,7 +231,7 @@ class MountModel: """ def __init__(self, model_param_set: ModelParamSet): - """Construct an instance of MountModel + """Construct an instance of MountModel. Args: model_param_set: Set of model parameters to use in calculations. @@ -366,7 +366,6 @@ def encoders_to_spherical( mount axis 0 and latitude corresponds to mount axis 1, and the second element is the meridian side this position lies on. """ - # apply encoder offsets encoder_positions = MountEncoderPositions( Longitude(encoder_positions[0] - self.model_params.axis_0_offset), @@ -400,7 +399,6 @@ def encoders_to_meridian_side(self, encoder_positions: MountEncoderPositions) -> Returns: The meridian side corresponding to the encoder positions. """ - # apply encoder offset encoder_1_position = Longitude(encoder_positions[1] - self.model_params.axis_1_offset) @@ -411,7 +409,7 @@ def spherical_to_encoders( coord: UnitSphericalRepresentation, meridian_side: MeridianSide = MeridianSide.EAST, ) -> MountEncoderPositions: - """Convert from mount-relative spherical coordinates to mount encoder positions + """Convert from mount-relative spherical coordinates to mount encoder positions. See docstring of `encoders_to_spherical`, which is the inverse of this method. @@ -517,7 +515,6 @@ def topocentric_to_encoders( Raises: TypeError if frame of sky_coord is not AltAz. """ - if not isinstance(sky_coord.frame, AltAz): raise TypeError('frame of sky_coord must be AltAz') @@ -529,7 +526,7 @@ def topocentric_to_encoders( def ha_to_ra(hour_angle: Longitude, longitude: Longitude, t: Time) -> Longitude: - """Converts hour angle to right ascension + """Converts hour angle to right ascension. Args: hour_angle: The hour angle to be converted. @@ -543,7 +540,7 @@ def ha_to_ra(hour_angle: Longitude, longitude: Longitude, t: Time) -> Longitude: def ra_to_ha(ra_angle: Longitude, longitude: Longitude, t: Time) -> Longitude: - """Converts right ascension to hour angle + """Converts right ascension to hour angle. Args: ra_angle: The right ascension angle to be converted. @@ -561,7 +558,7 @@ def residual( observation: pd.Series, model_params: ModelParameters, ) -> pd.Series: - """Compute the residual (error) between observed and modeled positions + """Compute the residual (error) between observed and modeled positions. Args: observation: A single observation. @@ -645,7 +642,6 @@ def solve_model(observations: pd.DataFrame) -> tuple[ModelParameters, OptimizeRe Raises: NoSolutionException if a solution could not be found. """ - # best starting guess for parameters init_values = ModelParameters( axis_0_offset=Angle(0 * u.deg), @@ -691,7 +687,7 @@ def solve_model(observations: pd.DataFrame) -> tuple[ModelParameters, OptimizeRe class StaleParametersException(Exception): - """Raised when model parameters are stale and potentially invalid""" + """Raised when model parameters are stale and potentially invalid.""" def save_default_param_set(model_param_set: ModelParamSet) -> None: diff --git a/track/mounts.py b/track/mounts.py index ec9840b..23a27f7 100644 --- a/track/mounts.py +++ b/track/mounts.py @@ -84,7 +84,7 @@ def __exit__(self, exc_type, exc_value, traceback) -> bool: @property @abstractmethod def slew_accel(self) -> float: - """Expected acceleration of when changing slew rate in degrees per second squared""" + """Expected acceleration of when changing slew rate in degrees per second squared.""" raise NotImplementedError @abstractmethod @@ -291,13 +291,13 @@ class NexStarMount(TelescopeMount): """ class AxisName(IntEnum): - """Mapping from axis index to/from names""" + """Mapping from axis index to/from names.""" AZIMUTH = 0 ALTITUDE = 1 def short_name(self) -> str: - """Abbreviated axis name""" + """Abbreviated axis name.""" return 'az' if self == self.AZIMUTH else 'alt' # pylint: disable=too-many-arguments @@ -487,13 +487,13 @@ class LosmandyGeminiMount(TelescopeMount): """ class AxisName(IntEnum): - """Mapping from axis index to/from names""" + """Mapping from axis index to/from names.""" RIGHT_ASCENSION = 0 DECLINATION = 1 def short_name(self) -> str: - """Axis names as used by the point package API for this mount""" + """Axis names as used by the point package API for this mount.""" return 'ra' if self == self.RIGHT_ASCENSION else 'dec' # pylint: disable=too-many-arguments diff --git a/track/ntp.py b/track/ntp.py index 75fb93c..e01792b 100755 --- a/track/ntp.py +++ b/track/ntp.py @@ -13,7 +13,7 @@ class NTPCheckFailure(Exception): - """Raised when NTP status check fails""" + """Raised when NTP status check fails.""" def check_ntp_status( @@ -36,7 +36,6 @@ def check_ntp_status( Raises: NTPCheckFailure if any of the criteria are not met. """ - # The implementation relies on parsing the console output of the ntpq program since no obvious # alternative method was found. This unfortunately means that the implementation relies on # some specifics of the formatting of the peers table printed by ntpq. diff --git a/track/object_position.py b/track/object_position.py index 93952ec..0002f80 100755 --- a/track/object_position.py +++ b/track/object_position.py @@ -13,7 +13,6 @@ def main(): """See module docstring at the top of this file.""" - parser = ArgParser() parser.add_argument('--timestamp', required=False, help='UNIX timestamp', type=float) diff --git a/track/plate_solve.py b/track/plate_solve.py index a394fcc..a39d592 100644 --- a/track/plate_solve.py +++ b/track/plate_solve.py @@ -44,7 +44,6 @@ def plate_solve(frame: np.ndarray, camera_width: float | None = None) -> tuple[W NoSolutionException when a solution could not be found. subprocess.CalledProcessError if astrometry.net has a non-zero exit code. """ - # Must pass frame to astrometry.net as a file and read results from files, so do this in a # unique temporary directory that is deleted as soon as we are done using it with tempfile.TemporaryDirectory() as tempdir: diff --git a/track/ser.py b/track/ser.py index 9bdc314..85deb58 100644 --- a/track/ser.py +++ b/track/ser.py @@ -21,7 +21,7 @@ class SERColorID(IntEnum): - """ColorID field of the SER file header""" + """ColorID field of the SER file header.""" MONO = 0 BAYER_RGGB = 8 @@ -37,13 +37,13 @@ class SERColorID(IntEnum): class SEREndianness(IntEnum): - """Endianness of multi-byte pixel values""" + """Endianness of multi-byte pixel values.""" BIG_ENDIAN = 0 LITTLE_ENDIAN = 1 def dtype_char(self) -> str: - """Get the endian character ('<' or '>') to use with `numpy.dtype`""" + """Get the endian character ('<' or '>') to use with `numpy.dtype`.""" return '<' if self == self.LITTLE_ENDIAN else '>' @@ -262,7 +262,6 @@ def __init__(self, filename: str): Args: filename: Filename of SER file to read. """ - # pylint: disable=consider-using-with self.file = open(filename, 'rb') @@ -308,7 +307,6 @@ def get_frame(self, frame_index: int) -> np.ndarray: Returns: The frame data as a numpy array. """ - if frame_index < 0 or frame_index >= self._header.frame_count: raise ValueError('Invalid frame index') @@ -394,7 +392,6 @@ def __exit__(self, exc_type, exc_value, exc_tb): def close(self) -> None: """Write header and trailer (if enabled) and close file.""" - # write the header self.file.seek(0, SEEK_SET) self.file.write(self.header.buffer) diff --git a/track/skyfield_utils.py b/track/skyfield_utils.py index 555fdc0..f1262e4 100644 --- a/track/skyfield_utils.py +++ b/track/skyfield_utils.py @@ -1,4 +1,4 @@ -"""Skyfield utilities""" +"""Skyfield utilities.""" import logging diff --git a/track/skyplot.py b/track/skyplot.py index 53b1a7d..663addb 100755 --- a/track/skyplot.py +++ b/track/skyplot.py @@ -139,7 +139,6 @@ def make_sky_plot() -> matplotlib.axes.Axes: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ - plt.style.use('dark_background') fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}) fig.canvas.manager.set_window_title('Skyplot') @@ -193,7 +192,6 @@ def plot_tle( time_stop: datetime, ) -> tuple[datetime, datetime]: """Plot the trajectory of the first above-horizon pass within a specified time window.""" - tle = [] with open(tle_filename) as tlefile: for line in tlefile: @@ -380,7 +378,6 @@ def plot_mount_motion( time_start: Beginning of time interval to plot. time_stop: End of time interval to plot. """ - client = telem.open_client() query_api = client.query_api() df = query_api.query_data_frame( @@ -402,8 +399,7 @@ def plot_mount_motion( def main(): - """See module docstring at the top of this file""" - + """See module docstring at the top of this file.""" parser = ArgParser(description='Make a plot of the sky to plan and evaluate tracking') parser.add_argument('--tle', help='filename of two-line element (TLE) target ephemeris') parser.add_argument( diff --git a/track/slew_rate_test.py b/track/slew_rate_test.py index 983f88e..f55b282 100755 --- a/track/slew_rate_test.py +++ b/track/slew_rate_test.py @@ -13,8 +13,7 @@ def main(): - """See module docstring""" - + """See module docstring.""" parser = config.ArgParser() mounts.add_program_arguments(parser) args = parser.parse_args() diff --git a/track/stars.py b/track/stars.py index ab34ed5..b933999 100755 --- a/track/stars.py +++ b/track/stars.py @@ -1,8 +1,6 @@ #!/usr/bin/env python3 -""" -Identify bright stars near zenith. -""" +"""Identify bright stars near zenith.""" from __future__ import annotations from importlib.resources import files @@ -22,7 +20,7 @@ class NoStarFoundException(Exception): def lookup_star_names(hip: int) -> tuple[str]: - """Look up a star's common name given its Hipparcos ID (HIP) + """Look up a star's common name given its Hipparcos ID (HIP). Args: hip: Hipparcos ID of star to look up. @@ -43,7 +41,7 @@ def lookup_star_names(hip: int) -> tuple[str]: def lookup_star_magnitude(hip: int) -> float: - """Lookup a star's apparent magnitude given its Hipparcos ID (HIP)""" + """Lookup a star's apparent magnitude given its Hipparcos ID (HIP).""" return hipparcos_df[hipparcos_df.index == hip].magnitude.item() @@ -185,7 +183,6 @@ def make_star_from_args(args: Namespace, observer: Barycentric) -> Star: def main(): """See module docstring.""" - parser = ArgParser() gps_client.add_program_arguments(parser) logs.add_program_arguments(parser) diff --git a/track/startracker.py b/track/startracker.py index 7a4b67e..225b610 100755 --- a/track/startracker.py +++ b/track/startracker.py @@ -17,7 +17,6 @@ def main(): """Repeatedly prints coordinates of camera frame center found by plate solving.""" - parser = ArgParser() parser.add_argument('--skip-solve', help='skip plate solving', action='store_true') cameras.add_program_arguments(parser) diff --git a/track/step_response.py b/track/step_response.py index 254fbcc..a28f1b8 100755 --- a/track/step_response.py +++ b/track/step_response.py @@ -14,7 +14,6 @@ def main(): """Apply step functions of varying magnitudes to a mount axis and plot the responses.""" - parser = ArgParser() mounts.add_program_arguments(parser) parser.add_argument( diff --git a/track/targets.py b/track/targets.py index 021cf66..1230bfa 100644 --- a/track/targets.py +++ b/track/targets.py @@ -122,7 +122,7 @@ def __init__(self, enc: MountEncoderPositions, mount_model: MountModel): self.position_topo = mount_model.encoders_to_topocentric(enc) def get_position(self, t: Time | None = None) -> TargetPosition: - """Since the position is fixed the t argument is ignored""" + """Since the position is fixed the t argument is ignored.""" return TargetPosition(t, self.position_topo, self.position_enc) @@ -148,7 +148,7 @@ def __init__(self, coord: SkyCoord, mount_model: MountModel, meridian_side: Meri self.position_enc = mount_model.topocentric_to_encoders(coord, meridian_side) def get_position(self, t: Time | None = None) -> TargetPosition: - """Since the topocentric position is fixed the t argument is ignored""" + """Since the topocentric position is fixed the t argument is ignored.""" return TargetPosition(t, self.position_topo, self.position_enc) @@ -187,7 +187,6 @@ def __init__( def get_position(self, t: Time) -> TargetPosition: """Gets the position of the simulated target for a specific time.""" - if self.time_start is None: # Don't do this in constructor because it may be a couple seconds between when the # constructor is called until the first call to this method. @@ -237,7 +236,7 @@ def get_position(self, t: Time) -> TargetPosition: class FlightclubLaunchTrajectoryTarget(Target): - """A target that follows a trajectory predicted by a flightclub.io simulation""" + """A target that follows a trajectory predicted by a flightclub.io simulation.""" def __init__( self, @@ -257,8 +256,7 @@ def __init__( @lru_cache(maxsize=128) # cache results to avoid re-computing unnecessarily def get_position(self, t: Time) -> TargetPosition: - """Get apparent position of this target""" - + """Get apparent position of this target.""" # use linear interpolation between available trajectory data points time_from_t0 = (t - self.time_t0).to_value('sec') position_az = np.interp(time_from_t0, self.times_from_t0, self.az) @@ -270,12 +268,12 @@ def get_position(self, t: Time) -> TargetPosition: class PyEphemTarget(Target): - """A target using the PyEphem package""" + """A target using the PyEphem package.""" def __init__( self, target, location: EarthLocation, mount_model: MountModel, meridian_side: MeridianSide ): - """Init a PyEphem target + """Init a PyEphem target. This target type uses PyEphem, the legacy package for ephemeris calculations. @@ -286,7 +284,6 @@ def __init__( mount_model: An instance of class MountModel for coordinate system conversions. meridian_side: Desired side of mount-relative meridian. """ - self.target = target # Create a PyEphem Observer object for the given location @@ -300,7 +297,7 @@ def __init__( @lru_cache(maxsize=128) # cache results to avoid re-computing unnecessarily def get_position(self, t: Time) -> TargetPosition: - """Get apparent position of this target""" + """Get apparent position of this target.""" self.observer.date = ephem.Date(t.datetime) self.target.compute(self.observer) position_topo = SkyCoord(self.target.az * u.rad, self.target.alt * u.rad, frame='altaz') @@ -325,7 +322,7 @@ def __init__( telem_logger: TelemLogger | None = None, separation_callback: Callable[[Angle], None] | None = None, ): - """Construct an instance of CameraTarget + """Construct an instance of CameraTarget. Args: camera: Camera from which to capture imagery. @@ -370,7 +367,7 @@ def camera_to_directional_offset( target_y: Angle, mount_meridian_side: MeridianSide, ) -> tuple[Angle, Angle]: - """Transform from a position in camera frame to a magnitude and position angle + """Transform from a position in camera frame to a magnitude and position angle. Args: target_x: Target position in camera's x-axis @@ -382,7 +379,6 @@ def camera_to_directional_offset( A tuple containing the target position offset magnitude and angle in the mount-relative frame, as would be passed to `SkyCoord.directional_offset_by()`. """ - # position of target relative to center of camera frame target_position_cam = target_x + 1j * target_y @@ -404,7 +400,7 @@ def _camera_to_mount_position( target_x: Angle, target_y: Angle, ) -> UnitSphericalRepresentation: - """Transform from target position in camera frame to position in mount frame + """Transform from target position in camera frame to position in mount frame. Args: target_x: Target position in camera's x-axis @@ -413,7 +409,6 @@ def _camera_to_mount_position( Returns: Target position in mount frame """ - # Mount position is not queried at the exact same time that the camera frame was obtained. # The unknown time offset contributes to error in the transformation. mount_enc_positions = self.mount.get_position() @@ -584,7 +579,7 @@ def process_sensor_data(self) -> None: class SensorFusionTarget(Target): - """Uses sensor fusion to combine data from a `CameraTarget` and another `Target`""" + """Uses sensor fusion to combine data from a `CameraTarget` and another `Target`.""" def __init__( self, @@ -598,7 +593,7 @@ def __init__( telem_logger: TelemLogger | None = None, spiral_search: bool = False, ): - """Construct an instance of SensorFusionTarget + """Construct an instance of SensorFusionTarget. Args: blind_target: A `Target` that is not `CameraTarget`. This should predict the position @@ -614,7 +609,6 @@ def __init__( value to prevent it from growing excessively large. spiral_search: Do a spiral search until a target is found in the camera. """ - self.blind_target = blind_target self.camera_target = camera_target self.mount = mount @@ -649,7 +643,6 @@ def get_position(self, t: Time) -> TargetPosition: Raises: IndeterminatePosition if the target position cannot be determined. """ - # Only the topocentric position of the target is needed here since the mount encoder # positions will be re-computed later in this method. A potential optimization is to find # a way to prevent the blind target from generating this unused output. @@ -678,7 +671,7 @@ def get_position(self, t: Time) -> TargetPosition: ) def _post_telemetry(self) -> None: - """Post telemetry points""" + """Post telemetry points.""" if self.telem_logger is not None: p = Point('sensor_fusion') p.field('blind_target_bias_mag', np.abs(self.blind_target_bias)) @@ -736,7 +729,7 @@ def process_sensor_data(self) -> None: class TargetType(enum.Flag): - """All supported target types""" + """All supported target types.""" NONE = 0 FLIGHTCLUB = enum.auto() @@ -750,7 +743,7 @@ class TargetType(enum.Flag): @classmethod def all(cls): - """Returns the union of all target types""" + """Returns the union of all target types.""" retval = cls.NONE for member in cls.__members__.values(): retval |= member diff --git a/track/telem.py b/track/telem.py index ca04d4b..a8600ff 100644 --- a/track/telem.py +++ b/track/telem.py @@ -185,7 +185,7 @@ def register_sources(self, sources: dict[str, TelemSource]) -> None: self.sources.update(sources) def poll_sources(self) -> None: - """Poll all registered `TelemSource` objects and post to the database""" + """Poll all registered `TelemSource` objects and post to the database.""" for source in self.sources.values(): for point in source.get_telem_points(): # tag point with the class name of the object that generated it @@ -202,7 +202,6 @@ def post_points(self, points: list[Point]) -> None: def _worker_thread(self) -> None: """Gathers telemetry and posts to database once per sample period.""" - # Make sure this thread does not have realtime priority os.sched_setscheduler(0, os.SCHED_OTHER, os.sched_param(0)) diff --git a/track/tsp.py b/track/tsp.py index 6813e21..41469fc 100644 --- a/track/tsp.py +++ b/track/tsp.py @@ -7,11 +7,11 @@ class Destination(ABC): - """Abstract class representing a destination along a route""" + """Abstract class representing a destination along a route.""" @abstractmethod def distance_to(self, other_destination: "Destination") -> int: - """Returns the distance (cost) of travelling from this to the other destination + """Returns the distance (cost) of travelling from this to the other destination. Args: other_destination: Another instance of this class @@ -23,7 +23,7 @@ def distance_to(self, other_destination: "Destination") -> int: def solve_route(destinations: list[Destination]) -> list[Destination]: - """Travelling salesman problem solver + """Travelling salesman problem solver. Takes a list of destinations and sorts them such that the total time taken to visit each one is minimized. This is known as the travelling salesman problem. @@ -39,7 +39,6 @@ def solve_route(destinations: list[Destination]) -> list[Destination]: The list of places ordered such that visiting them in that order minimizes the total trip time. """ - # pre-compute a matrix of distances between each destination distance_matrix = np.zeros((len(destinations),) * 2) for idx, dest in enumerate(destinations):