Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
18f343f
Implement ShadowCam ISIS apps and camera
Oct 6, 2025
2582cd1
Add KPLO and ShadowCam translations
Oct 6, 2025
a249601
Move shadowcam macro constants to constexprs in a ShadowCam sub-names…
cordellmichaud Apr 7, 2026
5a894d7
Refactor ShadowCamUtilities
cordellmichaud Apr 7, 2026
005d2c0
Add license comment to ShadowCamConstants.h and ShadowCamUtilities.h
cordellmichaud Apr 7, 2026
6957af4
Simplify ShadowCamCamera for readability
cordellmichaud Apr 7, 2026
ac57fde
Slightly refactor ShadowCamDistortionMap
cordellmichaud Apr 7, 2026
d245ae0
Refactor shadowcam2isis and fix indexing bug
cordellmichaud Apr 7, 2026
da167c9
Fix docstring history formatting
cordellmichaud Apr 7, 2026
574a656
Cleanup shadowcamcal.cpp
cordellmichaud Apr 7, 2026
fc9f25f
Cleanup shadowcamcal.h to match shadowcamcal.cpp
cordellmichaud Apr 7, 2026
3d6b5be
Cleanup shadowcamcal main.cpp
cordellmichaud Apr 7, 2026
332b3b0
Improve WriteCube and move it to shadowcamcal
cordellmichaud Apr 7, 2026
537f38b
Make include guard lowercase
cordellmichaud Apr 7, 2026
aa32154
Add missing scope prefix and header
cordellmichaud Apr 7, 2026
0008ea7
Tidy and move bias pixel subtraction to sub-namespace in shadowcamcal
cordellmichaud Apr 7, 2026
074e269
Update shadowcamcal docstring and add docstrings for WriteCube and Su…
cordellmichaud Apr 7, 2026
ad651ab
Refactor and move dark subtraction to shadowcamcal
cordellmichaud Apr 7, 2026
1f6c70a
Changed INTRCPTCOEFF parameter to INTERCEPTCOEFF and updated document…
cordellmichaud Apr 7, 2026
017f2c4
Move flatfield correction to shadowcamcal and refactor it
cordellmichaud Apr 7, 2026
859dd65
Add history to CorrectFlatfield docstring
cordellmichaud Apr 7, 2026
16bd1b0
Move gain correction to shadowcamcal and refactor it
cordellmichaud Apr 7, 2026
4090fbc
Add additional context to CorrectDark and CorrectFlatfield docstring …
cordellmichaud Apr 7, 2026
e556798
Fix QString error text formatting
cordellmichaud Apr 7, 2026
73a3452
Make variable naming consistent and remove debug messages
cordellmichaud Apr 7, 2026
e40100d
Rename, refactor, and move radiance correction to shadowcamcal
cordellmichaud Apr 7, 2026
8865d71
Replace exits and cerr printouts with thrown ISIS exceptions
cordellmichaud Apr 7, 2026
72d89bb
Rename GetTdiFactor instrument parameter to instrumentGroup for consi…
cordellmichaud Apr 7, 2026
204e0f3
Use GetTdiFactor helper rather than repeating code
cordellmichaud Apr 7, 2026
d13940b
Add missing header
cordellmichaud Apr 7, 2026
d0dfe3f
Fix incorrect timing in ShadowCamCamera
cordellmichaud Apr 7, 2026
755a5e3
Correct usage of ShadowCam constants in shadowcam2isis
cordellmichaud Apr 7, 2026
240b8d8
Cleanup error messages and casting
cordellmichaud Apr 7, 2026
0225b14
Remove unused TDIDirection logic from ShadowCamCamera
cordellmichaud Apr 7, 2026
da3ce2d
Add safe casting during index comparison
cordellmichaud Apr 7, 2026
2816c11
Move CorrectRadiance so it is actually within the ShadowCam namespace
cordellmichaud Apr 7, 2026
1049247
Remove now-nonexistent RadianceCoefficients include
cordellmichaud Apr 7, 2026
87e5c82
Ensure LoadOutputCube's type agrees with that expected by StartProcess
cordellmichaud Apr 7, 2026
e546bdf
Make variable naming consistent
cordellmichaud Apr 7, 2026
f124ef1
Remove unused log parameter from shadowcam2isis
cordellmichaud Apr 7, 2026
a49bf49
Add TDI offset and constant time offset back into ShadowCamCamera timing
cordellmichaud Apr 7, 2026
37c0b80
Minor change to ShadowCamCamera instrument check
cordellmichaud Apr 7, 2026
87790eb
Update shadowcam2isis xml documentation and add an error if users try…
acpaquette Apr 14, 2026
347e526
Added error in shadowcamcal if users try to run the program on calibr…
acpaquette Apr 14, 2026
422e589
Add calibration check function
acpaquette Apr 14, 2026
baa87bf
Addressed comments
acpaquette Apr 14, 2026
af14f86
Merge pull request #1 from acpaquette/dev-shadowcam-isis
cordellmichaud Apr 15, 2026
1a308c6
Add tests for shadowcam2isis
acpaquette Apr 21, 2026
a122909
Add tests for shadowcamcal
acpaquette Apr 21, 2026
3be2757
Add tests for shadowcam camera
acpaquette Apr 22, 2026
144a76d
Added missed shadowcam camera fixture
acpaquette Apr 24, 2026
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
33 changes: 33 additions & 0 deletions isis/appdata/serialnumbers/KploShadowCamCameraSerialNumber.trn
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
ObservationKeys =3

