Skip to content
Open
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
72 changes: 55 additions & 17 deletions Utils/TrackStack.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#!/usr/bin/env python3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shebang and the 100644 -> 100755 mode change are unrelated to the feature. Only 4 of the Utils/*.py scripts carry a shebang, so this is a bit inconsistent as housekeeping. Not harmful — but I'd rather it were a separate commit, or applied across the directory at once.

from __future__ import print_function

import os, sys
Expand All @@ -14,6 +15,7 @@
from RMS.Astrometry.ApplyAstrometry import xyToRaDecPP, raDecToXYPP
from RMS.Astrometry.Conversions import date2JD, jd2Date
from RMS.Formats.FFfile import validFFName, getMiddleTimeFF
from RMS.Formats.FTPdetectinfo import readFTPdetectinfo, validDefaultFTPdetectinfo
from RMS.Formats.FFfile import read as readFF
from RMS.Formats.Platepar import Platepar
from RMS.Math import angularSeparation
Expand All @@ -26,11 +28,35 @@
import time
import datetime

def findFTPFile(dir_path, config):
if os.path.isfile(os.path.join(dir_path,'.config')):
tmpcfg = cr.loadConfigFromDirectory('.config', dir_path)
else:
tmpcfg = config
ftp_list = glob(os.path.join(dir_path, 'FTPdetectinfo_{}*.txt'.format(tmpcfg.stationID)))
ftp_list = [x for x in ftp_list if 'backup' not in x and 'unfiltered' not in x]
Comment on lines +36 to +37

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This filter duplicates validDefaultFTPdetectinfo() in RMS/Formats/FTPdetectinfo.py:31, and misses uncalibrated, which that predicate also excludes. findFTPdetectinfoFile(path) at RMS/Formats/FTPdetectinfo.py:251 covers most of this too. The existing helper doesn't do the stationID/.config matching, so a thin wrapper is defensible — but it should call the shared predicate rather than re-implementing the substring checks.

(To be clear, the duplication is pre-existing — this PR just moved it into a function. Since it's being touched anyway, it's a good moment to switch.)

ftp_list.sort()

if len(ftp_list) < 1:
raise FileNotFoundError('unable to find FTPdetect file in {}'.format(dir_path))

return ftp_list[0]
Comment on lines +40 to +43

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning False as a sentinel is what makes the two call sites silently misbehave. Since RMS/Formats/FTPdetectinfo.py:251 already raises FileNotFoundError for this case, raising here would be consistent with the rest of the codebase; otherwise return None and check it. Either way both call sites need to handle it.


def makeMeteorMask(ftp_points_list, initial_mask):
"""Make a mask in which only the meteor is visible"""
meteor_mask = np.zeros_like(initial_mask)

for ftp_points in ftp_points_list:
pts = np.array([[round(p[2]), round(p[3])] for p in ftp_points], dtype=np.int32)
meteor_mask = cv2.polylines(meteor_mask, [pts], False, 255, 1)

meteor_mask = cv2.dilate(meteor_mask, np.ones((150, 150), np.uint8))
return np.minimum(meteor_mask, initial_mask)

