lgrcia / prose

Modular image processing pipelines with Python. Built for Astronomy.
https://prose.readthedocs.io
MIT License
52 stars 11 forks source link

Satellites detection project #74

Open lgrcia opened 1 year ago

lgrcia commented 1 year ago

Started with @szunigaf and @MouradGHACHOUI.

szunigaf commented 1 year ago

First attempt to satellite trace detection using prose:

from prose import Sequence, blocks

SatDetection = Sequence([
    blocks.detection.AutoSourceDetection(), # source detection
    blocks.Get('sources', 'path')
])

SatDetection.run(fm.all_images)

from prose.core.source import TraceSource

traces = []

for sources, path in zip(SatDetection.get.values['sources'], SatDetection.get.values['path']):
    long_sources = [source for source in sources if isinstance(source, TraceSource)]
    long_sources = [source for source in long_sources if source.a > 200] # Not elegant way to discriminate between any source and a Satellite trace
    if len(long_sources) > 0:
        traces.append([path,long_sources])

def plot_trace(path, traces):
    im = Image(path)
    im.show()
    for t in traces:
        t.plot()

#Check the trace
plot_trace(*traces[2])

Is working but in order to look at LEO satellites databases and to detect which satellite constellation correspond the trace we need to get from the image the JD, FoV, approximated image center, exp time and observatory location.

lgrcia commented 1 year ago

Thanks @szunigaf!

for sources, path in zip(SatDetection.get.values['sources'], SatDetection.get.values['path']):
    long_sources = [source for source in sources if isinstance(source, TraceSource)]
    long_sources = [source for source in long_sources if source.a > 200]
if len(long_sources) > 0:
   traces.append([path,long_sources])

I think the TraceDetection block now does most of this task (otherwise it should).

JD, FoV, approximated image center, exp time and observatory location

These should be easily obtained through the Image and Telescope objects.

As I told you the trace vertexes are computed the same way as any other sources, using the semi major axis assuming traces are elongated ellipses (this was pointed out by @MouradGHACHOUI). As this is wrong, it bias the true coordinates of the vertexes. In prose/blocks/detection.py - line 237

sources = np.array([TraceSource.from_region(region) for region in regions])

i.e. sources are created from skimage region properties. This object contains the bounding box of the source (the trace) that can be used to locate the trace vertexes. We could simply reimplement the from_region for the TraceSource object.

It could be a nice first contribution if you like :) (I can help with the process)

szunigaf commented 1 year ago

New version of LEO detection sequence:

# LEO detection sequence

SatDetection = Sequence([
    #blocks.detection.AutoSourceDetection(minor_length=200), # source detection
    blocks.detection.TraceDetection(minor_length=200), # source detection
    blocks.Get(
        "sources",
        "fov",
        "filter",
        jd=lambda im: im.fits_header[im.telescope.keyword_jd], # JD at the start of the exposure
        date_utc=lambda im: Time(im.date).fits, # Datetime (UTC) at the start of the exposure
        c=lambda im: im.skycoord, # center approx (deg)
        earth_location=lambda im: im.telescope.earth_location, # telescope location
        telescope=lambda im: im.telescope.name,
        exp_time=lambda im: im.fits_header[im.telescope.keyword_exposure_time], # exposure time
        path=lambda im: im.metadata['path'], #path of the image
        arrays = False,
    ),
])

SatDetection.run(fm.all_images)

SatTraces = SatDetection[1]

# Check the data
print(SatTraces.jd)
print(SatTraces.date_utc)
print(SatTraces.exp_time)
print(SatTraces.earth_location[0].geodetic)

# sources[image][trace source]
SatTraces.sources[0][0].vertexes

Then plot the results just to double check:

def plot_trace(path, traces):
    im = FITSImage(path)
    im.show()
    for t in traces:
        t.plot()
# Example
plot_trace(SatTraces.path[0],SatTraces.sources[0])

To do: Adapt the Tracesource to get the real vertices value.

shfxia commented 1 year ago

May be we can update a and b after create TraceSource in the following way. region_parser is a custom region parser function, can be passed to TraceDetection. The custom region parser is support in #95.

def region_parser(source, region):
    if isinstance(source, TraceSource):
        width = region.bbox[3] - region.bbox[1]
        height = region.bbox[2] - region.bbox[0]

        theta = source.orientation
        source.a = (np.sin(theta) * height - np.cos(theta) * width) / (
            np.sin(theta) ** 2 - np.cos(theta) ** 2
        )
        source.b = (np.sin(theta) * width - np.cos(theta) * height) / (
            np.sin(theta) ** 2 - np.cos(theta) ** 2
        )

...
TraceDetection(parser=region_parser).run(image)
'''

The equation used to update a, b is not checked and may be wrong.

lgrcia commented 1 year ago

Indeed! I think once #95 is done we will have a very elegant way to define how the sources properties are set!

I think for the TraceSource it makes sense to define a default parser like the one you just wrote.

Edit: Actually the default TraceSource creation can simply be handled by implementing its from_region class method, as suggested above. But providing a callable for specific properties setting (without subclassing, which is the only difference here) might be interesting for some applications.