Group = Keyword1
Auto
InputKey = SpacecraftName
InputGroup = "IsisCube,Instrument"
InputPosition = (IsisCube, Instrument)
OutputName = Keyword1
OutputPosition = (Group, SerialNumberKeywords)
Translation = (KPLO, "KOREA PATHFINDER LUNAR ORBITER")
Translation = (*, *)
End_Group

Group = Keyword2
Auto
InputKey = ExecutionSpacecraftTime
InputGroup = "IsisCube,Instrument"
InputPosition = (IsisCube, Instrument)
OutputName = Keyword2
OutputPosition = (Group, SerialNumberKeywords)
Translation = (*, *)
End_Group

Group = Keyword3
Auto
InputKey = InstrumentId
InputGroup = "IsisCube,Instrument"
InputPosition = (IsisCube, Instrument)
OutputName = Keyword3
OutputPosition = (Group, SerialNumberKeywords)
Translation = (*, *)
End_Group
End
6 changes: 6 additions & 0 deletions isis/appdata/translations/Instruments.trn
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ Group = InstrumentName
Translation = (Tc, TC1)
Translation = (Tc, TC2)

# KPLO
#Translation = (ShadowCamCamera, KPLO_SHADOWCAM)
#Translation = (ShadowCam, SHADOWCAM)
Translation = (ShadowCamCamera, KPLO_SHC)
Translation = (ShadowCamCamera, ShadowCam)

# LO
Translation = (Hrc, "High Resolution Camera")
Translation = (Mrc, "Medium Resolution Camera")
Expand Down
3 changes: 3 additions & 0 deletions isis/appdata/translations/MissionName2DataDir.trn
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ Group = MissionName
Translation = (Lo, "Lunar Orbiter 3")
Translation = (Lo, "Lunar Orbiter 4")
Translation = (Lo, "Lunar Orbiter 5")
Translation = (Kplo, "KOREA PATHFINDER LUNAR ORBITER")
Translation = (Kplo, "Korea Pathfinder Lunar Orbiter")
Translation = (Kplo, KOREA_PATHFINDER_LUNAR_ORBITER)
Translation = (Lro, "LUNAR RECONNAISSANCE ORBITER")
Translation = (Lro, "Lunar Reconnaissance Orbiter")
Translation = (Mariner10, Mariner_10)
Expand Down
2 changes: 2 additions & 0 deletions isis/src/kplo/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include $(ISISROOT)/make/isismake.cat

