# LightCurve1

Keywords:

## Summary

This demo shows how to use the LightCurve sensor plugin to generate a 1D (magnitude vs. time) light curve from a terrestrial observation location of a simple spherical object in orbit. The light curve captures the changing magnitude of the total reflected (and emitted) light from the object as it traverses the sky.

## Related Materials

The following demos, manuals and tutorials can provide additional information about the topics at the focus of this demo:

- Related Demos

  - N/A

- Related Manuals

  - The LightCurve sensor plugin manual.

- Related Tutorials

  - N/A

## Details

This section explains any technical details of the simulation.

## Important Files

This section highlights key files important to the simulation.

### Scene Geometry

The scene in this demo consists entirely of the orbiting sphere object. The sphere itself is a facetized model (see `geometry/sphere.obj`), which is instanced using FlexMotion using the SGP4 location engine with a Two-Line Element (TLE) for the SEASAT 1 satellite (NORAD 10967). Since the sphere is uniform, the orientation setup for this object is not important (a static Euler angle setup was used).

The GLIST setup for the orbiting sphere object.

    <geometrylist enabled="true">
      <object>
        <basegeometry>
          <obj><filename>sphere.obj</filename></obj>
        </basegeometry>
        <dynamicinstance tags="sphere">
          <motion type="flexible">
            <locationengine type="sgp4">
              <data source="internal">
                <tle1>1 10967U 78064A   23320.39076064  .00000335  00000-0  13623-3 0  9992</tle1>
                <tle2>2 10967 107.9972 156.5508 0001869 301.4908  58.6055 14.44619060384514</tle2>
              </data>
            </locationengine>
            <orientationengine type="euler">
              <data source="internal" datetime="relative" frame="scene" order="xyz" delimiter=" ">
                <![CDATA[
                  0 0 0 0
                ]]>
              </data>
            </orientationengine>
          </motion>
        </dynamicinstance>
      </object>
    </geometrylist>

An important attribute of this setup is that the `<dynamicinstance>` has a tag associated with it (i.e., `tags="sphere"`). This tag name (in this case, `sphere`) will be given to the LightCurve sensor plugin so that it will track it.

### Sensor Configuration

The LightCurve plugin configuration is shown below. They key components of the setup are:

- The location of the observation (see the `sensor_location`),

- The tag associated with the target object to track (see `target_tag`),

- The start, duration and number of time samples for the observation (see the `date_time`, `duration` and `sample_count`), and

- The spectral bandpass of the sensor making the observation (see the `bandpass`).

An example configuration for the LightCurve plugin.

                "name": "LightCurve",
                "inputs": {
                    "sensor_location": {
                        "latitude" : 35,
                        "longitude": -106.0,
                        "altitude" : 0
                    },
                    "bandpass": {
                        "units"  : "angstroms",
                        "minimum": 5070,
                        "maximum": 5950,
                        "delta"  : 1
                    },
                    "target_tag": "sphere",
                    "date_time": "2023-11-16T12:47:00.0000-00:00",
                    "duration": 840,
                    "sample_count": 840,
                    "debug_filename": "sphere_debug.grid"
                }

## Simulations and Results

### Running the "Sphere" Simulation

The simulation is run using the `sphere.jsim` file, either from the JSIM Editor in the graphical user interface (GUI) or from the command-line:

Running the sphere simulation

    $ dirsig5 sphere.jsim

During the setup phase, the plugin will output the parameters being used for the simulation:

    Setting up the 'LightCurve' plugin
        Sensor location (lat,lon,alt) = 35, -106, 0
        Target object requested = 'sphere'
        Start date/time (ISO8601) = 2023-11-16T12:47:00.0000+00:00
        Curve duration = 840s
        Curve samples = 100
        Bandpass (min,max,delta) = 5070, 5950, 1
        Grid size = 100 x 100
        Output filename = test.txt
        Band zeropoint = 3.55e-09 erg/(s cm^2 A)
        Debug grid filename = debug.grid

During the simulation, a status message will be print for each time step:

