Commit e68f87a6 authored by Morten Ofstad's avatar Morten Ofstad
Browse files

Refactored things in the SEGY namespace into a separate file. Started on a SEGYUpload tool.

parent 61389d70
include (${PROJECT_SOURCE_DIR}/CMake/CopyDllForTarget.cmake)
set(SOURCE_FILES
add_executable(SEGYScan
SEGY.h
SEGY.cpp
SEGYFileInfo.h
SEGYFileInfo.cpp
SEGYScan.cpp)
set(PRIVATE_HEADER_FILES
SEGYFileInfo.h)
add_executable(SEGYScan
${PRIVATE_HEADER_FILES}
${SOURCE_FILES}
add_executable(SEGYUpload
SEGY.h
SEGY.cpp
SEGYFileInfo.h
SEGYFileInfo.cpp
SEGYUpload.cpp
)
target_include_directories(SEGYScan PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_link_libraries(SEGYScan PUBLIC Threads::Threads openvds jsoncpp_lib_static)
copyDllForTarget(SEGYScan)
target_include_directories(SEGYUpload PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_link_libraries(SEGYUpload PUBLIC Threads::Threads openvds jsoncpp_lib_static)
copyDllForTarget(SEGYUpload)
if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}/Dist/OpenVDS CACHE STRING "" FORCE)
endif(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
......@@ -25,7 +33,7 @@ if (${MSVC_TOOLSET_VERSION})
set(LIB_TOOLSET_DIR "/msvc_${MSVC_TOOLSET_VERSION}")
endif()
install(TARGETS SEGYScan
install(TARGETS SEGYScan SEGYUpload
RUNTIME
DESTINATION bin${LIB_TOOLSET_DIR}
)
/****************************************************************************
** Copyright 2019 The Open Group
** Copyright 2019 Bluware, Inc.
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
****************************************************************************/
#include "SEGY.h"
#include <stdint.h>
#include <stdlib.h>
/////////////////////////////////////////////////////////////////////////////
// ibm2ieee
/* ibm2ieee - Converts a number from IBM 370 single precision floating
point format to IEEE 754 single precision format. For normalized
numbers, the IBM format has greater range but less precision than the
IEEE format. Numbers within the overlapping range are converted
exactly. Numbers which are too large are converted to IEEE Infinity
with the correct sign. Numbers which are too small are converted to
IEEE denormalized numbers with a potential loss of precision (including
complete loss of precision which results in zero with the correct
sign). When precision is lost, rounding is toward zero (because it's
fast and easy -- if someone really wants round to nearest it shouldn't
be TOO difficult). */
void
ibm2ieee(void *to, const void *from, size_t len)
{
for (; len-- > 0; to = (char *)to + 4, from = (const char *)from + 4)
{
unsigned int
fr = *(const uint32_t *)from;
if (fr == 0)
{
/* short-circuit for everything is zero. Then (also endian converted) matissa also is */
*(unsigned *)to = 0x00000000;
continue;
}
#ifdef WIN32
fr =_byteswap_ulong(fr);
#else
fr = __builtin_bswap32(fr);
#endif // WIN32
unsigned int
sgn = fr & 0x80000000; /* save sign */
unsigned int
expIBM4 = (fr & 0x7f000000) >> 22; // i.e. 4 * expIBM, shift (24 down and then 2 up in one go).
fr &= 0x00ffffff;
if (fr == 0)
{
/* short-circuit for zero matissa*/
*(unsigned *)to = sgn; // Matissa all zeroes, set exponent all zero, maintain sign.
continue;
}
// The involved magic numbers will fall out when studying the IEEE and IBM representations.
const bool
isNormalNumberEnsured = (expIBM4 > 154) && (expIBM4 < 385);
// Inside this range, none of the special considerations needs be taken.
if (isNormalNumberEnsured)
{
// This approach should be SSE friendly, for further optimization.
// First let the CPU instruction convert the IBM matissa conversion into an IEEE float.
// This will handle the possible leading zeroes on the IBM matissa without any loop,
// or use of count leading zero instuction, which is not part of SSE (to the version
// we can currently assume in common use.)
float
rValue = float (fr);
uint32_t
uValue = reinterpret_cast<uint32_t&> (rValue);
// Then mod the exponent on the IEEE number, and we are done.
uint32_t
uExp = (expIBM4 - uint32_t(280)) << 23;
uValue = uValue + uExp;
*(unsigned int*)to = (uValue | sgn);
}
else
{
// Note: This else clause is able to handle all cases (i.e. also the normal range above).
/*
adjust exponent from base 16 offset 64 radix point before first digit
to base 2 offset 127 radix point after first digit
(exp - 64) * 4 + 127 - 1 == exp * 4 - 256 + 126 == (exp << 2) - 130
*/
int
expModified = expIBM4 - 130;
int
nNormalizeShift = 0;
/* normalize */
while (fr < 0x00800000)
{
fr <<= 1;
nNormalizeShift++;
}
expModified = expModified - nNormalizeShift;
if (expModified <= 0)
{
/* underflow */
if (expModified < -23)
{
/* complete underflow - return properly signed zero */
fr = 0;
}
else
{
/* partial underflow - return denormalized number */
fr >>= 1-expModified;
}
expModified = 0;
}
else if (expModified >= 255)
{
/* overflow - return infinity */
fr = 0;
expModified = 255;
}
else
{
/* Standard number - assumed high bit masked away below */
}
*(unsigned *)to = (fr & 0x007fffff) | (expModified << 23) | sgn;
}
}
}
/////////////////////////////////////////////////////////////////////////////
// ieee2ibm
/* ieee2ibm - Converts a number from IEEE 754 single precision floating
point format to IBM 370 single precision format. For normalized
numbers, the IBM format has greater range but less precision than the
IEEE format. IEEE Infinity is mapped to the largest representable
IBM 370 number. When precision is lost, rounding is toward zero
(because it's fast and easy -- if someone really wants round to nearest
it shouldn't be TOO difficult). */
void
ieee2ibm(void *to, const void *from, size_t len)
{
register unsigned fr; /* fraction */
register int exp; /* exponent */
register int sgn; /* sign */
for (; len-- > 0; to = (char *)to + 4, from = (const char *)from + 4)
{
/* split into sign, exponent, and fraction */
fr = *(const unsigned *)from; /* pick up value */
sgn = fr >> 31; /* save sign */
fr <<= 1; /* shift sign out */
exp = fr >> 24; /* save exponent */
fr <<= 8; /* shift exponent out */
if (exp == 255) { /* infinity (or NAN) - map to largest */
fr = 0xffffff00;
exp = 0x7f;
goto done;
}
else if (exp > 0) /* add assumed digit */
fr = (fr >> 1) | 0x80000000;
else if (fr == 0) /* short-circuit for zero */
goto done;
/* adjust exponent from base 2 offset 127 radix point after first digit
to base 16 offset 64 radix point before first digit */
exp += 130;
fr >>= -exp & 3;
exp = (exp + 3) >> 2;
/* (re)normalize */
while (fr < 0x10000000)
{ /* never executed for normalized input */
--exp;
fr <<= 4;
}
done:
/* put the pieces back together and return it */
fr = (fr >> 8) | (exp << 24) | (sgn << 31);
#ifdef WIN32
fr =_byteswap_ulong(fr);
#else
fr = __builtin_bswap32(fr);
#endif // WIN32
*(unsigned *)to = fr;
}
}
/****************************************************************************
** Copyright 2019 The Open Group
** Copyright 2019 Bluware, Inc.
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
****************************************************************************/
namespace SEGY
{
enum
{
TextualFileHeaderColumns = 80,
TextualFileHeaderLines = 40,
TextualFileHeaderSize = TextualFileHeaderColumns * TextualFileHeaderLines,
BinaryFileHeaderSize = 400,
TraceHeaderSize = 240
};
enum class FieldWidth
{
TwoByte,
FourByte
};
enum class Endianness
{
BigEndian,
LittleEndian
};
struct HeaderField
{
int m_byteLocation;
FieldWidth m_fieldWidth;
HeaderField() : m_byteLocation(), m_fieldWidth() {}
HeaderField(int byteLocation, FieldWidth fieldWidth) : m_byteLocation(byteLocation), m_fieldWidth(fieldWidth) {}
bool defined() const { return m_byteLocation != 0; }
};
namespace BinaryHeader
{
enum class DataSampleFormatCode
{
Unknown = 0,
IBMFloat = 1, // 4-byte IBM floating-point
Int32 = 2, // 4-byte, two's complement integer
Int16 = 3, // 2-byte, two's complement integer
FixedPoint = 4, // 4-byte fixed-point with gain (obsolete)
IEEEFloat = 5, // 4-byte IEEE floating-point
IEEEDouble = 6, // 8-byte IEEE floating-point
Int24 = 7, // 3-byte, two's complement integer
Int8 = 8, // 1-byte, two's complement integer
Int64 = 9, // 8-byte, two's complement integer
UInt32 = 10, // 4-byte, unsigned integer
UInt16 = 11, // 2-byte, unsigned integer
UInt64 = 12, // 8-byte, unsigned integer
UInt24 = 15, // 3-byte, unsigned integer
UInt8 = 16 // 1-byte, unsigned integer
};
enum class SortCode
{
Other = -1, // Other (should be explained in a user Extended Textual File Header stanza)
Unknown = 0, // Unknown
As_Recorded = 1, // As recorded (no sorting)
CDP_Ensemble = 2, // CDP ensemble
Single_Fold_Continuous_Profile = 3, // Single fold continuous profile
Horizontally_Stacked = 4, // Horizontally stacked
Common_Source_Point = 5, // Common source point
Common_Receiver_Point = 6, // Common receiver point
Common_Offset_Point = 7, // Common offset point
Common_Mid_Point = 8, // Common mid-point
Common_Conversion_Point = 9 // Common conversion point
};
enum class MeasurementSystem
{
Unknown = 0,
Meters = 1,
Feet = 2
};
// Standard header fields
static const HeaderField TracesPerEnsembleHeaderField(13, FieldWidth::TwoByte);
static const HeaderField AuxiliaryTracesPerEnsembleHeaderField(15, FieldWidth::TwoByte);
static const HeaderField SampleIntervalHeaderField(17, FieldWidth::TwoByte);
static const HeaderField NumSamplesHeaderField(21, FieldWidth::TwoByte);
static const HeaderField DataSampleFormatCodeHeaderField(25, FieldWidth::TwoByte);
static const HeaderField EnsembleFoldHeaderField(27, FieldWidth::TwoByte);
static const HeaderField TraceSortingCodeHeaderField(29, FieldWidth::TwoByte);
static const HeaderField MeasurementSystemHeaderField(55, FieldWidth::TwoByte);
static const HeaderField SEGYFormatRevisionNumberHeaderField(301, FieldWidth::TwoByte);
static const HeaderField FixedLengthTraceFlagHeaderField(303, FieldWidth::TwoByte);
static const HeaderField ExtendedTextualFileHeaderCountHeaderField(305, FieldWidth::TwoByte);
} // end namespace BinaryHeader
namespace TraceHeader
{
enum class TraceIdentificationCode
{
Other = -1, // Other
Unknown = 0, // Unknown
TimeDomainSeismicData = 1, // Time domain seismic data
Dead = 2, // Dead
Dummy = 3, // Dummy
TimeBreak = 4, // Time break
Uphole = 5, // Uphole
Sweep = 6, // Sweep
Timing = 7, // Timing
Waterbreak = 8, // Waterbreak
NearFieldGunSignature = 9, // Near-field gun signature
FarFieldGunSignature = 10, // Far-field gun signature
SeismicPressureSensor = 11, // Seismic pressure sensor
MulticomponentSeismicSensor_VerticalComponent = 12, // Multicomponent seismic sensor Vertical component
MulticomponentSeismicSensor_CrosslineComponent = 13, // Multicomponent seismic sensor Cross-line component
MulticomponentSeismicSensor_InlineComponent = 14, // Multicomponent seismic sensor In-line component
RotatedMulticomponentSeismicSensor_VerticalComponent = 15, // Rotated multicomponent seismic sensor Vertical component
RotatedMulticomponentSeismicSensor_TransverseComponent = 16, // Rotated multicomponent seismic sensor Transverse component
RotatedMulticomponentSeismicSensor_RadialComponent = 17, // Rotated multicomponent seismic sensor Radial component
VibratorReactionMass = 18, // Vibrator reaction mass
VibratorBaseplate = 19, // Vibrator baseplate
VibratorEstimatedGroundForce = 20, // Vibrator estimated ground force
VibratorReference = 21, // Vibrator reference
TimeVelocityPairs = 22, // Time-velocity pairs
TimeDepthPairs = 23, // Time-depth pairs
DepthVelocityPairs = 24, // Depth-velocity pairs
DepthDomainSeismicData = 25, // Depth domain seismic data
GravityPotential = 26, // Gravity potential
ElectricField_VerticalComponent = 27, // Electric field Vertical component
ElectricField_CrosslineComponent = 28, // Electric field Cross-line component
ElectricField_InlineComponent = 29, // Electric field In-line component
RotatedElectricField_VerticalComponent = 30, // Rotated electric field Vertical component
RotatedElectricField_TransverseComponent = 31, // Rotated electric field Transverse component
RotatedElectricField_RadialComponent = 32, // Rotated electric field Radial component
MagneticField_VerticalComponent = 33, // Magnetic field Vertical component
MagneticField_CrosslineComponent = 34, // Magnetic field Cross-line component
MagneticField_InlineComponent = 35, // Magnetic field In-line component
RotatedMagneticField_VerticalComponent = 36, // Rotated magnetic field Vertical component
RotatedMagneticField_TransverseComponent = 37, // Rotated magnetic field Transverse component
RotatedMagneticField_RadialComponent = 38, // Rotated magnetic field Radial component
RotationalSensor_Pitch = 39, // Rotational sensor Pitch
RotationalSensor_Roll = 40, // Rotational sensor Roll
RotationalSensor_Yaw = 41 // Rotational sensor Yaw
};
enum class CoordinateUnits
{
Unknown = 0,
Length = 1, // Length (meters or feet as specified in Binary File Header bytes 3255-3256 and in Extended Textual Header if Location Data are included in the file)
ArcSeconds = 2, // Seconds of arc (deprecated)
DecimalDegrees = 3, // Decimal degrees (preferred degree representation)
DegreesMinutesSeconds = 4 // Degrees, minutes, seconds (DMS)
};
static const HeaderField TraceSequenceNumberHeaderField(1, FieldWidth::FourByte);
static const HeaderField TraceSequenceNumberWithinFileHeaderField(5, FieldWidth::FourByte);
static const HeaderField EnergySourcePointNumberHeaderField(17, FieldWidth::FourByte);
static const HeaderField EnsembleNumberHeaderField(21, FieldWidth::FourByte);
static const HeaderField TraceNumberWithinEnsembleHeaderField(25, FieldWidth::FourByte);
static const HeaderField TraceIdentificationCodeHeaderField(29, FieldWidth::TwoByte);
static const HeaderField CoordinateScaleHeaderField(71, FieldWidth::TwoByte);
static const HeaderField SourceXCoordinateHeaderField(73, FieldWidth::FourByte);
static const HeaderField SourceYCoordinateHeaderField(77, FieldWidth::FourByte);
static const HeaderField GroupXCoordinateHeaderField(81, FieldWidth::FourByte);
static const HeaderField GroupYCoordinateHeaderField(84, FieldWidth::FourByte);
static const HeaderField CoordinateUnitsHeaderField(89, FieldWidth::TwoByte);
static const HeaderField StartTimeHeaderField(109, FieldWidth::TwoByte);
static const HeaderField NumSamplesHeaderField(115, FieldWidth::TwoByte);
static const HeaderField SampleIntervalHeaderField(117, FieldWidth::TwoByte);
static const HeaderField EnsembleXCoordinateHeaderField(181, FieldWidth::FourByte);
static const HeaderField EnsembleYCoordinateHeaderField(185, FieldWidth::FourByte);
static const HeaderField InlineNumberHeaderField(189, FieldWidth::FourByte);
static const HeaderField CrosslineNumberHeaderField(193, FieldWidth::FourByte);
} // end namespace TraceHeader
// Conversion functions
void ibm2ieee(void *to, const void *from, size_t len);
void ieee2ibm(void *to, const void *from, size_t len);
} // end namespace SEGY
......@@ -23,6 +23,7 @@
#include <cassert>
using namespace OpenVDS;
using namespace SEGY;
static int
readFieldFromHeader(const char *header, HeaderField const &headerField, Endianness endianness)
......@@ -78,7 +79,7 @@ readBinInfoFromHeader(const char *header, SEGYBinInfoHeaderFields const &headerF
if(headerFields.m_scaleOverride == 0.0)
{
int scale = readFieldFromHeader(header, SEGY::TraceHeader::CoordinateScaleHeaderField, endianness);
int scale = readFieldFromHeader(header, TraceHeader::CoordinateScaleHeaderField, endianness);
if(scale < 0)
{
scaleFactor = 1.0 / float(scale);
......@@ -105,57 +106,57 @@ SEGYFileInfo::traceByteSize()
{
default:
return false;
case SEGY::BinaryHeader::DataSampleFormatCode::Int8:
case SEGY::BinaryHeader::DataSampleFormatCode::UInt8:
case BinaryHeader::DataSampleFormatCode::Int8:
case BinaryHeader::DataSampleFormatCode::UInt8:
formatSize = 1; break;
case SEGY::BinaryHeader::DataSampleFormatCode::Int16:
case SEGY::BinaryHeader::DataSampleFormatCode::UInt16:
case BinaryHeader::DataSampleFormatCode::Int16:
case BinaryHeader::DataSampleFormatCode::UInt16:
formatSize = 2; break;
case SEGY::BinaryHeader::DataSampleFormatCode::Int24:
case SEGY::BinaryHeader::DataSampleFormatCode::UInt24:
case BinaryHeader::DataSampleFormatCode::Int24:
case BinaryHeader::DataSampleFormatCode::UInt24:
formatSize = 3; break;
case SEGY::BinaryHeader::DataSampleFormatCode::IBMFloat:
case SEGY::BinaryHeader::DataSampleFormatCode::Int32:
case SEGY::BinaryHeader::DataSampleFormatCode::FixedPoint:
case SEGY::BinaryHeader::DataSampleFormatCode::IEEEFloat:
case SEGY::BinaryHeader::DataSampleFormatCode::UInt32:
case BinaryHeader::DataSampleFormatCode::IBMFloat:
case BinaryHeader::DataSampleFormatCode::Int32:
case BinaryHeader::DataSampleFormatCode::FixedPoint:
case BinaryHeader::DataSampleFormatCode::IEEEFloat:
case BinaryHeader::DataSampleFormatCode::UInt32:
formatSize = 4; break;
case SEGY::BinaryHeader::DataSampleFormatCode::IEEEDouble:
case SEGY::BinaryHeader::DataSampleFormatCode::Int64:
case SEGY::BinaryHeader::DataSampleFormatCode::UInt64:
case BinaryHeader::DataSampleFormatCode::IEEEDouble:
case BinaryHeader::DataSampleFormatCode::Int64:
case BinaryHeader::DataSampleFormatCode::UInt64:
formatSize = 8; break;
}
return SEGY::TraceHeaderSize + m_sampleCount * formatSize;
return TraceHeaderSize + m_sampleCount * formatSize;
}
bool
SEGYFileInfo::readTraceHeader(OpenVDS::File const &file, int64_t trace, char (&header)[SEGY::TraceHeaderSize], OpenVDS::IOError &error)
SEGYFileInfo::readTraceHeader(OpenVDS::File const &file, int64_t trace, char (&header)[TraceHeaderSize], OpenVDS::IOError &error)
{
int64_t
offset = SEGY::TextualFileHeaderSize + SEGY::BinaryFileHeaderSize + trace * traceByteSize();
offset = TextualFileHeaderSize + BinaryFileHeaderSize + trace * traceByteSize();
return file.read(header, offset, SEGY::TraceHeaderSize, error);
return file.read(header, offset, TraceHeaderSize, error);
}
bool
SEGYFileInfo::scan(OpenVDS::File const &file, HeaderField const &primaryKeyHeaderField, SEGYBinInfoHeaderFields const &binInfoHeaderFields)
{
char textualFileHeader[SEGY::TextualFileHeaderSize];
char binaryFileHeader[SEGY::BinaryFileHeaderSize];
char traceHeader[SEGY::TraceHeaderSize];
char textualFileHeader[TextualFileHeaderSize];
char binaryFileHeader[BinaryFileHeaderSize];
char traceHeader[TraceHeaderSize];
OpenVDS::IOError error;
file.read(textualFileHeader, 0, SEGY::TextualFileHeaderSize, error) &&
file.read(binaryFileHeader, SEGY::TextualFileHeaderSize, SEGY::BinaryFileHeaderSize, error);
file.read(textualFileHeader, 0, TextualFileHeaderSize, error) &&
file.read(binaryFileHeader, TextualFileHeaderSize, BinaryFileHeaderSize, error);
if(error.code != 0)
{
return false;
}
m_dataSampleFormatCode = SEGY::BinaryHeader::DataSampleFormatCode(readFieldFromHeader(binaryFileHeader, SEGY::BinaryHeader::DataSampleFormatCodeHeaderField, m_headerEndianness));
m_dataSampleFormatCode = BinaryHeader::DataSampleFormatCode(readFieldFromHeader(binaryFileHeader, BinaryHeader::DataSampleFormatCodeHeaderField, m_headerEndianness));
int64_t fileSize = file.size(error);
......@@ -164,13 +165,21 @@ SEGYFileInfo::scan(OpenVDS::File const &file, HeaderField const &primaryKeyHeade
return false;
}
m_sampleCount = readFieldFromHeader(binaryFileHeader, SEGY::BinaryHeader::NumSamplesHeaderField, m_headerEndianness);
m_sampleCount = readFieldFromHeader(binaryFileHeader, BinaryHeader::NumSamplesHeaderField, m_headerEndianness);
m_sampleIntervalMilliseconds = readFieldFromHeader(binaryFileHeader, BinaryHeader::SampleIntervalHeaderField, m_headerEndianness) / 1000.0;
// If the sample count is not set in the binary header we try to read the first trace header and find the sample count there
if(m_sampleCount == 0)
{
readTraceHeader(file, 0, traceHeader, error);
m_sampleCount = readFieldFromHeader(traceHeader, SEGY::TraceHeader::NumSamplesHeaderField, m_headerEndianness);
m_sampleCount = readFieldFromHeader(traceHeader, TraceHeader::NumSamplesHeaderField, m_headerEndianness);
}
if(m_sampleIntervalMilliseconds == 0.0)
{
readTraceHeader(file, 0, traceHeader, error);
m_sampleIntervalMilliseconds = readFieldFromHeader(traceHeader, TraceHeader::SampleIntervalHeaderField, m_headerEndianness) / 1000.0;
}
if(m_sampleCount == 0)
......@@ -178,7 +187,7 @@ SEGYFileInfo::scan(OpenVDS::File const &file, HeaderField const &primaryKeyHeade
return false;
}
int64_t traceDataSize = (fileSize - SEGY::TextualFileHeaderSize - SEGY::BinaryFileHeaderSize);
int64_t traceDataSize = (fileSize - TextualFileHeaderSize - BinaryFileHeaderSize);
m_traceCount = traceDataSize / traceByteSize();
......
......@@ -15,185 +15,13 @@
** limitations under the License.
****************************************************************************/
#include "IO/File.h"
#include "SEGY.h"
#include <cstdint>
#include <vector>
#include <functional>
enum class FieldWidth
{
TwoByte,
FourByte
};
enum class Endi