2 changes: 2 additions & 0 deletions isis/src/kplo/apps/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include $(ISISROOT)/make/isismake.appstree

8 changes: 8 additions & 0 deletions isis/src/kplo/apps/shadowcam2isis/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ifeq ($(ISISROOT), $(BLANK))
.SILENT:
error:
echo "Please set ISISROOT";
else
include $(ISISROOT)/make/isismake.apps
endif

20 changes: 20 additions & 0 deletions isis/src/kplo/apps/shadowcam2isis/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/** This is free and unencumbered software released into the public domain.

The authors of ISIS do not claim copyright on the contents of this file.
For more details about the LICENSE terms and the AUTHORS, you will
find files of those names at the top level of this repository. **/

/* SPDX-License-Identifier: CC0-1.0 */
#include "Isis.h"

#include "Application.h"
#include "UserInterface.h"

#include "shadowcam2isis.h"

using namespace Isis;

void IsisMain (){
UserInterface &ui = Application::GetUserInterface();
shadowcam2isis(ui);
}
206 changes: 206 additions & 0 deletions isis/src/kplo/apps/shadowcam2isis/shadowcam2isis.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/** This is free and unencumbered software released into the public domain.

The authors of ISIS do not claim copyright on the contents of this file.
For more details about the LICENSE terms and the AUTHORS, you will
find files of those names at the top level of this repository. **/

/* SPDX-License-Identifier: CC0-1.0 */
#include <array>
#include <cstdint>
#include <memory>

#include <QString>
#include <QStringBuilder>

#include "Buffer.h"
#include "CubeAttribute.h"
#include "FileName.h"
#include "IException.h"
#include "IString.h"
#include "ProcessByLine.h"
#include "Pvl.h"
#include "PvlGroup.h"
#include "UserInterface.h"

#include "ShadowCamConstants.h"
#include "ShadowCamUtilities.h"

#include "shadowcam2isis.h"

using std::uint8_t, std::uint16_t;

