From ST: + * + *
+ * + *+ */ +public class NestedRangeImageLocalSet implements IUasDatalinkValue, INestedKlvValue { + private final RangeImageLocalSet rangeImageLocalSet; + + /** + * Create from value. + * + * @param rangeImage the Range Image data as a local set + */ + public NestedRangeImageLocalSet(RangeImageLocalSet rangeImage) { + this.rangeImageLocalSet = rangeImage; + } + + /** + * Create from encoded bytes. + * + * @param bytes The byte array + * @throws KlvParseException if the input is invalid + */ + public NestedRangeImageLocalSet(byte[] bytes) throws KlvParseException { + this.rangeImageLocalSet = RangeImageLocalSet.fromNestedBytes(bytes, 0, bytes.length); + } + + @Override + public byte[] getBytes() { + return this.rangeImageLocalSet.frameMessage(true); + } + + @Override + public String getDisplayableValue() { + return "[Range Image]"; + } + + @Override + public String getDisplayName() { + return "Range Image"; + } + + /** + * Get the Range Image data. + * + * @return the Range Image data as a local set + */ + public RangeImageLocalSet getRangeImage() { + return this.rangeImageLocalSet; + } + + @Override + public IRangeImageMetadataValue getField(IKlvKey tag) { + return this.rangeImageLocalSet.getField(tag); + } + + @Override + public Set extends IKlvKey> getIdentifiers() { + return this.rangeImageLocalSet.getIdentifiers(); + } +} diff --git a/st0601/src/main/java/org/jmisb/st0601/UasDatalinkFactory.java b/st0601/src/main/java/org/jmisb/st0601/UasDatalinkFactory.java index 0989ab227..932709d80 100644 --- a/st0601/src/main/java/org/jmisb/st0601/UasDatalinkFactory.java +++ b/st0601/src/main/java/org/jmisb/st0601/UasDatalinkFactory.java @@ -214,8 +214,7 @@ public static IUasDatalinkValue createValue(UasDatalinkTag tag, byte[] bytes) case TargetWidthExtended: return new TargetWidthExtended(bytes); case RangeImage: - // TODO Implement ST 1002 - return new OpaqueValue(bytes); + return new NestedRangeImageLocalSet(bytes); case Georegistration: return new NestedGeoRegistrationLocalSet(bytes); case CompositeImaging: diff --git a/st0601/src/main/java/org/jmisb/st0601/UasDatalinkTag.java b/st0601/src/main/java/org/jmisb/st0601/UasDatalinkTag.java index 4b98152c7..9779cf455 100644 --- a/st0601/src/main/java/org/jmisb/st0601/UasDatalinkTag.java +++ b/st0601/src/main/java/org/jmisb/st0601/UasDatalinkTag.java @@ -318,7 +318,8 @@ public enum UasDatalinkTag implements IKlvKey { /** Tag 96; Target width within sensor field of view; Value is a {@link TargetWidthExtended}. */ TargetWidthExtended(96), /** - * Tag 97; MISB ST 1002 Range Imaging Local Set metadata items; Value is a {@link OpaqueValue}. + * Tag 97; MISB ST 1002 Range Imaging Local Set metadata items; Value is a {@link + * NestedRangeImageLocalSet}. */ RangeImage(97), /** diff --git a/st0601/src/test/java/org/jmisb/st0601/NestedRangeImageLocalSetTest.java b/st0601/src/test/java/org/jmisb/st0601/NestedRangeImageLocalSetTest.java new file mode 100644 index 000000000..6667cb8b4 --- /dev/null +++ b/st0601/src/test/java/org/jmisb/st0601/NestedRangeImageLocalSetTest.java @@ -0,0 +1,121 @@ +package org.jmisb.st0601; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import org.jmisb.api.common.KlvParseException; +import org.jmisb.st1002.IRangeImageMetadataValue; +import org.jmisb.st1002.RangeImageLocalSet; +import org.jmisb.st1002.RangeImageMetadataKey; +import org.jmisb.st1002.ST1002PrecisionTimeStamp; +import org.jmisb.st1002.ST1002VersionNumber; +import org.jmisb.st1002.SinglePointRangeMeasurement; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class NestedRangeImageLocalSetTest { + + private final byte[] localSetAsByteArray = + new byte[] { + 0x01, + 0x08, + 0x00, + 0x05, + (byte) 0xf2, + (byte) 0xf6, + 0x17, + 0x66, + 0x25, + 0x00, + 0x0b, + 0x01, + 0x02, + 0x0d, + 0x08, + 0x40, + (byte) 0xa5, + (byte) 0xe8, + 0x33, + 0x33, + 0x33, + 0x33, + 0x33, + 0x15, + 0x02, + (byte) 0x72, + (byte) 0x41 + }; + + @Test + public void testConstructFromLocalSet() { + NestedRangeImageLocalSet uut = new NestedRangeImageLocalSet(makeLocalSet()); + Assert.assertNotNull(uut); + checkLocalSetValues(uut); + assertEquals(uut.getBytes(), localSetAsByteArray); + } + + @Test + public void testConstructFromBytes() throws KlvParseException { + NestedRangeImageLocalSet localSetFromBytes = + new NestedRangeImageLocalSet(localSetAsByteArray); + Assert.assertNotNull(localSetFromBytes); + checkLocalSetValues(localSetFromBytes); + } + + @Test + public void testFactory() throws KlvParseException { + IUasDatalinkValue value = + UasDatalinkFactory.createValue(UasDatalinkTag.RangeImage, localSetAsByteArray); + Assert.assertTrue(value instanceof NestedRangeImageLocalSet); + NestedRangeImageLocalSet localSet = (NestedRangeImageLocalSet) value; + Assert.assertNotNull(localSet); + checkLocalSetValues(localSet); + } + + private void checkLocalSetValues(NestedRangeImageLocalSet nestedLocalSet) { + Assert.assertEquals(nestedLocalSet.getDisplayName(), "Range Image"); + Assert.assertEquals(nestedLocalSet.getDisplayableValue(), "[Range Image]"); + assertTrue(nestedLocalSet.getRangeImage() instanceof RangeImageLocalSet); + assertEquals(nestedLocalSet.getRangeImage().getIdentifiers().size(), 3); + assertEquals(nestedLocalSet.getIdentifiers().size(), 3); + assertTrue( + nestedLocalSet + .getIdentifiers() + .containsAll( + Set.The Range Image Local Set item allows users to include the Range Image LS (MISB ST 1002) + * within MISB ST 0601. Range Motion Imagery is a temporal sequence of range images. Each range + * image is a collection of range measurements from a sensor to target scene. A range measurement is + * the distance (e.g., meters) from an object (or area) in the scene to the sensor. The KLV + * structures of this standard are intended to allow for flexibility, efficient packing, and future + * extensions. Range Motion Imagery can be used standalone, or in collaboration with other Motion + * Imagery. + * + *
See MISB ST 1002 for generation and usage requirements. + * + *
This module provides an implementation of the MISB ST 1002 standard, which describes Range + * Motion Imagery, its format and supporting metadata. + * + *
Range Motion Imagery is a temporal sequence of Range Images. Each Range Image is a collection + * of Range Measurements from a sensor to target scene. A Range Measurement is the distance (e.g., + * meters) from an object (or area) in the scene to the sensor. The KLV structures of this Standard + * are intended to allow for flexibility, efficient packing, and future extensions. Range Motion + * Imagery can be used standalone or in collaboration with other Motion Imagery. MISB ST 1107 Metric + * Geopositioning Metadata Set provides the basis for collaborating with other Motion Imagery types. + * + *
This standard describes the: + * + *
The Generalized Transformation is a mathematical transformation used to project information, + * points, or lines from one image plane into a second image plane. The use of the Generalized + * Transformation Local Set, MISB ST 1202, is for specific cases when aligning a Range Image to a + * Collaborative Sensors Image. In this case, the Range Image is said to be a child of the + * Collaborative Image (i.e., the parent image), so the Child-Parent Transformation (CPT) + * enumeration 2 defined in Table 1 of MISB ST 1202, is used. + * + *
The boresighted imaging case is a hardware solution, where multiple focal plane arrays are + * simultaneously imaging through a single aperture. The hardware limits the variation in the + * perspective centres and the principal axis of the system to be coincident. Therefore, the + * three-dimensional scene is imaged on two, dependent, focal plane arrays, where the dependency is + * in the geometry. These focal planes do not necessarily have the same orientation or identical + * pixel sizes. Also, the magnification of the two optical paths can be different causing different + * image scales. These effects can be sufficiently modelled using the Generalized Transformation. + * + *
+ * + *
When the Range Imagery does not meet the requirements for being co-boresighted, the Range + * Imagery is called non-boresighted, and a transformation is needed to align the Range Imagery with + * the Collaborative Imagery. + * + *
+ */ +public class GeneralizedTransformation implements IRangeImageMetadataValue, INestedKlvValue { + + private final GeneralizedTransformationLocalSet localSet; + + /** + * Create from encoded bytes. + * + * @param localset value part of the nested local set, without UniversalLabel or overall length + */ + public GeneralizedTransformation(final GeneralizedTransformationLocalSet localset) { + this.localSet = localset; + } + + /** + * Create from encoded bytes. + * + * @param bytes Encoded byte array for the nested local set. + * @throws KlvParseException if parsing fails + */ + public GeneralizedTransformation(byte[] bytes) throws KlvParseException { + this.localSet = new GeneralizedTransformationLocalSet(bytes); + } + + @Override + public byte[] getBytes() { + return localSet.frameMessage(true); + } + + @Override + public String getDisplayName() { + return "Generalized Transformation Local Set"; + } + + @Override + public String getDisplayableValue() { + return localSet.displayHeader(); + } + + @Override + public IKlvValue getField(IKlvKey tag) { + return localSet.getField(tag); + } + + @Override + public Set extends IKlvKey> getIdentifiers() { + return localSet.getIdentifiers(); + } +} diff --git a/st1002/src/main/java/org/jmisb/st1002/IRangeImageMetadataValue.java b/st1002/src/main/java/org/jmisb/st1002/IRangeImageMetadataValue.java new file mode 100644 index 000000000..aaff4563f --- /dev/null +++ b/st1002/src/main/java/org/jmisb/st1002/IRangeImageMetadataValue.java @@ -0,0 +1,17 @@ +package org.jmisb.st1002; + +import org.jmisb.api.klv.IKlvValue; + +/** + * ST 1002 metadata value. + * + *
All ST 1002 Range Image Local Set values implement this interface. + */ +public interface IRangeImageMetadataValue extends IKlvValue { + /** + * Get the encoded bytes. + * + * @return The encoded byte array + */ + byte[] getBytes(); +} diff --git a/st1002/src/main/java/org/jmisb/st1002/NumberOfSectionsInX.java b/st1002/src/main/java/org/jmisb/st1002/NumberOfSectionsInX.java new file mode 100644 index 000000000..555855a02 --- /dev/null +++ b/st1002/src/main/java/org/jmisb/st1002/NumberOfSectionsInX.java @@ -0,0 +1,84 @@ +package org.jmisb.st1002; + +import org.jmisb.api.klv.Ber; +import org.jmisb.api.klv.BerDecoder; +import org.jmisb.api.klv.BerEncoder; +import org.jmisb.api.klv.BerField; + +/** + * Number of Sections in X (ST 1002 Range Image Local Set Tag 17). + * + *
Range Imagery Data is a rectangular array of Range Measurements. Range Imagery Data can be + * formatted in whole or in separate parts, where each part is called a Section. Sections are + * rectangular areas that when combined form the full image. Each Section can be compressed to + * provide the most efficient transmission and storage. ST 1002.2 requires Sections to be in simple + * layout. A simple Section layout divides the image into either horizontal or vertical strips. All + * horizontal strips have the same width as the full image but can vary in height as needed as + * illustrated below. + * + *
+ * + *
All vertical strips have the same height as the full image but can vary in width as + * illustrated below. + * + *
+ * + *
When the Range Image is formatted into separate Sections, the Range Image Local Set will + * contain multiple Section Data Variable Length Packs. Specifically, the number of Section Data + * Variable Length Packs will be the Number of Sections in X multiplied by the Number of Sections in + * Y. + * + * @see NumberOfSectionsInY + */ +public class NumberOfSectionsInX implements IRangeImageMetadataValue { + + private final int value; + + /** + * Create from value. + * + * @param numberOfSections the number of Sections, at least 1 + */ + public NumberOfSectionsInX(int numberOfSections) { + if (numberOfSections < 1) { + throw new IllegalArgumentException("ST 1002 Number of Sections in X must be positive"); + } + this.value = numberOfSections; + } + + /** + * Create from encoded bytes. + * + * @param bytes Byte array containing BER-OID formatted value number + */ + public NumberOfSectionsInX(byte[] bytes) { + BerField berField = BerDecoder.decode(bytes, 0, true); + this.value = berField.getValue(); + } + + /** + * Get the number of sections. + * + * @return the number of sections + */ + public int getNumberOfSections() { + return value; + } + + @Override + public byte[] getBytes() { + return BerEncoder.encode(value, Ber.OID); + } + + @Override + public String getDisplayName() { + return "Number of Sections in X"; + } + + @Override + public String getDisplayableValue() { + return String.format("%d", value); + } +} diff --git a/st1002/src/main/java/org/jmisb/st1002/NumberOfSectionsInY.java b/st1002/src/main/java/org/jmisb/st1002/NumberOfSectionsInY.java new file mode 100644 index 000000000..4fd168030 --- /dev/null +++ b/st1002/src/main/java/org/jmisb/st1002/NumberOfSectionsInY.java @@ -0,0 +1,84 @@ +package org.jmisb.st1002; + +import org.jmisb.api.klv.Ber; +import org.jmisb.api.klv.BerDecoder; +import org.jmisb.api.klv.BerEncoder; +import org.jmisb.api.klv.BerField; + +/** + * Number of Sections in Y (ST 1002 Range Image Local Set Tag 18). + * + *
Range Imagery Data is a rectangular array of Range Measurements. Range Imagery Data can be + * formatted in whole or in separate parts, where each part is called a Section. Sections are + * rectangular areas that when combined form the full image. Each Section can be compressed to + * provide the most efficient transmission and storage. ST 1002.2 requires Sections to be in simple + * layout. A simple Section layout divides the image into either horizontal or vertical strips. All + * horizontal strips have the same width as the full image but can vary in height as needed as + * illustrated below. + * + *
+ * + *
All vertical strips have the same height as the full image but can vary in width as + * illustrated below. + * + *
+ * + *
When the Range Image is formatted into separate Sections, the Range Image Local Set will + * contain multiple Section Data Variable Length Packs. Specifically, the number of Section Data + * Variable Length Packs will be the Number of Sections in X multiplied by the Number of Sections in + * Y. + * + * @see NumberOfSectionsInX + */ +public class NumberOfSectionsInY implements IRangeImageMetadataValue { + + private final int value; + + /** + * Create from value. + * + * @param numberOfSections the number of Sections, at least 1 + */ + public NumberOfSectionsInY(int numberOfSections) { + if (numberOfSections < 1) { + throw new IllegalArgumentException("ST 1002 Number of Sections in Y must be positive"); + } + this.value = numberOfSections; + } + + /** + * Create from encoded bytes. + * + * @param bytes Byte array containing BER-OID formatted value number + */ + public NumberOfSectionsInY(byte[] bytes) { + BerField berField = BerDecoder.decode(bytes, 0, true); + this.value = berField.getValue(); + } + + /** + * Get the number of sections. + * + * @return the number of sections + */ + public int getNumberOfSections() { + return value; + } + + @Override + public byte[] getBytes() { + return BerEncoder.encode(value, Ber.OID); + } + + @Override + public String getDisplayName() { + return "Number of Sections in Y"; + } + + @Override + public String getDisplayableValue() { + return String.format("%d", value); + } +} diff --git a/st1002/src/main/java/org/jmisb/st1002/RangeImageCompressionMethod.java b/st1002/src/main/java/org/jmisb/st1002/RangeImageCompressionMethod.java new file mode 100644 index 000000000..a6a532a18 --- /dev/null +++ b/st1002/src/main/java/org/jmisb/st1002/RangeImageCompressionMethod.java @@ -0,0 +1,64 @@ +package org.jmisb.st1002; + +/** + * Range Image Compression Method. + * + *
This enumeration describes the available compression used for reducing the number of bytes of + * the range image. There are currently only two valid values - no compression, and planar fit. See + * ST 1002.2 Section 7 for a description of planar fit. + * + * @see RangeImageEnumerations + */ +public enum RangeImageCompressionMethod { + /** + * Unknown value. + * + *
This is not a valid compression method, and indicates a problem with decoding. + */ + UNKNOWN(-1, "Unknown"), + /** No compression. */ + NO_COMPRESSION(0, "No Compression"), + /** Planar fit. */ + PLANAR_FIT(1, "Planar Fit"); + + /** + * Look up Range Image Compression Method by encoded value. + * + * @param value the encoded (integer) value + * @return the corresponding enumeration value + */ + public static RangeImageCompressionMethod lookup(int value) { + for (RangeImageCompressionMethod method : values()) { + if (method.getEncodedValue() == value) { + return method; + } + } + return UNKNOWN; + } + + private final int encodedValue; + private final String textDescription; + + private RangeImageCompressionMethod(int value, String description) { + this.encodedValue = value; + this.textDescription = description; + } + + /** + * Encoded value. + * + * @return integer representation of the enumeration + */ + public int getEncodedValue() { + return encodedValue; + } + + /** + * Text description. + * + * @return human readable description of the enumeration meaning. + */ + public String getTextDescription() { + return textDescription; + } +} diff --git a/st1002/src/main/java/org/jmisb/st1002/RangeImageEnumerations.java b/st1002/src/main/java/org/jmisb/st1002/RangeImageEnumerations.java new file mode 100644 index 000000000..d2a4466e8 --- /dev/null +++ b/st1002/src/main/java/org/jmisb/st1002/RangeImageEnumerations.java @@ -0,0 +1,137 @@ +package org.jmisb.st1002; + +import org.jmisb.api.klv.Ber; +import org.jmisb.api.klv.BerDecoder; +import org.jmisb.api.klv.BerEncoder; +import org.jmisb.api.klv.BerField; + +/** + * Range Image Enumerations (ST 1002 Range Image Local Set Tag 12). + * + *
Range Image Enumerations is a set of enumerated values encoded as a BER-OID integer value. + * Range Image Enumerations contains three separate enumerated values: + * + *
Range Image Source declares how the Range Imagery was created, either from a Range Sensor or + * Computationally Extracted, as described in Section 5. This enumeration has two values, so it + * consumes one bit of the Range Image Enumerations value. + * + *
Range Imagery Data Type declares the type of Range Imagery, either Perspective Range Image or + * Depth Range Image. To allow for further types to be defined in the future this enumeration has + * eight values (0 through 7), where currently only two values are defined: 0=Perspective Range + * Image and 1=Depth Range Image. + * + *
Range Imagery Compression Method declares the method of compression used for reducing the + * number of bytes of the Range Image. One method of compression is called Planar Fit, described in + * ST 1002.2 Section 7. To allow for further compression techniques to be defined in the future, + * this enumeration has eight values (0 through 7), where currently only two values are defined: + * 0=No Compression and 1=Planar Fit. + */ +public class RangeImageEnumerations implements IRangeImageMetadataValue { + + private final RangeImageCompressionMethod compressionMethod; + private final RangeImageryDataType dataType; + private final RangeImageSource rangeImageSource; + private static final int COMPRESSION_METHOD_MASK = 0b00000111; + private static final int DATA_TYPE_BIT_SHIFT = 3; + private static final int DATA_TYPE_MASK = 0b00111000; + private static final int SOURCE_BIT_SHIFT = 6; + private static final int SOURCE_MASK = 0b01000000; + + /** + * Construct from values. + * + * @param compressionMethod the compression method enumeration value + * @param dataType the range imagery data type enumeration value + * @param rangeImageSource the range image source enumeration value + */ + public RangeImageEnumerations( + RangeImageCompressionMethod compressionMethod, + RangeImageryDataType dataType, + RangeImageSource rangeImageSource) { + this.compressionMethod = compressionMethod; + this.dataType = dataType; + this.rangeImageSource = rangeImageSource; + } + + /** + * Construct from encoded bytes. + * + * @param bytes byte array (currently only length 1) + */ + public RangeImageEnumerations(byte[] bytes) { + BerField ber = BerDecoder.decode(bytes, 0, true); + int value = ber.getValue(); + this.compressionMethod = + RangeImageCompressionMethod.lookup(value & COMPRESSION_METHOD_MASK); + this.dataType = + RangeImageryDataType.lookup((value & DATA_TYPE_MASK) >> DATA_TYPE_BIT_SHIFT); + this.rangeImageSource = RangeImageSource.lookup((value & SOURCE_MASK) >> SOURCE_BIT_SHIFT); + } + + @Override + public byte[] getBytes() { + if (compressionMethod == RangeImageCompressionMethod.UNKNOWN) { + throw new IllegalArgumentException("Range Image Compression Method cannot be UNKNOWN"); + } + if (dataType == RangeImageryDataType.UNKNOWN) { + throw new IllegalArgumentException("Range Imagery Data Type cannot be UNKNOWN"); + } + if (rangeImageSource == RangeImageSource.UNKNOWN) { + throw new IllegalArgumentException("Range Image Source cannot be UNKNOWN"); + } + int value = 0; + value += compressionMethod.getEncodedValue(); + value += (dataType.getEncodedValue() << DATA_TYPE_BIT_SHIFT); + value += (rangeImageSource.getEncodedValue() << SOURCE_BIT_SHIFT); + return BerEncoder.encode(value, Ber.OID); + } + + @Override + public String getDisplayName() { + return "Range Image Enumerations"; + } + + @Override + public String getDisplayableValue() { + StringBuilder sb = new StringBuilder(); + sb.append(rangeImageSource.getTextDescription()); + sb.append(" | "); + sb.append(dataType.getTextDescription()); + sb.append(" | "); + sb.append(compressionMethod.getTextDescription()); + return sb.toString(); + } + + /** + * Range image compression method. + * + * @return the range image compression method as an enumerated value. + */ + public RangeImageCompressionMethod getCompressionMethod() { + return this.compressionMethod; + } + + /** + * Range imagery data type. + * + * @return the range imagery data type as an enumerated value. + */ + public RangeImageryDataType getDataType() { + return dataType; + } + + /** + * Range image source. + * + * @return the range image source as an enumerated value. + */ + public RangeImageSource getRangeImageSource() { + return rangeImageSource; + } +} diff --git a/st1002/src/main/java/org/jmisb/st1002/RangeImageLocalSet.java b/st1002/src/main/java/org/jmisb/st1002/RangeImageLocalSet.java new file mode 100644 index 000000000..a8087d921 --- /dev/null +++ b/st1002/src/main/java/org/jmisb/st1002/RangeImageLocalSet.java @@ -0,0 +1,222 @@ +package org.jmisb.st1002; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import org.jmisb.api.common.InvalidDataHandler; +import org.jmisb.api.common.KlvParseException; +import org.jmisb.api.klv.ArrayBuilder; +import org.jmisb.api.klv.BerDecoder; +import org.jmisb.api.klv.BerField; +import org.jmisb.api.klv.CrcCcitt; +import org.jmisb.api.klv.IKlvKey; +import org.jmisb.api.klv.IMisbMessage; +import org.jmisb.api.klv.LdsField; +import org.jmisb.api.klv.LdsParser; +import org.jmisb.api.klv.UniversalLabel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Range Image Local Set. + * + *
This is the core ST 1002 Local Set. + */ +public class RangeImageLocalSet implements IMisbMessage { + + private static final int CRC16_LENGTH = 2; + + /** + * Universal label for Range Image Local Set. + * + *
See ST 1002.2 Table 2.
+ */
+ public static final UniversalLabel RangeImageLocalSetUl =
+ new UniversalLabel(
+ new byte[] {
+ 0x06, 0x0E, 0x2B, 0x34, 0x02, 0x0B, 0x01, 0x01, 0x0E, 0x01, 0x03, 0x03,
+ 0x0C, 0x00, 0x00, 0x00
+ });
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(RangeImageLocalSet.class);
+
+ /**
+ * Create a {@link IRangeImageMetadataValue} instance from encoded bytes.
+ *
+ * @param tag The tag defining the value type
+ * @param bytes Encoded bytes
+ * @return The new instance
+ * @throws KlvParseException if the parsing of the encoded bytes fails
+ */
+ static IRangeImageMetadataValue createValue(RangeImageMetadataKey tag, byte[] bytes)
+ throws KlvParseException {
+ switch (tag) {
+ case PrecisionTimeStamp:
+ return new ST1002PrecisionTimeStamp(bytes);
+ case DocumentVersion:
+ return new ST1002VersionNumber(bytes);
+ case RangeImageEnumerations:
+ return new RangeImageEnumerations(bytes);
+ case SinglePointRangeMeasurement:
+ return new SinglePointRangeMeasurement(bytes);
+ case SinglePointRangeMeasurementUncertainty:
+ return new SinglePointRangeMeasurementUncertainty(bytes);
+ case SinglePointRangeMeasurementRowCoordinate:
+ return new SinglePointRangeMeasurementRow(bytes);
+ case SinglePointRangeMeasurementColumnCoordinate:
+ return new SinglePointRangeMeasurementColumn(bytes);
+ case NumberOfSectionsInX:
+ return new NumberOfSectionsInX(bytes);
+ case NumberOfSectionsInY:
+ return new NumberOfSectionsInY(bytes);
+ case GeneralizedTransformationLocalSet:
+ return new GeneralizedTransformation(bytes);
+ default:
+ LOGGER.info("Unknown Range Image Metadata tag: {}", tag);
+ }
+ return null;
+ }
+
+ /** Map containing all elements in the message. */
+ private final SortedMap The encoding is assumed to be nested (i.e. it does not have the Universal Label or length
+ * parts). You can use the constructor taking a byte array for the non-nested case.
+ *
+ * @param bytes the bytes to build from
+ * @param offset the index into the {@code bytes} array to start parsing from
+ * @param len the number of bytes to parse (starting at {@code offset}.
+ * @return local set corresponding to the provided bytes
+ * @throws KlvParseException if parsing fails
+ */
+ public static RangeImageLocalSet fromNestedBytes(final byte[] bytes, int offset, int len)
+ throws KlvParseException {
+ return new RangeImageLocalSet(parseValues(bytes, offset, len));
+ }
+
+ /**
+ * Build a Range Image Local Set from encoded bytes.
+ *
+ * @param bytes the bytes to build from
+ * @throws KlvParseException if parsing fails
+ */
+ public RangeImageLocalSet(final byte[] bytes) throws KlvParseException {
+ int offset = UniversalLabel.LENGTH;
+ BerField len = BerDecoder.decode(bytes, offset, false);
+ offset += len.getLength();
+ SortedMap These tags are used to identify each element in the local set.
+ */
+public enum RangeImageMetadataKey implements IKlvKey {
+ /** Unknown key. This should not be created. */
+ Undefined(0),
+ /** Range Image Precision Time Stamp. */
+ PrecisionTimeStamp(1),
+ /** Document Version. */
+ DocumentVersion(11),
+ /** Range Image Enumerations. */
+ RangeImageEnumerations(12),
+ /** Single Point Range Measurement (SPRM). */
+ SinglePointRangeMeasurement(13),
+ /**
+ * Single Point Range Measurement (SPRM) Uncertainty.
+ *
+ * Range measurement uncertainty.
+ */
+ SinglePointRangeMeasurementUncertainty(14),
+ /**
+ * Single Point Range Measurement (SPRM) Row Coordinate.
+ *
+ * Measured Row Coordinate for Range.
+ */
+ SinglePointRangeMeasurementRowCoordinate(15),
+ /**
+ * Single Point Range Measurement (SPRM) Column Coordinate.
+ *
+ * Measured Column Coordinate for Range.
+ */
+ SinglePointRangeMeasurementColumnCoordinate(16),
+ /** Number of Sections in X. */
+ NumberOfSectionsInX(17),
+ /** Number of Sections in Y. */
+ NumberOfSectionsInY(18),
+ /** Generalized Transformation Local Set. */
+ GeneralizedTransformationLocalSet(19),
+ /** Section Data Variable Length Pack. */
+ SectionDataVLP(20),
+ /** CRC-16-CCITT. */
+ CRC16CCITT(21);
+
+ private final int tag;
+
+ private static final Map This enumeration describes how the Range Imagery was created, either from a range sensor (e.g.
+ * laser range finder, or LIDAR) or computationally extracted.
+ *
+ * @see RangeImageEnumerations
+ */
+public enum RangeImageSource {
+ /**
+ * Unknown value.
+ *
+ * This is not a valid image source, and indicates a problem with decoding.
+ */
+ UNKNOWN(-1, "Unknown"),
+ /** Computationally extracted. */
+ COMPUTATIONALLY_EXTRACTED(0, "Computationally Extracted"),
+ /** Range sensor. */
+ RANGE_SENSOR(1, "Range Sensor");
+
+ /**
+ * Look up Range Image Source by encoded value.
+ *
+ * This looks up the basic value (without any bit shifting applied, so Range Sensor is {@code
+ * 0b1}).
+ *
+ * @param value the encoded (integer) value
+ * @return the corresponding enumeration value
+ */
+ public static RangeImageSource lookup(int value) {
+ for (RangeImageSource source : values()) {
+ if (source.getEncodedValue() == value) {
+ return source;
+ }
+ }
+ return UNKNOWN;
+ }
+
+ private final int encodedValue;
+ private final String textDescription;
+
+ private RangeImageSource(int value, String description) {
+ this.encodedValue = value;
+ this.textDescription = description;
+ }
+
+ /**
+ * Encoded value.
+ *
+ * This looks up the basic value (without any bit shifting applied, so Range Sensor is {@code
+ * 0b1}).
+ *
+ * @return integer representation of the enumeration
+ */
+ public int getEncodedValue() {
+ return encodedValue;
+ }
+
+ /**
+ * Text description.
+ *
+ * @return human readable description of the enumeration meaning.
+ */
+ public String getTextDescription() {
+ return textDescription;
+ }
+}
diff --git a/st1002/src/main/java/org/jmisb/st1002/RangeImageryDataType.java b/st1002/src/main/java/org/jmisb/st1002/RangeImageryDataType.java
new file mode 100644
index 000000000..dd51433f8
--- /dev/null
+++ b/st1002/src/main/java/org/jmisb/st1002/RangeImageryDataType.java
@@ -0,0 +1,78 @@
+package org.jmisb.st1002;
+
+/**
+ * Range Imagery Data Type.
+ *
+ * This enumeration describes the range imagery, either Perspective Range Image or Depth Range
+ * Image. Other types may be added in the future, however these two are the only valid values as of
+ * ST 1002.2.
+ *
+ * *
+ *
+ *
+ *
+ *
+ *
+ * @see RangeImageEnumerations
+ */
+public enum RangeImageryDataType {
+ /**
+ * Unknown value.
+ *
+ * This is not a valid data type, and indicates a problem with decoding.
+ */
+ UNKNOWN(-1, "Unknown"),
+ /** Perspective range image. */
+ PERSPECTIVE(0, "Perspective Range Image"),
+ /** Depth range image. */
+ DEPTH(1, "Depth Range Image");
+
+ /**
+ * Look up Range Imagery Data Type by encoded value.
+ *
+ * This looks up the basic value (without any bit shifting applied, so Depth is {@code
+ * 0b001}).
+ *
+ * @param value the encoded (integer) value
+ * @return the corresponding enumeration value
+ */
+ public static RangeImageryDataType lookup(int value) {
+ for (RangeImageryDataType dataType : values()) {
+ if (dataType.getEncodedValue() == value) {
+ return dataType;
+ }
+ }
+ return UNKNOWN;
+ }
+
+ private final int encodedValue;
+ private final String textDescription;
+
+ private RangeImageryDataType(int value, String description) {
+ this.encodedValue = value;
+ this.textDescription = description;
+ }
+
+ /**
+ * Encoded value.
+ *
+ * This looks up the basic value (without any bit shifting applied, so Depth is {@code
+ * 0b001}).
+ *
+ * @return integer representation of the enumeration
+ */
+ public int getEncodedValue() {
+ return encodedValue;
+ }
+
+ /**
+ * Text description.
+ *
+ * @return human readable description of the enumeration meaning.
+ */
+ public String getTextDescription() {
+ return textDescription;
+ }
+}
diff --git a/st1002/src/main/java/org/jmisb/st1002/ST1002PrecisionTimeStamp.java b/st1002/src/main/java/org/jmisb/st1002/ST1002PrecisionTimeStamp.java
new file mode 100644
index 000000000..f9a195b53
--- /dev/null
+++ b/st1002/src/main/java/org/jmisb/st1002/ST1002PrecisionTimeStamp.java
@@ -0,0 +1,61 @@
+package org.jmisb.st1002;
+
+import java.time.LocalDateTime;
+import org.jmisb.api.klv.st0603.ST0603TimeStamp;
+
+/**
+ * Range Image Precision Time Stamp (ST 1002 Range Image Local Set Tag 1).
+ *
+ * The Range Image Precision Time Stamp is the time when the measurements of the Range Image
+ * occurred. This time information is used to coordinate the Range Image with other sources of data,
+ * such as a collaborative sensors image or other sensor data. The time value is an invocation of
+ * the MISP Precision Time Stamp, a 64-bit unsigned integer that represents the number of
+ * microseconds since midnight of January 1st 1970 without leap-seconds, as defined in MISB ST 0603
+ * and detailed in the Motion Imagery Handbook.
+ *
+ * The Range Image Precision Time Stamp is required in all Range Image Local Sets, and must be
+ * the first item. Positioning the Precision Time Stamp tag as the first item facilitates rapidly
+ * checking whether the Local Set matches the desired time for processing a collaborative image.
+ */
+public class ST1002PrecisionTimeStamp extends ST0603TimeStamp implements IRangeImageMetadataValue {
+ /**
+ * Create from value.
+ *
+ * @param microseconds Microseconds since the epoch
+ */
+ public ST1002PrecisionTimeStamp(long microseconds) {
+ super(microseconds);
+ }
+
+ /**
+ * Create from encoded bytes.
+ *
+ * @param bytes Encoded byte array, length of 8 bytes.
+ */
+ public ST1002PrecisionTimeStamp(byte[] bytes) {
+ super(bytes);
+ if (bytes.length < 8) {
+ throw new IllegalArgumentException(
+ this.getDisplayName() + " encoding is an 8-byte unsigned int");
+ }
+ }
+
+ /**
+ * Create from {@code LocalDateTime}.
+ *
+ * @param dateTime The date and time
+ */
+ public ST1002PrecisionTimeStamp(LocalDateTime dateTime) {
+ super(dateTime);
+ }
+
+ @Override
+ public final String getDisplayName() {
+ return "Precision Time Stamp";
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return getBytesFull();
+ }
+}
diff --git a/st1002/src/main/java/org/jmisb/st1002/ST1002VersionNumber.java b/st1002/src/main/java/org/jmisb/st1002/ST1002VersionNumber.java
new file mode 100644
index 000000000..ea34e82b0
--- /dev/null
+++ b/st1002/src/main/java/org/jmisb/st1002/ST1002VersionNumber.java
@@ -0,0 +1,77 @@
+package org.jmisb.st1002;
+
+import org.jmisb.api.klv.Ber;
+import org.jmisb.api.klv.BerDecoder;
+import org.jmisb.api.klv.BerEncoder;
+import org.jmisb.api.klv.BerField;
+
+/**
+ * Version Number (ST 1002 Range Image Local Set Tag 11).
+ *
+ * The version number is the same of the minor version number of the standard document. For
+ * example, with MISB ST 1002.1, the version number value is {@code 1} and with ST 1002.2, the
+ * version number value is {@code 2}.
+ */
+public class ST1002VersionNumber implements IRangeImageMetadataValue {
+
+ private final int version;
+
+ /**
+ * The currently supported revision is 1002.2.
+ *
+ * This may be useful in the constructor.
+ */
+ public static final short ST_VERSION_NUMBER = 2;
+
+ /**
+ * Create from value.
+ *
+ * The current version is available as {@link #ST_VERSION_NUMBER}.
+ *
+ * @param versionNumber The version number
+ */
+ public ST1002VersionNumber(int versionNumber) {
+ if (versionNumber < 0) {
+ throw new IllegalArgumentException("ST 1002 Version Number cannot be negative");
+ }
+ this.version = versionNumber;
+ }
+
+ /**
+ * Create from encoded bytes.
+ *
+ * @param bytes Byte array containing BER-OID formatted version number
+ */
+ public ST1002VersionNumber(byte[] bytes) {
+ BerField berField = BerDecoder.decode(bytes, 0, true);
+ this.version = berField.getValue();
+ }
+
+ /**
+ * Get the document version number.
+ *
+ * @return The version number
+ */
+ public int getVersion() {
+ return version;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return BerEncoder.encode(version, Ber.OID);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Version Number";
+ }
+
+ @Override
+ public String getDisplayableValue() {
+ if (version == 0) {
+ return "ST 1002";
+ } else {
+ return "ST 1002." + version;
+ }
+ }
+}
diff --git a/st1002/src/main/java/org/jmisb/st1002/SectionData.java b/st1002/src/main/java/org/jmisb/st1002/SectionData.java
new file mode 100644
index 000000000..397a95bd1
--- /dev/null
+++ b/st1002/src/main/java/org/jmisb/st1002/SectionData.java
@@ -0,0 +1,316 @@
+package org.jmisb.st1002;
+
+import org.jmisb.api.common.KlvParseException;
+import org.jmisb.api.klv.ArrayBuilder;
+import org.jmisb.api.klv.Ber;
+import org.jmisb.api.klv.BerDecoder;
+import org.jmisb.api.klv.BerEncoder;
+import org.jmisb.api.klv.BerField;
+import org.jmisb.api.klv.IKlvValue;
+import org.jmisb.api.klv.st1303.MDAPDecoder;
+import org.jmisb.api.klv.st1303.NaturalFormatEncoder;
+import org.jmisb.core.klv.PrimitiveConverter;
+
+/**
+ * Section Data Variable Length Pack.
+ *
+ * Section data, along with its supporting information is formatted in a Variable Length Pack
+ * (VLP) (see SMPTE ST 336) called the Section Data VLP. The information in each Section Data VLP
+ * includes Section coordinates, Section data array, uncertainty values, and optional compression
+ * parameters. The optional compression parameters are truncated from the VLP (along with their
+ * lengths) as a group (in a similar fashion to MISB RP 0701 Floating Length Packs). The figure
+ * below illustrates a Section Data VLP with each item in the VLP prefixed with its item length. The
+ * green items indicate required values (along with their lengths in blue); the pink item can be
+ * zero-length; and the yellow items can be truncated (along with their lengths).
+ *
+ *
+ */
+public class SectionData implements IKlvValue {
+
+ private final int sectionNumberX;
+ private final int sectionNumberY;
+ private final double[][] arrayOfMeasuredValues;
+ private final double[][] arrayOfUncertaintyValues;
+ private final double planeXScaleFactor;
+ private final double planeYScaleFactor;
+ private final double planeConstantValue;
+
+ /**
+ * Create from values.
+ *
+ * This constructor assumes the planar fit compression factors are not included.
+ *
+ * @param x the section number X value
+ * @param y the section number Y value
+ * @param measurements the array of measurements
+ * @param uncertainties the array of uncertainties (can be null)
+ */
+ public SectionData(int x, int y, double[][] measurements, double[][] uncertainties) {
+ this.sectionNumberX = x;
+ this.sectionNumberY = y;
+ this.arrayOfMeasuredValues = measurements.clone();
+ if (uncertainties == null) {
+ this.arrayOfUncertaintyValues = null;
+ } else {
+ this.arrayOfUncertaintyValues = uncertainties.clone();
+ }
+ this.planeXScaleFactor = 0.0;
+ this.planeYScaleFactor = 0.0;
+ this.planeConstantValue = 0.0;
+ }
+
+ /**
+ * Create from values.
+ *
+ * @param x the section number X value
+ * @param y the section number Y value
+ * @param measurements the array of measurements
+ * @param uncertainties the array of uncertainties (can be null)
+ * @param planeXscale the plane X scale factor
+ * @param planeYscale the plane Y scale factor
+ * @param planeConstant the plane constant value
+ */
+ public SectionData(
+ int x,
+ int y,
+ double[][] measurements,
+ double[][] uncertainties,
+ double planeXscale,
+ double planeYscale,
+ double planeConstant) {
+ this.sectionNumberX = x;
+ this.sectionNumberY = y;
+ this.arrayOfMeasuredValues = measurements.clone();
+ if (uncertainties == null) {
+ this.arrayOfUncertaintyValues = null;
+ } else {
+ this.arrayOfUncertaintyValues = uncertainties.clone();
+ }
+ this.planeXScaleFactor = planeXscale;
+ this.planeYScaleFactor = planeYscale;
+ this.planeConstantValue = planeConstant;
+ }
+
+ /**
+ * Create from encoded bytes.
+ *
+ * The encoded byte array is assumed to start from the first item length ({@code L1} in the
+ * diagram above, and does not include the overall VLP Key or Length field.
+ *
+ * @param bytes the encoded byte array
+ * @throws KlvParseException if parsing fails
+ */
+ public SectionData(byte[] bytes) throws KlvParseException {
+ MDAPDecoder mdapDecoder = new MDAPDecoder();
+ int offset = 0;
+ BerField l1field = BerDecoder.decode(bytes, offset, false);
+ offset += l1field.getLength();
+ BerField sectionXfield = BerDecoder.decode(bytes, offset, true);
+ offset += sectionXfield.getLength();
+ sectionNumberX = sectionXfield.getValue();
+ BerField l2field = BerDecoder.decode(bytes, offset, false);
+ offset += l2field.getLength();
+ BerField sectionYfield = BerDecoder.decode(bytes, offset, true);
+ offset += sectionYfield.getLength();
+ sectionNumberY = sectionYfield.getValue();
+ BerField l3field = BerDecoder.decode(bytes, offset, false);
+ offset += l3field.getLength();
+ this.arrayOfMeasuredValues = mdapDecoder.decodeFloatingPoint2D(bytes, offset);
+ offset += l3field.getValue();
+ BerField l4field = BerDecoder.decode(bytes, offset, false);
+ offset += l4field.getLength();
+ if (l4field.getValue() > 0) {
+ this.arrayOfUncertaintyValues = mdapDecoder.decodeFloatingPoint2D(bytes, offset);
+ offset += l4field.getValue();
+ } else {
+ this.arrayOfUncertaintyValues = null;
+ }
+ if (offset < bytes.length) {
+ BerField l5field = BerDecoder.decode(bytes, offset, false);
+ offset += l5field.getLength();
+ if (l5field.getValue() == 4) {
+ this.planeXScaleFactor = PrimitiveConverter.toFloat32(bytes, offset);
+ } else if (l5field.getValue() == 8) {
+ this.planeXScaleFactor = PrimitiveConverter.toFloat64(bytes, offset);
+ } else {
+ throw new KlvParseException(
+ "unsupported Plane X Scale Factor size: " + l5field.getValue());
+ }
+ offset += l5field.getValue();
+ } else {
+ this.planeXScaleFactor = 0.0;
+ }
+ if (offset < bytes.length) {
+ BerField l6field = BerDecoder.decode(bytes, offset, false);
+ offset += l6field.getLength();
+ if (l6field.getValue() == 4) {
+ this.planeYScaleFactor = PrimitiveConverter.toFloat32(bytes, offset);
+ } else if (l6field.getValue() == 8) {
+ this.planeYScaleFactor = PrimitiveConverter.toFloat64(bytes, offset);
+ } else {
+ throw new KlvParseException(
+ "unsupported Plane Y Scale Factor size: " + l6field.getValue());
+ }
+ offset += l6field.getValue();
+ } else {
+ this.planeYScaleFactor = 0.0;
+ }
+
+ if (offset < bytes.length) {
+ BerField l7field = BerDecoder.decode(bytes, offset, false);
+ offset += l7field.getLength();
+ if (l7field.getValue() == 4) {
+ this.planeConstantValue = PrimitiveConverter.toFloat32(bytes, offset);
+ } else if (l7field.getValue() == 8) {
+ this.planeConstantValue = PrimitiveConverter.toFloat64(bytes, offset);
+ } else {
+ throw new KlvParseException(
+ "unsupported Plane Constant size: " + l7field.getValue());
+ }
+ offset += l7field.getValue();
+ } else {
+ this.planeConstantValue = 0.0;
+ }
+ }
+
+ /**
+ * Copy Constructor.
+ *
+ * @param other the section data instance to copy values from
+ */
+ public SectionData(SectionData other) {
+ this.sectionNumberX = other.getSectionNumberX();
+ this.sectionNumberY = other.getSectionNumberY();
+ this.arrayOfMeasuredValues = other.getArrayOfMeasuredValues().clone();
+ if (other.getArrayOfUncertaintyValues() == null) {
+ this.arrayOfUncertaintyValues = null;
+ } else {
+ this.arrayOfUncertaintyValues = other.getArrayOfUncertaintyValues().clone();
+ }
+ this.planeXScaleFactor = other.getPlaneXScaleFactor();
+ this.planeYScaleFactor = other.getPlaneYScaleFactor();
+ this.planeConstantValue = other.getPlaneConstantValue();
+ }
+
+ /**
+ * Serialise the Section Data to encoded byte array.
+ *
+ * @return the byte array
+ * @throws KlvParseException if serialisation fails.
+ */
+ public byte[] getBytes() throws KlvParseException {
+ ArrayBuilder builder = new ArrayBuilder();
+ byte[] sectionNumberXbytes = BerEncoder.encode(sectionNumberX, Ber.OID);
+ builder.appendAsBerLength(sectionNumberXbytes.length);
+ builder.append(sectionNumberXbytes);
+ byte[] sectionNumberYbytes = BerEncoder.encode(sectionNumberY, Ber.OID);
+ builder.appendAsBerLength(sectionNumberYbytes.length);
+ builder.append(sectionNumberYbytes);
+ NaturalFormatEncoder mdapEncoder = new NaturalFormatEncoder();
+ byte[] arrayOfMeasuredValuesBytes = mdapEncoder.encode(arrayOfMeasuredValues);
+ builder.appendAsBerLength(arrayOfMeasuredValuesBytes.length);
+ builder.append(arrayOfMeasuredValuesBytes);
+ if (arrayOfUncertaintyValues == null) {
+ builder.appendAsBerLength(0);
+ } else {
+ byte[] arrayOfUncertaintyValuesBytes = mdapEncoder.encode(arrayOfUncertaintyValues);
+ builder.appendAsBerLength(arrayOfUncertaintyValuesBytes.length);
+ builder.append(arrayOfUncertaintyValuesBytes);
+ }
+ if ((this.planeXScaleFactor == 0.0)
+ && (this.planeYScaleFactor == 0.0)
+ && (this.planeConstantValue == 0.0)) {
+ // just omit the rest.
+ } else {
+ builder.appendAsBerLength(Double.BYTES);
+ builder.appendAsFloat64Primitive(this.planeXScaleFactor);
+ builder.appendAsBerLength(Double.BYTES);
+ builder.appendAsFloat64Primitive(this.planeYScaleFactor);
+ builder.appendAsBerLength(Double.BYTES);
+ builder.appendAsFloat64Primitive(this.planeConstantValue);
+ }
+ return builder.toBytes();
+ }
+
+ /**
+ * Section Number in X direction.
+ *
+ * @return section number as an integer
+ */
+ public int getSectionNumberX() {
+ return sectionNumberX;
+ }
+
+ /**
+ * Section Number in Y direction.
+ *
+ * @return section number as an integer.
+ */
+ public int getSectionNumberY() {
+ return sectionNumberY;
+ }
+
+ /**
+ * Array of measured values.
+ *
+ * @return the measured values as a 2-dimensional array
+ */
+ public double[][] getArrayOfMeasuredValues() {
+ return arrayOfMeasuredValues.clone();
+ }
+ /**
+ * Array of uncertainty values.
+ *
+ * @return the uncertainty values as a 2-dimensional array, or null if not provided.
+ */
+ public double[][] getArrayOfUncertaintyValues() {
+ if (arrayOfUncertaintyValues == null) {
+ return null;
+ }
+ return arrayOfUncertaintyValues.clone();
+ }
+
+ /**
+ * Plane X scale factor.
+ *
+ * This is used for planar fit compression. See ST 1002.2 Section 7.
+ *
+ * @return the scale factor as a double.
+ */
+ public double getPlaneXScaleFactor() {
+ return planeXScaleFactor;
+ }
+
+ /**
+ * Plane Y scale factor.
+ *
+ * This is used for planar fit compression. See ST 1002.2 Section 7.
+ *
+ * @return the scale factor as a double.
+ */
+ public double getPlaneYScaleFactor() {
+ return planeYScaleFactor;
+ }
+
+ /**
+ * Plane Constant value.
+ *
+ * This is used for planar fit compression. See ST 1002.2 Section 7.
+ *
+ * @return the scale factor as a double.
+ */
+ public double getPlaneConstantValue() {
+ return planeConstantValue;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Section Data";
+ }
+
+ @Override
+ public String getDisplayableValue() {
+ return String.format("Section [%d,%d]", this.sectionNumberX, this.sectionNumberY);
+ }
+}
diff --git a/st1002/src/main/java/org/jmisb/st1002/SectionDataIdentifierKey.java b/st1002/src/main/java/org/jmisb/st1002/SectionDataIdentifierKey.java
new file mode 100644
index 000000000..15d9ca4f4
--- /dev/null
+++ b/st1002/src/main/java/org/jmisb/st1002/SectionDataIdentifierKey.java
@@ -0,0 +1,78 @@
+package org.jmisb.st1002;
+
+import org.jmisb.api.klv.IKlvKey;
+
+/** Pseudo-key item for Section Data identifier. */
+public class SectionDataIdentifierKey implements IKlvKey, Comparable Section numbers in the simple layout (mandated by ST 1002.2) only vary in one direction (X
+ * for horizontal composition, Y for vertical composition).
+ *
+ * @param x the section number X identifier.
+ * @param y the section number Y identifier.
+ */
+ public SectionDataIdentifierKey(final int x, final int y) {
+ this.sectionX = x;
+ this.sectionY = y;
+ }
+
+ @Override
+ public int getIdentifier() {
+ return sectionX + sectionY - 1;
+ }
+
+ /**
+ * Section Number X value.
+ *
+ * @return the section number as an integer
+ */
+ public int getSectionX() {
+ return sectionX;
+ }
+
+ /**
+ * Section Number Y value.
+ *
+ * @return the section number as an integer
+ */
+ public int getSectionY() {
+ return sectionY;
+ }
+
+ @Override
+ public int compareTo(SectionDataIdentifierKey other) {
+ return Integer.compare(getIdentifier(), other.getIdentifier());
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 29 * hash + this.sectionX;
+ hash = 29 * hash + this.sectionY;
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final SectionDataIdentifierKey other = (SectionDataIdentifierKey) obj;
+ if (this.sectionX != other.sectionX) {
+ return false;
+ }
+ return this.sectionY == other.sectionY;
+ }
+}
diff --git a/st1002/src/main/java/org/jmisb/st1002/SectionDataList.java b/st1002/src/main/java/org/jmisb/st1002/SectionDataList.java
new file mode 100644
index 000000000..bfbf859cf
--- /dev/null
+++ b/st1002/src/main/java/org/jmisb/st1002/SectionDataList.java
@@ -0,0 +1,84 @@
+package org.jmisb.st1002;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import org.jmisb.api.klv.IKlvKey;
+import org.jmisb.api.klv.IKlvValue;
+import org.jmisb.api.klv.INestedKlvValue;
+
+/**
+ * Section Data list (ST 1002 Range Image Local Set Tag 20).
+ *
+ * Section data, along with its supporting information is formatted in a Variable Length Pack
+ * (VLP) (see SMPTE ST 336) called the Section Data VLP.
+ *
+ * Section data is repeatable within the Range Image Local Set. {@code SectionDataList}
+ * represents zero or more Section Data VLPs.
+ *
+ * @see NumberOfSectionsInX
+ * @see NumberOfSectionsInY
+ */
+public class SectionDataList implements IRangeImageMetadataValue, INestedKlvValue {
+
+ private final List The Single Point Range Measurement (SPRM) is the measure of distance (in metres) from either
+ * the principle point, or backplane of a Collaborative Sensor through the image plane to a point in
+ * the scene.
+ *
+ * These measurement concepts are shown in the following images, which are from MISB ST 1002.2.
+ * Note that the red line closer to the terrain (green area) represents the focal plane, and that
+ * the images are unlikely to be to scale in most scenarios.
+ *
+ *
+ *
+ *
+ *
+ * This value can be either a 32-bit or 64-bit IEEE floating point value.
+ *
+ * @see SinglePointRangeMeasurementUncertainty
+ */
+public class SinglePointRangeMeasurement implements IRangeImageMetadataValue {
+
+ private final double value;
+
+ /**
+ * Create from value.
+ *
+ * @param range the range in metres.
+ */
+ public SinglePointRangeMeasurement(double range) {
+ this.value = range;
+ }
+
+ /**
+ * Create from encoded bytes.
+ *
+ * @param bytes Encoded byte array of length 4 or 8
+ * @throws KlvParseException if the byte array is not of the correct length
+ */
+ public SinglePointRangeMeasurement(byte[] bytes) throws KlvParseException {
+ try {
+ this.value = PrimitiveConverter.toFloat64(bytes);
+ } catch (IllegalArgumentException ex) {
+ throw new KlvParseException("Single Point Range Measurement is of length 4 or 8 bytes");
+ }
+ }
+
+ @Override
+ public final String getDisplayName() {
+ return "Single Point Range Measurement";
+ }
+
+ /**
+ * Get the range.
+ *
+ * @return range in metres.
+ */
+ public double getRange() {
+ return this.value;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return PrimitiveConverter.float64ToBytes(value);
+ }
+
+ @Override
+ public String getDisplayableValue() {
+ return String.format("%.3f m", this.value);
+ }
+}
diff --git a/st1002/src/main/java/org/jmisb/st1002/SinglePointRangeMeasurementColumn.java b/st1002/src/main/java/org/jmisb/st1002/SinglePointRangeMeasurementColumn.java
new file mode 100644
index 000000000..3612aaffa
--- /dev/null
+++ b/st1002/src/main/java/org/jmisb/st1002/SinglePointRangeMeasurementColumn.java
@@ -0,0 +1,75 @@
+package org.jmisb.st1002;
+
+import org.jmisb.api.common.KlvParseException;
+import org.jmisb.core.klv.PrimitiveConverter;
+
+/**
+ * Single Point Range Measurement Column (ST 1002 Local Set Item 16).
+ *
+ * The Single Point Range Measurement is not necessarily measured directly through the centre of
+ * the Collaborative Image; therefore, the location within the image needs to be indicated. The
+ * Single Point Range Measurement Row and Column Coordinates are the coordinates within the
+ * Collaborative Sensor’s Image where the measurement was taken.
+ *
+ *
+ *
+ * These values are either 32-bit or 64-bit IEEE floating point values. If the Row and Column
+ * values are omitted from the Range Image Local Set, then the default values are set to the centre
+ * of the Collaborative Sensor's Image.
+ *
+ * @see SinglePointRangeMeasurement
+ * @see SinglePointRangeMeasurementRow
+ */
+public class SinglePointRangeMeasurementColumn implements IRangeImageMetadataValue {
+
+ private final double value;
+
+ /**
+ * Create from value.
+ *
+ * @param row the row coordinate in pixels.
+ */
+ public SinglePointRangeMeasurementColumn(double row) {
+ this.value = row;
+ }
+
+ /**
+ * Create from encoded bytes.
+ *
+ * @param bytes Encoded byte array of length 4 or 8
+ * @throws KlvParseException if the byte array is not of the correct length
+ */
+ public SinglePointRangeMeasurementColumn(byte[] bytes) throws KlvParseException {
+ try {
+ this.value = PrimitiveConverter.toFloat64(bytes);
+ } catch (IllegalArgumentException ex) {
+ throw new KlvParseException(
+ "Single Point Range Measurement Column Coordinate is of length 4 or 8 bytes");
+ }
+ }
+
+ @Override
+ public final String getDisplayName() {
+ return "Single Point Range Measurement Column";
+ }
+
+ /**
+ * Get the column coordinate.
+ *
+ * @return column coordinate in pixels.
+ */
+ public double getColumn() {
+ return this.value;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return PrimitiveConverter.float64ToBytes(value);
+ }
+
+ @Override
+ public String getDisplayableValue() {
+ return String.format("%.1f", this.value);
+ }
+}
diff --git a/st1002/src/main/java/org/jmisb/st1002/SinglePointRangeMeasurementRow.java b/st1002/src/main/java/org/jmisb/st1002/SinglePointRangeMeasurementRow.java
new file mode 100644
index 000000000..ebd85de9b
--- /dev/null
+++ b/st1002/src/main/java/org/jmisb/st1002/SinglePointRangeMeasurementRow.java
@@ -0,0 +1,75 @@
+package org.jmisb.st1002;
+
+import org.jmisb.api.common.KlvParseException;
+import org.jmisb.core.klv.PrimitiveConverter;
+
+/**
+ * Single Point Range Measurement Row (ST 1002 Local Set Item 15).
+ *
+ * The Single Point Range Measurement is not necessarily measured directly through the centre of
+ * the Collaborative Image; therefore, the location within the image needs to be indicated. The
+ * Single Point Range Measurement Row and Column Coordinates are the coordinates within the
+ * Collaborative Sensor’s Image where the measurement was taken.
+ *
+ *
+ *
+ * These values are either 32-bit or 64-bit IEEE floating point values. If the Row and Column
+ * values are omitted from the Range Image Local Set, then the default values are set to the centre
+ * of the Collaborative Sensor's Image.
+ *
+ * @see SinglePointRangeMeasurement
+ * @see SinglePointRangeMeasurementColumn
+ */
+public class SinglePointRangeMeasurementRow implements IRangeImageMetadataValue {
+
+ private final double value;
+
+ /**
+ * Create from value.
+ *
+ * @param row the row coordinate in pixels.
+ */
+ public SinglePointRangeMeasurementRow(double row) {
+ this.value = row;
+ }
+
+ /**
+ * Create from encoded bytes.
+ *
+ * @param bytes Encoded byte array of length 4 or 8
+ * @throws KlvParseException if the byte array is not of the correct length
+ */
+ public SinglePointRangeMeasurementRow(byte[] bytes) throws KlvParseException {
+ try {
+ this.value = PrimitiveConverter.toFloat64(bytes);
+ } catch (IllegalArgumentException ex) {
+ throw new KlvParseException(
+ "Single Point Range Measurement Row Coordinate is of length 4 or 8 bytes");
+ }
+ }
+
+ @Override
+ public final String getDisplayName() {
+ return "Single Point Range Measurement Row";
+ }
+
+ /**
+ * Get the row coordinate.
+ *
+ * @return row coordinate in pixels.
+ */
+ public double getRow() {
+ return this.value;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return PrimitiveConverter.float64ToBytes(value);
+ }
+
+ @Override
+ public String getDisplayableValue() {
+ return String.format("%.1f", this.value);
+ }
+}
diff --git a/st1002/src/main/java/org/jmisb/st1002/SinglePointRangeMeasurementUncertainty.java b/st1002/src/main/java/org/jmisb/st1002/SinglePointRangeMeasurementUncertainty.java
new file mode 100644
index 000000000..8505d0412
--- /dev/null
+++ b/st1002/src/main/java/org/jmisb/st1002/SinglePointRangeMeasurementUncertainty.java
@@ -0,0 +1,67 @@
+package org.jmisb.st1002;
+
+import org.jmisb.api.common.KlvParseException;
+import org.jmisb.core.klv.PrimitiveConverter;
+
+/**
+ * Single Point Range Measurement Uncertainty (ST 1002 Local Set Item 14).
+ *
+ * The Single Point Range Measurement Uncertainty is the uncertainty (sigma,σ) of the Single
+ * Point Range Measurement data, in metres, along the measured vector from either the perspective
+ * centre or depth range measurement backplane to the scene. This value can be either a 32-bit or
+ * 64-bit IEEE floating point value.
+ *
+ * @see SinglePointRangeMeasurement
+ */
+public class SinglePointRangeMeasurementUncertainty implements IRangeImageMetadataValue {
+
+ private final double value;
+
+ /**
+ * Create from value.
+ *
+ * @param uncertainty the range uncertainty in metres.
+ */
+ public SinglePointRangeMeasurementUncertainty(double uncertainty) {
+ this.value = uncertainty;
+ }
+
+ /**
+ * Create from encoded bytes.
+ *
+ * @param bytes Encoded byte array of length 4 or 8
+ * @throws KlvParseException if the byte array is not of the correct length
+ */
+ public SinglePointRangeMeasurementUncertainty(byte[] bytes) throws KlvParseException {
+ try {
+ this.value = PrimitiveConverter.toFloat64(bytes);
+ } catch (IllegalArgumentException ex) {
+ throw new KlvParseException(
+ "Single Point Range Measurement Uncertainty is of length 4 or 8 bytes");
+ }
+ }
+
+ @Override
+ public final String getDisplayName() {
+ return "Single Point Range Measurement Uncertainty";
+ }
+
+ /**
+ * Get the uncertainty.
+ *
+ * @return range uncertainty (one standard deviation) in metres.
+ */
+ public double getRange() {
+ return this.value;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return PrimitiveConverter.float64ToBytes(value);
+ }
+
+ @Override
+ public String getDisplayableValue() {
+ return String.format("%.3f m", this.value);
+ }
+}
diff --git a/st1002/src/main/java/org/jmisb/st1002/package-info.java b/st1002/src/main/java/org/jmisb/st1002/package-info.java
new file mode 100644
index 000000000..49e1cbbc0
--- /dev/null
+++ b/st1002/src/main/java/org/jmisb/st1002/package-info.java
@@ -0,0 +1,17 @@
+/**
+ * ST 1002: Range Motion Imagery.
+ *
+ * This standard describes Range Motion Imagery, its format and supporting metadata. Range Motion
+ * Imagery is a temporal sequence of Range Images. Each Range Image is a collection of Range
+ * Measurements from a sensor to target scene. A Range Measurement is the distance (e.g., meters)
+ * from an object (or area) in the scene to the sensor. The KLV structures of this Standard are
+ * intended to allow for flexibility, efficient packing, and future extensions. Range Motion Imagery
+ * can be used standalone or in collaboration with other Motion Imagery. MISB ST 1107 Metric
+ * Geopositioning Metadata Set provides the basis for collaborating with other Motion Imagery types.
+ *
+ * This standard describes the: Perspective Range Motion Imagery and Depth Range Motion Imagery;
+ * the collection methods of Range Motion Imagery; the formats used for storing or transmitting
+ * Range Motion Imagery; the supporting metadata needed for Range Motion Imagery including,
+ * temporal, uncertainty, and compression parameters; and the alignment to Collaborative Imagery.
+ */
+package org.jmisb.st1002;
diff --git a/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/coboresightedsensors.png b/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/coboresightedsensors.png
new file mode 100644
index 000000000..6bf944c20
Binary files /dev/null and b/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/coboresightedsensors.png differ
diff --git a/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/depthrangeimage.png b/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/depthrangeimage.png
new file mode 100644
index 000000000..bff190ee8
Binary files /dev/null and b/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/depthrangeimage.png differ
diff --git a/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/fivehorizontalsections.png b/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/fivehorizontalsections.png
new file mode 100644
index 000000000..6d58a060f
Binary files /dev/null and b/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/fivehorizontalsections.png differ
diff --git a/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/nonboresightedsensors.png b/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/nonboresightedsensors.png
new file mode 100644
index 000000000..1dd860df9
Binary files /dev/null and b/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/nonboresightedsensors.png differ
diff --git a/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/perspectiverangeimage.png b/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/perspectiverangeimage.png
new file mode 100644
index 000000000..fe0877acb
Binary files /dev/null and b/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/perspectiverangeimage.png differ
diff --git a/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/sectiondatavlp.png b/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/sectiondatavlp.png
new file mode 100644
index 000000000..e4d3db69d
Binary files /dev/null and b/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/sectiondatavlp.png differ
diff --git a/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/singlepointrangemeasurement.png b/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/singlepointrangemeasurement.png
new file mode 100644
index 000000000..e6b1eb765
Binary files /dev/null and b/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/singlepointrangemeasurement.png differ
diff --git a/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/threeverticalsections.png b/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/threeverticalsections.png
new file mode 100644
index 000000000..c421a09e2
Binary files /dev/null and b/st1002/src/main/javadoc/org/jmisb/st1002/doc-files/threeverticalsections.png differ
diff --git a/st1002/src/main/resources/META-INF/services/org.jmisb.api.klv.IMisbMessageFactory b/st1002/src/main/resources/META-INF/services/org.jmisb.api.klv.IMisbMessageFactory
new file mode 100644
index 000000000..349abc422
--- /dev/null
+++ b/st1002/src/main/resources/META-INF/services/org.jmisb.api.klv.IMisbMessageFactory
@@ -0,0 +1 @@
+org.jmisb.st1002.RangeImageLocalSetFactory
diff --git a/st1002/src/test/java/org/jmisb/st1002/GeneralizedTransformationTest.java b/st1002/src/test/java/org/jmisb/st1002/GeneralizedTransformationTest.java
new file mode 100644
index 000000000..cade31b05
--- /dev/null
+++ b/st1002/src/test/java/org/jmisb/st1002/GeneralizedTransformationTest.java
@@ -0,0 +1,87 @@
+package org.jmisb.st1002;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.jmisb.api.common.KlvParseException;
+import org.jmisb.st1202.GeneralizedTransformationLocalSet;
+import org.jmisb.st1202.GeneralizedTransformationParametersKey;
+import org.jmisb.st1202.IGeneralizedTransformationMetadataValue;
+import org.jmisb.st1202.ST1202DocumentVersion;
+import org.testng.annotations.Test;
+
+/** Tests for ST 1002 Generalized Transformation (ST 1002 Tag 19). */
+public class GeneralizedTransformationTest {
+
+ @Test
+ public void testConstructFromValueMap() throws KlvParseException {
+ Map The concept is that there is a test logger that should always contain no log messages after a
+ * test has been run. That gets verified in the AfterMethod so if there is a message, the logger
+ * needs to be checked and clear()ed before the test method returns.
+ *
+ * Only ERROR, WARN and INFO levels are checked. DEBUG and lower are implementation detail.
+ *
+ * The subclass is responsible for initialising the LOGGER correctly by calling the super
+ * constructor with the class that is creating the log messages.
+ */
+public abstract class LoggerChecks {
+ protected TestLogger LOGGER;
+
+ public LoggerChecks(Class T) {
+ LOGGER = TestLoggerFactory.getTestLogger(T);
+ LOGGER.setEnabledLevelsForAllThreads(Level.ERROR, Level.WARN, Level.INFO);
+ }
+
+ @BeforeMethod
+ public void clearLogger() {
+ LOGGER.clear();
+ }
+
+ @AfterMethod
+ public void checkLogger() {
+ verifyNoLoggerMessages();
+ }
+
+ protected void verifyNoLoggerMessages() {
+ if (LOGGER.getLoggingEvents().size() > 0) {
+ List This standard (ST) defines a bit-efficient method for transmitting standard deviation and
+ * correlation coefficient data. In support of this method a Standard Deviation and Correlation
+ * Coefficient Floating Length Pack (SDCC-FLP) construct is defined. The construct leverages the
+ * symmetry of the variance-covariance matrix and the fixed data range of the correlation
+ * coefficients to reduce the number of bytes transmitted, in effect compressing the data. This
+ * method is, therefore, not extendable to more generic matrix cases. This ST is dependent on
+ * context from an invoking standard (or other document), called a Parent Document, which provides a
+ * list of values (random variables) that can have corresponding standard deviation and correlation
+ * coefficients; this list of random variables is called the Source List.
+ */
+@SuppressWarnings("module") // That is not a version number - its a document number.
+module org.jmisb.st1010 {
+ requires org.jmisb.api;
+ requires org.slf4j;
+
+ exports org.jmisb.st1010;
+}
diff --git a/st1010/src/main/java/org/jmisb/st1010/EncodingFormat.java b/st1010/src/main/java/org/jmisb/st1010/EncodingFormat.java
new file mode 100644
index 000000000..dc3fbba94
--- /dev/null
+++ b/st1010/src/main/java/org/jmisb/st1010/EncodingFormat.java
@@ -0,0 +1,60 @@
+package org.jmisb.st1010;
+
+import org.jmisb.api.common.KlvParseException;
+
+/**
+ * Encoding format for Standard Deviation and Correlation Coefficient values.
+ *
+ * ST 1010 supports the encoding of values in two formats (at least in Mode 2 parsing) - IEEE 754
+ * floating point, and ST 1201 integer encoding. This enumeration allows identification of the
+ * parsing format.
+ *
+ * In Mode 1 parsing, the format for standard deviation are externally specified (in the invoking
+ * specification document), but the correlation coefficients are always in ST 1201 format.
+ */
+public enum EncodingFormat {
+ /**
+ * Value is encoded in an IEEE 754 floating point format.
+ *
+ * The supported formats are per MISB ST 0107.
+ */
+ IEEE(0),
+ /**
+ * Value is encoded in MISB ST 1201 format.
+ *
+ * The encoding is IMAPB(-1.0, 1.0).
+ */
+ ST1201(1);
+
+ private EncodingFormat(int value) {
+ v = value;
+ }
+
+ private final int v;
+
+ /**
+ * Get the value associated with this encoding format.
+ *
+ * @return 1 if the format is ST1201, otherwise 0 to mean IEEE format.
+ */
+ public int getValue() {
+ return v;
+ }
+
+ /**
+ * Get the encoding from an integer value.
+ *
+ * @param v the value to look up (0 or 1)
+ * @return the corresponding EncodingFormat for the value.
+ * @throws KlvParseException if the value is out of range.
+ */
+ public static EncodingFormat getEncoding(int v) throws KlvParseException {
+ if (v == 1) {
+ return EncodingFormat.ST1201;
+ } else if (v == 0) {
+ return EncodingFormat.IEEE;
+ } else {
+ throw new KlvParseException("The only encoding format values are 0 or 1, got " + v);
+ }
+ }
+}
diff --git a/st1010/src/main/java/org/jmisb/st1010/SDCC.java b/st1010/src/main/java/org/jmisb/st1010/SDCC.java
new file mode 100644
index 000000000..f359b0392
--- /dev/null
+++ b/st1010/src/main/java/org/jmisb/st1010/SDCC.java
@@ -0,0 +1,187 @@
+package org.jmisb.st1010;
+
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import org.jmisb.api.klv.IKlvKey;
+import org.jmisb.api.klv.INestedKlvValue;
+
+/**
+ * Standard Deviation and Correlation Coefficient Matrix.
+ *
+ * An instance of this class represents the "full" version of the Standard Deviation and Cross
+ * Correlation (SDCC) matrix. The standard deviation values (error estimates) are on the main
+ * diagonal. The off-diagonal values are the estimates of correlation between the errors on
+ * corresponding rows / columns. The SDCC is symmetrical about the main diagonal.
+ *
+ * As an example, consider the case where a local set (such as ST 0601) has Sensor Latitude and
+ * Sensor Longitude information, and there is an estimate of the error (at the one
+ * standard-deviation level) - say based on the GPS sensor performance and satellite geometry
+ * (Horizontal Dilution of Precision - HDOP). In this situation, the latitude and longitude parts of
+ * the position error are probably fairly closely related (and would have cross-correlation values
+ * approaching 1). SDCC provides the structure to represent this information.
+ */
+public class SDCC implements INestedKlvValue {
+ private int stdDevLength = Float.BYTES;
+ private int corrCoefLength = 3;
+ private EncodingFormat stdDevFormat = EncodingFormat.IEEE;
+ private EncodingFormat corrCoefFormat = EncodingFormat.ST1201;
+ private double[][] values = new double[0][0];
+
+ /**
+ * Constructor.
+ *
+ * This will produce an empty SDCC, initialised to use four-byte IEEE format for standard
+ * deviations, and three-byte ST 1201 for correlation coefficients.
+ */
+ public SDCC() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param other the object to copy from.
+ */
+ public SDCC(SDCC other) {
+ this.stdDevLength = other.getStandardDeviationLength();
+ this.corrCoefLength = other.getCorrelationCoefficientLength();
+ this.stdDevFormat = other.getStandardDeviationFormat();
+ this.corrCoefFormat = other.getCorrelationCoefficientFormat();
+ this.values = other.getValues().clone();
+ }
+
+ /**
+ * Get the encoded standard deviation length.
+ *
+ * @return the length of each encoded value.
+ */
+ public int getStandardDeviationLength() {
+ return stdDevLength;
+ }
+
+ /**
+ * Set the length of each encoded standard deviation value.
+ *
+ * Note that the format needs to be consistent with the standard deviation format. In
+ * particular, {@code EncodingFormat.IEEE} requires that the length be 2, 4 or 8 bytes, while
+ * {@code EncodingFormat.ST1201} requires that the length be consistent with the IMAP encoding.
+ *
+ * All standard deviation values in an SDCC instance need to have the same length.
+ *
+ * @param length the length in bytes
+ */
+ public void setStandardDeviationLength(int length) {
+ this.stdDevLength = length;
+ }
+
+ /**
+ * Get the encoded correlation coefficient value length.
+ *
+ * @return the length of each encoded value.
+ */
+ public int getCorrelationCoefficientLength() {
+ return corrCoefLength;
+ }
+
+ /**
+ * Set the length of each encoded correlation coefficient value.
+ *
+ * Note that the format needs to be consistent with the correlation coefficient format. In
+ * particular, {@code EncodingFormat.IEEE} requires that the length be 2, 4 or 8 bytes, while
+ * {@code EncodingFormat.ST1201} requires that the length be consistent with the IMAP encoding.
+ *
+ * All correlation coefficients in an SDCC instance need to have the same length.
+ *
+ * @param length the length in bytes
+ */
+ public void setCorrelationCoefficientLength(int length) {
+ this.corrCoefLength = length;
+ }
+
+ /**
+ * Get the standard deviation encoding format.
+ *
+ * @return the format for encoding standard deviations.
+ */
+ public EncodingFormat getStandardDeviationFormat() {
+ return stdDevFormat;
+ }
+
+ /**
+ * Set the standard deviation encoding format.
+ *
+ * Note that the format needs to be consistent with the standard deviation length.
+ *
+ * Also, use of {@code EncodingFormat.ST1201} requires specifying the IMAP encoding for the
+ * standard deviation value.
+ *
+ * @param format the format to use for encoding standard deviation values.
+ */
+ public void setStandardDeviationFormat(EncodingFormat format) {
+ this.stdDevFormat = format;
+ }
+
+ /**
+ * Get the correlation coefficient encoding format.
+ *
+ * @return the format for encoding correlation coefficients.
+ */
+ public EncodingFormat getCorrelationCoefficientFormat() {
+ return corrCoefFormat;
+ }
+
+ /**
+ * Set the correlation coefficient encoding format.
+ *
+ * Note that the format needs to be consistent with the correlation coefficient length.
+ *
+ * @param format the format to use for encoding correlation coefficients.
+ */
+ public void setCorrelationCoefficientFormat(EncodingFormat format) {
+ this.corrCoefFormat = format;
+ }
+
+ /**
+ * Get the reconstructed matrix values.
+ *
+ * The matrix will have the standard deviations on the main diagonal, and the correlation
+ * coefficients symmetrical about the main diagonal. Any sparse entries from the encoded format
+ * will be filled with zeros.
+ *
+ * @return the reconstructed matrix.
+ */
+ public double[][] getValues() {
+ return values.clone();
+ }
+
+ /**
+ * Set the matrix values.
+ *
+ * The matrix should have the standard deviations on the main diagonal, and the correlation
+ * coefficients symmetrical about the main diagonal.
+ *
+ * @param values the matrix values.
+ */
+ public void setValues(double[][] values) {
+ this.values = values.clone();
+ }
+
+ @Override
+ public Set This turns an ST 1010 encoded byte array into an SDCC structure.
+ *
+ * Note that parsing of an SDCC instance that uses Mode 1 parse control requires out-of-band
+ * information (defined in the invoking standards document) on the encoding of standard deviation
+ * values (i.e. either ST 1201 IMAP, or IEEE-754 float).
+ */
+public class SDCCParser {
+
+ private EncodingFormat standardDeviationFormat = EncodingFormat.IEEE;
+
+ /**
+ * Constructor.
+ *
+ * Creates a new SDCC Parser, initialized to use IEEE format for standard deviation encoding
+ * (applicable to Mode 1). Use {@link setStandardDeviationFormat} prior to calling {@link parse}
+ * if required.
+ */
+ public SDCCParser() {}
+
+ /**
+ * Get the standard deviation encoding format.
+ *
+ * @return the encoding format as an enumerated value.
+ */
+ public EncodingFormat getStandardDeviationFormat() {
+ return standardDeviationFormat;
+ }
+
+ /**
+ * Set the standard deviation encoding format.
+ *
+ * This is only required for "Mode 1" parsing.
+ *
+ * @param format the encoding format to use
+ */
+ public void setStandardDeviationFormat(EncodingFormat format) {
+ this.standardDeviationFormat = format;
+ }
+
+ /**
+ * Parse a byte array into an SDCC structure.
+ *
+ * This will automatically detect and handle both Mode 1 and Mode 2 parse control modes.
+ *
+ * @param bytes the ST 1010 encoded data
+ * @return SDCC equivalent to the {@code bytes} data.
+ * @throws KlvParseException if the byte array could not be parsed.
+ */
+ public SDCC parse(byte[] bytes) throws KlvParseException {
+ int offset = 0;
+ BerField berMatrix = BerDecoder.decode(bytes, offset, true);
+ int matrixSize = berMatrix.getValue();
+ offset += berMatrix.getLength();
+ SDCCParserContext parserContext;
+ int modeByte1 = bytes[offset];
+ offset += 1;
+ if (isLastByte(modeByte1)) {
+ parserContext = initialiseMode1(modeByte1);
+ } else {
+ int modeByte2 = bytes[offset];
+ offset += 1;
+ parserContext = initialiseMode2(modeByte1, modeByte2);
+ }
+ byte[] bitVector = new byte[0];
+ if ((matrixSize > 0) && parserContext.isSparseMode()) {
+ bitVector = parseBitVector(bytes, offset, matrixSize);
+ offset += bitVector.length;
+ }
+ double[][] values = new double[matrixSize][matrixSize];
+ for (int r = 0; r < values.length; r++) {
+ if (parserContext.getSdcc().getStandardDeviationFormat().equals(EncodingFormat.IEEE)) {
+ values[r][r] =
+ PrimitiveConverter.toFloat64(
+ bytes,
+ offset,
+ parserContext.getSdcc().getStandardDeviationLength());
+ } else {
+ throw new UnsupportedOperationException(
+ "Parsing of ST 1201 encoded SDCC Standard Deviation values needs to be implemented");
+ }
+ offset += parserContext.getSdcc().getStandardDeviationLength();
+ }
+
+ if (parserContext.getSdcc().getCorrelationCoefficientLength() > 0) {
+ FpEncoder correlationCoefficientDecoder =
+ new FpEncoder(
+ -1.0,
+ 1.0,
+ parserContext.getSdcc().getCorrelationCoefficientLength(),
+ OutOfRangeBehaviour.Default);
+ int bitVectorOffset = 7;
+ int bitVectorValidByte = 0;
+ for (int r = 0; r < matrixSize; r++) {
+ for (int c = r + 1; c < matrixSize; c++) {
+ if (parserContext.isSparseMode()
+ && ((bitVector[bitVectorValidByte] & (1 << bitVectorOffset)) == 0)) {
+ values[r][c] = 0.0;
+ } else {
+ if (parserContext
+ .getSdcc()
+ .getCorrelationCoefficientFormat()
+ .equals(EncodingFormat.IEEE)) {
+ values[r][c] =
+ PrimitiveConverter.toFloat64(
+ bytes,
+ offset,
+ parserContext
+ .getSdcc()
+ .getCorrelationCoefficientLength());
+ } else {
+ values[r][c] = correlationCoefficientDecoder.decode(bytes, offset);
+ }
+ offset += parserContext.getSdcc().getCorrelationCoefficientLength();
+ }
+ bitVectorOffset -= 1;
+ if (bitVectorOffset < 0) {
+ bitVectorOffset = 7;
+ bitVectorValidByte += 1;
+ }
+ values[c][r] = values[r][c];
+ }
+ }
+ }
+ parserContext.getSdcc().setValues(values);
+ return parserContext.getSdcc();
+ }
+
+ private byte[] parseBitVector(byte[] bytes, int offset, int matrixSize) {
+ int numCoefficients = matrixSize * (matrixSize - 1);
+ int numBits = numCoefficients / 2;
+ int numBytes = (numBits + 7) / Byte.SIZE;
+ byte[] bitVector = new byte[numBytes];
+ for (int i = 0; i < bitVector.length; i++) {
+ bitVector[i] = bytes[i + offset];
+ }
+ return bitVector;
+ }
+
+ private SDCCParserContext initialiseMode2(int modeByte1, int modeByte2)
+ throws KlvParseException {
+ // Mode 2 parse control
+ SDCCParserContext parserContext = new SDCCParserContext();
+ if (!isLastByte(modeByte2)) {
+ throw new KlvParseException(
+ String.format(
+ "ST 1010 parsing only supports 1 and 2 byte mode selection, got 0x%02x, 0x%02x",
+ modeByte1 & 0xFF, modeByte2 & 0xFF));
+ }
+ parserContext.setSparseMode((modeByte1 & 0x20) == 0x20);
+ int cf = (modeByte1 & 0x10) >> 4;
+ SDCC sdcc = new SDCC();
+ sdcc.setCorrelationCoefficientFormat(EncodingFormat.getEncoding(cf));
+ sdcc.setCorrelationCoefficientLength(modeByte1 & 0x0F);
+ int sf = (modeByte2 & 0x10) >> 4;
+ sdcc.setStandardDeviationFormat(EncodingFormat.getEncoding(sf));
+ sdcc.setStandardDeviationLength(modeByte2 & 0x0F);
+ parserContext.setSdcc(sdcc);
+ return parserContext;
+ }
+
+ private SDCCParserContext initialiseMode1(int modeByte1) {
+ // Mode 1 parse control
+ SDCCParserContext parserContext = new SDCCParserContext();
+ SDCC sdcc = new SDCC();
+ sdcc.setStandardDeviationLength((modeByte1 & 0x70) >> 4);
+ parserContext.setSparseMode((modeByte1 & 0x08) == 0x08);
+ sdcc.setCorrelationCoefficientLength(modeByte1 & 0x07);
+ sdcc.setCorrelationCoefficientFormat(EncodingFormat.ST1201);
+ sdcc.setStandardDeviationFormat(getStandardDeviationFormat());
+ parserContext.setSdcc(sdcc);
+ return parserContext;
+ }
+
+ private static boolean isLastByte(int modeByte1) {
+ return (modeByte1 & 0x80) == 0x00;
+ }
+}
diff --git a/st1010/src/main/java/org/jmisb/st1010/SDCCParserContext.java b/st1010/src/main/java/org/jmisb/st1010/SDCCParserContext.java
new file mode 100644
index 000000000..2d471f16e
--- /dev/null
+++ b/st1010/src/main/java/org/jmisb/st1010/SDCCParserContext.java
@@ -0,0 +1,49 @@
+package org.jmisb.st1010;
+
+/**
+ * Context for {@link SDCC} Parsing.
+ *
+ * This is used by the {@link SDCCParser} implementation, and is an internal implementation
+ * detail.
+ */
+class SDCCParserContext {
+
+ private boolean sparseMode;
+ private SDCC sdcc;
+
+ /**
+ * Get whether the byte array being parsed uses sparse mode.
+ *
+ * @return true if sparse mode is used, false if sparse mode is not used.
+ */
+ public boolean isSparseMode() {
+ return sparseMode;
+ }
+
+ /**
+ * Set whether the byte array being parsed uses spare mode.
+ *
+ * @param sparseMode true if sparse mode is used, false if sparse mode is not used.
+ */
+ public void setSparseMode(boolean sparseMode) {
+ this.sparseMode = sparseMode;
+ }
+
+ /**
+ * Get the SDCC matrix.
+ *
+ * @return the SDCC matrix.
+ */
+ public SDCC getSdcc() {
+ return sdcc;
+ }
+
+ /**
+ * Set the SDCC matrix.
+ *
+ * @param sdcc the matrix.
+ */
+ public void setSdcc(SDCC sdcc) {
+ this.sdcc = sdcc;
+ }
+}
diff --git a/st1010/src/main/java/org/jmisb/st1010/SDCCSerialiser.java b/st1010/src/main/java/org/jmisb/st1010/SDCCSerialiser.java
new file mode 100644
index 000000000..088a6b81b
--- /dev/null
+++ b/st1010/src/main/java/org/jmisb/st1010/SDCCSerialiser.java
@@ -0,0 +1,265 @@
+package org.jmisb.st1010;
+
+import org.jmisb.api.klv.ArrayBuilder;
+import org.jmisb.api.klv.Ber;
+import org.jmisb.api.klv.BerEncoder;
+import org.jmisb.api.klv.st1201.FpEncoder;
+import org.jmisb.api.klv.st1201.OutOfRangeBehaviour;
+import org.jmisb.core.klv.PrimitiveConverter;
+
+/**
+ * Serialization support for {@link SDCC} instances.
+ *
+ * ST 1010 has two encoding formats, referred to as "Parse Modes". Mode 1 is more compact but
+ * Mode 2 parse control is more flexible, and is required by some standards, and therefore is the
+ * default. Use {@link setPreferMode1} to select which mode is preferred. There are modes and bit
+ * lengths that cannot be supported in Mode 1 control. In particular, specifying lengths > 7, or
+ * {@code EncodingFormat.IEEE} for correlation coefficients will result in Mode 2 being used
+ * (irrespective of the preferred mode).
+ *
+ * ST 1010 also supports not encoding correlation coefficients that are 0.0 (i.e. no
+ * correlation), using "sparse mode". This requires adding a bit mask which is not always required.
+ * By default, sparse mode will be used if it is more efficient (i.e. would save bytes overall). It
+ * is also possible to disable use of sparse mode using {@link setSparseEnabled}. The result is that
+ * sparse mode is only used if both it would be more efficient and it is not disabled.
+ */
+public class SDCCSerialiser {
+ private boolean preferMode1 = false;
+ private boolean sparseEnabled = true;
+ private static final byte NOT_FINAL_BYTE = (byte) (1 << 7);
+ private static final byte FINAL_BYTE = 0;
+ private static final byte MODE1_SLEN_SHIFT = 4;
+ private static final byte MODE1_SPARSE_SHIFT = 3;
+ private static final byte MODE2_SPARSE_SHIFT = 5;
+ private static final byte CORRELATION_COEFFICIENT_FORMAT_SHIFT = 4;
+ private static final byte STANDARD_DEVIATION_FORMAT_SHIFT = 4;
+ private static final int MAX_MODE1_CORRELATION_COEFFICIENT_LEN = 7;
+ private static final int MAX_MODE1_STANDARD_DEVIATION_LEN = 7;
+
+ /**
+ * Get whether encoding should prefer Mode 1 parse control.
+ *
+ * @return true if encoding will prefer Mode 1 parse control.
+ */
+ public boolean isPreferMode1() {
+ return preferMode1;
+ }
+
+ /**
+ * Get whether sparse representation is allowed.
+ *
+ * SDCC can reduce space by omitting the encoding of uncorrelated variable covariances (i.e.
+ * 0 values). See MISB ST 1010.3 for more information.
+ *
+ * @return true if sparse encoding is enabled, otherwise false.
+ */
+ public boolean isSparseEnabled() {
+ return sparseEnabled;
+ }
+
+ /**
+ * Set whether sparse representation is allowed.
+ *
+ * SDCC can reduce space by omitting the encoding of uncorrelated variable covariances (i.e.
+ * 0 values). See MISB ST 1010.3 for more information.
+ *
+ * When serializing, sparse representation will be used if it is enabled and would be more
+ * efficient than serializing the 0 values (i.e. save bytes). If it is not enabled (set to
+ * false), full values will be used. If it is not more efficient, full values will be used.
+ *
+ * Sparse representation is enabled by default.
+ *
+ * @param sparseEnabled true to enable sparse representation, false to disable sparse
+ * representation.
+ */
+ public void setSparseEnabled(boolean sparseEnabled) {
+ this.sparseEnabled = sparseEnabled;
+ }
+
+ /**
+ * Set whether encoding should prefer Mode 1 parse control.
+ *
+ * In general, Mode 2 parse control is more flexible, and is required by some standards, and
+ * therefore is the default. In addition, there are modes and bit lengths that cannot be
+ * supported in Mode 1 control. In particular, specifying lengths > 7, or {@code
+ * EncodingFormat.IEEE} for correlation coefficients will result in Mode 2 being used,
+ * irrespective of this setting.
+ *
+ * If this option is enabled, and the required encoding can work with Mode 1, it will be
+ * used.
+ *
+ * @param preferMode1 true to enable Mode 1 parse control as an option, false to disable Mode 1
+ * parse control.
+ */
+ public void setPreferMode1(boolean preferMode1) {
+ this.preferMode1 = preferMode1;
+ }
+
+ /**
+ * Serialize a Standard Deviation and Cross Correlation instance to bytes.
+ *
+ * @param sdcc the matrix of values to encode
+ * @return the corresponding byte array representation
+ */
+ public byte[] encode(SDCC sdcc) {
+ if (sdcc.getValues().length != sdcc.getValues()[0].length) {
+ throw new IllegalArgumentException("SDCC requires a square input array");
+ }
+ ArrayBuilder arrayBuilder = new ArrayBuilder();
+ byte[] matrixBytes = BerEncoder.encode(sdcc.getValues().length, Ber.OID);
+ arrayBuilder.append(matrixBytes);
+ boolean usingSparse = isSparseEnabled() && sparseWouldSaveSpace(sdcc);
+ arrayBuilder.append(getModeBytes(sdcc, usingSparse));
+ byte[] bitVector = new byte[0];
+ if (usingSparse) {
+ bitVector = new byte[getBitVectorLength(sdcc)];
+ // The values will get filled in as we work through the coefficients
+ arrayBuilder.append(bitVector);
+ }
+ for (int r = 0; r < sdcc.getValues().length; r++) {
+ // TODO: ST1201
+ double standardDeviation = sdcc.getValues()[r][r];
+ switch (sdcc.getStandardDeviationLength()) {
+ case 4:
+ arrayBuilder.append(
+ PrimitiveConverter.float32ToBytes((float) standardDeviation));
+ break;
+ case 8:
+ arrayBuilder.append(PrimitiveConverter.float64ToBytes(standardDeviation));
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "SDCC IEEE format length 4 and 8 is currently supported. More work required for 2 byte. Other lengths not sensible.");
+ }
+ }
+ FpEncoder correlationCoefficientEncoder =
+ new FpEncoder(
+ -1.0,
+ 1.0,
+ sdcc.getCorrelationCoefficientLength(),
+ OutOfRangeBehaviour.Default);
+ int bitVectorOffset = 7;
+ int bitVectorValidByte = 0;
+ for (int r = 0; r < sdcc.getValues().length; r++) {
+ for (int c = r + 1; c < sdcc.getValues().length; c++) {
+
+ double coef = sdcc.getValues()[r][c];
+ if (usingSparse) {
+ if (coef != 0.0) {
+ bitVector[bitVectorValidByte] |= 1 << bitVectorOffset;
+ }
+ bitVectorOffset -= 1;
+ if (bitVectorOffset < 0) {
+ bitVectorValidByte += 1;
+ bitVectorOffset = 7;
+ }
+ if (coef == 0.0) {
+ continue;
+ }
+ }
+ if (sdcc.getCorrelationCoefficientFormat().equals(EncodingFormat.IEEE)) {
+ switch (sdcc.getCorrelationCoefficientLength()) {
+ case 4:
+ arrayBuilder.append(PrimitiveConverter.float32ToBytes((float) coef));
+ break;
+ case 8:
+ arrayBuilder.append(PrimitiveConverter.float64ToBytes(coef));
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "SDCC IEEE format length 4 and 8 is currently supported. More work required for 2 byte. Other lengths not sensible.");
+ }
+ } else {
+ arrayBuilder.append(correlationCoefficientEncoder.encode(coef));
+ }
+ }
+ }
+ return arrayBuilder.toBytes();
+ }
+
+ private byte[] getModeBytes(SDCC sdcc, boolean usingSparse) {
+ if (isPreferMode1() && isMode1Compatible(sdcc)) {
+ return getModeBytesMode1(sdcc, usingSparse);
+ }
+ return getModeBytesMode2(sdcc, usingSparse);
+ }
+
+ private byte[] getModeBytesMode1(SDCC sdcc, boolean usingSparse) {
+ byte[] parseControl = new byte[1];
+ parseControl[0] = FINAL_BYTE;
+ byte slen = (byte) (sdcc.getStandardDeviationLength() & 0x07);
+ parseControl[0] |= (slen << MODE1_SLEN_SHIFT);
+ if (usingSparse) {
+ parseControl[0] |= 1 << MODE1_SPARSE_SHIFT;
+ }
+ byte clen = (byte) (sdcc.getCorrelationCoefficientLength() & 0x07);
+ parseControl[0] |= clen;
+ return parseControl;
+ }
+
+ private byte[] getModeBytesMode2(SDCC sdcc, boolean usingSparse) {
+ byte[] parseControl = new byte[2];
+ parseControl[0] = NOT_FINAL_BYTE;
+ parseControl[1] = FINAL_BYTE;
+ int numCoefficients = getNumCoefficients(sdcc);
+ if (numCoefficients != 0) {
+ if (usingSparse) {
+ parseControl[0] |= 1 << MODE2_SPARSE_SHIFT;
+ }
+ parseControl[0] |=
+ (sdcc.getCorrelationCoefficientFormat().getValue()
+ << CORRELATION_COEFFICIENT_FORMAT_SHIFT);
+ byte clen = (byte) (sdcc.getCorrelationCoefficientLength() & 0x0F);
+ parseControl[0] |= clen;
+ }
+ parseControl[1] |=
+ (sdcc.getStandardDeviationFormat().getValue() << STANDARD_DEVIATION_FORMAT_SHIFT);
+ byte slen = (byte) (sdcc.getStandardDeviationLength() & 0x0F);
+ parseControl[1] |= slen;
+ return parseControl;
+ }
+
+ private int getNumCoefficients(SDCC sdcc) {
+ return (sdcc.getValues().length * (sdcc.getValues().length - 1));
+ }
+
+ private boolean sparseWouldSaveSpace(SDCC sdcc) {
+ int numZeros = 0;
+ for (int r = 0; r < sdcc.getValues().length; r++) {
+ for (int c = r + 1; c < sdcc.getValues().length; c++) {
+ if (sdcc.getValues()[r][c] == 0.0) {
+ numZeros += 1;
+ }
+ }
+ }
+ return (sdcc.getCorrelationCoefficientLength() * numZeros > getBitVectorLength(sdcc));
+ }
+
+ /**
+ * Get the length of the BitVector.
+ *
+ * @param sdcc the SDCC to calculate for
+ * @return the length in bytes
+ */
+ private int getBitVectorLength(SDCC sdcc) {
+ int numBits = getNumCoefficients(sdcc) / 2;
+ int numBytes = (numBits + 7) / Byte.SIZE;
+ return numBytes;
+ }
+
+ private boolean isMode1Compatible(SDCC sdcc) {
+ if (sdcc.getCorrelationCoefficientFormat().equals(EncodingFormat.IEEE)) {
+ return false;
+ }
+ if (sdcc.getStandardDeviationFormat().equals(EncodingFormat.ST1201)) {
+ return false;
+ }
+ if (sdcc.getCorrelationCoefficientLength() > MAX_MODE1_CORRELATION_COEFFICIENT_LEN) {
+ return false;
+ }
+ if (sdcc.getStandardDeviationLength() > MAX_MODE1_STANDARD_DEVIATION_LEN) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/st1010/src/main/java/org/jmisb/st1010/SDCCValueIdentifierKey.java b/st1010/src/main/java/org/jmisb/st1010/SDCCValueIdentifierKey.java
new file mode 100644
index 000000000..b322ecae9
--- /dev/null
+++ b/st1010/src/main/java/org/jmisb/st1010/SDCCValueIdentifierKey.java
@@ -0,0 +1,86 @@
+package org.jmisb.st1010;
+
+import java.util.Comparator;
+import org.jmisb.api.klv.IKlvKey;
+
+/**
+ * Pseudo-key item for SDCC value identification.
+ *
+ * Each identifier corresponds to one item in the SDCC value matrix (i.e. a unique row/column
+ * combination).
+ */
+public class SDCCValueIdentifierKey implements IKlvKey, Comparable This acts as an adapter (facade) around the double value, to present it as an IKlvValue.
+ */
+public class SDCCValueWrap implements IKlvValue {
+
+ private final int row;
+ private final int column;
+ private final double value;
+
+ /**
+ * Constructor.
+ *
+ * @param row the row number
+ * @param column the column number
+ * @param value the value
+ */
+ public SDCCValueWrap(final int row, final int column, final double value) {
+ this.row = row;
+ this.column = column;
+ this.value = value;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return String.format("[%d][%d]", row, column);
+ }
+
+ @Override
+ public String getDisplayableValue() {
+ return String.format("%.3f", value);
+ }
+
+ /**
+ * Row index.
+ *
+ * @return the row index of the value
+ */
+ public int getRow() {
+ return row;
+ }
+
+ /**
+ * Column index.
+ *
+ * @return the column index of the value
+ */
+ public int getColumn() {
+ return column;
+ }
+
+ /**
+ * Value.
+ *
+ * @return the wrapped value, as a double.
+ */
+ public double getValue() {
+ return value;
+ }
+}
diff --git a/st1010/src/main/java/org/jmisb/st1010/package-info.java b/st1010/src/main/java/org/jmisb/st1010/package-info.java
new file mode 100644
index 000000000..681e71c67
--- /dev/null
+++ b/st1010/src/main/java/org/jmisb/st1010/package-info.java
@@ -0,0 +1,17 @@
+/**
+ * ST 1010: Generalized Standard Deviation and Correlation Coefficient Metadata.
+ *
+ * This standard (ST) defines a bit-efficient method for transmitting standard deviation and
+ * correlation coefficient data.
+ *
+ * In support of this method, a Standard Deviation and Correlation Coefficient Floating Length
+ * Pack (SDCC-FLP) construct is defined. The construct leverages the symmetry of the
+ * variance-covariance matrix and the fixed data range of the correlation coefficients to reduce the
+ * number of bytes transmitted, in effect compressing the data. This method is, therefore, not
+ * extendable to more generic matrix cases.
+ *
+ * This ST is dependent on context from an invoking standard (or other document), called a Parent
+ * Document, which provides a list of values (random variables) that can have corresponding standard
+ * deviation and correlation coefficients; this list of random variables is called the Source List.
+ */
+package org.jmisb.st1010;
diff --git a/st1010/src/test/java/org/jmisb/st1010/EncodingFormatTest.java b/st1010/src/test/java/org/jmisb/st1010/EncodingFormatTest.java
new file mode 100644
index 000000000..3677c9e86
--- /dev/null
+++ b/st1010/src/test/java/org/jmisb/st1010/EncodingFormatTest.java
@@ -0,0 +1,48 @@
+package org.jmisb.st1010;
+
+import static org.testng.Assert.*;
+
+import org.jmisb.api.common.KlvParseException;
+import org.testng.annotations.Test;
+
+/** Unit tests for the EncodingFormat enum. */
+public class EncodingFormatTest {
+
+ public EncodingFormatTest() {}
+
+ @Test
+ public void checkST1201() {
+ EncodingFormat uut = EncodingFormat.ST1201;
+ assertEquals(uut.getValue(), 1);
+ }
+
+ @Test
+ public void checkST1201Lookup() throws KlvParseException {
+ EncodingFormat uut = EncodingFormat.getEncoding(1);
+ assertEquals(uut, EncodingFormat.ST1201);
+ assertEquals(uut.getValue(), 1);
+ }
+
+ @Test
+ public void checkIEEE() {
+ EncodingFormat uut = EncodingFormat.IEEE;
+ assertEquals(uut.getValue(), 0);
+ }
+
+ @Test
+ public void checkIEEELookup() throws KlvParseException {
+ EncodingFormat uut = EncodingFormat.getEncoding(0);
+ assertEquals(uut, EncodingFormat.IEEE);
+ assertEquals(uut.getValue(), 0);
+ }
+
+ @Test(expectedExceptions = KlvParseException.class)
+ public void checkBadLookup2() throws KlvParseException {
+ EncodingFormat.getEncoding(2);
+ }
+
+ @Test(expectedExceptions = KlvParseException.class)
+ public void checkBadLookupNeg() throws KlvParseException {
+ EncodingFormat.getEncoding(-1);
+ }
+}
diff --git a/st1010/src/test/java/org/jmisb/st1010/Mode1ParseControlTest.java b/st1010/src/test/java/org/jmisb/st1010/Mode1ParseControlTest.java
new file mode 100644
index 000000000..5b44c13a8
--- /dev/null
+++ b/st1010/src/test/java/org/jmisb/st1010/Mode1ParseControlTest.java
@@ -0,0 +1,22 @@
+package org.jmisb.st1010;
+
+import static org.testng.Assert.*;
+
+import org.jmisb.api.common.KlvParseException;
+import org.testng.annotations.Test;
+
+public class Mode1ParseControlTest {
+
+ public Mode1ParseControlTest() {}
+
+ @Test
+ public void check4B() throws KlvParseException {
+ SDCCParser parser = new SDCCParser();
+ parser.setStandardDeviationFormat(EncodingFormat.IEEE);
+ SDCC result = parser.parse(new byte[] {0x00, 0x4B});
+ assertEquals(result.getStandardDeviationLength(), 4);
+ assertEquals(result.getCorrelationCoefficientLength(), 3);
+ assertEquals(result.getStandardDeviationFormat(), EncodingFormat.IEEE);
+ assertEquals(result.getCorrelationCoefficientFormat(), EncodingFormat.ST1201);
+ }
+}
diff --git a/st1010/src/test/java/org/jmisb/st1010/Mode2ParseControlTest.java b/st1010/src/test/java/org/jmisb/st1010/Mode2ParseControlTest.java
new file mode 100644
index 000000000..06bd3080a
--- /dev/null
+++ b/st1010/src/test/java/org/jmisb/st1010/Mode2ParseControlTest.java
@@ -0,0 +1,31 @@
+package org.jmisb.st1010;
+
+import static org.testng.Assert.*;
+
+import org.jmisb.api.common.KlvParseException;
+import org.testng.annotations.Test;
+
+public class Mode2ParseControlTest {
+
+ public Mode2ParseControlTest() {}
+
+ @Test
+ public void checkB308() throws KlvParseException {
+ SDCCParser parser = new SDCCParser();
+ SDCC result = parser.parse(new byte[] {(byte) 0x00, (byte) 0xB3, (byte) 0x08});
+ assertEquals(result.getStandardDeviationLength(), 8);
+ assertEquals(result.getCorrelationCoefficientLength(), 3);
+ assertEquals(result.getStandardDeviationFormat(), EncodingFormat.IEEE);
+ assertEquals(result.getCorrelationCoefficientFormat(), EncodingFormat.ST1201);
+ }
+
+ @Test
+ public void check8413() throws KlvParseException {
+ SDCCParser parser = new SDCCParser();
+ SDCC result = parser.parse(new byte[] {(byte) 0x00, (byte) 0x84, (byte) 0x13});
+ assertEquals(result.getStandardDeviationLength(), 3);
+ assertEquals(result.getCorrelationCoefficientLength(), 4);
+ assertEquals(result.getStandardDeviationFormat(), EncodingFormat.ST1201);
+ assertEquals(result.getCorrelationCoefficientFormat(), EncodingFormat.IEEE);
+ }
+}
diff --git a/st1010/src/test/java/org/jmisb/st1010/SDCCParserTest.java b/st1010/src/test/java/org/jmisb/st1010/SDCCParserTest.java
new file mode 100644
index 000000000..3c3b1b747
--- /dev/null
+++ b/st1010/src/test/java/org/jmisb/st1010/SDCCParserTest.java
@@ -0,0 +1,236 @@
+package org.jmisb.st1010;
+
+import static org.testng.Assert.*;
+
+import org.jmisb.api.common.KlvParseException;
+import org.testng.annotations.Test;
+
+public class SDCCParserTest {
+
+ public SDCCParserTest() {}
+
+ @Test
+ public void checkSingleStandardDeviation() throws KlvParseException {
+ SDCCParser parser = new SDCCParser();
+ SDCC result =
+ parser.parse(
+ new byte[] {
+ (byte) 0x01,
+ (byte) 0x80,
+ (byte) 0x04,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD
+ });
+ assertEquals(result.getStandardDeviationLength(), 4);
+ assertEquals(result.getCorrelationCoefficientLength(), 0);
+ assertEquals(result.getStandardDeviationFormat(), EncodingFormat.IEEE);
+ assertEquals(result.getCorrelationCoefficientFormat(), EncodingFormat.IEEE);
+ assertValuesAreEqual(result.getValues(), new double[][] {{3.2}});
+ }
+
+ @Test(
+ expectedExceptions = KlvParseException.class,
+ expectedExceptionsMessageRegExp =
+ "ST 1010 parsing only supports 1 and 2 byte mode selection, got 0x80, 0x84")
+ public void checkBadModeControl() throws KlvParseException {
+ SDCCParser parser = new SDCCParser();
+ parser.parse(
+ new byte[] {
+ (byte) 0x01,
+ (byte) 0x80,
+ (byte) 0x84,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD
+ });
+ }
+
+ @Test
+ public void checkSingleStandardDeviationDouble() throws KlvParseException {
+ SDCCParser parser = new SDCCParser();
+ SDCC result =
+ parser.parse(
+ new byte[] {
+ (byte) 0x01,
+ (byte) 0x90,
+ (byte) 0x08,
+ (byte) 0x40,
+ (byte) 0x2A,
+ (byte) 0x66,
+ (byte) 0x66,
+ (byte) 0x66,
+ (byte) 0x66,
+ (byte) 0x66,
+ (byte) 0x66
+ });
+ assertEquals(result.getStandardDeviationLength(), 8);
+ assertEquals(result.getCorrelationCoefficientLength(), 0);
+ assertEquals(result.getStandardDeviationFormat(), EncodingFormat.IEEE);
+ assertEquals(result.getCorrelationCoefficientFormat(), EncodingFormat.ST1201);
+ assertValuesAreEqual(result.getValues(), new double[][] {{13.2}});
+ }
+
+ @Test
+ public void checkTwoVariable() throws KlvParseException {
+ SDCCParser parser = new SDCCParser();
+ SDCC result =
+ parser.parse(
+ new byte[] {
+ (byte) 0x02,
+ (byte) 0x84,
+ (byte) 0x04,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD,
+ (byte) 0x41,
+ (byte) 0x81,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x3e,
+ (byte) 0xfa,
+ (byte) 0xe1,
+ (byte) 0x48
+ });
+ assertEquals(result.getStandardDeviationLength(), 4);
+ assertEquals(result.getCorrelationCoefficientLength(), 4);
+ assertEquals(result.getStandardDeviationFormat(), EncodingFormat.IEEE);
+ assertEquals(result.getCorrelationCoefficientFormat(), EncodingFormat.IEEE);
+ assertValuesAreEqual(result.getValues(), new double[][] {{3.2, 0.49}, {0.49, 16.2}});
+ }
+
+ @Test
+ public void checkTwoVariableMode1() throws KlvParseException {
+ SDCCParser parser = new SDCCParser();
+ parser.setStandardDeviationFormat(EncodingFormat.IEEE);
+ SDCC result =
+ parser.parse(
+ new byte[] {
+ (byte) 0x02,
+ (byte) 0x42,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD,
+ (byte) 0x41,
+ (byte) 0x81,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x5f,
+ (byte) 0x5c
+ });
+ assertEquals(result.getStandardDeviationLength(), 4);
+ assertEquals(result.getCorrelationCoefficientLength(), 2);
+ assertEquals(result.getStandardDeviationFormat(), EncodingFormat.IEEE);
+ assertEquals(result.getCorrelationCoefficientFormat(), EncodingFormat.ST1201);
+ assertValuesAreEqual(result.getValues(), new double[][] {{3.2, 0.49}, {0.49, 16.2}});
+ }
+
+ private void assertValuesAreEqual(double[][] actual, double[][] expected) {
+ assertEquals(actual.length, expected.length);
+ if (expected.length > 0) {
+ assertEquals(actual[0].length, expected[0].length);
+ for (int r = 0; r < expected.length; r++) {
+ for (int c = 0; c < expected[r].length; c++) {
+ assertEquals(actual[r][c], expected[r][c], 0.00001);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void checkThreeVariableSparse() throws KlvParseException {
+ SDCCParser parser = new SDCCParser();
+ SDCC result =
+ parser.parse(
+ new byte[] {
+ (byte) 0x03,
+ (byte) 0xB3,
+ (byte) 0x04,
+ (byte) 0b01000000,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD,
+ (byte) 0x41,
+ (byte) 0x81,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x43,
+ (byte) 0x6a,
+ (byte) 0x33,
+ (byte) 0x33,
+ (byte) 0x5f,
+ (byte) 0x5c,
+ (byte) 0x28
+ });
+ assertEquals(result.getStandardDeviationLength(), 4);
+ assertEquals(result.getCorrelationCoefficientLength(), 3);
+ assertEquals(result.getStandardDeviationFormat(), EncodingFormat.IEEE);
+ assertEquals(result.getCorrelationCoefficientFormat(), EncodingFormat.ST1201);
+ assertValuesAreEqual(
+ result.getValues(),
+ new double[][] {
+ {3.2, 0.0, 0.49},
+ {0.0, 16.2, 0.0},
+ {0.49, 0.0, 234.2}
+ });
+ }
+
+ @Test
+ public void checkFiveVariableSparse() throws KlvParseException {
+ SDCCParser parser = new SDCCParser();
+ SDCC result =
+ parser.parse(
+ new byte[] {
+ (byte) 0x05,
+ (byte) 0xB3,
+ (byte) 0x04,
+ (byte) 0b01000000,
+ (byte) 0b10000000,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD,
+ (byte) 0x41,
+ (byte) 0x81,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x43,
+ (byte) 0x6a,
+ (byte) 0x33,
+ (byte) 0x33,
+ (byte) 0x3e,
+ (byte) 0xa3,
+ (byte) 0xd7,
+ (byte) 0x0a,
+ (byte) 0x40,
+ (byte) 0xc9,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x5f,
+ (byte) 0x5c,
+ (byte) 0x28,
+ (byte) 0x80,
+ (byte) 0x00,
+ (byte) 0x00
+ });
+ assertEquals(result.getStandardDeviationLength(), 4);
+ assertEquals(result.getCorrelationCoefficientLength(), 3);
+ assertEquals(result.getStandardDeviationFormat(), EncodingFormat.IEEE);
+ assertEquals(result.getCorrelationCoefficientFormat(), EncodingFormat.ST1201);
+ assertValuesAreEqual(
+ result.getValues(),
+ new double[][] {
+ {3.2, 0.0, 0.49, 0.0, 0.0},
+ {0.0, 16.2, 0.0, 0.0, 0.0},
+ {0.49, 0.0, 234.2, 0.0, 1.0},
+ {0.0, 0.0, 0.0, 0.32, 0.0},
+ {0.0, 0.0, 1.0, 0.0, 6.3}
+ });
+ }
+}
diff --git a/st1010/src/test/java/org/jmisb/st1010/SDCCTest.java b/st1010/src/test/java/org/jmisb/st1010/SDCCTest.java
new file mode 100644
index 000000000..18a49fca6
--- /dev/null
+++ b/st1010/src/test/java/org/jmisb/st1010/SDCCTest.java
@@ -0,0 +1,97 @@
+package org.jmisb.st1010;
+
+import static org.testng.Assert.*;
+
+import org.testng.annotations.Test;
+
+/** Unit tests for SDCC. */
+public class SDCCTest {
+
+ public SDCCTest() {}
+
+ @Test
+ public void checkDefaults() {
+ SDCC uut = new SDCC();
+ assertEquals(uut.getCorrelationCoefficientFormat(), EncodingFormat.ST1201);
+ assertEquals(uut.getStandardDeviationFormat(), EncodingFormat.IEEE);
+ assertEquals(uut.getCorrelationCoefficientLength(), 3);
+ assertEquals(uut.getStandardDeviationLength(), 4);
+ assertEquals(uut.getValues().length, 0);
+ }
+
+ @Test
+ public void checkSetterAndCopy() {
+ SDCC uut = new SDCC();
+ uut.setCorrelationCoefficientFormat(EncodingFormat.IEEE);
+ uut.setStandardDeviationFormat(EncodingFormat.ST1201);
+ uut.setCorrelationCoefficientLength(8);
+ uut.setStandardDeviationLength(3);
+ uut.setValues(new double[][] {{5.3, 0.2}, {0.2, 1.6}});
+ assertEquals(uut.getCorrelationCoefficientFormat(), EncodingFormat.IEEE);
+ assertEquals(uut.getStandardDeviationFormat(), EncodingFormat.ST1201);
+ assertEquals(uut.getCorrelationCoefficientLength(), 8);
+ assertEquals(uut.getStandardDeviationLength(), 3);
+ assertEquals(uut.getValues().length, 2);
+ assertEquals(uut.getValues()[0].length, 2);
+ assertEquals(uut.getValues()[0][0], 5.3);
+ assertEquals(uut.getValues()[0][1], 0.2);
+ assertEquals(uut.getValues()[1][0], 0.2);
+ assertEquals(uut.getValues()[1][1], 1.6);
+ assertEquals(uut.getIdentifiers().size(), 4);
+ boolean found00 = false;
+ boolean found01 = false;
+ boolean found10 = false;
+ boolean found11 = false;
+ for (SDCCValueIdentifierKey identifier : uut.getIdentifiers()) {
+ if ((identifier.getRow() == 0) && (identifier.getColumn() == 0)) {
+ found00 = true;
+ assertEquals(uut.getField(identifier).getDisplayName(), "[0][0]");
+ assertEquals(uut.getField(identifier).getDisplayableValue(), "5.300");
+ assertEquals(uut.getField(identifier).getRow(), 0);
+ assertEquals(uut.getField(identifier).getColumn(), 0);
+ assertEquals(uut.getField(identifier).getValue(), 5.3, 0.0001);
+ }
+ if ((identifier.getRow() == 0) && (identifier.getColumn() == 1)) {
+ found01 = true;
+ assertEquals(uut.getField(identifier).getDisplayName(), "[0][1]");
+ assertEquals(uut.getField(identifier).getDisplayableValue(), "0.200");
+ assertEquals(uut.getField(identifier).getRow(), 0);
+ assertEquals(uut.getField(identifier).getColumn(), 1);
+ assertEquals(uut.getField(identifier).getValue(), 0.2, 0.0001);
+ }
+ if ((identifier.getRow() == 1) && (identifier.getColumn() == 0)) {
+ found10 = true;
+ assertEquals(uut.getField(identifier).getDisplayName(), "[1][0]");
+ assertEquals(uut.getField(identifier).getDisplayableValue(), "0.200");
+ assertEquals(uut.getField(identifier).getRow(), 1);
+ assertEquals(uut.getField(identifier).getColumn(), 0);
+ assertEquals(uut.getField(identifier).getValue(), 0.2, 0.0001);
+ }
+
+ if ((identifier.getRow() == 1) && (identifier.getColumn() == 1)) {
+ found11 = true;
+ assertEquals(uut.getField(identifier).getDisplayName(), "[1][1]");
+ assertEquals(uut.getField(identifier).getDisplayableValue(), "1.600");
+ assertEquals(uut.getField(identifier).getRow(), 1);
+ assertEquals(uut.getField(identifier).getColumn(), 1);
+ assertEquals(uut.getField(identifier).getValue(), 1.6, 0.0001);
+ }
+ }
+ assertTrue(found00);
+ assertTrue(found01);
+ assertTrue(found10);
+ assertTrue(found11);
+ SDCC copy = new SDCC(uut);
+ assertEquals(copy.getCorrelationCoefficientFormat(), EncodingFormat.IEEE);
+ assertEquals(copy.getStandardDeviationFormat(), EncodingFormat.ST1201);
+ assertEquals(copy.getCorrelationCoefficientLength(), 8);
+ assertEquals(copy.getStandardDeviationLength(), 3);
+ assertEquals(copy.getValues().length, 2);
+ assertEquals(copy.getValues()[0].length, 2);
+ assertEquals(copy.getValues()[0][0], 5.3);
+ assertEquals(copy.getValues()[0][1], 0.2);
+ assertEquals(copy.getValues()[1][0], 0.2);
+ assertEquals(copy.getValues()[1][1], 1.6);
+ assertEquals(uut.getIdentifiers().size(), 4);
+ }
+}
diff --git a/st1010/src/test/java/org/jmisb/st1010/SDCCValueIdentifierKeyTest.java b/st1010/src/test/java/org/jmisb/st1010/SDCCValueIdentifierKeyTest.java
new file mode 100644
index 000000000..d9e87f9c4
--- /dev/null
+++ b/st1010/src/test/java/org/jmisb/st1010/SDCCValueIdentifierKeyTest.java
@@ -0,0 +1,49 @@
+package org.jmisb.st1010;
+
+import static org.testng.Assert.*;
+
+import org.testng.annotations.Test;
+
+/** Unit tests for SDCCValueIdentifierKey. */
+public class SDCCValueIdentifierKeyTest {
+
+ @Test
+ public void checkIdentifier() {
+ SDCCValueIdentifierKey uut = new SDCCValueIdentifierKey(3, 1, 4);
+ assertEquals(uut.getIdentifier(), 13);
+ }
+
+ @Test
+ public void checkRowAndColumn() {
+ SDCCValueIdentifierKey uut = new SDCCValueIdentifierKey(3, 1, 4);
+ assertEquals(uut.getRow(), 3);
+ assertEquals(uut.getColumn(), 1);
+ }
+
+ @Test
+ public void checkHash() {
+ SDCCValueIdentifierKey uut = new SDCCValueIdentifierKey(3, 1, 4);
+ assertEquals(uut.hashCode(), 22647);
+ }
+
+ @Test
+ public void checkEquals() {
+ SDCCValueIdentifierKey uut = new SDCCValueIdentifierKey(3, 1, 4);
+ assertFalse(uut.equals(null));
+ assertTrue(uut.equals(new SDCCValueIdentifierKey(3, 1, 4)));
+ assertFalse(uut.equals(new SDCCValueIdentifierKey(2, 1, 4)));
+ assertFalse(uut.equals(new SDCCValueIdentifierKey(1, 3, 4)));
+ assertFalse(uut.equals(new SDCCValueIdentifierKey(3, 3, 4)));
+ assertFalse(uut.equals(3));
+ assertTrue(uut.equals(uut));
+ }
+
+ @Test
+ public void checkComparison() {
+ SDCCValueIdentifierKey uut1 = new SDCCValueIdentifierKey(1, 3, 4);
+ SDCCValueIdentifierKey uut2 = new SDCCValueIdentifierKey(1, 2, 4);
+ assertEquals(uut1.compareTo(uut2), 1);
+ assertEquals(uut2.compareTo(uut1), -1);
+ assertEquals(uut1.compareTo(uut1), 0);
+ }
+}
diff --git a/st1010/src/test/java/org/jmisb/st1010/SDCCWriterTest.java b/st1010/src/test/java/org/jmisb/st1010/SDCCWriterTest.java
new file mode 100644
index 000000000..0592ae1e0
--- /dev/null
+++ b/st1010/src/test/java/org/jmisb/st1010/SDCCWriterTest.java
@@ -0,0 +1,563 @@
+package org.jmisb.st1010;
+
+import static org.testng.Assert.*;
+
+import org.jmisb.api.common.KlvParseException;
+import org.testng.annotations.Test;
+
+/** Unit tests for SDCCWriter */
+public class SDCCWriterTest {
+
+ public SDCCWriterTest() {}
+
+ @Test
+ public void checkSingleStandardDeviation() throws KlvParseException {
+ SDCC sdcc = new SDCC();
+ sdcc.setValues(new double[][] {{3.2}});
+ sdcc.setStandardDeviationLength(4);
+ sdcc.setStandardDeviationFormat(EncodingFormat.IEEE);
+ SDCCSerialiser writer = new SDCCSerialiser();
+ byte[] encoded = writer.encode(sdcc);
+ assertEquals(
+ encoded,
+ new byte[] {
+ (byte) 0x01,
+ (byte) 0x80,
+ (byte) 0x04,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD
+ });
+ }
+
+ @Test
+ public void checkSingleStandardDeviationDouble() throws KlvParseException {
+ SDCC sdcc = new SDCC();
+ sdcc.setValues(new double[][] {{13.2}});
+ sdcc.setStandardDeviationLength(8);
+ sdcc.setStandardDeviationFormat(EncodingFormat.IEEE);
+ SDCCSerialiser writer = new SDCCSerialiser();
+ byte[] encoded = writer.encode(sdcc);
+ assertEquals(
+ encoded,
+ new byte[] {
+ (byte) 0x01,
+ (byte) 0x80,
+ (byte) 0x08,
+ (byte) 0x40,
+ (byte) 0x2A,
+ (byte) 0x66,
+ (byte) 0x66,
+ (byte) 0x66,
+ (byte) 0x66,
+ (byte) 0x66,
+ (byte) 0x66
+ });
+ }
+
+ @Test
+ public void checkSingleStandardDeviationDoubleMode1Fallback() throws KlvParseException {
+ SDCC sdcc = new SDCC();
+ sdcc.setValues(new double[][] {{13.2}});
+ sdcc.setStandardDeviationLength(8);
+ sdcc.setStandardDeviationFormat(EncodingFormat.IEEE);
+ SDCCSerialiser writer = new SDCCSerialiser();
+ writer.setPreferMode1(true);
+ byte[] encoded = writer.encode(sdcc);
+ assertEquals(
+ encoded,
+ new byte[] {
+ (byte) 0x01,
+ (byte) 0x80,
+ (byte) 0x08,
+ (byte) 0x40,
+ (byte) 0x2A,
+ (byte) 0x66,
+ (byte) 0x66,
+ (byte) 0x66,
+ (byte) 0x66,
+ (byte) 0x66,
+ (byte) 0x66
+ });
+ }
+
+ @Test
+ public void checkTwoVariable() throws KlvParseException {
+ SDCC sdcc = new SDCC();
+ sdcc.setValues(new double[][] {{3.2, 0.49}, {0.49, 16.2}});
+ sdcc.setStandardDeviationLength(4);
+ sdcc.setStandardDeviationFormat(EncodingFormat.IEEE);
+ sdcc.setCorrelationCoefficientLength(4);
+ sdcc.setCorrelationCoefficientFormat(EncodingFormat.IEEE);
+ SDCCSerialiser writer = new SDCCSerialiser();
+ byte[] encoded = writer.encode(sdcc);
+ assertEquals(
+ encoded,
+ new byte[] {
+ (byte) 0x02,
+ (byte) 0x84,
+ (byte) 0x04,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD,
+ (byte) 0x41,
+ (byte) 0x81,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x3e,
+ (byte) 0xfa,
+ (byte) 0xe1,
+ (byte) 0x48
+ });
+ }
+
+ @Test
+ public void checkTwoVariableST1201() throws KlvParseException {
+ SDCC sdcc = new SDCC();
+ sdcc.setValues(new double[][] {{3.2, 0.49}, {0.49, 16.2}});
+ sdcc.setStandardDeviationLength(4);
+ sdcc.setStandardDeviationFormat(EncodingFormat.IEEE);
+ sdcc.setCorrelationCoefficientLength(3);
+ sdcc.setCorrelationCoefficientFormat(EncodingFormat.ST1201);
+ SDCCSerialiser writer = new SDCCSerialiser();
+ byte[] encoded = writer.encode(sdcc);
+ assertEquals(
+ encoded,
+ new byte[] {
+ (byte) 0x02,
+ (byte) 0x93,
+ (byte) 0x04,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD,
+ (byte) 0x41,
+ (byte) 0x81,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x5f,
+ (byte) 0x5c,
+ (byte) 0x28,
+ });
+ }
+
+ @Test
+ public void checkTwoVariableST1201Mode1Fallback() throws KlvParseException {
+ SDCC sdcc = new SDCC();
+ sdcc.setValues(new double[][] {{3.2, 0.49}, {0.49, 16.2}});
+ sdcc.setStandardDeviationLength(4);
+ sdcc.setStandardDeviationFormat(EncodingFormat.IEEE);
+ sdcc.setCorrelationCoefficientLength(8);
+ sdcc.setCorrelationCoefficientFormat(EncodingFormat.ST1201);
+ SDCCSerialiser writer = new SDCCSerialiser();
+ writer.setSparseEnabled(false);
+ writer.setPreferMode1(true);
+ byte[] encoded = writer.encode(sdcc);
+ assertEquals(
+ encoded,
+ new byte[] {
+ (byte) 0x02,
+ (byte) 0x98,
+ (byte) 0x04,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD,
+ (byte) 0x41,
+ (byte) 0x81,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x5f,
+ (byte) 0x5c,
+ (byte) 0x28,
+ (byte) 0xf5,
+ (byte) 0xc2,
+ (byte) 0x8f,
+ (byte) 0x5c,
+ (byte) 0x00
+ });
+ }
+
+ @Test
+ public void checkTwoVariableIEEEDoubleCovariance() throws KlvParseException {
+ SDCC sdcc = new SDCC();
+ sdcc.setValues(new double[][] {{3.2, 0.49}, {0.49, 16.2}});
+ sdcc.setStandardDeviationLength(4);
+ sdcc.setStandardDeviationFormat(EncodingFormat.IEEE);
+ sdcc.setCorrelationCoefficientLength(8);
+ sdcc.setCorrelationCoefficientFormat(EncodingFormat.IEEE);
+ SDCCSerialiser writer = new SDCCSerialiser();
+ byte[] encoded = writer.encode(sdcc);
+ assertEquals(
+ encoded,
+ new byte[] {
+ (byte) 0x02,
+ (byte) 0x88,
+ (byte) 0x04,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD,
+ (byte) 0x41,
+ (byte) 0x81,
+ (byte) 0x99,
+ (byte) 0x9A,
+ (byte) 0x3F,
+ (byte) 0xDF,
+ (byte) 0x5C,
+ (byte) 0x28,
+ (byte) 0xF5,
+ (byte) 0xC2,
+ (byte) 0x8F,
+ (byte) 0x5C
+ });
+ }
+
+ @Test
+ public void checkTwoVariableIEEEDoubleCovarianceMode1Fallback() throws KlvParseException {
+ SDCC sdcc = new SDCC();
+ sdcc.setValues(new double[][] {{3.2, 0.49}, {0.49, 16.2}});
+ sdcc.setStandardDeviationLength(4);
+ sdcc.setStandardDeviationFormat(EncodingFormat.IEEE);
+ sdcc.setCorrelationCoefficientLength(8);
+ sdcc.setCorrelationCoefficientFormat(EncodingFormat.IEEE);
+ SDCCSerialiser writer = new SDCCSerialiser();
+ writer.setPreferMode1(true);
+ byte[] encoded = writer.encode(sdcc);
+ assertEquals(
+ encoded,
+ new byte[] {
+ (byte) 0x02,
+ (byte) 0x88,
+ (byte) 0x04,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD,
+ (byte) 0x41,
+ (byte) 0x81,
+ (byte) 0x99,
+ (byte) 0x9A,
+ (byte) 0x3F,
+ (byte) 0xDF,
+ (byte) 0x5C,
+ (byte) 0x28,
+ (byte) 0xF5,
+ (byte) 0xC2,
+ (byte) 0x8F,
+ (byte) 0x5C
+ });
+ }
+
+ @Test
+ public void checkTwoVariableMode1() throws KlvParseException {
+ SDCC sdcc = new SDCC();
+ sdcc.setValues(new double[][] {{3.2, 0.49}, {0.49, 16.2}});
+ sdcc.setStandardDeviationLength(4);
+ sdcc.setStandardDeviationFormat(EncodingFormat.IEEE);
+ sdcc.setCorrelationCoefficientLength(2);
+ sdcc.setCorrelationCoefficientFormat(EncodingFormat.ST1201);
+ SDCCSerialiser writer = new SDCCSerialiser();
+ writer.setPreferMode1(true);
+ writer.setSparseEnabled(false);
+ byte[] encoded = writer.encode(sdcc);
+ assertEquals(
+ encoded,
+ new byte[] {
+ (byte) 0x02,
+ (byte) 0x42,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD,
+ (byte) 0x41,
+ (byte) 0x81,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x5f,
+ (byte) 0x5c
+ });
+ assertTrue(writer.isPreferMode1());
+ }
+
+ @Test
+ public void checkThreeVariableNotSparse() throws KlvParseException {
+ SDCC sdcc = new SDCC();
+ sdcc.setValues(
+ new double[][] {
+ {3.2, 0.0, 0.49},
+ {0.0, 16.2, 0.0},
+ {0.49, 0.0, 234.2}
+ });
+ sdcc.setStandardDeviationLength(4);
+ sdcc.setStandardDeviationFormat(EncodingFormat.IEEE);
+ sdcc.setCorrelationCoefficientLength(3);
+ sdcc.setCorrelationCoefficientFormat(EncodingFormat.ST1201);
+ SDCCSerialiser writer = new SDCCSerialiser();
+ writer.setSparseEnabled(false);
+ byte[] encoded = writer.encode(sdcc);
+ assertEquals(
+ encoded,
+ new byte[] {
+ (byte) 0x03,
+ (byte) 0x93,
+ (byte) 0x04,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD,
+ (byte) 0x41,
+ (byte) 0x81,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x43,
+ (byte) 0x6a,
+ (byte) 0x33,
+ (byte) 0x33,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x00,
+ (byte) 0x5f,
+ (byte) 0x5c,
+ (byte) 0x28,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x00,
+ });
+ }
+
+ @Test
+ public void checkThreeVariableSparse() throws KlvParseException {
+ SDCC sdcc = new SDCC();
+ sdcc.setValues(
+ new double[][] {
+ {3.2, 0.0, 0.49},
+ {0.0, 16.2, 0.0},
+ {0.49, 0.0, 234.2}
+ });
+ sdcc.setStandardDeviationLength(4);
+ sdcc.setStandardDeviationFormat(EncodingFormat.IEEE);
+ sdcc.setCorrelationCoefficientLength(3);
+ sdcc.setCorrelationCoefficientFormat(EncodingFormat.ST1201);
+ SDCCSerialiser writer = new SDCCSerialiser();
+ writer.setSparseEnabled(true);
+ byte[] encoded = writer.encode(sdcc);
+ assertEquals(
+ encoded,
+ new byte[] {
+ (byte) 0x03,
+ (byte) 0xB3,
+ (byte) 0x04,
+ (byte) 0b01000000,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD,
+ (byte) 0x41,
+ (byte) 0x81,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x43,
+ (byte) 0x6a,
+ (byte) 0x33,
+ (byte) 0x33,
+ (byte) 0x5f,
+ (byte) 0x5c,
+ (byte) 0x28
+ });
+ }
+
+ @Test(
+ expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = "SDCC requires a square input array")
+ public void checkNonSquareValues() {
+ SDCC sdcc = new SDCC();
+ sdcc.setValues(
+ new double[][] {
+ {3.2, 0.0, 0.49},
+ {0.0, 16.2, 0.0}
+ });
+ SDCCSerialiser writer = new SDCCSerialiser();
+ writer.encode(sdcc);
+ }
+
+ @Test(
+ expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp =
+ "SDCC IEEE format length 4 and 8 is currently supported. More work required for 2 byte. Other lengths not sensible.")
+ public void checkIEEEBadLengthStandardDeviation() {
+ SDCC sdcc = new SDCC();
+ sdcc.setValues(new double[][] {{3.2, 0.0, 0.49}, {0.0, 16.2, 0.0}, {0.49, 0.0, 234.2}});
+ sdcc.setStandardDeviationLength(3);
+ sdcc.setStandardDeviationFormat(EncodingFormat.IEEE);
+ sdcc.setCorrelationCoefficientLength(4);
+ sdcc.setCorrelationCoefficientFormat(EncodingFormat.IEEE);
+ SDCCSerialiser writer = new SDCCSerialiser();
+ writer.encode(sdcc);
+ }
+
+ @Test(
+ expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp =
+ "SDCC IEEE format length 4 and 8 is currently supported. More work required for 2 byte. Other lengths not sensible.")
+ public void checkIEEEBadLengthCorrelationCoefficients() {
+ SDCC sdcc = new SDCC();
+ sdcc.setValues(new double[][] {{3.2, 0.0, 0.49}, {0.0, 16.2, 0.0}, {0.49, 0.0, 234.2}});
+ sdcc.setStandardDeviationLength(4);
+ sdcc.setStandardDeviationFormat(EncodingFormat.IEEE);
+ sdcc.setCorrelationCoefficientLength(3);
+ sdcc.setCorrelationCoefficientFormat(EncodingFormat.IEEE);
+ SDCCSerialiser writer = new SDCCSerialiser();
+ writer.encode(sdcc);
+ }
+
+ @Test
+ public void checkThreeVariableSparseDefault() throws KlvParseException {
+ SDCC sdcc = new SDCC();
+ sdcc.setValues(
+ new double[][] {
+ {3.2, 0.0, 0.49},
+ {0.0, 16.2, 0.0},
+ {0.49, 0.0, 234.2}
+ });
+ sdcc.setStandardDeviationLength(4);
+ sdcc.setStandardDeviationFormat(EncodingFormat.IEEE);
+ sdcc.setCorrelationCoefficientLength(3);
+ sdcc.setCorrelationCoefficientFormat(EncodingFormat.ST1201);
+ SDCCSerialiser writer = new SDCCSerialiser();
+ byte[] encoded = writer.encode(sdcc);
+ assertEquals(
+ encoded,
+ new byte[] {
+ (byte) 0x03,
+ (byte) 0xB3,
+ (byte) 0x04,
+ (byte) 0b01000000,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD,
+ (byte) 0x41,
+ (byte) 0x81,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x43,
+ (byte) 0x6a,
+ (byte) 0x33,
+ (byte) 0x33,
+ (byte) 0x5f,
+ (byte) 0x5c,
+ (byte) 0x28
+ });
+ }
+
+ @Test
+ public void checkFiveVariableSparse() throws KlvParseException {
+ SDCC sdcc = new SDCC();
+ sdcc.setValues(
+ new double[][] {
+ {3.2, 0.0, 0.49, 0.0, 0.0},
+ {0.0, 16.2, 0.0, 0.0, 0.0},
+ {0.49, 0.0, 234.2, 0.0, 1.0},
+ {0.0, 0.0, 0.0, 0.32, 0.0},
+ {0.0, 0.0, 1.0, 0.0, 6.3}
+ });
+ sdcc.setStandardDeviationLength(4);
+ sdcc.setStandardDeviationFormat(EncodingFormat.IEEE);
+ sdcc.setCorrelationCoefficientLength(3);
+ sdcc.setCorrelationCoefficientFormat(EncodingFormat.ST1201);
+ SDCCSerialiser writer = new SDCCSerialiser();
+ byte[] encoded = writer.encode(sdcc);
+ assertEquals(
+ encoded,
+ new byte[] {
+ (byte) 0x05,
+ (byte) 0xB3,
+ (byte) 0x04,
+ (byte) 0b01000000,
+ (byte) 0b10000000,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD,
+ (byte) 0x41,
+ (byte) 0x81,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x43,
+ (byte) 0x6a,
+ (byte) 0x33,
+ (byte) 0x33,
+ (byte) 0x3e,
+ (byte) 0xa3,
+ (byte) 0xd7,
+ (byte) 0x0a,
+ (byte) 0x40,
+ (byte) 0xc9,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x5f,
+ (byte) 0x5c,
+ (byte) 0x28,
+ (byte) 0x80,
+ (byte) 0x00,
+ (byte) 0x00
+ });
+ }
+
+ @Test
+ public void checkFiveVariableSparseMode1() throws KlvParseException {
+ SDCC sdcc = new SDCC();
+ sdcc.setValues(
+ new double[][] {
+ {3.2, 0.0, 0.49, 0.0, 0.0},
+ {0.0, 16.2, 0.0, 0.0, 0.0},
+ {0.49, 0.0, 234.2, 0.0, 1.0},
+ {0.0, 0.0, 0.0, 0.32, 0.0},
+ {0.0, 0.0, 1.0, 0.0, 6.3}
+ });
+ sdcc.setStandardDeviationLength(4);
+ sdcc.setStandardDeviationFormat(EncodingFormat.IEEE);
+ sdcc.setCorrelationCoefficientLength(3);
+ sdcc.setCorrelationCoefficientFormat(EncodingFormat.ST1201);
+ SDCCSerialiser writer = new SDCCSerialiser();
+ writer.setPreferMode1(true);
+ byte[] encoded = writer.encode(sdcc);
+ assertEquals(
+ encoded,
+ new byte[] {
+ (byte) 0x05,
+ (byte) 0x4B,
+ (byte) 0b01000000,
+ (byte) 0b10000000,
+ (byte) 0x40,
+ (byte) 0x4C,
+ (byte) 0xCC,
+ (byte) 0xCD,
+ (byte) 0x41,
+ (byte) 0x81,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x43,
+ (byte) 0x6a,
+ (byte) 0x33,
+ (byte) 0x33,
+ (byte) 0x3e,
+ (byte) 0xa3,
+ (byte) 0xd7,
+ (byte) 0x0a,
+ (byte) 0x40,
+ (byte) 0xc9,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x5f,
+ (byte) 0x5c,
+ (byte) 0x28,
+ (byte) 0x80,
+ (byte) 0x00,
+ (byte) 0x00
+ });
+ }
+}
diff --git a/st1010/testng-unit.xml b/st1010/testng-unit.xml
new file mode 100644
index 000000000..0c28a01b9
--- /dev/null
+++ b/st1010/testng-unit.xml
@@ -0,0 +1,10 @@
+
+
+
+ This standard describes a generalized method of transforming two-dimensional data (or points)
+ * from one coordinate system into a second two-dimensional coordinate system. This Generalized
+ * Transformation may be used for various image-to-image transformations such as an affine
+ * transformation by simply equating some parameters to be equal to zero. In addition, this
+ * Generalized Transformation may describe some homographic-like transformations.
+ *
+ * This standard defines three items:
+ *
+ * Since so much of the local set is common, this is a single representation.
+ */
+public abstract class AbstractGeneralizedTransformationMetadataValue
+ implements IGeneralizedTransformationMetadataValue {
+
+ private final float value;
+ private final String displayName;
+
+ /**
+ * Create from value.
+ *
+ * @param value the value as a float.
+ * @param displayName the human-readable display name for the value
+ */
+ protected AbstractGeneralizedTransformationMetadataValue(
+ final float value, final String displayName) {
+ this.value = value;
+ this.displayName = displayName;
+ }
+
+ /**
+ * Create from encoded bytes.
+ *
+ * @param bytes Encoded byte array of length 4
+ * @param displayName the human-readable display name for the value
+ * @throws KlvParseException if the byte array is not of the correct length
+ */
+ protected AbstractGeneralizedTransformationMetadataValue(byte[] bytes, final String displayName)
+ throws KlvParseException {
+ try {
+ this.value = PrimitiveConverter.toFloat32(bytes);
+ this.displayName = displayName;
+ } catch (IllegalArgumentException ex) {
+ throw new KlvParseException(getDisplayName() + " is of length 4 bytes");
+ }
+ }
+
+ @Override
+ public final String getDisplayName() {
+ return displayName;
+ }
+
+ /**
+ * Get the value.
+ *
+ * @return value as a floating point value.
+ */
+ public float getValue() {
+ return this.value;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return PrimitiveConverter.float32ToBytes(value);
+ }
+
+ @Override
+ public String getDisplayableValue() {
+ return String.format("%.3f", this.value);
+ }
+}
diff --git a/st1202/src/main/java/org/jmisb/st1202/Denominator_X.java b/st1202/src/main/java/org/jmisb/st1202/Denominator_X.java
new file mode 100644
index 000000000..8ea3926ae
--- /dev/null
+++ b/st1202/src/main/java/org/jmisb/st1202/Denominator_X.java
@@ -0,0 +1,28 @@
+package org.jmisb.st1202;
+
+import org.jmisb.api.common.KlvParseException;
+
+/** Denominator - x factor (ST 1202 Local Set Item 7). */
+public class Denominator_X extends AbstractGeneralizedTransformationMetadataValue {
+
+ private static final String DISPLAY_NAME = "Denominator - x factor";
+
+ /**
+ * Create from value.
+ *
+ * @param value the value as a float.
+ */
+ public Denominator_X(float value) {
+ super(value, DISPLAY_NAME);
+ }
+
+ /**
+ * Create from encoded bytes.
+ *
+ * @param bytes Encoded byte array of length 4
+ * @throws KlvParseException if the byte array is not of the correct length
+ */
+ public Denominator_X(byte[] bytes) throws KlvParseException {
+ super(bytes, DISPLAY_NAME);
+ }
+}
diff --git a/st1202/src/main/java/org/jmisb/st1202/Denominator_Y.java b/st1202/src/main/java/org/jmisb/st1202/Denominator_Y.java
new file mode 100644
index 000000000..3decf444a
--- /dev/null
+++ b/st1202/src/main/java/org/jmisb/st1202/Denominator_Y.java
@@ -0,0 +1,28 @@
+package org.jmisb.st1202;
+
+import org.jmisb.api.common.KlvParseException;
+
+/** Denominator - y factor (ST 1202 Local Set Item 8). */
+public class Denominator_Y extends AbstractGeneralizedTransformationMetadataValue {
+
+ private static final String DISPLAY_NAME = "Denominator - y factor";
+
+ /**
+ * Create from value.
+ *
+ * @param value the value as a float.
+ */
+ public Denominator_Y(float value) {
+ super(value, DISPLAY_NAME);
+ }
+
+ /**
+ * Create from encoded bytes.
+ *
+ * @param bytes Encoded byte array of length 4
+ * @throws KlvParseException if the byte array is not of the correct length
+ */
+ public Denominator_Y(byte[] bytes) throws KlvParseException {
+ super(bytes, DISPLAY_NAME);
+ }
+}
diff --git a/st1202/src/main/java/org/jmisb/st1202/GeneralizedTransformationLocalSet.java b/st1202/src/main/java/org/jmisb/st1202/GeneralizedTransformationLocalSet.java
new file mode 100644
index 000000000..13e9fcba8
--- /dev/null
+++ b/st1202/src/main/java/org/jmisb/st1202/GeneralizedTransformationLocalSet.java
@@ -0,0 +1,156 @@
+package org.jmisb.st1202;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import org.jmisb.api.common.KlvParseException;
+import org.jmisb.api.klv.ArrayBuilder;
+import org.jmisb.api.klv.IKlvKey;
+import org.jmisb.api.klv.IMisbMessage;
+import org.jmisb.api.klv.LdsField;
+import org.jmisb.api.klv.LdsParser;
+import org.jmisb.api.klv.UniversalLabel;
+import org.jmisb.st1010.SDCC;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Generalized Transformation Local Set.
+ *
+ * This is the core ST 1202 Local Set.
+ */
+public class GeneralizedTransformationLocalSet implements IMisbMessage {
+
+ /**
+ * Universal label for Generalized Transformation Local Set.
+ *
+ * See ST 1202.2 Table 2.
+ */
+ public static final UniversalLabel GeneralizedTransformationLocalSetUl =
+ new UniversalLabel(
+ new byte[] {
+ 0x06, 0x0E, 0x2B, 0x34, 0x02, 0x0B, 0x01, 0x01, 0x0E, 0x01, 0x03, 0x05,
+ 0x05, 0x00, 0x00, 0x00
+ });
+
+ private static final Logger LOGGER =
+ LoggerFactory.getLogger(GeneralizedTransformationLocalSet.class);
+
+ /**
+ * Create a {@link IGeneralizedTransformationMetadataValue} instance from encoded bytes.
+ *
+ * @param tag The tag defining the value type
+ * @param bytes Encoded bytes
+ * @return The new instance
+ * @throws KlvParseException if the parsing of the encoded bytes fails
+ */
+ static IGeneralizedTransformationMetadataValue createValue(
+ GeneralizedTransformationParametersKey tag, byte[] bytes) throws KlvParseException {
+ switch (tag) {
+ case X_Numerator_x:
+ return new X_Numerator_X(bytes);
+ case X_Numerator_y:
+ return new X_Numerator_Y(bytes);
+ case X_Numerator_Constant:
+ return new X_Numerator_Constant(bytes);
+ case Y_Numerator_x:
+ return new Y_Numerator_X(bytes);
+ case Y_Numerator_y:
+ return new Y_Numerator_Y(bytes);
+ case Y_Numerator_Constant:
+ return new Y_Numerator_Constant(bytes);
+ case Denominator_x:
+ return new Denominator_X(bytes);
+ case Denominator_y:
+ return new Denominator_Y(bytes);
+ case SDCC:
+ return new SDCC_FLP(bytes);
+ case DocumentVersion:
+ return new ST1202DocumentVersion(bytes);
+ case TransformationEnumeration:
+ return TransformationEnumeration.fromBytes(bytes);
+ default:
+ LOGGER.info("Unknown Generalized Transformation Metadata tag: {}", tag);
+ }
+ return null;
+ }
+
+ /** Map containing all elements in the message. */
+ private final SortedMap<
+ GeneralizedTransformationParametersKey, IGeneralizedTransformationMetadataValue>
+ map = new TreeMap<>();
+
+ /**
+ * Create the local set from the given key/value pairs.
+ *
+ * @param values Tag/value pairs to be included in the local set
+ */
+ public GeneralizedTransformationLocalSet(
+ Map This assumes the byte array is always nested (see requirement ST 1202.1-09).
+ *
+ * @param bytes the bytes to build from
+ * @throws KlvParseException if parsing fails
+ */
+ public GeneralizedTransformationLocalSet(final byte[] bytes) throws KlvParseException {
+ List This enumeration maps the Generalized Transformation Local Set tag values to a name, and names
+ * to tag values. It conceptually corresponds to the Tag ID and Name columns in ST 1202.2 Table 2.
+ */
+public enum GeneralizedTransformationParametersKey implements IKlvKey {
+ /**
+ * Unknown key.
+ *
+ * This should not be created. It does not correspond to any known ST 1202 tag / key.
+ */
+ Undefined(0),
+
+ /**
+ * x Equation Numerator - x factor.
+ *
+ * ST 1202 Local Set Tag 1.
+ *
+ * This is {code A} in ST 1202.2 Equation 1.
+ */
+ X_Numerator_x(1),
+
+ /**
+ * x Equation Numerator - y factor.
+ *
+ * ST 1202 Local Set Tag 2.
+ *
+ * This is {code B} in ST 1202.2 Equation 1.
+ */
+ X_Numerator_y(2),
+
+ /**
+ * x Equation Numerator - constant factor.
+ *
+ * ST 1202 Local Set Tag 3.
+ *
+ * This is {code C} in ST 1202.2 Equation 1.
+ */
+ X_Numerator_Constant(3),
+
+ /**
+ * y Equation Numerator - x factor.
+ *
+ * ST 1202 Local Set Tag 4.
+ *
+ * This is {code D} in ST 1202.2 Equation 2.
+ */
+ Y_Numerator_x(4),
+
+ /**
+ * y Equation Numerator - y factor.
+ *
+ * ST 1202 Local Set Tag 5.
+ *
+ * This is {code E} in ST 1202.2 Equation 2.
+ */
+ Y_Numerator_y(5),
+
+ /**
+ * y Equation Numerator - constant factor.
+ *
+ * ST 1202 Local Set Tag 6.
+ *
+ * This is {code F} in ST 1202.2 Equation 2.
+ */
+ Y_Numerator_Constant(6),
+
+ /**
+ * Denominator - x factor.
+ *
+ * ST 1202 Local Set Tag 7.
+ *
+ * This is {code G} in ST 1202.2 Equations 1 and 2.
+ */
+ Denominator_x(7),
+
+ /**
+ * Denominator - y factor.
+ *
+ * ST 1202 Local Set Tag 8.
+ *
+ * This is {code H} in ST 1202.2 Equations 1 and 2.
+ */
+ Denominator_y(8),
+
+ /**
+ * Standard Deviation and Correlation Coefficients (SDCC).
+ *
+ * ST 1202 Local Set Tag 9.
+ */
+ SDCC(9),
+
+ /**
+ * Document Version.
+ *
+ * ST 1202 Local Set Tag 10.
+ */
+ DocumentVersion(10),
+
+ /**
+ * Transformation Enumeration.
+ *
+ * ST 1202 Local Set Tag 11.
+ */
+ TransformationEnumeration(11);
+
+ private final int tag;
+
+ private static final Map All Generalized Transformation Local Set values implement this interface. Users are unlikely
+ * to need to implement this interface. Instead, use one of the implementations either directly, or
+ * via the Local Set implementation.
+ */
+public interface IGeneralizedTransformationMetadataValue extends IKlvValue {
+ /**
+ * Get the encoded bytes.
+ *
+ * @return The encoded byte array
+ */
+ byte[] getBytes();
+}
diff --git a/st1202/src/main/java/org/jmisb/st1202/SDCC_FLP.java b/st1202/src/main/java/org/jmisb/st1202/SDCC_FLP.java
new file mode 100644
index 000000000..829972b46
--- /dev/null
+++ b/st1202/src/main/java/org/jmisb/st1202/SDCC_FLP.java
@@ -0,0 +1,91 @@
+package org.jmisb.st1202;
+
+import java.util.Set;
+import org.jmisb.api.common.KlvParseException;
+import org.jmisb.api.klv.IKlvKey;
+import org.jmisb.api.klv.IKlvValue;
+import org.jmisb.api.klv.INestedKlvValue;
+import org.jmisb.st1010.SDCC;
+import org.jmisb.st1010.SDCCParser;
+import org.jmisb.st1010.SDCCSerialiser;
+
+/**
+ * Standard Deviation and Correlation Coefficient (SDCC) Floating Length Pack (FLP).
+ *
+ * In many applications, the knowledge of the uncertainty of all estimated values is critical to
+ * understand the performance of a system. Thus, it is desirable to provide a means to propagate the
+ * uncertainty information of the transformation parameters. The Generalized Transformation LS
+ * utilizes the format described in MISB ST 1010 for transmitting the standard deviation and
+ * correlation coefficient information.
+ */
+public class SDCC_FLP implements IGeneralizedTransformationMetadataValue, INestedKlvValue {
+
+ /** Underlying SDCC value. */
+ private SDCC sdcc;
+
+ /**
+ * Construct from value.
+ *
+ * @param value the SDCC instance to copy values from
+ */
+ public SDCC_FLP(final SDCC value) {
+ this.sdcc = new SDCC(value);
+ }
+ /**
+ * Construct from encoded bytes.
+ *
+ * @param bytes encoded byte array, per ST 1010.
+ * @throws KlvParseException if parsing fails
+ */
+ public SDCC_FLP(byte[] bytes) throws KlvParseException {
+ SDCCParser parser = new SDCCParser();
+ this.sdcc = parser.parse(bytes);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ SDCCSerialiser serialiser = new SDCCSerialiser();
+ serialiser.setPreferMode1(true);
+ serialiser.setSparseEnabled(false);
+ return serialiser.encode(sdcc);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Standard Deviation and Correlation Coefficients";
+ }
+
+ @Override
+ public String getDisplayableValue() {
+ // TODO: see if we can do better
+ return "[SDCC]";
+ }
+
+ /**
+ * Get the standard deviation and correlation coefficient object.
+ *
+ * @return copy of the ST 1010 SDCC object.
+ */
+ public SDCC getSDCC() {
+ return new SDCC(sdcc);
+ }
+
+ /**
+ * Get the standard deviation and correlation coefficient matrix.
+ *
+ * @return copy of the ST 1010 SDCC values matrix.
+ */
+ public double[][] getSDCCMatrix() {
+ return sdcc.getValues();
+ }
+
+ @Override
+ public IKlvValue getField(IKlvKey identifier) {
+ return sdcc.getField(identifier);
+ }
+
+ @Override
+ public Set extends IKlvKey> getIdentifiers() {
+ return sdcc.getIdentifiers();
+ }
+}
diff --git a/st1202/src/main/java/org/jmisb/st1202/ST1202DocumentVersion.java b/st1202/src/main/java/org/jmisb/st1202/ST1202DocumentVersion.java
new file mode 100644
index 000000000..d7044c01d
--- /dev/null
+++ b/st1202/src/main/java/org/jmisb/st1202/ST1202DocumentVersion.java
@@ -0,0 +1,78 @@
+package org.jmisb.st1202;
+
+import org.jmisb.core.klv.PrimitiveConverter;
+
+/**
+ * ST 1202 Document Version (ST 1202 Generalized Transformation Local Set Tag 10).
+ *
+ * The version number is the same of the minor version number of the standard document. For
+ * example, with MISB ST 1202.1, the version number value is {@code 1} and with ST 1202.2, the
+ * version number value is {@code 2}.
+ *
+ * This implementation assumes the document version is UINT8 encoded - the standard states
+ * BER-OID encoded, but also states 1 byte, and a valid range of 0 to 255. That is inconsistent,
+ * although it makes no practical difference for feasible version numbers. MISB have indicated that
+ * a future update (assuming there ever is one) to ST 1202 would change this to UINT8.
+ */
+public class ST1202DocumentVersion implements IGeneralizedTransformationMetadataValue {
+
+ private final int version;
+
+ /**
+ * The currently supported revision is 1202.2.
+ *
+ * This may be useful in the constructor.
+ */
+ public static final short ST_VERSION_NUMBER = 2;
+
+ /**
+ * Create from value.
+ *
+ * The current version is available as {@link #ST_VERSION_NUMBER}.
+ *
+ * @param versionNumber The version number
+ */
+ public ST1202DocumentVersion(int versionNumber) {
+ if (versionNumber < 0) {
+ throw new IllegalArgumentException("ST 1202 Document Version cannot be negative");
+ }
+ this.version = versionNumber;
+ }
+
+ /**
+ * Create from encoded bytes.
+ *
+ * @param bytes Byte array containing UINT8 formatted version number
+ */
+ public ST1202DocumentVersion(byte[] bytes) {
+ this.version = PrimitiveConverter.toUint8(bytes);
+ }
+
+ /**
+ * Get the document version number.
+ *
+ * @return The version number
+ */
+ public int getVersion() {
+ return version;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return PrimitiveConverter.uint8ToBytes((short) version);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Document Version";
+ }
+
+ @Override
+ public String getDisplayableValue() {
+ if (version == 0) {
+ return "ST 1202";
+ } else {
+ return "ST 1202." + version;
+ }
+ }
+}
diff --git a/st1202/src/main/java/org/jmisb/st1202/TransformationEnumeration.java b/st1202/src/main/java/org/jmisb/st1202/TransformationEnumeration.java
new file mode 100644
index 000000000..af768f802
--- /dev/null
+++ b/st1202/src/main/java/org/jmisb/st1202/TransformationEnumeration.java
@@ -0,0 +1,157 @@
+package org.jmisb.st1202;
+
+import org.jmisb.api.common.KlvParseException;
+
+/** Transformation type enumeration. */
+public enum TransformationEnumeration implements IGeneralizedTransformationMetadataValue {
+ /**
+ * Unknown value.
+ *
+ * This is not a valid enumeration value, and typically indicates a problem with decoding
+ * (e.g. bad data or implementation bug).
+ */
+ UNKNOWN(-1, "UNKNOWN", ""),
+
+ /**
+ * Other – No Defined Transformation.
+ *
+ * An enumeration value equal to {@code 0} implies the transformation type is not defined;
+ * however, this does not prevent the user from exploiting the information contained within the
+ * Generalized Transformation LS.
+ */
+ OTHER(0, "Other - No Defined Transformation (NDT)", ""),
+
+ /**
+ * Chipping Transformation.
+ *
+ * An enumeration value equal to {@code 1} signifies the transmitted image is a chip (or
+ * sub-region) from a larger image. Examples of a chipped image are: 1) a sub-region of an image
+ * that may be digitally enlarged (zoom); 2) a sub-region of an image selected to reduce
+ * bandwidth, or to provide higher quality within the sub-region.
+ */
+ CHIPPING(1, "Chipping Transformation (CT)", "px"),
+
+ /**
+ * Child-Parent Transformation.
+ *
+ * An enumeration value equal to {@code 2} indicates the transformation of a child focal
+ * plane array (FPA) to its parent FPA (e.g. example defined in MISB ST 1002). This CPT is a
+ * plane-to-plane transformation used to transform between FPA's in image space.
+ */
+ CHILD_PARENT(2, "Child-Parent Transformation (CPT)", "mm"),
+
+ /**
+ * Default Pixel-Space to Image-Space Transformation.
+ *
+ * An enumeration value equal to {@code 3} is the default pixel-space to image-space
+ * transformation.
+ */
+ DPIT(3, "Default Pixel-Space to Image-Space Transformation (DPIT)", "mm"),
+
+ /**
+ * Optical Transformation.
+ *
+ * An enumeration value equal to {@code 4} indicates the pixel data of an image is a
+ * translation, rotation, scale or skew from the originating FPA to final optical focal plane.
+ * This may occur when the originating FPA is a subset of an entire optical focal plane. An
+ * example is a Combined Composite Focal Plane Array (CCFPA) sensor, where multiple focal plane
+ * array detectors combine to image a single optical focal plane. This optical transformation is
+ * a plane-to-plane transformation to transform from FPA to the optical image plane. In addition
+ * to providing a transformation from FPA to CCFPA, the optical transformation may also support
+ * the effects of Coudé paths or Fast Steering Mirrors (FSM). Coudé path and FSM effects may
+ * mimic that of the transformation between FPA and CCFPA. They may also differ, however, by
+ * translating, rotating, scaling or skewing the optical image plane.
+ */
+ OPTICAL(4, "Optical Transformation (OT)", "mm");
+
+ /**
+ * Get the enumeration value for a byte array.
+ *
+ * @param bytes the byte array, length 1
+ * @return the corresponding enumeration value, or UNKNOWN if there is no matching enumeration
+ * value.
+ * @throws KlvParseException if the parsing fails (e.g. wrong length byte array)
+ */
+ static TransformationEnumeration fromBytes(byte[] bytes) throws KlvParseException {
+ if (bytes.length != 1) {
+ throw new KlvParseException(
+ "Transformation Enumeration should be encoded as a 1 byte array");
+ }
+ int value = bytes[0];
+ return TransformationEnumeration.lookup(value);
+ }
+
+ /**
+ * Look up enumeration value by encoded value.
+ *
+ * @param value the encoded (integer) value
+ * @return the corresponding enumeration value, or UNKNOWN if the value did not match any entry
+ */
+ public static TransformationEnumeration lookup(int value) {
+ for (TransformationEnumeration transformationType : values()) {
+ if (transformationType.getEncodedValue() == value) {
+ return transformationType;
+ }
+ }
+ return UNKNOWN;
+ }
+
+ private final int encodedValue;
+ private final String description;
+ private final String units;
+
+ private TransformationEnumeration(
+ final int encodedValue, final String description, final String units) {
+ this.encodedValue = encodedValue;
+ this.description = description;
+ this.units = units;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ if (this == UNKNOWN) {
+ throw new IllegalArgumentException(
+ "Cannot serialise UNKNOWN Transformation Enumeration");
+ }
+ return new byte[] {(byte) encodedValue};
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Transformation Enumeration";
+ }
+
+ @Override
+ public String getDisplayableValue() {
+ return description;
+ }
+
+ /**
+ * Encoded value.
+ *
+ * @return integer encoded value for the enumeration
+ */
+ public int getEncodedValue() {
+ return encodedValue;
+ }
+
+ /**
+ * Text description.
+ *
+ * @return String containing human-readable enumeration description.
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Transformation units.
+ *
+ * The transformation units as provided in ST 1202.2 Table 1.
+ *
+ * @return units as a text string (blank if "None").
+ */
+ public String getUnits() {
+ return units;
+ }
+}
diff --git a/st1202/src/main/java/org/jmisb/st1202/X_Numerator_Constant.java b/st1202/src/main/java/org/jmisb/st1202/X_Numerator_Constant.java
new file mode 100644
index 000000000..d8560b8d9
--- /dev/null
+++ b/st1202/src/main/java/org/jmisb/st1202/X_Numerator_Constant.java
@@ -0,0 +1,28 @@
+package org.jmisb.st1202;
+
+import org.jmisb.api.common.KlvParseException;
+
+/** x Equation Numerator - Constant factor (ST 1202 Local Set Item 3). */
+public class X_Numerator_Constant extends AbstractGeneralizedTransformationMetadataValue {
+
+ private static final String DISPLAY_NAME = "x Equation Numerator - Constant factor";
+
+ /**
+ * Create from value.
+ *
+ * @param value the value as a float.
+ */
+ public X_Numerator_Constant(float value) {
+ super(value, DISPLAY_NAME);
+ }
+
+ /**
+ * Create from encoded bytes.
+ *
+ * @param bytes Encoded byte array of length 4
+ * @throws KlvParseException if the byte array is not of the correct length
+ */
+ public X_Numerator_Constant(byte[] bytes) throws KlvParseException {
+ super(bytes, DISPLAY_NAME);
+ }
+}
diff --git a/st1202/src/main/java/org/jmisb/st1202/X_Numerator_X.java b/st1202/src/main/java/org/jmisb/st1202/X_Numerator_X.java
new file mode 100644
index 000000000..cd3f4bf5e
--- /dev/null
+++ b/st1202/src/main/java/org/jmisb/st1202/X_Numerator_X.java
@@ -0,0 +1,28 @@
+package org.jmisb.st1202;
+
+import org.jmisb.api.common.KlvParseException;
+
+/** x Equation Numerator - x factor (ST 1202 Local Set Item 1). */
+public class X_Numerator_X extends AbstractGeneralizedTransformationMetadataValue {
+
+ private static final String DISPLAY_NAME = "x Equation Numerator - x factor";
+
+ /**
+ * Create from value.
+ *
+ * @param value the value as a float.
+ */
+ public X_Numerator_X(float value) {
+ super(value, DISPLAY_NAME);
+ }
+
+ /**
+ * Create from encoded bytes.
+ *
+ * @param bytes Encoded byte array of length 4
+ * @throws KlvParseException if the byte array is not of the correct length
+ */
+ public X_Numerator_X(byte[] bytes) throws KlvParseException {
+ super(bytes, DISPLAY_NAME);
+ }
+}
diff --git a/st1202/src/main/java/org/jmisb/st1202/X_Numerator_Y.java b/st1202/src/main/java/org/jmisb/st1202/X_Numerator_Y.java
new file mode 100644
index 000000000..a4d0ed074
--- /dev/null
+++ b/st1202/src/main/java/org/jmisb/st1202/X_Numerator_Y.java
@@ -0,0 +1,28 @@
+package org.jmisb.st1202;
+
+import org.jmisb.api.common.KlvParseException;
+
+/** x Equation Numerator - y factor (ST 1202 Local Set Item 2). */
+public class X_Numerator_Y extends AbstractGeneralizedTransformationMetadataValue {
+
+ private static final String DISPLAY_NAME = "x Equation Numerator - y factor";
+
+ /**
+ * Create from value.
+ *
+ * @param value the value as a float.
+ */
+ public X_Numerator_Y(float value) {
+ super(value, DISPLAY_NAME);
+ }
+
+ /**
+ * Create from encoded bytes.
+ *
+ * @param bytes Encoded byte array of length 4
+ * @throws KlvParseException if the byte array is not of the correct length
+ */
+ public X_Numerator_Y(byte[] bytes) throws KlvParseException {
+ super(bytes, DISPLAY_NAME);
+ }
+}
diff --git a/st1202/src/main/java/org/jmisb/st1202/Y_Numerator_Constant.java b/st1202/src/main/java/org/jmisb/st1202/Y_Numerator_Constant.java
new file mode 100644
index 000000000..5242c141b
--- /dev/null
+++ b/st1202/src/main/java/org/jmisb/st1202/Y_Numerator_Constant.java
@@ -0,0 +1,28 @@
+package org.jmisb.st1202;
+
+import org.jmisb.api.common.KlvParseException;
+
+/** y Equation Numerator - Constant factor (ST 1202 Local Set Item 6). */
+public class Y_Numerator_Constant extends AbstractGeneralizedTransformationMetadataValue {
+
+ private static final String DISPLAY_NAME = "y Equation Numerator - Constant factor";
+
+ /**
+ * Create from value.
+ *
+ * @param value the value as a float.
+ */
+ public Y_Numerator_Constant(float value) {
+ super(value, DISPLAY_NAME);
+ }
+
+ /**
+ * Create from encoded bytes.
+ *
+ * @param bytes Encoded byte array of length 4
+ * @throws KlvParseException if the byte array is not of the correct length
+ */
+ public Y_Numerator_Constant(byte[] bytes) throws KlvParseException {
+ super(bytes, DISPLAY_NAME);
+ }
+}
diff --git a/st1202/src/main/java/org/jmisb/st1202/Y_Numerator_X.java b/st1202/src/main/java/org/jmisb/st1202/Y_Numerator_X.java
new file mode 100644
index 000000000..cd62ce6ca
--- /dev/null
+++ b/st1202/src/main/java/org/jmisb/st1202/Y_Numerator_X.java
@@ -0,0 +1,28 @@
+package org.jmisb.st1202;
+
+import org.jmisb.api.common.KlvParseException;
+
+/** y Equation Numerator - x factor (ST 1202 Local Set Item 4). */
+public class Y_Numerator_X extends AbstractGeneralizedTransformationMetadataValue {
+
+ private static final String DISPLAY_NAME = "y Equation Numerator - x factor";
+
+ /**
+ * Create from value.
+ *
+ * @param value the value as a float.
+ */
+ public Y_Numerator_X(float value) {
+ super(value, DISPLAY_NAME);
+ }
+
+ /**
+ * Create from encoded bytes.
+ *
+ * @param bytes Encoded byte array of length 4
+ * @throws KlvParseException if the byte array is not of the correct length
+ */
+ public Y_Numerator_X(byte[] bytes) throws KlvParseException {
+ super(bytes, DISPLAY_NAME);
+ }
+}
diff --git a/st1202/src/main/java/org/jmisb/st1202/Y_Numerator_Y.java b/st1202/src/main/java/org/jmisb/st1202/Y_Numerator_Y.java
new file mode 100644
index 000000000..ea1ec41a0
--- /dev/null
+++ b/st1202/src/main/java/org/jmisb/st1202/Y_Numerator_Y.java
@@ -0,0 +1,28 @@
+package org.jmisb.st1202;
+
+import org.jmisb.api.common.KlvParseException;
+
+/** y Equation Numerator - y factor (ST 1202 Local Set Item 5). */
+public class Y_Numerator_Y extends AbstractGeneralizedTransformationMetadataValue {
+
+ private static final String DISPLAY_NAME = "y Equation Numerator - y factor";
+
+ /**
+ * Create from value.
+ *
+ * @param value the value as a float.
+ */
+ public Y_Numerator_Y(float value) {
+ super(value, DISPLAY_NAME);
+ }
+
+ /**
+ * Create from encoded bytes.
+ *
+ * @param bytes Encoded byte array of length 4
+ * @throws KlvParseException if the byte array is not of the correct length
+ */
+ public Y_Numerator_Y(byte[] bytes) throws KlvParseException {
+ super(bytes, DISPLAY_NAME);
+ }
+}
diff --git a/st1202/src/main/java/org/jmisb/st1202/package-info.java b/st1202/src/main/java/org/jmisb/st1202/package-info.java
new file mode 100644
index 000000000..34174d2b2
--- /dev/null
+++ b/st1202/src/main/java/org/jmisb/st1202/package-info.java
@@ -0,0 +1,10 @@
+/**
+ * MISB ST 1202.2 Generalized Transformation Parameters.
+ *
+ * This standard describes a generalized method of transforming two-dimensional data (or points)
+ * from one coordinate system into a second two-dimensional coordinate system. This Generalized
+ * Transformation may be used for various image-to-image transformations such as an affine
+ * transformation by simply equating some parameters to be equal to zero. In addition, this
+ * Generalized Transformation may describe some homographic-like transformations.
+ */
+package org.jmisb.st1202;
diff --git a/st1202/src/test/java/org/jmisb/st1202/Denominator_X_Test.java b/st1202/src/test/java/org/jmisb/st1202/Denominator_X_Test.java
new file mode 100644
index 000000000..455ed191e
--- /dev/null
+++ b/st1202/src/test/java/org/jmisb/st1202/Denominator_X_Test.java
@@ -0,0 +1,62 @@
+package org.jmisb.st1202;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import org.jmisb.api.common.KlvParseException;
+import org.testng.annotations.Test;
+
+/** Tests for Denominator_X. */
+public class Denominator_X_Test {
+
+ @Test
+ public void fromValue() {
+ Denominator_X uut = new Denominator_X(0.0f);
+ assertEquals(uut.getValue(), 0.0f);
+ assertEquals(uut.getDisplayName(), "Denominator - x factor");
+ assertEquals(uut.getDisplayableValue(), "0.000");
+ assertEquals(
+ uut.getBytes(), new byte[] {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
+ }
+
+ @Test
+ public void fromBytes() throws KlvParseException {
+ Denominator_X uut =
+ new Denominator_X(new byte[] {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
+ assertEquals(uut.getValue(), 0.0f);
+ assertEquals(uut.getDisplayName(), "Denominator - x factor");
+ assertEquals(uut.getDisplayableValue(), "0.000");
+ assertEquals(
+ uut.getBytes(), new byte[] {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
+ }
+
+ @Test
+ public void fromBytesFactory() throws KlvParseException {
+ IGeneralizedTransformationMetadataValue v =
+ GeneralizedTransformationLocalSet.createValue(
+ GeneralizedTransformationParametersKey.Denominator_x,
+ new byte[] {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
+ assertTrue(v instanceof Denominator_X);
+ Denominator_X uut = (Denominator_X) v;
+ assertEquals(uut.getValue(), 0.0f);
+ assertEquals(uut.getDisplayName(), "Denominator - x factor");
+ assertEquals(uut.getDisplayableValue(), "0.000");
+ assertEquals(
+ uut.getBytes(), new byte[] {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
+ }
+
+ @Test(expectedExceptions = KlvParseException.class)
+ public void testTooShort() throws KlvParseException {
+ new Denominator_X(new byte[] {0x01, 0x02, 0x03});
+ }
+
+ @Test(expectedExceptions = KlvParseException.class)
+ public void testTooLong() throws KlvParseException {
+ new Denominator_X(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05});
+ }
+
+ @Test(expectedExceptions = KlvParseException.class)
+ public void testNotDouble() throws KlvParseException {
+ new Denominator_X(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08});
+ }
+}
diff --git a/st1202/src/test/java/org/jmisb/st1202/Denominator_Y_Test.java b/st1202/src/test/java/org/jmisb/st1202/Denominator_Y_Test.java
new file mode 100644
index 000000000..d1331933c
--- /dev/null
+++ b/st1202/src/test/java/org/jmisb/st1202/Denominator_Y_Test.java
@@ -0,0 +1,62 @@
+package org.jmisb.st1202;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import org.jmisb.api.common.KlvParseException;
+import org.testng.annotations.Test;
+
+/** Tests for Denominator_Y. */
+public class Denominator_Y_Test {
+
+ @Test
+ public void fromValue() {
+ Denominator_Y uut = new Denominator_Y(0.2f);
+ assertEquals(uut.getValue(), 0.2f);
+ assertEquals(uut.getDisplayName(), "Denominator - y factor");
+ assertEquals(uut.getDisplayableValue(), "0.200");
+ assertEquals(
+ uut.getBytes(), new byte[] {(byte) 0x3E, (byte) 0x4C, (byte) 0xCC, (byte) 0xCD});
+ }
+
+ @Test
+ public void fromBytes() throws KlvParseException {
+ Denominator_Y uut =
+ new Denominator_Y(new byte[] {(byte) 0x3E, (byte) 0x4C, (byte) 0xCC, (byte) 0xCD});
+ assertEquals(uut.getValue(), 0.2f);
+ assertEquals(uut.getDisplayName(), "Denominator - y factor");
+ assertEquals(uut.getDisplayableValue(), "0.200");
+ assertEquals(
+ uut.getBytes(), new byte[] {(byte) 0x3E, (byte) 0x4C, (byte) 0xCC, (byte) 0xCD});
+ }
+
+ @Test
+ public void fromBytesFactory() throws KlvParseException {
+ IGeneralizedTransformationMetadataValue v =
+ GeneralizedTransformationLocalSet.createValue(
+ GeneralizedTransformationParametersKey.Denominator_y,
+ new byte[] {(byte) 0x3E, (byte) 0x4C, (byte) 0xCC, (byte) 0xCD});
+ assertTrue(v instanceof Denominator_Y);
+ Denominator_Y uut = (Denominator_Y) v;
+ assertEquals(uut.getValue(), 0.2f);
+ assertEquals(uut.getDisplayName(), "Denominator - y factor");
+ assertEquals(uut.getDisplayableValue(), "0.200");
+ assertEquals(
+ uut.getBytes(), new byte[] {(byte) 0x3E, (byte) 0x4C, (byte) 0xCC, (byte) 0xCD});
+ }
+
+ @Test(expectedExceptions = KlvParseException.class)
+ public void testTooShort() throws KlvParseException {
+ new Denominator_Y(new byte[] {0x01, 0x02, 0x03});
+ }
+
+ @Test(expectedExceptions = KlvParseException.class)
+ public void testTooLong() throws KlvParseException {
+ new Denominator_Y(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05});
+ }
+
+ @Test(expectedExceptions = KlvParseException.class)
+ public void testNotDouble() throws KlvParseException {
+ new Denominator_Y(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08});
+ }
+}
diff --git a/st1202/src/test/java/org/jmisb/st1202/GeneralizedTransformationLocalSetTest.java b/st1202/src/test/java/org/jmisb/st1202/GeneralizedTransformationLocalSetTest.java
new file mode 100644
index 000000000..c9c389975
--- /dev/null
+++ b/st1202/src/test/java/org/jmisb/st1202/GeneralizedTransformationLocalSetTest.java
@@ -0,0 +1,351 @@
+package org.jmisb.st1202;
+
+import static org.testng.Assert.*;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.jmisb.api.common.KlvParseException;
+import org.testng.annotations.Test;
+
+/** Unit tests for Generalized Transformation Local Set implementation. */
+public class GeneralizedTransformationLocalSetTest extends LoggerChecks {
+
+ private final byte[] bytesAll =
+ new byte[] {
+ 0x01,
+ 0x04,
+ 0x3F,
+ (byte) 0x80,
+ 0x00,
+ 0x00,
+ 0x02,
+ 0x04,
+ 0x40,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x03,
+ 0x04,
+ 0x40,
+ 0x40,
+ 0x00,
+ 0x00,
+ 0x04,
+ 0x04,
+ 0x40,
+ (byte) 0x80,
+ 0x00,
+ 0x00,
+ 0x05,
+ 0x04,
+ 0x40,
+ (byte) 0xa0,
+ 0x00,
+ 0x00,
+ 0x06,
+ 0x04,
+ 0x40,
+ (byte) 0xc0,
+ 0x00,
+ 0x00,
+ 0x07,
+ 0x04,
+ 0x40,
+ (byte) 0xe0,
+ 0x00,
+ 0x00,
+ 0x08,
+ 0x04,
+ 0x41,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x09,
+ 90,
+ 0x08,
+ 0b01000010,
+ (byte) 0x3d,
+ (byte) 0xcc,
+ (byte) 0xcc,
+ (byte) 0xcd,
+ (byte) 0x3e,
+ (byte) 0x4c,
+ (byte) 0xcc,
+ (byte) 0xcd,
+ (byte) 0x3e,
+ (byte) 0x99,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x3e,
+ (byte) 0xcc,
+ (byte) 0xcc,
+ (byte) 0xcd,
+ (byte) 0x3f,
+ (byte) 0x00,
+ (byte) 0x00,
+ (byte) 0x00,
+ (byte) 0x3f,
+ (byte) 0x19,
+ (byte) 0x99,
+ (byte) 0x9a,
+ (byte) 0x3f,
+ (byte) 0x33,
+ (byte) 0x33,
+ (byte) 0x33,
+ (byte) 0x3f,
+ (byte) 0x4c,
+ (byte) 0xcc,
+ (byte) 0xcd,
+ (byte) 0x40,
+ (byte) 0xa3,
+ (byte) 0x41,
+ (byte) 0x47,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ (byte) 0x40,
+ (byte) 0x00,
+ 0x0a,
+ 0x01,
+ 0x02,
+ 0x0b,
+ 0x01,
+ 0x02
+ };
+
+ public GeneralizedTransformationLocalSetTest() {
+ super(GeneralizedTransformationLocalSet.class);
+ }
+
+ @Test
+ public void checkFromBytesSimple() throws KlvParseException {
+ // This is not a reasonable local set, just serves for testing
+ byte[] bytes = new byte[] {0x0a, 0x01, 0x02};
+ GeneralizedTransformationLocalSet uut = new GeneralizedTransformationLocalSet(bytes);
+ verifyNoLoggerMessages();
+ assertEquals(
+ uut.getUniversalLabel(),
+ GeneralizedTransformationLocalSet.GeneralizedTransformationLocalSetUl);
+ assertEquals(uut.displayHeader(), "ST 1202 Generalized Transformation Local Set");
+ assertEquals(uut.getIdentifiers().size(), 1);
+ assertTrue(
+ uut.getIdentifiers()
+ .contains(GeneralizedTransformationParametersKey.DocumentVersion));
+ assertEquals(
+ uut.getField(GeneralizedTransformationParametersKey.DocumentVersion)
+ .getDisplayableValue(),
+ "ST 1202.2");
+ assertEquals(uut.frameMessage(true), bytes);
+ verifyNoLoggerMessages();
+ }
+
+ @Test
+ public void checkFromBytesWithUnknown() throws KlvParseException {
+ // This is not a reasonable local set, just serves for testing
+ byte[] bytes = new byte[] {0x7f, 0x01, 0x02, 0x0a, 0x01, 0x02};
+ GeneralizedTransformationLocalSet uut = new GeneralizedTransformationLocalSet(bytes);
+ this.verifySingleLoggerMessage("Unknown Generalized Transformation tag: 127");
+ assertEquals(
+ uut.getUniversalLabel(),
+ GeneralizedTransformationLocalSet.GeneralizedTransformationLocalSetUl);
+ assertEquals(uut.displayHeader(), "ST 1202 Generalized Transformation Local Set");
+ assertEquals(uut.getIdentifiers().size(), 1);
+ assertTrue(
+ uut.getIdentifiers()
+ .contains(GeneralizedTransformationParametersKey.DocumentVersion));
+ assertEquals(
+ uut.getField(GeneralizedTransformationParametersKey.DocumentVersion)
+ .getDisplayableValue(),
+ "ST 1202.2");
+ assertEquals(uut.frameMessage(true), new byte[] {0x0a, 0x01, 0x02});
+ verifyNoLoggerMessages();
+ }
+
+ @Test
+ public void checkFromValues() throws KlvParseException {
+ Map The concept is that there is a test logger that should always contain no log messages after a
+ * test has been run. That gets verified in the AfterMethod so if there is a message, the logger
+ * needs to be checked and clear()ed before the test method returns.
+ *
+ * Only ERROR, WARN and INFO levels are checked. DEBUG and lower are implementation detail.
+ *
+ * The subclass is responsible for initialising the LOGGER correctly by calling the super
+ * constructor with the class that is creating the log messages.
+ */
+public abstract class LoggerChecks {
+ protected TestLogger LOGGER;
+
+ public LoggerChecks(Class T) {
+ LOGGER = TestLoggerFactory.getTestLogger(T);
+ LOGGER.setEnabledLevelsForAllThreads(Level.ERROR, Level.WARN, Level.INFO);
+ }
+
+ @BeforeMethod
+ public void clearLogger() {
+ LOGGER.clear();
+ }
+
+ @AfterMethod
+ public void checkLogger() {
+ verifyNoLoggerMessages();
+ }
+
+ protected void verifyNoLoggerMessages() {
+ if (LOGGER.getLoggingEvents().size() > 0) {
+ List
+ *
+ */
+@SuppressWarnings("module") // That is not a version number - its a document number.
+module org.jmisb.st1202 {
+ requires org.jmisb.api;
+ requires org.jmisb.st1010;
+ requires org.slf4j;
+
+ exports org.jmisb.st1202;
+}
diff --git a/st1202/src/main/java/org/jmisb/st1202/AbstractGeneralizedTransformationMetadataValue.java b/st1202/src/main/java/org/jmisb/st1202/AbstractGeneralizedTransformationMetadataValue.java
new file mode 100644
index 000000000..7af8ace13
--- /dev/null
+++ b/st1202/src/main/java/org/jmisb/st1202/AbstractGeneralizedTransformationMetadataValue.java
@@ -0,0 +1,69 @@
+package org.jmisb.st1202;
+
+import org.jmisb.api.common.KlvParseException;
+import org.jmisb.core.klv.PrimitiveConverter;
+
+/**
+ * Abstract shared representation for 4 byte float values.
+ *
+ *