Unverified Commit 76f0fc67 authored by Jesse Mapel's avatar Jesse Mapel Committed by GitHub
Browse files

New push frame stitch application (#4936)

* Added new framestitch app

* Added changelog entry

* Code updates

* Fixed comment, removed dep tags
parent a284a6d5
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -35,6 +35,8 @@ release.

## [Unreleased]
- Updated the LRO photometry application Lronacpho, to use by default the current 2019 photometric model (LROC_Empirical). The model's coefficients are found in the PVL file that exists in the LRO data/mission/calibration directory. If the old parameter file is provided, the old algorithm(2014) will be used. This functionality is desired for calculation comparisons. Issue: [#4512](https://github.com/USGS-Astrogeology/ISIS3/issues/4512), PR: [#4519](https://github.com/USGS-Astrogeology/ISIS3/pull/4519)
- Added a new application, framestitch, for stitching even and odd push frame images back together prior to processing in other applications. [4924](https://github.com/USGS-Astrogeology/ISIS3/issues/4924)

### Changed

### Added
+222 −0
Original line number Diff line number Diff line
#include "framestitch.h"

#include <QString>

#include "Brick.h"
#include "Buffer.h"
#include "Cube.h"
#include "CubeAttribute.h"
#include "Camera.h"
#include "IException.h"
#include "IString.h"
#include "LineManager.h"
#include "ProcessByBrick.h"
#include "PvlGroup.h"
#include "PvlKeyword.h"
#include "Statistics.h"
#include "UserInterface.h"

using namespace std;
using namespace Isis;

namespace Isis {

  /**
   * Helper function to count the height of frames in a cube.
   *
   * Iterates over the lines in the cube and counts sequential all NULL lines
   * assuming these are frames that have been removed from the cube.
   *
   * If different frame heights are computed within the cube or there are
   * no all null lines in the cube an error is raised.
   */
  int computeFrameHeight(Cube *cube) {
    // Use a Statistics object to track min/max height count
    Statistics frameHeights;
    LineManager cubeLine(*cube);
    int currentFrameHeight = 0;
    for (int line = 1; line <= cube->lineCount(); line++) {
      cubeLine.SetLine(line);
      cube->read(cubeLine);
      Statistics lineStats;
      lineStats.AddData(cubeLine.DoubleBuffer(), cubeLine.size());
      // If the line is all NULL add it to the current frame count
      if (lineStats.TotalPixels() == lineStats.NullPixels()) {
        ++currentFrameHeight;
      }
      // If the line has non-NULL pixels and we have previously counted
      // some all null lines add the count to our histogram and reset
      else if (currentFrameHeight > 0) {
        frameHeights.AddData(currentFrameHeight);
        currentFrameHeight = 0;
      }
    }

    // If the last line is part of a NULL frame handle it now
    if (currentFrameHeight > 0) {
      frameHeights.AddData(currentFrameHeight);
    }

    if (frameHeights.TotalPixels() == 0) {
      QString msg = "Failed to find any NULL frames in cube [" + cube->fileName() + "]."
                    "Please manually enter the frame height.";
      throw IException(IException::User, msg, _FILEINFO_);
    }
    if (frameHeights.Minimum() != frameHeights.Maximum()) {
      QString msg = "Found different frame heights between [" + toString((int)frameHeights.Minimum())
                  + "] and [" + toString((int)frameHeights.Maximum()) + "] lines in cube ["
                  + cube->fileName() + "]. Please manually enter the frame height.";
      throw IException(IException::User, msg, _FILEINFO_);
    }

    // The statistics object contains only a single value at this point
    // so minimum=average=maximum
    return frameHeights.Average();
  }

  /**
   * Combine even and odd cubes from a push frame image into a single cube.
   *
   * @param ui The User Interface to parse the parameters from
   */
  void framestitch(UserInterface &ui) {
    ProcessByBrick process;

    // It is very important that the even cube gets added first as later on
    // we'll use frameNumber % 2 to index the input cube vector
    QString evenCubeFile = ui.GetCubeName("EVEN");
    Cube* evenCube = process.SetInputCube(evenCubeFile,
                                          CubeAttributeInput(evenCubeFile));
    QString oddCubeFile = ui.GetCubeName("ODD");
    Cube* oddCube = process.SetInputCube(oddCubeFile,
                                         CubeAttributeInput(oddCubeFile));


    // Check that all the inputs are valid
    if (evenCube->sampleCount() != oddCube->sampleCount() ||
        evenCube->lineCount() != oddCube->lineCount() ||
        evenCube->bandCount() != oddCube->bandCount()) {
      QString msg = "Even and odd cube dimensions must match.";
      throw IException(IException::User, msg, _FILEINFO_);
    }

    bool inputFlipped = false;

    if (evenCube->hasGroup("Instrument") && oddCube->hasGroup("Instrument")) {
      const PvlGroup &evenInst = evenCube->group("Instrument");
      const PvlGroup &oddInst = oddCube->group("Instrument");
      // Use the start time as an indicator of being the same original image
      if (QString::compare(QString(evenInst["StartTime"]), QString(oddInst["StartTime"])) != 0) {
        QString msg = "Even and odd cube start times must match.";
        throw IException(IException::User, msg, _FILEINFO_);
      }

      if (evenInst.hasKeyword("DataFlipped") && oddInst.hasKeyword("DataFlipped")) {
        if (toBool(evenInst["DataFlipped"]) != toBool(oddInst["DataFlipped"])) {
          QString msg = "Both input cubes must be flipped or not flipped. Cannot combine "
                        "a flipped and unflipped cube.";
          throw IException(IException::User, msg, _FILEINFO_);
        }
        inputFlipped = toBool(evenInst["DataFlipped"]);
      }
    }

    QString outCubeFile = ui.GetCubeName("TO");
    Cube *outCube = process.SetOutputCube(
          outCubeFile, CubeAttributeOutput(outCubeFile),
          evenCube->sampleCount(), evenCube->lineCount(), evenCube->bandCount());

    int frameHeight = 1;
    if (ui.WasEntered("frameHeight")) {
      frameHeight = ui.GetInteger("FRAMEHEIGHT");
    }
    else {
      // User didn't pass the size of the frames so attempt to infer it from
      // the cubes
      int evenFrameHeight = computeFrameHeight(evenCube);
      int oddFrameHeight = computeFrameHeight(oddCube);

      if (evenFrameHeight != oddFrameHeight) {
        QString msg = "Computed frame heights for even cube [" + toString(evenFrameHeight)
                    + "] and odd cube [" + toString(oddFrameHeight) + "] do not match.";
        throw IException(IException::User, msg, _FILEINFO_);
      }

      frameHeight = evenFrameHeight;
    }

    // If there's an even number of frames and the inputs are flipped, we have to
    // swap even and odd because the first frame in the even cube is valid and the
    // first frame in the odd cube is now all NULL.
    //
    //  Before   --Flip-->  After
    // Even  Odd          Even  Odd
    // 0000  ####         ####  0000
    // ####  0000         0000  ####
    // 0000  ####         ####  0000
    // ####  0000         0000  ####
    //
    int numFrames = evenCube->lineCount() / frameHeight;
    bool swapInputCubes = inputFlipped && numFrames % 2 == 0;

    // Processing Setup
    process.SetBrickSize(evenCube->sampleCount(), frameHeight, evenCube->bandCount());
    process.PropagateTables(false);
    process.PropagatePolygons(false);

    /**
     * Frame stitching processing function
     */
    auto interweaveFrames = [&](std::vector<Buffer *> &in, std::vector<Buffer *> &out)->void {
      // Assumes even is first and odd is second
      int inIndex = (in[0]->Line() / frameHeight) % 2;
      if (swapInputCubes) {
        inIndex = 1 - inIndex;
      }
      out[0]->Copy(*in[inIndex]);
    };

    process.StartProcess(interweaveFrames);

    // Update the output label
    outCube->deleteGroup("Kernels");
    if (!outCube->hasGroup("Instrument")) {
      outCube->putGroup(PvlGroup("Instrument"));
    }
    PvlGroup &outInst = outCube->group("Instrument");
    if (!outInst.hasKeyword("Framelets")) {
      outInst.addKeyword(PvlKeyword("Framelets"));
    }
    outInst["Framelets"].setValue("All");

    // ProcessByBrick requires that all buffers move through the cubes the same
    // way so we have to do a second manual pass to flip the output cube if requested.
    if (ui.GetBoolean("FLIP")) {
      Brick topBuff(outCube->sampleCount(), frameHeight, outCube->bandCount(), outCube->pixelType());
      Brick bottomBuff(outCube->sampleCount(), frameHeight, outCube->bandCount(), outCube->pixelType());
      Brick tempBuff(outCube->sampleCount(), frameHeight, outCube->bandCount(), outCube->pixelType());

      for (int frame = 0; frame < numFrames / 2; frame++) {
        topBuff.SetBasePosition(1,frame * frameHeight + 1,1);
        outCube->read(topBuff);
        bottomBuff.SetBasePosition(1,outCube->lineCount() - (frame + 1) * frameHeight + 1,1);
        outCube->read(bottomBuff);

        tempBuff.Copy(topBuff);
        topBuff.Copy(bottomBuff);
        outCube->write(topBuff);
        bottomBuff.Copy(tempBuff);
        outCube->write(bottomBuff);
      }

      if (!outInst.hasKeyword("DataFlipped")) {
        outInst.addKeyword(PvlKeyword("DataFlipped"));
      }
      outInst["DataFlipped"].setValue(toString(!inputFlipped));
    }

    process.EndProcess();

  }

}
+16 −0
Original line number Diff line number Diff line
#ifndef framestitch_h
#define framestitch_h

#include <iostream>
#include <vector>

#include <QString>

#include "Cube.h"
#include "UserInterface.h"

namespace Isis {
  extern void framestitch(UserInterface &ui);
}

#endif
+133 −0
Original line number Diff line number Diff line
<?xml version="1.0"?>
<application name="framestitch" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://isis.astrogeology.usgs.gov/Schemas/Application/application.xsd">
  <brief>
    Stitches even and odd frames from push frame <def link="Cube">cubes</def> back into a single cube.
  </brief>

  <description>
    <p>
      This program combines the even and odd frames from push frame <def link="Cube">cubes</def> back
      into a single interwoven <def link="Cube">cube</def>. It can also optionally flip the frame order
      so that adjacent ground features appear adjacent between frames. This program
      is designed to be run before processing the data in another software package
      as it removes the camera information from the output <def link="Cube">cube</def>.
    </p>
    <p>
      When many push frame images are ingested into ISIS, their frames are separated
      into two different cubes, one with even frames and one with odd frames. In the
      odd frame cube, the lines for the even frames are Null and in the even frame cube,
      the lines for the odd frames are null. This makes processing in other applications
      difficult because each image only has half the data and covers half the ground.
      The footprint for each image is also composed of multiple disjoint pieces and can
      be difficult to work with.
    </p>
  </description>

  <category>
    <categoryItem>Geometry</categoryItem>
  </category>

  <history>
    <change name="Jesse Mapel" date="2022-04-22">
      Original version
    </change>
  </history>

  <seeAlso>
    <applications>
    <item>marciflip</item>
    </applications>
  </seeAlso>

  <groups>
    <group name="Files">
      <parameter name="EVEN">
        <type>cube</type>
        <fileMode>input</fileMode>
        <brief>
          Even frame cube
        </brief>
        <description>
          This specifies the input <def link="Cube">cube</def> containing even frames.
        </description>
        <filter>
          *.cub
        </filter>
      </parameter>

      <parameter name="ODD">
        <type>cube</type>
        <fileMode>input</fileMode>
        <brief>
          Odd frame cube
        </brief>
        <description>
          This specifies the input <def link="Cube">cube</def> containing odd frames.
        </description>
        <filter>
          *.cub
        </filter>
      </parameter>

      <parameter name="TO">
        <type>cube</type>
        <fileMode>output</fileMode>
        <brief>
          Output file that contains all frames
        </brief>
        <description>
          The output filename for the <def link="Cube">cube</def> containing the
          combined frames from the input even and odd <def link="Cube">cubes</def>.
        </description>
        <filter>
          *.cub
        </filter>
      </parameter>
    </group>

    <group name="Settings">
      <parameter name="FLIP">
        <type>boolean</type>
        <brief>
          Flip the frame order
        </brief>
        <description>
          <p>
            If true, the order of the frames in the output cube will be reversed.
            So the firt frame in the output cube will be the last frame in the input
            cubes and the last frame in the output cube will be the first frame in
            the input cubes.
          </p>
          <p>
            This option can be used to correct for adjacent ground points not being
            adjacent between frames. This occurs when the flight direction and the
            increasing line direction on the detector are opposite each other.
          </p>
          <p>
            This option can also be used to undo any flipping in the input cubes.
          </p>
        </description>
        <default><item>FALSE</item></default>
      </parameter>

      <parameter name="FRAMEHEIGHT">
        <type>integer</type>
        <brief>
          The number of lines in each frame
        </brief>
        <description>
          <p>
            The number of lines in each frame.
          </p>
          <p>
            If not entered, then the program will attempt to count the number of
            adjacent NULL and non-NULL lines in each image to determine a frame size.
          </p>
        </description>
        <internalDefault>Camera</internalDefault>
        <minimum>1</minimum>
      </parameter>
    </group>
  </groups>

</application>
+11 −0
Original line number Diff line number Diff line
#include "Isis.h"

#include "UserInterface.h"
#include "framestitch.h"

using namespace Isis;

void IsisMain() {
  UserInterface &ui = Application::GetUserInterface();
  framestitch(ui);
}
Loading