namespace Isis {

void shadowcam2isis(UserInterface &ui) {
try{
const bool keepSpecial = ui.GetBoolean("KEEPSPECIALPIXELS");
const FileName from = FileName(ui.GetCubeName("FROM"));

if (ShadowCam::IsCalibrated(from)) {
const QString msg = "File [" + from.name() + "] is calibrated, no need to run shadowcam2isis";
throw IException(IException::User, msg, _FILEINFO_);
}

// Use a smart pointer for automatic memory management
const auto inLabel = std::make_unique<Pvl>(from.expanded());

// Error check for instrument label
const PvlGroup &instrument = inLabel->findObject("IsisCube").findGroup("Instrument");

if (!instrument.hasKeyword("InstrumentId") && !instrument.hasKeyword("InstrumentID")) {
const QString msg = "Keyword InstrumentID or InstrumentId was not found in labels.";
throw IException(IException::User, msg, _FILEINFO_);
}

if (QString::compare(instrument["InstrumentId"], "ShadowCam", Qt::CaseInsensitive) != 0) {
const QString msg = "Error: InstrumentId not equal to ShadowCam.";
throw IException(IException::User, msg, _FILEINFO_);
}

// Get xTerm, bTerm, lines from raw edr label
std::array<int, 6> bTerm;
bTerm.fill(9999);
std::array<int, 6> xTerm;
xTerm.fill(9999);
xTerm[5] = 4096;

// Load term arrays
for (int i = 0; i < 6; i++) {
if (i != 0) {
QString bTermKey = "Bterm" % toString(i);
bTerm[i] = (ShadowCam::GetFromLabels(instrument, bTermKey)).toInt();
}

if (i != 5) {
QString xTermKey = "Xterm" % toString(i);
xTerm[i] = (ShadowCam::GetFromLabels(instrument, xTermKey)).toInt();
}
}

/**
* @brief Compands DN values to 8-bit values.
*
* This function is used to create a companding table for the decompanding routine.
*
* @param dn Incoming DN pixel value.
* @param bTerm ShadowCam bterms.
* @param xTerm ShadowCam xterms.
*
* @return Companded 8-bit value.
**/
const auto compand = [](uint16_t dn, std::array<int, 6> bTerm, std::array<int, 6> xTerm) -> uint8_t {
for (int xTermIndex = 0; static_cast<size_t>(xTermIndex) < xTerm.size(); xTermIndex++) {
if (dn < xTerm[xTermIndex]) {
return ((dn >> xTermIndex) + bTerm[xTermIndex]) & 0xff;
}
}
const QString msg = QString("Failed to compand value: %1").arg(toString(dn));
throw IException(IException::User, msg, _FILEINFO_);
};

/**
* @brief Decompands 8-bit DN values.
*
* Decompands 8-bit DN values and handles special pixels.
*
* @param in Input buffer.
* @param out Output buffer.
**/
const auto decompand = [&compand, &bTerm, &xTerm, keepSpecial](Buffer &in, Buffer &out) -> void {
std::array<uint8_t, 4096> companding_table;

// Create companding table
for (uint16_t tableIndex = 0; tableIndex < companding_table.size(); tableIndex++) {
companding_table[tableIndex] = compand(tableIndex, bTerm, xTerm);
}

std::array<uint16_t, 256> decompanding_table;

// Create decompanding table
for (uint16_t decompandIndex = 0; decompandIndex < decompanding_table.size(); decompandIndex++) {
uint16_t min = 9999, max = 0;
for (uint16_t compandIndex = 0; compandIndex < 4096; compandIndex++) {
if (companding_table[compandIndex] == decompandIndex) {
if (min == 9999) {
min = compandIndex;
}
max = compandIndex;
}

if (min != 9999 && companding_table[compandIndex] != decompandIndex) {
break;
}
}

decompanding_table[decompandIndex] = (min + max) / 2;
}

constexpr int bufferSize = ShadowCam::SHC_CHANNELS * ShadowCam::SHC_AFE_WIDTH;

// handle 8 bit special pixels first

// Process buffer
for (int bufferIndex = 0; bufferIndex < bufferSize; bufferIndex++) {
uint16_t tmpVal = static_cast<float> (in[bufferIndex]);
float tmpFloatVal;
if (keepSpecial) {
tmpFloatVal = ShadowCam::Set8bitMaxMintoSpecialPixelsHIS4LIS4(tmpVal);
}
else {
tmpFloatVal = static_cast<float>(tmpVal);
}

if (!ShadowCam::IsSpecialPixelSHC(tmpFloatVal)){
// only decompanding non-special pixels
out[bufferIndex] = static_cast<uint16_t>(decompanding_table[(uint16_t) tmpFloatVal]);
if (out[bufferIndex] < 0) {
QString msg = QString("Value is less than zero for line: %1, pixel: %2.").arg(
QString::number(in.Line()), QString::number(bufferIndex));
throw IException(IException::User, msg, _FILEINFO_);
}
}

/*
if (ShadowCam::IsSpecialPixelSHC(in[bufferIndex])) {
if (keepSpecial)
out[bufferIndex] = ShadowCam::Set8bitMaxMintoSpecialPixelsHIS4LIS4(in[bufferIndex]);
else {
// Special pixel handling
cout << "WARNING: Special pixels set to 0 or 255." << endl;
out[bufferIndex] = static_cast<float>(ShadowCam::Set_LIS_HIS_SpecialPixelsTo_0_255(in[bufferIndex]));
}
}
else {
// Decompanding non-special pixels
out[bufferIndex] = static_cast<float>(decompanding_table[(uint16_t) in[bufferIndex]]);
#if (out[bufferIndex] < 0){
# cout << "Value is less than zero for line: " << std::to_string(in.Line()) << ", pixel: " << std::to_string(bufferIndex) << "at(i): " << std::to_string(in.at(bufferIndex)) << endl;
}*/
Comment on lines +163 to +178
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What is this for?

}
};

// Process raw EDR cube
CubeAttributeOutput outAttr = CubeAttributeOutput("+Real");
Isis::CubeAttributeInput &att = ui.GetInputAttribute("FROM");
ProcessByLine p;
p.SetInputCube(ui.GetCubeName("FROM"), att);
p.SetOutputCube(ui.GetCubeName("TO"), outAttr);
p.Progress()->SetText("Importing 8-bit EDR cube and decompanding...");
p.StartProcess(decompand);
p.Progress()->SetText("Finalizing import...");
p.Finalize();
p.ClearCubes();
}
catch (const IException &e) {
QString msg = QString("ISIS Exception. Unable to import ShadowCam image to ISIS");
throw IException(e, IException::Unknown, msg, _FILEINFO_);
}
catch (const std::exception &e) {
QString msg = QString("Standard exception: %1. Unable to import ShadowCam image to ISIS").arg(QString(e.what()));
throw IException(IException::Programmer, msg, _FILEINFO_);
}
catch (...) {
throw IException(IException::Programmer, "Unknown exception occurred. Unable to import ShadowCam image to ISIS", _FILEINFO_);
}
}
}
16 changes: 16 additions & 0 deletions isis/src/kplo/apps/shadowcam2isis/shadowcam2isis.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#ifndef shadowcam2isis_h
#define shadowcam2isis_h
/** This is free and unencumbered software released into the public domain.

The authors of ISIS do not claim copyright on the contents of this file.
For more details about the LICENSE terms and the AUTHORS, you will
find files of those names at the top level of this repository. **/