Output of the LightCurve sensor plugin during the simulation.

    [|][#-------------------------------------------------]
    [/][##------------------------------------------------]
    [-][###-----------------------------------------------]
    ...
    [lines deleted for documentation purposes]
    ...
    [-][################################################--]
    [\][#################################################-]
    [|][##################################################]

    Setup time: 0m 0.367s
    Render time: 0m 23.610s
    Total run time: 0m 23.976s

Note that enabling debug output via the command-line `--log_level=debug` option will produce additional information for each time (capture) step:

    [debug] LightCurve::captureStarted:
    [debug]   Target center in ENU  = -3.144e+05, 9.463e+05, 6.791e+05
    [debug]   Target center in ECEF = -1.748e+06, -4.954e+06, 4.803e+06
    [debug]   Target radius     = 6.928 [meters]
    [debug]   Target distance   = 1206482.011 [meters]
    [debug]   Target half angle = 3.356e-04 [degrees]

#### Light Curve Output

The primary output of the simulation is a multi-column, ASCII/Text file containing the following values for each time sample:

1. The relative capture time in seconds

2. The magnitude (see note on units and zero point below)

3. The spectrally integrated radiance (Watts per cm2 per steradian)

4. The radius of the bounding volume for the target (in radians), and

5. The solid angle of the bounding volume for the target (in steradians).

The default output filename for the plugin is `lightcurve.dat`. This filename can be overridden via the `output_filename` variable in the input configuration.

The default zero point for the magnitude is for Vega in the V band (~5556Å) is `3.55e-09` erg per second per cm2 per Angstrom. This zero point can be overridden via the `band_zeropoint` in the input configuration.

The output `lightcurve.dat` file for this demo.

    0.500000 6.528754e+00 2.673413e-04 2.849708e-06 3.248335e-11
    1.500000 6.522927e+00 2.673109e-04 2.857528e-06 3.266187e-11
    2.500000 6.515175e+00 2.677482e-04 2.865405e-06 3.284217e-11
    ...
    97.500000 5.695878e+00 3.136188e-04 3.861069e-06 5.963141e-11
    98.500000 5.682760e+00 3.151637e-04 3.874932e-06 6.006040e-11
    99.500000 5.676179e+00 3.148080e-04 3.888889e-06 6.049383e-11
    100.500000 5.662197e+00 3.165966e-04 3.902938e-06 6.093170e-11

Tip | The `0` value for the radiances and corresponding `inf` value for the magnitude indicates that the target was unobservable (below the horizon) at that time.

The magnitude vs. time data from the output file is plotted below:

Figure 1. Plot of the magnitude vs. time (converted to UTC) for the demo.

#### Visualizing the Field-of-View

In some situations it is helpful to visualize the samples within the field-of-view (FOV) being used to compute the total radiance (and resulting magnitude) for the FOV of the sensor. The plugin provides two options for this.

An optional debug "grid file" can be generated during the simulation (see the optional `debug_filename` variable in the input configuration). The FOV was sampled with a M x M uniform grid (`100` is the default, but can be changed via the `grid_size` variable). The format of this debug grid file is ASCII/Text, with each line containing the spectrally integrated radiance for each of the M samples in a row of the FOV sampling grid. M lines with M values captures all the samples for a given time. These blocks of M lines of M values repeats for each time. Hence, a simulation with N time samples will result in a file with N x M lines and N x (M x M) samples. Below, these samples for this demo were plotted and animated as a function of time to show how the solar loading on the sphere changes as a function of position as it traverses the sky.

Figure 2. An animation of the sub-IFOV samples on the sphere target as a function of time.

Tip | This animation was created using GNUplot. See the `plot_grid.gp` script used to create this animation.

### Running the "Spinning Box" Simulation

The second primary simulation is configured to emulate a "spectral waterfall" collection, resulting in a 2D "light curve" where one dimension is time and the other is the spectral radiance. To accomplish this, the spectral data cube output is enabled, which will then be post-processed with Python to produce the waterfall image. The target object in this case is a box that is attributed with shiny red, green and blue car paints that is tumbling as a function of time. Hence, as the object passes from horizon to horizon, different colored sides are facing the ground-based sensor.

The spectral data cube output is configured by setting the `image_basename` variable to a non-zero length string (in this case, `"cube"`).

Running the sphere simulation

    $ dirsig5 box_spin.jsim

For this simulation, at each time step, a spatial-spectral data cube is produced in the ENVI image file format. This output is generated when the user sets the optional `image_basename` variable to the base name of the image file sequence to be produced.

Note | If the `image_basename` is set to `test`, then the resulting image files will be `test_000.img`, `test_001.img`, etc. The number of digits in the sequence portion of the filename will be sized to hold the number of samples in the light curve.

The spatial dimensions of the images are `grid_size` x `grid_size`. The spectral dimension is the number of wavelengths in the `bandpass`. These spatial-spectral data cubes can be used by the user to introduce arbitrary spectral weightings, apply spatial MTFs, produce temporal-spectral waterfall visualations, etc.

As a reminder, the LightCurve plugin automatically adjusts the FOV on a frame-to-frame basis to span the extents of the target object. The traditional 1D lightcurve output can be reproduced as the spatial and spectral integral of these spectral data cube files.

Figure 3. An RGB made from the spatial-spectral data cube for step #436.

The video below shows the colored cube tumbling as a function of time (and, hence, position) as it traverses from horizon to horizon in this simulation:

A video of the colored cube tumbling as a function of time (position).
Your browser does not support the video tag.

The `sp_waterfall.py` Python script leverages the SpectralPython (SPy) module to process the spatial-spectral data cubes into a spectral waterfall image by reading each 3D datacube and averaging the two spatial dimensions to produce a 1D spectral curve. These spectral curves are then appended as rows to the waterfall image to produce a spectral vs. time image file.

The `sp_waterfall.py` Python script.

    # we use NumPy to do array processing of the cube data
    import numpy as np

    # we use SPy to read/write ENVI image files
    import spectral.io.envi as envi

    # the number of spectral cubes are to be processed
    imgCount = 840

    # read the first cube to get the spectral dimension
    img0 = envi.open("cube_000.img.hdr")
    bandCount = img0.shape[2]

    # create a new output ENVI image containing the spectral waterfall
    hdrDict = {
      "samples": bandCount,
      "lines": imgCount,
      "bands": 1,
      "data type": 5
    }
    outImage = envi.create_image("waterfall.hdr",hdrDict,force=True)
    outData = outImage.open_memmap(writable=True)
    print(outData.shape)

    # start processing cubes
    for iIdx in range(0,imgCount):

      # load the spectral data cube
      imgFilename = "cube_" + f"{iIdx:03d}" + ".img"
      print(imgFilename)
      img = envi.open(imgFilename+".hdr", imgFilename)
      imgData = img.load()

      # average the spatial dimension to make a spectral curve
      imgAvg = np.average(imgData,axis=(0,1))

      # copy this curve into the waterfall image
      for bIdx in np.arange(0,bandCount):
        outData[iIdx,bIdx] = imgAvg[bIdx]

The resulting `waterfall.img` ENVI image file can then be loaded in the DIRSIG image viewer or any other ENVI compatible image data viewer. The vertical dimension of the image is time and the horizontal dimension is wavelength. The variation in orientation and illumination produces the characteristic patterns in this data product.

Figure 4. The spectral waterfall image.