Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ pip install .

Once your system is fully set up and configured (via the `.config` file, as explained in the Wiki), you can run various RMS modules.

### Available tools
To see a list of the most commonly used tools with short descriptions, run:

```bash
python -m RMS
```

Any of the listed commands can then be run as e.g. `python -m RMS skyfit .` or `python -m RMS status`. A full catalog of the utility scripts is in [Utils/README.md](Utils/README.md).

### Capturing video and saving data
To start the automated video capture, navigate to your base RMS folder in the terminal and run:

Expand Down
60 changes: 60 additions & 0 deletions RMS/CLITools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
""" Shared helpers for command-line interfaces of RMS scripts.

This module is intentionally lightweight (stdlib-only at import time) so that it can be imported at the
top of any script without pulling in heavy dependencies.
"""

from __future__ import print_function, division, absolute_import


def addConfigArgument(parser, help_text=None):
""" Register the standard -c/--config argument on an argparse parser.

Unlike the legacy nargs=1 pattern, the value is stored as a plain string, so scripts can pass
cml_args.config directly to loadConfig() without indexing.

Arguments:
parser: [argparse.ArgumentParser] Parser to add the argument to.

Keyword arguments:
help_text: [str] Custom help text. If None, a standard description is used.

"""

if help_text is None:
help_text = ("Path to a config file which will be used instead of the default one."
" To load the .config file in the given data directory, use '.' as the value.")

parser.add_argument('-c', '--config', metavar='CONFIG_PATH', type=str, help=help_text)


def loadConfig(cml_args_config, dir_path=None):
""" Load the RMS config given the value of the --config argument.

Accepts the value in any historical shape (None, a plain string, or the 1-element list produced by
the legacy nargs=1 pattern) and normalizes it before handing it to
RMS.ConfigReader.loadConfigFromDirectory, which expects a list.

Arguments:
cml_args_config: [None/str/list] Value of cml_args.config from argparse.

Keyword arguments:
dir_path: [str or list] Path to the working directory (or several). If None, the current
directory is used.

Return:
config: [Config instance] Loaded config.

"""

# Deferred import so importing this module stays cheap
import RMS.ConfigReader as cr

if dir_path is None:
dir_path = '.'

# Normalize the config argument to the list shape loadConfigFromDirectory expects
if isinstance(cml_args_config, str):
cml_args_config = [cml_args_config]

return cr.loadConfigFromDirectory(cml_args_config, dir_path)
23 changes: 23 additions & 0 deletions RMS/Formats/ObservationSummary.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,29 @@ def addRequiredColumns(conn, d):

return set(getColumns(conn))

def getLatestObservationRecord(conn):
"""Get the most recent observation summary record from the database.

Arguments:
conn: connection to database.

Return:
[dict or None] The latest record as a column name -> value dict, or None if the table is empty.
"""

cursor = conn.execute(
f"SELECT * FROM {OBSERVATIONS_TABLE_NAME} ORDER BY {NIGHT_DATA_DIR_COL} DESC LIMIT 1")

row = cursor.fetchone()

if row is None:
return None

columns = [description[0] for description in cursor.description]

return dict(zip(columns, row))


def storeDictInDB(conn, d, debug=False):
"""Store the dict d in the observation summary database, create new columns if needed.

Expand Down
106 changes: 106 additions & 0 deletions RMS/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
""" Umbrella launcher for RMS tools.