def trackStack(dir_paths, config, border=5, background_compensation=True,
hide_plot=False, showers=None, darkbackground=False, out_dir=None,
scalefactor=None, draw_constellations=False, one_core_free=False,
textoption=0):
textoption=0, mask_meteors=False):
Comment thread
tammojan marked this conversation as resolved.
""" Generate a stack with aligned stars, so the sky appears static. The folder should have a
platepars_all_recalibrated.json file.

Expand All @@ -51,6 +77,7 @@ def trackStack(dir_paths, config, border=5, background_compensation=True,
draw_constellations: [bool] Show constellation lines on stacked image
one_core_free: [bool] leave one core free whilst processing
textoption: [int] 0 - no text, 1 - filename, 2 - stationID, date, meteor count overlayed
mask_meteors: [bool] use only a portion of the image around a detection
"""
start_time = time.time()
# normalise the path in a platform neutral way
Expand Down Expand Up @@ -88,20 +115,11 @@ def trackStack(dir_paths, config, border=5, background_compensation=True,
# Get FTP file so we can filter by shower
for dir_path in dir_paths:

if os.path.isfile(os.path.join(dir_path,'.config')):
tmpcfg = cr.loadConfigFromDirectory('.config', dir_path)
else:
tmpcfg = config

ftp_list = glob(os.path.join(dir_path, 'FTPdetectinfo_{}*.txt'.format(tmpcfg.stationID)))
ftp_list = [x for x in ftp_list if 'backup' not in x and 'unfiltered' not in x]
ftp_list.sort()

if len(ftp_list) < 1:
print('unable to find FTPdetect file in {}'.format(dir_path))
try:
ftp_file = findFTPFile(dir_path, config)
except FileNotFoundError as e:
print(e)
return False

ftp_file = ftp_list[0]

print('Performing shower association using {}'.format(ftp_file))

Expand All @@ -123,6 +141,18 @@ def trackStack(dir_paths, config, border=5, background_compensation=True,
ff_list.append(file_name)
ff_list = list(set(ff_list))

ftp_points = {}
if mask_meteors:
for dir_path in dir_paths:
try:
ftp_file = findFTPFile(dir_path, config)
except FileNotFoundError as e:
print(e)
return False

for ftp_entry in readFTPdetectinfo(os.path.dirname(ftp_file), os.path.basename(ftp_file)):
Comment thread
tammojan marked this conversation as resolved.
ftp_points.setdefault(ftp_entry[0], []).append(ftp_entry[-1])

# Take the platepar with the middle time as the reference one
ff_found_list = []
jd_list = []
Expand Down Expand Up @@ -272,7 +302,7 @@ def trackStack(dir_paths, config, border=5, background_compensation=True,
thead_pool = QueuedPool(stackFrame, cores=cores, backup_dir=None, print_state=False, func_extra_args=(recalibrated_platepars, mask, border,
pp_ref, img_size, jd_middle, pp_stack, config,
avg_stack_sum_shared, avg_stack_count_shared, max_deaveraged_shared,
background_compensation, finished_count, num_ffs))
background_compensation, finished_count, num_ffs, ftp_points, mask_meteors))
thead_pool.startPool()
# add jobs
for i, ff_name in enumerate(enumlist):
Expand Down Expand Up @@ -374,7 +404,7 @@ def trackStack(dir_paths, config, border=5, background_compensation=True,


def stackFrame(ff_name, recalibrated_platepars, mask, border, pp_ref, img_size, jd_middle, pp_stack, conf, avg_stack_sum_arr,
avg_stack_count_arr, max_deaveraged_arr, background_compensation, finished_count, num_ffs):
avg_stack_count_arr, max_deaveraged_arr, background_compensation, finished_count, num_ffs, ftp_points, mask_meteors):
ff_basename = os.path.basename(ff_name)

avg_stack_sum = getArray(img_size, avg_stack_sum_arr)
Expand Down Expand Up @@ -420,6 +450,11 @@ def stackFrame(ff_name, recalibrated_platepars, mask, border, pp_ref, img_size,
# Compute deaveraged maxpixel image
max_deavg = maxpixel - avepixel

if mask_meteors:
meteor_tracks = ftp_points.get(ff_basename, [])
ff_mask = makeMeteorMask(meteor_tracks, mask.img)
max_deavg[ff_mask == 0] = 0

# Normalize the background brightness by applying a large-kernel median filter to avepixel
if background_compensation:

Expand Down Expand Up @@ -526,6 +561,9 @@ def getArray(size, shared_arr):
arg_parser.add_argument('--freecore', action="store_true",
help="""Leave at least one core free""")

arg_parser.add_argument('--mask-meteors', action="store_true",
help="""Render only the part of the image around the meteor to suppress planes and satellites (works best for large trackstacks)""")

# Parse the command line arguments
cml_args = arg_parser.parse_args()

Expand All @@ -548,4 +586,4 @@ def getArray(size, shared_arr):
hide_plot=cml_args.hideplot, showers=showers,
darkbackground=cml_args.darkbackground, out_dir=cml_args.output, scalefactor=cml_args.scalefactor,
draw_constellations=cml_args.constellations, one_core_free=cml_args.freecore,
textoption = text_option)
textoption = text_option, mask_meteors=cml_args.mask_meteors)