/* SPDX-License-Identifier: CC0-1.0 */
#include "UserInterface.h"

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

#endif
70 changes: 70 additions & 0 deletions isis/src/kplo/apps/shadowcam2isis/shadowcam2isis.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>

<application name="shadowcam2isis" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://isis.astrogeology.usgs.gov/Schemas/Application/application.xsd">

<brief>
Import an ShadowCam image as an Isis cube
</brief>

<description>
This program takes a raw EDR image from the KPLO ShadowCam (Camera) and produces an Isis cube containing the image data.
ShadowCam images in the CDR or RDR format can be used directly, with no processing required.
</description>

<history>
<change name="Victor Silva" date="2022-06-25">
Original Version
</change>
</history>

<category>
<missionItem>Korean Pathfinder Lunar Orbiter</missionItem>
</category>

<groups>
<group name="Files">
<parameter name="FROM">
<type>cube</type>
<fileMode>input</fileMode>
<brief>
Input cube
</brief>
<description>
The image to be processed
</description>
<filter>

</filter>
</parameter>

<parameter name="TO">
<type>cube</type>
<pixelType>real</pixelType>
<fileMode>output</fileMode>
<brief>
Output cube
</brief>
<description>
This is the resultant cube, containing the image and label data.
</description>
<filter>
*.cub
</filter>
</parameter>
</group>

<group name="PixelOptions">
<parameter name="KEEPSPECIALPIXELS">
<type>boolean</type>
<default><item>True</item></default>
<brief>
Don't replace 0/255 with Special Pixels LIS/HIS
</brief>
<description>
Don't replace 0/255 with LIS/HIS
(default is to replace special pixels)
</description>
</parameter>
</group>
</groups>
</application>
7 changes: 7 additions & 0 deletions isis/src/kplo/apps/shadowcamcal/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ifeq ($(ISISROOT), $(BLANK))
.SILENT:
error:
echo "Please set ISISROOT";
else
include $(ISISROOT)/make/isismake.apps
endif
Loading
Loading