Run "python -m RMS" to list the available tools, and "python -m RMS <command> [args...]" to run one.
Every command is a thin alias for a module which can also be run directly, e.g.
"python -m RMS skyfit ." is the same as "python -m Utils.SkyFit2 .".
"""

from __future__ import print_function, division, absolute_import

import runpy
import sys


# Registry of subcommands: (command, module, one-line description), grouped by topic.
# Keep the descriptions short - they are printed as a single line each.
COMMANDS = [
("Station operation", [
("start", "RMS.StartCapture", "Start the capture pipeline (recording, detection, upload)"),
("reprocess", "RMS.Reprocess", "Reprocess a night directory from raw FF files"),
("status", "Utils.StationStatus", "Show the station health report from the last night"),
("capture-duration", "RMS.CaptureDuration", "Print the capture start time and duration for tonight"),
("delete-old", "RMS.DeleteOldObservations", "Free up disk space by deleting old observations"),
]),
("Calibration & astrometry", [
("skyfit", "Utils.SkyFit2", "Astrometric calibration and manual reduction GUI"),
("calibration-report", "Utils.CalibrationReport", "Generate a calibration quality report for a night"),
("flat", "Utils.MakeFlat", "Make a flat field image from a night of data"),
("shower-association", "Utils.ShowerAssociation", "Associate detected meteors with showers"),
]),
("Camera setup", [
("camera-control", "Utils.CameraControl", "Get/set IP camera parameters"),
("camera-manager", "Utils.CamManager", "Find and configure cameras on the network (GUI)"),
("camera-address", "Utils.SetCameraAddress", "Change the camera IP address"),
("camera-params", "Utils.setAllCameraParams", "Apply the standard RMS camera settings"),
("livestream", "Utils.ShowLiveStream", "Show the live video stream from the camera"),
]),
("Viewing & media", [
("liveview", "Utils.LiveViewer", "Slideshow of FF files as they are created"),
("frbin", "Utils.FRbinViewer", "View fireball detections from FR bin files"),
("checknight", "Utils.CheckNight", "Visually inspect the images of a night"),
("stack", "Utils.StackFFs", "Stack FF files into a single image"),
("trackstack", "Utils.TrackStack", "Star-aligned stack of a whole night"),
("timelapse", "Utils.GenerateTimelapse", "Make a timelapse video of a night"),
("mp4s", "Utils.GenerateMP4s", "Make MP4 clips of individual detections"),
("thumbnails", "Utils.GenerateThumbnails", "Make a thumbnail grid of a night"),
]),
("Configuration", [
("audit-config", "Utils.AuditConfig", "Compare the station .config against the current template"),
("migrate-config", "Utils.MigrateConfig", "Upgrade the .config to the latest template format"),
]),
("Analysis", [
("flux", "Utils.Flux", "Compute single-station meteor shower flux"),
("fov-kml", "Utils.FOVKML", "Make a Google Earth KML of the camera field of view"),
("fov-skymap", "Utils.FOVSkyMap", "Plot the camera field of view on a sky map"),
]),
]


def printToolList():

print(__doc__)

for group_name, entries in COMMANDS:

print("{:s}:".format(group_name))

for command, module, description in entries:
print(" {:<20s} {:s}".format(command, description))

print()

print("Run \"python -m RMS <command> -h\" for the arguments of an individual tool.")
print("Tools not listed here can be run directly as modules - see Utils/README.md for a full catalog.")


def main():

args = sys.argv[1:]

# No command, an explicit list request, or a help flag - print the tool list
if (not args) or (args[0] in ("tools", "list", "-h", "--help", "help")):
printToolList()
return

command = args[0]

# Find the command in the registry
module = None
for _, entries in COMMANDS:
for cmd_name, cmd_module, _ in entries:
if command == cmd_name:
module = cmd_module
break

if module is None:
print("Unknown command: {:s}".format(command))
print("Run \"python -m RMS\" to list the available commands.")
sys.exit(2)

# Hand over to the target module as if it was run with python -m
sys.argv = [module] + args[1:]
runpy.run_module(module, run_name="__main__", alter_sys=True)


if __name__ == "__main__":
main()
19 changes: 14 additions & 5 deletions Utils/Grouping3DRunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

from __future__ import print_function, division, absolute_import

from RMS.CLITools import addConfigArgument, loadConfig
from RMS.VideoExtraction import Extractor
import RMS.ConfigReader as cr
import RMS.Formats.FFfile as FFfile
from RMS.Routines.Grouping3D import find3DLines, getAllPoints


import os
import sys
import argparse
import time


Expand All @@ -27,11 +27,20 @@

if __name__ == "__main__":

# Extract the directory name from the given argument
bin_dir = sys.argv[1]
arg_parser = argparse.ArgumentParser(description="""Run the fireball extractor on FF files in the \
given directory and show the detected points and lines as 3D plots.""")

arg_parser.add_argument('dir_path', metavar='DIR_PATH', type=str,
help='Path to the directory with FF files.')

addConfigArgument(arg_parser)

cml_args = arg_parser.parse_args()

bin_dir = cml_args.dir_path

# Load config file
config = cr.parse(".config")
config = loadConfig(cml_args.config, bin_dir)

print('Directory:', bin_dir)

Expand Down
56 changes: 39 additions & 17 deletions Utils/PointsViewer.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,70 @@
""" Show points detected by the video extractor on a given FF file as a 3D plot. """

from __future__ import print_function, division, absolute_import

import os
import argparse

import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import sys

from RMS.CLITools import addConfigArgument, loadConfig
from RMS.Formats import FFfile
from RMS import VideoExtraction
import RMS.ConfigReader as cr

def view(ff):
config = cr.parse(".config")


def view(dir_path, file_name, config):

ff = FFfile.read(dir_path, file_name, array=True)

ve = VideoExtraction.Extractor(config)
ve.frames = np.empty((256, ff.nrows, ff.ncols))
ve.compressed = ff.array

points = np.array(ve.findPoints())

plot(points, ff.nrows//config.f, ff.ncols//config.f)

def plot(points, y_dim, x_dim):
plot(points, ff.nrows//config.f, ff.ncols//config.f, file_name)

def plot(points, y_dim, x_dim, name):
fig = plt.figure()

ax = fig.add_subplot(111, projection='3d')
plt.title(name)

y = points[:,0]
x = points[:,1]
z = points[:,2]

# Plot points in 3D
ax.scatter(x, y, z)

# Set axes limits
ax.set_zlim(0, 255)
plt.xlim([0, x_dim])
plt.ylim([0, y_dim])

ax.set_ylabel("Y")
ax.set_xlabel("X")
ax.set_zlabel("Time")

plt.show()


if __name__ == "__main__":
ff = FFfile.read(sys.argv[1], sys.argv[2], array=True)

view(ff)

arg_parser = argparse.ArgumentParser(description="""Show points detected by the video extractor \
on the given FF file as a 3D plot.""")

arg_parser.add_argument('ff_path', metavar='FF_PATH', type=str,
help='Path to the FF file to inspect.')

addConfigArgument(arg_parser)

cml_args = arg_parser.parse_args()

dir_path, file_name = os.path.split(os.path.abspath(cml_args.ff_path))

config = loadConfig(cml_args.config, dir_path)

view(dir_path, file_name, config)
31 changes: 25 additions & 6 deletions Utils/SaturationSimulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from __future__ import print_function, division, absolute_import

import argparse

import numpy as np

import matplotlib.pyplot as plt
Expand Down Expand Up @@ -139,13 +141,30 @@ def _costFunc(mag_app_unsaturated, params):

if __name__ == "__main__":

arg_parser = argparse.ArgumentParser(description="""Simulate the saturation of a moving meteor \
modelled as a moving Gaussian, and plot the apparent vs actual magnitude for a range of PSF sizes.""")

arg_parser.add_argument('-p', '--photomoffset', metavar='PHOTOM_OFFSET', type=float, default=10.7,
help='Photometric offset. Default: 10.7.')

arg_parser.add_argument('-b', '--background', metavar='BACKGROUND_LVL', type=float, default=60,
help='Background level (0-255). Default: 60.')

arg_parser.add_argument('-f', '--fps', metavar='FPS', type=float, default=25,
help='Frames per second of the simulated camera. Default: 25.')

arg_parser.add_argument('-a', '--angvel', metavar='ANG_VEL', type=float, default=250,
help='Angular velocity of the meteor in px/s. Default: 250.')

photom_offset = 10.7
arg_parser.add_argument('-s', '--stddevs', metavar='GAUSS_STDDEV', type=float, nargs='+',
default=[1.5, 1.3, 1.15], help='Gaussian PSF standard deviations to simulate. Default: 1.5 1.3 1.15.')

cml_args = arg_parser.parse_args()

background_lvl = 60
fps = 25
ang_vel = 250 # px/s
photom_offset = cml_args.photomoffset
background_lvl = cml_args.background
fps = cml_args.fps
ang_vel = cml_args.angvel # px/s


#print(findUnsaturatedMagnitude(-0.5, photom_offset, 50, 25, 100, 1))
Expand All @@ -156,8 +175,8 @@ def _costFunc(mag_app_unsaturated, params):
app_mag_range = np.append(np.linspace(-0.5, 2, 1000), np.linspace(2, 6, 10))


# Generate a range of gaussian stddevs
gauss_stddevs = [1.5, 1.3, 1.15]
# Range of gaussian stddevs to simulate
gauss_stddevs = cml_args.stddevs



Expand Down
Loading